@pascal-app/viewer 1.0.0-beta.5 → 1.0.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 (208) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -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 +198 -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 +76 -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 +70 -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/capture/use-json-artifact.d.ts +2 -0
  49. package/dist/capture/use-json-artifact.d.ts.map +1 -0
  50. package/dist/capture/use-json-artifact.js +42 -0
  51. package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
  52. package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
  53. package/dist/components/viewer/batched-mesh-spike.js +64 -0
  54. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  55. package/dist/components/viewer/frame-limiter.js +4 -3
  56. package/dist/components/viewer/glb-scene.d.ts.map +1 -1
  57. package/dist/components/viewer/glb-scene.js +3 -1
  58. package/dist/components/viewer/index.d.ts.map +1 -1
  59. package/dist/components/viewer/index.js +70 -62
  60. package/dist/components/viewer/lights.d.ts.map +1 -1
  61. package/dist/components/viewer/lights.js +123 -89
  62. package/dist/components/viewer/perf-monitor.d.ts +6 -1
  63. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  64. package/dist/components/viewer/perf-monitor.js +307 -116
  65. package/dist/components/viewer/perf-panel.d.ts +3 -0
  66. package/dist/components/viewer/perf-panel.d.ts.map +1 -0
  67. package/dist/components/viewer/perf-panel.js +178 -0
  68. package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
  69. package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
  70. package/dist/components/viewer/pointer-raycast-layers.js +28 -0
  71. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  72. package/dist/components/viewer/post-processing.js +130 -83
  73. package/dist/components/viewer/registered-systems.d.ts +14 -5
  74. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  75. package/dist/components/viewer/registered-systems.js +22 -10
  76. package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
  77. package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
  78. package/dist/components/viewer/scene-atmosphere.js +89 -0
  79. package/dist/components/viewer/scene-environment.d.ts.map +1 -1
  80. package/dist/components/viewer/scene-environment.js +19 -5
  81. package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
  82. package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
  83. package/dist/components/viewer/scene-ground-replacement.js +35 -0
  84. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  85. package/dist/components/viewer/selection-manager.js +8 -16
  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 +16 -7
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +16 -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/gpu-perf.d.ts +0 -2
  103. package/dist/lib/gpu-perf.d.ts.map +1 -1
  104. package/dist/lib/gpu-perf.js +7 -21
  105. package/dist/lib/isolation.d.ts +14 -30
  106. package/dist/lib/isolation.d.ts.map +1 -1
  107. package/dist/lib/isolation.js +62 -66
  108. package/dist/lib/layer-pass.d.ts +28 -0
  109. package/dist/lib/layer-pass.d.ts.map +1 -0
  110. package/dist/lib/layer-pass.js +254 -0
  111. package/dist/lib/materials.d.ts +12 -0
  112. package/dist/lib/materials.d.ts.map +1 -1
  113. package/dist/lib/materials.js +52 -17
  114. package/dist/lib/merged-outline-node.d.ts +17 -8
  115. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  116. package/dist/lib/merged-outline-node.js +194 -52
  117. package/dist/lib/perf-actions.d.ts +55 -0
  118. package/dist/lib/perf-actions.d.ts.map +1 -0
  119. package/dist/lib/perf-actions.js +211 -0
  120. package/dist/lib/perf-observers.d.ts +2 -0
  121. package/dist/lib/perf-observers.d.ts.map +1 -0
  122. package/dist/lib/perf-observers.js +17 -0
  123. package/dist/lib/perf-panel-store.d.ts +71 -0
  124. package/dist/lib/perf-panel-store.d.ts.map +1 -0
  125. package/dist/lib/perf-panel-store.js +29 -0
  126. package/dist/lib/perf-tracks.d.ts +43 -0
  127. package/dist/lib/perf-tracks.d.ts.map +1 -0
  128. package/dist/lib/perf-tracks.js +113 -0
  129. package/dist/lib/pointer-events.d.ts +7 -0
  130. package/dist/lib/pointer-events.d.ts.map +1 -0
  131. package/dist/lib/pointer-events.js +680 -0
  132. package/dist/lib/post-processing-resources.d.ts +12 -0
  133. package/dist/lib/post-processing-resources.d.ts.map +1 -0
  134. package/dist/lib/post-processing-resources.js +19 -0
  135. package/dist/lib/renderer-capability.d.ts.map +1 -1
  136. package/dist/lib/renderer-capability.js +4 -2
  137. package/dist/lib/scene-visibility.d.ts +2 -1
  138. package/dist/lib/scene-visibility.d.ts.map +1 -1
  139. package/dist/lib/scene-visibility.js +25 -2
  140. package/dist/lib/snapshot-pipeline.d.ts +3 -1
  141. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  142. package/dist/lib/snapshot-pipeline.js +32 -16
  143. package/dist/store/use-viewer.d.ts +5 -3
  144. package/dist/store/use-viewer.d.ts.map +1 -1
  145. package/dist/store/use-viewer.js +2 -0
  146. package/dist/systems/ceiling/ceiling-system.js +1 -1
  147. package/dist/systems/door/door-animation-system.d.ts.map +1 -1
  148. package/dist/systems/door/door-animation-system.js +6 -9
  149. package/dist/systems/door/door-system.d.ts.map +1 -1
  150. package/dist/systems/door/door-system.js +13 -2
  151. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
  152. package/dist/systems/floor-elevation/floor-elevation-system.js +7 -3
  153. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  154. package/dist/systems/geometry/geometry-system.js +7 -2
  155. package/dist/systems/level/level-system.d.ts.map +1 -1
  156. package/dist/systems/level/level-system.js +1 -1
  157. package/dist/systems/level/level-utils.d.ts +9 -0
  158. package/dist/systems/level/level-utils.d.ts.map +1 -1
  159. package/dist/systems/level/level-utils.js +14 -0
  160. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
  161. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
  162. package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
  163. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  164. package/dist/systems/roof/roof-system.js +1196 -58
  165. package/dist/systems/scan/scan-system.js +6 -5
  166. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  167. package/dist/systems/slab/slab-system.js +1 -2
  168. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  169. package/dist/systems/stair/stair-system.js +9 -8
  170. package/dist/systems/surface-hole-geometry.d.ts +1 -1
  171. package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
  172. package/dist/systems/surface-hole-geometry.js +1 -1
  173. package/dist/systems/wall/level-miter-cache.d.ts.map +1 -1
  174. package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
  175. package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
  176. package/dist/systems/wall/wall-build-lifecycle.js +74 -0
  177. package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
  178. package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
  179. package/dist/systems/wall/wall-cutout-cache.js +304 -0
  180. package/dist/systems/wall/wall-cutout.d.ts +12 -1
  181. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  182. package/dist/systems/wall/wall-cutout.js +24 -161
  183. package/dist/systems/wall/wall-material-variant.d.ts +31 -0
  184. package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
  185. package/dist/systems/wall/wall-material-variant.js +10 -0
  186. package/dist/systems/wall/wall-materials.d.ts +6 -0
  187. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  188. package/dist/systems/wall/wall-materials.js +29 -13
  189. package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
  190. package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
  191. package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
  192. package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
  193. package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
  194. package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
  195. package/dist/systems/wall/wall-system.d.ts +11 -4
  196. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  197. package/dist/systems/wall/wall-system.js +405 -153
  198. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  199. package/dist/systems/window/window-animation-system.js +9 -8
  200. package/dist/systems/window/window-system.d.ts +1 -0
  201. package/dist/systems/window/window-system.d.ts.map +1 -1
  202. package/dist/systems/window/window-system.js +16 -2
  203. package/dist/systems/zone/zone-system.d.ts.map +1 -1
  204. package/dist/systems/zone/zone-system.js +28 -2
  205. package/package.json +17 -4
  206. package/dist/lib/polygon-union.d.ts +0 -4
  207. package/dist/lib/polygon-union.d.ts.map +0 -1
  208. package/dist/lib/polygon-union.js +0 -289
@@ -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,10 @@ 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
+ const color = slotDefault.startsWith('#') ? slotDefault.toLowerCase() : slotDefault;
479
+ return cachedDefaultMaterial(`slot-${color}-${roughness}`, color, roughness, shading);
459
480
  }
460
481
  export function createDefaultMaterial(color = '#ffffff', roughness = 0.9, shading = 'rendered', side = THREE.FrontSide) {
461
482
  const resolvedSide = resolveNodeMaterialSide(side);
@@ -487,9 +508,7 @@ export function createSurfaceRoleMaterial(role, preset, side = THREE.FrontSide,
487
508
  // on `glassMaterial` above — the validator rejects the back-face variant
488
509
  // for missing MRT outputs and poisons the render context (manifests as
489
510
  // "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
511
+ // open). Callers that need both sides visible must rotate the host mesh 180° so the
493
512
  // FrontSide faces the viewer.
494
513
  const resolvedSide = role === 'glazing' ? THREE.FrontSide : resolveNodeMaterialSide(side ?? THREE.FrontSide);
495
514
  const cacheKey = `${role}-${preset}-${resolvedSide}-${sceneThemeId ?? 'base'}`;
@@ -566,22 +585,38 @@ export function DEFAULT_STAIR_MATERIAL(shading = 'rendered') {
566
585
  export function disposeMaterial(material) {
567
586
  material.dispose();
568
587
  }
588
+ const materialCacheCleanups = new Set();
589
+ export function registerMaterialCacheCleanup(cleanup) {
590
+ materialCacheCleanups.add(cleanup);
591
+ return () => {
592
+ materialCacheCleanups.delete(cleanup);
593
+ };
594
+ }
569
595
  export function clearMaterialCache() {
570
- for (const material of materialCache.values()) {
571
- material.dispose();
572
- }
596
+ const previous = [
597
+ ...materialCache.values(),
598
+ ...defaultMaterialCache.values(),
599
+ ...surfaceRoleMaterialCache.values(),
600
+ ...textureCache.values(),
601
+ ];
573
602
  materialCache.clear();
574
- for (const material of defaultMaterialCache.values()) {
575
- material.dispose();
576
- }
577
603
  defaultMaterialCache.clear();
578
- for (const material of surfaceRoleMaterialCache.values()) {
579
- material.dispose();
580
- }
581
604
  surfaceRoleMaterialCache.clear();
582
- for (const texture of textureCache.values()) {
583
- texture.dispose();
584
- }
585
605
  textureCache.clear();
586
606
  textureLoadPromises.clear();
607
+ const disposals = [];
608
+ for (const cleanup of materialCacheCleanups) {
609
+ const dispose = cleanup();
610
+ if (dispose)
611
+ disposals.push(dispose);
612
+ }
613
+ // Consumers must rebuild sources and release batches before the old caches die.
614
+ const unsubscribe = addAfterEffect(() => {
615
+ unsubscribe();
616
+ for (const resource of previous)
617
+ resource.dispose();
618
+ for (const dispose of disposals)
619
+ dispose();
620
+ });
621
+ invalidate();
587
622
  }
@@ -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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"}
@@ -2,20 +2,17 @@
2
2
  // this file is a fork of OutlineNode and is intentionally exempt from strict TS checking.
3
3
  /**
4
4
  * MergedOutlineNode — a fork of Three.js OutlineNode that processes two object
5
- * groups (primary = selected, secondary = hovered) in a single pass, sharing the
6
- * expensive non-selected depth pre-render between both groups.
7
- *
8
- * Cost comparison vs two separate OutlineNode instances:
9
- * Before: depth_A + mask_A + edge_A×6 + depth_B + mask_B + edge_B×6 = 2 depth passes
10
- * After: depth_AB (shared) + mask_A + edge_A×6 + mask_B + edge_B×6 = 1 depth pass
5
+ * groups (primary = selected, secondary = hovered), reusing scene depth when
6
+ * supplied and rendering masks from small private scenes. Callers without scene
7
+ * depth retain one shared non-selected depth pass for both groups.
11
8
  *
12
9
  * Additional early-outs:
13
10
  * - Both empty → skip everything (0 passes)
14
11
  * - Only primary → skip secondary mask/edge/blur
15
12
  * - Only secondary → skip primary mask/edge/blur
16
13
  */
17
- import { DepthTexture, FloatType, RenderTarget, Vector2 } from 'three';
18
- import { color, exp, Fn, float, int, Loop, min, mul, nodeObject, orthographicDepthToViewZ, passTexture, perspectiveDepthToViewZ, positionView, reference, screenUV, texture, textureSize, uniform, uv, vec2, vec3, vec4, } from 'three/tsl';
14
+ import { DepthTexture, FloatType, Mesh, Object3D, RenderTarget, Scene, Sprite, Vector2, } from 'three';
15
+ import { builtin, color, exp, Fn, float, floatBitsToUint, int, Loop, min, mul, nodeObject, orthographicDepthToViewZ, passTexture, perspectiveDepthToViewZ, positionView, reference, screenUV, texture, textureSize, uniform, uv, vec2, vec3, vec4, } from 'three/tsl';
19
16
  import { NodeMaterial, NodeUpdateType, QuadMesh, RendererUtils, SpriteNodeMaterial, TempNode, } from 'three/webgpu';
20
17
  import { hasDrawableGeometry } from './drawable-geometry';
21
18
  const _quadMesh = new QuadMesh();
@@ -27,6 +24,8 @@ let _rendererState; // eslint-disable-line @typescript-eslint/no-explicit-any
27
24
  // Helper: render targets for one outline group
28
25
  // ---------------------------------------------------------------------------
29
26
  function makeGroupTargets(downSampleRatio) {
27
+ // Preserve the original pixel-center mask coverage. MSAA-resolved masks mix
28
+ // visible/background samples into hidden edges before edge detection.
30
29
  const maskBuffer = new RenderTarget();
31
30
  const maskDownSample = new RenderTarget(1, 1, { depthBuffer: false });
32
31
  const edgeBuffer1 = new RenderTarget(1, 1, { depthBuffer: false });
@@ -85,6 +84,7 @@ export class MergedOutlineNode extends TempNode {
85
84
  secondaryEdgeGlowNode;
86
85
  downSampleRatio;
87
86
  updateBeforeType;
87
+ _sceneDepthNode;
88
88
  _depthRT;
89
89
  _depthTexUniform;
90
90
  _groupA;
@@ -121,6 +121,11 @@ export class MergedOutlineNode extends TempNode {
121
121
  _compositeMatB;
122
122
  _cacheA = new Set();
123
123
  _cacheB = new Set();
124
+ _proxiesA = new Map();
125
+ _proxiesB = new Map();
126
+ _proxyMaskMaterials = new WeakMap();
127
+ _maskSceneA = new Scene();
128
+ _maskSceneB = new Scene();
124
129
  // Tracks whether either group rendered last frame. We use this to decide
125
130
  // when it's safe to skip renderer state manipulation entirely — touching
126
131
  // the renderer (resetRendererAndSceneState + setRenderTarget + clearColor)
@@ -132,7 +137,12 @@ export class MergedOutlineNode extends TempNode {
132
137
  _textureNodeB;
133
138
  constructor(scene, camera, params = {}) {
134
139
  super('vec4');
135
- const { primaryObjects = [], secondaryObjects = [], primaryEdgeThickness = float(1), secondaryEdgeThickness = float(1), primaryEdgeGlow = float(0), secondaryEdgeGlow = float(0), downSampleRatio = 2, } = params;
140
+ const { primaryObjects = [], secondaryObjects = [], primaryEdgeThickness = float(1), secondaryEdgeThickness = float(1), primaryEdgeGlow = float(0), secondaryEdgeGlow = float(0), downSampleRatio = 2, sceneDepthNode = null, } = params;
141
+ this._sceneDepthNode = sceneDepthNode;
142
+ this._maskSceneA.matrixWorldAutoUpdate = false;
143
+ this._maskSceneB.matrixWorldAutoUpdate = false;
144
+ this._maskSceneA.name = 'MergedOutline [ Mask A ]';
145
+ this._maskSceneB.name = 'MergedOutline [ Mask B ]';
136
146
  this.scene = scene;
137
147
  this.camera = camera;
138
148
  this.primaryObjects = primaryObjects;
@@ -233,6 +243,11 @@ export class MergedOutlineNode extends TempNode {
233
243
  }
234
244
  const { renderer } = frame;
235
245
  const { camera, scene } = this;
246
+ // Update the producer before resetting renderer state, even if this outline
247
+ // is the first consumer in the graph. NodeFrame deduplicates FRAME updates.
248
+ if (hasAny && this._sceneDepthNode?.passNode) {
249
+ frame.updateBeforeNode(this._sceneDepthNode.passNode);
250
+ }
236
251
  _rendererState = RendererUtils.resetRendererAndSceneState(renderer, scene, _rendererState);
237
252
  const size = renderer.getDrawingBufferSize(_size);
238
253
  this.setSize(size.width, size.height);
@@ -248,6 +263,10 @@ export class MergedOutlineNode extends TempNode {
248
263
  renderer.clearColor();
249
264
  this._wroteGroupBLastFrame = false;
250
265
  }
266
+ this._buildCache(this.primaryObjects, this._cacheA);
267
+ this._buildCache(this.secondaryObjects, this._cacheB);
268
+ const useProxiesA = this._syncProxies(this._cacheA, this._proxiesA, this._maskSceneA);
269
+ const useProxiesB = this._syncProxies(this._cacheB, this._proxiesB, this._maskSceneB);
251
270
  if (!hasAny) {
252
271
  RendererUtils.restoreRendererAndSceneState(renderer, scene, _rendererState);
253
272
  return;
@@ -255,55 +274,26 @@ export class MergedOutlineNode extends TempNode {
255
274
  renderer.setClearColor(0xff_ff_ff, 1);
256
275
  this._wroteGroupALastFrame = hasPrimary;
257
276
  this._wroteGroupBLastFrame = hasSecondary;
258
- if (hasPrimary)
259
- this._buildCache(this.primaryObjects, this._cacheA);
260
- if (hasSecondary)
261
- this._buildCache(this.secondaryObjects, this._cacheB);
262
277
  const savedName = scene.name;
263
- // ── 1. Shared depth pass: all objects NOT in either group ─────────────────
264
- renderer.setRenderTarget(this._depthRT);
265
- renderer.setRenderObjectFunction((obj, sc, cam, geo, _mat, grp, lights, clip) => {
266
- if (!hasDrawableGeometry(geo))
267
- return;
268
- const inCache = this._cacheA.has(obj) || this._cacheB.has(obj);
269
- if (!inCache) {
270
- const m = obj.isSprite ? this._depthSpriteMaterial : this._depthMaterial;
271
- renderer.renderObject(obj, sc, cam, geo, m, grp, lights, clip);
272
- }
273
- });
274
- scene.name = 'MergedOutline [ Depth ]';
275
- renderer.render(scene, camera);
276
- // ── 2a. Primary mask pass ─────────────────────────────────────────────────
277
- if (hasPrimary) {
278
- renderer.setRenderTarget(this._groupA.maskBuffer);
279
- renderer.setRenderObjectFunction((obj, sc, cam, geo, _mat, grp, lights, clip) => {
280
- if (!hasDrawableGeometry(geo))
281
- return;
282
- if (this._cacheA.has(obj)) {
283
- const m = obj.isSprite ? this._prepareMaskSpriteMatA : this._prepareMaskMatA;
284
- renderer.renderObject(obj, sc, cam, geo, m, grp, lights, clip);
285
- }
286
- });
287
- scene.name = 'MergedOutline [ Mask A ]';
288
- renderer.render(scene, camera);
289
- }
290
- // ── 2b. Secondary mask pass ───────────────────────────────────────────────
291
- if (hasSecondary) {
292
- renderer.setRenderTarget(this._groupB.maskBuffer);
278
+ if (!this._sceneDepthNode) {
279
+ renderer.setRenderTarget(this._depthRT);
293
280
  renderer.setRenderObjectFunction((obj, sc, cam, geo, _mat, grp, lights, clip) => {
294
281
  if (!hasDrawableGeometry(geo))
295
282
  return;
296
- if (this._cacheB.has(obj)) {
297
- const m = obj.isSprite ? this._prepareMaskSpriteMatB : this._prepareMaskMatB;
283
+ if (!(this._cacheA.has(obj) || this._cacheB.has(obj))) {
284
+ const m = obj.isSprite ? this._depthSpriteMaterial : this._depthMaterial;
298
285
  renderer.renderObject(obj, sc, cam, geo, m, grp, lights, clip);
299
286
  }
300
287
  });
301
- scene.name = 'MergedOutline [ Mask B ]';
288
+ scene.name = 'MergedOutline [ Depth ]';
302
289
  renderer.render(scene, camera);
303
290
  }
291
+ // The fallback depth render may have updated source matrices since sync.
292
+ if (hasPrimary)
293
+ this._renderMask(renderer, 'A', useProxiesA);
294
+ if (hasSecondary)
295
+ this._renderMask(renderer, 'B', useProxiesB);
304
296
  renderer.setRenderObjectFunction(_rendererState.renderObjectFunction);
305
- this._cacheA.clear();
306
- this._cacheB.clear();
307
297
  scene.name = savedName;
308
298
  // ── 3–7. Edge detect + blur + composite per active group ──────────────────
309
299
  if (hasPrimary)
@@ -312,6 +302,117 @@ export class MergedOutlineNode extends TempNode {
312
302
  this._runEdgePipeline(renderer, 'B');
313
303
  RendererUtils.restoreRendererAndSceneState(renderer, scene, _rendererState);
314
304
  }
305
+ _renderMask(renderer, group, useProxies) {
306
+ const isA = group === 'A';
307
+ const cache = isA ? this._cacheA : this._cacheB;
308
+ const proxies = isA ? this._proxiesA : this._proxiesB;
309
+ const maskScene = isA ? this._maskSceneA : this._maskSceneB;
310
+ const material = isA ? this._prepareMaskMatA : this._prepareMaskMatB;
311
+ const spriteMaterial = isA ? this._prepareMaskSpriteMatA : this._prepareMaskSpriteMatB;
312
+ if (useProxies) {
313
+ for (const [source, proxy] of proxies)
314
+ proxy.matrixWorld.copy(source.matrixWorld);
315
+ }
316
+ renderer.setRenderTarget((isA ? this._groupA : this._groupB).maskBuffer);
317
+ // Keep source materials on proxies for material visibility and geometry
318
+ // groups; substitute only at submission, just like the full-scene path.
319
+ renderer.setRenderObjectFunction((obj, sc, cam, geo, _mat, grp, lights, clip) => {
320
+ if (!hasDrawableGeometry(geo))
321
+ return;
322
+ if (useProxies || cache.has(obj)) {
323
+ let maskMaterial = obj.isSprite ? spriteMaterial : material;
324
+ if (useProxies) {
325
+ let ownedMaterial = this._proxyMaskMaterials.get(obj);
326
+ if (!ownedMaterial) {
327
+ // Disposing this private material releases the proxy's RenderObjects
328
+ // without disposing geometry or materials owned by the source scene.
329
+ ownedMaterial = maskMaterial.clone();
330
+ this._proxyMaskMaterials.set(obj, ownedMaterial);
331
+ }
332
+ if (ownedMaterial.colorNode !== maskMaterial.colorNode) {
333
+ ownedMaterial.colorNode = maskMaterial.colorNode;
334
+ ownedMaterial.needsUpdate = true;
335
+ }
336
+ maskMaterial = ownedMaterial;
337
+ }
338
+ renderer.renderObject(obj, sc, cam, geo, maskMaterial, grp, lights, clip);
339
+ }
340
+ });
341
+ renderer.render(useProxies ? maskScene : this.scene, this.camera);
342
+ }
343
+ _syncProxies(cache, proxies, scene) {
344
+ const supported = this._supportsProxies(cache);
345
+ for (const [source, proxy] of proxies) {
346
+ if (!supported || !cache.has(source) || !hasDrawableGeometry(source.geometry)) {
347
+ this._disposeProxyMaterial(proxy);
348
+ scene.remove(proxy);
349
+ proxies.delete(source);
350
+ }
351
+ }
352
+ if (!supported)
353
+ return false;
354
+ for (const source of cache) {
355
+ if (!hasDrawableGeometry(source.geometry))
356
+ continue;
357
+ let proxy = proxies.get(source);
358
+ if (!proxy) {
359
+ proxy = source.isSprite
360
+ ? new Sprite(source.material)
361
+ : new Mesh(source.geometry, source.material);
362
+ proxy.matrixAutoUpdate = false;
363
+ proxy.matrixWorldAutoUpdate = false;
364
+ proxy.frustumCulled = false;
365
+ proxies.set(source, proxy);
366
+ scene.add(proxy);
367
+ }
368
+ else if (proxy.geometry !== source.geometry) {
369
+ // Three's RenderObject.setGeometry does not move its dispose listener.
370
+ this._disposeProxyMaterial(proxy);
371
+ }
372
+ let visible = true;
373
+ let attached = false;
374
+ for (let ancestor = source; ancestor; ancestor = ancestor.parent) {
375
+ if (!ancestor.visible)
376
+ visible = false;
377
+ if (ancestor === this.scene)
378
+ attached = true;
379
+ }
380
+ proxy.geometry = source.geometry;
381
+ proxy.material = source.material;
382
+ proxy.matrixWorld.copy(source.matrixWorld);
383
+ proxy.layers.mask = source.layers.mask;
384
+ proxy.visible = visible && attached;
385
+ proxy.renderOrder = source.renderOrder;
386
+ proxy.morphTargetInfluences = source.morphTargetInfluences;
387
+ proxy.morphTargetDictionary = source.morphTargetDictionary;
388
+ proxy.count = source.count;
389
+ if (source.isSprite)
390
+ proxy.center.copy(source.center);
391
+ }
392
+ return true;
393
+ }
394
+ _supportsProxies(cache) {
395
+ for (const source of cache) {
396
+ const prototype = Object.getPrototypeOf(source);
397
+ if ((prototype !== Mesh.prototype && prototype !== Sprite.prototype) ||
398
+ source.onBeforeRender !== Object3D.prototype.onBeforeRender ||
399
+ source.onAfterRender !== Object3D.prototype.onAfterRender) {
400
+ return false;
401
+ }
402
+ for (let ancestor = source; ancestor; ancestor = ancestor.parent) {
403
+ if (ancestor.isLOD ||
404
+ ancestor.isClippingGroup ||
405
+ (ancestor.isGroup && ancestor.renderOrder !== 0)) {
406
+ return false;
407
+ }
408
+ }
409
+ }
410
+ return true;
411
+ }
412
+ _disposeProxyMaterial(proxy) {
413
+ this._proxyMaskMaterials.get(proxy)?.dispose();
414
+ this._proxyMaskMaterials.delete(proxy);
415
+ }
315
416
  _runEdgePipeline(renderer, group) {
316
417
  const isA = group === 'A';
317
418
  const g = isA ? this._groupA : this._groupB;
@@ -350,15 +451,45 @@ export class MergedOutlineNode extends TempNode {
350
451
  renderer.setRenderTarget(g.composite);
351
452
  _quadMesh.render(renderer);
352
453
  }
353
- setup(_builder) {
454
+ setup(builder) {
455
+ if (this._sceneDepthNode) {
456
+ builder.getNodeProperties(this).sceneDepthNode = this._sceneDepthNode;
457
+ }
458
+ const reversed = builder.renderer.reversedDepthBuffer;
459
+ const depthTexture = this._sceneDepthNode?.value;
460
+ const floatDepth = reversed || depthTexture?.type === FloatType;
461
+ const depthSamples = this._sceneDepthNode && builder.renderer.backend.isWebGPUBackend
462
+ ? (this._sceneDepthNode.passNode?.options.samples ?? builder.renderer.samples)
463
+ : 0;
354
464
  // ── prepareMask ───────────────────────────────────────────────────────────
355
465
  const buildPrepareMask = () => {
356
- const depth = this._depthTexUniform.sample(screenUV);
466
+ let depth = (this._sceneDepthNode ?? this._depthTexUniform).sample(screenUV).r;
467
+ if (this._sceneDepthNode) {
468
+ // The symmetric MSAA sample positions average to the pixel center where
469
+ // the mask rasterizes. Average differences to avoid rounding a sum of
470
+ // nearly-one depths and consuming the one-ULP allowance below.
471
+ let depthOffset = float(0);
472
+ for (let sampleIndex = 1; sampleIndex < depthSamples; sampleIndex++) {
473
+ const sampleDepth = this._sceneDepthNode.sample(screenUV).level(sampleIndex).r;
474
+ depthOffset = depthOffset.add(sampleDepth.sub(depth));
475
+ }
476
+ if (depthSamples > 1)
477
+ depth = depth.add(depthOffset.div(depthSamples));
478
+ // Extract the float exponent for one ULP; a relative epsilon can hide
479
+ // centimetre-scale occlusion at long distances with conventional depth.
480
+ const bias = floatDepth
481
+ ? float(floatBitsToUint(depth.abs()).shiftRight(23).bitAnd(255).max(1)).sub(150).exp2()
482
+ : float(2 ** -24);
483
+ // Both Three builders expose fragCoord.xy; read rasterized z directly
484
+ // so view-Z reconstruction cannot consume the small depth allowance.
485
+ const fragmentDepth = Fn((_, shaderBuilder) => builtin(shaderBuilder.getFragCoord().replace(/\.xy$/, '.z')))();
486
+ const separation = reversed ? depth.sub(fragmentDepth) : fragmentDepth.sub(depth);
487
+ return vec3(0.0, separation.greaterThan(bias).select(1, 0), 1.0);
488
+ }
357
489
  const viewZ = this.camera.isPerspectiveCamera
358
490
  ? perspectiveDepthToViewZ(depth, this._cameraNear, this._cameraFar)
359
491
  : orthographicDepthToViewZ(depth, this._cameraNear, this._cameraFar);
360
- const depthTest = positionView.z.lessThanEqual(viewZ).select(1, 0);
361
- return vec3(0.0, depthTest, 1.0);
492
+ return vec3(0.0, positionView.z.lessThanEqual(viewZ).select(1, 0), 1.0);
362
493
  };
363
494
  const maskColorA = buildPrepareMask();
364
495
  this._prepareMaskMatA.colorNode = maskColorA;
@@ -445,6 +576,16 @@ export class MergedOutlineNode extends TempNode {
445
576
  dispose() {
446
577
  this.primaryObjects.length = 0;
447
578
  this.secondaryObjects.length = 0;
579
+ for (const proxy of this._proxiesA.values())
580
+ this._disposeProxyMaterial(proxy);
581
+ for (const proxy of this._proxiesB.values())
582
+ this._disposeProxyMaterial(proxy);
583
+ this._maskSceneA.clear();
584
+ this._maskSceneB.clear();
585
+ this._proxiesA.clear();
586
+ this._proxiesB.clear();
587
+ this._cacheA.clear();
588
+ this._cacheB.clear();
448
589
  this._depthRT.dispose();
449
590
  this._groupA.dispose();
450
591
  this._groupB.dispose();
@@ -466,6 +607,7 @@ export class MergedOutlineNode extends TempNode {
466
607
  this._compositeMatB.dispose();
467
608
  }
468
609
  _buildCache(objects, cache) {
610
+ cache.clear();
469
611
  for (const obj of objects) {
470
612
  obj.traverse((child) => {
471
613
  if (child.isMesh || child.isSprite)