From 325f2e460351fc31d38bca0fb25864ceac317ad0 Mon Sep 17 00:00:00 2001 From: sudhir Date: Fri, 24 Apr 2026 22:49:04 +0530 Subject: [PATCH] Refactor viewer hover styles and remove preview state --- .../editor/src/components/editor/index.tsx | 20 +++- .../components/editor/selection-manager.tsx | 14 +-- .../renderers/ceiling/ceiling-renderer.tsx | 16 +-- .../renderers/fence/fence-renderer.tsx | 17 +-- .../roof-segment/roof-segment-renderer.tsx | 36 +----- .../renderers/roof/roof-renderer.tsx | 33 +----- .../renderers/slab/slab-renderer.tsx | 15 +-- .../stair-segment/stair-segment-renderer.tsx | 39 +------ .../renderers/stair/stair-renderer.tsx | 50 ++------ .../renderers/wall/wall-renderer.tsx | 26 +---- .../viewer/src/components/viewer/index.tsx | 6 +- .../src/components/viewer/post-processing.tsx | 109 +++++++++++++----- packages/viewer/src/index.ts | 11 +- packages/viewer/src/store/use-viewer.d.ts | 36 +----- packages/viewer/src/store/use-viewer.ts | 74 +----------- 15 files changed, 142 insertions(+), 360 deletions(-) diff --git a/packages/editor/src/components/editor/index.tsx b/packages/editor/src/components/editor/index.tsx index b61644af..a7591d90 100644 --- a/packages/editor/src/components/editor/index.tsx +++ b/packages/editor/src/components/editor/index.tsx @@ -7,7 +7,7 @@ import { spatialGridManager, useScene, } from '@pascal-app/core' -import { InteractiveSystem, useViewer, Viewer } from '@pascal-app/viewer' +import { type HoverStyles, InteractiveSystem, useViewer, Viewer } from '@pascal-app/viewer' import { memo, type ReactNode, @@ -75,6 +75,17 @@ const PAINT_CURSOR_BADGE_COLOR = '#f59e0b' const PAINT_CURSOR_BADGE_DISABLED_COLOR = '#94a3b8' const PAINT_CURSOR_BADGE_OFFSET_X = 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 @@ -885,7 +896,10 @@ const ViewerCanvas = memo(function ViewerCanvas({ /> ) : null} - + + diff --git a/packages/editor/src/components/editor/selection-manager.tsx b/packages/editor/src/components/editor/selection-manager.tsx index a53c870d..bf1fd870 100755 --- a/packages/editor/src/components/editor/selection-manager.tsx +++ b/packages/editor/src/components/editor/selection-manager.tsx @@ -25,20 +25,18 @@ import { type WallSurfaceSide, } 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 { applyMaterialPresetToMaterials, createMaterial, createMaterialFromPresetRef, -} from '../../../../viewer/src/lib/materials' -import { getRoofMaterialArray } from '../../../../viewer/src/systems/roof/roof-materials' -import { + getRoofMaterialArray, getStairBodyMaterials, getStairRailingMaterial, -} from '../../../../viewer/src/systems/stair/stair-materials' -import { getVisibleWallMaterials } from '../../../../viewer/src/systems/wall/wall-materials' + getVisibleWallMaterials, + useViewer, +} from '@pascal-app/viewer' +import { useCallback, useEffect, useRef } from 'react' +import { type BufferGeometry, Color, type Material, type Mesh, type Object3D } from 'three' import { type ActivePaintMaterial, buildRoofSurfaceMaterialPatch, diff --git a/packages/viewer/src/components/renderers/ceiling/ceiling-renderer.tsx b/packages/viewer/src/components/renderers/ceiling/ceiling-renderer.tsx index 398abf54..91e7487c 100644 --- a/packages/viewer/src/components/renderers/ceiling/ceiling-renderer.tsx +++ b/packages/viewer/src/components/renderers/ceiling/ceiling-renderer.tsx @@ -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 ( diff --git a/packages/viewer/src/components/renderers/fence/fence-renderer.tsx b/packages/viewer/src/components/renderers/fence/fence-renderer.tsx index c5448ada..4e563572 100644 --- a/packages/viewer/src/components/renderers/fence/fence-renderer.tsx +++ b/packages/viewer/src/components/renderers/fence/fence-renderer.tsx @@ -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(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) diff --git a/packages/viewer/src/components/renderers/roof-segment/roof-segment-renderer.tsx b/packages/viewer/src/components/renderers/roof-segment/roof-segment-renderer.tsx index daebf9fb..8bd052fa 100644 --- a/packages/viewer/src/components/renderers/roof-segment/roof-segment-renderer.tsx +++ b/packages/viewer/src/components/renderers/roof-segment/roof-segment-renderer.tsx @@ -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 diff --git a/packages/viewer/src/components/renderers/roof/roof-renderer.tsx b/packages/viewer/src/components/renderers/roof/roof-renderer.tsx index 4a4eb866..1dee2a98 100644 --- a/packages/viewer/src/components/renderers/roof/roof-renderer.tsx +++ b/packages/viewer/src/components/renderers/roof/roof-renderer.tsx @@ -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 diff --git a/packages/viewer/src/components/renderers/slab/slab-renderer.tsx b/packages/viewer/src/components/renderers/slab/slab-renderer.tsx index 0e97f23d..e2b935f6 100644 --- a/packages/viewer/src/components/renderers/slab/slab-renderer.tsx +++ b/packages/viewer/src/components/renderers/slab/slab-renderer.tsx @@ -8,7 +8,6 @@ import { createMaterial, DEFAULT_SLAB_MATERIAL, } from '../../../lib/materials' -import useViewer from '../../../store/use-viewer' const slabMaterialCache = new Map() @@ -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 ( diff --git a/packages/viewer/src/components/renderers/stair-segment/stair-segment-renderer.tsx b/packages/viewer/src/components/renderers/stair-segment/stair-segment-renderer.tsx index 1c27a364..73c834ea 100644 --- a/packages/viewer/src/components/renderers/stair-segment/stair-segment-renderer.tsx +++ b/packages/viewer/src/components/renderers/stair-segment/stair-segment-renderer.tsx @@ -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() diff --git a/packages/viewer/src/components/renderers/stair/stair-renderer.tsx b/packages/viewer/src/components/renderers/stair/stair-renderer.tsx index 4bc8821a..d05ed49c 100644 --- a/packages/viewer/src/components/renderers/stair/stair-renderer.tsx +++ b/packages/viewer/src/components/renderers/stair/stair-renderer.tsx @@ -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() diff --git a/packages/viewer/src/components/renderers/wall/wall-renderer.tsx b/packages/viewer/src/components/renderers/wall/wall-renderer.tsx index a61fbb82..9ff38006 100644 --- a/packages/viewer/src/components/renderers/wall/wall-renderer.tsx +++ b/packages/viewer/src/components/renderers/wall/wall-renderer.tsx @@ -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 ( diff --git a/packages/viewer/src/components/viewer/index.tsx b/packages/viewer/src/components/viewer/index.tsx index 42182d9a..6ee40f54 100644 --- a/packages/viewer/src/components/viewer/index.tsx +++ b/packages/viewer/src/components/viewer/index.tsx @@ -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 = ({ children, + hoverStyles = DEFAULT_HOVER_STYLES, selectionManager = 'default', perf = false, }) => { @@ -165,7 +167,7 @@ const Viewer: React.FC = ({ - + {/* */} diff --git a/packages/viewer/src/components/viewer/post-processing.tsx b/packages/viewer/src/components/viewer/post-processing.tsx index 63217975..253fb927 100644 --- a/packages/viewer/src/components/viewer/post-processing.tsx +++ b/packages/viewer/src/components/viewer/post-processing.tsx @@ -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 + +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(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) diff --git a/packages/viewer/src/index.ts b/packages/viewer/src/index.ts index 799e1312..e867a444 100644 --- a/packages/viewer/src/index.ts +++ b/packages/viewer/src/index.ts @@ -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' diff --git a/packages/viewer/src/store/use-viewer.d.ts b/packages/viewer/src/store/use-viewer.d.ts index 64586624..1342032a 100644 --- a/packages/viewer/src/store/use-viewer.d.ts +++ b/packages/viewer/src/store/use-viewer.d.ts @@ -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' diff --git a/packages/viewer/src/store/use-viewer.ts b/packages/viewer/src/store/use-viewer.ts index c431ab71..00649a63 100644 --- a/packages/viewer/src/store/use-viewer.ts +++ b/packages/viewer/src/store/use-viewer.ts @@ -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()( 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()( selectedIds: [], }, previewSelectedIds: [], - materialPreview: null, }), outliner: { selectedObjects: [], hoveredObjects: [] },