Fix editor interaction, loading, and IFC cleanup (#385)
* fix: update site labels after camera swaps * fix: gate scene display until viewer is ready * fix: render scene loader on first paint * fix: keep loader during pending scene graph * feat: add wasd camera panning * feat: add x delete mode shortcut * feat: add floorplan north compass * fix: move floorplan compass to lower corner * fix: progressively rebuild heavy scene geometry * fix: simplify noisy ifc wall output
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
useScene,
|
||||
} from '@pascal-app/core'
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import * as THREE from 'three'
|
||||
import {
|
||||
createSurfaceRoleMaterial,
|
||||
@@ -28,6 +28,9 @@ let revealMaterial: THREE.Material = defaultRevealMaterial
|
||||
let glassMaterial: THREE.Material = defaultGlassMaterial
|
||||
|
||||
const DOOR_RENDER_DEFAULTS = DoorNodeSchema.parse({ id: 'door_render_default' })
|
||||
const MAX_DOOR_REBUILDS_PER_FRAME = 16
|
||||
const DOOR_PROGRESSIVE_DIRTY_THRESHOLD = MAX_DOOR_REBUILDS_PER_FRAME
|
||||
const DOOR_PROGRESSIVE_TIME_BUDGET_MS = 8
|
||||
|
||||
// Legacy/unparsed door nodes can miss schema-defaulted fields (segments,
|
||||
// columnRatios, dividerThickness, …) and crash the geometry build. Re-apply the
|
||||
@@ -47,6 +50,7 @@ export const DoorSystem = () => {
|
||||
const shading = useViewer((state) => state.shading)
|
||||
const textures = useViewer((state) => state.textures)
|
||||
const colorPreset = useViewer((state) => state.colorPreset)
|
||||
const materialRevisionRef = useRef<string | null>(null)
|
||||
// Subscribe so an override-only update (no scene write) still re-runs
|
||||
// the component, letting the gate below pick up the latest dirtyNodes
|
||||
// set from the same render pass that received the override-publishing
|
||||
@@ -59,13 +63,17 @@ export const DoorSystem = () => {
|
||||
glassMaterial = textures ? defaultGlassMaterial : joineryMaterial
|
||||
|
||||
useEffect(() => {
|
||||
const materialRevision = `${shading}:${textures ? 'textures' : 'solid'}:${colorPreset}`
|
||||
if (materialRevisionRef.current === materialRevision) return
|
||||
materialRevisionRef.current = materialRevision
|
||||
|
||||
const nodes = useScene.getState().nodes
|
||||
for (const node of Object.values(nodes)) {
|
||||
if (node?.type === 'door') {
|
||||
useScene.getState().dirtyNodes.add(node.id as AnyNodeId)
|
||||
}
|
||||
}
|
||||
}, [shading, textures, colorPreset])
|
||||
})
|
||||
|
||||
useFrame(() => {
|
||||
if (dirtyNodes.size === 0) return
|
||||
@@ -75,13 +83,35 @@ export const DoorSystem = () => {
|
||||
glassMaterial = textures ? defaultGlassMaterial : frameJoineryMaterial
|
||||
|
||||
const nodes = useScene.getState().nodes
|
||||
const dirtyDoorIds: AnyNodeId[] = []
|
||||
|
||||
dirtyNodes.forEach((id) => {
|
||||
const node = nodes[id]
|
||||
if (!node || node.type !== 'door') return
|
||||
dirtyDoorIds.push(id as AnyNodeId)
|
||||
})
|
||||
|
||||
const useProgressiveDoorRebuilds = dirtyDoorIds.length > DOOR_PROGRESSIVE_DIRTY_THRESHOLD
|
||||
const frameStartedAt = performance.now()
|
||||
let rebuiltDoorsThisFrame = 0
|
||||
|
||||
for (const id of dirtyDoorIds) {
|
||||
if (useProgressiveDoorRebuilds) {
|
||||
if (rebuiltDoorsThisFrame >= MAX_DOOR_REBUILDS_PER_FRAME) {
|
||||
break
|
||||
}
|
||||
if (
|
||||
rebuiltDoorsThisFrame > 0 &&
|
||||
performance.now() - frameStartedAt >= DOOR_PROGRESSIVE_TIME_BUDGET_MS
|
||||
) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const node = nodes[id]
|
||||
if (!node || node.type !== 'door') continue
|
||||
const mesh = sceneRegistry.nodes.get(id) as THREE.Mesh
|
||||
if (!mesh) return // Keep dirty until mesh mounts
|
||||
if (!mesh) continue // Keep dirty until mesh mounts
|
||||
|
||||
// Merge any live override (width / height / position) so the mesh
|
||||
// rebuild reflects the in-flight drag without zustand churn. When
|
||||
@@ -89,6 +119,7 @@ export const DoorSystem = () => {
|
||||
const effectiveNode = getEffectiveNode(node as DoorNode)
|
||||
updateDoorMesh(effectiveNode, mesh)
|
||||
clearDirty(id as AnyNodeId)
|
||||
rebuiltDoorsThisFrame += 1
|
||||
|
||||
// Rebuild the parent wall so its cutout reflects the updated door geometry
|
||||
// Avoid triggering expensive wall CSG rebuilds while the door is being interactively moved/duplicated.
|
||||
@@ -97,7 +128,7 @@ export const DoorSystem = () => {
|
||||
if (!isTransient && effectiveNode.parentId) {
|
||||
useScene.getState().dirtyNodes.add(effectiveNode.parentId as AnyNodeId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 3)
|
||||
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user