前文
正文
前文提到了用 Babylon.js 里的 Mesh 画了一个火箭模型。
使用的是最原始的方法:把火箭拆成几个部分,每个部分是一个基础的几何形状,最终通过设置各部分的位置,使其看起来像一个整体。
但是如果我们想让这个火箭动起来,那么很自然地就会想到把它作为一个整体来操作,而不是对它地每个零部件单独操作。
于是搜索了一下,找到了官方文档里对组合部件的介绍——《https://doc.babylonjs.com/divingDeeper/mesh/mergeMeshes》。里面提到了官方提供的两种方式(两个 API),一种是使用 Mesh.MergeMeshes
把一组 Mesh 合成一个 Mesh;另一种是使用 CSG(Constructive Solid Geometry),把每个 Mesh 转成 CSG 再合并,然后再把合并得到的 CSG 转回 Mesh。
CSG 可以用来实现更复杂的组合方式,支持 subtract, inverse, union, and intersect 等等多种组合方式。例如用 substract
可以从一个几何体里面“掏空”另一个几何体的空间,得到一个“镂空”的几何体。可以看到文档里的例子,用 substract
实现了一个中空的管子。
使用 Mesh.MergeMeshes
回到画火箭的场景,我们用 MergeMeshes
来实现的逻辑大概是这样:
// 只展示相关逻辑代码,省略其他代码
class App {
constructor() {
// ...
// 主火箭
const rocketMain = this._createRocket('main', 10, 2, 0, 0, 0, scene)
// 辅助推进器——体积较小的火箭
const rocket1 = this._createRocket('r1', 4, 1, 1.5, -3, 0, scene)
// 一边一个
const rocket2 = this._createRocket('r2', 4, 1, -1.5, -3, 0, scene)
// 每个火箭都是一个 Mesh,把它们组装成一个整体(也是 Mesh),可以直接改变整体的位置
const rocketWhole = Mesh.MergeMeshes([rocketMain, rocket1, rocket2])
// 将火箭中心点(origin)的 y 坐标设为 5,即整个火箭上移 5
rocketWhole.position.y = 5
// ...
}
/**
* 封装了一个绘制火箭的方法。
* 火箭由三个部分组成:箭头、箭体、箭尾(就是底部喷火的地方,不知道术语叫什么,暂时先叫它箭尾吧)
*/
private _createRocket(
// 实体名称
name: string,
// 箭体高度
height: number,
// 箭体直径
diameter: number,
// 箭体的坐标
x: number,
y: number,
z: number,
) {
// ...
// 创建三个部件的逻辑与之前相同
// 把三个部分组装成一个整体,返回合并后的 Mesh 实例
const rocketMesh = Mesh.MergeMeshes([coneHead, cylinder, coneTail])
return rocketMesh
}
}
效果如下:
放大图片,从左边的 Nodes 里可以看到,整个火箭是一个 Mesh,并且空间位置比之前整体上移了 5。
MergeMeshes 的文档里有一个纯手写的合并函数,演示了 MergeMeshes 的内部逻辑,。还没仔细看。改天看一下,搞清楚合并原理。
使用 CSG
如果使用 CSG
来合并,代码大致如下:
class App {
// ...
private _createRocket() {
// ...
// 把三个部分都转成 CSG
const headCSG = CSG.FromMesh(coneHead)
const bodyCSG = CSG.FromMesh(cylinder)
const tailCSG = CSG.FromMesh(coneTail)
// 使用 CSG 的 union 方法来合并,因为其返回值是合并后的新 CSG,所以可以链式调用
const rocketCSG = headCSG.union(bodyCSG).union(tailCSG)
// 把合并生成的 CSG 转回 Mesh
const rocket = rocketCSG.toMesh('rocket', null, scene)
// 销毁之前创建的 Mesh。可以把它们理解为创建 CSG 的原材料,现在不需要了。
coneHead.dispose()
cylinder.dispose()
coneTail.dispose()
// 从场景里删除废弃的 Mesh
scene.removeMesh(coneHead)
scene.removeMesh(cylinder)
scene.removeMesh(coneTail)
return rocket
}
}
可以实现同样的效果。
对于 CSG
的各种合并方法的具体行为,还没有仔细看,也需要看一下。
ToDo
- 搞清楚
Mesh.MergeMeshes
的实现原理。 - 搞清楚
CSG
各种合并方法的具体行为。