feat(paint-slots): migrate elevator onto the unified slot model

Elevator exposes four finish slots — cab / doors / shaft / glass (glass only
when shaft or door style is glass) — on node.slots. The finish materials now
resolve through a per-node material set (context provider) keyed on node.slots
+ scene materials: textures-off joinery role -> node.slots ref -> declared
default (cab preset-softwhite, doors metal-steel, shaft preset-lightgrey,
glass preset-glass), with glass transparency flags re-applied. Cab/door/shaft/
glass meshes tag userData.slotId; buttons, indicators, control panels, and
queue strips stay untagged (functional UI, not paintable). Interaction system
untouched. Monochrome unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-17 15:35:41 -04:00
co-authored by Claude Opus 4.8
parent a8f32dd47a
commit 1199c04fac
4 changed files with 208 additions and 51 deletions
@@ -13,7 +13,9 @@ import {
import { buildElevatorFloorplan } from './floorplan' import { buildElevatorFloorplan } from './floorplan'
import { elevatorResizeAffordance, elevatorRotateAffordance } from './floorplan-affordances' import { elevatorResizeAffordance, elevatorRotateAffordance } from './floorplan-affordances'
import { elevatorParametrics } from './parametrics' import { elevatorParametrics } from './parametrics'
import { elevatorPaint } from './paint'
import { ElevatorNode } from './schema' import { ElevatorNode } from './schema'
import { elevatorSlots } from './slots'
const SIDE_HANDLE_OFFSET = 0.22 const SIDE_HANDLE_OFFSET = 0.22
const HEIGHT_HANDLE_OFFSET = 0.3 const HEIGHT_HANDLE_OFFSET = 0.3
@@ -223,6 +225,8 @@ export const elevatorDefinition: NodeDefinition<typeof ElevatorNode> = {
}, },
duplicable: true, duplicable: true,
deletable: true, deletable: true,
slots: (node) => elevatorSlots(node as ElevatorNodeType),
paint: elevatorPaint,
}, },
parametrics: elevatorParametrics, parametrics: elevatorParametrics,
+7
View File
@@ -0,0 +1,7 @@
import { createSlotPaintCapability, previewSlotByUserData } from '../shared/slot-paint'
export const elevatorPaint = createSlotPaintCapability({
resolveRole: (args) => (args.hitObject?.userData?.slotId as string) ?? null,
applyPreview: previewSlotByUserData,
legacyEffective: () => null,
})
+168 -51
View File
@@ -28,11 +28,13 @@ import {
createDefaultMaterial, createDefaultMaterial,
createSurfaceRoleMaterial, createSurfaceRoleMaterial,
type RenderShading, type RenderShading,
resolveMaterialRef,
resolveSlotDefaultMaterial,
useNodeEvents, useNodeEvents,
useViewer, useViewer,
} from '@pascal-app/viewer' } from '@pascal-app/viewer'
import { useFrame } from '@react-three/fiber' import { useFrame } from '@react-three/fiber'
import { useCallback, useLayoutEffect, useMemo, useRef } from 'react' import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useRef } from 'react'
import { import {
BoxGeometry, BoxGeometry,
CylinderGeometry, CylinderGeometry,
@@ -43,6 +45,13 @@ import {
TorusGeometry, TorusGeometry,
} from 'three' } from 'three'
import { useShallow } from 'zustand/react/shallow' import { useShallow } from 'zustand/react/shallow'
import {
ELEVATOR_CAB_SLOT_DEFAULT,
ELEVATOR_DOORS_SLOT_DEFAULT,
ELEVATOR_GLASS_SLOT_DEFAULT,
ELEVATOR_SHAFT_SLOT_DEFAULT,
type ElevatorSlotId,
} from './slots'
const DEFAULT_STRUCTURE_WHITE = '#f2f0ed' const DEFAULT_STRUCTURE_WHITE = '#f2f0ed'
const SHAFT_WALL_COLOR = DEFAULT_STRUCTURE_WHITE const SHAFT_WALL_COLOR = DEFAULT_STRUCTURE_WHITE
@@ -360,48 +369,102 @@ function getElevatorMaterials(
return materials return materials
} }
let { type ElevatorSceneMaterials = ReturnType<typeof useScene.getState>['materials']
SHAFT_WALL_MATERIAL,
SHAFT_SIDE_MATERIAL,
SHAFT_TRIM_MATERIAL,
CAB_MATERIAL,
DOOR_MATERIAL,
DOOR_GROOVE_MATERIAL,
GLASS_MATERIAL,
PANEL_MATERIAL,
LANDING_PANEL_MATERIAL,
INDICATOR_SCREEN_MATERIALS,
INDICATOR_GLYPH_MATERIALS,
BUTTON_FACE_MATERIALS,
BUTTON_RING_MATERIALS,
BUTTON_GLOW_MATERIALS,
BUTTON_LABEL_MATERIALS,
QUEUE_STRIP_MATERIALS,
} = getElevatorMaterials('rendered')
function setElevatorMaterials( function resolveElevatorFinishMaterial(
node: ElevatorNode,
slotId: ElevatorSlotId,
slotDefault: string,
sceneMaterials: ElevatorSceneMaterials,
shading: RenderShading, shading: RenderShading,
textures = true, roughness: number,
colorPreset: ColorPreset = 'clay', ): Material {
) { const ref = node.slots?.[slotId]
;({ if (ref) {
SHAFT_WALL_MATERIAL, const resolved = resolveMaterialRef(ref, sceneMaterials, shading)
SHAFT_SIDE_MATERIAL, if (resolved) return resolved
SHAFT_TRIM_MATERIAL, }
CAB_MATERIAL, return resolveSlotDefaultMaterial(slotDefault, shading, roughness)
DOOR_MATERIAL, }
DOOR_GROOVE_MATERIAL,
GLASS_MATERIAL, function withElevatorGlassTransparency(material: Material): Material {
PANEL_MATERIAL, const glass = material.clone()
LANDING_PANEL_MATERIAL, glass.depthWrite = false
INDICATOR_SCREEN_MATERIALS, glass.opacity = 0.2
INDICATOR_GLYPH_MATERIALS, glass.transparent = true
BUTTON_FACE_MATERIALS, glass.needsUpdate = true
BUTTON_RING_MATERIALS, return glass
BUTTON_GLOW_MATERIALS, }
BUTTON_LABEL_MATERIALS,
QUEUE_STRIP_MATERIALS, function getResolvedElevatorMaterials(
} = getElevatorMaterials(shading, textures, colorPreset)) node: ElevatorNode,
shading: RenderShading,
textures: boolean,
colorPreset: ColorPreset,
sceneMaterials: ElevatorSceneMaterials,
): ElevatorMaterialSet {
const materials = getElevatorMaterials(shading, textures, colorPreset)
if (!textures) return materials
const cab = resolveElevatorFinishMaterial(
node,
'cab',
ELEVATOR_CAB_SLOT_DEFAULT,
sceneMaterials,
shading,
0.48,
)
const doors = resolveElevatorFinishMaterial(
node,
'doors',
ELEVATOR_DOORS_SLOT_DEFAULT,
sceneMaterials,
shading,
0.34,
)
const shaft = resolveElevatorFinishMaterial(
node,
'shaft',
ELEVATOR_SHAFT_SLOT_DEFAULT,
sceneMaterials,
shading,
0.56,
)
const glass = withElevatorGlassTransparency(
resolveElevatorFinishMaterial(
node,
'glass',
ELEVATOR_GLASS_SLOT_DEFAULT,
sceneMaterials,
shading,
0.08,
),
)
return {
...materials,
SHAFT_WALL_MATERIAL: shaft,
SHAFT_SIDE_MATERIAL: shaft,
SHAFT_TRIM_MATERIAL: shaft,
CAB_MATERIAL: cab,
DOOR_MATERIAL: doors,
DOOR_GROOVE_MATERIAL: doors,
GLASS_MATERIAL: glass,
}
}
const DEFAULT_ELEVATOR_MATERIALS = getElevatorMaterials('rendered')
const ElevatorMaterialsContext = createContext<ElevatorMaterialSet>(DEFAULT_ELEVATOR_MATERIALS)
function useElevatorMaterialSet(): ElevatorMaterialSet {
return useContext(ElevatorMaterialsContext)
}
const ELEVATOR_SLOT_USER_DATA: Record<ElevatorSlotId, { slotId: ElevatorSlotId }> = {
cab: { slotId: 'cab' },
doors: { slotId: 'doors' },
shaft: { slotId: 'shaft' },
glass: { slotId: 'glass' },
} }
type ElevatorButtonAction = 'open-door' | 'request-level' type ElevatorButtonAction = 'open-door' | 'request-level'
@@ -449,6 +512,7 @@ function BoxPrimitive({
receiveShadow = false, receiveShadow = false,
rotation, rotation,
scale, scale,
slotId,
}: { }: {
castShadow?: boolean castShadow?: boolean
material: Material material: Material
@@ -456,6 +520,7 @@ function BoxPrimitive({
receiveShadow?: boolean receiveShadow?: boolean
rotation?: Vector3Tuple rotation?: Vector3Tuple
scale: Vector3Tuple scale: Vector3Tuple
slotId?: ElevatorSlotId
}) { }) {
return ( return (
<mesh <mesh
@@ -467,6 +532,7 @@ function BoxPrimitive({
receiveShadow={receiveShadow} receiveShadow={receiveShadow}
rotation={rotation} rotation={rotation}
scale={scale} scale={scale}
userData={slotId ? ELEVATOR_SLOT_USER_DATA[slotId] : undefined}
/> />
) )
} }
@@ -597,6 +663,8 @@ function ElevatorFloorIndicator({
scale?: number scale?: number
showReadout?: boolean showReadout?: boolean
}) { }) {
const { INDICATOR_GLYPH_MATERIALS, INDICATOR_SCREEN_MATERIALS, PANEL_MATERIAL } =
useElevatorMaterialSet()
const glyphMaterial = active ? INDICATOR_GLYPH_MATERIALS.active : INDICATOR_GLYPH_MATERIALS.idle const glyphMaterial = active ? INDICATOR_GLYPH_MATERIALS.active : INDICATOR_GLYPH_MATERIALS.idle
const screenMaterial = active const screenMaterial = active
? INDICATOR_SCREEN_MATERIALS.active ? INDICATOR_SCREEN_MATERIALS.active
@@ -721,6 +789,12 @@ function ElevatorMeshButton({
queued: boolean queued: boolean
radius?: number radius?: number
}) { }) {
const {
BUTTON_FACE_MATERIALS,
BUTTON_GLOW_MATERIALS,
BUTTON_LABEL_MATERIALS,
BUTTON_RING_MATERIALS,
} = useElevatorMaterialSet()
const state = disabled ? 'disabled' : active ? 'active' : queued ? 'queued' : 'idle' const state = disabled ? 'disabled' : active ? 'active' : queued ? 'queued' : 'idle'
const depth = active ? 0.028 : 0.04 const depth = active ? 0.028 : 0.04
const faceZ = faceSign * (depth / 2 + 0.004) const faceZ = faceSign * (depth / 2 + 0.004)
@@ -845,6 +919,7 @@ function DoorLeaf({
y: number y: number
z: number z: number
}) { }) {
const { DOOR_GROOVE_MATERIAL, DOOR_MATERIAL, GLASS_MATERIAL } = useElevatorMaterialSet()
const ref = useRef<Group>(null) const ref = useRef<Group>(null)
const getLeafX = (openAmount: number) => getElevatorDoorLeafX(side, width, openAmount, doorStyle) const getLeafX = (openAmount: number) => getElevatorDoorLeafX(side, width, openAmount, doorStyle)
const leafWidth = getElevatorDoorLeafWidth(width, doorStyle) const leafWidth = getElevatorDoorLeafWidth(width, doorStyle)
@@ -880,6 +955,7 @@ function DoorLeaf({
position={[0, height / 2 - railHeight / 2, 0]} position={[0, height / 2 - railHeight / 2, 0]}
receiveShadow receiveShadow
scale={[leafWidth, railHeight, 0.05]} scale={[leafWidth, railHeight, 0.05]}
slotId="doors"
/> />
<BoxPrimitive <BoxPrimitive
castShadow castShadow
@@ -887,6 +963,7 @@ function DoorLeaf({
position={[0, -height / 2 + railHeight / 2, 0]} position={[0, -height / 2 + railHeight / 2, 0]}
receiveShadow receiveShadow
scale={[leafWidth, railHeight, 0.05]} scale={[leafWidth, railHeight, 0.05]}
slotId="doors"
/> />
<BoxPrimitive <BoxPrimitive
castShadow castShadow
@@ -894,6 +971,7 @@ function DoorLeaf({
position={[-leafWidth / 2 + stileWidth / 2, 0, 0]} position={[-leafWidth / 2 + stileWidth / 2, 0, 0]}
receiveShadow receiveShadow
scale={[stileWidth, height, 0.05]} scale={[stileWidth, height, 0.05]}
slotId="doors"
/> />
<BoxPrimitive <BoxPrimitive
castShadow castShadow
@@ -901,11 +979,13 @@ function DoorLeaf({
position={[leafWidth / 2 - stileWidth / 2, 0, 0]} position={[leafWidth / 2 - stileWidth / 2, 0, 0]}
receiveShadow receiveShadow
scale={[stileWidth, height, 0.05]} scale={[stileWidth, height, 0.05]}
slotId="doors"
/> />
<BoxPrimitive <BoxPrimitive
material={GLASS_MATERIAL} material={GLASS_MATERIAL}
position={[0, 0, -0.004]} position={[0, 0, -0.004]}
scale={[glassWidth, glassHeight, 0.012]} scale={[glassWidth, glassHeight, 0.012]}
slotId="glass"
/> />
</> </>
) : ( ) : (
@@ -916,11 +996,13 @@ function DoorLeaf({
position={[0, 0, 0]} position={[0, 0, 0]}
receiveShadow receiveShadow
scale={[leafWidth, height, 0.05]} scale={[leafWidth, height, 0.05]}
slotId="doors"
/> />
<BoxPrimitive <BoxPrimitive
material={DOOR_GROOVE_MATERIAL} material={DOOR_GROOVE_MATERIAL}
position={[0, 0, -0.028]} position={[0, 0, -0.028]}
scale={[0.018, panelInsetHeight, 0.01]} scale={[0.018, panelInsetHeight, 0.01]}
slotId="doors"
/> />
{resolvedPanelStyle === 'segmented-panel' {resolvedPanelStyle === 'segmented-panel'
? Array.from({ length: segmentCount - 1 }).map((_, index) => ( ? Array.from({ length: segmentCount - 1 }).map((_, index) => (
@@ -929,6 +1011,7 @@ function DoorLeaf({
material={DOOR_GROOVE_MATERIAL} material={DOOR_GROOVE_MATERIAL}
position={[0, -panelInsetHeight / 2 + segmentSpacing * (index + 1), -0.03]} position={[0, -panelInsetHeight / 2 + segmentSpacing * (index + 1), -0.03]}
scale={[panelInsetWidth, 0.018, 0.012]} scale={[panelInsetWidth, 0.018, 0.012]}
slotId="doors"
/> />
)) ))
: null} : null}
@@ -936,11 +1019,13 @@ function DoorLeaf({
material={DOOR_GROOVE_MATERIAL} material={DOOR_GROOVE_MATERIAL}
position={[0, panelInsetHeight / 2, -0.029]} position={[0, panelInsetHeight / 2, -0.029]}
scale={[panelInsetWidth, 0.012, 0.01]} scale={[panelInsetWidth, 0.012, 0.01]}
slotId="doors"
/> />
<BoxPrimitive <BoxPrimitive
material={DOOR_GROOVE_MATERIAL} material={DOOR_GROOVE_MATERIAL}
position={[0, -panelInsetHeight / 2, -0.029]} position={[0, -panelInsetHeight / 2, -0.029]}
scale={[panelInsetWidth, 0.012, 0.01]} scale={[panelInsetWidth, 0.012, 0.01]}
slotId="doors"
/> />
</> </>
)} )}
@@ -1011,6 +1096,7 @@ function LandingDoorFrame({
shaftWidth: number shaftWidth: number
z: number z: number
}) { }) {
const { SHAFT_TRIM_MATERIAL, SHAFT_WALL_MATERIAL } = useElevatorMaterialSet()
const wallDepth = 0.09 const wallDepth = 0.09
const levelHeight = Math.max(levelTopY - levelY, 0.01) const levelHeight = Math.max(levelTopY - levelY, 0.01)
const jambWidth = Math.max((shaftWidth - doorWidth) / 2, 0.08) const jambWidth = Math.max((shaftWidth - doorWidth) / 2, 0.08)
@@ -1026,6 +1112,7 @@ function LandingDoorFrame({
position={[-jambCenterOffset, levelY + levelHeight / 2, z]} position={[-jambCenterOffset, levelY + levelHeight / 2, z]}
receiveShadow receiveShadow
scale={[jambWidth, levelHeight, wallDepth]} scale={[jambWidth, levelHeight, wallDepth]}
slotId="shaft"
/> />
<BoxPrimitive <BoxPrimitive
castShadow castShadow
@@ -1033,6 +1120,7 @@ function LandingDoorFrame({
position={[jambCenterOffset, levelY + levelHeight / 2, z]} position={[jambCenterOffset, levelY + levelHeight / 2, z]}
receiveShadow receiveShadow
scale={[jambWidth, levelHeight, wallDepth]} scale={[jambWidth, levelHeight, wallDepth]}
slotId="shaft"
/> />
{headerHeight > 0.01 && ( {headerHeight > 0.01 && (
<BoxPrimitive <BoxPrimitive
@@ -1041,6 +1129,7 @@ function LandingDoorFrame({
position={[0, levelY + doorHeight + headerHeight / 2, z]} position={[0, levelY + doorHeight + headerHeight / 2, z]}
receiveShadow receiveShadow
scale={[shaftWidth, headerHeight, wallDepth]} scale={[shaftWidth, headerHeight, wallDepth]}
slotId="shaft"
/> />
)} )}
<BoxPrimitive <BoxPrimitive
@@ -1049,6 +1138,7 @@ function LandingDoorFrame({
position={[0, levelY + trim / 2, z - 0.006]} position={[0, levelY + trim / 2, z - 0.006]}
receiveShadow receiveShadow
scale={[doorWidth + trim * 2, trim, wallDepth * 1.12]} scale={[doorWidth + trim * 2, trim, wallDepth * 1.12]}
slotId="shaft"
/> />
<BoxPrimitive <BoxPrimitive
castShadow castShadow
@@ -1056,6 +1146,7 @@ function LandingDoorFrame({
position={[-doorWidth / 2 - trim / 2, levelY + doorHeight / 2, z - 0.006]} position={[-doorWidth / 2 - trim / 2, levelY + doorHeight / 2, z - 0.006]}
receiveShadow receiveShadow
scale={[trim, doorHeight, wallDepth * 1.12]} scale={[trim, doorHeight, wallDepth * 1.12]}
slotId="shaft"
/> />
<BoxPrimitive <BoxPrimitive
castShadow castShadow
@@ -1063,6 +1154,7 @@ function LandingDoorFrame({
position={[doorWidth / 2 + trim / 2, levelY + doorHeight / 2, z - 0.006]} position={[doorWidth / 2 + trim / 2, levelY + doorHeight / 2, z - 0.006]}
receiveShadow receiveShadow
scale={[trim, doorHeight, wallDepth * 1.12]} scale={[trim, doorHeight, wallDepth * 1.12]}
slotId="shaft"
/> />
<BoxPrimitive <BoxPrimitive
castShadow castShadow
@@ -1070,6 +1162,7 @@ function LandingDoorFrame({
position={[0, levelY + doorHeight + trim / 2, z - 0.006]} position={[0, levelY + doorHeight + trim / 2, z - 0.006]}
receiveShadow receiveShadow
scale={[doorWidth + trim * 2, trim, wallDepth * 1.12]} scale={[doorWidth + trim * 2, trim, wallDepth * 1.12]}
slotId="shaft"
/> />
</> </>
) )
@@ -1119,6 +1212,7 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
const shading = useViewer((state) => state.shading) const shading = useViewer((state) => state.shading)
const textures = useViewer((state) => state.textures) const textures = useViewer((state) => state.textures)
const colorPreset = useViewer((state) => state.colorPreset) const colorPreset = useViewer((state) => state.colorPreset)
const sceneMaterials = useScene((state) => state.materials)
const liveOverrides = useLiveNodeOverrides((state) => state.get(node.id)) const liveOverrides = useLiveNodeOverrides((state) => state.get(node.id))
const liveTransform = useLiveTransforms((state) => state.get(node.id)) const liveTransform = useLiveTransforms((state) => state.get(node.id))
const renderNode = useMemo( const renderNode = useMemo(
@@ -1128,8 +1222,19 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
const levelContextNodes = useScene( const levelContextNodes = useScene(
useShallow((state) => getElevatorLevelContextNodes(renderNode, state.nodes)), useShallow((state) => getElevatorLevelContextNodes(renderNode, state.nodes)),
) )
const materials = useMemo(
setElevatorMaterials(shading, textures, colorPreset) () => getResolvedElevatorMaterials(renderNode, shading, textures, colorPreset, sceneMaterials),
[colorPreset, renderNode, sceneMaterials, shading, textures],
)
const {
CAB_MATERIAL,
GLASS_MATERIAL,
LANDING_PANEL_MATERIAL,
PANEL_MATERIAL,
QUEUE_STRIP_MATERIALS,
SHAFT_SIDE_MATERIAL,
SHAFT_TRIM_MATERIAL,
} = materials
useRegistry(node.id, 'elevator', ref) useRegistry(node.id, 'elevator', ref)
@@ -1174,6 +1279,7 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
const doorStyle = getResolvedDoorStyle(renderNode.doorStyle) const doorStyle = getResolvedDoorStyle(renderNode.doorStyle)
const shaftStyle = getResolvedShaftStyle(renderNode.shaftStyle) const shaftStyle = getResolvedShaftStyle(renderNode.shaftStyle)
const shaftShellMaterial = shaftStyle === 'glass' ? GLASS_MATERIAL : SHAFT_SIDE_MATERIAL const shaftShellMaterial = shaftStyle === 'glass' ? GLASS_MATERIAL : SHAFT_SIDE_MATERIAL
const shaftShellSlotId: ElevatorSlotId = shaftStyle === 'glass' ? 'glass' : 'shaft'
const shaftTopMaterial = shaftStyle === 'glass' ? SHAFT_TRIM_MATERIAL : SHAFT_SIDE_MATERIAL const shaftTopMaterial = shaftStyle === 'glass' ? SHAFT_TRIM_MATERIAL : SHAFT_SIDE_MATERIAL
const shaftHeight = Math.max(totalHeight, cabHeight + 0.3) const shaftHeight = Math.max(totalHeight, cabHeight + 0.3)
const shaftBodyHeight = Math.max(shaftHeight - shaftWallThickness, 0.01) const shaftBodyHeight = Math.max(shaftHeight - shaftWallThickness, 0.01)
@@ -1269,19 +1375,21 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
) )
return ( return (
<group <ElevatorMaterialsContext.Provider value={materials}>
position={liveTransform?.position ?? renderNode.position} <group
ref={ref} position={liveTransform?.position ?? renderNode.position}
rotation-y={liveTransform?.rotation ?? renderNode.rotation} ref={ref}
visible={renderNode.visible} rotation-y={liveTransform?.rotation ?? renderNode.rotation}
{...handlers} visible={renderNode.visible}
> {...handlers}
>
<BoxPrimitive <BoxPrimitive
castShadow castShadow
material={shaftShellMaterial} material={shaftShellMaterial}
position={[0, shaftBodyCenterY, shaftDepth / 2 + shaftWallThickness / 2]} position={[0, shaftBodyCenterY, shaftDepth / 2 + shaftWallThickness / 2]}
receiveShadow receiveShadow
scale={[shaftWidth + shaftWallThickness * 2, shaftBodyHeight, shaftWallThickness]} scale={[shaftWidth + shaftWallThickness * 2, shaftBodyHeight, shaftWallThickness]}
slotId={shaftShellSlotId}
/> />
<BoxPrimitive <BoxPrimitive
castShadow castShadow
@@ -1289,6 +1397,7 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
position={[-shaftWidth / 2 - shaftWallThickness / 2, shaftBodyCenterY, 0]} position={[-shaftWidth / 2 - shaftWallThickness / 2, shaftBodyCenterY, 0]}
receiveShadow receiveShadow
scale={[shaftWallThickness, shaftBodyHeight, shaftDepth + shaftWallThickness * 2]} scale={[shaftWallThickness, shaftBodyHeight, shaftDepth + shaftWallThickness * 2]}
slotId={shaftShellSlotId}
/> />
<BoxPrimitive <BoxPrimitive
castShadow castShadow
@@ -1296,6 +1405,7 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
position={[shaftWidth / 2 + shaftWallThickness / 2, shaftBodyCenterY, 0]} position={[shaftWidth / 2 + shaftWallThickness / 2, shaftBodyCenterY, 0]}
receiveShadow receiveShadow
scale={[shaftWallThickness, shaftBodyHeight, shaftDepth + shaftWallThickness * 2]} scale={[shaftWallThickness, shaftBodyHeight, shaftDepth + shaftWallThickness * 2]}
slotId={shaftShellSlotId}
/> />
<BoxPrimitive <BoxPrimitive
castShadow castShadow
@@ -1307,6 +1417,7 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
shaftWallThickness, shaftWallThickness,
shaftDepth + shaftWallThickness * 2, shaftDepth + shaftWallThickness * 2,
]} ]}
slotId="shaft"
/> />
<group ref={cabRef} position={[0, cabBaseY, 0]}> <group ref={cabRef} position={[0, cabBaseY, 0]}>
@@ -1316,6 +1427,7 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
position={[0, 0.04, cabCenterZ]} position={[0, 0.04, cabCenterZ]}
receiveShadow receiveShadow
scale={[cabWidth, 0.08, cabDepth]} scale={[cabWidth, 0.08, cabDepth]}
slotId="cab"
/> />
<BoxPrimitive <BoxPrimitive
@@ -1324,6 +1436,7 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
position={[0, cabHeight - 0.04, cabCenterZ]} position={[0, cabHeight - 0.04, cabCenterZ]}
receiveShadow receiveShadow
scale={[cabWidth, 0.08, cabDepth]} scale={[cabWidth, 0.08, cabDepth]}
slotId="cab"
/> />
<BoxPrimitive <BoxPrimitive
@@ -1332,6 +1445,7 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
position={[0, cabHeight / 2, cabCenterZ + cabDepth / 2 - 0.04]} position={[0, cabHeight / 2, cabCenterZ + cabDepth / 2 - 0.04]}
receiveShadow receiveShadow
scale={[cabWidth, cabHeight, 0.08]} scale={[cabWidth, cabHeight, 0.08]}
slotId="cab"
/> />
<BoxPrimitive <BoxPrimitive
@@ -1340,6 +1454,7 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
position={[-cabWidth / 2 + 0.04, cabHeight / 2, cabCenterZ]} position={[-cabWidth / 2 + 0.04, cabHeight / 2, cabCenterZ]}
receiveShadow receiveShadow
scale={[0.08, cabHeight, cabDepth]} scale={[0.08, cabHeight, cabDepth]}
slotId="cab"
/> />
<BoxPrimitive <BoxPrimitive
@@ -1348,6 +1463,7 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
position={[cabWidth / 2 - 0.04, cabHeight / 2, cabCenterZ]} position={[cabWidth / 2 - 0.04, cabHeight / 2, cabCenterZ]}
receiveShadow receiveShadow
scale={[0.08, cabHeight, cabDepth]} scale={[0.08, cabHeight, cabDepth]}
slotId="cab"
/> />
<ElevatorDoorLeaves <ElevatorDoorLeaves
@@ -1479,7 +1595,8 @@ export const ElevatorRenderer = ({ node }: { node: ElevatorNode }) => {
</group> </group>
) )
})} })}
</group> </group>
</ElevatorMaterialsContext.Provider>
) )
} }
+29
View File
@@ -0,0 +1,29 @@
import {
type ElevatorNode,
getResolvedElevatorDoorPanelStyle,
getResolvedElevatorShaftStyle,
type SlotDeclaration,
} from '@pascal-app/core'
export type ElevatorSlotId = 'cab' | 'doors' | 'shaft' | 'glass'
export const ELEVATOR_CAB_SLOT_DEFAULT = 'library:preset-softwhite'
export const ELEVATOR_DOORS_SLOT_DEFAULT = 'library:metal-steel'
export const ELEVATOR_SHAFT_SLOT_DEFAULT = 'library:preset-lightgrey'
export const ELEVATOR_GLASS_SLOT_DEFAULT = 'library:preset-glass'
export function elevatorSlots(node: ElevatorNode): SlotDeclaration[] {
const slots: SlotDeclaration[] = [
{ slotId: 'cab', label: 'Cab', default: ELEVATOR_CAB_SLOT_DEFAULT },
{ slotId: 'doors', label: 'Doors', default: ELEVATOR_DOORS_SLOT_DEFAULT },
{ slotId: 'shaft', label: 'Shaft', default: ELEVATOR_SHAFT_SLOT_DEFAULT },
]
const hasGlass =
getResolvedElevatorShaftStyle(node.shaftStyle) === 'glass' ||
getResolvedElevatorDoorPanelStyle(node.doorPanelStyle) === 'glass-frame'
if (hasGlass) slots.push({ slotId: 'glass', label: 'Glass', default: ELEVATOR_GLASS_SLOT_DEFAULT })
return slots
}