From a3378a666d0b56f9da2ee70da92519a38c41effa Mon Sep 17 00:00:00 2001 From: sudhir Date: Fri, 15 May 2026 12:13:00 +0530 Subject: [PATCH] Preserve elevator stop order and handle viewport resize --- packages/core/src/store/use-interactive.ts | 2 + .../systems/elevator/elevator-runtime.test.ts | 3 + .../src/systems/elevator/elevator-runtime.ts | 12 +- packages/nodes/src/elevator/panel.tsx | 122 +++++++++--------- .../src/components/viewer/post-processing.tsx | 32 ++++- 5 files changed, 107 insertions(+), 64 deletions(-) diff --git a/packages/core/src/store/use-interactive.ts b/packages/core/src/store/use-interactive.ts index 22997afc..426e42d7 100644 --- a/packages/core/src/store/use-interactive.ts +++ b/packages/core/src/store/use-interactive.ts @@ -49,6 +49,7 @@ export type ElevatorInteractiveState = { phase: ElevatorPhase phaseStartedAt: number | null queue: AnyNodeId[] + requestedStops: AnyNodeId[] } type InteractiveStore = { @@ -241,6 +242,7 @@ export const useInteractive = create((set, get) => ({ phase: 'idle', phaseStartedAt: null, queue: [], + requestedStops: [], }, }, })) diff --git a/packages/core/src/systems/elevator/elevator-runtime.test.ts b/packages/core/src/systems/elevator/elevator-runtime.test.ts index 4ebedb2b..43e714df 100644 --- a/packages/core/src/systems/elevator/elevator-runtime.test.ts +++ b/packages/core/src/systems/elevator/elevator-runtime.test.ts @@ -30,7 +30,9 @@ describe('elevator runtime helpers', () => { const duplicated = queueElevatorRequest(queued, upperLevelId) expect(queued.queue).toEqual([upperLevelId]) + expect(queued.requestedStops).toEqual([upperLevelId]) expect(duplicated.queue).toEqual([upperLevelId]) + expect(duplicated.requestedStops).toEqual([upperLevelId]) }) test('opens doors only when the elevator is not moving', () => { @@ -78,5 +80,6 @@ describe('elevator runtime helpers', () => { expect(arrived.phase).toBe('opening') expect(open.phase).toBe('open') expect(open.queue).toEqual([]) + expect(open.requestedStops).toEqual([upperLevelId]) }) }) diff --git a/packages/core/src/systems/elevator/elevator-runtime.ts b/packages/core/src/systems/elevator/elevator-runtime.ts index bba14e0d..7278dea0 100644 --- a/packages/core/src/systems/elevator/elevator-runtime.ts +++ b/packages/core/src/systems/elevator/elevator-runtime.ts @@ -23,6 +23,7 @@ export function createElevatorInteractiveState( phase: 'idle', phaseStartedAt: null, queue: [], + requestedStops: [], } } @@ -64,6 +65,9 @@ export function queueElevatorRequest( return { ...state, queue: [...state.queue, levelId], + requestedStops: state.requestedStops.includes(levelId) + ? state.requestedStops + : [...state.requestedStops, levelId], } } @@ -124,6 +128,7 @@ export function stepElevatorRuntimeState({ phase: 'idle', phaseStartedAt: null, queue: [], + requestedStops: [], doorOpen: 0, } } @@ -147,7 +152,11 @@ export function stepElevatorRuntimeState({ doorOpen: Math.max(0, state.doorOpen - doorStep), } } - return state + if (state.requestedStops.length === 0) return state + return { + ...state, + requestedStops: [], + } } return { @@ -180,6 +189,7 @@ export function stepElevatorRuntimeState({ targetLevelId: null, phase: 'idle', queue: [], + requestedStops: [], } } diff --git a/packages/nodes/src/elevator/panel.tsx b/packages/nodes/src/elevator/panel.tsx index 0e5f6b8f..50fefe00 100644 --- a/packages/nodes/src/elevator/panel.tsx +++ b/packages/nodes/src/elevator/panel.tsx @@ -170,6 +170,7 @@ export default function ElevatorPanel() { if (!state) return null return { currentLevelId: state.currentLevelId, + requestedStops: state.requestedStops, queue: state.queue, targetLevelId: state.targetLevelId, } @@ -463,14 +464,9 @@ export default function ElevatorPanel() { : fromLevelId || levels[0]?.id) ?? null const destinationOrderByLevelId = new Map() - const orderedDestinationIds: string[] = [] - if (runtime?.targetLevelId) orderedDestinationIds.push(runtime.targetLevelId) - for (const levelId of runtime?.queue ?? []) { - if (!orderedDestinationIds.includes(levelId)) orderedDestinationIds.push(levelId) - } - orderedDestinationIds.forEach((levelId, index) => { + for (const [index, levelId] of (runtime?.requestedStops ?? []).entries()) { destinationOrderByLevelId.set(levelId, index + 1) - }) + } return ( + +
+
+
+ From +
+ +
+ +
+
+ To +
+ +
+
+ +
+
+ Default Floor +
+ +
+
+ - -
-
-
- From -
- -
- -
-
- To -
- -
-
- -
-
- Default Floor -
- -
-
-
{servedLevels.map((level) => { diff --git a/packages/viewer/src/components/viewer/post-processing.tsx b/packages/viewer/src/components/viewer/post-processing.tsx index b64ffabf..8e0c873e 100644 --- a/packages/viewer/src/components/viewer/post-processing.tsx +++ b/packages/viewer/src/components/viewer/post-processing.tsx @@ -127,11 +127,12 @@ const PostProcessingPasses = ({ }: { hoverStyles?: HoverStyles }) => { - const { gl: renderer, invalidate, scene, camera } = useThree() + const { gl: renderer, invalidate, scene, camera, size } = useThree() const renderPipelineRef = useRef(null) const hasPipelineErrorRef = useRef(false) const retryCountRef = useRef(0) const rebuildTimeoutRef = useRef | null>(null) + const skippedZeroSizeRef = useRef(false) // Background color uniform — updated every frame via lerp, read by the TSL pipeline. // Initialised from the current theme so there's no flash on first render. @@ -207,6 +208,9 @@ const PostProcessingPasses = ({ // Build / rebuild the post-processing pipeline useEffect(() => { + const width = Math.floor(size.width) + const height = Math.floor(size.height) + if (!(renderer && scene && camera)) { console.warn('[viewer/post-processing] Skipping pipeline build — missing dependency.', { hasRenderer: !!renderer, @@ -216,6 +220,24 @@ const PostProcessingPasses = ({ return } + if (width < 1 || height < 1) { + skippedZeroSizeRef.current = true + hasPipelineErrorRef.current = false + if (renderPipelineRef.current) { + renderPipelineRef.current.dispose() + } + renderPipelineRef.current = null + return + } + + if (skippedZeroSizeRef.current) { + console.log('[viewer/post-processing] Rebuilding pipeline after zero-sized viewport.', { + width, + height, + }) + skippedZeroSizeRef.current = false + } + const perfDisable = readPerfDisableFlags() const ssgiEnabled = SSGI_PARAMS.enabled && !perfDisable.ao const denoiseEnabled = ssgiEnabled && !perfDisable.denoise @@ -230,6 +252,8 @@ const PostProcessingPasses = ({ hoverHighlightMode, projectId, rendererCtor: (renderer as any).constructor?.name, + width, + height, }) hasPipelineErrorRef.current = false @@ -413,10 +437,16 @@ const PostProcessingPasses = ({ projectId, renderer, scene, + size.height, + size.width, zoneLayers, ]) useFrame((_, delta) => { + if (size.width < 1 || size.height < 1) { + return + } + // Animate background colour toward the current theme target (same lerp as AnimatedBackground) bgTarget.current.set(useViewer.getState().theme === 'dark' ? DARK_BG : LIGHT_BG) bgCurrent.current.lerp(bgTarget.current, Math.min(delta, 0.1) * 4)