ThreeJS —— 机房Demo(二)
Three世界中的物体都是大部分都是由一个个几何体Geometry构成的,上一节我们在场景中加入了一些几何体模拟机器,并渲染出来了画面,这一节我们将加入一些新的几何体,来模拟线路等
目录结构
├── font // 字体文件
|├──── font.ttf // 字体源文件
|└──── font.json // 转换后的字体文件
├── img // 素材图片
|├──── xx.png
|├──── xxx.jpg
|└──── …
├── js // 自己编写的js文件
|├──── composer_fn.js // 后期处理
|├──── create_fn.js // 创建各种几何
|├──── init_fn.js // 初始化项目
|└──── util_fn.js // 工具函数
├── lib // 需要引入的js文件
|├──── three.js
|├──── OrbitControls.js
|├──── RenderPass.js
|└──── …
├── model // 建模工具导出的模型
|├──── computer.gltf
|└──── …
└── index.html // 入口文件
模拟一条管线
机房中必不可少的当然是一条条类似管道的线路了,里面在不停的传输着数据源,那么这次我们将模拟出一条管线来
创建TubeGeometry几何体
首先我们利用TubeGeometry创建一条管道,第一个参数传入自定义好的路径
// create_fn.js
// 传入一组三维坐标点,例如:([-15, -5, 15], [-15, -5, -40], [40, -5, -40]),按照这组点形成一条路径,在此路径基础上创建管道
function createTube(...pointsArr) {
const path = createPath(pointsArr); // createPath是我们编写的创建路径的函数,详细如下
const geometry = new THREE.TubeGeometry(path, 64, 0.3); // 第一个参数为路径,必须为Curve类,第二个参数为分段值(可理解为细粒度),第三个参数为管道横截面半径
// curve是基类,表示曲线,子类有lineCurve二维直线,lineCurve3三维直线
// curvePath是一组curve构成的路径,可以算是curve的子类,curvePath的子类path二维路径,shape是path的子类,所以第一个参数可以传入curvePath
const material = new THREE.MeshBasicMaterial({
color: "#00ffff" });
const mesh = new THREE.Mesh(geometry, material);
return mesh;
}
// 创建一条路径,可以是三维或二维路径,传入一组点,例如:[[-15, -5, 15], [-15, -5, -40], [40, -5, -40]]
function createPath(pointsArr) {
pointsArr = pointsArr.map((point) => new THREE.Vector3(...point)); // 将参数数组转换成点数组的形式
// 方法一:自定义三维路径 curvePath
const path = new THREE.CurvePath();
for (let i = 0; i < pointsArr.length - 1; i++) {
const lineCurve = new THREE.LineCurve3(pointsArr[i], pointsArr[i + 1]); // 每两个点之间形成一条三维直线
path.curves.push(lineCurve); // curvePath有一个curves属性,里面存放组成该三维路径的各个子路径
}
// 方法二:利用CatmullRomCurve3创建三维路径,不过CatmullRomCurve3是平滑的三维样条曲线
// const path = new THREE.CatmullRomCurve3(pointsArr);
return path;
}
效果图:
到这里,管道已经创建完毕了,不过只有一条管道并不能很好的模拟除管线的效果,因为缺少了很重要的一个元素——“动画”
为管线添加动画
管道实现动画原理:
- 对管道进行贴图,图片由两种相近的颜色组成,较亮的颜色可以模拟正在传输的数据元
- 在animate动画中,动态的改变贴图的偏移量offset,产生运动效果
运动素材贴图 tube.jpg :
创建管线:
// create_fn.js
async function createTube(...pointsArr) {
const path = createPath(pointsArr);
const geometry = new THREE.TubeGeometry(path, 64, 0.3);
// 模拟管线运动动画的贴图texture
const texture = new THREE.TextureLoader().load('../img/tube.jpg');
texture.wrapS = THREE.RepeatWrapping; // 设置x方向能够重复,这样才可以设置texture的偏移量offset
texture.repeat.x = 1; // 设置x方向的重复数为1,也可设置为2,这样产生的动画效果代表管道内同时有两端数据元在传输
const material = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
});
const mesh = new THREE.Mesh(geometry, material);
return {
texture, mesh };
}
给管线添加动画:
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<div id="canvas-frame"></div>
<!-- 引入的一些JS -->
<script src="lib/three.js"></script>
<script src="..."></script>
<script>
// ...
// 新添加的代码
const {
texture, mesh } = await createTube([-15, -5, 15], [-15, -5, -40], [40, -5, -40]); // 获取到管线mesh,以及管线贴图texture
scene.add(mesh); // 将管线添加到场景中
function animate(time) {
// ...
texture.offset.x -= 0.022; // 每次让贴图的x偏移量减少0.022,以产生动画效果
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>
自定义管线运动部分的长度
上面虽然实现了管线的动画效果,但是由于贴图素材中,运动部分和不运动部分的比例是固定的,导致如果管线很长的话,运动部分所占的长度也会相应变长,影响美观(例如:运动部分和不运动部分原本比例1:4,如果是10m长的管道,那么运动部分占了2m,但如果是100m长的管道,则运动部分就占了20m,显然过长),所以需要自定义运动部分所占的比例
实现原理:
- 首先准备两张长宽相同的素材图片,一张用作运动部分,一张用作不运动部分
- 根据传入的比例,在canvas中将两张图片按照比例合并成一张图片
- 将合成后的图片作为管线的贴图
首先我们提前准备好一个函数 mergeImage,用来将两张图片按比例合并成为一张
// util_fn.js
function mergeImage(imgSrc1, imgSrc2, a, b) {
return new Promise((res, rej) => {
const canvas = document.createElement("canvas"); // 创建canvas
const ctx = canvas.getContext