[Astro] #114 Point Cloud Viewer #9 — Octree全点LOD・Worker化・カメラ追従更新

[Astro] #114 Point Cloud Viewer #9 — Octree全点LOD・Worker化・カメラ追従更新

概要

前回[#113]でSOR Worker化とDelaunayメッシュを追加したが、Octree LODは200万点にサンプリングした後のデータでしか動いていなかった。今回は全点でOctreeを構築し、カメラ距離に応じて動的に解像度を切り替えるようにした。

今回の追加機能

機能概要
Octree全点対応サンプリング前の全点(1230万点)でOctree構築
動的LOD描画カメラ距離に応じてノード別に解像度切替、予算3M点
buildOctree Worker化Octree構築をWorkerスレッドで実行(UIブロッキング防止)
カメラ移動検知LOD更新固定間隔→移動検知ベース、移動中6フレームスロットル
LOD表示点数リアルタイム表示現在の描画点数をシアンで動的表示
カラーモード・PointSize即時反映LOD ON中の設定変更で即座に再描画
20M点以上の確認ダイアログメモリ爆死防止、推定メモリ量を表示
非LOD表示上限引き上げ2M→5M点

スクリーンショット

LOD ON — 引きで30万点

Point Cloud Viewer — LOD ON 引き

LOD ON — 近づいて69万点

Point Cloud Viewer — LOD ON 近接

動画(GIF)

キーフレーム「Add KF」を登録してアニメーション再生。

Point Cloud Viewer — キーフレーム「Add KF」を登録してアニメーション再生

従来の問題

Parse → allPositions[] (number[]) → 2Mサンプリング → Float32Array → BufferGeometry

                                                                    buildOctree (2M点のみ)

                                                                    selectLOD (2M内でフィルタ)
  • パース後にFisher-Yatesで200万点にサンプリング → その2M点でOctree構築
  • 元の数千万点はサンプリング時点で捨てられていた
  • Octree LODは2M点の中でのカメラ距離ベース間引きに過ぎなかった

新しいデータフロー

Parse → fullData (Float32Array直接) → メモリに全点保持

                                    buildOctree (全点, Worker)

  useFrame: selectLOD → fullDataから → GPU geometry (予算3M点固定バッファ)
  • number[] を経由した後すぐ Float32Array に変換しGC対象にする
  • fullDataRef で全点を保持(positions, colors, intensity, classification)
  • 表示用geometryはサンプリング(非LODモード用、5M点上限)
  • LOD ONでは fullDataRef から直接GPUバッファにコピー

fullDataRef — 全点メモリ保持

パース後の全点をFloat32Arrayで保持する。number[] は変換後すぐに .length = 0 で参照を切りGCに任せる。

fullDataRef.current = {
  positions: fullPos,    // Float32Array (count × 3)
  colors: fullCol,       // Float32Array (count × 3)
  intensity: fullInt,    // Float32Array (count)
  classification: fullCls, // Uint8Array (count)
  count: totalCount,
};

1230万点の場合、全データで約350MB。ブラウザの4GBメモリ制限内に収まる。チャンク段階ロードは不要と判断した(ローカルファイルドロップなので読み込み時点で全データがメモリに載るため)。

Octree定数の調整

全点対応に合わせて定数を変更:

定数旧(v8)新(v9)理由
MAX_DEPTH58空間解像度向上(近/遠の境界を細かく)
MIN_POINTS256512リーフサイズ適正化
LOD_SAMPLES64128遠景の点密度向上

buildOctree Worker化

1230万点のOctree構築は約1秒だが、さらに大きなファイルでUIがフリーズするのを防ぐためWeb Workerで実行する。

// octreeWorker.ts — buildOctreeをWorkerスレッドで実行
self.onmessage = (e: MessageEvent) => {
  const { positions, count } = e.data;
  self.postMessage({ type: 'progress', message: `Building Octree: ${count.toLocaleString()} points...` });
  const t0 = performance.now();
  const tree = buildOctree(positions, count);
  self.postMessage({ type: 'done', tree, elapsed: performance.now() - t0 });
};

メインスレッド側ではpositionsのコピーを Transferable で転送し、完了時にOctreeNodeをrefに格納する。元の fullDataRef のデータは転送せず保持し続ける。

LOD更新 — カメラ移動検知ベース

旧実装は固定15フレーム間隔で selectLOD を実行していたが、カメラが止まっている時に無駄な処理が走り、動いている時はレスポンスが遅かった。

// カメラ移動検知
const camMoved =
  prevCamPos.current.distanceToSquared(camera.position) > 1e-8 ||
  Math.abs(prevCamQuat.current.dot(camera.quaternion)) < 0.99999;

if (camMoved) {
  lodMoveFrames.current++;
  // 移動中は6フレームに1回だけ更新(初回は即更新)
  if (lodMoveFrames.current > 1 && lodMoveFrames.current % 6 !== 0) return;
} else if (!settingsChanged) {
  // 停止後1フレームだけ追加更新してスキップ
  if (lodCooldown.current > 1) return;
}
  • カメラ移動開始:即座に1回LOD更新
  • 移動中:6フレームに1回(約10fps相当で3M点コピーの負荷を1/6に)
  • 停止:1フレーム追加更新してファイナライズ、以降完全スキップ

カラーモード・PointSize即時反映

LOD ONの状態で設定変更しても、カメラを動かすまで反映されないバグがあった。設定変更を検知して prevLodHash をリセットし、強制再描画する。

const settingsChanged =
  settings.colorMode !== prevColorMode.current ||
  settings.pointSize !== prevPointSize.current;
if (settingsChanged) {
  prevColorMode.current = settings.colorMode;
  prevPointSize.current = settings.pointSize;
  prevLodHash.current = 0; // ハッシュリセットで再描画強制
}

LOD描画時にカラーモード(Original/Height/Intensity/Classification)をオンザフライでサブセットに適用するため、全点の色を再計算する必要はない。

20M点以上の確認ダイアログ

全点保持はメモリを使うため、20M点以上のファイルでは確認ダイアログを表示する。推定メモリ使用量も提示し、ユーザーに判断を委ねる。

if (totalCount > 20_000_000) {
  keepFullData = confirm(
    `${totalCount.toLocaleString()} 点検出(推定メモリ: ${estimatedMB} MB)\n` +
    `全点を保持してOctree LODを有効にしますか?\n` +
    `[OK] → 全点保持 + Octree LOD\n` +
    `[キャンセル] → 5M点サンプリングのみ`
  );
}

キャンセル時は fullDataRef.current = null にして全点データを破棄。Octree LODチェックボックスは空振りになるが、5Mサンプリング表示で利用可能。

LOD表示点数リアルタイム表示

LOD ON時に現在何点描画しているかをUIに反映する。onLodCountChange コールバックでScene→App→ControlPanelに通知。

  • LOD OFF: 2,000,000 / 12,293,816 (sampled) — 黄色
  • LOD ON: 309,996 / 12,293,816 (LOD) — シアン、カメラ移動で動的変動

変更ファイル

ファイル変更内容
octreeWorker.ts新規 — buildOctreeをWorkerスレッドで実行
octree.ts定数調整(MAX_DEPTH=8, MIN_POINTS=512, LOD_SAMPLES=128)
PointCloudScene.tsxfullDataRef追加、全点Octree構築(Worker)、LODカメラ検知更新、カラーモード/PointSize即時反映、20M点確認ダイアログ
PointCloudApp.tsxlodPointCount state追加、onLodCountChangeコールバック
ControlPanel.tsxLOD表示点数のシアン/黄色切替表示

まとめ

  • v1: PLY読み込み、ヒートマップ、スライス、計測、SOR
  • v2: LAS/PCD読み込み、200万点サンプリング、正規化
  • v3: LAZ読み込み、XYZ/CSV入出力、ローディング
  • v4: Intensity/Classification表示、LAS/PCD/スクショエクスポート
  • v5: ボックス選択+削除、断面ポリライン+DXF、Octree LOD、座標系変換
  • v6: 計測ラベル3D表示、元座標復元、Voxel Downsampling
  • v7: 任意直線断面+DXF、SOR進捗表示
  • v8: SOR高速化+Worker化、Voxel Worker化、Delaunayメッシュ生成
  • v9: Octree全点LOD(1230万点対応)、Worker化、カメラ追従更新、非LOD上限5M

今後の課題

  • Potree形式読み込み(Octreeタイルストリーミング)
  • Poisson Reconstruction(3Dサーフェス復元)
  • GPU Octree(WebGPU compute shader)