最新进展
最近这两天因为项目上比较空闲了,所以就想着怎么给我这个粗劣的小玩意儿加点高大上的东西,经过身边同事的提醒,我发现自己做的这个仓库只有一个房间,但是一般来讲厂房内可能会有多个仓库,或者说同一个仓库也有可能会有好几层。
所以开发一个场景切换的功能至关重要。经过一天的探索与发现,我终于顺利解决了这个问题,归功于网络上眼花缭乱的开源Js和插件,现将效果展示在下面:
可见右侧新添了一个选择的控件,总共有两个场景(第二个场景暂时还没做,先用一张图片代替),通过点击不同的按钮来实现不同场景的切换,切换过程中有类似翻转的特效。这部分的内容如果大家有兴趣的话我会放到以后讲解,今天还是继续第一章的内容,来看看如何添加墙壁、窗户、门和一个很关键的重点:如何选中一个物体并添加选中特效。如何选中一个物体并添加选中特效。如何选中一个物体并添加选中特效。(重要的话说三遍,选中是之后很多功能的前提)
如何添加墙壁、窗户、门
这部分内容整体来说并不难,无论是墙壁,还是门窗户,其实质都是一个长方体,我们使用THREE.BoxGeometry这个几何体来构建这一切,完成后的效果如下:
添加三面实心的墙壁
实心的墙壁是很简单的,这里直接给出代码
//创建墙
function createCubeWall(width, height, depth, angle, material, x, y, z, name){
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; //-逆时针旋转,+顺时针
cube.name = name;
scene.add(cube);
}
稍微解释一下,width, height, depth这三个参数定义了长方体的长宽高,angle定义了长方体旋转的角度,material定义了物体的材质,x, y, z定义了该物体放在场景中的具体位置,name定义了该物体的名字。然后再初始化的函数中加入
//创建墙
createCubeWall(10, 200, 1400, 0, new THREE.MeshPhongMaterial({color: 0xafc0ca}), -1295, 100, 0, "墙面");
然后就可以看到这样的效果,地面上出现了一堵左侧的墙
依葫芦画瓢可以很简单地创建出三面实心的墙
//创建墙
createCubeWall(10, 200, 1400, 0, new THREE.MeshPhongMaterial({color: 0xafc0ca}), -1295, 100, 0, "墙面");
createCubeWall(10, 200, 1400, 1, new THREE.MeshPhongMaterial({color: 0xafc0ca}), 1295, 100, 0, "墙面");
createCubeWall(10, 200, 2600, 1.5, new THREE.MeshPhongMaterial({color: 0xafc0ca}), 0, 100, -700, "墙面");
创建挖去门窗的墙
首先我们来分析下,要创造一个挖去某些部分的墙其实也很简单,其实质就是先创建一个实心的墙面,然后再创建出实心的门窗,最后用某个工具像做减法一样:实心的墙面减去实心的门窗。就可以得到挖去了门窗的墙面。很幸运的是,ThreeJs给我们提供了这样的方法,使用ThreeBSP这个库就可以实现差集(相减)、并集(组合、相加)、交集(两几何体重合的部分)等一系列功能
我们先来创建需要的一面实心墙,两扇门,四扇窗户,代码如下:
//返回墙对象
function returnWallObject(width, height, depth, angle, material, x, y, z, name){
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;
cube.name = name;
return cube;
}
//创建挖了门的墙
var wall = returnWallObject(2600, 200, 10, 0, matArrayB, 0, 100, 700, "墙面");
var door_cube1 = returnWallObject(200, 180, 10, 0, matArrayB, -600, 90, 700, "前门1");
var door_cube2 = returnWallObject(200, 180, 10, 0, matArrayB, 600, 90, 700, "前门2");
var window_cube1 = returnWallObject(100, 100, 10, 0, matArrayB, -900, 90, 700, "窗户1");
var window_cube2 = returnWallObject(100, 100, 10, 0, matArrayB, 900, 90, 700, "窗户2");
var window_cube3 = returnWallObject(100, 100, 10, 0, matArrayB, -200, 90, 700, "窗户3");
var window_cube4 = returnWallObject(100, 100, 10, 0, matArrayB, 200, 90, 700, "窗户4");
var objects_cube = [];
objects_cube.push(door_cube1);
objects_cube.push(door_cube2);
objects_cube.push(window_cube1);
objects_cube.push(window_cube2);
objects_cube.push(window_cube3);
objects_cube.push(window_cube4);
createResultBsp(wall, objects_cube);
这里我们创建了一个数组objects_cube来存放要挖去的内容,最后使用createResultBsp(wall, objects_cube)这个方法创建出挖去门窗的墙面,参数很简单,第一个是被挖去的墙,第二个是要挖去的物体的数组。该方法代码如下:
//墙上挖门窗,通过两个几何体生成BSP对象
function createResultBsp(bsp,objects_cube){
var material = new THREE.MeshPhongMaterial({color:0x9cb2d1,specular:0x9cb2d1,shininess:30,transparent:true,opacity:1});
var BSP = new ThreeBSP(bsp);
for(var i = 0; i < objects_cube.length; i++){
var less_bsp = new ThreeBSP(objects_cube[i]);
BSP = BSP.subtract(less_bsp);
}
var result = BSP.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);
}
如此这般,我们就能看到如下的效果(是不是还挺简单的呢):
安装门及窗户
接下来我们要在上面完成的挖去了门窗的墙面上安装门和窗户,我们使用三个方法来实现这个功能,因为我这里的门分为左门和右门,如果不需要的话只留下一种门就可以啦,代码如下,这三个方法类似,我就只介绍一个,方法参数很简单,width, height, depth定义了门窗的长宽高,angle定义了门窗的旋转角度,x, y, z定义了门窗的空间位置,name定义了门窗的名字。
这里都使用了THREE.TextureLoader来加载本地的图片作为门窗的贴图,门窗的实体设置为全透明,也就是opacity = 1.0和transparent = true,其他不做过多阐述。
//创建门_左侧
function createDoor_left(width, height, depth, angle, x, y, z, name){
var loader = new THREE.TextureLoader();
loader.load("./ThreeJs/images/door_left.png",function(texture){
var doorgeometry = new THREE.BoxGeometry(width, height, depth);
doorgeometry.translate(50, 0, 0);
var doormaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});
doormaterial.opacity = 1.0;
doormaterial.transparent = true;
var door = new THREE.Mesh( doorgeometry,doormaterial);
door.position.set(x, y, z);
door.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针
door.name = name;
scene.add(door);
});
}
//创建门_右侧
function createDoor_right(width, height, depth, angle, x, y, z, name){
var loader = new THREE.TextureLoader();
loader.load("./ThreeJs/images/door_right.png",function(texture){
var doorgeometry = new THREE.BoxGeometry(width, height, depth);
doorgeometry.translate(-50, 0, 0);
var doormaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});
doormaterial.opacity = 1.0;
doormaterial.transparent = true;
var door = new THREE.Mesh( doorgeometry,doormaterial);
door.position.set(x, y, z);
door.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针
door.name = name;
scene.add(door);
});
}
//创建窗户
function createWindow(width, height, depth, angle, x, y, z, name){
var loader = new THREE.TextureLoader();
loader.load("./ThreeJs/images/window.png",function(texture){
var windowgeometry = new THREE.BoxGeometry(width, height, depth);
var windowmaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});
windowmaterial.opacity = 1.0;
windowmaterial.transparent = true;
var window = new THREE.Mesh( windowgeometry,windowmaterial);
window.position.set(x, y, z);
window.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针
window.name = name;
scene.add(window);
});
}
最后在初始化函数里加上如下的代码,就可以看到门窗都已经顺利安装成功了!
//为墙面安装门
createDoor_left(100, 180, 2, 0, -700, 90, 700, "左门1");
createDoor_right(100, 180, 2, 0, -500, 90, 700, "右门1");
createDoor_left(100, 180, 2, 0, 500, 90, 700, "左门2");
createDoor_right(100, 180, 2, 0, 700, 90, 700, "右门2");
//为墙面安装窗户
createWindow(100, 100, 2, 0, -900, 90, 700, "窗户");
createWindow(100, 100, 2, 0, 900, 90, 700, "窗户");
createWindow(100, 100, 2, 0, -200, 90, 700, "窗户");
createWindow(100, 100, 2, 0, 200, 90, 700, "窗户");
完整的代码
想了想还是把选中的功能放到下一章来讲吧,这部分比较复杂,写在这一章的话实在太长了,还请同学们多多期待吧,最后照例给出该章全部的代码。
<!DOCTYPE html>
<html>
<head includeDefault="true">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>3D库图显示</title>
<style>
body {
margin: 0;
overflow: hidden;
}
#label {
position: absolute;
padding: 10px;
background: rgba(255, 255, 255, 0.6);
line-height: 1;
border-radius: 5px;
}
</style>
<script src="./ThreeJs/three.js"></script>
<script src="./ThreeJs/stats.min.js"></script>
<script src="./ThreeJs/OrbitControls.js"></script>
<script src="./ThreeJs/OBJLoader.js"></script>
<script src="./ThreeJs/MTLLoader.js"></script>
<script src="./ThreeJs/ThreeBSP.js"></script>
</head>
<body>
<div id="label"></div>
<div id="container"></div>
<script>
var stats = initStats();
var scene, camera, renderer, controls, light, composer;
var matArrayA=[];//内墙
var matArrayB = [];//外墙
var group = new THREE.Group();
// 初始化场景
function initScene() {
scene = new THREE.Scene();
scene.fog = new THREE.Fog( scene.background, 3000, 5000 );
}
// 初始化相机
function initCamera() {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(0, 800, 1500);
camera.lookAt(new THREE.Vector3(0, 0, 0));
}
// 初始化灯光
function initLight() {
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.3 );//模拟远处类似太阳的光源
directionalLight.color.setHSL( 0.1, 1, 0.95 );
directionalLight.position.set( 0, 200, 0).normalize();
scene.add( directionalLight );
var ambient = new THREE.AmbientLight( 0xffffff, 1 ); //AmbientLight,影响整个场景的光源
ambient.position.set(0,0,0);
scene.add( ambient );
}
// 初始化性能插件
function initStats() {
var stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
return stats;
}
// 初始化渲染器
function initRenderer() {
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x4682B4,1.0);
document.body.appendChild(renderer.domElement);
}
//创建地板
function createFloor(){
var loader = new THREE.TextureLoader();
loader.load("./ThreeJs/images/floor.jpg",function(texture){
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 10, 10 );
var floorGeometry = new THREE.BoxGeometry(2600, 1400, 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;
floor.name = "地面";
scene.add(floor);
});
}
//创建墙
function createCubeWall(width, height, depth, angle, material, x, y, z, name){
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; //-逆时针旋转,+顺时针
cube.name = name;
scene.add(cube);
}
//创建门_左侧
function createDoor_left(width, height, depth, angle, x, y, z, name){
var loader = new THREE.TextureLoader();
loader.load("./ThreeJs/images/door_left.png",function(texture){
var doorgeometry = new THREE.BoxGeometry(width, height, depth);
doorgeometry.translate(50, 0, 0);
var doormaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});
doormaterial.opacity = 1.0;
doormaterial.transparent = true;
var door = new THREE.Mesh( doorgeometry,doormaterial);
door.position.set(x, y, z);
door.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针
door.name = name;
scene.add(door);
});
}
//创建门_右侧
function createDoor_right(width, height, depth, angle, x, y, z, name){
var loader = new THREE.TextureLoader();
loader.load("./ThreeJs/images/door_right.png",function(texture){
var doorgeometry = new THREE.BoxGeometry(width, height, depth);
doorgeometry.translate(-50, 0, 0);
var doormaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});
doormaterial.opacity = 1.0;
doormaterial.transparent = true;
var door = new THREE.Mesh( doorgeometry,doormaterial);
door.position.set(x, y, z);
door.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针
door.name = name;
scene.add(door);
});
}
//创建窗户
function createWindow(width, height, depth, angle, x, y, z, name){
var loader = new THREE.TextureLoader();
loader.load("./ThreeJs/images/window.png",function(texture){
var windowgeometry = new THREE.BoxGeometry(width, height, depth);
var windowmaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});
windowmaterial.opacity = 1.0;
windowmaterial.transparent = true;
var window = new THREE.Mesh( windowgeometry,windowmaterial);
window.position.set(x, y, z);
window.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针
window.name = name;
scene.add(window);
});
}
//返回墙对象
function returnWallObject(width, height, depth, angle, material, x, y, z, name){
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;
cube.name = name;
return cube;
}
//墙上挖门,通过两个几何体生成BSP对象
function createResultBsp(bsp,objects_cube){
var material = new THREE.MeshPhongMaterial({color:0x9cb2d1,specular:0x9cb2d1,shininess:30,transparent:true,opacity:1});
var BSP = new ThreeBSP(bsp);
for(var i = 0; i < objects_cube.length; i++){
var less_bsp = new ThreeBSP(objects_cube[i]);
BSP = BSP.subtract(less_bsp);
}
var result = BSP.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 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 initContent() {
createFloor();
createWallMaterail();
createCubeWall(10, 200, 1400, 0, matArrayB, -1295, 100, 0, "墙面");
createCubeWall(10, 200, 1400, 1, matArrayB, 1295, 100, 0, "墙面");
createCubeWall(10, 200, 2600, 1.5, matArrayB, 0, 100, -700, "墙面");
//创建挖了门的墙
var wall = returnWallObject(2600, 200, 10, 0, matArrayB, 0, 100, 700, "墙面");
var door_cube1 = returnWallObject(200, 180, 10, 0, matArrayB, -600, 90, 700, "前门1");
var door_cube2 = returnWallObject(200, 180, 10, 0, matArrayB, 600, 90, 700, "前门2");
var window_cube1 = returnWallObject(100, 100, 10, 0, matArrayB, -900, 90, 700, "窗户1");
var window_cube2 = returnWallObject(100, 100, 10, 0, matArrayB, 900, 90, 700, "窗户2");
var window_cube3 = returnWallObject(100, 100, 10, 0, matArrayB, -200, 90, 700, "窗户3");
var window_cube4 = returnWallObject(100, 100, 10, 0, matArrayB, 200, 90, 700, "窗户4");
var objects_cube = [];
objects_cube.push(door_cube1);
objects_cube.push(door_cube2);
objects_cube.push(window_cube1);
objects_cube.push(window_cube2);
objects_cube.push(window_cube3);
objects_cube.push(window_cube4);
createResultBsp(wall, objects_cube);
//为墙面安装门
createDoor_left(100, 180, 2, 0, -700, 90, 700, "左门1");
createDoor_right(100, 180, 2, 0, -500, 90, 700, "右门1");
createDoor_left(100, 180, 2, 0, 500, 90, 700, "左门2");
createDoor_right(100, 180, 2, 0, 700, 90, 700, "右门2");
//为墙面安装窗户
createWindow(100, 100, 2, 0, -900, 90, 700, "窗户");
createWindow(100, 100, 2, 0, 900, 90, 700, "窗户");
createWindow(100, 100, 2, 0, -200, 90, 700, "窗户");
createWindow(100, 100, 2, 0, 200, 90, 700, "窗户");
}
// 初始化轨迹球控件
function initControls() {
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableDamping = true;
controls.dampingFactor = 0.5;
// 视角最小距离
controls.minDistance = 100;
// 视角最远距离
controls.maxDistance = 5000;
// 最大角度
controls.maxPolarAngle = Math.PI/2.2;
}
// 更新控件
function update() {
stats.update();
controls.update();
}
// 初始化
function init() {
initScene();
initCamera();
initRenderer();
initContent();
initLight();
initControls();
document.addEventListener('resize', onWindowResize, false);
}
// 窗口变动触发的方法
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
update();
}
init();
animate();
</script>
</body>
</html>
结束语
回顾下第一章我们涉及了基础场景的创建,包括场景、相机、光源、控制器等等。第二章我们讲解了如何创造实心的墙面和带有门窗的墙面。下一章我们将会推出如何选中一个物体并给其加上选中后的特效。
我跟广大学习ThreeJs的初学者一样,仍带着懵懂的心去探索这片新大陆,CSDN上的许多前辈都给了我很多关键的灵感和技术方法,如果大家有兴趣,也可以互相交流成长,这里给出我的邮箱18302128586@163.com,欢迎大家指导咨询。
交流微信
链接:使用ThreeJs从零开始构建3D智能仓库——第一章: https://blog.csdn.net/homula123/article/details/101197463.
来源:https://blog.csdn.net/homula123/article/details/101293425