@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-themes.d.ts","sourceRoot":"","sources":["../../src/lib/scene-themes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnD,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IAIZ,UAAU,EAAE,OAAO,GAAG,MAAM,CAAA;IAC5B,UAAU,EAAE,MAAM,CAAA;IAIlB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IACzD,MAAM,EAAE,KAAK,CAAC;QACZ,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;QAClC,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,OAAO,CAAA;KACrB,CAAC,CAAA;IACF,mBAAmB,EAAE,MAAM,CAAA;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAA;CACjD,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"scene-themes.d.ts","sourceRoot":"","sources":["../../src/lib/scene-themes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnD,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IAIZ,UAAU,EAAE,OAAO,GAAG,MAAM,CAAA;IAC5B,UAAU,EAAE,MAAM,CAAA;IAIlB,aAAa,CAAC,EAAE,MAAM,CAAA;IAItB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IACzD,MAAM,EAAE,KAAK,CAAC;QACZ,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;QAClC,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,OAAO,CAAA;KACrB,CAAC,CAAA;IACF,mBAAmB,EAAE,MAAM,CAAA;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAA;CACjD,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,UAAU,EAoMpC,CAAA;AAED,eAAO,MAAM,eAAe,UAAwC,CAAA;AAIpE,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,CAEpD"}
|
package/dist/lib/scene-themes.js
CHANGED
|
@@ -3,13 +3,14 @@ export const SCENE_THEMES = [
|
|
|
3
3
|
id: 'studio',
|
|
4
4
|
name: 'Studio',
|
|
5
5
|
appearance: 'light',
|
|
6
|
-
background: '#
|
|
7
|
-
|
|
6
|
+
background: '#fbfbfa',
|
|
7
|
+
backgroundSky: '#b6cfe7',
|
|
8
|
+
ground: '#e9e7e2',
|
|
8
9
|
ambient: { color: '#ffffff', intensity: 0.15 },
|
|
9
|
-
hemi: { sky: '#ffffff', ground: '#aaa49a', intensity: 0.
|
|
10
|
+
hemi: { sky: '#ffffff', ground: '#aaa49a', intensity: 0.45 },
|
|
10
11
|
lights: [
|
|
11
12
|
{ position: [10, 10, 10], color: '#ffffff', intensity: 4, castShadow: true },
|
|
12
|
-
{ position: [-10, 10, -10], color: '#ffffff', intensity: 0.
|
|
13
|
+
{ position: [-10, 10, -10], color: '#ffffff', intensity: 0.6 },
|
|
13
14
|
],
|
|
14
15
|
toneMappingExposure: 0.9,
|
|
15
16
|
clayTints: {
|
|
@@ -25,6 +26,7 @@ export const SCENE_THEMES = [
|
|
|
25
26
|
name: 'Paper',
|
|
26
27
|
appearance: 'light',
|
|
27
28
|
background: '#ede9df',
|
|
29
|
+
backgroundSky: '#c0d2e4',
|
|
28
30
|
ground: '#e7e1d3',
|
|
29
31
|
ambient: { color: '#fff9eb', intensity: 0.55 },
|
|
30
32
|
hemi: { sky: '#fff5d9', ground: '#c2b89c', intensity: 0.35 },
|
|
@@ -46,6 +48,7 @@ export const SCENE_THEMES = [
|
|
|
46
48
|
name: 'Sunset',
|
|
47
49
|
appearance: 'light',
|
|
48
50
|
background: '#f6e8d4',
|
|
51
|
+
backgroundSky: '#b5bede',
|
|
49
52
|
ground: '#ecd9bf',
|
|
50
53
|
ambient: { color: '#ffd9a8', intensity: 0.45 },
|
|
51
54
|
hemi: { sky: '#ffd9a8', ground: '#5b4634', intensity: 0.4 },
|
|
@@ -67,11 +70,12 @@ export const SCENE_THEMES = [
|
|
|
67
70
|
name: 'Overcast',
|
|
68
71
|
appearance: 'light',
|
|
69
72
|
background: '#e6e7e6',
|
|
73
|
+
backgroundSky: '#c3ccd6',
|
|
70
74
|
ground: '#dadcd9',
|
|
71
75
|
ambient: { color: '#eef0ef', intensity: 1.1 },
|
|
72
76
|
hemi: { sky: '#f4f5f3', ground: '#bcbfbb', intensity: 0.9 },
|
|
73
77
|
lights: [{ position: [12, 28, 10], color: '#f4f5f3', intensity: 0.8, castShadow: true }],
|
|
74
|
-
toneMappingExposure: 0.
|
|
78
|
+
toneMappingExposure: 0.9,
|
|
75
79
|
clayTints: {
|
|
76
80
|
wall: '#dedfdc',
|
|
77
81
|
floor: '#cdcec9',
|
|
@@ -85,6 +89,7 @@ export const SCENE_THEMES = [
|
|
|
85
89
|
name: 'Blueprint',
|
|
86
90
|
appearance: 'light',
|
|
87
91
|
background: '#dde6ef',
|
|
92
|
+
backgroundSky: '#a5c4e2',
|
|
88
93
|
ground: '#c9d6e6',
|
|
89
94
|
ambient: { color: '#cfdcec', intensity: 0.7 },
|
|
90
95
|
hemi: { sky: '#dfeaf6', ground: '#5b6b80', intensity: 0.55 },
|
|
@@ -92,7 +97,7 @@ export const SCENE_THEMES = [
|
|
|
92
97
|
{ position: [16, 24, 12], color: '#e6efff', intensity: 1.8, castShadow: true },
|
|
93
98
|
{ position: [-12, 10, -8], color: '#9fb6d8', intensity: 0.4 },
|
|
94
99
|
],
|
|
95
|
-
toneMappingExposure: 0.
|
|
100
|
+
toneMappingExposure: 0.9,
|
|
96
101
|
clayTints: {
|
|
97
102
|
wall: '#9fb6d2',
|
|
98
103
|
floor: '#8ba2c2',
|
|
@@ -106,6 +111,7 @@ export const SCENE_THEMES = [
|
|
|
106
111
|
name: 'Mediterranean',
|
|
107
112
|
appearance: 'light',
|
|
108
113
|
background: '#bdd6e8',
|
|
114
|
+
backgroundSky: '#8ab4d6',
|
|
109
115
|
ground: '#ddd2bb',
|
|
110
116
|
ambient: { color: '#d6e6f3', intensity: 0.5 },
|
|
111
117
|
hemi: { sky: '#a8c8e2', ground: '#d8c9a4', intensity: 0.6 },
|
|
@@ -127,14 +133,15 @@ export const SCENE_THEMES = [
|
|
|
127
133
|
name: 'Twilight',
|
|
128
134
|
appearance: 'dark',
|
|
129
135
|
background: '#3a3550',
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
backgroundSky: '#272338',
|
|
137
|
+
ground: '#67618a',
|
|
138
|
+
ambient: { color: '#a89cc8', intensity: 0.55 },
|
|
139
|
+
hemi: { sky: '#d8a8c0', ground: '#3a3450', intensity: 0.7 },
|
|
133
140
|
lights: [
|
|
134
141
|
{ position: [-14, 22, -10], color: '#a4b6e8', intensity: 1.4, castShadow: true },
|
|
135
142
|
{ position: [14, 6, 8], color: '#ffb070', intensity: 0.9 },
|
|
136
143
|
],
|
|
137
|
-
toneMappingExposure:
|
|
144
|
+
toneMappingExposure: 0.9,
|
|
138
145
|
clayTints: {
|
|
139
146
|
wall: '#c5b9cf',
|
|
140
147
|
floor: '#ad9fbb',
|
|
@@ -148,12 +155,13 @@ export const SCENE_THEMES = [
|
|
|
148
155
|
name: 'Night',
|
|
149
156
|
appearance: 'dark',
|
|
150
157
|
background: '#1f2433',
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
158
|
+
backgroundSky: '#12161f',
|
|
159
|
+
ground: '#4a5470',
|
|
160
|
+
ambient: { color: '#a0b0ff', intensity: 0.25 },
|
|
161
|
+
hemi: { sky: '#3a4666', ground: '#232a3d', intensity: 0.55 },
|
|
154
162
|
lights: [
|
|
155
|
-
{ position: [10, 10, 10], color: '#e0e5ff', intensity:
|
|
156
|
-
{ position: [-10, 10, -10], color: '#8090ff', intensity: 0.
|
|
163
|
+
{ position: [10, 10, 10], color: '#e0e5ff', intensity: 1.2, castShadow: true },
|
|
164
|
+
{ position: [-10, 10, -10], color: '#8090ff', intensity: 0.3 },
|
|
157
165
|
],
|
|
158
166
|
toneMappingExposure: 0.9,
|
|
159
167
|
clayTints: {
|
|
@@ -169,6 +177,7 @@ export const SCENE_THEMES = [
|
|
|
169
177
|
name: 'Verdant',
|
|
170
178
|
appearance: 'light',
|
|
171
179
|
background: '#d6e4d2',
|
|
180
|
+
backgroundSky: '#aecde0',
|
|
172
181
|
ground: '#c7d6b4',
|
|
173
182
|
ambient: { color: '#e3efdd', intensity: 0.5 },
|
|
174
183
|
hemi: { sky: '#cfe6cf', ground: '#8ea06f', intensity: 0.65 },
|
|
@@ -176,7 +185,7 @@ export const SCENE_THEMES = [
|
|
|
176
185
|
{ position: [16, 22, 12], color: '#fff6d8', intensity: 3, castShadow: true },
|
|
177
186
|
{ position: [-12, 10, -8], color: '#bfe0c2', intensity: 0.5 },
|
|
178
187
|
],
|
|
179
|
-
toneMappingExposure: 0.
|
|
188
|
+
toneMappingExposure: 0.9,
|
|
180
189
|
clayTints: {
|
|
181
190
|
wall: '#eef0e6',
|
|
182
191
|
floor: '#d8ddc6',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow-only.d.ts","sourceRoot":"","sources":["../../src/lib/shadow-only.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAuBrC,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CASpD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAOpD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SCENE_LAYER, SHADOW_ONLY_LAYER } from './layers';
|
|
2
|
+
/**
|
|
3
|
+
* Shadow-caster-only hiding: removes an object (and its descendants) from the
|
|
4
|
+
* color passes while keeping it in the shadow map, so a hidden roof or level
|
|
5
|
+
* still shadows the interior and sun enters through windows correctly.
|
|
6
|
+
*
|
|
7
|
+
* Uses layer masks instead of `visible = false` for two reasons: `visible`
|
|
8
|
+
* cascades (and, critically, prunes the object from the shadow pass too),
|
|
9
|
+
* while layers are tested per-object against the rendering camera — the main
|
|
10
|
+
* camera never enables {@link SHADOW_ONLY_LAYER}, but every shadow-casting
|
|
11
|
+
* light's shadow camera does (see `lights.tsx`).
|
|
12
|
+
*
|
|
13
|
+
* The original `layers.mask` is stashed under a private Symbol so
|
|
14
|
+
* {@link clearShadowOnly} restores the exact prior state. Both calls are
|
|
15
|
+
* idempotent and cheap to reapply.
|
|
16
|
+
*/
|
|
17
|
+
const ORIGINAL_LAYERS = Symbol('pascal:shadow-only:original-layers');
|
|
18
|
+
export function applyShadowOnly(root) {
|
|
19
|
+
root.traverse((obj) => {
|
|
20
|
+
const carrier = obj;
|
|
21
|
+
if (carrier[ORIGINAL_LAYERS] === undefined) {
|
|
22
|
+
carrier[ORIGINAL_LAYERS] = obj.layers.mask;
|
|
23
|
+
}
|
|
24
|
+
obj.layers.disable(SCENE_LAYER);
|
|
25
|
+
obj.layers.enable(SHADOW_ONLY_LAYER);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function clearShadowOnly(root) {
|
|
29
|
+
root.traverse((obj) => {
|
|
30
|
+
const carrier = obj;
|
|
31
|
+
if (carrier[ORIGINAL_LAYERS] === undefined)
|
|
32
|
+
return;
|
|
33
|
+
obj.layers.mask = carrier[ORIGINAL_LAYERS];
|
|
34
|
+
delete carrier[ORIGINAL_LAYERS];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Camera, type Scene } from 'three';
|
|
2
|
+
import { type WebGPURenderer } from 'three/webgpu';
|
|
3
|
+
import { type EdgeMode } from './edge-style';
|
|
4
|
+
export declare const THUMBNAIL_WIDTH = 1920;
|
|
5
|
+
export declare const THUMBNAIL_HEIGHT = 1080;
|
|
6
|
+
export type SnapshotCaptureMode = 'standard' | 'viewport' | 'area';
|
|
7
|
+
export type SnapshotCropRegion = {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
|
13
|
+
export type SnapshotSize = {
|
|
14
|
+
w: number;
|
|
15
|
+
h: number;
|
|
16
|
+
};
|
|
17
|
+
export type SnapshotCaptureResult = {
|
|
18
|
+
blob: Blob;
|
|
19
|
+
outW: number;
|
|
20
|
+
outH: number;
|
|
21
|
+
};
|
|
22
|
+
export type SnapshotPipeline = {
|
|
23
|
+
applyEnvironment: ({ theme, transparent, grade, edges, camera, }: {
|
|
24
|
+
theme: string;
|
|
25
|
+
transparent: boolean;
|
|
26
|
+
grade: boolean;
|
|
27
|
+
edges: EdgeMode;
|
|
28
|
+
camera: Camera;
|
|
29
|
+
}) => void;
|
|
30
|
+
capture: ({ captureMode, cropRegion, standardSize, }: {
|
|
31
|
+
captureMode?: SnapshotCaptureMode;
|
|
32
|
+
cropRegion?: SnapshotCropRegion;
|
|
33
|
+
standardSize?: SnapshotSize;
|
|
34
|
+
}) => Promise<SnapshotCaptureResult>;
|
|
35
|
+
dispose: () => void;
|
|
36
|
+
};
|
|
37
|
+
export declare function createSnapshotPipeline({ renderer, scene, camera, }: {
|
|
38
|
+
renderer: WebGPURenderer;
|
|
39
|
+
scene: Scene;
|
|
40
|
+
camera: Camera;
|
|
41
|
+
}): Promise<SnapshotPipeline | null>;
|
|
42
|
+
//# sourceMappingURL=snapshot-pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-pipeline.d.ts","sourceRoot":"","sources":["../../src/lib/snapshot-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAkB,KAAK,KAAK,EAAoB,MAAM,OAAO,CAAA;AAqBjF,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAGhF,OAAO,EAAE,KAAK,QAAQ,EAAqC,MAAM,cAAc,CAAA;AAK/E,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AAEpC,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAA;AAElE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,IAAI,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gBAAgB,EAAE,CAAC,EACjB,KAAK,EACL,WAAW,EACX,KAAK,EACL,KAAK,EACL,MAAM,GACP,EAAE;QACD,KAAK,EAAE,MAAM,CAAA;QACb,WAAW,EAAE,OAAO,CAAA;QACpB,KAAK,EAAE,OAAO,CAAA;QACd,KAAK,EAAE,QAAQ,CAAA;QACf,MAAM,EAAE,MAAM,CAAA;KACf,KAAK,IAAI,CAAA;IACV,OAAO,EAAE,CAAC,EACR,WAAW,EACX,UAAU,EACV,YAAY,GACb,EAAE;QACD,WAAW,CAAC,EAAE,mBAAmB,CAAA;QACjC,UAAU,CAAC,EAAE,kBAAkB,CAAA;QAC/B,YAAY,CAAC,EAAE,YAAY,CAAA;KAC5B,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACpC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAED,wBAAsB,sBAAsB,CAAC,EAC3C,QAAQ,EACR,KAAK,EACL,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,cAAc,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAoSnC"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { Color, Matrix4, UnsignedByteType } from 'three';
|
|
2
|
+
import { ssgi } from 'three/addons/tsl/display/SSGINode.js';
|
|
3
|
+
import { denoise } from 'three/examples/jsm/tsl/display/DenoiseNode.js';
|
|
4
|
+
import { fxaa } from 'three/examples/jsm/tsl/display/FXAANode.js';
|
|
5
|
+
import { convertToTexture, diffuseColor, float, mix, mrt, normalView, output, pass, sample, saturation, screenUV, smoothstep, uniform, vec3, vec4, } from 'three/tsl';
|
|
6
|
+
import { RenderPipeline, RenderTarget } from 'three/webgpu';
|
|
7
|
+
import { GRADE_PARAMS, SSGI_PARAMS } from '../components/viewer/post-processing';
|
|
8
|
+
import { backdropGradient, deepSkyColor, horizonHazeColor } from './backdrop';
|
|
9
|
+
import { edgeColorFor, edgeOpacityScaleFor } from './edge-style';
|
|
10
|
+
import { inkedEdges } from './ink-edges';
|
|
11
|
+
import { getSceneTheme } from './scene-themes';
|
|
12
|
+
import { packNormalToRGB, unpackRGBToNormal } from './tsl-compat';
|
|
13
|
+
export const THUMBNAIL_WIDTH = 1920;
|
|
14
|
+
export const THUMBNAIL_HEIGHT = 1080;
|
|
15
|
+
export async function createSnapshotPipeline({ renderer, scene, camera, }) {
|
|
16
|
+
try {
|
|
17
|
+
if (renderer.init)
|
|
18
|
+
await renderer.init();
|
|
19
|
+
// Backdrop compositing for scene snapshots (studio renders, project
|
|
20
|
+
// thumbnails): theme background + sky gradient, same world-ray math as the
|
|
21
|
+
// viewport backdrop in viewer's post-processing. Uniform-driven so the one
|
|
22
|
+
// cached pipeline serves both opaque and transparent (preset/item) captures.
|
|
23
|
+
const bgColorUniform = uniform(new Color('#ffffff'));
|
|
24
|
+
const bgSkyUniform = uniform(new Color('#ffffff'));
|
|
25
|
+
const bgSkyDeepUniform = uniform(new Color('#ffffff'));
|
|
26
|
+
const bgHazeUniform = uniform(new Color('#ffffff'));
|
|
27
|
+
const bgProjInvUniform = uniform(new Matrix4());
|
|
28
|
+
const bgCamWorldUniform = uniform(new Matrix4());
|
|
29
|
+
const bgMixUniform = uniform(1);
|
|
30
|
+
const gradeMixUniform = uniform(0);
|
|
31
|
+
const inkMixUniform = uniform(0);
|
|
32
|
+
const inkColorUniform = uniform(new Color('#1a1d24'));
|
|
33
|
+
const inkOpacityUniform = uniform(0.5);
|
|
34
|
+
const inkOpacityScaleUniform = uniform(1);
|
|
35
|
+
// pass() handles MRT internally for all material types, including custom
|
|
36
|
+
// shaders — unlike renderer.setMRT() which crashes on non-NodeMaterials.
|
|
37
|
+
// pass() also respects camera.layers, so caller-disabled objects are filtered.
|
|
38
|
+
const scenePass = pass(scene, camera);
|
|
39
|
+
scenePass.setMRT(mrt({
|
|
40
|
+
output,
|
|
41
|
+
diffuseColor,
|
|
42
|
+
normal: packNormalToRGB(normalView),
|
|
43
|
+
}));
|
|
44
|
+
const scenePassColor = scenePass.getTextureNode('output');
|
|
45
|
+
const scenePassDepth = scenePass.getTextureNode('depth');
|
|
46
|
+
const scenePassNormal = scenePass.getTextureNode('normal');
|
|
47
|
+
scenePass.getTexture('diffuseColor').type = UnsignedByteType;
|
|
48
|
+
scenePass.getTexture('normal').type = UnsignedByteType;
|
|
49
|
+
const sceneNormal = sample((uv) => unpackRGBToNormal(scenePassNormal.sample(uv)));
|
|
50
|
+
const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, camera);
|
|
51
|
+
giPass.sliceCount.value = SSGI_PARAMS.sliceCount;
|
|
52
|
+
giPass.stepCount.value = SSGI_PARAMS.stepCount;
|
|
53
|
+
giPass.radius.value = SSGI_PARAMS.radius;
|
|
54
|
+
giPass.expFactor.value = SSGI_PARAMS.expFactor;
|
|
55
|
+
giPass.thickness.value = SSGI_PARAMS.thickness;
|
|
56
|
+
giPass.backfaceLighting.value = SSGI_PARAMS.backfaceLighting;
|
|
57
|
+
giPass.aoIntensity.value = SSGI_PARAMS.aoIntensity;
|
|
58
|
+
giPass.giIntensity.value = SSGI_PARAMS.giIntensity;
|
|
59
|
+
giPass.useLinearThickness.value = SSGI_PARAMS.useLinearThickness;
|
|
60
|
+
giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling;
|
|
61
|
+
giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering;
|
|
62
|
+
// r185: SSGI's AO lives in its own single-channel texture (getAONode)
|
|
63
|
+
// rather than the alpha of one packed rgba texture.
|
|
64
|
+
const aoTexture = giPass.getAONode();
|
|
65
|
+
const aoAsRgb = vec4(aoTexture.r, aoTexture.r, aoTexture.r, float(1));
|
|
66
|
+
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
|
|
67
|
+
denoisePass.index.value = 0;
|
|
68
|
+
denoisePass.radius.value = 4;
|
|
69
|
+
// Same far-field AO fade as the viewport pipeline — without it the
|
|
70
|
+
// horizon picks up a visible AO line in captures.
|
|
71
|
+
const aoFarFade = smoothstep(float(0.9994), float(0.9998), scenePassDepth.sample(screenUV).r);
|
|
72
|
+
const ao = mix(denoisePass.r, float(1), aoFarFade);
|
|
73
|
+
const aoRgb = scenePassColor.rgb.mul(ao);
|
|
74
|
+
// Ink edges, mirroring the viewport pipeline (AO → ink → grade) so
|
|
75
|
+
// captures carry the same soft/strong edge look the canvas shows.
|
|
76
|
+
// Uniform-gated like grade/backdrop: one cached pipeline serves all modes.
|
|
77
|
+
// Radius scales with render height: a supersampled capture (4K → 1080p)
|
|
78
|
+
// would otherwise halve the apparent line weight vs the viewport's 1px.
|
|
79
|
+
const inkRadius = Math.max(1, Math.round(renderer.domElement.height / 1080));
|
|
80
|
+
const inkedRgb = inkedEdges({
|
|
81
|
+
sceneRgb: aoRgb,
|
|
82
|
+
depthTex: scenePassDepth,
|
|
83
|
+
normalTex: scenePassNormal,
|
|
84
|
+
inkColor: inkColorUniform,
|
|
85
|
+
radius: inkRadius,
|
|
86
|
+
opacity: float(inkOpacityUniform).mul(inkOpacityScaleUniform),
|
|
87
|
+
});
|
|
88
|
+
const ungradedSceneRgb = mix(aoRgb, inkedRgb, inkMixUniform);
|
|
89
|
+
const gradeRgb = (rgb) => saturation(rgb.div(0.18).pow(vec3(GRADE_PARAMS.contrast)).mul(0.18), GRADE_PARAMS.saturation);
|
|
90
|
+
const sceneRgb = mix(ungradedSceneRgb, gradeRgb(ungradedSceneRgb), gradeMixUniform);
|
|
91
|
+
// Per-pixel world ray from the capture camera → sky gradient above the
|
|
92
|
+
// horizon (dir.y = 0), flat background below — mirrors the viewport
|
|
93
|
+
// backdrop. bgMix 0 bypasses it and keeps the capture transparent.
|
|
94
|
+
const ndc = vec4(screenUV.x.mul(2).sub(1), float(1).sub(screenUV.y).mul(2).sub(1), 1, 1);
|
|
95
|
+
const viewRay = bgProjInvUniform.mul(ndc);
|
|
96
|
+
const worldDir = bgCamWorldUniform.mul(vec4(viewRay.xyz, 0)).xyz.normalize();
|
|
97
|
+
const ungradedBgGradient = backdropGradient({
|
|
98
|
+
dirY: worldDir.y,
|
|
99
|
+
background: bgColorUniform,
|
|
100
|
+
haze: bgHazeUniform,
|
|
101
|
+
sky: bgSkyUniform,
|
|
102
|
+
skyDeep: bgSkyDeepUniform,
|
|
103
|
+
});
|
|
104
|
+
const bgGradient = mix(ungradedBgGradient, gradeRgb(ungradedBgGradient), gradeMixUniform);
|
|
105
|
+
const alpha = scenePassColor.a;
|
|
106
|
+
const finalOutput = vec4(mix(sceneRgb, mix(bgGradient, sceneRgb, alpha), bgMixUniform), mix(alpha, float(1), bgMixUniform));
|
|
107
|
+
// FXAA requires a texture node as input; convertToTexture renders finalOutput
|
|
108
|
+
// into an intermediate RT so FXAA can sample it with neighbour UV offsets.
|
|
109
|
+
const aaOutput = fxaa(convertToTexture(finalOutput));
|
|
110
|
+
const pipeline = new RenderPipeline(renderer);
|
|
111
|
+
pipeline.outputNode = aaOutput;
|
|
112
|
+
// Dedicated render target — pipeline outputs here instead of the canvas,
|
|
113
|
+
// so R3F's main render loop can never overwrite our capture.
|
|
114
|
+
const { width, height } = renderer.domElement;
|
|
115
|
+
const renderTarget = new RenderTarget(width, height, { depthBuffer: true });
|
|
116
|
+
return {
|
|
117
|
+
applyEnvironment: ({ theme, transparent, grade, edges, camera: captureCamera }) => {
|
|
118
|
+
const sceneTheme = getSceneTheme(theme);
|
|
119
|
+
inkMixUniform.value = edges === 'off' ? 0 : 1;
|
|
120
|
+
inkOpacityUniform.value = edges === 'strong' ? 1 : 0.5;
|
|
121
|
+
inkColorUniform.value.set(edgeColorFor(sceneTheme.background));
|
|
122
|
+
inkOpacityScaleUniform.value = edgeOpacityScaleFor(sceneTheme.background);
|
|
123
|
+
bgColorUniform.value.set(sceneTheme.background);
|
|
124
|
+
bgSkyUniform.value.set(sceneTheme.backgroundSky ?? sceneTheme.background);
|
|
125
|
+
bgSkyDeepUniform.value.set(deepSkyColor(sceneTheme.backgroundSky ?? sceneTheme.background));
|
|
126
|
+
bgHazeUniform.value.set(horizonHazeColor(sceneTheme.backgroundSky ?? sceneTheme.background, sceneTheme.appearance));
|
|
127
|
+
bgMixUniform.value = transparent ? 0 : 1;
|
|
128
|
+
gradeMixUniform.value = grade ? 1 : 0;
|
|
129
|
+
// The capture camera never joins the scene graph, so its matrixWorld
|
|
130
|
+
// is only refreshed by the render itself — too late for the backdrop
|
|
131
|
+
// uniforms below.
|
|
132
|
+
captureCamera.updateMatrixWorld();
|
|
133
|
+
bgProjInvUniform.value.copy(captureCamera.projectionMatrixInverse);
|
|
134
|
+
bgCamWorldUniform.value.copy(captureCamera.matrixWorld);
|
|
135
|
+
},
|
|
136
|
+
capture: async ({ captureMode, cropRegion, standardSize }) => {
|
|
137
|
+
const standardW = standardSize?.w ?? THUMBNAIL_WIDTH;
|
|
138
|
+
const standardH = standardSize?.h ?? THUMBNAIL_HEIGHT;
|
|
139
|
+
const { width: captureWidth, height: captureHeight } = renderer.domElement;
|
|
140
|
+
// Resize RT if the canvas dimensions changed
|
|
141
|
+
if (renderTarget.width !== captureWidth || renderTarget.height !== captureHeight) {
|
|
142
|
+
renderTarget.setSize(captureWidth, captureHeight);
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
;
|
|
146
|
+
renderer.setClearAlpha(0);
|
|
147
|
+
renderer.setRenderTarget(renderTarget);
|
|
148
|
+
pipeline.render();
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
renderer.setRenderTarget(null);
|
|
152
|
+
}
|
|
153
|
+
// Let callers restore visibility and other capture policy immediately
|
|
154
|
+
// after the render, before the asynchronous GPU readback begins.
|
|
155
|
+
await Promise.resolve();
|
|
156
|
+
// Read pixels from the RT asynchronously.
|
|
157
|
+
// WebGPU copyTextureToBuffer aligns each row to 256 bytes, so we must
|
|
158
|
+
// depad the rows before constructing ImageData.
|
|
159
|
+
const pixels = (await renderer.readRenderTargetPixelsAsync(renderTarget, 0, 0, captureWidth, captureHeight));
|
|
160
|
+
const actualBytesPerRow = captureWidth * 4;
|
|
161
|
+
const tightTotal = actualBytesPerRow * captureHeight;
|
|
162
|
+
const paddedBytesPerRow = Math.ceil(actualBytesPerRow / 256) * 256;
|
|
163
|
+
// Two readback shapes to handle:
|
|
164
|
+
// - WebGPU (`copyTextureToBuffer`): top-down + 256-byte row padding
|
|
165
|
+
// when width*4 isn't already a multiple of 256.
|
|
166
|
+
// - WebGL2 fallback (iOS Chrome, etc.): tightly-packed but bottom-up
|
|
167
|
+
// (OpenGL framebuffer convention).
|
|
168
|
+
// `isWebGPURenderer` lies — it stays true even when the renderer
|
|
169
|
+
// falls back to the WebGL backend. Inspect the actual backend
|
|
170
|
+
// instead (presence of a GPU device, or backend constructor name).
|
|
171
|
+
const backend = renderer.backend;
|
|
172
|
+
const isWebGPU = !!backend?.device ||
|
|
173
|
+
backend?.isWebGPUBackend === true ||
|
|
174
|
+
backend?.constructor?.name === 'WebGPUBackend';
|
|
175
|
+
let tightPixels;
|
|
176
|
+
if (isWebGPU) {
|
|
177
|
+
// WebGPU: depad rows if needed; orientation is already top-down.
|
|
178
|
+
if (paddedBytesPerRow === actualBytesPerRow) {
|
|
179
|
+
tightPixels = new Uint8ClampedArray(pixels.buffer, pixels.byteOffset, Math.min(pixels.byteLength, tightTotal));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
tightPixels = new Uint8ClampedArray(tightTotal);
|
|
183
|
+
for (let row = 0; row < captureHeight; row++) {
|
|
184
|
+
tightPixels.set(pixels.subarray(row * paddedBytesPerRow, row * paddedBytesPerRow + actualBytesPerRow), row * actualBytesPerRow);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
// WebGL2: tight buffer in bottom-up order — flip rows.
|
|
190
|
+
tightPixels = new Uint8ClampedArray(tightTotal);
|
|
191
|
+
for (let row = 0; row < captureHeight; row++) {
|
|
192
|
+
const srcStart = (captureHeight - 1 - row) * actualBytesPerRow;
|
|
193
|
+
tightPixels.set(pixels.subarray(srcStart, srcStart + actualBytesPerRow), row * actualBytesPerRow);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const imageData = new ImageData(tightPixels, captureWidth, captureHeight);
|
|
197
|
+
const srcCanvas = new OffscreenCanvas(captureWidth, captureHeight);
|
|
198
|
+
srcCanvas.getContext('2d').putImageData(imageData, 0, 0);
|
|
199
|
+
let outW;
|
|
200
|
+
let outH;
|
|
201
|
+
let blob;
|
|
202
|
+
if (captureMode === 'viewport') {
|
|
203
|
+
outW = captureWidth;
|
|
204
|
+
outH = captureHeight;
|
|
205
|
+
const offscreen = new OffscreenCanvas(outW, outH);
|
|
206
|
+
offscreen.getContext('2d').drawImage(srcCanvas, 0, 0);
|
|
207
|
+
blob = await offscreen.convertToBlob({ type: 'image/png' });
|
|
208
|
+
}
|
|
209
|
+
else if (captureMode === 'area' && cropRegion) {
|
|
210
|
+
const sx = Math.round(cropRegion.x * captureWidth);
|
|
211
|
+
const sy = Math.round(cropRegion.y * captureHeight);
|
|
212
|
+
outW = Math.round(cropRegion.width * captureWidth);
|
|
213
|
+
outH = Math.round(cropRegion.height * captureHeight);
|
|
214
|
+
const offscreen = new OffscreenCanvas(outW, outH);
|
|
215
|
+
offscreen.getContext('2d').drawImage(srcCanvas, sx, sy, outW, outH, 0, 0, outW, outH);
|
|
216
|
+
blob = await offscreen.convertToBlob({ type: 'image/png' });
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// Standard: center-crop to the requested aspect (default 1920×1080)
|
|
220
|
+
const srcAspect = captureWidth / captureHeight;
|
|
221
|
+
const dstAspect = standardW / standardH;
|
|
222
|
+
let sx = 0;
|
|
223
|
+
let sy = 0;
|
|
224
|
+
let sWidth = captureWidth;
|
|
225
|
+
let sHeight = captureHeight;
|
|
226
|
+
if (srcAspect > dstAspect) {
|
|
227
|
+
sWidth = Math.round(captureHeight * dstAspect);
|
|
228
|
+
sx = Math.round((captureWidth - sWidth) / 2);
|
|
229
|
+
}
|
|
230
|
+
else if (srcAspect < dstAspect) {
|
|
231
|
+
sHeight = Math.round(captureWidth / dstAspect);
|
|
232
|
+
sy = Math.round((captureHeight - sHeight) / 2);
|
|
233
|
+
}
|
|
234
|
+
outW = standardW;
|
|
235
|
+
outH = standardH;
|
|
236
|
+
const offscreen = new OffscreenCanvas(outW, outH);
|
|
237
|
+
offscreen
|
|
238
|
+
.getContext('2d')
|
|
239
|
+
.drawImage(srcCanvas, sx, sy, sWidth, sHeight, 0, 0, outW, outH);
|
|
240
|
+
blob = await offscreen.convertToBlob({ type: 'image/png' });
|
|
241
|
+
}
|
|
242
|
+
return { blob, outW, outH };
|
|
243
|
+
},
|
|
244
|
+
dispose: () => {
|
|
245
|
+
pipeline.dispose();
|
|
246
|
+
renderTarget.dispose();
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
console.error('[thumbnail] Failed to build post-processing pipeline, will use fallback render.', error);
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BufferGeometry } from 'three';
|
|
2
|
+
export type TerrainPerimeterPoint = {
|
|
3
|
+
x: number;
|
|
4
|
+
z: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function buildTerrainPerimeterFillGeometry(points: readonly TerrainPerimeterPoint[], bottomY: readonly number[], topY: number, epsilon?: number): BufferGeometry | null;
|
|
7
|
+
//# sourceMappingURL=terrain-perimeter-fill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terrain-perimeter-fill.d.ts","sourceRoot":"","sources":["../../src/lib/terrain-perimeter-fill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA+C,MAAM,OAAO,CAAA;AAGnF,MAAM,MAAM,qBAAqB,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5D,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,SAAS,qBAAqB,EAAE,EACxC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,SAAO,GACb,cAAc,GAAG,IAAI,CA2EvB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { BufferGeometry, Float32BufferAttribute, ShapeUtils, Vector2 } from 'three';
|
|
2
|
+
import { ensureRenderableGeometryAttributes } from './csg-utils';
|
|
3
|
+
export function buildTerrainPerimeterFillGeometry(points, bottomY, topY, epsilon = 1e-6) {
|
|
4
|
+
if (points.length < 3 || bottomY.length !== points.length)
|
|
5
|
+
return null;
|
|
6
|
+
if (bottomY.every((y) => y >= topY - epsilon))
|
|
7
|
+
return null;
|
|
8
|
+
let signedArea = 0;
|
|
9
|
+
for (let index = 0; index < points.length; index += 1) {
|
|
10
|
+
const a = points[index];
|
|
11
|
+
const b = points[(index + 1) % points.length];
|
|
12
|
+
signedArea += a.x * b.z - b.x * a.z;
|
|
13
|
+
}
|
|
14
|
+
const counterClockwise = signedArea > 0;
|
|
15
|
+
const positions = [];
|
|
16
|
+
const push = (point, y) => {
|
|
17
|
+
positions.push(point.x, y, point.z);
|
|
18
|
+
};
|
|
19
|
+
for (let index = 0; index < points.length; index += 1) {
|
|
20
|
+
const next = (index + 1) % points.length;
|
|
21
|
+
const a = points[index];
|
|
22
|
+
const b = points[next];
|
|
23
|
+
const ay = bottomY[index];
|
|
24
|
+
const by = bottomY[next];
|
|
25
|
+
if (ay >= topY - epsilon && by >= topY - epsilon)
|
|
26
|
+
continue;
|
|
27
|
+
if (counterClockwise) {
|
|
28
|
+
push(a, topY);
|
|
29
|
+
push(b, by);
|
|
30
|
+
push(a, ay);
|
|
31
|
+
push(a, topY);
|
|
32
|
+
push(b, topY);
|
|
33
|
+
push(b, by);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
push(a, topY);
|
|
37
|
+
push(a, ay);
|
|
38
|
+
push(b, by);
|
|
39
|
+
push(a, topY);
|
|
40
|
+
push(b, by);
|
|
41
|
+
push(b, topY);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const faces = ShapeUtils.triangulateShape(points.map((point) => new Vector2(point.x, point.z)), []);
|
|
45
|
+
for (const face of faces) {
|
|
46
|
+
const [ia, ib, ic] = face;
|
|
47
|
+
if (ia == null || ib == null || ic == null)
|
|
48
|
+
continue;
|
|
49
|
+
if (bottomY[ia] >= topY - epsilon &&
|
|
50
|
+
bottomY[ib] >= topY - epsilon &&
|
|
51
|
+
bottomY[ic] >= topY - epsilon) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const a = points[ia];
|
|
55
|
+
const b = points[ib];
|
|
56
|
+
const c = points[ic];
|
|
57
|
+
const cross = (b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x);
|
|
58
|
+
push(a, bottomY[ia]);
|
|
59
|
+
if (cross >= 0) {
|
|
60
|
+
push(b, bottomY[ib]);
|
|
61
|
+
push(c, bottomY[ic]);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
push(c, bottomY[ic]);
|
|
65
|
+
push(b, bottomY[ib]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (positions.length === 0)
|
|
69
|
+
return null;
|
|
70
|
+
const geometry = new BufferGeometry();
|
|
71
|
+
geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
|
|
72
|
+
geometry.computeVertexNormals();
|
|
73
|
+
ensureRenderableGeometryAttributes(geometry);
|
|
74
|
+
return geometry;
|
|
75
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* three's node system pulls texture uniforms from materials each frame via
|
|
3
|
+
* reference nodes, and several override-material passes (shadow, depth/normal
|
|
4
|
+
* prepasses) copy per-object texture slots onto shared materials whose cached
|
|
5
|
+
* per-mesh node graphs can disagree about a slot's presence. When they do,
|
|
6
|
+
* `TextureNode.update` dereferences a null texture and the exception kills the
|
|
7
|
+
* whole render pass — the scene goes black. Substitute a 1×1 black fallback
|
|
8
|
+
* instead (skipping is not enough: the null would still reach the backend's
|
|
9
|
+
* texture-binding WeakMap). The slot renders black for a frame and recovers
|
|
10
|
+
* as soon as the reference pulls a real value again.
|
|
11
|
+
*/
|
|
12
|
+
export declare function installTextureNodeNullGuard(): void;
|
|
13
|
+
//# sourceMappingURL=texture-node-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"texture-node-guard.d.ts","sourceRoot":"","sources":["../../src/lib/texture-node-guard.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAiBlD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DataTexture, TextureNode } from 'three/webgpu';
|
|
2
|
+
let installed = false;
|
|
3
|
+
let reported = 0;
|
|
4
|
+
let fallbackTexture = null;
|
|
5
|
+
function getFallbackTexture() {
|
|
6
|
+
if (!fallbackTexture) {
|
|
7
|
+
fallbackTexture = new DataTexture(new Uint8Array([0, 0, 0, 255]), 1, 1);
|
|
8
|
+
fallbackTexture.needsUpdate = true;
|
|
9
|
+
}
|
|
10
|
+
return fallbackTexture;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* three's node system pulls texture uniforms from materials each frame via
|
|
14
|
+
* reference nodes, and several override-material passes (shadow, depth/normal
|
|
15
|
+
* prepasses) copy per-object texture slots onto shared materials whose cached
|
|
16
|
+
* per-mesh node graphs can disagree about a slot's presence. When they do,
|
|
17
|
+
* `TextureNode.update` dereferences a null texture and the exception kills the
|
|
18
|
+
* whole render pass — the scene goes black. Substitute a 1×1 black fallback
|
|
19
|
+
* instead (skipping is not enough: the null would still reach the backend's
|
|
20
|
+
* texture-binding WeakMap). The slot renders black for a frame and recovers
|
|
21
|
+
* as soon as the reference pulls a real value again.
|
|
22
|
+
*/
|
|
23
|
+
export function installTextureNodeNullGuard() {
|
|
24
|
+
if (installed)
|
|
25
|
+
return;
|
|
26
|
+
installed = true;
|
|
27
|
+
const prototype = TextureNode.prototype;
|
|
28
|
+
const originalUpdate = prototype.update;
|
|
29
|
+
prototype.update = function update() {
|
|
30
|
+
if (this.value == null) {
|
|
31
|
+
if (reported < 5) {
|
|
32
|
+
reported += 1;
|
|
33
|
+
console.warn(`[viewer] TextureNode ${this.uuid} has no texture — using fallback`);
|
|
34
|
+
}
|
|
35
|
+
this.value = getFallbackTexture();
|
|
36
|
+
}
|
|
37
|
+
originalUpdate.call(this);
|
|
38
|
+
};
|
|
39
|
+
}
|