Refactor viewer hover styles and remove preview state

This commit is contained in:
sudhir
2026-04-24 22:49:04 +05:30
parent 7297c76204
commit 325f2e4603
15 changed files with 142 additions and 360 deletions
@@ -8,7 +8,6 @@ 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
@@ -55,16 +54,10 @@ export const CeilingRenderer = ({ node }: { node: CeilingNode }) => {
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(materialPreview?.materialPreset ?? node.materialPreset)
const props =
preset?.mapProperties ?? resolveMaterial(materialPreview?.material ?? node.material)
const preset = getMaterialPresetByRef(node.materialPreset)
const props = preset?.mapProperties ?? resolveMaterial(node.material)
const color = props.color || '#999999'
return getCeilingMaterials(color)
}, [
@@ -73,11 +66,6 @@ export const CeilingRenderer = ({ node }: { node: CeilingNode }) => {
node.material?.preset,
node.material?.properties,
node.material?.texture,
materialPreview?.materialPreset,
materialPreview?.material,
materialPreview?.material?.preset,
materialPreview?.material?.properties,
materialPreview?.material?.texture,
])
return (
@@ -7,22 +7,14 @@ 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(
materialPreview?.materialPreset ?? node.materialPreset,
)
const presetMaterial = createMaterialFromPresetRef(node.materialPreset)
if (presetMaterial) return presetMaterial
const mat = materialPreview?.material ?? node.material
const mat = node.material
if (!mat) return DEFAULT_STAIR_MATERIAL
return createMaterial(mat)
}, [
@@ -31,11 +23,6 @@ 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)
@@ -23,11 +23,6 @@ export const RoofSegmentRenderer = ({ node }: { node: RoofSegmentNode }) => {
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))
@@ -38,40 +33,13 @@ 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 previewParentNode ? getRoofMaterialArray(previewParentNode) : null
}, [node, previewParentNode])
return parentNode ? getRoofMaterialArray(parentNode) : null
}, [node, parentNode])
const material = debugColors ? roofDebugMaterials : customMaterial || roofMaterials
@@ -14,11 +14,6 @@ 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))
@@ -29,33 +24,7 @@ export const RoofRenderer = ({ node }: { node: RoofNode }) => {
return geometry
}, [])
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 customMaterial = useMemo(() => getRoofMaterialArray(node), [node])
const material = debugColors ? roofDebugMaterials : customMaterial || roofMaterials
@@ -8,7 +8,6 @@ import {
createMaterial,
DEFAULT_SLAB_MATERIAL,
} from '../../../lib/materials'
import useViewer from '../../../store/use-viewer'
const slabMaterialCache = new Map<string, THREE.MeshStandardMaterial>()
@@ -52,15 +51,10 @@ 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 resolvedMaterial = materialPreview?.material ?? node.material
const resolvedMaterialPreset = materialPreview?.materialPreset ?? node.materialPreset
const resolvedMaterial = node.material
const resolvedMaterialPreset = node.materialPreset
const cacheKey = JSON.stringify({
material: resolvedMaterial ?? null,
materialPreset: resolvedMaterialPreset ?? null,
@@ -76,11 +70,6 @@ export const SlabRenderer = ({ node }: { node: SlabNode }) => {
node.material?.properties,
node.material?.texture,
node.materialPreset,
materialPreview?.material,
materialPreview?.material?.preset,
materialPreview?.material?.properties,
materialPreview?.material?.texture,
materialPreview?.materialPreset,
])
return (
@@ -8,7 +8,6 @@ import {
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 }) => {
@@ -25,42 +24,10 @@ export const StairSegmentRenderer = ({ node }: { node: StairSegmentNode }) => {
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 material = useMemo(
() => getStraightStairSegmentBodyMaterials(node, parentNode),
[node, parentNode],
)
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 geometry = new THREE.BufferGeometry()
@@ -13,7 +13,6 @@ import {
createMaterialFromPresetRef,
DEFAULT_STAIR_MATERIAL,
} from '../../../lib/materials'
import useViewer from '../../../store/use-viewer'
import {
getStairBodyMaterials,
getStairRailingMaterial,
@@ -62,55 +61,24 @@ 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(previewNode.materialPreset)
const presetMaterial = createMaterialFromPresetRef(node.materialPreset)
if (presetMaterial) return presetMaterial
const mat = previewNode.material
const mat = node.material
if (!mat) return DEFAULT_STAIR_MATERIAL
return createMaterial(mat)
}, [
previewNode.materialPreset,
previewNode.material,
previewNode.material?.preset,
previewNode.material?.properties,
previewNode.material?.texture,
node.materialPreset,
node.material,
node.material?.preset,
node.material?.properties,
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 geometry = new THREE.BufferGeometry()
@@ -2,7 +2,6 @@ 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'
@@ -16,30 +15,7 @@ export const WallRenderer = ({ node }: { node: WallNode }) => {
}, [node.id])
const handlers = useNodeEvents(node, 'wall')
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)
const material = getVisibleWallMaterials(node)
return (
<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 { Lights } from './lights'
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 { ViewerCamera } from './viewer-camera'
@@ -101,12 +101,14 @@ function GPUDeviceWatcher() {
interface ViewerProps {
children?: React.ReactNode
hoverStyles?: HoverStyles
selectionManager?: 'default' | 'custom'
perf?: boolean
}
const Viewer: React.FC<ViewerProps> = ({
children,
hoverStyles = DEFAULT_HOVER_STYLES,
selectionManager = 'default',
perf = false,
}) => {
@@ -165,7 +167,7 @@ const Viewer: React.FC<ViewerProps> = ({
<WallSystem />
<WindowSystem />
<ZoneSystem />
<PostProcessing />
<PostProcessing hoverStyles={hoverStyles} />
{/* <DebugRenderer /> */}
<GPUDeviceWatcher />
@@ -47,6 +47,46 @@ const RETRY_DELAY_MS = 500
const DARK_BG = '#1f2433'
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[]) {
let nextIndex = 0
@@ -62,7 +102,11 @@ function sanitizeOutlineObjects(objects: Object3D[]) {
objects.length = nextIndex
}
const PostProcessingPasses = () => {
const PostProcessingPasses = ({
hoverStyles = DEFAULT_HOVER_STYLES,
}: {
hoverStyles?: HoverStyles
}) => {
const { gl: renderer, scene, camera } = useThree()
const renderPipelineRef = useRef<RenderPipeline | null>(null)
const hasPipelineErrorRef = useRef(false)
@@ -83,6 +127,10 @@ const PostProcessingPasses = () => {
return l
}, [])
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
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
useEffect(() => {
// Intentionally touch these so React/biome treat project switches and retry bumps
@@ -248,36 +311,9 @@ const PostProcessingPasses = () => {
.mul(selectedStrength)
// 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 osc =
hoverHighlightMode === 'delete' || hoverHighlightMode === 'paint-disabled'
? float(1)
: oscSine(time.div(pulsePeriod).mul(2)).mul(0.5).add(0.5) // [ 0.5, 1.0 ]
const oscillating = oscSine(time.div(pulsePeriod).mul(2)).mul(0.5).add(0.5)
const osc = mix(oscillating, float(1), hoverPulseMix)
const hoverOutline = outlineNode.secondaryVisibleEdge
.mul(hoverVisibleColor)
.add(outlineNode.secondaryHiddenEdge.mul(hoverHiddenColor))
@@ -316,7 +352,18 @@ const PostProcessingPasses = () => {
}
renderPipelineRef.current = null
}
}, [renderer, scene, camera, hoverHighlightMode, zoneLayers, projectId, pipelineVersion])
}, [
camera,
hoverHiddenColor,
hoverPulseMix,
hoverStrength,
hoverVisibleColor,
pipelineVersion,
projectId,
renderer,
scene,
zoneLayers,
])
useFrame((_, delta) => {
// Animate background colour toward the current theme target (same lerp as AnimatedBackground)
+10 -1
View File
@@ -1,12 +1,18 @@
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 { ASSETS_CDN_URL, resolveAssetUrl, resolveCdnUrl } from './lib/asset-url'
export { SCENE_LAYER, ZONE_LAYER } from './lib/layers'
export {
applyMaterialPresetToMaterials,
clearMaterialCache,
createDefaultMaterial,
createMaterial,
createMaterialFromPresetRef,
DEFAULT_CEILING_MATERIAL,
DEFAULT_DOOR_MATERIAL,
DEFAULT_ROOF_MATERIAL,
@@ -19,3 +25,6 @@ export { mergedOutline } from './lib/merged-outline-node'
export { default as useViewer } from './store/use-viewer'
export { InteractiveSystem } from './systems/interactive/interactive-system'
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
View File
@@ -3,10 +3,6 @@ import type {
BaseNode,
BuildingNode,
LevelNode,
MaterialSchema,
RoofSurfaceMaterialRole,
StairSurfaceMaterialRole,
WallSurfaceSide,
ZoneNode,
} from '@pascal-app/core'
import type { Object3D } from 'three'
@@ -20,40 +16,12 @@ type Outliner = {
selectedObjects: 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 = {
selection: SelectionPath
previewSelectedIds: BaseNode['id'][]
setPreviewSelectedIds: (ids: BaseNode['id'][]) => void
materialPreview: MaterialPreview
setMaterialPreview: (preview: MaterialPreview) => void
clearMaterialPreview: () => void
hoverHighlightMode: 'default' | 'delete' | 'paint-ready' | 'paint-disabled'
setHoverHighlightMode: (mode: 'default' | 'delete' | 'paint-ready' | 'paint-disabled') => void
hoverHighlightMode: string
setHoverHighlightMode: (mode: string) => void
hoveredId: AnyNode['id'] | ZoneNode['id'] | null
setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void
cameraMode: 'perspective' | 'orthographic'
+3 -71
View File
@@ -1,17 +1,6 @@
'use client'
import type {
AnyNode,
AnyNodeId,
BaseNode,
BuildingNode,
LevelNode,
MaterialSchema,
RoofSurfaceMaterialRole,
StairSurfaceMaterialRole,
WallSurfaceSide,
ZoneNode,
} from '@pascal-app/core'
import type { AnyNode, BaseNode, BuildingNode, LevelNode, ZoneNode } from '@pascal-app/core'
import type { Object3D } from 'three'
import { create } from 'zustand'
@@ -29,59 +18,12 @@ type Outliner = {
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 = {
selection: SelectionPath
previewSelectedIds: BaseNode['id'][]
setPreviewSelectedIds: (ids: BaseNode['id'][]) => void
materialPreview: MaterialPreview
setMaterialPreview: (preview: MaterialPreview) => void
clearMaterialPreview: () => void
hoverHighlightMode: 'default' | 'delete' | 'paint-ready' | 'paint-disabled'
setHoverHighlightMode: (mode: 'default' | 'delete' | 'paint-ready' | 'paint-disabled') => void
hoverHighlightMode: string
setHoverHighlightMode: (mode: string) => void
hoveredId: AnyNode['id'] | ZoneNode['id'] | null
setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void
@@ -142,15 +84,6 @@ const useViewer = create<ViewerState>()(
selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] },
previewSelectedIds: [],
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',
setHoverHighlightMode: (mode) =>
set((state) => (state.hoverHighlightMode === mode ? state : { hoverHighlightMode: mode })),
@@ -255,7 +188,6 @@ const useViewer = create<ViewerState>()(
selectedIds: [],
},
previewSelectedIds: [],
materialPreview: null,
}),
outliner: { selectedObjects: [], hoveredObjects: [] },