前文

正文

前文提到了用 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
  }
}

效果如下: 合并Mesh

放大图片,从左边的 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

  1. 搞清楚 Mesh.MergeMeshes 的实现原理。
  2. 搞清楚 CSG 各种合并方法的具体行为。