Refactor viewer hover styles and remove preview state
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
|||||||
spatialGridManager,
|
spatialGridManager,
|
||||||
useScene,
|
useScene,
|
||||||
} from '@pascal-app/core'
|
} from '@pascal-app/core'
|
||||||
import { InteractiveSystem, useViewer, Viewer } from '@pascal-app/viewer'
|
import { type HoverStyles, InteractiveSystem, useViewer, Viewer } from '@pascal-app/viewer'
|
||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
@@ -75,6 +75,17 @@ const PAINT_CURSOR_BADGE_COLOR = '#f59e0b'
|
|||||||
const PAINT_CURSOR_BADGE_DISABLED_COLOR = '#94a3b8'
|
const PAINT_CURSOR_BADGE_DISABLED_COLOR = '#94a3b8'
|
||||||
const PAINT_CURSOR_BADGE_OFFSET_X = 14
|
const PAINT_CURSOR_BADGE_OFFSET_X = 14
|
||||||
const PAINT_CURSOR_BADGE_OFFSET_Y = 14
|
const PAINT_CURSOR_BADGE_OFFSET_Y = 14
|
||||||
|
const EDITOR_HOVER_STYLES: HoverStyles = {
|
||||||
|
default: { visibleColor: 0x00_aaff, hiddenColor: 0xf3_ff47, strength: 5, pulse: true },
|
||||||
|
delete: { visibleColor: 0xef_4444, hiddenColor: 0x99_1b1b, strength: 6, pulse: false },
|
||||||
|
'paint-ready': { visibleColor: 0xf5_9e0b, hiddenColor: 0xfd_e068, strength: 5, pulse: true },
|
||||||
|
'paint-disabled': {
|
||||||
|
visibleColor: 0x94_a3b8,
|
||||||
|
hiddenColor: 0x47_5569,
|
||||||
|
strength: 4,
|
||||||
|
pulse: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wire up module-level singletons (spatial grid, space detection, SFX) for
|
* Wire up module-level singletons (spatial grid, space detection, SFX) for
|
||||||
@@ -885,7 +896,10 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<SelectionPersistenceManager enabled={hasLoadedInitialScene && !showLoader} />
|
<SelectionPersistenceManager enabled={hasLoadedInitialScene && !showLoader} />
|
||||||
<Viewer selectionManager={isFirstPersonMode ? 'default' : 'custom'}>
|
<Viewer
|
||||||
|
hoverStyles={EDITOR_HOVER_STYLES}
|
||||||
|
selectionManager={isFirstPersonMode ? 'default' : 'custom'}
|
||||||
|
>
|
||||||
<ViewerSceneContent
|
<ViewerSceneContent
|
||||||
isFirstPersonMode={isFirstPersonMode}
|
isFirstPersonMode={isFirstPersonMode}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
@@ -1017,7 +1031,7 @@ export default function Editor({
|
|||||||
const showLoader = isLoading || isSceneLoading
|
const showLoader = isLoading || isSceneLoading
|
||||||
|
|
||||||
const previewViewerContent = (
|
const previewViewerContent = (
|
||||||
<Viewer selectionManager="default">
|
<Viewer hoverStyles={EDITOR_HOVER_STYLES} selectionManager="default">
|
||||||
<ExportManager />
|
<ExportManager />
|
||||||
<ViewerZoneSystem />
|
<ViewerZoneSystem />
|
||||||
<CeilingSystem />
|
<CeilingSystem />
|
||||||
|
|||||||
@@ -25,20 +25,18 @@ import {
|
|||||||
type WallSurfaceSide,
|
type WallSurfaceSide,
|
||||||
} from '@pascal-app/core'
|
} from '@pascal-app/core'
|
||||||
|
|
||||||
import { useViewer } from '@pascal-app/viewer'
|
|
||||||
import { useCallback, useEffect, useRef } from 'react'
|
|
||||||
import { type BufferGeometry, Color, type Material, type Mesh, type Object3D } from 'three'
|
|
||||||
import {
|
import {
|
||||||
applyMaterialPresetToMaterials,
|
applyMaterialPresetToMaterials,
|
||||||
createMaterial,
|
createMaterial,
|
||||||
createMaterialFromPresetRef,
|
createMaterialFromPresetRef,
|
||||||
} from '../../../../viewer/src/lib/materials'
|
getRoofMaterialArray,
|
||||||
import { getRoofMaterialArray } from '../../../../viewer/src/systems/roof/roof-materials'
|
|
||||||
import {
|
|
||||||
getStairBodyMaterials,
|
getStairBodyMaterials,
|
||||||
getStairRailingMaterial,
|
getStairRailingMaterial,
|
||||||
} from '../../../../viewer/src/systems/stair/stair-materials'
|
getVisibleWallMaterials,
|
||||||
import { getVisibleWallMaterials } from '../../../../viewer/src/systems/wall/wall-materials'
|
useViewer,
|
||||||
|
} from '@pascal-app/viewer'
|
||||||
|
import { useCallback, useEffect, useRef } from 'react'
|
||||||
|
import { type BufferGeometry, Color, type Material, type Mesh, type Object3D } from 'three'
|
||||||
import {
|
import {
|
||||||
type ActivePaintMaterial,
|
type ActivePaintMaterial,
|
||||||
buildRoofSurfaceMaterialPatch,
|
buildRoofSurfaceMaterialPatch,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { useMemo, useRef } from 'react'
|
|||||||
import { float, mix, positionWorld, smoothstep } from 'three/tsl'
|
import { float, mix, positionWorld, smoothstep } from 'three/tsl'
|
||||||
import { BackSide, FrontSide, type Mesh, MeshBasicNodeMaterial } from 'three/webgpu'
|
import { BackSide, FrontSide, type Mesh, MeshBasicNodeMaterial } from 'three/webgpu'
|
||||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||||
import useViewer from '../../../store/use-viewer'
|
|
||||||
import { NodeRenderer } from '../node-renderer'
|
import { NodeRenderer } from '../node-renderer'
|
||||||
|
|
||||||
const gridScale = 5
|
const gridScale = 5
|
||||||
@@ -55,16 +54,10 @@ export const CeilingRenderer = ({ node }: { node: CeilingNode }) => {
|
|||||||
|
|
||||||
useRegistry(node.id, 'ceiling', ref)
|
useRegistry(node.id, 'ceiling', ref)
|
||||||
const handlers = useNodeEvents(node, 'ceiling')
|
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 materials = useMemo(() => {
|
||||||
const preset = getMaterialPresetByRef(materialPreview?.materialPreset ?? node.materialPreset)
|
const preset = getMaterialPresetByRef(node.materialPreset)
|
||||||
const props =
|
const props = preset?.mapProperties ?? resolveMaterial(node.material)
|
||||||
preset?.mapProperties ?? resolveMaterial(materialPreview?.material ?? node.material)
|
|
||||||
const color = props.color || '#999999'
|
const color = props.color || '#999999'
|
||||||
return getCeilingMaterials(color)
|
return getCeilingMaterials(color)
|
||||||
}, [
|
}, [
|
||||||
@@ -73,11 +66,6 @@ export const CeilingRenderer = ({ node }: { node: CeilingNode }) => {
|
|||||||
node.material?.preset,
|
node.material?.preset,
|
||||||
node.material?.properties,
|
node.material?.properties,
|
||||||
node.material?.texture,
|
node.material?.texture,
|
||||||
materialPreview?.materialPreset,
|
|
||||||
materialPreview?.material,
|
|
||||||
materialPreview?.material?.preset,
|
|
||||||
materialPreview?.material?.properties,
|
|
||||||
materialPreview?.material?.texture,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -7,22 +7,14 @@ import {
|
|||||||
createMaterialFromPresetRef,
|
createMaterialFromPresetRef,
|
||||||
DEFAULT_STAIR_MATERIAL,
|
DEFAULT_STAIR_MATERIAL,
|
||||||
} from '../../../lib/materials'
|
} from '../../../lib/materials'
|
||||||
import useViewer from '../../../store/use-viewer'
|
|
||||||
|
|
||||||
export const FenceRenderer = ({ node }: { node: FenceNode }) => {
|
export const FenceRenderer = ({ node }: { node: FenceNode }) => {
|
||||||
const ref = useRef<Mesh>(null!)
|
const ref = useRef<Mesh>(null!)
|
||||||
const handlers = useNodeEvents(node, 'fence')
|
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 material = useMemo(() => {
|
||||||
const presetMaterial = createMaterialFromPresetRef(
|
const presetMaterial = createMaterialFromPresetRef(node.materialPreset)
|
||||||
materialPreview?.materialPreset ?? node.materialPreset,
|
|
||||||
)
|
|
||||||
if (presetMaterial) return presetMaterial
|
if (presetMaterial) return presetMaterial
|
||||||
const mat = materialPreview?.material ?? node.material
|
const mat = node.material
|
||||||
if (!mat) return DEFAULT_STAIR_MATERIAL
|
if (!mat) return DEFAULT_STAIR_MATERIAL
|
||||||
return createMaterial(mat)
|
return createMaterial(mat)
|
||||||
}, [
|
}, [
|
||||||
@@ -31,11 +23,6 @@ export const FenceRenderer = ({ node }: { node: FenceNode }) => {
|
|||||||
node.material?.preset,
|
node.material?.preset,
|
||||||
node.material?.properties,
|
node.material?.properties,
|
||||||
node.material?.texture,
|
node.material?.texture,
|
||||||
materialPreview?.materialPreset,
|
|
||||||
materialPreview?.material,
|
|
||||||
materialPreview?.material?.preset,
|
|
||||||
materialPreview?.material?.properties,
|
|
||||||
materialPreview?.material?.texture,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
useRegistry(node.id, 'fence', ref)
|
useRegistry(node.id, 'fence', ref)
|
||||||
|
|||||||
@@ -23,11 +23,6 @@ export const RoofSegmentRenderer = ({ node }: { node: RoofSegmentNode }) => {
|
|||||||
const parentNode = node.parentId
|
const parentNode = node.parentId
|
||||||
? (nodes[node.parentId as AnyNodeId] as RoofNode | undefined)
|
? (nodes[node.parentId as AnyNodeId] as RoofNode | undefined)
|
||||||
: undefined
|
: undefined
|
||||||
const materialPreview = useViewer((state) =>
|
|
||||||
state.materialPreview?.target === 'roof' && state.materialPreview.nodeId === parentNode?.id
|
|
||||||
? state.materialPreview
|
|
||||||
: null,
|
|
||||||
)
|
|
||||||
const placeholderGeometry = useMemo(() => {
|
const placeholderGeometry = useMemo(() => {
|
||||||
const geometry = new THREE.BufferGeometry()
|
const geometry = new THREE.BufferGeometry()
|
||||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3))
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3))
|
||||||
@@ -38,40 +33,13 @@ export const RoofSegmentRenderer = ({ node }: { node: RoofSegmentNode }) => {
|
|||||||
return geometry
|
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(() => {
|
const customMaterial = useMemo(() => {
|
||||||
if (node.material !== undefined || typeof node.materialPreset === 'string') {
|
if (node.material !== undefined || typeof node.materialPreset === 'string') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return previewParentNode ? getRoofMaterialArray(previewParentNode) : null
|
return parentNode ? getRoofMaterialArray(parentNode) : null
|
||||||
}, [node, previewParentNode])
|
}, [node, parentNode])
|
||||||
|
|
||||||
const material = debugColors ? roofDebugMaterials : customMaterial || roofMaterials
|
const material = debugColors ? roofDebugMaterials : customMaterial || roofMaterials
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,6 @@ export const RoofRenderer = ({ node }: { node: RoofNode }) => {
|
|||||||
|
|
||||||
const handlers = useNodeEvents(node, 'roof')
|
const handlers = useNodeEvents(node, 'roof')
|
||||||
const debugColors = useViewer((s) => s.debugColors)
|
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 placeholderGeometry = useMemo(() => {
|
||||||
const geometry = new THREE.BufferGeometry()
|
const geometry = new THREE.BufferGeometry()
|
||||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3))
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3))
|
||||||
@@ -29,33 +24,7 @@ export const RoofRenderer = ({ node }: { node: RoofNode }) => {
|
|||||||
return geometry
|
return geometry
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const previewNode =
|
const customMaterial = useMemo(() => getRoofMaterialArray(node), [node])
|
||||||
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
|
const material = debugColors ? roofDebugMaterials : customMaterial || roofMaterials
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
createMaterial,
|
createMaterial,
|
||||||
DEFAULT_SLAB_MATERIAL,
|
DEFAULT_SLAB_MATERIAL,
|
||||||
} from '../../../lib/materials'
|
} from '../../../lib/materials'
|
||||||
import useViewer from '../../../store/use-viewer'
|
|
||||||
|
|
||||||
const slabMaterialCache = new Map<string, THREE.MeshStandardMaterial>()
|
const slabMaterialCache = new Map<string, THREE.MeshStandardMaterial>()
|
||||||
|
|
||||||
@@ -52,15 +51,10 @@ export const SlabRenderer = ({ node }: { node: SlabNode }) => {
|
|||||||
useRegistry(node.id, 'slab', ref)
|
useRegistry(node.id, 'slab', ref)
|
||||||
|
|
||||||
const handlers = useNodeEvents(node, 'slab')
|
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 material = useMemo(() => {
|
||||||
const resolvedMaterial = materialPreview?.material ?? node.material
|
const resolvedMaterial = node.material
|
||||||
const resolvedMaterialPreset = materialPreview?.materialPreset ?? node.materialPreset
|
const resolvedMaterialPreset = node.materialPreset
|
||||||
const cacheKey = JSON.stringify({
|
const cacheKey = JSON.stringify({
|
||||||
material: resolvedMaterial ?? null,
|
material: resolvedMaterial ?? null,
|
||||||
materialPreset: resolvedMaterialPreset ?? null,
|
materialPreset: resolvedMaterialPreset ?? null,
|
||||||
@@ -76,11 +70,6 @@ export const SlabRenderer = ({ node }: { node: SlabNode }) => {
|
|||||||
node.material?.properties,
|
node.material?.properties,
|
||||||
node.material?.texture,
|
node.material?.texture,
|
||||||
node.materialPreset,
|
node.materialPreset,
|
||||||
materialPreview?.material,
|
|
||||||
materialPreview?.material?.preset,
|
|
||||||
materialPreview?.material?.properties,
|
|
||||||
materialPreview?.material?.texture,
|
|
||||||
materialPreview?.materialPreset,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
import { useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
import { useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
||||||
import * as THREE from 'three'
|
import * as THREE from 'three'
|
||||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||||
import useViewer from '../../../store/use-viewer'
|
|
||||||
import { getStraightStairSegmentBodyMaterials } from '../../../systems/stair/stair-materials'
|
import { getStraightStairSegmentBodyMaterials } from '../../../systems/stair/stair-materials'
|
||||||
|
|
||||||
export const StairSegmentRenderer = ({ node }: { node: StairSegmentNode }) => {
|
export const StairSegmentRenderer = ({ node }: { node: StairSegmentNode }) => {
|
||||||
@@ -25,42 +24,10 @@ export const StairSegmentRenderer = ({ node }: { node: StairSegmentNode }) => {
|
|||||||
const parentNode = node.parentId
|
const parentNode = node.parentId
|
||||||
? (nodes[node.parentId as AnyNodeId] as StairNode | undefined)
|
? (nodes[node.parentId as AnyNodeId] as StairNode | undefined)
|
||||||
: undefined
|
: undefined
|
||||||
const materialPreview = useViewer((state) =>
|
const material = useMemo(
|
||||||
state.materialPreview?.target === 'stair' && state.materialPreview.nodeId === parentNode?.id
|
() => getStraightStairSegmentBodyMaterials(node, parentNode),
|
||||||
? state.materialPreview
|
[node, parentNode],
|
||||||
: 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, previewParentNode)
|
|
||||||
}, [node, previewParentNode])
|
|
||||||
|
|
||||||
const placeholderGeometry = useMemo(() => {
|
const placeholderGeometry = useMemo(() => {
|
||||||
const geometry = new THREE.BufferGeometry()
|
const geometry = new THREE.BufferGeometry()
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
createMaterialFromPresetRef,
|
createMaterialFromPresetRef,
|
||||||
DEFAULT_STAIR_MATERIAL,
|
DEFAULT_STAIR_MATERIAL,
|
||||||
} from '../../../lib/materials'
|
} from '../../../lib/materials'
|
||||||
import useViewer from '../../../store/use-viewer'
|
|
||||||
import {
|
import {
|
||||||
getStairBodyMaterials,
|
getStairBodyMaterials,
|
||||||
getStairRailingMaterial,
|
getStairRailingMaterial,
|
||||||
@@ -62,55 +61,24 @@ export const StairRenderer = ({ node }: { node: StairNode }) => {
|
|||||||
}, [node.id])
|
}, [node.id])
|
||||||
|
|
||||||
const handlers = useNodeEvents(node, 'stair')
|
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 material = useMemo(() => {
|
||||||
const presetMaterial = createMaterialFromPresetRef(previewNode.materialPreset)
|
const presetMaterial = createMaterialFromPresetRef(node.materialPreset)
|
||||||
if (presetMaterial) return presetMaterial
|
if (presetMaterial) return presetMaterial
|
||||||
const mat = previewNode.material
|
const mat = node.material
|
||||||
if (!mat) return DEFAULT_STAIR_MATERIAL
|
if (!mat) return DEFAULT_STAIR_MATERIAL
|
||||||
return createMaterial(mat)
|
return createMaterial(mat)
|
||||||
}, [
|
}, [
|
||||||
previewNode.materialPreset,
|
node.materialPreset,
|
||||||
previewNode.material,
|
node.material,
|
||||||
previewNode.material?.preset,
|
node.material?.preset,
|
||||||
previewNode.material?.properties,
|
node.material?.properties,
|
||||||
previewNode.material?.texture,
|
node.material?.texture,
|
||||||
])
|
])
|
||||||
|
|
||||||
const straightBodyMaterials = useMemo(() => getStairBodyMaterials(previewNode), [previewNode])
|
const straightBodyMaterials = useMemo(() => getStairBodyMaterials(node), [node])
|
||||||
|
|
||||||
const railingMaterial = useMemo(() => getStairRailingMaterial(previewNode), [previewNode])
|
const railingMaterial = useMemo(() => getStairRailingMaterial(node), [node])
|
||||||
|
|
||||||
const straightPlaceholderGeometry = useMemo(() => {
|
const straightPlaceholderGeometry = useMemo(() => {
|
||||||
const geometry = new THREE.BufferGeometry()
|
const geometry = new THREE.BufferGeometry()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { useRegistry, useScene, type WallNode } from '@pascal-app/core'
|
|||||||
import { useLayoutEffect, useRef } from 'react'
|
import { useLayoutEffect, useRef } from 'react'
|
||||||
import type { Mesh } from 'three'
|
import type { Mesh } from 'three'
|
||||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||||
import useViewer from '../../../store/use-viewer'
|
|
||||||
import { getVisibleWallMaterials } from '../../../systems/wall/wall-materials'
|
import { getVisibleWallMaterials } from '../../../systems/wall/wall-materials'
|
||||||
import { NodeRenderer } from '../node-renderer'
|
import { NodeRenderer } from '../node-renderer'
|
||||||
|
|
||||||
@@ -16,30 +15,7 @@ export const WallRenderer = ({ node }: { node: WallNode }) => {
|
|||||||
}, [node.id])
|
}, [node.id])
|
||||||
|
|
||||||
const handlers = useNodeEvents(node, 'wall')
|
const handlers = useNodeEvents(node, 'wall')
|
||||||
const materialPreview = useViewer((state) =>
|
const material = getVisibleWallMaterials(node)
|
||||||
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 (
|
return (
|
||||||
<mesh castShadow material={material} receiveShadow ref={ref} visible={node.visible}>
|
<mesh castShadow material={material} receiveShadow ref={ref} visible={node.visible}>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { SceneRenderer } from '../renderers/scene-renderer'
|
|||||||
import FrameLimiter from './frame-limiter'
|
import FrameLimiter from './frame-limiter'
|
||||||
import { Lights } from './lights'
|
import { Lights } from './lights'
|
||||||
import { PerfMonitor } from './perf-monitor'
|
import { PerfMonitor } from './perf-monitor'
|
||||||
import PostProcessing from './post-processing'
|
import PostProcessing, { DEFAULT_HOVER_STYLES, type HoverStyles } from './post-processing'
|
||||||
import { SelectionManager } from './selection-manager'
|
import { SelectionManager } from './selection-manager'
|
||||||
import { ViewerCamera } from './viewer-camera'
|
import { ViewerCamera } from './viewer-camera'
|
||||||
|
|
||||||
@@ -101,12 +101,14 @@ function GPUDeviceWatcher() {
|
|||||||
|
|
||||||
interface ViewerProps {
|
interface ViewerProps {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
|
hoverStyles?: HoverStyles
|
||||||
selectionManager?: 'default' | 'custom'
|
selectionManager?: 'default' | 'custom'
|
||||||
perf?: boolean
|
perf?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Viewer: React.FC<ViewerProps> = ({
|
const Viewer: React.FC<ViewerProps> = ({
|
||||||
children,
|
children,
|
||||||
|
hoverStyles = DEFAULT_HOVER_STYLES,
|
||||||
selectionManager = 'default',
|
selectionManager = 'default',
|
||||||
perf = false,
|
perf = false,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -165,7 +167,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
<WallSystem />
|
<WallSystem />
|
||||||
<WindowSystem />
|
<WindowSystem />
|
||||||
<ZoneSystem />
|
<ZoneSystem />
|
||||||
<PostProcessing />
|
<PostProcessing hoverStyles={hoverStyles} />
|
||||||
{/* <DebugRenderer /> */}
|
{/* <DebugRenderer /> */}
|
||||||
<GPUDeviceWatcher />
|
<GPUDeviceWatcher />
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,46 @@ const RETRY_DELAY_MS = 500
|
|||||||
const DARK_BG = '#1f2433'
|
const DARK_BG = '#1f2433'
|
||||||
const LIGHT_BG = '#ffffff'
|
const LIGHT_BG = '#ffffff'
|
||||||
|
|
||||||
|
export type HoverStyle = {
|
||||||
|
visibleColor: number
|
||||||
|
hiddenColor: number
|
||||||
|
strength: number
|
||||||
|
pulse: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HoverStyles = {
|
||||||
|
default: HoverStyle
|
||||||
|
} & Record<string, HoverStyle>
|
||||||
|
|
||||||
|
const DEFAULT_HOVER_STYLE: HoverStyle = {
|
||||||
|
visibleColor: 0x00_aa_ff,
|
||||||
|
hiddenColor: 0xf3_ff_47,
|
||||||
|
strength: 5,
|
||||||
|
pulse: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_HOVER_STYLES: HoverStyles = {
|
||||||
|
default: DEFAULT_HOVER_STYLE,
|
||||||
|
delete: {
|
||||||
|
visibleColor: 0xef_44_44,
|
||||||
|
hiddenColor: 0x99_1b_1b,
|
||||||
|
strength: 6,
|
||||||
|
pulse: false,
|
||||||
|
},
|
||||||
|
'paint-ready': {
|
||||||
|
visibleColor: 0xf5_9e_0b,
|
||||||
|
hiddenColor: 0xfd_e0_68,
|
||||||
|
strength: 5,
|
||||||
|
pulse: true,
|
||||||
|
},
|
||||||
|
'paint-disabled': {
|
||||||
|
visibleColor: 0x94_a3_b8,
|
||||||
|
hiddenColor: 0x47_55_69,
|
||||||
|
strength: 4,
|
||||||
|
pulse: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
function sanitizeOutlineObjects(objects: Object3D[]) {
|
function sanitizeOutlineObjects(objects: Object3D[]) {
|
||||||
let nextIndex = 0
|
let nextIndex = 0
|
||||||
|
|
||||||
@@ -62,7 +102,11 @@ function sanitizeOutlineObjects(objects: Object3D[]) {
|
|||||||
objects.length = nextIndex
|
objects.length = nextIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostProcessingPasses = () => {
|
const PostProcessingPasses = ({
|
||||||
|
hoverStyles = DEFAULT_HOVER_STYLES,
|
||||||
|
}: {
|
||||||
|
hoverStyles?: HoverStyles
|
||||||
|
}) => {
|
||||||
const { gl: renderer, scene, camera } = useThree()
|
const { gl: renderer, scene, camera } = useThree()
|
||||||
const renderPipelineRef = useRef<RenderPipeline | null>(null)
|
const renderPipelineRef = useRef<RenderPipeline | null>(null)
|
||||||
const hasPipelineErrorRef = useRef(false)
|
const hasPipelineErrorRef = useRef(false)
|
||||||
@@ -83,6 +127,10 @@ const PostProcessingPasses = () => {
|
|||||||
return l
|
return l
|
||||||
}, [])
|
}, [])
|
||||||
const hoverHighlightMode = useViewer((s) => s.hoverHighlightMode)
|
const hoverHighlightMode = useViewer((s) => s.hoverHighlightMode)
|
||||||
|
const hoverVisibleColor = useMemo(() => uniform(new Color(DEFAULT_HOVER_STYLE.visibleColor)), [])
|
||||||
|
const hoverHiddenColor = useMemo(() => uniform(new Color(DEFAULT_HOVER_STYLE.hiddenColor)), [])
|
||||||
|
const hoverStrength = useMemo(() => uniform(DEFAULT_HOVER_STYLE.strength), [])
|
||||||
|
const hoverPulseMix = useMemo(() => uniform(DEFAULT_HOVER_STYLE.pulse ? 0 : 1), [])
|
||||||
|
|
||||||
// Subscribe to projectId so the pipeline rebuilds on project switch
|
// Subscribe to projectId so the pipeline rebuilds on project switch
|
||||||
const projectId = useViewer((s) => s.projectId)
|
const projectId = useViewer((s) => s.projectId)
|
||||||
@@ -119,6 +167,21 @@ const PostProcessingPasses = () => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const style = hoverStyles[hoverHighlightMode] ?? hoverStyles.default
|
||||||
|
hoverVisibleColor.value.setHex(style.visibleColor)
|
||||||
|
hoverHiddenColor.value.setHex(style.hiddenColor)
|
||||||
|
hoverStrength.value = style.strength
|
||||||
|
hoverPulseMix.value = style.pulse ? 0 : 1
|
||||||
|
}, [
|
||||||
|
hoverHiddenColor,
|
||||||
|
hoverHighlightMode,
|
||||||
|
hoverPulseMix,
|
||||||
|
hoverStrength,
|
||||||
|
hoverStyles,
|
||||||
|
hoverVisibleColor,
|
||||||
|
])
|
||||||
|
|
||||||
// Build / rebuild the post-processing pipeline
|
// Build / rebuild the post-processing pipeline
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Intentionally touch these so React/biome treat project switches and retry bumps
|
// Intentionally touch these so React/biome treat project switches and retry bumps
|
||||||
@@ -248,36 +311,9 @@ const PostProcessingPasses = () => {
|
|||||||
.mul(selectedStrength)
|
.mul(selectedStrength)
|
||||||
|
|
||||||
// Hovered: blue visible, yellow hidden, pulsing
|
// Hovered: blue visible, yellow hidden, pulsing
|
||||||
const hoverVisibleColor = uniform(
|
|
||||||
new Color(
|
|
||||||
hoverHighlightMode === 'delete'
|
|
||||||
? 0xef_44_44
|
|
||||||
: hoverHighlightMode === 'paint-ready'
|
|
||||||
? 0xf59e_0b
|
|
||||||
: hoverHighlightMode === 'paint-disabled'
|
|
||||||
? 0x94_a3_b8
|
|
||||||
: 0x00_aa_ff,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
const hoverHiddenColor = uniform(
|
|
||||||
new Color(
|
|
||||||
hoverHighlightMode === 'delete'
|
|
||||||
? 0x99_1b_1b
|
|
||||||
: hoverHighlightMode === 'paint-ready'
|
|
||||||
? 0xfde0_68
|
|
||||||
: hoverHighlightMode === 'paint-disabled'
|
|
||||||
? 0x47_55_69
|
|
||||||
: 0xf3_ff_47,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
const hoverStrength = uniform(
|
|
||||||
hoverHighlightMode === 'delete' ? 6 : hoverHighlightMode === 'paint-disabled' ? 4 : 5,
|
|
||||||
)
|
|
||||||
const pulsePeriod = uniform(3)
|
const pulsePeriod = uniform(3)
|
||||||
const osc =
|
const oscillating = oscSine(time.div(pulsePeriod).mul(2)).mul(0.5).add(0.5)
|
||||||
hoverHighlightMode === 'delete' || hoverHighlightMode === 'paint-disabled'
|
const osc = mix(oscillating, float(1), hoverPulseMix)
|
||||||
? float(1)
|
|
||||||
: oscSine(time.div(pulsePeriod).mul(2)).mul(0.5).add(0.5) // [ 0.5, 1.0 ]
|
|
||||||
const hoverOutline = outlineNode.secondaryVisibleEdge
|
const hoverOutline = outlineNode.secondaryVisibleEdge
|
||||||
.mul(hoverVisibleColor)
|
.mul(hoverVisibleColor)
|
||||||
.add(outlineNode.secondaryHiddenEdge.mul(hoverHiddenColor))
|
.add(outlineNode.secondaryHiddenEdge.mul(hoverHiddenColor))
|
||||||
@@ -316,7 +352,18 @@ const PostProcessingPasses = () => {
|
|||||||
}
|
}
|
||||||
renderPipelineRef.current = null
|
renderPipelineRef.current = null
|
||||||
}
|
}
|
||||||
}, [renderer, scene, camera, hoverHighlightMode, zoneLayers, projectId, pipelineVersion])
|
}, [
|
||||||
|
camera,
|
||||||
|
hoverHiddenColor,
|
||||||
|
hoverPulseMix,
|
||||||
|
hoverStrength,
|
||||||
|
hoverVisibleColor,
|
||||||
|
pipelineVersion,
|
||||||
|
projectId,
|
||||||
|
renderer,
|
||||||
|
scene,
|
||||||
|
zoneLayers,
|
||||||
|
])
|
||||||
|
|
||||||
useFrame((_, delta) => {
|
useFrame((_, delta) => {
|
||||||
// Animate background colour toward the current theme target (same lerp as AnimatedBackground)
|
// Animate background colour toward the current theme target (same lerp as AnimatedBackground)
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
export { default as Viewer } from './components/viewer'
|
export { default as Viewer } from './components/viewer'
|
||||||
export { SSGI_PARAMS } from './components/viewer/post-processing'
|
export type { HoverStyle, HoverStyles } from './components/viewer/post-processing'
|
||||||
|
export {
|
||||||
|
DEFAULT_HOVER_STYLES,
|
||||||
|
SSGI_PARAMS,
|
||||||
|
} from './components/viewer/post-processing'
|
||||||
export { WalkthroughControls } from './components/viewer/walkthrough-controls'
|
export { WalkthroughControls } from './components/viewer/walkthrough-controls'
|
||||||
export { ASSETS_CDN_URL, resolveAssetUrl, resolveCdnUrl } from './lib/asset-url'
|
export { ASSETS_CDN_URL, resolveAssetUrl, resolveCdnUrl } from './lib/asset-url'
|
||||||
export { SCENE_LAYER, ZONE_LAYER } from './lib/layers'
|
export { SCENE_LAYER, ZONE_LAYER } from './lib/layers'
|
||||||
export {
|
export {
|
||||||
|
applyMaterialPresetToMaterials,
|
||||||
clearMaterialCache,
|
clearMaterialCache,
|
||||||
createDefaultMaterial,
|
createDefaultMaterial,
|
||||||
createMaterial,
|
createMaterial,
|
||||||
|
createMaterialFromPresetRef,
|
||||||
DEFAULT_CEILING_MATERIAL,
|
DEFAULT_CEILING_MATERIAL,
|
||||||
DEFAULT_DOOR_MATERIAL,
|
DEFAULT_DOOR_MATERIAL,
|
||||||
DEFAULT_ROOF_MATERIAL,
|
DEFAULT_ROOF_MATERIAL,
|
||||||
@@ -19,3 +25,6 @@ export { mergedOutline } from './lib/merged-outline-node'
|
|||||||
export { default as useViewer } from './store/use-viewer'
|
export { default as useViewer } from './store/use-viewer'
|
||||||
export { InteractiveSystem } from './systems/interactive/interactive-system'
|
export { InteractiveSystem } from './systems/interactive/interactive-system'
|
||||||
export { snapLevelsToTruePositions } from './systems/level/level-utils'
|
export { snapLevelsToTruePositions } from './systems/level/level-utils'
|
||||||
|
export { getRoofMaterialArray } from './systems/roof/roof-materials'
|
||||||
|
export { getStairBodyMaterials, getStairRailingMaterial } from './systems/stair/stair-materials'
|
||||||
|
export { getVisibleWallMaterials } from './systems/wall/wall-materials'
|
||||||
|
|||||||
+2
-34
@@ -3,10 +3,6 @@ import type {
|
|||||||
BaseNode,
|
BaseNode,
|
||||||
BuildingNode,
|
BuildingNode,
|
||||||
LevelNode,
|
LevelNode,
|
||||||
MaterialSchema,
|
|
||||||
RoofSurfaceMaterialRole,
|
|
||||||
StairSurfaceMaterialRole,
|
|
||||||
WallSurfaceSide,
|
|
||||||
ZoneNode,
|
ZoneNode,
|
||||||
} from '@pascal-app/core'
|
} from '@pascal-app/core'
|
||||||
import type { Object3D } from 'three'
|
import type { Object3D } from 'three'
|
||||||
@@ -20,40 +16,12 @@ type Outliner = {
|
|||||||
selectedObjects: Object3D[]
|
selectedObjects: Object3D[]
|
||||||
hoveredObjects: Object3D[]
|
hoveredObjects: Object3D[]
|
||||||
}
|
}
|
||||||
type MaterialPreview = {
|
|
||||||
nodeId: string
|
|
||||||
target: 'wall'
|
|
||||||
role: WallSurfaceSide
|
|
||||||
material?: MaterialSchema
|
|
||||||
materialPreset?: string
|
|
||||||
} | {
|
|
||||||
nodeId: string
|
|
||||||
target: 'roof'
|
|
||||||
role: RoofSurfaceMaterialRole
|
|
||||||
material?: MaterialSchema
|
|
||||||
materialPreset?: string
|
|
||||||
} | {
|
|
||||||
nodeId: string
|
|
||||||
target: 'stair'
|
|
||||||
role: StairSurfaceMaterialRole
|
|
||||||
material?: MaterialSchema
|
|
||||||
materialPreset?: string
|
|
||||||
} | {
|
|
||||||
nodeId: string
|
|
||||||
target: 'fence' | 'slab' | 'ceiling'
|
|
||||||
role: 'surface'
|
|
||||||
material?: MaterialSchema
|
|
||||||
materialPreset?: string
|
|
||||||
} | null
|
|
||||||
type ViewerState = {
|
type ViewerState = {
|
||||||
selection: SelectionPath
|
selection: SelectionPath
|
||||||
previewSelectedIds: BaseNode['id'][]
|
previewSelectedIds: BaseNode['id'][]
|
||||||
setPreviewSelectedIds: (ids: BaseNode['id'][]) => void
|
setPreviewSelectedIds: (ids: BaseNode['id'][]) => void
|
||||||
materialPreview: MaterialPreview
|
hoverHighlightMode: string
|
||||||
setMaterialPreview: (preview: MaterialPreview) => void
|
setHoverHighlightMode: (mode: string) => void
|
||||||
clearMaterialPreview: () => void
|
|
||||||
hoverHighlightMode: 'default' | 'delete' | 'paint-ready' | 'paint-disabled'
|
|
||||||
setHoverHighlightMode: (mode: 'default' | 'delete' | 'paint-ready' | 'paint-disabled') => void
|
|
||||||
hoveredId: AnyNode['id'] | ZoneNode['id'] | null
|
hoveredId: AnyNode['id'] | ZoneNode['id'] | null
|
||||||
setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void
|
setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void
|
||||||
cameraMode: 'perspective' | 'orthographic'
|
cameraMode: 'perspective' | 'orthographic'
|
||||||
|
|||||||
@@ -1,17 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type {
|
import type { AnyNode, BaseNode, BuildingNode, LevelNode, ZoneNode } from '@pascal-app/core'
|
||||||
AnyNode,
|
|
||||||
AnyNodeId,
|
|
||||||
BaseNode,
|
|
||||||
BuildingNode,
|
|
||||||
LevelNode,
|
|
||||||
MaterialSchema,
|
|
||||||
RoofSurfaceMaterialRole,
|
|
||||||
StairSurfaceMaterialRole,
|
|
||||||
WallSurfaceSide,
|
|
||||||
ZoneNode,
|
|
||||||
} from '@pascal-app/core'
|
|
||||||
import type { Object3D } from 'three'
|
import type { Object3D } from 'three'
|
||||||
|
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
@@ -29,59 +18,12 @@ type Outliner = {
|
|||||||
hoveredObjects: Object3D[]
|
hoveredObjects: Object3D[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type MaterialPreview =
|
|
||||||
| {
|
|
||||||
nodeId: AnyNodeId
|
|
||||||
target: 'wall'
|
|
||||||
role: WallSurfaceSide
|
|
||||||
material?: MaterialSchema
|
|
||||||
materialPreset?: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
nodeId: AnyNodeId
|
|
||||||
target: 'roof'
|
|
||||||
role: RoofSurfaceMaterialRole
|
|
||||||
material?: MaterialSchema
|
|
||||||
materialPreset?: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
nodeId: AnyNodeId
|
|
||||||
target: 'stair'
|
|
||||||
role: StairSurfaceMaterialRole
|
|
||||||
material?: MaterialSchema
|
|
||||||
materialPreset?: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
nodeId: AnyNodeId
|
|
||||||
target: 'fence' | 'slab' | 'ceiling'
|
|
||||||
role: 'surface'
|
|
||||||
material?: MaterialSchema
|
|
||||||
materialPreset?: string
|
|
||||||
}
|
|
||||||
| null
|
|
||||||
|
|
||||||
function isSameMaterialPreview(left: MaterialPreview, right: MaterialPreview) {
|
|
||||||
if (left === right) return true
|
|
||||||
if (!left || !right) return false
|
|
||||||
|
|
||||||
return (
|
|
||||||
left.nodeId === right.nodeId &&
|
|
||||||
left.target === right.target &&
|
|
||||||
left.role === right.role &&
|
|
||||||
left.material === right.material &&
|
|
||||||
left.materialPreset === right.materialPreset
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ViewerState = {
|
type ViewerState = {
|
||||||
selection: SelectionPath
|
selection: SelectionPath
|
||||||
previewSelectedIds: BaseNode['id'][]
|
previewSelectedIds: BaseNode['id'][]
|
||||||
setPreviewSelectedIds: (ids: BaseNode['id'][]) => void
|
setPreviewSelectedIds: (ids: BaseNode['id'][]) => void
|
||||||
materialPreview: MaterialPreview
|
hoverHighlightMode: string
|
||||||
setMaterialPreview: (preview: MaterialPreview) => void
|
setHoverHighlightMode: (mode: string) => void
|
||||||
clearMaterialPreview: () => void
|
|
||||||
hoverHighlightMode: 'default' | 'delete' | 'paint-ready' | 'paint-disabled'
|
|
||||||
setHoverHighlightMode: (mode: 'default' | 'delete' | 'paint-ready' | 'paint-disabled') => void
|
|
||||||
hoveredId: AnyNode['id'] | ZoneNode['id'] | null
|
hoveredId: AnyNode['id'] | ZoneNode['id'] | null
|
||||||
setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void
|
setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void
|
||||||
|
|
||||||
@@ -142,15 +84,6 @@ const useViewer = create<ViewerState>()(
|
|||||||
selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] },
|
selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] },
|
||||||
previewSelectedIds: [],
|
previewSelectedIds: [],
|
||||||
setPreviewSelectedIds: (ids) => set({ previewSelectedIds: ids }),
|
setPreviewSelectedIds: (ids) => set({ previewSelectedIds: ids }),
|
||||||
materialPreview: null,
|
|
||||||
setMaterialPreview: (materialPreview) =>
|
|
||||||
set((state) =>
|
|
||||||
isSameMaterialPreview(state.materialPreview, materialPreview)
|
|
||||||
? state
|
|
||||||
: { materialPreview },
|
|
||||||
),
|
|
||||||
clearMaterialPreview: () =>
|
|
||||||
set((state) => (state.materialPreview === null ? state : { materialPreview: null })),
|
|
||||||
hoverHighlightMode: 'default',
|
hoverHighlightMode: 'default',
|
||||||
setHoverHighlightMode: (mode) =>
|
setHoverHighlightMode: (mode) =>
|
||||||
set((state) => (state.hoverHighlightMode === mode ? state : { hoverHighlightMode: mode })),
|
set((state) => (state.hoverHighlightMode === mode ? state : { hoverHighlightMode: mode })),
|
||||||
@@ -255,7 +188,6 @@ const useViewer = create<ViewerState>()(
|
|||||||
selectedIds: [],
|
selectedIds: [],
|
||||||
},
|
},
|
||||||
previewSelectedIds: [],
|
previewSelectedIds: [],
|
||||||
materialPreview: null,
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
outliner: { selectedObjects: [], hoveredObjects: [] },
|
outliner: { selectedObjects: [], hoveredObjects: [] },
|
||||||
|
|||||||
Reference in New Issue
Block a user