@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.
Files changed (206) hide show
  1. package/README.md +20 -5
  2. package/dist/components/error-boundary.d.ts +6 -0
  3. package/dist/components/error-boundary.d.ts.map +1 -1
  4. package/dist/components/error-boundary.js +6 -0
  5. package/dist/components/renderers/node-renderer.d.ts +5 -1
  6. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  7. package/dist/components/renderers/node-renderer.js +5 -2
  8. package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -1
  9. package/dist/components/renderers/parametric-node-renderer.js +7 -4
  10. package/dist/components/viewer/bvh-ecctrl.d.ts +77 -0
  11. package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -0
  12. package/dist/components/viewer/bvh-ecctrl.js +552 -0
  13. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  14. package/dist/components/viewer/frame-limiter.js +59 -4
  15. package/dist/components/viewer/glb-interactive.d.ts +41 -0
  16. package/dist/components/viewer/glb-interactive.d.ts.map +1 -0
  17. package/dist/components/viewer/glb-interactive.js +394 -0
  18. package/dist/components/viewer/glb-reference-nodes.d.ts +29 -0
  19. package/dist/components/viewer/glb-reference-nodes.d.ts.map +1 -0
  20. package/dist/components/viewer/glb-reference-nodes.js +80 -0
  21. package/dist/components/viewer/glb-replace-instances.d.ts +18 -0
  22. package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -0
  23. package/dist/components/viewer/glb-replace-instances.js +61 -0
  24. package/dist/components/viewer/glb-scene.d.ts +54 -0
  25. package/dist/components/viewer/glb-scene.d.ts.map +1 -0
  26. package/dist/components/viewer/glb-scene.js +920 -0
  27. package/dist/components/viewer/glb-walkthrough-controller.d.ts +22 -0
  28. package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -0
  29. package/dist/components/viewer/glb-walkthrough-controller.js +445 -0
  30. package/dist/components/viewer/index.d.ts +19 -2
  31. package/dist/components/viewer/index.d.ts.map +1 -1
  32. package/dist/components/viewer/index.js +113 -12
  33. package/dist/components/viewer/lights.d.ts.map +1 -1
  34. package/dist/components/viewer/lights.js +36 -5
  35. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  36. package/dist/components/viewer/perf-monitor.js +17 -2
  37. package/dist/components/viewer/post-processing.d.ts +7 -1
  38. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  39. package/dist/components/viewer/post-processing.js +180 -24
  40. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  41. package/dist/components/viewer/registered-systems.js +6 -4
  42. package/dist/components/viewer/scene-bvh.d.ts +2 -1
  43. package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
  44. package/dist/components/viewer/scene-bvh.js +3 -0
  45. package/dist/components/viewer/scene-bvh.test.d.ts +2 -0
  46. package/dist/components/viewer/scene-bvh.test.d.ts.map +1 -0
  47. package/dist/components/viewer/scene-bvh.test.js +13 -0
  48. package/dist/components/viewer/scene-environment.d.ts +2 -0
  49. package/dist/components/viewer/scene-environment.d.ts.map +1 -0
  50. package/dist/components/viewer/scene-environment.js +79 -0
  51. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  52. package/dist/components/viewer/selection-manager.js +23 -12
  53. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  54. package/dist/hooks/use-gltf-ktx2.js +2 -24
  55. package/dist/hooks/use-node-events.d.ts +1 -1
  56. package/dist/hooks/use-node-events.d.ts.map +1 -1
  57. package/dist/hooks/use-node-events.js +22 -14
  58. package/dist/index.d.ts +24 -10
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +29 -13
  61. package/dist/lib/backdrop.d.ts +33 -0
  62. package/dist/lib/backdrop.d.ts.map +1 -0
  63. package/dist/lib/backdrop.js +92 -0
  64. package/dist/lib/box-uv.d.ts +13 -0
  65. package/dist/lib/box-uv.d.ts.map +1 -0
  66. package/dist/lib/box-uv.js +32 -0
  67. package/dist/lib/csg-utils.d.ts +3 -9
  68. package/dist/lib/csg-utils.d.ts.map +1 -1
  69. package/dist/lib/csg-utils.js +65 -5
  70. package/dist/lib/csg-utils.test.d.ts +2 -0
  71. package/dist/lib/csg-utils.test.d.ts.map +1 -0
  72. package/dist/lib/csg-utils.test.js +45 -0
  73. package/dist/lib/edge-style.d.ts +1 -0
  74. package/dist/lib/edge-style.d.ts.map +1 -1
  75. package/dist/lib/edge-style.js +26 -6
  76. package/dist/lib/hero-pose.d.ts +47 -0
  77. package/dist/lib/hero-pose.d.ts.map +1 -0
  78. package/dist/lib/hero-pose.js +227 -0
  79. package/dist/lib/ink-edges.d.ts +1 -1
  80. package/dist/lib/ink-edges.d.ts.map +1 -1
  81. package/dist/lib/ink-edges.js +13 -7
  82. package/dist/lib/ktx2-loader.d.ts +46 -0
  83. package/dist/lib/ktx2-loader.d.ts.map +1 -0
  84. package/dist/lib/ktx2-loader.js +142 -0
  85. package/dist/lib/layers.d.ts +10 -0
  86. package/dist/lib/layers.d.ts.map +1 -1
  87. package/dist/lib/layers.js +10 -0
  88. package/dist/lib/materials.d.ts +16 -1
  89. package/dist/lib/materials.d.ts.map +1 -1
  90. package/dist/lib/materials.js +158 -34
  91. package/dist/lib/materials.test.d.ts +2 -0
  92. package/dist/lib/materials.test.d.ts.map +1 -0
  93. package/dist/lib/materials.test.js +25 -0
  94. package/dist/lib/merged-outline-node.test.d.ts +2 -0
  95. package/dist/lib/merged-outline-node.test.d.ts.map +1 -0
  96. package/dist/lib/merged-outline-node.test.js +22 -0
  97. package/dist/lib/scene-themes.d.ts +1 -0
  98. package/dist/lib/scene-themes.d.ts.map +1 -1
  99. package/dist/lib/scene-themes.js +25 -16
  100. package/dist/lib/shadow-only.d.ts +4 -0
  101. package/dist/lib/shadow-only.d.ts.map +1 -0
  102. package/dist/lib/shadow-only.js +36 -0
  103. package/dist/lib/snapshot-pipeline.d.ts +42 -0
  104. package/dist/lib/snapshot-pipeline.d.ts.map +1 -0
  105. package/dist/lib/snapshot-pipeline.js +254 -0
  106. package/dist/lib/terrain-perimeter-fill.d.ts +7 -0
  107. package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -0
  108. package/dist/lib/terrain-perimeter-fill.js +75 -0
  109. package/dist/lib/texture-node-guard.d.ts +13 -0
  110. package/dist/lib/texture-node-guard.d.ts.map +1 -0
  111. package/dist/lib/texture-node-guard.js +39 -0
  112. package/dist/lib/texture-reference.d.ts +31 -0
  113. package/dist/lib/texture-reference.d.ts.map +1 -0
  114. package/dist/lib/texture-reference.js +139 -0
  115. package/dist/lib/texture-reference.test.d.ts +2 -0
  116. package/dist/lib/texture-reference.test.d.ts.map +1 -0
  117. package/dist/lib/texture-reference.test.js +86 -0
  118. package/dist/lib/tsl-compat.d.ts +12 -0
  119. package/dist/lib/tsl-compat.d.ts.map +1 -0
  120. package/dist/lib/tsl-compat.js +13 -0
  121. package/dist/store/use-viewer.d.ts +73 -18
  122. package/dist/store/use-viewer.d.ts.map +1 -1
  123. package/dist/store/use-viewer.js +191 -4
  124. package/dist/store/use-viewer.test.d.ts +2 -0
  125. package/dist/store/use-viewer.test.d.ts.map +1 -0
  126. package/dist/store/use-viewer.test.js +61 -0
  127. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  128. package/dist/systems/ceiling/ceiling-system.js +12 -5
  129. package/dist/systems/door/door-animation.test.d.ts +2 -0
  130. package/dist/systems/door/door-animation.test.d.ts.map +1 -0
  131. package/dist/systems/door/door-animation.test.js +51 -0
  132. package/dist/systems/door/door-floor-alignment.test.d.ts +2 -0
  133. package/dist/systems/door/door-floor-alignment.test.d.ts.map +1 -0
  134. package/dist/systems/door/door-floor-alignment.test.js +50 -0
  135. package/dist/systems/door/door-system.d.ts +24 -0
  136. package/dist/systems/door/door-system.d.ts.map +1 -1
  137. package/dist/systems/door/door-system.js +494 -161
  138. package/dist/systems/fence/fence-system.d.ts +17 -0
  139. package/dist/systems/fence/fence-system.d.ts.map +1 -1
  140. package/dist/systems/fence/fence-system.js +221 -53
  141. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
  142. package/dist/systems/floor-elevation/floor-elevation-system.js +30 -7
  143. package/dist/systems/geometry/geometry-system.d.ts +7 -0
  144. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  145. package/dist/systems/geometry/geometry-system.js +95 -26
  146. package/dist/systems/geometry/geometry-system.test.d.ts +2 -0
  147. package/dist/systems/geometry/geometry-system.test.d.ts.map +1 -0
  148. package/dist/systems/geometry/geometry-system.test.js +30 -0
  149. package/dist/systems/interactive/control-widget.d.ts +10 -0
  150. package/dist/systems/interactive/control-widget.d.ts.map +1 -0
  151. package/dist/systems/interactive/control-widget.js +35 -0
  152. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  153. package/dist/systems/interactive/interactive-system.js +55 -56
  154. package/dist/systems/item/item-system.d.ts.map +1 -1
  155. package/dist/systems/item/item-system.js +20 -6
  156. package/dist/systems/level/level-system.d.ts.map +1 -1
  157. package/dist/systems/level/level-system.js +34 -9
  158. package/dist/systems/level/level-utils.d.ts +0 -3
  159. package/dist/systems/level/level-utils.d.ts.map +1 -1
  160. package/dist/systems/level/level-utils.js +7 -45
  161. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  162. package/dist/systems/roof/roof-materials.js +29 -8
  163. package/dist/systems/roof/roof-system.d.ts +21 -8
  164. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  165. package/dist/systems/roof/roof-system.js +1079 -221
  166. package/dist/systems/slab/slab-system.d.ts +7 -4
  167. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  168. package/dist/systems/slab/slab-system.js +31 -83
  169. package/dist/systems/slab/slab-system.test.js +54 -14
  170. package/dist/systems/stair/stair-system.js +7 -4
  171. package/dist/systems/wall/opening-cutout-geometry.d.ts +31 -0
  172. package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -0
  173. package/dist/systems/wall/opening-cutout-geometry.js +167 -0
  174. package/dist/systems/wall/opening-cutout-geometry.test.d.ts +2 -0
  175. package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +1 -0
  176. package/dist/systems/wall/opening-cutout-geometry.test.js +172 -0
  177. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  178. package/dist/systems/wall/wall-cutout.js +49 -17
  179. package/dist/systems/wall/wall-materials.d.ts +11 -7
  180. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  181. package/dist/systems/wall/wall-materials.js +244 -35
  182. package/dist/systems/wall/wall-opening-cutout.test.d.ts +2 -0
  183. package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +1 -0
  184. package/dist/systems/wall/wall-opening-cutout.test.js +59 -0
  185. package/dist/systems/wall/wall-support-extension.test.d.ts +2 -0
  186. package/dist/systems/wall/wall-support-extension.test.d.ts.map +1 -0
  187. package/dist/systems/wall/wall-support-extension.test.js +116 -0
  188. package/dist/systems/wall/wall-system.d.ts +4 -8
  189. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  190. package/dist/systems/wall/wall-system.js +397 -177
  191. package/dist/systems/wall/wall-terrain-bottom.test.d.ts +2 -0
  192. package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +1 -0
  193. package/dist/systems/wall/wall-terrain-bottom.test.js +34 -0
  194. package/dist/systems/window/window-animation-system.d.ts +12 -0
  195. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  196. package/dist/systems/window/window-animation-system.js +16 -5
  197. package/dist/systems/window/window-system.d.ts +7 -0
  198. package/dist/systems/window/window-system.d.ts.map +1 -1
  199. package/dist/systems/window/window-system.js +240 -14
  200. package/package.json +4 -4
  201. package/dist/components/viewer/ground-occluder.d.ts +0 -2
  202. package/dist/components/viewer/ground-occluder.d.ts.map +0 -1
  203. package/dist/components/viewer/ground-occluder.js +0 -67
  204. package/dist/components/viewer/walkthrough-controls.d.ts +0 -2
  205. package/dist/components/viewer/walkthrough-controls.d.ts.map +0 -1
  206. package/dist/components/viewer/walkthrough-controls.js +0 -131
@@ -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 direct
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=csg-utils.test.d.ts.map
@@ -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
+ });
@@ -1,3 +1,4 @@
1
1
  export type EdgeMode = 'off' | 'soft' | 'strong';
2
2
  export declare function edgeColorFor(background: string): string;
3
+ export declare function edgeOpacityScaleFor(background: string): number;
3
4
  //# sourceMappingURL=edge-style.d.ts.map
@@ -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;AAKhD,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAOvD"}
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"}
@@ -1,11 +1,31 @@
1
- // Ink line colour follows background luminance — light backgrounds get
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
- const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
10
- return luma > 0.5 ? '#1a1d24' : '#dde2eb';
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
+ }
@@ -3,7 +3,7 @@ export declare function inkedEdges({ depthTex, normalTex, inkColor, radius, opac
3
3
  normalTex: any;
4
4
  inkColor: any;
5
5
  radius: number;
6
- opacity: number;
6
+ opacity: any;
7
7
  sceneRgb: any;
8
8
  }): any;
9
9
  //# sourceMappingURL=ink-edges.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ink-edges.d.ts","sourceRoot":"","sources":["../../src/lib/ink-edges.ts"],"names":[],"mappings":"AA6BA,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;IAIb,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,GAAG,CAAA;CACd,OAgCA"}
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"}
@@ -1,4 +1,5 @@
1
- import { abs, colorToDirection, float, max, min, mix, screenSize, screenUV, smoothstep, vec2, } from 'three/tsl';
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 = colorToDirection(normalTex.sample(uvN)).normalize();
33
- const nR = colorToDirection(normalTex.sample(uvN.add(vec2(px.x, 0)))).normalize();
34
- const nL = colorToDirection(normalTex.sample(uvN.sub(vec2(px.x, 0)))).normalize();
35
- const nU = colorToDirection(normalTex.sample(uvN.add(vec2(0, px.y)))).normalize();
36
- const nD = colorToDirection(normalTex.sample(uvN.sub(vec2(0, px.y)))).normalize();
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"}