gps-plus-slam-app-framework 1.0.7 → 1.2.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 (189) hide show
  1. package/README.md +69 -15
  2. package/dist/{app-selectors-BeU0_ab8.d.ts → app-selectors-CcZ5UV-8.d.ts} +1 -1
  3. package/dist/ar/bresenham3d.d.ts +2 -0
  4. package/dist/ar/bresenham3d.js +81 -0
  5. package/dist/ar/camera-blit-capture.d.ts +1 -1
  6. package/dist/ar/camera-blit-capture.js +30 -2
  7. package/dist/ar/capability-checker.d.ts +2 -0
  8. package/dist/ar/capability-checker.js +25 -0
  9. package/dist/ar/capture-failure-tracker.d.ts +1 -1
  10. package/dist/ar/chromium-camera-access-workaround.d.ts +2 -2
  11. package/dist/ar/chromium-camera-access-workaround.js +197 -8
  12. package/dist/ar/depth-sampler.d.ts +3 -3
  13. package/dist/ar/depth-sampler.js +63 -8
  14. package/dist/ar/depth-unprojection.d.ts +2 -0
  15. package/dist/ar/depth-unprojection.js +94 -0
  16. package/dist/ar/enable-gps-ar.d.ts +2 -0
  17. package/dist/ar/enable-gps-ar.js +137 -0
  18. package/dist/ar/frame-loop.d.ts +1 -1
  19. package/dist/ar/frame-loop.js +24 -1
  20. package/dist/ar/image-capture.d.ts +1 -1
  21. package/dist/ar/image-capture.js +2 -1
  22. package/dist/ar/index.d.ts +19 -12
  23. package/dist/ar/index.js +11 -4
  24. package/dist/ar/occupancy-grid.d.ts +2 -0
  25. package/dist/ar/occupancy-grid.js +164 -0
  26. package/dist/ar/replay-scene.d.ts +1 -1
  27. package/dist/ar/replay-scene.js +1 -1
  28. package/dist/ar/scene-node-names.d.ts +1 -1
  29. package/dist/ar/webxr-nue-basis.d.ts +2 -0
  30. package/dist/ar/webxr-nue-basis.js +54 -0
  31. package/dist/ar/webxr-session.d.ts +4 -4
  32. package/dist/ar/webxr-session.js +1 -984
  33. package/dist/ar/xr-camera-texture.d.ts +1 -1
  34. package/dist/ar/xr-error-handler.d.ts +1 -1
  35. package/dist/ar/xr-frame-loop.d.ts +2 -0
  36. package/dist/ar/xr-frame-loop.js +69 -0
  37. package/dist/{ar-types-Ck8LEq8k.d.ts → ar-types-Dan1MVTi.d.ts} +28 -5
  38. package/dist/ar-world-group-alignment-ColHB_5A.d.ts +43 -0
  39. package/dist/bresenham3d-CFfd7n6B.d.ts +59 -0
  40. package/dist/{camera-blit-capture-C1V9I1Z6.d.ts → camera-blit-capture-D5_mNhla.d.ts} +20 -0
  41. package/dist/capability-checker-CQXJHxXg.d.ts +43 -0
  42. package/dist/chromium-camera-access-workaround-Jqbn0apH.d.ts +137 -0
  43. package/dist/core/index.d.ts +1 -1
  44. package/dist/{create-slam-app-store-CNsZ3rcX.d.ts → create-slam-app-store-qNXQ-tq5.d.ts} +5 -5
  45. package/dist/depth-sampler-Bhanfwla.d.ts +169 -0
  46. package/dist/depth-unprojection-Y6i_mJuT.d.ts +52 -0
  47. package/dist/enable-gps-ar-CapmADVN.d.ts +95 -0
  48. package/dist/{file-system-DTApEv_Q.d.ts → file-system-C_eqiEA8.d.ts} +1 -1
  49. package/dist/frame-conversions-BfsafGWR.d.ts +54 -0
  50. package/dist/{frame-loop-QJIxLin0.d.ts → frame-loop-CMewS8cY.d.ts} +5 -0
  51. package/dist/{fused-path-CB4DwWOG.d.ts → fused-path-DJoVu98b.d.ts} +1 -1
  52. package/dist/geo/h3-proximity.d.ts +1 -1
  53. package/dist/geo/index.d.ts +1 -1
  54. package/dist/{gps-anchor-CnWY61XK.d.ts → gps-anchor-dYeCvvp5.d.ts} +12 -1
  55. package/dist/{gps-ar-pose-sampler-DsM1Kb2N.d.ts → gps-ar-pose-sampler-DVb96iOy.d.ts} +1 -1
  56. package/dist/{gps-event-coordinator-DBx9I1YX.d.ts → gps-event-coordinator-BDIQde2x.d.ts} +3 -3
  57. package/dist/hit-test-reticle-BEzEybxZ.d.ts +45 -0
  58. package/dist/{image-capture-DZLxHDN9.d.ts → image-capture-DZ4HFRgS.d.ts} +15 -2
  59. package/dist/index-KJq2amHF.d.ts +37 -0
  60. package/dist/index.d.ts +67 -58
  61. package/dist/index.js +18 -8
  62. package/dist/{leaflet-map-overlay-Cew7XAoO.d.ts → leaflet-map-overlay-6CJC-E1l.d.ts} +1 -1
  63. package/dist/{map-data-sih4g64v.d.ts → map-data-CkloBxIl.d.ts} +1 -1
  64. package/dist/{map-overlay-draw-B3R1c9Xp.d.ts → map-overlay-draw-krOKwDAq.d.ts} +1 -1
  65. package/dist/{null-storage-backend-BKOS7JpL.d.ts → null-storage-backend-CxEjZHRW.d.ts} +2 -2
  66. package/dist/occupancy-grid-BRNRaPyT.d.ts +72 -0
  67. package/dist/{opfs-storage-backend-Cj_7PWoq.d.ts → opfs-storage-backend-CjSAuqg_.d.ts} +2 -2
  68. package/dist/{persistence-middleware-BkHIqvaA.d.ts → persistence-middleware-DwyzgfWr.d.ts} +1 -1
  69. package/dist/{recording-options-Dq6IsKAv.d.ts → recording-options-DJBXuMxV.d.ts} +22 -6
  70. package/dist/{recording-replayer-CmCIIZ02.d.ts → recording-replayer-BL-x-7k0.d.ts} +2 -2
  71. package/dist/{recording-slice-d6OtCrza.d.ts → recording-slice-89O07ebs.d.ts} +10 -2
  72. package/dist/{replay-engine-BGBRQyXs.d.ts → replay-engine-BRqszuQ9.d.ts} +1 -1
  73. package/dist/{replay-scene-DpAGcAKY.d.ts → replay-scene-Bw58VL6p.d.ts} +3 -3
  74. package/dist/sensors/gps-error-handler.d.ts +1 -1
  75. package/dist/sensors/gps.d.ts +1 -1
  76. package/dist/sensors/index.d.ts +3 -3
  77. package/dist/sensors/index.js +1 -1
  78. package/dist/sensors/permission-checker.d.ts +1 -1
  79. package/dist/session-disposers-M-oashRH.js +57 -0
  80. package/dist/state/app-selectors.d.ts +1 -1
  81. package/dist/state/combined-root-state.d.ts +1 -1
  82. package/dist/state/create-slam-app-store.d.ts +1 -1
  83. package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
  84. package/dist/state/gps-event-coordinator.d.ts +1 -1
  85. package/dist/state/index.d.ts +17 -17
  86. package/dist/state/index.js +2 -2
  87. package/dist/state/persistence-middleware.d.ts +1 -1
  88. package/dist/state/persistence-middleware.js +9 -3
  89. package/dist/state/recording-options.d.ts +1 -1
  90. package/dist/state/recording-options.js +5 -3
  91. package/dist/state/recording-replayer.d.ts +2 -2
  92. package/dist/state/recording-slice.d.ts +1 -1
  93. package/dist/state/recording-slice.js +11 -2
  94. package/dist/state/replay-engine.d.ts +1 -1
  95. package/dist/state/store-subscribers.d.ts +2 -2
  96. package/dist/state/subscribe-to-selector.d.ts +1 -1
  97. package/dist/state/tracking-quality.d.ts +1 -1
  98. package/dist/state/tracking-quality.js +1 -1
  99. package/dist/state/tracking-slice.d.ts +1 -1
  100. package/dist/state-BkIfpiyA.js +87 -0
  101. package/dist/storage/file-system-utils.d.ts +1 -1
  102. package/dist/storage/file-system.d.ts +1 -1
  103. package/dist/storage/index.d.ts +8 -8
  104. package/dist/storage/null-storage-backend.d.ts +1 -1
  105. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  106. package/dist/storage/opfs-storage.d.ts +1 -1
  107. package/dist/storage/storage-backend.d.ts +1 -1
  108. package/dist/storage/zip-export.d.ts +1 -1
  109. package/dist/storage/zip-reader.d.ts +1 -1
  110. package/dist/{storage-backend-CT0Il_AB.d.ts → storage-backend-C9HLFtgz.d.ts} +1 -1
  111. package/dist/{store-subscribers-Dce_CoJF.d.ts → store-subscribers-rrkLkFTt.d.ts} +2 -2
  112. package/dist/{subscribe-to-selector-DIHm3BRZ.d.ts → subscribe-to-selector-CP3BKSbF.d.ts} +1 -1
  113. package/dist/test-utils/browser-mocks.d.ts +1 -1
  114. package/dist/test-utils/zip-round-trip-helpers.d.ts +1 -1
  115. package/dist/{tracking-slice-utCNND8t.d.ts → tracking-slice-CdK8c-8P.d.ts} +1 -1
  116. package/dist/types/ar-types.d.ts +2 -2
  117. package/dist/types/geo-types.d.ts +1 -1
  118. package/dist/types/index.d.ts +3 -3
  119. package/dist/utils/concurrency.d.ts +1 -1
  120. package/dist/utils/failure-tracker.d.ts +1 -1
  121. package/dist/utils/format-file-size.d.ts +1 -1
  122. package/dist/utils/fused-path.d.ts +1 -1
  123. package/dist/utils/index.d.ts +6 -6
  124. package/dist/utils/list-formatter.d.ts +1 -1
  125. package/dist/utils/logger.d.ts +1 -1
  126. package/dist/visualization/accuracy-circles.d.ts +1 -1
  127. package/dist/visualization/alignment-lerper.d.ts +1 -1
  128. package/dist/visualization/ar-world-group-alignment.d.ts +2 -0
  129. package/dist/visualization/ar-world-group-alignment.js +51 -0
  130. package/dist/visualization/camera-follower.d.ts +1 -1
  131. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  132. package/dist/visualization/frame-conversions.d.ts +2 -0
  133. package/dist/visualization/frame-conversions.js +88 -0
  134. package/dist/visualization/frustum-visibility.d.ts +1 -1
  135. package/dist/visualization/gps-anchor.d.ts +1 -1
  136. package/dist/visualization/gps-anchor.js +216 -1
  137. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  138. package/dist/visualization/gps-event-markers.d.ts +1 -1
  139. package/dist/visualization/gps-event-markers.js +1 -1
  140. package/dist/visualization/hit-test-reticle.d.ts +2 -0
  141. package/dist/visualization/hit-test-reticle.js +81 -0
  142. package/dist/visualization/index.d.ts +19 -17
  143. package/dist/visualization/index.js +5 -2
  144. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  145. package/dist/visualization/lerp-utils.d.ts +1 -1
  146. package/dist/visualization/map-data.d.ts +1 -1
  147. package/dist/visualization/map-overlay-draw.d.ts +1 -1
  148. package/dist/visualization/map-overlay.d.ts +1 -1
  149. package/dist/visualization/three-dispose.d.ts +1 -1
  150. package/dist/visualization/vis-colors.d.ts +1 -1
  151. package/dist/webxr-nue-basis-CqKQmZzT.d.ts +23 -0
  152. package/dist/{webxr-session-DB5cIZnN.d.ts → webxr-session-DtAG9srK.d.ts} +45 -13
  153. package/dist/webxr-session-QhcSSub5.js +1041 -0
  154. package/dist/xr-frame-loop-BtYytZJg.d.ts +70 -0
  155. package/package.json +1 -1
  156. package/dist/chromium-camera-access-workaround-NvChXsi8.d.ts +0 -61
  157. package/dist/depth-sampler-xg8vna6c.d.ts +0 -108
  158. package/dist/gps-anchor-DjC_3MoT.js +0 -257
  159. package/dist/index-pOjUCKEP.d.ts +0 -24
  160. /package/dist/{accuracy-circles-CsxE_7x1.d.ts → accuracy-circles-BpwWAs8G.d.ts} +0 -0
  161. /package/dist/{alignment-lerper-L1RbWHQ4.d.ts → alignment-lerper-Dm9G7JJO.d.ts} +0 -0
  162. /package/dist/{camera-follower-1k8mnw5J.d.ts → camera-follower-C2mwEn7c.d.ts} +0 -0
  163. /package/dist/{capture-failure-tracker-JKb1ST8E.d.ts → capture-failure-tracker-CUeU3L_M.d.ts} +0 -0
  164. /package/dist/{concurrency-CMw8pSaG.d.ts → concurrency-CO1FQvs3.d.ts} +0 -0
  165. /package/dist/{css3d-renderer-manager-CkjCb2HW.d.ts → css3d-renderer-manager-ptu_t3B3.d.ts} +0 -0
  166. /package/dist/{failure-tracker-DnHXOIRa.d.ts → failure-tracker-Cn8Fo9D4.d.ts} +0 -0
  167. /package/dist/{file-system-utils-CtKiiw27.d.ts → file-system-utils-DyAmcuvX.d.ts} +0 -0
  168. /package/dist/{format-file-size-BTS5H62-.d.ts → format-file-size-CLCZz3lC.d.ts} +0 -0
  169. /package/dist/{frustum-visibility-BHnFrSKQ.d.ts → frustum-visibility-CmI96ESS.d.ts} +0 -0
  170. /package/dist/{geo-types-CTuF23gG.d.ts → geo-types-SaPnIEUM.d.ts} +0 -0
  171. /package/dist/{gps-DXoQb9Kf.d.ts → gps-VuezJWK3.d.ts} +0 -0
  172. /package/dist/{gps-compass-cubes-CsLtFv4B.d.ts → gps-compass-cubes-BM1uGiYV.d.ts} +0 -0
  173. /package/dist/{gps-error-handler-Dg3ziRVY.d.ts → gps-error-handler-CJl_2MVm.d.ts} +0 -0
  174. /package/dist/{gps-event-markers-BGr-iLvi.d.ts → gps-event-markers-Dc9FT-q1.d.ts} +0 -0
  175. /package/dist/{h3-proximity-BhfgNbdW.d.ts → h3-proximity-I1msGPgE.d.ts} +0 -0
  176. /package/dist/{index-gzWJMVpo.d.ts → index-CF05QV2m.d.ts} +0 -0
  177. /package/dist/{lerp-utils-B8MM8zCR.d.ts → lerp-utils-BpV-0Dtk.d.ts} +0 -0
  178. /package/dist/{list-formatter-CNqi-E2j.d.ts → list-formatter-RRcBe8Vg.d.ts} +0 -0
  179. /package/dist/{logger-C9vugiQb.d.ts → logger-BEHXsnnq.d.ts} +0 -0
  180. /package/dist/{map-overlay-DAmreyA3.d.ts → map-overlay-bcogY5fK.d.ts} +0 -0
  181. /package/dist/{opfs-storage-DOPkzgeT.d.ts → opfs-storage-DYm8WHq7.d.ts} +0 -0
  182. /package/dist/{permission-checker-DPJl_RU6.d.ts → permission-checker-GpAiSCwd.d.ts} +0 -0
  183. /package/dist/{scene-node-names-6LBjPmwz.d.ts → scene-node-names-Bu0S66H1.d.ts} +0 -0
  184. /package/dist/{three-dispose-C2drkp6f.d.ts → three-dispose-D950t71B.d.ts} +0 -0
  185. /package/dist/{vis-colors-CHQhjal3.d.ts → vis-colors-D6omC64K.d.ts} +0 -0
  186. /package/dist/{xr-camera-texture-BU5YvKAM.d.ts → xr-camera-texture-DBwCaKTd.d.ts} +0 -0
  187. /package/dist/{xr-error-handler-CDY5QxGb.d.ts → xr-error-handler-C0AQRmxs.d.ts} +0 -0
  188. /package/dist/{zip-export-QO4iLIi7.d.ts → zip-export-D_5HAvFz.d.ts} +0 -0
  189. /package/dist/{zip-reader-D1105idz.d.ts → zip-reader-DSEXt7K6.d.ts} +0 -0
@@ -1,9 +1,32 @@
1
1
  import { extractOdomPosition } from "../state/gps-event-coordinator.js";
2
2
  //#region ../src/ar/depth-sampler.ts
3
+ /**
4
+ * Wrap a raw browser XRDepthInformation object into a {@link DepthInfo}.
5
+ *
6
+ * - `getDepthInMeters` is bound to the source object (browser
7
+ * implementations are this-sensitive).
8
+ * - `projectionMatrix` (typically `XRView.projectionMatrix`, a Float32Array
9
+ * the UA may reuse across frames) is defensively validated and copied into
10
+ * a plain serializable 16-tuple; invalid input (wrong length, non-finite
11
+ * entries) yields a DepthInfo without a matrix rather than an error.
12
+ */
13
+ function wrapXRDepthInfo(raw, projectionMatrix) {
14
+ const wrapped = {
15
+ width: raw.width,
16
+ height: raw.height,
17
+ getDepthInMeters: raw.getDepthInMeters.bind(raw)
18
+ };
19
+ if (projectionMatrix && projectionMatrix.length === 16) {
20
+ const copy = Array.from(projectionMatrix);
21
+ if (copy.every((v) => Number.isFinite(v))) wrapped.projectionMatrix = copy;
22
+ }
23
+ return wrapped;
24
+ }
3
25
  const DEFAULT_CONFIG = {
4
26
  intervalMs: 1e3,
5
- gridSize: 3,
6
- unavailabilityThresholdMs: 5e3
27
+ gridSize: 16,
28
+ unavailabilityThresholdMs: 5e3,
29
+ rgb: true
7
30
  };
8
31
  /**
9
32
  * Samples sparse depth points from WebXR depth sensing.
@@ -74,6 +97,18 @@ var DepthSampler = class {
74
97
  return { ...this.config };
75
98
  }
76
99
  /**
100
+ * Apply partial configuration overrides (e.g. the user's recording
101
+ * options, plumbed in by `startDepthCapture` just before sampling
102
+ * starts). Invalid values are ignored defensively: every key requires a
103
+ * finite positive number, and `gridSize` additionally an integer.
104
+ */
105
+ updateConfig(config) {
106
+ if (isFinitePositive(config.intervalMs)) this.config.intervalMs = config.intervalMs;
107
+ if (isFinitePositive(config.gridSize) && Number.isInteger(config.gridSize)) this.config.gridSize = config.gridSize;
108
+ if (isFinitePositive(config.unavailabilityThresholdMs)) this.config.unavailabilityThresholdMs = config.unavailabilityThresholdMs;
109
+ if (typeof config.rgb === "boolean") this.config.rgb = config.rgb;
110
+ }
111
+ /**
77
112
  * Called each frame with depth information.
78
113
  *
79
114
  * @param timestamp - Current frame timestamp in milliseconds
@@ -89,7 +124,7 @@ var DepthSampler = class {
89
124
  if (timestamp - this.lastSampleTime < this.config.intervalMs) return;
90
125
  const pose = this.callbacks.getCurrentPose();
91
126
  if (!pose) return;
92
- const points = this.sampleGrid(depthInfo);
127
+ const points = this.sampleGrid(depthInfo, this.acquireRgbLookupSafely());
93
128
  const sample = {
94
129
  timestamp: performance.timeOrigin + timestamp,
95
130
  cameraPos: extractOdomPosition(pose),
@@ -99,26 +134,43 @@ var DepthSampler = class {
99
134
  pose.orientation.z,
100
135
  pose.orientation.w
101
136
  ],
102
- points
137
+ points,
138
+ ...depthInfo.projectionMatrix ? { projectionMatrix: depthInfo.projectionMatrix } : {}
103
139
  };
104
140
  this.lastSampleTime = timestamp;
105
141
  this.sampleCount++;
106
142
  this.callbacks.onSampleCaptured(sample);
107
143
  }
108
144
  /**
109
- * Sample a grid of depth points from the depth buffer.
145
+ * Acquire the per-sample RGB lookup, gated by `config.rgb` and guarded so
146
+ * a failing acquisition (e.g. GL context loss during the blit) can never
147
+ * break the sample emission in the XR frame loop.
148
+ */
149
+ acquireRgbLookupSafely() {
150
+ if (!this.config.rgb || !this.callbacks.acquireRgbLookup) return null;
151
+ try {
152
+ return this.callbacks.acquireRgbLookup();
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ /**
158
+ * Sample a grid of depth points from the depth buffer, attaching the
159
+ * camera color per point when a lookup is available.
110
160
  */
111
- sampleGrid(depthInfo) {
161
+ sampleGrid(depthInfo, rgbLookup) {
112
162
  const points = [];
113
163
  const gridSize = this.config.gridSize;
114
164
  for (let row = 0; row < gridSize; row++) for (let col = 0; col < gridSize; col++) {
115
165
  const screenX = (col + 1) / (gridSize + 1);
116
166
  const screenY = (row + 1) / (gridSize + 1);
117
167
  const depthM = depthInfo.getDepthInMeters(screenX, screenY);
168
+ const rgb = rgbLookup?.(screenX, screenY);
118
169
  points.push({
119
170
  screenX,
120
171
  screenY,
121
- depthM
172
+ depthM,
173
+ ...rgb ? { rgb } : {}
122
174
  });
123
175
  }
124
176
  return points;
@@ -143,5 +195,8 @@ var DepthSampler = class {
143
195
  return this.depthReceived;
144
196
  }
145
197
  };
198
+ function isFinitePositive(value) {
199
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
200
+ }
146
201
  //#endregion
147
- export { DepthSampler };
202
+ export { DepthSampler, wrapXRDepthInfo };
@@ -0,0 +1,2 @@
1
+ import { n as createDepthUnprojector, r as unprojectDepthPoint, t as DepthUnprojector } from "../depth-unprojection-Y6i_mJuT.js";
2
+ export { DepthUnprojector, createDepthUnprojector, unprojectDepthPoint };
@@ -0,0 +1,94 @@
1
+ import { mat4, quat, vec3, vec4 } from "gl-matrix";
2
+ //#region ../src/ar/depth-unprojection.ts
3
+ /**
4
+ * Depth Unprojection
5
+ *
6
+ * Pure math: turn a persisted depth read (normalized view coordinates +
7
+ * depth in meters) back into a 3D point in raw WebXR (local-floor) space
8
+ * using the capturing view's projection matrix.
9
+ *
10
+ * Convention (single source of truth for NDC flips, see the 2026-06-11
11
+ * occupancy-grid port plan §6):
12
+ * - screenX/screenY are normalized view coordinates with a TOP-LEFT origin
13
+ * (screenY grows downward), exactly as fed to `getDepthInMeters`.
14
+ * - NDC: x = 2·sx − 1, y = 1 − 2·sy (flip Y to bottom-up).
15
+ * - View space is the WebXR camera frame: +x right, +y up, −z forward;
16
+ * `depthM` is the z-depth (distance along −z), not euclidean distance.
17
+ *
18
+ * @see depth-unprojection.ts.md for detailed documentation
19
+ */
20
+ /**
21
+ * Build a {@link DepthUnprojector} for one depth sample. The projection
22
+ * inverse and camera quaternion/position are sample-invariant, so they are
23
+ * computed once here instead of per point (the per-point hot path then only
24
+ * does the cheap NDC→view→world transform). Callers that fold many points
25
+ * from the same sample — e.g. `OccupancyGrid.addSample` — should build the
26
+ * unprojector once and reuse it for all points.
27
+ *
28
+ * @param cameraPos - camera position, raw WebXR (`DepthSample.cameraPos`)
29
+ * @param cameraRot - camera quaternion [x,y,z,w], raw WebXR (`DepthSample.cameraRot`)
30
+ * @param projectionMatrix - column-major projection matrix of the capturing
31
+ * view (`DepthSample.projectionMatrix`). `undefined` for recordings made
32
+ * before intrinsics capture — those points cannot be unprojected.
33
+ * @returns an unprojector, or `null` when the sample cannot be unprojected at
34
+ * all (missing or singular projection matrix).
35
+ */
36
+ function createDepthUnprojector(cameraPos, cameraRot, projectionMatrix) {
37
+ if (!projectionMatrix || projectionMatrix.length !== 16) return null;
38
+ const invProj = mat4.invert(mat4.create(), projectionMatrix);
39
+ if (!invProj) return null;
40
+ const cameraQuat = quat.fromValues(cameraRot[0], cameraRot[1], cameraRot[2], cameraRot[3]);
41
+ const cameraPosVec = vec3.fromValues(cameraPos[0], cameraPos[1], cameraPos[2]);
42
+ const ndc = vec4.create();
43
+ const view = vec4.create();
44
+ const viewPoint = vec3.create();
45
+ const world = vec3.create();
46
+ return { unproject(point) {
47
+ if (!isUsablePoint(point)) return null;
48
+ const { screenX, screenY, depthM } = point;
49
+ vec4.set(ndc, 2 * screenX - 1, 1 - 2 * screenY, -1, 1);
50
+ vec4.transformMat4(view, ndc, invProj);
51
+ if (view[3] === 0) return null;
52
+ const rayX = view[0] / view[3];
53
+ const rayY = view[1] / view[3];
54
+ const rayZ = view[2] / view[3];
55
+ if (rayZ >= 0) return null;
56
+ const scale = -depthM / rayZ;
57
+ vec3.set(viewPoint, rayX * scale, rayY * scale, -depthM);
58
+ vec3.transformQuat(world, viewPoint, cameraQuat);
59
+ vec3.add(world, world, cameraPosVec);
60
+ const result = [
61
+ world[0],
62
+ world[1],
63
+ world[2]
64
+ ];
65
+ return result.every((v) => Number.isFinite(v)) ? result : null;
66
+ } };
67
+ }
68
+ /**
69
+ * Unproject a single depth point into raw WebXR space. Convenience wrapper
70
+ * over {@link createDepthUnprojector} for one-off callers; when unprojecting
71
+ * many points from the same sample, build the unprojector once instead.
72
+ *
73
+ * @param point - normalized view coordinates + depth in meters
74
+ * @param cameraPos - camera position, raw WebXR (`DepthSample.cameraPos`)
75
+ * @param cameraRot - camera quaternion [x,y,z,w], raw WebXR (`DepthSample.cameraRot`)
76
+ * @param projectionMatrix - column-major projection matrix of the capturing
77
+ * view (`DepthSample.projectionMatrix`). `undefined` for recordings made
78
+ * before intrinsics capture — those points cannot be unprojected.
79
+ * @returns the 3D point in raw WebXR space, or `null` when the input is not
80
+ * usable (missing/singular matrix, non-positive or non-finite depth,
81
+ * out-of-range screen coordinates).
82
+ */
83
+ function unprojectDepthPoint(point, cameraPos, cameraRot, projectionMatrix) {
84
+ const unprojector = createDepthUnprojector(cameraPos, cameraRot, projectionMatrix);
85
+ return unprojector ? unprojector.unproject(point) : null;
86
+ }
87
+ function isUsablePoint(point) {
88
+ return Number.isFinite(point.depthM) && point.depthM > 0 && isInUnitRange(point.screenX) && isInUnitRange(point.screenY);
89
+ }
90
+ function isInUnitRange(v) {
91
+ return Number.isFinite(v) && v >= 0 && v <= 1;
92
+ }
93
+ //#endregion
94
+ export { createDepthUnprojector, unprojectDepthPoint };
@@ -0,0 +1,2 @@
1
+ import { a as EnableGpsArState, i as EnableGpsArResult, n as EnableGpsArController, o as EnableGpsArStatus, r as EnableGpsArDeps, s as createEnableGpsArController, t as EnableGpsArConfig } from "../enable-gps-ar-CapmADVN.js";
2
+ export { EnableGpsArConfig, EnableGpsArController, EnableGpsArDeps, EnableGpsArResult, EnableGpsArState, EnableGpsArStatus, createEnableGpsArController };
@@ -0,0 +1,137 @@
1
+ import { createLogger } from "../utils/logger.js";
2
+ import { requestGeolocationPermission, requestOrientationPermission, requestWebXRWithDepthPermission } from "../sensors/permission-checker.js";
3
+ import { startGpsWatch, startOrientationWatch, stopGpsWatch, stopOrientationWatch } from "../sensors/gps.js";
4
+ import { _ as isWebXRSupported, g as initAR, i as endARSession } from "../webxr-session-QhcSSub5.js";
5
+ //#region ../src/ar/enable-gps-ar.ts
6
+ const log = createLogger("EnableGpsAr");
7
+ const defaultDeps = {
8
+ isWebXRSupported,
9
+ requestGeolocationPermission,
10
+ requestOrientationPermission,
11
+ requestWebXRWithDepthPermission,
12
+ startGpsWatch,
13
+ startOrientationWatch,
14
+ initAR,
15
+ stopGpsWatch,
16
+ stopOrientationWatch,
17
+ endARSession
18
+ };
19
+ /**
20
+ * Create an "Enable GPS AR" controller. Pass partial `deps` in tests to inject
21
+ * fakes; production callers use the defaults.
22
+ */
23
+ function createEnableGpsArController(deps = {}) {
24
+ const resolved = {
25
+ ...defaultDeps,
26
+ ...deps
27
+ };
28
+ let state = { status: "checking" };
29
+ const listeners = /* @__PURE__ */ new Set();
30
+ let gpsWatchActive = false;
31
+ let orientationWatchActive = false;
32
+ function setState(next) {
33
+ state = next;
34
+ for (const listener of [...listeners]) try {
35
+ listener(state);
36
+ } catch (err) {
37
+ log.error("State listener threw; continuing dispatch:", err);
38
+ }
39
+ }
40
+ async function refreshSupport() {
41
+ if (state.status === "starting" || state.status === "running" || state.status === "stopping") return;
42
+ setState({ status: "checking" });
43
+ const supported = await probeSupport();
44
+ if (state.status !== "checking") return;
45
+ setState({ status: supported ? "ready" : "unsupported" });
46
+ }
47
+ async function probeSupport() {
48
+ try {
49
+ return await resolved.isWebXRSupported();
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+ /**
55
+ * Request the configured permissions. Returns an error message when a
56
+ * *blocking* permission (geolocation, or depth when opted in) is not granted,
57
+ * or `null` on success. Orientation is best-effort and never blocks.
58
+ */
59
+ async function requestPermissions(config) {
60
+ const geolocation = await resolved.requestGeolocationPermission();
61
+ if (geolocation.granted !== true) return geolocation.error ?? "Location permission is required for GPS AR.";
62
+ try {
63
+ await resolved.requestOrientationPermission();
64
+ } catch {}
65
+ if (config.requestDepth) {
66
+ const depth = await resolved.requestWebXRWithDepthPermission();
67
+ if (depth.granted !== true) return depth.error ?? "Depth (3D map) permission was denied.";
68
+ }
69
+ return null;
70
+ }
71
+ async function enable(config) {
72
+ if (state.status === "starting" || state.status === "running" || state.status === "stopping") return {
73
+ ok: false,
74
+ error: "AR is already starting, running or stopping."
75
+ };
76
+ setState({ status: "starting" });
77
+ try {
78
+ const permissionError = await requestPermissions(config);
79
+ if (permissionError !== null) return fail(permissionError);
80
+ await resolved.initAR(config.container, config.isolationOptions, { requestHitTest: config.requestHitTest });
81
+ if (config.onGpsPosition) {
82
+ resolved.startGpsWatch(config.onGpsPosition);
83
+ gpsWatchActive = true;
84
+ }
85
+ if (config.onOrientation) {
86
+ resolved.startOrientationWatch(config.onOrientation);
87
+ orientationWatchActive = true;
88
+ }
89
+ setState({ status: "running" });
90
+ return { ok: true };
91
+ } catch (err) {
92
+ return fail(err instanceof Error ? err.message : String(err));
93
+ }
94
+ }
95
+ function fail(error) {
96
+ setState({
97
+ status: "error",
98
+ error
99
+ });
100
+ return {
101
+ ok: false,
102
+ error
103
+ };
104
+ }
105
+ async function disable() {
106
+ if (state.status !== "running") return;
107
+ setState({ status: "stopping" });
108
+ if (gpsWatchActive) {
109
+ resolved.stopGpsWatch();
110
+ gpsWatchActive = false;
111
+ }
112
+ if (orientationWatchActive) {
113
+ resolved.stopOrientationWatch();
114
+ orientationWatchActive = false;
115
+ }
116
+ try {
117
+ await resolved.endARSession();
118
+ } catch (err) {
119
+ log.error("endARSession threw during disable; continuing teardown:", err);
120
+ }
121
+ setState({ status: "ready" });
122
+ }
123
+ return {
124
+ getState: () => state,
125
+ subscribe: (listener) => {
126
+ listeners.add(listener);
127
+ return () => {
128
+ listeners.delete(listener);
129
+ };
130
+ },
131
+ refreshSupport,
132
+ enable,
133
+ disable
134
+ };
135
+ }
136
+ //#endregion
137
+ export { createEnableGpsArController };
@@ -1,2 +1,2 @@
1
- import { i as runFrameUpdates, n as clearFrameUpdates, r as registerFrameUpdate, t as FrameUpdate } from "../frame-loop-QJIxLin0.js";
1
+ import { i as runFrameUpdates, n as clearFrameUpdates, r as registerFrameUpdate, t as FrameUpdate } from "../frame-loop-CMewS8cY.js";
2
2
  export { FrameUpdate, clearFrameUpdates, registerFrameUpdate, runFrameUpdates };
@@ -1,4 +1,18 @@
1
+ import { createLogger } from "../utils/logger.js";
1
2
  //#region ../src/ar/frame-loop.ts
3
+ /**
4
+ * Per-frame callback registry. The WebXR session owns the single
5
+ * `renderer.setAnimationLoop(...)` hook for the page; components that need
6
+ * a per-frame tick register here and the session's `onXRFrame` invokes
7
+ * `runFrameUpdates(dt, elapsed)` once per frame.
8
+ *
9
+ * Plain functions, not a class: there is exactly one frame loop per session
10
+ * and singletons-of-singletons add no value. See
11
+ * `2026-05-13-ecs-migration-plan.md` for the design rationale and the
12
+ * rules new `FrameUpdate` bodies must follow (pure function of `dt` plus
13
+ * selectors; no direct Redux dispatch from inside a tick).
14
+ */
15
+ const log = createLogger("FrameLoop");
2
16
  const updates = /* @__PURE__ */ new Set();
3
17
  /**
4
18
  * Register a per-frame callback. Returns an unregister function.
@@ -23,10 +37,19 @@ function registerFrameUpdate(fn) {
23
37
  * same frame are deferred to the next tick. Iterating the live `Set`
24
38
  * would otherwise skip a not-yet-visited entry that an earlier handler
25
39
  * unregistered — a hard-to-debug source of non-determinism.
40
+ *
41
+ * Each callback is invoked in its own `try/catch` so a throwing handler is
42
+ * isolated: it cannot abort the remaining callbacks nor propagate up through
43
+ * `onXRFrame` and kill the scene render for the whole frame. Failures are
44
+ * logged and the loop continues — mirrored by `runXrFrameUpdates`.
26
45
  */
27
46
  function runFrameUpdates(dt, elapsed) {
28
47
  const snapshot = Array.from(updates);
29
- for (const fn of snapshot) fn(dt, elapsed);
48
+ for (const fn of snapshot) try {
49
+ fn(dt, elapsed);
50
+ } catch (error) {
51
+ log.error("A registered FrameUpdate threw; continuing the loop", error);
52
+ }
30
53
  }
31
54
  /**
32
55
  * Clear all registrations. Called from `resetWebXRState()` so a fresh
@@ -1,2 +1,2 @@
1
- import { a as ImageCaptureManager, i as ImageCaptureConfig, n as DEFAULT_CAPTURE_CONFIG, o as MIN_VALID_IMAGE_BYTES, r as ImageCaptureCallbacks, t as CapturedImage } from "../image-capture-DZLxHDN9.js";
1
+ import { a as ImageCaptureManager, i as ImageCaptureConfig, n as DEFAULT_CAPTURE_CONFIG, o as MIN_VALID_IMAGE_BYTES, r as ImageCaptureCallbacks, t as CapturedImage } from "../image-capture-DZ4HFRgS.js";
2
2
  export { CapturedImage, DEFAULT_CAPTURE_CONFIG, ImageCaptureCallbacks, ImageCaptureConfig, ImageCaptureManager, MIN_VALID_IMAGE_BYTES };
@@ -23,7 +23,8 @@ const MIN_VALID_IMAGE_BYTES = 5e3;
23
23
  const DEFAULT_CAPTURE_CONFIG = {
24
24
  intervalMs: 2e3,
25
25
  quality: .7,
26
- captureTimeoutMs: 5e3
26
+ captureTimeoutMs: 5e3,
27
+ resolutionDivisor: 1
27
28
  };
28
29
  /**
29
30
  * Manages periodic image capture from a canvas.
@@ -1,12 +1,19 @@
1
- import { i as computeCaptureSize, n as CameraBlitCaptureConfig, r as DEFAULT_BLIT_CONFIG, t as CameraBlitCapture } from "../camera-blit-capture-C1V9I1Z6.js";
2
- import { a as createCaptureFailureTracker, i as DEFAULT_CAPTURE_TRACKER_CONFIG, n as CaptureFailureTracker, r as CaptureFailureTrackerConfig, t as CAPTURE_FAILURE_WARNING } from "../capture-failure-tracker-JKb1ST8E.js";
3
- import { n as applyChromiumProjectionLayerWorkaround, t as ChromiumProjectionLayerWorkaroundResult } from "../chromium-camera-access-workaround-NvChXsi8.js";
4
- import { i as DepthSamplerConfig, n as DepthSampler, r as DepthSamplerCallbacks, t as DepthInfo } from "../depth-sampler-xg8vna6c.js";
5
- import { r as registerFrameUpdate, t as FrameUpdate } from "../frame-loop-QJIxLin0.js";
6
- import { a as ImageCaptureManager, i as ImageCaptureConfig, n as DEFAULT_CAPTURE_CONFIG, o as MIN_VALID_IMAGE_BYTES, r as ImageCaptureCallbacks, t as CapturedImage } from "../image-capture-DZLxHDN9.js";
7
- import { a as getCameraFollower, c as getReplayState, d as updateOrbitTarget, i as getAlignmentLerper, l as initReplayScene, n as ReplaySceneState, o as getCameraMode, r as disposeReplayScene, t as CameraMode, u as toggleCameraMode } from "../replay-scene-DpAGcAKY.js";
8
- import { t as SCENE_NODE } from "../scene-node-names-6LBjPmwz.js";
9
- import { i as acquireCameraTexture, n as RendererLike, r as XRCameraLike, t as CameraTextureResult } from "../xr-camera-texture-BU5YvKAM.js";
10
- import { A as setTrackingCallbacks, D as setFrameCallback, E as setDepthCaptureCallback, F as startDepthCapture, I as startImageCapture, L as stopDepthCapture, N as setTrackingStore, O as setImageCaptureCallback, R as stopImageCapture, _ as initAR, a as endARSession, f as getDepthSampleCount, j as setTrackingLostCallback, m as getLiveCss3dManager, p as getImageCaptureFrameCount } from "../webxr-session-DB5cIZnN.js";
11
- import { n as XR_ERROR_MESSAGE_UNKNOWN, r as getXrErrorMessage, t as XR_ERROR_MESSAGES } from "../xr-error-handler-CDY5QxGb.js";
12
- export { CAPTURE_FAILURE_WARNING, CameraBlitCapture, type CameraBlitCaptureConfig, type CameraMode, type CameraTextureResult, type CaptureFailureTracker, type CaptureFailureTrackerConfig, type CapturedImage, type ChromiumProjectionLayerWorkaroundResult, DEFAULT_BLIT_CONFIG, DEFAULT_CAPTURE_CONFIG, DEFAULT_CAPTURE_TRACKER_CONFIG, type DepthInfo, DepthSampler, type DepthSamplerCallbacks, type DepthSamplerConfig, type FrameUpdate, type ImageCaptureCallbacks, type ImageCaptureConfig, ImageCaptureManager, MIN_VALID_IMAGE_BYTES, type RendererLike, type ReplaySceneState, SCENE_NODE, type XRCameraLike, XR_ERROR_MESSAGES, XR_ERROR_MESSAGE_UNKNOWN, acquireCameraTexture, applyChromiumProjectionLayerWorkaround, computeCaptureSize, createCaptureFailureTracker, disposeReplayScene, endARSession, getAlignmentLerper, getCameraFollower, getCameraMode, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getReplayState, getXrErrorMessage, initAR, initReplayScene, registerFrameUpdate, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setTrackingCallbacks, setTrackingLostCallback, setTrackingStore, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture, toggleCameraMode, updateOrbitTarget };
1
+ import { r as bresenham3d, t as GridCell } from "../bresenham3d-CFfd7n6B.js";
2
+ import { i as computeCaptureSize, n as CameraBlitCaptureConfig, r as DEFAULT_BLIT_CONFIG, t as CameraBlitCapture } from "../camera-blit-capture-D5_mNhla.js";
3
+ import { i as isFullySupported, n as CapabilitySupport, r as capabilityMessage, t as CapabilityMessageOptions } from "../capability-checker-CQXJHxXg.js";
4
+ import { a as createCaptureFailureTracker, i as DEFAULT_CAPTURE_TRACKER_CONFIG, n as CaptureFailureTracker, r as CaptureFailureTrackerConfig, t as CAPTURE_FAILURE_WARNING } from "../capture-failure-tracker-CUeU3L_M.js";
5
+ import { a as applyChromiumProjectionLayerWorkaround, i as ChromiumProjectionLayerWorkaroundResult, n as BASELAYER_WINDOW_MIN, o as needsBaseLayerPersistence, r as ChromeVersion, s as parseChromeVersion, t as BASELAYER_WINDOW_MAX } from "../chromium-camera-access-workaround-Jqbn0apH.js";
6
+ import { a as wrapXRDepthInfo, i as DepthSamplerConfig, n as DepthSampler, r as DepthSamplerCallbacks, t as DepthInfo } from "../depth-sampler-Bhanfwla.js";
7
+ import { n as createDepthUnprojector, r as unprojectDepthPoint, t as DepthUnprojector } from "../depth-unprojection-Y6i_mJuT.js";
8
+ import { a as ImageCaptureManager, i as ImageCaptureConfig, n as DEFAULT_CAPTURE_CONFIG, o as MIN_VALID_IMAGE_BYTES, r as ImageCaptureCallbacks, t as CapturedImage } from "../image-capture-DZ4HFRgS.js";
9
+ import { i as acquireCameraTexture, n as RendererLike, r as XRCameraLike, t as CameraTextureResult } from "../xr-camera-texture-DBwCaKTd.js";
10
+ import { D as setDepthCaptureCallback, I as startDepthCapture, L as startImageCapture, M as setTrackingLostCallback, O as setFrameCallback, P as setTrackingStore, R as stopDepthCapture, d as getCamera, f as getCurrentArPose, g as getScene, h as getLiveCss3dManager, j as setTrackingCallbacks, k as setImageCaptureCallback, m as getImageCaptureFrameCount, o as endARSession, p as getDepthSampleCount, t as SessionFeatureOptions, u as getArWorldGroup, v as initAR, z as stopImageCapture } from "../webxr-session-DtAG9srK.js";
11
+ import { a as EnableGpsArState, i as EnableGpsArResult, n as EnableGpsArController, o as EnableGpsArStatus, r as EnableGpsArDeps, s as createEnableGpsArController, t as EnableGpsArConfig } from "../enable-gps-ar-CapmADVN.js";
12
+ import { r as registerFrameUpdate, t as FrameUpdate } from "../frame-loop-CMewS8cY.js";
13
+ import { n as OccupancyGridOptions, t as OccupancyGrid } from "../occupancy-grid-BRNRaPyT.js";
14
+ import { i as registerXrFrameUpdate, n as XrFrameUpdate, t as XrFrameContext } from "../xr-frame-loop-BtYytZJg.js";
15
+ import { a as getCameraFollower, c as getReplayState, d as updateOrbitTarget, i as getAlignmentLerper, l as initReplayScene, n as ReplaySceneState, o as getCameraMode, r as disposeReplayScene, t as CameraMode, u as toggleCameraMode } from "../replay-scene-Bw58VL6p.js";
16
+ import { t as SCENE_NODE } from "../scene-node-names-Bu0S66H1.js";
17
+ import { t as WEBXR_TO_NUE } from "../webxr-nue-basis-CqKQmZzT.js";
18
+ import { n as XR_ERROR_MESSAGE_UNKNOWN, r as getXrErrorMessage, t as XR_ERROR_MESSAGES } from "../xr-error-handler-C0AQRmxs.js";
19
+ export { BASELAYER_WINDOW_MAX, BASELAYER_WINDOW_MIN, CAPTURE_FAILURE_WARNING, CameraBlitCapture, type CameraBlitCaptureConfig, type CameraMode, type CameraTextureResult, type CapabilityMessageOptions, type CapabilitySupport, type CaptureFailureTracker, type CaptureFailureTrackerConfig, type CapturedImage, type ChromeVersion, type ChromiumProjectionLayerWorkaroundResult, DEFAULT_BLIT_CONFIG, DEFAULT_CAPTURE_CONFIG, DEFAULT_CAPTURE_TRACKER_CONFIG, type DepthInfo, DepthSampler, type DepthSamplerCallbacks, type DepthSamplerConfig, type DepthUnprojector, type EnableGpsArConfig, type EnableGpsArController, type EnableGpsArDeps, type EnableGpsArResult, type EnableGpsArState, type EnableGpsArStatus, type FrameUpdate, type GridCell, type ImageCaptureCallbacks, type ImageCaptureConfig, ImageCaptureManager, MIN_VALID_IMAGE_BYTES, OccupancyGrid, type OccupancyGridOptions, type RendererLike, type ReplaySceneState, SCENE_NODE, type SessionFeatureOptions, WEBXR_TO_NUE, type XRCameraLike, XR_ERROR_MESSAGES, XR_ERROR_MESSAGE_UNKNOWN, type XrFrameContext, type XrFrameUpdate, acquireCameraTexture, applyChromiumProjectionLayerWorkaround, bresenham3d, capabilityMessage, computeCaptureSize, createCaptureFailureTracker, createDepthUnprojector, createEnableGpsArController, disposeReplayScene, endARSession, getAlignmentLerper, getArWorldGroup, getCamera, getCameraFollower, getCameraMode, getCurrentArPose, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getReplayState, getScene, getXrErrorMessage, initAR, initReplayScene, isFullySupported, needsBaseLayerPersistence, parseChromeVersion, registerFrameUpdate, registerXrFrameUpdate, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setTrackingCallbacks, setTrackingLostCallback, setTrackingStore, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture, toggleCameraMode, unprojectDepthPoint, updateOrbitTarget, wrapXRDepthInfo };
package/dist/ar/index.js CHANGED
@@ -1,12 +1,19 @@
1
1
  import { CameraBlitCapture, DEFAULT_BLIT_CONFIG, computeCaptureSize } from "./camera-blit-capture.js";
2
- import { applyChromiumProjectionLayerWorkaround } from "./chromium-camera-access-workaround.js";
2
+ import { capabilityMessage, isFullySupported } from "./capability-checker.js";
3
+ import { BASELAYER_WINDOW_MAX, BASELAYER_WINDOW_MIN, applyChromiumProjectionLayerWorkaround, needsBaseLayerPersistence, parseChromeVersion } from "./chromium-camera-access-workaround.js";
3
4
  import { CAPTURE_FAILURE_WARNING, DEFAULT_CAPTURE_TRACKER_CONFIG, createCaptureFailureTracker } from "./capture-failure-tracker.js";
4
- import { DepthSampler } from "./depth-sampler.js";
5
+ import { DepthSampler, wrapXRDepthInfo } from "./depth-sampler.js";
6
+ import { createDepthUnprojector, unprojectDepthPoint } from "./depth-unprojection.js";
7
+ import { bresenham3d } from "./bresenham3d.js";
8
+ import { OccupancyGrid } from "./occupancy-grid.js";
5
9
  import { registerFrameUpdate } from "./frame-loop.js";
10
+ import { registerXrFrameUpdate } from "./xr-frame-loop.js";
11
+ import { WEBXR_TO_NUE } from "./webxr-nue-basis.js";
6
12
  import { DEFAULT_CAPTURE_CONFIG, ImageCaptureManager, MIN_VALID_IMAGE_BYTES } from "./image-capture.js";
13
+ import { A as setTrackingLostCallback, D as setImageCaptureCallback, E as setFrameCallback, F as startImageCapture, I as stopDepthCapture, L as stopImageCapture, M as setTrackingStore, P as startDepthCapture, T as setDepthCaptureCallback, c as getArWorldGroup, d as getDepthSampleCount, f as getImageCaptureFrameCount, g as initAR, i as endARSession, k as setTrackingCallbacks, l as getCamera, m as getScene, p as getLiveCss3dManager, u as getCurrentArPose } from "../webxr-session-QhcSSub5.js";
7
14
  import { acquireCameraTexture } from "./xr-camera-texture.js";
8
15
  import { SCENE_NODE } from "./scene-node-names.js";
9
- import { endARSession, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, initAR, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setTrackingCallbacks, setTrackingLostCallback, setTrackingStore, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture } from "./webxr-session.js";
16
+ import { createEnableGpsArController } from "./enable-gps-ar.js";
10
17
  import { disposeReplayScene, getAlignmentLerper, getCameraFollower, getCameraMode, getReplayState, initReplayScene, toggleCameraMode, updateOrbitTarget } from "./replay-scene.js";
11
18
  import { XR_ERROR_MESSAGES, XR_ERROR_MESSAGE_UNKNOWN, getXrErrorMessage } from "./xr-error-handler.js";
12
- export { CAPTURE_FAILURE_WARNING, CameraBlitCapture, DEFAULT_BLIT_CONFIG, DEFAULT_CAPTURE_CONFIG, DEFAULT_CAPTURE_TRACKER_CONFIG, DepthSampler, ImageCaptureManager, MIN_VALID_IMAGE_BYTES, SCENE_NODE, XR_ERROR_MESSAGES, XR_ERROR_MESSAGE_UNKNOWN, acquireCameraTexture, applyChromiumProjectionLayerWorkaround, computeCaptureSize, createCaptureFailureTracker, disposeReplayScene, endARSession, getAlignmentLerper, getCameraFollower, getCameraMode, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getReplayState, getXrErrorMessage, initAR, initReplayScene, registerFrameUpdate, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setTrackingCallbacks, setTrackingLostCallback, setTrackingStore, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture, toggleCameraMode, updateOrbitTarget };
19
+ export { BASELAYER_WINDOW_MAX, BASELAYER_WINDOW_MIN, CAPTURE_FAILURE_WARNING, CameraBlitCapture, DEFAULT_BLIT_CONFIG, DEFAULT_CAPTURE_CONFIG, DEFAULT_CAPTURE_TRACKER_CONFIG, DepthSampler, ImageCaptureManager, MIN_VALID_IMAGE_BYTES, OccupancyGrid, SCENE_NODE, WEBXR_TO_NUE, XR_ERROR_MESSAGES, XR_ERROR_MESSAGE_UNKNOWN, acquireCameraTexture, applyChromiumProjectionLayerWorkaround, bresenham3d, capabilityMessage, computeCaptureSize, createCaptureFailureTracker, createDepthUnprojector, createEnableGpsArController, disposeReplayScene, endARSession, getAlignmentLerper, getArWorldGroup, getCamera, getCameraFollower, getCameraMode, getCurrentArPose, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getReplayState, getScene, getXrErrorMessage, initAR, initReplayScene, isFullySupported, needsBaseLayerPersistence, parseChromeVersion, registerFrameUpdate, registerXrFrameUpdate, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setTrackingCallbacks, setTrackingLostCallback, setTrackingStore, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture, toggleCameraMode, unprojectDepthPoint, updateOrbitTarget, wrapXRDepthInfo };
@@ -0,0 +1,2 @@
1
+ import { n as OccupancyGridOptions, t as OccupancyGrid } from "../occupancy-grid-BRNRaPyT.js";
2
+ export { OccupancyGrid, OccupancyGridOptions };
@@ -0,0 +1,164 @@
1
+ import { createDepthUnprojector } from "./depth-unprojection.js";
2
+ import { bresenham3d } from "./bresenham3d.js";
3
+ //#region ../src/ar/occupancy-grid.ts
4
+ var OccupancyGrid = class {
5
+ cellSizeM;
6
+ carveStopCells;
7
+ cells = /* @__PURE__ */ new Map();
8
+ constructor(options) {
9
+ const cellSizeM = options?.cellSizeM ?? .15;
10
+ const carveStopCells = options?.carveStopCells ?? 2;
11
+ if (!Number.isFinite(cellSizeM) || cellSizeM <= 0) throw new RangeError(`cellSizeM must be a positive number, got ${cellSizeM}`);
12
+ if (!Number.isSafeInteger(carveStopCells) || carveStopCells < 0) throw new RangeError(`carveStopCells must be a non-negative integer, got ${carveStopCells}`);
13
+ this.cellSizeM = cellSizeM;
14
+ this.carveStopCells = carveStopCells;
15
+ }
16
+ /** Number of occupied cells. */
17
+ get size() {
18
+ return this.cells.size;
19
+ }
20
+ /**
21
+ * Fold one depth sample into the grid: unproject each point, carve free
22
+ * space from the camera cell to the point cell, then count the point's
23
+ * cell as occupied. Points that cannot be unprojected (no
24
+ * projectionMatrix on old recordings, invalid depth/coords) are skipped.
25
+ *
26
+ * Carving and incrementing run as two separate passes over the sample's
27
+ * points: all rays are carved first, then every endpoint is incremented.
28
+ * A single interleaved pass would be order-dependent — a deeper point's
29
+ * carve could erase the endpoint a nearer point added earlier in the same
30
+ * sample. Splitting the passes makes the result deterministic and lets an
31
+ * endpoint observed within a sample survive other rays in that same
32
+ * sample. (Deeper-carves-nearer still applies ACROSS samples: a later
33
+ * sample's ray carves an earlier sample's endpoint as before.)
34
+ *
35
+ * @returns the number of points actually added.
36
+ */
37
+ addSample(sample) {
38
+ if (!isFiniteTriple(sample.cameraPos)) return 0;
39
+ const unprojector = createDepthUnprojector(sample.cameraPos, sample.cameraRot, sample.projectionMatrix);
40
+ if (!unprojector) return 0;
41
+ const cameraCell = this.cellForPosition(sample.cameraPos);
42
+ const endpoints = [];
43
+ for (const point of sample.points) {
44
+ const world = unprojector.unproject(point);
45
+ if (!world) continue;
46
+ const cell = this.cellForPosition(world);
47
+ if (!cellsEqual(cameraCell, cell)) this.carve(cameraCell, cell);
48
+ endpoints.push({
49
+ cell,
50
+ rgb: point.rgb
51
+ });
52
+ }
53
+ for (const endpoint of endpoints) this.increment(endpoint.cell, endpoint.rgb);
54
+ return endpoints.length;
55
+ }
56
+ /** Occupied cells observed at least `minObservations` times (default 1). */
57
+ getOccupiedCells(minObservations = 1) {
58
+ const result = [];
59
+ for (const record of this.cells.values()) if (record.count >= minObservations) result.push(record.cell);
60
+ return result;
61
+ }
62
+ /** Quantize a raw-WebXR position to its grid cell (round per axis). */
63
+ cellForPosition(pos) {
64
+ return [
65
+ Math.round(pos[0] / this.cellSizeM) + 0,
66
+ Math.round(pos[1] / this.cellSizeM) + 0,
67
+ Math.round(pos[2] / this.cellSizeM) + 0
68
+ ];
69
+ }
70
+ /** Center of a cell in raw WebXR space (round-consistent: cell · cellSizeM). */
71
+ getCellCenter(cell) {
72
+ return [
73
+ cell[0] * this.cellSizeM,
74
+ cell[1] * this.cellSizeM,
75
+ cell[2] * this.cellSizeM
76
+ ];
77
+ }
78
+ /**
79
+ * Running-average color of the cell's colored observations (Iter 8), or
80
+ * null when the cell is unknown or was only ever observed without color
81
+ * (rgb option off / pre-Iter-8 recordings) — consumers fall back to
82
+ * height-based coloring. Channels are rounded and clamped to 0–255.
83
+ */
84
+ getCellColor(cell) {
85
+ const record = this.cells.get(cellKey(cell));
86
+ if (!record || record.colorCount === 0) return null;
87
+ const average = (sum) => Math.min(255, Math.max(0, Math.round(sum / record.colorCount)));
88
+ return [
89
+ average(record.colorSum[0]),
90
+ average(record.colorSum[1]),
91
+ average(record.colorSum[2])
92
+ ];
93
+ }
94
+ /**
95
+ * Walk the grid from `startPos` to `endPos` and return the center of the
96
+ * first cell occupied at least `minObservations` times, or null.
97
+ * Port of Unity's `TryRaycast` (hook for cursor/floor-detection parity).
98
+ */
99
+ raycast(startPos, endPos, minObservations = 1) {
100
+ if (!isFiniteTriple(startPos) || !isFiniteTriple(endPos)) return null;
101
+ let hit = null;
102
+ bresenham3d(this.cellForPosition(startPos), this.cellForPosition(endPos), (cell) => {
103
+ const record = this.cells.get(cellKey(cell));
104
+ if (record && record.count >= minObservations) {
105
+ hit = cell;
106
+ return false;
107
+ }
108
+ return true;
109
+ });
110
+ return hit ? this.getCellCenter(hit) : null;
111
+ }
112
+ /** Remove all occupied cells (e.g. on store swap / new session). */
113
+ clear() {
114
+ this.cells.clear();
115
+ }
116
+ /**
117
+ * Delete occupied cells along the camera→point ray (the space was seen
118
+ * through, so it must be free), stopping `carveStopCells` dominant-axis
119
+ * steps before the endpoint. The endpoint cell itself is additionally
120
+ * protected so a current observation is never erased (relevant for
121
+ * carveStopCells = 0 and for the unconditional start-cell visit).
122
+ */
123
+ carve(cameraCell, pointCell) {
124
+ bresenham3d(cameraCell, pointCell, (cell) => {
125
+ if (!cellsEqual(cell, pointCell)) this.cells.delete(cellKey(cell));
126
+ return true;
127
+ }, this.carveStopCells);
128
+ }
129
+ increment(cell, rgb) {
130
+ const key = cellKey(cell);
131
+ let record = this.cells.get(key);
132
+ if (!record) {
133
+ record = {
134
+ cell,
135
+ count: 0,
136
+ colorCount: 0,
137
+ colorSum: [
138
+ 0,
139
+ 0,
140
+ 0
141
+ ]
142
+ };
143
+ this.cells.set(key, record);
144
+ }
145
+ record.count++;
146
+ if (rgb && isFiniteTriple(rgb)) {
147
+ record.colorCount++;
148
+ record.colorSum[0] += rgb[0];
149
+ record.colorSum[1] += rgb[1];
150
+ record.colorSum[2] += rgb[2];
151
+ }
152
+ }
153
+ };
154
+ function cellKey(cell) {
155
+ return `${cell[0]},${cell[1]},${cell[2]}`;
156
+ }
157
+ function cellsEqual(a, b) {
158
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
159
+ }
160
+ function isFiniteTriple(v) {
161
+ return Number.isFinite(v[0]) && Number.isFinite(v[1]) && Number.isFinite(v[2]);
162
+ }
163
+ //#endregion
164
+ export { OccupancyGrid };