Refine elevator sync and viewer rebuild handling

This commit is contained in:
sudhir
2026-05-13 22:28:19 +05:30
parent 9838d2cd4f
commit ce69433a1a
8 changed files with 76 additions and 40 deletions
@@ -56,6 +56,7 @@ import useEditor, {
import { boxSelectHandled } from '../tools/select/box-select-tool'
const isNodeInCurrentLevel = (node: AnyNode): boolean => {
// Elevators are building-scoped, so they stay selectable across level filters.
if (node.type === 'elevator') return true
const currentLevelId = useViewer.getState().selection.levelId
if (!currentLevelId) return true // No level selected, allow all
@@ -9,7 +9,7 @@ import {
} from '@pascal-app/core'
import { useEffect, useMemo, useRef } from 'react'
import * as THREE from 'three'
import { resolveElevatorSupportY } from '../../../lib/elevator-support'
import { resolveCurrentBuildingId, resolveElevatorSupportY } from '../../../lib/elevator-support'
import { sfxEmitter } from '../../../lib/sfx-bus'
import { CursorSphere } from '../shared/cursor-sphere'
import {
@@ -31,21 +31,6 @@ type ElevatorToolProps = {
onPlaced?: (elevatorId: AnyNodeId, buildingId: BuildingNode['id']) => void
}
function resolveCurrentBuildingId(
buildingId: BuildingNode['id'] | null,
levelId: LevelNode['id'] | null,
): BuildingNode['id'] | null {
if (buildingId) return buildingId as BuildingNode['id']
if (!levelId) return null
const level = useScene.getState().nodes[levelId as AnyNodeId]
if (level?.type === 'level' && level.parentId) {
return level.parentId as BuildingNode['id']
}
return null
}
function resolveDefaultServiceRange(
buildingId: BuildingNode['id'],
selectedLevelId: LevelNode['id'] | null,
@@ -130,7 +115,11 @@ export const ElevatorTool: React.FC<ElevatorToolProps> = ({ buildingId, levelId,
const previewGeometry = useMemo(() => createElevatorPreviewGeometry(), [])
useEffect(() => {
const currentBuildingId = resolveCurrentBuildingId(buildingId, levelId)
const currentBuildingId = resolveCurrentBuildingId({
buildingId,
levelId,
nodes: useScene.getState().nodes,
})
if (!currentBuildingId) return
rotationRef.current = 0
@@ -160,7 +149,11 @@ export const ElevatorTool: React.FC<ElevatorToolProps> = ({ buildingId, levelId,
}
const onGridClick = (event: GridEvent) => {
const latestBuildingId = resolveCurrentBuildingId(buildingId, levelId)
const latestBuildingId = resolveCurrentBuildingId({
buildingId,
levelId,
nodes: useScene.getState().nodes,
})
if (!latestBuildingId) return
const gridX = Math.round(event.localPosition[0] * 2) / 2
@@ -6,6 +6,8 @@ import {
emitter,
type GridEvent,
type LevelNode,
pauseSceneHistory,
resumeSceneHistory,
sceneRegistry,
useLiveTransforms,
useScene,
@@ -36,6 +38,7 @@ export function MoveElevatorTool({
onCommitted?: (nodeId: AnyNodeId) => void
}) {
const onCommittedRef = useRef(onCommitted)
const historyPausedRef = useRef(false)
const previousGridPosRef = useRef<[number, number] | null>(null)
const previewPositionRef = useRef<ElevatorNode['position']>([
movingNode.position[0],
@@ -57,7 +60,19 @@ export function MoveElevatorTool({
}, [onCommitted])
useEffect(() => {
useScene.temporal.getState().pause()
const pauseHistory = () => {
const temporal = useScene.temporal.getState()
if (historyPausedRef.current || !temporal.isTracking) return
pauseSceneHistory(useScene)
historyPausedRef.current = true
}
const resumeHistory = () => {
if (!historyPausedRef.current) return
resumeSceneHistory(useScene)
historyPausedRef.current = false
}
pauseHistory()
const movingNodeId = (movingNode as { id?: ElevatorNode['id'] }).id
const meta =
@@ -148,7 +163,7 @@ export function MoveElevatorTool({
wasCommitted = true
clearPreview()
useScene.temporal.getState().resume()
resumeHistory()
if (movingNodeId && useScene.getState().nodes[movingNodeId as AnyNodeId]) {
useScene.getState().updateNode(movingNodeId as AnyNodeId, {
position: nextPosition,
@@ -188,7 +203,7 @@ export function MoveElevatorTool({
}
}
resetObject(original.position, original.rotation)
useScene.temporal.getState().resume()
resumeHistory()
markToolCancelConsumed()
exitMoveMode()
}
@@ -226,7 +241,7 @@ export function MoveElevatorTool({
})
resetObject(original.position, original.rotation)
}
useScene.temporal.getState().resume()
resumeHistory()
emitter.off('grid:move', onGridMove)
emitter.off('grid:click', onGridClick)
emitter.off('tool:cancel', onCancel)
+32 -4
View File
@@ -1,6 +1,7 @@
import {
type AnyNode,
type AnyNodeId,
type BuildingNode,
type ElevatorNode,
type LevelNode,
spatialGridManager,
@@ -21,6 +22,30 @@ function getBuildingLevels(
.sort((left, right) => left.level - right.level)
}
export function resolveCurrentBuildingId({
buildingId,
levelId,
nodes,
}: {
buildingId: BuildingNode['id'] | null
levelId: LevelNode['id'] | null
nodes: Record<string, AnyNode>
}): BuildingNode['id'] | null {
if (buildingId) return buildingId
if (!levelId) return null
const level = nodes[levelId as AnyNodeId]
if (
level?.type === 'level' &&
level.parentId &&
nodes[level.parentId as AnyNodeId]?.type === 'building'
) {
return level.parentId as BuildingNode['id']
}
return null
}
export function resolveElevatorSupportLevelId({
buildingId,
preferredLevelId,
@@ -29,13 +54,16 @@ export function resolveElevatorSupportLevelId({
preferredLevelId?: string | null
}): LevelNode['id'] | null {
const nodes = useScene.getState().nodes
const preferred = preferredLevelId ? nodes[preferredLevelId as AnyNodeId] : undefined
const levels = getBuildingLevels(buildingId, nodes)
if (levels.length === 0) return null
const preferred = preferredLevelId
const preferredInBuilding = preferredLevelId
? levels.find((level) => level.id === preferredLevelId)
: undefined
return preferred?.id ?? levels[0]?.id ?? null
if (preferredInBuilding) return preferredInBuilding.id
if (levels.length === 0) return preferred?.type === 'level' ? preferred.id : null
return levels[0]?.id ?? null
}
export function resolveElevatorSupportY({