gps-plus-slam-app-framework 1.0.7 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -15
- package/dist/{app-selectors-BeU0_ab8.d.ts → app-selectors-CcZ5UV-8.d.ts} +1 -1
- package/dist/ar/bresenham3d.d.ts +2 -0
- package/dist/ar/bresenham3d.js +81 -0
- package/dist/ar/camera-blit-capture.d.ts +1 -1
- package/dist/ar/camera-blit-capture.js +30 -2
- 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 +3 -3
- package/dist/ar/depth-sampler.js +63 -8
- package/dist/ar/depth-unprojection.d.ts +2 -0
- package/dist/ar/depth-unprojection.js +94 -0
- 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 +1 -1
- package/dist/ar/frame-loop.js +24 -1
- package/dist/ar/image-capture.d.ts +1 -1
- package/dist/ar/image-capture.js +2 -1
- package/dist/ar/index.d.ts +19 -12
- package/dist/ar/index.js +11 -4
- package/dist/ar/occupancy-grid.d.ts +2 -0
- package/dist/ar/occupancy-grid.js +164 -0
- package/dist/ar/replay-scene.d.ts +1 -1
- package/dist/ar/replay-scene.js +1 -1
- package/dist/ar/scene-node-names.d.ts +1 -1
- package/dist/ar/webxr-nue-basis.d.ts +2 -0
- package/dist/ar/webxr-nue-basis.js +54 -0
- package/dist/ar/webxr-session.d.ts +4 -4
- package/dist/ar/webxr-session.js +1 -984
- 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-types-Ck8LEq8k.d.ts → ar-types-Dan1MVTi.d.ts} +28 -5
- package/dist/ar-world-group-alignment-ColHB_5A.d.ts +43 -0
- package/dist/bresenham3d-CFfd7n6B.d.ts +59 -0
- package/dist/{camera-blit-capture-C1V9I1Z6.d.ts → camera-blit-capture-D5_mNhla.d.ts} +20 -0
- package/dist/capability-checker-CQXJHxXg.d.ts +43 -0
- package/dist/chromium-camera-access-workaround-Jqbn0apH.d.ts +137 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/{create-slam-app-store-CNsZ3rcX.d.ts → create-slam-app-store-qNXQ-tq5.d.ts} +5 -5
- package/dist/depth-sampler-Bhanfwla.d.ts +169 -0
- package/dist/depth-unprojection-Y6i_mJuT.d.ts +52 -0
- package/dist/enable-gps-ar-CapmADVN.d.ts +95 -0
- package/dist/{file-system-DTApEv_Q.d.ts → file-system-C_eqiEA8.d.ts} +1 -1
- package/dist/frame-conversions-BfsafGWR.d.ts +54 -0
- package/dist/{frame-loop-QJIxLin0.d.ts → frame-loop-CMewS8cY.d.ts} +5 -0
- package/dist/{fused-path-CB4DwWOG.d.ts → fused-path-DJoVu98b.d.ts} +1 -1
- package/dist/geo/h3-proximity.d.ts +1 -1
- package/dist/geo/index.d.ts +1 -1
- package/dist/{gps-anchor-CnWY61XK.d.ts → gps-anchor-dYeCvvp5.d.ts} +12 -1
- package/dist/{gps-ar-pose-sampler-DsM1Kb2N.d.ts → gps-ar-pose-sampler-DVb96iOy.d.ts} +1 -1
- package/dist/{gps-event-coordinator-DBx9I1YX.d.ts → gps-event-coordinator-BDIQde2x.d.ts} +3 -3
- package/dist/hit-test-reticle-BEzEybxZ.d.ts +45 -0
- package/dist/{image-capture-DZLxHDN9.d.ts → image-capture-DZ4HFRgS.d.ts} +15 -2
- package/dist/index-KJq2amHF.d.ts +37 -0
- package/dist/index.d.ts +67 -58
- package/dist/index.js +18 -8
- package/dist/{leaflet-map-overlay-Cew7XAoO.d.ts → leaflet-map-overlay-6CJC-E1l.d.ts} +1 -1
- package/dist/{map-data-sih4g64v.d.ts → map-data-CkloBxIl.d.ts} +1 -1
- package/dist/{map-overlay-draw-B3R1c9Xp.d.ts → map-overlay-draw-krOKwDAq.d.ts} +1 -1
- package/dist/{null-storage-backend-BKOS7JpL.d.ts → null-storage-backend-CxEjZHRW.d.ts} +2 -2
- package/dist/occupancy-grid-BRNRaPyT.d.ts +72 -0
- package/dist/{opfs-storage-backend-Cj_7PWoq.d.ts → opfs-storage-backend-CjSAuqg_.d.ts} +2 -2
- package/dist/{persistence-middleware-BkHIqvaA.d.ts → persistence-middleware-DwyzgfWr.d.ts} +1 -1
- package/dist/{recording-options-Dq6IsKAv.d.ts → recording-options-DJBXuMxV.d.ts} +22 -6
- package/dist/{recording-replayer-CmCIIZ02.d.ts → recording-replayer-BL-x-7k0.d.ts} +2 -2
- package/dist/{recording-slice-d6OtCrza.d.ts → recording-slice-89O07ebs.d.ts} +10 -2
- package/dist/{replay-engine-BGBRQyXs.d.ts → replay-engine-BRqszuQ9.d.ts} +1 -1
- package/dist/{replay-scene-DpAGcAKY.d.ts → replay-scene-Bw58VL6p.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/index.d.ts +3 -3
- package/dist/sensors/index.js +1 -1
- package/dist/sensors/permission-checker.d.ts +1 -1
- package/dist/session-disposers-M-oashRH.js +57 -0
- package/dist/state/app-selectors.d.ts +1 -1
- package/dist/state/combined-root-state.d.ts +1 -1
- package/dist/state/create-slam-app-store.d.ts +1 -1
- 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 -17
- package/dist/state/index.js +2 -2
- package/dist/state/persistence-middleware.d.ts +1 -1
- package/dist/state/persistence-middleware.js +9 -3
- package/dist/state/recording-options.d.ts +1 -1
- package/dist/state/recording-options.js +5 -3
- package/dist/state/recording-replayer.d.ts +2 -2
- package/dist/state/recording-slice.d.ts +1 -1
- package/dist/state/recording-slice.js +11 -2
- package/dist/state/replay-engine.d.ts +1 -1
- package/dist/state/store-subscribers.d.ts +2 -2
- package/dist/state/subscribe-to-selector.d.ts +1 -1
- package/dist/state/tracking-quality.d.ts +1 -1
- package/dist/state/tracking-quality.js +1 -1
- package/dist/state/tracking-slice.d.ts +1 -1
- package/dist/state-BkIfpiyA.js +87 -0
- package/dist/storage/file-system-utils.d.ts +1 -1
- package/dist/storage/file-system.d.ts +1 -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-backend-CT0Il_AB.d.ts → storage-backend-C9HLFtgz.d.ts} +1 -1
- package/dist/{store-subscribers-Dce_CoJF.d.ts → store-subscribers-rrkLkFTt.d.ts} +2 -2
- package/dist/{subscribe-to-selector-DIHm3BRZ.d.ts → subscribe-to-selector-CP3BKSbF.d.ts} +1 -1
- package/dist/test-utils/browser-mocks.d.ts +1 -1
- package/dist/test-utils/zip-round-trip-helpers.d.ts +1 -1
- package/dist/{tracking-slice-utCNND8t.d.ts → tracking-slice-CdK8c-8P.d.ts} +1 -1
- package/dist/types/ar-types.d.ts +2 -2
- package/dist/types/geo-types.d.ts +1 -1
- 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/visualization/accuracy-circles.d.ts +1 -1
- 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 +1 -1
- package/dist/visualization/gps-anchor.d.ts +1 -1
- package/dist/visualization/gps-anchor.js +216 -1
- package/dist/visualization/gps-compass-cubes.d.ts +1 -1
- package/dist/visualization/gps-event-markers.d.ts +1 -1
- package/dist/visualization/gps-event-markers.js +1 -1
- 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 -17
- package/dist/visualization/index.js +5 -2
- package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
- package/dist/visualization/lerp-utils.d.ts +1 -1
- package/dist/visualization/map-data.d.ts +1 -1
- package/dist/visualization/map-overlay-draw.d.ts +1 -1
- 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-CqKQmZzT.d.ts +23 -0
- package/dist/{webxr-session-DB5cIZnN.d.ts → webxr-session-DtAG9srK.d.ts} +45 -13
- package/dist/webxr-session-QhcSSub5.js +1041 -0
- package/dist/xr-frame-loop-BtYytZJg.d.ts +70 -0
- package/package.json +1 -1
- package/dist/chromium-camera-access-workaround-NvChXsi8.d.ts +0 -61
- package/dist/depth-sampler-xg8vna6c.d.ts +0 -108
- package/dist/gps-anchor-DjC_3MoT.js +0 -257
- package/dist/index-pOjUCKEP.d.ts +0 -24
- /package/dist/{accuracy-circles-CsxE_7x1.d.ts → accuracy-circles-BpwWAs8G.d.ts} +0 -0
- /package/dist/{alignment-lerper-L1RbWHQ4.d.ts → alignment-lerper-Dm9G7JJO.d.ts} +0 -0
- /package/dist/{camera-follower-1k8mnw5J.d.ts → camera-follower-C2mwEn7c.d.ts} +0 -0
- /package/dist/{capture-failure-tracker-JKb1ST8E.d.ts → capture-failure-tracker-CUeU3L_M.d.ts} +0 -0
- /package/dist/{concurrency-CMw8pSaG.d.ts → concurrency-CO1FQvs3.d.ts} +0 -0
- /package/dist/{css3d-renderer-manager-CkjCb2HW.d.ts → css3d-renderer-manager-ptu_t3B3.d.ts} +0 -0
- /package/dist/{failure-tracker-DnHXOIRa.d.ts → failure-tracker-Cn8Fo9D4.d.ts} +0 -0
- /package/dist/{file-system-utils-CtKiiw27.d.ts → file-system-utils-DyAmcuvX.d.ts} +0 -0
- /package/dist/{format-file-size-BTS5H62-.d.ts → format-file-size-CLCZz3lC.d.ts} +0 -0
- /package/dist/{frustum-visibility-BHnFrSKQ.d.ts → frustum-visibility-CmI96ESS.d.ts} +0 -0
- /package/dist/{geo-types-CTuF23gG.d.ts → geo-types-SaPnIEUM.d.ts} +0 -0
- /package/dist/{gps-DXoQb9Kf.d.ts → gps-VuezJWK3.d.ts} +0 -0
- /package/dist/{gps-compass-cubes-CsLtFv4B.d.ts → gps-compass-cubes-BM1uGiYV.d.ts} +0 -0
- /package/dist/{gps-error-handler-Dg3ziRVY.d.ts → gps-error-handler-CJl_2MVm.d.ts} +0 -0
- /package/dist/{gps-event-markers-BGr-iLvi.d.ts → gps-event-markers-Dc9FT-q1.d.ts} +0 -0
- /package/dist/{h3-proximity-BhfgNbdW.d.ts → h3-proximity-I1msGPgE.d.ts} +0 -0
- /package/dist/{index-gzWJMVpo.d.ts → index-CF05QV2m.d.ts} +0 -0
- /package/dist/{lerp-utils-B8MM8zCR.d.ts → lerp-utils-BpV-0Dtk.d.ts} +0 -0
- /package/dist/{list-formatter-CNqi-E2j.d.ts → list-formatter-RRcBe8Vg.d.ts} +0 -0
- /package/dist/{logger-C9vugiQb.d.ts → logger-BEHXsnnq.d.ts} +0 -0
- /package/dist/{map-overlay-DAmreyA3.d.ts → map-overlay-bcogY5fK.d.ts} +0 -0
- /package/dist/{opfs-storage-DOPkzgeT.d.ts → opfs-storage-DYm8WHq7.d.ts} +0 -0
- /package/dist/{permission-checker-DPJl_RU6.d.ts → permission-checker-GpAiSCwd.d.ts} +0 -0
- /package/dist/{scene-node-names-6LBjPmwz.d.ts → scene-node-names-Bu0S66H1.d.ts} +0 -0
- /package/dist/{three-dispose-C2drkp6f.d.ts → three-dispose-D950t71B.d.ts} +0 -0
- /package/dist/{vis-colors-CHQhjal3.d.ts → vis-colors-D6omC64K.d.ts} +0 -0
- /package/dist/{xr-camera-texture-BU5YvKAM.d.ts → xr-camera-texture-DBwCaKTd.d.ts} +0 -0
- /package/dist/{xr-error-handler-CDY5QxGb.d.ts → xr-error-handler-C0AQRmxs.d.ts} +0 -0
- /package/dist/{zip-export-QO4iLIi7.d.ts → zip-export-D_5HAvFz.d.ts} +0 -0
- /package/dist/{zip-reader-D1105idz.d.ts → zip-reader-DSEXt7K6.d.ts} +0 -0
package/dist/ar/webxr-session.js
CHANGED
|
@@ -1,985 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CameraBlitCapture, computeCaptureSize } from "./camera-blit-capture.js";
|
|
3
|
-
import { getLastDeviceOrientation } from "../state/gps-event-coordinator.js";
|
|
4
|
-
import { DepthSampler } from "./depth-sampler.js";
|
|
5
|
-
import { clearFrameUpdates, runFrameUpdates } from "./frame-loop.js";
|
|
6
|
-
import { DEFAULT_CAPTURE_CONFIG, ImageCaptureManager } from "./image-capture.js";
|
|
7
|
-
import { clearLastRestartedPayload, originReset, poseLost, poseReceived, resetTracking, selectLastRestartedPayload, selectTrackingPhase } from "../state/tracking-slice.js";
|
|
8
|
-
import { acquireCameraTexture } from "./xr-camera-texture.js";
|
|
9
|
-
import { DEFAULT_RECORDING_OPTIONS, validateArCrashIsolationOptions } from "../state/recording-options.js";
|
|
10
|
-
import { SCENE_NODE } from "./scene-node-names.js";
|
|
11
|
-
import { createCss3dRendererManager } from "../visualization/css3d-renderer-manager.js";
|
|
12
|
-
import * as THREE from "three";
|
|
13
|
-
import { nueQuaternionToWebXR as nueQuaternionToWebXR$1, nueToWebXR } from "gps-plus-slam-js";
|
|
14
|
-
//#region ../src/ar/webxr-session.ts
|
|
15
|
-
/**
|
|
16
|
-
* WebXR Session Module
|
|
17
|
-
*
|
|
18
|
-
* Handles WebXR AR session initialization and frame loop.
|
|
19
|
-
*
|
|
20
|
-
* ARCHITECTURE NOTE: See docs/architecture-ar-gps-pose-separation.md
|
|
21
|
-
*
|
|
22
|
-
* Scene Hierarchy:
|
|
23
|
-
* scene (GPS world frame — NUE: X=North, Y=Up, Z=East)
|
|
24
|
-
* └── arWorldGroup (local space = NUE; receives alignment matrix)
|
|
25
|
-
* └── basisChangeNode ('webxr-to-nue', constant WEBXR_TO_NUE matrix)
|
|
26
|
-
* └── arpose (local space = WebXR)
|
|
27
|
-
* └── camera (raw AR pose from WebXR)
|
|
28
|
-
*
|
|
29
|
-
* The arWorldGroup is transformed by the GpsPlusSlamJs alignment matrix.
|
|
30
|
-
* The camera's LOCAL transform is the raw AR pose from WebXR.
|
|
31
|
-
* The camera's WORLD transform is the GPS-aligned world pose.
|
|
32
|
-
*/
|
|
33
|
-
const log = createLogger("WebXR");
|
|
34
|
-
function isXRCameraLike(value) {
|
|
35
|
-
if (typeof value !== "object" || value === null) return false;
|
|
36
|
-
const camera = value;
|
|
37
|
-
return typeof camera.width === "number" && Number.isFinite(camera.width) && camera.width > 0 && typeof camera.height === "number" && Number.isFinite(camera.height) && camera.height > 0;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Extract a validated XRCameraLike from an XRViewerPose.
|
|
41
|
-
*
|
|
42
|
-
* Returns null in every case where the per-frame texture acquisition must be
|
|
43
|
-
* skipped — i.e. whenever the caller should treat any previously cached
|
|
44
|
-
* texture as stale and clear it. Centralising the preconditions here makes
|
|
45
|
-
* it impossible to accidentally fall through without clearing the cache.
|
|
46
|
-
*
|
|
47
|
-
* Cases that return null:
|
|
48
|
-
* - pose is null (tracking lost)
|
|
49
|
-
* - pose has no views
|
|
50
|
-
* - the first view has no `camera` property (camera-access not granted)
|
|
51
|
-
* - the camera property is not a valid XRCameraLike (zero/NaN dimensions)
|
|
52
|
-
*
|
|
53
|
-
* @see docs/2026-02-06-bug-camera-frames-black.md
|
|
54
|
-
*/
|
|
55
|
-
function getXrCameraFromPose(pose) {
|
|
56
|
-
if (!pose) return null;
|
|
57
|
-
const view = pose.views[0];
|
|
58
|
-
if (!view) return null;
|
|
59
|
-
const candidate = view.camera;
|
|
60
|
-
return isXRCameraLike(candidate) ? candidate : null;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Decide whether the one-time camera-access grant diagnostic should fire
|
|
64
|
-
* on the current XR frame.
|
|
65
|
-
*
|
|
66
|
-
* The diagnostic is only meaningful when we actually have a valid pose:
|
|
67
|
-
* if `pose` is null (e.g. session starts with tracking lost), then the
|
|
68
|
-
* derived `xrCamera` is null regardless of permission state, and logging
|
|
69
|
-
* "NOT GRANTED" would be a false negative that permanently suppresses the
|
|
70
|
-
* correct status because `cameraAccessLoggedOnce` would latch to true.
|
|
71
|
-
*
|
|
72
|
-
* Additional conditions:
|
|
73
|
-
* - `alreadyLogged` prevents spamming the log every frame.
|
|
74
|
-
* - `captureActive` ensures we only care when an image capture session
|
|
75
|
-
* is running (otherwise the diagnostic has no audience).
|
|
76
|
-
*
|
|
77
|
-
* This predicate is extracted as a pure function so the guard against the
|
|
78
|
-
* "first frame has no pose" false-negative regression is directly testable.
|
|
79
|
-
*/
|
|
80
|
-
function shouldLogCameraAccessDiagnostic(pose, alreadyLogged, captureActive) {
|
|
81
|
-
return pose !== null && !alreadyLogged && captureActive;
|
|
82
|
-
}
|
|
83
|
-
let renderer = null;
|
|
84
|
-
let scene = null;
|
|
85
|
-
let camera = null;
|
|
86
|
-
let xrSession = null;
|
|
87
|
-
/**
|
|
88
|
-
* Monotonic time of the previous XR frame, in milliseconds (XR `time`
|
|
89
|
-
* argument). Reset to 0 by `resetWebXRState()` so the first frame of a
|
|
90
|
-
* new session sees `dt = 0` rather than a stale delta from the prior
|
|
91
|
-
* session.
|
|
92
|
-
*/
|
|
93
|
-
let lastFrameTime = 0;
|
|
94
|
-
/**
|
|
95
|
-
* Reset WebXR module state - exported for testing only.
|
|
96
|
-
* @internal
|
|
97
|
-
*/
|
|
98
|
-
function resetWebXRState() {
|
|
99
|
-
if (renderer) {
|
|
100
|
-
renderer.setAnimationLoop(null);
|
|
101
|
-
if (renderer.domElement.parentElement) renderer.domElement.parentElement.removeChild(renderer.domElement);
|
|
102
|
-
renderer.dispose();
|
|
103
|
-
}
|
|
104
|
-
renderer = null;
|
|
105
|
-
scene = null;
|
|
106
|
-
camera = null;
|
|
107
|
-
xrSession = null;
|
|
108
|
-
arWorldGroup = null;
|
|
109
|
-
arPoseNode = null;
|
|
110
|
-
latestArPose = null;
|
|
111
|
-
lastFrameTime = 0;
|
|
112
|
-
clearFrameUpdates();
|
|
113
|
-
imageCaptureManager = null;
|
|
114
|
-
onImageCaptured = null;
|
|
115
|
-
getScreenRotation = null;
|
|
116
|
-
onCaptureFailed = null;
|
|
117
|
-
onSuspiciousImage = null;
|
|
118
|
-
if (trackingPhaseUnsubscribe) {
|
|
119
|
-
trackingPhaseUnsubscribe();
|
|
120
|
-
trackingPhaseUnsubscribe = null;
|
|
121
|
-
}
|
|
122
|
-
trackingStore = null;
|
|
123
|
-
onTrackingRestarted = null;
|
|
124
|
-
onTrackingLost = null;
|
|
125
|
-
onTrackingRecovered = null;
|
|
126
|
-
depthSampler = null;
|
|
127
|
-
onDepthCaptured = null;
|
|
128
|
-
onDepthUnavailable = null;
|
|
129
|
-
onFrameCallback = null;
|
|
130
|
-
if (css3dManager) {
|
|
131
|
-
css3dManager.dispose();
|
|
132
|
-
css3dManager = null;
|
|
133
|
-
}
|
|
134
|
-
cameraAccessLoggedOnce = false;
|
|
135
|
-
getCameraTextureNullCount = 0;
|
|
136
|
-
latestCameraWidth = 0;
|
|
137
|
-
latestCameraHeight = 0;
|
|
138
|
-
currentArCrashIsolationOptions = { ...DEFAULT_RECORDING_OPTIONS.arCrashIsolation };
|
|
139
|
-
cleanupBlitResources();
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* The AR world group - parent of camera and all AR-tracked content.
|
|
143
|
-
* This group's transform = the alignment matrix from GpsPlusSlamJs.
|
|
144
|
-
* When the library computes a new alignment, apply it to this group.
|
|
145
|
-
*/
|
|
146
|
-
let arWorldGroup = null;
|
|
147
|
-
/**
|
|
148
|
-
* The arpose Object3D — intermediate node between arWorldGroup and camera.
|
|
149
|
-
* During recording it stays at identity; during replay it receives recorded
|
|
150
|
-
* odomPosition/odomRotation from the store subscriber.
|
|
151
|
-
*/
|
|
152
|
-
let arPoseNode = null;
|
|
153
|
-
/**
|
|
154
|
-
* Stores the latest raw AR pose from WebXR (updated every frame).
|
|
155
|
-
* This is read by the GPS callback to create paired GPS+AR events.
|
|
156
|
-
*/
|
|
157
|
-
let latestArPose = null;
|
|
158
|
-
/**
|
|
159
|
-
* Image capture manager instance (created when AR session starts)
|
|
160
|
-
*/
|
|
161
|
-
let imageCaptureManager = null;
|
|
162
|
-
/**
|
|
163
|
-
* Callback for when an image is captured (set via setImageCaptureCallback)
|
|
164
|
-
*/
|
|
165
|
-
let onImageCaptured = null;
|
|
166
|
-
/**
|
|
167
|
-
* Callback for when image capture fails (set via setImageCaptureCallback)
|
|
168
|
-
*/
|
|
169
|
-
let onCaptureFailed = null;
|
|
170
|
-
/**
|
|
171
|
-
* Callback for when a captured image appears suspicious/black
|
|
172
|
-
* (set via setImageCaptureCallback)
|
|
173
|
-
*/
|
|
174
|
-
let onSuspiciousImage = null;
|
|
175
|
-
/**
|
|
176
|
-
* Screen rotation getter (set via setImageCaptureCallback)
|
|
177
|
-
*/
|
|
178
|
-
let getScreenRotation = null;
|
|
179
|
-
/**
|
|
180
|
-
* Redux store injected by the host (`setTrackingStore`). When present
|
|
181
|
-
* together with `onTrackingRestarted`, `onXRFrame` dispatches
|
|
182
|
-
* `poseReceived`/`poseLost`, the XR reference-space reset listener
|
|
183
|
-
* dispatches `originReset`, and a `subscribeToSelector` translation
|
|
184
|
-
* surface translates phase transitions back into the existing
|
|
185
|
-
* `onTrackingLost` / `onTrackingRestarted` / `onTrackingRecovered`
|
|
186
|
-
* callbacks. When the store is absent the tracking pipeline simply
|
|
187
|
-
* no-ops.
|
|
188
|
-
*/
|
|
189
|
-
let trackingStore = null;
|
|
190
|
-
/**
|
|
191
|
-
* Unsubscribe handle returned by the phase subscription set up in
|
|
192
|
-
* `initAR`. Cleared in `resetWebXRState` and on session-end so we never
|
|
193
|
-
* leave dangling listeners on a stale store.
|
|
194
|
-
*/
|
|
195
|
-
let trackingPhaseUnsubscribe = null;
|
|
196
|
-
/**
|
|
197
|
-
* Callback for when tracking restarts (set via setTrackingCallbacks)
|
|
198
|
-
*/
|
|
199
|
-
let onTrackingRestarted = null;
|
|
200
|
-
/**
|
|
201
|
-
* Callback for when tracking is lost (set via setTrackingLostCallback)
|
|
202
|
-
* Field Test Readiness Issue #3: Provide user feedback when tracking is lost
|
|
203
|
-
*/
|
|
204
|
-
let onTrackingLost = null;
|
|
205
|
-
/**
|
|
206
|
-
* Callback for when tracking recovers seamlessly without origin reset (Case 1).
|
|
207
|
-
* Set via setTrackingRecoveredCallback.
|
|
208
|
-
*/
|
|
209
|
-
let onTrackingRecovered = null;
|
|
210
|
-
/**
|
|
211
|
-
* Depth sampler instance (created when AR session starts with depth callbacks)
|
|
212
|
-
*/
|
|
213
|
-
let depthSampler = null;
|
|
214
|
-
/**
|
|
215
|
-
* Callback for when a depth sample is captured (set via setDepthCaptureCallback)
|
|
216
|
-
*/
|
|
217
|
-
let onDepthCaptured = null;
|
|
218
|
-
/**
|
|
219
|
-
* Callback for when depth sensing is determined to be unavailable.
|
|
220
|
-
* Field Test Readiness Issue #8: Notify user if depth was requested but not granted.
|
|
221
|
-
*/
|
|
222
|
-
let onDepthUnavailable = null;
|
|
223
|
-
/**
|
|
224
|
-
* Per-frame callback for custom updates (e.g., map overlay position).
|
|
225
|
-
* Called every XR frame after pose updates but before render.
|
|
226
|
-
*/
|
|
227
|
-
let onFrameCallback = null;
|
|
228
|
-
/**
|
|
229
|
-
* CSS3D renderer manager for rendering DOM-based 3D objects (e.g., Leaflet map)
|
|
230
|
-
* alongside the WebGL render. Created in initAR(), disposed in resetWebXRState().
|
|
231
|
-
*/
|
|
232
|
-
let css3dManager = null;
|
|
233
|
-
/**
|
|
234
|
-
* Camera blit capture instance for reading WebXR opaque camera textures.
|
|
235
|
-
* Created when image capture starts, disposed when it stops.
|
|
236
|
-
* @see docs/2026-02-06-bug-camera-frames-black.md
|
|
237
|
-
*/
|
|
238
|
-
let blitCapture = null;
|
|
239
|
-
/**
|
|
240
|
-
* Latest WebXR camera texture, updated each frame when camera-access is enabled.
|
|
241
|
-
* Acquired via Three.js's renderer.xr.getCameraTexture() API (ExternalTexture).
|
|
242
|
-
* @see xr-camera-texture.ts
|
|
243
|
-
*/
|
|
244
|
-
let latestCameraTexture = null;
|
|
245
|
-
/**
|
|
246
|
-
* Latest camera frame dimensions from XRCamera (native resolution).
|
|
247
|
-
* Used to dynamically resize the blit render target for full-quality captures.
|
|
248
|
-
*/
|
|
249
|
-
let latestCameraWidth = 0;
|
|
250
|
-
let latestCameraHeight = 0;
|
|
251
|
-
/**
|
|
252
|
-
* Track whether camera-access diagnostic status has been logged.
|
|
253
|
-
* Reset on each new session via resetWebXRState().
|
|
254
|
-
*/
|
|
255
|
-
let cameraAccessLoggedOnce = false;
|
|
256
|
-
/**
|
|
257
|
-
* Counter for throttled getCameraTexture diagnostic logging.
|
|
258
|
-
* Only logs the first few null returns to avoid log spam.
|
|
259
|
-
*/
|
|
260
|
-
let getCameraTextureNullCount = 0;
|
|
261
|
-
const GET_CAMERA_TEXTURE_LOG_THRESHOLD = 5;
|
|
262
|
-
let currentArCrashIsolationOptions = { ...DEFAULT_RECORDING_OPTIONS.arCrashIsolation };
|
|
263
|
-
/**
|
|
264
|
-
* Dispose blit capture resources and clear the cached camera texture.
|
|
265
|
-
* Shared by resetWebXRState() and stopImageCapture() to avoid duplication.
|
|
266
|
-
*/
|
|
267
|
-
function cleanupBlitResources() {
|
|
268
|
-
if (blitCapture) {
|
|
269
|
-
blitCapture.dispose();
|
|
270
|
-
blitCapture = null;
|
|
271
|
-
}
|
|
272
|
-
latestCameraTexture = null;
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Extract the reset transform from an XRReferenceSpaceEvent-like object.
|
|
276
|
-
*
|
|
277
|
-
* Distinguishes three cases per OdometryTrackingRestartedPayload semantics:
|
|
278
|
-
* - Transform property missing (older browsers) → returns `undefined`
|
|
279
|
-
* - Transform property present but null (runtime can't determine delta) → returns `null`
|
|
280
|
-
* - Transform property present with data → returns `ResetTransformData`
|
|
281
|
-
*
|
|
282
|
-
* This is a pure function extracted for testability.
|
|
283
|
-
*
|
|
284
|
-
* @param event - The event object, cast to a record with an optional transform property
|
|
285
|
-
* @returns ResetTransformData, null, or undefined
|
|
286
|
-
*/
|
|
287
|
-
function extractResetTransformData(event) {
|
|
288
|
-
if (!("transform" in event)) return;
|
|
289
|
-
const transform = event.transform;
|
|
290
|
-
if (!transform) return null;
|
|
291
|
-
const pos = transform.position;
|
|
292
|
-
const ori = transform.orientation;
|
|
293
|
-
return {
|
|
294
|
-
position: [
|
|
295
|
-
pos.x,
|
|
296
|
-
pos.y,
|
|
297
|
-
pos.z
|
|
298
|
-
],
|
|
299
|
-
orientation: [
|
|
300
|
-
ori.x,
|
|
301
|
-
ori.y,
|
|
302
|
-
ori.z,
|
|
303
|
-
ori.w
|
|
304
|
-
]
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Extract pose data from an XRViewerPose.
|
|
309
|
-
* Returns null if pose or views are unavailable.
|
|
310
|
-
*
|
|
311
|
-
* This is a pure function extracted for testability.
|
|
312
|
-
*
|
|
313
|
-
* @param pose - The XRViewerPose from frame.getViewerPose()
|
|
314
|
-
* @returns ARPose with position and orientation, or null
|
|
315
|
-
*/
|
|
316
|
-
function extractPoseFromViewer(pose) {
|
|
317
|
-
if (!pose) return null;
|
|
318
|
-
const view = pose.views[0];
|
|
319
|
-
if (!view) return null;
|
|
320
|
-
const { position, orientation } = view.transform;
|
|
321
|
-
return {
|
|
322
|
-
position: {
|
|
323
|
-
x: position.x,
|
|
324
|
-
y: position.y,
|
|
325
|
-
z: position.z
|
|
326
|
-
},
|
|
327
|
-
orientation: {
|
|
328
|
-
x: orientation.x,
|
|
329
|
-
y: orientation.y,
|
|
330
|
-
z: orientation.z,
|
|
331
|
-
w: orientation.w
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Get the current raw AR pose from the latest XR frame.
|
|
337
|
-
* This is updated every frame and should be called when GPS arrives
|
|
338
|
-
* to get the AR pose at that moment.
|
|
339
|
-
*
|
|
340
|
-
* IMPORTANT: This returns the RAW pose from WebXR, NOT transformed
|
|
341
|
-
* by any alignment matrix. This is what we record to the store.
|
|
342
|
-
*
|
|
343
|
-
* @returns The latest AR pose, or null if no pose available yet
|
|
344
|
-
*/
|
|
345
|
-
function getCurrentArPose() {
|
|
346
|
-
return latestArPose;
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Build XR session init options.
|
|
350
|
-
* Extracted as a pure function for testability.
|
|
351
|
-
*
|
|
352
|
-
* @param rootElement - The DOM element for DOM overlay
|
|
353
|
-
* @returns XRSessionInit options
|
|
354
|
-
* @throws Error if rootElement is null
|
|
355
|
-
*/
|
|
356
|
-
function buildSessionOptions(rootElement, isolationOptions = {}) {
|
|
357
|
-
if (!rootElement) throw new Error("App root element not found");
|
|
358
|
-
const normalizedOptions = validateArCrashIsolationOptions(isolationOptions);
|
|
359
|
-
const optionalFeatures = [];
|
|
360
|
-
const sessionOptions = { requiredFeatures: ["local-floor"] };
|
|
361
|
-
if (normalizedOptions.enableDomOverlay) {
|
|
362
|
-
optionalFeatures.push("dom-overlay");
|
|
363
|
-
sessionOptions.domOverlay = { root: rootElement };
|
|
364
|
-
}
|
|
365
|
-
if (normalizedOptions.enableDepthSensingFeature) {
|
|
366
|
-
optionalFeatures.push("depth-sensing");
|
|
367
|
-
Object.assign(sessionOptions, { depthSensing: {
|
|
368
|
-
usagePreference: ["cpu-optimized"],
|
|
369
|
-
dataFormatPreference: ["luminance-alpha", "float32"]
|
|
370
|
-
} });
|
|
371
|
-
}
|
|
372
|
-
if (normalizedOptions.enableCameraAccess) optionalFeatures.push("camera-access");
|
|
373
|
-
if (optionalFeatures.length > 0) sessionOptions.optionalFeatures = optionalFeatures;
|
|
374
|
-
return sessionOptions;
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* Check if WebXR immersive-ar is supported
|
|
378
|
-
*/
|
|
379
|
-
async function isWebXRSupported() {
|
|
380
|
-
if (!navigator.xr) return false;
|
|
381
|
-
try {
|
|
382
|
-
return await navigator.xr.isSessionSupported("immersive-ar");
|
|
383
|
-
} catch {
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Create the scene hierarchy with proper AR/GPS frame separation.
|
|
389
|
-
* This is a pure function for testability.
|
|
390
|
-
*
|
|
391
|
-
* Hierarchy:
|
|
392
|
-
* scene (GPS world frame — NUE: X=North, Y=Up, Z=East)
|
|
393
|
-
* ├── ambientLight
|
|
394
|
-
* ├── directionalLight
|
|
395
|
-
* └── arWorldGroup (local space = NUE; receives alignment matrix)
|
|
396
|
-
* └── basisChangeNode ('webxr-to-nue', constant WEBXR_TO_NUE matrix)
|
|
397
|
-
* └── arpose (Object3D — AR pose; local space = WebXR)
|
|
398
|
-
* └── camera (PerspectiveCamera)
|
|
399
|
-
*
|
|
400
|
-
* basisChangeNode is a static scene-graph node that holds the WEBXR_TO_NUE
|
|
401
|
-
* basis-change matrix permanently (matrixAutoUpdate=false). Moving it here
|
|
402
|
-
* instead of composing it in applyAlignmentMatrix() keeps arWorldGroup's
|
|
403
|
-
* local space in the **NUE axis convention** (X=North, Y=Up, Z=East), so no
|
|
404
|
-
* WebXR↔NUE swizzle is needed for children.
|
|
405
|
-
*
|
|
406
|
-
* CAUTION — two NUE frames: arWorldGroup's local space is the *AR-odometry*
|
|
407
|
-
* NUE frame, i.e. the **domain** of the alignment matrix, NOT the GPS-world
|
|
408
|
-
* NUE frame of the scene root. Only content authored in AR-odometry
|
|
409
|
-
* coordinates (e.g. the camera subtree) may be placed with raw local values.
|
|
410
|
-
* GPS-world content (a lat/lon → NUE point) is expressed in the scene-root
|
|
411
|
-
* frame and must be pre-multiplied by alignment⁻¹ before being used as a
|
|
412
|
-
* local position under arWorldGroup — see createGpsAnchor and the
|
|
413
|
-
* alignment-frame bug doc
|
|
414
|
-
* (GpsPlusSlamJs_Docs/docs/2026-05-31-gps-anchor-alignment-frame-bug.md).
|
|
415
|
-
*
|
|
416
|
-
* - Recording: arpose stays at identity; WebXRManager writes to camera.
|
|
417
|
-
* - Replay: arpose receives recorded odomPosition/odomRotation;
|
|
418
|
-
* camera is owned by user controls (OrbitControls / FPS).
|
|
419
|
-
*
|
|
420
|
-
* @returns Object containing scene, arWorldGroup, arpose, and camera
|
|
421
|
-
*/
|
|
422
|
-
function createSceneHierarchy() {
|
|
423
|
-
const newScene = new THREE.Scene();
|
|
424
|
-
const newArWorldGroup = new THREE.Group();
|
|
425
|
-
newArWorldGroup.name = "ar-world";
|
|
426
|
-
newScene.add(newArWorldGroup);
|
|
427
|
-
const newBasisChangeNode = new THREE.Group();
|
|
428
|
-
newBasisChangeNode.name = SCENE_NODE.BASIS_CHANGE;
|
|
429
|
-
newBasisChangeNode.matrix.copy(WEBXR_TO_NUE);
|
|
430
|
-
newBasisChangeNode.matrixAutoUpdate = false;
|
|
431
|
-
newArWorldGroup.add(newBasisChangeNode);
|
|
432
|
-
const newArPose = new THREE.Object3D();
|
|
433
|
-
newArPose.name = "ar-pose";
|
|
434
|
-
newBasisChangeNode.add(newArPose);
|
|
435
|
-
const newCamera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, .01, 100);
|
|
436
|
-
newArPose.add(newCamera);
|
|
437
|
-
const ambientLight = new THREE.AmbientLight(16777215, .5);
|
|
438
|
-
newScene.add(ambientLight);
|
|
439
|
-
const directionalLight = new THREE.DirectionalLight(16777215, .8);
|
|
440
|
-
directionalLight.position.set(0, 10, 5);
|
|
441
|
-
newScene.add(directionalLight);
|
|
442
|
-
return {
|
|
443
|
-
scene: newScene,
|
|
444
|
-
arWorldGroup: newArWorldGroup,
|
|
445
|
-
arpose: newArPose,
|
|
446
|
-
camera: newCamera
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Initialize the AR session and Three.js renderer.
|
|
451
|
-
* @param container - DOM element to host the AR canvas and CSS3D overlay.
|
|
452
|
-
*/
|
|
453
|
-
async function initAR(container, isolationOptions = {}) {
|
|
454
|
-
if (!navigator.xr) throw new Error("WebXR not available");
|
|
455
|
-
if (renderer || xrSession) throw new Error("AR session already initialized — call endARSession() before initAR() again");
|
|
456
|
-
currentArCrashIsolationOptions = validateArCrashIsolationOptions(isolationOptions);
|
|
457
|
-
renderer = new THREE.WebGLRenderer({
|
|
458
|
-
antialias: true,
|
|
459
|
-
alpha: true
|
|
460
|
-
});
|
|
461
|
-
renderer.setPixelRatio(window.devicePixelRatio);
|
|
462
|
-
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
463
|
-
renderer.xr.enabled = true;
|
|
464
|
-
container.insertBefore(renderer.domElement, container.firstChild);
|
|
465
|
-
if (currentArCrashIsolationOptions.enableCss3dRenderer) css3dManager = createCss3dRendererManager(container, window.innerWidth, window.innerHeight);
|
|
466
|
-
const hierarchy = createSceneHierarchy();
|
|
467
|
-
scene = hierarchy.scene;
|
|
468
|
-
arWorldGroup = hierarchy.arWorldGroup;
|
|
469
|
-
arPoseNode = hierarchy.arpose;
|
|
470
|
-
camera = hierarchy.camera;
|
|
471
|
-
const sessionOptions = buildSessionOptions(container, currentArCrashIsolationOptions);
|
|
472
|
-
xrSession = await navigator.xr.requestSession("immersive-ar", sessionOptions);
|
|
473
|
-
xrSession.addEventListener("end", () => {
|
|
474
|
-
log.info("Session ended");
|
|
475
|
-
if (trackingStore) trackingStore.dispatch(resetTracking());
|
|
476
|
-
xrSession = null;
|
|
477
|
-
latestArPose = null;
|
|
478
|
-
});
|
|
479
|
-
await renderer.xr.setSession(xrSession);
|
|
480
|
-
if (onTrackingRestarted && trackingStore) {
|
|
481
|
-
const store = trackingStore;
|
|
482
|
-
store.dispatch(resetTracking());
|
|
483
|
-
trackingPhaseUnsubscribe = subscribeToTrackingPhase(store);
|
|
484
|
-
const referenceSpace = renderer.xr.getReferenceSpace();
|
|
485
|
-
if (referenceSpace) referenceSpace.addEventListener("reset", (event) => {
|
|
486
|
-
const transformData = extractResetTransformData(event);
|
|
487
|
-
store.dispatch(originReset(transformData));
|
|
488
|
-
log.warn("XR reference space reset detected", transformData ? "(transform available)" : "(no transform)");
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
if (onDepthCaptured) depthSampler = new DepthSampler({
|
|
492
|
-
onSampleCaptured: onDepthCaptured,
|
|
493
|
-
getCurrentPose: getCurrentArPose,
|
|
494
|
-
onDepthUnavailable: onDepthUnavailable ?? void 0
|
|
495
|
-
});
|
|
496
|
-
renderer.setAnimationLoop(onXRFrame);
|
|
497
|
-
log.info("AR session started");
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* Snapshot the current `DeviceOrientation` (with documented fallback
|
|
501
|
-
* defaults) for inclusion in `poseReceived` payloads.
|
|
502
|
-
*/
|
|
503
|
-
function snapshotDeviceOrientation() {
|
|
504
|
-
const orientation = getLastDeviceOrientation();
|
|
505
|
-
return {
|
|
506
|
-
alpha: orientation?.alpha ?? 0,
|
|
507
|
-
beta: orientation?.beta ?? 0,
|
|
508
|
-
gamma: orientation?.gamma ?? 0,
|
|
509
|
-
absolute: orientation?.absolute ?? false
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
/**
|
|
513
|
-
* Wire the tracking-slice → host-callbacks translation. The subscriber
|
|
514
|
-
* runs synchronously inside each `dispatch`, so the host callbacks fire
|
|
515
|
-
* in the same order as a direct invocation would.
|
|
516
|
-
*
|
|
517
|
-
* Translation rules (locked in by tracking-slice tests):
|
|
518
|
-
* - `tracking → lost`: clear `latestArPose` (drops in-flight GPS events)
|
|
519
|
-
* and call `onTrackingLost?.()`.
|
|
520
|
-
* - `lost → tracking` with `lastRestartedPayload !== null` (Case 2):
|
|
521
|
-
* call `onTrackingRestarted?.(payload)` then dispatch
|
|
522
|
-
* `clearLastRestartedPayload` so a subsequent loss cycle starts clean.
|
|
523
|
-
* - `lost → tracking` with payload null (Case 1: seamless recovery):
|
|
524
|
-
* call `onTrackingRecovered?.()`.
|
|
525
|
-
* - `initializing → tracking`: no callback (initial acquisition is not
|
|
526
|
-
* a restart — same behaviour as the manager).
|
|
527
|
-
*/
|
|
528
|
-
function subscribeToTrackingPhase(store) {
|
|
529
|
-
let prev = "initializing";
|
|
530
|
-
return store.subscribe(() => {
|
|
531
|
-
const next = selectTrackingPhase(store.getState());
|
|
532
|
-
if (next === prev) return;
|
|
533
|
-
const previous = prev;
|
|
534
|
-
prev = next;
|
|
535
|
-
if (previous === "tracking" && next === "lost") {
|
|
536
|
-
log.warn("Tracking lost");
|
|
537
|
-
latestArPose = null;
|
|
538
|
-
onTrackingLost?.();
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
if (previous === "lost" && next === "tracking") {
|
|
542
|
-
const payload = selectLastRestartedPayload(store.getState());
|
|
543
|
-
if (payload !== null) {
|
|
544
|
-
log.info("Tracking restarted (origin reset)");
|
|
545
|
-
onTrackingRestarted?.(payload);
|
|
546
|
-
store.dispatch(clearLastRestartedPayload());
|
|
547
|
-
} else {
|
|
548
|
-
log.info("Tracking recovered (same coordinate frame)");
|
|
549
|
-
onTrackingRecovered?.();
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* Dispatch the per-frame `poseReceived` / `poseLost` action into the
|
|
556
|
-
* tracking slice. No-op when no store is bound or when tracking wiring
|
|
557
|
-
* was not requested.
|
|
558
|
-
*/
|
|
559
|
-
function updateTrackingState(arPose) {
|
|
560
|
-
if (!trackingStore || !onTrackingRestarted) return;
|
|
561
|
-
if (arPose) trackingStore.dispatch(poseReceived({
|
|
562
|
-
pose: arPose,
|
|
563
|
-
sensorOrientation: snapshotDeviceOrientation()
|
|
564
|
-
}));
|
|
565
|
-
else trackingStore.dispatch(poseLost());
|
|
566
|
-
}
|
|
567
|
-
/**
|
|
568
|
-
* Called each XR frame
|
|
569
|
-
*/
|
|
570
|
-
function onXRFrame(time, frame) {
|
|
571
|
-
if (!renderer || !scene || !camera || !frame) return;
|
|
572
|
-
const referenceSpace = renderer.xr.getReferenceSpace();
|
|
573
|
-
if (!referenceSpace) return;
|
|
574
|
-
const pose = frame.getViewerPose(referenceSpace);
|
|
575
|
-
const arPose = extractPoseFromViewer(pose);
|
|
576
|
-
updateTrackingState(arPose);
|
|
577
|
-
const dt = lastFrameTime === 0 ? 0 : (time - lastFrameTime) / 1e3;
|
|
578
|
-
const elapsed = time / 1e3;
|
|
579
|
-
lastFrameTime = time;
|
|
580
|
-
runFrameUpdates(dt, elapsed);
|
|
581
|
-
if (arPose) latestArPose = arPose;
|
|
582
|
-
latestCameraTexture = null;
|
|
583
|
-
if (currentArCrashIsolationOptions.enableCameraTextureAcquisition) {
|
|
584
|
-
const xrCamera = getXrCameraFromPose(pose);
|
|
585
|
-
if (shouldLogCameraAccessDiagnostic(pose, cameraAccessLoggedOnce, imageCaptureManager !== null)) {
|
|
586
|
-
cameraAccessLoggedOnce = true;
|
|
587
|
-
if (xrCamera) log.info("camera-access GRANTED — XRView.camera is available for blit capture");
|
|
588
|
-
else log.warn("camera-access NOT GRANTED — XRView.camera is undefined. Image capture will fall back to canvas.toBlob() which may produce black/empty frames in WebXR.");
|
|
589
|
-
}
|
|
590
|
-
if (xrCamera) {
|
|
591
|
-
const result = acquireCameraTexture(renderer, xrCamera);
|
|
592
|
-
if (result) {
|
|
593
|
-
latestCameraTexture = result.texture;
|
|
594
|
-
latestCameraWidth = result.width;
|
|
595
|
-
latestCameraHeight = result.height;
|
|
596
|
-
} else {
|
|
597
|
-
getCameraTextureNullCount++;
|
|
598
|
-
if (getCameraTextureNullCount <= GET_CAMERA_TEXTURE_LOG_THRESHOLD) log.warn(`getCameraTexture() returned null (occurrence ${getCameraTextureNullCount}/${GET_CAMERA_TEXTURE_LOG_THRESHOLD}). camera-access is granted but Three.js did not provide a texture.`);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
if (imageCaptureManager) imageCaptureManager.onFrame(time);
|
|
603
|
-
if (depthSampler) {
|
|
604
|
-
const depthInfo = getDepthInfoFromFrame(frame, pose);
|
|
605
|
-
depthSampler.onFrame(time, depthInfo);
|
|
606
|
-
}
|
|
607
|
-
if (onFrameCallback) try {
|
|
608
|
-
onFrameCallback();
|
|
609
|
-
} catch (error) {
|
|
610
|
-
log.error("Error in onFrameCallback:", error);
|
|
611
|
-
}
|
|
612
|
-
renderer.render(scene, camera);
|
|
613
|
-
if (currentArCrashIsolationOptions.enableCss3dRenderer && css3dManager) css3dManager.render(scene, camera);
|
|
614
|
-
}
|
|
615
|
-
/**
|
|
616
|
-
* Extract depth information from an XR frame.
|
|
617
|
-
* Returns null if depth sensing is not available.
|
|
618
|
-
*/
|
|
619
|
-
function getDepthInfoFromFrame(frame, pose) {
|
|
620
|
-
if (!pose || !pose.views[0]) return null;
|
|
621
|
-
const xrFrame = frame;
|
|
622
|
-
if (typeof xrFrame.getDepthInformation !== "function") return null;
|
|
623
|
-
try {
|
|
624
|
-
return xrFrame.getDepthInformation(pose.views[0]) ?? null;
|
|
625
|
-
} catch {
|
|
626
|
-
return null;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Get the current Three.js scene (for adding objects like map)
|
|
631
|
-
*/
|
|
632
|
-
function getScene() {
|
|
633
|
-
return scene;
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Get the AR world group (for adding AR-tracked content)
|
|
637
|
-
* Content added here will be transformed by the alignment matrix.
|
|
638
|
-
*/
|
|
639
|
-
function getArWorldGroup() {
|
|
640
|
-
return arWorldGroup;
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* Get the current camera
|
|
644
|
-
*/
|
|
645
|
-
function getCamera() {
|
|
646
|
-
return camera;
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* Set the scene externally (for replay mode).
|
|
650
|
-
* Allows non-WebXR code paths to register a scene so that modules
|
|
651
|
-
* calling getScene() receive it.
|
|
652
|
-
* @see docs/2026-02-19-replay-mode.md Risk R1
|
|
653
|
-
*/
|
|
654
|
-
function setScene(s) {
|
|
655
|
-
scene = s;
|
|
656
|
-
}
|
|
657
|
-
/**
|
|
658
|
-
* Set the AR world group externally (for replay mode).
|
|
659
|
-
* Allows non-WebXR code paths to register an arWorldGroup so that
|
|
660
|
-
* applyAlignmentMatrix() and visualizers work correctly.
|
|
661
|
-
* @see docs/2026-02-19-replay-mode.md Risk R1
|
|
662
|
-
*/
|
|
663
|
-
function setArWorldGroup(g) {
|
|
664
|
-
arWorldGroup = g;
|
|
665
|
-
}
|
|
666
|
-
/**
|
|
667
|
-
* Set the camera externally (for replay mode).
|
|
668
|
-
* Allows non-WebXR code paths to register a camera so that modules
|
|
669
|
-
* calling getCamera() receive it.
|
|
670
|
-
* @see docs/2026-02-19-replay-mode.md Risk R1
|
|
671
|
-
*/
|
|
672
|
-
function setCamera(c) {
|
|
673
|
-
camera = c;
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Get the arpose Object3D (intermediate node between arWorldGroup and camera).
|
|
677
|
-
* Returns null before scene initialization.
|
|
678
|
-
*/
|
|
679
|
-
function getArPose() {
|
|
680
|
-
return arPoseNode;
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* Set the arpose Object3D externally (for replay mode).
|
|
684
|
-
* Allows non-WebXR code paths to register an arpose so that
|
|
685
|
-
* store subscribers can update it with recorded odom data.
|
|
686
|
-
*/
|
|
687
|
-
function setArPose(a) {
|
|
688
|
-
arPoseNode = a;
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* Constant matrix converting WebXR local-floor coordinates to the internal
|
|
692
|
-
* NUE (North-Up-East) convention.
|
|
693
|
-
*
|
|
694
|
-
* WebXR: X=East, Y=Up, Z=South (right-handed, toward viewer)
|
|
695
|
-
* NUE: X=North, Y=Up, Z=East (right-handed)
|
|
696
|
-
*
|
|
697
|
-
* Mapping: NUE_X = -WebXR_Z, NUE_Y = WebXR_Y, NUE_Z = WebXR_X
|
|
698
|
-
*
|
|
699
|
-
* Row-major:
|
|
700
|
-
* [ 0 0 -1 0 ]
|
|
701
|
-
* [ 0 1 0 0 ]
|
|
702
|
-
* [ 1 0 0 0 ]
|
|
703
|
-
* [ 0 0 0 1 ]
|
|
704
|
-
*
|
|
705
|
-
* Stored column-major (Three.js / gl-matrix convention).
|
|
706
|
-
*/
|
|
707
|
-
const WEBXR_TO_NUE = new THREE.Matrix4().fromArray([
|
|
708
|
-
0,
|
|
709
|
-
0,
|
|
710
|
-
1,
|
|
711
|
-
0,
|
|
712
|
-
0,
|
|
713
|
-
1,
|
|
714
|
-
0,
|
|
715
|
-
0,
|
|
716
|
-
-1,
|
|
717
|
-
0,
|
|
718
|
-
0,
|
|
719
|
-
0,
|
|
720
|
-
0,
|
|
721
|
-
0,
|
|
722
|
-
0,
|
|
723
|
-
1
|
|
724
|
-
]);
|
|
725
|
-
/**
|
|
726
|
-
* Apply an alignment matrix to the AR world group.
|
|
727
|
-
*
|
|
728
|
-
* The alignment matrix maps odometry positions in NUE space
|
|
729
|
-
* to GPS world space (also NUE). It is applied directly to arWorldGroup
|
|
730
|
-
* whose local space is NUE.
|
|
731
|
-
*
|
|
732
|
-
* The WebXR→NUE basis change lives permanently in basisChangeNode (a
|
|
733
|
-
* child of arWorldGroup), so the full camera world transform is:
|
|
734
|
-
*
|
|
735
|
-
* camera_world = arWorldGroup × basisChangeNode × arpose × camera_local
|
|
736
|
-
* = alignment × WEBXR_TO_NUE × arpose × camera_local
|
|
737
|
-
*
|
|
738
|
-
* This is mathematically identical to the previous composition
|
|
739
|
-
* (alignment × WEBXR_TO_NUE applied directly to arWorldGroup.matrix),
|
|
740
|
-
* but arWorldGroup's local space is now NUE: objects placed as children
|
|
741
|
-
* of arWorldGroup use NUE coordinates directly ([1,0,0]=North, [0,0,1]=East).
|
|
742
|
-
*
|
|
743
|
-
* Replay note: arpose still lives in WebXR space (below basisChangeNode),
|
|
744
|
-
* so nuePositionToWebXR() is still required when setting arpose.position.
|
|
745
|
-
*
|
|
746
|
-
* @param matrix - 16-element column-major matrix (gl-matrix mat4 format)
|
|
747
|
-
*/
|
|
748
|
-
function applyAlignmentMatrix(matrix) {
|
|
749
|
-
if (!arWorldGroup) {
|
|
750
|
-
log.warn("Cannot apply alignment - arWorldGroup not initialized");
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
if (matrix.length !== 16) {
|
|
754
|
-
log.error("Invalid alignment matrix - expected 16 elements");
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
arWorldGroup.matrix.fromArray(matrix);
|
|
758
|
-
arWorldGroup.matrixAutoUpdate = false;
|
|
759
|
-
arWorldGroup.updateMatrixWorld(true);
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Convert a position from internal NUE convention (X=North, Y=Up, Z=East)
|
|
763
|
-
* to WebXR local-floor convention (X=East, Y=Up, Z=South).
|
|
764
|
-
*
|
|
765
|
-
* Delegates to the canonical library implementation (nueToWebXR).
|
|
766
|
-
* Accepts `readonly number[]` for call-site convenience.
|
|
767
|
-
*
|
|
768
|
-
* NUE [n, u, e] → WebXR [e, u, -n]
|
|
769
|
-
*/
|
|
770
|
-
function nuePositionToWebXR(nue) {
|
|
771
|
-
return nueToWebXR(nue);
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* Convert a quaternion from internal NUE convention to WebXR local-floor convention.
|
|
775
|
-
*
|
|
776
|
-
* Delegates to the canonical library implementation (nueQuaternionToWebXR).
|
|
777
|
-
* Accepts `readonly number[]` for call-site convenience.
|
|
778
|
-
*
|
|
779
|
-
* NUE [x, y, z, w] → WebXR [z, y, -x, w]
|
|
780
|
-
*/
|
|
781
|
-
function nueQuaternionToWebXR(nue) {
|
|
782
|
-
return nueQuaternionToWebXR$1(nue);
|
|
783
|
-
}
|
|
784
|
-
/**
|
|
785
|
-
* End the current XR session and clean up all resources.
|
|
786
|
-
*
|
|
787
|
-
* Stops the animation loop, ends the XR session, then delegates the full
|
|
788
|
-
* teardown to {@link resetWebXRState} so every module-level reference is
|
|
789
|
-
* cleared (renderer/scene/camera, image-capture, depth, the tracking-phase
|
|
790
|
-
* subscription, the frame-update registry, diagnostics, blit resources).
|
|
791
|
-
* This is the production cleanup path — call it when the AR experience is
|
|
792
|
-
* finished.
|
|
793
|
-
*/
|
|
794
|
-
async function endARSession() {
|
|
795
|
-
if (renderer) renderer.setAnimationLoop(null);
|
|
796
|
-
try {
|
|
797
|
-
if (xrSession) await xrSession.end();
|
|
798
|
-
} finally {
|
|
799
|
-
resetWebXRState();
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* Set up image capture callbacks.
|
|
804
|
-
* Call this before starting image capture to wire up the callback handlers.
|
|
805
|
-
*
|
|
806
|
-
* @param onCaptured - Called when an image is successfully captured
|
|
807
|
-
* @param screenRotationGetter - Returns current device screen rotation (0, 90, 180, 270)
|
|
808
|
-
* @param onFailed - Optional callback for when image capture fails (e.g., low memory)
|
|
809
|
-
* @param onSuspicious - Optional callback for when a captured image appears black/empty
|
|
810
|
-
*/
|
|
811
|
-
function setImageCaptureCallback(onCaptured, screenRotationGetter, onFailed, onSuspicious) {
|
|
812
|
-
onImageCaptured = onCaptured;
|
|
813
|
-
getScreenRotation = screenRotationGetter;
|
|
814
|
-
onCaptureFailed = onFailed ?? null;
|
|
815
|
-
onSuspiciousImage = onSuspicious ?? null;
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* Start capturing images during recording.
|
|
819
|
-
* Must call setImageCaptureCallback first.
|
|
820
|
-
* @param config - Optional capture configuration (intervalMs, quality)
|
|
821
|
-
* @param resolutionDivisor - Resolution divisor: 1 = full, 2 = half, 4 = quarter (default: 1)
|
|
822
|
-
*/
|
|
823
|
-
function startImageCapture(config, resolutionDivisor = 1) {
|
|
824
|
-
if (!renderer) {
|
|
825
|
-
log.warn("Cannot start image capture - renderer not initialized");
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
if (!onImageCaptured || !getScreenRotation) {
|
|
829
|
-
log.warn("Cannot start image capture - callbacks not set");
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
if (imageCaptureManager || blitCapture) {
|
|
833
|
-
log.warn("Image capture already running - stopping previous session");
|
|
834
|
-
stopImageCapture();
|
|
835
|
-
}
|
|
836
|
-
const callbacks = {
|
|
837
|
-
getCurrentPose: getCurrentArPose,
|
|
838
|
-
getScreenRotation,
|
|
839
|
-
onCaptured: onImageCaptured,
|
|
840
|
-
onCaptureFailed: onCaptureFailed ?? void 0,
|
|
841
|
-
onSuspiciousImage: onSuspiciousImage ?? void 0
|
|
842
|
-
};
|
|
843
|
-
blitCapture = new CameraBlitCapture();
|
|
844
|
-
const currentRenderer = renderer;
|
|
845
|
-
const divisor = resolutionDivisor;
|
|
846
|
-
callbacks.captureFrame = async (quality) => {
|
|
847
|
-
if (!blitCapture || !latestCameraTexture) return new Promise((resolve) => {
|
|
848
|
-
currentRenderer.domElement.toBlob((blob) => resolve(blob), "image/jpeg", quality);
|
|
849
|
-
});
|
|
850
|
-
if (latestCameraWidth > 0 && latestCameraHeight > 0) {
|
|
851
|
-
const target = computeCaptureSize(latestCameraWidth, latestCameraHeight, divisor);
|
|
852
|
-
blitCapture.resizeIfNeeded(target.width, target.height);
|
|
853
|
-
}
|
|
854
|
-
return blitCapture.captureToBlob(currentRenderer, latestCameraTexture, quality);
|
|
855
|
-
};
|
|
856
|
-
log.info(`Blit capture pipeline initialized (resolutionDivisor=${divisor})`);
|
|
857
|
-
const mergedConfig = {
|
|
858
|
-
...DEFAULT_CAPTURE_CONFIG,
|
|
859
|
-
...config
|
|
860
|
-
};
|
|
861
|
-
imageCaptureManager = new ImageCaptureManager(renderer.domElement, callbacks, mergedConfig);
|
|
862
|
-
imageCaptureManager.start();
|
|
863
|
-
log.info("Image capture started");
|
|
864
|
-
}
|
|
865
|
-
/**
|
|
866
|
-
* Stop capturing images.
|
|
867
|
-
*/
|
|
868
|
-
function stopImageCapture() {
|
|
869
|
-
if (imageCaptureManager) {
|
|
870
|
-
imageCaptureManager.stop();
|
|
871
|
-
log.info(`Image capture stopped (${imageCaptureManager.getFrameCount()} frames captured)`);
|
|
872
|
-
imageCaptureManager = null;
|
|
873
|
-
}
|
|
874
|
-
cleanupBlitResources();
|
|
875
|
-
}
|
|
876
|
-
/**
|
|
877
|
-
* Get the current image capture frame count.
|
|
878
|
-
*/
|
|
879
|
-
function getImageCaptureFrameCount() {
|
|
880
|
-
return imageCaptureManager?.getFrameCount() ?? 0;
|
|
881
|
-
}
|
|
882
|
-
/**
|
|
883
|
-
* Inject the Redux store used by the tracking-state slice pipeline.
|
|
884
|
-
*
|
|
885
|
-
* MUST be called before `initAR()` whenever the host also wires tracking
|
|
886
|
-
* callbacks via `setTrackingCallbacks`. Without a store the tracking
|
|
887
|
-
* pipeline silently no-ops.
|
|
888
|
-
*
|
|
889
|
-
* @param store — any store satisfying {@link TrackingSubscribableStore}.
|
|
890
|
-
* `null` clears the binding (useful for teardown in tests).
|
|
891
|
-
*/
|
|
892
|
-
function setTrackingStore(store) {
|
|
893
|
-
if (trackingPhaseUnsubscribe) {
|
|
894
|
-
trackingPhaseUnsubscribe();
|
|
895
|
-
trackingPhaseUnsubscribe = null;
|
|
896
|
-
}
|
|
897
|
-
trackingStore = store;
|
|
898
|
-
}
|
|
899
|
-
/**
|
|
900
|
-
* Set up tracking state callbacks.
|
|
901
|
-
* Call this BEFORE initAR() to enable tracking restart detection.
|
|
902
|
-
*
|
|
903
|
-
* @param onRestarted - Called when tracking restarts after being lost
|
|
904
|
-
*/
|
|
905
|
-
function setTrackingCallbacks(onRestarted) {
|
|
906
|
-
onTrackingRestarted = onRestarted;
|
|
907
|
-
}
|
|
908
|
-
/**
|
|
909
|
-
* Set a callback for when AR tracking is lost.
|
|
910
|
-
* Call this BEFORE initAR() to enable tracking loss notifications.
|
|
911
|
-
* Field Test Readiness Issue #3: Provide user feedback when tracking is lost.
|
|
912
|
-
*
|
|
913
|
-
* @param callback - Called when tracking is lost (pose becomes null)
|
|
914
|
-
*/
|
|
915
|
-
function setTrackingLostCallback(callback) {
|
|
916
|
-
onTrackingLost = callback;
|
|
917
|
-
}
|
|
918
|
-
/**
|
|
919
|
-
* Set a callback for when AR tracking recovers seamlessly (Case 1: same coordinate frame).
|
|
920
|
-
* Call this BEFORE initAR() to enable seamless recovery notifications.
|
|
921
|
-
*
|
|
922
|
-
* @param callback - Called when tracking recovers without origin reset
|
|
923
|
-
*/
|
|
924
|
-
function setTrackingRecoveredCallback(callback) {
|
|
925
|
-
onTrackingRecovered = callback;
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* Set up depth capture callback.
|
|
929
|
-
* Call this BEFORE initAR() to enable depth sampling.
|
|
930
|
-
*
|
|
931
|
-
* @param onCaptured - Called when a depth sample is captured
|
|
932
|
-
* @param onUnavailable - Called once if depth is unavailable after threshold
|
|
933
|
-
*/
|
|
934
|
-
function setDepthCaptureCallback(onCaptured, onUnavailable) {
|
|
935
|
-
onDepthCaptured = onCaptured;
|
|
936
|
-
onDepthUnavailable = onUnavailable ?? null;
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Start depth sampling during recording.
|
|
940
|
-
* Must call setDepthCaptureCallback before initAR.
|
|
941
|
-
*/
|
|
942
|
-
function startDepthCapture() {
|
|
943
|
-
if (!depthSampler) {
|
|
944
|
-
log.warn("Cannot start depth capture - sampler not initialized");
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
947
|
-
depthSampler.start();
|
|
948
|
-
log.info("Depth capture started");
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Stop depth sampling.
|
|
952
|
-
*/
|
|
953
|
-
function stopDepthCapture() {
|
|
954
|
-
if (depthSampler) {
|
|
955
|
-
const count = depthSampler.getSampleCount();
|
|
956
|
-
depthSampler.stop();
|
|
957
|
-
log.info(`Depth capture stopped (${count} samples captured)`);
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Get the current depth sample count.
|
|
962
|
-
*/
|
|
963
|
-
function getDepthSampleCount() {
|
|
964
|
-
return depthSampler?.getSampleCount() ?? 0;
|
|
965
|
-
}
|
|
966
|
-
/**
|
|
967
|
-
* Set a per-frame callback for custom updates.
|
|
968
|
-
* The callback is invoked every XR frame after pose updates but before render.
|
|
969
|
-
* Useful for updating elements that need to follow the camera smoothly
|
|
970
|
-
* (e.g., map overlay position).
|
|
971
|
-
*
|
|
972
|
-
* @param callback - Function to call each frame, or null to clear
|
|
973
|
-
*/
|
|
974
|
-
function setFrameCallback(callback) {
|
|
975
|
-
onFrameCallback = callback;
|
|
976
|
-
}
|
|
977
|
-
/**
|
|
978
|
-
* Get the CSS3D renderer manager for live AR mode.
|
|
979
|
-
* Returns null if no AR session is active or CSS3D was not created.
|
|
980
|
-
*/
|
|
981
|
-
function getLiveCss3dManager() {
|
|
982
|
-
return css3dManager;
|
|
983
|
-
}
|
|
984
|
-
//#endregion
|
|
1
|
+
import { A as setTrackingLostCallback, C as setArWorldGroup, D as setImageCaptureCallback, E as setFrameCallback, F as startImageCapture, I as stopDepthCapture, L as stopImageCapture, M as setTrackingStore, N as shouldLogCameraAccessDiagnostic, O as setScene, P as startDepthCapture, 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-QhcSSub5.js";
|
|
985
2
|
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 };
|