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
@@ -1,6 +1,7 @@
import {
type AnyNodeId,
type ElevatorNode,
resolveElevatorDispatchTarget,
useInteractive,
useLiveNodeOverrides,
useLiveTransforms,
@@ -29,6 +30,7 @@ const CAB_COLOR = '#d7dde5'
const GLASS_COLOR = '#f8fafc'
const DOOR_COLOR = '#8e98a6'
const PANEL_COLOR = '#1f2937'
const DEFAULT_SHAFT_WALL_THICKNESS = 0.09
type Vector3Tuple = [number, number, number]
@@ -39,6 +41,11 @@ const BUTTON_RING_GEOMETRY = new TorusGeometry(1.12, 0.12, 8, 24)
const LABEL_MATRIX_DUMMY = new Object3D()
const SHAFT_TOP_FRAME_CLEARANCE = 0.006
type ElevatorDoorSide = 'left' | 'right'
type ElevatorDoorPanelStyleValue = ElevatorNode['doorPanelStyle']
type ElevatorDoorStyleValue = ElevatorNode['doorStyle']
type ElevatorShaftStyleValue = ElevatorNode['shaftStyle']
const SHAFT_WALL_MATERIAL = new MeshStandardMaterial({
color: SHAFT_WALL_COLOR,
metalness: 0.08,
@@ -64,6 +71,11 @@ const DOOR_MATERIAL = new MeshStandardMaterial({
metalness: 0.34,
roughness: 0.34,
})
const DOOR_GROOVE_MATERIAL = new MeshStandardMaterial({
color: '#5f6978',
metalness: 0.28,
roughness: 0.42,
})
const GLASS_MATERIAL = new MeshStandardMaterial({
color: GLASS_COLOR,
depthWrite: false,
@@ -132,6 +144,11 @@ const BUTTON_FACE_MATERIALS = {
metalness: 0.22,
roughness: 0.3,
}),
disabled: new MeshStandardMaterial({
color: '#475569',
metalness: 0.12,
roughness: 0.52,
}),
}
const BUTTON_RING_MATERIALS = {
active: new MeshStandardMaterial({
@@ -153,6 +170,11 @@ const BUTTON_RING_MATERIALS = {
metalness: 0.48,
roughness: 0.28,
}),
disabled: new MeshStandardMaterial({
color: '#334155',
metalness: 0.28,
roughness: 0.5,
}),
}
const BUTTON_GLOW_MATERIALS = {
active: new MeshStandardMaterial({
@@ -183,6 +205,11 @@ const BUTTON_LABEL_MATERIALS = {
metalness: 0.12,
roughness: 0.34,
}),
disabled: new MeshStandardMaterial({
color: '#94a3b8',
metalness: 0.08,
roughness: 0.5,
}),
}
const QUEUE_STRIP_MATERIALS = {
queued: new MeshStandardMaterial({
@@ -490,6 +517,7 @@ function ElevatorMeshButton({
action = 'request-level',
active,
buttonKind,
disabled = false,
elevatorId,
faceSign = -1,
glyph,
@@ -502,6 +530,7 @@ function ElevatorMeshButton({
action?: ElevatorButtonAction
active: boolean
buttonKind: 'cab' | 'landing'
disabled?: boolean
elevatorId: AnyNodeId
faceSign?: -1 | 1
glyph?: 'door-open'
@@ -511,34 +540,51 @@ function ElevatorMeshButton({
queued: boolean
radius?: number
}) {
const state = active ? 'active' : queued ? 'queued' : 'idle'
const state = disabled ? 'disabled' : active ? 'active' : queued ? 'queued' : 'idle'
const depth = active ? 0.028 : 0.04
const faceZ = faceSign * (depth / 2 + 0.004)
const labelMaterial = active || queued ? BUTTON_LABEL_MATERIALS.lit : BUTTON_LABEL_MATERIALS.idle
const labelMaterial = disabled
? BUTTON_LABEL_MATERIALS.disabled
: active || queued
? BUTTON_LABEL_MATERIALS.lit
: BUTTON_LABEL_MATERIALS.idle
const userData = useMemo(
() => ({
elevatorButton: {
action,
disabled,
elevatorId,
kind: buttonKind,
levelId,
},
}),
[action, buttonKind, elevatorId, levelId],
[action, buttonKind, disabled, elevatorId, levelId],
)
const press = (event: ThreeEvent<PointerEvent>) => {
if (event.button !== 0) return
if (disabled) return
if (action === 'open-door') {
useInteractive.getState().openElevatorDoor(elevatorId)
return
}
if (levelId) useInteractive.getState().requestElevator(elevatorId, levelId)
if (levelId) {
const targetElevatorId =
buttonKind === 'landing'
? resolveElevatorDispatchTarget({
elevators: useInteractive.getState().elevators,
levelId,
nodes: useScene.getState().nodes,
requestedElevatorId: elevatorId,
})
: elevatorId
useInteractive.getState().requestElevator(targetElevatorId, levelId)
}
}
return (
<group onPointerDown={press} position={position} userData={userData}>
{(active || queued) && (
{!disabled && (active || queued) && (
<mesh
dispose={null}
geometry={BUTTON_GLOW_GEOMETRY}
@@ -582,9 +628,83 @@ function ElevatorMeshButton({
)
}
function getResolvedDoorStyle(
doorStyle: ElevatorDoorStyleValue | undefined,
): ElevatorDoorStyleValue {
return doorStyle ?? 'center-opening'
}
function getResolvedDoorPanelStyle(
doorPanelStyle: ElevatorDoorPanelStyleValue | undefined,
): ElevatorDoorPanelStyleValue {
return doorPanelStyle ?? 'glass-frame'
}
function getResolvedShaftStyle(
shaftStyle: ElevatorShaftStyleValue | undefined,
): ElevatorShaftStyleValue {
return shaftStyle ?? 'solid'
}
function getElevatorDoorLeafSides(
doorStyle: ElevatorDoorStyleValue | undefined,
): ElevatorDoorSide[] {
const resolvedDoorStyle = getResolvedDoorStyle(doorStyle)
if (resolvedDoorStyle === 'single-left') return ['left']
if (resolvedDoorStyle === 'single-right') return ['right']
return ['left', 'right']
}
function getElevatorDoorLeafX(
side: ElevatorDoorSide,
openingWidth: number,
doorOpen: number,
doorStyle: ElevatorDoorStyleValue | undefined,
) {
const resolvedDoorStyle = getResolvedDoorStyle(doorStyle)
if (resolvedDoorStyle === 'center-opening') {
const direction = side === 'left' ? -1 : 1
return direction * (openingWidth / 4 + doorOpen * openingWidth * 0.34)
}
const direction = resolvedDoorStyle === 'single-left' ? -1 : 1
return direction * doorOpen * openingWidth * 0.68
}
function getElevatorDoorLeafWidth(
openingWidth: number,
doorStyle: ElevatorDoorStyleValue | undefined,
) {
return getResolvedDoorStyle(doorStyle) === 'center-opening'
? Math.max(openingWidth / 2 - 0.018, 0.12)
: Math.max(openingWidth - 0.018, 0.18)
}
function getElevatorCabWidth(node: ElevatorNode) {
return Math.max(node.width, 0.8)
}
function getElevatorCabDepth(node: ElevatorNode) {
return Math.max(node.depth, 0.8)
}
function getElevatorShaftWallThickness(node: ElevatorNode) {
return Math.max(node.shaftWallThickness ?? DEFAULT_SHAFT_WALL_THICKNESS, 0.04)
}
function getElevatorShaftWidth(node: ElevatorNode, cabWidth = getElevatorCabWidth(node)) {
return Math.max(node.shaftWidth ?? cabWidth, cabWidth, 0.8)
}
function getElevatorShaftDepth(node: ElevatorNode, cabDepth = getElevatorCabDepth(node)) {
return Math.max(node.shaftDepth ?? cabDepth, cabDepth, 0.8)
}
function DoorLeaf({
animated,
doorOpen,
doorPanelStyle,
doorStyle,
height,
side,
width,
@@ -602,20 +722,26 @@ function DoorLeaf({
levelId: AnyNodeId
}
doorOpen: number
doorPanelStyle: ElevatorDoorPanelStyleValue
doorStyle: ElevatorDoorStyleValue
height: number
side: 'left' | 'right'
side: ElevatorDoorSide
width: number
y: number
z: number
}) {
const ref = useRef<Group>(null)
const direction = side === 'left' ? -1 : 1
const getLeafX = (openAmount: number) => direction * (width / 4 + openAmount * width * 0.34)
const leafWidth = Math.max(width / 2 - 0.018, 0.12)
const getLeafX = (openAmount: number) => getElevatorDoorLeafX(side, width, openAmount, doorStyle)
const leafWidth = getElevatorDoorLeafWidth(width, doorStyle)
const resolvedPanelStyle = getResolvedDoorPanelStyle(doorPanelStyle)
const railHeight = Math.min(0.09, Math.max(0.055, height * 0.04))
const stileWidth = Math.min(0.07, Math.max(0.04, leafWidth * 0.18))
const glassWidth = Math.max(leafWidth - stileWidth * 2.2, 0.03)
const glassHeight = Math.max(height - railHeight * 3, 0.2)
const panelInsetWidth = Math.max(leafWidth - 0.12, 0.05)
const panelInsetHeight = Math.max(height - 0.26, 0.2)
const segmentCount = 4
const segmentSpacing = panelInsetHeight / segmentCount
useFrame(() => {
if (!(animated && ref.current)) return
@@ -631,43 +757,130 @@ function DoorLeaf({
return (
<group ref={ref} position={[getLeafX(doorOpen), y + height / 2, z]}>
<BoxPrimitive
castShadow
material={DOOR_MATERIAL}
position={[0, height / 2 - railHeight / 2, 0]}
receiveShadow
scale={[leafWidth, railHeight, 0.05]}
/>
<BoxPrimitive
castShadow
material={DOOR_MATERIAL}
position={[0, -height / 2 + railHeight / 2, 0]}
receiveShadow
scale={[leafWidth, railHeight, 0.05]}
/>
<BoxPrimitive
castShadow
material={DOOR_MATERIAL}
position={[-leafWidth / 2 + stileWidth / 2, 0, 0]}
receiveShadow
scale={[stileWidth, height, 0.05]}
/>
<BoxPrimitive
castShadow
material={DOOR_MATERIAL}
position={[leafWidth / 2 - stileWidth / 2, 0, 0]}
receiveShadow
scale={[stileWidth, height, 0.05]}
/>
<BoxPrimitive
material={GLASS_MATERIAL}
position={[0, 0, -0.004]}
scale={[glassWidth, glassHeight, 0.012]}
/>
{resolvedPanelStyle === 'glass-frame' ? (
<>
<BoxPrimitive
castShadow
material={DOOR_MATERIAL}
position={[0, height / 2 - railHeight / 2, 0]}
receiveShadow
scale={[leafWidth, railHeight, 0.05]}
/>
<BoxPrimitive
castShadow
material={DOOR_MATERIAL}
position={[0, -height / 2 + railHeight / 2, 0]}
receiveShadow
scale={[leafWidth, railHeight, 0.05]}
/>
<BoxPrimitive
castShadow
material={DOOR_MATERIAL}
position={[-leafWidth / 2 + stileWidth / 2, 0, 0]}
receiveShadow
scale={[stileWidth, height, 0.05]}
/>
<BoxPrimitive
castShadow
material={DOOR_MATERIAL}
position={[leafWidth / 2 - stileWidth / 2, 0, 0]}
receiveShadow
scale={[stileWidth, height, 0.05]}
/>
<BoxPrimitive
material={GLASS_MATERIAL}
position={[0, 0, -0.004]}
scale={[glassWidth, glassHeight, 0.012]}
/>
</>
) : (
<>
<BoxPrimitive
castShadow
material={DOOR_MATERIAL}
position={[0, 0, 0]}
receiveShadow
scale={[leafWidth, height, 0.05]}
/>
<BoxPrimitive
material={DOOR_GROOVE_MATERIAL}
position={[0, 0, -0.028]}
scale={[0.018, panelInsetHeight, 0.01]}
/>
{resolvedPanelStyle === 'segmented-panel'
? Array.from({ length: segmentCount - 1 }).map((_, index) => (
<BoxPrimitive
key={index}
material={DOOR_GROOVE_MATERIAL}
position={[0, -panelInsetHeight / 2 + segmentSpacing * (index + 1), -0.03]}
scale={[panelInsetWidth, 0.018, 0.012]}
/>
))
: null}
<BoxPrimitive
material={DOOR_GROOVE_MATERIAL}
position={[0, panelInsetHeight / 2, -0.029]}
scale={[panelInsetWidth, 0.012, 0.01]}
/>
<BoxPrimitive
material={DOOR_GROOVE_MATERIAL}
position={[0, -panelInsetHeight / 2, -0.029]}
scale={[panelInsetWidth, 0.012, 0.01]}
/>
</>
)}
</group>
)
}
function ElevatorDoorLeaves({
animated,
doorOpen,
doorPanelStyle,
doorStyle,
height,
width,
y,
z,
}: {
animated?:
| {
elevatorId: AnyNodeId
kind: 'cab'
}
| {
elevatorId: AnyNodeId
kind: 'landing'
levelId: AnyNodeId
}
doorOpen: number
doorPanelStyle: ElevatorDoorPanelStyleValue
doorStyle: ElevatorDoorStyleValue
height: number
width: number
y: number
z: number
}) {
return (
<>
{getElevatorDoorLeafSides(doorStyle).map((side) => (
<DoorLeaf
animated={animated}
doorOpen={doorOpen}
doorPanelStyle={doorPanelStyle}
doorStyle={doorStyle}
height={height}
key={side}
side={side}
width={width}
y={y}
z={z}
/>
))}
</>
)
}
function LandingDoorFrame({
doorHeight,
doorWidth,
@@ -749,6 +962,8 @@ function LandingDoorFrame({
function LandingDoor({
animated,
doorPanelStyle,
doorStyle,
elevatorId,
doorOpen,
doorHeight,
@@ -758,6 +973,8 @@ function LandingDoor({
z,
}: {
animated: boolean
doorPanelStyle: ElevatorDoorPanelStyleValue
doorStyle: ElevatorDoorStyleValue
elevatorId: AnyNodeId
doorOpen: number
doorHeight: number
@@ -767,26 +984,16 @@ function LandingDoor({
z: number
}) {
return (
<>
<DoorLeaf
animated={animated ? { elevatorId, kind: 'landing', levelId } : undefined}
doorOpen={doorOpen}
height={doorHeight}
side="left"
width={doorWidth}
y={levelY}
z={z}
/>
<DoorLeaf
animated={animated ? { elevatorId, kind: 'landing', levelId } : undefined}
doorOpen={doorOpen}
height={doorHeight}
side="right"
width={doorWidth}
y={levelY}
z={z}
/>
</>
<ElevatorDoorLeaves
animated={animated ? { elevatorId, kind: 'landing', levelId } : undefined}
doorOpen={doorOpen}
doorPanelStyle={doorPanelStyle}
doorStyle={doorStyle}
height={doorHeight}
width={doorWidth}
y={levelY}
z={z}
/>
)
}
@@ -856,13 +1063,24 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
cabRef.current.position.y = runtime.carY
}, 2.6)
const shaftWidth = Math.max(renderNode.width, 0.8)
const shaftDepth = Math.max(renderNode.depth, 0.8)
const cabWidth = getElevatorCabWidth(renderNode)
const cabDepth = getElevatorCabDepth(renderNode)
const shaftWidth = getElevatorShaftWidth(renderNode, cabWidth)
const shaftDepth = getElevatorShaftDepth(renderNode, cabDepth)
const cabHeight = Math.max(renderNode.cabHeight, 1.4)
const doorWidth = Math.min(Math.max(renderNode.doorWidth, 0.45), shaftWidth - 0.18)
const shaftWallThickness = getElevatorShaftWallThickness(renderNode)
const doorWidth = Math.min(
Math.max(renderNode.doorWidth, 0.45),
cabWidth - 0.18,
shaftWidth - 0.18,
)
const doorHeight = Math.min(Math.max(renderNode.doorHeight, 1.2), cabHeight - 0.1)
const doorPanelStyle = getResolvedDoorPanelStyle(renderNode.doorPanelStyle)
const doorStyle = getResolvedDoorStyle(renderNode.doorStyle)
const shaftStyle = getResolvedShaftStyle(renderNode.shaftStyle)
const shaftShellMaterial = shaftStyle === 'glass' ? GLASS_MATERIAL : SHAFT_SIDE_MATERIAL
const shaftTopMaterial = shaftStyle === 'glass' ? SHAFT_TRIM_MATERIAL : SHAFT_SIDE_MATERIAL
const shaftHeight = Math.max(totalHeight, cabHeight + 0.3)
const shaftWallThickness = 0.09
const shaftBodyHeight = Math.max(shaftHeight - shaftWallThickness, 0.01)
const shaftBodyCenterY = shaftBaseY + shaftBodyHeight / 2
const shaftTopCapBottomY = shaftBaseY + shaftHeight - shaftWallThickness
@@ -906,6 +1124,14 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
runtimeStatus?.queue,
runtimeStatus?.targetLevelId,
])
const disabledLevelIds = useMemo(
() => new Set(renderNode.disabledLevelIds ?? []),
[renderNode.disabledLevelIds],
)
const serviceOnlyLevelIds = useMemo(
() => new Set(renderNode.serviceOnlyLevelIds ?? []),
[renderNode.serviceOnlyLevelIds],
)
const doorOpen = runtimeSnapshot?.doorOpen ?? 0
const doorOpenButtonActive =
doorOpen > 0.12 ||
@@ -916,8 +1142,9 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
const frontWallZ = -shaftDepth / 2 - shaftWallThickness / 2
const frontZ = frontWallZ - shaftWallThickness / 2 - 0.018
const landingPanelX = Math.min(shaftWidth / 2 - 0.16, doorWidth / 2 + 0.18)
const cabPanelX = shaftWidth / 2 - 0.075
const cabPanelZ = -shaftDepth / 2 + 0.36
const cabCenterZ = -shaftDepth / 2 + cabDepth / 2
const cabPanelX = cabWidth / 2 - 0.075
const cabPanelZ = cabCenterZ - cabDepth / 2 + 0.36
const cabButtonColumns = entries.length > 1 ? 2 : 1
const cabButtonRows = Math.max(1, Math.ceil(entries.length / cabButtonColumns))
const cabButtonSpacingX = 0.14
@@ -956,28 +1183,28 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
>
<BoxPrimitive
castShadow
material={SHAFT_SIDE_MATERIAL}
material={shaftShellMaterial}
position={[0, shaftBodyCenterY, shaftDepth / 2 + shaftWallThickness / 2]}
receiveShadow
scale={[shaftWidth + shaftWallThickness * 2, shaftBodyHeight, shaftWallThickness]}
/>
<BoxPrimitive
castShadow
material={SHAFT_SIDE_MATERIAL}
material={shaftShellMaterial}
position={[-shaftWidth / 2 - shaftWallThickness / 2, shaftBodyCenterY, 0]}
receiveShadow
scale={[shaftWallThickness, shaftBodyHeight, shaftDepth + shaftWallThickness * 2]}
/>
<BoxPrimitive
castShadow
material={SHAFT_SIDE_MATERIAL}
material={shaftShellMaterial}
position={[shaftWidth / 2 + shaftWallThickness / 2, shaftBodyCenterY, 0]}
receiveShadow
scale={[shaftWallThickness, shaftBodyHeight, shaftDepth + shaftWallThickness * 2]}
/>
<BoxPrimitive
castShadow
material={SHAFT_SIDE_MATERIAL}
material={shaftTopMaterial}
position={[0, shaftBaseY + shaftHeight - shaftWallThickness / 2, 0]}
receiveShadow
scale={[
@@ -991,57 +1218,49 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
<BoxPrimitive
castShadow
material={CAB_MATERIAL}
position={[0, 0.04, 0]}
position={[0, 0.04, cabCenterZ]}
receiveShadow
scale={[shaftWidth, 0.08, shaftDepth]}
scale={[cabWidth, 0.08, cabDepth]}
/>
<BoxPrimitive
castShadow
material={CAB_MATERIAL}
position={[0, cabHeight - 0.04, 0]}
position={[0, cabHeight - 0.04, cabCenterZ]}
receiveShadow
scale={[shaftWidth, 0.08, shaftDepth]}
scale={[cabWidth, 0.08, cabDepth]}
/>
<BoxPrimitive
castShadow
material={CAB_MATERIAL}
position={[0, cabHeight / 2, shaftDepth / 2 - 0.04]}
position={[0, cabHeight / 2, cabCenterZ + cabDepth / 2 - 0.04]}
receiveShadow
scale={[shaftWidth, cabHeight, 0.08]}
scale={[cabWidth, cabHeight, 0.08]}
/>
<BoxPrimitive
castShadow
material={CAB_MATERIAL}
position={[-shaftWidth / 2 + 0.04, cabHeight / 2, 0]}
position={[-cabWidth / 2 + 0.04, cabHeight / 2, cabCenterZ]}
receiveShadow
scale={[0.08, cabHeight, shaftDepth]}
scale={[0.08, cabHeight, cabDepth]}
/>
<BoxPrimitive
castShadow
material={CAB_MATERIAL}
position={[shaftWidth / 2 - 0.04, cabHeight / 2, 0]}
position={[cabWidth / 2 - 0.04, cabHeight / 2, cabCenterZ]}
receiveShadow
scale={[0.08, cabHeight, shaftDepth]}
scale={[0.08, cabHeight, cabDepth]}
/>
<DoorLeaf
<ElevatorDoorLeaves
animated={{ elevatorId, kind: 'cab' }}
doorOpen={doorOpen}
doorPanelStyle={doorPanelStyle}
doorStyle={doorStyle}
height={doorHeight}
side="left"
width={doorWidth}
y={0}
z={frontZ}
/>
<DoorLeaf
animated={{ elevatorId, kind: 'cab' }}
doorOpen={doorOpen}
height={doorHeight}
side="right"
width={doorWidth}
y={0}
z={frontZ}
@@ -1067,21 +1286,23 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
{entries.map((entry, index) => {
const column = index % cabButtonColumns
const row = Math.floor(index / cabButtonColumns)
const isDisabledLevel = disabledLevelIds.has(entry.id)
const x =
cabFloorButtonOffsetX + (column - (cabButtonColumns - 1) / 2) * cabButtonSpacingX
const y = (row - (cabButtonRows - 1) / 2) * cabButtonSpacingY
return (
<ElevatorMeshButton
active={activeLevelId === entry.id}
active={!isDisabledLevel && activeLevelId === entry.id}
buttonKind="cab"
disabled={isDisabledLevel}
elevatorId={elevatorId}
faceSign={1}
key={entry.id}
label={entry.label}
levelId={entry.id as AnyNodeId}
position={[x, y, 0.045]}
queued={queuedLevelIds.has(entry.id)}
queued={!isDisabledLevel && queuedLevelIds.has(entry.id)}
/>
)
})}
@@ -1101,7 +1322,9 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
{entrySpans.map(({ entry, levelTopY }) => {
const isCurrentLevel = activeLevelId === entry.id
const isQueuedLevel = queuedLevelIds.has(entry.id)
const isDisabledLevel = disabledLevelIds.has(entry.id)
const isServiceOnlyLevel = serviceOnlyLevelIds.has(entry.id)
const isQueuedLevel = !isDisabledLevel && queuedLevelIds.has(entry.id)
const isPendingLevel = pendingLevelId === entry.id
const showLandingReadout = isCurrentLevel || isPendingLevel || isQueuedLevel
@@ -1117,6 +1340,8 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
/>
<LandingDoor
animated={isCurrentLevel}
doorPanelStyle={doorPanelStyle}
doorStyle={doorStyle}
elevatorId={elevatorId}
doorHeight={doorHeight}
doorOpen={isCurrentLevel ? doorOpen : 0}
@@ -1141,8 +1366,9 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
scale={[0.18, 0.42, 0.04]}
/>
<ElevatorMeshButton
active={isCurrentLevel && doorOpen > 0.5}
active={!isDisabledLevel && !isServiceOnlyLevel && isCurrentLevel && doorOpen > 0.5}
buttonKind="landing"
disabled={isDisabledLevel || isServiceOnlyLevel}
elevatorId={elevatorId}
levelId={entry.id as AnyNodeId}
position={[0, 0.06, -0.045]}