@pascal-app/viewer 0.8.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/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 +6 -2
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +41 -22
- package/dist/components/renderers/parametric-node-renderer.d.ts +5 -0
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -0
- package/dist/components/renderers/parametric-node-renderer.js +37 -0
- package/dist/components/renderers/scene-renderer.d.ts +1 -1
- package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
- package/dist/components/viewer/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 +39 -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 +12 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -0
- package/dist/components/viewer/glb-walkthrough-controller.js +357 -0
- package/dist/components/viewer/index.d.ts +56 -3
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +280 -65
- package/dist/components/viewer/lights.d.ts +1 -1
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +201 -43
- package/dist/components/viewer/perf-monitor.d.ts +1 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +115 -12
- 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 +395 -63
- package/dist/components/viewer/registered-systems.d.ts +12 -0
- package/dist/components/viewer/registered-systems.d.ts.map +1 -0
- package/dist/components/viewer/registered-systems.js +49 -0
- package/dist/components/viewer/scene-bvh.d.ts +18 -0
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.js +97 -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 +1 -1
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +37 -14
- package/dist/components/viewer/viewer-camera.d.ts +1 -1
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/walkthrough-controls.d.ts +2 -1
- package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
- package/dist/components/viewer/walkthrough-controls.js +20 -0
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +3 -22
- package/dist/hooks/use-node-events.d.ts +6 -74
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +28 -8
- package/dist/index.d.ts +48 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -4
- 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 +10 -0
- package/dist/lib/csg-utils.d.ts.map +1 -0
- package/dist/lib/csg-utils.js +93 -0
- 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/drawable-geometry.d.ts +21 -0
- package/dist/lib/drawable-geometry.d.ts.map +1 -0
- package/dist/lib/drawable-geometry.js +22 -0
- package/dist/lib/edge-style.d.ts +4 -0
- package/dist/lib/edge-style.d.ts.map +1 -0
- package/dist/lib/edge-style.js +31 -0
- package/dist/lib/gpu-perf.d.ts +4 -0
- package/dist/lib/gpu-perf.d.ts.map +1 -0
- package/dist/lib/gpu-perf.js +23 -0
- package/dist/lib/ink-edges.d.ts +9 -0
- package/dist/lib/ink-edges.d.ts.map +1 -0
- package/dist/lib/ink-edges.js +49 -0
- package/dist/lib/isolation.d.ts +39 -0
- package/dist/lib/isolation.d.ts.map +1 -0
- package/dist/lib/isolation.js +103 -0
- package/dist/lib/ktx2-loader.d.ts +43 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -0
- package/dist/lib/ktx2-loader.js +136 -0
- package/dist/lib/layers.d.ts +28 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +28 -0
- package/dist/lib/materials.d.ts +38 -15
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +364 -72
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +7 -0
- package/dist/lib/polygon-union.d.ts +4 -0
- package/dist/lib/polygon-union.d.ts.map +1 -0
- package/dist/lib/polygon-union.js +289 -0
- package/dist/lib/polygon-union.test.d.ts +2 -0
- package/dist/lib/polygon-union.test.d.ts.map +1 -0
- package/dist/lib/polygon-union.test.js +105 -0
- package/dist/lib/scene-themes.d.ts +30 -0
- package/dist/lib/scene-themes.d.ts.map +1 -0
- package/dist/lib/scene-themes.js +202 -0
- 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/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 +92 -12
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +201 -6
- 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 +42 -0
- package/dist/systems/ceiling/ceiling-system.d.ts +6 -2
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +66 -13
- 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 +864 -180
- package/dist/systems/elevator/elevator-interaction-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-interaction-system.js +62 -0
- package/dist/systems/fence/fence-system.d.ts +20 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -1
- package/dist/systems/fence/fence-system.js +229 -57
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts +20 -0
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -0
- package/dist/systems/floor-elevation/floor-elevation-system.js +78 -0
- package/dist/systems/geometry/geometry-system.d.ts +41 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.js +348 -0
- 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 +1 -1
- 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 +7 -0
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +28 -17
- package/dist/systems/item-light/item-light-system.d.ts +1 -1
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
- package/dist/systems/item-light/item-light-system.js +18 -5
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +28 -4
- 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 +2 -41
- package/dist/systems/roof/roof-materials.d.ts +3 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +59 -14
- package/dist/systems/roof/roof-system.d.ts +46 -6
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1365 -170
- package/dist/systems/slab/slab-system.d.ts +5 -3
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +204 -68
- package/dist/systems/slab/slab-system.test.d.ts +2 -0
- package/dist/systems/slab/slab-system.test.d.ts.map +1 -0
- package/dist/systems/slab/slab-system.test.js +63 -0
- package/dist/systems/stair/stair-materials.d.ts +4 -3
- package/dist/systems/stair/stair-materials.d.ts.map +1 -1
- package/dist/systems/stair/stair-materials.js +30 -15
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +55 -63
- package/dist/systems/surface-hole-geometry.d.ts +3 -0
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -0
- package/dist/systems/surface-hole-geometry.js +4 -0
- package/dist/systems/wall/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 +62 -17
- package/dist/systems/wall/wall-materials.d.ts +12 -7
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +279 -46
- 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 +48 -0
- package/dist/systems/wall/wall-system.d.ts +2 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +493 -193
- 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 +308 -18
- package/package.json +5 -6
- package/dist/components/renderers/building/building-renderer.d.ts +0 -5
- package/dist/components/renderers/building/building-renderer.d.ts.map +0 -1
- package/dist/components/renderers/building/building-renderer.js +0 -11
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +0 -5
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +0 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.js +0 -58
- package/dist/components/renderers/column/column-renderer.d.ts +0 -5
- package/dist/components/renderers/column/column-renderer.d.ts.map +0 -1
- package/dist/components/renderers/column/column-renderer.js +0 -597
- package/dist/components/renderers/door/door-renderer.d.ts +0 -5
- package/dist/components/renderers/door/door-renderer.d.ts.map +0 -1
- package/dist/components/renderers/door/door-renderer.js +0 -16
- package/dist/components/renderers/fence/fence-renderer.d.ts +0 -5
- package/dist/components/renderers/fence/fence-renderer.d.ts.map +0 -1
- package/dist/components/renderers/fence/fence-renderer.js +0 -15
- package/dist/components/renderers/guide/guide-renderer.d.ts +0 -5
- package/dist/components/renderers/guide/guide-renderer.d.ts.map +0 -1
- package/dist/components/renderers/guide/guide-renderer.js +0 -38
- package/dist/components/renderers/item/item-renderer.d.ts +0 -5
- package/dist/components/renderers/item/item-renderer.d.ts.map +0 -1
- package/dist/components/renderers/item/item-renderer.js +0 -172
- package/dist/components/renderers/level/level-renderer.d.ts +0 -5
- package/dist/components/renderers/level/level-renderer.d.ts.map +0 -1
- package/dist/components/renderers/level/level-renderer.js +0 -11
- package/dist/components/renderers/roof/roof-materials.d.ts +0 -4
- package/dist/components/renderers/roof/roof-materials.d.ts.map +0 -1
- package/dist/components/renderers/roof/roof-materials.js +0 -16
- package/dist/components/renderers/roof/roof-renderer.d.ts +0 -5
- package/dist/components/renderers/roof/roof-renderer.d.ts.map +0 -1
- package/dist/components/renderers/roof/roof-renderer.js +0 -32
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +0 -5
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +0 -1
- package/dist/components/renderers/roof-segment/roof-segment-renderer.js +0 -40
- package/dist/components/renderers/scan/scan-renderer.d.ts +0 -5
- package/dist/components/renderers/scan/scan-renderer.d.ts.map +0 -1
- package/dist/components/renderers/scan/scan-renderer.js +0 -54
- package/dist/components/renderers/site/site-renderer.d.ts +0 -5
- package/dist/components/renderers/site/site-renderer.d.ts.map +0 -1
- package/dist/components/renderers/site/site-renderer.js +0 -106
- package/dist/components/renderers/slab/slab-renderer.d.ts +0 -5
- package/dist/components/renderers/slab/slab-renderer.d.ts.map +0 -1
- package/dist/components/renderers/slab/slab-renderer.js +0 -58
- package/dist/components/renderers/spawn/spawn-renderer.d.ts +0 -5
- package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +0 -1
- package/dist/components/renderers/spawn/spawn-renderer.js +0 -32
- package/dist/components/renderers/stair/stair-renderer.d.ts +0 -5
- package/dist/components/renderers/stair/stair-renderer.d.ts.map +0 -1
- package/dist/components/renderers/stair/stair-renderer.js +0 -603
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts +0 -5
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +0 -1
- package/dist/components/renderers/stair-segment/stair-segment-renderer.js +0 -51
- package/dist/components/renderers/wall/wall-renderer.d.ts +0 -5
- package/dist/components/renderers/wall/wall-renderer.d.ts.map +0 -1
- package/dist/components/renderers/wall/wall-renderer.js +0 -16
- package/dist/components/renderers/window/window-renderer.d.ts +0 -5
- package/dist/components/renderers/window/window-renderer.d.ts.map +0 -1
- package/dist/components/renderers/window/window-renderer.js +0 -21
- package/dist/components/renderers/zone/zone-renderer.d.ts +0 -5
- package/dist/components/renderers/zone/zone-renderer.d.ts.map +0 -1
- package/dist/components/renderers/zone/zone-renderer.js +0 -181
- 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 -79
|
@@ -1,13 +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';
|
|
10
|
+
import { PERF_OVERLAY_ENABLED, pushGpuSample } from '../../lib/gpu-perf';
|
|
11
|
+
import { inkedEdges } from '../../lib/ink-edges';
|
|
12
|
+
import { GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, ZONE_LAYER } from '../../lib/layers';
|
|
9
13
|
import { mergedOutline } from '../../lib/merged-outline-node';
|
|
14
|
+
import { getSceneTheme } from '../../lib/scene-themes';
|
|
15
|
+
import { packNormalToRGB, unpackRGBToNormal } from '../../lib/tsl-compat';
|
|
10
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
|
+
};
|
|
11
24
|
// SSGI Parameters - adjust these to fine-tune global illumination and ambient occlusion
|
|
12
25
|
export const SSGI_PARAMS = {
|
|
13
26
|
enabled: true,
|
|
@@ -23,10 +36,49 @@ export const SSGI_PARAMS = {
|
|
|
23
36
|
useScreenSpaceSampling: true,
|
|
24
37
|
useTemporalFiltering: false,
|
|
25
38
|
};
|
|
39
|
+
// Diagnostic toggles for thermal A/B testing. Add `?disable=ao,denoise,outline,postFx`
|
|
40
|
+
// to the URL (any subset) and reload to skip those passes. Each flag prevents
|
|
41
|
+
// allocation + per-frame work for that stage, so device temperature deltas
|
|
42
|
+
// across combos isolate which pass is the actual culprit. Picked up once at
|
|
43
|
+
// pipeline build; reload after changing the URL.
|
|
44
|
+
// - ao: skip SSGI entirely (and denoise, since denoise has nothing to denoise)
|
|
45
|
+
// - denoise: keep SSGI but feed its raw noisy AO straight to the composite
|
|
46
|
+
// - outline: skip the merged-outline node and its 14 internal RTs
|
|
47
|
+
// - postFx: bypass the whole RenderPipeline and use renderer.render(scene, camera)
|
|
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.
|
|
54
|
+
function readPerfDisableFlags() {
|
|
55
|
+
if (typeof window === 'undefined') {
|
|
56
|
+
return { ao: false, denoise: false, outline: false, postFx: false };
|
|
57
|
+
}
|
|
58
|
+
const raw = new URLSearchParams(window.location.search).get('disable') ?? '';
|
|
59
|
+
const set = new Set(raw
|
|
60
|
+
.split(',')
|
|
61
|
+
.map((s) => s.trim())
|
|
62
|
+
.filter(Boolean));
|
|
63
|
+
return {
|
|
64
|
+
ao: set.has('ao'),
|
|
65
|
+
denoise: set.has('denoise'),
|
|
66
|
+
outline: set.has('outline'),
|
|
67
|
+
postFx: set.has('postFx'),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const PERF_POST_FX_DISABLED = typeof window !== 'undefined' &&
|
|
71
|
+
new Set((new URLSearchParams(window.location.search).get('disable') ?? '')
|
|
72
|
+
.split(',')
|
|
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();
|
|
26
80
|
const MAX_PIPELINE_RETRIES = 3;
|
|
27
81
|
const RETRY_DELAY_MS = 500;
|
|
28
|
-
const DARK_BG = '#1f2433';
|
|
29
|
-
const LIGHT_BG = '#ffffff';
|
|
30
82
|
const DEFAULT_HOVER_STYLE = {
|
|
31
83
|
visibleColor: 0x00_aa_ff,
|
|
32
84
|
hiddenColor: 0xf3_ff_47,
|
|
@@ -47,24 +99,69 @@ function sanitizeOutlineObjects(objects) {
|
|
|
47
99
|
}
|
|
48
100
|
objects.length = nextIndex;
|
|
49
101
|
}
|
|
50
|
-
const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
51
|
-
const { gl: renderer, invalidate, scene, camera } = useThree();
|
|
102
|
+
const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostFx = false, }) => {
|
|
103
|
+
const { gl: renderer, invalidate, scene, camera, size } = useThree();
|
|
52
104
|
const renderPipelineRef = useRef(null);
|
|
53
105
|
const hasPipelineErrorRef = useRef(false);
|
|
54
106
|
const retryCountRef = useRef(0);
|
|
55
107
|
const rebuildTimeoutRef = useRef(null);
|
|
108
|
+
const skippedZeroSizeRef = useRef(false);
|
|
56
109
|
// Background color uniform — updated every frame via lerp, read by the TSL pipeline.
|
|
57
|
-
// Initialised from the current theme so there's no flash on first render.
|
|
58
|
-
const
|
|
110
|
+
// Initialised from the current scene theme so there's no flash on first render.
|
|
111
|
+
const initTheme = getSceneTheme(useViewer.getState().sceneTheme);
|
|
112
|
+
const initBg = initTheme.background;
|
|
59
113
|
const bgUniform = useRef(uniform(new Color(initBg)));
|
|
60
114
|
const bgCurrent = useRef(new Color(initBg));
|
|
61
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()));
|
|
135
|
+
// Ink-line colour follows the scene-theme background luminance (dark lines on
|
|
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).
|
|
138
|
+
const inkColorUniform = useRef(uniform(new Color(edgeColorFor(initBg))));
|
|
139
|
+
const inkOpacityScaleUniform = useRef(uniform(edgeOpacityScaleFor(initBg)));
|
|
62
140
|
const zoneLayers = useMemo(() => {
|
|
63
141
|
const l = new Layers();
|
|
64
142
|
l.enable(ZONE_LAYER);
|
|
65
143
|
l.disable(SCENE_LAYER);
|
|
66
144
|
return l;
|
|
67
145
|
}, []);
|
|
146
|
+
// Scene pass renders the main geometry layer plus the grid. The default camera
|
|
147
|
+
// mask also has the overlay layer enabled (custom controls enable it for
|
|
148
|
+
// picking), so without this the gizmos/handles/tool previews land in the
|
|
149
|
+
// depth+normal MRT and get inked / AO'd as if they were geometry. The grid is
|
|
150
|
+
// kept in here (not the overlay pass) so scene geometry depth-occludes it; it's
|
|
151
|
+
// a flat, depth-non-writing plane so the ink never picks it up.
|
|
152
|
+
const sceneOnlyLayers = useMemo(() => {
|
|
153
|
+
const l = new Layers();
|
|
154
|
+
l.set(SCENE_LAYER);
|
|
155
|
+
l.enable(GRID_LAYER);
|
|
156
|
+
return l;
|
|
157
|
+
}, []);
|
|
158
|
+
// Editor overlays render in their own pass, composited on top after the ink
|
|
159
|
+
// and outlines so they read as crisp UI rather than scene geometry.
|
|
160
|
+
const overlayLayers = useMemo(() => {
|
|
161
|
+
const l = new Layers();
|
|
162
|
+
l.set(OVERLAY_LAYER);
|
|
163
|
+
return l;
|
|
164
|
+
}, []);
|
|
68
165
|
const hoverHighlightMode = useViewer((s) => s.hoverHighlightMode);
|
|
69
166
|
const hoverVisibleColor = useMemo(() => uniform(new Color(DEFAULT_HOVER_STYLE.visibleColor)), []);
|
|
70
167
|
const hoverHiddenColor = useMemo(() => uniform(new Color(DEFAULT_HOVER_STYLE.hiddenColor)), []);
|
|
@@ -72,6 +169,11 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
72
169
|
const hoverPulseMix = useMemo(() => uniform(DEFAULT_HOVER_STYLE.pulse ? 0 : 1), []);
|
|
73
170
|
// Subscribe to projectId so the pipeline rebuilds on project switch
|
|
74
171
|
const projectId = useViewer((s) => s.projectId);
|
|
172
|
+
const shading = useViewer((s) => s.shading);
|
|
173
|
+
const edges = useViewer((s) => s.edges);
|
|
174
|
+
const inkOpacityOverride = useViewer((s) => s.inkOpacity);
|
|
175
|
+
const transparentBackground = useViewer((s) => s.transparentBackground);
|
|
176
|
+
const lastProjectIdRef = useRef(projectId);
|
|
75
177
|
// Bump this to force a pipeline rebuild (used by retry logic)
|
|
76
178
|
const [pipelineVersion, setPipelineVersion] = useState(0);
|
|
77
179
|
const requestPipelineRebuild = useCallback(() => {
|
|
@@ -83,6 +185,9 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
83
185
|
}, []);
|
|
84
186
|
// Reset retry state when project changes
|
|
85
187
|
useEffect(() => {
|
|
188
|
+
if (lastProjectIdRef.current === projectId)
|
|
189
|
+
return;
|
|
190
|
+
lastProjectIdRef.current = projectId;
|
|
86
191
|
retryCountRef.current = 0;
|
|
87
192
|
if (rebuildTimeoutRef.current !== null) {
|
|
88
193
|
clearTimeout(rebuildTimeoutRef.current);
|
|
@@ -115,6 +220,8 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
115
220
|
]);
|
|
116
221
|
// Build / rebuild the post-processing pipeline
|
|
117
222
|
useEffect(() => {
|
|
223
|
+
const width = Math.floor(size.width);
|
|
224
|
+
const height = Math.floor(size.height);
|
|
118
225
|
if (!(renderer && scene && camera)) {
|
|
119
226
|
console.warn('[viewer/post-processing] Skipping pipeline build — missing dependency.', {
|
|
120
227
|
hasRenderer: !!renderer,
|
|
@@ -123,14 +230,72 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
123
230
|
});
|
|
124
231
|
return;
|
|
125
232
|
}
|
|
233
|
+
if (width < 1 || height < 1) {
|
|
234
|
+
skippedZeroSizeRef.current = true;
|
|
235
|
+
hasPipelineErrorRef.current = false;
|
|
236
|
+
if (renderPipelineRef.current) {
|
|
237
|
+
renderPipelineRef.current.dispose();
|
|
238
|
+
}
|
|
239
|
+
renderPipelineRef.current = null;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (skippedZeroSizeRef.current) {
|
|
243
|
+
skippedZeroSizeRef.current = false;
|
|
244
|
+
}
|
|
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
|
+
}
|
|
258
|
+
const ssgiEnabled = shading === 'rendered' && SSGI_PARAMS.enabled && !perfDisable.ao;
|
|
259
|
+
const denoiseEnabled = ssgiEnabled && !perfDisable.denoise;
|
|
260
|
+
const outlineEnabled = !perfDisable.outline;
|
|
261
|
+
const inkEnabled = edges !== 'off';
|
|
262
|
+
// The depth+normal MRT feeds both SSGI and the screen-space ink pass.
|
|
263
|
+
const needsNormalMRT = ssgiEnabled || inkEnabled;
|
|
264
|
+
// Soft = thin (1px sample radius) + faint (50% opacity); strong = thick
|
|
265
|
+
// (2px, ~2× wider detected band) + solid (100%). The edge masks saturate,
|
|
266
|
+
// so radius+opacity are what actually separate the two modes — gain wouldn't.
|
|
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%.
|
|
269
|
+
const inkRadius = 1;
|
|
270
|
+
const inkOpacity = inkOpacityOverride ?? (edges === 'strong' ? 1 : 0.5);
|
|
126
271
|
console.log('[viewer/post-processing] Building pipeline', {
|
|
127
272
|
version: pipelineVersion,
|
|
128
|
-
ssgi:
|
|
129
|
-
|
|
273
|
+
ssgi: ssgiEnabled,
|
|
274
|
+
denoise: denoiseEnabled,
|
|
275
|
+
outline: outlineEnabled,
|
|
276
|
+
perfDisable,
|
|
130
277
|
projectId,
|
|
278
|
+
shading,
|
|
279
|
+
transparentBackground,
|
|
131
280
|
rendererCtor: renderer.constructor?.name,
|
|
281
|
+
width,
|
|
282
|
+
height,
|
|
132
283
|
});
|
|
133
284
|
hasPipelineErrorRef.current = false;
|
|
285
|
+
// WebGPU availability check: SSGI, denoise, and RenderPipeline are all
|
|
286
|
+
// WebGPU-only APIs. When the browser falls back to WebGL2 (no
|
|
287
|
+
// `navigator.gpu`, or the device couldn't be created), building the
|
|
288
|
+
// pipeline either throws silently or produces a broken output where
|
|
289
|
+
// the scene renders for a few frames and then goes black as the retry
|
|
290
|
+
// loop fights the direct-render fallback path. Short-circuit here so
|
|
291
|
+
// `useFrame` uses the direct `renderer.render(scene, camera)` path
|
|
292
|
+
// exclusively and never attempts the TSL pipeline.
|
|
293
|
+
const hasWebGPU = typeof navigator !== 'undefined' && 'gpu' in navigator;
|
|
294
|
+
if (!hasWebGPU) {
|
|
295
|
+
hasPipelineErrorRef.current = true;
|
|
296
|
+
renderPipelineRef.current = null;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
134
299
|
// Clear outliner arrays synchronously to prevent stale Object3D refs
|
|
135
300
|
// from the previous project leaking into the new pipeline's outline passes.
|
|
136
301
|
const outliner = useViewer.getState().outliner;
|
|
@@ -140,8 +305,15 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
140
305
|
outliner.hoveredObjects.length = 0;
|
|
141
306
|
try {
|
|
142
307
|
const scenePass = pass(scene, camera);
|
|
308
|
+
scenePass.setLayers(sceneOnlyLayers);
|
|
143
309
|
const zonePass = pass(scene, camera);
|
|
144
310
|
zonePass.setLayers(zoneLayers);
|
|
311
|
+
// Editor overlays (gizmos, move handles, tool previews, grid) on their own
|
|
312
|
+
// layer, kept out of the depth/normal MRT above so the ink + SSGI ignore
|
|
313
|
+
// them, then composited on top of the final image below.
|
|
314
|
+
const overlayPass = pass(scene, camera);
|
|
315
|
+
overlayPass.setLayers(overlayLayers);
|
|
316
|
+
const overlayColor = overlayPass.getTextureNode('output');
|
|
145
317
|
const scenePassColor = scenePass.getTextureNode('output');
|
|
146
318
|
// Background detection via alpha: renderer clears with alpha=0 (setClearAlpha(0) in useFrame),
|
|
147
319
|
// so background pixels have scenePassColor.a=0 while geometry pixels have output.a=1.
|
|
@@ -149,24 +321,32 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
149
321
|
// is the reliable geometry mask — no normals, no flicker.
|
|
150
322
|
const hasGeometry = scenePassColor.a;
|
|
151
323
|
const contentAlpha = hasGeometry.max(zonePass.a);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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);
|
|
328
|
+
// Depth + normal MRT — shared by SSGI (diffuse/normal) and the ink pass
|
|
329
|
+
// (depth/normal). Built whenever either is active.
|
|
330
|
+
let scenePassDepth = null;
|
|
331
|
+
let scenePassNormal = null;
|
|
332
|
+
let sceneNormal = null;
|
|
333
|
+
if (needsNormalMRT) {
|
|
155
334
|
scenePass.setMRT(mrt({
|
|
156
335
|
output,
|
|
157
336
|
diffuseColor,
|
|
158
|
-
normal:
|
|
337
|
+
normal: packNormalToRGB(normalView),
|
|
159
338
|
}));
|
|
339
|
+
scenePassDepth = scenePass.getTextureNode('depth');
|
|
340
|
+
scenePassNormal = scenePass.getTextureNode('normal');
|
|
341
|
+
const normalTexture = scenePass.getTexture('normal');
|
|
342
|
+
normalTexture.type = UnsignedByteType;
|
|
343
|
+
// Extract normal from color-encoded texture (SSGI consumes the node form)
|
|
344
|
+
sceneNormal = sample((uv) => unpackRGBToNormal(scenePassNormal.sample(uv)));
|
|
345
|
+
}
|
|
346
|
+
if (ssgiEnabled) {
|
|
160
347
|
const scenePassDiffuse = scenePass.getTextureNode('diffuseColor');
|
|
161
|
-
const scenePassDepth = scenePass.getTextureNode('depth');
|
|
162
|
-
const scenePassNormal = scenePass.getTextureNode('normal');
|
|
163
|
-
// Optimize texture bandwidth
|
|
164
348
|
const diffuseTexture = scenePass.getTexture('diffuseColor');
|
|
165
349
|
diffuseTexture.type = UnsignedByteType;
|
|
166
|
-
const normalTexture = scenePass.getTexture('normal');
|
|
167
|
-
normalTexture.type = UnsignedByteType;
|
|
168
|
-
// Extract normal from color-encoded texture
|
|
169
|
-
const sceneNormal = sample((uv) => colorToDirection(scenePassNormal.sample(uv)));
|
|
170
350
|
const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, camera);
|
|
171
351
|
giPass.sliceCount.value = SSGI_PARAMS.sliceCount;
|
|
172
352
|
giPass.stepCount.value = SSGI_PARAMS.stepCount;
|
|
@@ -179,50 +359,127 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
179
359
|
giPass.useLinearThickness.value = SSGI_PARAMS.useLinearThickness;
|
|
180
360
|
giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling;
|
|
181
361
|
giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering;
|
|
182
|
-
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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;
|
|
366
|
+
let ao;
|
|
367
|
+
if (denoiseEnabled) {
|
|
368
|
+
// DenoiseNode only denoises RGB — alpha is passed through unchanged.
|
|
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));
|
|
371
|
+
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
|
|
372
|
+
denoisePass.index.value = 0;
|
|
373
|
+
denoisePass.radius.value = 4;
|
|
374
|
+
ao = denoisePass.r;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
// Diagnostic path: feed raw noisy SSGI AO straight through. Will
|
|
378
|
+
// look grainy — that's the point, it isolates denoise cost.
|
|
379
|
+
ao = aoTexture.r;
|
|
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);
|
|
191
387
|
// Composite: scene * AO + diffuse * GI
|
|
192
388
|
sceneColor = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))), contentAlpha);
|
|
193
389
|
}
|
|
390
|
+
// Screen-space ink outline (SketchUp look) — depth/normal edge detection
|
|
391
|
+
// over the composited scene. Topology-agnostic, so it handles CSG-cut
|
|
392
|
+
// walls cleanly. Applied before the selection outline + background mix.
|
|
393
|
+
if (inkEnabled) {
|
|
394
|
+
sceneColor = vec4(inkedEdges({
|
|
395
|
+
sceneRgb: sceneColor.rgb,
|
|
396
|
+
depthTex: scenePassDepth,
|
|
397
|
+
normalTex: scenePassNormal,
|
|
398
|
+
inkColor: inkColorUniform.current,
|
|
399
|
+
radius: inkRadius,
|
|
400
|
+
opacity: float(inkOpacity).mul(inkOpacityScaleUniform.current),
|
|
401
|
+
}), sceneColor.a);
|
|
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
|
+
}
|
|
194
412
|
// Single merged outline node: one shared depth pass for both selected + hovered groups.
|
|
195
413
|
const outliner = useViewer.getState().outliner;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
414
|
+
let compositeWithOutlines = sceneColor;
|
|
415
|
+
let visualAlpha = contentAlpha;
|
|
416
|
+
if (outlineEnabled) {
|
|
417
|
+
const outlineNode = mergedOutline(scene, camera, {
|
|
418
|
+
primaryObjects: outliner.selectedObjects,
|
|
419
|
+
secondaryObjects: outliner.hoveredObjects,
|
|
420
|
+
primaryEdgeThickness: uniform(1),
|
|
421
|
+
secondaryEdgeThickness: uniform(1.5),
|
|
422
|
+
});
|
|
423
|
+
// Selected: white visible, yellow hidden
|
|
424
|
+
const selectedVisibleColor = uniform(new Color(0xff_ff_ff));
|
|
425
|
+
const selectedHiddenColor = uniform(new Color(0xf3_ff_47));
|
|
426
|
+
const selectedStrength = uniform(3);
|
|
427
|
+
const selectedOutline = outlineNode.primaryVisibleEdge
|
|
428
|
+
.mul(selectedVisibleColor)
|
|
429
|
+
.add(outlineNode.primaryHiddenEdge.mul(selectedHiddenColor))
|
|
430
|
+
.mul(selectedStrength);
|
|
431
|
+
// Hovered: blue visible, yellow hidden, pulsing
|
|
432
|
+
const pulsePeriod = uniform(3);
|
|
433
|
+
const oscillating = oscSine(time.div(pulsePeriod).mul(2)).mul(0.5).add(0.5);
|
|
434
|
+
const osc = mix(oscillating, float(1), hoverPulseMix);
|
|
435
|
+
const hoverOutline = outlineNode.secondaryVisibleEdge
|
|
436
|
+
.mul(hoverVisibleColor)
|
|
437
|
+
.add(outlineNode.secondaryHiddenEdge.mul(hoverHiddenColor))
|
|
438
|
+
.mul(hoverStrength)
|
|
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);
|
|
445
|
+
compositeWithOutlines = vec4(add(sceneColor.rgb, selectedOutline.add(hoverOutline)), sceneColor.a);
|
|
446
|
+
}
|
|
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,
|
|
201
460
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const compositeWithOutlines = vec4(add(sceneColor.rgb, selectedOutline.add(hoverOutline)), sceneColor.a);
|
|
220
|
-
const finalOutput = vec4(mix(bgUniform.current, compositeWithOutlines.rgb, contentAlpha), float(1));
|
|
461
|
+
if (shading === 'rendered') {
|
|
462
|
+
bgGradient = gradeRgb(bgGradient);
|
|
463
|
+
}
|
|
464
|
+
const composited = mix(bgGradient, compositeWithOutlines.rgb, contentAlpha);
|
|
465
|
+
// Editor overlays painted on top by their own alpha — they never get inked,
|
|
466
|
+
// AO'd, or outlined, and always read crisp regardless of scene depth.
|
|
467
|
+
const withOverlay = mix(composited, overlayColor.rgb, overlayColor.a);
|
|
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
|
+
}
|
|
221
478
|
const renderPipeline = new RenderPipeline(renderer);
|
|
479
|
+
renderPipeline.outputColorTransform = !transparentBackground;
|
|
222
480
|
renderPipeline.outputNode = finalOutput;
|
|
223
481
|
renderPipelineRef.current = renderPipeline;
|
|
224
482
|
retryCountRef.current = 0;
|
|
225
|
-
console.log('[viewer/post-processing] Pipeline built OK', { version: pipelineVersion });
|
|
226
483
|
}
|
|
227
484
|
catch (error) {
|
|
228
485
|
hasPipelineErrorRef.current = true;
|
|
@@ -243,33 +500,97 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
243
500
|
renderPipelineRef.current = null;
|
|
244
501
|
};
|
|
245
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.
|
|
246
507
|
camera,
|
|
508
|
+
disablePostFx,
|
|
247
509
|
hoverHiddenColor,
|
|
248
510
|
hoverPulseMix,
|
|
249
511
|
hoverStrength,
|
|
250
512
|
hoverVisibleColor,
|
|
513
|
+
edges,
|
|
514
|
+
inkOpacityOverride,
|
|
251
515
|
pipelineVersion,
|
|
252
516
|
projectId,
|
|
253
517
|
renderer,
|
|
254
518
|
scene,
|
|
519
|
+
shading,
|
|
520
|
+
transparentBackground,
|
|
521
|
+
size.height,
|
|
522
|
+
size.width,
|
|
255
523
|
zoneLayers,
|
|
524
|
+
sceneOnlyLayers,
|
|
525
|
+
overlayLayers,
|
|
256
526
|
]);
|
|
257
527
|
useFrame((_, delta) => {
|
|
258
|
-
|
|
259
|
-
|
|
528
|
+
if (size.width < 1 || size.height < 1) {
|
|
529
|
+
return;
|
|
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
|
+
}
|
|
549
|
+
// Animate background colour toward the current scene theme target (same lerp as AnimatedBackground)
|
|
550
|
+
const bgTheme = getSceneTheme(useViewer.getState().sceneTheme);
|
|
551
|
+
bgTarget.current.set(bgTheme.background);
|
|
260
552
|
bgCurrent.current.lerp(bgTarget.current, Math.min(delta, 0.1) * 4);
|
|
261
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);
|
|
565
|
+
// Ink colour follows the (lerping) background luminance — snaps dark↔light.
|
|
566
|
+
const bgHex = `#${bgCurrent.current.getHexString()}`;
|
|
567
|
+
inkColorUniform.current.value.set(edgeColorFor(bgHex));
|
|
568
|
+
inkOpacityScaleUniform.current.value = edgeOpacityScaleFor(bgHex);
|
|
262
569
|
const outliner = useViewer.getState().outliner;
|
|
263
570
|
sanitizeOutlineObjects(outliner.selectedObjects);
|
|
264
571
|
sanitizeOutlineObjects(outliner.hoveredObjects);
|
|
265
|
-
if (
|
|
572
|
+
if (disablePostFx ||
|
|
573
|
+
PERF_POST_FX_DISABLED ||
|
|
574
|
+
hasPipelineErrorRef.current ||
|
|
575
|
+
!renderPipelineRef.current) {
|
|
266
576
|
try {
|
|
267
|
-
|
|
577
|
+
const clearAlpha = transparentBackground ? 0 : 1;
|
|
578
|
+
if (renderer.setClearColor) {
|
|
268
579
|
;
|
|
269
|
-
renderer.
|
|
580
|
+
renderer.setClearColor(bgCurrent.current, clearAlpha);
|
|
270
581
|
}
|
|
271
|
-
|
|
582
|
+
else if (renderer.setClearAlpha) {
|
|
583
|
+
;
|
|
584
|
+
renderer.setClearAlpha(clearAlpha);
|
|
585
|
+
}
|
|
586
|
+
const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
|
|
272
587
|
renderer.render(scene, camera);
|
|
588
|
+
if (PERF_OVERLAY_ENABLED) {
|
|
589
|
+
const queue = renderer.backend?.device?.queue;
|
|
590
|
+
queue?.onSubmittedWorkDone?.().then(() => {
|
|
591
|
+
pushGpuSample(performance.now() - submittedAt);
|
|
592
|
+
});
|
|
593
|
+
}
|
|
273
594
|
}
|
|
274
595
|
catch (fallbackError) {
|
|
275
596
|
console.error('[viewer/post-processing] Fallback render failed.', fallbackError);
|
|
@@ -281,7 +602,19 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
281
602
|
// making scenePassColor.a a reliable geometry mask (geometry pixels write a=1 via output node).
|
|
282
603
|
;
|
|
283
604
|
renderer.setClearAlpha(0);
|
|
605
|
+
const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
|
|
284
606
|
renderPipelineRef.current.render();
|
|
607
|
+
if (PERF_OVERLAY_ENABLED) {
|
|
608
|
+
// device.queue.onSubmittedWorkDone() resolves once the GPU has
|
|
609
|
+
// finished the work we just submitted — the delta from our submit
|
|
610
|
+
// timestamp is a clean per-frame GPU duration. Doesn't block CPU
|
|
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
|
+
}
|
|
285
618
|
}
|
|
286
619
|
catch (error) {
|
|
287
620
|
hasPipelineErrorRef.current = true;
|
|
@@ -297,7 +630,6 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
|
|
|
297
630
|
if (retryCountRef.current < MAX_PIPELINE_RETRIES) {
|
|
298
631
|
// Auto-retry: schedule a pipeline rebuild if we haven't exceeded the retry limit
|
|
299
632
|
retryCountRef.current++;
|
|
300
|
-
console.warn(`[viewer/post-processing] Scheduling pipeline rebuild (attempt ${retryCountRef.current}/${MAX_PIPELINE_RETRIES})`);
|
|
301
633
|
if (rebuildTimeoutRef.current !== null) {
|
|
302
634
|
clearTimeout(rebuildTimeoutRef.current);
|
|
303
635
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mounts every registered node kind's system component, ordered by
|
|
3
|
+
* `system.priority` (default {@link DEFAULT_PRIORITY}).
|
|
4
|
+
*
|
|
5
|
+
* Today the registry is empty so this component mounts nothing — coexists
|
|
6
|
+
* with legacy `*-System` components in `<Viewer>`. Once kinds register via
|
|
7
|
+
* `@pascal-app/nodes`, each kind's registry-driven system takes over and
|
|
8
|
+
* its legacy counterpart short-circuits via the `nodeRegistry.has(kind)`
|
|
9
|
+
* guard added to each legacy system.
|
|
10
|
+
*/
|
|
11
|
+
export declare function RegisteredSystems(): import("react").JSX.Element | null;
|
|
12
|
+
//# sourceMappingURL=registered-systems.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registered-systems.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/registered-systems.tsx"],"names":[],"mappings":"AA8BA;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,uCAyBhC"}
|