@pascal-app/viewer 1.0.0-beta.5 → 1.0.0
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/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/capture/asset-url.d.ts +2 -0
- package/dist/capture/asset-url.d.ts.map +1 -0
- package/dist/capture/asset-url.js +13 -0
- package/dist/capture/capture-runtime.d.ts +43 -0
- package/dist/capture/capture-runtime.d.ts.map +1 -0
- package/dist/capture/capture-runtime.js +228 -0
- package/dist/capture/frame.d.ts +4 -0
- package/dist/capture/frame.d.ts.map +1 -0
- package/dist/capture/frame.js +21 -0
- package/dist/capture/index.d.ts +12 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +11 -0
- package/dist/capture/layer-visibility.d.ts +5 -0
- package/dist/capture/layer-visibility.d.ts.map +1 -0
- package/dist/capture/layer-visibility.js +11 -0
- package/dist/capture/layers/clay-matcap.d.ts +3 -0
- package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
- package/dist/capture/layers/clay-matcap.js +26 -0
- package/dist/capture/layers/device-motion-layer.d.ts +7 -0
- package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
- package/dist/capture/layers/device-motion-layer.js +78 -0
- package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
- package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
- package/dist/capture/layers/point-cloud-layer.js +106 -0
- package/dist/capture/layers/room-model-layer.d.ts +9 -0
- package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
- package/dist/capture/layers/room-model-layer.js +71 -0
- package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
- package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-data.js +74 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-layer.js +16 -0
- package/dist/capture/preview.d.ts +3 -0
- package/dist/capture/preview.d.ts.map +1 -0
- package/dist/capture/preview.js +2 -0
- package/dist/capture/source-state.d.ts +22 -0
- package/dist/capture/source-state.d.ts.map +1 -0
- package/dist/capture/source-state.js +182 -0
- package/dist/capture/stream-rendering.d.ts +14 -0
- package/dist/capture/stream-rendering.d.ts.map +1 -0
- package/dist/capture/stream-rendering.js +69 -0
- package/dist/capture/trajectory.d.ts +20 -0
- package/dist/capture/trajectory.d.ts.map +1 -0
- package/dist/capture/trajectory.js +77 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
- package/dist/components/viewer/batched-mesh-spike.js +64 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +4 -3
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +70 -62
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +123 -89
- package/dist/components/viewer/perf-monitor.d.ts +6 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +307 -116
- package/dist/components/viewer/perf-panel.d.ts +3 -0
- package/dist/components/viewer/perf-panel.d.ts.map +1 -0
- package/dist/components/viewer/perf-panel.js +178 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
- package/dist/components/viewer/pointer-raycast-layers.js +28 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +124 -83
- package/dist/components/viewer/registered-systems.d.ts +14 -5
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +22 -10
- package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
- package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
- package/dist/components/viewer/scene-atmosphere.js +89 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +19 -5
- package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
- package/dist/components/viewer/scene-ground-replacement.js +35 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +4 -1
- package/dist/components/viewer/viewer-presentations.d.ts +67 -0
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
- package/dist/components/viewer/viewer-presentations.js +120 -0
- package/dist/hooks/use-library-materials-version.d.ts +7 -0
- package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
- package/dist/hooks/use-library-materials-version.js +10 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +1 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -6
- package/dist/lib/csg-utils.d.ts +1 -0
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +6 -1
- package/dist/lib/gpu-perf.d.ts +0 -2
- package/dist/lib/gpu-perf.d.ts.map +1 -1
- package/dist/lib/gpu-perf.js +7 -21
- package/dist/lib/layer-pass.d.ts +28 -0
- package/dist/lib/layer-pass.d.ts.map +1 -0
- package/dist/lib/layer-pass.js +254 -0
- package/dist/lib/materials.d.ts +12 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +51 -17
- package/dist/lib/merged-outline-node.d.ts +17 -8
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +194 -52
- package/dist/lib/perf-actions.d.ts +55 -0
- package/dist/lib/perf-actions.d.ts.map +1 -0
- package/dist/lib/perf-actions.js +211 -0
- package/dist/lib/perf-observers.d.ts +2 -0
- package/dist/lib/perf-observers.d.ts.map +1 -0
- package/dist/lib/perf-observers.js +17 -0
- package/dist/lib/perf-panel-store.d.ts +71 -0
- package/dist/lib/perf-panel-store.d.ts.map +1 -0
- package/dist/lib/perf-panel-store.js +29 -0
- package/dist/lib/perf-tracks.d.ts +43 -0
- package/dist/lib/perf-tracks.d.ts.map +1 -0
- package/dist/lib/perf-tracks.js +113 -0
- package/dist/lib/pointer-events.d.ts +7 -0
- package/dist/lib/pointer-events.d.ts.map +1 -0
- package/dist/lib/pointer-events.js +680 -0
- package/dist/lib/post-processing-resources.d.ts +12 -0
- package/dist/lib/post-processing-resources.d.ts.map +1 -0
- package/dist/lib/post-processing-resources.js +19 -0
- package/dist/lib/renderer-capability.d.ts.map +1 -1
- package/dist/lib/renderer-capability.js +4 -2
- package/dist/lib/scene-visibility.d.ts +2 -1
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +22 -1
- package/dist/store/use-viewer.d.ts +2 -2
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +1 -1
- package/dist/systems/door/door-animation-system.d.ts.map +1 -1
- package/dist/systems/door/door-animation-system.js +6 -9
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +13 -2
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +3 -2
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +1 -1
- package/dist/systems/level/level-utils.d.ts +9 -0
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +14 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1196 -58
- package/dist/systems/scan/scan-system.js +6 -5
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +1 -2
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +9 -8
- package/dist/systems/surface-hole-geometry.d.ts +1 -1
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
- package/dist/systems/surface-hole-geometry.js +1 -1
- package/dist/systems/wall/level-miter-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
- package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
- package/dist/systems/wall/wall-build-lifecycle.js +74 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
- package/dist/systems/wall/wall-cutout-cache.js +304 -0
- package/dist/systems/wall/wall-cutout.d.ts +12 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +24 -161
- package/dist/systems/wall/wall-material-variant.d.ts +31 -0
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
- package/dist/systems/wall/wall-material-variant.js +10 -0
- package/dist/systems/wall/wall-materials.d.ts +6 -0
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +29 -13
- package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
- package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
- package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
- package/dist/systems/wall/wall-system.d.ts +11 -4
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +405 -153
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +9 -8
- package/dist/systems/window/window-system.d.ts +1 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +16 -2
- package/package.json +17 -4
- package/dist/lib/polygon-union.d.ts +0 -4
- package/dist/lib/polygon-union.d.ts.map +0 -1
- package/dist/lib/polygon-union.js +0 -289
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useThree } from '@react-three/fiber';
|
|
3
|
+
import { useLayoutEffect } from 'react';
|
|
4
|
+
import { BATCHED_LAYER } from '../../lib/layers';
|
|
5
|
+
/**
|
|
6
|
+
* Lets R3F's pointer raycaster see geometry a collective batch draws.
|
|
7
|
+
*
|
|
8
|
+
* R3F picks with one shared raycaster whose default mask is `SCENE_LAYER`
|
|
9
|
+
* alone. A wall sewn into its level's merged mesh is moved off that layer
|
|
10
|
+
* (`hideBatchedWall`) while staying in the graph with its pointer handlers
|
|
11
|
+
* intact — so without this the wall answers no hover, paint or click the
|
|
12
|
+
* moment it joins a batch, and a floor's walls go dead a fraction of a second
|
|
13
|
+
* after the last edit settles.
|
|
14
|
+
*
|
|
15
|
+
* Additive rather than `setSurfaceRaycastLayers`: that helper resets the mask
|
|
16
|
+
* for the private raycasters callers build per query, and this one is shared.
|
|
17
|
+
*/
|
|
18
|
+
export const PointerRaycastLayers = () => {
|
|
19
|
+
const raycaster = useThree((state) => state.raycaster);
|
|
20
|
+
useLayoutEffect(() => {
|
|
21
|
+
const mask = raycaster.layers.mask;
|
|
22
|
+
raycaster.layers.enable(BATCHED_LAYER);
|
|
23
|
+
return () => {
|
|
24
|
+
raycaster.layers.mask = mask;
|
|
25
|
+
};
|
|
26
|
+
}, [raycaster]);
|
|
27
|
+
return null;
|
|
28
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA6CA,eAAO,MAAM,YAAY;;;CAGxB,CAAA;AAGD,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAgED,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,UAAU,CAAA;CACpB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAS9B,eAAO,MAAM,oBAAoB,EAAE,WAElC,CAAA;AA+CD,QAAA,MAAM,oBAAoB,GAAI,iCAG3B;IACD,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,SA+mBA,CAAA;AAED,eAAe,oBAAoB,CAAA"}
|
|
@@ -3,17 +3,21 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
3
3
|
import { Color, Layers, Matrix4, Scene, UnsignedByteType } from 'three';
|
|
4
4
|
import { ssgi } from 'three/addons/tsl/display/SSGINode.js';
|
|
5
5
|
import { denoise } from 'three/examples/jsm/tsl/display/DenoiseNode.js';
|
|
6
|
-
import { add, diffuseColor,
|
|
7
|
-
import { RenderPipeline } from 'three/webgpu';
|
|
6
|
+
import { add, diffuseColor, mix, mrt, normalView, normalWorldGeometry, oscSine, output, pass, premultiplyAlpha, renderOutput, sample, saturation, screenUV, smoothstep, time, float as tslFloat, uniform, vec3, vec4, } from 'three/tsl';
|
|
7
|
+
import { RenderPipeline, TimestampQuery } from 'three/webgpu';
|
|
8
8
|
import { backdropGradient, deepSkyColor, horizonHazeColor } from '../../lib/backdrop';
|
|
9
9
|
import { edgeColorFor, edgeOpacityScaleFor } from '../../lib/edge-style';
|
|
10
|
-
import { PERF_OVERLAY_ENABLED
|
|
10
|
+
import { PERF_OVERLAY_ENABLED } from '../../lib/gpu-perf';
|
|
11
11
|
import { inkedEdges } from '../../lib/ink-edges';
|
|
12
|
+
import { LayerPassIndex, LayerPassNode } from '../../lib/layer-pass';
|
|
12
13
|
import { GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, ZONE_LAYER } from '../../lib/layers';
|
|
13
14
|
import { mergedOutline } from '../../lib/merged-outline-node';
|
|
15
|
+
import { recordPerfSample, timeSpan } from '../../lib/perf-tracks';
|
|
16
|
+
import { PostProcessingResources } from '../../lib/post-processing-resources';
|
|
14
17
|
import { getSceneTheme } from '../../lib/scene-themes';
|
|
15
18
|
import { packNormalToRGB, unpackRGBToNormal } from '../../lib/tsl-compat';
|
|
16
19
|
import useViewer from '../../store/use-viewer';
|
|
20
|
+
import { useSceneAtmosphere } from './scene-atmosphere';
|
|
17
21
|
// Scene-referred grade applied before the output tone mapping (AgX). AgX rolls
|
|
18
22
|
// highlights off gently but reads flat on its own; a mild mid-gray-pivot
|
|
19
23
|
// contrast + saturation lift restores the punch. Rendered shading only.
|
|
@@ -36,7 +40,8 @@ export const SSGI_PARAMS = {
|
|
|
36
40
|
useScreenSpaceSampling: true,
|
|
37
41
|
useTemporalFiltering: false,
|
|
38
42
|
};
|
|
39
|
-
// Diagnostic toggles for thermal A/B testing. Add
|
|
43
|
+
// Diagnostic toggles for thermal A/B testing. Add
|
|
44
|
+
// `?disable=ao,denoise,outline,postFx`
|
|
40
45
|
// to the URL (any subset) and reload to skip those passes. Each flag prevents
|
|
41
46
|
// allocation + per-frame work for that stage, so device temperature deltas
|
|
42
47
|
// across combos isolate which pass is the actual culprit. Picked up once at
|
|
@@ -53,7 +58,12 @@ export const SSGI_PARAMS = {
|
|
|
53
58
|
// per-frame vertex/draw cost dominates the whole capture.
|
|
54
59
|
function readPerfDisableFlags() {
|
|
55
60
|
if (typeof window === 'undefined') {
|
|
56
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
ao: false,
|
|
63
|
+
denoise: false,
|
|
64
|
+
outline: false,
|
|
65
|
+
postFx: false,
|
|
66
|
+
};
|
|
57
67
|
}
|
|
58
68
|
const raw = new URLSearchParams(window.location.search).get('disable') ?? '';
|
|
59
69
|
const set = new Set(raw
|
|
@@ -99,9 +109,39 @@ function sanitizeOutlineObjects(objects) {
|
|
|
99
109
|
}
|
|
100
110
|
objects.length = nextIndex;
|
|
101
111
|
}
|
|
112
|
+
// Two independent GPU readings per frame, both `?perf`-only:
|
|
113
|
+
// - `gpu-render`: three's WebGPU timestamp queries — the summed GPU duration of
|
|
114
|
+
// the frame's render passes, measured on the device. The only honest "GPU ms".
|
|
115
|
+
// - `gpu-queue`: submit → `onSubmittedWorkDone()` wall time. That covers queue
|
|
116
|
+
// backlog and CPU work that ran before the microtask got to resume, so it is
|
|
117
|
+
// a backpressure signal, not GPU time.
|
|
118
|
+
// `resolveTimestampsAsync` returns the previous resolve's value while one is in
|
|
119
|
+
// flight, so calling it every frame is safe (and required — the query pool warns
|
|
120
|
+
// once it fills).
|
|
121
|
+
function recordFrameGpuTiming(renderer, submittedAt) {
|
|
122
|
+
const queue = renderer.backend?.device?.queue;
|
|
123
|
+
queue?.onSubmittedWorkDone?.().then(() => {
|
|
124
|
+
recordPerfSample('gpu-queue', performance.now() - submittedAt);
|
|
125
|
+
});
|
|
126
|
+
// Off unless the device advertised 'timestamp-query' at init — the backend
|
|
127
|
+
// clears its own flag when the feature is missing, so this is the truth.
|
|
128
|
+
if (renderer.backend?.trackTimestamp !== true)
|
|
129
|
+
return;
|
|
130
|
+
renderer
|
|
131
|
+
.resolveTimestampsAsync?.(TimestampQuery.RENDER)
|
|
132
|
+
?.then((ms) => {
|
|
133
|
+
if (typeof ms === 'number' && ms > 0)
|
|
134
|
+
recordPerfSample('gpu-render', ms);
|
|
135
|
+
})
|
|
136
|
+
.catch(() => {
|
|
137
|
+
// Pool disposed mid-flight (pipeline rebuild / unmount) — nothing to report.
|
|
138
|
+
});
|
|
139
|
+
}
|
|
102
140
|
const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostFx = false, }) => {
|
|
103
141
|
const { gl: renderer, invalidate, scene, camera, size } = useThree();
|
|
104
|
-
const
|
|
142
|
+
const resourcesRef = useRef(null);
|
|
143
|
+
const atmosphere = useSceneAtmosphere();
|
|
144
|
+
const directSkyNode = useMemo(() => (atmosphere ? atmosphere.skyRadiance(normalWorldGeometry) : null), [atmosphere]);
|
|
105
145
|
const hasPipelineErrorRef = useRef(false);
|
|
106
146
|
const retryCountRef = useRef(0);
|
|
107
147
|
const rebuildTimeoutRef = useRef(null);
|
|
@@ -183,6 +223,10 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
183
223
|
}
|
|
184
224
|
setPipelineVersion((v) => v + 1);
|
|
185
225
|
}, []);
|
|
226
|
+
const disposePipeline = useCallback(() => {
|
|
227
|
+
resourcesRef.current?.dispose();
|
|
228
|
+
resourcesRef.current = null;
|
|
229
|
+
}, []);
|
|
186
230
|
// Reset retry state when project changes
|
|
187
231
|
useEffect(() => {
|
|
188
232
|
if (lastProjectIdRef.current === projectId)
|
|
@@ -233,10 +277,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
233
277
|
if (width < 1 || height < 1) {
|
|
234
278
|
skippedZeroSizeRef.current = true;
|
|
235
279
|
hasPipelineErrorRef.current = false;
|
|
236
|
-
|
|
237
|
-
renderPipelineRef.current.dispose();
|
|
238
|
-
}
|
|
239
|
-
renderPipelineRef.current = null;
|
|
280
|
+
disposePipeline();
|
|
240
281
|
return;
|
|
241
282
|
}
|
|
242
283
|
if (skippedZeroSizeRef.current) {
|
|
@@ -249,23 +290,17 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
249
290
|
// allocated every pass.
|
|
250
291
|
if (disablePostFx || perfDisable.postFx) {
|
|
251
292
|
hasPipelineErrorRef.current = false;
|
|
252
|
-
|
|
253
|
-
renderPipelineRef.current.dispose();
|
|
254
|
-
}
|
|
255
|
-
renderPipelineRef.current = null;
|
|
293
|
+
disposePipeline();
|
|
256
294
|
return;
|
|
257
295
|
}
|
|
258
296
|
const ssgiEnabled = shading === 'rendered' && SSGI_PARAMS.enabled && !perfDisable.ao;
|
|
259
297
|
const denoiseEnabled = ssgiEnabled && !perfDisable.denoise;
|
|
260
298
|
const outlineEnabled = !perfDisable.outline;
|
|
261
299
|
const inkEnabled = edges !== 'off';
|
|
262
|
-
// The depth+normal MRT feeds both SSGI and the screen-space ink pass.
|
|
263
300
|
const needsNormalMRT = ssgiEnabled || inkEnabled;
|
|
264
301
|
// Soft = thin (1px sample radius) + faint (50% opacity); strong = thick
|
|
265
302
|
// (2px, ~2× wider detected band) + solid (100%). The edge masks saturate,
|
|
266
|
-
// so radius+opacity are what actually separate the two modes
|
|
267
|
-
// Same 1px line thickness for both (soft's thickness is the nice one);
|
|
268
|
-
// strong reads heavier purely by being fully solid vs soft's lighter 50%.
|
|
303
|
+
// so radius+opacity are what actually separate the two modes.
|
|
269
304
|
const inkRadius = 1;
|
|
270
305
|
const inkOpacity = inkOpacityOverride ?? (edges === 'strong' ? 1 : 0.5);
|
|
271
306
|
console.log('[viewer/post-processing] Building pipeline', {
|
|
@@ -293,7 +328,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
293
328
|
const hasWebGPU = typeof navigator !== 'undefined' && 'gpu' in navigator;
|
|
294
329
|
if (!hasWebGPU) {
|
|
295
330
|
hasPipelineErrorRef.current = true;
|
|
296
|
-
|
|
331
|
+
resourcesRef.current = null;
|
|
297
332
|
return;
|
|
298
333
|
}
|
|
299
334
|
// Clear outliner arrays synchronously to prevent stale Object3D refs
|
|
@@ -303,15 +338,22 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
303
338
|
sanitizeOutlineObjects(outliner.hoveredObjects);
|
|
304
339
|
outliner.selectedObjects.length = 0;
|
|
305
340
|
outliner.hoveredObjects.length = 0;
|
|
341
|
+
const resources = new PostProcessingResources();
|
|
342
|
+
resourcesRef.current = resources;
|
|
306
343
|
try {
|
|
344
|
+
const layerIndex = new LayerPassIndex(scene, [ZONE_LAYER, OVERLAY_LAYER]);
|
|
345
|
+
resources.layerIndex = layerIndex;
|
|
307
346
|
const scenePass = pass(scene, camera);
|
|
347
|
+
resources.passes.push(scenePass);
|
|
308
348
|
scenePass.setLayers(sceneOnlyLayers);
|
|
309
|
-
const zonePass =
|
|
349
|
+
const zonePass = new LayerPassNode(layerIndex, camera, ZONE_LAYER, scenePass);
|
|
350
|
+
resources.passes.push(zonePass);
|
|
310
351
|
zonePass.setLayers(zoneLayers);
|
|
311
352
|
// Editor overlays (gizmos, move handles, tool previews, grid) on their own
|
|
312
353
|
// layer, kept out of the depth/normal MRT above so the ink + SSGI ignore
|
|
313
354
|
// them, then composited on top of the final image below.
|
|
314
|
-
const overlayPass =
|
|
355
|
+
const overlayPass = new LayerPassNode(layerIndex, camera, OVERLAY_LAYER, scenePass);
|
|
356
|
+
resources.passes.push(overlayPass);
|
|
315
357
|
overlayPass.setLayers(overlayLayers);
|
|
316
358
|
const overlayColor = overlayPass.getTextureNode('output');
|
|
317
359
|
const scenePassColor = scenePass.getTextureNode('output');
|
|
@@ -325,25 +367,28 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
325
367
|
// not SSGI is enabled. When SSGI is on, the branch below overwrites this
|
|
326
368
|
// with its own zone-inclusive composite (no double-add).
|
|
327
369
|
let sceneColor = vec4(add(scenePassColor.rgb, zonePass.rgb), contentAlpha);
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
|
|
370
|
+
// Scene depth is shared by SSGI, ink, and outlines.
|
|
371
|
+
// The normal MRT is only built when SSGI or ink needs it.
|
|
372
|
+
const scenePassDepth = scenePass.getTextureNode('depth');
|
|
331
373
|
let scenePassNormal = null;
|
|
332
|
-
let sceneNormal = null;
|
|
333
374
|
if (needsNormalMRT) {
|
|
334
375
|
scenePass.setMRT(mrt({
|
|
335
376
|
output,
|
|
336
377
|
diffuseColor,
|
|
337
378
|
normal: packNormalToRGB(normalView),
|
|
338
379
|
}));
|
|
339
|
-
scenePassDepth = scenePass.getTextureNode('depth');
|
|
340
380
|
scenePassNormal = scenePass.getTextureNode('normal');
|
|
381
|
+
}
|
|
382
|
+
if (scenePassNormal) {
|
|
341
383
|
const normalTexture = scenePass.getTexture('normal');
|
|
342
384
|
normalTexture.type = UnsignedByteType;
|
|
343
|
-
// Extract normal from color-encoded texture (SSGI consumes the node form)
|
|
344
|
-
sceneNormal = sample((uv) => unpackRGBToNormal(scenePassNormal.sample(uv)));
|
|
345
385
|
}
|
|
346
|
-
|
|
386
|
+
// Extract normal from color-encoded texture (SSGI consumes the node form).
|
|
387
|
+
const normalNode = scenePassNormal;
|
|
388
|
+
const sceneNormal = normalNode
|
|
389
|
+
? sample((uv) => unpackRGBToNormal(normalNode.sample(uv)))
|
|
390
|
+
: null;
|
|
391
|
+
if (ssgiEnabled && scenePassDepth && sceneNormal) {
|
|
347
392
|
const scenePassDiffuse = scenePass.getTextureNode('diffuseColor');
|
|
348
393
|
const diffuseTexture = scenePass.getTexture('diffuseColor');
|
|
349
394
|
diffuseTexture.type = UnsignedByteType;
|
|
@@ -367,7 +412,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
367
412
|
if (denoiseEnabled) {
|
|
368
413
|
// DenoiseNode only denoises RGB — alpha is passed through unchanged.
|
|
369
414
|
// SSGI's AO is a single red channel, so we remap it into RGB before denoising.
|
|
370
|
-
const aoAsRgb = vec4(aoTexture.r, aoTexture.r, aoTexture.r,
|
|
415
|
+
const aoAsRgb = vec4(aoTexture.r, aoTexture.r, aoTexture.r, tslFloat(1));
|
|
371
416
|
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
|
|
372
417
|
denoisePass.index.value = 0;
|
|
373
418
|
denoisePass.radius.value = 4;
|
|
@@ -382,22 +427,22 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
382
427
|
// (same ≈150→350 m window as ink-edges' distanceFade) so the horizon
|
|
383
428
|
// disc and the geometry↔sky depth cliff never grow an AO band — that
|
|
384
429
|
// band read as a visible line along the horizon.
|
|
385
|
-
const aoFarFade = smoothstep(
|
|
386
|
-
ao = mix(ao,
|
|
430
|
+
const aoFarFade = smoothstep(tslFloat(0.9994), tslFloat(0.9998), scenePassDepth.sample(screenUV).r);
|
|
431
|
+
ao = mix(ao, tslFloat(1), aoFarFade);
|
|
387
432
|
// Composite: scene * AO + diffuse * GI
|
|
388
433
|
sceneColor = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))), contentAlpha);
|
|
389
434
|
}
|
|
390
435
|
// Screen-space ink outline (SketchUp look) — depth/normal edge detection
|
|
391
436
|
// over the composited scene. Topology-agnostic, so it handles CSG-cut
|
|
392
437
|
// walls cleanly. Applied before the selection outline + background mix.
|
|
393
|
-
if (inkEnabled) {
|
|
438
|
+
if (inkEnabled && scenePassDepth && scenePassNormal) {
|
|
394
439
|
sceneColor = vec4(inkedEdges({
|
|
395
440
|
sceneRgb: sceneColor.rgb,
|
|
396
441
|
depthTex: scenePassDepth,
|
|
397
442
|
normalTex: scenePassNormal,
|
|
398
443
|
inkColor: inkColorUniform.current,
|
|
399
444
|
radius: inkRadius,
|
|
400
|
-
opacity:
|
|
445
|
+
opacity: tslFloat(inkOpacity).mul(inkOpacityScaleUniform.current),
|
|
401
446
|
}), sceneColor.a);
|
|
402
447
|
}
|
|
403
448
|
// Scene-referred grade (contrast around mid-gray + saturation) before the
|
|
@@ -409,17 +454,20 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
409
454
|
if (shading === 'rendered') {
|
|
410
455
|
sceneColor = vec4(gradeRgb(sceneColor.rgb), sceneColor.a);
|
|
411
456
|
}
|
|
412
|
-
//
|
|
457
|
+
// Reused scene depth lets outlined groups occlude each other; materials
|
|
458
|
+
// with depthWrite=false (including glazing) no longer occlude outlines.
|
|
413
459
|
const outliner = useViewer.getState().outliner;
|
|
414
460
|
let compositeWithOutlines = sceneColor;
|
|
415
461
|
let visualAlpha = contentAlpha;
|
|
416
462
|
if (outlineEnabled) {
|
|
417
463
|
const outlineNode = mergedOutline(scene, camera, {
|
|
464
|
+
sceneDepthNode: scenePassDepth,
|
|
418
465
|
primaryObjects: outliner.selectedObjects,
|
|
419
466
|
secondaryObjects: outliner.hoveredObjects,
|
|
420
467
|
primaryEdgeThickness: uniform(1),
|
|
421
468
|
secondaryEdgeThickness: uniform(1.5),
|
|
422
469
|
});
|
|
470
|
+
resources.outline = outlineNode;
|
|
423
471
|
// Selected: white visible, yellow hidden
|
|
424
472
|
const selectedVisibleColor = uniform(new Color(0xff_ff_ff));
|
|
425
473
|
const selectedHiddenColor = uniform(new Color(0xf3_ff_47));
|
|
@@ -431,7 +479,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
431
479
|
// Hovered: blue visible, yellow hidden, pulsing
|
|
432
480
|
const pulsePeriod = uniform(3);
|
|
433
481
|
const oscillating = oscSine(time.div(pulsePeriod).mul(2)).mul(0.5).add(0.5);
|
|
434
|
-
const osc = mix(oscillating,
|
|
482
|
+
const osc = mix(oscillating, tslFloat(1), hoverPulseMix);
|
|
435
483
|
const hoverOutline = outlineNode.secondaryVisibleEdge
|
|
436
484
|
.mul(hoverVisibleColor)
|
|
437
485
|
.add(outlineNode.secondaryHiddenEdge.mul(hoverHiddenColor))
|
|
@@ -448,37 +496,40 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
448
496
|
// sky gradient (shared formula in lib/backdrop.ts). The horizon disc
|
|
449
497
|
// dissolves into the same formula, so backdrop and ground meet
|
|
450
498
|
// seamlessly exactly where the disc vanishes.
|
|
451
|
-
const ndc = vec4(screenUV.x.mul(2).sub(1),
|
|
499
|
+
const ndc = vec4(screenUV.x.mul(2).sub(1), tslFloat(1).sub(screenUV.y).mul(2).sub(1), 1, 1);
|
|
452
500
|
const viewRay = camProjInvUniform.current.mul(ndc);
|
|
453
501
|
const worldDir = camWorldUniform.current.mul(vec4(viewRay.xyz, 0)).xyz.normalize();
|
|
454
|
-
let bgGradient =
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
502
|
+
let bgGradient = atmosphere
|
|
503
|
+
? atmosphere.skyRadiance(worldDir)
|
|
504
|
+
: backdropGradient({
|
|
505
|
+
dirY: worldDir.y,
|
|
506
|
+
background: bgUniform.current,
|
|
507
|
+
haze: bgHazeUniform.current,
|
|
508
|
+
sky: bgSkyUniform.current,
|
|
509
|
+
skyDeep: bgSkyDeepUniform.current,
|
|
510
|
+
});
|
|
461
511
|
if (shading === 'rendered') {
|
|
462
512
|
bgGradient = gradeRgb(bgGradient);
|
|
463
513
|
}
|
|
464
|
-
const
|
|
514
|
+
const sceneComposite = compositeWithOutlines.rgb;
|
|
515
|
+
const composited = mix(bgGradient, sceneComposite, contentAlpha);
|
|
465
516
|
// Editor overlays painted on top by their own alpha — they never get inked,
|
|
466
517
|
// AO'd, or outlined, and always read crisp regardless of scene depth.
|
|
467
518
|
const withOverlay = mix(composited, overlayColor.rgb, overlayColor.a);
|
|
468
|
-
let finalOutput = vec4(withOverlay,
|
|
519
|
+
let finalOutput = vec4(withOverlay, tslFloat(1));
|
|
469
520
|
if (transparentBackground) {
|
|
470
521
|
const overlayAlpha = overlayColor.a;
|
|
471
522
|
const alpha = overlayAlpha.add(visualAlpha.mul(overlayAlpha.oneMinus()));
|
|
472
523
|
const straightRgb = overlayColor.rgb
|
|
473
524
|
.mul(overlayAlpha)
|
|
474
|
-
.add(
|
|
475
|
-
.div(alpha.max(
|
|
525
|
+
.add(sceneComposite.mul(visualAlpha).mul(overlayAlpha.oneMinus()))
|
|
526
|
+
.div(alpha.max(tslFloat(0.00001)));
|
|
476
527
|
finalOutput = premultiplyAlpha(renderOutput(vec4(straightRgb, alpha)));
|
|
477
528
|
}
|
|
478
529
|
const renderPipeline = new RenderPipeline(renderer);
|
|
530
|
+
resources.pipeline = renderPipeline;
|
|
479
531
|
renderPipeline.outputColorTransform = !transparentBackground;
|
|
480
532
|
renderPipeline.outputNode = finalOutput;
|
|
481
|
-
renderPipelineRef.current = renderPipeline;
|
|
482
533
|
retryCountRef.current = 0;
|
|
483
534
|
}
|
|
484
535
|
catch (error) {
|
|
@@ -488,24 +539,18 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
488
539
|
ssgi: SSGI_PARAMS.enabled,
|
|
489
540
|
rendererCtor: renderer.constructor?.name,
|
|
490
541
|
}, error);
|
|
491
|
-
|
|
492
|
-
renderPipelineRef.current.dispose();
|
|
493
|
-
}
|
|
494
|
-
renderPipelineRef.current = null;
|
|
542
|
+
disposePipeline();
|
|
495
543
|
}
|
|
496
|
-
return
|
|
497
|
-
if (renderPipelineRef.current) {
|
|
498
|
-
renderPipelineRef.current.dispose();
|
|
499
|
-
}
|
|
500
|
-
renderPipelineRef.current = null;
|
|
501
|
-
};
|
|
544
|
+
return disposePipeline;
|
|
502
545
|
}, [
|
|
503
546
|
// NOTE: hoverHighlightMode intentionally excluded — the hover style is
|
|
504
547
|
// pushed to uniforms in a separate effect, so a hover must NOT rebuild the
|
|
505
548
|
// whole pipeline. The uniform refs below are stable (useMemo), so they
|
|
506
549
|
// never trigger a rebuild either.
|
|
550
|
+
atmosphere,
|
|
507
551
|
camera,
|
|
508
552
|
disablePostFx,
|
|
553
|
+
disposePipeline,
|
|
509
554
|
hoverHiddenColor,
|
|
510
555
|
hoverPulseMix,
|
|
511
556
|
hoverStrength,
|
|
@@ -572,7 +617,10 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
572
617
|
if (disablePostFx ||
|
|
573
618
|
PERF_POST_FX_DISABLED ||
|
|
574
619
|
hasPipelineErrorRef.current ||
|
|
575
|
-
!
|
|
620
|
+
!resourcesRef.current?.pipeline) {
|
|
621
|
+
const previousBackgroundNode = scene.backgroundNode;
|
|
622
|
+
if (directSkyNode && !transparentBackground)
|
|
623
|
+
scene.backgroundNode = directSkyNode;
|
|
576
624
|
try {
|
|
577
625
|
const clearAlpha = transparentBackground ? 0 : 1;
|
|
578
626
|
if (renderer.setClearColor) {
|
|
@@ -584,37 +632,33 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
584
632
|
renderer.setClearAlpha(clearAlpha);
|
|
585
633
|
}
|
|
586
634
|
const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
635
|
+
timeSpan('render-encode', () => {
|
|
636
|
+
;
|
|
637
|
+
renderer.render(scene, camera);
|
|
638
|
+
});
|
|
639
|
+
if (PERF_OVERLAY_ENABLED)
|
|
640
|
+
recordFrameGpuTiming(renderer, submittedAt);
|
|
594
641
|
}
|
|
595
642
|
catch (fallbackError) {
|
|
596
643
|
console.error('[viewer/post-processing] Fallback render failed.', fallbackError);
|
|
597
644
|
}
|
|
645
|
+
finally {
|
|
646
|
+
scene.backgroundNode = previousBackgroundNode;
|
|
647
|
+
}
|
|
598
648
|
return;
|
|
599
649
|
}
|
|
650
|
+
const pipeline = resourcesRef.current.pipeline;
|
|
600
651
|
try {
|
|
601
652
|
// Clear alpha=0 so background pixels in the output MRT attachment (index 0) get a=0,
|
|
602
653
|
// making scenePassColor.a a reliable geometry mask (geometry pixels write a=1 via output node).
|
|
603
654
|
;
|
|
604
655
|
renderer.setClearAlpha(0);
|
|
605
656
|
const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
// (no await) and works for the custom RenderPipeline path that
|
|
612
|
-
// bypasses three.js's timestamp-query infrastructure.
|
|
613
|
-
const queue = renderer.backend?.device?.queue;
|
|
614
|
-
queue?.onSubmittedWorkDone?.().then(() => {
|
|
615
|
-
pushGpuSample(performance.now() - submittedAt);
|
|
616
|
-
});
|
|
617
|
-
}
|
|
657
|
+
timeSpan('render-encode', () => {
|
|
658
|
+
pipeline.render();
|
|
659
|
+
});
|
|
660
|
+
if (PERF_OVERLAY_ENABLED)
|
|
661
|
+
recordFrameGpuTiming(renderer, submittedAt);
|
|
618
662
|
}
|
|
619
663
|
catch (error) {
|
|
620
664
|
hasPipelineErrorRef.current = true;
|
|
@@ -624,10 +668,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
624
668
|
rendererCtor: renderer.constructor?.name,
|
|
625
669
|
error,
|
|
626
670
|
});
|
|
627
|
-
|
|
628
|
-
renderPipelineRef.current.dispose();
|
|
629
|
-
}
|
|
630
|
-
renderPipelineRef.current = null;
|
|
671
|
+
disposePipeline();
|
|
631
672
|
if (retryCountRef.current < MAX_PIPELINE_RETRIES) {
|
|
632
673
|
// Auto-retry: schedule a pipeline rebuild if we haven't exceeded the retry limit
|
|
633
674
|
retryCountRef.current++;
|
|
@@ -2,11 +2,20 @@
|
|
|
2
2
|
* Mounts every registered node kind's system component, ordered by
|
|
3
3
|
* `system.priority` (default {@link DEFAULT_PRIORITY}).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Two resilience rules, both learned from a live session in which the wall
|
|
6
|
+
* systems bundle (geometry rebuild + cutout stamps + batching) never ran
|
|
7
|
+
* while everything else did (QA f2 probe6: 24 walls stuck on placeholder
|
|
8
|
+
* geometry, no `wallHidden` stamps, base materials untouched):
|
|
9
|
+
*
|
|
10
|
+
* 1. `entries` re-derives on `useRegistryVersion()` — kinds register
|
|
11
|
+
* asynchronously (plugin discovery, HMR), and a list snapshotted once at
|
|
12
|
+
* mount permanently drops any system whose kind registers later. Same
|
|
13
|
+
* staleness class SelectionManager already guards against ("plugin
|
|
14
|
+
* nodes select-but-never-hover").
|
|
15
|
+
* 2. Each system gets its OWN Suspense boundary. With one shared boundary,
|
|
16
|
+
* ANY lazily-loading (or load-failing) system chunk unmounts every
|
|
17
|
+
* other system while it is pending — one bad chunk must not take the
|
|
18
|
+
* wall pipeline down with it.
|
|
10
19
|
*/
|
|
11
20
|
export declare function RegisteredSystems(): import("react").JSX.Element | null;
|
|
12
21
|
//# sourceMappingURL=registered-systems.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registered-systems.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/registered-systems.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"registered-systems.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/registered-systems.tsx"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,uCAgChC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { createSceneApi, isNodeKindEnabled, nodeRegistry, useScene, } from '@pascal-app/core';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { createSceneApi, isNodeKindEnabled, nodeRegistry, useRegistryVersion, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { lazy, Suspense, useMemo } from 'react';
|
|
5
5
|
const DEFAULT_PRIORITY = 5;
|
|
6
6
|
const lazyCache = new WeakMap();
|
|
@@ -18,16 +18,28 @@ function loadSystem(def) {
|
|
|
18
18
|
* Mounts every registered node kind's system component, ordered by
|
|
19
19
|
* `system.priority` (default {@link DEFAULT_PRIORITY}).
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* Two resilience rules, both learned from a live session in which the wall
|
|
22
|
+
* systems bundle (geometry rebuild + cutout stamps + batching) never ran
|
|
23
|
+
* while everything else did (QA f2 probe6: 24 walls stuck on placeholder
|
|
24
|
+
* geometry, no `wallHidden` stamps, base materials untouched):
|
|
25
|
+
*
|
|
26
|
+
* 1. `entries` re-derives on `useRegistryVersion()` — kinds register
|
|
27
|
+
* asynchronously (plugin discovery, HMR), and a list snapshotted once at
|
|
28
|
+
* mount permanently drops any system whose kind registers later. Same
|
|
29
|
+
* staleness class SelectionManager already guards against ("plugin
|
|
30
|
+
* nodes select-but-never-hover").
|
|
31
|
+
* 2. Each system gets its OWN Suspense boundary. With one shared boundary,
|
|
32
|
+
* ANY lazily-loading (or load-failing) system chunk unmounts every
|
|
33
|
+
* other system while it is pending — one bad chunk must not take the
|
|
34
|
+
* wall pipeline down with it.
|
|
26
35
|
*/
|
|
27
36
|
export function RegisteredSystems() {
|
|
28
37
|
const sceneApi = useMemo(() => createSceneApi(useScene), []);
|
|
29
38
|
const installedPlugins = useScene((state) => state.installedPlugins);
|
|
39
|
+
const registryVersion = useRegistryVersion();
|
|
30
40
|
const entries = useMemo(() => {
|
|
41
|
+
// re-derive when kinds register after mount (async plugin load)
|
|
42
|
+
void registryVersion;
|
|
31
43
|
return Array.from(nodeRegistry.entries())
|
|
32
44
|
.filter(([, def]) => def.system != null)
|
|
33
45
|
.sort(([, a], [, b]) => {
|
|
@@ -35,15 +47,15 @@ export function RegisteredSystems() {
|
|
|
35
47
|
const pb = b.system?.priority ?? DEFAULT_PRIORITY;
|
|
36
48
|
return pa - pb;
|
|
37
49
|
});
|
|
38
|
-
}, []);
|
|
50
|
+
}, [registryVersion]);
|
|
39
51
|
if (entries.length === 0)
|
|
40
52
|
return null;
|
|
41
|
-
return (_jsx(
|
|
53
|
+
return (_jsx(_Fragment, { children: entries.map(([kind, def]) => {
|
|
42
54
|
if (!isNodeKindEnabled(kind, installedPlugins))
|
|
43
55
|
return null;
|
|
44
56
|
const Comp = loadSystem(def);
|
|
45
57
|
if (!Comp)
|
|
46
58
|
return null;
|
|
47
|
-
return _jsx(Comp, { sceneApi: sceneApi }, `registered-system:${kind}`);
|
|
59
|
+
return (_jsx(Suspense, { fallback: null, children: _jsx(Comp, { sceneApi: sceneApi }) }, `registered-system:${kind}`));
|
|
48
60
|
}) }));
|
|
49
61
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Color, Node, Vector3 } from 'three/webgpu';
|
|
2
|
+
export type SceneAtmosphereSource = {
|
|
3
|
+
skyRadiance(direction: Node<'vec3'>): Node<'vec3'>;
|
|
4
|
+
reflectionRadiance(direction: Node<'vec3'>): Node<'vec3'>;
|
|
5
|
+
fogRadiance(direction: Node<'vec3'>): Node<'vec3'>;
|
|
6
|
+
environmentNode: Node<'vec3'>;
|
|
7
|
+
sunDirection: Vector3;
|
|
8
|
+
sunColor: Color;
|
|
9
|
+
sunIntensity: number;
|
|
10
|
+
moonDirection: Vector3;
|
|
11
|
+
moonColor: Color;
|
|
12
|
+
moonIntensity: number;
|
|
13
|
+
skyColor: Color;
|
|
14
|
+
groundColor: Color;
|
|
15
|
+
hemisphereIntensity: number;
|
|
16
|
+
ambientIntensity: number;
|
|
17
|
+
exposure: number;
|
|
18
|
+
fogStart: number;
|
|
19
|
+
fogEnd: number;
|
|
20
|
+
};
|
|
21
|
+
/** Returns the atmosphere currently owning this React Three Fiber scene. */
|
|
22
|
+
export declare function useSceneAtmosphere(): SceneAtmosphereSource | null;
|
|
23
|
+
/**
|
|
24
|
+
* Installs a generic radiance source into the current scene. The source object is
|
|
25
|
+
* expected to remain stable while its colors, vectors, and numbers mutate.
|
|
26
|
+
*/
|
|
27
|
+
export declare function SceneAtmosphere({ source }: {
|
|
28
|
+
source: SceneAtmosphereSource;
|
|
29
|
+
}): null;
|
|
30
|
+
//# sourceMappingURL=scene-atmosphere.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-atmosphere.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/scene-atmosphere.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAS,OAAO,EAAE,MAAM,cAAc,CAAA;AAI/D,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;IAClD,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;IACzD,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;IAClD,eAAe,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IAC7B,YAAY,EAAE,OAAO,CAAA;IACrB,QAAQ,EAAE,KAAK,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,OAAO,CAAA;IACtB,SAAS,EAAE,KAAK,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,KAAK,CAAA;IACf,WAAW,EAAE,KAAK,CAAA;IAClB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AA6DD,4EAA4E;AAC5E,wBAAgB,kBAAkB,IAAI,qBAAqB,GAAG,IAAI,CAIjE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,qBAAqB,CAAA;CAAE,QA8C5E"}
|