an OOP simulator based on THREEJS

/*----------------- ThreeSim.js------------------
an OOP simulator based on THREEJS
石家庄铁路职业技术学院 信息工程系 郑华 2024.11
tel:15227854875 , mail:7047052@qq.com

引用此模拟器,需在网页文件中引入以下代码:
<script type="importmap">
	{
	"imports": {
				"three": "./js_three/build/three.module.min.js"
	}
}
</script>
------------------------------------------------*/
import * as THREE from '../js_three/build/three.module.min.js';
import { Projector } from '../js_three/jsm/renderers/projector.js';
import { EffectComposer } from '../js_three/jsm/postprocessing/EffectComposer.js';
import { UnrealBloomPass } from '../js_three/jsm/postprocessing/UnrealBloomPass.js';
import { RenderPass } from '../js_three/jsm/postprocessing/RenderPass.js';
import { ThreeBase } from './ThreeBase.js';

//-----------------应用程序类-------------------
class ThreeApp extends ThreeBase {

	static KeyCodes = {
		KEY_LEFT  	: 37,
		KEY_UP  	: 38,
		KEY_RIGHT  	: 39,
		KEY_DOWN  	: 40,
		
		KEY_W 		: 87,
		KEY_A  		: 65,
		KEY_S 		: 83,
		KEY_D  		: 68,
		KEY_SPACE  	: 32
	};

	constructor() {
		super(); 	// ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。
		this.renderer = null;	//只有调用super()之后,才可以使用this关键字,否则会报错。
		this.scene = null;
		this.camera = null;
		this.objects = [];
	}
	
	init(param) {
	    param = param || {};
	    let container = param.container;
	    let canvas = param.canvas;
	    let renderer = new THREE.WebGLRenderer({ antialias: true, canvas: canvas, alpha: true });
	    renderer.setSize($(container).width(), $(container).height()); 
	
	    container.appendChild(renderer.domElement);
	
	    // Create a new Three.js scene
	    let scene = new THREE.Scene();
	    // 初始环境光
	    // scene.add( new THREE.AmbientLight( 0x101010 ) );
	    
	    //这可用于通过ThreeObject对象反向获取ThreeApp对象
	    scene.data = this;
	
	    // Put in a camera at a good default location
	    let camera = new THREE.PerspectiveCamera(75, container.offsetWidth / container.offsetHeight, 0.1, 10000);
	    camera.position.set(0, 0, 0);
	
	    scene.add(camera);
	
	    // Create a root object to contain all other scene objects
	    let root = new THREE.Object3D();
	    scene.add(root);
	
	    // Create a projector to handle picking
	    let projector = new Projector();
	
	    // Save away a few things
	    this.container = container;
	    this.renderer = renderer;
	    this.scene = scene;
	    this.camera = camera;
	    this.projector = projector;
	    this.root = root;
	
	    // Set up event handlers
	    this.initMouse();
	    this.initKeyboard();
	    this.addDomHandlers();
	}
	
	initBloom() {
		if (!this.container) {alert('应用程序没有初始化,请先执行init函数。');return;}
		
		let that=this;
	    
	    let width=$(this.container).width();
	    let height=$(this.container).height();
	    
	    const params = {
			exposure: 1,
			bloomStrength: 1,
			bloomThreshold: 0,
			bloomRadius: 1,
			scene: 'Scene with Glow'
		};
	    
	    let renderscene = new RenderPass(this.scene,this.camera);
	    
		let bloomPass = new UnrealBloomPass(new THREE.Vector2(width,height),1.5,0.4,0.85);
		bloomPass.renderToscreen = true;
		bloomPass.threshold = params.bloomThreshold;
		bloomPass.strength = params.bloomStrength;
		bloomPass.radius = params.bloomRadius;
		this.bloomPass=bloomPass;
		
		let composer = new EffectComposer(this.renderer);
		composer.setSize(width,height);
		composer.addPass(renderscene);
		composer.addPass(bloomPass);
		this.composer=composer;
	}
	

	run () {
	    this.update();
	    this.renderer.render( this.scene, this.camera );
	    let that = this;
	    requestAnimationFrame(function () { that.run(); });
	}
	
	runNorender() {
	    this.update();
	    let that = this;
	    requestAnimationFrame(function () { that.runNorender(); });
	}
	
	//必须配合initBloom()函数使用
	runBloom() {
	    this.update();
	    let that = this;
	    this.camera.layers.set(1);
	    this.composer.render();		//先渲染bloom层
	        
	    this.renderer.clearDepth();		//非常重要
	    	
	    this.camera.layers.set(0);
		this.renderer.render(this.scene,this.camera);
	    requestAnimationFrame(function () { that.runBloom(); });
	}
	
	update(){
		let i, len;
		len = this.objects.length;
		for (i = 0; i < len; i++)
		{
			this.objects[i].update();
		}
	}
	
	addObject(obj){
		this.objects.push(obj);
	
		// If this is a renderable object, add it to the root scene
		if (obj.object3D)
		{
			this.root.add(obj.object3D);
		}
	}

	removeObject(obj){
		let index = this.objects.indexOf(obj);
		if (index != -1)
		{
			this.objects.splice(index, 1);
			// If this is a renderable object, remove it from the root scene
			if (obj.object3D)
			{
				this.root.remove(obj.object3D);
			}
		}
	}
	
	initMouse(){
		let dom = this.renderer.domElement;
		
		let that = this;
		dom.addEventListener( 'mousemove', 
				function(e) { that.onDocumentMouseMove(e); }, false );
		dom.addEventListener( 'mousedown', 
				function(e) { that.onDocumentMouseDown(e); }, false );
		dom.addEventListener( 'mouseup', 
				function(e) { that.onDocumentMouseUp(e); }, false );
		$(dom).mousewheel(
		        function(e, delta) {
		            that.onDocumentMouseScroll(e, delta);
		        }
		    );
		this.overObject = null;
		this.clickedObject = null;
	}
	
	initKeyboard(){
		let dom = this.renderer.domElement;
		
		let that = this;
		dom.addEventListener( 'keydown', 
				function(e) { that.onKeyDown(e); }, false );
		dom.addEventListener( 'keyup', 
				function(e) { that.onKeyUp(e); }, false );
		dom.addEventListener( 'keypress', 
				function(e) { that.onKeyPress(e); }, false );
	
		// so it can take focus
		dom.setAttribute("tabindex", 1);
	    dom.style.outline='none';
	}
	
	addDomHandlers(){
		let that = this;
		window.addEventListener( 'resize', function(event) { that.onWindowResize(event); }, false );
	}
	
	onDocumentMouseMove(event){
	    event.preventDefault();
	    
	    //单击某个对象了,并且有handleMouseMove事件
	    if (this.clickedObject && this.clickedObject.handleMouseMove)
	    {
		    let hitpoint = null, hitnormal = null;
		    let intersected = this.objectFromMouse(event.pageX, event.pageY);
		    if (intersected.object == this.clickedObject)
		    {
		    	hitpoint = intersected.point;
		    	hitnormal = intersected.normal;
		    }
			this.clickedObject.handleMouseMove(event.pageX, event.pageY, hitpoint, hitnormal);
	    }
	    else
	    {
		    let handled = false;
		    
		    let oldObj = this.overObject;
		    let intersected = this.objectFromMouse(event.pageX, event.pageY);
		    this.overObject = intersected.object;
		
		    if (this.overObject != oldObj)
		    {
		        if (oldObj)
		        {
	        		this.container.style.cursor = 'auto';
	        		
	        		if (oldObj.handleMouseOut)
	        		{
	        			oldObj.handleMouseOut(event.pageX, event.pageY);
	        		}
		        }
		
		        if (this.overObject)
		        {
		        	if (this.overObject.overCursor)
		        	{
		        		this.container.style.cursor = this.overObject.overCursor;
		        	}
		        	
		        	if (this.overObject.handleMouseOver)
		        	{
		        		this.overObject.handleMouseOver(event.pageX, event.pageY);
		        	}
		        }
		        
		        handled = true;
		    }
			
		    if (this.overObject)
		    {
		    	let hitpoint = intersected.point;
		    	let hitnormal = intersected.normal;
				this.overObject.handleMouseMove(event.pageX, event.pageY, hitpoint, hitnormal);
		    }
			
		    if (!handled && this.handleMouseMove)		//处理app类的handleMouseMove事件
		    {
		    	this.handleMouseMove(event.pageX, event.pageY,event);
		    }
	    }
	}
	
	onDocumentMouseDown(event) {
	    event.preventDefault();
	
	    let handled = false;
	    
	    let intersected = this.objectFromMouse(event.pageX, event.pageY);
	    if (intersected.object) {
	        if (intersected.object.handleMouseDown) {
	            intersected.object.handleMouseDown(event.pageX, event.pageY, intersected.point, intersected.normal, event);
	            this.clickedObject = intersected.object;
	        }
	    }
	
	    if (!handled && this.handleMouseDown) {
	        this.handleMouseDown(event.pageX, event.pageY, event);
	    }
	}
	
	onDocumentMouseUp(event){
	    event.preventDefault();
	    
	    let handled = false;
	    
	    let intersected = this.objectFromMouse(event.pageX, event.pageY);
	    if (intersected.object)
	    {
	    	if (intersected.object.handleMouseUp)
	    	{
	    	    intersected.object.handleMouseUp(event.pageX, event.pageY, intersected.point, intersected.normal, event);
	    	}
	    }
	    
	    if (!handled && this.handleMouseUp)
	    {
	        this.handleMouseUp(event.pageX, event.pageY, event);
	    }
	    
	    this.clickedObject = null;
	}
	
	onDocumentMouseScroll(event, delta){
	    event.preventDefault();
	
	    if (this.handleMouseScroll)
	    {
	    	this.handleMouseScroll(delta);
	    }
	}
	
	objectFromMouse(pagex, pagey) {
	    // Translate page coords to element coords
	    let offset = $(this.renderer.domElement).offset();
	    let eltx = pagex - offset.left;
	    let elty = pagey - offset.top;
	
	    // Translate client coords into viewport x,y
	    let vpx = (eltx / this.container.offsetWidth) * 2 - 1;
	    let vpy = -(elty / this.container.offsetHeight) * 2 + 1;
	
	    let vector = new THREE.Vector3(vpx, vpy, 0.5);
	
	    //this.projector.unprojectVector(vector, this.camera);	//已淘汰
	    vector.unproject( this.camera );	//zh.h,2022.10.2
	
	    //var ray = new THREE.Ray( this.camera.position, vector.subSelf( this.camera.position ).normalize() );
	    let ray = new THREE.Raycaster(this.camera.position, vector.sub(this.camera.position).normalize());
		
		//当对象处于其他层(默认为0层)时,也能被鼠标选中(按需修改),zhh,2024.4.29
		ray.layers.enableAll();
		
	    //var intersects = ray.intersectScene( this.scene );
	    let objs = [];
	    for (let i = 0; i < this.objects.length; i++) {

	    	if (!this.objects[i].object3D.type == "Mesh") continue;	//2024.5.4
	
	        objs.push(this.objects[i].object3D); 	//by zh.h,2014.9.12(郑华)
	
	        this.objects[i].object3D.traverse(function (obj) { 
	        	if (obj.type == "Mesh")	{	//2024.5.4
	        		objs.push(obj);
	        	}
	        });
	    }
	
	    let intersects = ray.intersectObjects(objs);
	
	    //alert(intersects.length);
	
	    if (intersects.length > 0) {
	
	        let i = 0;
	        while (!intersects[i].object.visible) {
	            i++;
	        }
	
	        let intersected = intersects[i];
	        //var mat = new THREE.Matrix4().getInverse(intersected.object.matrixWorld);	//已淘汰
	        let mat = new THREE.Matrix4().copy( intersected.object.matrixWorld).invert();
	        //zh.h,2022.10.2
	        
	        //var point = mat.multiplyVector3(intersected.point);//已淘汰
	        let point = intersected.point.applyMatrix4( mat );
	        //zh.h,2022.10.2
	
	        return (this.findObjectFromIntersected(intersected.object, intersected.point, intersected.face.normal));
	    }
	    else {
	        return { object: null, point: null, normal: null };
	    }
	}
	
	findObjectFromIntersected(object, point, normal){
		if (object.data)
		{
			return { object: object.data, point: point, normal: normal };
		}
		else if (object.parent)
		{
			return this.findObjectFromIntersected(object.parent, point, normal);
		}
		else
		{
			return { object : null, point : null, normal : null };
		}
	}
	
	onKeyDown(event){
		// N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
		event.preventDefault();
	
	    if (this.handleKeyDown)
	    {
	    	this.handleKeyDown(event);
	    }
	}
	
	onKeyUp(event){
		// N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
		event.preventDefault();
	
		if (this.handleKeyUp)
		{
			this.handleKeyUp(event);
		}
	}
	
	onKeyPress(event){
		// N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
		event.preventDefault();
	
		if (this.handleKeyPress)
		{
			this.handleKeyPress(event);
		}
	}
	
	onWindowResize(event) {
		this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight);
		this.camera.aspect = this.container.offsetWidth / this.container.offsetHeight;
		this.camera.updateProjectionMatrix();
	}
	
	focus()	{
		if (this.renderer && this.renderer.domElement)
		{
			this.renderer.domElement.focus();
		}
	}
}

//-----------------3D对象类-------------------
class ThreeObject extends ThreeBase {
	constructor() {
		super(); 	// ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。
		this.object3D = null;
		this.children = [];
	    this.visible = true;    //zh.h,2022.5.5
	}
	
	init(){
	}
	
	update(){
		let i, len;
		len = this.children.length;
		for (i = 0; i < len; i++)
		{
			this.children[i].update();
		}
	}
	
	setPosition(x, y, z){
		if (this.object3D)
		{
			this.object3D.position.set(x, y, z);
		}
	}
	
	setRotation(x, y, z){
		if (this.object3D)
		{
			this.object3D.rotation.set(x, y, z);
		}
	}
	
	setScale(x, y, z){
		if (this.object3D)
		{
			this.object3D.scale.set(x, y, z);
		}
	}
	
	setVisible(visible){
		function setVisible(obj, visible)
		{
			obj.visible = visible;
			let i, len = obj.children.length;
			for (i = 0; i < len; i++)
			{
				setVisible(obj.children[i], visible);
			}
		}
		
		if (this.object3D)
		{
			setVisible(this.object3D, visible);
		}
	
	    this.visible=visible;
	}
	
	setLayers(layer){
		function setLayers(obj, layer)
		{
			obj.layers.set(layer);
			let i, len = obj.children.length;
			for (i = 0; i < len; i++)
			{
				setLayers(obj.children[i], layer);
			}
		}
		
		if (this.object3D)
		{
			setLayers(this.object3D, layer);
		}
	}
	
	setObject3D (object3D){
		//这可用于反向取得ThreeApp对象
		object3D.data = this;
		this.object3D = object3D;
	}
	
	addChild(child){
		this.children.push(child);
		
		// If this is a renderable object, add its object3D as a child of mine
		if (child.object3D)
		{
			this.object3D.add(child.object3D);
		}
	}
	
	removeChild(child){
		var index = this.children.indexOf(child);
		if (index != -1)
		{
			this.children.splice(index, 1);
			// If this is a renderable object, remove its object3D as a child of mine
			if (child.object3D)
			{
				this.object3D.remove(child.object3D);
			}
		}
	}
	
	getScene(){
		let scene = null;
		if (this.object3D)
		{
			let obj = this.object3D;
			while (obj.parent)
			{
				obj = obj.parent;
			}
			scene = obj;
		}
		return scene;
	}
	
	getApp(){
		let scene = this.getScene();
		return scene ? scene.data : null;
	}
	
	//待用户自定义,可用于回调函数
	userAction(){
	}
}

export {ThreeBase,ThreeApp,ThreeObject};

该类支持以面向对象的方式进行threejs编程,在事件处理、对象管理等方面带来很多便利。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值