Add paint hover previews to viewer
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
import { type CeilingNode, getMaterialPresetByRef, resolveMaterial, useRegistry } from '@pascal-app/core'
|
||||
import {
|
||||
type CeilingNode,
|
||||
getMaterialPresetByRef,
|
||||
resolveMaterial,
|
||||
useRegistry,
|
||||
} from '@pascal-app/core'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { float, mix, positionWorld, smoothstep } from 'three/tsl'
|
||||
import { BackSide, FrontSide, type Mesh, MeshBasicNodeMaterial } from 'three/webgpu'
|
||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||
import useViewer from '../../../store/use-viewer'
|
||||
import { NodeRenderer } from '../node-renderer'
|
||||
|
||||
const gridScale = 5
|
||||
@@ -32,18 +38,47 @@ function createCeilingMaterials(color = '#999999') {
|
||||
return { topMaterial, bottomMaterial }
|
||||
}
|
||||
|
||||
const ceilingMaterialCache = new Map<string, ReturnType<typeof createCeilingMaterials>>()
|
||||
|
||||
function getCeilingMaterials(color = '#999999') {
|
||||
const cacheKey = color
|
||||
const cached = ceilingMaterialCache.get(cacheKey)
|
||||
if (cached) return cached
|
||||
|
||||
const materials = createCeilingMaterials(color)
|
||||
ceilingMaterialCache.set(cacheKey, materials)
|
||||
return materials
|
||||
}
|
||||
|
||||
export const CeilingRenderer = ({ node }: { node: CeilingNode }) => {
|
||||
const ref = useRef<Mesh>(null!)
|
||||
|
||||
useRegistry(node.id, 'ceiling', ref)
|
||||
const handlers = useNodeEvents(node, 'ceiling')
|
||||
const materialPreview = useViewer((state) =>
|
||||
state.materialPreview?.target === 'ceiling' && state.materialPreview.nodeId === node.id
|
||||
? state.materialPreview
|
||||
: null,
|
||||
)
|
||||
|
||||
const materials = useMemo(() => {
|
||||
const preset = getMaterialPresetByRef(node.materialPreset)
|
||||
const props = preset?.mapProperties ?? resolveMaterial(node.material)
|
||||
const preset = getMaterialPresetByRef(materialPreview?.materialPreset ?? node.materialPreset)
|
||||
const props =
|
||||
preset?.mapProperties ?? resolveMaterial(materialPreview?.material ?? node.material)
|
||||
const color = props.color || '#999999'
|
||||
return createCeilingMaterials(color)
|
||||
}, [node.materialPreset, node.material, node.material?.preset, node.material?.properties, node.material?.texture])
|
||||
return getCeilingMaterials(color)
|
||||
}, [
|
||||
node.materialPreset,
|
||||
node.material,
|
||||
node.material?.preset,
|
||||
node.material?.properties,
|
||||
node.material?.texture,
|
||||
materialPreview?.materialPreset,
|
||||
materialPreview?.material,
|
||||
materialPreview?.material?.preset,
|
||||
materialPreview?.material?.properties,
|
||||
materialPreview?.material?.texture,
|
||||
])
|
||||
|
||||
return (
|
||||
<mesh material={materials.bottomMaterial} ref={ref}>
|
||||
|
||||
@@ -7,14 +7,22 @@ import {
|
||||
createMaterialFromPresetRef,
|
||||
DEFAULT_STAIR_MATERIAL,
|
||||
} from '../../../lib/materials'
|
||||
import useViewer from '../../../store/use-viewer'
|
||||
|
||||
export const FenceRenderer = ({ node }: { node: FenceNode }) => {
|
||||
const ref = useRef<Mesh>(null!)
|
||||
const handlers = useNodeEvents(node, 'fence')
|
||||
const materialPreview = useViewer((state) =>
|
||||
state.materialPreview?.target === 'fence' && state.materialPreview.nodeId === node.id
|
||||
? state.materialPreview
|
||||
: null,
|
||||
)
|
||||
const material = useMemo(() => {
|
||||
const presetMaterial = createMaterialFromPresetRef(node.materialPreset)
|
||||
const presetMaterial = createMaterialFromPresetRef(
|
||||
materialPreview?.materialPreset ?? node.materialPreset,
|
||||
)
|
||||
if (presetMaterial) return presetMaterial
|
||||
const mat = node.material
|
||||
const mat = materialPreview?.material ?? node.material
|
||||
if (!mat) return DEFAULT_STAIR_MATERIAL
|
||||
return createMaterial(mat)
|
||||
}, [
|
||||
@@ -23,6 +31,11 @@ export const FenceRenderer = ({ node }: { node: FenceNode }) => {
|
||||
node.material?.preset,
|
||||
node.material?.properties,
|
||||
node.material?.texture,
|
||||
materialPreview?.materialPreset,
|
||||
materialPreview?.material,
|
||||
materialPreview?.material?.preset,
|
||||
materialPreview?.material?.properties,
|
||||
materialPreview?.material?.texture,
|
||||
])
|
||||
|
||||
useRegistry(node.id, 'fence', ref)
|
||||
@@ -31,7 +44,14 @@ export const FenceRenderer = ({ node }: { node: FenceNode }) => {
|
||||
}, [node.id])
|
||||
|
||||
return (
|
||||
<mesh castShadow material={material} receiveShadow ref={ref} visible={node.visible} {...handlers}>
|
||||
<mesh
|
||||
castShadow
|
||||
material={material}
|
||||
receiveShadow
|
||||
ref={ref}
|
||||
visible={node.visible}
|
||||
{...handlers}
|
||||
>
|
||||
<boxGeometry args={[0, 0, 0]} />
|
||||
</mesh>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { type AnyNodeId, type RoofNode, type RoofSegmentNode, useRegistry, useScene } from '@pascal-app/core'
|
||||
import {
|
||||
type AnyNodeId,
|
||||
type RoofNode,
|
||||
type RoofSegmentNode,
|
||||
useRegistry,
|
||||
useScene,
|
||||
} from '@pascal-app/core'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import * as THREE from 'three'
|
||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||
@@ -14,8 +20,14 @@ export const RoofSegmentRenderer = ({ node }: { node: RoofSegmentNode }) => {
|
||||
|
||||
const handlers = useNodeEvents(node, 'roof-segment')
|
||||
const debugColors = useViewer((s) => s.debugColors)
|
||||
const parentNode =
|
||||
node.parentId ? (nodes[node.parentId as AnyNodeId] as RoofNode | undefined) : undefined
|
||||
const parentNode = node.parentId
|
||||
? (nodes[node.parentId as AnyNodeId] as RoofNode | undefined)
|
||||
: undefined
|
||||
const materialPreview = useViewer((state) =>
|
||||
state.materialPreview?.target === 'roof' && state.materialPreview.nodeId === parentNode?.id
|
||||
? state.materialPreview
|
||||
: null,
|
||||
)
|
||||
const placeholderGeometry = useMemo(() => {
|
||||
const geometry = new THREE.BufferGeometry()
|
||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3))
|
||||
@@ -26,30 +38,40 @@ export const RoofSegmentRenderer = ({ node }: { node: RoofSegmentNode }) => {
|
||||
return geometry
|
||||
}, [])
|
||||
|
||||
const previewParentNode = !parentNode
|
||||
? undefined
|
||||
: materialPreview?.role === 'top'
|
||||
? {
|
||||
...parentNode,
|
||||
topMaterial: materialPreview.material,
|
||||
topMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: materialPreview?.role === 'edge'
|
||||
? {
|
||||
...parentNode,
|
||||
edgeMaterial: materialPreview.material,
|
||||
edgeMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: materialPreview?.role === 'wall'
|
||||
? {
|
||||
...parentNode,
|
||||
wallMaterial: materialPreview.material,
|
||||
wallMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: parentNode
|
||||
const customMaterial = useMemo(() => {
|
||||
if (node.material !== undefined || typeof node.materialPreset === 'string') {
|
||||
return null
|
||||
}
|
||||
|
||||
return parentNode ? getRoofMaterialArray(parentNode) : null
|
||||
}, [
|
||||
node.materialPreset,
|
||||
node.material,
|
||||
node.material?.preset,
|
||||
node.material?.properties,
|
||||
node.material?.texture,
|
||||
parentNode?.materialPreset,
|
||||
parentNode?.material,
|
||||
parentNode?.material?.preset,
|
||||
parentNode?.material?.properties,
|
||||
parentNode?.material?.texture,
|
||||
parentNode?.topMaterial,
|
||||
parentNode?.topMaterialPreset,
|
||||
parentNode?.edgeMaterial,
|
||||
parentNode?.edgeMaterialPreset,
|
||||
parentNode?.wallMaterial,
|
||||
parentNode?.wallMaterialPreset,
|
||||
])
|
||||
return previewParentNode ? getRoofMaterialArray(previewParentNode) : null
|
||||
}, [node, previewParentNode])
|
||||
|
||||
const material = debugColors ? roofDebugMaterials : customMaterial || roofMaterials
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@ export const RoofRenderer = ({ node }: { node: RoofNode }) => {
|
||||
|
||||
const handlers = useNodeEvents(node, 'roof')
|
||||
const debugColors = useViewer((s) => s.debugColors)
|
||||
const materialPreview = useViewer((state) =>
|
||||
state.materialPreview?.target === 'roof' && state.materialPreview.nodeId === node.id
|
||||
? state.materialPreview
|
||||
: null,
|
||||
)
|
||||
const placeholderGeometry = useMemo(() => {
|
||||
const geometry = new THREE.BufferGeometry()
|
||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3))
|
||||
@@ -24,22 +29,33 @@ export const RoofRenderer = ({ node }: { node: RoofNode }) => {
|
||||
return geometry
|
||||
}, [])
|
||||
|
||||
const customMaterial = useMemo(
|
||||
() => getRoofMaterialArray(node),
|
||||
[
|
||||
node.materialPreset,
|
||||
node.material,
|
||||
node.material?.preset,
|
||||
node.material?.properties,
|
||||
node.material?.texture,
|
||||
node.topMaterial,
|
||||
node.topMaterialPreset,
|
||||
node.edgeMaterial,
|
||||
node.edgeMaterialPreset,
|
||||
node.wallMaterial,
|
||||
node.wallMaterialPreset,
|
||||
],
|
||||
)
|
||||
const previewNode =
|
||||
materialPreview?.role === 'top'
|
||||
? {
|
||||
...node,
|
||||
topMaterial: materialPreview.material,
|
||||
topMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: materialPreview?.role === 'edge'
|
||||
? {
|
||||
...node,
|
||||
edgeMaterial: materialPreview.material,
|
||||
edgeMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: materialPreview?.role === 'wall'
|
||||
? {
|
||||
...node,
|
||||
wallMaterial: materialPreview.material,
|
||||
wallMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: node
|
||||
const customMaterial = useMemo(() => getRoofMaterialArray(previewNode), [previewNode])
|
||||
|
||||
const material = debugColors ? roofDebugMaterials : customMaterial || roofMaterials
|
||||
|
||||
|
||||
@@ -1,13 +1,50 @@
|
||||
import { getMaterialPresetByRef, type SlabNode, useRegistry } from '@pascal-app/core'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import * as THREE from 'three'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import type { Mesh } from 'three'
|
||||
import * as THREE from 'three'
|
||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||
import {
|
||||
applyMaterialPresetToMaterials,
|
||||
createMaterial,
|
||||
DEFAULT_SLAB_MATERIAL,
|
||||
} from '../../../lib/materials'
|
||||
import useViewer from '../../../store/use-viewer'
|
||||
|
||||
const slabMaterialCache = new Map<string, THREE.MeshStandardMaterial>()
|
||||
|
||||
function getSlabMaterial(
|
||||
cacheKey: string,
|
||||
params: { material?: SlabNode['material']; materialPreset?: string },
|
||||
) {
|
||||
const cached = slabMaterialCache.get(cacheKey)
|
||||
if (cached) return cached
|
||||
|
||||
const preset = getMaterialPresetByRef(params.materialPreset)
|
||||
const slabMaterial = preset
|
||||
? new THREE.MeshStandardMaterial()
|
||||
: params.material
|
||||
? createMaterial(params.material).clone()
|
||||
: DEFAULT_SLAB_MATERIAL.clone()
|
||||
|
||||
if (preset) {
|
||||
// Apply the preset to the slab-owned material so async texture loads update
|
||||
// the instance we actually render after refresh as well.
|
||||
applyMaterialPresetToMaterials(slabMaterial, preset)
|
||||
}
|
||||
|
||||
// Slabs participate in the WebGPU MRT scene pass. Keeping them opaque avoids
|
||||
// pipeline variants that can fail when geometry is regenerated while a
|
||||
// transparent/custom material is attached.
|
||||
slabMaterial.transparent = false
|
||||
slabMaterial.opacity = 1
|
||||
slabMaterial.alphaMap = null
|
||||
slabMaterial.side = THREE.DoubleSide
|
||||
slabMaterial.depthWrite = true
|
||||
slabMaterial.needsUpdate = true
|
||||
|
||||
slabMaterialCache.set(cacheKey, slabMaterial)
|
||||
return slabMaterial
|
||||
}
|
||||
|
||||
export const SlabRenderer = ({ node }: { node: SlabNode }) => {
|
||||
const ref = useRef<Mesh>(null!)
|
||||
@@ -15,46 +52,37 @@ export const SlabRenderer = ({ node }: { node: SlabNode }) => {
|
||||
useRegistry(node.id, 'slab', ref)
|
||||
|
||||
const handlers = useNodeEvents(node, 'slab')
|
||||
const materialPreview = useViewer((state) =>
|
||||
state.materialPreview?.target === 'slab' && state.materialPreview.nodeId === node.id
|
||||
? state.materialPreview
|
||||
: null,
|
||||
)
|
||||
|
||||
const material = useMemo(() => {
|
||||
const preset = getMaterialPresetByRef(node.materialPreset)
|
||||
const slabMaterial = preset
|
||||
? new THREE.MeshStandardMaterial()
|
||||
: node.material
|
||||
? createMaterial(node.material).clone()
|
||||
: DEFAULT_SLAB_MATERIAL.clone()
|
||||
const resolvedMaterial = materialPreview?.material ?? node.material
|
||||
const resolvedMaterialPreset = materialPreview?.materialPreset ?? node.materialPreset
|
||||
const cacheKey = JSON.stringify({
|
||||
material: resolvedMaterial ?? null,
|
||||
materialPreset: resolvedMaterialPreset ?? null,
|
||||
})
|
||||
|
||||
if (preset) {
|
||||
// Apply the preset to the slab-owned material so async texture loads update
|
||||
// the instance we actually render after refresh as well.
|
||||
applyMaterialPresetToMaterials(slabMaterial, preset)
|
||||
}
|
||||
|
||||
// Slabs participate in the WebGPU MRT scene pass. Keeping them opaque avoids
|
||||
// pipeline variants that can fail when geometry is regenerated while a
|
||||
// transparent/custom material is attached.
|
||||
slabMaterial.transparent = false
|
||||
slabMaterial.opacity = 1
|
||||
slabMaterial.alphaMap = null
|
||||
slabMaterial.side = THREE.DoubleSide
|
||||
slabMaterial.depthWrite = true
|
||||
slabMaterial.needsUpdate = true
|
||||
|
||||
return slabMaterial
|
||||
return getSlabMaterial(cacheKey, {
|
||||
material: resolvedMaterial,
|
||||
materialPreset: resolvedMaterialPreset,
|
||||
})
|
||||
}, [
|
||||
node.material,
|
||||
node.material?.preset,
|
||||
node.material?.properties,
|
||||
node.material?.texture,
|
||||
node.materialPreset,
|
||||
materialPreview?.material,
|
||||
materialPreview?.material?.preset,
|
||||
materialPreview?.material?.properties,
|
||||
materialPreview?.material?.texture,
|
||||
materialPreview?.materialPreset,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
material.dispose()
|
||||
}
|
||||
}, [material])
|
||||
|
||||
return (
|
||||
<mesh
|
||||
castShadow
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { type AnyNodeId, type StairNode, type StairSegmentNode, useRegistry, useScene } from '@pascal-app/core'
|
||||
import {
|
||||
type AnyNodeId,
|
||||
type StairNode,
|
||||
type StairSegmentNode,
|
||||
useRegistry,
|
||||
useScene,
|
||||
} from '@pascal-app/core'
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
||||
import * as THREE from 'three'
|
||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||
import useViewer from '../../../store/use-viewer'
|
||||
import { getStraightStairSegmentBodyMaterials } from '../../../systems/stair/stair-materials'
|
||||
|
||||
export const StairSegmentRenderer = ({ node }: { node: StairSegmentNode }) => {
|
||||
@@ -15,29 +22,45 @@ export const StairSegmentRenderer = ({ node }: { node: StairSegmentNode }) => {
|
||||
}, [node.id])
|
||||
|
||||
const handlers = useNodeEvents(node, 'stair-segment')
|
||||
const parentNode =
|
||||
node.parentId ? (nodes[node.parentId as AnyNodeId] as StairNode | undefined) : undefined
|
||||
const parentNode = node.parentId
|
||||
? (nodes[node.parentId as AnyNodeId] as StairNode | undefined)
|
||||
: undefined
|
||||
const materialPreview = useViewer((state) =>
|
||||
state.materialPreview?.target === 'stair' && state.materialPreview.nodeId === parentNode?.id
|
||||
? state.materialPreview
|
||||
: null,
|
||||
)
|
||||
const previewParentNode = !parentNode
|
||||
? undefined
|
||||
: materialPreview?.role === 'railing'
|
||||
? {
|
||||
...parentNode,
|
||||
railingMaterial: materialPreview.material,
|
||||
railingMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: materialPreview?.role === 'tread'
|
||||
? {
|
||||
...parentNode,
|
||||
treadMaterial: materialPreview.material,
|
||||
treadMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: materialPreview?.role === 'side'
|
||||
? {
|
||||
...parentNode,
|
||||
sideMaterial: materialPreview.material,
|
||||
sideMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: parentNode
|
||||
|
||||
const material = useMemo(() => {
|
||||
return getStraightStairSegmentBodyMaterials(node, parentNode)
|
||||
}, [
|
||||
node.materialPreset,
|
||||
node.material,
|
||||
node.material?.preset,
|
||||
node.material?.properties,
|
||||
node.material?.texture,
|
||||
parentNode?.materialPreset,
|
||||
parentNode?.material,
|
||||
parentNode?.material?.preset,
|
||||
parentNode?.material?.properties,
|
||||
parentNode?.material?.texture,
|
||||
parentNode?.railingMaterialPreset,
|
||||
parentNode?.railingMaterial,
|
||||
parentNode?.sideMaterialPreset,
|
||||
parentNode?.sideMaterial,
|
||||
parentNode?.treadMaterialPreset,
|
||||
parentNode?.treadMaterial,
|
||||
])
|
||||
return getStraightStairSegmentBodyMaterials(node, previewParentNode)
|
||||
}, [node, previewParentNode])
|
||||
|
||||
const placeholderGeometry = useMemo(() => {
|
||||
const geometry = new THREE.BufferGeometry()
|
||||
|
||||
@@ -8,10 +8,15 @@ import {
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
||||
import * as THREE from 'three'
|
||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||
import { createMaterial, createMaterialFromPresetRef, DEFAULT_STAIR_MATERIAL } from '../../../lib/materials'
|
||||
import {
|
||||
getStairRailingMaterial,
|
||||
createMaterial,
|
||||
createMaterialFromPresetRef,
|
||||
DEFAULT_STAIR_MATERIAL,
|
||||
} from '../../../lib/materials'
|
||||
import useViewer from '../../../store/use-viewer'
|
||||
import {
|
||||
getStairBodyMaterials,
|
||||
getStairRailingMaterial,
|
||||
type StairBodyMaterials,
|
||||
} from '../../../systems/stair/stair-materials'
|
||||
import { NodeRenderer } from '../node-renderer'
|
||||
@@ -57,48 +62,55 @@ export const StairRenderer = ({ node }: { node: StairNode }) => {
|
||||
}, [node.id])
|
||||
|
||||
const handlers = useNodeEvents(node, 'stair')
|
||||
const materialPreview = useViewer((state) =>
|
||||
state.materialPreview?.target === 'stair' && state.materialPreview.nodeId === node.id
|
||||
? state.materialPreview
|
||||
: null,
|
||||
)
|
||||
const previewNode =
|
||||
materialPreview?.role === 'railing'
|
||||
? {
|
||||
...node,
|
||||
railingMaterial: materialPreview.material,
|
||||
railingMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: materialPreview?.role === 'tread'
|
||||
? {
|
||||
...node,
|
||||
treadMaterial: materialPreview.material,
|
||||
treadMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: materialPreview?.role === 'side'
|
||||
? {
|
||||
...node,
|
||||
sideMaterial: materialPreview.material,
|
||||
sideMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: node
|
||||
|
||||
const material = useMemo(() => {
|
||||
const presetMaterial = createMaterialFromPresetRef(node.materialPreset)
|
||||
const presetMaterial = createMaterialFromPresetRef(previewNode.materialPreset)
|
||||
if (presetMaterial) return presetMaterial
|
||||
const mat = node.material
|
||||
const mat = previewNode.material
|
||||
if (!mat) return DEFAULT_STAIR_MATERIAL
|
||||
return createMaterial(mat)
|
||||
}, [
|
||||
node.materialPreset,
|
||||
node.material,
|
||||
node.material?.preset,
|
||||
node.material?.properties,
|
||||
node.material?.texture,
|
||||
previewNode.materialPreset,
|
||||
previewNode.material,
|
||||
previewNode.material?.preset,
|
||||
previewNode.material?.properties,
|
||||
previewNode.material?.texture,
|
||||
])
|
||||
|
||||
const straightBodyMaterials = useMemo(
|
||||
() => getStairBodyMaterials(node),
|
||||
[
|
||||
node.material,
|
||||
node.materialPreset,
|
||||
node.railingMaterial,
|
||||
node.railingMaterialPreset,
|
||||
node.sideMaterial,
|
||||
node.sideMaterialPreset,
|
||||
node.treadMaterial,
|
||||
node.treadMaterialPreset,
|
||||
],
|
||||
)
|
||||
const straightBodyMaterials = useMemo(() => getStairBodyMaterials(previewNode), [previewNode])
|
||||
|
||||
const railingMaterial = useMemo(
|
||||
() => getStairRailingMaterial(node),
|
||||
[
|
||||
node.material,
|
||||
node.materialPreset,
|
||||
node.railingMaterial,
|
||||
node.railingMaterialPreset,
|
||||
node.sideMaterial,
|
||||
node.sideMaterialPreset,
|
||||
node.treadMaterial,
|
||||
node.treadMaterialPreset,
|
||||
],
|
||||
)
|
||||
const railingMaterial = useMemo(() => getStairRailingMaterial(previewNode), [previewNode])
|
||||
|
||||
const straightPlaceholderGeometry = useMemo(() => {
|
||||
const geometry = new THREE.BufferGeometry()
|
||||
@@ -132,7 +144,9 @@ export const StairRenderer = ({ node }: { node: StairNode }) => {
|
||||
receiveShadow
|
||||
/>
|
||||
) : null}
|
||||
{!isSegmentBasedStair ? <CurvedStairBody bodyMaterials={straightBodyMaterials} stair={node} /> : null}
|
||||
{!isSegmentBasedStair ? (
|
||||
<CurvedStairBody bodyMaterials={straightBodyMaterials} stair={node} />
|
||||
) : null}
|
||||
<StairRailings material={railingMaterial} stair={node} />
|
||||
{isSegmentBasedStair ? (
|
||||
<group name="segments-wrapper" visible={false}>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useRegistry, useScene, type WallNode } from '@pascal-app/core'
|
||||
import { useLayoutEffect, useRef } from 'react'
|
||||
import type { Mesh } from 'three'
|
||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||
import useViewer from '../../../store/use-viewer'
|
||||
import { getVisibleWallMaterials } from '../../../systems/wall/wall-materials'
|
||||
import { NodeRenderer } from '../node-renderer'
|
||||
|
||||
@@ -15,7 +16,30 @@ export const WallRenderer = ({ node }: { node: WallNode }) => {
|
||||
}, [node.id])
|
||||
|
||||
const handlers = useNodeEvents(node, 'wall')
|
||||
const material = getVisibleWallMaterials(node)
|
||||
const materialPreview = useViewer((state) =>
|
||||
state.materialPreview?.target === 'wall' && state.materialPreview.nodeId === node.id
|
||||
? state.materialPreview
|
||||
: null,
|
||||
)
|
||||
const previewNode =
|
||||
materialPreview && materialPreview.role === 'interior'
|
||||
? {
|
||||
...node,
|
||||
interiorMaterial: materialPreview.material,
|
||||
interiorMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: materialPreview && materialPreview.role === 'exterior'
|
||||
? {
|
||||
...node,
|
||||
exteriorMaterial: materialPreview.material,
|
||||
exteriorMaterialPreset: materialPreview.materialPreset,
|
||||
material: undefined,
|
||||
materialPreset: undefined,
|
||||
}
|
||||
: node
|
||||
const material = getVisibleWallMaterials(previewNode)
|
||||
|
||||
return (
|
||||
<mesh castShadow material={material} receiveShadow ref={ref} visible={node.visible}>
|
||||
|
||||
Reference in New Issue
Block a user