@pascal-app/viewer 0.9.1 → 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 +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 +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 +19 -2
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +104 -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 +179 -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 +18 -10
- package/dist/components/viewer/walkthrough-controls.d.ts +1 -0
- 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 +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 +19 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -8
- 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/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 +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 +10 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +10 -0
- package/dist/lib/materials.d.ts +14 -1
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +103 -26
- 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/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 +60 -18
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +175 -3
- 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.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +4 -1
- 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/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 +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.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 +5 -3
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +41 -23
- package/dist/systems/slab/slab-system.test.js +3 -2
- package/dist/systems/stair/stair-system.js +6 -3
- 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 +46 -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 +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 +329 -174
- 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
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { abs, exp, mix, smoothstep } from 'three/tsl';
|
|
2
|
+
/**
|
|
3
|
+
* Shared backdrop gradient: flat background looking down, a warm haze band
|
|
4
|
+
* hugging the horizon, the theme sky arriving just above it and deepening
|
|
5
|
+
* toward the zenith. One formula serves the post-processing backdrop and the
|
|
6
|
+
* thumbnail pipeline (per-pixel view ray) AND the site horizon disc's
|
|
7
|
+
* far-field dissolve (per-fragment view direction) — the disc converges to
|
|
8
|
+
* exactly this colour, so ground and backdrop meet without a seam from any
|
|
9
|
+
* camera pose.
|
|
10
|
+
*
|
|
11
|
+
* `dirY` is the world-space view direction's Y component (horizon = 0).
|
|
12
|
+
* Colour inputs are TSL nodes (uniforms or literals).
|
|
13
|
+
*/
|
|
14
|
+
export function backdropGradient({ dirY, background, haze, sky, skyDeep, }) {
|
|
15
|
+
// The pale sky arrives fast (full by ≈8° elevation) so eye-level frames
|
|
16
|
+
// actually show it, then deepens toward the zenith for a real gradient —
|
|
17
|
+
// a single pale stop reads as a white void with blue "hiding" up top.
|
|
18
|
+
let base = mix(background, sky, smoothstep(-0.02, 0.14, dirY));
|
|
19
|
+
base = mix(base, skyDeep, smoothstep(0.1, 0.55, dirY));
|
|
20
|
+
// Haze as an exponential glow hugging the horizon tightly: C¹-smooth on
|
|
21
|
+
// both sides, so it brightens the junction without ever drawing an edge.
|
|
22
|
+
const hazeWeight = exp(abs(dirY).mul(-11)).mul(0.8);
|
|
23
|
+
return mix(base, haze, hazeWeight);
|
|
24
|
+
}
|
|
25
|
+
// Warm sun tint the haze pulls toward — aerial perspective is sky-coloured
|
|
26
|
+
// light plus a little sun scatter, not white fog.
|
|
27
|
+
const HAZE_SUN_TINT = [255, 244, 222];
|
|
28
|
+
/**
|
|
29
|
+
* Atmospheric haze at the horizon: the theme's *sky* colour pulled toward a
|
|
30
|
+
* warm sun tint — skyish and sunish at once, so the band reads as part of
|
|
31
|
+
* the sky rather than a white stripe. Dark themes keep it faint (a low glow
|
|
32
|
+
* over the night zenith).
|
|
33
|
+
*/
|
|
34
|
+
export function horizonHazeColor(sky, appearance) {
|
|
35
|
+
const amount = appearance === 'dark' ? 0.25 : 0.5;
|
|
36
|
+
const hex = sky.replace('#', '');
|
|
37
|
+
let out = '#';
|
|
38
|
+
for (let i = 0; i < 3; i++) {
|
|
39
|
+
const c = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
40
|
+
out += Math.round(c + (HAZE_SUN_TINT[i] - c) * amount)
|
|
41
|
+
.toString(16)
|
|
42
|
+
.padStart(2, '0');
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Zenith colour derived from the theme's sky: saturated and darkened in HSL
|
|
48
|
+
* so the hue stays the theme's own (blue studio, lavender sunset, near-black
|
|
49
|
+
* night) while the top of the frame gets real colour depth.
|
|
50
|
+
*/
|
|
51
|
+
export function deepSkyColor(sky) {
|
|
52
|
+
const hex = sky.replace('#', '');
|
|
53
|
+
const r = Number.parseInt(hex.slice(0, 2), 16) / 255;
|
|
54
|
+
const g = Number.parseInt(hex.slice(2, 4), 16) / 255;
|
|
55
|
+
const b = Number.parseInt(hex.slice(4, 6), 16) / 255;
|
|
56
|
+
const max = Math.max(r, g, b);
|
|
57
|
+
const min = Math.min(r, g, b);
|
|
58
|
+
const l = (max + min) / 2;
|
|
59
|
+
const d = max - min;
|
|
60
|
+
let h = 0;
|
|
61
|
+
let s = 0;
|
|
62
|
+
if (d > 0) {
|
|
63
|
+
s = d / (1 - Math.abs(2 * l - 1));
|
|
64
|
+
if (max === r)
|
|
65
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
66
|
+
else if (max === g)
|
|
67
|
+
h = ((b - r) / d + 2) / 6;
|
|
68
|
+
else
|
|
69
|
+
h = ((r - g) / d + 4) / 6;
|
|
70
|
+
}
|
|
71
|
+
const s2 = Math.min(1, s * 1.5 + 0.05);
|
|
72
|
+
const l2 = l * 0.72;
|
|
73
|
+
const c = (1 - Math.abs(2 * l2 - 1)) * s2;
|
|
74
|
+
const x = c * (1 - Math.abs(((h * 6) % 2) - 1));
|
|
75
|
+
const m = l2 - c / 2;
|
|
76
|
+
const sector = Math.floor(h * 6) % 6;
|
|
77
|
+
const rgb = [
|
|
78
|
+
[c, x, 0],
|
|
79
|
+
[x, c, 0],
|
|
80
|
+
[0, c, x],
|
|
81
|
+
[0, x, c],
|
|
82
|
+
[x, 0, c],
|
|
83
|
+
[c, 0, x],
|
|
84
|
+
][sector];
|
|
85
|
+
let out = '#';
|
|
86
|
+
for (const channel of rgb) {
|
|
87
|
+
out += Math.round((channel + m) * 255)
|
|
88
|
+
.toString(16)
|
|
89
|
+
.padStart(2, '0');
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BoxGeometry } from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Rewrite a default `BoxGeometry`'s UVs to world scale — 1 UV unit = 1 metre —
|
|
4
|
+
* so tiled finishes (with `repeat` in tiles-per-metre) render at a consistent
|
|
5
|
+
* real-world scale instead of stretching to fit each face. Matches the
|
|
6
|
+
* world-scale UV convention used by the procedural slab/wall geometry.
|
|
7
|
+
*
|
|
8
|
+
* three.js builds box faces in the fixed order [+X, -X, +Y, -Y, +Z, -Z], four
|
|
9
|
+
* verts each, with UVs spanning 0→1 across the face. Each face's two in-plane
|
|
10
|
+
* dimensions differ, so we scale U/V per face by that face's size in metres.
|
|
11
|
+
*/
|
|
12
|
+
export declare function applyWorldScaleBoxUVs(geometry: BoxGeometry, w: number, h: number, d: number): void;
|
|
13
|
+
//# sourceMappingURL=box-uv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"box-uv.d.ts","sourceRoot":"","sources":["../../src/lib/box-uv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAExC;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,WAAW,EACrB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,GACR,IAAI,CAsBN"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite a default `BoxGeometry`'s UVs to world scale — 1 UV unit = 1 metre —
|
|
3
|
+
* so tiled finishes (with `repeat` in tiles-per-metre) render at a consistent
|
|
4
|
+
* real-world scale instead of stretching to fit each face. Matches the
|
|
5
|
+
* world-scale UV convention used by the procedural slab/wall geometry.
|
|
6
|
+
*
|
|
7
|
+
* three.js builds box faces in the fixed order [+X, -X, +Y, -Y, +Z, -Z], four
|
|
8
|
+
* verts each, with UVs spanning 0→1 across the face. Each face's two in-plane
|
|
9
|
+
* dimensions differ, so we scale U/V per face by that face's size in metres.
|
|
10
|
+
*/
|
|
11
|
+
export function applyWorldScaleBoxUVs(geometry, w, h, d) {
|
|
12
|
+
const uv = geometry.getAttribute('uv');
|
|
13
|
+
if (!uv || uv.count < 24)
|
|
14
|
+
return; // non-default segmentation — leave as-is
|
|
15
|
+
// [uScaleMetres, vScaleMetres] per face, in three's face order.
|
|
16
|
+
const faceScale = [
|
|
17
|
+
[d, h], // +X
|
|
18
|
+
[d, h], // -X
|
|
19
|
+
[w, d], // +Y
|
|
20
|
+
[w, d], // -Y
|
|
21
|
+
[w, h], // +Z
|
|
22
|
+
[w, h], // -Z
|
|
23
|
+
];
|
|
24
|
+
for (let face = 0; face < 6; face += 1) {
|
|
25
|
+
const [us, vs] = faceScale[face];
|
|
26
|
+
for (let v = 0; v < 4; v += 1) {
|
|
27
|
+
const i = face * 4 + v;
|
|
28
|
+
uv.setXY(i, uv.getX(i) * us, uv.getY(i) * vs);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
uv.needsUpdate = true;
|
|
32
|
+
}
|
package/dist/lib/csg-utils.d.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as THREE from 'three';
|
|
2
2
|
import { type Brush, Evaluator } from 'three-bvh-csg';
|
|
3
|
-
|
|
4
|
-
* Shared CSG primitives used by kinds whose geometry subtracts pieces
|
|
5
|
-
* against their host (chimney trimmed by the roof shell, skylight
|
|
6
|
-
* frame as a ring cut from a box, etc.). Lives in viewer because
|
|
7
|
-
* three-bvh-csg + three-mesh-bvh are viewer-only deps; kinds living
|
|
8
|
-
* in `@pascal-app/nodes` import these through the public surface.
|
|
9
|
-
*/
|
|
3
|
+
export declare function ensureRenderableGeometryAttributes(geometry: THREE.BufferGeometry): THREE.BufferGeometry;
|
|
10
4
|
export declare function csgGeometry(brush: Brush): THREE.BufferGeometry;
|
|
11
5
|
export declare function csgMaterials(brush: Brush): THREE.Material[];
|
|
12
6
|
export declare const csgEvaluator: Evaluator;
|
|
13
7
|
export declare function computeGeometryBoundsTree(geometry: THREE.BufferGeometry): void;
|
|
14
8
|
export declare function prepareBrushForCSG(brush: Brush): void;
|
|
15
|
-
export { ADDITION, Brush, SUBTRACTION } from 'three-bvh-csg';
|
|
9
|
+
export { ADDITION, Brush, INTERSECTION, SUBTRACTION } from 'three-bvh-csg';
|
|
16
10
|
//# sourceMappingURL=csg-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csg-utils.d.ts","sourceRoot":"","sources":["../../src/lib/csg-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,
|
|
1
|
+
{"version":3,"file":"csg-utils.d.ts","sourceRoot":"","sources":["../../src/lib/csg-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,KAAK,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AA4CrD,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,KAAK,CAAC,cAAc,GAC7B,KAAK,CAAC,cAAc,CAoCtB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAAC,cAAc,CAE9D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAAC,QAAQ,EAAE,CAG3D;AAED,eAAO,MAAM,YAAY,WAAkB,CAAA;AAK3C,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,KAAK,CAAC,cAAc,QAMvE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,QAI9C;AAID,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
|
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
|
}
|
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,43 @@
|
|
|
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 isKtx2Url(url: string): boolean;
|
|
42
|
+
export {};
|
|
43
|
+
//# 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,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9C"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { DataTexture, LinearFilter, LinearMipmapLinearFilter, RGBAFormat, UnsignedByteType, } from 'three';
|
|
2
|
+
// Deep path, NOT the `Addons.js` aggregate: the aggregate re-exports
|
|
3
|
+
// LottieLoader/TTFLoader, whose CDN URL imports (`lottie-web`, `opentype.js`)
|
|
4
|
+
// crash bun's test runtime for every consumer of the viewer barrel.
|
|
5
|
+
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
|
6
|
+
/** Base width/height of a Basis-supercompressed KTX2 payload needing transcode
|
|
7
|
+
* (vkFormat 0 = ETC1S/UASTC) whose dimensions aren't block-aligned, or null
|
|
8
|
+
* for anything safe. Little-endian u32s: vkFormat at byte 12, pixelWidth at
|
|
9
|
+
* 20, pixelHeight at 24. */
|
|
10
|
+
function readMisalignedBasisSize(buffer) {
|
|
11
|
+
if (buffer.byteLength < 28)
|
|
12
|
+
return null;
|
|
13
|
+
const view = new DataView(buffer);
|
|
14
|
+
const vkFormat = view.getUint32(12, true);
|
|
15
|
+
if (vkFormat !== 0)
|
|
16
|
+
return null;
|
|
17
|
+
const width = view.getUint32(20, true);
|
|
18
|
+
const height = view.getUint32(24, true);
|
|
19
|
+
return width % 4 !== 0 || height % 4 !== 0 ? { width, height } : null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* KTX2Loader that survives block-misaligned textures. WebGPU rejects
|
|
23
|
+
* block-compressed textures whose base dimensions aren't multiples of 4;
|
|
24
|
+
* three's KTX2Loader only warns and transcodes anyway, and the resulting
|
|
25
|
+
* invalid texture poisons every render pass that binds it (endless
|
|
26
|
+
* "Invalid BindGroup … Invalid CommandBuffer" spam — baked GLBs carrying an
|
|
27
|
+
* odd-sized user-item texture triggered exactly this). Such payloads are
|
|
28
|
+
* routed to a fallback loader whose support flags report no compressed
|
|
29
|
+
* formats, which makes the transcoder emit plain RGBA32 — uncompressed
|
|
30
|
+
* textures have no alignment requirement.
|
|
31
|
+
*/
|
|
32
|
+
class AlignmentSafeKTX2Loader extends KTX2Loader {
|
|
33
|
+
rgbaFallback = null;
|
|
34
|
+
fallbackLoader() {
|
|
35
|
+
if (!this.rgbaFallback) {
|
|
36
|
+
this.rgbaFallback = new KTX2Loader(this.manager);
|
|
37
|
+
this.rgbaFallback.setTranscoderPath(this.transcoderPath);
|
|
38
|
+
this.rgbaFallback.workerConfig = {
|
|
39
|
+
astcSupported: false,
|
|
40
|
+
astcHDRSupported: false,
|
|
41
|
+
etc1Supported: false,
|
|
42
|
+
etc2Supported: false,
|
|
43
|
+
dxtSupported: false,
|
|
44
|
+
bptcSupported: false,
|
|
45
|
+
pvrtcSupported: false,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return this.rgbaFallback;
|
|
49
|
+
}
|
|
50
|
+
/** Both `load()` and GLTFLoader's KHR_texture_basisu path funnel through
|
|
51
|
+
* this internal — overriding it covers every entry point. */
|
|
52
|
+
async _createTexture(buffer, config) {
|
|
53
|
+
const misaligned = readMisalignedBasisSize(buffer);
|
|
54
|
+
if (misaligned) {
|
|
55
|
+
console.warn(`[viewer] KTX2 texture is ${misaligned.width}x${misaligned.height} (not multiple-of-4); ` +
|
|
56
|
+
'transcoding uncompressed so WebGPU can create it.');
|
|
57
|
+
const transcoded = (await this.fallbackLoader()._createTexture(buffer, config));
|
|
58
|
+
// The RGBA32 result still arrives as a CompressedTexture; three's WebGPU
|
|
59
|
+
// texture upload treats every CompressedTexture as block-compressed and
|
|
60
|
+
// crashes looking up a block descriptor for rgba8. Repackage the decoded
|
|
61
|
+
// pixels as a plain DataTexture instead.
|
|
62
|
+
const mip = transcoded.mipmaps?.[0];
|
|
63
|
+
if (!mip)
|
|
64
|
+
return transcoded;
|
|
65
|
+
const texture = new DataTexture(mip.data, mip.width, mip.height, RGBAFormat, UnsignedByteType);
|
|
66
|
+
texture.colorSpace = transcoded.colorSpace;
|
|
67
|
+
texture.generateMipmaps = true;
|
|
68
|
+
texture.minFilter = LinearMipmapLinearFilter;
|
|
69
|
+
texture.magFilter = LinearFilter;
|
|
70
|
+
texture.needsUpdate = true;
|
|
71
|
+
transcoded.dispose();
|
|
72
|
+
return texture;
|
|
73
|
+
}
|
|
74
|
+
return KTX2Loader.prototype._createTexture.call(this, buffer, config);
|
|
75
|
+
}
|
|
76
|
+
dispose() {
|
|
77
|
+
this.rgbaFallback?.dispose();
|
|
78
|
+
this.rgbaFallback = null;
|
|
79
|
+
super.dispose();
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Single shared KTX2 loader for the whole viewer — used both by the GLB loader
|
|
85
|
+
* (`use-gltf-ktx2`) and by catalog finish textures (`materials.ts`). KTX2 must
|
|
86
|
+
* be transcoded at load via the Basis WASM, and `detectSupport(renderer)` has to
|
|
87
|
+
* run once before any `.ktx2` is loaded so the loader picks a GPU format the
|
|
88
|
+
* device supports. `ensureKtx2Support` is idempotent per renderer and is called
|
|
89
|
+
* from the viewer root the moment the renderer is ready (even when no GLB is in
|
|
90
|
+
* the scene, so catalog `.ktx2` finishes still load).
|
|
91
|
+
*/
|
|
92
|
+
export const ktx2Loader = new AlignmentSafeKTX2Loader();
|
|
93
|
+
ktx2Loader.setTranscoderPath('https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/');
|
|
94
|
+
const configuredRenderers = new WeakSet();
|
|
95
|
+
const warnedRenderers = new WeakSet();
|
|
96
|
+
let resolveKtx2Ready;
|
|
97
|
+
const ktx2ReadyPromise = new Promise((resolve) => {
|
|
98
|
+
resolveKtx2Ready = resolve;
|
|
99
|
+
});
|
|
100
|
+
/**
|
|
101
|
+
* Resolves once `detectSupport` has succeeded for any renderer. `.ktx2` loads
|
|
102
|
+
* issued before that point would throw inside KTX2Loader ("Missing
|
|
103
|
+
* initialization with `.detectSupport( renderer )`"), so texture loaders await
|
|
104
|
+
* this instead of failing — covers materials created while the renderer is
|
|
105
|
+
* still initializing (e.g. a standalone capture canvas).
|
|
106
|
+
*/
|
|
107
|
+
export function whenKtx2Ready() {
|
|
108
|
+
return ktx2ReadyPromise;
|
|
109
|
+
}
|
|
110
|
+
/** Returns true once support has been detected for this renderer (KTX2 safe to load). */
|
|
111
|
+
export function ensureKtx2Support(renderer) {
|
|
112
|
+
const key = renderer;
|
|
113
|
+
if (!key)
|
|
114
|
+
return false;
|
|
115
|
+
if (configuredRenderers.has(key))
|
|
116
|
+
return true;
|
|
117
|
+
try {
|
|
118
|
+
;
|
|
119
|
+
ktx2Loader.detectSupport(renderer);
|
|
120
|
+
configuredRenderers.add(key);
|
|
121
|
+
resolveKtx2Ready();
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
// Some WebGPU flows can transiently call this before backend init; don't
|
|
126
|
+
// crash the scene — a later call (or the next render) retries.
|
|
127
|
+
if (!warnedRenderers.has(key)) {
|
|
128
|
+
console.warn('[viewer] Skipping KTX2 support detection for now.', error);
|
|
129
|
+
warnedRenderers.add(key);
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export function isKtx2Url(url) {
|
|
135
|
+
return url.toLowerCase().endsWith('.ktx2');
|
|
136
|
+
}
|
package/dist/lib/layers.d.ts
CHANGED
|
@@ -20,4 +20,14 @@ export declare const ZONE_LAYER = 2;
|
|
|
20
20
|
* from thumbnails like the other editor-only layers.
|
|
21
21
|
*/
|
|
22
22
|
export declare const GRID_LAYER = 3;
|
|
23
|
+
/**
|
|
24
|
+
* Layer for geometry hidden from the color passes but still rendered into the
|
|
25
|
+
* shadow map ("shadow-caster-only"). Used when cutaway/solo views hide roofs
|
|
26
|
+
* or non-selected levels: the sun keeps casting their shadows, so interiors
|
|
27
|
+
* get window-shaped light patches instead of flooding with uniform sun.
|
|
28
|
+
* No camera or pass enables this layer — only each shadow-casting light's
|
|
29
|
+
* shadow camera does (see `lights.tsx`). Applied per-object (layers don't
|
|
30
|
+
* cascade) via `applyShadowOnly` / `clearShadowOnly` in `lib/shadow-only.ts`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const SHADOW_ONLY_LAYER = 4;
|
|
23
33
|
//# sourceMappingURL=layers.d.ts.map
|
package/dist/lib/layers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/lib/layers.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,eAAO,MAAM,WAAW,IAAI,CAAA;AAE5B;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,IAAI,CAAA;AAE9B,oEAAoE;AACpE,eAAO,MAAM,UAAU,IAAI,CAAA;AAE3B;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,IAAI,CAAA"}
|
|
1
|
+
{"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/lib/layers.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,eAAO,MAAM,WAAW,IAAI,CAAA;AAE5B;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,IAAI,CAAA;AAE9B,oEAAoE;AACpE,eAAO,MAAM,UAAU,IAAI,CAAA;AAE3B;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,IAAI,CAAA;AAE3B;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAA"}
|
package/dist/lib/layers.js
CHANGED
|
@@ -20,3 +20,13 @@ export const ZONE_LAYER = 2;
|
|
|
20
20
|
* from thumbnails like the other editor-only layers.
|
|
21
21
|
*/
|
|
22
22
|
export const GRID_LAYER = 3;
|
|
23
|
+
/**
|
|
24
|
+
* Layer for geometry hidden from the color passes but still rendered into the
|
|
25
|
+
* shadow map ("shadow-caster-only"). Used when cutaway/solo views hide roofs
|
|
26
|
+
* or non-selected levels: the sun keeps casting their shadows, so interiors
|
|
27
|
+
* get window-shaped light patches instead of flooding with uniform sun.
|
|
28
|
+
* No camera or pass enables this layer — only each shadow-casting light's
|
|
29
|
+
* shadow camera does (see `lights.tsx`). Applied per-object (layers don't
|
|
30
|
+
* cascade) via `applyShadowOnly` / `clearShadowOnly` in `lib/shadow-only.ts`.
|
|
31
|
+
*/
|
|
32
|
+
export const SHADOW_ONLY_LAYER = 4;
|