Improve elevator floorplan resize and drag controls

This commit is contained in:
sudhir
2026-05-10 12:20:53 +05:30
parent fdf08c091f
commit 909f96ef09
10 changed files with 1531 additions and 232 deletions
@@ -0,0 +1,90 @@
import type { AnyNode, AnyNodeId, ElevatorNode } from '../../schema'
import type { ElevatorInteractiveState } from '../../store/use-interactive'
import { resolveElevatorServiceLevels } from './elevator-service'
type ElevatorRuntimeMap = Record<AnyNodeId, ElevatorInteractiveState>
type ResolveElevatorDispatchTargetArgs = {
elevators: ElevatorRuntimeMap
levelId: AnyNodeId
nodes: Record<string, AnyNode>
requestedElevatorId: AnyNodeId
}
function getRuntimeLevelId(runtime: ElevatorInteractiveState | undefined, elevator: ElevatorNode) {
return runtime?.currentLevelId ?? elevator.defaultLevelId ?? elevator.fromLevelId ?? null
}
function scoreDispatchCandidate({
elevator,
elevators,
levelId,
nodes,
}: {
elevator: ElevatorNode
elevators: ElevatorRuntimeMap
levelId: AnyNodeId
nodes: Record<string, AnyNode>
}) {
if ((elevator.disabledLevelIds ?? []).includes(levelId)) return null
if ((elevator.serviceOnlyLevelIds ?? []).includes(levelId)) return null
const serviceLevels = resolveElevatorServiceLevels(elevator, nodes)
const targetIndex = serviceLevels.findIndex((level) => level.id === levelId)
if (targetIndex < 0) return null
const runtime = elevators[elevator.id as AnyNodeId]
const runtimeLevelId = getRuntimeLevelId(runtime, elevator)
const currentIndex = runtimeLevelId
? serviceLevels.findIndex((level) => level.id === runtimeLevelId)
: -1
const resolvedCurrentIndex = currentIndex >= 0 ? currentIndex : 0
const distance = Math.abs(targetIndex - resolvedCurrentIndex)
const queuePenalty = (runtime?.queue.length ?? 0) * 2 + (runtime?.targetLevelId ? 1 : 0)
const motionPenalty = runtime?.phase === 'moving' || runtime?.phase === 'closing' ? 0.5 : 0
const openPenalty = runtime?.phase === 'open' || runtime?.phase === 'opening' ? 0.2 : 0
return distance + queuePenalty + motionPenalty + openPenalty
}
export function resolveElevatorDispatchTarget({
elevators,
levelId,
nodes,
requestedElevatorId,
}: ResolveElevatorDispatchTargetArgs): AnyNodeId {
const requestedElevator = nodes[requestedElevatorId]
if (!(requestedElevator?.type === 'elevator' && requestedElevator.parentId)) {
return requestedElevatorId
}
const building = nodes[requestedElevator.parentId as AnyNodeId]
if (building?.type !== 'building') {
return requestedElevatorId
}
let bestElevatorId: AnyNodeId | null = null
let bestScore = Number.POSITIVE_INFINITY
for (const childId of building.children) {
const candidate = nodes[childId as AnyNodeId]
if (!(candidate?.type === 'elevator' && candidate.visible !== false)) continue
const score = scoreDispatchCandidate({
elevator: candidate,
elevators,
levelId,
nodes,
})
if (score === null) continue
const tieBreaker = candidate.id === requestedElevatorId ? -0.01 : 0
const finalScore = score + tieBreaker
if (finalScore < bestScore) {
bestScore = finalScore
bestElevatorId = candidate.id as AnyNodeId
}
}
return bestElevatorId ?? requestedElevatorId
}
@@ -11,6 +11,7 @@ type SurfaceHoleMetadata = {
}
const ELEVATOR_OPENING_PADDING = 0.08
const DEFAULT_ELEVATOR_SHAFT_WALL_THICKNESS = 0.09
function pointsEqual(a: Point2D, b: Point2D, tolerance = 1e-5) {
const dx = a[0] - b[0]
@@ -141,8 +142,14 @@ function shouldApplyElevatorToCeiling(
}
function getElevatorOpeningPolygon(elevator: ElevatorNode): Point2D[] {
const halfWidth = Math.max(elevator.width, 0.8) / 2 + ELEVATOR_OPENING_PADDING
const halfDepth = Math.max(elevator.depth, 0.8) / 2 + ELEVATOR_OPENING_PADDING
const wallThickness = Math.max(
elevator.shaftWallThickness ?? DEFAULT_ELEVATOR_SHAFT_WALL_THICKNESS,
0.04,
)
const shaftWidth = Math.max(elevator.shaftWidth ?? elevator.width, elevator.width, 0.8)
const shaftDepth = Math.max(elevator.shaftDepth ?? elevator.depth, elevator.depth, 0.8)
const halfWidth = shaftWidth / 2 + wallThickness + ELEVATOR_OPENING_PADDING
const halfDepth = shaftDepth / 2 + wallThickness + ELEVATOR_OPENING_PADDING
const corners: Point2D[] = [
[-halfWidth, -halfDepth],
[halfWidth, -halfDepth],