@pascal-app/viewer 1.0.0-beta.5 → 1.0.0

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 (190) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/capture/asset-url.d.ts +2 -0
  4. package/dist/capture/asset-url.d.ts.map +1 -0
  5. package/dist/capture/asset-url.js +13 -0
  6. package/dist/capture/capture-runtime.d.ts +43 -0
  7. package/dist/capture/capture-runtime.d.ts.map +1 -0
  8. package/dist/capture/capture-runtime.js +228 -0
  9. package/dist/capture/frame.d.ts +4 -0
  10. package/dist/capture/frame.d.ts.map +1 -0
  11. package/dist/capture/frame.js +21 -0
  12. package/dist/capture/index.d.ts +12 -0
  13. package/dist/capture/index.d.ts.map +1 -0
  14. package/dist/capture/index.js +11 -0
  15. package/dist/capture/layer-visibility.d.ts +5 -0
  16. package/dist/capture/layer-visibility.d.ts.map +1 -0
  17. package/dist/capture/layer-visibility.js +11 -0
  18. package/dist/capture/layers/clay-matcap.d.ts +3 -0
  19. package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
  20. package/dist/capture/layers/clay-matcap.js +26 -0
  21. package/dist/capture/layers/device-motion-layer.d.ts +7 -0
  22. package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
  23. package/dist/capture/layers/device-motion-layer.js +78 -0
  24. package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
  25. package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
  26. package/dist/capture/layers/point-cloud-layer.js +106 -0
  27. package/dist/capture/layers/room-model-layer.d.ts +9 -0
  28. package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
  29. package/dist/capture/layers/room-model-layer.js +71 -0
  30. package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
  31. package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
  32. package/dist/capture/layers/surface-mesh-data.js +74 -0
  33. package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
  34. package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
  35. package/dist/capture/layers/surface-mesh-layer.js +16 -0
  36. package/dist/capture/preview.d.ts +3 -0
  37. package/dist/capture/preview.d.ts.map +1 -0
  38. package/dist/capture/preview.js +2 -0
  39. package/dist/capture/source-state.d.ts +22 -0
  40. package/dist/capture/source-state.d.ts.map +1 -0
  41. package/dist/capture/source-state.js +182 -0
  42. package/dist/capture/stream-rendering.d.ts +14 -0
  43. package/dist/capture/stream-rendering.d.ts.map +1 -0
  44. package/dist/capture/stream-rendering.js +69 -0
  45. package/dist/capture/trajectory.d.ts +20 -0
  46. package/dist/capture/trajectory.d.ts.map +1 -0
  47. package/dist/capture/trajectory.js +77 -0
  48. package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
  49. package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
  50. package/dist/components/viewer/batched-mesh-spike.js +64 -0
  51. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  52. package/dist/components/viewer/frame-limiter.js +4 -3
  53. package/dist/components/viewer/index.d.ts.map +1 -1
  54. package/dist/components/viewer/index.js +70 -62
  55. package/dist/components/viewer/lights.d.ts.map +1 -1
  56. package/dist/components/viewer/lights.js +123 -89
  57. package/dist/components/viewer/perf-monitor.d.ts +6 -1
  58. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  59. package/dist/components/viewer/perf-monitor.js +307 -116
  60. package/dist/components/viewer/perf-panel.d.ts +3 -0
  61. package/dist/components/viewer/perf-panel.d.ts.map +1 -0
  62. package/dist/components/viewer/perf-panel.js +178 -0
  63. package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
  64. package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
  65. package/dist/components/viewer/pointer-raycast-layers.js +28 -0
  66. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  67. package/dist/components/viewer/post-processing.js +124 -83
  68. package/dist/components/viewer/registered-systems.d.ts +14 -5
  69. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  70. package/dist/components/viewer/registered-systems.js +22 -10
  71. package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
  72. package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
  73. package/dist/components/viewer/scene-atmosphere.js +89 -0
  74. package/dist/components/viewer/scene-environment.d.ts.map +1 -1
  75. package/dist/components/viewer/scene-environment.js +19 -5
  76. package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
  77. package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
  78. package/dist/components/viewer/scene-ground-replacement.js +35 -0
  79. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  80. package/dist/components/viewer/viewer-camera.js +4 -1
  81. package/dist/components/viewer/viewer-presentations.d.ts +67 -0
  82. package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
  83. package/dist/components/viewer/viewer-presentations.js +120 -0
  84. package/dist/hooks/use-library-materials-version.d.ts +7 -0
  85. package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
  86. package/dist/hooks/use-library-materials-version.js +10 -0
  87. package/dist/hooks/use-node-events.d.ts.map +1 -1
  88. package/dist/hooks/use-node-events.js +1 -0
  89. package/dist/index.d.ts +15 -6
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +15 -6
  92. package/dist/lib/csg-utils.d.ts +1 -0
  93. package/dist/lib/csg-utils.d.ts.map +1 -1
  94. package/dist/lib/csg-utils.js +6 -1
  95. package/dist/lib/gpu-perf.d.ts +0 -2
  96. package/dist/lib/gpu-perf.d.ts.map +1 -1
  97. package/dist/lib/gpu-perf.js +7 -21
  98. package/dist/lib/layer-pass.d.ts +28 -0
  99. package/dist/lib/layer-pass.d.ts.map +1 -0
  100. package/dist/lib/layer-pass.js +254 -0
  101. package/dist/lib/materials.d.ts +12 -0
  102. package/dist/lib/materials.d.ts.map +1 -1
  103. package/dist/lib/materials.js +51 -17
  104. package/dist/lib/merged-outline-node.d.ts +17 -8
  105. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  106. package/dist/lib/merged-outline-node.js +194 -52
  107. package/dist/lib/perf-actions.d.ts +55 -0
  108. package/dist/lib/perf-actions.d.ts.map +1 -0
  109. package/dist/lib/perf-actions.js +211 -0
  110. package/dist/lib/perf-observers.d.ts +2 -0
  111. package/dist/lib/perf-observers.d.ts.map +1 -0
  112. package/dist/lib/perf-observers.js +17 -0
  113. package/dist/lib/perf-panel-store.d.ts +71 -0
  114. package/dist/lib/perf-panel-store.d.ts.map +1 -0
  115. package/dist/lib/perf-panel-store.js +29 -0
  116. package/dist/lib/perf-tracks.d.ts +43 -0
  117. package/dist/lib/perf-tracks.d.ts.map +1 -0
  118. package/dist/lib/perf-tracks.js +113 -0
  119. package/dist/lib/pointer-events.d.ts +7 -0
  120. package/dist/lib/pointer-events.d.ts.map +1 -0
  121. package/dist/lib/pointer-events.js +680 -0
  122. package/dist/lib/post-processing-resources.d.ts +12 -0
  123. package/dist/lib/post-processing-resources.d.ts.map +1 -0
  124. package/dist/lib/post-processing-resources.js +19 -0
  125. package/dist/lib/renderer-capability.d.ts.map +1 -1
  126. package/dist/lib/renderer-capability.js +4 -2
  127. package/dist/lib/scene-visibility.d.ts +2 -1
  128. package/dist/lib/scene-visibility.d.ts.map +1 -1
  129. package/dist/lib/scene-visibility.js +22 -1
  130. package/dist/store/use-viewer.d.ts +2 -2
  131. package/dist/store/use-viewer.d.ts.map +1 -1
  132. package/dist/systems/ceiling/ceiling-system.js +1 -1
  133. package/dist/systems/door/door-animation-system.d.ts.map +1 -1
  134. package/dist/systems/door/door-animation-system.js +6 -9
  135. package/dist/systems/door/door-system.d.ts.map +1 -1
  136. package/dist/systems/door/door-system.js +13 -2
  137. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  138. package/dist/systems/geometry/geometry-system.js +3 -2
  139. package/dist/systems/level/level-system.d.ts.map +1 -1
  140. package/dist/systems/level/level-system.js +1 -1
  141. package/dist/systems/level/level-utils.d.ts +9 -0
  142. package/dist/systems/level/level-utils.d.ts.map +1 -1
  143. package/dist/systems/level/level-utils.js +14 -0
  144. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
  145. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
  146. package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
  147. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  148. package/dist/systems/roof/roof-system.js +1196 -58
  149. package/dist/systems/scan/scan-system.js +6 -5
  150. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  151. package/dist/systems/slab/slab-system.js +1 -2
  152. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  153. package/dist/systems/stair/stair-system.js +9 -8
  154. package/dist/systems/surface-hole-geometry.d.ts +1 -1
  155. package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
  156. package/dist/systems/surface-hole-geometry.js +1 -1
  157. package/dist/systems/wall/level-miter-cache.d.ts.map +1 -1
  158. package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
  159. package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
  160. package/dist/systems/wall/wall-build-lifecycle.js +74 -0
  161. package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
  162. package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
  163. package/dist/systems/wall/wall-cutout-cache.js +304 -0
  164. package/dist/systems/wall/wall-cutout.d.ts +12 -1
  165. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  166. package/dist/systems/wall/wall-cutout.js +24 -161
  167. package/dist/systems/wall/wall-material-variant.d.ts +31 -0
  168. package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
  169. package/dist/systems/wall/wall-material-variant.js +10 -0
  170. package/dist/systems/wall/wall-materials.d.ts +6 -0
  171. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  172. package/dist/systems/wall/wall-materials.js +29 -13
  173. package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
  174. package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
  175. package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
  176. package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
  177. package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
  178. package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
  179. package/dist/systems/wall/wall-system.d.ts +11 -4
  180. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  181. package/dist/systems/wall/wall-system.js +405 -153
  182. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  183. package/dist/systems/window/window-animation-system.js +9 -8
  184. package/dist/systems/window/window-system.d.ts +1 -0
  185. package/dist/systems/window/window-system.d.ts.map +1 -1
  186. package/dist/systems/window/window-system.js +16 -2
  187. package/package.json +17 -4
  188. package/dist/lib/polygon-union.d.ts +0 -4
  189. package/dist/lib/polygon-union.d.ts.map +0 -1
  190. package/dist/lib/polygon-union.js +0 -289
@@ -1,4 +1,2 @@
1
1
  export declare const PERF_OVERLAY_ENABLED: boolean;
2
- export declare function pushGpuSample(ms: number): void;
3
- export declare function drainGpuSamples(): number[];
4
2
  //# sourceMappingURL=gpu-perf.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gpu-perf.d.ts","sourceRoot":"","sources":["../../src/lib/gpu-perf.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,oBAAoB,SACyD,CAAA;AAK1F,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAG9C;AAED,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAK1C"}
1
+ {"version":3,"file":"gpu-perf.d.ts","sourceRoot":"","sources":["../../src/lib/gpu-perf.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,oBAAoB,SACyD,CAAA"}
@@ -1,23 +1,9 @@
1
- // GPU work-time measurement, gated by `?perf` in the URL.
1
+ // `?perf` gate. Kept in its own module because both the overlay and
2
+ // `lib/perf-tracks.ts` (the instrumentation sink every system writes to) read
3
+ // it, and perf-tracks must not import a React component tree.
2
4
  //
3
- // We can't use WebGPU timestamp queries here because the editor renders via
4
- // a custom `RenderPipeline.render()` path that bypasses three.js's built-in
5
- // timestamp infrastructure. Instead we use `device.queue.onSubmittedWorkDone()`,
6
- // which resolves when the GPU finishes all submitted work — measuring the
7
- // CPU→GPU-done delta gives a clean approximation of per-frame GPU duration
8
- // regardless of which render path produced it.
5
+ // Timing itself lives in perf-tracks: `gpu-render` carries three's WebGPU
6
+ // timestamp-query total for the frame's render passes, `gpu-queue` the
7
+ // submit→onSubmittedWorkDone fence, `render-encode` the synchronous CPU cost of
8
+ // building and submitting the frame. See components/viewer/post-processing.tsx.
9
9
  export const PERF_OVERLAY_ENABLED = typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('perf');
10
- const MAX_SAMPLES = 256;
11
- const samples = [];
12
- export function pushGpuSample(ms) {
13
- samples.push(ms);
14
- if (samples.length > MAX_SAMPLES)
15
- samples.shift();
16
- }
17
- export function drainGpuSamples() {
18
- if (samples.length === 0)
19
- return [];
20
- const out = samples.slice();
21
- samples.length = 0;
22
- return out;
23
- }
@@ -0,0 +1,28 @@
1
+ import { type Camera, Object3D, type Scene } from 'three';
2
+ import { type NodeFrame, PassNode } from 'three/webgpu';
3
+ export declare class LayerPassIndex {
4
+ readonly source: Scene;
5
+ private readonly members;
6
+ private readonly cleanups;
7
+ constructor(source: Scene, layers: number[]);
8
+ register(subtree: Object3D): void;
9
+ private attach;
10
+ private detach;
11
+ prepare(layer: number, roots: Object3D[]): {
12
+ drawable: boolean;
13
+ shadowLight: boolean;
14
+ };
15
+ dispose(): void;
16
+ }
17
+ export declare class LayerPassNode extends PassNode {
18
+ private readonly index;
19
+ private readonly layer;
20
+ private readonly mainPass;
21
+ private readonly roots;
22
+ private readonly size;
23
+ private needsClear;
24
+ constructor(index: LayerPassIndex, camera: Camera, layer: number, mainPass: PassNode);
25
+ dispose(): void;
26
+ updateBefore(frame: NodeFrame): undefined;
27
+ }
28
+ //# sourceMappingURL=layer-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layer-pass.d.ts","sourceRoot":"","sources":["../../src/lib/layer-pass.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,MAAM,EAAiB,QAAQ,EAAE,KAAK,KAAK,EAAW,MAAM,OAAO,CAAA;AACjF,OAAO,EAAE,KAAK,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAoBvD,qBAAa,cAAc;IAKvB,QAAQ,CAAC,MAAM,EAAE,KAAK;IAJxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;gBAGhD,MAAM,EAAE,KAAK,EACtB,MAAM,EAAE,MAAM,EAAE;IAWlB,QAAQ,CAAC,OAAO,EAAE,QAAQ;IAU1B,OAAO,CAAC,MAAM,CA2Cb;IAED,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE;;;;IAoCxC,OAAO;CAIR;AAiBD,qBAAa,aAAc,SAAQ,QAAQ;IAMvC,OAAO,CAAC,QAAQ,CAAC,KAAK;IAEtB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,UAAU,CAAO;gBAGN,KAAK,EAAE,cAAc,EACtC,MAAM,EAAE,MAAM,EACG,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,QAAQ;IAc5B,OAAO;IAKP,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS;CAgEnD"}
@@ -0,0 +1,254 @@
1
+ // Only one index may observe a scene, and observed objects must own their Layers.
2
+ // Insertions without childadded are not observed: call register(subtree) afterward.
3
+ // Replaced Layers are repaired for known members during prepare(); other objects
4
+ // need register(object), since discovering them would require a full-scene scan.
5
+ import { Object3D, Vector2 } from 'three';
6
+ import { PassNode } from 'three/webgpu';
7
+ const maskObserver = Symbol('LayerPassIndex.maskObserver');
8
+ function maskOwner(object) {
9
+ const get = Object.getOwnPropertyDescriptor(object.layers, 'mask')?.get;
10
+ return get?.[maskObserver];
11
+ }
12
+ // Observe mask writes as well as child events: R3F's numeric `layers` prop calls
13
+ // Layers.set(), while imperative producers also use enable() and direct masks.
14
+ // Nothing on Object3D/Layers.prototype is patched, and detach restores the field.
15
+ export class LayerPassIndex {
16
+ source;
17
+ members = new Map();
18
+ cleanups = new Map();
19
+ constructor(source, layers) {
20
+ this.source = source;
21
+ for (const layer of layers)
22
+ this.members.set(1 << layer, new Set());
23
+ try {
24
+ this.attach(source);
25
+ }
26
+ catch (error) {
27
+ this.dispose();
28
+ throw error;
29
+ }
30
+ }
31
+ register(subtree) {
32
+ this.detach(subtree);
33
+ for (let object = subtree; object; object = object.parent) {
34
+ if (object === this.source) {
35
+ this.attach(subtree);
36
+ return;
37
+ }
38
+ }
39
+ }
40
+ attach = (object) => {
41
+ if (this.cleanups.has(object))
42
+ return;
43
+ const layers = object.layers;
44
+ if (maskOwner(object)) {
45
+ throw new Error('LayerPassIndex requires one index per scene and unshared Layers');
46
+ }
47
+ let mask = layers.mask;
48
+ const sync = () => {
49
+ for (const [bit, members] of this.members) {
50
+ if ((mask & bit) !== 0 && object !== this.source)
51
+ members.add(object);
52
+ else
53
+ members.delete(object);
54
+ }
55
+ };
56
+ const getMask = Object.assign(() => mask, { [maskObserver]: object });
57
+ Object.defineProperty(layers, 'mask', {
58
+ configurable: true,
59
+ enumerable: true,
60
+ get: getMask,
61
+ set: (value) => {
62
+ if (mask === value)
63
+ return;
64
+ mask = value;
65
+ sync();
66
+ },
67
+ });
68
+ const added = ({ child }) => this.attach(child);
69
+ const removed = ({ child }) => this.detach(child);
70
+ object.addEventListener('childadded', added);
71
+ object.addEventListener('childremoved', removed);
72
+ this.cleanups.set(object, () => {
73
+ object.removeEventListener('childadded', added);
74
+ object.removeEventListener('childremoved', removed);
75
+ if (Object.getOwnPropertyDescriptor(layers, 'mask')?.get === getMask) {
76
+ Object.defineProperty(layers, 'mask', {
77
+ configurable: true,
78
+ enumerable: true,
79
+ writable: true,
80
+ value: mask,
81
+ });
82
+ }
83
+ for (const members of this.members.values())
84
+ members.delete(object);
85
+ });
86
+ sync();
87
+ for (const child of object.children)
88
+ this.attach(child);
89
+ };
90
+ detach(object) {
91
+ for (const child of object.children)
92
+ this.detach(child);
93
+ this.cleanups.get(object)?.();
94
+ this.cleanups.delete(object);
95
+ }
96
+ prepare(layer, roots) {
97
+ roots.length = 0;
98
+ const members = this.members.get(1 << layer);
99
+ // Repairs can change membership, so finish them before choosing nested roots.
100
+ for (const tracked of this.members.values()) {
101
+ for (const object of [...tracked]) {
102
+ let root = object;
103
+ while (root.parent && root !== this.source)
104
+ root = root.parent;
105
+ if (root !== this.source)
106
+ this.detach(root);
107
+ else if (maskOwner(object) !== object)
108
+ this.register(object);
109
+ }
110
+ }
111
+ let drawable = false;
112
+ let shadowLight = false;
113
+ for (const object of members) {
114
+ let visible = object.visible;
115
+ let nested = false;
116
+ for (let parent = object.parent; parent; parent = parent.parent) {
117
+ if (!parent.visible)
118
+ visible = false;
119
+ if (members.has(parent))
120
+ nested = true;
121
+ }
122
+ if (!visible)
123
+ continue;
124
+ const { material, isLight, castShadow } = object;
125
+ if (material) {
126
+ drawable ||= Array.isArray(material) ? material.some((m) => m.visible) : material.visible;
127
+ }
128
+ shadowLight ||= isLight === true && castShadow;
129
+ if (!nested)
130
+ roots.push(object);
131
+ }
132
+ // Keep source traversal order even when a producer switches layers after
133
+ // mounting. Ancestors on this layer remain intact, retaining group order,
134
+ // clipping and LOD behavior; layers do not inherit through other ancestors.
135
+ roots.sort(compareSceneOrder);
136
+ return { drawable, shadowLight };
137
+ }
138
+ dispose() {
139
+ for (const cleanup of this.cleanups.values())
140
+ cleanup();
141
+ this.cleanups.clear();
142
+ }
143
+ }
144
+ function compareSceneOrder(a, b) {
145
+ const pathA = [];
146
+ const pathB = [];
147
+ for (let object = a; object; object = object.parent)
148
+ pathA.push(object);
149
+ for (let object = b; object; object = object.parent)
150
+ pathB.push(object);
151
+ let i = pathA.length - 1;
152
+ let j = pathB.length - 1;
153
+ while (i >= 0 && j >= 0 && pathA[i] === pathB[j]) {
154
+ i--;
155
+ j--;
156
+ }
157
+ const siblings = pathA[i + 1].children;
158
+ return siblings.indexOf(pathA[i]) - siblings.indexOf(pathB[j]);
159
+ }
160
+ export class LayerPassNode extends PassNode {
161
+ index;
162
+ layer;
163
+ mainPass;
164
+ roots = [];
165
+ size = new Vector2();
166
+ needsClear = true;
167
+ constructor(index, camera, layer, mainPass) {
168
+ super(PassNode.COLOR, index.source, camera);
169
+ this.index = index;
170
+ this.layer = layer;
171
+ this.mainPass = mainPass;
172
+ // A scene view, not cloned meshes: callbacks, environment, fog, materials,
173
+ // skeletons and world matrices retain their original owners and values.
174
+ this.scene = new Proxy(index.source, {
175
+ get: (source, key) => {
176
+ if (key === 'children')
177
+ return this.roots;
178
+ if (key === 'matrixWorldAutoUpdate')
179
+ return false;
180
+ return Reflect.get(source, key, source);
181
+ },
182
+ });
183
+ }
184
+ dispose() {
185
+ this.roots.length = 0;
186
+ super.dispose();
187
+ }
188
+ updateBefore(frame) {
189
+ // NodeFrame deduplicates FRAME updates. Make the dependency explicit rather
190
+ // than relying on which composite expression the TSL builder visits first.
191
+ frame.updateBeforeNode(this.mainPass);
192
+ const { drawable, shadowLight } = this.index.prepare(this.layer, this.roots);
193
+ const renderer = frame.renderer;
194
+ const source = this.index.source;
195
+ const hasBackground = source.background !== null || ('backgroundNode' in source && source.backgroundNode != null);
196
+ const hasSceneCallbacks = source.onBeforeRender !== Object3D.prototype.onBeforeRender ||
197
+ source.onAfterRender !== Object3D.prototype.onAfterRender;
198
+ if (drawable || hasBackground || hasSceneCallbacks) {
199
+ this.needsClear = true;
200
+ // Shadows need all source casters; custom callbacks need the original
201
+ // scene receiver and graph, including when they enable an empty layer.
202
+ const root = this.scene;
203
+ if (shadowLight || hasSceneCallbacks)
204
+ this.scene = source;
205
+ try {
206
+ if (this.scene !== source) {
207
+ const updatedAncestors = new Set();
208
+ const updateAncestor = (object) => {
209
+ if (!object || updatedAncestors.has(object))
210
+ return;
211
+ updateAncestor(object.parent);
212
+ // Force world refreshes even when a manual local matrix is clean.
213
+ object.matrixWorldNeedsUpdate = true;
214
+ object.updateWorldMatrix(false, false);
215
+ updatedAncestors.add(object);
216
+ };
217
+ for (const root of this.roots) {
218
+ updateAncestor(root.parent);
219
+ root.updateMatrixWorld(true);
220
+ }
221
+ }
222
+ super.updateBefore(frame);
223
+ }
224
+ finally {
225
+ this.scene = root;
226
+ }
227
+ return;
228
+ }
229
+ const outputTarget = renderer.getOutputRenderTarget();
230
+ if (outputTarget && 'isXRRenderTarget' in outputTarget && outputTarget.isXRRenderTarget)
231
+ this.size.set(outputTarget.width, outputTarget.height);
232
+ else
233
+ renderer.getDrawingBufferSize(this.size);
234
+ const width = this.renderTarget.width;
235
+ const height = this.renderTarget.height;
236
+ this.setSize(this.size.x, this.size.y);
237
+ if (width !== this.renderTarget.width || height !== this.renderTarget.height)
238
+ this.needsClear = true;
239
+ if (!this.needsClear)
240
+ return;
241
+ const target = renderer.getRenderTarget();
242
+ const mrt = renderer.getMRT();
243
+ try {
244
+ renderer.setRenderTarget(this.renderTarget);
245
+ renderer.setMRT(null);
246
+ renderer.clear(true, true, true);
247
+ this.needsClear = false;
248
+ }
249
+ finally {
250
+ renderer.setRenderTarget(target);
251
+ renderer.setMRT(mrt);
252
+ }
253
+ }
254
+ }
@@ -10,12 +10,21 @@ export declare const BLUEPRINT_PALETTE: Record<SurfaceRole, string>;
10
10
  export declare const PRESET_PALETTES: Record<ColorPreset, Record<SurfaceRole, string>>;
11
11
  export declare function resolveSurfaceColor(role: SurfaceRole, preset: ColorPreset, sceneThemeId?: string): string;
12
12
  export declare const glassMaterial: MeshLambertNodeMaterial;
13
+ export declare function getMaterialTextureVersion(): number;
13
14
  export declare function resolveTextureRepeat(repeat: unknown, scale: unknown): [number, number];
14
15
  export declare function getTextureKey(material?: MaterialSchema): string;
15
16
  export declare function applyMaterialPresetToMaterials(materialInput: THREE.Material | THREE.Material[], preset: MaterialPresetPayload | null | undefined): void;
16
17
  export declare function createMaterialFromPreset(preset: MaterialPresetPayload, shading?: RenderShading): THREE.Material;
17
18
  export declare function createMaterialFromPresetRef(materialPreset?: string, shading?: RenderShading): THREE.Material | null;
18
19
  export declare function createMaterial(material?: MaterialSchema, shading?: RenderShading): THREE.Material;
20
+ /**
21
+ * Cache-signature fragment for a catalog preset ref. Dynamic library
22
+ * materials (AI-generated `library:mtl_*`) register asynchronously, so a ref
23
+ * that fails to resolve is NOT static content: tag it so signature-keyed
24
+ * material caches re-resolve once the library registers instead of pinning
25
+ * the dangling-ref fallback for the whole session.
26
+ */
27
+ export declare function materialPresetRefSignature(ref: string): string;
19
28
  /**
20
29
  * Resolve a MaterialRef ('library:<id>' | 'scene:<id>') to a three.js material.
21
30
  * Returns null for an unknown / dangling ref so callers fall back to the
@@ -41,5 +50,8 @@ export declare function DEFAULT_ROOF_MATERIAL(shading?: RenderShading): THREE.Ma
41
50
  export declare function DEFAULT_SHELF_MATERIAL(shading?: RenderShading): THREE.Material;
42
51
  export declare function DEFAULT_STAIR_MATERIAL(shading?: RenderShading): THREE.Material;
43
52
  export declare function disposeMaterial(material: THREE.Material): void;
53
+ type MaterialCacheCleanup = (() => void) | (() => () => void);
54
+ export declare function registerMaterialCacheCleanup(cleanup: MaterialCacheCleanup): () => void;
44
55
  export declare function clearMaterialCache(): void;
56
+ export {};
45
57
  //# sourceMappingURL=materials.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { getMaterialPresetByRef, parseMaterialRef, resolveMaterial, } from '@pascal-app/core';
2
+ import { addAfterEffect, invalidate } from '@react-three/fiber';
2
3
  import * as THREE from 'three';
3
4
  import { float, mix, positionViewDirection, transformedNormalView } from 'three/tsl';
4
5
  import { MeshLambertNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
@@ -82,6 +83,11 @@ const surfaceRoleMaterialCache = new Map();
82
83
  const textureCache = new Map();
83
84
  const textureLoadPromises = new Map();
84
85
  const textureLoader = new THREE.TextureLoader();
86
+ let materialTextureVersion = 0;
87
+ // Highlight clones can observe late assignments without polling every material.
88
+ export function getMaterialTextureVersion() {
89
+ return materialTextureVersion;
90
+ }
85
91
  // `.ktx2` finish maps transcode through the shared KTX2 loader (support is
86
92
  // detected once at viewer init); everything else loads as a normal image.
87
93
  function pickTextureLoader(url) {
@@ -276,6 +282,7 @@ function queueTextureAssignment(material, slot, path, props) {
276
282
  // and crash in TextureNode.update ("null (reading 'matrix')").
277
283
  textureMaterial[slot] = null;
278
284
  material.needsUpdate = true;
285
+ materialTextureVersion++;
279
286
  }
280
287
  return;
281
288
  }
@@ -289,6 +296,7 @@ function queueTextureAssignment(material, slot, path, props) {
289
296
  if (cached) {
290
297
  textureMaterial[slot] = createAssignedTexture(cached, props, slot);
291
298
  material.needsUpdate = true;
299
+ materialTextureVersion++;
292
300
  return;
293
301
  }
294
302
  // Cold load: clear the slot for the fetch window, and rebuild the node
@@ -298,12 +306,14 @@ function queueTextureAssignment(material, slot, path, props) {
298
306
  if (textureMaterial[slot] != null) {
299
307
  textureMaterial[slot] = null;
300
308
  material.needsUpdate = true;
309
+ materialTextureVersion++;
301
310
  }
302
311
  loadPresetTexture(path, props, slot).then((texture) => {
303
312
  if (!texture)
304
313
  return;
305
314
  textureMaterial[slot] = createAssignedTexture(texture, props, slot);
306
315
  material.needsUpdate = true;
316
+ materialTextureVersion++;
307
317
  });
308
318
  }
309
319
  function applyMaterialMapProperties(material, mapProperties) {
@@ -428,6 +438,16 @@ export function createMaterial(material, shading = 'rendered') {
428
438
  materialCache.set(cacheKey, threeMaterial);
429
439
  return threeMaterial;
430
440
  }
441
+ /**
442
+ * Cache-signature fragment for a catalog preset ref. Dynamic library
443
+ * materials (AI-generated `library:mtl_*`) register asynchronously, so a ref
444
+ * that fails to resolve is NOT static content: tag it so signature-keyed
445
+ * material caches re-resolve once the library registers instead of pinning
446
+ * the dangling-ref fallback for the whole session.
447
+ */
448
+ export function materialPresetRefSignature(ref) {
449
+ return getMaterialPresetByRef(ref) ? ref : `${ref}#unresolved`;
450
+ }
431
451
  /**
432
452
  * Resolve a MaterialRef ('library:<id>' | 'scene:<id>') to a three.js material.
433
453
  * Returns null for an unknown / dangling ref so callers fall back to the
@@ -453,9 +473,9 @@ export function resolveMaterialRef(ref, sceneMaterials, shading = 'rendered') {
453
473
  export function resolveSlotDefaultMaterial(slotDefault, shading = 'rendered', roughness = 0.9) {
454
474
  if (parseMaterialRef(slotDefault)?.kind === 'library') {
455
475
  return (createMaterialFromPresetRef(slotDefault, shading) ??
456
- createDefaultMaterial('#ffffff', roughness, shading));
476
+ cachedDefaultMaterial(`slot-#ffffff-${roughness}`, '#ffffff', roughness, shading));
457
477
  }
458
- return createDefaultMaterial(slotDefault, roughness, shading);
478
+ return cachedDefaultMaterial(`slot-${slotDefault}-${roughness}`, slotDefault, roughness, shading);
459
479
  }
460
480
  export function createDefaultMaterial(color = '#ffffff', roughness = 0.9, shading = 'rendered', side = THREE.FrontSide) {
461
481
  const resolvedSide = resolveNodeMaterialSide(side);
@@ -487,9 +507,7 @@ export function createSurfaceRoleMaterial(role, preset, side = THREE.FrontSide,
487
507
  // on `glassMaterial` above — the validator rejects the back-face variant
488
508
  // for missing MRT outputs and poisons the render context (manifests as
489
509
  // "Color target has no corresponding fragment stage output" on scene
490
- // open, since the dormer's window-assembly mounts the glazing material
491
- // on both gable faces on the first frame). Callers that need both sides
492
- // visible (e.g. dormer back gable) must rotate the host mesh 180° so the
510
+ // open). Callers that need both sides visible must rotate the host mesh 180° so the
493
511
  // FrontSide faces the viewer.
494
512
  const resolvedSide = role === 'glazing' ? THREE.FrontSide : resolveNodeMaterialSide(side ?? THREE.FrontSide);
495
513
  const cacheKey = `${role}-${preset}-${resolvedSide}-${sceneThemeId ?? 'base'}`;
@@ -566,22 +584,38 @@ export function DEFAULT_STAIR_MATERIAL(shading = 'rendered') {
566
584
  export function disposeMaterial(material) {
567
585
  material.dispose();
568
586
  }
587
+ const materialCacheCleanups = new Set();
588
+ export function registerMaterialCacheCleanup(cleanup) {
589
+ materialCacheCleanups.add(cleanup);
590
+ return () => {
591
+ materialCacheCleanups.delete(cleanup);
592
+ };
593
+ }
569
594
  export function clearMaterialCache() {
570
- for (const material of materialCache.values()) {
571
- material.dispose();
572
- }
595
+ const previous = [
596
+ ...materialCache.values(),
597
+ ...defaultMaterialCache.values(),
598
+ ...surfaceRoleMaterialCache.values(),
599
+ ...textureCache.values(),
600
+ ];
573
601
  materialCache.clear();
574
- for (const material of defaultMaterialCache.values()) {
575
- material.dispose();
576
- }
577
602
  defaultMaterialCache.clear();
578
- for (const material of surfaceRoleMaterialCache.values()) {
579
- material.dispose();
580
- }
581
603
  surfaceRoleMaterialCache.clear();
582
- for (const texture of textureCache.values()) {
583
- texture.dispose();
584
- }
585
604
  textureCache.clear();
586
605
  textureLoadPromises.clear();
606
+ const disposals = [];
607
+ for (const cleanup of materialCacheCleanups) {
608
+ const dispose = cleanup();
609
+ if (dispose)
610
+ disposals.push(dispose);
611
+ }
612
+ // Consumers must rebuild sources and release batches before the old caches die.
613
+ const unsubscribe = addAfterEffect(() => {
614
+ unsubscribe();
615
+ for (const resource of previous)
616
+ resource.dispose();
617
+ for (const dispose of disposals)
618
+ dispose();
619
+ });
620
+ invalidate();
587
621
  }
@@ -1,18 +1,15 @@
1
1
  /**
2
2
  * MergedOutlineNode — a fork of Three.js OutlineNode that processes two object
3
- * groups (primary = selected, secondary = hovered) in a single pass, sharing the
4
- * expensive non-selected depth pre-render between both groups.
5
- *
6
- * Cost comparison vs two separate OutlineNode instances:
7
- * Before: depth_A + mask_A + edge_A×6 + depth_B + mask_B + edge_B×6 = 2 depth passes
8
- * After: depth_AB (shared) + mask_A + edge_A×6 + mask_B + edge_B×6 = 1 depth pass
3
+ * groups (primary = selected, secondary = hovered), reusing scene depth when
4
+ * supplied and rendering masks from small private scenes. Callers without scene
5
+ * depth retain one shared non-selected depth pass for both groups.
9
6
  *
10
7
  * Additional early-outs:
11
8
  * - Both empty → skip everything (0 passes)
12
9
  * - Only primary → skip secondary mask/edge/blur
13
10
  * - Only secondary → skip primary mask/edge/blur
14
11
  */
15
- import { type Object3D } from 'three';
12
+ import { Object3D } from 'three';
16
13
  import { TempNode } from 'three/webgpu';
17
14
  export declare class MergedOutlineNode extends TempNode {
18
15
  static get type(): string;
@@ -26,6 +23,7 @@ export declare class MergedOutlineNode extends TempNode {
26
23
  secondaryEdgeGlowNode: any;
27
24
  downSampleRatio: number;
28
25
  updateBeforeType: string;
26
+ private readonly _sceneDepthNode;
29
27
  private readonly _depthRT;
30
28
  private readonly _depthTexUniform;
31
29
  private readonly _groupA;
@@ -62,6 +60,11 @@ export declare class MergedOutlineNode extends TempNode {
62
60
  private readonly _compositeMatB;
63
61
  private readonly _cacheA;
64
62
  private readonly _cacheB;
63
+ private readonly _proxiesA;
64
+ private readonly _proxiesB;
65
+ private readonly _proxyMaskMaterials;
66
+ private readonly _maskSceneA;
67
+ private readonly _maskSceneB;
65
68
  private _wroteGroupALastFrame;
66
69
  private _wroteGroupBLastFrame;
67
70
  private readonly _textureNodeA;
@@ -74,6 +77,8 @@ export declare class MergedOutlineNode extends TempNode {
74
77
  primaryEdgeGlow?: any;
75
78
  secondaryEdgeGlow?: any;
76
79
  downSampleRatio?: number;
80
+ /** Current depth from pass(scene, camera).getTextureNode('depth'). */
81
+ sceneDepthNode?: any;
77
82
  });
78
83
  get primaryVisibleEdge(): any;
79
84
  get primaryHiddenEdge(): any;
@@ -81,8 +86,12 @@ export declare class MergedOutlineNode extends TempNode {
81
86
  get secondaryHiddenEdge(): any;
82
87
  setSize(width: number, height: number): void;
83
88
  updateBefore(frame: any): void;
89
+ private _renderMask;
90
+ private _syncProxies;
91
+ private _supportsProxies;
92
+ private _disposeProxyMaterial;
84
93
  private _runEdgePipeline;
85
- setup(_builder: any): any;
94
+ setup(builder: any): any;
86
95
  dispose(): void;
87
96
  private _buildCache;
88
97
  }
@@ -1 +1 @@
1
- {"version":3,"file":"merged-outline-node.d.ts","sourceRoot":"","sources":["../../src/lib/merged-outline-node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"merged-outline-node.d.ts","sourceRoot":"","sources":["../../src/lib/merged-outline-node.ts"],"names":[],"mappings":"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"}