gps-plus-slam-app-framework 1.0.6 → 1.1.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 +4 -4
- package/README.md +310 -115
- package/dist/accuracy-circles-DEKr0Hoh.d.ts +43 -0
- package/dist/{app-selectors-CHt7DLuq.d.ts → app-selectors-CR9OWodf.d.ts} +8 -10
- package/dist/ar/camera-blit-capture.d.ts +1 -1
- package/dist/ar/capability-checker.d.ts +2 -0
- package/dist/ar/capability-checker.js +25 -0
- package/dist/ar/capture-failure-tracker.d.ts +1 -1
- package/dist/ar/chromium-camera-access-workaround.d.ts +2 -2
- package/dist/ar/chromium-camera-access-workaround.js +197 -8
- package/dist/ar/depth-sampler.d.ts +2 -2
- package/dist/ar/enable-gps-ar.d.ts +2 -0
- package/dist/ar/enable-gps-ar.js +137 -0
- package/dist/ar/frame-loop.d.ts +2 -0
- package/dist/ar/frame-loop.js +63 -0
- package/dist/ar/image-capture.d.ts +1 -1
- package/dist/ar/image-capture.js +1 -1
- package/dist/ar/index.d.ts +16 -12
- package/dist/ar/index.js +8 -4
- package/dist/ar/replay-scene.d.ts +1 -1
- package/dist/ar/scene-node-names.d.ts +1 -1
- package/dist/ar/webxr-session.d.ts +4 -4
- package/dist/ar/webxr-session.js +173 -93
- package/dist/ar/xr-camera-texture.d.ts +1 -1
- package/dist/ar/xr-error-handler.d.ts +1 -1
- package/dist/ar/xr-frame-loop.d.ts +2 -0
- package/dist/ar/xr-frame-loop.js +69 -0
- package/dist/ar-world-group-alignment-Dn4rQk_c.d.ts +43 -0
- package/dist/capability-checker-BTCmZRL4.d.ts +43 -0
- package/dist/chromium-camera-access-workaround-MifIwK9x.d.ts +137 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/create-slam-app-store-B76AGaI0.d.ts +371 -0
- package/dist/{depth-sampler-BNCtnEoP.d.ts → depth-sampler-CqrkRr2N.d.ts} +1 -1
- package/dist/enable-gps-ar-DNbO0zbg.d.ts +95 -0
- package/dist/{file-system-DD5TczSO.d.ts → file-system-TIsDfamK.d.ts} +32 -2
- package/dist/frame-conversions-D2EYjeNa.d.ts +54 -0
- package/dist/frame-loop-BTeRpDm4.d.ts +47 -0
- package/dist/frustum-visibility-DGnJqls0.d.ts +58 -0
- package/dist/{fused-path-DtqCSqJh.d.ts → fused-path-BdVHmDW9.d.ts} +1 -1
- package/dist/geo/h3-proximity.d.ts +1 -1
- package/dist/geo/index.d.ts +1 -1
- package/dist/{geo-types-Xx-LVcnw.d.ts → geo-types-clgzl8b5.d.ts} +16 -1
- package/dist/{gps-GKc6_Cly.d.ts → gps-B7AlMPz5.d.ts} +1 -1
- package/dist/gps-anchor-CISLPQIb.d.ts +59 -0
- package/dist/{gps-ar-pose-sampler-D_Zso40V.d.ts → gps-ar-pose-sampler-BypXZUqC.d.ts} +1 -1
- package/dist/{gps-event-coordinator-fwvmVfku.d.ts → gps-event-coordinator-BKVZbS1M.d.ts} +3 -3
- package/dist/gps-event-markers-BlHq3jYV.d.ts +103 -0
- package/dist/hit-test-reticle-BGXOxrUh.d.ts +45 -0
- package/dist/{image-capture-2pKjYUS6.d.ts → image-capture-BXZUmj7j.d.ts} +1 -1
- package/dist/index--ldLph4V.d.ts +2 -0
- package/dist/index-22v8MXJX.d.ts +23 -0
- package/dist/index-BB2KEWbN2.d.ts +37 -0
- package/dist/index.d.ts +64 -52
- package/dist/index.js +21 -8
- package/dist/{leaflet-map-overlay-CQZcnxbc.d.ts → leaflet-map-overlay-jNt-kcUr.d.ts} +22 -11
- package/dist/{logger-B81iwxx0.d.ts → logger-BZ44Dhd3.d.ts} +7 -1
- package/dist/map-data-BGIBM5rv.d.ts +49 -0
- package/dist/map-overlay-draw-DcGs9UV3.d.ts +42 -0
- package/dist/{null-storage-backend-C0TiwwzQ.d.ts → null-storage-backend-BPQJSaMw.d.ts} +2 -2
- package/dist/{opfs-storage-backend-Gt3rd76D.d.ts → opfs-storage-backend-u4QKvgvP.d.ts} +2 -2
- package/dist/{permission-checker-Cu3nbH1S.d.ts → permission-checker-CfV7INCa.d.ts} +41 -1
- package/dist/persistence-middleware-xB5sTllJ.d.ts +55 -0
- package/dist/{recording-options-BYUFX8iU.d.ts → recording-options-BSFpBSZH.d.ts} +10 -4
- package/dist/{recording-replayer-BFhFs7Mc.d.ts → recording-replayer-CTL2cUE0.d.ts} +2 -2
- package/dist/{recording-slice-BiktUIWF.d.ts → recording-slice-Cb888P6d.d.ts} +2 -2
- package/dist/{replay-engine-B88Va62x.d.ts → replay-engine-B-5GIZtc.d.ts} +1 -1
- package/dist/{replay-scene-DtUm6bsr.d.ts → replay-scene-DkNfldPh.d.ts} +3 -3
- package/dist/sensors/gps-error-handler.d.ts +1 -1
- package/dist/sensors/gps.d.ts +1 -1
- package/dist/sensors/gps.js +2 -2
- package/dist/sensors/index.d.ts +3 -3
- package/dist/sensors/index.js +1 -1
- package/dist/sensors/permission-checker.d.ts +2 -2
- package/dist/sensors/permission-checker.js +79 -1
- package/dist/session-disposers-M-oashRH.js +57 -0
- package/dist/state/app-selectors.d.ts +2 -2
- package/dist/state/app-selectors.js +33 -3
- package/dist/state/combined-root-state.d.ts +2 -0
- package/dist/state/combined-root-state.js +1 -0
- package/dist/state/create-slam-app-store.d.ts +1 -1
- package/dist/state/create-slam-app-store.js +27 -13
- package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
- package/dist/state/gps-event-coordinator.d.ts +1 -1
- package/dist/state/index.d.ts +17 -16
- package/dist/state/index.js +6 -4
- package/dist/state/persistence-middleware.d.ts +2 -2
- package/dist/state/persistence-middleware.js +43 -9
- package/dist/state/recording-options.d.ts +1 -1
- package/dist/state/recording-replayer.d.ts +2 -2
- package/dist/state/recording-slice.d.ts +1 -1
- package/dist/state/replay-engine.d.ts +1 -1
- package/dist/state/store-subscribers.d.ts +2 -2
- package/dist/state/store-subscribers.js +38 -12
- package/dist/state/subscribe-to-selector.d.ts +1 -1
- package/dist/state/tracking-quality.d.ts +2 -0
- package/dist/state/tracking-quality.js +723 -0
- package/dist/state/tracking-slice.d.ts +2 -0
- package/dist/state/tracking-slice.js +147 -0
- package/dist/state-BkIfpiyA.js +87 -0
- package/dist/storage/file-system-utils.d.ts +1 -1
- package/dist/storage/file-system.d.ts +2 -2
- package/dist/storage/file-system.js +52 -1
- package/dist/storage/index.d.ts +8 -8
- package/dist/storage/null-storage-backend.d.ts +1 -1
- package/dist/storage/opfs-storage-backend.d.ts +1 -1
- package/dist/storage/opfs-storage.d.ts +1 -1
- package/dist/storage/storage-backend.d.ts +1 -1
- package/dist/storage/zip-export.d.ts +1 -1
- package/dist/storage/zip-reader.d.ts +1 -1
- package/dist/storage/zip-reader.js +8 -4
- package/dist/{storage-backend-yVnuWGTr.d.ts → storage-backend-yDSKafAQ.d.ts} +1 -1
- package/dist/{store-subscribers-DKKHld49.d.ts → store-subscribers-swlbtg_z.d.ts} +31 -6
- package/dist/{subscribe-to-selector-ghDKYtap.d.ts → subscribe-to-selector--KQpgLXG.d.ts} +1 -1
- package/dist/test-utils/browser-mocks.d.ts +1 -1
- package/dist/test-utils/zip-round-trip-helpers.d.ts +7 -1
- package/dist/test-utils/zip-round-trip-helpers.js +2 -1
- package/dist/tracking-slice-CDREeoZ_.d.ts +86 -0
- package/dist/types/ar-types.d.ts +1 -1
- package/dist/types/geo-types.d.ts +2 -2
- package/dist/types/index.d.ts +3 -3
- package/dist/utils/concurrency.d.ts +1 -1
- package/dist/utils/failure-tracker.d.ts +1 -1
- package/dist/utils/format-file-size.d.ts +1 -1
- package/dist/utils/fused-path.d.ts +1 -1
- package/dist/utils/index.d.ts +6 -6
- package/dist/utils/list-formatter.d.ts +1 -1
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +68 -8
- package/dist/visualization/accuracy-circles.d.ts +2 -0
- package/dist/visualization/accuracy-circles.js +57 -0
- package/dist/visualization/alignment-lerper.d.ts +1 -1
- package/dist/visualization/ar-world-group-alignment.d.ts +2 -0
- package/dist/visualization/ar-world-group-alignment.js +51 -0
- package/dist/visualization/camera-follower.d.ts +1 -1
- package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
- package/dist/visualization/frame-conversions.d.ts +2 -0
- package/dist/visualization/frame-conversions.js +88 -0
- package/dist/visualization/frustum-visibility.d.ts +2 -0
- package/dist/visualization/frustum-visibility.js +139 -0
- package/dist/visualization/gps-anchor.d.ts +2 -0
- package/dist/visualization/gps-anchor.js +217 -0
- package/dist/visualization/gps-compass-cubes.d.ts +1 -1
- package/dist/visualization/gps-event-markers.d.ts +2 -2
- package/dist/visualization/gps-event-markers.js +69 -2
- package/dist/visualization/hit-test-reticle.d.ts +2 -0
- package/dist/visualization/hit-test-reticle.js +81 -0
- package/dist/visualization/index.d.ts +19 -12
- package/dist/visualization/index.js +9 -2
- package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
- package/dist/visualization/leaflet-map-overlay.js +33 -79
- package/dist/visualization/lerp-utils.d.ts +1 -1
- package/dist/visualization/map-data.d.ts +2 -0
- package/dist/visualization/map-data.js +33 -0
- package/dist/visualization/map-overlay-draw.d.ts +2 -0
- package/dist/visualization/map-overlay-draw.js +107 -0
- package/dist/visualization/map-overlay.d.ts +1 -1
- package/dist/visualization/three-dispose.d.ts +1 -1
- package/dist/visualization/vis-colors.d.ts +1 -1
- package/dist/webxr-nue-basis-BbdqhqY6.js +54 -0
- package/dist/{webxr-session-COlSrXdL.d.ts → webxr-session-BnArFCew.d.ts} +76 -14
- package/dist/xr-frame-loop-BKckC7xC.d.ts +70 -0
- package/dist/{zip-reader-KgJEXbca.d.ts → zip-reader-B2lzN8F5.d.ts} +5 -0
- package/package.json +16 -16
- package/dist/ar/tracking-state.d.ts +0 -2
- package/dist/ar/tracking-state.js +0 -164
- package/dist/chromium-camera-access-workaround-CU5zSKNr.d.ts +0 -61
- package/dist/combined-root-state-B3SvPNjU.d.ts +0 -6
- package/dist/create-slam-app-store-DBm7g2Zd.d.ts +0 -85
- package/dist/gps-anchored-mesh-manager-CovAFK4g.d.ts +0 -56
- package/dist/gps-event-markers-sVyO9utS.d.ts +0 -67
- package/dist/persistence-middleware-z-75iisb.d.ts +0 -31
- package/dist/tracking-state-B9QfuZCg.d.ts +0 -101
- package/dist/visualization/gps-anchored-mesh-manager.d.ts +0 -2
- package/dist/visualization/gps-anchored-mesh-manager.js +0 -128
- /package/dist/{alignment-lerper-D9BeDwZX.d.ts → alignment-lerper-CCZJf-Xb.d.ts} +0 -0
- /package/dist/{ar-types-B-ORgk6Z.d.ts → ar-types-isPsQptb.d.ts} +0 -0
- /package/dist/{camera-blit-capture-DHuyrrxr.d.ts → camera-blit-capture-75WWa9Xb.d.ts} +0 -0
- /package/dist/{camera-follower-BMqPddxx.d.ts → camera-follower-B-nS1sr6.d.ts} +0 -0
- /package/dist/{capture-failure-tracker-2Q6oI2uQ.d.ts → capture-failure-tracker-D4HYJNOj.d.ts} +0 -0
- /package/dist/{concurrency-p6gB30fT.d.ts → concurrency-Bsmv53qw.d.ts} +0 -0
- /package/dist/{css3d-renderer-manager-rgLMVAvd.d.ts → css3d-renderer-manager-EDED3nvg.d.ts} +0 -0
- /package/dist/{failure-tracker-1sQ_Cgyw.d.ts → failure-tracker-D7ELffm0.d.ts} +0 -0
- /package/dist/{file-system-utils-BH8FdyIu.d.ts → file-system-utils-BH6uIs2i.d.ts} +0 -0
- /package/dist/{format-file-size-onGq0k1Q.d.ts → format-file-size-B7gJb3Md.d.ts} +0 -0
- /package/dist/{gps-compass-cubes-jRIJMOuh.d.ts → gps-compass-cubes-Ys6Hf1nc.d.ts} +0 -0
- /package/dist/{gps-error-handler-CH1S528z.d.ts → gps-error-handler-BLDhUlw5.d.ts} +0 -0
- /package/dist/{h3-proximity-GwesPTFT.d.ts → h3-proximity-DJnghtir.d.ts} +0 -0
- /package/dist/{lerp-utils-CZgQlrmu.d.ts → lerp-utils-DNcSmKVX.d.ts} +0 -0
- /package/dist/{list-formatter-DKQQVLcl.d.ts → list-formatter-CsqcU4v5.d.ts} +0 -0
- /package/dist/{map-overlay-C9ifl_4-.d.ts → map-overlay-yrFsUtGv.d.ts} +0 -0
- /package/dist/{opfs-storage-Dlq1I_Gb.d.ts → opfs-storage-LsLY6VZV.d.ts} +0 -0
- /package/dist/{scene-node-names-DPsaE2z3.d.ts → scene-node-names-IHW7HR4S.d.ts} +0 -0
- /package/dist/{three-dispose-qt_r5wR1.d.ts → three-dispose-WTBAfaWU.d.ts} +0 -0
- /package/dist/{vis-colors-BKkdTIKb.d.ts → vis-colors-Djnyg_UH.d.ts} +0 -0
- /package/dist/{xr-camera-texture-BbukNE79.d.ts → xr-camera-texture-BU5YvKAM.d.ts} +0 -0
- /package/dist/{xr-error-handler-DTHfmvrl.d.ts → xr-error-handler-DcUFshNF.d.ts} +0 -0
- /package/dist/{zip-export-BjrQPv0X.d.ts → zip-export-C0_ErAqT.d.ts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as DepthSample, t as ARPose } from "../ar-types-
|
|
2
|
-
import { t as CapturedImage } from "../image-capture-
|
|
3
|
-
import { A as
|
|
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 };
|
|
1
|
+
import { i as DepthSample, t as ARPose } from "../ar-types-isPsQptb.js";
|
|
2
|
+
import { t as CapturedImage } from "../image-capture-BXZUmj7j.js";
|
|
3
|
+
import { A as setScene, C as resetWebXRState, D as setDepthCaptureCallback, E as setCamera, F as shouldLogCameraAccessDiagnostic, I as startDepthCapture, L as startImageCapture, M as setTrackingLostCallback, N as setTrackingRecoveredCallback, O as setFrameCallback, P as setTrackingStore, R as stopDepthCapture, S as nueQuaternionToWebXR, T as setArWorldGroup, _ as getXrCameraFromPose, a as createSceneHierarchy, b as isXRCameraLike, c as extractResetTransformData, d as getCamera, f as getCurrentArPose, g as getScene, h as getLiveCss3dManager, i as buildSessionOptions, j as setTrackingCallbacks, k as setImageCaptureCallback, l as getArPose, m as getImageCaptureFrameCount, n as TrackingSubscribableStore, o as endARSession, p as getDepthSampleCount, r as applyAlignmentMatrix, s as extractPoseFromViewer, t as SessionFeatureOptions, u as getArWorldGroup, v as initAR, w as setArPose, x as nuePositionToWebXR, y as isWebXRSupported, z as stopImageCapture } from "../webxr-session-BnArFCew.js";
|
|
4
|
+
export { ARPose, CapturedImage, DepthSample, SessionFeatureOptions, TrackingSubscribableStore, 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, setTrackingStore, shouldLogCameraAccessDiagnostic, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture };
|
package/dist/ar/webxr-session.js
CHANGED
|
@@ -2,9 +2,13 @@ import { createLogger } from "../utils/logger.js";
|
|
|
2
2
|
import { CameraBlitCapture, computeCaptureSize } from "./camera-blit-capture.js";
|
|
3
3
|
import { getLastDeviceOrientation } from "../state/gps-event-coordinator.js";
|
|
4
4
|
import { DepthSampler } from "./depth-sampler.js";
|
|
5
|
+
import { clearFrameUpdates, runFrameUpdates } from "./frame-loop.js";
|
|
6
|
+
import { clearXrFrameUpdates, runXrFrameUpdates } from "./xr-frame-loop.js";
|
|
7
|
+
import { t as WEBXR_TO_NUE } from "../webxr-nue-basis-BbdqhqY6.js";
|
|
5
8
|
import { DEFAULT_CAPTURE_CONFIG, ImageCaptureManager } from "./image-capture.js";
|
|
6
|
-
import {
|
|
9
|
+
import { clearLastRestartedPayload, originReset, poseLost, poseReceived, resetTracking, selectLastRestartedPayload, selectTrackingPhase } from "../state/tracking-slice.js";
|
|
7
10
|
import { acquireCameraTexture } from "./xr-camera-texture.js";
|
|
11
|
+
import { n as runSessionDisposers } from "../session-disposers-M-oashRH.js";
|
|
8
12
|
import { DEFAULT_RECORDING_OPTIONS, validateArCrashIsolationOptions } from "../state/recording-options.js";
|
|
9
13
|
import { SCENE_NODE } from "./scene-node-names.js";
|
|
10
14
|
import { createCss3dRendererManager } from "../visualization/css3d-renderer-manager.js";
|
|
@@ -84,6 +88,13 @@ let scene = null;
|
|
|
84
88
|
let camera = null;
|
|
85
89
|
let xrSession = null;
|
|
86
90
|
/**
|
|
91
|
+
* Monotonic time of the previous XR frame, in milliseconds (XR `time`
|
|
92
|
+
* argument). Reset to 0 by `resetWebXRState()` so the first frame of a
|
|
93
|
+
* new session sees `dt = 0` rather than a stale delta from the prior
|
|
94
|
+
* session.
|
|
95
|
+
*/
|
|
96
|
+
let lastFrameTime = 0;
|
|
97
|
+
/**
|
|
87
98
|
* Reset WebXR module state - exported for testing only.
|
|
88
99
|
* @internal
|
|
89
100
|
*/
|
|
@@ -100,12 +111,20 @@ function resetWebXRState() {
|
|
|
100
111
|
arWorldGroup = null;
|
|
101
112
|
arPoseNode = null;
|
|
102
113
|
latestArPose = null;
|
|
114
|
+
lastFrameTime = 0;
|
|
115
|
+
clearFrameUpdates();
|
|
116
|
+
clearXrFrameUpdates();
|
|
117
|
+
runSessionDisposers();
|
|
103
118
|
imageCaptureManager = null;
|
|
104
119
|
onImageCaptured = null;
|
|
105
120
|
getScreenRotation = null;
|
|
106
121
|
onCaptureFailed = null;
|
|
107
122
|
onSuspiciousImage = null;
|
|
108
|
-
|
|
123
|
+
if (trackingPhaseUnsubscribe) {
|
|
124
|
+
trackingPhaseUnsubscribe();
|
|
125
|
+
trackingPhaseUnsubscribe = null;
|
|
126
|
+
}
|
|
127
|
+
trackingStore = null;
|
|
109
128
|
onTrackingRestarted = null;
|
|
110
129
|
onTrackingLost = null;
|
|
111
130
|
onTrackingRecovered = null;
|
|
@@ -163,9 +182,22 @@ let onSuspiciousImage = null;
|
|
|
163
182
|
*/
|
|
164
183
|
let getScreenRotation = null;
|
|
165
184
|
/**
|
|
166
|
-
*
|
|
185
|
+
* Redux store injected by the host (`setTrackingStore`). When present
|
|
186
|
+
* together with `onTrackingRestarted`, `onXRFrame` dispatches
|
|
187
|
+
* `poseReceived`/`poseLost`, the XR reference-space reset listener
|
|
188
|
+
* dispatches `originReset`, and a `subscribeToSelector` translation
|
|
189
|
+
* surface translates phase transitions back into the existing
|
|
190
|
+
* `onTrackingLost` / `onTrackingRestarted` / `onTrackingRecovered`
|
|
191
|
+
* callbacks. When the store is absent the tracking pipeline simply
|
|
192
|
+
* no-ops.
|
|
167
193
|
*/
|
|
168
|
-
let
|
|
194
|
+
let trackingStore = null;
|
|
195
|
+
/**
|
|
196
|
+
* Unsubscribe handle returned by the phase subscription set up in
|
|
197
|
+
* `initAR`. Cleared in `resetWebXRState` and on session-end so we never
|
|
198
|
+
* leave dangling listeners on a stale store.
|
|
199
|
+
*/
|
|
200
|
+
let trackingPhaseUnsubscribe = null;
|
|
169
201
|
/**
|
|
170
202
|
* Callback for when tracking restarts (set via setTrackingCallbacks)
|
|
171
203
|
*/
|
|
@@ -323,10 +355,14 @@ function getCurrentArPose() {
|
|
|
323
355
|
* Extracted as a pure function for testability.
|
|
324
356
|
*
|
|
325
357
|
* @param rootElement - The DOM element for DOM overlay
|
|
358
|
+
* @param isolationOptions - Crash-isolation diagnostic flags (DOM overlay,
|
|
359
|
+
* depth-sensing, camera-access)
|
|
360
|
+
* @param sessionFeatures - Opt-in standard WebXR features that are independent
|
|
361
|
+
* of crash isolation (currently `requestHitTest`)
|
|
326
362
|
* @returns XRSessionInit options
|
|
327
363
|
* @throws Error if rootElement is null
|
|
328
364
|
*/
|
|
329
|
-
function buildSessionOptions(rootElement, isolationOptions = {}) {
|
|
365
|
+
function buildSessionOptions(rootElement, isolationOptions = {}, sessionFeatures = {}) {
|
|
330
366
|
if (!rootElement) throw new Error("App root element not found");
|
|
331
367
|
const normalizedOptions = validateArCrashIsolationOptions(isolationOptions);
|
|
332
368
|
const optionalFeatures = [];
|
|
@@ -343,6 +379,7 @@ function buildSessionOptions(rootElement, isolationOptions = {}) {
|
|
|
343
379
|
} });
|
|
344
380
|
}
|
|
345
381
|
if (normalizedOptions.enableCameraAccess) optionalFeatures.push("camera-access");
|
|
382
|
+
if (sessionFeatures.requestHitTest) optionalFeatures.push("hit-test");
|
|
346
383
|
if (optionalFeatures.length > 0) sessionOptions.optionalFeatures = optionalFeatures;
|
|
347
384
|
return sessionOptions;
|
|
348
385
|
}
|
|
@@ -373,8 +410,18 @@ async function isWebXRSupported() {
|
|
|
373
410
|
* basisChangeNode is a static scene-graph node that holds the WEBXR_TO_NUE
|
|
374
411
|
* basis-change matrix permanently (matrixAutoUpdate=false). Moving it here
|
|
375
412
|
* instead of composing it in applyAlignmentMatrix() keeps arWorldGroup's
|
|
376
|
-
* local space
|
|
377
|
-
*
|
|
413
|
+
* local space in the **NUE axis convention** (X=North, Y=Up, Z=East), so no
|
|
414
|
+
* WebXR↔NUE swizzle is needed for children.
|
|
415
|
+
*
|
|
416
|
+
* CAUTION — two NUE frames: arWorldGroup's local space is the *AR-odometry*
|
|
417
|
+
* NUE frame, i.e. the **domain** of the alignment matrix, NOT the GPS-world
|
|
418
|
+
* NUE frame of the scene root. Only content authored in AR-odometry
|
|
419
|
+
* coordinates (e.g. the camera subtree) may be placed with raw local values.
|
|
420
|
+
* GPS-world content (a lat/lon → NUE point) is expressed in the scene-root
|
|
421
|
+
* frame and must be pre-multiplied by alignment⁻¹ before being used as a
|
|
422
|
+
* local position under arWorldGroup — see createGpsAnchor and the
|
|
423
|
+
* alignment-frame bug doc
|
|
424
|
+
* (GpsPlusSlamJs_Docs/docs/2026-05-31-gps-anchor-alignment-frame-bug.md).
|
|
378
425
|
*
|
|
379
426
|
* - Recording: arpose stays at identity; WebXRManager writes to camera.
|
|
380
427
|
* - Replay: arpose receives recorded odomPosition/odomRotation;
|
|
@@ -412,9 +459,13 @@ function createSceneHierarchy() {
|
|
|
412
459
|
/**
|
|
413
460
|
* Initialize the AR session and Three.js renderer.
|
|
414
461
|
* @param container - DOM element to host the AR canvas and CSS3D overlay.
|
|
462
|
+
* @param isolationOptions - Crash-isolation diagnostic flags.
|
|
463
|
+
* @param sessionFeatures - Opt-in standard WebXR features (e.g.
|
|
464
|
+
* `requestHitTest`) forwarded to the session negotiation.
|
|
415
465
|
*/
|
|
416
|
-
async function initAR(container, isolationOptions = {}) {
|
|
466
|
+
async function initAR(container, isolationOptions = {}, sessionFeatures = {}) {
|
|
417
467
|
if (!navigator.xr) throw new Error("WebXR not available");
|
|
468
|
+
if (renderer || xrSession) throw new Error("AR session already initialized — call endARSession() before initAR() again");
|
|
418
469
|
currentArCrashIsolationOptions = validateArCrashIsolationOptions(isolationOptions);
|
|
419
470
|
renderer = new THREE.WebGLRenderer({
|
|
420
471
|
antialias: true,
|
|
@@ -430,44 +481,23 @@ async function initAR(container, isolationOptions = {}) {
|
|
|
430
481
|
arWorldGroup = hierarchy.arWorldGroup;
|
|
431
482
|
arPoseNode = hierarchy.arpose;
|
|
432
483
|
camera = hierarchy.camera;
|
|
433
|
-
const sessionOptions = buildSessionOptions(container, currentArCrashIsolationOptions);
|
|
484
|
+
const sessionOptions = buildSessionOptions(container, currentArCrashIsolationOptions, sessionFeatures);
|
|
434
485
|
xrSession = await navigator.xr.requestSession("immersive-ar", sessionOptions);
|
|
435
486
|
xrSession.addEventListener("end", () => {
|
|
436
487
|
log.info("Session ended");
|
|
437
|
-
if (
|
|
488
|
+
if (trackingStore) trackingStore.dispatch(resetTracking());
|
|
438
489
|
xrSession = null;
|
|
439
490
|
latestArPose = null;
|
|
440
491
|
});
|
|
441
492
|
await renderer.xr.setSession(xrSession);
|
|
442
|
-
if (onTrackingRestarted) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
latestArPose = null;
|
|
447
|
-
onTrackingLost?.();
|
|
448
|
-
},
|
|
449
|
-
onTrackingRestarted: (payload) => {
|
|
450
|
-
log.info("Tracking restarted (origin reset)");
|
|
451
|
-
onTrackingRestarted?.(payload);
|
|
452
|
-
},
|
|
453
|
-
onTrackingRecovered: () => {
|
|
454
|
-
log.info("Tracking recovered (same coordinate frame)");
|
|
455
|
-
onTrackingRecovered?.();
|
|
456
|
-
},
|
|
457
|
-
getDeviceOrientation: () => {
|
|
458
|
-
const orientation = getLastDeviceOrientation();
|
|
459
|
-
return {
|
|
460
|
-
alpha: orientation?.alpha ?? 0,
|
|
461
|
-
beta: orientation?.beta ?? 0,
|
|
462
|
-
gamma: orientation?.gamma ?? 0,
|
|
463
|
-
absolute: orientation?.absolute ?? false
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
});
|
|
493
|
+
if (onTrackingRestarted && trackingStore) {
|
|
494
|
+
const store = trackingStore;
|
|
495
|
+
store.dispatch(resetTracking());
|
|
496
|
+
trackingPhaseUnsubscribe = subscribeToTrackingPhase(store);
|
|
467
497
|
const referenceSpace = renderer.xr.getReferenceSpace();
|
|
468
498
|
if (referenceSpace) referenceSpace.addEventListener("reset", (event) => {
|
|
469
499
|
const transformData = extractResetTransformData(event);
|
|
470
|
-
|
|
500
|
+
store.dispatch(originReset(transformData));
|
|
471
501
|
log.warn("XR reference space reset detected", transformData ? "(transform available)" : "(no transform)");
|
|
472
502
|
});
|
|
473
503
|
}
|
|
@@ -480,12 +510,72 @@ async function initAR(container, isolationOptions = {}) {
|
|
|
480
510
|
log.info("AR session started");
|
|
481
511
|
}
|
|
482
512
|
/**
|
|
483
|
-
*
|
|
513
|
+
* Snapshot the current `DeviceOrientation` (with documented fallback
|
|
514
|
+
* defaults) for inclusion in `poseReceived` payloads.
|
|
515
|
+
*/
|
|
516
|
+
function snapshotDeviceOrientation() {
|
|
517
|
+
const orientation = getLastDeviceOrientation();
|
|
518
|
+
return {
|
|
519
|
+
alpha: orientation?.alpha ?? 0,
|
|
520
|
+
beta: orientation?.beta ?? 0,
|
|
521
|
+
gamma: orientation?.gamma ?? 0,
|
|
522
|
+
absolute: orientation?.absolute ?? false
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Wire the tracking-slice → host-callbacks translation. The subscriber
|
|
527
|
+
* runs synchronously inside each `dispatch`, so the host callbacks fire
|
|
528
|
+
* in the same order as a direct invocation would.
|
|
529
|
+
*
|
|
530
|
+
* Translation rules (locked in by tracking-slice tests):
|
|
531
|
+
* - `tracking → lost`: clear `latestArPose` (drops in-flight GPS events)
|
|
532
|
+
* and call `onTrackingLost?.()`.
|
|
533
|
+
* - `lost → tracking` with `lastRestartedPayload !== null` (Case 2):
|
|
534
|
+
* call `onTrackingRestarted?.(payload)` then dispatch
|
|
535
|
+
* `clearLastRestartedPayload` so a subsequent loss cycle starts clean.
|
|
536
|
+
* - `lost → tracking` with payload null (Case 1: seamless recovery):
|
|
537
|
+
* call `onTrackingRecovered?.()`.
|
|
538
|
+
* - `initializing → tracking`: no callback (initial acquisition is not
|
|
539
|
+
* a restart — same behaviour as the manager).
|
|
540
|
+
*/
|
|
541
|
+
function subscribeToTrackingPhase(store) {
|
|
542
|
+
let prev = "initializing";
|
|
543
|
+
return store.subscribe(() => {
|
|
544
|
+
const next = selectTrackingPhase(store.getState());
|
|
545
|
+
if (next === prev) return;
|
|
546
|
+
const previous = prev;
|
|
547
|
+
prev = next;
|
|
548
|
+
if (previous === "tracking" && next === "lost") {
|
|
549
|
+
log.warn("Tracking lost");
|
|
550
|
+
latestArPose = null;
|
|
551
|
+
onTrackingLost?.();
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (previous === "lost" && next === "tracking") {
|
|
555
|
+
const payload = selectLastRestartedPayload(store.getState());
|
|
556
|
+
if (payload !== null) {
|
|
557
|
+
log.info("Tracking restarted (origin reset)");
|
|
558
|
+
onTrackingRestarted?.(payload);
|
|
559
|
+
store.dispatch(clearLastRestartedPayload());
|
|
560
|
+
} else {
|
|
561
|
+
log.info("Tracking recovered (same coordinate frame)");
|
|
562
|
+
onTrackingRecovered?.();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Dispatch the per-frame `poseReceived` / `poseLost` action into the
|
|
569
|
+
* tracking slice. No-op when no store is bound or when tracking wiring
|
|
570
|
+
* was not requested.
|
|
484
571
|
*/
|
|
485
572
|
function updateTrackingState(arPose) {
|
|
486
|
-
if (!
|
|
487
|
-
if (arPose)
|
|
488
|
-
|
|
573
|
+
if (!trackingStore || !onTrackingRestarted) return;
|
|
574
|
+
if (arPose) trackingStore.dispatch(poseReceived({
|
|
575
|
+
pose: arPose,
|
|
576
|
+
sensorOrientation: snapshotDeviceOrientation()
|
|
577
|
+
}));
|
|
578
|
+
else trackingStore.dispatch(poseLost());
|
|
489
579
|
}
|
|
490
580
|
/**
|
|
491
581
|
* Called each XR frame
|
|
@@ -497,6 +587,17 @@ function onXRFrame(time, frame) {
|
|
|
497
587
|
const pose = frame.getViewerPose(referenceSpace);
|
|
498
588
|
const arPose = extractPoseFromViewer(pose);
|
|
499
589
|
updateTrackingState(arPose);
|
|
590
|
+
const dt = lastFrameTime === 0 ? 0 : (time - lastFrameTime) / 1e3;
|
|
591
|
+
const elapsed = time / 1e3;
|
|
592
|
+
lastFrameTime = time;
|
|
593
|
+
runFrameUpdates(dt, elapsed);
|
|
594
|
+
if (xrSession) runXrFrameUpdates({
|
|
595
|
+
frame,
|
|
596
|
+
referenceSpace,
|
|
597
|
+
session: xrSession,
|
|
598
|
+
dt,
|
|
599
|
+
elapsed
|
|
600
|
+
});
|
|
500
601
|
if (arPose) latestArPose = arPose;
|
|
501
602
|
latestCameraTexture = null;
|
|
502
603
|
if (currentArCrashIsolationOptions.enableCameraTextureAcquisition) {
|
|
@@ -607,41 +708,6 @@ function setArPose(a) {
|
|
|
607
708
|
arPoseNode = a;
|
|
608
709
|
}
|
|
609
710
|
/**
|
|
610
|
-
* Constant matrix converting WebXR local-floor coordinates to the internal
|
|
611
|
-
* NUE (North-Up-East) convention.
|
|
612
|
-
*
|
|
613
|
-
* WebXR: X=East, Y=Up, Z=South (right-handed, toward viewer)
|
|
614
|
-
* NUE: X=North, Y=Up, Z=East (right-handed)
|
|
615
|
-
*
|
|
616
|
-
* Mapping: NUE_X = -WebXR_Z, NUE_Y = WebXR_Y, NUE_Z = WebXR_X
|
|
617
|
-
*
|
|
618
|
-
* Row-major:
|
|
619
|
-
* [ 0 0 -1 0 ]
|
|
620
|
-
* [ 0 1 0 0 ]
|
|
621
|
-
* [ 1 0 0 0 ]
|
|
622
|
-
* [ 0 0 0 1 ]
|
|
623
|
-
*
|
|
624
|
-
* Stored column-major (Three.js / gl-matrix convention).
|
|
625
|
-
*/
|
|
626
|
-
const WEBXR_TO_NUE = new THREE.Matrix4().fromArray([
|
|
627
|
-
0,
|
|
628
|
-
0,
|
|
629
|
-
1,
|
|
630
|
-
0,
|
|
631
|
-
0,
|
|
632
|
-
1,
|
|
633
|
-
0,
|
|
634
|
-
0,
|
|
635
|
-
-1,
|
|
636
|
-
0,
|
|
637
|
-
0,
|
|
638
|
-
0,
|
|
639
|
-
0,
|
|
640
|
-
0,
|
|
641
|
-
0,
|
|
642
|
-
1
|
|
643
|
-
]);
|
|
644
|
-
/**
|
|
645
711
|
* Apply an alignment matrix to the AR world group.
|
|
646
712
|
*
|
|
647
713
|
* The alignment matrix maps odometry positions in NUE space
|
|
@@ -703,27 +769,20 @@ function nueQuaternionToWebXR(nue) {
|
|
|
703
769
|
/**
|
|
704
770
|
* End the current XR session and clean up all resources.
|
|
705
771
|
*
|
|
706
|
-
* Stops the animation loop,
|
|
707
|
-
*
|
|
708
|
-
*
|
|
772
|
+
* Stops the animation loop, ends the XR session, then delegates the full
|
|
773
|
+
* teardown to {@link resetWebXRState} so every module-level reference is
|
|
774
|
+
* cleared (renderer/scene/camera, image-capture, depth, the tracking-phase
|
|
775
|
+
* subscription, the frame-update registry, diagnostics, blit resources).
|
|
776
|
+
* This is the production cleanup path — call it when the AR experience is
|
|
777
|
+
* finished.
|
|
709
778
|
*/
|
|
710
779
|
async function endARSession() {
|
|
711
780
|
if (renderer) renderer.setAnimationLoop(null);
|
|
712
|
-
|
|
713
|
-
await xrSession.end();
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
latestArPose = null;
|
|
717
|
-
if (css3dManager) {
|
|
718
|
-
css3dManager.dispose();
|
|
719
|
-
css3dManager = null;
|
|
720
|
-
}
|
|
721
|
-
if (renderer) {
|
|
722
|
-
if (renderer.domElement.parentElement) renderer.domElement.parentElement.removeChild(renderer.domElement);
|
|
723
|
-
renderer.dispose();
|
|
724
|
-
renderer = null;
|
|
781
|
+
try {
|
|
782
|
+
if (xrSession) await xrSession.end();
|
|
783
|
+
} finally {
|
|
784
|
+
resetWebXRState();
|
|
725
785
|
}
|
|
726
|
-
cleanupBlitResources();
|
|
727
786
|
}
|
|
728
787
|
/**
|
|
729
788
|
* Set up image capture callbacks.
|
|
@@ -755,6 +814,10 @@ function startImageCapture(config, resolutionDivisor = 1) {
|
|
|
755
814
|
log.warn("Cannot start image capture - callbacks not set");
|
|
756
815
|
return;
|
|
757
816
|
}
|
|
817
|
+
if (imageCaptureManager || blitCapture) {
|
|
818
|
+
log.warn("Image capture already running - stopping previous session");
|
|
819
|
+
stopImageCapture();
|
|
820
|
+
}
|
|
758
821
|
const callbacks = {
|
|
759
822
|
getCurrentPose: getCurrentArPose,
|
|
760
823
|
getScreenRotation,
|
|
@@ -802,6 +865,23 @@ function getImageCaptureFrameCount() {
|
|
|
802
865
|
return imageCaptureManager?.getFrameCount() ?? 0;
|
|
803
866
|
}
|
|
804
867
|
/**
|
|
868
|
+
* Inject the Redux store used by the tracking-state slice pipeline.
|
|
869
|
+
*
|
|
870
|
+
* MUST be called before `initAR()` whenever the host also wires tracking
|
|
871
|
+
* callbacks via `setTrackingCallbacks`. Without a store the tracking
|
|
872
|
+
* pipeline silently no-ops.
|
|
873
|
+
*
|
|
874
|
+
* @param store — any store satisfying {@link TrackingSubscribableStore}.
|
|
875
|
+
* `null` clears the binding (useful for teardown in tests).
|
|
876
|
+
*/
|
|
877
|
+
function setTrackingStore(store) {
|
|
878
|
+
if (trackingPhaseUnsubscribe) {
|
|
879
|
+
trackingPhaseUnsubscribe();
|
|
880
|
+
trackingPhaseUnsubscribe = null;
|
|
881
|
+
}
|
|
882
|
+
trackingStore = store;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
805
885
|
* Set up tracking state callbacks.
|
|
806
886
|
* Call this BEFORE initAR() to enable tracking restart detection.
|
|
807
887
|
*
|
|
@@ -887,4 +967,4 @@ function getLiveCss3dManager() {
|
|
|
887
967
|
return css3dManager;
|
|
888
968
|
}
|
|
889
969
|
//#endregion
|
|
890
|
-
export { 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 };
|
|
970
|
+
export { 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, setTrackingStore, shouldLogCameraAccessDiagnostic, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as acquireCameraTexture, n as RendererLike, r as XRCameraLike, t as CameraTextureResult } from "../xr-camera-texture-
|
|
1
|
+
import { i as acquireCameraTexture, n as RendererLike, r as XRCameraLike, t as CameraTextureResult } from "../xr-camera-texture-BU5YvKAM.js";
|
|
2
2
|
export { CameraTextureResult, RendererLike, XRCameraLike, acquireCameraTexture };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as XR_ERROR_MESSAGE_UNKNOWN, r as getXrErrorMessage, t as XR_ERROR_MESSAGES } from "../xr-error-handler-
|
|
1
|
+
import { n as XR_ERROR_MESSAGE_UNKNOWN, r as getXrErrorMessage, t as XR_ERROR_MESSAGES } from "../xr-error-handler-DcUFshNF.js";
|
|
2
2
|
export { XR_ERROR_MESSAGES, XR_ERROR_MESSAGE_UNKNOWN, getXrErrorMessage };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createLogger } from "../utils/logger.js";
|
|
2
|
+
//#region ../src/ar/xr-frame-loop.ts
|
|
3
|
+
/**
|
|
4
|
+
* Per-frame **XR-access** callback registry — the safe seam that lets app
|
|
5
|
+
* code run standard WebXR work (hit-test, light estimation, depth, the WebXR
|
|
6
|
+
* Anchors API, …) without the framework wrapping each capability.
|
|
7
|
+
*
|
|
8
|
+
* The framework owns the single `renderer.setAnimationLoop(onXRFrame)` hook,
|
|
9
|
+
* and the live `XRFrame` never leaves `onXRFrame`. The plain `FrameUpdate`
|
|
10
|
+
* registry (`frame-loop.ts`) only passes `(dt, elapsed)`, which is enough for
|
|
11
|
+
* pose-free ticks but cannot drive hit-test. This registry additionally hands
|
|
12
|
+
* the callback the live `frame`, `referenceSpace`, and `session`.
|
|
13
|
+
*
|
|
14
|
+
* SAFETY CONTRACT (non-negotiable): `frame` / `referenceSpace` / `session` are
|
|
15
|
+
* valid **only synchronously inside the callback**. The `XRFrame` is
|
|
16
|
+
* use-after-frame-unsafe — storing it and reading it on a later tick throws or
|
|
17
|
+
* crashes. Passing them as arguments (never a stashable `getXrFrame()` getter)
|
|
18
|
+
* makes correct use the easy path and stashing the awkward one. Do NOT retain
|
|
19
|
+
* `ctx` or its fields beyond the callback's synchronous execution.
|
|
20
|
+
*
|
|
21
|
+
* See `2026-06-03-threejs-arbutton-minimal-ar-example-user-feedback.md`
|
|
22
|
+
* §6.2/§6.3 (option H-A2) for the design rationale.
|
|
23
|
+
*/
|
|
24
|
+
const log = createLogger("XrFrameLoop");
|
|
25
|
+
const updates = /* @__PURE__ */ new Set();
|
|
26
|
+
/**
|
|
27
|
+
* Register a per-frame XR-access callback. Returns an unregister function.
|
|
28
|
+
*
|
|
29
|
+
* Registration is idempotent — registering the same `fn` twice is a no-op
|
|
30
|
+
* (it remains a single entry in the underlying `Set`).
|
|
31
|
+
*/
|
|
32
|
+
function registerXrFrameUpdate(fn) {
|
|
33
|
+
updates.add(fn);
|
|
34
|
+
return () => {
|
|
35
|
+
updates.delete(fn);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Invoke all registered XR-access callbacks. Called by the WebXR session's
|
|
40
|
+
* `onXRFrame` once per frame, only when a live `frame` / `referenceSpace` /
|
|
41
|
+
* `session` are all available.
|
|
42
|
+
*
|
|
43
|
+
* The set is snapshotted before iterating so that
|
|
44
|
+
* `registerXrFrameUpdate` / unregister calls made by a handler during the
|
|
45
|
+
* same frame are deferred to the next tick — mirroring `runFrameUpdates`.
|
|
46
|
+
*
|
|
47
|
+
* Each callback is invoked in its own `try/catch`: this registry is the public
|
|
48
|
+
* app seam, so a bug in one app-registered callback (which throws every frame)
|
|
49
|
+
* must not abort the remaining callbacks nor propagate up through `onXRFrame`
|
|
50
|
+
* and kill the scene render for the whole session. Failures are logged and the
|
|
51
|
+
* loop continues — mirroring `runFrameUpdates`.
|
|
52
|
+
*/
|
|
53
|
+
function runXrFrameUpdates(ctx) {
|
|
54
|
+
const snapshot = Array.from(updates);
|
|
55
|
+
for (const fn of snapshot) try {
|
|
56
|
+
fn(ctx);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
log.error("A registered XrFrameUpdate threw; continuing the loop", error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Clear all registrations. Called from `resetWebXRState()` so a fresh
|
|
63
|
+
* session starts with an empty registry.
|
|
64
|
+
*/
|
|
65
|
+
function clearXrFrameUpdates() {
|
|
66
|
+
updates.clear();
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { clearXrFrameUpdates, registerXrFrameUpdate, runXrFrameUpdates };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { t as SubscribableStore } from "./subscribe-to-selector--KQpgLXG.js";
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
|
|
4
|
+
//#region ../src/visualization/ar-world-group-alignment.d.ts
|
|
5
|
+
interface ArWorldGroupAlignmentOptions {
|
|
6
|
+
/** The store whose alignment selector drives the lerper. */
|
|
7
|
+
readonly store: SubscribableStore;
|
|
8
|
+
/** The AR world group whose `.matrix` is lerped toward each alignment. */
|
|
9
|
+
readonly arWorldGroup: THREE.Object3D;
|
|
10
|
+
/**
|
|
11
|
+
* Lerp speed multiplier forwarded to {@link createAlignmentLerper}
|
|
12
|
+
* (default matches the lerper's own `DEFAULT_LERP_RATE`).
|
|
13
|
+
*/
|
|
14
|
+
readonly lerpRate?: number;
|
|
15
|
+
}
|
|
16
|
+
interface ArWorldGroupAlignmentHandle {
|
|
17
|
+
/** Stop the subscription and the per-frame update. */
|
|
18
|
+
dispose(): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Begin applying the store's (lerped) alignment to `arWorldGroup`. Returns a
|
|
22
|
+
* handle whose `dispose()` removes both the store subscription and the
|
|
23
|
+
* per-frame update.
|
|
24
|
+
*
|
|
25
|
+
* Disposal is tied to the AR session lifecycle: the binding registers itself
|
|
26
|
+
* with the session-disposer registry that `resetWebXRState()` flushes on
|
|
27
|
+
* teardown, so a caller can `enableArWorldGroupAlignment(...)` once and never
|
|
28
|
+
* hold the handle — the binding cannot outlive the session (which is what two
|
|
29
|
+
* apps independently leaked: the store subscription survives a teardown because
|
|
30
|
+
* `clearFrameUpdates` only drops the per-frame tick). Because `initAR()` throws
|
|
31
|
+
* while a prior session is live, every restart passes through that flush. The
|
|
32
|
+
* handle is still returned for callers that want to stop alignment mid-session,
|
|
33
|
+
* and is idempotent + self-deregistering so a manual `dispose()` and the
|
|
34
|
+
* teardown flush never double-run. See
|
|
35
|
+
* gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-06-08-arworldgroup-alignment-session-scoped-disposal.md.
|
|
36
|
+
*
|
|
37
|
+
* Double-drive is still the caller's concern: the recorder owns its own lerper
|
|
38
|
+
* and must NOT also call this (it would double-lerp the same group). It does not
|
|
39
|
+
* call this helper, so flushing the registry on teardown never affects it.
|
|
40
|
+
*/
|
|
41
|
+
declare function enableArWorldGroupAlignment(options: ArWorldGroupAlignmentOptions): ArWorldGroupAlignmentHandle;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { ArWorldGroupAlignmentOptions as n, enableArWorldGroupAlignment as r, ArWorldGroupAlignmentHandle as t };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region ../src/ar/capability-checker.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Pure capability-gating helpers, promoted from the AnchorStarter app so the
|
|
4
|
+
* minimal AR example and AnchorStarter share the *decision* and *message*
|
|
5
|
+
* without sharing app-specific copy.
|
|
6
|
+
*
|
|
7
|
+
* The async feature probing (`isWebXRSupported()` / geolocation availability)
|
|
8
|
+
* stays in each app's `main.ts`; only the browser-free decision and the
|
|
9
|
+
* user-facing message live here so they can be unit-tested without a browser.
|
|
10
|
+
*
|
|
11
|
+
* See `2026-06-03-threejs-arbutton-minimal-ar-example-user-feedback.md` §6.4.
|
|
12
|
+
*/
|
|
13
|
+
/** The two capabilities every GPS+AR demo requires to run. */
|
|
14
|
+
interface CapabilitySupport {
|
|
15
|
+
/** Browser supports a WebXR `immersive-ar` session. */
|
|
16
|
+
readonly webxr: boolean;
|
|
17
|
+
/** Geolocation API is available. */
|
|
18
|
+
readonly geolocation: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Options that let an app flavour the otherwise-neutral gating message. */
|
|
21
|
+
interface CapabilityMessageOptions {
|
|
22
|
+
/**
|
|
23
|
+
* App-specific "what this demo lets you do" phrase, appended as
|
|
24
|
+
* `…outdoors, to try ${contextLabel}.`. Omit for a neutral message that
|
|
25
|
+
* ends at `…outdoors.`. Example: `'the persistent-anchor flow'`.
|
|
26
|
+
*/
|
|
27
|
+
readonly contextLabel?: string;
|
|
28
|
+
}
|
|
29
|
+
/** True only when both AR and GPS are available — the demo can run. */
|
|
30
|
+
declare function isFullySupported(support: CapabilitySupport): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Build the capability-gated message shown when the demo cannot run. Names
|
|
33
|
+
* exactly which capabilities are missing so the user understands *why*, and
|
|
34
|
+
* always points them at the supported environment (an AR phone, outdoors).
|
|
35
|
+
* Returns `null` when everything is supported (no message needed).
|
|
36
|
+
*
|
|
37
|
+
* The trailing "to try …" clause is app-specific and supplied via
|
|
38
|
+
* `options.contextLabel`; without it the message ends neutrally at
|
|
39
|
+
* "…outdoors.".
|
|
40
|
+
*/
|
|
41
|
+
declare function capabilityMessage(support: CapabilitySupport, options?: CapabilityMessageOptions): string | null;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { isFullySupported as i, CapabilitySupport as n, capabilityMessage as r, CapabilityMessageOptions as t };
|