Three.jsを用いた設定駆動型3Dデータセンター構築ガイド

システム設計思想と低コードアプローチ

IoT可視化およびデジタルツイン分野において、データセンターの三维表現は標準的な要件となっています。従来の開発フローでは、手動によるメッシュ作成と個別スクリプトの実装にリソースが割かれ、納期と保守コストがボトルネック化していました。本架構はThree.js(WebGL)を基盤とし、外部JSON設定ファイルのみで空間構成と附属機器を自動構築する「設定駆動型」パターンを実装します。これにより、反復作業を排除し、コア分析ロジックの再利用性を最大化します。

パラメータ抽象化とスキーマ設計

自動生成エンジンの精度は、入出力データの正規化に依存します。建築構造、ラック配置、環境モニタリングデバイスの三段階に焦点を当て、共通項を抽出してスキーマを定義します。初期フェーズでは装飾的マテリアルや複雑な切削形状を省き、トポロジーと位置情報に集中させます。

{
  "entityType": "structural_wall",
  "identifier": "barrier_north_01",
  "dimensions": { "lengthX": 8000, "thicknessZ": 100, "heightY": 1000 },
  "anchorOrigin": { "x": -4000, "y": 0, "z": 2700 },
  "openings": [
    {
      "id": "main_access_door",
      "style": 2,
      "offsetX": 400,
      "width": 600,
      "height": 700,
      "materialDepth": 40
    },
    {
      "style": 3,
      "offsetX": 2600,
      "width": 2000,
      "height": 700,
      "sillHeight": 100,
      "depth": 10
    }
  ]
}
{
  "uid": "rack_rowA_05",
  "category": "server_rack",
  "displayName": "rack_05A",
  "assetTag": "DB_ASSET_05",
  "worldPosition": { "x": -2550, "y": 450, "z": 1315 }
}

エンジン層:ジオメトリファクトリパターン

Three.jsにおけるレンダリングオブジェクト生成は、関心事ごとに分離する必要があります。建築部品や装置ごとの専用ファクトリ関数を定義し、渡された設定値をマップしてからScene Graphに登録します。この手法により、マテリアル更新やスケール変更時の修正範囲を局所化できます。

/**
 * ジオメトリ生成パイプライン
 */
const GeometryEngine = {
  createBasePlatform(label, sizeVec, pivotPos, rotationEuler) {
    const geo = new THREE.BoxGeometry(sizeVec.x, sizeVec.y, sizeVec.z);
    const mat = new THREE.MeshStandardMaterial({ color: 0xa0a0a0, roughness: 0.6 });
    const mesh = new THREE.Mesh(geo, mat);
    mesh.name = label;
    mesh.position.copy(pivotPos);
    mesh.rotation.copy(rotationEuler);
    return mesh;
  },

  buildEnclosureWall(label, wallDims, baseOffset, apertureList, rotMatrix) {
    const shape = new THREE.Shape();
    const { lengthX, heightY, thicknessZ } = wallDims;
    
    shape.moveTo(0, 0);
    shape.lineTo(lengthX, 0);
    shape.lineTo(lengthX, heightY);
    shape.lineTo(0, heightY);
    shape.lineTo(0, 0);

    if (Array.isArray(apertureList)) {
      apertureList.forEach(opp => {
        const path = new THREE.Path();
        path.moveTo(opp.offsetX, opp.sillHeight || 0);
        path.lineTo(opp.offsetX + opp.width, opp.sillHeight || 0);
        path.lineTo(opp.offsetX + opp.width, opp.sillHeight + opp.height);
        path.lineTo(opp.offsetX, opp.sillHeight + opp.height);
        shape.holes.push(path);
      });
    }

    const extrudeParams = { depth: thicknessZ, bevelEnabled: false };
    const profileGeo = new THREE.ExtrudeGeometry(shape, extrudeParams);
    const surfaceMat = new THREE.MeshStandardMaterial({ 
      color: 0xe8e8e8, side: THREE.DoubleSide 
    });
    
    const wallMesh = new THREE.Mesh(profileGeo, surfaceMat);
    wallMesh.name = label;
    wallMesh.position.copy(baseOffset);
    wallMesh.rotation.copy(rotMatrix);
    return wallMesh;
  }
};

分析機能のステート分離

温度分布可視化や容量シミュレーションといった分析ロジックは、メインレンダリングループと干渉しないよう独立クラスとして封装します。UIフラグの状態遷移とシーン内の一時オブジェクトプールを連携させ、トグル操作時のメモリリークを防ぎます。

class VolumetricAnalyzer {
  constructor(sceneController) {
    this.ctrl = sceneController;
    this.isActive = false;
    this.layerName = "analysis_voxel_layer";
  }

  toggleAnalysisMode(cb) {
    if (!this.isActive) {
      this.prepareViewport();
      this.loadDataAndConstructVoxels(cb);
      this.isActive = true;
    } else {
      this.purgeLayer();
      this.isActive = false;
    }
  }

  prepareViewport() {
    this.ctrl.viewManager.dimInteractiveLayers();
  }

  purgeLayer() {
    this.ctrl.objectManager.removeByName(this.layerName, true);
    this.ctrl.viewManager.restoreInteractiveLayers();
  }

  async loadDataAndConstructVoxels(completeCb) {
    try {
      const rawMetrics = await this.ctrl.dataBridge.queryThermalDistribution();
      const projectedData = this.applyCoordinateScaling(rawMetrics);
      this.renderSliceStack(projectedData);
      completeCb?.();
    } catch (err) {
      console.error("Metric retrieval error:", err);
    }
  }

  applyCoordinateScaling(dataset) {
    const scaleFactor = this.ctrl.env.config.unitScale || 1.0;
    return dataset.map(node => ({ ...node, positionY: node.positionY * scaleFactor }));
  }

  renderSliceStack(dataPoints) {
    const totalHeights = this.ctrl.env.totalDepth;
    for (let level = 0; level < 10; level++) {
      const yPos = (level * (totalHeights / 10)) + 50;
      const sliceDef = {
        type: "heat_grid",
        groupName: this.layerName,
        initialPos: { x: 0, y: yPos, z: 0 },
        pointData: dataPoints,
        alphaValue: 0.75 - (level * 0.06)
      };
      this.ctrl.renderer.injectCustomMesh(sliceDef);
    }
  }
}

イベントルーティングと画面制御の脱結合

DOMイベントとレンダリングロジックを直接紐づけるとスパゲッティコード化します。中央バスまたはディスパッチャー経由で操作を検知し、対応するビジネスモジュールへ委譲する構造を採ります。これにより、可視化モードの追加や既存機能の無効化が安全に行えます。

const InteractionRouter = {
  currentMode: 'default',
  
  dispatch(actionId) {
    const handlers = {
      toggle_alarm_effect() {
        const state = document.querySelector('#alarm_btn').dataset.active === 'true';
        if (!state) {
          EventBus.publish('ui:flash_on');
          AlertSubsystem.enablePulseAnimation();
          document.querySelector('#alarm_btn').dataset.active = 'true';
        } else {
          EventBus.publish('ui:flash_off');
          AlertSubsystem.disablePulseAnimation();
          document.querySelector('#alarm_btn').dataset.active = 'false';
        }
      },
      
      invoke_capacity_map() {
        InteractionRouter.currentMode = 'utilization';
        RenderModules.displayUsageOverlay();
      },
      
      invoke_temperature_scan() {
        InteractionRouter.currentMode = 'thermal';
        const analyzer = new VolumetricAnalyzer(GlobalSceneRoot);
        analyzer.toggleAnalysisMode(() => EventBus.publish('status:heatmap_ready'));
      },
      
      calculate_structural_load() {
        InteractionRouter.currentMode = 'engineering';
        PhysicsModules.evaluateWeightDistribution();
      }
    };
    
    if (handlers[actionId]) handlers[actionId]();
  }
};

// 登録例
document.getElementById('btn-load-calc').onclick = () => InteractionRouter.dispatch('calculate_structural_load');

データゲートウェイとキャッシュ戦略

大規模なシミュレーションでは、断続的なネットワーク待機がUXを損ないます。REST APIからのフェッチ結果とWebSocketストリームを統合管理する統一アダプターを実装し、重複通信を回避します。メモリ上のキーバリューストアにて最新値を保持し、UI再描画時はローカルメモリを参照することでフレームレートを安定させます。

class SimulationDataGateway {
  constructor(opts) {
    this.baseUri = opts.endpoint || '/api/v1/idc-sim';
    this.zoneId = opts.identifier || 'zone_alpha';
    this.memoryStore = {
      assetRegistry: null,
      sensorTelemetry: new Map(),
      anomalyLog: [],
      graphTopo: null
    };
    
    this.routeTable = {
      featureFlags: '/conf/system-enables.json',
      cabinetInfo: `/assets/racks?zone=${this.zoneId}`,
      tempMetrics: '/analyses/thermal-map.json',
      loadRates: '/evaluations/floor-weight.json',
      liveSocket: 'ws://gateway.internal:8800/sensor-stream'
    };
  }

  async retrieve(key) {
    const targetUrl = `${this.baseUri}${this.routeTable[key]}`;
    try {
      if (this.memoryStore[key]) return this.memoryStore[key];
      
      const res = await fetch(`${targetUrl}&nonce=${Date.now()}`);
      if (!res.ok) throw new ReferenceError(`Network fail ${res.status}`);
      
      const payload = await res.json();
      this.memoryStore[key] = payload;
      return payload;
    } catch (err) {
      console.warn(`Cache miss for ${key}:`, err.message);
      return this.memoryStore[key] ?? {};
    }
  }

  connectEventStream(targetCb) {
    const conn = new WebSocket(this.routeTable.liveSocket);
    conn.onopen = () => console.log('Stream connection established');
    conn.onmessage = evt => {
      const msg = JSON.parse(evt.data);
      if (msg.category === 'THRESHOLD_BREACH') {
        this.memoryStore.anomalyLog.unshift(msg);
        targetCb(msg);
      }
    };
    return conn;
  }

  getStatusPalette() {
    return {
      severe: 0xff0000,
      caution: 0xff8800,
      advisory: 0xffdd00,
      baseline: 0x22aadd
    };
  }
}

実装における主要検討点

  1. パラメータ検証と単位統一: 外部ソースから流入する寸法データは常に同一座標軸(XYZ)と長さ単位(mm/cm)に変換するパイプラインを経由させます。不正値の検出時にフォールバックジオメトリを返す機構を組み込みます。
  2. GPU アクセラレーション: 温度雲図やUラック占有率を表示する際、個々のメッシュ生成はDraw Call爆発を招きます。代替案として、複数のボクセルをまとめて描画するInstancedGeometryの採用か、シェーダー内でカラーマッピングを行うアプローチが有効です。
  3. プラグイン型ビジネス拡張: 新たな計測指標や異常判定ルールを追加する際、コアエンジンへ手を加えずとも、規定のインターフェースを満たすサブクラスをダイナミックに登録できるコンテナ設計を採用します。これにより、長期運用時の改修リスクを大幅に低減します。

タグ: Three.js WebGL Digital Twin JSON Schema Frontend Architecture

7月30日 18:35 投稿