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:
co-authored by
Claude Opus 4.8
parent
a8f32dd47a
commit
1199c04fac
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user