gps-plus-slam-app-framework 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 (200) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +222 -0
  3. package/dist/alignment-lerper-D9BeDwZX.d.ts +29 -0
  4. package/dist/app-selectors-DyMzxCEC.d.ts +140 -0
  5. package/dist/ar/camera-blit-capture.d.ts +2 -0
  6. package/dist/ar/camera-blit-capture.js +253 -0
  7. package/dist/ar/capture-failure-tracker.d.ts +2 -0
  8. package/dist/ar/capture-failure-tracker.js +45 -0
  9. package/dist/ar/chromium-camera-access-workaround.d.ts +2 -0
  10. package/dist/ar/chromium-camera-access-workaround.js +32 -0
  11. package/dist/ar/depth-sampler.d.ts +3 -0
  12. package/dist/ar/depth-sampler.js +147 -0
  13. package/dist/ar/image-capture.d.ts +2 -0
  14. package/dist/ar/image-capture.js +152 -0
  15. package/dist/ar/index.d.ts +12 -0
  16. package/dist/ar/index.js +12 -0
  17. package/dist/ar/replay-scene.d.ts +2 -0
  18. package/dist/ar/replay-scene.js +325 -0
  19. package/dist/ar/scene-node-names.d.ts +2 -0
  20. package/dist/ar/scene-node-names.js +16 -0
  21. package/dist/ar/tracking-state.d.ts +2 -0
  22. package/dist/ar/tracking-state.js +164 -0
  23. package/dist/ar/webxr-session.d.ts +4 -0
  24. package/dist/ar/webxr-session.js +890 -0
  25. package/dist/ar/xr-camera-texture.d.ts +2 -0
  26. package/dist/ar/xr-camera-texture.js +39 -0
  27. package/dist/ar/xr-error-handler.d.ts +2 -0
  28. package/dist/ar/xr-error-handler.js +37 -0
  29. package/dist/ar-types-B-ORgk6Z.d.ts +82 -0
  30. package/dist/camera-blit-capture-DHuyrrxr.d.ts +108 -0
  31. package/dist/camera-follower-BMqPddxx.d.ts +29 -0
  32. package/dist/capture-failure-tracker-2Q6oI2uQ.d.ts +52 -0
  33. package/dist/chromium-camera-access-workaround-CU5zSKNr.d.ts +61 -0
  34. package/dist/community-license-key-ChZLCJyO.d.ts +21 -0
  35. package/dist/concurrency-p6gB30fT.d.ts +24 -0
  36. package/dist/css3d-renderer-manager-rgLMVAvd.d.ts +23 -0
  37. package/dist/depth-sampler-BNCtnEoP.d.ts +108 -0
  38. package/dist/failure-tracker-1sQ_Cgyw.d.ts +49 -0
  39. package/dist/file-system-BsQgYCU3.d.ts +128 -0
  40. package/dist/file-system-utils-5cWO8Xyv.d.ts +24 -0
  41. package/dist/format-file-size-onGq0k1Q.d.ts +24 -0
  42. package/dist/fused-path-DtqCSqJh.d.ts +25 -0
  43. package/dist/geo-types-Xx-LVcnw.d.ts +29 -0
  44. package/dist/gps-Ddzr2KlX.d.ts +54 -0
  45. package/dist/gps-compass-cubes-C-6JEnpR.d.ts +22 -0
  46. package/dist/gps-error-handler-VPuNOuf8.d.ts +40 -0
  47. package/dist/gps-event-markers-DpEy2qRd.d.ts +67 -0
  48. package/dist/h3-ref-point-CFhayNSZ.d.ts +59 -0
  49. package/dist/image-capture-2pKjYUS6.d.ts +130 -0
  50. package/dist/index.d.ts +56 -0
  51. package/dist/index.js +61 -0
  52. package/dist/leaflet-map-overlay-CvfQXTye.d.ts +114 -0
  53. package/dist/lerp-utils-87NWjUOD.d.ts +20 -0
  54. package/dist/licensing/community-license-key.d.ts +2 -0
  55. package/dist/licensing/community-license-key.js +21 -0
  56. package/dist/licensing/index.d.ts +2 -0
  57. package/dist/licensing/index.js +2 -0
  58. package/dist/list-formatter-DKQQVLcl.d.ts +14 -0
  59. package/dist/logger-B81iwxx0.d.ts +74 -0
  60. package/dist/map-overlay-DuiZuCTx.d.ts +153 -0
  61. package/dist/null-storage-backend-CsWvQwN8.d.ts +11 -0
  62. package/dist/opfs-storage-ChOzF_Wp.d.ts +181 -0
  63. package/dist/opfs-storage-backend-CVcmfJHN.d.ts +11 -0
  64. package/dist/permission-checker-jO2ziCWC.d.ts +146 -0
  65. package/dist/persistence-middleware-DrptY02i.d.ts +31 -0
  66. package/dist/recorder-slice-fcwAXrsh.d.ts +35 -0
  67. package/dist/recording-coordinator-DzflP07_.d.ts +81 -0
  68. package/dist/recording-options-BYUFX8iU.d.ts +166 -0
  69. package/dist/recording-replayer-oOzPITrd.d.ts +32 -0
  70. package/dist/ref-point-importer-B_wn5ABk.d.ts +62 -0
  71. package/dist/ref-point-loader-Pt1smgHS.d.ts +117 -0
  72. package/dist/ref-points/h3-ref-point.d.ts +2 -0
  73. package/dist/ref-points/h3-ref-point.js +74 -0
  74. package/dist/ref-points/index.d.ts +2 -0
  75. package/dist/ref-points/index.js +2 -0
  76. package/dist/ref-points-slice-tC-Wd6XJ.d.ts +48 -0
  77. package/dist/reference-points-CFWy8_wj.d.ts +68 -0
  78. package/dist/replay-engine-DuX316ae.d.ts +103 -0
  79. package/dist/replay-scene-DtUm6bsr.d.ts +109 -0
  80. package/dist/routing-slice-BlHTM8eh.d.ts +23 -0
  81. package/dist/scene-node-names-DPsaE2z3.d.ts +14 -0
  82. package/dist/sensors/gps-error-handler.d.ts +2 -0
  83. package/dist/sensors/gps-error-handler.js +53 -0
  84. package/dist/sensors/gps.d.ts +2 -0
  85. package/dist/sensors/gps.js +93 -0
  86. package/dist/sensors/index.d.ts +4 -0
  87. package/dist/sensors/index.js +4 -0
  88. package/dist/sensors/permission-checker.d.ts +2 -0
  89. package/dist/sensors/permission-checker.js +397 -0
  90. package/dist/state/app-selectors.d.ts +2 -0
  91. package/dist/state/app-selectors.js +35 -0
  92. package/dist/state/index.d.ts +17 -0
  93. package/dist/state/index.js +13 -0
  94. package/dist/state/persistence-middleware.d.ts +2 -0
  95. package/dist/state/persistence-middleware.js +71 -0
  96. package/dist/state/recorder-slice.d.ts +2 -0
  97. package/dist/state/recorder-slice.js +47 -0
  98. package/dist/state/recording-coordinator.d.ts +2 -0
  99. package/dist/state/recording-coordinator.js +150 -0
  100. package/dist/state/recording-options.d.ts +2 -0
  101. package/dist/state/recording-options.js +192 -0
  102. package/dist/state/recording-replayer.d.ts +2 -0
  103. package/dist/state/recording-replayer.js +39 -0
  104. package/dist/state/ref-points-slice.d.ts +2 -0
  105. package/dist/state/ref-points-slice.js +44 -0
  106. package/dist/state/replay-engine.d.ts +2 -0
  107. package/dist/state/replay-engine.js +230 -0
  108. package/dist/state/routing-slice.d.ts +2 -0
  109. package/dist/state/routing-slice.js +13 -0
  110. package/dist/state/store-subscribers.d.ts +3 -0
  111. package/dist/state/store-subscribers.js +97 -0
  112. package/dist/state/store.d.ts +9 -0
  113. package/dist/state/store.js +71 -0
  114. package/dist/state/subscribe-to-selector.d.ts +2 -0
  115. package/dist/state/subscribe-to-selector.js +26 -0
  116. package/dist/storage/file-system-utils.d.ts +2 -0
  117. package/dist/storage/file-system-utils.js +31 -0
  118. package/dist/storage/file-system.d.ts +2 -0
  119. package/dist/storage/file-system.js +229 -0
  120. package/dist/storage/index.d.ts +11 -0
  121. package/dist/storage/index.js +10 -0
  122. package/dist/storage/null-storage-backend.d.ts +2 -0
  123. package/dist/storage/null-storage-backend.js +8 -0
  124. package/dist/storage/opfs-storage-backend.d.ts +2 -0
  125. package/dist/storage/opfs-storage-backend.js +15 -0
  126. package/dist/storage/opfs-storage.d.ts +2 -0
  127. package/dist/storage/opfs-storage.js +242 -0
  128. package/dist/storage/ref-point-importer.d.ts +2 -0
  129. package/dist/storage/ref-point-importer.js +163 -0
  130. package/dist/storage/ref-point-loader.d.ts +2 -0
  131. package/dist/storage/ref-point-loader.js +248 -0
  132. package/dist/storage/ref-point-recovery.d.ts +28 -0
  133. package/dist/storage/ref-point-recovery.js +159 -0
  134. package/dist/storage/storage-backend.d.ts +2 -0
  135. package/dist/storage/storage-backend.js +1 -0
  136. package/dist/storage/zip-export.d.ts +2 -0
  137. package/dist/storage/zip-export.js +214 -0
  138. package/dist/storage/zip-reader.d.ts +2 -0
  139. package/dist/storage/zip-reader.js +190 -0
  140. package/dist/storage-backend-BcEItnFr.d.ts +20 -0
  141. package/dist/store-C7PKOj8S.d.ts +96 -0
  142. package/dist/store-subscribers-CuoIzB5T.d.ts +77 -0
  143. package/dist/subscribe-to-selector-UTZ0KkAc.d.ts +26 -0
  144. package/dist/test-utils/browser-mocks.d.ts +294 -0
  145. package/dist/test-utils/browser-mocks.js +568 -0
  146. package/dist/test-utils/zip-round-trip-helpers.d.ts +60 -0
  147. package/dist/test-utils/zip-round-trip-helpers.js +207 -0
  148. package/dist/three-dispose-CCBHjE_a.d.ts +30 -0
  149. package/dist/tracking-state-B9QfuZCg.d.ts +101 -0
  150. package/dist/types/ar-types.d.ts +2 -0
  151. package/dist/types/ar-types.js +1 -0
  152. package/dist/types/geo-types.d.ts +2 -0
  153. package/dist/types/geo-types.js +1 -0
  154. package/dist/types/index.d.ts +3 -0
  155. package/dist/types/index.js +1 -0
  156. package/dist/utils/concurrency.d.ts +2 -0
  157. package/dist/utils/concurrency.js +40 -0
  158. package/dist/utils/failure-tracker.d.ts +2 -0
  159. package/dist/utils/failure-tracker.js +48 -0
  160. package/dist/utils/format-file-size.d.ts +2 -0
  161. package/dist/utils/format-file-size.js +41 -0
  162. package/dist/utils/fused-path.d.ts +2 -0
  163. package/dist/utils/fused-path.js +44 -0
  164. package/dist/utils/index.d.ts +7 -0
  165. package/dist/utils/index.js +7 -0
  166. package/dist/utils/list-formatter.d.ts +2 -0
  167. package/dist/utils/list-formatter.js +17 -0
  168. package/dist/utils/logger.d.ts +2 -0
  169. package/dist/utils/logger.js +226 -0
  170. package/dist/vis-colors-Qi2vlRGt.d.ts +63 -0
  171. package/dist/visualization/alignment-lerper.d.ts +2 -0
  172. package/dist/visualization/alignment-lerper.js +68 -0
  173. package/dist/visualization/camera-follower.d.ts +2 -0
  174. package/dist/visualization/camera-follower.js +51 -0
  175. package/dist/visualization/css3d-renderer-manager.d.ts +2 -0
  176. package/dist/visualization/css3d-renderer-manager.js +41 -0
  177. package/dist/visualization/gps-compass-cubes.d.ts +2 -0
  178. package/dist/visualization/gps-compass-cubes.js +129 -0
  179. package/dist/visualization/gps-event-markers.d.ts +2 -0
  180. package/dist/visualization/gps-event-markers.js +159 -0
  181. package/dist/visualization/index.d.ts +12 -0
  182. package/dist/visualization/index.js +12 -0
  183. package/dist/visualization/leaflet-map-overlay.d.ts +2 -0
  184. package/dist/visualization/leaflet-map-overlay.js +313 -0
  185. package/dist/visualization/lerp-utils.d.ts +2 -0
  186. package/dist/visualization/lerp-utils.js +22 -0
  187. package/dist/visualization/map-overlay.d.ts +2 -0
  188. package/dist/visualization/map-overlay.js +241 -0
  189. package/dist/visualization/reference-points.d.ts +2 -0
  190. package/dist/visualization/reference-points.js +160 -0
  191. package/dist/visualization/three-dispose.d.ts +2 -0
  192. package/dist/visualization/three-dispose.js +55 -0
  193. package/dist/visualization/vis-colors.d.ts +2 -0
  194. package/dist/visualization/vis-colors.js +74 -0
  195. package/dist/webxr-session-COlSrXdL.d.ts +316 -0
  196. package/dist/xr-camera-texture-BbukNE79.d.ts +53 -0
  197. package/dist/xr-error-handler-DTHfmvrl.d.ts +24 -0
  198. package/dist/zip-export-Rtpi75JJ.d.ts +87 -0
  199. package/dist/zip-reader-D7pkpX8K.d.ts +97 -0
  200. package/package.json +104 -0
@@ -0,0 +1,325 @@
1
+ import { createLogger } from "../utils/logger.js";
2
+ import { createCss3dRendererManager } from "../visualization/css3d-renderer-manager.js";
3
+ import { createSceneHierarchy, setArPose, setArWorldGroup, setCamera, setScene } from "./webxr-session.js";
4
+ import { createCameraFollower } from "../visualization/camera-follower.js";
5
+ import { createAlignmentLerper } from "../visualization/alignment-lerper.js";
6
+ import { createGpsCompassCubes } from "../visualization/gps-compass-cubes.js";
7
+ import * as THREE from "three";
8
+ import { OrbitControls } from "three/addons/controls/OrbitControls.js";
9
+ //#region ../src/ar/replay-scene.ts
10
+ /**
11
+ * Replay Scene Module
12
+ *
13
+ * Sets up a standard Three.js rendering environment for desktop replay mode
14
+ * (no WebXR). Uses createSceneHierarchy() for the scene graph, then
15
+ * reparents the camera to the scene root (Issue 5 fix) so orbit and FPS
16
+ * controls operate in a stable world-space frame, unaffected by the
17
+ * alignment matrix on arWorldGroup or odom pose updates on arpose.
18
+ *
19
+ * Registers the scene objects with webxr-session.ts module-global getters
20
+ * (Risk R1 fix) so existing visualizers work without modification.
21
+ *
22
+ * Camera modes:
23
+ * - orbit (default): OrbitControls — click-drag orbit + scroll zoom
24
+ * - fps: Drag-based mouse look + WASD — left-click-drag rotates camera,
25
+ * WASD moves, Space/Shift for vertical movement (Issue 6)
26
+ *
27
+ * @see docs/2026-02-19-replay-mode.md Issue 4, Issue 5, Risk R1, R5
28
+ * @see docs/2026-03-12-user-feedback.md Issue 5
29
+ */
30
+ const log = createLogger("ReplayScene");
31
+ let state = null;
32
+ /** WASD key tracking for FPS mode */
33
+ const keysPressed = {};
34
+ /** Movement speed in world-units per second (≈0.15 per frame × 60 fps). */
35
+ const FPS_MOVE_SPEED = 9;
36
+ /** Reusable scratch vectors — avoids per-frame allocations in updateFpsMovement. */
37
+ const _direction = new THREE.Vector3();
38
+ const _right = new THREE.Vector3();
39
+ const MOUSE_SENSITIVITY = .002;
40
+ const MAX_PITCH = Math.PI / 2 - .01;
41
+ let isDragging = false;
42
+ let lastPointerX = 0;
43
+ let lastPointerY = 0;
44
+ let fpsPitch = 0;
45
+ let fpsYaw = 0;
46
+ const _euler = new THREE.Euler(0, 0, 0, "YXZ");
47
+ let savedTabindex = { state: "none" };
48
+ function onKeyDown(e) {
49
+ keysPressed[e.code] = true;
50
+ }
51
+ function onKeyUp(e) {
52
+ keysPressed[e.code] = false;
53
+ }
54
+ function updateFpsMovement(dt) {
55
+ if (!state || state.cameraMode !== "fps") return;
56
+ const speed = FPS_MOVE_SPEED * dt;
57
+ const camera = state.camera;
58
+ camera.getWorldDirection(_direction);
59
+ _right.crossVectors(_direction, camera.up).normalize();
60
+ if (keysPressed["KeyW"]) camera.position.addScaledVector(_direction, speed);
61
+ if (keysPressed["KeyS"]) camera.position.addScaledVector(_direction, -speed);
62
+ if (keysPressed["KeyA"]) camera.position.addScaledVector(_right, -speed);
63
+ if (keysPressed["KeyD"]) camera.position.addScaledVector(_right, speed);
64
+ if (keysPressed["Space"]) camera.position.y += speed;
65
+ if (keysPressed["ShiftLeft"] || keysPressed["ShiftRight"]) camera.position.y -= speed;
66
+ }
67
+ /**
68
+ * Initialize the replay scene with a standard Three.js renderer.
69
+ *
70
+ * - Creates the scene hierarchy via createSceneHierarchy()
71
+ * - Reparents camera from arWorldGroup to scene root (Risk R5)
72
+ * - Creates a WebGLRenderer WITHOUT WebXR
73
+ * - Registers scene with module-global getters (Risk R1)
74
+ * - Inserts canvas into the provided container
75
+ * - Starts a requestAnimationFrame render loop
76
+ * - Sets up OrbitControls as default camera mode
77
+ *
78
+ * @param container - DOM element to insert the canvas into
79
+ * @returns Scene state object for downstream use
80
+ * @throws If already initialized (call disposeReplayScene first)
81
+ */
82
+ function initReplayScene(container) {
83
+ if (state) throw new Error("Replay scene already initialized. Call disposeReplayScene() first.");
84
+ const { scene, arWorldGroup, arpose, camera } = createSceneHierarchy();
85
+ scene.add(camera);
86
+ camera.position.set(0, 5, 10);
87
+ camera.lookAt(0, 0, 0);
88
+ const renderer = new THREE.WebGLRenderer({
89
+ antialias: true,
90
+ alpha: true
91
+ });
92
+ renderer.setPixelRatio(window.devicePixelRatio);
93
+ const width = container.clientWidth || window.innerWidth;
94
+ const height = container.clientHeight || window.innerHeight;
95
+ renderer.setSize(width, height);
96
+ renderer.xr.enabled = false;
97
+ container.appendChild(renderer.domElement);
98
+ const css3dManager = createCss3dRendererManager(container, width, height);
99
+ setScene(scene);
100
+ setArWorldGroup(arWorldGroup);
101
+ setArPose(arpose);
102
+ setCamera(camera);
103
+ const orbitControls = new OrbitControls(camera, renderer.domElement);
104
+ orbitControls.enableDamping = true;
105
+ orbitControls.target.set(0, 0, 0);
106
+ const alignmentLerper = createAlignmentLerper(arWorldGroup);
107
+ const cameraFollower = createCameraFollower(scene);
108
+ createGpsCompassCubes(cameraFollower.object3D);
109
+ state = {
110
+ scene,
111
+ arWorldGroup,
112
+ camera,
113
+ renderer,
114
+ orbitControls,
115
+ cameraMode: "orbit",
116
+ rAFId: startRenderLoop(renderer, scene, camera, orbitControls, css3dManager),
117
+ container,
118
+ cameraFollower,
119
+ alignmentLerper,
120
+ css3dManager
121
+ };
122
+ log.info("Replay scene initialized");
123
+ return {
124
+ scene,
125
+ arWorldGroup,
126
+ camera,
127
+ renderer
128
+ };
129
+ }
130
+ /**
131
+ * Dispose the replay scene, cleaning up all resources.
132
+ *
133
+ * - Cancels the rAF render loop
134
+ * - Disposes renderer (frees GPU resources)
135
+ * - Disposes camera controls
136
+ * - Removes canvas from DOM
137
+ * - Clears module-global scene references
138
+ *
139
+ * Safe to call multiple times (idempotent).
140
+ */
141
+ function disposeReplayScene() {
142
+ if (!state) return;
143
+ cancelAnimationFrame(state.rAFId);
144
+ state.alignmentLerper.dispose();
145
+ state.cameraFollower.dispose();
146
+ state.css3dManager.dispose();
147
+ state.orbitControls.dispose();
148
+ if (state.cameraMode === "fps") removeFpsListeners();
149
+ state.container.removeEventListener("keydown", onKeyDown);
150
+ state.container.removeEventListener("keyup", onKeyUp);
151
+ restoreTabindex();
152
+ if (state.renderer.domElement.parentElement) state.renderer.domElement.parentElement.removeChild(state.renderer.domElement);
153
+ state.renderer.dispose();
154
+ setScene(null);
155
+ setArWorldGroup(null);
156
+ setArPose(null);
157
+ setCamera(null);
158
+ state = null;
159
+ log.info("Replay scene disposed");
160
+ }
161
+ /**
162
+ * Get the current replay scene state, or null if not initialized.
163
+ */
164
+ function getReplayState() {
165
+ return state;
166
+ }
167
+ /** Scratch vector for orbit-target follow delta — avoids per-call allocation. */
168
+ const _targetDelta = new THREE.Vector3();
169
+ /**
170
+ * Update the OrbitControls target position and translate the camera
171
+ * by the same delta so the orbit viewing relationship is preserved.
172
+ *
173
+ * OrbitControls.update() does NOT automatically move the camera when
174
+ * the target changes — it recomputes offset = camera.position - target
175
+ * each frame, which is a no-op without user input. Without explicit
176
+ * camera translation, the camera would sit still while the trajectory
177
+ * moves away. Moving the camera by the target delta keeps the viewing
178
+ * angle and distance constant as the trajectory progresses.
179
+ *
180
+ * Call this as new odom poses are replayed so the orbit camera
181
+ * auto-follows the recorded trajectory.
182
+ *
183
+ * No-op if not initialized.
184
+ */
185
+ function updateOrbitTarget(position) {
186
+ if (!state) return;
187
+ _targetDelta.copy(position).sub(state.orbitControls.target);
188
+ state.camera.position.add(_targetDelta);
189
+ state.orbitControls.target.copy(position);
190
+ }
191
+ /**
192
+ * Get the current camera control mode.
193
+ * Returns 'orbit' if not initialized.
194
+ */
195
+ function getCameraMode() {
196
+ return state?.cameraMode ?? "orbit";
197
+ }
198
+ /**
199
+ * Get the camera follower's Object3D, or null if the replay scene
200
+ * is not initialized. Use this to attach children (e.g., map mesh)
201
+ * that should track the camera's position but stay GPS-aligned.
202
+ */
203
+ function getCameraFollower() {
204
+ return state?.cameraFollower.object3D ?? null;
205
+ }
206
+ /**
207
+ * Get the alignment lerper, or null if not initialized.
208
+ * Used by replay-mode to route store-subscriber alignment updates
209
+ * through the lerper instead of applying them directly.
210
+ */
211
+ function getAlignmentLerper() {
212
+ return state?.alignmentLerper ?? null;
213
+ }
214
+ /**
215
+ * Get the CSS3D renderer manager, or null if not initialized.
216
+ * Used for external resize handling or to verify the CSS3D layer exists.
217
+ */
218
+ function getCss3dManager() {
219
+ return state?.css3dManager ?? null;
220
+ }
221
+ /**
222
+ * Toggle between orbit and FPS camera modes.
223
+ *
224
+ * - orbit → fps: Disables OrbitControls, creates and enables
225
+ * PointerLockControls + WASD keyboard handler
226
+ * - fps → orbit: Disables PointerLockControls, re-enables OrbitControls
227
+ *
228
+ * No-op if not initialized.
229
+ */
230
+ function toggleCameraMode() {
231
+ if (!state) return;
232
+ if (state.cameraMode === "orbit") switchToFps();
233
+ else switchToOrbit();
234
+ }
235
+ function startRenderLoop(renderer, scene, camera, orbitControls, css3dManager) {
236
+ const timer = new THREE.Timer();
237
+ function animate() {
238
+ if (!state) return;
239
+ state.rAFId = requestAnimationFrame(animate);
240
+ timer.update();
241
+ const dt = timer.getDelta();
242
+ if (state.cameraMode === "orbit") orbitControls.update();
243
+ else updateFpsMovement(dt);
244
+ state.alignmentLerper.update(dt);
245
+ state.cameraFollower.update(state.camera, dt);
246
+ renderer.render(scene, camera);
247
+ css3dManager.render(scene, camera);
248
+ }
249
+ return requestAnimationFrame(animate);
250
+ }
251
+ function onPointerDown(e) {
252
+ if (e.button !== 0) return;
253
+ isDragging = true;
254
+ lastPointerX = e.clientX;
255
+ lastPointerY = e.clientY;
256
+ }
257
+ function onPointerMove(e) {
258
+ if (!isDragging || !state) return;
259
+ const dx = e.clientX - lastPointerX;
260
+ const dy = e.clientY - lastPointerY;
261
+ lastPointerX = e.clientX;
262
+ lastPointerY = e.clientY;
263
+ fpsYaw -= dx * MOUSE_SENSITIVITY;
264
+ fpsPitch -= dy * MOUSE_SENSITIVITY;
265
+ fpsPitch = Math.max(-MAX_PITCH, Math.min(MAX_PITCH, fpsPitch));
266
+ _euler.set(fpsPitch, fpsYaw, 0, "YXZ");
267
+ state.camera.quaternion.setFromEuler(_euler);
268
+ }
269
+ function onPointerUp() {
270
+ isDragging = false;
271
+ }
272
+ /** Restore the container's tabindex to its pre-FPS-mode value. */
273
+ function restoreTabindex() {
274
+ if (!state || savedTabindex.state === "none") return;
275
+ if (savedTabindex.value === void 0) state.container.removeAttribute("tabindex");
276
+ else if (savedTabindex.value !== state.container.getAttribute("tabindex")) state.container.setAttribute("tabindex", savedTabindex.value);
277
+ savedTabindex = { state: "none" };
278
+ }
279
+ function addFpsListeners() {
280
+ if (!state) return;
281
+ const canvas = state.renderer.domElement;
282
+ canvas.addEventListener("pointerdown", onPointerDown);
283
+ canvas.addEventListener("pointermove", onPointerMove);
284
+ canvas.addEventListener("pointerup", onPointerUp);
285
+ }
286
+ function removeFpsListeners() {
287
+ if (!state) return;
288
+ const canvas = state.renderer.domElement;
289
+ canvas.removeEventListener("pointerdown", onPointerDown);
290
+ canvas.removeEventListener("pointermove", onPointerMove);
291
+ canvas.removeEventListener("pointerup", onPointerUp);
292
+ isDragging = false;
293
+ }
294
+ function switchToFps() {
295
+ if (!state) return;
296
+ state.orbitControls.enabled = false;
297
+ _euler.setFromQuaternion(state.camera.quaternion, "YXZ");
298
+ fpsYaw = _euler.y;
299
+ fpsPitch = _euler.x;
300
+ addFpsListeners();
301
+ const currentTabindex = state.container.hasAttribute("tabindex") ? state.container.getAttribute("tabindex") ?? "" : void 0;
302
+ savedTabindex = {
303
+ state: "saved",
304
+ value: currentTabindex
305
+ };
306
+ if (currentTabindex === void 0) state.container.setAttribute("tabindex", "0");
307
+ state.container.focus();
308
+ state.container.addEventListener("keydown", onKeyDown);
309
+ state.container.addEventListener("keyup", onKeyUp);
310
+ state.cameraMode = "fps";
311
+ log.info("Switched to FPS camera mode");
312
+ }
313
+ function switchToOrbit() {
314
+ if (!state || state.cameraMode === "orbit") return;
315
+ state.cameraMode = "orbit";
316
+ removeFpsListeners();
317
+ state.container.removeEventListener("keydown", onKeyDown);
318
+ state.container.removeEventListener("keyup", onKeyUp);
319
+ restoreTabindex();
320
+ state.orbitControls.enabled = true;
321
+ for (const k of Object.keys(keysPressed)) keysPressed[k] = false;
322
+ log.info("Switched to Orbit camera mode");
323
+ }
324
+ //#endregion
325
+ export { disposeReplayScene, getAlignmentLerper, getCameraFollower, getCameraMode, getCss3dManager, getReplayState, initReplayScene, toggleCameraMode, updateOrbitTarget };
@@ -0,0 +1,2 @@
1
+ import { t as SCENE_NODE } from "../scene-node-names-DPsaE2z3.js";
2
+ export { SCENE_NODE };
@@ -0,0 +1,16 @@
1
+ //#region ../src/ar/scene-node-names.ts
2
+ /**
3
+ * Scene-graph node name constants.
4
+ *
5
+ * Used in .name assignments and getObjectByName() lookups.
6
+ * Centralised here (R4) so a rename is a single-point change
7
+ * with compile-time safety instead of grep-and-replace.
8
+ */
9
+ const SCENE_NODE = {
10
+ /** Basis-change node (WebXR → NUE coordinate system) */
11
+ BASIS_CHANGE: "webxr-to-nue",
12
+ /** Camera follower node (GPS-world-aligned, tracks camera position) */
13
+ CAMERA_FOLLOWER: "camera-follower"
14
+ };
15
+ //#endregion
16
+ export { SCENE_NODE };
@@ -0,0 +1,2 @@
1
+ import { a as TrackingStateManager, i as TrackingStateCallbacks, n as ResetTransformData, r as TrackingState, t as DeviceOrientation } from "../tracking-state-B9QfuZCg.js";
2
+ export { DeviceOrientation, ResetTransformData, TrackingState, TrackingStateCallbacks, TrackingStateManager };
@@ -0,0 +1,164 @@
1
+ import { createLogger } from "../utils/logger.js";
2
+ import { extractOdomPosition } from "../state/recording-coordinator.js";
3
+ //#region ../src/ar/tracking-state.ts
4
+ /**
5
+ * Tracking State Manager
6
+ *
7
+ * Detects AR tracking loss and restart events, maintaining state
8
+ * about the last valid odometry pose for alignment correction.
9
+ *
10
+ * ARCHITECTURE NOTE: See docs/architecture-ar-gps-pose-separation.md
11
+ *
12
+ * When WebXR tracking is lost and restarted, the odometry frame resets
13
+ * to a new origin. To maintain GPS+AR alignment, we need to:
14
+ * 1. Store the last valid pose before tracking was lost
15
+ * 2. Detect when tracking resumes with a new pose
16
+ * 3. Dispatch odometryTrackingRestarted with both poses for offset calculation
17
+ */
18
+ const log = createLogger("TrackingState");
19
+ /**
20
+ * Possible tracking states
21
+ */
22
+ let TrackingState = /* @__PURE__ */ function(TrackingState) {
23
+ /** AR session started but no pose received yet */
24
+ TrackingState["INITIALIZING"] = "initializing";
25
+ /** Actively receiving valid poses */
26
+ TrackingState["TRACKING"] = "tracking";
27
+ /** Pose became unavailable (tracking lost) */
28
+ TrackingState["LOST"] = "lost";
29
+ return TrackingState;
30
+ }({});
31
+ /**
32
+ * Manages AR tracking state and detects loss/restart events.
33
+ */
34
+ var TrackingStateManager = class {
35
+ state = "initializing";
36
+ lastValidPose = null;
37
+ lastSensorOrientation = null;
38
+ lostFrameCount = 0;
39
+ originResetDuringLoss = false;
40
+ resetTransform = void 0;
41
+ callbacks;
42
+ constructor(callbacks) {
43
+ this.callbacks = callbacks;
44
+ }
45
+ /**
46
+ * Get current tracking state.
47
+ */
48
+ getState() {
49
+ return this.state;
50
+ }
51
+ /**
52
+ * Get the last valid AR pose (before tracking was lost).
53
+ */
54
+ getLastValidPose() {
55
+ return this.lastValidPose;
56
+ }
57
+ /**
58
+ * Get count of consecutive frames without pose (during LOST state).
59
+ */
60
+ getLostFrameCount() {
61
+ return this.lostFrameCount;
62
+ }
63
+ /**
64
+ * Flag that an XRReferenceSpace reset event occurred during tracking loss.
65
+ * Only meaningful while in LOST state — ignored otherwise.
66
+ *
67
+ * @param transform — Serialized XRReferenceSpaceEvent.transform (post-reset
68
+ * origin in pre-reset coordinates). Pass `null` when the event's
69
+ * transform property is null (runtime can't determine delta).
70
+ * Omit entirely for backwards-compat with callers that don't have it.
71
+ */
72
+ markOriginReset(transform) {
73
+ if (this.state === "lost") {
74
+ this.originResetDuringLoss = true;
75
+ this.resetTransform = transform;
76
+ }
77
+ }
78
+ /**
79
+ * Called when a valid pose is received from WebXR.
80
+ */
81
+ onPoseReceived(pose) {
82
+ if (this.state === "lost") this.handleTrackingRestarted(pose);
83
+ this.state = "tracking";
84
+ this.lastValidPose = pose;
85
+ this.lastSensorOrientation = this.callbacks.getDeviceOrientation();
86
+ this.lostFrameCount = 0;
87
+ }
88
+ /**
89
+ * Called when pose is null/unavailable from WebXR.
90
+ */
91
+ onPoseLost() {
92
+ this.lostFrameCount++;
93
+ if (this.state === "tracking") {
94
+ this.state = "lost";
95
+ this.callbacks.onTrackingLost();
96
+ log.warn("AR tracking lost");
97
+ }
98
+ }
99
+ /**
100
+ * Reset the manager to initial state.
101
+ * Call this when starting a new session.
102
+ */
103
+ reset() {
104
+ this.state = "initializing";
105
+ this.lastValidPose = null;
106
+ this.lastSensorOrientation = null;
107
+ this.lostFrameCount = 0;
108
+ this.originResetDuringLoss = false;
109
+ this.resetTransform = void 0;
110
+ }
111
+ /**
112
+ * Handle the transition from LOST to TRACKING (restart detected).
113
+ */
114
+ handleTrackingRestarted(newPose) {
115
+ const hadReset = this.originResetDuringLoss;
116
+ const savedResetTransform = this.resetTransform;
117
+ this.originResetDuringLoss = false;
118
+ this.resetTransform = void 0;
119
+ if (!hadReset) {
120
+ log.info("AR tracking recovered (same coordinate frame)");
121
+ this.callbacks.onTrackingRecovered?.();
122
+ return;
123
+ }
124
+ if (!this.lastValidPose) {
125
+ log.warn("Tracking restarted but no previous pose available");
126
+ return;
127
+ }
128
+ const currentSensorOrientation = this.callbacks.getDeviceOrientation();
129
+ const lastOrientation = this.lastSensorOrientation ?? currentSensorOrientation;
130
+ const payload = {
131
+ lastValidOdomPos: extractOdomPosition(this.lastValidPose),
132
+ lastValidOdomRot: [
133
+ this.lastValidPose.orientation.x,
134
+ this.lastValidPose.orientation.y,
135
+ this.lastValidPose.orientation.z,
136
+ this.lastValidPose.orientation.w
137
+ ],
138
+ lastSensorOrientation: {
139
+ alpha: lastOrientation.alpha,
140
+ beta: lastOrientation.beta,
141
+ gamma: lastOrientation.gamma,
142
+ absolute: lastOrientation.absolute
143
+ },
144
+ newOdomRot: [
145
+ newPose.orientation.x,
146
+ newPose.orientation.y,
147
+ newPose.orientation.z,
148
+ newPose.orientation.w
149
+ ],
150
+ newSensorOrientation: {
151
+ alpha: currentSensorOrientation.alpha,
152
+ beta: currentSensorOrientation.beta,
153
+ gamma: currentSensorOrientation.gamma,
154
+ absolute: currentSensorOrientation.absolute
155
+ },
156
+ newOdomPos: extractOdomPosition(newPose),
157
+ resetTransform: savedResetTransform
158
+ };
159
+ log.info("AR tracking restarted (origin reset), dispatching alignment correction");
160
+ this.callbacks.onTrackingRestarted(payload);
161
+ }
162
+ };
163
+ //#endregion
164
+ export { TrackingState, TrackingStateManager };
@@ -0,0 +1,4 @@
1
+ import { i as DepthSample, t as ARPose } from "../ar-types-B-ORgk6Z.js";
2
+ import { t as CapturedImage } from "../image-capture-2pKjYUS6.js";
3
+ import { A as setTrackingLostCallback, C as setArWorldGroup, D as setImageCaptureCallback, E as setFrameCallback, F as stopDepthCapture, I as stopImageCapture, M as shouldLogCameraAccessDiagnostic, N as startDepthCapture, O as setScene, P as startImageCapture, S as setArPose, T as setDepthCaptureCallback, _ as isWebXRSupported, a as extractPoseFromViewer, b as nueQuaternionToWebXR, c as getArWorldGroup, d as getDepthSampleCount, f as getImageCaptureFrameCount, g as initAR, h as getXrCameraFromPose, i as endARSession, j as setTrackingRecoveredCallback, k as setTrackingCallbacks, l as getCamera, m as getScene, n as buildSessionOptions, o as extractResetTransformData, p as getLiveCss3dManager, r as createSceneHierarchy, s as getArPose, t as applyAlignmentMatrix, u as getCurrentArPose, v as isXRCameraLike, w as setCamera, x as resetWebXRState, y as nuePositionToWebXR } from "../webxr-session-COlSrXdL.js";
4
+ export { ARPose, CapturedImage, DepthSample, applyAlignmentMatrix, buildSessionOptions, createSceneHierarchy, endARSession, extractPoseFromViewer, extractResetTransformData, getArPose, getArWorldGroup, getCamera, getCurrentArPose, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getScene, getXrCameraFromPose, initAR, isWebXRSupported, isXRCameraLike, nuePositionToWebXR, nueQuaternionToWebXR, resetWebXRState, setArPose, setArWorldGroup, setCamera, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setScene, setTrackingCallbacks, setTrackingLostCallback, setTrackingRecoveredCallback, shouldLogCameraAccessDiagnostic, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture };