threejs開關門動畫

<!DOCTYPE html>
<html>
<head>
    <title>House</title>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
	<script src="/threejs/three.min.js"></script>
	<script src="/threejs/OrbitControls.js"></script>
	<script src="/threejs/threebsp.js"></script>
	<style>
	body,canvas{margin:0;padding:0;overflow:hidden}
	</style>
</head>
<body>
    <script>
			var mat1 = new THREE.MeshPhongMaterial({
			    color: 0xafc0ca,
			});
			var mat2 = new THREE.MeshPhongMaterial({
			    color: 0xd6e4ec
			});
			var mat3 = new THREE.MeshPhongMaterial({
			    color: 0x9cb2d1
			});
			var matArr1 = [mat3, mat1, mat1, mat1, mat1, mat1];
			var matArr2 = [mat1, mat1, mat2, mat2, mat3, mat3];
			var width,height,position,scene,camera,renderer,tea,leftDoor,mixer,leftdoorgroup;
		    var leftdoorstatus = 'close';
		   var clock = new THREE.Clock();
		function createTea(color){
		    var MTLLoader = new THREE.MTLLoader();//材質文件加載器
			MTLLoader.load('/threejs/obj/'+color+'.mtl', function(material){
			    var OBJLoader = new THREE.OBJLoader();//obj加載器
				OBJLoader.setMaterials(material);
			    OBJLoader.load('/threejs/obj/'+color+'.obj', function(obj){
				    scene.remove(tea);
				    tea = obj;
					console.log(tea);
					scene.add(tea);
			    });
			});
		}

		//尺寸
		function commonSet(){
		    width    = window.innerWidth;
			height   = window.innerHeight;
		}
		
		//場景
		function scene(){
		    scene  = new THREE.Scene();
			scene.background = new THREE.CubeTextureLoader().setPath("/threejs/img/").load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg', ])
		}
		
	    //攝像機
		function camera(){
		    camera = new THREE.PerspectiveCamera(60, width/height, 0.1, 10000);
			camera.position.set(2000,2000,2000);
			camera.lookAt(scene.position);
		}
		
		//渲染器
		function renderer(){
		    renderer = new THREE.WebGLRenderer({antialias:true});
			renderer.setSize(width, height);
			renderer.setClearColor(0xdddddd);
			document.body.appendChild(renderer.domElement);
		}
		
		//輔助工具
		function assist(){
		    var axes = new THREE.AxesHelper(800);
			scene.add(axes);
			var orbit = new THREE.OrbitControls(camera,renderer.domElement);
			//orbit.addEventListener('change', render);
			var cameraHelper = new THREE.CameraHelper(camera);
			//scene.add(cameraHelper);

		}
		
		//光源
		function light(){
		    var ambientLight = new THREE.AmbientLight(0x444444);
			scene.add(ambientLight);
			var light = new THREE.DirectionalLight(0xffffff);
			light.position.set(100,100,100);
			scene.add(light);
			var lightHelper = new THREE.DirectionalLightHelper(light,50);
			scene.add(lightHelper);

		}
		
		//繪製模型
		function model(){
		    //地板
		    var floorTexture  = new THREE.TextureLoader().load('/threejs/img/floor.jpg');
            floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
			floorTexture.repeat.set(10,10);
		    var doorGeometry = new THREE.BoxGeometry(4000,1,2000);
			var doorMaterial = new THREE.MeshPhongMaterial({
			    map:floorTexture
			});
			var door = new THREE.Mesh(doorGeometry, doorMaterial);
			door.name = '地板';
			scene.add(door);
			var northWall = createWall(4000,500,40,'北牆');
			var doorHole  = createDoorHole(500,460,40);
			doorHole.translateY(-20);
			var wallbsp  = new ThreeBSP(northWall);
			var doorbsp  = new ThreeBSP(doorHole);
			var wallHole = wallbsp.subtract(doorbsp).toMesh(mat3);
			wallHole.name = '北牆';
			wallHole.geometry.computeFaceNormals();
			wallHole.geometry.computeVertexNormals();
            wallHole.material.needsUpdate = true; //更新紋理
            wallHole.geometry.buffersNeedUpdate = true;
            wallHole.geometry.uvsNeedUpdate = true;
			wallHole.translateY(250);
			wallHole.translateZ(1000);
			scene.add(wallHole);
			//門
			var leftDoorTexture = new THREE.TextureLoader().load('/threejs/img/door_left.png');
			leftDoor  = createDoor(250,460,40,leftDoorTexture);
			leftDoor.name = 'leftdoor';
			leftdoorgroup = new THREE.Group();
			leftdoorgroup.name = 'leftdoorgroup';
			leftdoorgroup.position.set(-256,230,1000);
			leftDoor.position.set(128,0,0);
			leftdoorgroup.add(leftDoor);
			scene.add(leftdoorgroup);
			
			var rightDoorTexture = new THREE.TextureLoader().load('/threejs/img/door_right.png');
			var rightDoor  = createDoor(250,460,40,rightDoorTexture);
			rightDoor.name = 'rightdoor';
			rightDoor.position.set(128,230,1000);
			scene.add(rightDoor);
			var southWall = createWall(4000,500,1,'南牆');
			southWall.translateY(250);
			southWall.translateZ(-1000);
			scene.add(southWall);
			var westWall = createWall(40,500,2000,'西牆');
			westWall.translateY(250);
			westWall.translateX(-2000);
			scene.add(westWall);
			var eastWall = createWall(40,500,2000,'東牆');
			eastWall.translateY(250);
			eastWall.translateX(2000);
			scene.add(eastWall);			
			//createTea('green');
		}
		
		//牆體
		function createWall(length, width , depth, name){
		    var wallGeometry = new THREE.BoxGeometry(length, width, depth);
			var wall = new THREE.Mesh(wallGeometry, matArr2);
			wall.name = name;
			return wall;
		}
		
		//創建門洞
		function createDoorHole(length, width, depth){
		    var geometry = new THREE.BoxGeometry(length, width, depth);
			var material = new THREE.MeshPhongMaterial({color:0xafc0ca});
			var doorHole = new THREE.Mesh(geometry, material);
			return doorHole;
		}
		
		//創建門
		function createDoor(length, width, depth, texture){
		    var geometry = new THREE.BoxGeometry(length, width, depth);
			var material = new THREE.MeshPhongMaterial({map:texture});
			var door = new THREE.Mesh(geometry, material);
			return door;
		}
		
		//創建窗戶
		function createWindow(){
		
		}
		
		//渲染
		function render(){
		    if(mixer){
			    mixer.update(clock.getDelta());
			}
		    renderer.render(scene, camera);
		    requestAnimationFrame(render);
		}
		
		
		
		//初始化
		function init(){
		    commonSet();
		    scene();
		    camera();
			renderer();
			assist();
			light();
			model();
			objClick();
			render();
		}
		
		function objClick(){
            var mouse = new THREE.Vector2();
			//移動端
			document.addEventListener('touchstart',function(event){
			    if(event.touches.length == 1){
			        mouse.x = (event.touches[0].pageX / renderer.domElement.clientWidth) * 2 - 1;
                  mouse.y = - (event.touches[0].pageY / renderer.domElement.clientHeight) * 2 + 1;
                    change2DTo3D(mouse);
			    }
			},false);
			//PC端
            document.addEventListener("click", (event) => {
              mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
              mouse.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;
                change2DTo3D(mouse);
            }, false)

		}
		
		//屏幕座標轉換
		function change2DTo3D(screenCoordinate){
		    var objects=[];
            var raycaster = new THREE.Raycaster();
			raycaster.setFromCamera(screenCoordinate, camera);
          scene.children.forEach(child => {
            if (child instanceof THREE.Mesh || child instanceof THREE.Group) {//根據需求判斷哪些加入objects,也可以在生成object的時候push進objects
              objects.push(child)
            }
            });
            var intersects = raycaster.intersectObjects(objects,true);
            if (intersects.length > 0) {
			    if(intersects[0].object.name == 'leftdoor'){
				    if(leftdoorstatus == 'close'){
					    leftdoorstatus = 'open';
						var leftdoorTrack = new THREE.KeyframeTrack('leftdoorgroup.rotation[y]', [0,20], [0,-Math.PI/2]);
					}else{
					    leftdoorstatus = 'close';
						var leftdoorTrack = new THREE.KeyframeTrack('leftdoorgroup.rotation[y]', [0,20,30,40], [-Math.PI/2,0,Math.PI/4,0]);
					}
			        var duration = 40;
			        var clip = new THREE.AnimationClip('default', duration, [leftdoorTrack]);
			        mixer = new THREE.AnimationMixer(leftdoorgroup);
			        var AnimationAction = mixer.clipAction(clip);
			        AnimationAction.timeScale = 20;
			        AnimationAction.loop = THREE.LoopOnce;
					AnimationAction.clampWhenFinished = true;
			        AnimationAction.play();
				}
            }
		}
		init();  
	</script>
</body>
</html>

測試代碼,僅供參考(僅添加左門動畫,右門未添加)
重點難點
1.帶有門洞的幾何體需要兩個幾何體進行差值運算,藉助ThreeBSP生成。
2.ThreeBSP生成的幾何體,material只能是單一material,平時幾何體的六面material不適用,暫未找到解決辦法。
3.物體點擊事件需要藉助Raycaster,將屏幕的2D座標,映射成爲3D座標,由於click事件在移動端無效,所以我們需要對移動端和PC端分別進行計算。
4.默認情況下,物體的中心點都在正中央,因此如果直接對門進行旋轉的話,旋轉效果不是我們預期的,所以需要設置組,通過組來設置門需要的旋轉中心點,然後門根據情況,進行position的重新定位。
5.獲取場景的對象的時候,很多教程會直接獲取Mesh,但是像是我們剛纔添加了組,就無法獲取了,所以判斷類型的時候,把組也加進去。
6.添加了組,那麼Mesh就是組下面的子對象了,所以raycaster.intersectObjects(objects,true)的時候添加第二個參數true。
7.觸發點擊事件的是門,而進行動畫的是組。
8.動畫只播放一次,播放完成停止。AnimationAction的loop和clampWhenFinished需要設置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章