BabylonJS デカールメッシュによるデカールの実現

# デカールメッシュ

BabylonJS ではデカールの実現方法について2つの方法が紹介されていますが今回はデカールメッシュで実現する方法を紹介します。[`CreateDecal`](https://doc.babylonjs.com/typedoc/modules/BABYLON#CreateDecal-2) を使用します。

デカールメッシュのメッシュの決まり方はざっくりと以下のようになります。
- `position`, `size`, `normal`, `angle` の各オプションパラメータに従って1つの直方体を空間に配置する。
- 直方体の元々のZ軸負の面とZ軸正の面からそれぞれ反対側の面へ向かって平行移動させていく。
- 平行移動の過程でひっかかったデカール適用先メッシュに沿うようにメッシュを決定していく。

`position` オプションはデカールメッシュを決定するために配置する直方体の中心位置を指定します。デフォルトは `(0, 0, 0)` です。
`size` オプションは直方体のサイズを指定します。デフォルトは `(1, 1, 1)` です。
`normal` オプションは直方体のZ軸負面をどちらに向かせるかを指定します。デフォルトは `Vector3.Up` です。
`angle` オプションは `normal` まわりにどれだけねじるかをラジアン単位で指定します。デフォルトは `0` です。
`cullBackFaces` は直方体のZ軸正の面から平行移動して張るメッシュを無効化するかどうかを指定します。`true` を指定するとZ軸正の面から張られるメッシュが無効化され、Z軸負の面からだけのメッシュだけになります。デフォルトは `false` です。

生成したメッシュデカールをそのまま表示すると適用先メッシュとの奥行の関係でちらつきが出てしまうため，Material.zOffset = -2 のようにオフセットを掛けてあげると良いようです。

以下のコードは2023年12月9日の [BabylonJS Playground](https://playground.babylonjs.com/) 6.33.1 Javascript で動作したことがあります。

```js
const createScene = async () => {
    const scene = new BABYLON.Scene(engine);
    const camera = new BABYLON.ArcRotateCamera('camera1',
        Math.PI * -60 / 180, Math.PI * 60 / 180, 20,
        new BABYLON.Vector3(4, 0, 0), scene);
    camera.wheelDeltaPercentage = 0.01;
    camera.attachControl(true);

    const light = new BABYLON.HemisphericLight('light1', new BABYLON.Vector3(0, 1, 0), scene);
    light.intensity = 0.7;

// 張りつけられるメッシュを作成する
    const b = 0.5;
    const hw = 2;
    const hh = 3;
    const shape = [
        [-hw+b, hh, 0],
        [-hw, hh-b, 0],
        [-hw, -hh+b, 0],
        [-hw+b, -hh, 0]
    ];
    for (let i = 0; i <= 16; ++i) {
        const ang = i * Math.PI / 16;
        const s = Math.sin(ang);
        shape.push([hw * s, -hh * Math.cos(ang), 0]);
    }

    const path = [];
    for (let i = 0; i <= 10; ++i) {
        path.push([i, 0, 0]);
    }

    const opt = {
        shape: shape.map(p => new BABYLON.Vector3(...p)),
        closeShape: true,
        path: path.map(p => new BABYLON.Vector3(...p)),
        cap: BABYLON.Mesh.CAP_ALL,
        rotationFunction: (i, distance) => {
            return Math.PI * 90 / 180 / 10;
        },
    };
    const mesh = BABYLON.MeshBuilder.ExtrudeShapeCustom('mesh1', opt, scene);
    scene.onBeforeRenderObservable.add((scene) => {
        mesh.rotate(new BABYLON.Vector3(1, 0, 0), 0.008);
    });

// デカールメッシュを生成する
    const decalOpt = {
        position: new BABYLON.Vector3(5, 0, 0),
        normal: new BABYLON.Vector3(0, 1, -1).normalize(),
        size: new BABYLON.Vector3(6, 6, 10),
        angle: Math.PI * 20 / 180,
        localMode: true,
        cullBackFaces: true,
    };
    const meshDecal = BABYLON.MeshBuilder.CreateDecal('decal1',
        mesh, decalOpt);

// テクスチャの生成と描画
    const texOpt = { width: 512, height: 512 };
    const tex = new BABYLON.DynamicTexture('tex1', texOpt, scene);
    tex.hasAlpha = true;
    const c = tex.getContext();
    c.fillStyle = 'rgba(255, 153, 0, 1)';
    const w = c.canvas.width;
    const h = c.canvas.height;
    c.fillRect(0, h/4, w, h/2);
    c.fillStyle = 'red';
    c.textAlign = 'center';
    c.textBaseline = 'middle';
    c.font = 'bold 120px Ariel';
    c.fillText('babylon', w / 2, h / 2);
    tex.update();

    const material = new BABYLON.StandardMaterial('material1', scene);
    material.zOffset = -2; // ちらつき防止
    material.diffuseTexture = tex;
    meshDecal.material = material;

    return scene;
};
```

|実行結果1|
|:-:|
|![dm1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/9b3d371b-7700-8fb1-bf88-01cda0445830.png)|
|曲面に張りつけた表現になりました|

|実行結果2|
|:-:|
|![dm2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/0fa1f45d-8f8c-51f6-3802-c83804f57b7c.png)|
|デカールメッシュをワイヤーフレーム表示した状態|

# リンク

- Decals
https://doc.babylonjs.com/features/featuresDeepDive/mesh/decals
デカールの実現方法