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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/capture/asset-url.d.ts +2 -0
  4. package/dist/capture/asset-url.d.ts.map +1 -0
  5. package/dist/capture/asset-url.js +13 -0
  6. package/dist/capture/capture-runtime.d.ts +43 -0
  7. package/dist/capture/capture-runtime.d.ts.map +1 -0
  8. package/dist/capture/capture-runtime.js +228 -0
  9. package/dist/capture/frame.d.ts +4 -0
  10. package/dist/capture/frame.d.ts.map +1 -0
  11. package/dist/capture/frame.js +21 -0
  12. package/dist/capture/index.d.ts +12 -0
  13. package/dist/capture/index.d.ts.map +1 -0
  14. package/dist/capture/index.js +11 -0
  15. package/dist/capture/layer-visibility.d.ts +5 -0
  16. package/dist/capture/layer-visibility.d.ts.map +1 -0
  17. package/dist/capture/layer-visibility.js +11 -0
  18. package/dist/capture/layers/clay-matcap.d.ts +3 -0
  19. package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
  20. package/dist/capture/layers/clay-matcap.js +26 -0
  21. package/dist/capture/layers/device-motion-layer.d.ts +7 -0
  22. package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
  23. package/dist/capture/layers/device-motion-layer.js +78 -0
  24. package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
  25. package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
  26. package/dist/capture/layers/point-cloud-layer.js +106 -0
  27. package/dist/capture/layers/room-model-layer.d.ts +9 -0
  28. package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
  29. package/dist/capture/layers/room-model-layer.js +71 -0
  30. package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
  31. package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
  32. package/dist/capture/layers/surface-mesh-data.js +74 -0
  33. package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
  34. package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
  35. package/dist/capture/layers/surface-mesh-layer.js +16 -0
  36. package/dist/capture/preview.d.ts +3 -0
  37. package/dist/capture/preview.d.ts.map +1 -0
  38. package/dist/capture/preview.js +2 -0
  39. package/dist/capture/source-state.d.ts +22 -0
  40. package/dist/capture/source-state.d.ts.map +1 -0
  41. package/dist/capture/source-state.js +182 -0
  42. package/dist/capture/stream-rendering.d.ts +14 -0
  43. package/dist/capture/stream-rendering.d.ts.map +1 -0
  44. package/dist/capture/stream-rendering.js +69 -0
  45. package/dist/capture/trajectory.d.ts +20 -0
  46. package/dist/capture/trajectory.d.ts.map +1 -0
  47. package/dist/capture/trajectory.js +77 -0
  48. package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
  49. package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
  50. package/dist/components/viewer/batched-mesh-spike.js +64 -0
  51. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  52. package/dist/components/viewer/frame-limiter.js +4 -3
  53. package/dist/components/viewer/index.d.ts.map +1 -1
  54. package/dist/components/viewer/index.js +70 -62
  55. package/dist/components/viewer/lights.d.ts.map +1 -1
  56. package/dist/components/viewer/lights.js +123 -89
  57. package/dist/components/viewer/perf-monitor.d.ts +6 -1
  58. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  59. package/dist/components/viewer/perf-monitor.js +307 -116
  60. package/dist/components/viewer/perf-panel.d.ts +3 -0
  61. package/dist/components/viewer/perf-panel.d.ts.map +1 -0
  62. package/dist/components/viewer/perf-panel.js +178 -0
  63. package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
  64. package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
  65. package/dist/components/viewer/pointer-raycast-layers.js +28 -0
  66. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  67. package/dist/components/viewer/post-processing.js +124 -83
  68. package/dist/components/viewer/registered-systems.d.ts +14 -5
  69. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  70. package/dist/components/viewer/registered-systems.js +22 -10
  71. package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
  72. package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
  73. package/dist/components/viewer/scene-atmosphere.js +89 -0
  74. package/dist/components/viewer/scene-environment.d.ts.map +1 -1
  75. package/dist/components/viewer/scene-environment.js +19 -5
  76. package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
  77. package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
  78. package/dist/components/viewer/scene-ground-replacement.js +35 -0
  79. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  80. package/dist/components/viewer/viewer-camera.js +4 -1
  81. package/dist/components/viewer/viewer-presentations.d.ts +67 -0
  82. package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
  83. package/dist/components/viewer/viewer-presentations.js +120 -0
  84. package/dist/hooks/use-library-materials-version.d.ts +7 -0
  85. package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
  86. package/dist/hooks/use-library-materials-version.js +10 -0
  87. package/dist/hooks/use-node-events.d.ts.map +1 -1
  88. package/dist/hooks/use-node-events.js +1 -0
  89. package/dist/index.d.ts +15 -6
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +15 -6
  92. package/dist/lib/csg-utils.d.ts +1 -0
  93. package/dist/lib/csg-utils.d.ts.map +1 -1
  94. package/dist/lib/csg-utils.js +6 -1
  95. package/dist/lib/gpu-perf.d.ts +0 -2
  96. package/dist/lib/gpu-perf.d.ts.map +1 -1
  97. package/dist/lib/gpu-perf.js +7 -21
  98. package/dist/lib/layer-pass.d.ts +28 -0
  99. package/dist/lib/layer-pass.d.ts.map +1 -0
  100. package/dist/lib/layer-pass.js +254 -0
  101. package/dist/lib/materials.d.ts +12 -0
  102. package/dist/lib/materials.d.ts.map +1 -1
  103. package/dist/lib/materials.js +51 -17
  104. package/dist/lib/merged-outline-node.d.ts +17 -8
  105. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  106. package/dist/lib/merged-outline-node.js +194 -52
  107. package/dist/lib/perf-actions.d.ts +55 -0
  108. package/dist/lib/perf-actions.d.ts.map +1 -0
  109. package/dist/lib/perf-actions.js +211 -0
  110. package/dist/lib/perf-observers.d.ts +2 -0
  111. package/dist/lib/perf-observers.d.ts.map +1 -0
  112. package/dist/lib/perf-observers.js +17 -0
  113. package/dist/lib/perf-panel-store.d.ts +71 -0
  114. package/dist/lib/perf-panel-store.d.ts.map +1 -0
  115. package/dist/lib/perf-panel-store.js +29 -0
  116. package/dist/lib/perf-tracks.d.ts +43 -0
  117. package/dist/lib/perf-tracks.d.ts.map +1 -0
  118. package/dist/lib/perf-tracks.js +113 -0
  119. package/dist/lib/pointer-events.d.ts +7 -0
  120. package/dist/lib/pointer-events.d.ts.map +1 -0
  121. package/dist/lib/pointer-events.js +680 -0
  122. package/dist/lib/post-processing-resources.d.ts +12 -0
  123. package/dist/lib/post-processing-resources.d.ts.map +1 -0
  124. package/dist/lib/post-processing-resources.js +19 -0
  125. package/dist/lib/renderer-capability.d.ts.map +1 -1
  126. package/dist/lib/renderer-capability.js +4 -2
  127. package/dist/lib/scene-visibility.d.ts +2 -1
  128. package/dist/lib/scene-visibility.d.ts.map +1 -1
  129. package/dist/lib/scene-visibility.js +22 -1
  130. package/dist/store/use-viewer.d.ts +2 -2
  131. package/dist/store/use-viewer.d.ts.map +1 -1
  132. package/dist/systems/ceiling/ceiling-system.js +1 -1
  133. package/dist/systems/door/door-animation-system.d.ts.map +1 -1
  134. package/dist/systems/door/door-animation-system.js +6 -9
  135. package/dist/systems/door/door-system.d.ts.map +1 -1
  136. package/dist/systems/door/door-system.js +13 -2
  137. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  138. package/dist/systems/geometry/geometry-system.js +3 -2
  139. package/dist/systems/level/level-system.d.ts.map +1 -1
  140. package/dist/systems/level/level-system.js +1 -1
  141. package/dist/systems/level/level-utils.d.ts +9 -0
  142. package/dist/systems/level/level-utils.d.ts.map +1 -1
  143. package/dist/systems/level/level-utils.js +14 -0
  144. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
  145. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
  146. package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
  147. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  148. package/dist/systems/roof/roof-system.js +1196 -58
  149. package/dist/systems/scan/scan-system.js +6 -5
  150. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  151. package/dist/systems/slab/slab-system.js +1 -2
  152. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  153. package/dist/systems/stair/stair-system.js +9 -8
  154. package/dist/systems/surface-hole-geometry.d.ts +1 -1
  155. package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
  156. package/dist/systems/surface-hole-geometry.js +1 -1
  157. package/dist/systems/wall/level-miter-cache.d.ts.map +1 -1
  158. package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
  159. package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
  160. package/dist/systems/wall/wall-build-lifecycle.js +74 -0
  161. package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
  162. package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
  163. package/dist/systems/wall/wall-cutout-cache.js +304 -0
  164. package/dist/systems/wall/wall-cutout.d.ts +12 -1
  165. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  166. package/dist/systems/wall/wall-cutout.js +24 -161
  167. package/dist/systems/wall/wall-material-variant.d.ts +31 -0
  168. package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
  169. package/dist/systems/wall/wall-material-variant.js +10 -0
  170. package/dist/systems/wall/wall-materials.d.ts +6 -0
  171. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  172. package/dist/systems/wall/wall-materials.js +29 -13
  173. package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
  174. package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
  175. package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
  176. package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
  177. package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
  178. package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
  179. package/dist/systems/wall/wall-system.d.ts +11 -4
  180. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  181. package/dist/systems/wall/wall-system.js +405 -153
  182. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  183. package/dist/systems/window/window-animation-system.js +9 -8
  184. package/dist/systems/window/window-system.d.ts +1 -0
  185. package/dist/systems/window/window-system.d.ts.map +1 -1
  186. package/dist/systems/window/window-system.js +16 -2
  187. package/package.json +17 -4
  188. package/dist/lib/polygon-union.d.ts +0 -4
  189. package/dist/lib/polygon-union.d.ts.map +0 -1
  190. package/dist/lib/polygon-union.js +0 -289
@@ -0,0 +1,77 @@
1
+ import { DeviceMotionSampleSchema, DeviceMotionTrajectorySchema, } from '@pascal-app/core/capture';
2
+ import { Matrix4, Quaternion, Vector3 } from 'three';
3
+ export function parseDeviceTrajectoryPayload(trajectory) {
4
+ if (!trajectory)
5
+ return null;
6
+ const parsed = DeviceMotionTrajectorySchema.safeParse(trajectory);
7
+ if (!parsed.success)
8
+ return null;
9
+ const poses = parsed.data.samples
10
+ .map(parsePose)
11
+ .sort((left, right) => left.timestamp - right.timestamp);
12
+ if (poses.length < 2)
13
+ return null;
14
+ const firstTimestamp = poses[0]?.timestamp ?? 0;
15
+ for (const pose of poses)
16
+ pose.timestamp -= firstTimestamp;
17
+ const duration = poses.at(-1)?.timestamp ?? 0;
18
+ if (!(duration > 0))
19
+ return null;
20
+ return { duration, poses };
21
+ }
22
+ export function parseDeviceTrajectoryPackets(payloads) {
23
+ let coordinateSystem = 'source';
24
+ let samples = [];
25
+ for (const payload of payloads) {
26
+ const trajectory = DeviceMotionTrajectorySchema.safeParse(payload);
27
+ if (trajectory.success) {
28
+ coordinateSystem = trajectory.data.coordinateSystem;
29
+ samples = [...trajectory.data.samples];
30
+ continue;
31
+ }
32
+ const sample = DeviceMotionSampleSchema.safeParse(payload);
33
+ if (sample.success)
34
+ samples.push(sample.data);
35
+ }
36
+ if (samples.length < 2)
37
+ return null;
38
+ return parseDeviceTrajectoryPayload({ coordinateSystem, samples });
39
+ }
40
+ export function sampleDeviceTrajectory(trajectory, elapsed) {
41
+ const time = positiveModulo(elapsed, trajectory.duration);
42
+ const poses = trajectory.poses;
43
+ const first = poses[0];
44
+ const last = poses.at(-1);
45
+ if (!(first && last))
46
+ throw new Error('Device trajectory requires at least two poses.');
47
+ let upperIndex = poses.findIndex((pose) => pose.timestamp > time);
48
+ if (upperIndex < 0)
49
+ upperIndex = poses.length - 1;
50
+ if (upperIndex === 0)
51
+ return { alpha: 0, from: first, to: first };
52
+ const from = poses[upperIndex - 1] ?? first;
53
+ const to = poses[upperIndex] ?? last;
54
+ if (from.segment !== to.segment)
55
+ return { alpha: 0, from, to: from };
56
+ const interval = to.timestamp - from.timestamp;
57
+ return {
58
+ alpha: interval > 0 ? Math.min(1, Math.max(0, (time - from.timestamp) / interval)) : 0,
59
+ from,
60
+ to,
61
+ };
62
+ }
63
+ function parsePose(value) {
64
+ const matrix = new Matrix4().fromArray(value.transform);
65
+ const position = new Vector3();
66
+ const quaternion = new Quaternion();
67
+ matrix.decompose(position, quaternion, new Vector3());
68
+ return {
69
+ position: [position.x, position.y, position.z],
70
+ quaternion: [quaternion.x, quaternion.y, quaternion.z, quaternion.w],
71
+ segment: value.segment,
72
+ timestamp: value.timestamp,
73
+ };
74
+ }
75
+ function positiveModulo(value, divisor) {
76
+ return ((value % divisor) + divisor) % divisor;
77
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * De-risk spike for item instancing (charter backlog #3a): mounts one
3
+ * BatchedMesh — two geometries, a few hundred instances, one material —
4
+ * through the real render pipeline (post-FX, shadow pass, WebGPU backend).
5
+ * `?spike=batch` only; never mounted in normal sessions.
6
+ *
7
+ * What it proves, read via the ?perf panel + `window.__batchSpike`:
8
+ * - DRAW rises by ~1 per pass, not by the instance count.
9
+ * - TRI drops as instances leave the frustum (perObjectFrustumCulled works).
10
+ * - Shadows cast/receive; no pipeline crash.
11
+ */
12
+ export declare const BATCH_SPIKE_ENABLED: boolean;
13
+ export declare const BatchedMeshSpike: () => null;
14
+ //# sourceMappingURL=batched-mesh-spike.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batched-mesh-spike.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/batched-mesh-spike.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,SAEsC,CAAA;AAStE,eAAO,MAAM,gBAAgB,YAoD5B,CAAA"}
@@ -0,0 +1,64 @@
1
+ import { useThree } from '@react-three/fiber';
2
+ import { useEffect } from 'react';
3
+ import * as THREE from 'three';
4
+ /**
5
+ * De-risk spike for item instancing (charter backlog #3a): mounts one
6
+ * BatchedMesh — two geometries, a few hundred instances, one material —
7
+ * through the real render pipeline (post-FX, shadow pass, WebGPU backend).
8
+ * `?spike=batch` only; never mounted in normal sessions.
9
+ *
10
+ * What it proves, read via the ?perf panel + `window.__batchSpike`:
11
+ * - DRAW rises by ~1 per pass, not by the instance count.
12
+ * - TRI drops as instances leave the frustum (perObjectFrustumCulled works).
13
+ * - Shadows cast/receive; no pipeline crash.
14
+ */
15
+ export const BATCH_SPIKE_ENABLED = typeof window !== 'undefined' &&
16
+ new URLSearchParams(window.location.search).get('spike') === 'batch';
17
+ // `?spike=batch&spikeGrid=70` → 4,900 instances; default 20×20 = 400.
18
+ const GRID = typeof window !== 'undefined'
19
+ ? Math.min(120, Number(new URLSearchParams(window.location.search).get('spikeGrid')) || 20)
20
+ : 20;
21
+ const SPACING = 1.2;
22
+ export const BatchedMeshSpike = () => {
23
+ const scene = useThree((s) => s.scene);
24
+ useEffect(() => {
25
+ const box = new THREE.BoxGeometry(0.4, 0.4, 0.4);
26
+ const sphere = new THREE.SphereGeometry(0.25, 16, 12);
27
+ const material = new THREE.MeshStandardMaterial({ color: '#c2410c', roughness: 0.6 });
28
+ const maxVertices = (box.attributes.position?.count ?? 0) + (sphere.attributes.position?.count ?? 0);
29
+ const maxIndices = (box.index?.count ?? 0) + (sphere.index?.count ?? 0);
30
+ const batch = new THREE.BatchedMesh(GRID * GRID, maxVertices, maxIndices, material);
31
+ batch.castShadow = true;
32
+ batch.receiveShadow = true;
33
+ // Default is true — asserted explicitly because per-instance frustum
34
+ // culling is the property the whole plan depends on.
35
+ batch.perObjectFrustumCulled = true;
36
+ const boxGeomId = batch.addGeometry(box);
37
+ const sphereGeomId = batch.addGeometry(sphere);
38
+ const m = new THREE.Matrix4();
39
+ let count = 0;
40
+ for (let x = 0; x < GRID; x++) {
41
+ for (let z = 0; z < GRID; z++) {
42
+ const geomId = (x + z) % 2 === 0 ? boxGeomId : sphereGeomId;
43
+ const id = batch.addInstance(geomId);
44
+ m.setPosition((x - GRID / 2) * SPACING, 6 + Math.sin(x * 0.7) * 0.5 + Math.cos(z * 0.5) * 0.5, (z - GRID / 2) * SPACING);
45
+ batch.setMatrixAt(id, m);
46
+ count++;
47
+ }
48
+ }
49
+ scene.add(batch);
50
+ window.__batchSpike = {
51
+ instances: count,
52
+ geometries: 2,
53
+ };
54
+ return () => {
55
+ scene.remove(batch);
56
+ batch.dispose();
57
+ box.dispose();
58
+ sphere.dispose();
59
+ material.dispose();
60
+ delete window.__batchSpike;
61
+ };
62
+ }, [scene]);
63
+ return null;
64
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"frame-limiter.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/frame-limiter.tsx"],"names":[],"mappings":"AAIA,KAAK,iBAAiB,GAAG;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IACjE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;CAClC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,SAAI,GAAG,UAAU,CAwB5D;AAgBD,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAoF7C,CAAA;AAED,eAAe,YAAY,CAAA"}
1
+ {"version":3,"file":"frame-limiter.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/frame-limiter.tsx"],"names":[],"mappings":"AAKA,KAAK,iBAAiB,GAAG;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IACjE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;CAClC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,SAAI,GAAG,UAAU,CAwB5D;AAgBD,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAoF7C,CAAA;AAED,eAAe,YAAY,CAAA"}
@@ -1,5 +1,6 @@
1
1
  import { useThree } from '@react-three/fiber';
2
2
  import { useLayoutEffect, useRef } from 'react';
3
+ import { timeSpan } from '../../lib/perf-tracks';
3
4
  import useViewer from '../../store/use-viewer';
4
5
  /**
5
6
  * Keeps R3F's manual clock monotonic while each limiter effect owns a fresh
@@ -70,13 +71,13 @@ const FrameLimiter = ({ fps = 50, paused = false }) => {
70
71
  if (frameTime === null)
71
72
  return;
72
73
  nextFrameTimeRef.current = frameTime;
73
- advance(frameTime);
74
+ timeSpan('frame-cpu', () => advance(frameTime));
74
75
  }
75
76
  function kick() {
76
77
  syncSize();
77
78
  const frameTime = clock.step(1 / 1000);
78
79
  nextFrameTimeRef.current = frameTime;
79
- advance(frameTime);
80
+ timeSpan('frame-cpu', () => advance(frameTime));
80
81
  }
81
82
  function onVisibilityChange() {
82
83
  if (document.visibilityState === 'visible')
@@ -88,7 +89,7 @@ const FrameLimiter = ({ fps = 50, paused = false }) => {
88
89
  timer = setInterval(() => {
89
90
  const frameTime = clock.step(interval / 1000);
90
91
  nextFrameTimeRef.current = frameTime;
91
- advance(frameTime);
92
+ timeSpan('frame-cpu', () => advance(frameTime));
92
93
  }, interval);
93
94
  }
94
95
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAKf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAkB,KAAK,YAAY,EAAsB,MAAM,oBAAoB,CAAA;AAS1F,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAKrC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAIrE,OAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAQtE,OAAuB,EAAwB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAW1F,OAAO,QAAQ,oBAAoB,CAAC;IAOlC,UAAU,aAAa;QACrB,qBAAqB,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,qBAAqB,CAAC,CAAA;KACxE;CACF;AAuQD,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,gBAAgB,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACvC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,aAAa,CAAA;QACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;QAClB,WAAW,CAAC,EAAE,WAAW,CAAA;KAC1B,CAAA;IACD;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACtC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,yDAAyD;AACzD,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;OAKG;IACH,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;CAC3C,CAAA;AAED,QAAA,MAAM,MAAM,sGAoOV,CAAA;AAkBF,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAMf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAkB,KAAK,YAAY,EAAsB,MAAM,oBAAoB,CAAA;AAS1F,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAKrC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAKrE,OAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAatE,OAAuB,EAAwB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAY1F,OAAO,QAAQ,oBAAoB,CAAC;IAOlC,UAAU,aAAa;QACrB,qBAAqB,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,qBAAqB,CAAC,CAAA;KACxE;CACF;AA8QD,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,gBAAgB,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACvC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,aAAa,CAAA;QACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;QAClB,WAAW,CAAC,EAAE,WAAW,CAAA;KAC1B,CAAA;IACD;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACtC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,yDAAyD;AACzD,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;OAKG;IACH,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;CAC3C,CAAA;AAED,QAAA,MAAM,MAAM,sGA0PV,CAAA;AAEF,eAAe,MAAM,CAAA"}
@@ -1,26 +1,33 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { nodeRegistry, StairOpeningSystem, sceneRegistry, useScene, } from '@pascal-app/core';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { nodeRegistry, RoofElevationSystem, StairOpeningSystem, sceneRegistry, useScene, } from '@pascal-app/core';
4
4
  import { Canvas, extend, useFrame, useThree } from '@react-three/fiber';
5
5
  import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
6
6
  import * as THREE from 'three/webgpu';
7
7
  import { hasDrawableGeometry } from '../../lib/drawable-geometry';
8
- import { PERF_OVERLAY_ENABLED, pushGpuSample } from '../../lib/gpu-perf';
8
+ import { PERF_OVERLAY_ENABLED } from '../../lib/gpu-perf';
9
9
  import { applyIsolation, clearIsolation } from '../../lib/isolation';
10
10
  import { ensureKtx2Support } from '../../lib/ktx2-loader';
11
+ import { choosePointerEvents } from '../../lib/pointer-events';
11
12
  import { initializeGpuRenderer } from '../../lib/renderer-capability';
12
13
  import { getSceneTheme } from '../../lib/scene-themes';
13
14
  import { installTextureNodeNullGuard } from '../../lib/texture-node-guard';
14
15
  import useViewer, {} from '../../store/use-viewer';
15
16
  import { FloorElevationSystem } from '../../systems/floor-elevation/floor-elevation-system';
16
17
  import { GeometrySystem } from '../../systems/geometry/geometry-system';
18
+ import { PerfActionSettleSystem } from '../../systems/perf-action-settle/perf-action-settle-system';
19
+ import { subscribeWallBuildInteractions } from '../../systems/wall/wall-build-lifecycle';
17
20
  import { ErrorBoundary } from '../error-boundary';
18
21
  import { SceneRenderer } from '../renderers/scene-renderer';
22
+ import { BATCH_SPIKE_ENABLED, BatchedMeshSpike } from './batched-mesh-spike';
19
23
  import FrameLimiter from './frame-limiter';
20
24
  import { Lights } from './lights';
21
25
  import { PerfMonitor } from './perf-monitor';
26
+ import { PerfPanel } from './perf-panel';
27
+ import { PointerRaycastLayers } from './pointer-raycast-layers';
22
28
  import PostProcessing, { DEFAULT_HOVER_STYLES } from './post-processing';
23
29
  import { RegisteredSystems } from './registered-systems';
30
+ import { useSceneAtmosphere } from './scene-atmosphere';
24
31
  import { SceneBvh } from './scene-bvh';
25
32
  import { SelectionManager } from './selection-manager';
26
33
  import { UnsupportedGpuViewerFallback } from './unsupported-gpu-fallback';
@@ -125,12 +132,20 @@ function GPUDeviceWatcher() {
125
132
  }
126
133
  function ToneMappingExposure() {
127
134
  const sceneTheme = useViewer((state) => state.sceneTheme);
135
+ const atmosphere = useSceneAtmosphere();
128
136
  const gl = useThree((state) => state.gl);
129
137
  const invalidate = useThree((state) => state.invalidate);
130
138
  useEffect(() => {
139
+ if (atmosphere)
140
+ return;
131
141
  gl.toneMappingExposure = getSceneTheme(sceneTheme).toneMappingExposure;
132
142
  invalidate();
133
- }, [gl, invalidate, sceneTheme]);
143
+ }, [atmosphere, gl, invalidate, sceneTheme]);
144
+ useFrame(() => {
145
+ if (!atmosphere)
146
+ return;
147
+ gl.toneMappingExposure = atmosphere.exposure;
148
+ }, -1);
134
149
  return null;
135
150
  }
136
151
  function hasPendingSceneBuildWork() {
@@ -234,6 +249,7 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
234
249
  clearIsolation();
235
250
  };
236
251
  }, [isolate]);
252
+ const [pointerEvents] = useState(() => choosePointerEvents());
237
253
  const [rendererInitFailed, setRendererInitFailed] = useState(false);
238
254
  const isDark = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark');
239
255
  const transparentBackground = useViewer((state) => state.transparentBackground);
@@ -300,64 +316,56 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
300
316
  if (showGpuFallback) {
301
317
  return _jsx(UnsupportedGpuViewerFallback, {});
302
318
  }
303
- return (_jsxs(Canvas, { camera: { position: [50, 50, 50], fov: 50 }, className: `transition-colors duration-700 ${transparentBackground ? 'bg-transparent' : isDark ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`, dpr: [1, maxDpr], frameloop: "never", gl: ((props) => {
304
- const canvas = props.canvas;
305
- const cached = canvas ? WEBGPU_RENDERER_CACHE.get(canvas) : undefined;
306
- if (cached)
307
- return cached;
308
- const promise = (async () => {
309
- const result = await initializeGpuRenderer({
310
- // Supplying `device` makes three skip its own `requestAdapter`,
311
- // so R3F's `powerPreference` only reaches the GPU if we forward it.
312
- powerPreference: props.powerPreference,
313
- createRenderer: (backendParameters) => {
314
- const renderer = new THREE.WebGPURenderer({
315
- ...props,
316
- ...backendParameters,
317
- alpha: true,
319
+ return (_jsxs(_Fragment, { children: [(perf || PERF_OVERLAY_ENABLED) && _jsx(PerfPanel, {}), _jsxs(Canvas, { ref: subscribeWallBuildInteractions, camera: { position: [50, 50, 50], fov: 50 }, className: `transition-colors duration-700 ${transparentBackground ? 'bg-transparent' : isDark ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`, dpr: [1, maxDpr], events: pointerEvents, frameloop: "never", gl: ((props) => {
320
+ const canvas = props.canvas;
321
+ const cached = canvas ? WEBGPU_RENDERER_CACHE.get(canvas) : undefined;
322
+ if (cached)
323
+ return cached;
324
+ const promise = (async () => {
325
+ const result = await initializeGpuRenderer({
326
+ // Supplying `device` makes three skip its own `requestAdapter`,
327
+ // so R3F's `powerPreference` only reaches the GPU if we forward it.
328
+ powerPreference: props.powerPreference,
329
+ createRenderer: (backendParameters) => {
330
+ const renderer = new THREE.WebGPURenderer({
331
+ ...props,
332
+ ...backendParameters,
333
+ alpha: true,
334
+ // Allocates the backend's timestamp query pool so
335
+ // `resolveTimestampsAsync()` can report real GPU render-pass
336
+ // time (post-processing.tsx). The backend self-disables it
337
+ // when the device lacks 'timestamp-query'.
338
+ trackTimestamp: PERF_OVERLAY_ENABLED,
339
+ });
340
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
341
+ renderer.toneMappingExposure = getSceneTheme(useViewer.getState().sceneTheme).toneMappingExposure;
342
+ return renderer;
343
+ },
318
344
  });
319
- renderer.toneMapping = THREE.ACESFilmicToneMapping;
320
- renderer.toneMappingExposure = getSceneTheme(useViewer.getState().sceneTheme).toneMappingExposure;
321
- return renderer;
322
- },
323
- });
324
- if (result.status === 'ready') {
325
- installEmptyDrawGuard(result.renderer);
326
- return result.renderer;
327
- }
328
- if (canvas)
329
- WEBGPU_RENDERER_CACHE.delete(canvas);
330
- console.error('[viewer] WebGPURenderer init failed', result.error);
331
- setRendererInitFailed(true);
332
- // Never settles on purpose. Rejecting is what produced
333
- // MONOREPO-EDITOR-59: R3F awaits this inside its own configure()
334
- // with no catch, so a rejection surfaces as an unhandled rejection.
335
- // Resolving is worse still — R3F would call render() on a renderer
336
- // that has no context. The state update above unmounts this Canvas,
337
- // which is what releases the pending configure().
338
- return new Promise(() => undefined);
339
- })();
340
- if (canvas)
341
- WEBGPU_RENDERER_CACHE.set(canvas, promise);
342
- return promise;
343
- }), resize: {
344
- debounce: 100,
345
- }, shadows: {
346
- type: THREE.PCFShadowMap,
347
- enabled: shadowsEnabled,
348
- }, children: [_jsx(FrameLimiter, { fps: maxFps, paused: renderPaused }), _jsx(ViewerCamera, {}), _jsx(GPUDeviceWatcher, {}), _jsx(ToneMappingExposure, {}), _jsx(SceneReadyTracker, { onSceneReadyChange: onSceneReadyChange, sceneReadyKey: sceneReadyKey, sceneReadyMaxWaitMs: sceneReadyMaxWaitMs }), _jsxs(ErrorBoundary, { fallback: null, scope: "viewer-scene", children: [_jsx(Lights, {}), useBvh ? (_jsx(SceneBvh, { children: _jsx(SceneRenderer, {}) })) : (_jsx(SceneRenderer, {})), _jsx(FloorElevationSystem, {}), _jsx(GeometrySystem, {}), _jsx(StairOpeningSystem, {}), _jsx(RegisteredSystems, {}), _jsx(PostProcessing, { disablePostFx: disablePostFx, hoverStyles: hoverStyles }), selectionManager === 'default' && _jsx(SelectionManager, {}), (perf || PERF_OVERLAY_ENABLED) && _jsx(PerfMonitor, {}), children] })] }));
345
+ if (result.status === 'ready') {
346
+ installEmptyDrawGuard(result.renderer);
347
+ return result.renderer;
348
+ }
349
+ if (canvas)
350
+ WEBGPU_RENDERER_CACHE.delete(canvas);
351
+ console.error('[viewer] WebGPURenderer init failed', result.error);
352
+ setRendererInitFailed(true);
353
+ // Never settles on purpose. Rejecting is what produced
354
+ // MONOREPO-EDITOR-59: R3F awaits this inside its own configure()
355
+ // with no catch, so a rejection surfaces as an unhandled rejection.
356
+ // Resolving is worse still — R3F would call render() on a renderer
357
+ // that has no context. The state update above unmounts this Canvas,
358
+ // which is what releases the pending configure().
359
+ return new Promise(() => undefined);
360
+ })();
361
+ if (canvas)
362
+ WEBGPU_RENDERER_CACHE.set(canvas, promise);
363
+ return promise;
364
+ }), resize: {
365
+ debounce: 100,
366
+ }, shadows: {
367
+ type: THREE.PCFShadowMap,
368
+ enabled: shadowsEnabled,
369
+ }, children: [_jsx(FrameLimiter, { fps: maxFps, paused: renderPaused }), _jsx(ViewerCamera, {}), _jsx(PointerRaycastLayers, {}), _jsx(GPUDeviceWatcher, {}), _jsx(ToneMappingExposure, {}), _jsx(SceneReadyTracker, { onSceneReadyChange: onSceneReadyChange, sceneReadyKey: sceneReadyKey, sceneReadyMaxWaitMs: sceneReadyMaxWaitMs }), _jsxs(ErrorBoundary, { fallback: null, scope: "viewer-scene", children: [_jsx(Lights, {}), useBvh ? (_jsx(SceneBvh, { children: _jsx(SceneRenderer, {}) })) : (_jsx(SceneRenderer, {})), _jsx(FloorElevationSystem, {}), _jsx(GeometrySystem, {}), _jsx(StairOpeningSystem, {}), _jsx(RoofElevationSystem, {}), _jsx(RegisteredSystems, {}), _jsx(PostProcessing, { disablePostFx: disablePostFx, hoverStyles: hoverStyles }), selectionManager === 'default' && _jsx(SelectionManager, {}), (perf || PERF_OVERLAY_ENABLED) && _jsx(PerfMonitor, {}), (perf || PERF_OVERLAY_ENABLED) && _jsx(PerfActionSettleSystem, {}), BATCH_SPIKE_ENABLED && _jsx(BatchedMeshSpike, {}), children] })] })] }));
349
370
  });
350
- const DebugRenderer = () => {
351
- useFrame(({ gl, scene, camera }) => {
352
- const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
353
- gl.render(scene, camera);
354
- if (PERF_OVERLAY_ENABLED) {
355
- const queue = gl.backend?.device?.queue;
356
- queue?.onSubmittedWorkDone?.().then(() => {
357
- pushGpuSample(performance.now() - submittedAt);
358
- });
359
- }
360
- });
361
- return null;
362
- };
363
371
  export default Viewer;
@@ -1 +1 @@
1
- {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/lights.tsx"],"names":[],"mappings":"AA4EA,wBAAgB,MAAM,gCAgPrB"}
1
+ {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/lights.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,MAAM,gCAqQrB"}