Chapter 15

2-09: メッシュをコピー (Copying Meshes)

ちょまど (千代田まどか)
ちょまど (千代田まどか)
2022.05.20に更新

このページの原文: https://doc.babylonjs.com/start/chap2/copies

🏘🏘 2-09: メッシュをコピー (Copying Meshes)

メッシュをコピーする

メッシュをコピーするには、主な方法が 2 つあります。作ったメッシュをクローンするか そのインスタンスを作成するかです。
クローンを作成すると、メッシュの独立したコピーを得られますが、一方で、そのインスタンスはオリジナルのマテリアルのリンクを持ったままです。基本的に、メッシュのインスタンスのマテリアルを変更することはできません。

家をクローンするにはこうします。

clonedHouse = house.clone("clonedHouse")

インスタンスはこうなります。

instanceHouse = house.createInstance("instanceHouse")

この時点で、私たちの世界(いま村を作っているのでしたね)の全ての家は createInstance で使用するのと同じマテリアルを使用することになります。

🔨🏘 家を作る関数

家(幅 1, 2 の家)を複数個 作る関数を作りましょう。

→ Playgroundでライブデモとコードを確認

const createScene = () => {
    const scene = new BABYLON.Scene(engine);
    const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 10, new BABYLON.Vector3(0, 0, 0));
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0));
    
    const ground = buildGround();
    const house = buildHouse(2); // 関数呼び出し元
    
    return scene;
}

/** ここから!家を作る関数 **/
const buildGround = () => {
    //color
    const groundMat = new BABYLON.StandardMaterial("groundMat");
    groundMat.diffuseColor = new BABYLON.Color3(0, 1, 0);

    const ground = BABYLON.MeshBuilder.CreateGround("ground", {width:10, height:10});
    ground.material = groundMat;
}

// 幅 width を引数で受けるようになっている
const buildHouse = (width) => {
    const box = buildBox(width);
    const roof = buildRoof(width);

    return BABYLON.Mesh.MergeMeshes([box, roof], true, false, null, false, true);
}

const buildBox = (width) => {
    //texture
    const boxMat = new BABYLON.StandardMaterial("boxMat");
    if (width == 2) {
        boxMat.diffuseTexture = new BABYLON.Texture("https://assets.babylonjs.com/environments/semihouse.png");
    } else {
        boxMat.diffuseTexture = new BABYLON.Texture("https://assets.babylonjs.com/environments/cubehouse.png");   
    }

    const faceUV = [];
    if (width == 2) {
        faceUV[0] = new BABYLON.Vector4(0.6, 0.0, 1.0, 1.0); //rear face
        faceUV[1] = new BABYLON.Vector4(0.0, 0.0, 0.4, 1.0); //front face
        faceUV[2] = new BABYLON.Vector4(0.4, 0, 0.6, 1.0); //right side
        faceUV[3] = new BABYLON.Vector4(0.4, 0, 0.6, 1.0); //left side
    } else {
        faceUV[0] = new BABYLON.Vector4(0.5, 0.0, 0.75, 1.0); //rear face
        faceUV[1] = new BABYLON.Vector4(0.0, 0.0, 0.25, 1.0); //front face
        faceUV[2] = new BABYLON.Vector4(0.25, 0, 0.5, 1.0); //right side
        faceUV[3] = new BABYLON.Vector4(0.75, 0, 1.0, 1.0); //left side
    }

    /**** World Objects *****/
    const box = BABYLON.MeshBuilder.CreateBox("box", {width: width, faceUV: faceUV, wrap: true});
    box.material = boxMat;
    box.position.y = 0.5;

    return box;
}

const buildRoof = (width) => {
    //texture
    const roofMat = new BABYLON.StandardMaterial("roofMat");
    roofMat.diffuseTexture = new BABYLON.Texture("https://assets.babylonjs.com/environments/roof.jpg");

    const roof = BABYLON.MeshBuilder.CreateCylinder("roof", {diameter: 1.3, height: 1.2, tessellation: 3});
    roof.material = roofMat;
    roof.scaling.x = 0.75;
    roof.scaling.y = width;
    roof.rotation.z = Math.PI / 2;
    roof.position.y = 1.22;

    return roof;
}

🏘🏘 家を複製して配置する

ここで、地面を拡大し、カメラの半径を少し大きくし、家がよく見えるよう表示するようにしましょう。
まず、各タイプの家を 1 つずつ建てて、配置します。
そしてそれらのインスタンスを作成し、それぞれの向きや位置を決めたら、ループさせて置いていきます。

→ Playgroundでライブデモとコードを確認

const detached_house = buildHouse(1);
detached_house.rotation.y = -Math.PI / 16;
detached_house.position.x = -6.8;
detached_house.position.z = 2.5;

const semi_house = buildHouse(2);
semi_house .rotation.y = -Math.PI / 16;
semi_house.position.x = -4.5;
semi_house.position.z = 3;

// 配置
const places = []; // 配列 [家のタイプ, 向き, x, z]
places.push([1, -Math.PI / 16, -6.8, 2.5]);
places.push([2, -Math.PI / 16, -4.5, 3]);
places.push([2, -Math.PI / 16, -1.5, 4]);
places.push([2, -Math.PI / 3, 1.5, 6]);
places.push([2, 15 * Math.PI / 16, -6.4, -1.5]);
places.push([1, 15 * Math.PI / 16, -4.1, -1]);
places.push([2, 15 * Math.PI / 16, -2.1, -0.5]);
places.push([1, 5 * Math.PI / 4, 0, -1]);
places.push([1, Math.PI + Math.PI / 2.5, 0.5, -3]);
places.push([2, Math.PI + Math.PI / 2.1, 0.75, -5]);
places.push([1, Math.PI + Math.PI / 2.25, 0.75, -7]);
places.push([2, Math.PI / 1.9, 4.75, -1]);
places.push([1, Math.PI / 1.95, 4.5, -3]);
places.push([2, Math.PI / 1.9, 4.75, -5]);
places.push([1, Math.PI / 1.9, 4.75, -7]);
places.push([2, -Math.PI / 3, 5.25, 2]);
places.push([1, -Math.PI / 3, 6, 4]);

// 家のインスタンスを作っていって配列に突っ込んでる
const houses = [];
for (let i = 0; i < places.length; i++) {
    if (places[i][0] === 1) {
        houses[i] = detached_house.createInstance("house" + i);
    } else {
        houses[i] = semi_house.createInstance("house" + i);
    }
    houses[i].rotation.y = places[i][1];
    houses[i].position.x = places[i][2];
    houses[i].position.z = places[i][3];
}

コードの関数化

以前と同様に、これらの家の建てる処理を関数化しましょう。(軽いリファクタリング)

↑ JS タブでコード見れます。オブジェクトもクリックでグリグリ動かせます

function createScene() {
    const scene = new BABYLON.Scene(engine);

    /**** Set camera and light *****/
    const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 15, new BABYLON.Vector3(0, 0, 0));
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0));
    
    buildDwellings(); // 関数呼び出し
    return scene;
  }
  
  /******Build Functions***********/
  const buildDwellings = () => {
      const ground = buildGround();

      const detached_house = buildHouse(1);
      detached_house.rotation.y = -Math.PI / 16;
      detached_house.position.x = -6.8;
      detached_house.position.z = 2.5;

      const semi_house = buildHouse(2);
      semi_house .rotation.y = -Math.PI / 16;
      semi_house.position.x = -4.5;
      semi_house.position.z = 3;

      const places = []; //each entry is an array [house type, rotation, x, z]
      places.push([1, -Math.PI / 16, -6.8, 2.5 ]);
      places.push([2, -Math.PI / 16, -4.5, 3 ]);
      places.push([2, -Math.PI / 16, -1.5, 4 ]);
      places.push([2, -Math.PI / 3, 1.5, 6 ]);
      places.push([2, 15 * Math.PI / 16, -6.4, -1.5 ]);
      places.push([1, 15 * Math.PI / 16, -4.1, -1 ]);
      places.push([2, 15 * Math.PI / 16, -2.1, -0.5 ]);
      places.push([1, 5 * Math.PI / 4, 0, -1 ]);
      places.push([1, Math.PI + Math.PI / 2.5, 0.5, -3 ]);
      places.push([2, Math.PI + Math.PI / 2.1, 0.75, -5 ]);
      places.push([1, Math.PI + Math.PI / 2.25, 0.75, -7 ]);
      places.push([2, Math.PI / 1.9, 4.75, -1 ]);
      places.push([1, Math.PI / 1.95, 4.5, -3 ]);
      places.push([2, Math.PI / 1.9, 4.75, -5 ]);
      places.push([1, Math.PI / 1.9, 4.75, -7 ]);
      places.push([2, -Math.PI / 3, 5.25, 2 ]);
      places.push([1, -Math.PI / 3, 6, 4 ]);

      //Create instances from the first two that were built 
      const houses = [];
      for (let i = 0; i < places.length; i++) {
          if (places[i][0] === 1) {
              houses[i] = detached_house.createInstance("house" + i);
          } else {
              houses[i] = semi_house.createInstance("house" + i);
          }
          houses[i].rotation.y = places[i][1];
          houses[i].position.x = places[i][2];
          houses[i].position.z = places[i][3];
      }
  }

  const buildGround = () => {
      //color
      const groundMat = new BABYLON.StandardMaterial("groundMat");
      groundMat.diffuseColor = new BABYLON.Color3(0, 1, 0);

      const ground = BABYLON.MeshBuilder.CreateGround("ground", {width:15, height:16});
      ground.material = groundMat;
  }

  const buildHouse = (width) => {
      const box = buildBox(width);
      const roof = buildRoof(width);

      return BABYLON.Mesh.MergeMeshes([box, roof], true, false, null, false, true);
  }

  const buildBox = (width) => {
      //texture
      const boxMat = new BABYLON.StandardMaterial("boxMat");
      if (width == 2) {
          boxMat.diffuseTexture = new BABYLON.Texture("https://assets.babylonjs.com/environments/semihouse.png");
      } else {
          boxMat.diffuseTexture = new BABYLON.Texture("https://assets.babylonjs.com/environments/cubehouse.png");   
      }

      // それぞれの面に違った画像をあてるためのオプションパラメータ
      const faceUV = [];
      if (width == 2) {
          faceUV[0] = new BABYLON.Vector4(0.6, 0.0, 1.0, 1.0); //rear face
          faceUV[1] = new BABYLON.Vector4(0.0, 0.0, 0.4, 1.0); //front face
          faceUV[2] = new BABYLON.Vector4(0.4, 0, 0.6, 1.0); //right side
          faceUV[3] = new BABYLON.Vector4(0.4, 0, 0.6, 1.0); //left side
      } else {
          faceUV[0] = new BABYLON.Vector4(0.5, 0.0, 0.75, 1.0); //rear face
          faceUV[1] = new BABYLON.Vector4(0.0, 0.0, 0.25, 1.0); //front face
          faceUV[2] = new BABYLON.Vector4(0.25, 0, 0.5, 1.0); //right side
          faceUV[3] = new BABYLON.Vector4(0.75, 0, 1.0, 1.0); //left side
      }

      const box = BABYLON.MeshBuilder.CreateBox("box", {width: width, faceUV: faceUV, wrap: true});
      box.material = boxMat;
      box.position.y = 0.5;

      return box;
  }

  const buildRoof = (width) => {
      // テクスチャ
      const roofMat = new BABYLON.StandardMaterial("roofMat");
      roofMat.diffuseTexture = new BABYLON.Texture("https://assets.babylonjs.com/environments/roof.jpg");

      const roof = BABYLON.MeshBuilder.CreateCylinder("roof", {diameter: 1.3, height: 1.2, tessellation: 3});
      roof.material = roofMat;
      roof.scaling.x = 0.75;
      roof.scaling.y = width;
      roof.rotation.z = Math.PI / 2;
      roof.position.y = 1.22;

      return roof;
  }
  const scene = createScene();

.glb 形式でエクスポート

この作った村モデルを web ページに埋め込むために、.glb でエクスポートしましょう。(エクスポートは Babylon.js Playground でできます。やり方は 1-01 のページを参照)

🚴‍♀️ 次

次は、この出力した village.glb モデルを web サイトに表示させてみましょう。