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/depth-sampler.js
CHANGED
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
import { extractOdomPosition } from "../state/gps-event-coordinator.js";
|
|
2
2
|
//#region ../src/ar/depth-sampler.ts
|
|
3
|
+
/**
|
|
4
|
+
* Wrap a raw browser XRDepthInformation object into a {@link DepthInfo}.
|
|
5
|
+
*
|
|
6
|
+
* - `getDepthInMeters` is bound to the source object (browser
|
|
7
|
+
* implementations are this-sensitive).
|
|
8
|
+
* - `projectionMatrix` (typically `XRView.projectionMatrix`, a Float32Array
|
|
9
|
+
* the UA may reuse across frames) is defensively validated and copied into
|
|
10
|
+
* a plain serializable 16-tuple; invalid input (wrong length, non-finite
|
|
11
|
+
* entries) yields a DepthInfo without a matrix rather than an error.
|
|
12
|
+
*/
|
|
13
|
+
function wrapXRDepthInfo(raw, projectionMatrix) {
|
|
14
|
+
const wrapped = {
|
|
15
|
+
width: raw.width,
|
|
16
|
+
height: raw.height,
|
|
17
|
+
getDepthInMeters: raw.getDepthInMeters.bind(raw)
|
|
18
|
+
};
|
|
19
|
+
if (projectionMatrix && projectionMatrix.length === 16) {
|
|
20
|
+
const copy = Array.from(projectionMatrix);
|
|
21
|
+
if (copy.every((v) => Number.isFinite(v))) wrapped.projectionMatrix = copy;
|
|
22
|
+
}
|
|
23
|
+
return wrapped;
|
|
24
|
+
}
|
|
3
25
|
const DEFAULT_CONFIG = {
|
|
4
26
|
intervalMs: 1e3,
|
|
5
|
-
gridSize:
|
|
6
|
-
unavailabilityThresholdMs: 5e3
|
|
27
|
+
gridSize: 16,
|
|
28
|
+
unavailabilityThresholdMs: 5e3,
|
|
29
|
+
rgb: true
|
|
7
30
|
};
|
|
8
31
|
/**
|
|
9
32
|
* Samples sparse depth points from WebXR depth sensing.
|
|
@@ -74,6 +97,18 @@ var DepthSampler = class {
|
|
|
74
97
|
return { ...this.config };
|
|
75
98
|
}
|
|
76
99
|
/**
|
|
100
|
+
* Apply partial configuration overrides (e.g. the user's recording
|
|
101
|
+
* options, plumbed in by `startDepthCapture` just before sampling
|
|
102
|
+
* starts). Invalid values are ignored defensively: every key requires a
|
|
103
|
+
* finite positive number, and `gridSize` additionally an integer.
|
|
104
|
+
*/
|
|
105
|
+
updateConfig(config) {
|
|
106
|
+
if (isFinitePositive(config.intervalMs)) this.config.intervalMs = config.intervalMs;
|
|
107
|
+
if (isFinitePositive(config.gridSize) && Number.isInteger(config.gridSize)) this.config.gridSize = config.gridSize;
|
|
108
|
+
if (isFinitePositive(config.unavailabilityThresholdMs)) this.config.unavailabilityThresholdMs = config.unavailabilityThresholdMs;
|
|
109
|
+
if (typeof config.rgb === "boolean") this.config.rgb = config.rgb;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
77
112
|
* Called each frame with depth information.
|
|
78
113
|
*
|
|
79
114
|
* @param timestamp - Current frame timestamp in milliseconds
|
|
@@ -89,7 +124,7 @@ var DepthSampler = class {
|
|
|
89
124
|
if (timestamp - this.lastSampleTime < this.config.intervalMs) return;
|
|
90
125
|
const pose = this.callbacks.getCurrentPose();
|
|
91
126
|
if (!pose) return;
|
|
92
|
-
const points = this.sampleGrid(depthInfo);
|
|
127
|
+
const points = this.sampleGrid(depthInfo, this.acquireRgbLookupSafely());
|
|
93
128
|
const sample = {
|
|
94
129
|
timestamp: performance.timeOrigin + timestamp,
|
|
95
130
|
cameraPos: extractOdomPosition(pose),
|
|
@@ -99,26 +134,43 @@ var DepthSampler = class {
|
|
|
99
134
|
pose.orientation.z,
|
|
100
135
|
pose.orientation.w
|
|
101
136
|
],
|
|
102
|
-
points
|
|
137
|
+
points,
|
|
138
|
+
...depthInfo.projectionMatrix ? { projectionMatrix: depthInfo.projectionMatrix } : {}
|
|
103
139
|
};
|
|
104
140
|
this.lastSampleTime = timestamp;
|
|
105
141
|
this.sampleCount++;
|
|
106
142
|
this.callbacks.onSampleCaptured(sample);
|
|
107
143
|
}
|
|
108
144
|
/**
|
|
109
|
-
*
|
|
145
|
+
* Acquire the per-sample RGB lookup, gated by `config.rgb` and guarded so
|
|
146
|
+
* a failing acquisition (e.g. GL context loss during the blit) can never
|
|
147
|
+
* break the sample emission in the XR frame loop.
|
|
148
|
+
*/
|
|
149
|
+
acquireRgbLookupSafely() {
|
|
150
|
+
if (!this.config.rgb || !this.callbacks.acquireRgbLookup) return null;
|
|
151
|
+
try {
|
|
152
|
+
return this.callbacks.acquireRgbLookup();
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Sample a grid of depth points from the depth buffer, attaching the
|
|
159
|
+
* camera color per point when a lookup is available.
|
|
110
160
|
*/
|
|
111
|
-
sampleGrid(depthInfo) {
|
|
161
|
+
sampleGrid(depthInfo, rgbLookup) {
|
|
112
162
|
const points = [];
|
|
113
163
|
const gridSize = this.config.gridSize;
|
|
114
164
|
for (let row = 0; row < gridSize; row++) for (let col = 0; col < gridSize; col++) {
|
|
115
165
|
const screenX = (col + 1) / (gridSize + 1);
|
|
116
166
|
const screenY = (row + 1) / (gridSize + 1);
|
|
117
167
|
const depthM = depthInfo.getDepthInMeters(screenX, screenY);
|
|
168
|
+
const rgb = rgbLookup?.(screenX, screenY);
|
|
118
169
|
points.push({
|
|
119
170
|
screenX,
|
|
120
171
|
screenY,
|
|
121
|
-
depthM
|
|
172
|
+
depthM,
|
|
173
|
+
...rgb ? { rgb } : {}
|
|
122
174
|
});
|
|
123
175
|
}
|
|
124
176
|
return points;
|
|
@@ -143,5 +195,8 @@ var DepthSampler = class {
|
|
|
143
195
|
return this.depthReceived;
|
|
144
196
|
}
|
|
145
197
|
};
|
|
198
|
+
function isFinitePositive(value) {
|
|
199
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
200
|
+
}
|
|
146
201
|
//#endregion
|
|
147
|
-
export { DepthSampler };
|
|
202
|
+
export { DepthSampler, wrapXRDepthInfo };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { mat4, quat, vec3, vec4 } from "gl-matrix";
|
|
2
|
+
//#region ../src/ar/depth-unprojection.ts
|
|
3
|
+
/**
|
|
4
|
+
* Depth Unprojection
|
|
5
|
+
*
|
|
6
|
+
* Pure math: turn a persisted depth read (normalized view coordinates +
|
|
7
|
+
* depth in meters) back into a 3D point in raw WebXR (local-floor) space
|
|
8
|
+
* using the capturing view's projection matrix.
|
|
9
|
+
*
|
|
10
|
+
* Convention (single source of truth for NDC flips, see the 2026-06-11
|
|
11
|
+
* occupancy-grid port plan §6):
|
|
12
|
+
* - screenX/screenY are normalized view coordinates with a TOP-LEFT origin
|
|
13
|
+
* (screenY grows downward), exactly as fed to `getDepthInMeters`.
|
|
14
|
+
* - NDC: x = 2·sx − 1, y = 1 − 2·sy (flip Y to bottom-up).
|
|
15
|
+
* - View space is the WebXR camera frame: +x right, +y up, −z forward;
|
|
16
|
+
* `depthM` is the z-depth (distance along −z), not euclidean distance.
|
|
17
|
+
*
|
|
18
|
+
* @see depth-unprojection.ts.md for detailed documentation
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Build a {@link DepthUnprojector} for one depth sample. The projection
|
|
22
|
+
* inverse and camera quaternion/position are sample-invariant, so they are
|
|
23
|
+
* computed once here instead of per point (the per-point hot path then only
|
|
24
|
+
* does the cheap NDC→view→world transform). Callers that fold many points
|
|
25
|
+
* from the same sample — e.g. `OccupancyGrid.addSample` — should build the
|
|
26
|
+
* unprojector once and reuse it for all points.
|
|
27
|
+
*
|
|
28
|
+
* @param cameraPos - camera position, raw WebXR (`DepthSample.cameraPos`)
|
|
29
|
+
* @param cameraRot - camera quaternion [x,y,z,w], raw WebXR (`DepthSample.cameraRot`)
|
|
30
|
+
* @param projectionMatrix - column-major projection matrix of the capturing
|
|
31
|
+
* view (`DepthSample.projectionMatrix`). `undefined` for recordings made
|
|
32
|
+
* before intrinsics capture — those points cannot be unprojected.
|
|
33
|
+
* @returns an unprojector, or `null` when the sample cannot be unprojected at
|
|
34
|
+
* all (missing or singular projection matrix).
|
|
35
|
+
*/
|
|
36
|
+
function createDepthUnprojector(cameraPos, cameraRot, projectionMatrix) {
|
|
37
|
+
if (!projectionMatrix || projectionMatrix.length !== 16) return null;
|
|
38
|
+
const invProj = mat4.invert(mat4.create(), projectionMatrix);
|
|
39
|
+
if (!invProj) return null;
|
|
40
|
+
const cameraQuat = quat.fromValues(cameraRot[0], cameraRot[1], cameraRot[2], cameraRot[3]);
|
|
41
|
+
const cameraPosVec = vec3.fromValues(cameraPos[0], cameraPos[1], cameraPos[2]);
|
|
42
|
+
const ndc = vec4.create();
|
|
43
|
+
const view = vec4.create();
|
|
44
|
+
const viewPoint = vec3.create();
|
|
45
|
+
const world = vec3.create();
|
|
46
|
+
return { unproject(point) {
|
|
47
|
+
if (!isUsablePoint(point)) return null;
|
|
48
|
+
const { screenX, screenY, depthM } = point;
|
|
49
|
+
vec4.set(ndc, 2 * screenX - 1, 1 - 2 * screenY, -1, 1);
|
|
50
|
+
vec4.transformMat4(view, ndc, invProj);
|
|
51
|
+
if (view[3] === 0) return null;
|
|
52
|
+
const rayX = view[0] / view[3];
|
|
53
|
+
const rayY = view[1] / view[3];
|
|
54
|
+
const rayZ = view[2] / view[3];
|
|
55
|
+
if (rayZ >= 0) return null;
|
|
56
|
+
const scale = -depthM / rayZ;
|
|
57
|
+
vec3.set(viewPoint, rayX * scale, rayY * scale, -depthM);
|
|
58
|
+
vec3.transformQuat(world, viewPoint, cameraQuat);
|
|
59
|
+
vec3.add(world, world, cameraPosVec);
|
|
60
|
+
const result = [
|
|
61
|
+
world[0],
|
|
62
|
+
world[1],
|
|
63
|
+
world[2]
|
|
64
|
+
];
|
|
65
|
+
return result.every((v) => Number.isFinite(v)) ? result : null;
|
|
66
|
+
} };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Unproject a single depth point into raw WebXR space. Convenience wrapper
|
|
70
|
+
* over {@link createDepthUnprojector} for one-off callers; when unprojecting
|
|
71
|
+
* many points from the same sample, build the unprojector once instead.
|
|
72
|
+
*
|
|
73
|
+
* @param point - normalized view coordinates + depth in meters
|
|
74
|
+
* @param cameraPos - camera position, raw WebXR (`DepthSample.cameraPos`)
|
|
75
|
+
* @param cameraRot - camera quaternion [x,y,z,w], raw WebXR (`DepthSample.cameraRot`)
|
|
76
|
+
* @param projectionMatrix - column-major projection matrix of the capturing
|
|
77
|
+
* view (`DepthSample.projectionMatrix`). `undefined` for recordings made
|
|
78
|
+
* before intrinsics capture — those points cannot be unprojected.
|
|
79
|
+
* @returns the 3D point in raw WebXR space, or `null` when the input is not
|
|
80
|
+
* usable (missing/singular matrix, non-positive or non-finite depth,
|
|
81
|
+
* out-of-range screen coordinates).
|
|
82
|
+
*/
|
|
83
|
+
function unprojectDepthPoint(point, cameraPos, cameraRot, projectionMatrix) {
|
|
84
|
+
const unprojector = createDepthUnprojector(cameraPos, cameraRot, projectionMatrix);
|
|
85
|
+
return unprojector ? unprojector.unproject(point) : null;
|
|
86
|
+
}
|
|
87
|
+
function isUsablePoint(point) {
|
|
88
|
+
return Number.isFinite(point.depthM) && point.depthM > 0 && isInUnitRange(point.screenX) && isInUnitRange(point.screenY);
|
|
89
|
+
}
|
|
90
|
+
function isInUnitRange(v) {
|
|
91
|
+
return Number.isFinite(v) && v >= 0 && v <= 1;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { createDepthUnprojector, unprojectDepthPoint };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as EnableGpsArState, i as EnableGpsArResult, n as EnableGpsArController, o as EnableGpsArStatus, r as EnableGpsArDeps, s as createEnableGpsArController, t as EnableGpsArConfig } from "../enable-gps-ar-CapmADVN.js";
|
|
2
|
+
export { EnableGpsArConfig, EnableGpsArController, EnableGpsArDeps, EnableGpsArResult, EnableGpsArState, EnableGpsArStatus, createEnableGpsArController };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createLogger } from "../utils/logger.js";
|
|
2
|
+
import { requestGeolocationPermission, requestOrientationPermission, requestWebXRWithDepthPermission } from "../sensors/permission-checker.js";
|
|
3
|
+
import { startGpsWatch, startOrientationWatch, stopGpsWatch, stopOrientationWatch } from "../sensors/gps.js";
|
|
4
|
+
import { _ as isWebXRSupported, g as initAR, i as endARSession } from "../webxr-session-QhcSSub5.js";
|
|
5
|
+
//#region ../src/ar/enable-gps-ar.ts
|
|
6
|
+
const log = createLogger("EnableGpsAr");
|
|
7
|
+
const defaultDeps = {
|
|
8
|
+
isWebXRSupported,
|
|
9
|
+
requestGeolocationPermission,
|
|
10
|
+
requestOrientationPermission,
|
|
11
|
+
requestWebXRWithDepthPermission,
|
|
12
|
+
startGpsWatch,
|
|
13
|
+
startOrientationWatch,
|
|
14
|
+
initAR,
|
|
15
|
+
stopGpsWatch,
|
|
16
|
+
stopOrientationWatch,
|
|
17
|
+
endARSession
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Create an "Enable GPS AR" controller. Pass partial `deps` in tests to inject
|
|
21
|
+
* fakes; production callers use the defaults.
|
|
22
|
+
*/
|
|
23
|
+
function createEnableGpsArController(deps = {}) {
|
|
24
|
+
const resolved = {
|
|
25
|
+
...defaultDeps,
|
|
26
|
+
...deps
|
|
27
|
+
};
|
|
28
|
+
let state = { status: "checking" };
|
|
29
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
30
|
+
let gpsWatchActive = false;
|
|
31
|
+
let orientationWatchActive = false;
|
|
32
|
+
function setState(next) {
|
|
33
|
+
state = next;
|
|
34
|
+
for (const listener of [...listeners]) try {
|
|
35
|
+
listener(state);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
log.error("State listener threw; continuing dispatch:", err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function refreshSupport() {
|
|
41
|
+
if (state.status === "starting" || state.status === "running" || state.status === "stopping") return;
|
|
42
|
+
setState({ status: "checking" });
|
|
43
|
+
const supported = await probeSupport();
|
|
44
|
+
if (state.status !== "checking") return;
|
|
45
|
+
setState({ status: supported ? "ready" : "unsupported" });
|
|
46
|
+
}
|
|
47
|
+
async function probeSupport() {
|
|
48
|
+
try {
|
|
49
|
+
return await resolved.isWebXRSupported();
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Request the configured permissions. Returns an error message when a
|
|
56
|
+
* *blocking* permission (geolocation, or depth when opted in) is not granted,
|
|
57
|
+
* or `null` on success. Orientation is best-effort and never blocks.
|
|
58
|
+
*/
|
|
59
|
+
async function requestPermissions(config) {
|
|
60
|
+
const geolocation = await resolved.requestGeolocationPermission();
|
|
61
|
+
if (geolocation.granted !== true) return geolocation.error ?? "Location permission is required for GPS AR.";
|
|
62
|
+
try {
|
|
63
|
+
await resolved.requestOrientationPermission();
|
|
64
|
+
} catch {}
|
|
65
|
+
if (config.requestDepth) {
|
|
66
|
+
const depth = await resolved.requestWebXRWithDepthPermission();
|
|
67
|
+
if (depth.granted !== true) return depth.error ?? "Depth (3D map) permission was denied.";
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
async function enable(config) {
|
|
72
|
+
if (state.status === "starting" || state.status === "running" || state.status === "stopping") return {
|
|
73
|
+
ok: false,
|
|
74
|
+
error: "AR is already starting, running or stopping."
|
|
75
|
+
};
|
|
76
|
+
setState({ status: "starting" });
|
|
77
|
+
try {
|
|
78
|
+
const permissionError = await requestPermissions(config);
|
|
79
|
+
if (permissionError !== null) return fail(permissionError);
|
|
80
|
+
await resolved.initAR(config.container, config.isolationOptions, { requestHitTest: config.requestHitTest });
|
|
81
|
+
if (config.onGpsPosition) {
|
|
82
|
+
resolved.startGpsWatch(config.onGpsPosition);
|
|
83
|
+
gpsWatchActive = true;
|
|
84
|
+
}
|
|
85
|
+
if (config.onOrientation) {
|
|
86
|
+
resolved.startOrientationWatch(config.onOrientation);
|
|
87
|
+
orientationWatchActive = true;
|
|
88
|
+
}
|
|
89
|
+
setState({ status: "running" });
|
|
90
|
+
return { ok: true };
|
|
91
|
+
} catch (err) {
|
|
92
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function fail(error) {
|
|
96
|
+
setState({
|
|
97
|
+
status: "error",
|
|
98
|
+
error
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
error
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function disable() {
|
|
106
|
+
if (state.status !== "running") return;
|
|
107
|
+
setState({ status: "stopping" });
|
|
108
|
+
if (gpsWatchActive) {
|
|
109
|
+
resolved.stopGpsWatch();
|
|
110
|
+
gpsWatchActive = false;
|
|
111
|
+
}
|
|
112
|
+
if (orientationWatchActive) {
|
|
113
|
+
resolved.stopOrientationWatch();
|
|
114
|
+
orientationWatchActive = false;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
await resolved.endARSession();
|
|
118
|
+
} catch (err) {
|
|
119
|
+
log.error("endARSession threw during disable; continuing teardown:", err);
|
|
120
|
+
}
|
|
121
|
+
setState({ status: "ready" });
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
getState: () => state,
|
|
125
|
+
subscribe: (listener) => {
|
|
126
|
+
listeners.add(listener);
|
|
127
|
+
return () => {
|
|
128
|
+
listeners.delete(listener);
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
refreshSupport,
|
|
132
|
+
enable,
|
|
133
|
+
disable
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { createEnableGpsArController };
|
package/dist/ar/frame-loop.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as runFrameUpdates, n as clearFrameUpdates, r as registerFrameUpdate, t as FrameUpdate } from "../frame-loop-
|
|
1
|
+
import { i as runFrameUpdates, n as clearFrameUpdates, r as registerFrameUpdate, t as FrameUpdate } from "../frame-loop-CMewS8cY.js";
|
|
2
2
|
export { FrameUpdate, clearFrameUpdates, registerFrameUpdate, runFrameUpdates };
|
package/dist/ar/frame-loop.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
import { createLogger } from "../utils/logger.js";
|
|
1
2
|
//#region ../src/ar/frame-loop.ts
|
|
3
|
+
/**
|
|
4
|
+
* Per-frame callback registry. The WebXR session owns the single
|
|
5
|
+
* `renderer.setAnimationLoop(...)` hook for the page; components that need
|
|
6
|
+
* a per-frame tick register here and the session's `onXRFrame` invokes
|
|
7
|
+
* `runFrameUpdates(dt, elapsed)` once per frame.
|
|
8
|
+
*
|
|
9
|
+
* Plain functions, not a class: there is exactly one frame loop per session
|
|
10
|
+
* and singletons-of-singletons add no value. See
|
|
11
|
+
* `2026-05-13-ecs-migration-plan.md` for the design rationale and the
|
|
12
|
+
* rules new `FrameUpdate` bodies must follow (pure function of `dt` plus
|
|
13
|
+
* selectors; no direct Redux dispatch from inside a tick).
|
|
14
|
+
*/
|
|
15
|
+
const log = createLogger("FrameLoop");
|
|
2
16
|
const updates = /* @__PURE__ */ new Set();
|
|
3
17
|
/**
|
|
4
18
|
* Register a per-frame callback. Returns an unregister function.
|
|
@@ -23,10 +37,19 @@ function registerFrameUpdate(fn) {
|
|
|
23
37
|
* same frame are deferred to the next tick. Iterating the live `Set`
|
|
24
38
|
* would otherwise skip a not-yet-visited entry that an earlier handler
|
|
25
39
|
* unregistered — a hard-to-debug source of non-determinism.
|
|
40
|
+
*
|
|
41
|
+
* Each callback is invoked in its own `try/catch` so a throwing handler is
|
|
42
|
+
* isolated: it cannot abort the remaining callbacks nor propagate up through
|
|
43
|
+
* `onXRFrame` and kill the scene render for the whole frame. Failures are
|
|
44
|
+
* logged and the loop continues — mirrored by `runXrFrameUpdates`.
|
|
26
45
|
*/
|
|
27
46
|
function runFrameUpdates(dt, elapsed) {
|
|
28
47
|
const snapshot = Array.from(updates);
|
|
29
|
-
for (const fn of snapshot)
|
|
48
|
+
for (const fn of snapshot) try {
|
|
49
|
+
fn(dt, elapsed);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
log.error("A registered FrameUpdate threw; continuing the loop", error);
|
|
52
|
+
}
|
|
30
53
|
}
|
|
31
54
|
/**
|
|
32
55
|
* Clear all registrations. Called from `resetWebXRState()` so a fresh
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as ImageCaptureManager, i as ImageCaptureConfig, n as DEFAULT_CAPTURE_CONFIG, o as MIN_VALID_IMAGE_BYTES, r as ImageCaptureCallbacks, t as CapturedImage } from "../image-capture-
|
|
1
|
+
import { a as ImageCaptureManager, i as ImageCaptureConfig, n as DEFAULT_CAPTURE_CONFIG, o as MIN_VALID_IMAGE_BYTES, r as ImageCaptureCallbacks, t as CapturedImage } from "../image-capture-DZ4HFRgS.js";
|
|
2
2
|
export { CapturedImage, DEFAULT_CAPTURE_CONFIG, ImageCaptureCallbacks, ImageCaptureConfig, ImageCaptureManager, MIN_VALID_IMAGE_BYTES };
|
package/dist/ar/image-capture.js
CHANGED
package/dist/ar/index.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
4
|
-
import { i as
|
|
5
|
-
import { r as
|
|
6
|
-
import { a as
|
|
7
|
-
import {
|
|
8
|
-
import { t as
|
|
9
|
-
import { i as acquireCameraTexture, n as RendererLike, r as XRCameraLike, t as CameraTextureResult } from "../xr-camera-texture-
|
|
10
|
-
import {
|
|
11
|
-
import { n as
|
|
12
|
-
|
|
1
|
+
import { r as bresenham3d, t as GridCell } from "../bresenham3d-CFfd7n6B.js";
|
|
2
|
+
import { i as computeCaptureSize, n as CameraBlitCaptureConfig, r as DEFAULT_BLIT_CONFIG, t as CameraBlitCapture } from "../camera-blit-capture-D5_mNhla.js";
|
|
3
|
+
import { i as isFullySupported, n as CapabilitySupport, r as capabilityMessage, t as CapabilityMessageOptions } from "../capability-checker-CQXJHxXg.js";
|
|
4
|
+
import { a as createCaptureFailureTracker, i as DEFAULT_CAPTURE_TRACKER_CONFIG, n as CaptureFailureTracker, r as CaptureFailureTrackerConfig, t as CAPTURE_FAILURE_WARNING } from "../capture-failure-tracker-CUeU3L_M.js";
|
|
5
|
+
import { a as applyChromiumProjectionLayerWorkaround, i as ChromiumProjectionLayerWorkaroundResult, n as BASELAYER_WINDOW_MIN, o as needsBaseLayerPersistence, r as ChromeVersion, s as parseChromeVersion, t as BASELAYER_WINDOW_MAX } from "../chromium-camera-access-workaround-Jqbn0apH.js";
|
|
6
|
+
import { a as wrapXRDepthInfo, i as DepthSamplerConfig, n as DepthSampler, r as DepthSamplerCallbacks, t as DepthInfo } from "../depth-sampler-Bhanfwla.js";
|
|
7
|
+
import { n as createDepthUnprojector, r as unprojectDepthPoint, t as DepthUnprojector } from "../depth-unprojection-Y6i_mJuT.js";
|
|
8
|
+
import { a as ImageCaptureManager, i as ImageCaptureConfig, n as DEFAULT_CAPTURE_CONFIG, o as MIN_VALID_IMAGE_BYTES, r as ImageCaptureCallbacks, t as CapturedImage } from "../image-capture-DZ4HFRgS.js";
|
|
9
|
+
import { i as acquireCameraTexture, n as RendererLike, r as XRCameraLike, t as CameraTextureResult } from "../xr-camera-texture-DBwCaKTd.js";
|
|
10
|
+
import { D as setDepthCaptureCallback, I as startDepthCapture, L as startImageCapture, M as setTrackingLostCallback, O as setFrameCallback, P as setTrackingStore, R as stopDepthCapture, d as getCamera, f as getCurrentArPose, g as getScene, h as getLiveCss3dManager, j as setTrackingCallbacks, k as setImageCaptureCallback, m as getImageCaptureFrameCount, o as endARSession, p as getDepthSampleCount, t as SessionFeatureOptions, u as getArWorldGroup, v as initAR, z as stopImageCapture } from "../webxr-session-DtAG9srK.js";
|
|
11
|
+
import { a as EnableGpsArState, i as EnableGpsArResult, n as EnableGpsArController, o as EnableGpsArStatus, r as EnableGpsArDeps, s as createEnableGpsArController, t as EnableGpsArConfig } from "../enable-gps-ar-CapmADVN.js";
|
|
12
|
+
import { r as registerFrameUpdate, t as FrameUpdate } from "../frame-loop-CMewS8cY.js";
|
|
13
|
+
import { n as OccupancyGridOptions, t as OccupancyGrid } from "../occupancy-grid-BRNRaPyT.js";
|
|
14
|
+
import { i as registerXrFrameUpdate, n as XrFrameUpdate, t as XrFrameContext } from "../xr-frame-loop-BtYytZJg.js";
|
|
15
|
+
import { a as getCameraFollower, c as getReplayState, d as updateOrbitTarget, i as getAlignmentLerper, l as initReplayScene, n as ReplaySceneState, o as getCameraMode, r as disposeReplayScene, t as CameraMode, u as toggleCameraMode } from "../replay-scene-Bw58VL6p.js";
|
|
16
|
+
import { t as SCENE_NODE } from "../scene-node-names-Bu0S66H1.js";
|
|
17
|
+
import { t as WEBXR_TO_NUE } from "../webxr-nue-basis-CqKQmZzT.js";
|
|
18
|
+
import { n as XR_ERROR_MESSAGE_UNKNOWN, r as getXrErrorMessage, t as XR_ERROR_MESSAGES } from "../xr-error-handler-C0AQRmxs.js";
|
|
19
|
+
export { BASELAYER_WINDOW_MAX, BASELAYER_WINDOW_MIN, CAPTURE_FAILURE_WARNING, CameraBlitCapture, type CameraBlitCaptureConfig, type CameraMode, type CameraTextureResult, type CapabilityMessageOptions, type CapabilitySupport, type CaptureFailureTracker, type CaptureFailureTrackerConfig, type CapturedImage, type ChromeVersion, type ChromiumProjectionLayerWorkaroundResult, DEFAULT_BLIT_CONFIG, DEFAULT_CAPTURE_CONFIG, DEFAULT_CAPTURE_TRACKER_CONFIG, type DepthInfo, DepthSampler, type DepthSamplerCallbacks, type DepthSamplerConfig, type DepthUnprojector, type EnableGpsArConfig, type EnableGpsArController, type EnableGpsArDeps, type EnableGpsArResult, type EnableGpsArState, type EnableGpsArStatus, type FrameUpdate, type GridCell, type ImageCaptureCallbacks, type ImageCaptureConfig, ImageCaptureManager, MIN_VALID_IMAGE_BYTES, OccupancyGrid, type OccupancyGridOptions, type RendererLike, type ReplaySceneState, SCENE_NODE, type SessionFeatureOptions, WEBXR_TO_NUE, type XRCameraLike, XR_ERROR_MESSAGES, XR_ERROR_MESSAGE_UNKNOWN, type XrFrameContext, type XrFrameUpdate, acquireCameraTexture, applyChromiumProjectionLayerWorkaround, bresenham3d, capabilityMessage, computeCaptureSize, createCaptureFailureTracker, createDepthUnprojector, createEnableGpsArController, disposeReplayScene, endARSession, getAlignmentLerper, getArWorldGroup, getCamera, getCameraFollower, getCameraMode, getCurrentArPose, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getReplayState, getScene, getXrErrorMessage, initAR, initReplayScene, isFullySupported, needsBaseLayerPersistence, parseChromeVersion, registerFrameUpdate, registerXrFrameUpdate, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setTrackingCallbacks, setTrackingLostCallback, setTrackingStore, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture, toggleCameraMode, unprojectDepthPoint, updateOrbitTarget, wrapXRDepthInfo };
|
package/dist/ar/index.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { CameraBlitCapture, DEFAULT_BLIT_CONFIG, computeCaptureSize } from "./camera-blit-capture.js";
|
|
2
|
-
import {
|
|
2
|
+
import { capabilityMessage, isFullySupported } from "./capability-checker.js";
|
|
3
|
+
import { BASELAYER_WINDOW_MAX, BASELAYER_WINDOW_MIN, applyChromiumProjectionLayerWorkaround, needsBaseLayerPersistence, parseChromeVersion } from "./chromium-camera-access-workaround.js";
|
|
3
4
|
import { CAPTURE_FAILURE_WARNING, DEFAULT_CAPTURE_TRACKER_CONFIG, createCaptureFailureTracker } from "./capture-failure-tracker.js";
|
|
4
|
-
import { DepthSampler } from "./depth-sampler.js";
|
|
5
|
+
import { DepthSampler, wrapXRDepthInfo } from "./depth-sampler.js";
|
|
6
|
+
import { createDepthUnprojector, unprojectDepthPoint } from "./depth-unprojection.js";
|
|
7
|
+
import { bresenham3d } from "./bresenham3d.js";
|
|
8
|
+
import { OccupancyGrid } from "./occupancy-grid.js";
|
|
5
9
|
import { registerFrameUpdate } from "./frame-loop.js";
|
|
10
|
+
import { registerXrFrameUpdate } from "./xr-frame-loop.js";
|
|
11
|
+
import { WEBXR_TO_NUE } from "./webxr-nue-basis.js";
|
|
6
12
|
import { DEFAULT_CAPTURE_CONFIG, ImageCaptureManager, MIN_VALID_IMAGE_BYTES } from "./image-capture.js";
|
|
13
|
+
import { A as setTrackingLostCallback, D as setImageCaptureCallback, E as setFrameCallback, F as startImageCapture, I as stopDepthCapture, L as stopImageCapture, M as setTrackingStore, P as startDepthCapture, T as setDepthCaptureCallback, c as getArWorldGroup, d as getDepthSampleCount, f as getImageCaptureFrameCount, g as initAR, i as endARSession, k as setTrackingCallbacks, l as getCamera, m as getScene, p as getLiveCss3dManager, u as getCurrentArPose } from "../webxr-session-QhcSSub5.js";
|
|
7
14
|
import { acquireCameraTexture } from "./xr-camera-texture.js";
|
|
8
15
|
import { SCENE_NODE } from "./scene-node-names.js";
|
|
9
|
-
import {
|
|
16
|
+
import { createEnableGpsArController } from "./enable-gps-ar.js";
|
|
10
17
|
import { disposeReplayScene, getAlignmentLerper, getCameraFollower, getCameraMode, getReplayState, initReplayScene, toggleCameraMode, updateOrbitTarget } from "./replay-scene.js";
|
|
11
18
|
import { XR_ERROR_MESSAGES, XR_ERROR_MESSAGE_UNKNOWN, getXrErrorMessage } from "./xr-error-handler.js";
|
|
12
|
-
export { CAPTURE_FAILURE_WARNING, CameraBlitCapture, DEFAULT_BLIT_CONFIG, DEFAULT_CAPTURE_CONFIG, DEFAULT_CAPTURE_TRACKER_CONFIG, DepthSampler, ImageCaptureManager, MIN_VALID_IMAGE_BYTES, SCENE_NODE, XR_ERROR_MESSAGES, XR_ERROR_MESSAGE_UNKNOWN, acquireCameraTexture, applyChromiumProjectionLayerWorkaround, computeCaptureSize, createCaptureFailureTracker, disposeReplayScene, endARSession, getAlignmentLerper, getCameraFollower, getCameraMode, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getReplayState, getXrErrorMessage, initAR, initReplayScene, registerFrameUpdate, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setTrackingCallbacks, setTrackingLostCallback, setTrackingStore, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture, toggleCameraMode, updateOrbitTarget };
|
|
19
|
+
export { BASELAYER_WINDOW_MAX, BASELAYER_WINDOW_MIN, CAPTURE_FAILURE_WARNING, CameraBlitCapture, DEFAULT_BLIT_CONFIG, DEFAULT_CAPTURE_CONFIG, DEFAULT_CAPTURE_TRACKER_CONFIG, DepthSampler, ImageCaptureManager, MIN_VALID_IMAGE_BYTES, OccupancyGrid, SCENE_NODE, WEBXR_TO_NUE, XR_ERROR_MESSAGES, XR_ERROR_MESSAGE_UNKNOWN, acquireCameraTexture, applyChromiumProjectionLayerWorkaround, bresenham3d, capabilityMessage, computeCaptureSize, createCaptureFailureTracker, createDepthUnprojector, createEnableGpsArController, disposeReplayScene, endARSession, getAlignmentLerper, getArWorldGroup, getCamera, getCameraFollower, getCameraMode, getCurrentArPose, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getReplayState, getScene, getXrErrorMessage, initAR, initReplayScene, isFullySupported, needsBaseLayerPersistence, parseChromeVersion, registerFrameUpdate, registerXrFrameUpdate, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setTrackingCallbacks, setTrackingLostCallback, setTrackingStore, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture, toggleCameraMode, unprojectDepthPoint, updateOrbitTarget, wrapXRDepthInfo };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createDepthUnprojector } from "./depth-unprojection.js";
|
|
2
|
+
import { bresenham3d } from "./bresenham3d.js";
|
|
3
|
+
//#region ../src/ar/occupancy-grid.ts
|
|
4
|
+
var OccupancyGrid = class {
|
|
5
|
+
cellSizeM;
|
|
6
|
+
carveStopCells;
|
|
7
|
+
cells = /* @__PURE__ */ new Map();
|
|
8
|
+
constructor(options) {
|
|
9
|
+
const cellSizeM = options?.cellSizeM ?? .15;
|
|
10
|
+
const carveStopCells = options?.carveStopCells ?? 2;
|
|
11
|
+
if (!Number.isFinite(cellSizeM) || cellSizeM <= 0) throw new RangeError(`cellSizeM must be a positive number, got ${cellSizeM}`);
|
|
12
|
+
if (!Number.isSafeInteger(carveStopCells) || carveStopCells < 0) throw new RangeError(`carveStopCells must be a non-negative integer, got ${carveStopCells}`);
|
|
13
|
+
this.cellSizeM = cellSizeM;
|
|
14
|
+
this.carveStopCells = carveStopCells;
|
|
15
|
+
}
|
|
16
|
+
/** Number of occupied cells. */
|
|
17
|
+
get size() {
|
|
18
|
+
return this.cells.size;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Fold one depth sample into the grid: unproject each point, carve free
|
|
22
|
+
* space from the camera cell to the point cell, then count the point's
|
|
23
|
+
* cell as occupied. Points that cannot be unprojected (no
|
|
24
|
+
* projectionMatrix on old recordings, invalid depth/coords) are skipped.
|
|
25
|
+
*
|
|
26
|
+
* Carving and incrementing run as two separate passes over the sample's
|
|
27
|
+
* points: all rays are carved first, then every endpoint is incremented.
|
|
28
|
+
* A single interleaved pass would be order-dependent — a deeper point's
|
|
29
|
+
* carve could erase the endpoint a nearer point added earlier in the same
|
|
30
|
+
* sample. Splitting the passes makes the result deterministic and lets an
|
|
31
|
+
* endpoint observed within a sample survive other rays in that same
|
|
32
|
+
* sample. (Deeper-carves-nearer still applies ACROSS samples: a later
|
|
33
|
+
* sample's ray carves an earlier sample's endpoint as before.)
|
|
34
|
+
*
|
|
35
|
+
* @returns the number of points actually added.
|
|
36
|
+
*/
|
|
37
|
+
addSample(sample) {
|
|
38
|
+
if (!isFiniteTriple(sample.cameraPos)) return 0;
|
|
39
|
+
const unprojector = createDepthUnprojector(sample.cameraPos, sample.cameraRot, sample.projectionMatrix);
|
|
40
|
+
if (!unprojector) return 0;
|
|
41
|
+
const cameraCell = this.cellForPosition(sample.cameraPos);
|
|
42
|
+
const endpoints = [];
|
|
43
|
+
for (const point of sample.points) {
|
|
44
|
+
const world = unprojector.unproject(point);
|
|
45
|
+
if (!world) continue;
|
|
46
|
+
const cell = this.cellForPosition(world);
|
|
47
|
+
if (!cellsEqual(cameraCell, cell)) this.carve(cameraCell, cell);
|
|
48
|
+
endpoints.push({
|
|
49
|
+
cell,
|
|
50
|
+
rgb: point.rgb
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
for (const endpoint of endpoints) this.increment(endpoint.cell, endpoint.rgb);
|
|
54
|
+
return endpoints.length;
|
|
55
|
+
}
|
|
56
|
+
/** Occupied cells observed at least `minObservations` times (default 1). */
|
|
57
|
+
getOccupiedCells(minObservations = 1) {
|
|
58
|
+
const result = [];
|
|
59
|
+
for (const record of this.cells.values()) if (record.count >= minObservations) result.push(record.cell);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/** Quantize a raw-WebXR position to its grid cell (round per axis). */
|
|
63
|
+
cellForPosition(pos) {
|
|
64
|
+
return [
|
|
65
|
+
Math.round(pos[0] / this.cellSizeM) + 0,
|
|
66
|
+
Math.round(pos[1] / this.cellSizeM) + 0,
|
|
67
|
+
Math.round(pos[2] / this.cellSizeM) + 0
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
/** Center of a cell in raw WebXR space (round-consistent: cell · cellSizeM). */
|
|
71
|
+
getCellCenter(cell) {
|
|
72
|
+
return [
|
|
73
|
+
cell[0] * this.cellSizeM,
|
|
74
|
+
cell[1] * this.cellSizeM,
|
|
75
|
+
cell[2] * this.cellSizeM
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Running-average color of the cell's colored observations (Iter 8), or
|
|
80
|
+
* null when the cell is unknown or was only ever observed without color
|
|
81
|
+
* (rgb option off / pre-Iter-8 recordings) — consumers fall back to
|
|
82
|
+
* height-based coloring. Channels are rounded and clamped to 0–255.
|
|
83
|
+
*/
|
|
84
|
+
getCellColor(cell) {
|
|
85
|
+
const record = this.cells.get(cellKey(cell));
|
|
86
|
+
if (!record || record.colorCount === 0) return null;
|
|
87
|
+
const average = (sum) => Math.min(255, Math.max(0, Math.round(sum / record.colorCount)));
|
|
88
|
+
return [
|
|
89
|
+
average(record.colorSum[0]),
|
|
90
|
+
average(record.colorSum[1]),
|
|
91
|
+
average(record.colorSum[2])
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Walk the grid from `startPos` to `endPos` and return the center of the
|
|
96
|
+
* first cell occupied at least `minObservations` times, or null.
|
|
97
|
+
* Port of Unity's `TryRaycast` (hook for cursor/floor-detection parity).
|
|
98
|
+
*/
|
|
99
|
+
raycast(startPos, endPos, minObservations = 1) {
|
|
100
|
+
if (!isFiniteTriple(startPos) || !isFiniteTriple(endPos)) return null;
|
|
101
|
+
let hit = null;
|
|
102
|
+
bresenham3d(this.cellForPosition(startPos), this.cellForPosition(endPos), (cell) => {
|
|
103
|
+
const record = this.cells.get(cellKey(cell));
|
|
104
|
+
if (record && record.count >= minObservations) {
|
|
105
|
+
hit = cell;
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
});
|
|
110
|
+
return hit ? this.getCellCenter(hit) : null;
|
|
111
|
+
}
|
|
112
|
+
/** Remove all occupied cells (e.g. on store swap / new session). */
|
|
113
|
+
clear() {
|
|
114
|
+
this.cells.clear();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Delete occupied cells along the camera→point ray (the space was seen
|
|
118
|
+
* through, so it must be free), stopping `carveStopCells` dominant-axis
|
|
119
|
+
* steps before the endpoint. The endpoint cell itself is additionally
|
|
120
|
+
* protected so a current observation is never erased (relevant for
|
|
121
|
+
* carveStopCells = 0 and for the unconditional start-cell visit).
|
|
122
|
+
*/
|
|
123
|
+
carve(cameraCell, pointCell) {
|
|
124
|
+
bresenham3d(cameraCell, pointCell, (cell) => {
|
|
125
|
+
if (!cellsEqual(cell, pointCell)) this.cells.delete(cellKey(cell));
|
|
126
|
+
return true;
|
|
127
|
+
}, this.carveStopCells);
|
|
128
|
+
}
|
|
129
|
+
increment(cell, rgb) {
|
|
130
|
+
const key = cellKey(cell);
|
|
131
|
+
let record = this.cells.get(key);
|
|
132
|
+
if (!record) {
|
|
133
|
+
record = {
|
|
134
|
+
cell,
|
|
135
|
+
count: 0,
|
|
136
|
+
colorCount: 0,
|
|
137
|
+
colorSum: [
|
|
138
|
+
0,
|
|
139
|
+
0,
|
|
140
|
+
0
|
|
141
|
+
]
|
|
142
|
+
};
|
|
143
|
+
this.cells.set(key, record);
|
|
144
|
+
}
|
|
145
|
+
record.count++;
|
|
146
|
+
if (rgb && isFiniteTriple(rgb)) {
|
|
147
|
+
record.colorCount++;
|
|
148
|
+
record.colorSum[0] += rgb[0];
|
|
149
|
+
record.colorSum[1] += rgb[1];
|
|
150
|
+
record.colorSum[2] += rgb[2];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
function cellKey(cell) {
|
|
155
|
+
return `${cell[0]},${cell[1]},${cell[2]}`;
|
|
156
|
+
}
|
|
157
|
+
function cellsEqual(a, b) {
|
|
158
|
+
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
|
|
159
|
+
}
|
|
160
|
+
function isFiniteTriple(v) {
|
|
161
|
+
return Number.isFinite(v[0]) && Number.isFinite(v[1]) && Number.isFinite(v[2]);
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
export { OccupancyGrid };
|