feat: add model hierarchy panel

This commit is contained in:
Tom Boullay
2026-05-13 17:41:54 +02:00
parent 606df93b69
commit 30ff9826dc
2 changed files with 185 additions and 29 deletions
+46 -2
View File
@@ -18,6 +18,16 @@ export interface ModelStats {
triangles: number
}
export interface ModelHierarchyNode {
children: ModelHierarchyNode[]
id: string
name: string
position: [number, number, number]
rotation: [number, number, number]
type: string
visible: boolean
}
interface OpacityMapEntry {
target: string
url: string
@@ -207,6 +217,30 @@ function getModelStats(scene: Object3D, assetUrls: Record<string, string>): Mode
}
}
function roundTransformValue(value: number) {
return Number(value.toFixed(4))
}
function getObjectHierarchy(object: Object3D): ModelHierarchyNode {
return {
children: object.children.map(getObjectHierarchy),
id: object.uuid,
name: object.name || object.type,
position: [
roundTransformValue(object.position.x),
roundTransformValue(object.position.y),
roundTransformValue(object.position.z),
],
rotation: [
roundTransformValue(object.rotation.x),
roundTransformValue(object.rotation.y),
roundTransformValue(object.rotation.z),
],
type: object.type,
visible: object.visible,
}
}
function pickOpacityMap(
mesh: Mesh,
material: Material,
@@ -234,10 +268,12 @@ function Model({
url,
assetUrls,
onStatsReady,
onHierarchyReady,
}: {
url: string
assetUrls: Record<string, string>
onStatsReady: (stats: ModelStats) => void
onHierarchyReady: (hierarchy: ModelHierarchyNode) => void
}) {
const { scene } = useLoader(GLTFLoader, url, (loader) => {
loader.manager.setURLModifier((requestedUrl) => resolveAssetUrl(requestedUrl, assetUrls))
@@ -247,7 +283,8 @@ function Model({
useEffect(() => {
onStatsReady(getModelStats(scene, assetUrls))
}, [assetUrls, onStatsReady, scene])
onHierarchyReady(getObjectHierarchy(scene))
}, [assetUrls, onHierarchyReady, onStatsReady, scene])
useEffect(() => {
if (opacityMapEntries.length === 0) return
@@ -270,16 +307,23 @@ export default function SceneViewer({
url,
assetUrls,
onStatsReady,
onHierarchyReady,
}: {
url: string
assetUrls: Record<string, string>
onStatsReady: (stats: ModelStats) => void
onHierarchyReady: (hierarchy: ModelHierarchyNode) => void
}) {
return (
<Canvas dpr={[1, 2]} camera={{ fov: 50 }}>
<Suspense fallback={null}>
<Stage environment="city" intensity={0.6} adjustCamera={1.2}>
<Model url={url} assetUrls={assetUrls} onStatsReady={onStatsReady} />
<Model
url={url}
assetUrls={assetUrls}
onStatsReady={onStatsReady}
onHierarchyReady={onHierarchyReady}
/>
</Stage>
</Suspense>
<OrbitControls makeDefault autoRotate autoRotateSpeed={0.5} />