fix(viewer): keep ceiling-hosted items visible in dollhouse

Items mounted on a ceiling (lamps, fans, recessed lights) are child nodes of
the ceiling, so hiding the whole occluder node hid them too. Hide only the
ceiling/roof's own meshes (stop descending at nested identity nodes) so hosted
items stay visible when a floor is opened up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-24 16:51:00 -04:00
co-authored by Claude Opus 4.8
parent 5aedc366a6
commit 0e55d86416
@@ -387,6 +387,26 @@ export function GlbScene({
}, [gltf.scene]) }, [gltf.scene])
const zoneById = useMemo(() => new Map(zoneEntries.map((zone) => [zone.id, zone])), [zoneEntries]) const zoneById = useMemo(() => new Map(zoneEntries.map((zone) => [zone.id, zone])), [zoneEntries])
// The dollhouse hides ceilings/roof — but only their OWN geometry. Items hosted
// on a ceiling (lamps, fans, recessed lights) are child identity nodes; hiding
// the whole occluder node would hide them too, so collect just the occluder's
// own meshes (stop descending at any nested identity node) and toggle those.
const occluderOwnMeshes = useMemo(() => {
const meshes: THREE.Mesh[] = []
const walk = (node: THREE.Object3D) => {
for (const child of node.children) {
if ((child.userData as PascalExtras).pascalId) continue // hosted item — keep visible
if ((child as THREE.Mesh).isMesh) meshes.push(child as THREE.Mesh)
walk(child)
}
}
for (const occluder of occluders) {
if ((occluder as THREE.Mesh).isMesh) meshes.push(occluder as THREE.Mesh)
walk(occluder)
}
return meshes
}, [occluders])
// Move the camera to match the drill depth: a saved bookmark (extras.camera) // Move the camera to match the drill depth: a saved bookmark (extras.camera)
// wins; otherwise fit to the target's bounds (the object, the room's polygon // wins; otherwise fit to the target's bounds (the object, the room's polygon
// footprint for empty zone nodes, the level, or the whole building). Mirrors // footprint for empty zone nodes, the level, or the whole building). Mirrors
@@ -726,7 +746,7 @@ export function GlbScene({
// focused level actually has rooms. Focusing a zone-less floor keeps the // focused level actually has rooms. Focusing a zone-less floor keeps the
// building intact (otherwise its roof would just vanish with nothing to show). // building intact (otherwise its roof would just vanish with nothing to show).
const revealing = !walk && selection.levelId != null && levelsWithZones.has(selection.levelId) const revealing = !walk && selection.levelId != null && levelsWithZones.has(selection.levelId)
for (const occluder of occluders) occluder.visible = !revealing for (const mesh of occluderOwnMeshes) mesh.visible = !revealing
for (const { id, levelId, meshes, uniforms } of zoneFills.current) { for (const { id, levelId, meshes, uniforms } of zoneFills.current) {
const show = const show =
@@ -938,9 +958,9 @@ export function GlbScene({
outliner.hoveredObjects.length = 0 outliner.hoveredObjects.length = 0
document.body.style.cursor = 'auto' document.body.style.cursor = 'auto'
// Restore ceilings/roof — the GLB scene is cached by drei and may be reused. // Restore ceilings/roof — the GLB scene is cached by drei and may be reused.
for (const occluder of occluders) occluder.visible = true for (const mesh of occluderOwnMeshes) mesh.visible = true
}, },
[occluders], [occluderOwnMeshes],
) )
return ( return (