[Astro] #114 Point Cloud Viewer #9 — Octree全点LOD・Worker化・カメラ追従更新
概要
前回[#113]でSOR Worker化とDelaunayメッシュを追加したが、Octree LODは200万点にサンプリングした後のデータでしか動いていなかった。今回は全点でOctreeを構築し、カメラ距離に応じて動的に解像度を切り替えるようにした。
[Astro] #113 Point Cloud Viewer v8 — SOR高速化・Worker化・Delaunayメッシュ生成
SOR高速化+Worker化、Voxel Worker化、Delaunayメッシュ生成。
lain-lab.com今回の追加機能
| 機能 | 概要 |
|---|---|
| Octree全点対応 | サンプリング前の全点(1230万点)でOctree構築 |
| 動的LOD描画 | カメラ距離に応じてノード別に解像度切替、予算3M点 |
| buildOctree Worker化 | Octree構築をWorkerスレッドで実行(UIブロッキング防止) |
| カメラ移動検知LOD更新 | 固定間隔→移動検知ベース、移動中6フレームスロットル |
| LOD表示点数リアルタイム表示 | 現在の描画点数をシアンで動的表示 |
| カラーモード・PointSize即時反映 | LOD ON中の設定変更で即座に再描画 |
| 20M点以上の確認ダイアログ | メモリ爆死防止、推定メモリ量を表示 |
| 非LOD表示上限引き上げ | 2M→5M点 |
スクリーンショット
LOD ON — 引きで30万点
LOD ON — 近づいて69万点
動画(GIF)
キーフレーム「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_DEPTH | 5 | 8 | 空間解像度向上(近/遠の境界を細かく) |
| MIN_POINTS | 256 | 512 | リーフサイズ適正化 |
| LOD_SAMPLES | 64 | 128 | 遠景の点密度向上 |
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.tsx | fullDataRef追加、全点Octree構築(Worker)、LODカメラ検知更新、カラーモード/PointSize即時反映、20M点確認ダイアログ |
| PointCloudApp.tsx | lodPointCount state追加、onLodCountChangeコールバック |
| ControlPanel.tsx | LOD表示点数のシアン/黄色切替表示 |
まとめ
- 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)