用Three.js實現簡單佈局的3D房間

廢話不說了,直接上成果圖。

 代碼如下

<!doctype html>
<html lang="en">
<head>
<title>房間佈局</title>
<meta charset="utf-8">
<meta name="viewport"
	content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
</head>
<body>


	<script src="js/jquery-1.9.1.js"></script>
	<script src="js/Three.min.js"></script>
	<script src="js/OrbitControls.js"></script>
	<script src="js/ThreeBSP.js"></script>
	<script src="js/Detector.js"></script>
	<script src="js/Stats.js"></script>


	<script src="js/THREEx.KeyboardState.js"></script>
	<script src="js/THREEx.FullScreen.js"></script>
	<script src="js/THREEx.WindowResize.js"></script>


	<!-- people -->
	<script src="people/js/three.js"></script>
	<script src="people/js/DDSLoader.js"></script>
	<script src="people/js/MTLLoader.js"></script>
	<script src="people/js/OBJLoader.js"></script>
	<script src="people/js/Detector.js"></script>
	<script src="people/js/stats.min.js"></script>
	<script src="people/js/PathControls.js"></script>
	<script src="people/js/Tween.js"></script>
	<script src="people/js/RequestAnimationFrame.js"></script>


	<div id="ThreeJS" style="position: absolute; left: 0px; top: 0px"></div>


	<script>
		// 設置全局變量
		var scene, camera, renderer, controls, tween, door;
		var keyboard = new THREEx.KeyboardState();//保持鍵盤的當前狀態,可以隨時查詢
		var clock = new THREE.Clock();
		var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;
		//var VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 20000;
		var VIEW_ANGLE = 75, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 10000;
		var materialArrayA = [];
		var materialArrayB = [];
		var matArrayA = [];//內牆
		var matArrayB = [];//外牆
		var dummy = new THREE.Object3D();//仿製品
		init();
		animate();

		//1.場景         
		function initScene() {
			scene = new THREE.Scene();
		}

		//2.相機
		function initCamera() {
			camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
			camera.position.set(0, 1000, 1800);
			camera.lookAt(scene.position);
			camera.lookAt(0, 0, 0);
			scene.add(camera);
		}

		//3.渲染器
		function initRender() {
			if (Detector.webgl)
				renderer = new THREE.WebGLRenderer({
					antialias : true
				});
			else
				renderer = new THREE.CanvasRenderer();
			//設置渲染器的大小爲窗口的內寬度,也就是內容區的寬度。
			renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
			container = document.getElementById('ThreeJS');
			container.appendChild(renderer.domElement);
			renderer.setClearColor(0x4682B4, 1.0);
		}

		//4.事件
		function initEvent() {
			THREEx.WindowResize(renderer, camera);
			THREEx.FullScreen.bindKey({
				charCode : 'm'.charCodeAt(0)
			});
		}

		//5.控制
		function initControls() {
			controls = new THREE.OrbitControls(camera, renderer.domElement);
		}

		//6.光源
		function initLight() {
			// 位置不同,方向光作用於物體的面也不同,看到的物體各個面的顏色也不同 
			// A start, 第二個參數是光源強度
			var directionalLight = new THREE.DirectionalLight(0xffffff, 1);//模擬遠處類似太陽的光源
			directionalLight.position.set(0, 100, 0).normalize();
			scene.add(directionalLight);
			//A end
			var ambient = new THREE.AmbientLight(0xffffff, 1); //AmbientLight,影響整個場景的光源
			ambient.position.set(0, 0, 0);
			scene.add(ambient);
			//var pointlight = new THREE.PointLight(0x000000,1.5,2000);
			//scene.add(pointlight); 
		}

		//創建地板  
		function createFloor() {
			var loader = new THREE.TextureLoader();
			loader.load("images/floor.jpg", function(texture) {
				texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
				texture.repeat.set(10, 10);
				var floorGeometry = new THREE.BoxGeometry(1600, 1100, 1);
				var floorMaterial = new THREE.MeshBasicMaterial({
					map : texture,
					side : THREE.DoubleSide
				});
				var floor = new THREE.Mesh(floorGeometry, floorMaterial);
				floor.position.y = -0.5;
				floor.rotation.x = Math.PI / 2;
				scene.add(floor);
			});

			//茶色:0x58ACFA   透明玻璃色:0XECF1F3
			var glass_material = new THREE.MeshBasicMaterial({
				color : 0XECF1F3
			});
			glass_material.opacity = 0.4;
			glass_material.transparent = true;

			var left_wall = returnWallObject(20, 200, 1100, 0, matArrayB, -801,
					100, 0);
			var left_cube = returnWallObject(20, 110, 1100, 0, matArrayB, -801,
					100, 0);
			createResultBsp(left_wall, left_cube, 1);
			createCubeWall(1, 110, 1100, 0, glass_material, -801, 100, 0);

			var right_wall = returnWallObject(20, 200, 1100, 1, matArrayB, 801,
					100, 0);
			var right_cube = returnWallObject(20, 110, 1100, 0, matArrayB, 801,
					100, 0);
			createResultBsp(right_wall, right_cube, 1);
			createCubeWall(1, 110, 1100, 0, glass_material, 801, 100, 0);
		}

		//牆上挖門,通過兩個幾何體生成BSP對象
		function createResultBsp(bsp, less_bsp, mat) {
			switch (mat) {
			case 1:
				var material = new THREE.MeshPhongMaterial({
					color : 0x9cb2d1,
					specular : 0x9cb2d1,
					shininess : 30,
					transparent : true,
					opacity : 1
				});
				break;
			case 2:
				var material = new THREE.MeshPhongMaterial({
					color : 0xafc0ca,
					specular : 0xafc0ca,
					shininess : 30,
					transparent : true,
					opacity : 1
				});
				break;
			default:
			}

			var sphere1BSP = new ThreeBSP(bsp);
			var cube2BSP = new ThreeBSP(less_bsp);//0x9cb2d1 淡紫,0xC3C3C3 白灰 , 0xafc0ca灰
			var resultBSP = sphere1BSP.subtract(cube2BSP);
			var result = resultBSP.toMesh(material);
			result.material.flatshading = THREE.FlatShading;
			result.geometry.computeFaceNormals(); //重新計算幾何體側面法向量
			result.geometry.computeVertexNormals();
			result.material.needsUpdate = true; //更新紋理
			result.geometry.buffersNeedUpdate = true;
			result.geometry.uvsNeedUpdate = true;
			scene.add(result);
		}

		//創建牆
		function createCubeWall(width, height, depth, angle, material, x, y, z) {
			var cubeGeometry = new THREE.BoxGeometry(width, height, depth);
			var cube = new THREE.Mesh(cubeGeometry, material);
			cube.position.x = x;
			cube.position.y = y;
			cube.position.z = z;
			cube.rotation.y += angle * Math.PI; //-逆時針旋轉,+順時針
			scene.add(cube);
		}

		//返回牆對象
		function returnWallObject(width, height, depth, angle, material, x, y,
				z) {
			var cubeGeometry = new THREE.BoxGeometry(width, height, depth);
			var cube = new THREE.Mesh(cubeGeometry, material);
			cube.position.x = x;
			cube.position.y = y;
			cube.position.z = z;
			cube.rotation.y += angle * Math.PI;
			return cube;
		}

		//創建牆紋理
		function createWallMaterail() {
			matArrayA.push(new THREE.MeshPhongMaterial({
				color : 0xafc0ca
			})); //前  0xafc0ca :灰色
			matArrayA.push(new THREE.MeshPhongMaterial({
				color : 0xafc0ca
			})); //後  
			matArrayA.push(new THREE.MeshPhongMaterial({
				color : 0xd6e4ec
			})); //上  0xd6e4ec: 偏白色
			matArrayA.push(new THREE.MeshPhongMaterial({
				color : 0xd6e4ec
			})); //下  
			matArrayA.push(new THREE.MeshPhongMaterial({
				color : 0xafc0ca
			})); //左    0xafc0ca :灰色
			matArrayA.push(new THREE.MeshPhongMaterial({
				color : 0xafc0ca
			})); //右

			matArrayB.push(new THREE.MeshPhongMaterial({
				color : 0xafc0ca
			})); //前  0xafc0ca :灰色
			matArrayB.push(new THREE.MeshPhongMaterial({
				color : 0x9cb2d1
			})); //後  0x9cb2d1:淡紫
			matArrayB.push(new THREE.MeshPhongMaterial({
				color : 0xd6e4ec
			})); //上  0xd6e4ec: 偏白色
			matArrayB.push(new THREE.MeshPhongMaterial({
				color : 0xd6e4ec
			})); //下  
			matArrayB.push(new THREE.MeshPhongMaterial({
				color : 0xafc0ca
			})); //左   0xafc0ca :灰色
			matArrayB.push(new THREE.MeshPhongMaterial({
				color : 0xafc0ca
			})); //右

		}

		//創建房間佈局
		function createLayout() {

			// 牆面1 立方體比較長的面  左一
			createCubeWall(10, 200, 900, 0, matArrayB, -651, 100, 0);
			// 牆面2  立方體比較長的面   右一
			createCubeWall(10, 200, 900, 1, matArrayB, 651, 100, 0);
			// 牆面3 門對面的牆 立方體比較短的面  
			createCubeWall(10, 200, 1310, 1.5, matArrayB, 0, 100, -451);

			// 牆面4   帶門的面  
			var wall = returnWallObject(1310, 200, 10, 0, matArrayB, 0, 100,
					455);
			// 門框 
			var door_cube = returnWallObject(100, 180, 10, 0, matArrayB, 0, 90,
					455);
			createResultBsp(wall, door_cube, 1);

			//爲牆面安裝門,右門
			var loader = new THREE.TextureLoader();
			loader.load("images/door_right.png", function(texture) {
				var doorgeometry = new THREE.BoxGeometry(100, 180, 2);
				var doormaterial = new THREE.MeshBasicMaterial({
					map : texture,
					color : 0xffffff
				});
				doormaterial.opacity = 1.0;
				doormaterial.transparent = true;
				door = new THREE.Mesh(doorgeometry, doormaterial);
				door.position.set(-50, 0, 0);
				var door1 = door.clone();
				door1.position.set(50, 0, 0);
				door1.visible = false;
				dummy.add(door);
				dummy.add(door1);
				dummy.position.set(50, 90, 451)
				scene.add(dummy);
			});

			// 房間A:隔牆1 
			createCubeWall(10, 200, 250, 0, matArrayA, -151, 100, 325);
			//房間A:隔牆2  無門
			createCubeWall(10, 200, 220, 0.5, matArrayA, -256, 100, 201);
			// 廚房:隔牆3 
			createCubeWall(350, 200, 10, 0, matArrayA, 481, 100, 131);
			// 廚房:隔牆4 無門
			createCubeWall(10, 200, 200, 0, matArrayA, 301, 100, 225);
			// 房間B 
			createCubeWall(350, 200, 10, 0, matArrayA, -471, 100, -50);
			//房間B  無門
			createCubeWall(200, 200, 10, 0.5, matArrayA, 0, 100, -350);
			// 房間C
			createCubeWall(220, 200, 10, 0, matArrayA, 540, 100, -50);
			//房間C 無門
			createCubeWall(200, 200, 10, 0.5, matArrayA, 250, 100, -350);
			//廁所
			var cube = returnWallObject(10, 200, 260, 0.5, matArrayA, 125, 100,
					-250);
			//廁所門框
			var door_cube1 = returnWallObject(10, 160, 80, 0.5, matArrayA, 155,
					90, -250);
			createResultBsp(cube, door_cube1, 2);

			//茶色:0x58ACFA   透明玻璃色:0XECF1F3
			var glass_material = new THREE.MeshBasicMaterial({
				color : 0x58ACFA
			});
			glass_material.opacity = 0.6;
			glass_material.transparent = true;
			createCubeWall(1, 180, 80, 0.5, glass_material, 155, 90, -250);
		}

		//7.初始化OBJ對象
		function initObject() {
			//牆紋理
			createWallMaterail();
			createFloor();
			createLayout();
		}

		//初始化函數
		function init() {
			initScene();
			initCamera();
			initRender();
			initEvent();
			initControls();
			initLight();
			initObject();
			//監聽鍵盤按鍵
			document.addEventListener("keydown", onkeyDown, false);
		}

		var door_state = true;//默認是門是關閉的
		//Enter=13,Space=32;
		function onkeyDown(event) {
			switch (event.keyCode) {
			case 13:
				console.log(event.keyCode);
				if (door_state) {
					dummy.rotation.y += 0.5 * Math.PI;
					door_state = false;
				} else {
					dummy.rotation.y -= 0.5 * Math.PI;
					door_state = true;
				}
				break;
			default:
				console.log(event.keyCode);
				break;
			}
		}

		function animate() {
			requestAnimationFrame(animate);
			renderer.render(scene, camera);
			TWEEN.update();
			update();
		}

		function update() {
			var delta = clock.getDelta();
			var moveDistance = 200 * delta;
			var rotateAngle = Math.PI / 2 * delta;
			controls.update();
		}
	</script>
</body>
</html>

 

通過Enter鍵可控制開門和關門動作。門的旋轉是通過,把門克隆一份,把克隆的那個設置爲不可見,然後把兩個門打個組 ,這個時候中旋轉組就可以了。

 此時的旋轉中心實際是在組的中心,但設置一半不可見 ,看起來就像是門在旋轉了。注意的是,組內的東西的座標是相對於組的組內,兩個門的座標應該分別是x軸的正負軸上,整個組的位置應該是原來門應該在的位置。

(這也是我向一位大神請教的,真的很感謝他那麼耐心的教我,O(∩_∩)O)

 

運行方式:

在支持webgl的瀏覽器上打開room.html,即可看到效果圖。如果加載不出來,打開Chrome快捷方式的屬性中設置:右擊Chrome瀏覽器快捷方式, 選擇“屬性”,在“目標”中加上"--allow-file-access-from-files",注意前面有個空格。修改完成,點擊應用,確定後,關閉所有chrome上的窗口,重啓chrome。再找到該資源room.html文件,以Google Chrome瀏覽器方式打開即可。

 

錯誤解決。

如果出現地板和門的兩張圖片加載不出來時,提示已被跨源資源共享策略阻止加載。解決辦法第一種是如上圖所示在Chrome的屬性加"--allow-file-access-from-files";第二種就是把圖片位置的相對路徑改成絕對路徑。

原demo發郵件索取的太多了,都從網盤自取吧。就是個demo,不喜勿噴,渣渣博主很玻璃心。

鏈接:https://pan.baidu.com/s/1UmBcjFCQ5QzdLcfGp-gpug 密碼:r5c8

發佈了18 篇原創文章 · 獲贊 16 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章