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.
- package/LICENSE +201 -0
- package/README.md +222 -0
- package/dist/alignment-lerper-D9BeDwZX.d.ts +29 -0
- package/dist/app-selectors-DyMzxCEC.d.ts +140 -0
- package/dist/ar/camera-blit-capture.d.ts +2 -0
- package/dist/ar/camera-blit-capture.js +253 -0
- package/dist/ar/capture-failure-tracker.d.ts +2 -0
- package/dist/ar/capture-failure-tracker.js +45 -0
- package/dist/ar/chromium-camera-access-workaround.d.ts +2 -0
- package/dist/ar/chromium-camera-access-workaround.js +32 -0
- package/dist/ar/depth-sampler.d.ts +3 -0
- package/dist/ar/depth-sampler.js +147 -0
- package/dist/ar/image-capture.d.ts +2 -0
- package/dist/ar/image-capture.js +152 -0
- package/dist/ar/index.d.ts +12 -0
- package/dist/ar/index.js +12 -0
- package/dist/ar/replay-scene.d.ts +2 -0
- package/dist/ar/replay-scene.js +325 -0
- package/dist/ar/scene-node-names.d.ts +2 -0
- package/dist/ar/scene-node-names.js +16 -0
- package/dist/ar/tracking-state.d.ts +2 -0
- package/dist/ar/tracking-state.js +164 -0
- package/dist/ar/webxr-session.d.ts +4 -0
- package/dist/ar/webxr-session.js +890 -0
- package/dist/ar/xr-camera-texture.d.ts +2 -0
- package/dist/ar/xr-camera-texture.js +39 -0
- package/dist/ar/xr-error-handler.d.ts +2 -0
- package/dist/ar/xr-error-handler.js +37 -0
- package/dist/ar-types-B-ORgk6Z.d.ts +82 -0
- package/dist/camera-blit-capture-DHuyrrxr.d.ts +108 -0
- package/dist/camera-follower-BMqPddxx.d.ts +29 -0
- package/dist/capture-failure-tracker-2Q6oI2uQ.d.ts +52 -0
- package/dist/chromium-camera-access-workaround-CU5zSKNr.d.ts +61 -0
- package/dist/community-license-key-ChZLCJyO.d.ts +21 -0
- package/dist/concurrency-p6gB30fT.d.ts +24 -0
- package/dist/css3d-renderer-manager-rgLMVAvd.d.ts +23 -0
- package/dist/depth-sampler-BNCtnEoP.d.ts +108 -0
- package/dist/failure-tracker-1sQ_Cgyw.d.ts +49 -0
- package/dist/file-system-BsQgYCU3.d.ts +128 -0
- package/dist/file-system-utils-5cWO8Xyv.d.ts +24 -0
- package/dist/format-file-size-onGq0k1Q.d.ts +24 -0
- package/dist/fused-path-DtqCSqJh.d.ts +25 -0
- package/dist/geo-types-Xx-LVcnw.d.ts +29 -0
- package/dist/gps-Ddzr2KlX.d.ts +54 -0
- package/dist/gps-compass-cubes-C-6JEnpR.d.ts +22 -0
- package/dist/gps-error-handler-VPuNOuf8.d.ts +40 -0
- package/dist/gps-event-markers-DpEy2qRd.d.ts +67 -0
- package/dist/h3-ref-point-CFhayNSZ.d.ts +59 -0
- package/dist/image-capture-2pKjYUS6.d.ts +130 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +61 -0
- package/dist/leaflet-map-overlay-CvfQXTye.d.ts +114 -0
- package/dist/lerp-utils-87NWjUOD.d.ts +20 -0
- package/dist/licensing/community-license-key.d.ts +2 -0
- package/dist/licensing/community-license-key.js +21 -0
- package/dist/licensing/index.d.ts +2 -0
- package/dist/licensing/index.js +2 -0
- package/dist/list-formatter-DKQQVLcl.d.ts +14 -0
- package/dist/logger-B81iwxx0.d.ts +74 -0
- package/dist/map-overlay-DuiZuCTx.d.ts +153 -0
- package/dist/null-storage-backend-CsWvQwN8.d.ts +11 -0
- package/dist/opfs-storage-ChOzF_Wp.d.ts +181 -0
- package/dist/opfs-storage-backend-CVcmfJHN.d.ts +11 -0
- package/dist/permission-checker-jO2ziCWC.d.ts +146 -0
- package/dist/persistence-middleware-DrptY02i.d.ts +31 -0
- package/dist/recorder-slice-fcwAXrsh.d.ts +35 -0
- package/dist/recording-coordinator-DzflP07_.d.ts +81 -0
- package/dist/recording-options-BYUFX8iU.d.ts +166 -0
- package/dist/recording-replayer-oOzPITrd.d.ts +32 -0
- package/dist/ref-point-importer-B_wn5ABk.d.ts +62 -0
- package/dist/ref-point-loader-Pt1smgHS.d.ts +117 -0
- package/dist/ref-points/h3-ref-point.d.ts +2 -0
- package/dist/ref-points/h3-ref-point.js +74 -0
- package/dist/ref-points/index.d.ts +2 -0
- package/dist/ref-points/index.js +2 -0
- package/dist/ref-points-slice-tC-Wd6XJ.d.ts +48 -0
- package/dist/reference-points-CFWy8_wj.d.ts +68 -0
- package/dist/replay-engine-DuX316ae.d.ts +103 -0
- package/dist/replay-scene-DtUm6bsr.d.ts +109 -0
- package/dist/routing-slice-BlHTM8eh.d.ts +23 -0
- package/dist/scene-node-names-DPsaE2z3.d.ts +14 -0
- package/dist/sensors/gps-error-handler.d.ts +2 -0
- package/dist/sensors/gps-error-handler.js +53 -0
- package/dist/sensors/gps.d.ts +2 -0
- package/dist/sensors/gps.js +93 -0
- package/dist/sensors/index.d.ts +4 -0
- package/dist/sensors/index.js +4 -0
- package/dist/sensors/permission-checker.d.ts +2 -0
- package/dist/sensors/permission-checker.js +397 -0
- package/dist/state/app-selectors.d.ts +2 -0
- package/dist/state/app-selectors.js +35 -0
- package/dist/state/index.d.ts +17 -0
- package/dist/state/index.js +13 -0
- package/dist/state/persistence-middleware.d.ts +2 -0
- package/dist/state/persistence-middleware.js +71 -0
- package/dist/state/recorder-slice.d.ts +2 -0
- package/dist/state/recorder-slice.js +47 -0
- package/dist/state/recording-coordinator.d.ts +2 -0
- package/dist/state/recording-coordinator.js +150 -0
- package/dist/state/recording-options.d.ts +2 -0
- package/dist/state/recording-options.js +192 -0
- package/dist/state/recording-replayer.d.ts +2 -0
- package/dist/state/recording-replayer.js +39 -0
- package/dist/state/ref-points-slice.d.ts +2 -0
- package/dist/state/ref-points-slice.js +44 -0
- package/dist/state/replay-engine.d.ts +2 -0
- package/dist/state/replay-engine.js +230 -0
- package/dist/state/routing-slice.d.ts +2 -0
- package/dist/state/routing-slice.js +13 -0
- package/dist/state/store-subscribers.d.ts +3 -0
- package/dist/state/store-subscribers.js +97 -0
- package/dist/state/store.d.ts +9 -0
- package/dist/state/store.js +71 -0
- package/dist/state/subscribe-to-selector.d.ts +2 -0
- package/dist/state/subscribe-to-selector.js +26 -0
- package/dist/storage/file-system-utils.d.ts +2 -0
- package/dist/storage/file-system-utils.js +31 -0
- package/dist/storage/file-system.d.ts +2 -0
- package/dist/storage/file-system.js +229 -0
- package/dist/storage/index.d.ts +11 -0
- package/dist/storage/index.js +10 -0
- package/dist/storage/null-storage-backend.d.ts +2 -0
- package/dist/storage/null-storage-backend.js +8 -0
- package/dist/storage/opfs-storage-backend.d.ts +2 -0
- package/dist/storage/opfs-storage-backend.js +15 -0
- package/dist/storage/opfs-storage.d.ts +2 -0
- package/dist/storage/opfs-storage.js +242 -0
- package/dist/storage/ref-point-importer.d.ts +2 -0
- package/dist/storage/ref-point-importer.js +163 -0
- package/dist/storage/ref-point-loader.d.ts +2 -0
- package/dist/storage/ref-point-loader.js +248 -0
- package/dist/storage/ref-point-recovery.d.ts +28 -0
- package/dist/storage/ref-point-recovery.js +159 -0
- package/dist/storage/storage-backend.d.ts +2 -0
- package/dist/storage/storage-backend.js +1 -0
- package/dist/storage/zip-export.d.ts +2 -0
- package/dist/storage/zip-export.js +214 -0
- package/dist/storage/zip-reader.d.ts +2 -0
- package/dist/storage/zip-reader.js +190 -0
- package/dist/storage-backend-BcEItnFr.d.ts +20 -0
- package/dist/store-C7PKOj8S.d.ts +96 -0
- package/dist/store-subscribers-CuoIzB5T.d.ts +77 -0
- package/dist/subscribe-to-selector-UTZ0KkAc.d.ts +26 -0
- package/dist/test-utils/browser-mocks.d.ts +294 -0
- package/dist/test-utils/browser-mocks.js +568 -0
- package/dist/test-utils/zip-round-trip-helpers.d.ts +60 -0
- package/dist/test-utils/zip-round-trip-helpers.js +207 -0
- package/dist/three-dispose-CCBHjE_a.d.ts +30 -0
- package/dist/tracking-state-B9QfuZCg.d.ts +101 -0
- package/dist/types/ar-types.d.ts +2 -0
- package/dist/types/ar-types.js +1 -0
- package/dist/types/geo-types.d.ts +2 -0
- package/dist/types/geo-types.js +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/concurrency.d.ts +2 -0
- package/dist/utils/concurrency.js +40 -0
- package/dist/utils/failure-tracker.d.ts +2 -0
- package/dist/utils/failure-tracker.js +48 -0
- package/dist/utils/format-file-size.d.ts +2 -0
- package/dist/utils/format-file-size.js +41 -0
- package/dist/utils/fused-path.d.ts +2 -0
- package/dist/utils/fused-path.js +44 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/list-formatter.d.ts +2 -0
- package/dist/utils/list-formatter.js +17 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +226 -0
- package/dist/vis-colors-Qi2vlRGt.d.ts +63 -0
- package/dist/visualization/alignment-lerper.d.ts +2 -0
- package/dist/visualization/alignment-lerper.js +68 -0
- package/dist/visualization/camera-follower.d.ts +2 -0
- package/dist/visualization/camera-follower.js +51 -0
- package/dist/visualization/css3d-renderer-manager.d.ts +2 -0
- package/dist/visualization/css3d-renderer-manager.js +41 -0
- package/dist/visualization/gps-compass-cubes.d.ts +2 -0
- package/dist/visualization/gps-compass-cubes.js +129 -0
- package/dist/visualization/gps-event-markers.d.ts +2 -0
- package/dist/visualization/gps-event-markers.js +159 -0
- package/dist/visualization/index.d.ts +12 -0
- package/dist/visualization/index.js +12 -0
- package/dist/visualization/leaflet-map-overlay.d.ts +2 -0
- package/dist/visualization/leaflet-map-overlay.js +313 -0
- package/dist/visualization/lerp-utils.d.ts +2 -0
- package/dist/visualization/lerp-utils.js +22 -0
- package/dist/visualization/map-overlay.d.ts +2 -0
- package/dist/visualization/map-overlay.js +241 -0
- package/dist/visualization/reference-points.d.ts +2 -0
- package/dist/visualization/reference-points.js +160 -0
- package/dist/visualization/three-dispose.d.ts +2 -0
- package/dist/visualization/three-dispose.js +55 -0
- package/dist/visualization/vis-colors.d.ts +2 -0
- package/dist/visualization/vis-colors.js +74 -0
- package/dist/webxr-session-COlSrXdL.d.ts +316 -0
- package/dist/xr-camera-texture-BbukNE79.d.ts +53 -0
- package/dist/xr-error-handler-DTHfmvrl.d.ts +24 -0
- package/dist/zip-export-Rtpi75JJ.d.ts +87 -0
- package/dist/zip-reader-D7pkpX8K.d.ts +97 -0
- 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,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 };
|