@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,12 @@
1
+ import type { PassNode, RenderPipeline } from 'three/webgpu';
2
+ import type { LayerPassIndex } from './layer-pass';
3
+ export declare class PostProcessingResources {
4
+ layerIndex: LayerPassIndex | null;
5
+ readonly passes: PassNode[];
6
+ outline: {
7
+ dispose(): void;
8
+ } | null;
9
+ pipeline: RenderPipeline | null;
10
+ dispose(): void;
11
+ }
12
+ //# sourceMappingURL=post-processing-resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-processing-resources.d.ts","sourceRoot":"","sources":["../../src/lib/post-processing-resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAIlD,qBAAa,uBAAuB;IAClC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAO;IACxC,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAK;IAChC,OAAO,EAAE;QAAE,OAAO,IAAI,IAAI,CAAA;KAAE,GAAG,IAAI,CAAO;IAC1C,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAO;IAEtC,OAAO;CAUR"}
@@ -0,0 +1,19 @@
1
+ // RenderPipeline.dispose() only releases its fullscreen material, so the owner
2
+ // must also release the passes and scene observers on failure and teardown.
3
+ export class PostProcessingResources {
4
+ layerIndex = null;
5
+ passes = [];
6
+ outline = null;
7
+ pipeline = null;
8
+ dispose() {
9
+ this.layerIndex?.dispose();
10
+ this.layerIndex = null;
11
+ for (const pass of this.passes)
12
+ pass.dispose();
13
+ this.passes.length = 0;
14
+ this.outline?.dispose();
15
+ this.outline = null;
16
+ this.pipeline?.dispose();
17
+ this.pipeline = null;
18
+ }
19
+ }
@@ -0,0 +1,56 @@
1
+ export type RendererCapabilityCanvas = {
2
+ getContext(contextId: 'webgl2'): unknown;
3
+ };
4
+ /** Mirrors `GPUPowerPreference` without pulling WebGPU ambient types into the declaration build. */
5
+ export type RendererPowerPreference = 'high-performance' | 'low-power';
6
+ type RendererGpuAdapter = {
7
+ features?: Iterable<string>;
8
+ requestDevice(descriptor?: {
9
+ requiredFeatures?: string[];
10
+ }): Promise<unknown>;
11
+ };
12
+ type RendererGpu = {
13
+ requestAdapter(options?: Record<string, unknown>): Promise<RendererGpuAdapter | null>;
14
+ };
15
+ export type RendererCapability = {
16
+ backend: 'webgpu';
17
+ device: unknown;
18
+ status: 'supported';
19
+ } | {
20
+ backend: 'webgl';
21
+ status: 'supported';
22
+ } | {
23
+ error?: unknown;
24
+ status: 'unsupported';
25
+ };
26
+ export type RendererBackendParameters = {
27
+ device?: unknown;
28
+ forceWebGL?: boolean;
29
+ };
30
+ type InitializableRenderer = {
31
+ dispose?: () => void;
32
+ init: () => Promise<unknown>;
33
+ };
34
+ export type RendererInitializationResult<Renderer> = {
35
+ backend: 'webgpu' | 'webgl';
36
+ renderer: Renderer;
37
+ status: 'ready';
38
+ } | {
39
+ error?: unknown;
40
+ status: 'unsupported';
41
+ };
42
+ export declare function detectRendererCapability({ canvas, gpu, powerPreference, webgpuTimeoutMs, }?: {
43
+ canvas?: RendererCapabilityCanvas | null;
44
+ gpu?: RendererGpu | null;
45
+ powerPreference?: RendererPowerPreference;
46
+ webgpuTimeoutMs?: number;
47
+ }): Promise<RendererCapability>;
48
+ export declare function initializeGpuRenderer<Renderer extends InitializableRenderer>({ createRenderer, gpu, powerPreference, probeCanvas, webgpuTimeoutMs, }: {
49
+ createRenderer: (parameters: RendererBackendParameters) => Renderer;
50
+ gpu?: RendererGpu | null;
51
+ powerPreference?: RendererPowerPreference;
52
+ probeCanvas?: RendererCapabilityCanvas | null;
53
+ webgpuTimeoutMs?: number;
54
+ }): Promise<RendererInitializationResult<Renderer>>;
55
+ export {};
56
+ //# sourceMappingURL=renderer-capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer-capability.d.ts","sourceRoot":"","sources":["../../src/lib/renderer-capability.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,wBAAwB,GAAG;IACrC,UAAU,CAAC,SAAS,EAAE,QAAQ,GAAG,OAAO,CAAA;CACzC,CAAA;AAED,oGAAoG;AACpG,MAAM,MAAM,uBAAuB,GAAG,kBAAkB,GAAG,WAAW,CAAA;AAEtE,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC3B,aAAa,CAAC,UAAU,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC9E,CAAA;AAED,KAAK,WAAW,GAAG;IACjB,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAA;CACtF,CAAA;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,OAAO,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAC3D;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GACzC;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAAA;AAE9C,MAAM,MAAM,yBAAyB,GAAG;IACtC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,KAAK,qBAAqB,GAAG;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,4BAA4B,CAAC,QAAQ,IAC7C;IAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACpE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAAA;AAiD9C,wBAAsB,wBAAwB,CAAC,EAC7C,MAAwB,EACxB,GAAkB,EAClB,eAAe,EACf,eAAkD,GACnD,GAAE;IACD,MAAM,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAAA;IACxC,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IACxB,eAAe,CAAC,EAAE,uBAAuB,CAAA;IACzC,eAAe,CAAC,EAAE,MAAM,CAAA;CACpB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA2BnC;AAED,wBAAsB,qBAAqB,CAAC,QAAQ,SAAS,qBAAqB,EAAE,EAClF,cAAc,EACd,GAAG,EACH,eAAe,EACf,WAA6B,EAC7B,eAAkD,GACnD,EAAE;IACD,cAAc,EAAE,CAAC,UAAU,EAAE,yBAAyB,KAAK,QAAQ,CAAA;IACnE,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IACxB,eAAe,CAAC,EAAE,uBAAuB,CAAA;IACzC,WAAW,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAAA;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAAC,4BAA4B,CAAC,QAAQ,CAAC,CAAC,CAyClD"}
@@ -0,0 +1,116 @@
1
+ const WEBGPU_INITIALIZATION_TIMEOUT_MS = 4000;
2
+ function browserGpu() {
3
+ if (typeof navigator === 'undefined')
4
+ return null;
5
+ return navigator.gpu ?? null;
6
+ }
7
+ function browserCanvas() {
8
+ if (typeof document === 'undefined')
9
+ return null;
10
+ return document.createElement('canvas');
11
+ }
12
+ /**
13
+ * Because we hand the device to `WebGPURenderer` ourselves, three treats it as
14
+ * caller-owned and never destroys it, and `Renderer.dispose()` is a no-op when
15
+ * `init()` failed. Any device we request and then abandon has to be released
16
+ * here or it counts against the browser's concurrent-device limit for the rest
17
+ * of the page's life.
18
+ */
19
+ function releaseDevice(device) {
20
+ try {
21
+ ;
22
+ device?.destroy?.();
23
+ }
24
+ catch { }
25
+ }
26
+ function withTimeout(promise, timeoutMs, operation) {
27
+ let timeout;
28
+ const timeoutPromise = new Promise((_, reject) => {
29
+ timeout = setTimeout(() => {
30
+ reject(new Error(`${operation} timed out after ${timeoutMs}ms`));
31
+ }, timeoutMs);
32
+ });
33
+ return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timeout));
34
+ }
35
+ async function requestWebGpuDevice(gpu, powerPreference) {
36
+ const adapter = await gpu.requestAdapter({
37
+ featureLevel: 'compatibility',
38
+ ...(powerPreference ? { powerPreference } : {}),
39
+ });
40
+ if (!adapter)
41
+ return null;
42
+ const requiredFeatures = adapter.features ? Array.from(adapter.features) : undefined;
43
+ return adapter.requestDevice(requiredFeatures?.length ? { requiredFeatures } : undefined);
44
+ }
45
+ export async function detectRendererCapability({ canvas = browserCanvas(), gpu = browserGpu(), powerPreference, webgpuTimeoutMs = WEBGPU_INITIALIZATION_TIMEOUT_MS, } = {}) {
46
+ let capabilityError;
47
+ if (gpu) {
48
+ const pending = requestWebGpuDevice(gpu, powerPreference);
49
+ try {
50
+ const device = await withTimeout(pending, webgpuTimeoutMs, 'WebGPU adapter/device request');
51
+ if (device)
52
+ return { backend: 'webgpu', device, status: 'supported' };
53
+ }
54
+ catch (error) {
55
+ capabilityError = error;
56
+ // Losing the race means a device may still arrive after we have given up
57
+ // on it, so reclaim it. Attaching the handler only on this path keeps
58
+ // ownership of a device we did return unambiguous.
59
+ pending.then(releaseDevice).catch(() => { });
60
+ }
61
+ }
62
+ if (canvas) {
63
+ try {
64
+ const context = canvas.getContext('webgl2');
65
+ if (context)
66
+ return { backend: 'webgl', status: 'supported' };
67
+ }
68
+ catch (error) {
69
+ capabilityError ??= error;
70
+ }
71
+ }
72
+ return { error: capabilityError, status: 'unsupported' };
73
+ }
74
+ export async function initializeGpuRenderer({ createRenderer, gpu, powerPreference, probeCanvas = browserCanvas(), webgpuTimeoutMs = WEBGPU_INITIALIZATION_TIMEOUT_MS, }) {
75
+ const capability = await detectRendererCapability({
76
+ canvas: probeCanvas,
77
+ gpu,
78
+ powerPreference,
79
+ webgpuTimeoutMs,
80
+ });
81
+ if (capability.status === 'unsupported')
82
+ return capability;
83
+ let renderer;
84
+ try {
85
+ renderer = createRenderer(capability.backend === 'webgpu' ? { device: capability.device } : { forceWebGL: true });
86
+ const initPromise = renderer.init();
87
+ await (capability.backend === 'webgpu'
88
+ ? withTimeout(initPromise, webgpuTimeoutMs, 'WebGPU renderer initialization')
89
+ : initPromise);
90
+ return { backend: capability.backend, renderer, status: 'ready' };
91
+ }
92
+ catch (error) {
93
+ try {
94
+ // r186 made dispose() async: let it finish before the device is released
95
+ // and the WebGL fallback starts on the same canvas.
96
+ await renderer?.dispose?.();
97
+ }
98
+ catch { }
99
+ if (capability.backend !== 'webgpu')
100
+ return { error, status: 'unsupported' };
101
+ releaseDevice(capability.device);
102
+ renderer = undefined;
103
+ try {
104
+ renderer = createRenderer({ forceWebGL: true });
105
+ await renderer.init();
106
+ return { backend: 'webgl', renderer, status: 'ready' };
107
+ }
108
+ catch (fallbackError) {
109
+ try {
110
+ await renderer?.dispose?.();
111
+ }
112
+ catch { }
113
+ return { error: fallbackError, status: 'unsupported' };
114
+ }
115
+ }
116
+ }
@@ -0,0 +1,15 @@
1
+ import type { Object3D } from 'three';
2
+ /**
3
+ * Why an object is currently held off the scene layer.
4
+ *
5
+ * - `isolated` — outside the focused subtree of the viewer's isolation filter.
6
+ * - `shadow-only` — solo mode: out of the color passes, still casting shadows.
7
+ * - `batched` — a level's merged wall mesh draws this wall now.
8
+ */
9
+ export type HiddenReason = 'isolated' | 'shadow-only' | 'batched' | 'wall-batched';
10
+ /** Holds `obj` off the scene layer for `reason`. Idempotent per reason. */
11
+ export declare function hideFromScene(obj: Object3D, reason: HiddenReason): void;
12
+ /** Drops `reason`, restoring the mask `obj` had before the first one arrived. */
13
+ export declare function showInScene(obj: Object3D, reason: HiddenReason): void;
14
+ export declare function temporarilyShowShadowOnly(root: Object3D): () => void;
15
+ //# sourceMappingURL=scene-visibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-visibility.d.ts","sourceRoot":"","sources":["../../src/lib/scene-visibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAGrC;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,GAAG,cAAc,CAAA;AAmBlF,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAMvE;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAarE;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,IAAI,CAgBpE"}
@@ -0,0 +1,69 @@
1
+ import { BATCHED_LAYER, SCENE_LAYER, SHADOW_ONLY_LAYER } from './layers';
2
+ /**
3
+ * Single owner of `Object3D.layers` for every feature that hides an object.
4
+ *
5
+ * Isolation, solo's shadow-caster pass and wall batching all hide by clearing
6
+ * {@link SCENE_LAYER}, and they overlap freely — a wall can be sewn into a
7
+ * batch, then soloed, then isolated. While each stashed and restored the mask privately, the
8
+ * second to finish wrote back a mask the first had since changed. Recording
9
+ * *reasons* rather than masks makes the order irrelevant: the mask is
10
+ * recomputed from the one snapshot taken when the first reason arrived, and
11
+ * handed back only when the last one leaves.
12
+ */
13
+ const HOLD = Symbol('pascal:scene-visibility:hold');
14
+ /** Holds `obj` off the scene layer for `reason`. Idempotent per reason. */
15
+ export function hideFromScene(obj, reason) {
16
+ const holder = obj;
17
+ const hold = holder[HOLD] ?? { original: obj.layers.mask, reasons: new Set() };
18
+ holder[HOLD] = hold;
19
+ hold.reasons.add(reason);
20
+ applyHold(obj, hold);
21
+ }
22
+ /** Drops `reason`, restoring the mask `obj` had before the first one arrived. */
23
+ export function showInScene(obj, reason) {
24
+ const holder = obj;
25
+ const hold = holder[HOLD];
26
+ if (!hold)
27
+ return;
28
+ hold.reasons.delete(reason);
29
+ if (hold.reasons.size > 0) {
30
+ applyHold(obj, hold);
31
+ return;
32
+ }
33
+ obj.layers.mask = hold.original;
34
+ delete holder[HOLD];
35
+ }
36
+ export function temporarilyShowShadowOnly(root) {
37
+ const masks = new Map();
38
+ root.traverse((obj) => {
39
+ const hold = obj[HOLD];
40
+ if (!hold?.reasons.has('shadow-only'))
41
+ return;
42
+ masks.set(obj, obj.layers.mask);
43
+ const reasons = new Set(hold.reasons);
44
+ reasons.delete('shadow-only');
45
+ // A capture needs the original scene geometry, while editor overlays and
46
+ // objects hidden by isolation or batching must retain their own masks.
47
+ if (reasons.size === 0)
48
+ obj.layers.mask = hold.original;
49
+ else
50
+ applyHold(obj, { original: hold.original, reasons });
51
+ });
52
+ return () => {
53
+ for (const [obj, mask] of masks)
54
+ obj.layers.mask = mask;
55
+ };
56
+ }
57
+ function applyHold(obj, hold) {
58
+ obj.layers.mask = hold.original;
59
+ obj.layers.disable(SCENE_LAYER);
60
+ // A batched wall is both drawn and shadowed by the merged mesh, so it stays
61
+ // out of the shadow pass too — enabling the shadow-only bit would submit its
62
+ // triangles a second time, on top of the copy the batch already casts.
63
+ if (hold.reasons.has('batched') || hold.reasons.has('wall-batched')) {
64
+ obj.layers.enable(BATCHED_LAYER);
65
+ return;
66
+ }
67
+ if (hold.reasons.has('shadow-only'))
68
+ obj.layers.enable(SHADOW_ONLY_LAYER);
69
+ }
@@ -1,4 +1,20 @@
1
1
  import type { Object3D } from 'three';
2
+ /**
3
+ * Shadow-caster-only hiding: removes an object (and its descendants) from the
4
+ * color passes while keeping it in the shadow map, so a hidden roof or level
5
+ * still shadows the interior and sun enters through windows correctly.
6
+ *
7
+ * Uses layer masks instead of `visible = false` for two reasons: `visible`
8
+ * cascades (and, critically, prunes the object from the shadow pass too),
9
+ * while layers are tested per-object against the rendering camera — the main
10
+ * camera never enables the shadow-only layer, but every shadow-casting
11
+ * light's shadow camera does (see `lights.tsx`).
12
+ *
13
+ * The mask itself belongs to `lib/scene-visibility.ts`, which reconciles this
14
+ * with the isolation filter. Both calls are idempotent and cheap to reapply —
15
+ * solo re-runs `applyShadowOnly` every frame so meshes rebuilt while hidden
16
+ * get re-hidden.
17
+ */
2
18
  export declare function applyShadowOnly(root: Object3D): void;
3
19
  export declare function clearShadowOnly(root: Object3D): void;
4
20
  //# sourceMappingURL=shadow-only.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shadow-only.d.ts","sourceRoot":"","sources":["../../src/lib/shadow-only.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAuBrC,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CASpD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAOpD"}
1
+ {"version":3,"file":"shadow-only.d.ts","sourceRoot":"","sources":["../../src/lib/shadow-only.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAGrC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAIpD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAIpD"}
@@ -1,4 +1,4 @@
1
- import { SCENE_LAYER, SHADOW_ONLY_LAYER } from './layers';
1
+ import { hideFromScene, showInScene } from './scene-visibility';
2
2
  /**
3
3
  * Shadow-caster-only hiding: removes an object (and its descendants) from the
4
4
  * color passes while keeping it in the shadow map, so a hidden roof or level
@@ -7,30 +7,21 @@ import { SCENE_LAYER, SHADOW_ONLY_LAYER } from './layers';
7
7
  * Uses layer masks instead of `visible = false` for two reasons: `visible`
8
8
  * cascades (and, critically, prunes the object from the shadow pass too),
9
9
  * while layers are tested per-object against the rendering camera — the main
10
- * camera never enables {@link SHADOW_ONLY_LAYER}, but every shadow-casting
10
+ * camera never enables the shadow-only layer, but every shadow-casting
11
11
  * light's shadow camera does (see `lights.tsx`).
12
12
  *
13
- * The original `layers.mask` is stashed under a private Symbol so
14
- * {@link clearShadowOnly} restores the exact prior state. Both calls are
15
- * idempotent and cheap to reapply.
13
+ * The mask itself belongs to `lib/scene-visibility.ts`, which reconciles this
14
+ * with the isolation filter. Both calls are idempotent and cheap to reapply —
15
+ * solo re-runs `applyShadowOnly` every frame so meshes rebuilt while hidden
16
+ * get re-hidden.
16
17
  */
17
- const ORIGINAL_LAYERS = Symbol('pascal:shadow-only:original-layers');
18
18
  export function applyShadowOnly(root) {
19
19
  root.traverse((obj) => {
20
- const carrier = obj;
21
- if (carrier[ORIGINAL_LAYERS] === undefined) {
22
- carrier[ORIGINAL_LAYERS] = obj.layers.mask;
23
- }
24
- obj.layers.disable(SCENE_LAYER);
25
- obj.layers.enable(SHADOW_ONLY_LAYER);
20
+ hideFromScene(obj, 'shadow-only');
26
21
  });
27
22
  }
28
23
  export function clearShadowOnly(root) {
29
24
  root.traverse((obj) => {
30
- const carrier = obj;
31
- if (carrier[ORIGINAL_LAYERS] === undefined)
32
- return;
33
- obj.layers.mask = carrier[ORIGINAL_LAYERS];
34
- delete carrier[ORIGINAL_LAYERS];
25
+ showInScene(obj, 'shadow-only');
35
26
  });
36
27
  }
@@ -3,6 +3,15 @@ import { type WebGPURenderer } from 'three/webgpu';
3
3
  import { type EdgeMode } from './edge-style';
4
4
  export declare const THUMBNAIL_WIDTH = 1920;
5
5
  export declare const THUMBNAIL_HEIGHT = 1080;
6
+ /**
7
+ * Captures are re-renderable artifacts, not user originals, so they encode as
8
+ * webp: a 1920×1080 hero shot lands roughly an order of magnitude under PNG,
9
+ * which is what listings and the catalog actually ship over the wire. Alpha
10
+ * survives, so transparent item/preset captures keep working.
11
+ */
12
+ export declare const SNAPSHOT_MIME = "image/webp";
13
+ export declare const SNAPSHOT_QUALITY = 0.9;
14
+ export declare const SNAPSHOT_MAX_EDGE = 2048;
6
15
  export type SnapshotCaptureMode = 'standard' | 'viewport' | 'area';
7
16
  export type SnapshotCropRegion = {
8
17
  x: number;
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot-pipeline.d.ts","sourceRoot":"","sources":["../../src/lib/snapshot-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAkB,KAAK,KAAK,EAAoB,MAAM,OAAO,CAAA;AAqBjF,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAGhF,OAAO,EAAE,KAAK,QAAQ,EAAqC,MAAM,cAAc,CAAA;AAK/E,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AAEpC,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAA;AAElE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,IAAI,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gBAAgB,EAAE,CAAC,EACjB,KAAK,EACL,WAAW,EACX,KAAK,EACL,KAAK,EACL,MAAM,GACP,EAAE;QACD,KAAK,EAAE,MAAM,CAAA;QACb,WAAW,EAAE,OAAO,CAAA;QACpB,KAAK,EAAE,OAAO,CAAA;QACd,KAAK,EAAE,QAAQ,CAAA;QACf,MAAM,EAAE,MAAM,CAAA;KACf,KAAK,IAAI,CAAA;IACV,OAAO,EAAE,CAAC,EACR,WAAW,EACX,UAAU,EACV,YAAY,GACb,EAAE;QACD,WAAW,CAAC,EAAE,mBAAmB,CAAA;QACjC,UAAU,CAAC,EAAE,kBAAkB,CAAA;QAC/B,YAAY,CAAC,EAAE,YAAY,CAAA;KAC5B,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACpC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAED,wBAAsB,sBAAsB,CAAC,EAC3C,QAAQ,EACR,KAAK,EACL,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,cAAc,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAoSnC"}
1
+ {"version":3,"file":"snapshot-pipeline.d.ts","sourceRoot":"","sources":["../../src/lib/snapshot-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAkB,KAAK,KAAK,EAAoB,MAAM,OAAO,CAAA;AAqBjF,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAGhF,OAAO,EAAE,KAAK,QAAQ,EAAqC,MAAM,cAAc,CAAA;AAK/E,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AAEpC;;;;;GAKG;AACH,eAAO,MAAM,aAAa,eAAe,CAAA;AACzC,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAEnC,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAUrC,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAA;AAElE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,IAAI,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gBAAgB,EAAE,CAAC,EACjB,KAAK,EACL,WAAW,EACX,KAAK,EACL,KAAK,EACL,MAAM,GACP,EAAE;QACD,KAAK,EAAE,MAAM,CAAA;QACb,WAAW,EAAE,OAAO,CAAA;QACpB,KAAK,EAAE,OAAO,CAAA;QACd,KAAK,EAAE,QAAQ,CAAA;QACf,MAAM,EAAE,MAAM,CAAA;KACf,KAAK,IAAI,CAAA;IACV,OAAO,EAAE,CAAC,EACR,WAAW,EACX,UAAU,EACV,YAAY,GACb,EAAE;QACD,WAAW,CAAC,EAAE,mBAAmB,CAAA;QACjC,UAAU,CAAC,EAAE,kBAAkB,CAAA;QAC/B,YAAY,CAAC,EAAE,YAAY,CAAA;KAC5B,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACpC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAED,wBAAsB,sBAAsB,CAAC,EAC3C,QAAQ,EACR,KAAK,EACL,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,cAAc,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAwSnC"}
@@ -12,6 +12,23 @@ import { getSceneTheme } from './scene-themes';
12
12
  import { packNormalToRGB, unpackRGBToNormal } from './tsl-compat';
13
13
  export const THUMBNAIL_WIDTH = 1920;
14
14
  export const THUMBNAIL_HEIGHT = 1080;
15
+ /**
16
+ * Captures are re-renderable artifacts, not user originals, so they encode as
17
+ * webp: a 1920×1080 hero shot lands roughly an order of magnitude under PNG,
18
+ * which is what listings and the catalog actually ship over the wire. Alpha
19
+ * survives, so transparent item/preset captures keep working.
20
+ */
21
+ export const SNAPSHOT_MIME = 'image/webp';
22
+ export const SNAPSHOT_QUALITY = 0.9;
23
+ // Retina canvases make viewport/area captures multi-MB; 2048 keeps them near the 1920 presets.
24
+ export const SNAPSHOT_MAX_EDGE = 2048;
25
+ function clampSnapshotSize(width, height) {
26
+ const maxEdge = Math.max(width, height);
27
+ if (maxEdge <= SNAPSHOT_MAX_EDGE)
28
+ return { w: width, h: height };
29
+ const scale = SNAPSHOT_MAX_EDGE / maxEdge;
30
+ return { w: Math.round(width * scale), h: Math.round(height * scale) };
31
+ }
15
32
  export async function createSnapshotPipeline({ renderer, scene, camera, }) {
16
33
  try {
17
34
  if (renderer.init)
@@ -200,20 +217,27 @@ export async function createSnapshotPipeline({ renderer, scene, camera, }) {
200
217
  let outH;
201
218
  let blob;
202
219
  if (captureMode === 'viewport') {
203
- outW = captureWidth;
204
- outH = captureHeight;
220
+ ;
221
+ ({ w: outW, h: outH } = clampSnapshotSize(captureWidth, captureHeight));
205
222
  const offscreen = new OffscreenCanvas(outW, outH);
206
- offscreen.getContext('2d').drawImage(srcCanvas, 0, 0);
207
- blob = await offscreen.convertToBlob({ type: 'image/png' });
223
+ const ctx = offscreen.getContext('2d');
224
+ if (outW !== captureWidth || outH !== captureHeight)
225
+ ctx.imageSmoothingQuality = 'high';
226
+ ctx.drawImage(srcCanvas, 0, 0, captureWidth, captureHeight, 0, 0, outW, outH);
227
+ blob = await offscreen.convertToBlob({ type: SNAPSHOT_MIME, quality: SNAPSHOT_QUALITY });
208
228
  }
209
229
  else if (captureMode === 'area' && cropRegion) {
210
230
  const sx = Math.round(cropRegion.x * captureWidth);
211
231
  const sy = Math.round(cropRegion.y * captureHeight);
212
- outW = Math.round(cropRegion.width * captureWidth);
213
- outH = Math.round(cropRegion.height * captureHeight);
232
+ const sourceW = Math.round(cropRegion.width * captureWidth);
233
+ const sourceH = Math.round(cropRegion.height * captureHeight);
234
+ ({ w: outW, h: outH } = clampSnapshotSize(sourceW, sourceH));
214
235
  const offscreen = new OffscreenCanvas(outW, outH);
215
- offscreen.getContext('2d').drawImage(srcCanvas, sx, sy, outW, outH, 0, 0, outW, outH);
216
- blob = await offscreen.convertToBlob({ type: 'image/png' });
236
+ const ctx = offscreen.getContext('2d');
237
+ if (outW !== sourceW || outH !== sourceH)
238
+ ctx.imageSmoothingQuality = 'high';
239
+ ctx.drawImage(srcCanvas, sx, sy, sourceW, sourceH, 0, 0, outW, outH);
240
+ blob = await offscreen.convertToBlob({ type: SNAPSHOT_MIME, quality: SNAPSHOT_QUALITY });
217
241
  }
218
242
  else {
219
243
  // Standard: center-crop to the requested aspect (default 1920×1080)
@@ -237,7 +261,7 @@ export async function createSnapshotPipeline({ renderer, scene, camera, }) {
237
261
  offscreen
238
262
  .getContext('2d')
239
263
  .drawImage(srcCanvas, sx, sy, sWidth, sHeight, 0, 0, outW, outH);
240
- blob = await offscreen.convertToBlob({ type: 'image/png' });
264
+ blob = await offscreen.convertToBlob({ type: SNAPSHOT_MIME, quality: SNAPSHOT_QUALITY });
241
265
  }
242
266
  return { blob, outW, outH };
243
267
  },
@@ -4,6 +4,7 @@ import type { EdgeMode } from '../lib/edge-style';
4
4
  import type { ColorPreset, RenderShading } from '../lib/materials';
5
5
  export type RenderContext = 'editor' | 'viewer';
6
6
  export type MetricNotation = 'meters' | 'millimeters';
7
+ export type WallMode = 'up' | 'cutaway' | 'down' | 'translucent';
7
8
  type SelectionPath = {
8
9
  buildingId: BuildingNode['id'] | null;
9
10
  levelId: LevelNode['id'] | null;
@@ -70,8 +71,8 @@ type ViewerState = {
70
71
  unitExplicit: boolean;
71
72
  levelMode: 'stacked' | 'exploded' | 'solo' | 'manual';
72
73
  setLevelMode: (mode: 'stacked' | 'exploded' | 'solo' | 'manual') => void;
73
- wallMode: 'up' | 'cutaway' | 'down' | 'translucent';
74
- setWallMode: (mode: 'up' | 'cutaway' | 'down' | 'translucent') => void;
74
+ wallMode: WallMode;
75
+ setWallMode: (mode: WallMode) => void;
75
76
  showScans: boolean;
76
77
  setShowScans: (show: boolean) => void;
77
78
  showGuides: boolean;
@@ -101,8 +102,8 @@ type ViewerState = {
101
102
  * effects can re-apply to the freshly swapped meshes. */
102
103
  geometryRevision: number;
103
104
  bumpGeometryRevision: () => void;
104
- exportScene: ((format?: 'glb' | 'stl' | 'obj') => Promise<void>) | null;
105
- setExportScene: (fn: ((format?: 'glb' | 'stl' | 'obj') => Promise<void>) | null) => void;
105
+ exportScene: ((format?: 'glb' | 'usdz' | 'stl' | 'obj') => Promise<void>) | null;
106
+ setExportScene: (fn: ((format?: 'glb' | 'usdz' | 'stl' | 'obj') => Promise<void>) | null) => void;
106
107
  debugColors: boolean;
107
108
  setDebugColors: (enabled: boolean) => void;
108
109
  walkthroughMode: boolean;
@@ -133,7 +134,7 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
133
134
  setOptions: (options: Partial<import("zustand/middleware").PersistOptions<ViewerState, {
134
135
  metricNotation: MetricNotation;
135
136
  levelMode: "manual" | "stacked" | "exploded" | "solo";
136
- wallMode: "up" | "down" | "cutaway" | "translucent";
137
+ wallMode: WallMode;
137
138
  projectPreferences: Record<string, {
138
139
  showScans?: boolean;
139
140
  showGuides?: boolean;
@@ -157,7 +158,7 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
157
158
  getOptions: () => Partial<import("zustand/middleware").PersistOptions<ViewerState, {
158
159
  metricNotation: MetricNotation;
159
160
  levelMode: "manual" | "stacked" | "exploded" | "solo";
160
- wallMode: "up" | "down" | "cutaway" | "translucent";
161
+ wallMode: WallMode;
161
162
  projectPreferences: Record<string, {
162
163
  showScans?: boolean;
163
164
  showGuides?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
@@ -35,7 +35,7 @@ export const CeilingSystem = () => {
35
35
  }
36
36
  // If mesh not found, keep it dirty for next frame
37
37
  });
38
- });
38
+ }, 2);
39
39
  return null;
40
40
  };
41
41
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"door-animation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-animation-system.tsx"],"names":[],"mappings":"AAYA,eAAO,MAAM,mBAAmB,YA8C/B,CAAA"}
1
+ {"version":3,"file":"door-animation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-animation-system.tsx"],"names":[],"mappings":"AAKA,eAAO,MAAM,mBAAmB,YAkD/B,CAAA"}
@@ -1,13 +1,6 @@
1
1
  import { emitter, useInteractive, useScene } from '@pascal-app/core';
2
2
  import { useFrame } from '@react-three/fiber';
3
3
  const easeDoorAnimation = (value) => value * value * (3 - 2 * value);
4
- function markDoorDirty(doorId) {
5
- const scene = useScene.getState();
6
- const node = scene.nodes[doorId];
7
- scene.dirtyNodes.add(doorId);
8
- if (node?.parentId)
9
- scene.dirtyNodes.add(node.parentId);
10
- }
11
4
  export const DoorAnimationSystem = () => {
12
5
  useFrame(({ clock }) => {
13
6
  const interactive = useInteractive.getState();
@@ -30,19 +23,23 @@ export const DoorAnimationSystem = () => {
30
23
  }
31
24
  const progress = Math.min(1, (now - startedAt) / animation.durationMs);
32
25
  const value = animation.from + (animation.to - animation.from) * easeDoorAnimation(progress);
26
+ // No dirty mark per tick: DoorSystem rebuilds any door with an entry in
27
+ // `doorAnimations`, and a dirty mark is a one-shot work item, not a
28
+ // needs-frame signal — per-tick marks kept the scene from ever settling.
33
29
  interactive.setDoorOpenState(typedDoorId, { [animation.field]: value });
34
- markDoorDirty(typedDoorId);
35
30
  if (progress < 1)
36
31
  continue;
37
32
  interactive.cancelDoorAnimation(typedDoorId);
38
33
  if (animation.persist) {
39
34
  scene.updateNode(typedDoorId, { [animation.field]: animation.to });
40
35
  interactive.removeDoorOpenState(typedDoorId);
41
- markDoorDirty(typedDoorId);
42
36
  }
43
37
  else {
44
38
  interactive.setDoorOpenState(typedDoorId, { [animation.field]: animation.to });
45
39
  }
40
+ // One final mark so the settled pose gets a rebuild after the animation
41
+ // entry is gone (the persist branch's updateNode also marks, harmlessly).
42
+ scene.markDirty(typedDoorId);
46
43
  emitter.emit('door:animation-completed', {
47
44
  doorId: typedDoorId,
48
45
  field: animation.field,
@@ -1 +1 @@
1
- {"version":3,"file":"door-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAYd,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA0D9B,eAAO,MAAM,UAAU,YA6GtB,CAAA;AAupCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,EAChC,KAAK,EAAE,MAAM,GACZ,OAAO,CA6GT;AAyuCD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC,IAAI,CAI/D"}
1
+ {"version":3,"file":"door-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAYd,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA2D9B,eAAO,MAAM,UAAU,YAsHtB,CAAA;AAupCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,EAChC,KAAK,EAAE,MAAM,GACZ,OAAO,CA6GT;AAyuCD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC,IAAI,CAI/D"}