gps-plus-slam-app-framework 1.2.0 → 1.4.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/dist/{app-selectors-CcZ5UV-8.d.ts → app-selectors-CrOucL00.d.ts} +1 -1
- package/dist/ar/bresenham3d.d.ts +1 -1
- package/dist/ar/camera-blit-capture.d.ts +2 -2
- package/dist/ar/camera-blit-capture.js +87 -9
- package/dist/ar/capability-checker.d.ts +1 -1
- package/dist/ar/capture-failure-tracker.d.ts +1 -1
- package/dist/ar/chromium-camera-access-workaround.d.ts +1 -1
- package/dist/ar/depth-grid-lookup.d.ts +2 -0
- package/dist/ar/depth-grid-lookup.js +43 -0
- package/dist/ar/depth-sampler.d.ts +2 -2
- package/dist/ar/depth-sampler.js +2 -4
- package/dist/ar/depth-unprojection.d.ts +1 -1
- package/dist/ar/enable-gps-ar.d.ts +1 -1
- package/dist/ar/enable-gps-ar.js +26 -1
- package/dist/ar/frame-loop.d.ts +1 -1
- package/dist/ar/image-capture.d.ts +2 -2
- package/dist/ar/image-capture.js +7 -5
- package/dist/ar/index.d.ts +30 -19
- package/dist/ar/index.js +15 -4
- package/dist/ar/occupancy-grid.d.ts +1 -1
- package/dist/ar/occupancy-grid.js +29 -2
- package/dist/ar/planar-pnp.d.ts +2 -0
- package/dist/ar/planar-pnp.js +425 -0
- package/dist/ar/qr-debug-view.d.ts +2 -0
- package/dist/ar/qr-debug-view.js +89 -0
- package/dist/ar/qr-derived-pose.d.ts +2 -0
- package/dist/ar/qr-derived-pose.js +114 -0
- package/dist/ar/qr-detection-controller.d.ts +2 -0
- package/dist/ar/qr-detection-controller.js +2 -0
- package/dist/ar/qr-frontend.d.ts +2 -0
- package/dist/ar/qr-frontend.js +60 -0
- package/dist/ar/qr-pose.d.ts +2 -0
- package/dist/ar/qr-pose.js +267 -0
- package/dist/ar/qr-size-depth-context.d.ts +3 -0
- package/dist/ar/qr-size-depth-context.js +42 -0
- package/dist/ar/qr-size-measurer.d.ts +2 -0
- package/dist/ar/qr-size-measurer.js +2 -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 +1 -1
- package/dist/ar/webxr-session.d.ts +4 -4
- package/dist/ar/webxr-session.js +2 -2
- 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 +1 -1
- package/dist/ar-Dc72Ksiu.js +382 -0
- package/dist/{ar-world-group-alignment-ColHB_5A.d.ts → ar-world-group-alignment-DDLkgNf_.d.ts} +1 -1
- package/dist/{camera-blit-capture-D5_mNhla.d.ts → camera-blit-capture-CqWe1G7s.d.ts} +66 -4
- package/dist/concurrency-DqAlKX4j.d.ts +50 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/{create-slam-app-store-qNXQ-tq5.d.ts → create-slam-app-store-Ce2FMKCp.d.ts} +5 -5
- package/dist/depth-grid-lookup-vMDrGTk_.d.ts +21 -0
- package/dist/{depth-sampler-Bhanfwla.d.ts → depth-sampler-DwfPYzS7.d.ts} +1 -1
- package/dist/{depth-unprojection-Y6i_mJuT.d.ts → depth-unprojection-CK3b9htH.d.ts} +1 -1
- package/dist/{enable-gps-ar-CapmADVN.d.ts → enable-gps-ar-CjZ1nEdu.d.ts} +4 -4
- package/dist/file-system-utils-Bs7lpeVP.d.ts +40 -0
- package/dist/{frame-conversions-BfsafGWR.d.ts → frame-conversions-D_Af8Dt3.d.ts} +1 -1
- package/dist/{fused-path-DJoVu98b.d.ts → fused-path-C0FHe67t.d.ts} +1 -1
- package/dist/geo/h3-proximity.d.ts +2 -2
- package/dist/geo/h3-proximity.js +61 -2
- package/dist/geo/index.d.ts +2 -2
- package/dist/geo/index.js +2 -2
- package/dist/{gps-anchor-dYeCvvp5.d.ts → gps-anchor-C6I9NDHA.d.ts} +1 -1
- package/dist/{gps-ar-pose-sampler-DVb96iOy.d.ts → gps-ar-pose-sampler-DaHg2aPZ.d.ts} +1 -1
- package/dist/{gps-event-coordinator-BDIQde2x.d.ts → gps-event-coordinator-C7hwHyLA.d.ts} +3 -3
- package/dist/{gps-event-markers-Dc9FT-q1.d.ts → gps-event-markers-Dd0G2bD0.d.ts} +18 -0
- package/dist/{h3-proximity-I1msGPgE.d.ts → h3-proximity-B57C6rYL.d.ts} +39 -1
- package/dist/{image-capture-DZ4HFRgS.d.ts → image-capture-C2Feywet.d.ts} +30 -4
- package/dist/index-BvLo2poy.d.ts +399 -0
- package/dist/{index-KJq2amHF.d.ts → index-m_LneUSi.d.ts} +1 -1
- package/dist/index.d.ts +77 -67
- package/dist/index.js +18 -9
- package/dist/{leaflet-map-overlay-6CJC-E1l.d.ts → leaflet-map-overlay-bnb2U1NQ.d.ts} +1 -1
- package/dist/{map-data-CkloBxIl.d.ts → map-data-DdjPiSav.d.ts} +1 -1
- package/dist/{map-overlay-draw-krOKwDAq.d.ts → map-overlay-draw-CPVdAsFq.d.ts} +1 -1
- package/dist/{null-storage-backend-CxEjZHRW.d.ts → null-storage-backend-CuSdYFsM.d.ts} +2 -2
- package/dist/{occupancy-grid-BRNRaPyT.d.ts → occupancy-grid-Bvi2Gw1U.d.ts} +12 -2
- package/dist/{opfs-storage-DYm8WHq7.d.ts → opfs-storage-DMtlt57S.d.ts} +25 -6
- package/dist/{opfs-storage-backend-CjSAuqg_.d.ts → opfs-storage-backend-CeQAb0SD.d.ts} +3 -3
- package/dist/{persistence-middleware-DwyzgfWr.d.ts → persistence-middleware-5eearhSV.d.ts} +1 -1
- package/dist/planar-pnp-cjthe7Be.d.ts +67 -0
- package/dist/qr-debug-view-B8gTzjMm.d.ts +29 -0
- package/dist/qr-derived-pose-DinKE600.d.ts +122 -0
- package/dist/qr-detected-slice-Cpv-dg82.d.ts +166 -0
- package/dist/qr-detection-controller-B-tLVd-S.js +119 -0
- package/dist/qr-detection-controller-Cd2Ih4cC.d.ts +48 -0
- package/dist/qr-frontend-DufZp2xS.d.ts +58 -0
- package/dist/qr-pose-Bt2reSw7.d.ts +138 -0
- package/dist/qr-pose-aggregation-CFlmLm6w.d.ts +90 -0
- package/dist/qr-pose-aggregation-DXR9STHs.js +200 -0
- package/dist/qr-size-depth-context-onu36E9s.d.ts +13 -0
- package/dist/qr-size-measurer-D7739ZpB.d.ts +165 -0
- package/dist/qr-size-measurer-DLb33geH.js +596 -0
- package/dist/recording-options-D93kksJU.d.ts +369 -0
- package/dist/{recording-replayer-BL-x-7k0.d.ts → recording-replayer-CUEK82tM.d.ts} +2 -2
- package/dist/recording-slice-B-uCgGoh.d.ts +55 -0
- package/dist/{replay-engine-BRqszuQ9.d.ts → replay-engine-BSVd7Zzb.d.ts} +7 -4
- package/dist/{replay-scene-Bw58VL6p.d.ts → replay-scene-CPt56pps.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/permission-checker.d.ts +1 -1
- package/dist/sensors/permission-checker.js +1 -1
- package/dist/state/app-selectors.d.ts +1 -1
- package/dist/state/app-selectors.js +3 -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 +20 -17
- package/dist/state/index.js +3 -2
- package/dist/state/persistence-middleware.d.ts +1 -1
- package/dist/state/qr-detected-slice.d.ts +4 -0
- package/dist/state/qr-detected-slice.js +311 -0
- package/dist/state/recording-options.d.ts +2 -2
- package/dist/state/recording-options.js +128 -4
- package/dist/state/recording-replayer.d.ts +2 -2
- package/dist/state/recording-slice.d.ts +1 -1
- package/dist/state/recording-slice.js +5 -1
- package/dist/state/replay-engine.d.ts +1 -1
- package/dist/state/replay-engine.js +7 -3
- 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-slice.d.ts +1 -1
- package/dist/{state-BkIfpiyA.js → state-IEw0ZsBu.js} +1 -0
- package/dist/storage/file-system-utils.d.ts +2 -2
- package/dist/storage/file-system-utils.js +17 -1
- package/dist/storage/index.d.ts +9 -9
- package/dist/storage/index.js +4 -4
- 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-backend.js +3 -3
- package/dist/storage/opfs-storage.d.ts +1 -1
- package/dist/storage/opfs-storage.js +9 -8
- package/dist/storage/storage-backend.d.ts +1 -1
- package/dist/storage/zip-coverage-embed.d.ts +2 -0
- package/dist/storage/zip-coverage-embed.js +88 -0
- package/dist/storage/zip-export.d.ts +2 -2
- package/dist/storage/zip-export.js +38 -60
- package/dist/storage/zip-reader.d.ts +1 -1
- package/dist/storage/zip-reader.js +1 -1
- package/dist/{storage-backend-C9HLFtgz.d.ts → storage-backend-CTJHNdMp.d.ts} +1 -1
- package/dist/{store-subscribers-rrkLkFTt.d.ts → store-subscribers-BZP81hjh.d.ts} +2 -2
- package/dist/{subscribe-to-selector-CP3BKSbF.d.ts → subscribe-to-selector-Bp3sLx8L.d.ts} +1 -1
- package/dist/test-utils/browser-mocks.d.ts +7 -1
- package/dist/test-utils/browser-mocks.js +6 -0
- package/dist/test-utils/zip-round-trip-helpers.d.ts +9 -1
- package/dist/test-utils/zip-round-trip-helpers.js +8 -3
- package/dist/{tracking-slice-CdK8c-8P.d.ts → tracking-slice-DJZ9qeGg.d.ts} +1 -1
- package/dist/types/ar-types.d.ts +1 -1
- package/dist/types/geo-types.d.ts +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/utils/concurrency.d.ts +2 -2
- package/dist/utils/concurrency.js +66 -2
- 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 +1 -1
- package/dist/visualization/ar-world-group-alignment.js +1 -1
- 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 +1 -1
- package/dist/visualization/frustum-visibility.d.ts +1 -1
- package/dist/visualization/gps-anchor.d.ts +1 -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 +40 -5
- package/dist/visualization/hit-test-reticle.d.ts +1 -1
- package/dist/visualization/hit-test-reticle.js +1 -0
- package/dist/visualization/index.d.ts +18 -18
- package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
- package/dist/visualization/leaflet-map-overlay.js +15 -3
- 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-session-QhcSSub5.js → webxr-session-CJF9Nijp.js} +234 -9
- package/dist/{webxr-session-DtAG9srK.d.ts → webxr-session-COsOjJwn.d.ts} +76 -8
- package/dist/zip-coverage-embed-tFohI2J4.d.ts +41 -0
- package/dist/{zip-export-D_5HAvFz.d.ts → zip-export-DDWXInTR.d.ts} +28 -24
- package/package.json +2 -2
- package/dist/concurrency-CO1FQvs3.d.ts +0 -24
- package/dist/file-system-C_eqiEA8.d.ts +0 -114
- package/dist/file-system-utils-DyAmcuvX.d.ts +0 -24
- package/dist/recording-options-DJBXuMxV.d.ts +0 -182
- package/dist/recording-slice-89O07ebs.d.ts +0 -36
- package/dist/storage/file-system.d.ts +0 -2
- package/dist/storage/file-system.js +0 -289
- /package/dist/{accuracy-circles-BpwWAs8G.d.ts → accuracy-circles-Pi2YeCrM.d.ts} +0 -0
- /package/dist/{alignment-lerper-Dm9G7JJO.d.ts → alignment-lerper-BUTsQ2yu.d.ts} +0 -0
- /package/dist/{ar-types-Dan1MVTi.d.ts → ar-types-BckI1E-O.d.ts} +0 -0
- /package/dist/{bresenham3d-CFfd7n6B.d.ts → bresenham3d-ByjcmEgM.d.ts} +0 -0
- /package/dist/{camera-follower-C2mwEn7c.d.ts → camera-follower-BTb2Yp12.d.ts} +0 -0
- /package/dist/{capability-checker-CQXJHxXg.d.ts → capability-checker-CXqoJYtb.d.ts} +0 -0
- /package/dist/{capture-failure-tracker-CUeU3L_M.d.ts → capture-failure-tracker-BL5SoG2a.d.ts} +0 -0
- /package/dist/{chromium-camera-access-workaround-Jqbn0apH.d.ts → chromium-camera-access-workaround-BNK189P0.d.ts} +0 -0
- /package/dist/{css3d-renderer-manager-ptu_t3B3.d.ts → css3d-renderer-manager-m-ab19vp.d.ts} +0 -0
- /package/dist/{failure-tracker-Cn8Fo9D4.d.ts → failure-tracker-Cnc_xWUw.d.ts} +0 -0
- /package/dist/{format-file-size-CLCZz3lC.d.ts → format-file-size-BFUWvir1.d.ts} +0 -0
- /package/dist/{frame-loop-CMewS8cY.d.ts → frame-loop-BEO9_q3k.d.ts} +0 -0
- /package/dist/{frustum-visibility-CmI96ESS.d.ts → frustum-visibility-D_RGmH98.d.ts} +0 -0
- /package/dist/{geo-types-SaPnIEUM.d.ts → geo-types-DqiBzPO9.d.ts} +0 -0
- /package/dist/{gps-VuezJWK3.d.ts → gps-C5inWeNV.d.ts} +0 -0
- /package/dist/{gps-compass-cubes-BM1uGiYV.d.ts → gps-compass-cubes-BExxrWJ-.d.ts} +0 -0
- /package/dist/{gps-error-handler-CJl_2MVm.d.ts → gps-error-handler-C2bS69oJ.d.ts} +0 -0
- /package/dist/{hit-test-reticle-BEzEybxZ.d.ts → hit-test-reticle-Dqak9hiR.d.ts} +0 -0
- /package/dist/{index-CF05QV2m.d.ts → index-eaI1Z1KR.d.ts} +0 -0
- /package/dist/{lerp-utils-BpV-0Dtk.d.ts → lerp-utils-kLxMJ2Xj.d.ts} +0 -0
- /package/dist/{list-formatter-RRcBe8Vg.d.ts → list-formatter-ct8txwvR.d.ts} +0 -0
- /package/dist/{logger-BEHXsnnq.d.ts → logger-CzQrCe6C.d.ts} +0 -0
- /package/dist/{map-overlay-bcogY5fK.d.ts → map-overlay-Dci4yvm3.d.ts} +0 -0
- /package/dist/{permission-checker-GpAiSCwd.d.ts → permission-checker-CtPtPQRq.d.ts} +0 -0
- /package/dist/{scene-node-names-Bu0S66H1.d.ts → scene-node-names-CdNeQjOE.d.ts} +0 -0
- /package/dist/{session-disposers-M-oashRH.js → session-disposers-D8wnO-uW.js} +0 -0
- /package/dist/{three-dispose-D950t71B.d.ts → three-dispose-BCZkSJp2.d.ts} +0 -0
- /package/dist/{vis-colors-D6omC64K.d.ts → vis-colors-BZsSLI4i.d.ts} +0 -0
- /package/dist/{webxr-nue-basis-CqKQmZzT.d.ts → webxr-nue-basis-CW1wsbm6.d.ts} +0 -0
- /package/dist/{xr-camera-texture-DBwCaKTd.d.ts → xr-camera-texture-BmrP7iNZ.d.ts} +0 -0
- /package/dist/{xr-error-handler-C0AQRmxs.d.ts → xr-error-handler-BEjRgZjt.d.ts} +0 -0
- /package/dist/{xr-frame-loop-BtYytZJg.d.ts → xr-frame-loop-K8ebdjLw.d.ts} +0 -0
- /package/dist/{zip-reader-DSEXt7K6.d.ts → zip-reader-Q2ue6zlC.d.ts} +0 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import "./ar/camera-blit-capture.js";
|
|
2
|
+
import "./ar/chromium-camera-access-workaround.js";
|
|
3
|
+
import "./ar/capture-failure-tracker.js";
|
|
4
|
+
import "./ar/depth-sampler.js";
|
|
5
|
+
import "./ar/depth-unprojection.js";
|
|
6
|
+
import "./qr-size-measurer-DLb33geH.js";
|
|
7
|
+
import "./ar/qr-size-depth-context.js";
|
|
8
|
+
import { buildObjectPoints, transformPoint } from "./ar/qr-pose.js";
|
|
9
|
+
import "./ar/qr-derived-pose.js";
|
|
10
|
+
import "./ar/webxr-nue-basis.js";
|
|
11
|
+
import "./ar/qr-debug-view.js";
|
|
12
|
+
import { n as createDetectionScheduler } from "./qr-detection-controller-B-tLVd-S.js";
|
|
13
|
+
import "./qr-pose-aggregation-DXR9STHs.js";
|
|
14
|
+
import "./webxr-session-CJF9Nijp.js";
|
|
15
|
+
import "./ar/occupancy-grid.js";
|
|
16
|
+
import "./ar/frame-loop.js";
|
|
17
|
+
import "./ar/xr-frame-loop.js";
|
|
18
|
+
import "./ar/image-capture.js";
|
|
19
|
+
import "./ar/xr-camera-texture.js";
|
|
20
|
+
import "./ar/enable-gps-ar.js";
|
|
21
|
+
import "./ar/replay-scene.js";
|
|
22
|
+
//#region ../src/ar/qr-level.ts
|
|
23
|
+
/** Thrown when a fetched level file fails validation. */
|
|
24
|
+
var QrLevelValidationError = class extends Error {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(`qr-level: ${message}`);
|
|
27
|
+
this.name = "QrLevelValidationError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
function isFiniteNumber(v) {
|
|
31
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
32
|
+
}
|
|
33
|
+
function isRecord(v) {
|
|
34
|
+
return typeof v === "object" && v !== null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Validate the optional `qr.physicalSizeM`. When present it MUST be a positive
|
|
38
|
+
* number (a `0`/negative authored size is a bug, not a "measure it instead"
|
|
39
|
+
* signal). Returns `undefined` when omitted.
|
|
40
|
+
*/
|
|
41
|
+
function parsePhysicalSize(value) {
|
|
42
|
+
if (value === void 0) return void 0;
|
|
43
|
+
if (!isFiniteNumber(value) || value <= 0) throw new QrLevelValidationError("\"qr.physicalSizeM\" must be a positive number when present");
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validate the optional `qr.geo`. When present every field is validated (a
|
|
48
|
+
* partial geo is a bug — it would silently place the vote wrong). Returns
|
|
49
|
+
* `undefined` when omitted; heading is normalized into `[0, 360)`.
|
|
50
|
+
*/
|
|
51
|
+
function parseGeo(value) {
|
|
52
|
+
if (value === void 0) return void 0;
|
|
53
|
+
if (!isRecord(value)) throw new QrLevelValidationError("\"qr.geo\" must be an object when present");
|
|
54
|
+
const { lat, lon, alt, headingDeg } = value;
|
|
55
|
+
if (!isFiniteNumber(lat) || lat < -90 || lat > 90) throw new QrLevelValidationError("\"qr.geo.lat\" must be a number in [-90, 90]");
|
|
56
|
+
if (!isFiniteNumber(lon) || lon < -180 || lon > 180) throw new QrLevelValidationError("\"qr.geo.lon\" must be a number in [-180, 180]");
|
|
57
|
+
if (!isFiniteNumber(alt)) throw new QrLevelValidationError("\"qr.geo.alt\" must be a finite number");
|
|
58
|
+
if (!isFiniteNumber(headingDeg)) throw new QrLevelValidationError("\"qr.geo.headingDeg\" must be a finite number");
|
|
59
|
+
return {
|
|
60
|
+
lat,
|
|
61
|
+
lon,
|
|
62
|
+
alt,
|
|
63
|
+
headingDeg: (headingDeg % 360 + 360) % 360
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Validate an already-parsed value as a {@link QrLevel}. Throws
|
|
68
|
+
* {@link QrLevelValidationError} with a descriptive message on any violation.
|
|
69
|
+
*/
|
|
70
|
+
function parseQrLevel(data) {
|
|
71
|
+
if (!isRecord(data)) throw new QrLevelValidationError("level file must be a JSON object");
|
|
72
|
+
if (!isFiniteNumber(data.version)) throw new QrLevelValidationError("missing/invalid \"version\"");
|
|
73
|
+
if (!isRecord(data.qr)) throw new QrLevelValidationError("missing/invalid \"qr\"");
|
|
74
|
+
const physicalSizeM = parsePhysicalSize(data.qr.physicalSizeM);
|
|
75
|
+
const geo = parseGeo(data.qr.geo);
|
|
76
|
+
return {
|
|
77
|
+
version: data.version,
|
|
78
|
+
qr: {
|
|
79
|
+
...physicalSizeM !== void 0 ? { physicalSizeM } : {},
|
|
80
|
+
...geo !== void 0 ? { geo } : {}
|
|
81
|
+
},
|
|
82
|
+
content: "content" in data ? data.content : void 0
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Fetch and validate a level file from `url`. Rejects with
|
|
87
|
+
* {@link QrLevelValidationError} on a non-OK response, non-JSON body, or a
|
|
88
|
+
* schema violation.
|
|
89
|
+
*/
|
|
90
|
+
async function fetchQrLevel(url, options = {}) {
|
|
91
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
92
|
+
if (!fetchImpl) throw new QrLevelValidationError("no fetch implementation available");
|
|
93
|
+
let response;
|
|
94
|
+
try {
|
|
95
|
+
response = await fetchImpl(url, { signal: options.signal });
|
|
96
|
+
} catch (err) {
|
|
97
|
+
throw new QrLevelValidationError(`fetch failed for ${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
98
|
+
}
|
|
99
|
+
if (!response.ok) throw new QrLevelValidationError(`fetch ${url} returned status ${response.status}`);
|
|
100
|
+
let body;
|
|
101
|
+
try {
|
|
102
|
+
body = await response.json();
|
|
103
|
+
} catch {
|
|
104
|
+
throw new QrLevelValidationError(`response for ${url} was not valid JSON`);
|
|
105
|
+
}
|
|
106
|
+
return parseQrLevel(body);
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region ../src/ar/qr-gps-vote.ts
|
|
110
|
+
/** Mean meters per degree of latitude (WGS-84 average). */
|
|
111
|
+
const METERS_PER_DEG_LAT = 111320;
|
|
112
|
+
/**
|
|
113
|
+
* `count` points evenly spaced on a circle of radius `baselineM` in the QR
|
|
114
|
+
* plane (z = 0), starting at local +x. The symmetric ring's centroid is the QR
|
|
115
|
+
* center, so translation stays anchored there while the wide radius stiffens
|
|
116
|
+
* the rotation fit. Non-collinear for `count ≥ 3`.
|
|
117
|
+
*/
|
|
118
|
+
function widePlanePoints(baselineM, count) {
|
|
119
|
+
const points = [];
|
|
120
|
+
for (let i = 0; i < count; i++) {
|
|
121
|
+
const theta = 2 * Math.PI * i / count;
|
|
122
|
+
points.push([
|
|
123
|
+
baselineM * Math.cos(theta),
|
|
124
|
+
baselineM * Math.sin(theta),
|
|
125
|
+
0
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
return points;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Map a QR-local plane offset (meters; +x right, +y up on the printed face) to
|
|
132
|
+
* an East/North/Up offset, given the QR's compass heading. Vertical-QR
|
|
133
|
+
* convention: +y is world up; +x is horizontal at `headingDeg` (clockwise from
|
|
134
|
+
* North), so east = x·sin(h), north = x·cos(h).
|
|
135
|
+
*/
|
|
136
|
+
function localPlaneToEnu(localX, localY, headingDeg) {
|
|
137
|
+
const h = headingDeg * Math.PI / 180;
|
|
138
|
+
return {
|
|
139
|
+
east: localX * Math.sin(h),
|
|
140
|
+
north: localX * Math.cos(h),
|
|
141
|
+
up: localY
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** Apply an ENU meter offset to a geo pose (equirectangular; exact enough for sub-meter QR corners). */
|
|
145
|
+
function offsetGeo(center, enu) {
|
|
146
|
+
const dLat = enu.north / METERS_PER_DEG_LAT;
|
|
147
|
+
const metersPerDegLon = METERS_PER_DEG_LAT * Math.cos(center.lat * Math.PI / 180);
|
|
148
|
+
const dLon = Math.abs(metersPerDegLon) < 1e-9 ? 0 : enu.east / metersPerDegLon;
|
|
149
|
+
return {
|
|
150
|
+
latitude: center.lat + dLat,
|
|
151
|
+
longitude: center.lon + dLon,
|
|
152
|
+
altitude: center.alt + enu.up
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Build the synthetic `recordGpsEvent` payload(s) for one QR detection. Returns
|
|
157
|
+
* 4 corner correspondences by default, or 1 center correspondence when
|
|
158
|
+
* `multiCorrespondence` is false. Each payload pairs the corner's odom position
|
|
159
|
+
* (from the solved pose) with its absolute geo position (from the level file),
|
|
160
|
+
* stamped with the tiny synthetic accuracy.
|
|
161
|
+
*
|
|
162
|
+
* @throws RangeError on a non-positive `sizeM` or `syntheticAccuracyM`.
|
|
163
|
+
*/
|
|
164
|
+
function buildQrGpsVotes(input) {
|
|
165
|
+
const { qrPoseWorld, sizeM, qrGeo, syntheticAccuracyM, odomRotation = qrPoseWorld.rotation, multiCorrespondence = true, baselineM, count = 4, timestamp = Date.now(), idPrefix = "qr" } = input;
|
|
166
|
+
if (!(syntheticAccuracyM > 0) || !Number.isFinite(syntheticAccuracyM)) throw new RangeError(`qr-gps-vote: syntheticAccuracyM must be positive, got ${syntheticAccuracyM}`);
|
|
167
|
+
const useWideBaseline = baselineM !== void 0 && Number.isFinite(baselineM) && baselineM > 0;
|
|
168
|
+
if (useWideBaseline && count < 3) throw new RangeError(`qr-gps-vote: wide-baseline count must be >= 3 (collinear otherwise), got ${count}`);
|
|
169
|
+
return (useWideBaseline ? widePlanePoints(baselineM, count) : multiCorrespondence ? buildObjectPoints(sizeM) : [[
|
|
170
|
+
0,
|
|
171
|
+
0,
|
|
172
|
+
0
|
|
173
|
+
]]).map((local, i) => {
|
|
174
|
+
const odomPosition = transformPoint(local, qrPoseWorld);
|
|
175
|
+
const geo = offsetGeo(qrGeo, localPlaneToEnu(local[0], local[1], qrGeo.headingDeg));
|
|
176
|
+
return {
|
|
177
|
+
odomPosition,
|
|
178
|
+
odomRotation,
|
|
179
|
+
rawGpsPoint: {
|
|
180
|
+
id: `${idPrefix}-${timestamp}-${i}`,
|
|
181
|
+
latitude: geo.latitude,
|
|
182
|
+
longitude: geo.longitude,
|
|
183
|
+
altitude: geo.altitude,
|
|
184
|
+
latLongAccuracy: syntheticAccuracyM,
|
|
185
|
+
timestamp
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region ../src/ar/qr-tracking-controller.ts
|
|
192
|
+
function createQrTrackingController(config) {
|
|
193
|
+
const { frontEnd, solvePose, fetchLevel, dispatchVotes, onDetection, resolveSizeM, resolveStablePose, getCameraPose, getIntrinsics, syntheticAccuracyM, voteBaselineM, voteCount, isPlausible, onStatus, onLocked, onError, minIntervalMs = 150, requiredLockCount = 3, now } = config;
|
|
194
|
+
const timestampNow = now ?? (() => Date.now());
|
|
195
|
+
let status = "idle";
|
|
196
|
+
const levelCache = /* @__PURE__ */ new Map();
|
|
197
|
+
let active = null;
|
|
198
|
+
function setStatus(next) {
|
|
199
|
+
if (status === next) return;
|
|
200
|
+
status = next;
|
|
201
|
+
onStatus?.(next);
|
|
202
|
+
}
|
|
203
|
+
async function ensureLevel(url) {
|
|
204
|
+
const cached = levelCache.get(url);
|
|
205
|
+
if (cached) return cached;
|
|
206
|
+
setStatus("loading-level");
|
|
207
|
+
const level = await fetchLevel(url);
|
|
208
|
+
levelCache.set(url, level);
|
|
209
|
+
return level;
|
|
210
|
+
}
|
|
211
|
+
async function detect(image) {
|
|
212
|
+
if (status === "idle" || status === "error") setStatus("scanning");
|
|
213
|
+
const detection = await frontEnd.detect(image);
|
|
214
|
+
if (!detection) {
|
|
215
|
+
active = null;
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const level = await ensureLevel(detection.text);
|
|
219
|
+
const sizeM = level.qr.physicalSizeM ?? resolveSizeM?.(detection.text, level) ?? null;
|
|
220
|
+
if (sizeM === null || !(sizeM > 0) || !Number.isFinite(sizeM)) {
|
|
221
|
+
active = null;
|
|
222
|
+
if (status === "loading-level") setStatus("scanning");
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const cameraPose = getCameraPose();
|
|
226
|
+
const intrinsics = getIntrinsics(image);
|
|
227
|
+
if (!cameraPose || !intrinsics) {
|
|
228
|
+
active = null;
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const solution = solvePose({
|
|
232
|
+
imagePoints: detection.corners,
|
|
233
|
+
sizeM,
|
|
234
|
+
intrinsics,
|
|
235
|
+
cameraPose
|
|
236
|
+
});
|
|
237
|
+
if (!solution) {
|
|
238
|
+
active = null;
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
if (isPlausible && !isPlausible(solution, cameraPose)) {
|
|
242
|
+
active = null;
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
active = {
|
|
246
|
+
level,
|
|
247
|
+
text: detection.text,
|
|
248
|
+
sizeM
|
|
249
|
+
};
|
|
250
|
+
return solution;
|
|
251
|
+
}
|
|
252
|
+
const scheduler = createDetectionScheduler({
|
|
253
|
+
detect,
|
|
254
|
+
minIntervalMs,
|
|
255
|
+
requiredLockCount,
|
|
256
|
+
now,
|
|
257
|
+
onLocked: (solution) => {
|
|
258
|
+
const current = active;
|
|
259
|
+
if (!current) return;
|
|
260
|
+
const { level, text, sizeM } = current;
|
|
261
|
+
onDetection?.({
|
|
262
|
+
text,
|
|
263
|
+
qrPoseWorld: solution.qrPoseWorld,
|
|
264
|
+
qrPoseInCamera: solution.qrPoseInCamera,
|
|
265
|
+
reprojectionErrorPx: solution.reprojectionErrorPx,
|
|
266
|
+
timestamp: timestampNow()
|
|
267
|
+
});
|
|
268
|
+
if (level.qr.geo) {
|
|
269
|
+
const votePose = resolveStablePose ? resolveStablePose(text) : solution.qrPoseWorld;
|
|
270
|
+
if (votePose) dispatchVotes(buildQrGpsVotes({
|
|
271
|
+
qrPoseWorld: votePose,
|
|
272
|
+
sizeM,
|
|
273
|
+
qrGeo: level.qr.geo,
|
|
274
|
+
syntheticAccuracyM,
|
|
275
|
+
...voteBaselineM !== void 0 ? { baselineM: voteBaselineM } : {},
|
|
276
|
+
...voteCount !== void 0 ? { count: voteCount } : {}
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
setStatus("tracking");
|
|
280
|
+
onLocked?.(solution, level);
|
|
281
|
+
},
|
|
282
|
+
onMiss: () => {
|
|
283
|
+
if (status === "tracking") setStatus("scanning");
|
|
284
|
+
},
|
|
285
|
+
onError: (err) => {
|
|
286
|
+
active = null;
|
|
287
|
+
setStatus("error");
|
|
288
|
+
onError?.(err);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
return {
|
|
292
|
+
offerFrame(image) {
|
|
293
|
+
scheduler.offerFrame(image);
|
|
294
|
+
},
|
|
295
|
+
get status() {
|
|
296
|
+
return status;
|
|
297
|
+
},
|
|
298
|
+
reset() {
|
|
299
|
+
levelCache.clear();
|
|
300
|
+
active = null;
|
|
301
|
+
setStatus("idle");
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region ../src/ar/qr-occupancy-check.ts
|
|
307
|
+
const DEFAULTS = {
|
|
308
|
+
surfaceToleranceCells: 1.5,
|
|
309
|
+
minObservations: 1,
|
|
310
|
+
rayCorridorCells: 1,
|
|
311
|
+
occlusionMarginCells: 2
|
|
312
|
+
};
|
|
313
|
+
function distance(a, b) {
|
|
314
|
+
return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Classify a solved QR center against the occupancy grid.
|
|
318
|
+
*
|
|
319
|
+
* - `no-grid` (ok): the grid has no occupied voxels — nothing to check against.
|
|
320
|
+
* - `floating` (not ok): nearest occupied voxel is farther than the tolerance.
|
|
321
|
+
* - `behind-surface` (not ok): an occupied voxel lies on the camera→QR ray
|
|
322
|
+
* clearly in front of the QR (the QR is inside / beyond geometry).
|
|
323
|
+
* - `plausible` (ok): on a surface with empty space toward the camera.
|
|
324
|
+
*
|
|
325
|
+
* All positions are raw-WebXR/odom space (the same frame as `qrPoseWorld`).
|
|
326
|
+
*/
|
|
327
|
+
function checkQrPlausibility(qrCenterWorld, cameraPosWorld, grid, options = {}) {
|
|
328
|
+
const surfaceToleranceCells = options.surfaceToleranceCells ?? DEFAULTS.surfaceToleranceCells;
|
|
329
|
+
const minObservations = options.minObservations ?? DEFAULTS.minObservations;
|
|
330
|
+
const rayCorridorCells = options.rayCorridorCells ?? DEFAULTS.rayCorridorCells;
|
|
331
|
+
const occlusionMarginCells = options.occlusionMarginCells ?? DEFAULTS.occlusionMarginCells;
|
|
332
|
+
const cells = grid.getOccupiedCells(minObservations);
|
|
333
|
+
if (cells.length === 0) return {
|
|
334
|
+
verdict: "no-grid",
|
|
335
|
+
ok: true,
|
|
336
|
+
nearestSurfaceDistanceM: Infinity
|
|
337
|
+
};
|
|
338
|
+
const cellSizeM = grid.cellSizeM;
|
|
339
|
+
const centers = cells.map((c) => grid.getCellCenter(c));
|
|
340
|
+
let nearest = Infinity;
|
|
341
|
+
for (const center of centers) {
|
|
342
|
+
const d = distance(center, qrCenterWorld);
|
|
343
|
+
if (d < nearest) nearest = d;
|
|
344
|
+
}
|
|
345
|
+
if (nearest > surfaceToleranceCells * cellSizeM) return {
|
|
346
|
+
verdict: "floating",
|
|
347
|
+
ok: false,
|
|
348
|
+
nearestSurfaceDistanceM: nearest
|
|
349
|
+
};
|
|
350
|
+
const dir = [
|
|
351
|
+
qrCenterWorld[0] - cameraPosWorld[0],
|
|
352
|
+
qrCenterWorld[1] - cameraPosWorld[1],
|
|
353
|
+
qrCenterWorld[2] - cameraPosWorld[2]
|
|
354
|
+
];
|
|
355
|
+
const rayLength = Math.hypot(dir[0], dir[1], dir[2]);
|
|
356
|
+
if (rayLength > cellSizeM) {
|
|
357
|
+
const nx = dir[0] / rayLength;
|
|
358
|
+
const ny = dir[1] / rayLength;
|
|
359
|
+
const nz = dir[2] / rayLength;
|
|
360
|
+
const corridor = rayCorridorCells * cellSizeM;
|
|
361
|
+
const tMax = rayLength - occlusionMarginCells * cellSizeM;
|
|
362
|
+
for (const center of centers) {
|
|
363
|
+
const ox = center[0] - cameraPosWorld[0];
|
|
364
|
+
const oy = center[1] - cameraPosWorld[1];
|
|
365
|
+
const oz = center[2] - cameraPosWorld[2];
|
|
366
|
+
const t = ox * nx + oy * ny + oz * nz;
|
|
367
|
+
if (t <= cellSizeM || t >= tMax) continue;
|
|
368
|
+
if (Math.hypot(ox - t * nx, oy - t * ny, oz - t * nz) < corridor) return {
|
|
369
|
+
verdict: "behind-surface",
|
|
370
|
+
ok: false,
|
|
371
|
+
nearestSurfaceDistanceM: nearest
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
verdict: "plausible",
|
|
377
|
+
ok: true,
|
|
378
|
+
nearestSurfaceDistanceM: nearest
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
//#endregion
|
|
382
|
+
export { localPlaneToEnu as a, fetchQrLevel as c, buildQrGpsVotes as i, parseQrLevel as l, createQrTrackingController as n, offsetGeo as o, METERS_PER_DEG_LAT as r, QrLevelValidationError as s, checkQrPlausibility as t };
|
|
@@ -31,6 +31,28 @@ declare function computeCaptureSize(cameraWidth: number, cameraHeight: number, d
|
|
|
31
31
|
width: number;
|
|
32
32
|
height: number;
|
|
33
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Compute blit dimensions that PRESERVE the camera aspect ratio with the
|
|
36
|
+
* **longer edge fixed at `maxEdge`**. Unlike {@link computeCaptureSize} (which
|
|
37
|
+
* divides the native resolution by a user divisor), this fits the frame into a
|
|
38
|
+
* fixed pixel budget, so a 4:3 camera becomes e.g. 512×384 — NOT a stretched
|
|
39
|
+
* 512×512. Used by the QR-detection blit (B2) so the detector sees an
|
|
40
|
+
* undistorted code while the readback cost stays bounded by `maxEdge²`.
|
|
41
|
+
*
|
|
42
|
+
* @param cameraWidth - Native camera width in pixels (from XRCamera)
|
|
43
|
+
* @param cameraHeight - Native camera height in pixels (from XRCamera)
|
|
44
|
+
* @param maxEdge - Target length (px) of the longer output edge.
|
|
45
|
+
* @returns Integer dimensions, longer edge == `maxEdge`, aspect preserved
|
|
46
|
+
* (within rounding), each clamped to ≥ 1. When `maxEdge` itself is invalid
|
|
47
|
+
* (< 1 / NaN / Infinity) the longer edge falls back to
|
|
48
|
+
* `DEFAULT_BLIT_CONFIG.width` (still aspect-preserving). When the camera
|
|
49
|
+
* dimensions are invalid (≤ 0 / NaN / Infinity) the aspect is unknown, so it
|
|
50
|
+
* returns a square at the (possibly defaulted) edge.
|
|
51
|
+
*/
|
|
52
|
+
declare function computeAspectFitSize(cameraWidth: number, cameraHeight: number, maxEdge: number): {
|
|
53
|
+
width: number;
|
|
54
|
+
height: number;
|
|
55
|
+
};
|
|
34
56
|
/**
|
|
35
57
|
* Captures readable pixel data from WebXR opaque camera textures
|
|
36
58
|
* using the intermediate render target "blit" technique.
|
|
@@ -83,6 +105,29 @@ declare class CameraBlitCapture {
|
|
|
83
105
|
width: number;
|
|
84
106
|
height: number;
|
|
85
107
|
} | null;
|
|
108
|
+
/**
|
|
109
|
+
* Capture the camera texture as **top-left-origin** RGBA — the orientation
|
|
110
|
+
* QR detection (and `BarcodeDetector`) expects — returning a FRESH copy that
|
|
111
|
+
* is safe to retain beyond the next capture.
|
|
112
|
+
*
|
|
113
|
+
* This is the efficient replacement for the demo's old JPEG round-trip: blit
|
|
114
|
+
* + readback (shared with {@link captureToBlob} / {@link captureToPixels}),
|
|
115
|
+
* then the same vertical flip the JPEG encoder applies, but with no encode →
|
|
116
|
+
* decode. Unlike {@link captureToPixels} (which returns the reusable internal
|
|
117
|
+
* buffer in WebGL bottom-row-first order), this returns an owned,
|
|
118
|
+
* correctly-oriented `Uint8ClampedArray`.
|
|
119
|
+
*
|
|
120
|
+
* IMPORTANT: Must be called within the XR animation frame callback, while the
|
|
121
|
+
* camera texture is valid.
|
|
122
|
+
*
|
|
123
|
+
* @returns `{ data, width, height }` (top-left RGBA), or null on
|
|
124
|
+
* failure/dispose.
|
|
125
|
+
*/
|
|
126
|
+
captureToRgba(renderer: THREE.WebGLRenderer, cameraTexture: THREE.Texture): {
|
|
127
|
+
data: Uint8ClampedArray;
|
|
128
|
+
width: number;
|
|
129
|
+
height: number;
|
|
130
|
+
} | null;
|
|
86
131
|
/**
|
|
87
132
|
* Check if a pixel buffer is entirely black (all zeros).
|
|
88
133
|
* Uses sampling for performance (checks BLACK_CHECK_SAMPLE_COUNT evenly-spaced pixels).
|
|
@@ -100,13 +145,21 @@ declare class CameraBlitCapture {
|
|
|
100
145
|
*/
|
|
101
146
|
private pixelsToJpegBlob;
|
|
102
147
|
private getFlippedImageData;
|
|
148
|
+
/**
|
|
149
|
+
* Produce a FRESH, top-left-origin RGBA copy of the internal readback buffer.
|
|
150
|
+
* WebGL `readPixels` returns bottom-row-first; this copies (so the internal
|
|
151
|
+
* buffer stays y-flipped for a subsequent capture) and swaps rows. Shared by
|
|
152
|
+
* the JPEG encode path ({@link getFlippedImageData}) and {@link captureToRgba}.
|
|
153
|
+
*/
|
|
154
|
+
private flippedPixelCopy;
|
|
103
155
|
private pixelsToJpegViaOffscreenCanvas;
|
|
104
156
|
private pixelsToJpegViaCanvas;
|
|
105
157
|
/**
|
|
106
|
-
* Flip
|
|
107
|
-
* WebGL readPixels returns bottom-row-first; canvas
|
|
158
|
+
* Flip an RGBA buffer vertically in-place (row swap).
|
|
159
|
+
* WebGL readPixels returns bottom-row-first; canvas / detectors expect
|
|
160
|
+
* top-row-first.
|
|
108
161
|
*/
|
|
109
|
-
private
|
|
162
|
+
private flipRowsVertically;
|
|
110
163
|
/**
|
|
111
164
|
* Resize the render target and pixel buffer to match new dimensions.
|
|
112
165
|
* Call this when the camera resolution is first known or changes.
|
|
@@ -119,10 +172,19 @@ declare class CameraBlitCapture {
|
|
|
119
172
|
* @returns true if resources were actually resized
|
|
120
173
|
*/
|
|
121
174
|
resizeIfNeeded(newWidth: number, newHeight: number): boolean;
|
|
175
|
+
/**
|
|
176
|
+
* Current render-target width in pixels. This is exactly the width of the
|
|
177
|
+
* JPEG produced by {@link captureToBlob} (the encode canvas is sized to the
|
|
178
|
+
* render target), so callers can persist it as the captured image's true
|
|
179
|
+
* pixel width without decoding the blob.
|
|
180
|
+
*/
|
|
181
|
+
getWidth(): number;
|
|
182
|
+
/** Current render-target height in pixels. See {@link getWidth}. */
|
|
183
|
+
getHeight(): number;
|
|
122
184
|
/**
|
|
123
185
|
* Dispose GPU resources. Call when AR session ends.
|
|
124
186
|
*/
|
|
125
187
|
dispose(): void;
|
|
126
188
|
}
|
|
127
189
|
//#endregion
|
|
128
|
-
export { computeCaptureSize as i, CameraBlitCaptureConfig as n, DEFAULT_BLIT_CONFIG as r, CameraBlitCapture as t };
|
|
190
|
+
export { computeCaptureSize as a, computeAspectFitSize as i, CameraBlitCaptureConfig as n, DEFAULT_BLIT_CONFIG as r, CameraBlitCapture as t };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region ../src/utils/concurrency.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Concurrency Utilities
|
|
4
|
+
*
|
|
5
|
+
* Provides helpers for limiting the number of concurrent async operations.
|
|
6
|
+
* Used to prevent excessive memory consumption when scanning many files
|
|
7
|
+
* in parallel (e.g., reading zip metadata during scenario discovery).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Map over an array with a concurrency limit on the async mapper function.
|
|
11
|
+
*
|
|
12
|
+
* Behaves like `Promise.all(items.map(fn))` but limits how many mapper
|
|
13
|
+
* invocations run simultaneously. Results are returned in the same order
|
|
14
|
+
* as the input items.
|
|
15
|
+
*
|
|
16
|
+
* @param items - Array of items to process
|
|
17
|
+
* @param limit - Maximum number of concurrent mapper invocations
|
|
18
|
+
* @param mapper - Async function to apply to each item
|
|
19
|
+
* @returns Array of results in the same order as input items
|
|
20
|
+
* @throws Re-throws the first error from any mapper invocation (fail-fast)
|
|
21
|
+
*/
|
|
22
|
+
declare function mapWithConcurrencyLimit<T, R>(items: T[], limit: number, mapper: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Run an async worker over each item with a concurrency cap, invoking the worker
|
|
25
|
+
* per item as the pool pulls it (rather than collecting results into an array).
|
|
26
|
+
*
|
|
27
|
+
* Unlike {@link mapWithConcurrencyLimit}, this returns no values — the worker is
|
|
28
|
+
* expected to produce its side effect (e.g. emit a streaming callback) itself.
|
|
29
|
+
* This is the shape progressive consumers need: each item is handled and can be
|
|
30
|
+
* reported as soon as it settles, instead of waiting for the whole batch.
|
|
31
|
+
*
|
|
32
|
+
* Completion order is **not** guaranteed (it depends on which worker finishes
|
|
33
|
+
* first). Each item's worker is invoked exactly once unless the run is aborted.
|
|
34
|
+
*
|
|
35
|
+
* When an `AbortSignal` is supplied, workers stop pulling **new** items as soon
|
|
36
|
+
* as the signal is aborted (checked before each pull). Items already in flight
|
|
37
|
+
* run to completion — the File System Access reads underneath cannot be torn
|
|
38
|
+
* mid-read — but no further items are started, so a destroyed consumer stops
|
|
39
|
+
* accumulating work. If the signal is already aborted, no work starts at all.
|
|
40
|
+
*
|
|
41
|
+
* @param items - Items to process
|
|
42
|
+
* @param limit - Maximum number of concurrent worker invocations (`>= 1`)
|
|
43
|
+
* @param worker - Async side-effecting function applied to each item
|
|
44
|
+
* @param signal - Optional abort signal that halts pulling new items
|
|
45
|
+
* @throws RangeError if `limit < 1`
|
|
46
|
+
* @throws Re-throws the first error from any worker invocation (fail-fast)
|
|
47
|
+
*/
|
|
48
|
+
declare function forEachWithConcurrencyLimit<T>(items: readonly T[], limit: number, worker: (item: T, index: number) => Promise<void>, signal?: AbortSignal): Promise<void>;
|
|
49
|
+
//#endregion
|
|
50
|
+
export { mapWithConcurrencyLimit as n, forEachWithConcurrencyLimit as t };
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as LibraryRootState, c as RootState, d as calcRelativeCoordsInMeters, f as createGpsSlamStore, g as webxrToNUE, h as validateLicenseKey, i as LatLongAlt, l as Vector3, m as odometryTrackingRestarted, n as GpsPoint, o as Matrix4, p as isIdentityMatrix4, r as LatLong, s as Quaternion, t as ArImageCapture, u as calcGpsCoords } from "../index-
|
|
1
|
+
import { a as LibraryRootState, c as RootState, d as calcRelativeCoordsInMeters, f as createGpsSlamStore, g as webxrToNUE, h as validateLicenseKey, i as LatLongAlt, l as Vector3, m as odometryTrackingRestarted, n as GpsPoint, o as Matrix4, p as isIdentityMatrix4, r as LatLong, s as Quaternion, t as ArImageCapture, u as calcGpsCoords } from "../index-eaI1Z1KR.js";
|
|
2
2
|
export { ArImageCapture, GpsPoint, LatLong, LatLongAlt, LibraryRootState, Matrix4, Quaternion, RootState, Vector3, calcGpsCoords, calcRelativeCoordsInMeters, createGpsSlamStore, isIdentityMatrix4, odometryTrackingRestarted, validateLicenseKey, webxrToNUE };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as ARPose } from "./ar-types-
|
|
2
|
-
import { a as TrackingSliceState, t as DeviceOrientation } from "./tracking-slice-
|
|
3
|
-
import { t as RecordingState } from "./recording-slice-
|
|
4
|
-
import { n as SessionMetadata } from "./opfs-storage-
|
|
5
|
-
import { n as StorageBackend } from "./storage-backend-
|
|
1
|
+
import { t as ARPose } from "./ar-types-BckI1E-O.js";
|
|
2
|
+
import { a as TrackingSliceState, t as DeviceOrientation } from "./tracking-slice-DJZ9qeGg.js";
|
|
3
|
+
import { t as RecordingState } from "./recording-slice-B-uCgGoh.js";
|
|
4
|
+
import { n as SessionMetadata } from "./opfs-storage-DMtlt57S.js";
|
|
5
|
+
import { n as StorageBackend } from "./storage-backend-CTJHNdMp.js";
|
|
6
6
|
import { GpsPoint, LatLong, Matrix4, RootState, Vector3 } from "gps-plus-slam-js";
|
|
7
7
|
import * as _$_reduxjs_toolkit0 from "@reduxjs/toolkit";
|
|
8
8
|
import { Middleware, Reducer, ReducersMapObject, configureStore } from "@reduxjs/toolkit";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { r as DepthPoint } from "./ar-types-BckI1E-O.js";
|
|
2
|
+
|
|
3
|
+
//#region ../src/ar/depth-grid-lookup.d.ts
|
|
4
|
+
interface DepthGridLookup {
|
|
5
|
+
/**
|
|
6
|
+
* Bilinearly-interpolated depth (m) at a normalized screen point (top-left
|
|
7
|
+
* origin), or `null` when the point is outside the node grid or any of the
|
|
8
|
+
* four surrounding nodes has no valid (finite, positive) depth.
|
|
9
|
+
*/
|
|
10
|
+
depthAt(screenX: number, screenY: number): number | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Build a bilinear lookup over a depth-sampler grid.
|
|
14
|
+
*
|
|
15
|
+
* @param points - the grid `DepthPoint`s (row-major, `row·g + col`).
|
|
16
|
+
* @param gridSize - points-per-side; inferred as `round(√points.length)` when
|
|
17
|
+
* omitted (the sampler always emits a full `g²` grid).
|
|
18
|
+
*/
|
|
19
|
+
declare function createDepthGridLookup(points: readonly DepthPoint[], gridSize?: number): DepthGridLookup;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { createDepthGridLookup as n, DepthGridLookup as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as ArCrashIsolationOptions } from "./recording-options-
|
|
2
|
-
import { n as RawDeviceOrientation, t as GpsPosition } from "./gps-
|
|
3
|
-
import { n as PermissionStatus } from "./permission-checker-
|
|
4
|
-
import {
|
|
1
|
+
import { t as ArCrashIsolationOptions } from "./recording-options-D93kksJU.js";
|
|
2
|
+
import { n as RawDeviceOrientation, t as GpsPosition } from "./gps-C5inWeNV.js";
|
|
3
|
+
import { n as PermissionStatus } from "./permission-checker-CtPtPQRq.js";
|
|
4
|
+
import { r as SessionFeatureOptions } from "./webxr-session-COsOjJwn.js";
|
|
5
5
|
|
|
6
6
|
//#region ../src/ar/enable-gps-ar.d.ts
|
|
7
7
|
/**
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region ../src/storage/file-system-utils.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* File System Utilities - Pure Functions
|
|
4
|
+
*
|
|
5
|
+
* These are extracted from file-system.ts so they can be tested
|
|
6
|
+
* without mocking browser APIs.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Format a Date as a timestamp string for folder names
|
|
10
|
+
* @example "2025-02-28_14-30-11utc"
|
|
11
|
+
*/
|
|
12
|
+
declare function formatTimestamp(date: Date): string;
|
|
13
|
+
/**
|
|
14
|
+
* Format an action file index as a zero-padded filename
|
|
15
|
+
* @example 42 -> "000042.json"
|
|
16
|
+
*/
|
|
17
|
+
declare function formatActionFilename(index: number): string;
|
|
18
|
+
/**
|
|
19
|
+
* Format a frame index as a zero-padded filename
|
|
20
|
+
* @example 42 -> "frame-000042.jpg"
|
|
21
|
+
*/
|
|
22
|
+
declare function formatFrameFilename(index: number): string;
|
|
23
|
+
/**
|
|
24
|
+
* Canonical session subdirectory for captured camera images, written by new
|
|
25
|
+
* recordings. Renamed from {@link LEGACY_SESSION_IMAGES_DIR} so an exported ZIP
|
|
26
|
+
* is a textbook COLMAP tree (`images/frame-NNNNNN.jpg` next to `sparse/0/`).
|
|
27
|
+
* Both the OPFS write path and the recorder's persisted `imageFile` value must
|
|
28
|
+
* use THIS constant so a recording stays self-consistent (COLMAP export plan
|
|
29
|
+
* Q5, gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-06-13-colmap-export-plan.md).
|
|
30
|
+
*/
|
|
31
|
+
declare const SESSION_IMAGES_DIR = "images";
|
|
32
|
+
/**
|
|
33
|
+
* The pre-2026-06 session image subdirectory. Recordings made before the
|
|
34
|
+
* {@link SESSION_IMAGES_DIR} rename store frames under `frames/` (in both the
|
|
35
|
+
* on-disk dir and the persisted `imageFile`); readers fall back to this prefix
|
|
36
|
+
* so old recordings still replay.
|
|
37
|
+
*/
|
|
38
|
+
declare const LEGACY_SESSION_IMAGES_DIR = "frames";
|
|
39
|
+
//#endregion
|
|
40
|
+
export { formatTimestamp as a, formatFrameFilename as i, SESSION_IMAGES_DIR as n, formatActionFilename as r, LEGACY_SESSION_IMAGES_DIR as t };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { H3_RESOLUTION, KnownGeoAnchor, approxDistanceMetres, findNearbyGeoAnchor, gpsToH3, h3CellsMatch, isH3Index };
|
|
1
|
+
import { a as findNearbyGeoAnchor, c as h3CellsMatch, i as clusterCellsByZoom, l as isH3Index, n as KnownGeoAnchor, o as gpsPathToCoverageCells, r as approxDistanceMetres, s as gpsToH3, t as H3_RESOLUTION } from "../h3-proximity-B57C6rYL.js";
|
|
2
|
+
export { H3_RESOLUTION, KnownGeoAnchor, approxDistanceMetres, clusterCellsByZoom, findNearbyGeoAnchor, gpsPathToCoverageCells, gpsToH3, h3CellsMatch, isH3Index };
|