Refactor viewer hover styles and remove preview state
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
@@ -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'
|
||||
|
||||
@@ -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: [] },
|
||||
|
||||
Reference in New Issue
Block a user