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
@@ -0,0 +1,70 @@
1
+ //#region ../src/ar/xr-frame-loop.d.ts
2
+ /**
3
+ * Per-frame **XR-access** callback registry — the safe seam that lets app
4
+ * code run standard WebXR work (hit-test, light estimation, depth, the WebXR
5
+ * Anchors API, …) without the framework wrapping each capability.
6
+ *
7
+ * The framework owns the single `renderer.setAnimationLoop(onXRFrame)` hook,
8
+ * and the live `XRFrame` never leaves `onXRFrame`. The plain `FrameUpdate`
9
+ * registry (`frame-loop.ts`) only passes `(dt, elapsed)`, which is enough for
10
+ * pose-free ticks but cannot drive hit-test. This registry additionally hands
11
+ * the callback the live `frame`, `referenceSpace`, and `session`.
12
+ *
13
+ * SAFETY CONTRACT (non-negotiable): `frame` / `referenceSpace` / `session` are
14
+ * valid **only synchronously inside the callback**. The `XRFrame` is
15
+ * use-after-frame-unsafe — storing it and reading it on a later tick throws or
16
+ * crashes. Passing them as arguments (never a stashable `getXrFrame()` getter)
17
+ * makes correct use the easy path and stashing the awkward one. Do NOT retain
18
+ * `ctx` or its fields beyond the callback's synchronous execution.
19
+ *
20
+ * See `2026-06-03-threejs-arbutton-minimal-ar-example-user-feedback.md`
21
+ * §6.2/§6.3 (option H-A2) for the design rationale.
22
+ */
23
+ /**
24
+ * Live, frame-scoped WebXR context. Valid only synchronously inside the
25
+ * `XrFrameUpdate` callback it is passed to.
26
+ */
27
+ interface XrFrameContext {
28
+ /** The live `XRFrame` for this animation-frame. Use-after-frame-unsafe. */
29
+ readonly frame: XRFrame;
30
+ /** The session's reference space (e.g. for `frame.getPose` / hit-test results). */
31
+ readonly referenceSpace: XRReferenceSpace;
32
+ /** The active `XRSession` (e.g. to call `requestHitTestSource` once). */
33
+ readonly session: XRSession;
34
+ /** Seconds since the previous frame (0 on the first frame after a reset). */
35
+ readonly dt: number;
36
+ /** Seconds since the session started. */
37
+ readonly elapsed: number;
38
+ }
39
+ /** A per-frame callback that needs live XR access. See the safety contract. */
40
+ type XrFrameUpdate = (ctx: XrFrameContext) => void;
41
+ /**
42
+ * Register a per-frame XR-access callback. Returns an unregister function.
43
+ *
44
+ * Registration is idempotent — registering the same `fn` twice is a no-op
45
+ * (it remains a single entry in the underlying `Set`).
46
+ */
47
+ declare function registerXrFrameUpdate(fn: XrFrameUpdate): () => void;
48
+ /**
49
+ * Invoke all registered XR-access callbacks. Called by the WebXR session's
50
+ * `onXRFrame` once per frame, only when a live `frame` / `referenceSpace` /
51
+ * `session` are all available.
52
+ *
53
+ * The set is snapshotted before iterating so that
54
+ * `registerXrFrameUpdate` / unregister calls made by a handler during the
55
+ * same frame are deferred to the next tick — mirroring `runFrameUpdates`.
56
+ *
57
+ * Each callback is invoked in its own `try/catch`: this registry is the public
58
+ * app seam, so a bug in one app-registered callback (which throws every frame)
59
+ * must not abort the remaining callbacks nor propagate up through `onXRFrame`
60
+ * and kill the scene render for the whole session. Failures are logged and the
61
+ * loop continues — mirroring `runFrameUpdates`.
62
+ */
63
+ declare function runXrFrameUpdates(ctx: XrFrameContext): void;
64
+ /**
65
+ * Clear all registrations. Called from `resetWebXRState()` so a fresh
66
+ * session starts with an empty registry.
67
+ */
68
+ declare function clearXrFrameUpdates(): void;
69
+ //#endregion
70
+ export { runXrFrameUpdates as a, registerXrFrameUpdate as i, XrFrameUpdate as n, clearXrFrameUpdates as r, XrFrameContext as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gps-plus-slam-app-framework",
3
- "version": "1.0.7",
3
+ "version": "1.2.0",
4
4
  "description": "Reusable AR+GPS app framework layer — WebXR session management, Three.js visualization, GPS sensors, storage abstractions, replay engine, and store wiring for gps-plus-slam-js.",
5
5
  "type": "module",
6
6
  "files": [
@@ -1,61 +0,0 @@
1
- //#region ../src/ar/chromium-camera-access-workaround.d.ts
2
- /**
3
- * Chromium WebXR camera-access tab-crash workaround.
4
- *
5
- * Background:
6
- * Requesting `camera-access` as an optional WebXR feature on recent Android
7
- * Chrome versions causes a fatal renderer-process crash (`CrRendererMain`)
8
- * 1–2 seconds after entering AR. The crash reproduces in the upstream
9
- * three.js `webxr_ar_hittest.html` example with `optionalFeatures:
10
- * ['camera-access']` added — i.e. it is not specific to this app's session
11
- * setup. See:
12
- *
13
- * - GpsPlusSlamJs_Docs/docs/2026-04-22-camera-access-reproducer-plan.md
14
- * - GpsPlusSlamJs_Docs/docs/2026-04-23-webxr-camera-access-crash-bug-report.html
15
- * - https://github.com/mrdoob/three.js/issues/33404
16
- *
17
- * Workaround (from the upstream issue thread):
18
- * Three.js's `WebXRManager` chooses between the newer `XRProjectionLayer`
19
- * and the older `XRWebGLLayer` based on whether
20
- * `XRWebGLBinding.prototype.createProjectionLayer` is available. The crash
21
- * appears to involve the projection-layer + camera-access combination, so
22
- * removing the prototype method forces three.js to fall back to
23
- * `XRWebGLLayer`, which sidesteps the bug.
24
- *
25
- * `XRRenderState.prototype.layers` is the equivalent capability check used
26
- * by older three.js (r158-era) and is included for completeness.
27
- *
28
- * Caveats:
29
- * - This is a Chromium-specific hack and the upstream comment explicitly
30
- * warns it "might break webxr on other devices" — keep it opt-in.
31
- * - Must run BEFORE any WebXR session setup. Three.js reads these prototype
32
- * members lazily when the first session starts, so calling this at app
33
- * bootstrap (before `initAR()`) is sufficient.
34
- * - Idempotent: safe to call repeatedly. Safe on environments where the
35
- * prototypes do not exist (e.g. desktop browsers, jsdom).
36
- */
37
- /**
38
- * Result of {@link applyChromiumProjectionLayerWorkaround}.
39
- * Useful for logging and tests.
40
- */
41
- interface ChromiumProjectionLayerWorkaroundResult {
42
- /** True if `XRWebGLBinding.prototype.createProjectionLayer` was deleted on this call. */
43
- deletedCreateProjectionLayer: boolean;
44
- /** True if `XRRenderState.prototype.layers` was deleted on this call. */
45
- deletedRenderStateLayers: boolean;
46
- }
47
- /**
48
- * Apply the Chromium camera-access tab-crash workaround.
49
- *
50
- * Removes `XRWebGLBinding.prototype.createProjectionLayer` (three.js r184)
51
- * and `XRRenderState.prototype.layers` (three.js r158) so three.js falls
52
- * back to `XRWebGLLayer`, which avoids the renderer-process crash that
53
- * happens when both projection layers and `camera-access` are enabled.
54
- *
55
- * Call once during bootstrap, before any `requestSession()` call.
56
- *
57
- * @returns which prototype members were actually deleted on this call.
58
- */
59
- declare function applyChromiumProjectionLayerWorkaround(): ChromiumProjectionLayerWorkaroundResult;
60
- //#endregion
61
- export { applyChromiumProjectionLayerWorkaround as n, ChromiumProjectionLayerWorkaroundResult as t };
@@ -1,108 +0,0 @@
1
- import { i as DepthSample, t as ARPose } from "./ar-types-Ck8LEq8k.js";
2
-
3
- //#region ../src/ar/depth-sampler.d.ts
4
- /**
5
- * Configuration for depth sampling behavior.
6
- */
7
- interface DepthSamplerConfig {
8
- /** Interval between samples in milliseconds. Default: 1000ms */
9
- intervalMs: number;
10
- /** Number of points per dimension (gridSize x gridSize). Default: 3 */
11
- gridSize: number;
12
- /** Time in ms to wait before declaring depth unavailable. Default: 5000ms */
13
- unavailabilityThresholdMs: number;
14
- }
15
- /**
16
- * Callbacks for depth sampler events.
17
- */
18
- interface DepthSamplerCallbacks {
19
- /** Called when a depth sample is captured */
20
- onSampleCaptured: (sample: DepthSample) => void;
21
- /** Returns the current AR pose, or null if not available */
22
- getCurrentPose: () => ARPose | null;
23
- /**
24
- * Called once when depth is determined to be unavailable.
25
- * Triggered after unavailabilityThresholdMs with no depth data.
26
- * Field Test Readiness Issue #8.
27
- */
28
- onDepthUnavailable?: () => void;
29
- }
30
- /**
31
- * WebXR depth info interface (subset of XRDepthInformation).
32
- */
33
- interface DepthInfo {
34
- width: number;
35
- height: number;
36
- getDepthInMeters: (x: number, y: number) => number;
37
- }
38
- /**
39
- * Samples sparse depth points from WebXR depth sensing.
40
- *
41
- * Usage:
42
- * ```ts
43
- * const sampler = new DepthSampler({
44
- * onSampleCaptured: (sample) => saveSample(sample),
45
- * getCurrentPose: () => arSession.getCurrentPose(),
46
- * });
47
- * sampler.start();
48
- * // In frame loop:
49
- * sampler.onFrame(timestamp, depthInfo);
50
- * ```
51
- */
52
- declare class DepthSampler {
53
- private readonly callbacks;
54
- private readonly config;
55
- private running;
56
- private sampleCount;
57
- private lastSampleTime;
58
- /** Timestamp when sampling started (for unavailability detection) */
59
- private startTime;
60
- /** Whether we've ever received valid depth data */
61
- private depthReceived;
62
- /** Whether we've already fired the unavailable callback */
63
- private unavailableCallbackFired;
64
- constructor(callbacks: DepthSamplerCallbacks, config?: Partial<DepthSamplerConfig>);
65
- /**
66
- * Start depth sampling.
67
- */
68
- start(): void;
69
- /**
70
- * Stop depth sampling.
71
- */
72
- stop(): void;
73
- /**
74
- * Check if sampler is currently running.
75
- */
76
- isRunning(): boolean;
77
- /**
78
- * Get the number of samples captured since start.
79
- */
80
- getSampleCount(): number;
81
- /**
82
- * Get the current configuration.
83
- */
84
- getConfig(): DepthSamplerConfig;
85
- /**
86
- * Called each frame with depth information.
87
- *
88
- * @param timestamp - Current frame timestamp in milliseconds
89
- * @param depthInfo - WebXR depth information, or null if unavailable
90
- */
91
- onFrame(timestamp: number, depthInfo: DepthInfo | null): void;
92
- /**
93
- * Sample a grid of depth points from the depth buffer.
94
- */
95
- private sampleGrid;
96
- /**
97
- * Check if depth has been unavailable for longer than the threshold.
98
- * If so, fire the onDepthUnavailable callback (once).
99
- */
100
- private checkDepthUnavailability;
101
- /**
102
- * Check if depth data has ever been received.
103
- * Useful for testing and status display.
104
- */
105
- hasReceivedDepth(): boolean;
106
- }
107
- //#endregion
108
- export { DepthSamplerConfig as i, DepthSampler as n, DepthSamplerCallbacks as r, DepthInfo as t };
@@ -1,257 +0,0 @@
1
- import { registerFrameUpdate } from "./ar/frame-loop.js";
2
- import { isObjectInCameraFrustum } from "./visualization/frustum-visibility.js";
3
- import { calcRelativeCoordsInMeters } from "./core/index.js";
4
- import * as THREE from "three";
5
- //#region ../src/visualization/frame-conversions.ts
6
- /**
7
- * `frame-conversions` — small, pure coordinate-frame helpers for the AR scene
8
- * graph. Currently a single function, `nueToArLocal`, that converts a
9
- * GPS-world NUE point into the AR-odometry local frame of `arWorldGroup`.
10
- *
11
- * Background: the scene root is GPS-world NUE; `arWorldGroup.matrix` IS the
12
- * alignment matrix, which maps **AR-odometry NUE → GPS-world NUE**. So a
13
- * direct child of `arWorldGroup` whose WORLD position must equal a GPS-world
14
- * point `nue` has to store `alignment⁻¹ · nue` as its LOCAL position —
15
- * writing raw `nue` double-applies the alignment. Getting this wrong was the
16
- * alignment-frame bug
17
- * (gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-05-31-gps-anchor-alignment-frame-bug.md);
18
- * this helper centralises the conversion behind one tested, well-named seam.
19
- *
20
- * Design (see the plan
21
- * gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-05-31-nue-to-ar-local-helper-plan.md):
22
- * - **Takes the explicit alignment matrix array**, NOT `Object3D.worldToLocal`.
23
- * `worldToLocal` would read the live, mid-lerp `matrixWorld` and break
24
- * replay determinism; the explicit target matrix keeps the result a pure
25
- * function of its inputs. Full rationale in the review doc
26
- * (gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-05-31-worldtolocal-frame-helper-review.md).
27
- * - **Positions only.** No pose/quaternion variant exists until a caller
28
- * needs one (YAGNI). No caller currently sets a GPS-world rotation.
29
- */
30
- const scratchMatrix = new THREE.Matrix4();
31
- /**
32
- * Convert a GPS-world NUE point into the AR-local position to write into a
33
- * direct child of `arWorldGroup`, given the current alignment matrix.
34
- *
35
- * Computes `alignment⁻¹ · nue`. When the result is later composed back by the
36
- * group's matrix (the same `alignment`), the child's WORLD position equals
37
- * `nue` exactly.
38
- *
39
- * @param alignment - The alignment matrix as a 16-element **column-major**
40
- * array (AR-odometry NUE → GPS-world NUE), e.g. from `getAlignmentMatrix()`
41
- * or `THREE.Matrix4.toArray()`. Not mutated.
42
- * @param nue - The GPS-world NUE point `[north, up, east]` in metres. Not
43
- * mutated.
44
- * @param out - Optional target vector to write into and return (pass a reused
45
- * scratch to avoid allocation on hot paths). Defaults to a fresh
46
- * `THREE.Vector3`.
47
- * @returns `out`, set to the AR-local position.
48
- */
49
- function nueToArLocal(alignment, nue, out = new THREE.Vector3()) {
50
- scratchMatrix.fromArray(alignment).invert();
51
- return out.set(nue[0], nue[1], nue[2]).applyMatrix4(scratchMatrix);
52
- }
53
- //#endregion
54
- //#region ../src/visualization/gps-anchor.ts
55
- /**
56
- * `createGpsAnchor` — GPS-anchored placement of a single `THREE.Object3D`.
57
- *
58
- * See the colocated sidecar (`gps-anchor.ts.md`) and the port plan at
59
- * `gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-05-13-gps-anchor-port-plan.md`
60
- * for the full design, state machine, and test matrix.
61
- *
62
- * This file implements sub-steps 2 (bootstrap phase), 3 (steady-state
63
- * `'snap-every-tick'` + distance-scaled threshold gate), and 4
64
- * (`'snap-when-offscreen'` mode gate + alignment-matrix large-jump
65
- * bypass). Floor-Y correction is sub-step 6 and remains deferred.
66
- */
67
- /**
68
- * Module-level registry of objects currently owned by a `GpsAnchor`.
69
- * Used to detect nested anchors (parent + child both anchored) which
70
- * we explicitly forbid; mirrors the C# invariant.
71
- */
72
- const anchoredObjects = /* @__PURE__ */ new WeakSet();
73
- function isObjectInAnchoredChain(object) {
74
- let cursor = object;
75
- while (cursor) {
76
- if (anchoredObjects.has(cursor)) return true;
77
- cursor = cursor.parent;
78
- }
79
- return false;
80
- }
81
- function median(values) {
82
- const sorted = [...values].sort((a, b) => a - b);
83
- const mid = Math.floor(sorted.length / 2);
84
- if (sorted.length % 2 === 0) return (sorted[mid - 1] + sorted[mid]) / 2;
85
- return sorted[mid];
86
- }
87
- function medianPoint(samples) {
88
- const lat = median(samples.map((s) => s.lat));
89
- const lon = median(samples.map((s) => s.lon));
90
- const alts = samples.map((s) => "altitude" in s ? s.altitude : void 0).filter((a) => typeof a === "number");
91
- if (alts.length > 0) return {
92
- lat,
93
- lon,
94
- altitude: median(alts)
95
- };
96
- return {
97
- lat,
98
- lon
99
- };
100
- }
101
- function createGpsAnchor(options) {
102
- if (isObjectInAnchoredChain(options.object3D)) throw new Error("createGpsAnchor: nested GpsAnchors are not supported — the supplied object3D is already inside an anchored parent chain.");
103
- anchoredObjects.add(options.object3D);
104
- const sampleCount = options.secondsToAccumulateGpsPose ?? 7;
105
- if (sampleCount < 1) {
106
- anchoredObjects.delete(options.object3D);
107
- throw new Error("createGpsAnchor: secondsToAccumulateGpsPose must be >= 1 — use skipBootstrap:true to bypass the accumulation phase.");
108
- }
109
- const settlingSeconds = options.settlingSeconds ?? 0;
110
- const distanceThreshold = options.distanceThreshold ?? 2;
111
- options.angleThresholdInDegrees;
112
- const mode = options.mode ?? "snap-when-offscreen";
113
- const LARGE_JUMP_TRANSLATION_M = 4;
114
- const LARGE_JUMP_Y_M = 20;
115
- const LARGE_JUMP_ROTATION_DEG = 2;
116
- const scratchTarget = new THREE.Vector3();
117
- const scratchCamWorld = new THREE.Vector3();
118
- const scratchObjWorld = new THREE.Vector3();
119
- const scratchPrevMatrix = new THREE.Matrix4();
120
- const scratchCurrMatrix = new THREE.Matrix4();
121
- const scratchPrevTrans = new THREE.Vector3();
122
- const scratchCurrTrans = new THREE.Vector3();
123
- const scratchPrevQuat = new THREE.Quaternion();
124
- const scratchCurrQuat = new THREE.Quaternion();
125
- let phase = options.skipBootstrap === true ? "anchored" : "bootstrap";
126
- let isFullyAnchored = phase === "anchored";
127
- let gpsPoint = options.gpsPoint;
128
- let phaseEnteredAtElapsed = null;
129
- let lastSampleAtElapsed = null;
130
- const samples = [];
131
- /**
132
- * Snapshot of the previous tick's alignment matrix. `null` until the
133
- * first steady-state tick in which `getAlignmentMatrix()` returned a
134
- * non-null value. Used by the large-jump bypass to compare against
135
- * the current tick's matrix.
136
- */
137
- let prevAlignmentMatrix = null;
138
- /**
139
- * Returns true iff the alignment matrix has jumped by more than the
140
- * configured large-jump thresholds between `prev` and `curr`. A
141
- * `null` `prev` (first tick) is treated as "no jump".
142
- */
143
- const detectLargeAlignmentJump = (prev, curr) => {
144
- if (prev === null || curr === null) return false;
145
- scratchPrevMatrix.fromArray(prev);
146
- scratchCurrMatrix.fromArray(curr);
147
- scratchPrevTrans.setFromMatrixPosition(scratchPrevMatrix);
148
- scratchCurrTrans.setFromMatrixPosition(scratchCurrMatrix);
149
- const dTrans = scratchPrevTrans.distanceTo(scratchCurrTrans);
150
- const dY = Math.abs(scratchCurrTrans.y - scratchPrevTrans.y);
151
- scratchPrevQuat.setFromRotationMatrix(scratchPrevMatrix);
152
- scratchCurrQuat.setFromRotationMatrix(scratchCurrMatrix);
153
- const dRotDeg = scratchPrevQuat.angleTo(scratchCurrQuat) * 180 / Math.PI;
154
- return dTrans > LARGE_JUMP_TRANSLATION_M || dY > LARGE_JUMP_Y_M || dRotDeg > LARGE_JUMP_ROTATION_DEG;
155
- };
156
- const enterBootstrap = () => {
157
- phase = "bootstrap";
158
- isFullyAnchored = false;
159
- phaseEnteredAtElapsed = null;
160
- lastSampleAtElapsed = null;
161
- samples.length = 0;
162
- prevAlignmentMatrix = null;
163
- };
164
- const commitMedian = () => {
165
- gpsPoint = medianPoint(samples);
166
- phase = "anchored";
167
- isFullyAnchored = true;
168
- samples.length = 0;
169
- };
170
- /**
171
- * Steady-state: compute the GPS-world NUE target from the stored
172
- * `gpsPoint` and the current `zeroRef`, map it into `arWorldGroup`'s
173
- * AR-local frame via the inverse alignment matrix, and commit it to
174
- * `object3D.position` iff the (AR-local) position delta exceeds the
175
- * distance-scaled threshold AND the mode gate (with optional large-jump
176
- * bypass) allows it.
177
- *
178
- * Frame note: `arWorldGroup.matrix` IS the alignment matrix, which maps
179
- * **AR-odometry NUE → GPS-world NUE**. `object3D.position` is a *local*
180
- * transform in that AR-odometry frame. To make the object's *world*
181
- * position equal the GPS-world `nue`, the local target must be
182
- * `alignment⁻¹ · nue` — writing raw `nue` would double-apply the
183
- * alignment (world = `alignment · nue`). See
184
- * `gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-05-31-gps-anchor-alignment-frame-bug.md`.
185
- *
186
- * Returns silently on missing inputs (no `zeroRef`, or no alignment
187
- * matrix yet — an AR-local object cannot be placed without knowing the
188
- * AR↔NUE transform).
189
- */
190
- const maybeCommitSteadyState = () => {
191
- const zero = options.getGpsZeroRef();
192
- if (zero === null || zero === void 0) return;
193
- const currentAlignment = options.getAlignmentMatrix();
194
- const largeJump = detectLargeAlignmentJump(prevAlignmentMatrix, currentAlignment);
195
- prevAlignmentMatrix = currentAlignment;
196
- if (currentAlignment === null || currentAlignment === void 0) return;
197
- const targetAlt = "altitude" in gpsPoint && typeof gpsPoint.altitude === "number" ? gpsPoint.altitude : 0;
198
- const nue = calcRelativeCoordsInMeters(zero, gpsPoint, targetAlt, 0);
199
- nueToArLocal(currentAlignment, [
200
- nue[0],
201
- nue[1],
202
- nue[2]
203
- ], scratchTarget);
204
- options.camera.getWorldPosition(scratchCamWorld);
205
- options.object3D.getWorldPosition(scratchObjWorld);
206
- const scale = 1 + 10 * scratchCamWorld.distanceTo(scratchObjWorld) / 100;
207
- if (options.object3D.position.distanceTo(scratchTarget) < distanceThreshold * scale) return;
208
- if (mode === "snap-when-offscreen" && !largeJump) {
209
- if (isObjectInCameraFrustum(options.camera, options.object3D)) return;
210
- }
211
- options.object3D.position.copy(scratchTarget);
212
- };
213
- const tick = (_dt, elapsed) => {
214
- if (phase === "anchored") {
215
- maybeCommitSteadyState();
216
- return;
217
- }
218
- if (phaseEnteredAtElapsed === null) {
219
- phaseEnteredAtElapsed = elapsed;
220
- lastSampleAtElapsed = elapsed - 1;
221
- }
222
- if (elapsed - phaseEnteredAtElapsed < settlingSeconds) return;
223
- if (lastSampleAtElapsed !== null && elapsed - lastSampleAtElapsed < 1) return;
224
- const sample = options.getCurrentGpsPoint();
225
- if (sample === null || sample === void 0) return;
226
- samples.push(sample);
227
- lastSampleAtElapsed = elapsed;
228
- if (samples.length >= sampleCount) commitMedian();
229
- };
230
- const unregister = registerFrameUpdate(tick);
231
- return {
232
- get phase() {
233
- return phase;
234
- },
235
- get isFullyAnchored() {
236
- return isFullyAnchored;
237
- },
238
- get gpsPoint() {
239
- return gpsPoint;
240
- },
241
- markMovedExternally() {
242
- enterBootstrap();
243
- },
244
- setGpsPoint(point) {
245
- gpsPoint = point;
246
- },
247
- dispose() {
248
- unregister();
249
- anchoredObjects.delete(options.object3D);
250
- },
251
- __tickForTests(dt, elapsed) {
252
- tick(dt, elapsed);
253
- }
254
- };
255
- }
256
- //#endregion
257
- export { nueToArLocal as n, createGpsAnchor as t };
@@ -1,24 +0,0 @@
1
- import * as THREE from "three";
2
-
3
- //#region ../src/visualization/frame-conversions.d.ts
4
- /**
5
- * Convert a GPS-world NUE point into the AR-local position to write into a
6
- * direct child of `arWorldGroup`, given the current alignment matrix.
7
- *
8
- * Computes `alignment⁻¹ · nue`. When the result is later composed back by the
9
- * group's matrix (the same `alignment`), the child's WORLD position equals
10
- * `nue` exactly.
11
- *
12
- * @param alignment - The alignment matrix as a 16-element **column-major**
13
- * array (AR-odometry NUE → GPS-world NUE), e.g. from `getAlignmentMatrix()`
14
- * or `THREE.Matrix4.toArray()`. Not mutated.
15
- * @param nue - The GPS-world NUE point `[north, up, east]` in metres. Not
16
- * mutated.
17
- * @param out - Optional target vector to write into and return (pass a reused
18
- * scratch to avoid allocation on hot paths). Defaults to a fresh
19
- * `THREE.Vector3`.
20
- * @returns `out`, set to the AR-local position.
21
- */
22
- declare function nueToArLocal(alignment: readonly number[], nue: readonly [number, number, number], out?: THREE.Vector3): THREE.Vector3;
23
- //#endregion
24
- export { nueToArLocal as t };
File without changes