@pascal-app/viewer 1.0.0-beta.5 → 1.0.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/LICENSE +21 -0
- package/README.md +67 -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 +198 -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 +76 -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 +70 -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/capture/use-json-artifact.d.ts +2 -0
- package/dist/capture/use-json-artifact.d.ts.map +1 -0
- package/dist/capture/use-json-artifact.js +42 -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/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +3 -1
- 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 +130 -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/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +8 -16
- 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 +16 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -7
- 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/isolation.d.ts +14 -30
- package/dist/lib/isolation.d.ts.map +1 -1
- package/dist/lib/isolation.js +62 -66
- 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 +52 -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 +25 -2
- package/dist/lib/snapshot-pipeline.d.ts +3 -1
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +32 -16
- package/dist/store/use-viewer.d.ts +5 -3
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +2 -0
- 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/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +7 -3
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +7 -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/dist/systems/zone/zone-system.d.ts.map +1 -1
- package/dist/systems/zone/zone-system.js +28 -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":"AA8CA,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,SAmnBA,CAAA;AAED,eAAe,oBAAoB,CAAA"}
|
|
@@ -3,17 +3,22 @@ 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 { refreshIsolation } from '../../lib/isolation';
|
|
13
|
+
import { LayerPassIndex, LayerPassNode } from '../../lib/layer-pass';
|
|
12
14
|
import { GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, ZONE_LAYER } from '../../lib/layers';
|
|
13
15
|
import { mergedOutline } from '../../lib/merged-outline-node';
|
|
16
|
+
import { recordPerfSample, timeSpan } from '../../lib/perf-tracks';
|
|
17
|
+
import { PostProcessingResources } from '../../lib/post-processing-resources';
|
|
14
18
|
import { getSceneTheme } from '../../lib/scene-themes';
|
|
15
19
|
import { packNormalToRGB, unpackRGBToNormal } from '../../lib/tsl-compat';
|
|
16
20
|
import useViewer from '../../store/use-viewer';
|
|
21
|
+
import { useSceneAtmosphere } from './scene-atmosphere';
|
|
17
22
|
// Scene-referred grade applied before the output tone mapping (AgX). AgX rolls
|
|
18
23
|
// highlights off gently but reads flat on its own; a mild mid-gray-pivot
|
|
19
24
|
// contrast + saturation lift restores the punch. Rendered shading only.
|
|
@@ -36,7 +41,8 @@ export const SSGI_PARAMS = {
|
|
|
36
41
|
useScreenSpaceSampling: true,
|
|
37
42
|
useTemporalFiltering: false,
|
|
38
43
|
};
|
|
39
|
-
// Diagnostic toggles for thermal A/B testing. Add
|
|
44
|
+
// Diagnostic toggles for thermal A/B testing. Add
|
|
45
|
+
// `?disable=ao,denoise,outline,postFx`
|
|
40
46
|
// to the URL (any subset) and reload to skip those passes. Each flag prevents
|
|
41
47
|
// allocation + per-frame work for that stage, so device temperature deltas
|
|
42
48
|
// across combos isolate which pass is the actual culprit. Picked up once at
|
|
@@ -53,7 +59,12 @@ export const SSGI_PARAMS = {
|
|
|
53
59
|
// per-frame vertex/draw cost dominates the whole capture.
|
|
54
60
|
function readPerfDisableFlags() {
|
|
55
61
|
if (typeof window === 'undefined') {
|
|
56
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
ao: false,
|
|
64
|
+
denoise: false,
|
|
65
|
+
outline: false,
|
|
66
|
+
postFx: false,
|
|
67
|
+
};
|
|
57
68
|
}
|
|
58
69
|
const raw = new URLSearchParams(window.location.search).get('disable') ?? '';
|
|
59
70
|
const set = new Set(raw
|
|
@@ -99,9 +110,39 @@ function sanitizeOutlineObjects(objects) {
|
|
|
99
110
|
}
|
|
100
111
|
objects.length = nextIndex;
|
|
101
112
|
}
|
|
113
|
+
// Two independent GPU readings per frame, both `?perf`-only:
|
|
114
|
+
// - `gpu-render`: three's WebGPU timestamp queries — the summed GPU duration of
|
|
115
|
+
// the frame's render passes, measured on the device. The only honest "GPU ms".
|
|
116
|
+
// - `gpu-queue`: submit → `onSubmittedWorkDone()` wall time. That covers queue
|
|
117
|
+
// backlog and CPU work that ran before the microtask got to resume, so it is
|
|
118
|
+
// a backpressure signal, not GPU time.
|
|
119
|
+
// `resolveTimestampsAsync` returns the previous resolve's value while one is in
|
|
120
|
+
// flight, so calling it every frame is safe (and required — the query pool warns
|
|
121
|
+
// once it fills).
|
|
122
|
+
function recordFrameGpuTiming(renderer, submittedAt) {
|
|
123
|
+
const queue = renderer.backend?.device?.queue;
|
|
124
|
+
queue?.onSubmittedWorkDone?.().then(() => {
|
|
125
|
+
recordPerfSample('gpu-queue', performance.now() - submittedAt);
|
|
126
|
+
});
|
|
127
|
+
// Off unless the device advertised 'timestamp-query' at init — the backend
|
|
128
|
+
// clears its own flag when the feature is missing, so this is the truth.
|
|
129
|
+
if (renderer.backend?.trackTimestamp !== true)
|
|
130
|
+
return;
|
|
131
|
+
renderer
|
|
132
|
+
.resolveTimestampsAsync?.(TimestampQuery.RENDER)
|
|
133
|
+
?.then((ms) => {
|
|
134
|
+
if (typeof ms === 'number' && ms > 0)
|
|
135
|
+
recordPerfSample('gpu-render', ms);
|
|
136
|
+
})
|
|
137
|
+
.catch(() => {
|
|
138
|
+
// Pool disposed mid-flight (pipeline rebuild / unmount) — nothing to report.
|
|
139
|
+
});
|
|
140
|
+
}
|
|
102
141
|
const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostFx = false, }) => {
|
|
103
142
|
const { gl: renderer, invalidate, scene, camera, size } = useThree();
|
|
104
|
-
const
|
|
143
|
+
const resourcesRef = useRef(null);
|
|
144
|
+
const atmosphere = useSceneAtmosphere();
|
|
145
|
+
const directSkyNode = useMemo(() => (atmosphere ? atmosphere.skyRadiance(normalWorldGeometry) : null), [atmosphere]);
|
|
105
146
|
const hasPipelineErrorRef = useRef(false);
|
|
106
147
|
const retryCountRef = useRef(0);
|
|
107
148
|
const rebuildTimeoutRef = useRef(null);
|
|
@@ -183,6 +224,10 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
183
224
|
}
|
|
184
225
|
setPipelineVersion((v) => v + 1);
|
|
185
226
|
}, []);
|
|
227
|
+
const disposePipeline = useCallback(() => {
|
|
228
|
+
resourcesRef.current?.dispose();
|
|
229
|
+
resourcesRef.current = null;
|
|
230
|
+
}, []);
|
|
186
231
|
// Reset retry state when project changes
|
|
187
232
|
useEffect(() => {
|
|
188
233
|
if (lastProjectIdRef.current === projectId)
|
|
@@ -233,10 +278,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
233
278
|
if (width < 1 || height < 1) {
|
|
234
279
|
skippedZeroSizeRef.current = true;
|
|
235
280
|
hasPipelineErrorRef.current = false;
|
|
236
|
-
|
|
237
|
-
renderPipelineRef.current.dispose();
|
|
238
|
-
}
|
|
239
|
-
renderPipelineRef.current = null;
|
|
281
|
+
disposePipeline();
|
|
240
282
|
return;
|
|
241
283
|
}
|
|
242
284
|
if (skippedZeroSizeRef.current) {
|
|
@@ -249,23 +291,17 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
249
291
|
// allocated every pass.
|
|
250
292
|
if (disablePostFx || perfDisable.postFx) {
|
|
251
293
|
hasPipelineErrorRef.current = false;
|
|
252
|
-
|
|
253
|
-
renderPipelineRef.current.dispose();
|
|
254
|
-
}
|
|
255
|
-
renderPipelineRef.current = null;
|
|
294
|
+
disposePipeline();
|
|
256
295
|
return;
|
|
257
296
|
}
|
|
258
297
|
const ssgiEnabled = shading === 'rendered' && SSGI_PARAMS.enabled && !perfDisable.ao;
|
|
259
298
|
const denoiseEnabled = ssgiEnabled && !perfDisable.denoise;
|
|
260
299
|
const outlineEnabled = !perfDisable.outline;
|
|
261
300
|
const inkEnabled = edges !== 'off';
|
|
262
|
-
// The depth+normal MRT feeds both SSGI and the screen-space ink pass.
|
|
263
301
|
const needsNormalMRT = ssgiEnabled || inkEnabled;
|
|
264
302
|
// Soft = thin (1px sample radius) + faint (50% opacity); strong = thick
|
|
265
303
|
// (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%.
|
|
304
|
+
// so radius+opacity are what actually separate the two modes.
|
|
269
305
|
const inkRadius = 1;
|
|
270
306
|
const inkOpacity = inkOpacityOverride ?? (edges === 'strong' ? 1 : 0.5);
|
|
271
307
|
console.log('[viewer/post-processing] Building pipeline', {
|
|
@@ -293,7 +329,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
293
329
|
const hasWebGPU = typeof navigator !== 'undefined' && 'gpu' in navigator;
|
|
294
330
|
if (!hasWebGPU) {
|
|
295
331
|
hasPipelineErrorRef.current = true;
|
|
296
|
-
|
|
332
|
+
resourcesRef.current = null;
|
|
297
333
|
return;
|
|
298
334
|
}
|
|
299
335
|
// Clear outliner arrays synchronously to prevent stale Object3D refs
|
|
@@ -303,15 +339,22 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
303
339
|
sanitizeOutlineObjects(outliner.hoveredObjects);
|
|
304
340
|
outliner.selectedObjects.length = 0;
|
|
305
341
|
outliner.hoveredObjects.length = 0;
|
|
342
|
+
const resources = new PostProcessingResources();
|
|
343
|
+
resourcesRef.current = resources;
|
|
306
344
|
try {
|
|
345
|
+
const layerIndex = new LayerPassIndex(scene, [ZONE_LAYER, OVERLAY_LAYER]);
|
|
346
|
+
resources.layerIndex = layerIndex;
|
|
307
347
|
const scenePass = pass(scene, camera);
|
|
348
|
+
resources.passes.push(scenePass);
|
|
308
349
|
scenePass.setLayers(sceneOnlyLayers);
|
|
309
|
-
const zonePass =
|
|
350
|
+
const zonePass = new LayerPassNode(layerIndex, camera, ZONE_LAYER, scenePass);
|
|
351
|
+
resources.passes.push(zonePass);
|
|
310
352
|
zonePass.setLayers(zoneLayers);
|
|
311
353
|
// Editor overlays (gizmos, move handles, tool previews, grid) on their own
|
|
312
354
|
// layer, kept out of the depth/normal MRT above so the ink + SSGI ignore
|
|
313
355
|
// them, then composited on top of the final image below.
|
|
314
|
-
const overlayPass =
|
|
356
|
+
const overlayPass = new LayerPassNode(layerIndex, camera, OVERLAY_LAYER, scenePass);
|
|
357
|
+
resources.passes.push(overlayPass);
|
|
315
358
|
overlayPass.setLayers(overlayLayers);
|
|
316
359
|
const overlayColor = overlayPass.getTextureNode('output');
|
|
317
360
|
const scenePassColor = scenePass.getTextureNode('output');
|
|
@@ -325,25 +368,28 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
325
368
|
// not SSGI is enabled. When SSGI is on, the branch below overwrites this
|
|
326
369
|
// with its own zone-inclusive composite (no double-add).
|
|
327
370
|
let sceneColor = vec4(add(scenePassColor.rgb, zonePass.rgb), contentAlpha);
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
|
|
371
|
+
// Scene depth is shared by SSGI, ink, and outlines.
|
|
372
|
+
// The normal MRT is only built when SSGI or ink needs it.
|
|
373
|
+
const scenePassDepth = scenePass.getTextureNode('depth');
|
|
331
374
|
let scenePassNormal = null;
|
|
332
|
-
let sceneNormal = null;
|
|
333
375
|
if (needsNormalMRT) {
|
|
334
376
|
scenePass.setMRT(mrt({
|
|
335
377
|
output,
|
|
336
378
|
diffuseColor,
|
|
337
379
|
normal: packNormalToRGB(normalView),
|
|
338
380
|
}));
|
|
339
|
-
scenePassDepth = scenePass.getTextureNode('depth');
|
|
340
381
|
scenePassNormal = scenePass.getTextureNode('normal');
|
|
382
|
+
}
|
|
383
|
+
if (scenePassNormal) {
|
|
341
384
|
const normalTexture = scenePass.getTexture('normal');
|
|
342
385
|
normalTexture.type = UnsignedByteType;
|
|
343
|
-
// Extract normal from color-encoded texture (SSGI consumes the node form)
|
|
344
|
-
sceneNormal = sample((uv) => unpackRGBToNormal(scenePassNormal.sample(uv)));
|
|
345
386
|
}
|
|
346
|
-
|
|
387
|
+
// Extract normal from color-encoded texture (SSGI consumes the node form).
|
|
388
|
+
const normalNode = scenePassNormal;
|
|
389
|
+
const sceneNormal = normalNode
|
|
390
|
+
? sample((uv) => unpackRGBToNormal(normalNode.sample(uv)))
|
|
391
|
+
: null;
|
|
392
|
+
if (ssgiEnabled && scenePassDepth && sceneNormal) {
|
|
347
393
|
const scenePassDiffuse = scenePass.getTextureNode('diffuseColor');
|
|
348
394
|
const diffuseTexture = scenePass.getTexture('diffuseColor');
|
|
349
395
|
diffuseTexture.type = UnsignedByteType;
|
|
@@ -367,7 +413,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
367
413
|
if (denoiseEnabled) {
|
|
368
414
|
// DenoiseNode only denoises RGB — alpha is passed through unchanged.
|
|
369
415
|
// 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,
|
|
416
|
+
const aoAsRgb = vec4(aoTexture.r, aoTexture.r, aoTexture.r, tslFloat(1));
|
|
371
417
|
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
|
|
372
418
|
denoisePass.index.value = 0;
|
|
373
419
|
denoisePass.radius.value = 4;
|
|
@@ -382,22 +428,22 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
382
428
|
// (same ≈150→350 m window as ink-edges' distanceFade) so the horizon
|
|
383
429
|
// disc and the geometry↔sky depth cliff never grow an AO band — that
|
|
384
430
|
// band read as a visible line along the horizon.
|
|
385
|
-
const aoFarFade = smoothstep(
|
|
386
|
-
ao = mix(ao,
|
|
431
|
+
const aoFarFade = smoothstep(tslFloat(0.9994), tslFloat(0.9998), scenePassDepth.sample(screenUV).r);
|
|
432
|
+
ao = mix(ao, tslFloat(1), aoFarFade);
|
|
387
433
|
// Composite: scene * AO + diffuse * GI
|
|
388
434
|
sceneColor = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))), contentAlpha);
|
|
389
435
|
}
|
|
390
436
|
// Screen-space ink outline (SketchUp look) — depth/normal edge detection
|
|
391
437
|
// over the composited scene. Topology-agnostic, so it handles CSG-cut
|
|
392
438
|
// walls cleanly. Applied before the selection outline + background mix.
|
|
393
|
-
if (inkEnabled) {
|
|
439
|
+
if (inkEnabled && scenePassDepth && scenePassNormal) {
|
|
394
440
|
sceneColor = vec4(inkedEdges({
|
|
395
441
|
sceneRgb: sceneColor.rgb,
|
|
396
442
|
depthTex: scenePassDepth,
|
|
397
443
|
normalTex: scenePassNormal,
|
|
398
444
|
inkColor: inkColorUniform.current,
|
|
399
445
|
radius: inkRadius,
|
|
400
|
-
opacity:
|
|
446
|
+
opacity: tslFloat(inkOpacity).mul(inkOpacityScaleUniform.current),
|
|
401
447
|
}), sceneColor.a);
|
|
402
448
|
}
|
|
403
449
|
// Scene-referred grade (contrast around mid-gray + saturation) before the
|
|
@@ -409,17 +455,20 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
409
455
|
if (shading === 'rendered') {
|
|
410
456
|
sceneColor = vec4(gradeRgb(sceneColor.rgb), sceneColor.a);
|
|
411
457
|
}
|
|
412
|
-
//
|
|
458
|
+
// Reused scene depth lets outlined groups occlude each other; materials
|
|
459
|
+
// with depthWrite=false (including glazing) no longer occlude outlines.
|
|
413
460
|
const outliner = useViewer.getState().outliner;
|
|
414
461
|
let compositeWithOutlines = sceneColor;
|
|
415
462
|
let visualAlpha = contentAlpha;
|
|
416
463
|
if (outlineEnabled) {
|
|
417
464
|
const outlineNode = mergedOutline(scene, camera, {
|
|
465
|
+
sceneDepthNode: scenePassDepth,
|
|
418
466
|
primaryObjects: outliner.selectedObjects,
|
|
419
467
|
secondaryObjects: outliner.hoveredObjects,
|
|
420
468
|
primaryEdgeThickness: uniform(1),
|
|
421
469
|
secondaryEdgeThickness: uniform(1.5),
|
|
422
470
|
});
|
|
471
|
+
resources.outline = outlineNode;
|
|
423
472
|
// Selected: white visible, yellow hidden
|
|
424
473
|
const selectedVisibleColor = uniform(new Color(0xff_ff_ff));
|
|
425
474
|
const selectedHiddenColor = uniform(new Color(0xf3_ff_47));
|
|
@@ -431,7 +480,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
431
480
|
// Hovered: blue visible, yellow hidden, pulsing
|
|
432
481
|
const pulsePeriod = uniform(3);
|
|
433
482
|
const oscillating = oscSine(time.div(pulsePeriod).mul(2)).mul(0.5).add(0.5);
|
|
434
|
-
const osc = mix(oscillating,
|
|
483
|
+
const osc = mix(oscillating, tslFloat(1), hoverPulseMix);
|
|
435
484
|
const hoverOutline = outlineNode.secondaryVisibleEdge
|
|
436
485
|
.mul(hoverVisibleColor)
|
|
437
486
|
.add(outlineNode.secondaryHiddenEdge.mul(hoverHiddenColor))
|
|
@@ -448,37 +497,40 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
448
497
|
// sky gradient (shared formula in lib/backdrop.ts). The horizon disc
|
|
449
498
|
// dissolves into the same formula, so backdrop and ground meet
|
|
450
499
|
// seamlessly exactly where the disc vanishes.
|
|
451
|
-
const ndc = vec4(screenUV.x.mul(2).sub(1),
|
|
500
|
+
const ndc = vec4(screenUV.x.mul(2).sub(1), tslFloat(1).sub(screenUV.y).mul(2).sub(1), 1, 1);
|
|
452
501
|
const viewRay = camProjInvUniform.current.mul(ndc);
|
|
453
502
|
const worldDir = camWorldUniform.current.mul(vec4(viewRay.xyz, 0)).xyz.normalize();
|
|
454
|
-
let bgGradient =
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
503
|
+
let bgGradient = atmosphere
|
|
504
|
+
? atmosphere.skyRadiance(worldDir)
|
|
505
|
+
: backdropGradient({
|
|
506
|
+
dirY: worldDir.y,
|
|
507
|
+
background: bgUniform.current,
|
|
508
|
+
haze: bgHazeUniform.current,
|
|
509
|
+
sky: bgSkyUniform.current,
|
|
510
|
+
skyDeep: bgSkyDeepUniform.current,
|
|
511
|
+
});
|
|
461
512
|
if (shading === 'rendered') {
|
|
462
513
|
bgGradient = gradeRgb(bgGradient);
|
|
463
514
|
}
|
|
464
|
-
const
|
|
515
|
+
const sceneComposite = compositeWithOutlines.rgb;
|
|
516
|
+
const composited = mix(bgGradient, sceneComposite, contentAlpha);
|
|
465
517
|
// Editor overlays painted on top by their own alpha — they never get inked,
|
|
466
518
|
// AO'd, or outlined, and always read crisp regardless of scene depth.
|
|
467
519
|
const withOverlay = mix(composited, overlayColor.rgb, overlayColor.a);
|
|
468
|
-
let finalOutput = vec4(withOverlay,
|
|
520
|
+
let finalOutput = vec4(withOverlay, tslFloat(1));
|
|
469
521
|
if (transparentBackground) {
|
|
470
522
|
const overlayAlpha = overlayColor.a;
|
|
471
523
|
const alpha = overlayAlpha.add(visualAlpha.mul(overlayAlpha.oneMinus()));
|
|
472
524
|
const straightRgb = overlayColor.rgb
|
|
473
525
|
.mul(overlayAlpha)
|
|
474
|
-
.add(
|
|
475
|
-
.div(alpha.max(
|
|
526
|
+
.add(sceneComposite.mul(visualAlpha).mul(overlayAlpha.oneMinus()))
|
|
527
|
+
.div(alpha.max(tslFloat(0.00001)));
|
|
476
528
|
finalOutput = premultiplyAlpha(renderOutput(vec4(straightRgb, alpha)));
|
|
477
529
|
}
|
|
478
530
|
const renderPipeline = new RenderPipeline(renderer);
|
|
531
|
+
resources.pipeline = renderPipeline;
|
|
479
532
|
renderPipeline.outputColorTransform = !transparentBackground;
|
|
480
533
|
renderPipeline.outputNode = finalOutput;
|
|
481
|
-
renderPipelineRef.current = renderPipeline;
|
|
482
534
|
retryCountRef.current = 0;
|
|
483
535
|
}
|
|
484
536
|
catch (error) {
|
|
@@ -488,24 +540,18 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
488
540
|
ssgi: SSGI_PARAMS.enabled,
|
|
489
541
|
rendererCtor: renderer.constructor?.name,
|
|
490
542
|
}, error);
|
|
491
|
-
|
|
492
|
-
renderPipelineRef.current.dispose();
|
|
493
|
-
}
|
|
494
|
-
renderPipelineRef.current = null;
|
|
543
|
+
disposePipeline();
|
|
495
544
|
}
|
|
496
|
-
return
|
|
497
|
-
if (renderPipelineRef.current) {
|
|
498
|
-
renderPipelineRef.current.dispose();
|
|
499
|
-
}
|
|
500
|
-
renderPipelineRef.current = null;
|
|
501
|
-
};
|
|
545
|
+
return disposePipeline;
|
|
502
546
|
}, [
|
|
503
547
|
// NOTE: hoverHighlightMode intentionally excluded — the hover style is
|
|
504
548
|
// pushed to uniforms in a separate effect, so a hover must NOT rebuild the
|
|
505
549
|
// whole pipeline. The uniform refs below are stable (useMemo), so they
|
|
506
550
|
// never trigger a rebuild either.
|
|
551
|
+
atmosphere,
|
|
507
552
|
camera,
|
|
508
553
|
disablePostFx,
|
|
554
|
+
disposePipeline,
|
|
509
555
|
hoverHiddenColor,
|
|
510
556
|
hoverPulseMix,
|
|
511
557
|
hoverStrength,
|
|
@@ -567,12 +613,16 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
567
613
|
inkColorUniform.current.value.set(edgeColorFor(bgHex));
|
|
568
614
|
inkOpacityScaleUniform.current.value = edgeOpacityScaleFor(bgHex);
|
|
569
615
|
const outliner = useViewer.getState().outliner;
|
|
616
|
+
const restoreAtmosphere = refreshIsolation(scene);
|
|
570
617
|
sanitizeOutlineObjects(outliner.selectedObjects);
|
|
571
618
|
sanitizeOutlineObjects(outliner.hoveredObjects);
|
|
572
619
|
if (disablePostFx ||
|
|
573
620
|
PERF_POST_FX_DISABLED ||
|
|
574
621
|
hasPipelineErrorRef.current ||
|
|
575
|
-
!
|
|
622
|
+
!resourcesRef.current?.pipeline) {
|
|
623
|
+
const previousBackgroundNode = scene.backgroundNode;
|
|
624
|
+
if (directSkyNode && !transparentBackground)
|
|
625
|
+
scene.backgroundNode = directSkyNode;
|
|
576
626
|
try {
|
|
577
627
|
const clearAlpha = transparentBackground ? 0 : 1;
|
|
578
628
|
if (renderer.setClearColor) {
|
|
@@ -584,37 +634,34 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
584
634
|
renderer.setClearAlpha(clearAlpha);
|
|
585
635
|
}
|
|
586
636
|
const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
637
|
+
timeSpan('render-encode', () => {
|
|
638
|
+
;
|
|
639
|
+
renderer.render(scene, camera);
|
|
640
|
+
});
|
|
641
|
+
if (PERF_OVERLAY_ENABLED)
|
|
642
|
+
recordFrameGpuTiming(renderer, submittedAt);
|
|
594
643
|
}
|
|
595
644
|
catch (fallbackError) {
|
|
596
645
|
console.error('[viewer/post-processing] Fallback render failed.', fallbackError);
|
|
597
646
|
}
|
|
647
|
+
finally {
|
|
648
|
+
scene.backgroundNode = previousBackgroundNode;
|
|
649
|
+
restoreAtmosphere();
|
|
650
|
+
}
|
|
598
651
|
return;
|
|
599
652
|
}
|
|
653
|
+
const pipeline = resourcesRef.current.pipeline;
|
|
600
654
|
try {
|
|
601
655
|
// Clear alpha=0 so background pixels in the output MRT attachment (index 0) get a=0,
|
|
602
656
|
// making scenePassColor.a a reliable geometry mask (geometry pixels write a=1 via output node).
|
|
603
657
|
;
|
|
604
658
|
renderer.setClearAlpha(0);
|
|
605
659
|
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
|
-
}
|
|
660
|
+
timeSpan('render-encode', () => {
|
|
661
|
+
pipeline.render();
|
|
662
|
+
});
|
|
663
|
+
if (PERF_OVERLAY_ENABLED)
|
|
664
|
+
recordFrameGpuTiming(renderer, submittedAt);
|
|
618
665
|
}
|
|
619
666
|
catch (error) {
|
|
620
667
|
hasPipelineErrorRef.current = true;
|
|
@@ -624,10 +671,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
624
671
|
rendererCtor: renderer.constructor?.name,
|
|
625
672
|
error,
|
|
626
673
|
});
|
|
627
|
-
|
|
628
|
-
renderPipelineRef.current.dispose();
|
|
629
|
-
}
|
|
630
|
-
renderPipelineRef.current = null;
|
|
674
|
+
disposePipeline();
|
|
631
675
|
if (retryCountRef.current < MAX_PIPELINE_RETRIES) {
|
|
632
676
|
// Auto-retry: schedule a pipeline rebuild if we haven't exceeded the retry limit
|
|
633
677
|
retryCountRef.current++;
|
|
@@ -640,6 +684,9 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
640
684
|
console.error('[viewer/post-processing] Retries exhausted. Rendering without post FX for this session.');
|
|
641
685
|
}
|
|
642
686
|
}
|
|
687
|
+
finally {
|
|
688
|
+
restoreAtmosphere();
|
|
689
|
+
}
|
|
643
690
|
}, 1);
|
|
644
691
|
return null;
|
|
645
692
|
};
|
|
@@ -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"}
|