Closed15

Babylon.js と IFCで遊ぶ

kiyukakiyuka

自分用作業メモです。

IFCをBabylon.jsで表示させて遊ぼうという思いつき。
web-ifc-three使えって?自分でもそう思います。

kiyukakiyuka

なんか環境をちゃんと作って作業してみたいので vite 使う。「ゔぃーと」って読むらしいですね。
React+TypeScriptでコードを書こうと思います。

kiyukakiyuka

IFCのパーサーにifcopenshellとweb-ifcのどちらを使おうか迷ったけど、今回はブラウザ上で処理を完結させたいのでweb-ifcを使います。
...ChatGPTに全部おまかせしようと思ったらweb-ifcがマイナーなせいか、おまかせできないようになので気合で作ります。

kiyukakiyuka

とりあえず web-ifc はこれでimport できた。ググってもサンプル少なすぎてこれを見つけるだけで時間がかかるという。

import * as WebIFC from 'web-ifc';

さて、次はIFCファイルからモデルを読み込むわけですが...

kiyukakiyuka

これで読めた...かな?
IFCファイルを選択したらファイルを読み込む。
次は3Dモデルを読み込んで表示させます。

ts部分
  function loadIFCFile(file: File){
    const reader = new FileReader();
    reader.onload = async (event) => {
      const buffer = new Uint8Array(event.target.result)
      await ifcapi.Init();
      const modelID = ifcapi.OpenModel(buffer)
    };
    reader.readAsArrayBuffer(file);
  }

  function handleFileChange(event: React.ChangeEvent<HTMLInputElement>){
    const file = event.target.files[0];
    if (file) {
      loadIFCFile(file);
    }
  }
html部分
<input type="file" onChange={handleFileChange} className="fileInput"/>
kiyukakiyuka

web-ifcで全メッシュ取得はおそらくこれでいいんだけど、取得したメッシュをBabylon.jsで表示させるのどうやるんだ...?

ifcapi.StreamAllMeshes(modelID, (mesh) => {
  const placedGeometries = mesh.geometries;
  const size = placedGeometries.size();
  for (let i = 0; i < size; i++) {
    const placedGeometry = placedGeometries.get(i)
    const geometry = ifcapi.GetGeometry(modelID, placedGeometry.geometryExpressID);
    // 6つで一組のデータ: x, y, z, normalx, normaly, normalz
    const verts = ifcapi.GetVertexArray(geometry.GetVertexData(), geometry.GetVertexDataSize());
    // 3つで一組のデータ:頂点index 1, 2, 3
    const indices = ifcapi.GetIndexArray(geometry.GetIndexData(), geometry.GetIndexDataSize());

  }
});
kiyukakiyuka

Babylon.jsでメッシュ作成。ChatGPT先生がつよつよでした。
ただ、web-ifcから取得したメッシュは位置情報と形状情報が別なので、次は位置情報を適応して正しい位置に移動させれば見た目は整うはず。

  function createMeshFromData(scene: BABYLON.Scene, vertexData: { positions: number[], normals: number[], indices: number[] }) {
    const mesh = new BABYLON.Mesh("mesh", scene);
    const vertexDataForBabylon = new BABYLON.VertexData();

    vertexDataForBabylon.positions = vertexData.positions;
    vertexDataForBabylon.normals = vertexData.normals;
    vertexDataForBabylon.indices = vertexData.indices;

    vertexDataForBabylon.applyToMesh(mesh);

    return mesh;
  }

公式ドキュメントだとこれかな
https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/custom/custom

kiyukakiyuka

Babylon.jsで変形行列を掛けるのはこれだそうで。さすがChatGPT先生です。

const matrix = Matrix.FromArray(matrixData);
mesh.setPivotMatrix(matrix, false);

ただ、見えてる面がおかしいので、次はそこを修正します。

kiyukakiyuka

面の表と裏が逆になっているのと、右手系と左手系の違いでZ方向が逆転していたっぽい。
作成したメッシュのZ軸を反転して、面も反転させると正しくなった。

mesh.scaling.z *= -1;
mesh.flipFaces(true);

次は色を付けます。

kiyukakiyuka

色の指定はこれで。

const {x, y, z, w} = placedGeometry.color
const color = new BABYLON.Color3(x, y, z);
const material = new BABYLON.StandardMaterial("material", scene);
material.diffuseColor = color;
material.alpha = w
mesh.material = material;

kiyukakiyuka

カラフルにしようと思ったら...
https://twitter.com/kiyuka_study/status/1674430404479324163?s=20

kiyukakiyuka

ちなみにコード。頂点カラーをアニメーションさせてる。とてつもなく重いです。

  function vertexColorAnimation(mesh: BABYLON.Mesh, positions: number[], flatTransformation: number[]){
    if(scene === null) return
    const px = flatTransformation[12]
    const pz = flatTransformation[13]
    const py = flatTransformation[14]

    let time = 0
    scene.registerBeforeRender(function () {
      // Update vertex colors over time
      const colors = []
      time += 0.05; // Increment the time
      for(let i = 0; i < positions.length; i += 3){
        const pix = positions[i + 0]
        const piy = positions[i + 1]
        const piz = positions[i + 2]
        const r = (Math.sin(time + px + pix) + 1) / 2
        const g = (Math.sin(time + py + piy + Math.PI/3) + 1) / 2
        const b = (Math.sin(time + pz + piz + Math.PI*2/3) + 1) / 2
        colors.push(r, g, b, 0)
      }
      mesh.setVerticesData(BABYLON.VertexBuffer.ColorKind, colors);
    });
  }
kiyukakiyuka

建築アニメーションっぽいこと
https://youtu.be/fuXY4eaQZ8c

kiyukakiyuka

おおよそのコード


  function createMesh(modelID : number){
    let meshData = []
    // アニメーションしたいのでメッシュを同期取得
    const meshes = ifcapi.LoadAllGeometry(modelID)
    for(let i = 0; i < meshes.size(); i++){
      const mesh = meshes.get(i)
      meshData = meshData.concat(getIfcMesh(modelID, mesh))
    }

    // 10秒くらいのアニメーションに
    const msec = 10000 / meshData.length

    // 低い位置にあるメッシュから表示されるようにソート
    meshData.sort((a, b) => a.ymax - b.ymax)
    meshData.forEach((v, i)=> {
      // アニメーションするように遅延させてメッシュ生成
      setTimeout(() => {
        ifc2babylonMesh(scene, v.vertexData, v.flatTransformation, v.color)
      }, msec * i);
    })
  }

  function getIfcMesh(modelID, mesh){
    if(scene === null) return

    const meshData = []
    const placedGeometries = mesh.geometries;
    const size = placedGeometries.size();
    for (let i = 0; i < size; i++) {
      const placedGeometry = placedGeometries.get(i)
      const geometry = ifcapi.GetGeometry(modelID, placedGeometry.geometryExpressID);
      // 6つで一組のデータ: x, y, z, normalx, normaly, normalz
      const verts = ifcapi.GetVertexArray(geometry.GetVertexData(), geometry.GetVertexDataSize());
      // 3つで一組のデータ:頂点index 1, 2, 3
      const indices = ifcapi.GetIndexArray(geometry.GetIndexData(), geometry.GetIndexDataSize());

      let ymax = 100000
      const positions = [];
      const normals = [];
      for(let i = 0; i < verts.length; i += 6) {
        positions.push(verts[i], verts[i + 1], verts[i + 2]);
        normals.push(verts[i + 3], verts[i + 4], verts[i + 5]);
        // アニメーション用に高さ最小値計算(変数名おかしいけど気にしない)
        ymax = Math.min(ymax, verts[i + 2])
      }

      const vertexData = {
        positions: positions,
        normals: normals,
        indices: Array.from(indices),
      }

      meshData.push({
        vertexData: vertexData,
        flatTransformation: placedGeometry.flatTransformation,
        color: placedGeometry.color,
        ymax: placedGeometry.flatTransformation[13] + ymax,
      })
    }

    return meshData
  }

kiyukakiyuka

IFCファイルをBabylon.jsで表示できたし、アニメーションっぽいこともして遊べたのでこれで閉じます。
どこかでちゃんとまとめるかも?

このスクラップは2023/07/11にクローズされました