@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
package/dist/lib/csg-utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
1
2
|
import { Evaluator } from 'three-bvh-csg';
|
|
2
3
|
import { computeBoundsTree } from 'three-mesh-bvh';
|
|
3
4
|
/**
|
|
@@ -7,8 +8,66 @@ import { computeBoundsTree } from 'three-mesh-bvh';
|
|
|
7
8
|
* three-bvh-csg + three-mesh-bvh are viewer-only deps; kinds living
|
|
8
9
|
* in `@pascal-app/nodes` import these through the public surface.
|
|
9
10
|
*/
|
|
11
|
+
function zeroAttribute(count, itemSize) {
|
|
12
|
+
return new THREE.Float32BufferAttribute(new Float32Array(count * itemSize), itemSize);
|
|
13
|
+
}
|
|
14
|
+
function upNormalAttribute(count) {
|
|
15
|
+
const values = new Float32Array(count * 3);
|
|
16
|
+
for (let index = 0; index < count; index += 1) {
|
|
17
|
+
values[index * 3 + 1] = 1;
|
|
18
|
+
}
|
|
19
|
+
return new THREE.Float32BufferAttribute(values, 3);
|
|
20
|
+
}
|
|
21
|
+
function ensureAttributeCount(geometry, name, itemSize, count) {
|
|
22
|
+
const attribute = geometry.getAttribute(name);
|
|
23
|
+
if (attribute?.count === count && attribute.itemSize === itemSize)
|
|
24
|
+
return;
|
|
25
|
+
geometry.setAttribute(name, zeroAttribute(count, itemSize));
|
|
26
|
+
}
|
|
27
|
+
function copyVec2Attribute(attribute) {
|
|
28
|
+
const values = new Float32Array(attribute.count * 2);
|
|
29
|
+
for (let index = 0; index < attribute.count; index += 1) {
|
|
30
|
+
values[index * 2] = attribute.getX(index);
|
|
31
|
+
values[index * 2 + 1] = attribute.getY(index);
|
|
32
|
+
}
|
|
33
|
+
return new THREE.Float32BufferAttribute(values, 2);
|
|
34
|
+
}
|
|
35
|
+
export function ensureRenderableGeometryAttributes(geometry) {
|
|
36
|
+
const position = geometry.getAttribute('position');
|
|
37
|
+
if (!position || position.count === 0 || position.itemSize !== 3) {
|
|
38
|
+
geometry.setIndex(null);
|
|
39
|
+
geometry.clearGroups();
|
|
40
|
+
geometry.setAttribute('position', zeroAttribute(3, 3));
|
|
41
|
+
geometry.setAttribute('normal', upNormalAttribute(3));
|
|
42
|
+
geometry.setAttribute('uv', zeroAttribute(3, 2));
|
|
43
|
+
geometry.setAttribute('uv2', zeroAttribute(3, 2));
|
|
44
|
+
return geometry;
|
|
45
|
+
}
|
|
46
|
+
const count = position.count;
|
|
47
|
+
const normal = geometry.getAttribute('normal');
|
|
48
|
+
if (normal?.count !== count || normal.itemSize !== 3) {
|
|
49
|
+
geometry.deleteAttribute('normal');
|
|
50
|
+
try {
|
|
51
|
+
geometry.computeVertexNormals();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
geometry.deleteAttribute('normal');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const computedNormal = geometry.getAttribute('normal');
|
|
58
|
+
if (computedNormal?.count !== count || computedNormal.itemSize !== 3) {
|
|
59
|
+
geometry.setAttribute('normal', upNormalAttribute(count));
|
|
60
|
+
}
|
|
61
|
+
ensureAttributeCount(geometry, 'uv', 2, count);
|
|
62
|
+
const uv = geometry.getAttribute('uv');
|
|
63
|
+
const uv2 = geometry.getAttribute('uv2');
|
|
64
|
+
if (uv2?.count !== count || uv2.itemSize !== 2) {
|
|
65
|
+
geometry.setAttribute('uv2', copyVec2Attribute(uv));
|
|
66
|
+
}
|
|
67
|
+
return geometry;
|
|
68
|
+
}
|
|
10
69
|
export function csgGeometry(brush) {
|
|
11
|
-
return brush.geometry;
|
|
70
|
+
return ensureRenderableGeometryAttributes(brush.geometry);
|
|
12
71
|
}
|
|
13
72
|
export function csgMaterials(brush) {
|
|
14
73
|
const mat = brush.material;
|
|
@@ -17,7 +76,7 @@ export function csgMaterials(brush) {
|
|
|
17
76
|
export const csgEvaluator = new Evaluator();
|
|
18
77
|
csgEvaluator.useGroups = true;
|
|
19
78
|
csgEvaluator.consolidateGroups = false;
|
|
20
|
-
csgEvaluator.attributes = ['position', 'normal', 'uv'];
|
|
79
|
+
csgEvaluator.attributes = ['position', 'normal', 'uv', 'uv2'];
|
|
21
80
|
export function computeGeometryBoundsTree(geometry) {
|
|
22
81
|
;
|
|
23
82
|
geometry.computeBoundsTree =
|
|
@@ -25,9 +84,10 @@ export function computeGeometryBoundsTree(geometry) {
|
|
|
25
84
|
geometry.computeBoundsTree({ maxLeafSize: 10 });
|
|
26
85
|
}
|
|
27
86
|
export function prepareBrushForCSG(brush) {
|
|
87
|
+
ensureRenderableGeometryAttributes(brush.geometry);
|
|
28
88
|
computeGeometryBoundsTree(brush.geometry);
|
|
29
89
|
brush.updateMatrixWorld();
|
|
30
90
|
}
|
|
31
|
-
// Re-export Brush + SUBTRACTION + ADDITION so kinds don't need a
|
|
32
|
-
// `three-bvh-csg` dependency.
|
|
33
|
-
export { ADDITION, Brush, SUBTRACTION } from 'three-bvh-csg';
|
|
91
|
+
// Re-export Brush + SUBTRACTION + ADDITION + INTERSECTION so kinds don't need a
|
|
92
|
+
// direct `three-bvh-csg` dependency.
|
|
93
|
+
export { ADDITION, Brush, INTERSECTION, SUBTRACTION } from 'three-bvh-csg';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csg-utils.test.d.ts","sourceRoot":"","sources":["../../src/lib/csg-utils.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
|
|
2
|
+
// depend on @types/bun so the import type is unresolved at compile time.
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
import { ensureRenderableGeometryAttributes } from './csg-utils';
|
|
6
|
+
describe('ensureRenderableGeometryAttributes', () => {
|
|
7
|
+
test('fills missing render attributes to match position count', () => {
|
|
8
|
+
const geometry = new THREE.BufferGeometry();
|
|
9
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), 3));
|
|
10
|
+
ensureRenderableGeometryAttributes(geometry);
|
|
11
|
+
expect(geometry.getAttribute('position')?.count).toBe(3);
|
|
12
|
+
expect(geometry.getAttribute('normal')?.count).toBe(3);
|
|
13
|
+
expect(geometry.getAttribute('uv')?.count).toBe(3);
|
|
14
|
+
expect(geometry.getAttribute('uv2')?.count).toBe(3);
|
|
15
|
+
});
|
|
16
|
+
test('replaces render attributes with the wrong item size', () => {
|
|
17
|
+
const geometry = new THREE.BufferGeometry();
|
|
18
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), 3));
|
|
19
|
+
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(new Float32Array([0, 0, 0]), 1));
|
|
20
|
+
geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(new Float32Array([0, 0, 0]), 1));
|
|
21
|
+
ensureRenderableGeometryAttributes(geometry);
|
|
22
|
+
expect(geometry.getAttribute('uv')?.itemSize).toBe(2);
|
|
23
|
+
expect(geometry.getAttribute('uv2')?.itemSize).toBe(2);
|
|
24
|
+
expect(geometry.getAttribute('uv')?.count).toBe(3);
|
|
25
|
+
expect(geometry.getAttribute('uv2')?.count).toBe(3);
|
|
26
|
+
});
|
|
27
|
+
test('copies uv into uv2 without depending on backing array layout', () => {
|
|
28
|
+
const geometry = new THREE.BufferGeometry();
|
|
29
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), 3));
|
|
30
|
+
geometry.setAttribute('uv', new THREE.InterleavedBufferAttribute(new THREE.InterleavedBuffer(new Float32Array([0, 0, 7, 1, 0, 8, 0, 1, 9]), 3), 2, 0));
|
|
31
|
+
ensureRenderableGeometryAttributes(geometry);
|
|
32
|
+
const uv2 = geometry.getAttribute('uv2');
|
|
33
|
+
expect(Array.from(uv2.array)).toEqual([0, 0, 1, 0, 0, 1]);
|
|
34
|
+
});
|
|
35
|
+
test('replaces empty geometries with a degenerate renderable triangle', () => {
|
|
36
|
+
const geometry = new THREE.BufferGeometry();
|
|
37
|
+
ensureRenderableGeometryAttributes(geometry);
|
|
38
|
+
expect(geometry.getIndex()).toBeNull();
|
|
39
|
+
expect(geometry.groups).toHaveLength(0);
|
|
40
|
+
expect(geometry.getAttribute('position')?.count).toBe(3);
|
|
41
|
+
expect(geometry.getAttribute('normal')?.count).toBe(3);
|
|
42
|
+
expect(geometry.getAttribute('uv')?.count).toBe(3);
|
|
43
|
+
expect(geometry.getAttribute('uv2')?.count).toBe(3);
|
|
44
|
+
});
|
|
45
|
+
});
|
package/dist/lib/edge-style.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edge-style.d.ts","sourceRoot":"","sources":["../../src/lib/edge-style.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"edge-style.d.ts","sourceRoot":"","sources":["../../src/lib/edge-style.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAA;AAgBhD,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAWvD;AAID,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9D"}
|
package/dist/lib/edge-style.js
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
// near-black lines, dark backgrounds get near-white. Same rule Mapbox uses for
|
|
3
|
-
// label outlines, so edges stay legible across every scene theme.
|
|
4
|
-
export function edgeColorFor(background) {
|
|
1
|
+
function lumaOf(background) {
|
|
5
2
|
const hex = background.replace('#', '');
|
|
6
3
|
const r = Number.parseInt(hex.slice(0, 2), 16) / 255;
|
|
7
4
|
const g = Number.parseInt(hex.slice(2, 4), 16) / 255;
|
|
8
5
|
const b = Number.parseInt(hex.slice(4, 6), 16) / 255;
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
7
|
+
}
|
|
8
|
+
// Ink line colour follows background luminance — light backgrounds get
|
|
9
|
+
// near-black lines (the rule Mapbox uses for label outlines). Dark backgrounds
|
|
10
|
+
// get a tint *derived from the background* rather than a near-white constant:
|
|
11
|
+
// full-white lines glow harshly against night/twilight scenes, while lifting
|
|
12
|
+
// the background's own hue toward white stays legible but reads native to the
|
|
13
|
+
// theme.
|
|
14
|
+
export function edgeColorFor(background) {
|
|
15
|
+
if (lumaOf(background) > 0.5)
|
|
16
|
+
return '#1a1d24';
|
|
17
|
+
const hex = background.replace('#', '');
|
|
18
|
+
let out = '#';
|
|
19
|
+
for (let i = 0; i < 3; i++) {
|
|
20
|
+
const c = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16) / 255;
|
|
21
|
+
out += Math.round((c + (1 - c) * 0.42) * 255)
|
|
22
|
+
.toString(16)
|
|
23
|
+
.padStart(2, '0');
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
// Dark scenes additionally run the ink slightly transparent — even a muted
|
|
28
|
+
// tint at full alpha reads as glowing wireframe against a low-luma backdrop.
|
|
29
|
+
export function edgeOpacityScaleFor(background) {
|
|
30
|
+
return lumaOf(background) > 0.5 ? 1 : 0.7;
|
|
11
31
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Box3 } from 'three';
|
|
2
|
+
export declare const DEFAULT_FRAMING_EXCLUDED_TYPES: readonly ["site", "scan", "guide", "spawn"];
|
|
3
|
+
export declare function heroCameraPose({ boxes, aspect, aim, fovDeg, azimuthRad, elevationRad, padding, minDistance, frameShift, }: {
|
|
4
|
+
boxes: Box3 | readonly Box3[];
|
|
5
|
+
aspect: number;
|
|
6
|
+
/** Where the camera looks (frame center). Defaults to the union-box center;
|
|
7
|
+
* pass the building's center to keep it dead-center while outlying boxes
|
|
8
|
+
* (lot plate, far palms) simply take asymmetric margin — the corner fit
|
|
9
|
+
* still guarantees every box stays in frame. */
|
|
10
|
+
aim?: [number, number, number];
|
|
11
|
+
fovDeg?: number;
|
|
12
|
+
azimuthRad?: number;
|
|
13
|
+
elevationRad?: number;
|
|
14
|
+
padding?: number;
|
|
15
|
+
minDistance?: number;
|
|
16
|
+
/** Fraction of the frustum half-height to drop the aim by. 0 keeps the aim
|
|
17
|
+
* (the building center) exactly at frame center. */
|
|
18
|
+
frameShift?: number;
|
|
19
|
+
}): {
|
|
20
|
+
position: [number, number, number];
|
|
21
|
+
target: [number, number, number];
|
|
22
|
+
};
|
|
23
|
+
export declare function unionRegisteredNodeBounds({ excludeTypes, }: {
|
|
24
|
+
excludeTypes: readonly string[];
|
|
25
|
+
}): Box3 | null;
|
|
26
|
+
export type HeroFraming = {
|
|
27
|
+
/** Fit constraints — everything that must stay in frame. */
|
|
28
|
+
boxes: Box3[];
|
|
29
|
+
/** Frame center: plate/structure center on XZ, building center on Y. */
|
|
30
|
+
aim: [number, number, number];
|
|
31
|
+
/** 45° to the dominant facade (falls back to world 45°). */
|
|
32
|
+
azimuthRad: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Framing for a scene hero shot. The base set is the built structure (walls,
|
|
36
|
+
* roofs, slabs, …) plus any intentionally-shaped site plate; an item joins
|
|
37
|
+
* only when it sits near that base — one palm at the far lot corner must not
|
|
38
|
+
* shrink the building. `building`/`level` container groups are skipped (their
|
|
39
|
+
* Object3D holds the whole subtree). The aim keeps the building dead-center:
|
|
40
|
+
* XZ from the plate+structure union, Y from the structure alone so the
|
|
41
|
+
* building — not the ground — is vertically centered. Scenes with no
|
|
42
|
+
* structure (pure furniture arrangements) fall back to framing every
|
|
43
|
+
* non-helper node.
|
|
44
|
+
*/
|
|
45
|
+
export declare function computeHeroFraming(): HeroFraming | null;
|
|
46
|
+
export declare function temporarilyHideNodeTypes(types: readonly string[]): () => void;
|
|
47
|
+
//# sourceMappingURL=hero-pose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hero-pose.d.ts","sourceRoot":"","sources":["../../src/lib/hero-pose.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAA0B,MAAM,OAAO,CAAA;AAEpD,eAAO,MAAM,8BAA8B,6CAA8C,CAAA;AAEzF,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,MAAM,EACN,GAAG,EACH,MAAW,EACX,UAAwB,EACxB,YAAmC,EACnC,OAAc,EACd,WAAe,EACf,UAAc,GACf,EAAE;IACD,KAAK,EAAE,IAAI,GAAG,SAAS,IAAI,EAAE,CAAA;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd;;;qDAGiD;IACjD,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;yDACqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GAAG;IACF,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC,CA2DA;AAED,wBAAgB,yBAAyB,CAAC,EACxC,YAAY,GACb,EAAE;IACD,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CAChC,GAAG,IAAI,GAAG,IAAI,CAgBd;AAiFD,MAAM,MAAM,WAAW,GAAG;IACxB,4DAA4D;IAC5D,KAAK,EAAE,IAAI,EAAE,CAAA;IACb,wEAAwE;IACxE,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,IAAI,WAAW,GAAG,IAAI,CAuCvD;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,IAAI,CAiB7E"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { sceneRegistry, useScene } from '@pascal-app/core';
|
|
2
|
+
import { Box3, Vector3 } from 'three';
|
|
3
|
+
export const DEFAULT_FRAMING_EXCLUDED_TYPES = ['site', 'scan', 'guide', 'spawn'];
|
|
4
|
+
export function heroCameraPose({ boxes, aspect, aim, fovDeg = 60, azimuthRad = Math.PI / 4, elevationRad = (13 * Math.PI) / 180, padding = 1.03, minDistance = 4, frameShift = 0, }) {
|
|
5
|
+
const list = Array.isArray(boxes) ? boxes : [boxes];
|
|
6
|
+
const union = new Box3();
|
|
7
|
+
for (const box of list)
|
|
8
|
+
union.union(box);
|
|
9
|
+
const center = aim ? new Vector3(aim[0], aim[1], aim[2]) : union.getCenter(new Vector3());
|
|
10
|
+
const tanVertical = Math.tan(((fovDeg / 2) * Math.PI) / 180);
|
|
11
|
+
const tanHorizontal = tanVertical * aspect;
|
|
12
|
+
// Camera basis for the chosen azimuth/elevation: `dir` points from the
|
|
13
|
+
// target toward the camera, `forward` is the view direction.
|
|
14
|
+
const dir = new Vector3(Math.sin(azimuthRad) * Math.cos(elevationRad), Math.sin(elevationRad), Math.cos(azimuthRad) * Math.cos(elevationRad));
|
|
15
|
+
const forward = dir.clone().negate();
|
|
16
|
+
const right = new Vector3().crossVectors(forward, new Vector3(0, 1, 0)).normalize();
|
|
17
|
+
const up = new Vector3().crossVectors(right, forward);
|
|
18
|
+
// Exact fit: for every corner of every per-node box, the minimal camera
|
|
19
|
+
// distance along `dir` that keeps it inside the frustum. Fitting the corners
|
|
20
|
+
// of the UNION box instead reads far too wide at a diagonal azimuth — the
|
|
21
|
+
// union AABB's extreme corners are the empty diamond tips nothing actually
|
|
22
|
+
// occupies. (A bounding-sphere fit is worse still for flat, spread scenes.)
|
|
23
|
+
const corner = new Vector3();
|
|
24
|
+
const offset = new Vector3();
|
|
25
|
+
let distance = minDistance;
|
|
26
|
+
for (const box of list) {
|
|
27
|
+
for (const x of [box.min.x, box.max.x]) {
|
|
28
|
+
for (const y of [box.min.y, box.max.y]) {
|
|
29
|
+
for (const z of [box.min.z, box.max.z]) {
|
|
30
|
+
corner.set(x, y, z);
|
|
31
|
+
offset.subVectors(corner, center);
|
|
32
|
+
const lateral = offset.dot(right);
|
|
33
|
+
const vertical = offset.dot(up);
|
|
34
|
+
const depth = offset.dot(forward);
|
|
35
|
+
distance = Math.max(distance, (Math.abs(lateral) / tanHorizontal - depth) * padding, (Math.abs(vertical) / tanVertical - depth) * padding);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Reframe: slide aim and camera together along the view-up axis — pure
|
|
41
|
+
// composition shift, no perspective change.
|
|
42
|
+
const drop = up.clone().multiplyScalar(-frameShift * distance * tanVertical);
|
|
43
|
+
const target = center.clone().add(drop);
|
|
44
|
+
return {
|
|
45
|
+
position: [
|
|
46
|
+
target.x + dir.x * distance,
|
|
47
|
+
target.y + dir.y * distance,
|
|
48
|
+
target.z + dir.z * distance,
|
|
49
|
+
],
|
|
50
|
+
target: [target.x, target.y, target.z],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function unionRegisteredNodeBounds({ excludeTypes, }) {
|
|
54
|
+
const excluded = new Set(excludeTypes);
|
|
55
|
+
const result = new Box3();
|
|
56
|
+
for (const [type, ids] of Object.entries(sceneRegistry.byType)) {
|
|
57
|
+
if (excluded.has(type))
|
|
58
|
+
continue;
|
|
59
|
+
for (const id of ids) {
|
|
60
|
+
const object = sceneRegistry.nodes.get(id);
|
|
61
|
+
if (!object)
|
|
62
|
+
continue;
|
|
63
|
+
const bounds = new Box3().setFromObject(object);
|
|
64
|
+
if (!bounds.isEmpty())
|
|
65
|
+
result.union(bounds);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result.isEmpty() ? null : result;
|
|
69
|
+
}
|
|
70
|
+
function perNodeBoxes(excludeTypes) {
|
|
71
|
+
const excluded = new Set(excludeTypes);
|
|
72
|
+
const boxes = [];
|
|
73
|
+
for (const [type, ids] of Object.entries(sceneRegistry.byType)) {
|
|
74
|
+
if (excluded.has(type))
|
|
75
|
+
continue;
|
|
76
|
+
for (const id of ids) {
|
|
77
|
+
const object = sceneRegistry.nodes.get(id);
|
|
78
|
+
if (!object)
|
|
79
|
+
continue;
|
|
80
|
+
const bounds = new Box3().setFromObject(object);
|
|
81
|
+
if (!bounds.isEmpty())
|
|
82
|
+
boxes.push(bounds);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return boxes;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Matches the `SiteNode` bootstrap polygon (a 30×30 square at the origin) —
|
|
89
|
+
* same rule as the editor's `computeSceneBoundsXZ`: an untouched default lot
|
|
90
|
+
* says nothing about the user's intent, so it shouldn't drive framing.
|
|
91
|
+
*/
|
|
92
|
+
function isDefaultSitePolygon(points) {
|
|
93
|
+
if (points.length !== 4)
|
|
94
|
+
return false;
|
|
95
|
+
const expected = [
|
|
96
|
+
[-15, -15],
|
|
97
|
+
[15, -15],
|
|
98
|
+
[15, 15],
|
|
99
|
+
[-15, 15],
|
|
100
|
+
];
|
|
101
|
+
for (let i = 0; i < 4; i++) {
|
|
102
|
+
const p = points[i];
|
|
103
|
+
const e = expected[i];
|
|
104
|
+
if (!Array.isArray(p) || p.length < 2)
|
|
105
|
+
return false;
|
|
106
|
+
if (p[0] !== e[0] || p[1] !== e[1])
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/** Flat boxes for intentionally-shaped site plates, from scene DATA — the
|
|
112
|
+
* site's Object3D can't be measured (it carries the ±400 m horizon disc). */
|
|
113
|
+
function sitePlateBoxes() {
|
|
114
|
+
const boxes = [];
|
|
115
|
+
for (const node of Object.values(useScene.getState().nodes)) {
|
|
116
|
+
if (node.type !== 'site')
|
|
117
|
+
continue;
|
|
118
|
+
const polygon = node.polygon;
|
|
119
|
+
const points = polygon?.points;
|
|
120
|
+
if (!Array.isArray(points) || isDefaultSitePolygon(points))
|
|
121
|
+
continue;
|
|
122
|
+
const box = new Box3();
|
|
123
|
+
for (const point of points) {
|
|
124
|
+
if (Array.isArray(point) && point.length >= 2) {
|
|
125
|
+
box.expandByPoint(new Vector3(Number(point[0]), 0, Number(point[1])));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!box.isEmpty())
|
|
129
|
+
boxes.push(box);
|
|
130
|
+
}
|
|
131
|
+
return boxes;
|
|
132
|
+
}
|
|
133
|
+
/** Dominant wall direction folded to a 90°-periodic axis (length-weighted
|
|
134
|
+
* vector sum of 4θ), so the hero azimuth sits at a true 45° to the building's
|
|
135
|
+
* facades no matter how the user rotated their plan. */
|
|
136
|
+
function dominantWallYaw() {
|
|
137
|
+
let vx = 0;
|
|
138
|
+
let vz = 0;
|
|
139
|
+
for (const node of Object.values(useScene.getState().nodes)) {
|
|
140
|
+
if (node.type !== 'wall')
|
|
141
|
+
continue;
|
|
142
|
+
const { start, end } = node;
|
|
143
|
+
if (!(Array.isArray(start) && Array.isArray(end)))
|
|
144
|
+
continue;
|
|
145
|
+
const dx = Number(end[0]) - Number(start[0]);
|
|
146
|
+
const dz = Number(end[1]) - Number(start[1]);
|
|
147
|
+
const length = Math.hypot(dx, dz);
|
|
148
|
+
if (!(Number.isFinite(length) && length > 0.01))
|
|
149
|
+
continue;
|
|
150
|
+
const theta = Math.atan2(dz, dx);
|
|
151
|
+
vx += length * Math.cos(4 * theta);
|
|
152
|
+
vz += length * Math.sin(4 * theta);
|
|
153
|
+
}
|
|
154
|
+
if (vx === 0 && vz === 0)
|
|
155
|
+
return null;
|
|
156
|
+
return Math.atan2(vz, vx) / 4;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Framing for a scene hero shot. The base set is the built structure (walls,
|
|
160
|
+
* roofs, slabs, …) plus any intentionally-shaped site plate; an item joins
|
|
161
|
+
* only when it sits near that base — one palm at the far lot corner must not
|
|
162
|
+
* shrink the building. `building`/`level` container groups are skipped (their
|
|
163
|
+
* Object3D holds the whole subtree). The aim keeps the building dead-center:
|
|
164
|
+
* XZ from the plate+structure union, Y from the structure alone so the
|
|
165
|
+
* building — not the ground — is vertically centered. Scenes with no
|
|
166
|
+
* structure (pure furniture arrangements) fall back to framing every
|
|
167
|
+
* non-helper node.
|
|
168
|
+
*/
|
|
169
|
+
export function computeHeroFraming() {
|
|
170
|
+
const structural = perNodeBoxes([...DEFAULT_FRAMING_EXCLUDED_TYPES, 'item', 'building', 'level']);
|
|
171
|
+
if (structural.length === 0) {
|
|
172
|
+
const all = perNodeBoxes([...DEFAULT_FRAMING_EXCLUDED_TYPES, 'building', 'level']);
|
|
173
|
+
if (all.length === 0)
|
|
174
|
+
return null;
|
|
175
|
+
const union = new Box3();
|
|
176
|
+
for (const box of all)
|
|
177
|
+
union.union(box);
|
|
178
|
+
const center = union.getCenter(new Vector3());
|
|
179
|
+
return { boxes: all, aim: [center.x, center.y, center.z], azimuthRad: Math.PI / 4 };
|
|
180
|
+
}
|
|
181
|
+
const structuralUnion = new Box3();
|
|
182
|
+
for (const box of structural)
|
|
183
|
+
structuralUnion.union(box);
|
|
184
|
+
const plates = sitePlateBoxes();
|
|
185
|
+
const groundUnion = structuralUnion.clone();
|
|
186
|
+
for (const box of plates)
|
|
187
|
+
groundUnion.union(box);
|
|
188
|
+
const nearby = groundUnion
|
|
189
|
+
.clone()
|
|
190
|
+
.expandByVector(groundUnion.getSize(new Vector3()).multiplyScalar(0.15));
|
|
191
|
+
const boxes = [...structural, ...plates];
|
|
192
|
+
const itemIds = sceneRegistry.byType.item;
|
|
193
|
+
for (const id of itemIds) {
|
|
194
|
+
const object = sceneRegistry.nodes.get(id);
|
|
195
|
+
if (!object)
|
|
196
|
+
continue;
|
|
197
|
+
const bounds = new Box3().setFromObject(object);
|
|
198
|
+
if (!bounds.isEmpty() && nearby.intersectsBox(bounds))
|
|
199
|
+
boxes.push(bounds);
|
|
200
|
+
}
|
|
201
|
+
const groundCenter = groundUnion.getCenter(new Vector3());
|
|
202
|
+
const structuralCenter = structuralUnion.getCenter(new Vector3());
|
|
203
|
+
const yaw = dominantWallYaw();
|
|
204
|
+
return {
|
|
205
|
+
boxes,
|
|
206
|
+
aim: [groundCenter.x, structuralCenter.y, groundCenter.z],
|
|
207
|
+
azimuthRad: Math.PI / 4 - (yaw ?? 0),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export function temporarilyHideNodeTypes(types) {
|
|
211
|
+
const saved = new Map();
|
|
212
|
+
for (const type of types) {
|
|
213
|
+
const ids = sceneRegistry.byType[type];
|
|
214
|
+
ids.forEach((id) => {
|
|
215
|
+
const node = sceneRegistry.nodes.get(id);
|
|
216
|
+
if (node) {
|
|
217
|
+
saved.set(node, node.visible);
|
|
218
|
+
node.visible = false;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return () => {
|
|
223
|
+
saved.forEach((wasVisible, node) => {
|
|
224
|
+
node.visible = wasVisible;
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
}
|
package/dist/lib/ink-edges.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ink-edges.d.ts","sourceRoot":"","sources":["../../src/lib/ink-edges.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ink-edges.d.ts","sourceRoot":"","sources":["../../src/lib/ink-edges.ts"],"names":[],"mappings":"AAoBA,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,MAAM,EACN,OAAO,EACP,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,GAAG,CAAA;IACb,SAAS,EAAE,GAAG,CAAA;IACd,QAAQ,EAAE,GAAG,CAAA;IAKb,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,GAAG,CAAA;IACZ,QAAQ,EAAE,GAAG,CAAA;CACd,OAsCA"}
|
package/dist/lib/ink-edges.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { abs,
|
|
1
|
+
import { abs, float, max, min, mix, screenSize, screenUV, smoothstep, vec2 } from 'three/tsl';
|
|
2
|
+
import { unpackRGBToNormal } from './tsl-compat';
|
|
2
3
|
// Screen-space ink outline (SketchUp / Moebius look). Reads the scene-pass
|
|
3
4
|
// depth + normal MRT and inks two signals:
|
|
4
5
|
//
|
|
@@ -29,15 +30,20 @@ export function inkedEdges({ depthTex, normalTex, inkColor, radius, opacity, sce
|
|
|
29
30
|
const noiseGate = smoothstep(float(0.00002), float(0.00006), depthLap);
|
|
30
31
|
// ≈ metres of step / near (near≈0.1): ~5cm starts a line, ~25cm solid.
|
|
31
32
|
const depthEdge = smoothstep(float(0.5), float(2.5), depthMetric).mul(noiseGate);
|
|
32
|
-
const nC =
|
|
33
|
-
const nR =
|
|
34
|
-
const nL =
|
|
35
|
-
const nU =
|
|
36
|
-
const nD =
|
|
33
|
+
const nC = unpackRGBToNormal(normalTex.sample(uvN)).normalize();
|
|
34
|
+
const nR = unpackRGBToNormal(normalTex.sample(uvN.add(vec2(px.x, 0)))).normalize();
|
|
35
|
+
const nL = unpackRGBToNormal(normalTex.sample(uvN.sub(vec2(px.x, 0)))).normalize();
|
|
36
|
+
const nU = unpackRGBToNormal(normalTex.sample(uvN.add(vec2(0, px.y)))).normalize();
|
|
37
|
+
const nD = unpackRGBToNormal(normalTex.sample(uvN.sub(vec2(0, px.y)))).normalize();
|
|
38
|
+
// Ink is a near/mid-field affordance: fade it out with raw depth so the
|
|
39
|
+
// horizon (the infinite ground disc vanishing against the backdrop) and
|
|
40
|
+
// other far-field depth cliffs never draw a line across the sky junction.
|
|
41
|
+
// ~full ink below ≈150 m, none beyond ≈350 m (perspective near 0.1/far 1000).
|
|
42
|
+
const distanceFade = float(1).sub(smoothstep(float(0.9994), float(0.9998), dC));
|
|
37
43
|
const nDiff = max(max(float(1).sub(nC.dot(nR)), float(1).sub(nC.dot(nL))), max(float(1).sub(nC.dot(nU)), float(1).sub(nC.dot(nD))));
|
|
38
44
|
const normalEdge = smoothstep(float(0.01), float(0.05), nDiff);
|
|
39
45
|
// TSL's typed overloads are finicky across versions; the runtime is proven in
|
|
40
46
|
// the aesthetic sandbox, so cast at the mask/mix boundary.
|
|
41
|
-
const edgeMask = min(max(depthEdge, normalEdge).mul(opacity), float(1));
|
|
47
|
+
const edgeMask = min(max(depthEdge, normalEdge).mul(opacity).mul(distanceFade), float(1));
|
|
42
48
|
return mix(sceneRgb, inkColor, edgeMask);
|
|
43
49
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
|
2
|
+
/**
|
|
3
|
+
* KTX2Loader that survives block-misaligned textures. WebGPU rejects
|
|
4
|
+
* block-compressed textures whose base dimensions aren't multiples of 4;
|
|
5
|
+
* three's KTX2Loader only warns and transcodes anyway, and the resulting
|
|
6
|
+
* invalid texture poisons every render pass that binds it (endless
|
|
7
|
+
* "Invalid BindGroup … Invalid CommandBuffer" spam — baked GLBs carrying an
|
|
8
|
+
* odd-sized user-item texture triggered exactly this). Such payloads are
|
|
9
|
+
* routed to a fallback loader whose support flags report no compressed
|
|
10
|
+
* formats, which makes the transcoder emit plain RGBA32 — uncompressed
|
|
11
|
+
* textures have no alignment requirement.
|
|
12
|
+
*/
|
|
13
|
+
declare class AlignmentSafeKTX2Loader extends KTX2Loader {
|
|
14
|
+
private rgbaFallback;
|
|
15
|
+
private fallbackLoader;
|
|
16
|
+
/** Both `load()` and GLTFLoader's KHR_texture_basisu path funnel through
|
|
17
|
+
* this internal — overriding it covers every entry point. */
|
|
18
|
+
_createTexture(buffer: ArrayBuffer, config?: Record<string, unknown>): Promise<unknown>;
|
|
19
|
+
dispose(): this;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Single shared KTX2 loader for the whole viewer — used both by the GLB loader
|
|
23
|
+
* (`use-gltf-ktx2`) and by catalog finish textures (`materials.ts`). KTX2 must
|
|
24
|
+
* be transcoded at load via the Basis WASM, and `detectSupport(renderer)` has to
|
|
25
|
+
* run once before any `.ktx2` is loaded so the loader picks a GPU format the
|
|
26
|
+
* device supports. `ensureKtx2Support` is idempotent per renderer and is called
|
|
27
|
+
* from the viewer root the moment the renderer is ready (even when no GLB is in
|
|
28
|
+
* the scene, so catalog `.ktx2` finishes still load).
|
|
29
|
+
*/
|
|
30
|
+
export declare const ktx2Loader: AlignmentSafeKTX2Loader;
|
|
31
|
+
/**
|
|
32
|
+
* Resolves once `detectSupport` has succeeded for any renderer. `.ktx2` loads
|
|
33
|
+
* issued before that point would throw inside KTX2Loader ("Missing
|
|
34
|
+
* initialization with `.detectSupport( renderer )`"), so texture loaders await
|
|
35
|
+
* this instead of failing — covers materials created while the renderer is
|
|
36
|
+
* still initializing (e.g. a standalone capture canvas).
|
|
37
|
+
*/
|
|
38
|
+
export declare function whenKtx2Ready(): Promise<void>;
|
|
39
|
+
/** Returns true once support has been detected for this renderer (KTX2 safe to load). */
|
|
40
|
+
export declare function ensureKtx2Support(renderer: unknown): boolean;
|
|
41
|
+
export declare function configureKtx2Support<T>(loader: {
|
|
42
|
+
setKTX2Loader: (ktx2: T) => unknown;
|
|
43
|
+
}, renderer: unknown): boolean;
|
|
44
|
+
export declare function isKtx2Url(url: string): boolean;
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=ktx2-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ktx2-loader.d.ts","sourceRoot":"","sources":["../../src/lib/ktx2-loader.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAA;AAwBrE;;;;;;;;;;GAUG;AACH,cAAM,uBAAwB,SAAQ,UAAU;IAC9C,OAAO,CAAC,YAAY,CAA0B;IAE9C,OAAO,CAAC,cAAc;IAkBtB;kEAC8D;IACxD,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAmC7F,OAAO,IAAI,IAAI;CAMhB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,yBAAgC,CAAA;AAWvD;;;;;;GAMG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAE7C;AAED,yFAAyF;AACzF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAkB5D;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,MAAM,EAAE;IAAE,aAAa,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAA;CAAE,EAC/C,QAAQ,EAAE,OAAO,GAChB,OAAO,CAIT;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9C"}
|