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