@pascal-app/viewer 0.9.1 → 1.0.0-beta.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/README.md +20 -5
- package/dist/components/error-boundary.d.ts +6 -0
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js +6 -0
- package/dist/components/renderers/node-renderer.d.ts +5 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +5 -2
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/parametric-node-renderer.js +7 -4
- package/dist/components/viewer/bvh-ecctrl.d.ts +77 -0
- package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -0
- package/dist/components/viewer/bvh-ecctrl.js +552 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +59 -4
- package/dist/components/viewer/glb-interactive.d.ts +41 -0
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -0
- package/dist/components/viewer/glb-interactive.js +394 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts +29 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts.map +1 -0
- package/dist/components/viewer/glb-reference-nodes.js +80 -0
- package/dist/components/viewer/glb-replace-instances.d.ts +18 -0
- package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -0
- package/dist/components/viewer/glb-replace-instances.js +61 -0
- package/dist/components/viewer/glb-scene.d.ts +54 -0
- package/dist/components/viewer/glb-scene.d.ts.map +1 -0
- package/dist/components/viewer/glb-scene.js +920 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +22 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -0
- package/dist/components/viewer/glb-walkthrough-controller.js +445 -0
- package/dist/components/viewer/index.d.ts +19 -2
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +113 -12
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +36 -5
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +17 -2
- package/dist/components/viewer/post-processing.d.ts +7 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +180 -24
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +6 -4
- package/dist/components/viewer/scene-bvh.d.ts +2 -1
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
- package/dist/components/viewer/scene-bvh.js +3 -0
- package/dist/components/viewer/scene-bvh.test.d.ts +2 -0
- package/dist/components/viewer/scene-bvh.test.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.test.js +13 -0
- package/dist/components/viewer/scene-environment.d.ts +2 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -0
- package/dist/components/viewer/scene-environment.js +79 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +23 -12
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +2 -24
- package/dist/hooks/use-node-events.d.ts +1 -1
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +22 -14
- package/dist/index.d.ts +24 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -13
- package/dist/lib/backdrop.d.ts +33 -0
- package/dist/lib/backdrop.d.ts.map +1 -0
- package/dist/lib/backdrop.js +92 -0
- package/dist/lib/box-uv.d.ts +13 -0
- package/dist/lib/box-uv.d.ts.map +1 -0
- package/dist/lib/box-uv.js +32 -0
- package/dist/lib/csg-utils.d.ts +3 -9
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +65 -5
- package/dist/lib/csg-utils.test.d.ts +2 -0
- package/dist/lib/csg-utils.test.d.ts.map +1 -0
- package/dist/lib/csg-utils.test.js +45 -0
- package/dist/lib/edge-style.d.ts +1 -0
- package/dist/lib/edge-style.d.ts.map +1 -1
- package/dist/lib/edge-style.js +26 -6
- package/dist/lib/hero-pose.d.ts +47 -0
- package/dist/lib/hero-pose.d.ts.map +1 -0
- package/dist/lib/hero-pose.js +227 -0
- package/dist/lib/ink-edges.d.ts +1 -1
- package/dist/lib/ink-edges.d.ts.map +1 -1
- package/dist/lib/ink-edges.js +13 -7
- package/dist/lib/ktx2-loader.d.ts +46 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -0
- package/dist/lib/ktx2-loader.js +142 -0
- package/dist/lib/layers.d.ts +10 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +10 -0
- package/dist/lib/materials.d.ts +16 -1
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +158 -34
- package/dist/lib/materials.test.d.ts +2 -0
- package/dist/lib/materials.test.d.ts.map +1 -0
- package/dist/lib/materials.test.js +25 -0
- package/dist/lib/merged-outline-node.test.d.ts +2 -0
- package/dist/lib/merged-outline-node.test.d.ts.map +1 -0
- package/dist/lib/merged-outline-node.test.js +22 -0
- package/dist/lib/scene-themes.d.ts +1 -0
- package/dist/lib/scene-themes.d.ts.map +1 -1
- package/dist/lib/scene-themes.js +25 -16
- package/dist/lib/shadow-only.d.ts +4 -0
- package/dist/lib/shadow-only.d.ts.map +1 -0
- package/dist/lib/shadow-only.js +36 -0
- package/dist/lib/snapshot-pipeline.d.ts +42 -0
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -0
- package/dist/lib/snapshot-pipeline.js +254 -0
- package/dist/lib/terrain-perimeter-fill.d.ts +7 -0
- package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -0
- package/dist/lib/terrain-perimeter-fill.js +75 -0
- package/dist/lib/texture-node-guard.d.ts +13 -0
- package/dist/lib/texture-node-guard.d.ts.map +1 -0
- package/dist/lib/texture-node-guard.js +39 -0
- package/dist/lib/texture-reference.d.ts +31 -0
- package/dist/lib/texture-reference.d.ts.map +1 -0
- package/dist/lib/texture-reference.js +139 -0
- package/dist/lib/texture-reference.test.d.ts +2 -0
- package/dist/lib/texture-reference.test.d.ts.map +1 -0
- package/dist/lib/texture-reference.test.js +86 -0
- package/dist/lib/tsl-compat.d.ts +12 -0
- package/dist/lib/tsl-compat.d.ts.map +1 -0
- package/dist/lib/tsl-compat.js +13 -0
- package/dist/store/use-viewer.d.ts +73 -18
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +191 -4
- package/dist/store/use-viewer.test.d.ts +2 -0
- package/dist/store/use-viewer.test.d.ts.map +1 -0
- package/dist/store/use-viewer.test.js +61 -0
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +12 -5
- package/dist/systems/door/door-animation.test.d.ts +2 -0
- package/dist/systems/door/door-animation.test.d.ts.map +1 -0
- package/dist/systems/door/door-animation.test.js +51 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts +2 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts.map +1 -0
- package/dist/systems/door/door-floor-alignment.test.js +50 -0
- package/dist/systems/door/door-system.d.ts +24 -0
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +494 -161
- package/dist/systems/fence/fence-system.d.ts +17 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -1
- package/dist/systems/fence/fence-system.js +221 -53
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +30 -7
- package/dist/systems/geometry/geometry-system.d.ts +7 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +95 -26
- package/dist/systems/geometry/geometry-system.test.d.ts +2 -0
- package/dist/systems/geometry/geometry-system.test.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.test.js +30 -0
- package/dist/systems/interactive/control-widget.d.ts +10 -0
- package/dist/systems/interactive/control-widget.d.ts.map +1 -0
- package/dist/systems/interactive/control-widget.js +35 -0
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +55 -56
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +20 -6
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +34 -9
- package/dist/systems/level/level-utils.d.ts +0 -3
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +7 -45
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +29 -8
- package/dist/systems/roof/roof-system.d.ts +21 -8
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1079 -221
- package/dist/systems/slab/slab-system.d.ts +7 -4
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +31 -83
- package/dist/systems/slab/slab-system.test.js +54 -14
- package/dist/systems/stair/stair-system.js +7 -4
- package/dist/systems/wall/opening-cutout-geometry.d.ts +31 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.js +167 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts +2 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.test.js +172 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +49 -17
- package/dist/systems/wall/wall-materials.d.ts +11 -7
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +244 -35
- package/dist/systems/wall/wall-opening-cutout.test.d.ts +2 -0
- package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-opening-cutout.test.js +59 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts +2 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-support-extension.test.js +116 -0
- package/dist/systems/wall/wall-system.d.ts +4 -8
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +397 -177
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts +2 -0
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-terrain-bottom.test.js +34 -0
- package/dist/systems/window/window-animation-system.d.ts +12 -0
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +16 -5
- package/dist/systems/window/window-system.d.ts +7 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +240 -14
- package/package.json +4 -4
- package/dist/components/viewer/ground-occluder.d.ts +0 -2
- package/dist/components/viewer/ground-occluder.d.ts.map +0 -1
- package/dist/components/viewer/ground-occluder.js +0 -67
- package/dist/components/viewer/walkthrough-controls.d.ts +0 -2
- package/dist/components/viewer/walkthrough-controls.d.ts.map +0 -1
- package/dist/components/viewer/walkthrough-controls.js +0 -131
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { useFrame, useThree } from '@react-three/fiber';
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { Color, Layers, UnsignedByteType } from 'three';
|
|
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,
|
|
6
|
+
import { add, diffuseColor, float, mix, mrt, normalView, oscSine, output, pass, premultiplyAlpha, renderOutput, sample, saturation, screenUV, smoothstep, time, uniform, vec3, vec4, } from 'three/tsl';
|
|
7
7
|
import { RenderPipeline } from 'three/webgpu';
|
|
8
|
-
import {
|
|
8
|
+
import { backdropGradient, deepSkyColor, horizonHazeColor } from '../../lib/backdrop';
|
|
9
|
+
import { edgeColorFor, edgeOpacityScaleFor } from '../../lib/edge-style';
|
|
9
10
|
import { PERF_OVERLAY_ENABLED, pushGpuSample } from '../../lib/gpu-perf';
|
|
10
11
|
import { inkedEdges } from '../../lib/ink-edges';
|
|
11
12
|
import { GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, ZONE_LAYER } from '../../lib/layers';
|
|
12
13
|
import { mergedOutline } from '../../lib/merged-outline-node';
|
|
13
14
|
import { getSceneTheme } from '../../lib/scene-themes';
|
|
15
|
+
import { packNormalToRGB, unpackRGBToNormal } from '../../lib/tsl-compat';
|
|
14
16
|
import useViewer from '../../store/use-viewer';
|
|
17
|
+
// Scene-referred grade applied before the output tone mapping (AgX). AgX rolls
|
|
18
|
+
// highlights off gently but reads flat on its own; a mild mid-gray-pivot
|
|
19
|
+
// contrast + saturation lift restores the punch. Rendered shading only.
|
|
20
|
+
export const GRADE_PARAMS = {
|
|
21
|
+
contrast: 1.05,
|
|
22
|
+
saturation: 1.1,
|
|
23
|
+
};
|
|
15
24
|
// SSGI Parameters - adjust these to fine-tune global illumination and ambient occlusion
|
|
16
25
|
export const SSGI_PARAMS = {
|
|
17
26
|
enabled: true,
|
|
@@ -37,6 +46,11 @@ export const SSGI_PARAMS = {
|
|
|
37
46
|
// - outline: skip the merged-outline node and its 14 internal RTs
|
|
38
47
|
// - postFx: bypass the whole RenderPipeline and use renderer.render(scene, camera)
|
|
39
48
|
// directly — isolates raw scene-render cost from any post-FX overhead
|
|
49
|
+
// - draw: skip the render call entirely — frames still tick (useFrame
|
|
50
|
+
// systems, scene-ready) but no draw is ever submitted. For
|
|
51
|
+
// consumers that only need the built scene graph, never pixels:
|
|
52
|
+
// the headless bake worker renders on SwiftShader (CPU), where
|
|
53
|
+
// per-frame vertex/draw cost dominates the whole capture.
|
|
40
54
|
function readPerfDisableFlags() {
|
|
41
55
|
if (typeof window === 'undefined') {
|
|
42
56
|
return { ao: false, denoise: false, outline: false, postFx: false };
|
|
@@ -57,6 +71,12 @@ const PERF_POST_FX_DISABLED = typeof window !== 'undefined' &&
|
|
|
57
71
|
new Set((new URLSearchParams(window.location.search).get('disable') ?? '')
|
|
58
72
|
.split(',')
|
|
59
73
|
.map((s) => s.trim())).has('postFx');
|
|
74
|
+
const PERF_DRAW_DISABLED = typeof window !== 'undefined' &&
|
|
75
|
+
new Set((new URLSearchParams(window.location.search).get('disable') ?? '')
|
|
76
|
+
.split(',')
|
|
77
|
+
.map((s) => s.trim())).has('draw');
|
|
78
|
+
// Stand-in scene for `?disable=draw` frames — cleared, never populated.
|
|
79
|
+
const emptyScene = new Scene();
|
|
60
80
|
const MAX_PIPELINE_RETRIES = 3;
|
|
61
81
|
const RETRY_DELAY_MS = 500;
|
|
62
82
|
const DEFAULT_HOVER_STYLE = {
|
|
@@ -79,7 +99,7 @@ function sanitizeOutlineObjects(objects) {
|
|
|
79
99
|
}
|
|
80
100
|
objects.length = nextIndex;
|
|
81
101
|
}
|
|
82
|
-
const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
102
|
+
const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostFx = false, }) => {
|
|
83
103
|
const { gl: renderer, invalidate, scene, camera, size } = useThree();
|
|
84
104
|
const renderPipelineRef = useRef(null);
|
|
85
105
|
const hasPipelineErrorRef = useRef(false);
|
|
@@ -88,13 +108,35 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
88
108
|
const skippedZeroSizeRef = useRef(false);
|
|
89
109
|
// Background color uniform — updated every frame via lerp, read by the TSL pipeline.
|
|
90
110
|
// Initialised from the current scene theme so there's no flash on first render.
|
|
91
|
-
const
|
|
111
|
+
const initTheme = getSceneTheme(useViewer.getState().sceneTheme);
|
|
112
|
+
const initBg = initTheme.background;
|
|
92
113
|
const bgUniform = useRef(uniform(new Color(initBg)));
|
|
93
114
|
const bgCurrent = useRef(new Color(initBg));
|
|
94
115
|
const bgTarget = useRef(new Color());
|
|
116
|
+
// Zenith colour of the backdrop gradient (falls back to the flat background).
|
|
117
|
+
const initSky = initTheme.backgroundSky ?? initBg;
|
|
118
|
+
const bgSkyUniform = useRef(uniform(new Color(initSky)));
|
|
119
|
+
const bgSkyCurrent = useRef(new Color(initSky));
|
|
120
|
+
const bgSkyTarget = useRef(new Color());
|
|
121
|
+
// Horizon haze band + deep zenith (derived — see lib/backdrop.ts).
|
|
122
|
+
const initHaze = horizonHazeColor(initSky, initTheme.appearance);
|
|
123
|
+
const bgHazeUniform = useRef(uniform(new Color(initHaze)));
|
|
124
|
+
const bgHazeCurrent = useRef(new Color(initHaze));
|
|
125
|
+
const bgHazeTarget = useRef(new Color());
|
|
126
|
+
const initSkyDeep = deepSkyColor(initSky);
|
|
127
|
+
const bgSkyDeepUniform = useRef(uniform(new Color(initSkyDeep)));
|
|
128
|
+
const bgSkyDeepCurrent = useRef(new Color(initSkyDeep));
|
|
129
|
+
const bgSkyDeepTarget = useRef(new Color());
|
|
130
|
+
// Scene-camera matrices for the backdrop: the pipeline's fullscreen quad has
|
|
131
|
+
// its own camera, so the sky gradient reconstructs each pixel's world-space
|
|
132
|
+
// view ray from these to find the true horizon (dir.y = 0).
|
|
133
|
+
const camProjInvUniform = useRef(uniform(new Matrix4()));
|
|
134
|
+
const camWorldUniform = useRef(uniform(new Matrix4()));
|
|
95
135
|
// Ink-line colour follows the scene-theme background luminance (dark lines on
|
|
96
136
|
// light scenes, light on dark), refreshed each frame like the background.
|
|
137
|
+
// Dark scenes also scale the ink opacity down (see edge-style.ts).
|
|
97
138
|
const inkColorUniform = useRef(uniform(new Color(edgeColorFor(initBg))));
|
|
139
|
+
const inkOpacityScaleUniform = useRef(uniform(edgeOpacityScaleFor(initBg)));
|
|
98
140
|
const zoneLayers = useMemo(() => {
|
|
99
141
|
const l = new Layers();
|
|
100
142
|
l.enable(ZONE_LAYER);
|
|
@@ -129,6 +171,8 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
129
171
|
const projectId = useViewer((s) => s.projectId);
|
|
130
172
|
const shading = useViewer((s) => s.shading);
|
|
131
173
|
const edges = useViewer((s) => s.edges);
|
|
174
|
+
const inkOpacityOverride = useViewer((s) => s.inkOpacity);
|
|
175
|
+
const transparentBackground = useViewer((s) => s.transparentBackground);
|
|
132
176
|
const lastProjectIdRef = useRef(projectId);
|
|
133
177
|
// Bump this to force a pipeline rebuild (used by retry logic)
|
|
134
178
|
const [pipelineVersion, setPipelineVersion] = useState(0);
|
|
@@ -199,6 +243,18 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
199
243
|
skippedZeroSizeRef.current = false;
|
|
200
244
|
}
|
|
201
245
|
const perfDisable = readPerfDisableFlags();
|
|
246
|
+
// postFx off (host prop or ?disable=postFx): never allocate the pipeline —
|
|
247
|
+
// useFrame's null-pipeline branch direct-renders. Before this check the
|
|
248
|
+
// URL flag only skipped the pipeline at render time; the build still
|
|
249
|
+
// allocated every pass.
|
|
250
|
+
if (disablePostFx || perfDisable.postFx) {
|
|
251
|
+
hasPipelineErrorRef.current = false;
|
|
252
|
+
if (renderPipelineRef.current) {
|
|
253
|
+
renderPipelineRef.current.dispose();
|
|
254
|
+
}
|
|
255
|
+
renderPipelineRef.current = null;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
202
258
|
const ssgiEnabled = shading === 'rendered' && SSGI_PARAMS.enabled && !perfDisable.ao;
|
|
203
259
|
const denoiseEnabled = ssgiEnabled && !perfDisable.denoise;
|
|
204
260
|
const outlineEnabled = !perfDisable.outline;
|
|
@@ -211,16 +267,16 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
211
267
|
// Same 1px line thickness for both (soft's thickness is the nice one);
|
|
212
268
|
// strong reads heavier purely by being fully solid vs soft's lighter 50%.
|
|
213
269
|
const inkRadius = 1;
|
|
214
|
-
const inkOpacity = edges === 'strong' ? 1 : 0.5;
|
|
270
|
+
const inkOpacity = inkOpacityOverride ?? (edges === 'strong' ? 1 : 0.5);
|
|
215
271
|
console.log('[viewer/post-processing] Building pipeline', {
|
|
216
272
|
version: pipelineVersion,
|
|
217
273
|
ssgi: ssgiEnabled,
|
|
218
274
|
denoise: denoiseEnabled,
|
|
219
275
|
outline: outlineEnabled,
|
|
220
276
|
perfDisable,
|
|
221
|
-
hoverHighlightMode,
|
|
222
277
|
projectId,
|
|
223
278
|
shading,
|
|
279
|
+
transparentBackground,
|
|
224
280
|
rendererCtor: renderer.constructor?.name,
|
|
225
281
|
width,
|
|
226
282
|
height,
|
|
@@ -265,7 +321,10 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
265
321
|
// is the reliable geometry mask — no normals, no flicker.
|
|
266
322
|
const hasGeometry = scenePassColor.a;
|
|
267
323
|
const contentAlpha = hasGeometry.max(zonePass.a);
|
|
268
|
-
|
|
324
|
+
// Composite the zone-pass tint into the base scene so rooms show whether or
|
|
325
|
+
// not SSGI is enabled. When SSGI is on, the branch below overwrites this
|
|
326
|
+
// with its own zone-inclusive composite (no double-add).
|
|
327
|
+
let sceneColor = vec4(add(scenePassColor.rgb, zonePass.rgb), contentAlpha);
|
|
269
328
|
// Depth + normal MRT — shared by SSGI (diffuse/normal) and the ink pass
|
|
270
329
|
// (depth/normal). Built whenever either is active.
|
|
271
330
|
let scenePassDepth = null;
|
|
@@ -275,14 +334,14 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
275
334
|
scenePass.setMRT(mrt({
|
|
276
335
|
output,
|
|
277
336
|
diffuseColor,
|
|
278
|
-
normal:
|
|
337
|
+
normal: packNormalToRGB(normalView),
|
|
279
338
|
}));
|
|
280
339
|
scenePassDepth = scenePass.getTextureNode('depth');
|
|
281
340
|
scenePassNormal = scenePass.getTextureNode('normal');
|
|
282
341
|
const normalTexture = scenePass.getTexture('normal');
|
|
283
342
|
normalTexture.type = UnsignedByteType;
|
|
284
343
|
// Extract normal from color-encoded texture (SSGI consumes the node form)
|
|
285
|
-
sceneNormal = sample((uv) =>
|
|
344
|
+
sceneNormal = sample((uv) => unpackRGBToNormal(scenePassNormal.sample(uv)));
|
|
286
345
|
}
|
|
287
346
|
if (ssgiEnabled) {
|
|
288
347
|
const scenePassDiffuse = scenePass.getTextureNode('diffuseColor');
|
|
@@ -300,13 +359,15 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
300
359
|
giPass.useLinearThickness.value = SSGI_PARAMS.useLinearThickness;
|
|
301
360
|
giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling;
|
|
302
361
|
giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering;
|
|
303
|
-
|
|
304
|
-
|
|
362
|
+
// r185: SSGI renders AO and GI into two separate textures (R8 + RG11B10)
|
|
363
|
+
// exposed via getAONode()/getGINode() instead of one rgba texture.
|
|
364
|
+
const aoTexture = giPass.getAONode();
|
|
365
|
+
const gi = giPass.getGINode().rgb;
|
|
305
366
|
let ao;
|
|
306
367
|
if (denoiseEnabled) {
|
|
307
368
|
// DenoiseNode only denoises RGB — alpha is passed through unchanged.
|
|
308
|
-
// SSGI
|
|
309
|
-
const aoAsRgb = vec4(
|
|
369
|
+
// 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, float(1));
|
|
310
371
|
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
|
|
311
372
|
denoisePass.index.value = 0;
|
|
312
373
|
denoisePass.radius.value = 4;
|
|
@@ -315,8 +376,14 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
315
376
|
else {
|
|
316
377
|
// Diagnostic path: feed raw noisy SSGI AO straight through. Will
|
|
317
378
|
// look grainy — that's the point, it isolates denoise cost.
|
|
318
|
-
ao =
|
|
379
|
+
ao = aoTexture.r;
|
|
319
380
|
}
|
|
381
|
+
// AO is a near/mid-field cue like the ink: fade it out with raw depth
|
|
382
|
+
// (same ≈150→350 m window as ink-edges' distanceFade) so the horizon
|
|
383
|
+
// disc and the geometry↔sky depth cliff never grow an AO band — that
|
|
384
|
+
// band read as a visible line along the horizon.
|
|
385
|
+
const aoFarFade = smoothstep(float(0.9994), float(0.9998), scenePassDepth.sample(screenUV).r);
|
|
386
|
+
ao = mix(ao, float(1), aoFarFade);
|
|
320
387
|
// Composite: scene * AO + diffuse * GI
|
|
321
388
|
sceneColor = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))), contentAlpha);
|
|
322
389
|
}
|
|
@@ -330,12 +397,22 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
330
397
|
normalTex: scenePassNormal,
|
|
331
398
|
inkColor: inkColorUniform.current,
|
|
332
399
|
radius: inkRadius,
|
|
333
|
-
opacity: inkOpacity,
|
|
400
|
+
opacity: float(inkOpacity).mul(inkOpacityScaleUniform.current),
|
|
334
401
|
}), sceneColor.a);
|
|
335
402
|
}
|
|
403
|
+
// Scene-referred grade (contrast around mid-gray + saturation) before the
|
|
404
|
+
// pipeline's output tone mapping. Kept out of solid/schematic shading so
|
|
405
|
+
// the flat presets stay exact. The same transform is applied to the
|
|
406
|
+
// backdrop below so geometry that fades to the background colour (the
|
|
407
|
+
// horizon disc) matches it exactly.
|
|
408
|
+
const gradeRgb = (rgb) => saturation(rgb.div(0.18).pow(vec3(GRADE_PARAMS.contrast)).mul(0.18), GRADE_PARAMS.saturation);
|
|
409
|
+
if (shading === 'rendered') {
|
|
410
|
+
sceneColor = vec4(gradeRgb(sceneColor.rgb), sceneColor.a);
|
|
411
|
+
}
|
|
336
412
|
// Single merged outline node: one shared depth pass for both selected + hovered groups.
|
|
337
413
|
const outliner = useViewer.getState().outliner;
|
|
338
414
|
let compositeWithOutlines = sceneColor;
|
|
415
|
+
let visualAlpha = contentAlpha;
|
|
339
416
|
if (outlineEnabled) {
|
|
340
417
|
const outlineNode = mergedOutline(scene, camera, {
|
|
341
418
|
primaryObjects: outliner.selectedObjects,
|
|
@@ -360,14 +437,46 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
360
437
|
.add(outlineNode.secondaryHiddenEdge.mul(hoverHiddenColor))
|
|
361
438
|
.mul(hoverStrength)
|
|
362
439
|
.mul(osc);
|
|
440
|
+
const outlineAlpha = outlineNode.primaryVisibleEdge
|
|
441
|
+
.max(outlineNode.primaryHiddenEdge)
|
|
442
|
+
.max(outlineNode.secondaryVisibleEdge)
|
|
443
|
+
.max(outlineNode.secondaryHiddenEdge);
|
|
444
|
+
visualAlpha = visualAlpha.max(outlineAlpha);
|
|
363
445
|
compositeWithOutlines = vec4(add(sceneColor.rgb, selectedOutline.add(hoverOutline)), sceneColor.a);
|
|
364
446
|
}
|
|
365
|
-
|
|
447
|
+
// Backdrop: world-space view ray per pixel → background / horizon haze /
|
|
448
|
+
// sky gradient (shared formula in lib/backdrop.ts). The horizon disc
|
|
449
|
+
// dissolves into the same formula, so backdrop and ground meet
|
|
450
|
+
// seamlessly exactly where the disc vanishes.
|
|
451
|
+
const ndc = vec4(screenUV.x.mul(2).sub(1), float(1).sub(screenUV.y).mul(2).sub(1), 1, 1);
|
|
452
|
+
const viewRay = camProjInvUniform.current.mul(ndc);
|
|
453
|
+
const worldDir = camWorldUniform.current.mul(vec4(viewRay.xyz, 0)).xyz.normalize();
|
|
454
|
+
let bgGradient = backdropGradient({
|
|
455
|
+
dirY: worldDir.y,
|
|
456
|
+
background: bgUniform.current,
|
|
457
|
+
haze: bgHazeUniform.current,
|
|
458
|
+
sky: bgSkyUniform.current,
|
|
459
|
+
skyDeep: bgSkyDeepUniform.current,
|
|
460
|
+
});
|
|
461
|
+
if (shading === 'rendered') {
|
|
462
|
+
bgGradient = gradeRgb(bgGradient);
|
|
463
|
+
}
|
|
464
|
+
const composited = mix(bgGradient, compositeWithOutlines.rgb, contentAlpha);
|
|
366
465
|
// Editor overlays painted on top by their own alpha — they never get inked,
|
|
367
466
|
// AO'd, or outlined, and always read crisp regardless of scene depth.
|
|
368
467
|
const withOverlay = mix(composited, overlayColor.rgb, overlayColor.a);
|
|
369
|
-
|
|
468
|
+
let finalOutput = vec4(withOverlay, float(1));
|
|
469
|
+
if (transparentBackground) {
|
|
470
|
+
const overlayAlpha = overlayColor.a;
|
|
471
|
+
const alpha = overlayAlpha.add(visualAlpha.mul(overlayAlpha.oneMinus()));
|
|
472
|
+
const straightRgb = overlayColor.rgb
|
|
473
|
+
.mul(overlayAlpha)
|
|
474
|
+
.add(compositeWithOutlines.rgb.mul(visualAlpha).mul(overlayAlpha.oneMinus()))
|
|
475
|
+
.div(alpha.max(float(0.00001)));
|
|
476
|
+
finalOutput = premultiplyAlpha(renderOutput(vec4(straightRgb, alpha)));
|
|
477
|
+
}
|
|
370
478
|
const renderPipeline = new RenderPipeline(renderer);
|
|
479
|
+
renderPipeline.outputColorTransform = !transparentBackground;
|
|
371
480
|
renderPipeline.outputNode = finalOutput;
|
|
372
481
|
renderPipelineRef.current = renderPipeline;
|
|
373
482
|
retryCountRef.current = 0;
|
|
@@ -391,18 +500,24 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
391
500
|
renderPipelineRef.current = null;
|
|
392
501
|
};
|
|
393
502
|
}, [
|
|
503
|
+
// NOTE: hoverHighlightMode intentionally excluded — the hover style is
|
|
504
|
+
// pushed to uniforms in a separate effect, so a hover must NOT rebuild the
|
|
505
|
+
// whole pipeline. The uniform refs below are stable (useMemo), so they
|
|
506
|
+
// never trigger a rebuild either.
|
|
394
507
|
camera,
|
|
508
|
+
disablePostFx,
|
|
395
509
|
hoverHiddenColor,
|
|
396
|
-
hoverHighlightMode,
|
|
397
510
|
hoverPulseMix,
|
|
398
511
|
hoverStrength,
|
|
399
512
|
hoverVisibleColor,
|
|
400
513
|
edges,
|
|
514
|
+
inkOpacityOverride,
|
|
401
515
|
pipelineVersion,
|
|
402
516
|
projectId,
|
|
403
517
|
renderer,
|
|
404
518
|
scene,
|
|
405
519
|
shading,
|
|
520
|
+
transparentBackground,
|
|
406
521
|
size.height,
|
|
407
522
|
size.width,
|
|
408
523
|
zoneLayers,
|
|
@@ -413,20 +528,60 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
413
528
|
if (size.width < 1 || size.height < 1) {
|
|
414
529
|
return;
|
|
415
530
|
}
|
|
531
|
+
// `?disable=draw`: nothing downstream wants pixels — render an EMPTY scene
|
|
532
|
+
// instead of the real one. This is the only render call (positive-priority
|
|
533
|
+
// useFrame subscribers already disable R3F's automatic render), so the real
|
|
534
|
+
// scene is never drawn: per-frame vertex/draw cost drops to a single 64×64
|
|
535
|
+
// clear, which is what makes headless bakes viable on SwiftShader (CPU).
|
|
536
|
+
// Rendering nothing at all is NOT an option — with zero submitted frames
|
|
537
|
+
// Chromium's no-damage scheduler throttles rAF to 1Hz (measured), stalling
|
|
538
|
+
// the useFrame systems the bake still needs.
|
|
539
|
+
if (PERF_DRAW_DISABLED) {
|
|
540
|
+
try {
|
|
541
|
+
;
|
|
542
|
+
renderer.render(emptyScene, camera);
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
// A failed empty draw changes nothing — systems keep ticking.
|
|
546
|
+
}
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
416
549
|
// Animate background colour toward the current scene theme target (same lerp as AnimatedBackground)
|
|
417
|
-
|
|
550
|
+
const bgTheme = getSceneTheme(useViewer.getState().sceneTheme);
|
|
551
|
+
bgTarget.current.set(bgTheme.background);
|
|
418
552
|
bgCurrent.current.lerp(bgTarget.current, Math.min(delta, 0.1) * 4);
|
|
419
553
|
bgUniform.current.value.copy(bgCurrent.current);
|
|
554
|
+
bgSkyTarget.current.set(bgTheme.backgroundSky ?? bgTheme.background);
|
|
555
|
+
bgSkyCurrent.current.lerp(bgSkyTarget.current, Math.min(delta, 0.1) * 4);
|
|
556
|
+
bgSkyUniform.current.value.copy(bgSkyCurrent.current);
|
|
557
|
+
bgHazeTarget.current.set(horizonHazeColor(bgTheme.backgroundSky ?? bgTheme.background, bgTheme.appearance));
|
|
558
|
+
bgHazeCurrent.current.lerp(bgHazeTarget.current, Math.min(delta, 0.1) * 4);
|
|
559
|
+
bgHazeUniform.current.value.copy(bgHazeCurrent.current);
|
|
560
|
+
bgSkyDeepTarget.current.set(deepSkyColor(bgTheme.backgroundSky ?? bgTheme.background));
|
|
561
|
+
bgSkyDeepCurrent.current.lerp(bgSkyDeepTarget.current, Math.min(delta, 0.1) * 4);
|
|
562
|
+
bgSkyDeepUniform.current.value.copy(bgSkyDeepCurrent.current);
|
|
563
|
+
camProjInvUniform.current.value.copy(camera.projectionMatrixInverse);
|
|
564
|
+
camWorldUniform.current.value.copy(camera.matrixWorld);
|
|
420
565
|
// Ink colour follows the (lerping) background luminance — snaps dark↔light.
|
|
421
|
-
|
|
566
|
+
const bgHex = `#${bgCurrent.current.getHexString()}`;
|
|
567
|
+
inkColorUniform.current.value.set(edgeColorFor(bgHex));
|
|
568
|
+
inkOpacityScaleUniform.current.value = edgeOpacityScaleFor(bgHex);
|
|
422
569
|
const outliner = useViewer.getState().outliner;
|
|
423
570
|
sanitizeOutlineObjects(outliner.selectedObjects);
|
|
424
571
|
sanitizeOutlineObjects(outliner.hoveredObjects);
|
|
425
|
-
if (
|
|
572
|
+
if (disablePostFx ||
|
|
573
|
+
PERF_POST_FX_DISABLED ||
|
|
574
|
+
hasPipelineErrorRef.current ||
|
|
575
|
+
!renderPipelineRef.current) {
|
|
426
576
|
try {
|
|
427
|
-
|
|
577
|
+
const clearAlpha = transparentBackground ? 0 : 1;
|
|
578
|
+
if (renderer.setClearColor) {
|
|
579
|
+
;
|
|
580
|
+
renderer.setClearColor(bgCurrent.current, clearAlpha);
|
|
581
|
+
}
|
|
582
|
+
else if (renderer.setClearAlpha) {
|
|
428
583
|
;
|
|
429
|
-
renderer.setClearAlpha(
|
|
584
|
+
renderer.setClearAlpha(clearAlpha);
|
|
430
585
|
}
|
|
431
586
|
const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
|
|
432
587
|
renderer.render(scene, camera);
|
|
@@ -463,6 +618,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
463
618
|
}
|
|
464
619
|
catch (error) {
|
|
465
620
|
hasPipelineErrorRef.current = true;
|
|
621
|
+
renderer.setRenderTarget?.(null);
|
|
466
622
|
console.error('[viewer/post-processing] Render pass failed.', {
|
|
467
623
|
retryCount: retryCountRef.current,
|
|
468
624
|
rendererCtor: renderer.constructor?.name,
|
|
@@ -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":"AA8BA;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,uCAyBhC"}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { nodeRegistry } from '@pascal-app/core';
|
|
3
|
+
import { createSceneApi, isNodeKindEnabled, nodeRegistry, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { lazy, Suspense, useMemo } from 'react';
|
|
5
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
6
|
const lazyCache = new WeakMap();
|
|
9
7
|
function loadSystem(def) {
|
|
10
8
|
if (!def.system)
|
|
@@ -27,6 +25,8 @@ function loadSystem(def) {
|
|
|
27
25
|
* guard added to each legacy system.
|
|
28
26
|
*/
|
|
29
27
|
export function RegisteredSystems() {
|
|
28
|
+
const sceneApi = useMemo(() => createSceneApi(useScene), []);
|
|
29
|
+
const installedPlugins = useScene((state) => state.installedPlugins);
|
|
30
30
|
const entries = useMemo(() => {
|
|
31
31
|
return Array.from(nodeRegistry.entries())
|
|
32
32
|
.filter(([, def]) => def.system != null)
|
|
@@ -39,9 +39,11 @@ export function RegisteredSystems() {
|
|
|
39
39
|
if (entries.length === 0)
|
|
40
40
|
return null;
|
|
41
41
|
return (_jsx(Suspense, { fallback: null, children: entries.map(([kind, def]) => {
|
|
42
|
+
if (!isNodeKindEnabled(kind, installedPlugins))
|
|
43
|
+
return null;
|
|
42
44
|
const Comp = loadSystem(def);
|
|
43
45
|
if (!Comp)
|
|
44
46
|
return null;
|
|
45
|
-
return _jsx(Comp, {}, `registered-system:${kind}`);
|
|
47
|
+
return _jsx(Comp, { sceneApi: sceneApi }, `registered-system:${kind}`);
|
|
46
48
|
}) }));
|
|
47
49
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import { type Group } from 'three';
|
|
2
|
+
import { type Group, Mesh } from 'three';
|
|
3
3
|
import { type SplitStrategy } from 'three-mesh-bvh';
|
|
4
4
|
type SceneBvhProps = {
|
|
5
5
|
children?: ReactNode;
|
|
@@ -12,6 +12,7 @@ type SceneBvhProps = {
|
|
|
12
12
|
maxLeafSize?: number;
|
|
13
13
|
indirect?: boolean;
|
|
14
14
|
};
|
|
15
|
+
export declare const isSceneBvhExcluded: (object: Mesh) => boolean;
|
|
15
16
|
export declare const SceneBvh: import("react").ForwardRefExoticComponent<SceneBvhProps & import("react").RefAttributes<Group<import("three").Object3DEventMap>>>;
|
|
16
17
|
export {};
|
|
17
18
|
//# sourceMappingURL=scene-bvh.d.ts.map
|
|
@@ -1 +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,
|
|
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,EAAE,IAAI,EAAE,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;AAKD,eAAO,MAAM,kBAAkB,GAAI,QAAQ,IAAI,YAA4C,CAAA;AAY3F,eAAO,MAAM,QAAQ,mIAoGpB,CAAA"}
|
|
@@ -4,6 +4,7 @@ import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
|
|
|
4
4
|
import { Mesh } from 'three';
|
|
5
5
|
import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree, SAH, } from 'three-mesh-bvh';
|
|
6
6
|
const isMesh = (object) => !!object && typeof object === 'object' && object.isMesh === true;
|
|
7
|
+
export const isSceneBvhExcluded = (object) => object.userData.excludeFromBvh === true;
|
|
7
8
|
const hasBvhCompatibleGeometry = (geometry) => {
|
|
8
9
|
if (!geometry)
|
|
9
10
|
return false;
|
|
@@ -35,6 +36,8 @@ export const SceneBvh = forwardRef(({ children, enabled = true, firstHitOnly = f
|
|
|
35
36
|
group.traverse((child) => {
|
|
36
37
|
if (!isMesh(child))
|
|
37
38
|
return;
|
|
39
|
+
if (isSceneBvhExcluded(child))
|
|
40
|
+
return;
|
|
38
41
|
if (child.raycast === Mesh.prototype.raycast) {
|
|
39
42
|
child.raycast = acceleratedRaycast;
|
|
40
43
|
acceleratedMeshes.add(child);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-bvh.test.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/scene-bvh.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
|
|
2
|
+
// include Bun ambient types in its production declaration build.
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
import { Mesh } from 'three';
|
|
5
|
+
import { isSceneBvhExcluded } from './scene-bvh';
|
|
6
|
+
describe('scene BVH exclusions', () => {
|
|
7
|
+
test('keeps annotation meshes on their normal raycast path', () => {
|
|
8
|
+
expect(isSceneBvhExcluded(new Mesh())).toBe(false);
|
|
9
|
+
const annotation = new Mesh();
|
|
10
|
+
annotation.userData.excludeFromBvh = true;
|
|
11
|
+
expect(isSceneBvhExcluded(annotation)).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-environment.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/scene-environment.tsx"],"names":[],"mappings":"AAmEA,wBAAgB,gBAAgB,SAkB/B"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useThree } from '@react-three/fiber';
|
|
3
|
+
import { useEffect, useMemo } from 'react';
|
|
4
|
+
import * as THREE from 'three/webgpu';
|
|
5
|
+
import { getSceneTheme } from '../../lib/scene-themes';
|
|
6
|
+
import useViewer from '../../store/use-viewer';
|
|
7
|
+
/**
|
|
8
|
+
* Scene IBL — a small procedural gradient sky (cool zenith → warm horizon →
|
|
9
|
+
* dim ground bounce) used purely as the environment light source. The visible
|
|
10
|
+
* backdrop stays the flat theme background (composited in post-processing);
|
|
11
|
+
* this texture is never shown. Replaces the old `venice_sunset_1k.hdr` fetch:
|
|
12
|
+
* no network dependency, and the vertical color split means upward-facing
|
|
13
|
+
* surfaces read cooler than vertical ones instead of everything getting the
|
|
14
|
+
* same directionless warm wash. Exported as an opt-in <Viewer> child so
|
|
15
|
+
* embed / thumbnail surfaces that don't want IBL simply don't mount it.
|
|
16
|
+
* Only affects `rendered` shading (Lambert ignores env maps).
|
|
17
|
+
*/
|
|
18
|
+
// Linear-space gradient stops.
|
|
19
|
+
const ZENITH = [0.4, 0.56, 0.78];
|
|
20
|
+
const HORIZON = [0.95, 0.84, 0.66];
|
|
21
|
+
const GROUND = [0.38, 0.35, 0.3];
|
|
22
|
+
const ENV_INTENSITY = 0.6;
|
|
23
|
+
// The gradient sky is a daylight source; dark themes only want a whisper of it.
|
|
24
|
+
const ENV_INTENSITY_DARK = 0.2;
|
|
25
|
+
const WIDTH = 64;
|
|
26
|
+
const HEIGHT = 32;
|
|
27
|
+
function buildGradientSky() {
|
|
28
|
+
const data = new Float32Array(WIDTH * HEIGHT * 4);
|
|
29
|
+
for (let y = 0; y < HEIGHT; y++) {
|
|
30
|
+
// Row 0 = v0 = nadir, top row = zenith (equirect v spans -90°..+90°).
|
|
31
|
+
const lat = ((y + 0.5) / HEIGHT) * 2 - 1;
|
|
32
|
+
let r;
|
|
33
|
+
let g;
|
|
34
|
+
let b;
|
|
35
|
+
if (lat <= 0) {
|
|
36
|
+
// Below the horizon: flat ground bounce, slightly darker toward nadir.
|
|
37
|
+
const k = 1 + lat * 0.35;
|
|
38
|
+
r = GROUND[0] * k;
|
|
39
|
+
g = GROUND[1] * k;
|
|
40
|
+
b = GROUND[2] * k;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// pow < 1 widens the warm horizon band.
|
|
44
|
+
const t = lat ** 0.65;
|
|
45
|
+
r = HORIZON[0] + (ZENITH[0] - HORIZON[0]) * t;
|
|
46
|
+
g = HORIZON[1] + (ZENITH[1] - HORIZON[1]) * t;
|
|
47
|
+
b = HORIZON[2] + (ZENITH[2] - HORIZON[2]) * t;
|
|
48
|
+
}
|
|
49
|
+
for (let x = 0; x < WIDTH; x++) {
|
|
50
|
+
const i = (y * WIDTH + x) * 4;
|
|
51
|
+
data[i] = r;
|
|
52
|
+
data[i + 1] = g;
|
|
53
|
+
data[i + 2] = b;
|
|
54
|
+
data[i + 3] = 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const texture = new THREE.DataTexture(data, WIDTH, HEIGHT, THREE.RGBAFormat, THREE.FloatType);
|
|
58
|
+
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
59
|
+
texture.colorSpace = THREE.LinearSRGBColorSpace;
|
|
60
|
+
texture.needsUpdate = true;
|
|
61
|
+
return texture;
|
|
62
|
+
}
|
|
63
|
+
export function SceneEnvironment() {
|
|
64
|
+
const scene = useThree((state) => state.scene);
|
|
65
|
+
const texture = useMemo(buildGradientSky, []);
|
|
66
|
+
const appearance = useViewer((state) => getSceneTheme(state.sceneTheme).appearance);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const prevEnvironment = scene.environment;
|
|
69
|
+
const prevIntensity = scene.environmentIntensity;
|
|
70
|
+
scene.environment = texture;
|
|
71
|
+
scene.environmentIntensity = appearance === 'dark' ? ENV_INTENSITY_DARK : ENV_INTENSITY;
|
|
72
|
+
return () => {
|
|
73
|
+
scene.environment = prevEnvironment;
|
|
74
|
+
scene.environmentIntensity = prevIntensity;
|
|
75
|
+
texture.dispose();
|
|
76
|
+
};
|
|
77
|
+
}, [scene, texture, appearance]);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
@@ -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":"AAiTA,eAAO,MAAM,gBAAgB,mCAqG5B,CAAA"}
|