<!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需要設置。