@pascal-app/viewer 1.0.0-beta.4 → 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 (265) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +107 -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 +11 -0
  52. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  53. package/dist/components/viewer/frame-limiter.js +59 -18
  54. package/dist/components/viewer/index.d.ts +15 -0
  55. package/dist/components/viewer/index.d.ts.map +1 -1
  56. package/dist/components/viewer/index.js +74 -82
  57. package/dist/components/viewer/lights.d.ts.map +1 -1
  58. package/dist/components/viewer/lights.js +123 -89
  59. package/dist/components/viewer/perf-monitor.d.ts +6 -1
  60. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  61. package/dist/components/viewer/perf-monitor.js +306 -115
  62. package/dist/components/viewer/perf-panel.d.ts +3 -0
  63. package/dist/components/viewer/perf-panel.d.ts.map +1 -0
  64. package/dist/components/viewer/perf-panel.js +178 -0
  65. package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
  66. package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
  67. package/dist/components/viewer/pointer-raycast-layers.js +28 -0
  68. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  69. package/dist/components/viewer/post-processing.js +124 -83
  70. package/dist/components/viewer/registered-systems.d.ts +14 -5
  71. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  72. package/dist/components/viewer/registered-systems.js +22 -10
  73. package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
  74. package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
  75. package/dist/components/viewer/scene-atmosphere.js +89 -0
  76. package/dist/components/viewer/scene-environment.d.ts.map +1 -1
  77. package/dist/components/viewer/scene-environment.js +19 -5
  78. package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
  79. package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
  80. package/dist/components/viewer/scene-ground-replacement.js +35 -0
  81. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  82. package/dist/components/viewer/selection-manager.js +7 -2
  83. package/dist/components/viewer/unsupported-gpu-fallback.d.ts +2 -0
  84. package/dist/components/viewer/unsupported-gpu-fallback.d.ts.map +1 -0
  85. package/dist/components/viewer/unsupported-gpu-fallback.js +4 -0
  86. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  87. package/dist/components/viewer/viewer-camera.js +4 -1
  88. package/dist/components/viewer/viewer-presentations.d.ts +67 -0
  89. package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
  90. package/dist/components/viewer/viewer-presentations.js +120 -0
  91. package/dist/hooks/use-library-materials-version.d.ts +7 -0
  92. package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
  93. package/dist/hooks/use-library-materials-version.js +10 -0
  94. package/dist/hooks/use-node-events.d.ts.map +1 -1
  95. package/dist/hooks/use-node-events.js +1 -0
  96. package/dist/index.d.ts +20 -8
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +19 -7
  99. package/dist/lib/csg-utils.d.ts +1 -0
  100. package/dist/lib/csg-utils.d.ts.map +1 -1
  101. package/dist/lib/csg-utils.js +6 -1
  102. package/dist/lib/dispose-object3d.d.ts +4 -0
  103. package/dist/lib/dispose-object3d.d.ts.map +1 -0
  104. package/dist/lib/dispose-object3d.js +27 -0
  105. package/dist/lib/geometry-groups.d.ts +17 -0
  106. package/dist/lib/geometry-groups.d.ts.map +1 -0
  107. package/dist/lib/geometry-groups.js +53 -0
  108. package/dist/lib/gpu-perf.d.ts +0 -2
  109. package/dist/lib/gpu-perf.d.ts.map +1 -1
  110. package/dist/lib/gpu-perf.js +7 -21
  111. package/dist/lib/isolation.d.ts +8 -8
  112. package/dist/lib/isolation.d.ts.map +1 -1
  113. package/dist/lib/isolation.js +18 -30
  114. package/dist/lib/layer-pass.d.ts +28 -0
  115. package/dist/lib/layer-pass.d.ts.map +1 -0
  116. package/dist/lib/layer-pass.js +254 -0
  117. package/dist/lib/layers.d.ts +16 -0
  118. package/dist/lib/layers.d.ts.map +1 -1
  119. package/dist/lib/layers.js +18 -0
  120. package/dist/lib/materials.d.ts +12 -0
  121. package/dist/lib/materials.d.ts.map +1 -1
  122. package/dist/lib/materials.js +51 -17
  123. package/dist/lib/merged-outline-node.d.ts +17 -8
  124. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  125. package/dist/lib/merged-outline-node.js +194 -52
  126. package/dist/lib/perf-actions.d.ts +55 -0
  127. package/dist/lib/perf-actions.d.ts.map +1 -0
  128. package/dist/lib/perf-actions.js +211 -0
  129. package/dist/lib/perf-observers.d.ts +2 -0
  130. package/dist/lib/perf-observers.d.ts.map +1 -0
  131. package/dist/lib/perf-observers.js +17 -0
  132. package/dist/lib/perf-panel-store.d.ts +71 -0
  133. package/dist/lib/perf-panel-store.d.ts.map +1 -0
  134. package/dist/lib/perf-panel-store.js +29 -0
  135. package/dist/lib/perf-tracks.d.ts +43 -0
  136. package/dist/lib/perf-tracks.d.ts.map +1 -0
  137. package/dist/lib/perf-tracks.js +113 -0
  138. package/dist/lib/pointer-events.d.ts +7 -0
  139. package/dist/lib/pointer-events.d.ts.map +1 -0
  140. package/dist/lib/pointer-events.js +680 -0
  141. package/dist/lib/post-processing-resources.d.ts +12 -0
  142. package/dist/lib/post-processing-resources.d.ts.map +1 -0
  143. package/dist/lib/post-processing-resources.js +19 -0
  144. package/dist/lib/renderer-capability.d.ts +56 -0
  145. package/dist/lib/renderer-capability.d.ts.map +1 -0
  146. package/dist/lib/renderer-capability.js +116 -0
  147. package/dist/lib/scene-visibility.d.ts +15 -0
  148. package/dist/lib/scene-visibility.d.ts.map +1 -0
  149. package/dist/lib/scene-visibility.js +69 -0
  150. package/dist/lib/shadow-only.d.ts +16 -0
  151. package/dist/lib/shadow-only.d.ts.map +1 -1
  152. package/dist/lib/shadow-only.js +8 -17
  153. package/dist/lib/snapshot-pipeline.d.ts +9 -0
  154. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  155. package/dist/lib/snapshot-pipeline.js +33 -9
  156. package/dist/store/use-viewer.d.ts +7 -6
  157. package/dist/store/use-viewer.d.ts.map +1 -1
  158. package/dist/systems/ceiling/ceiling-system.js +1 -1
  159. package/dist/systems/door/door-animation-system.d.ts.map +1 -1
  160. package/dist/systems/door/door-animation-system.js +6 -9
  161. package/dist/systems/door/door-system.d.ts.map +1 -1
  162. package/dist/systems/door/door-system.js +13 -2
  163. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
  164. package/dist/systems/floor-elevation/floor-elevation-system.js +9 -0
  165. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  166. package/dist/systems/geometry/geometry-system.js +5 -28
  167. package/dist/systems/level/level-system.d.ts.map +1 -1
  168. package/dist/systems/level/level-system.js +8 -2
  169. package/dist/systems/level/level-utils.d.ts +9 -0
  170. package/dist/systems/level/level-utils.d.ts.map +1 -1
  171. package/dist/systems/level/level-utils.js +14 -0
  172. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
  173. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
  174. package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
  175. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  176. package/dist/systems/roof/roof-system.js +1196 -58
  177. package/dist/systems/scan/scan-system.js +6 -5
  178. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  179. package/dist/systems/slab/slab-system.js +1 -2
  180. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  181. package/dist/systems/stair/stair-system.js +9 -8
  182. package/dist/systems/surface-hole-geometry.d.ts +1 -1
  183. package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
  184. package/dist/systems/surface-hole-geometry.js +1 -1
  185. package/dist/systems/wall/level-miter-cache.d.ts +10 -0
  186. package/dist/systems/wall/level-miter-cache.d.ts.map +1 -0
  187. package/dist/systems/wall/level-miter-cache.js +40 -0
  188. package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
  189. package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
  190. package/dist/systems/wall/wall-build-lifecycle.js +74 -0
  191. package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
  192. package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
  193. package/dist/systems/wall/wall-cutout-cache.js +304 -0
  194. package/dist/systems/wall/wall-cutout.d.ts +24 -1
  195. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  196. package/dist/systems/wall/wall-cutout.js +33 -140
  197. package/dist/systems/wall/wall-material-variant.d.ts +31 -0
  198. package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
  199. package/dist/systems/wall/wall-material-variant.js +10 -0
  200. package/dist/systems/wall/wall-materials.d.ts +6 -0
  201. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  202. package/dist/systems/wall/wall-materials.js +29 -13
  203. package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
  204. package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
  205. package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
  206. package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
  207. package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
  208. package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
  209. package/dist/systems/wall/wall-system.d.ts +13 -2
  210. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  211. package/dist/systems/wall/wall-system.js +413 -148
  212. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  213. package/dist/systems/window/window-animation-system.js +9 -8
  214. package/dist/systems/window/window-system.d.ts +1 -0
  215. package/dist/systems/window/window-system.d.ts.map +1 -1
  216. package/dist/systems/window/window-system.js +16 -2
  217. package/package.json +18 -3
  218. package/dist/components/viewer/scene-bvh.test.d.ts +0 -2
  219. package/dist/components/viewer/scene-bvh.test.d.ts.map +0 -1
  220. package/dist/components/viewer/scene-bvh.test.js +0 -13
  221. package/dist/lib/csg-utils.test.d.ts +0 -2
  222. package/dist/lib/csg-utils.test.d.ts.map +0 -1
  223. package/dist/lib/csg-utils.test.js +0 -45
  224. package/dist/lib/materials.test.d.ts +0 -2
  225. package/dist/lib/materials.test.d.ts.map +0 -1
  226. package/dist/lib/materials.test.js +0 -25
  227. package/dist/lib/merged-outline-node.test.d.ts +0 -2
  228. package/dist/lib/merged-outline-node.test.d.ts.map +0 -1
  229. package/dist/lib/merged-outline-node.test.js +0 -22
  230. package/dist/lib/polygon-union.d.ts +0 -4
  231. package/dist/lib/polygon-union.d.ts.map +0 -1
  232. package/dist/lib/polygon-union.js +0 -289
  233. package/dist/lib/polygon-union.test.d.ts +0 -2
  234. package/dist/lib/polygon-union.test.d.ts.map +0 -1
  235. package/dist/lib/polygon-union.test.js +0 -105
  236. package/dist/lib/texture-reference.test.d.ts +0 -2
  237. package/dist/lib/texture-reference.test.d.ts.map +0 -1
  238. package/dist/lib/texture-reference.test.js +0 -86
  239. package/dist/store/use-viewer.test.d.ts +0 -2
  240. package/dist/store/use-viewer.test.d.ts.map +0 -1
  241. package/dist/store/use-viewer.test.js +0 -61
  242. package/dist/systems/door/door-animation.test.d.ts +0 -2
  243. package/dist/systems/door/door-animation.test.d.ts.map +0 -1
  244. package/dist/systems/door/door-animation.test.js +0 -51
  245. package/dist/systems/door/door-floor-alignment.test.d.ts +0 -2
  246. package/dist/systems/door/door-floor-alignment.test.d.ts.map +0 -1
  247. package/dist/systems/door/door-floor-alignment.test.js +0 -50
  248. package/dist/systems/geometry/geometry-system.test.d.ts +0 -2
  249. package/dist/systems/geometry/geometry-system.test.d.ts.map +0 -1
  250. package/dist/systems/geometry/geometry-system.test.js +0 -30
  251. package/dist/systems/slab/slab-system.test.d.ts +0 -2
  252. package/dist/systems/slab/slab-system.test.d.ts.map +0 -1
  253. package/dist/systems/slab/slab-system.test.js +0 -102
  254. package/dist/systems/wall/opening-cutout-geometry.test.d.ts +0 -2
  255. package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +0 -1
  256. package/dist/systems/wall/opening-cutout-geometry.test.js +0 -172
  257. package/dist/systems/wall/wall-opening-cutout.test.d.ts +0 -2
  258. package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +0 -1
  259. package/dist/systems/wall/wall-opening-cutout.test.js +0 -59
  260. package/dist/systems/wall/wall-support-extension.test.d.ts +0 -2
  261. package/dist/systems/wall/wall-support-extension.test.d.ts.map +0 -1
  262. package/dist/systems/wall/wall-support-extension.test.js +0 -116
  263. package/dist/systems/wall/wall-terrain-bottom.test.d.ts +0 -2
  264. package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +0 -1
  265. package/dist/systems/wall/wall-terrain-bottom.test.js +0 -34
@@ -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
+ }
@@ -1,3 +1,4 @@
1
+ import type { Layers } from 'three';
1
2
  /** Default Three.js layer for main scene geometry. */
2
3
  export declare const SCENE_LAYER = 0;
3
4
  /**
@@ -30,4 +31,19 @@ export declare const GRID_LAYER = 3;
30
31
  * cascade) via `applyShadowOnly` / `clearShadowOnly` in `lib/shadow-only.ts`.
31
32
  */
32
33
  export declare const SHADOW_ONLY_LAYER = 4;
34
+ /**
35
+ * Layer for source geometry that a collective batch already draws. No camera
36
+ * or pass enables it, so a batched object costs no draw call while its children
37
+ * and interaction proxies remain in the graph.
38
+ *
39
+ * Raycasters that query real surfaces must opt in via
40
+ * {@link setSurfaceRaycastLayers}, otherwise a sewn wall would stop answering
41
+ * measurement rays.
42
+ */
43
+ export declare const BATCHED_LAYER = 5;
44
+ /**
45
+ * Aims a raycaster at every real scene surface, whether a wall still draws
46
+ * itself or a level batch draws it for us.
47
+ */
48
+ export declare function setSurfaceRaycastLayers(layers: Layers): void;
33
49
  //# sourceMappingURL=layers.d.ts.map
@@ -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;AAE3B;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAA"}
1
+ {"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/lib/layers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEnC,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;AAElC;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,IAAI,CAAA;AAE9B;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAG5D"}
@@ -30,3 +30,21 @@ export const GRID_LAYER = 3;
30
30
  * cascade) via `applyShadowOnly` / `clearShadowOnly` in `lib/shadow-only.ts`.
31
31
  */
32
32
  export const SHADOW_ONLY_LAYER = 4;
33
+ /**
34
+ * Layer for source geometry that a collective batch already draws. No camera
35
+ * or pass enables it, so a batched object costs no draw call while its children
36
+ * and interaction proxies remain in the graph.
37
+ *
38
+ * Raycasters that query real surfaces must opt in via
39
+ * {@link setSurfaceRaycastLayers}, otherwise a sewn wall would stop answering
40
+ * measurement rays.
41
+ */
42
+ export const BATCHED_LAYER = 5;
43
+ /**
44
+ * Aims a raycaster at every real scene surface, whether a wall still draws
45
+ * itself or a level batch draws it for us.
46
+ */
47
+ export function setSurfaceRaycastLayers(layers) {
48
+ layers.set(SCENE_LAYER);
49
+ layers.enable(BATCHED_LAYER);
50
+ }
@@ -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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"}