@pascal-app/viewer 0.8.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/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 +37 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +186 -63
- 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 +231 -54
- 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 -62
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +7 -0
- 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 +9 -5
- 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/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 +396 -59
- 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 +50 -61
- 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.map +1 -1
- package/dist/systems/wall/wall-system.js +171 -26
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +71 -7
- package/package.json +3 -4
- 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 -597
- 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 -15
- 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 -51
- 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
|
@@ -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,6 +141,9 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
83
141
|
}, []);
|
|
84
142
|
// Reset retry state when project changes
|
|
85
143
|
useEffect(() => {
|
|
144
|
+
if (lastProjectIdRef.current === projectId)
|
|
145
|
+
return;
|
|
146
|
+
lastProjectIdRef.current = projectId;
|
|
86
147
|
retryCountRef.current = 0;
|
|
87
148
|
if (rebuildTimeoutRef.current !== null) {
|
|
88
149
|
clearTimeout(rebuildTimeoutRef.current);
|
|
@@ -115,6 +176,8 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
115
176
|
]);
|
|
116
177
|
// Build / rebuild the post-processing pipeline
|
|
117
178
|
useEffect(() => {
|
|
179
|
+
const width = Math.floor(size.width);
|
|
180
|
+
const height = Math.floor(size.height);
|
|
118
181
|
if (!(renderer && scene && camera)) {
|
|
119
182
|
console.warn('[viewer/post-processing] Skipping pipeline build — missing dependency.', {
|
|
120
183
|
hasRenderer: !!renderer,
|
|
@@ -123,14 +186,60 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
123
186
|
});
|
|
124
187
|
return;
|
|
125
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;
|
|
126
215
|
console.log('[viewer/post-processing] Building pipeline', {
|
|
127
216
|
version: pipelineVersion,
|
|
128
|
-
ssgi:
|
|
217
|
+
ssgi: ssgiEnabled,
|
|
218
|
+
denoise: denoiseEnabled,
|
|
219
|
+
outline: outlineEnabled,
|
|
220
|
+
perfDisable,
|
|
129
221
|
hoverHighlightMode,
|
|
130
222
|
projectId,
|
|
223
|
+
shading,
|
|
131
224
|
rendererCtor: renderer.constructor?.name,
|
|
225
|
+
width,
|
|
226
|
+
height,
|
|
132
227
|
});
|
|
133
228
|
hasPipelineErrorRef.current = false;
|
|
229
|
+
// WebGPU availability check: SSGI, denoise, and RenderPipeline are all
|
|
230
|
+
// WebGPU-only APIs. When the browser falls back to WebGL2 (no
|
|
231
|
+
// `navigator.gpu`, or the device couldn't be created), building the
|
|
232
|
+
// pipeline either throws silently or produces a broken output where
|
|
233
|
+
// the scene renders for a few frames and then goes black as the retry
|
|
234
|
+
// loop fights the direct-render fallback path. Short-circuit here so
|
|
235
|
+
// `useFrame` uses the direct `renderer.render(scene, camera)` path
|
|
236
|
+
// exclusively and never attempts the TSL pipeline.
|
|
237
|
+
const hasWebGPU = typeof navigator !== 'undefined' && 'gpu' in navigator;
|
|
238
|
+
if (!hasWebGPU) {
|
|
239
|
+
hasPipelineErrorRef.current = true;
|
|
240
|
+
renderPipelineRef.current = null;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
134
243
|
// Clear outliner arrays synchronously to prevent stale Object3D refs
|
|
135
244
|
// from the previous project leaking into the new pipeline's outline passes.
|
|
136
245
|
const outliner = useViewer.getState().outliner;
|
|
@@ -140,8 +249,15 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
140
249
|
outliner.hoveredObjects.length = 0;
|
|
141
250
|
try {
|
|
142
251
|
const scenePass = pass(scene, camera);
|
|
252
|
+
scenePass.setLayers(sceneOnlyLayers);
|
|
143
253
|
const zonePass = pass(scene, camera);
|
|
144
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');
|
|
145
261
|
const scenePassColor = scenePass.getTextureNode('output');
|
|
146
262
|
// Background detection via alpha: renderer clears with alpha=0 (setClearAlpha(0) in useFrame),
|
|
147
263
|
// so background pixels have scenePassColor.a=0 while geometry pixels have output.a=1.
|
|
@@ -150,23 +266,28 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
150
266
|
const hasGeometry = scenePassColor.a;
|
|
151
267
|
const contentAlpha = hasGeometry.max(zonePass.a);
|
|
152
268
|
let sceneColor = scenePassColor;
|
|
153
|
-
|
|
154
|
-
|
|
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) {
|
|
155
275
|
scenePass.setMRT(mrt({
|
|
156
276
|
output,
|
|
157
277
|
diffuseColor,
|
|
158
278
|
normal: directionToColor(normalView),
|
|
159
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) {
|
|
160
288
|
const scenePassDiffuse = scenePass.getTextureNode('diffuseColor');
|
|
161
|
-
const scenePassDepth = scenePass.getTextureNode('depth');
|
|
162
|
-
const scenePassNormal = scenePass.getTextureNode('normal');
|
|
163
|
-
// Optimize texture bandwidth
|
|
164
289
|
const diffuseTexture = scenePass.getTexture('diffuseColor');
|
|
165
290
|
diffuseTexture.type = UnsignedByteType;
|
|
166
|
-
const normalTexture = scenePass.getTexture('normal');
|
|
167
|
-
normalTexture.type = UnsignedByteType;
|
|
168
|
-
// Extract normal from color-encoded texture
|
|
169
|
-
const sceneNormal = sample((uv) => colorToDirection(scenePassNormal.sample(uv)));
|
|
170
291
|
const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, camera);
|
|
171
292
|
giPass.sliceCount.value = SSGI_PARAMS.sliceCount;
|
|
172
293
|
giPass.stepCount.value = SSGI_PARAMS.stepCount;
|
|
@@ -180,49 +301,76 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
180
301
|
giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling;
|
|
181
302
|
giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering;
|
|
182
303
|
const giTexture = giPass.getTextureNode();
|
|
183
|
-
// DenoiseNode only denoises RGB — alpha is passed through unchanged.
|
|
184
|
-
// SSGI packs AO into alpha, so we remap it into RGB before denoising.
|
|
185
|
-
const aoAsRgb = vec4(giTexture.a, giTexture.a, giTexture.a, float(1));
|
|
186
|
-
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
|
|
187
|
-
denoisePass.index.value = 0;
|
|
188
|
-
denoisePass.radius.value = 4;
|
|
189
304
|
const gi = giPass.rgb;
|
|
190
|
-
|
|
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
|
+
}
|
|
191
320
|
// Composite: scene * AO + diffuse * GI
|
|
192
321
|
sceneColor = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))), contentAlpha);
|
|
193
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
|
+
}
|
|
194
336
|
// Single merged outline node: one shared depth pass for both selected + hovered groups.
|
|
195
337
|
const outliner = useViewer.getState().outliner;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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));
|
|
221
370
|
const renderPipeline = new RenderPipeline(renderer);
|
|
222
371
|
renderPipeline.outputNode = finalOutput;
|
|
223
372
|
renderPipelineRef.current = renderPipeline;
|
|
224
373
|
retryCountRef.current = 0;
|
|
225
|
-
console.log('[viewer/post-processing] Pipeline built OK', { version: pipelineVersion });
|
|
226
374
|
}
|
|
227
375
|
catch (error) {
|
|
228
376
|
hasPipelineErrorRef.current = true;
|
|
@@ -245,31 +393,49 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
245
393
|
}, [
|
|
246
394
|
camera,
|
|
247
395
|
hoverHiddenColor,
|
|
396
|
+
hoverHighlightMode,
|
|
248
397
|
hoverPulseMix,
|
|
249
398
|
hoverStrength,
|
|
250
399
|
hoverVisibleColor,
|
|
400
|
+
edges,
|
|
251
401
|
pipelineVersion,
|
|
252
402
|
projectId,
|
|
253
403
|
renderer,
|
|
254
404
|
scene,
|
|
405
|
+
shading,
|
|
406
|
+
size.height,
|
|
407
|
+
size.width,
|
|
255
408
|
zoneLayers,
|
|
409
|
+
sceneOnlyLayers,
|
|
410
|
+
overlayLayers,
|
|
256
411
|
]);
|
|
257
412
|
useFrame((_, delta) => {
|
|
258
|
-
|
|
259
|
-
|
|
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);
|
|
260
418
|
bgCurrent.current.lerp(bgTarget.current, Math.min(delta, 0.1) * 4);
|
|
261
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()}`));
|
|
262
422
|
const outliner = useViewer.getState().outliner;
|
|
263
423
|
sanitizeOutlineObjects(outliner.selectedObjects);
|
|
264
424
|
sanitizeOutlineObjects(outliner.hoveredObjects);
|
|
265
|
-
if (hasPipelineErrorRef.current || !renderPipelineRef.current) {
|
|
425
|
+
if (PERF_POST_FX_DISABLED || hasPipelineErrorRef.current || !renderPipelineRef.current) {
|
|
266
426
|
try {
|
|
267
427
|
if (renderer.setClearAlpha) {
|
|
268
428
|
;
|
|
269
429
|
renderer.setClearAlpha(1);
|
|
270
430
|
}
|
|
271
|
-
;
|
|
431
|
+
const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
|
|
272
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
|
+
}
|
|
273
439
|
}
|
|
274
440
|
catch (fallbackError) {
|
|
275
441
|
console.error('[viewer/post-processing] Fallback render failed.', fallbackError);
|
|
@@ -281,7 +447,19 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
281
447
|
// making scenePassColor.a a reliable geometry mask (geometry pixels write a=1 via output node).
|
|
282
448
|
;
|
|
283
449
|
renderer.setClearAlpha(0);
|
|
450
|
+
const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
|
|
284
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
|
+
}
|
|
285
463
|
}
|
|
286
464
|
catch (error) {
|
|
287
465
|
hasPipelineErrorRef.current = true;
|
|
@@ -297,7 +475,6 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
297
475
|
if (retryCountRef.current < MAX_PIPELINE_RETRIES) {
|
|
298
476
|
// Auto-retry: schedule a pipeline rebuild if we haven't exceeded the retry limit
|
|
299
477
|
retryCountRef.current++;
|
|
300
|
-
console.warn(`[viewer/post-processing] Scheduling pipeline rebuild (attempt ${retryCountRef.current}/${MAX_PIPELINE_RETRIES})`);
|
|
301
478
|
if (rebuildTimeoutRef.current !== null) {
|
|
302
479
|
clearTimeout(rebuildTimeoutRef.current);
|
|
303
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"}
|