@pascal-app/viewer 0.7.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/renderers/node-renderer.d.ts +1 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +38 -22
- package/dist/components/renderers/parametric-node-renderer.d.ts +5 -0
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -0
- package/dist/components/renderers/parametric-node-renderer.js +34 -0
- package/dist/components/renderers/scene-renderer.d.ts +1 -1
- package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +5 -1
- package/dist/components/viewer/ground-occluder.d.ts +1 -1
- package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
- package/dist/components/viewer/ground-occluder.js +12 -24
- package/dist/components/viewer/index.d.ts +38 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +183 -60
- package/dist/components/viewer/lights.d.ts +1 -1
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +170 -43
- package/dist/components/viewer/perf-monitor.d.ts +1 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +99 -11
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +217 -61
- package/dist/components/viewer/registered-systems.d.ts +12 -0
- package/dist/components/viewer/registered-systems.d.ts.map +1 -0
- package/dist/components/viewer/registered-systems.js +47 -0
- package/dist/components/viewer/scene-bvh.d.ts +17 -0
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.js +94 -0
- package/dist/components/viewer/selection-manager.d.ts +1 -1
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +20 -5
- package/dist/components/viewer/viewer-camera.d.ts +1 -1
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/walkthrough-controls.d.ts +1 -1
- package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-node-events.d.ts +5 -73
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +19 -7
- package/dist/index.d.ts +36 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +67 -3
- package/dist/lib/csg-utils.d.ts +16 -0
- package/dist/lib/csg-utils.d.ts.map +1 -0
- package/dist/lib/csg-utils.js +33 -0
- package/dist/lib/drawable-geometry.d.ts +21 -0
- package/dist/lib/drawable-geometry.d.ts.map +1 -0
- package/dist/lib/drawable-geometry.js +22 -0
- package/dist/lib/edge-style.d.ts +3 -0
- package/dist/lib/edge-style.d.ts.map +1 -0
- package/dist/lib/edge-style.js +11 -0
- package/dist/lib/gpu-perf.d.ts +4 -0
- package/dist/lib/gpu-perf.d.ts.map +1 -0
- package/dist/lib/gpu-perf.js +23 -0
- package/dist/lib/ink-edges.d.ts +9 -0
- package/dist/lib/ink-edges.d.ts.map +1 -0
- package/dist/lib/ink-edges.js +43 -0
- package/dist/lib/isolation.d.ts +39 -0
- package/dist/lib/isolation.d.ts.map +1 -0
- package/dist/lib/isolation.js +103 -0
- package/dist/lib/layers.d.ts +18 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +18 -0
- package/dist/lib/materials.d.ts +25 -15
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +277 -63
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +11 -11
- package/dist/lib/polygon-union.d.ts +4 -0
- package/dist/lib/polygon-union.d.ts.map +1 -0
- package/dist/lib/polygon-union.js +289 -0
- package/dist/lib/polygon-union.test.d.ts +2 -0
- package/dist/lib/polygon-union.test.d.ts.map +1 -0
- package/dist/lib/polygon-union.test.js +105 -0
- package/dist/lib/scene-themes.d.ts +29 -0
- package/dist/lib/scene-themes.d.ts.map +1 -0
- package/dist/lib/scene-themes.js +193 -0
- package/dist/store/use-viewer.d.ts +42 -4
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +26 -3
- package/dist/systems/ceiling/ceiling-system.d.ts +6 -2
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +62 -12
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +373 -22
- package/dist/systems/elevator/elevator-interaction-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-interaction-system.js +62 -0
- package/dist/systems/fence/fence-system.d.ts +3 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -1
- package/dist/systems/fence/fence-system.js +10 -6
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts +20 -0
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -0
- package/dist/systems/floor-elevation/floor-elevation-system.js +78 -0
- package/dist/systems/geometry/geometry-system.d.ts +34 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.js +279 -0
- package/dist/systems/guide/guide-system.d.ts.map +1 -1
- package/dist/systems/guide/guide-system.js +1 -26
- package/dist/systems/interactive/interactive-system.d.ts +1 -1
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.d.ts +7 -0
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +8 -11
- package/dist/systems/item-light/item-light-system.d.ts +1 -1
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
- package/dist/systems/item-light/item-light-system.js +18 -5
- package/dist/systems/roof/roof-materials.d.ts +3 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +38 -14
- package/dist/systems/roof/roof-system.d.ts +27 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +411 -71
- package/dist/systems/scan/scan-system.d.ts.map +1 -1
- package/dist/systems/scan/scan-system.js +1 -26
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +167 -49
- package/dist/systems/slab/slab-system.test.d.ts +2 -0
- package/dist/systems/slab/slab-system.test.d.ts.map +1 -0
- package/dist/systems/slab/slab-system.test.js +62 -0
- package/dist/systems/stair/stair-materials.d.ts +4 -3
- package/dist/systems/stair/stair-materials.d.ts.map +1 -1
- package/dist/systems/stair/stair-materials.js +30 -15
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +58 -71
- package/dist/systems/surface-hole-geometry.d.ts +3 -0
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -0
- package/dist/systems/surface-hole-geometry.js +4 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +18 -2
- package/dist/systems/wall/wall-materials.d.ts +4 -3
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +52 -28
- package/dist/systems/wall/wall-system.d.ts +1 -1
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +179 -31
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +71 -7
- package/package.json +4 -5
- package/dist/components/renderers/building/building-renderer.d.ts +0 -5
- package/dist/components/renderers/building/building-renderer.d.ts.map +0 -1
- package/dist/components/renderers/building/building-renderer.js +0 -11
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +0 -5
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +0 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.js +0 -58
- package/dist/components/renderers/column/column-renderer.d.ts +0 -5
- package/dist/components/renderers/column/column-renderer.d.ts.map +0 -1
- package/dist/components/renderers/column/column-renderer.js +0 -594
- package/dist/components/renderers/door/door-renderer.d.ts +0 -5
- package/dist/components/renderers/door/door-renderer.d.ts.map +0 -1
- package/dist/components/renderers/door/door-renderer.js +0 -16
- package/dist/components/renderers/fence/fence-renderer.d.ts +0 -5
- package/dist/components/renderers/fence/fence-renderer.d.ts.map +0 -1
- package/dist/components/renderers/fence/fence-renderer.js +0 -29
- package/dist/components/renderers/guide/guide-renderer.d.ts +0 -5
- package/dist/components/renderers/guide/guide-renderer.d.ts.map +0 -1
- package/dist/components/renderers/guide/guide-renderer.js +0 -38
- package/dist/components/renderers/item/item-renderer.d.ts +0 -5
- package/dist/components/renderers/item/item-renderer.d.ts.map +0 -1
- package/dist/components/renderers/item/item-renderer.js +0 -172
- package/dist/components/renderers/level/level-renderer.d.ts +0 -5
- package/dist/components/renderers/level/level-renderer.d.ts.map +0 -1
- package/dist/components/renderers/level/level-renderer.js +0 -11
- package/dist/components/renderers/roof/roof-materials.d.ts +0 -4
- package/dist/components/renderers/roof/roof-materials.d.ts.map +0 -1
- package/dist/components/renderers/roof/roof-materials.js +0 -16
- package/dist/components/renderers/roof/roof-renderer.d.ts +0 -5
- package/dist/components/renderers/roof/roof-renderer.d.ts.map +0 -1
- package/dist/components/renderers/roof/roof-renderer.js +0 -32
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +0 -5
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +0 -1
- package/dist/components/renderers/roof-segment/roof-segment-renderer.js +0 -40
- package/dist/components/renderers/scan/scan-renderer.d.ts +0 -5
- package/dist/components/renderers/scan/scan-renderer.d.ts.map +0 -1
- package/dist/components/renderers/scan/scan-renderer.js +0 -54
- package/dist/components/renderers/site/site-renderer.d.ts +0 -5
- package/dist/components/renderers/site/site-renderer.d.ts.map +0 -1
- package/dist/components/renderers/site/site-renderer.js +0 -106
- package/dist/components/renderers/slab/slab-renderer.d.ts +0 -5
- package/dist/components/renderers/slab/slab-renderer.d.ts.map +0 -1
- package/dist/components/renderers/slab/slab-renderer.js +0 -58
- package/dist/components/renderers/spawn/spawn-renderer.d.ts +0 -5
- package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +0 -1
- package/dist/components/renderers/spawn/spawn-renderer.js +0 -32
- package/dist/components/renderers/stair/stair-renderer.d.ts +0 -5
- package/dist/components/renderers/stair/stair-renderer.d.ts.map +0 -1
- package/dist/components/renderers/stair/stair-renderer.js +0 -603
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts +0 -5
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +0 -1
- package/dist/components/renderers/stair-segment/stair-segment-renderer.js +0 -32
- package/dist/components/renderers/wall/wall-renderer.d.ts +0 -5
- package/dist/components/renderers/wall/wall-renderer.d.ts.map +0 -1
- package/dist/components/renderers/wall/wall-renderer.js +0 -16
- package/dist/components/renderers/window/window-renderer.d.ts +0 -5
- package/dist/components/renderers/window/window-renderer.d.ts.map +0 -1
- package/dist/components/renderers/window/window-renderer.js +0 -21
- package/dist/components/renderers/zone/zone-renderer.d.ts +0 -5
- package/dist/components/renderers/zone/zone-renderer.d.ts.map +0 -1
- package/dist/components/renderers/zone/zone-renderer.js +0 -181
- package/dist/lib/suppress-three-clock-warning.d.ts +0 -25
- package/dist/lib/suppress-three-clock-warning.d.ts.map +0 -1
- package/dist/lib/suppress-three-clock-warning.js +0 -51
- package/dist/systems/export/export-system.d.ts +0 -2
- package/dist/systems/export/export-system.d.ts.map +0 -1
- package/dist/systems/export/export-system.js +0 -60
- package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts +0 -14
- package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts.map +0 -1
- package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.js +0 -155
- package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts +0 -6
- package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts.map +0 -1
- package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.js +0 -75
- package/dist/systems/item-mesh-metadata/sync-request.d.ts +0 -7
- package/dist/systems/item-mesh-metadata/sync-request.d.ts.map +0 -1
- package/dist/systems/item-mesh-metadata/sync-request.js +0 -25
|
@@ -5,8 +5,12 @@ import { ssgi } from 'three/addons/tsl/display/SSGINode.js';
|
|
|
5
5
|
import { denoise } from 'three/examples/jsm/tsl/display/DenoiseNode.js';
|
|
6
6
|
import { add, colorToDirection, diffuseColor, directionToColor, float, mix, mrt, normalView, oscSine, output, pass, sample, time, uniform, vec4, } from 'three/tsl';
|
|
7
7
|
import { RenderPipeline } from 'three/webgpu';
|
|
8
|
-
import {
|
|
8
|
+
import { edgeColorFor } from '../../lib/edge-style';
|
|
9
|
+
import { PERF_OVERLAY_ENABLED, pushGpuSample } from '../../lib/gpu-perf';
|
|
10
|
+
import { inkedEdges } from '../../lib/ink-edges';
|
|
11
|
+
import { GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, ZONE_LAYER } from '../../lib/layers';
|
|
9
12
|
import { mergedOutline } from '../../lib/merged-outline-node';
|
|
13
|
+
import { getSceneTheme } from '../../lib/scene-themes';
|
|
10
14
|
import useViewer from '../../store/use-viewer';
|
|
11
15
|
// SSGI Parameters - adjust these to fine-tune global illumination and ambient occlusion
|
|
12
16
|
export const SSGI_PARAMS = {
|
|
@@ -23,10 +27,38 @@ export const SSGI_PARAMS = {
|
|
|
23
27
|
useScreenSpaceSampling: true,
|
|
24
28
|
useTemporalFiltering: false,
|
|
25
29
|
};
|
|
30
|
+
// Diagnostic toggles for thermal A/B testing. Add `?disable=ao,denoise,outline,postFx`
|
|
31
|
+
// to the URL (any subset) and reload to skip those passes. Each flag prevents
|
|
32
|
+
// allocation + per-frame work for that stage, so device temperature deltas
|
|
33
|
+
// across combos isolate which pass is the actual culprit. Picked up once at
|
|
34
|
+
// pipeline build; reload after changing the URL.
|
|
35
|
+
// - ao: skip SSGI entirely (and denoise, since denoise has nothing to denoise)
|
|
36
|
+
// - denoise: keep SSGI but feed its raw noisy AO straight to the composite
|
|
37
|
+
// - outline: skip the merged-outline node and its 14 internal RTs
|
|
38
|
+
// - postFx: bypass the whole RenderPipeline and use renderer.render(scene, camera)
|
|
39
|
+
// directly — isolates raw scene-render cost from any post-FX overhead
|
|
40
|
+
function readPerfDisableFlags() {
|
|
41
|
+
if (typeof window === 'undefined') {
|
|
42
|
+
return { ao: false, denoise: false, outline: false, postFx: false };
|
|
43
|
+
}
|
|
44
|
+
const raw = new URLSearchParams(window.location.search).get('disable') ?? '';
|
|
45
|
+
const set = new Set(raw
|
|
46
|
+
.split(',')
|
|
47
|
+
.map((s) => s.trim())
|
|
48
|
+
.filter(Boolean));
|
|
49
|
+
return {
|
|
50
|
+
ao: set.has('ao'),
|
|
51
|
+
denoise: set.has('denoise'),
|
|
52
|
+
outline: set.has('outline'),
|
|
53
|
+
postFx: set.has('postFx'),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const PERF_POST_FX_DISABLED = typeof window !== 'undefined' &&
|
|
57
|
+
new Set((new URLSearchParams(window.location.search).get('disable') ?? '')
|
|
58
|
+
.split(',')
|
|
59
|
+
.map((s) => s.trim())).has('postFx');
|
|
26
60
|
const MAX_PIPELINE_RETRIES = 3;
|
|
27
61
|
const RETRY_DELAY_MS = 500;
|
|
28
|
-
const DARK_BG = '#1f2433';
|
|
29
|
-
const LIGHT_BG = '#ffffff';
|
|
30
62
|
const DEFAULT_HOVER_STYLE = {
|
|
31
63
|
visibleColor: 0x00_aa_ff,
|
|
32
64
|
hiddenColor: 0xf3_ff_47,
|
|
@@ -48,23 +80,46 @@ function sanitizeOutlineObjects(objects) {
|
|
|
48
80
|
objects.length = nextIndex;
|
|
49
81
|
}
|
|
50
82
|
const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
51
|
-
const { gl: renderer, invalidate, scene, camera } = useThree();
|
|
83
|
+
const { gl: renderer, invalidate, scene, camera, size } = useThree();
|
|
52
84
|
const renderPipelineRef = useRef(null);
|
|
53
85
|
const hasPipelineErrorRef = useRef(false);
|
|
54
86
|
const retryCountRef = useRef(0);
|
|
55
87
|
const rebuildTimeoutRef = useRef(null);
|
|
88
|
+
const skippedZeroSizeRef = useRef(false);
|
|
56
89
|
// Background color uniform — updated every frame via lerp, read by the TSL pipeline.
|
|
57
|
-
// Initialised from the current theme so there's no flash on first render.
|
|
58
|
-
const initBg = useViewer.getState().
|
|
90
|
+
// Initialised from the current scene theme so there's no flash on first render.
|
|
91
|
+
const initBg = getSceneTheme(useViewer.getState().sceneTheme).background;
|
|
59
92
|
const bgUniform = useRef(uniform(new Color(initBg)));
|
|
60
93
|
const bgCurrent = useRef(new Color(initBg));
|
|
61
94
|
const bgTarget = useRef(new Color());
|
|
95
|
+
// Ink-line colour follows the scene-theme background luminance (dark lines on
|
|
96
|
+
// light scenes, light on dark), refreshed each frame like the background.
|
|
97
|
+
const inkColorUniform = useRef(uniform(new Color(edgeColorFor(initBg))));
|
|
62
98
|
const zoneLayers = useMemo(() => {
|
|
63
99
|
const l = new Layers();
|
|
64
100
|
l.enable(ZONE_LAYER);
|
|
65
101
|
l.disable(SCENE_LAYER);
|
|
66
102
|
return l;
|
|
67
103
|
}, []);
|
|
104
|
+
// Scene pass renders the main geometry layer plus the grid. The default camera
|
|
105
|
+
// mask also has the overlay layer enabled (custom controls enable it for
|
|
106
|
+
// picking), so without this the gizmos/handles/tool previews land in the
|
|
107
|
+
// depth+normal MRT and get inked / AO'd as if they were geometry. The grid is
|
|
108
|
+
// kept in here (not the overlay pass) so scene geometry depth-occludes it; it's
|
|
109
|
+
// a flat, depth-non-writing plane so the ink never picks it up.
|
|
110
|
+
const sceneOnlyLayers = useMemo(() => {
|
|
111
|
+
const l = new Layers();
|
|
112
|
+
l.set(SCENE_LAYER);
|
|
113
|
+
l.enable(GRID_LAYER);
|
|
114
|
+
return l;
|
|
115
|
+
}, []);
|
|
116
|
+
// Editor overlays render in their own pass, composited on top after the ink
|
|
117
|
+
// and outlines so they read as crisp UI rather than scene geometry.
|
|
118
|
+
const overlayLayers = useMemo(() => {
|
|
119
|
+
const l = new Layers();
|
|
120
|
+
l.set(OVERLAY_LAYER);
|
|
121
|
+
return l;
|
|
122
|
+
}, []);
|
|
68
123
|
const hoverHighlightMode = useViewer((s) => s.hoverHighlightMode);
|
|
69
124
|
const hoverVisibleColor = useMemo(() => uniform(new Color(DEFAULT_HOVER_STYLE.visibleColor)), []);
|
|
70
125
|
const hoverHiddenColor = useMemo(() => uniform(new Color(DEFAULT_HOVER_STYLE.hiddenColor)), []);
|
|
@@ -72,6 +127,9 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
72
127
|
const hoverPulseMix = useMemo(() => uniform(DEFAULT_HOVER_STYLE.pulse ? 0 : 1), []);
|
|
73
128
|
// Subscribe to projectId so the pipeline rebuilds on project switch
|
|
74
129
|
const projectId = useViewer((s) => s.projectId);
|
|
130
|
+
const shading = useViewer((s) => s.shading);
|
|
131
|
+
const edges = useViewer((s) => s.edges);
|
|
132
|
+
const lastProjectIdRef = useRef(projectId);
|
|
75
133
|
// Bump this to force a pipeline rebuild (used by retry logic)
|
|
76
134
|
const [pipelineVersion, setPipelineVersion] = useState(0);
|
|
77
135
|
const requestPipelineRebuild = useCallback(() => {
|
|
@@ -83,8 +141,9 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
83
141
|
}, []);
|
|
84
142
|
// Reset retry state when project changes
|
|
85
143
|
useEffect(() => {
|
|
86
|
-
|
|
87
|
-
|
|
144
|
+
if (lastProjectIdRef.current === projectId)
|
|
145
|
+
return;
|
|
146
|
+
lastProjectIdRef.current = projectId;
|
|
88
147
|
retryCountRef.current = 0;
|
|
89
148
|
if (rebuildTimeoutRef.current !== null) {
|
|
90
149
|
clearTimeout(rebuildTimeoutRef.current);
|
|
@@ -117,10 +176,8 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
117
176
|
]);
|
|
118
177
|
// Build / rebuild the post-processing pipeline
|
|
119
178
|
useEffect(() => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
void projectId;
|
|
123
|
-
void pipelineVersion;
|
|
179
|
+
const width = Math.floor(size.width);
|
|
180
|
+
const height = Math.floor(size.height);
|
|
124
181
|
if (!(renderer && scene && camera)) {
|
|
125
182
|
console.warn('[viewer/post-processing] Skipping pipeline build — missing dependency.', {
|
|
126
183
|
hasRenderer: !!renderer,
|
|
@@ -129,12 +186,44 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
129
186
|
});
|
|
130
187
|
return;
|
|
131
188
|
}
|
|
189
|
+
if (width < 1 || height < 1) {
|
|
190
|
+
skippedZeroSizeRef.current = true;
|
|
191
|
+
hasPipelineErrorRef.current = false;
|
|
192
|
+
if (renderPipelineRef.current) {
|
|
193
|
+
renderPipelineRef.current.dispose();
|
|
194
|
+
}
|
|
195
|
+
renderPipelineRef.current = null;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (skippedZeroSizeRef.current) {
|
|
199
|
+
skippedZeroSizeRef.current = false;
|
|
200
|
+
}
|
|
201
|
+
const perfDisable = readPerfDisableFlags();
|
|
202
|
+
const ssgiEnabled = shading === 'rendered' && SSGI_PARAMS.enabled && !perfDisable.ao;
|
|
203
|
+
const denoiseEnabled = ssgiEnabled && !perfDisable.denoise;
|
|
204
|
+
const outlineEnabled = !perfDisable.outline;
|
|
205
|
+
const inkEnabled = edges !== 'off';
|
|
206
|
+
// The depth+normal MRT feeds both SSGI and the screen-space ink pass.
|
|
207
|
+
const needsNormalMRT = ssgiEnabled || inkEnabled;
|
|
208
|
+
// Soft = thin (1px sample radius) + faint (50% opacity); strong = thick
|
|
209
|
+
// (2px, ~2× wider detected band) + solid (100%). The edge masks saturate,
|
|
210
|
+
// so radius+opacity are what actually separate the two modes — gain wouldn't.
|
|
211
|
+
// Same 1px line thickness for both (soft's thickness is the nice one);
|
|
212
|
+
// strong reads heavier purely by being fully solid vs soft's lighter 50%.
|
|
213
|
+
const inkRadius = 1;
|
|
214
|
+
const inkOpacity = edges === 'strong' ? 1 : 0.5;
|
|
132
215
|
console.log('[viewer/post-processing] Building pipeline', {
|
|
133
216
|
version: pipelineVersion,
|
|
134
|
-
ssgi:
|
|
217
|
+
ssgi: ssgiEnabled,
|
|
218
|
+
denoise: denoiseEnabled,
|
|
219
|
+
outline: outlineEnabled,
|
|
220
|
+
perfDisable,
|
|
135
221
|
hoverHighlightMode,
|
|
136
222
|
projectId,
|
|
223
|
+
shading,
|
|
137
224
|
rendererCtor: renderer.constructor?.name,
|
|
225
|
+
width,
|
|
226
|
+
height,
|
|
138
227
|
});
|
|
139
228
|
hasPipelineErrorRef.current = false;
|
|
140
229
|
// WebGPU availability check: SSGI, denoise, and RenderPipeline are all
|
|
@@ -147,7 +236,6 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
147
236
|
// exclusively and never attempts the TSL pipeline.
|
|
148
237
|
const hasWebGPU = typeof navigator !== 'undefined' && 'gpu' in navigator;
|
|
149
238
|
if (!hasWebGPU) {
|
|
150
|
-
console.warn('[viewer] WebGPU unavailable — rendering without post-processing (SSGI, outlines, denoise).');
|
|
151
239
|
hasPipelineErrorRef.current = true;
|
|
152
240
|
renderPipelineRef.current = null;
|
|
153
241
|
return;
|
|
@@ -161,8 +249,15 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
161
249
|
outliner.hoveredObjects.length = 0;
|
|
162
250
|
try {
|
|
163
251
|
const scenePass = pass(scene, camera);
|
|
252
|
+
scenePass.setLayers(sceneOnlyLayers);
|
|
164
253
|
const zonePass = pass(scene, camera);
|
|
165
254
|
zonePass.setLayers(zoneLayers);
|
|
255
|
+
// Editor overlays (gizmos, move handles, tool previews, grid) on their own
|
|
256
|
+
// layer, kept out of the depth/normal MRT above so the ink + SSGI ignore
|
|
257
|
+
// them, then composited on top of the final image below.
|
|
258
|
+
const overlayPass = pass(scene, camera);
|
|
259
|
+
overlayPass.setLayers(overlayLayers);
|
|
260
|
+
const overlayColor = overlayPass.getTextureNode('output');
|
|
166
261
|
const scenePassColor = scenePass.getTextureNode('output');
|
|
167
262
|
// Background detection via alpha: renderer clears with alpha=0 (setClearAlpha(0) in useFrame),
|
|
168
263
|
// so background pixels have scenePassColor.a=0 while geometry pixels have output.a=1.
|
|
@@ -171,23 +266,28 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
171
266
|
const hasGeometry = scenePassColor.a;
|
|
172
267
|
const contentAlpha = hasGeometry.max(zonePass.a);
|
|
173
268
|
let sceneColor = scenePassColor;
|
|
174
|
-
|
|
175
|
-
|
|
269
|
+
// Depth + normal MRT — shared by SSGI (diffuse/normal) and the ink pass
|
|
270
|
+
// (depth/normal). Built whenever either is active.
|
|
271
|
+
let scenePassDepth = null;
|
|
272
|
+
let scenePassNormal = null;
|
|
273
|
+
let sceneNormal = null;
|
|
274
|
+
if (needsNormalMRT) {
|
|
176
275
|
scenePass.setMRT(mrt({
|
|
177
276
|
output,
|
|
178
277
|
diffuseColor,
|
|
179
278
|
normal: directionToColor(normalView),
|
|
180
279
|
}));
|
|
280
|
+
scenePassDepth = scenePass.getTextureNode('depth');
|
|
281
|
+
scenePassNormal = scenePass.getTextureNode('normal');
|
|
282
|
+
const normalTexture = scenePass.getTexture('normal');
|
|
283
|
+
normalTexture.type = UnsignedByteType;
|
|
284
|
+
// Extract normal from color-encoded texture (SSGI consumes the node form)
|
|
285
|
+
sceneNormal = sample((uv) => colorToDirection(scenePassNormal.sample(uv)));
|
|
286
|
+
}
|
|
287
|
+
if (ssgiEnabled) {
|
|
181
288
|
const scenePassDiffuse = scenePass.getTextureNode('diffuseColor');
|
|
182
|
-
const scenePassDepth = scenePass.getTextureNode('depth');
|
|
183
|
-
const scenePassNormal = scenePass.getTextureNode('normal');
|
|
184
|
-
// Optimize texture bandwidth
|
|
185
289
|
const diffuseTexture = scenePass.getTexture('diffuseColor');
|
|
186
290
|
diffuseTexture.type = UnsignedByteType;
|
|
187
|
-
const normalTexture = scenePass.getTexture('normal');
|
|
188
|
-
normalTexture.type = UnsignedByteType;
|
|
189
|
-
// Extract normal from color-encoded texture
|
|
190
|
-
const sceneNormal = sample((uv) => colorToDirection(scenePassNormal.sample(uv)));
|
|
191
291
|
const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, camera);
|
|
192
292
|
giPass.sliceCount.value = SSGI_PARAMS.sliceCount;
|
|
193
293
|
giPass.stepCount.value = SSGI_PARAMS.stepCount;
|
|
@@ -201,49 +301,76 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
201
301
|
giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling;
|
|
202
302
|
giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering;
|
|
203
303
|
const giTexture = giPass.getTextureNode();
|
|
204
|
-
// DenoiseNode only denoises RGB — alpha is passed through unchanged.
|
|
205
|
-
// SSGI packs AO into alpha, so we remap it into RGB before denoising.
|
|
206
|
-
const aoAsRgb = vec4(giTexture.a, giTexture.a, giTexture.a, float(1));
|
|
207
|
-
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
|
|
208
|
-
denoisePass.index.value = 0;
|
|
209
|
-
denoisePass.radius.value = 4;
|
|
210
304
|
const gi = giPass.rgb;
|
|
211
|
-
|
|
305
|
+
let ao;
|
|
306
|
+
if (denoiseEnabled) {
|
|
307
|
+
// DenoiseNode only denoises RGB — alpha is passed through unchanged.
|
|
308
|
+
// SSGI packs AO into alpha, so we remap it into RGB before denoising.
|
|
309
|
+
const aoAsRgb = vec4(giTexture.a, giTexture.a, giTexture.a, float(1));
|
|
310
|
+
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
|
|
311
|
+
denoisePass.index.value = 0;
|
|
312
|
+
denoisePass.radius.value = 4;
|
|
313
|
+
ao = denoisePass.r;
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
// Diagnostic path: feed raw noisy SSGI AO straight through. Will
|
|
317
|
+
// look grainy — that's the point, it isolates denoise cost.
|
|
318
|
+
ao = giTexture.a;
|
|
319
|
+
}
|
|
212
320
|
// Composite: scene * AO + diffuse * GI
|
|
213
321
|
sceneColor = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))), contentAlpha);
|
|
214
322
|
}
|
|
323
|
+
// Screen-space ink outline (SketchUp look) — depth/normal edge detection
|
|
324
|
+
// over the composited scene. Topology-agnostic, so it handles CSG-cut
|
|
325
|
+
// walls cleanly. Applied before the selection outline + background mix.
|
|
326
|
+
if (inkEnabled) {
|
|
327
|
+
sceneColor = vec4(inkedEdges({
|
|
328
|
+
sceneRgb: sceneColor.rgb,
|
|
329
|
+
depthTex: scenePassDepth,
|
|
330
|
+
normalTex: scenePassNormal,
|
|
331
|
+
inkColor: inkColorUniform.current,
|
|
332
|
+
radius: inkRadius,
|
|
333
|
+
opacity: inkOpacity,
|
|
334
|
+
}), sceneColor.a);
|
|
335
|
+
}
|
|
215
336
|
// Single merged outline node: one shared depth pass for both selected + hovered groups.
|
|
216
337
|
const outliner = useViewer.getState().outliner;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
338
|
+
let compositeWithOutlines = sceneColor;
|
|
339
|
+
if (outlineEnabled) {
|
|
340
|
+
const outlineNode = mergedOutline(scene, camera, {
|
|
341
|
+
primaryObjects: outliner.selectedObjects,
|
|
342
|
+
secondaryObjects: outliner.hoveredObjects,
|
|
343
|
+
primaryEdgeThickness: uniform(1),
|
|
344
|
+
secondaryEdgeThickness: uniform(1.5),
|
|
345
|
+
});
|
|
346
|
+
// Selected: white visible, yellow hidden
|
|
347
|
+
const selectedVisibleColor = uniform(new Color(0xff_ff_ff));
|
|
348
|
+
const selectedHiddenColor = uniform(new Color(0xf3_ff_47));
|
|
349
|
+
const selectedStrength = uniform(3);
|
|
350
|
+
const selectedOutline = outlineNode.primaryVisibleEdge
|
|
351
|
+
.mul(selectedVisibleColor)
|
|
352
|
+
.add(outlineNode.primaryHiddenEdge.mul(selectedHiddenColor))
|
|
353
|
+
.mul(selectedStrength);
|
|
354
|
+
// Hovered: blue visible, yellow hidden, pulsing
|
|
355
|
+
const pulsePeriod = uniform(3);
|
|
356
|
+
const oscillating = oscSine(time.div(pulsePeriod).mul(2)).mul(0.5).add(0.5);
|
|
357
|
+
const osc = mix(oscillating, float(1), hoverPulseMix);
|
|
358
|
+
const hoverOutline = outlineNode.secondaryVisibleEdge
|
|
359
|
+
.mul(hoverVisibleColor)
|
|
360
|
+
.add(outlineNode.secondaryHiddenEdge.mul(hoverHiddenColor))
|
|
361
|
+
.mul(hoverStrength)
|
|
362
|
+
.mul(osc);
|
|
363
|
+
compositeWithOutlines = vec4(add(sceneColor.rgb, selectedOutline.add(hoverOutline)), sceneColor.a);
|
|
364
|
+
}
|
|
365
|
+
const composited = mix(bgUniform.current, compositeWithOutlines.rgb, contentAlpha);
|
|
366
|
+
// Editor overlays painted on top by their own alpha — they never get inked,
|
|
367
|
+
// AO'd, or outlined, and always read crisp regardless of scene depth.
|
|
368
|
+
const withOverlay = mix(composited, overlayColor.rgb, overlayColor.a);
|
|
369
|
+
const finalOutput = vec4(withOverlay, float(1));
|
|
242
370
|
const renderPipeline = new RenderPipeline(renderer);
|
|
243
371
|
renderPipeline.outputNode = finalOutput;
|
|
244
372
|
renderPipelineRef.current = renderPipeline;
|
|
245
373
|
retryCountRef.current = 0;
|
|
246
|
-
console.log('[viewer/post-processing] Pipeline built OK', { version: pipelineVersion });
|
|
247
374
|
}
|
|
248
375
|
catch (error) {
|
|
249
376
|
hasPipelineErrorRef.current = true;
|
|
@@ -266,31 +393,49 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
266
393
|
}, [
|
|
267
394
|
camera,
|
|
268
395
|
hoverHiddenColor,
|
|
396
|
+
hoverHighlightMode,
|
|
269
397
|
hoverPulseMix,
|
|
270
398
|
hoverStrength,
|
|
271
399
|
hoverVisibleColor,
|
|
400
|
+
edges,
|
|
272
401
|
pipelineVersion,
|
|
273
402
|
projectId,
|
|
274
403
|
renderer,
|
|
275
404
|
scene,
|
|
405
|
+
shading,
|
|
406
|
+
size.height,
|
|
407
|
+
size.width,
|
|
276
408
|
zoneLayers,
|
|
409
|
+
sceneOnlyLayers,
|
|
410
|
+
overlayLayers,
|
|
277
411
|
]);
|
|
278
412
|
useFrame((_, delta) => {
|
|
279
|
-
|
|
280
|
-
|
|
413
|
+
if (size.width < 1 || size.height < 1) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
// Animate background colour toward the current scene theme target (same lerp as AnimatedBackground)
|
|
417
|
+
bgTarget.current.set(getSceneTheme(useViewer.getState().sceneTheme).background);
|
|
281
418
|
bgCurrent.current.lerp(bgTarget.current, Math.min(delta, 0.1) * 4);
|
|
282
419
|
bgUniform.current.value.copy(bgCurrent.current);
|
|
420
|
+
// Ink colour follows the (lerping) background luminance — snaps dark↔light.
|
|
421
|
+
inkColorUniform.current.value.set(edgeColorFor(`#${bgCurrent.current.getHexString()}`));
|
|
283
422
|
const outliner = useViewer.getState().outliner;
|
|
284
423
|
sanitizeOutlineObjects(outliner.selectedObjects);
|
|
285
424
|
sanitizeOutlineObjects(outliner.hoveredObjects);
|
|
286
|
-
if (hasPipelineErrorRef.current || !renderPipelineRef.current) {
|
|
425
|
+
if (PERF_POST_FX_DISABLED || hasPipelineErrorRef.current || !renderPipelineRef.current) {
|
|
287
426
|
try {
|
|
288
427
|
if (renderer.setClearAlpha) {
|
|
289
428
|
;
|
|
290
429
|
renderer.setClearAlpha(1);
|
|
291
430
|
}
|
|
292
|
-
;
|
|
431
|
+
const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
|
|
293
432
|
renderer.render(scene, camera);
|
|
433
|
+
if (PERF_OVERLAY_ENABLED) {
|
|
434
|
+
const queue = renderer.backend?.device?.queue;
|
|
435
|
+
queue?.onSubmittedWorkDone?.().then(() => {
|
|
436
|
+
pushGpuSample(performance.now() - submittedAt);
|
|
437
|
+
});
|
|
438
|
+
}
|
|
294
439
|
}
|
|
295
440
|
catch (fallbackError) {
|
|
296
441
|
console.error('[viewer/post-processing] Fallback render failed.', fallbackError);
|
|
@@ -302,7 +447,19 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
302
447
|
// making scenePassColor.a a reliable geometry mask (geometry pixels write a=1 via output node).
|
|
303
448
|
;
|
|
304
449
|
renderer.setClearAlpha(0);
|
|
450
|
+
const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
|
|
305
451
|
renderPipelineRef.current.render();
|
|
452
|
+
if (PERF_OVERLAY_ENABLED) {
|
|
453
|
+
// device.queue.onSubmittedWorkDone() resolves once the GPU has
|
|
454
|
+
// finished the work we just submitted — the delta from our submit
|
|
455
|
+
// timestamp is a clean per-frame GPU duration. Doesn't block CPU
|
|
456
|
+
// (no await) and works for the custom RenderPipeline path that
|
|
457
|
+
// bypasses three.js's timestamp-query infrastructure.
|
|
458
|
+
const queue = renderer.backend?.device?.queue;
|
|
459
|
+
queue?.onSubmittedWorkDone?.().then(() => {
|
|
460
|
+
pushGpuSample(performance.now() - submittedAt);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
306
463
|
}
|
|
307
464
|
catch (error) {
|
|
308
465
|
hasPipelineErrorRef.current = true;
|
|
@@ -318,7 +475,6 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
318
475
|
if (retryCountRef.current < MAX_PIPELINE_RETRIES) {
|
|
319
476
|
// Auto-retry: schedule a pipeline rebuild if we haven't exceeded the retry limit
|
|
320
477
|
retryCountRef.current++;
|
|
321
|
-
console.warn(`[viewer/post-processing] Scheduling pipeline rebuild (attempt ${retryCountRef.current}/${MAX_PIPELINE_RETRIES})`);
|
|
322
478
|
if (rebuildTimeoutRef.current !== null) {
|
|
323
479
|
clearTimeout(rebuildTimeoutRef.current);
|
|
324
480
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mounts every registered node kind's system component, ordered by
|
|
3
|
+
* `system.priority` (default {@link DEFAULT_PRIORITY}).
|
|
4
|
+
*
|
|
5
|
+
* Today the registry is empty so this component mounts nothing — coexists
|
|
6
|
+
* with legacy `*-System` components in `<Viewer>`. Once kinds register via
|
|
7
|
+
* `@pascal-app/nodes`, each kind's registry-driven system takes over and
|
|
8
|
+
* its legacy counterpart short-circuits via the `nodeRegistry.has(kind)`
|
|
9
|
+
* guard added to each legacy system.
|
|
10
|
+
*/
|
|
11
|
+
export declare function RegisteredSystems(): import("react").JSX.Element | null;
|
|
12
|
+
//# sourceMappingURL=registered-systems.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registered-systems.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/registered-systems.tsx"],"names":[],"mappings":"AAoBA;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,uCAsBhC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { nodeRegistry } from '@pascal-app/core';
|
|
4
|
+
import { lazy, Suspense, useMemo } from 'react';
|
|
5
|
+
const DEFAULT_PRIORITY = 5;
|
|
6
|
+
// Cache lazy components keyed by the module-loader function so React.lazy
|
|
7
|
+
// isn't re-invoked across renders.
|
|
8
|
+
const lazyCache = new WeakMap();
|
|
9
|
+
function loadSystem(def) {
|
|
10
|
+
if (!def.system)
|
|
11
|
+
return null;
|
|
12
|
+
const cached = lazyCache.get(def.system.module);
|
|
13
|
+
if (cached)
|
|
14
|
+
return cached;
|
|
15
|
+
const Comp = lazy(def.system.module);
|
|
16
|
+
lazyCache.set(def.system.module, Comp);
|
|
17
|
+
return Comp;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Mounts every registered node kind's system component, ordered by
|
|
21
|
+
* `system.priority` (default {@link DEFAULT_PRIORITY}).
|
|
22
|
+
*
|
|
23
|
+
* Today the registry is empty so this component mounts nothing — coexists
|
|
24
|
+
* with legacy `*-System` components in `<Viewer>`. Once kinds register via
|
|
25
|
+
* `@pascal-app/nodes`, each kind's registry-driven system takes over and
|
|
26
|
+
* its legacy counterpart short-circuits via the `nodeRegistry.has(kind)`
|
|
27
|
+
* guard added to each legacy system.
|
|
28
|
+
*/
|
|
29
|
+
export function RegisteredSystems() {
|
|
30
|
+
const entries = useMemo(() => {
|
|
31
|
+
return Array.from(nodeRegistry.entries())
|
|
32
|
+
.filter(([, def]) => def.system != null)
|
|
33
|
+
.sort(([, a], [, b]) => {
|
|
34
|
+
const pa = a.system?.priority ?? DEFAULT_PRIORITY;
|
|
35
|
+
const pb = b.system?.priority ?? DEFAULT_PRIORITY;
|
|
36
|
+
return pa - pb;
|
|
37
|
+
});
|
|
38
|
+
}, []);
|
|
39
|
+
if (entries.length === 0)
|
|
40
|
+
return null;
|
|
41
|
+
return (_jsx(Suspense, { fallback: null, children: entries.map(([kind, def]) => {
|
|
42
|
+
const Comp = loadSystem(def);
|
|
43
|
+
if (!Comp)
|
|
44
|
+
return null;
|
|
45
|
+
return _jsx(Comp, {}, `registered-system:${kind}`);
|
|
46
|
+
}) }));
|
|
47
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type Group } from 'three';
|
|
3
|
+
import { type SplitStrategy } from 'three-mesh-bvh';
|
|
4
|
+
type SceneBvhProps = {
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
firstHitOnly?: boolean;
|
|
8
|
+
strategy?: SplitStrategy;
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
setBoundingBox?: boolean;
|
|
11
|
+
maxDepth?: number;
|
|
12
|
+
maxLeafSize?: number;
|
|
13
|
+
indirect?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare const SceneBvh: import("react").ForwardRefExoticComponent<SceneBvhProps & import("react").RefAttributes<Group<import("three").Object3DEventMap>>>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=scene-bvh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-bvh.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/scene-bvh.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,SAAS,EAA0C,MAAM,OAAO,CAAA;AAC1F,OAAO,EAAuB,KAAK,KAAK,EAAQ,MAAM,OAAO,CAAA;AAC7D,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAA;AAEvB,KAAK,aAAa,GAAG;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAeD,eAAO,MAAM,QAAQ,mIAmGpB,CAAA"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useThree } from '@react-three/fiber';
|
|
3
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
|
|
4
|
+
import { Mesh } from 'three';
|
|
5
|
+
import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree, SAH, } from 'three-mesh-bvh';
|
|
6
|
+
const isMesh = (object) => !!object && typeof object === 'object' && object.isMesh === true;
|
|
7
|
+
const hasBvhCompatibleGeometry = (geometry) => {
|
|
8
|
+
if (!geometry)
|
|
9
|
+
return false;
|
|
10
|
+
const position = geometry.getAttribute('position');
|
|
11
|
+
if (!position)
|
|
12
|
+
return false;
|
|
13
|
+
const vertexCount = geometry.getIndex()?.count ?? position.count;
|
|
14
|
+
return vertexCount >= 3;
|
|
15
|
+
};
|
|
16
|
+
export const SceneBvh = forwardRef(({ children, enabled = true, firstHitOnly = false, strategy = SAH, verbose = false, setBoundingBox = true, maxDepth = 40, maxLeafSize = 10, indirect = false, }, forwardedRef) => {
|
|
17
|
+
const ref = useRef(null);
|
|
18
|
+
const raycaster = useThree((state) => state.raycaster);
|
|
19
|
+
useImperativeHandle(forwardedRef, () => ref.current, []);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!enabled || !ref.current)
|
|
22
|
+
return;
|
|
23
|
+
const options = {
|
|
24
|
+
strategy,
|
|
25
|
+
verbose,
|
|
26
|
+
setBoundingBox,
|
|
27
|
+
maxDepth,
|
|
28
|
+
maxLeafSize,
|
|
29
|
+
indirect,
|
|
30
|
+
};
|
|
31
|
+
const group = ref.current;
|
|
32
|
+
const acceleratedMeshes = new Set();
|
|
33
|
+
const computedGeometries = new Set();
|
|
34
|
+
raycaster.firstHitOnly = firstHitOnly;
|
|
35
|
+
group.traverse((child) => {
|
|
36
|
+
if (!isMesh(child))
|
|
37
|
+
return;
|
|
38
|
+
if (child.raycast === Mesh.prototype.raycast) {
|
|
39
|
+
child.raycast = acceleratedRaycast;
|
|
40
|
+
acceleratedMeshes.add(child);
|
|
41
|
+
}
|
|
42
|
+
if (child.raycast !== acceleratedRaycast)
|
|
43
|
+
return;
|
|
44
|
+
const geometry = child.geometry;
|
|
45
|
+
if (geometry.boundsTree || !hasBvhCompatibleGeometry(geometry))
|
|
46
|
+
return;
|
|
47
|
+
try {
|
|
48
|
+
// The three-mesh-bvh + @types/three combo doesn't agree on
|
|
49
|
+
// BVH option / class identity (ComputeBVHOptions vs
|
|
50
|
+
// MeshBVHOptions, GeometryBVH vs MeshBVH) — cast through
|
|
51
|
+
// `unknown` to bypass the structural mismatch. Runtime is
|
|
52
|
+
// fine; we're just calling the library's own helpers.
|
|
53
|
+
;
|
|
54
|
+
geometry.computeBoundsTree =
|
|
55
|
+
computeBoundsTree;
|
|
56
|
+
geometry.disposeBoundsTree =
|
|
57
|
+
disposeBoundsTree;
|
|
58
|
+
geometry.computeBoundsTree(options);
|
|
59
|
+
computedGeometries.add(geometry);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.warn('[viewer] Skipping BVH for incompatible mesh geometry.', {
|
|
63
|
+
mesh: child.name || child.type,
|
|
64
|
+
error,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return () => {
|
|
69
|
+
delete raycaster.firstHitOnly;
|
|
70
|
+
for (const geometry of computedGeometries) {
|
|
71
|
+
if (geometry.boundsTree) {
|
|
72
|
+
geometry.disposeBoundsTree();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const mesh of acceleratedMeshes) {
|
|
76
|
+
if (mesh.raycast === acceleratedRaycast) {
|
|
77
|
+
mesh.raycast = Mesh.prototype.raycast;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}, [
|
|
82
|
+
enabled,
|
|
83
|
+
firstHitOnly,
|
|
84
|
+
strategy,
|
|
85
|
+
verbose,
|
|
86
|
+
setBoundingBox,
|
|
87
|
+
maxDepth,
|
|
88
|
+
maxLeafSize,
|
|
89
|
+
indirect,
|
|
90
|
+
raycaster,
|
|
91
|
+
]);
|
|
92
|
+
return _jsx("group", { ref: ref, children: children });
|
|
93
|
+
});
|
|
94
|
+
SceneBvh.displayName = 'SceneBvh';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const SelectionManager: () => import("react
|
|
1
|
+
export declare const SelectionManager: () => import("react").JSX.Element;
|
|
2
2
|
//# sourceMappingURL=selection-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AA0SA,eAAO,MAAM,gBAAgB,mCA0F5B,CAAA"}
|