Babylon.js の基本メッシュを変形して、🎄に変更する。

これは [Babylon.js Advent Calendar 2023](https://qiita.com/advent-calendar/2023/babylonjs) の22日目の記事です。

### この記事について
[前回](https://qiita.com/Ykoba791/items/00aee40ef1b4e9e53797)、[前々回](https://qiita.com/Ykoba791/items/5a7be5cf4d6aabda7d8f)で、基本メッシュの頂点を編集、着色を行いました。
今回はそれらを使用して、クリスマスツリーを描画してみます。


### 原型のオブジェクトを用意する
編集用に、円柱を２つ作成します。

```javascript:オブジェクト作成
    const leaf = BABYLON.MeshBuilder.CreateCylinder("leaf", 
    {height:6,diameterTop: 0,diameterBottom:2,subdivisions:100,updatable:true});
    leaf.position.y = 4;
    const trunk = BABYLON.MeshBuilder.CreateCylinder("trunk", 
    {height:2,diameterTop: 0.7,diameterBottom:0.8,subdivisions:100,updatable:true});
    
```

円柱を2つ用意しました。
１つは、diameterTop: 0　と上面のサイズを0、diameterBottom:2 で下面のサイズを３に指定し、円錐を作成。
もう一つは、上面を0.7，下面を0.8にし、少し末広がりの円柱にしています。この後頂点の編集をするので、updatable:true も忘れず指定しておきます。

作成結果は下図です。
 <img width="160" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/1607a75f-0290-a2a9-0f4a-9052bf4cd0e6.png">

 ### randomで原型の木のオブジェクトをディティールアップ
それでは、上記をベースにツリーっぽくしていきます。
getVerticesDataで、頂点データを配列に取得し、着色用の配列も用意します。

```javascript:座標の保存
    var leafPos = leaf.getVerticesData(BABYLON.VertexBuffer.PositionKind);
    var leafColors = [];
    var trunkPos = trunk.getVerticesData(BABYLON.VertexBuffer.PositionKind);
    var trunkColors = [];
```

次に葉の部分を三角錐の半径を変形させて、🎄っぽくさせていきます。
上で取得した葉の頂点座標(x､y､z)を、乱数である程度間隔をあけながらx座標、z座標を2.5倍にして外側へ広げ、y座標は-0.5して下に下がるように変形させました。
```javascript:乱数で、x､zを2.5倍、y座標を-0.5（下へ下げる）する
    var idx=10;
    for(var i = 0; i<(leafPos.length/3); i++) {
        if(i==idx){
    		idx = i + Math.floor(Math.random() * 4) + 2;
            leafPos[i*3]   = leafPos[i*3] * 2.5;
            leafPos[i*3+1] = leafPos[i*3+1] - 0.5;
            leafPos[i*3+2] = leafPos[i*3+2] * 2.5;
        }
    }
    leaf.updateVerticesData(BABYLON.VertexBuffer.PositionKind, leafPos);
```

実行結果は下図です。少し枝葉っぽさが出ました。
<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/b0707e15-68fb-dd65-51df-e7bf1e94157e.png">

枝葉っぽさが出たので、緑色を塗ります
用意しておいた配列に、位置を2.5倍した外側の頂点には緑色を、拡大しなかった内側の頂点は影っぽくしたいので、やや暗めの緑色をPushしていきます。
用意した色の配列は、setVerticesDataでオブジェクトに色を反映します。
```javascript:拡大した頂点は緑を、内側の頂点は暗めの緑を着色
    var idx=10;
    for(var i = 0; i<leafPos.length/3; i++) {
        if(i==idx){
            idx = i + Math.floor(Math.random() * 4) + 4;
            leafPos[i*3]   = leafPos[i*3] * 2.5;
            leafPos[i*3+1] = leafPos[i*3+1] - 0.5;
            leafPos[i*3+2] = leafPos[i*3+2] * 2.5;
            leafColors.push(0, 0.8, 0.2, 1);  
        }else{
            leafColors.push(0, 0.6, 0.1, 1);         
        }
    }

    leaf.updateVerticesData(BABYLON.VertexBuffer.PositionKind, leafPos);
    leaf.setVerticesData(BABYLON.VertexBuffer.ColorKind, leafColors);
```

ここまでの実行結果は下の通りです。
<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/e18dc71f-a760-5156-88b8-0dff8bd775a2.png">


幹についても同様に編集します。
こちらは元の座標に少しランダム性を加えるだけにしました。

```javascript:幹の座標と色にランダムを加える。
    var idx=0;
    for(var i = 0; i<trunkPos.length/3; i++) {
            idx = i + Math.floor(Math.random() * 4) + 4;
            trunkPos[i*3]   = trunkPos[i*3] + Math.random()*0.05;
            trunkPos[i*3+1] = trunkPos[i*3+1] + Math.random()*0.4-0.2;
            trunkPos[i*3+2] = trunkPos[i*3+2] + Math.random()*0.05;
            trunkColors.push(0.3+Math.random()*0.2, 0.1+Math.random()*0.1, Math.random()*0.1, 1);  
    }
    trunk.updateVerticesData(BABYLON.VertexBuffer.PositionKind, trunkPos);
    trunk.setVerticesData(BABYLON.VertexBuffer.ColorKind, trunkColors);
```

ここまでの実行結果とPlaygroundのURLは下の通りです。
<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/cdeff636-7bb4-f53d-2ef4-3a53f2d1d5b7.png">
https://playground.babylonjs.com/#UY72D0#22

### 飾り付け
最期に飾り付けです。
CreateSphereで球体を作成したのち、木と同じように頂点の編集をしてみます。
元の頂点座標を、1つ置き、2つ置きに外側へずらしてみましょう。
```javascript:球体を加工する
    var spherePos = sphere.getVerticesData(BABYLON.VertexBuffer.PositionKind);
    for(var i = 0; i<spherePos.length/3; i++) {
        spherePos[i*3]   = spherePos[i*3] * 2;
        spherePos[i*3+1] = spherePos[i*3+1] * 2;
        spherePos[i*3+2] = spherePos[i*3+2] * 2;
        i=i+【+1,+2,…ここでカウンタを少し飛ばす】;
    }
    sphere.updateVerticesData(BABYLON.VertexBuffer.PositionKind, spherePos);
```

下図の左が通常の球体、中央が上のソースで　i=i+1 を指定した場合、右側がi=i+2　を指定した場合です。
少しずつずらすと、螺旋が現れました。
![baby20231222_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/94e9bb52-0850-01f2-29e4-fa48dd3eae43.png)

枝を伸ばした処理の部分で、ランダムで球体、編集した球体を追加するようにして下図のような結果になります。

![baby20231222_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/b0e9c4f2-f350-b641-2599-0ccf2c573d58.png)

Playgroundはこちら。
https://playground.babylonjs.com/#UY72D0#24


## 最後に

テクスチャや3Dモデルを使用せずに基本メッシュの頂点座標の変更、色の指定機能で🎄を作成してみました。

外部ファイルを参照しないので、実行環境を気にせず動かせるのはちょっと試したい時に外的要因を考えなくていいので、粗々でなにかする時にはいいかもしれませんね。