gps-plus-slam-app-framework 1.3.0 → 1.7.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/absolute-orientation-CAEh840t.d.ts +54 -0
- package/dist/{app-selectors-B8VQoMDY.d.ts → app-selectors-ikTUQZpe.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 +74 -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 +1 -154
- package/dist/ar/image-quality.d.ts +2 -0
- package/dist/ar/image-quality.js +168 -0
- package/dist/ar/index.d.ts +31 -19
- package/dist/ar/index.js +17 -5
- 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-MFpFlV7j.js +382 -0
- package/dist/{ar-world-group-alignment-C9lwwIyI.d.ts → ar-world-group-alignment-69fgnB39.d.ts} +1 -1
- package/dist/{camera-blit-capture-CnxUDbKx.d.ts → camera-blit-capture-NKT9H5-e.d.ts} +57 -4
- package/dist/capture-motion-gate-0Yl2NpQG.d.ts +24 -0
- package/dist/capture-motion-gate-4EscNZCw.js +94 -0
- package/dist/concurrency-By0oOTZt.d.ts +50 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/create-slam-app-store-DkCHOpvb.js +149 -0
- package/dist/{create-slam-app-store-BljusOua.d.ts → create-slam-app-store-n_VF63D5.d.ts} +50 -40
- package/dist/depth-grid-lookup-DQ-IYhO3.d.ts +21 -0
- package/dist/{depth-sampler-Bhanfwla.d.ts → depth-sampler-asc8exHt.d.ts} +1 -1
- package/dist/{depth-unprojection-Y6i_mJuT.d.ts → depth-unprojection-DjsbJW9L.d.ts} +1 -1
- package/dist/{enable-gps-ar-BfUJOgpW.d.ts → enable-gps-ar-DkNhFv4Y.d.ts} +4 -4
- package/dist/{frame-conversions-BfsafGWR.d.ts → frame-conversions-DObhj8Wf.d.ts} +1 -1
- package/dist/{fused-path-DJoVu98b.d.ts → fused-path-v6eOmLl3.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/geodesic-angle-BTldfFgs.js +39 -0
- package/dist/{gps-anchor-dYeCvvp5.d.ts → gps-anchor-BeFO2RYa.d.ts} +1 -1
- package/dist/{gps-ar-pose-sampler-DVb96iOy.d.ts → gps-ar-pose-sampler-DHMZmYAc.d.ts} +1 -1
- package/dist/{gps-event-coordinator-CWIDOk4U.d.ts → gps-event-coordinator-CjWW636x.d.ts} +21 -11
- package/dist/{gps-event-markers-Dc9FT-q1.d.ts → gps-event-markers-Cz9RMA2k.d.ts} +18 -0
- package/dist/{h3-proximity-I1msGPgE.d.ts → h3-proximity-C-j0pw1j.d.ts} +39 -1
- package/dist/image-capture-B2-b6tKK.js +374 -0
- package/dist/{image-capture-qAZ5Fh5X.d.ts → image-capture-CNZDrQXG.d.ts} +103 -3
- package/dist/image-quality-smCBoFj1.d.ts +158 -0
- package/dist/index-BAPokxMb.d.ts +22 -0
- package/dist/{index-BYYUdf9v.d.ts → index-BODDUTvD.d.ts} +1 -1
- package/dist/index-BneSj4_5.d.ts +399 -0
- package/dist/index-_Rhgoqkh.d.ts +2 -0
- package/dist/index.d.ts +80 -67
- package/dist/index.js +24 -12
- package/dist/{leaflet-map-overlay-6CJC-E1l.d.ts → leaflet-map-overlay-CmIyiE8n.d.ts} +1 -1
- package/dist/{map-data-CkloBxIl.d.ts → map-data-B4oEehUh.d.ts} +1 -1
- package/dist/{map-overlay-draw-krOKwDAq.d.ts → map-overlay-draw-ymz1s7fG.d.ts} +1 -1
- package/dist/{null-storage-backend-uKQE8tl7.d.ts → null-storage-backend-B9rtyDfG.d.ts} +2 -2
- package/dist/{occupancy-grid-BRNRaPyT.d.ts → occupancy-grid-DnOc-6PU.d.ts} +12 -2
- package/dist/{opfs-storage-D5zAn_lx.d.ts → opfs-storage-3VZh-qH6.d.ts} +23 -4
- package/dist/{opfs-storage-backend-6aYMUZlJ.d.ts → opfs-storage-backend-JJiUJo-3.d.ts} +3 -3
- package/dist/{persistence-middleware-B9ei4Nhx.d.ts → persistence-middleware-DAPTTwB2.d.ts} +1 -1
- package/dist/planar-pnp-my9bqULS.d.ts +67 -0
- package/dist/qr-debug-view-Brl73HGO.d.ts +29 -0
- package/dist/qr-derived-pose-CmxTP2m4.d.ts +122 -0
- package/dist/qr-detected-slice-VQssZmQJ.d.ts +166 -0
- package/dist/qr-detection-controller-B-tLVd-S.js +119 -0
- package/dist/qr-detection-controller-CDurHv8E.d.ts +48 -0
- package/dist/qr-frontend--fFX25mV.d.ts +58 -0
- package/dist/qr-pose-D2rfu5DA.d.ts +138 -0
- package/dist/qr-pose-aggregation-B7tWJq7j.d.ts +90 -0
- package/dist/qr-pose-aggregation-DBiaLW0O.js +191 -0
- package/dist/qr-size-depth-context-CeAG53fy.d.ts +13 -0
- package/dist/qr-size-measurer-DLb33geH.js +596 -0
- package/dist/qr-size-measurer-DQdAsid1.d.ts +165 -0
- package/dist/recording-options-BPCpMHLK.d.ts +514 -0
- package/dist/{recording-replayer-r0DZ9ALp.d.ts → recording-replayer-DQKpAGyQ.d.ts} +2 -2
- package/dist/recording-slice-DaxHZvNu.d.ts +55 -0
- package/dist/{replay-engine-XpDa0dy1.d.ts → replay-engine-CmKh_4wf.d.ts} +7 -4
- package/dist/{replay-scene-Bw58VL6p.d.ts → replay-scene-DBlDrV_x.d.ts} +3 -3
- package/dist/sensors/absolute-orientation.d.ts +2 -0
- package/dist/sensors/absolute-orientation.js +145 -0
- package/dist/sensors/gps-error-handler.d.ts +1 -1
- package/dist/sensors/gps.d.ts +1 -1
- package/dist/sensors/index.d.ts +5 -4
- package/dist/sensors/index.js +2 -1
- 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/combined-root-state.d.ts +1 -1
- package/dist/state/create-slam-app-store.d.ts +1 -1
- package/dist/state/create-slam-app-store.js +1 -74
- package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
- package/dist/state/gps-event-coordinator.d.ts +2 -2
- package/dist/state/gps-event-coordinator.js +27 -17
- package/dist/state/index.d.ts +20 -17
- package/dist/state/index.js +5 -4
- package/dist/state/persistence-middleware.d.ts +1 -1
- package/dist/state/persistence-middleware.js +30 -19
- 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 +273 -7
- package/dist/state/recording-replayer.d.ts +2 -2
- package/dist/state/recording-replayer.js +1 -1
- 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 +2 -2
- package/dist/state/tracking-quality.js +40 -128
- package/dist/state/tracking-slice.d.ts +1 -1
- package/dist/{state-BkIfpiyA.js → state-E51QT_-j.js} +2 -1
- package/dist/storage/file-system-utils.d.ts +1 -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 +5 -4
- 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 +37 -59
- package/dist/storage/zip-reader.d.ts +1 -1
- package/dist/{storage-backend-Do6YDpNo.d.ts → storage-backend-BdVLD0dU.d.ts} +1 -1
- package/dist/{store-subscribers-0YWyOLvV.d.ts → store-subscribers-CA6AJH_8.d.ts} +2 -2
- package/dist/{subscribe-to-selector-CZ6k1NFP.d.ts → subscribe-to-selector-B6EUmQ3I.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 +5 -1
- package/dist/{tracking-slice-CdK8c-8P.d.ts → tracking-slice-BuAZxXZ5.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 +8 -7
- package/dist/utils/index.js +2 -1
- 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-TIaD883f.js → webxr-session-DG-eNfny.js} +244 -11
- package/dist/{webxr-session-GjZ2BIpS.d.ts → webxr-session-Do5L6G3B.d.ts} +86 -8
- package/dist/zip-coverage-embed-DCAREVmw.d.ts +41 -0
- package/dist/{zip-export-G14hkSPG.d.ts → zip-export-ol0S_Ofd.d.ts} +27 -23
- package/package.json +2 -2
- package/dist/concurrency-CO1FQvs3.d.ts +0 -24
- package/dist/file-system-CwXTNHmB.d.ts +0 -114
- package/dist/index-CF05QV2m.d.ts +0 -2
- 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-BtqOP_v1.d.ts} +0 -0
- /package/dist/{alignment-lerper-Dm9G7JJO.d.ts → alignment-lerper-Bil6vbVw.d.ts} +0 -0
- /package/dist/{ar-types-Dan1MVTi.d.ts → ar-types-BFX2r1wk.d.ts} +0 -0
- /package/dist/{bresenham3d-CFfd7n6B.d.ts → bresenham3d-CU1Gv2W4.d.ts} +0 -0
- /package/dist/{camera-follower-C2mwEn7c.d.ts → camera-follower-CqJJ7zRj.d.ts} +0 -0
- /package/dist/{capability-checker-CQXJHxXg.d.ts → capability-checker-DUQLg6gq.d.ts} +0 -0
- /package/dist/{capture-failure-tracker-CUeU3L_M.d.ts → capture-failure-tracker-gsm8q7yi.d.ts} +0 -0
- /package/dist/{chromium-camera-access-workaround-Jqbn0apH.d.ts → chromium-camera-access-workaround-D4K2uwwQ.d.ts} +0 -0
- /package/dist/{css3d-renderer-manager-ptu_t3B3.d.ts → css3d-renderer-manager-B3kGNtcn.d.ts} +0 -0
- /package/dist/{failure-tracker-Cn8Fo9D4.d.ts → failure-tracker-BO7M7jF_.d.ts} +0 -0
- /package/dist/{file-system-utils-CXIoL-DM.d.ts → file-system-utils-DOajnMm-.d.ts} +0 -0
- /package/dist/{format-file-size-CLCZz3lC.d.ts → format-file-size-Dc98ulzB.d.ts} +0 -0
- /package/dist/{frame-loop-CMewS8cY.d.ts → frame-loop-CpVjQ1dH.d.ts} +0 -0
- /package/dist/{frustum-visibility-CmI96ESS.d.ts → frustum-visibility-CeoCZchO.d.ts} +0 -0
- /package/dist/{geo-types-SaPnIEUM.d.ts → geo-types-3Ip1NzVR.d.ts} +0 -0
- /package/dist/{gps-VuezJWK3.d.ts → gps-BBO3hKfT.d.ts} +0 -0
- /package/dist/{gps-compass-cubes-BM1uGiYV.d.ts → gps-compass-cubes-DSN87L8p.d.ts} +0 -0
- /package/dist/{gps-error-handler-CJl_2MVm.d.ts → gps-error-handler-BbPsfw-_.d.ts} +0 -0
- /package/dist/{hit-test-reticle-BEzEybxZ.d.ts → hit-test-reticle-DV-2TpB9.d.ts} +0 -0
- /package/dist/{lerp-utils-BpV-0Dtk.d.ts → lerp-utils-BLmHhkVe.d.ts} +0 -0
- /package/dist/{list-formatter-RRcBe8Vg.d.ts → list-formatter-KNMS0ay2.d.ts} +0 -0
- /package/dist/{logger-BEHXsnnq.d.ts → logger-CLnPrHiD.d.ts} +0 -0
- /package/dist/{map-overlay-bcogY5fK.d.ts → map-overlay-Dh2B97MP.d.ts} +0 -0
- /package/dist/{permission-checker-GpAiSCwd.d.ts → permission-checker-CDuI8BwC.d.ts} +0 -0
- /package/dist/{scene-node-names-Bu0S66H1.d.ts → scene-node-names-BiShOQDB.d.ts} +0 -0
- /package/dist/{session-disposers-M-oashRH.js → session-disposers-zv9OGXPB.js} +0 -0
- /package/dist/{three-dispose-D950t71B.d.ts → three-dispose-Bt1k7WKh.d.ts} +0 -0
- /package/dist/{vis-colors-D6omC64K.d.ts → vis-colors-Obw2pLqT.d.ts} +0 -0
- /package/dist/{webxr-nue-basis-CqKQmZzT.d.ts → webxr-nue-basis-3nfjDJJp.d.ts} +0 -0
- /package/dist/{xr-camera-texture-DBwCaKTd.d.ts → xr-camera-texture-Cva1q3Qj.d.ts} +0 -0
- /package/dist/{xr-error-handler-C0AQRmxs.d.ts → xr-error-handler-CtvHpjfm.d.ts} +0 -0
- /package/dist/{xr-frame-loop-BtYytZJg.d.ts → xr-frame-loop-DIYtRcUa.d.ts} +0 -0
- /package/dist/{zip-reader-DSEXt7K6.d.ts → zip-reader-D9iPQwdQ.d.ts} +0 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
//#region ../src/ar/planar-pnp.ts
|
|
2
|
+
const EPS = 1e-12;
|
|
3
|
+
/**
|
|
4
|
+
* Solve the dense square linear system `A·x = b` by Gaussian elimination with
|
|
5
|
+
* partial pivoting. `A` is `n×n` ROW-MAJOR (length n²), `b` is length n. Returns
|
|
6
|
+
* the solution, or `null` when the matrix is singular (no unique solution).
|
|
7
|
+
*/
|
|
8
|
+
function solveLinear(A, b) {
|
|
9
|
+
const n = b.length;
|
|
10
|
+
if (A.length !== n * n || n === 0) return null;
|
|
11
|
+
const m = A.slice();
|
|
12
|
+
const y = b.slice();
|
|
13
|
+
for (let col = 0; col < n; col++) {
|
|
14
|
+
let pivot = col;
|
|
15
|
+
let best = Math.abs(m[col * n + col]);
|
|
16
|
+
for (let row = col + 1; row < n; row++) {
|
|
17
|
+
const v = Math.abs(m[row * n + col]);
|
|
18
|
+
if (v > best) {
|
|
19
|
+
best = v;
|
|
20
|
+
pivot = row;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (best < EPS) return null;
|
|
24
|
+
if (pivot !== col) {
|
|
25
|
+
for (let k = 0; k < n; k++) {
|
|
26
|
+
const tmp = m[col * n + k];
|
|
27
|
+
m[col * n + k] = m[pivot * n + k];
|
|
28
|
+
m[pivot * n + k] = tmp;
|
|
29
|
+
}
|
|
30
|
+
const tmp = y[col];
|
|
31
|
+
y[col] = y[pivot];
|
|
32
|
+
y[pivot] = tmp;
|
|
33
|
+
}
|
|
34
|
+
const diag = m[col * n + col];
|
|
35
|
+
for (let row = col + 1; row < n; row++) {
|
|
36
|
+
const factor = m[row * n + col] / diag;
|
|
37
|
+
if (factor === 0) continue;
|
|
38
|
+
for (let k = col; k < n; k++) m[row * n + k] = m[row * n + k] - factor * m[col * n + k];
|
|
39
|
+
y[row] = y[row] - factor * y[col];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const x = new Array(n).fill(0);
|
|
43
|
+
for (let row = n - 1; row >= 0; row--) {
|
|
44
|
+
let sum = y[row];
|
|
45
|
+
for (let k = row + 1; k < n; k++) sum -= m[row * n + k] * x[k];
|
|
46
|
+
x[row] = sum / m[row * n + row];
|
|
47
|
+
}
|
|
48
|
+
return x.every(Number.isFinite) ? x : null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Estimate the exact planar homography H (row-major 3×3, h₃₃ = 1) mapping object
|
|
52
|
+
* plane coords `(X,Y,1)` to NORMALIZED image coords `(x,y,1)` from 4 (or more)
|
|
53
|
+
* correspondences via a DLT. Returns `null` when the configuration is degenerate
|
|
54
|
+
* (collinear points → singular system). `objectXY`/`imageXY` are parallel arrays
|
|
55
|
+
* of `[X,Y]` / `[x,y]`.
|
|
56
|
+
*/
|
|
57
|
+
function homographyFromCorrespondences(objectXY, imageXY) {
|
|
58
|
+
const n = objectXY.length;
|
|
59
|
+
if (n < 4 || imageXY.length !== n) return null;
|
|
60
|
+
const A = [];
|
|
61
|
+
const b = [];
|
|
62
|
+
for (let i = 0; i < 4; i++) {
|
|
63
|
+
const o = objectXY[i];
|
|
64
|
+
const im = imageXY[i];
|
|
65
|
+
const X = o[0];
|
|
66
|
+
const Y = o[1];
|
|
67
|
+
const x = im[0];
|
|
68
|
+
const y = im[1];
|
|
69
|
+
A.push(X, Y, 1, 0, 0, 0, -x * X, -x * Y);
|
|
70
|
+
b.push(x);
|
|
71
|
+
A.push(0, 0, 0, X, Y, 1, -y * X, -y * Y);
|
|
72
|
+
b.push(y);
|
|
73
|
+
}
|
|
74
|
+
const h = solveLinear(A, b);
|
|
75
|
+
if (!h) return null;
|
|
76
|
+
const H = [
|
|
77
|
+
h[0],
|
|
78
|
+
h[1],
|
|
79
|
+
h[2],
|
|
80
|
+
h[3],
|
|
81
|
+
h[4],
|
|
82
|
+
h[5],
|
|
83
|
+
h[6],
|
|
84
|
+
h[7],
|
|
85
|
+
1
|
|
86
|
+
];
|
|
87
|
+
return H.every(Number.isFinite) ? H : null;
|
|
88
|
+
}
|
|
89
|
+
/** Multiply two row-major 3×3 matrices: returns A·B. */
|
|
90
|
+
function mul3(a, b) {
|
|
91
|
+
const out = new Array(9).fill(0);
|
|
92
|
+
for (let r = 0; r < 3; r++) for (let c = 0; c < 3; c++) out[r * 3 + c] = a[r * 3] * b[c] + a[r * 3 + 1] * b[3 + c] + a[r * 3 + 2] * b[6 + c];
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
/** Transpose a row-major 3×3 matrix. */
|
|
96
|
+
function transpose3(m) {
|
|
97
|
+
return [
|
|
98
|
+
m[0],
|
|
99
|
+
m[3],
|
|
100
|
+
m[6],
|
|
101
|
+
m[1],
|
|
102
|
+
m[4],
|
|
103
|
+
m[7],
|
|
104
|
+
m[2],
|
|
105
|
+
m[5],
|
|
106
|
+
m[8]
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
/** Determinant of a row-major 3×3 matrix. */
|
|
110
|
+
function det3(m) {
|
|
111
|
+
return m[0] * (m[4] * m[8] - m[5] * m[7]) - m[1] * (m[3] * m[8] - m[5] * m[6]) + m[2] * (m[3] * m[7] - m[4] * m[6]);
|
|
112
|
+
}
|
|
113
|
+
/** Inverse-transpose of a row-major 3×3 matrix, or `null` if singular. */
|
|
114
|
+
function invTranspose3(m) {
|
|
115
|
+
const d = det3(m);
|
|
116
|
+
if (Math.abs(d) < EPS) return null;
|
|
117
|
+
const inv = 1 / d;
|
|
118
|
+
const c00 = m[4] * m[8] - m[5] * m[7];
|
|
119
|
+
const c01 = -(m[3] * m[8] - m[5] * m[6]);
|
|
120
|
+
const c02 = m[3] * m[7] - m[4] * m[6];
|
|
121
|
+
const c10 = -(m[1] * m[8] - m[2] * m[7]);
|
|
122
|
+
const c11 = m[0] * m[8] - m[2] * m[6];
|
|
123
|
+
const c12 = -(m[0] * m[7] - m[1] * m[6]);
|
|
124
|
+
const c20 = m[1] * m[5] - m[2] * m[4];
|
|
125
|
+
const c21 = -(m[0] * m[5] - m[2] * m[3]);
|
|
126
|
+
const c22 = m[0] * m[4] - m[1] * m[3];
|
|
127
|
+
return [
|
|
128
|
+
c00 * inv,
|
|
129
|
+
c01 * inv,
|
|
130
|
+
c02 * inv,
|
|
131
|
+
c10 * inv,
|
|
132
|
+
c11 * inv,
|
|
133
|
+
c12 * inv,
|
|
134
|
+
c20 * inv,
|
|
135
|
+
c21 * inv,
|
|
136
|
+
c22 * inv
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Project a near-orthogonal 3×3 matrix onto the nearest rotation (SO(3)) by
|
|
141
|
+
* iterative polar decomposition (Higham: `R_{k+1} = ½(R_k + R_k⁻ᵀ)`). Converges
|
|
142
|
+
* quadratically for an already near-orthonormal input; needs no SVD/eigensolver
|
|
143
|
+
* (gl-matrix has neither). Forces `det = +1` by flipping the least-aligned column
|
|
144
|
+
* if the input was left-handed. Returns the input unchanged if it is singular.
|
|
145
|
+
*/
|
|
146
|
+
function nearestRotation3x3(input) {
|
|
147
|
+
let R = input;
|
|
148
|
+
for (let i = 0; i < 12; i++) {
|
|
149
|
+
const invT = invTranspose3(R);
|
|
150
|
+
if (!invT) break;
|
|
151
|
+
const next = [
|
|
152
|
+
.5 * (R[0] + invT[0]),
|
|
153
|
+
.5 * (R[1] + invT[1]),
|
|
154
|
+
.5 * (R[2] + invT[2]),
|
|
155
|
+
.5 * (R[3] + invT[3]),
|
|
156
|
+
.5 * (R[4] + invT[4]),
|
|
157
|
+
.5 * (R[5] + invT[5]),
|
|
158
|
+
.5 * (R[6] + invT[6]),
|
|
159
|
+
.5 * (R[7] + invT[7]),
|
|
160
|
+
.5 * (R[8] + invT[8])
|
|
161
|
+
];
|
|
162
|
+
let delta = 0;
|
|
163
|
+
for (let k = 0; k < 9; k++) delta += Math.abs(next[k] - R[k]);
|
|
164
|
+
R = next;
|
|
165
|
+
if (delta < 1e-15) break;
|
|
166
|
+
}
|
|
167
|
+
if (det3(R) < 0) R = [
|
|
168
|
+
R[0],
|
|
169
|
+
R[1],
|
|
170
|
+
-R[2],
|
|
171
|
+
R[3],
|
|
172
|
+
R[4],
|
|
173
|
+
-R[5],
|
|
174
|
+
R[6],
|
|
175
|
+
R[7],
|
|
176
|
+
-R[8]
|
|
177
|
+
];
|
|
178
|
+
return R;
|
|
179
|
+
}
|
|
180
|
+
/** Cross product a × b. */
|
|
181
|
+
function cross(a, b) {
|
|
182
|
+
return [
|
|
183
|
+
a[1] * b[2] - a[2] * b[1],
|
|
184
|
+
a[2] * b[0] - a[0] * b[2],
|
|
185
|
+
a[0] * b[1] - a[1] * b[0]
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Generate the planar-pose candidates from a homography via true IPPE
|
|
190
|
+
* (Collins & Bartoli 2014). Returns up to 4 chirality-valid candidates (the two
|
|
191
|
+
* tilt-flip roots × the z-sign of the in-plane axes); the caller picks the
|
|
192
|
+
* lowest reprojection error. Returns `[]` when the homography is degenerate.
|
|
193
|
+
*
|
|
194
|
+
* Derivation (see planar-pnp.ts.md): with `[r1 r2]` the first two columns of R,
|
|
195
|
+
* the local first-order map at the model origin gives `M·[r1 r2] = t_z·J` where
|
|
196
|
+
* `M = [[1,0,-p],[0,1,-q]]`, `(p,q)` is the origin's normalized image and `J` the
|
|
197
|
+
* 2×2 Jacobian of the homography there. Aligning the origin's ray to +z (rotation
|
|
198
|
+
* `Rv`) reduces this to a 2×2 block `B = τ·g` whose unknown bottom row plus the
|
|
199
|
+
* orthonormality of `[r1 r2]` yields a biquadratic in `τ = t_z`:
|
|
200
|
+
* `D²·τ⁴ − S·τ² + 1 = 0`, with `S = ‖g‖_F²`, `D = det(g)`. Its two roots are the
|
|
201
|
+
* two planar poses (they coincide fronto-parallel — no flip there).
|
|
202
|
+
*/
|
|
203
|
+
function ippePoseCandidates(H) {
|
|
204
|
+
const p = H[2];
|
|
205
|
+
const q = H[5];
|
|
206
|
+
const Jxx = H[0] - p * H[6];
|
|
207
|
+
const Jxy = H[1] - p * H[7];
|
|
208
|
+
const Jyx = H[3] - q * H[6];
|
|
209
|
+
const Jyy = H[4] - q * H[7];
|
|
210
|
+
const t = Math.hypot(p, q);
|
|
211
|
+
const s = Math.hypot(p, q, 1);
|
|
212
|
+
let Rv;
|
|
213
|
+
if (t < EPS) Rv = [
|
|
214
|
+
1,
|
|
215
|
+
0,
|
|
216
|
+
0,
|
|
217
|
+
0,
|
|
218
|
+
1,
|
|
219
|
+
0,
|
|
220
|
+
0,
|
|
221
|
+
0,
|
|
222
|
+
1
|
|
223
|
+
];
|
|
224
|
+
else {
|
|
225
|
+
const ax = q / t;
|
|
226
|
+
const ay = -p / t;
|
|
227
|
+
const c = 1 / s;
|
|
228
|
+
const sn = t / s;
|
|
229
|
+
const omc = 1 - c;
|
|
230
|
+
Rv = [
|
|
231
|
+
c + ax * ax * omc,
|
|
232
|
+
ax * ay * omc,
|
|
233
|
+
ay * sn,
|
|
234
|
+
ax * ay * omc,
|
|
235
|
+
c + ay * ay * omc,
|
|
236
|
+
-ax * sn,
|
|
237
|
+
-ay * sn,
|
|
238
|
+
ax * sn,
|
|
239
|
+
c
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
const RvT = transpose3(Rv);
|
|
243
|
+
const w = Rv[6] * p + Rv[7] * q + Rv[8];
|
|
244
|
+
if (Math.abs(w) < EPS) return [];
|
|
245
|
+
const g00 = (Rv[0] * Jxx + Rv[1] * Jyx) / w;
|
|
246
|
+
const g01 = (Rv[0] * Jxy + Rv[1] * Jyy) / w;
|
|
247
|
+
const g10 = (Rv[3] * Jxx + Rv[4] * Jyx) / w;
|
|
248
|
+
const g11 = (Rv[3] * Jxy + Rv[4] * Jyy) / w;
|
|
249
|
+
const G1 = g00 * g00 + g10 * g10;
|
|
250
|
+
const G2 = g01 * g01 + g11 * g11;
|
|
251
|
+
const G12 = g00 * g01 + g10 * g11;
|
|
252
|
+
const S = G1 + G2;
|
|
253
|
+
const D = g00 * g11 - g01 * g10;
|
|
254
|
+
const tauSquared = [];
|
|
255
|
+
if (Math.abs(D) < EPS) {
|
|
256
|
+
if (S > EPS) tauSquared.push(1 / S);
|
|
257
|
+
} else {
|
|
258
|
+
const disc = Math.max(0, S * S - 4 * D * D);
|
|
259
|
+
const root = Math.sqrt(disc);
|
|
260
|
+
const denom = 2 * D * D;
|
|
261
|
+
const t1 = (S + root) / denom;
|
|
262
|
+
const t2 = (S - root) / denom;
|
|
263
|
+
if (t1 > EPS) tauSquared.push(t1);
|
|
264
|
+
if (t2 > EPS && Math.abs(t2 - t1) > 1e-9 * t1) tauSquared.push(t2);
|
|
265
|
+
}
|
|
266
|
+
const candidates = [];
|
|
267
|
+
for (const tau2 of tauSquared) {
|
|
268
|
+
const tau = Math.sqrt(tau2);
|
|
269
|
+
const a2 = Math.max(0, 1 - tau2 * G1);
|
|
270
|
+
const b2 = Math.max(0, 1 - tau2 * G2);
|
|
271
|
+
const aMag = Math.sqrt(a2);
|
|
272
|
+
const bMag = Math.sqrt(b2);
|
|
273
|
+
const wantNeg = -tau2 * G12 < 0;
|
|
274
|
+
for (const signA of [1, -1]) {
|
|
275
|
+
const a = signA * aMag;
|
|
276
|
+
let b = bMag;
|
|
277
|
+
if (wantNeg) b = -b;
|
|
278
|
+
b *= signA;
|
|
279
|
+
const cand = buildCandidate(g00, g10, g01, g11, a, b, tau, Rv, RvT);
|
|
280
|
+
if (cand) candidates.push(cand);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return candidates;
|
|
284
|
+
}
|
|
285
|
+
/** Assemble a pose candidate from the aligned-frame 2×2 block + z-components. */
|
|
286
|
+
function buildCandidate(g00, g10, g01, g11, a, b, tau, Rv, RvT) {
|
|
287
|
+
const r1 = [
|
|
288
|
+
tau * g00,
|
|
289
|
+
tau * g10,
|
|
290
|
+
a
|
|
291
|
+
];
|
|
292
|
+
const r2 = [
|
|
293
|
+
tau * g01,
|
|
294
|
+
tau * g11,
|
|
295
|
+
b
|
|
296
|
+
];
|
|
297
|
+
const r3 = cross(r1, r2);
|
|
298
|
+
const R = nearestRotation3x3(mul3(RvT, [
|
|
299
|
+
r1[0],
|
|
300
|
+
r2[0],
|
|
301
|
+
r3[0],
|
|
302
|
+
r1[1],
|
|
303
|
+
r2[1],
|
|
304
|
+
r3[1],
|
|
305
|
+
r1[2],
|
|
306
|
+
r2[2],
|
|
307
|
+
r3[2]
|
|
308
|
+
]));
|
|
309
|
+
const tt = [
|
|
310
|
+
tau * Rv[6],
|
|
311
|
+
tau * Rv[7],
|
|
312
|
+
tau * Rv[8]
|
|
313
|
+
];
|
|
314
|
+
if (!R.every(Number.isFinite) || !tt.every(Number.isFinite)) return null;
|
|
315
|
+
return {
|
|
316
|
+
R,
|
|
317
|
+
t: tt
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Convert a row-major rotation matrix to a Rodrigues vector (axis · angle).
|
|
322
|
+
* Double-precision, no gl-matrix Float32 round-trip.
|
|
323
|
+
*/
|
|
324
|
+
function rotationToRodrigues(R) {
|
|
325
|
+
let cosTheta = (R[0] + R[4] + R[8] - 1) / 2;
|
|
326
|
+
cosTheta = Math.min(1, Math.max(-1, cosTheta));
|
|
327
|
+
const theta = Math.acos(cosTheta);
|
|
328
|
+
if (theta < 1e-9) return [
|
|
329
|
+
0,
|
|
330
|
+
0,
|
|
331
|
+
0
|
|
332
|
+
];
|
|
333
|
+
if (Math.PI - theta < 1e-6) {
|
|
334
|
+
const xx = (R[0] + 1) / 2;
|
|
335
|
+
const yy = (R[4] + 1) / 2;
|
|
336
|
+
const zz = (R[8] + 1) / 2;
|
|
337
|
+
let ax = Math.sqrt(Math.max(0, xx));
|
|
338
|
+
let ay = Math.sqrt(Math.max(0, yy));
|
|
339
|
+
let az = Math.sqrt(Math.max(0, zz));
|
|
340
|
+
if (ax >= ay && ax >= az) {
|
|
341
|
+
ay = Math.sign(R[1] + R[3]) * ay;
|
|
342
|
+
az = Math.sign(R[2] + R[6]) * az;
|
|
343
|
+
} else if (ay >= az) {
|
|
344
|
+
ax = Math.sign(R[1] + R[3]) * ax;
|
|
345
|
+
az = Math.sign(R[5] + R[7]) * az;
|
|
346
|
+
} else {
|
|
347
|
+
ax = Math.sign(R[2] + R[6]) * ax;
|
|
348
|
+
ay = Math.sign(R[5] + R[7]) * ay;
|
|
349
|
+
}
|
|
350
|
+
const norm = Math.hypot(ax, ay, az) || 1;
|
|
351
|
+
return [
|
|
352
|
+
theta * ax / norm,
|
|
353
|
+
theta * ay / norm,
|
|
354
|
+
theta * az / norm
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
const k = theta / (2 * Math.sin(theta));
|
|
358
|
+
return [
|
|
359
|
+
k * (R[7] - R[5]),
|
|
360
|
+
k * (R[2] - R[6]),
|
|
361
|
+
k * (R[3] - R[1])
|
|
362
|
+
];
|
|
363
|
+
}
|
|
364
|
+
/** RMS reprojection error (px) of a candidate against the detected corners. */
|
|
365
|
+
function reprojectionError(cand, objectPoints, imagePoints, intr) {
|
|
366
|
+
const { R, t } = cand;
|
|
367
|
+
let sumSq = 0;
|
|
368
|
+
for (let i = 0; i < objectPoints.length; i++) {
|
|
369
|
+
const o = objectPoints[i];
|
|
370
|
+
const img = imagePoints[i];
|
|
371
|
+
const cx = R[0] * o[0] + R[1] * o[1] + R[2] * o[2] + t[0];
|
|
372
|
+
const cy = R[3] * o[0] + R[4] * o[1] + R[5] * o[2] + t[1];
|
|
373
|
+
const cz = R[6] * o[0] + R[7] * o[1] + R[8] * o[2] + t[2];
|
|
374
|
+
if (!(cz > 0)) return Infinity;
|
|
375
|
+
const px = intr.cx + intr.fx * cx / cz;
|
|
376
|
+
const py = intr.cy + intr.fy * cy / cz;
|
|
377
|
+
const dx = px - img.x;
|
|
378
|
+
const dy = py - img.y;
|
|
379
|
+
sumSq += dx * dx + dy * dy;
|
|
380
|
+
}
|
|
381
|
+
return Math.sqrt(sumSq / objectPoints.length);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* `SolvePnpSquare` for a centered planar square via pure-JS IPPE. Stateless and
|
|
385
|
+
* synchronous — construct once and reuse, or construct per call; either is fine.
|
|
386
|
+
*/
|
|
387
|
+
var PlanarPnpSquare = class {
|
|
388
|
+
solve(objectPoints, imagePoints, intrinsics) {
|
|
389
|
+
if (objectPoints.length !== imagePoints.length || objectPoints.length < 4) return null;
|
|
390
|
+
const { fx, fy, cx, cy } = intrinsics;
|
|
391
|
+
if (!(fx > 0) || !(fy > 0) || !Number.isFinite(cx) || !Number.isFinite(cy)) return null;
|
|
392
|
+
const objectXY = [];
|
|
393
|
+
const imageXY = [];
|
|
394
|
+
for (let i = 0; i < objectPoints.length; i++) {
|
|
395
|
+
const o = objectPoints[i];
|
|
396
|
+
const im = imagePoints[i];
|
|
397
|
+
if (!Number.isFinite(im.x) || !Number.isFinite(im.y)) return null;
|
|
398
|
+
objectXY.push([o[0], o[1]]);
|
|
399
|
+
imageXY.push([(im.x - cx) / fx, (im.y - cy) / fy]);
|
|
400
|
+
}
|
|
401
|
+
const H = homographyFromCorrespondences(objectXY, imageXY);
|
|
402
|
+
if (!H) return null;
|
|
403
|
+
const candidates = ippePoseCandidates(H);
|
|
404
|
+
if (candidates.length === 0) return null;
|
|
405
|
+
let best = null;
|
|
406
|
+
let bestErr = Infinity;
|
|
407
|
+
for (const cand of candidates) {
|
|
408
|
+
const err = reprojectionError(cand, objectPoints, imagePoints, intrinsics);
|
|
409
|
+
if (err < bestErr) {
|
|
410
|
+
bestErr = err;
|
|
411
|
+
best = cand;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (!best || !Number.isFinite(bestErr)) return null;
|
|
415
|
+
const rvec = rotationToRodrigues(best.R);
|
|
416
|
+
const tvec = best.t;
|
|
417
|
+
if (![...rvec, ...tvec].every(Number.isFinite) || !(tvec[2] > 0)) return null;
|
|
418
|
+
return {
|
|
419
|
+
rvec,
|
|
420
|
+
tvec
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
//#endregion
|
|
425
|
+
export { PlanarPnpSquare, homographyFromCorrespondences, ippePoseCandidates, nearestRotation3x3, rotationToRodrigues, solveLinear };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { WEBXR_TO_NUE } from "./webxr-nue-basis.js";
|
|
2
|
+
import { AxesHelper, BoxGeometry, Group, Mesh, MeshBasicMaterial } from "three";
|
|
3
|
+
//#region ../src/ar/qr-debug-view.ts
|
|
4
|
+
/**
|
|
5
|
+
* QR debug view — the shared 3D "is it glued to the code?" overlay: a 3D axis at
|
|
6
|
+
* the solved QR pose plus a semi-transparent cube sized to the QR so its front
|
|
7
|
+
* face lands on the printed corners. Both are parented under `arWorldGroup` so
|
|
8
|
+
* they ride the alignment / transform chain exactly like real content.
|
|
9
|
+
*
|
|
10
|
+
* Promoted to the framework (recorder live-QR WS-5) as the **shared consumer**
|
|
11
|
+
* view: both the QR-tracking demo and the GPS Recorder render this same overlay
|
|
12
|
+
* off the derived pose (`qr-derived-pose.ts` / `selectSolvedQrPose`). The view is
|
|
13
|
+
* pure three.js — it knows nothing about who solved the pose, so it runs
|
|
14
|
+
* identically live and on replay.
|
|
15
|
+
*
|
|
16
|
+
* Persistence across detection misses: `update()` is called on every lock;
|
|
17
|
+
* `clear()` is NOT called on a miss, so the objects keep their last pose and
|
|
18
|
+
* never flicker between throttled detections. They stay hidden until the first
|
|
19
|
+
* `update()`.
|
|
20
|
+
*
|
|
21
|
+
* Coordinate space (IMPORTANT): the QR pose is in **raw WebXR** space (the
|
|
22
|
+
* corners are depth-unprojected with the raw WebXR camera pose), but the
|
|
23
|
+
* injected `parent` (`arWorldGroup`) local space is **NUE**. So the objects must
|
|
24
|
+
* ride the SAME {@link WEBXR_TO_NUE} basis the camera does (`arWorldGroup →
|
|
25
|
+
* basisChangeNode → arpose → camera`). We mirror that by hanging them off an
|
|
26
|
+
* internal basis node carrying `WEBXR_TO_NUE`; parenting them directly under
|
|
27
|
+
* `arWorldGroup` would leave them East/North axis-swapped and they would NOT
|
|
28
|
+
* line up with the camera/QR on a real device (the recurring scene-frame bug —
|
|
29
|
+
* see the frame-tile / occupancy-cube / hit-test-reticle precedents). We never
|
|
30
|
+
* touch the camera; WebXR owns its pose.
|
|
31
|
+
*
|
|
32
|
+
* @see qr-derived-pose.ts — derives the `Pose` this view renders (size + PnP).
|
|
33
|
+
* @see webxr-nue-basis.ts — the `WEBXR_TO_NUE` basis the objects ride.
|
|
34
|
+
*/
|
|
35
|
+
/** A thin slab so the cube's front face sits on the printed code (1 cm deep). */
|
|
36
|
+
const CUBE_DEPTH_M = .01;
|
|
37
|
+
/**
|
|
38
|
+
* Create the debug axis + cube under `parent` (the `arWorldGroup`). Objects
|
|
39
|
+
* start hidden; the first `update()` reveals and positions them.
|
|
40
|
+
*/
|
|
41
|
+
function createQrDebugView(parent) {
|
|
42
|
+
const basis = new Group();
|
|
43
|
+
basis.name = "qr-debug-basis";
|
|
44
|
+
basis.matrix.copy(WEBXR_TO_NUE);
|
|
45
|
+
basis.matrixAutoUpdate = false;
|
|
46
|
+
parent.add(basis);
|
|
47
|
+
const axes = new AxesHelper(.15);
|
|
48
|
+
axes.visible = false;
|
|
49
|
+
const cubeMaterial = new MeshBasicMaterial({
|
|
50
|
+
color: 3399167,
|
|
51
|
+
transparent: true,
|
|
52
|
+
opacity: .35,
|
|
53
|
+
depthWrite: false
|
|
54
|
+
});
|
|
55
|
+
const cube = new Mesh(new BoxGeometry(1, 1, 1), cubeMaterial);
|
|
56
|
+
cube.visible = false;
|
|
57
|
+
basis.add(axes);
|
|
58
|
+
basis.add(cube);
|
|
59
|
+
function applyPose(object, pose) {
|
|
60
|
+
object.position.set(pose.position[0], pose.position[1], pose.position[2]);
|
|
61
|
+
object.quaternion.set(pose.rotation[0], pose.rotation[1], pose.rotation[2], pose.rotation[3]);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
update(pose, sizeM) {
|
|
65
|
+
applyPose(axes, pose);
|
|
66
|
+
axes.visible = true;
|
|
67
|
+
if (sizeM === null) {
|
|
68
|
+
cube.visible = false;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
applyPose(cube, pose);
|
|
72
|
+
cube.scale.set(sizeM, sizeM, CUBE_DEPTH_M);
|
|
73
|
+
cube.translateZ(-CUBE_DEPTH_M / 2);
|
|
74
|
+
cube.visible = true;
|
|
75
|
+
},
|
|
76
|
+
clear() {
|
|
77
|
+
axes.visible = false;
|
|
78
|
+
cube.visible = false;
|
|
79
|
+
},
|
|
80
|
+
dispose() {
|
|
81
|
+
parent.remove(basis);
|
|
82
|
+
axes.dispose();
|
|
83
|
+
cube.geometry.dispose();
|
|
84
|
+
cubeMaterial.dispose();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { createQrDebugView };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as createIncrementalQrPlacement, c as deriveSolvedQrPose, i as RawQrObservation, l as solveQrPoseFromObservation, n as DerivedQrPlacement, o as deriveQrPlacement, r as IncrementalQrPlacement, s as deriveQrSizeM, t as DeriveQrPoseDeps } from "../qr-derived-pose-CmxTP2m4.js";
|
|
2
|
+
export { DeriveQrPoseDeps, DerivedQrPlacement, IncrementalQrPlacement, RawQrObservation, createIncrementalQrPlacement, deriveQrPlacement, deriveQrSizeM, deriveSolvedQrPose, solveQrPoseFromObservation };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { t as createQrSizeMeasurer } from "../qr-size-measurer-DLb33geH.js";
|
|
2
|
+
import { intrinsicsFromProjection, solveQrPose } from "./qr-pose.js";
|
|
3
|
+
//#region ../src/ar/qr-derived-pose.ts
|
|
4
|
+
/**
|
|
5
|
+
* Replay a marker's observation history through a fresh size measurer (each
|
|
6
|
+
* observation sized against its own as-of depth context) and return the running
|
|
7
|
+
* median size in meters, or `null` if no observation could be sized yet.
|
|
8
|
+
*
|
|
9
|
+
* Re-runs the whole history per call — O(history), bounded by the slice ring
|
|
10
|
+
* buffer. Cheap for v1; memoize if the debug-viz cadence makes it hot.
|
|
11
|
+
*/
|
|
12
|
+
function deriveQrSizeM(text, observations, resolveDepthAt, sizeOptions) {
|
|
13
|
+
if (observations.length === 0) return null;
|
|
14
|
+
const measurer = createQrSizeMeasurer(sizeOptions);
|
|
15
|
+
for (const o of observations) {
|
|
16
|
+
const ctx = resolveDepthAt(o.timestamp);
|
|
17
|
+
if (!ctx) continue;
|
|
18
|
+
measurer.measure(text, o.corners, {
|
|
19
|
+
width: o.imageWidth,
|
|
20
|
+
height: o.imageHeight
|
|
21
|
+
}, ctx);
|
|
22
|
+
}
|
|
23
|
+
return measurer.current(text).estimateM;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Solve the world pose of ONE raw observation given a known metric size. Pure
|
|
27
|
+
* (no depth): intrinsics come from the observation's projection + frame size,
|
|
28
|
+
* the corners + camera pose are recorded. Returns `null` when PnP rejects the
|
|
29
|
+
* quad or exceeds the reprojection gate.
|
|
30
|
+
*/
|
|
31
|
+
function solveQrPoseFromObservation(observation, sizeM, solver, maxReprojectionErrorPx) {
|
|
32
|
+
const intrinsics = intrinsicsFromProjection(observation.projectionMatrix, observation.imageWidth, observation.imageHeight);
|
|
33
|
+
const solution = solveQrPose({
|
|
34
|
+
imagePoints: observation.corners,
|
|
35
|
+
sizeM,
|
|
36
|
+
intrinsics,
|
|
37
|
+
cameraPose: observation.cameraPose,
|
|
38
|
+
solver,
|
|
39
|
+
...maxReprojectionErrorPx !== void 0 ? { maxReprojectionErrorPx } : {}
|
|
40
|
+
});
|
|
41
|
+
return solution ? solution.qrPoseWorld : null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Derive the best-effort {@link DerivedQrPlacement} for a marker: accumulate the
|
|
45
|
+
* size across the whole observation history (each against its as-of depth), then
|
|
46
|
+
* solve the LATEST observation with that size — returning BOTH the pose and the
|
|
47
|
+
* size (the debug cube needs the size; deriving once avoids two history passes).
|
|
48
|
+
* Returns `null` when there is no history, no observation can be sized, or PnP
|
|
49
|
+
* rejects the latest quad — callers render nothing (best-effort viz) rather than
|
|
50
|
+
* throwing.
|
|
51
|
+
*/
|
|
52
|
+
function deriveQrPlacement(text, observations, deps) {
|
|
53
|
+
if (observations.length === 0) return null;
|
|
54
|
+
const sizeM = deriveQrSizeM(text, observations, deps.resolveDepthAt, deps.sizeOptions);
|
|
55
|
+
if (sizeM === null) return null;
|
|
56
|
+
const latest = observations[observations.length - 1];
|
|
57
|
+
const pose = solveQrPoseFromObservation(latest, sizeM, deps.solver, deps.maxReprojectionErrorPx);
|
|
58
|
+
return pose ? {
|
|
59
|
+
pose,
|
|
60
|
+
sizeM
|
|
61
|
+
} : null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Derive only the best-effort solved WORLD pose for a marker (the size is an
|
|
65
|
+
* internal of {@link deriveQrPlacement}). Returns `null` under the same
|
|
66
|
+
* conditions. Kept for callers that need the pose alone.
|
|
67
|
+
*/
|
|
68
|
+
function deriveSolvedQrPose(text, observations, deps) {
|
|
69
|
+
return deriveQrPlacement(text, observations, deps)?.pose ?? null;
|
|
70
|
+
}
|
|
71
|
+
function createIncrementalQrPlacement(deps) {
|
|
72
|
+
const measurer = createQrSizeMeasurer(deps.sizeOptions);
|
|
73
|
+
const lastFoldedTs = /* @__PURE__ */ new Map();
|
|
74
|
+
const lastPlacement = /* @__PURE__ */ new Map();
|
|
75
|
+
return {
|
|
76
|
+
update(text, observations) {
|
|
77
|
+
if (observations.length === 0) return null;
|
|
78
|
+
const newestTs = observations[observations.length - 1].timestamp;
|
|
79
|
+
const foldedTs = lastFoldedTs.get(text) ?? -Infinity;
|
|
80
|
+
if (newestTs <= foldedTs) return lastPlacement.get(text) ?? null;
|
|
81
|
+
let maxFoldedTs = foldedTs;
|
|
82
|
+
for (const o of observations) {
|
|
83
|
+
if (o.timestamp <= foldedTs) continue;
|
|
84
|
+
const ctx = deps.resolveDepthAt(o.timestamp);
|
|
85
|
+
if (!ctx) continue;
|
|
86
|
+
measurer.measure(text, o.corners, {
|
|
87
|
+
width: o.imageWidth,
|
|
88
|
+
height: o.imageHeight
|
|
89
|
+
}, ctx);
|
|
90
|
+
maxFoldedTs = o.timestamp;
|
|
91
|
+
}
|
|
92
|
+
lastFoldedTs.set(text, maxFoldedTs);
|
|
93
|
+
const sizeM = measurer.current(text).estimateM;
|
|
94
|
+
let placement = null;
|
|
95
|
+
if (sizeM !== null) {
|
|
96
|
+
const latest = observations[observations.length - 1];
|
|
97
|
+
const pose = solveQrPoseFromObservation(latest, sizeM, deps.solver, deps.maxReprojectionErrorPx);
|
|
98
|
+
placement = pose ? {
|
|
99
|
+
pose,
|
|
100
|
+
sizeM
|
|
101
|
+
} : null;
|
|
102
|
+
}
|
|
103
|
+
lastPlacement.set(text, placement);
|
|
104
|
+
return placement;
|
|
105
|
+
},
|
|
106
|
+
reset(text) {
|
|
107
|
+
lastFoldedTs.delete(text);
|
|
108
|
+
lastPlacement.delete(text);
|
|
109
|
+
measurer.reset(text);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { createIncrementalQrPlacement, deriveQrPlacement, deriveQrSizeM, deriveSolvedQrPose, solveQrPoseFromObservation };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as createQrDetectionController, i as RawObservationSink, n as QrDetectionControllerDeps, r as QrScanStatus, t as QrDetectionController } from "../qr-detection-controller-CDurHv8E.js";
|
|
2
|
+
export { QrDetectionController, QrDetectionControllerDeps, QrScanStatus, RawObservationSink, createQrDetectionController };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as QrFrontEnd, c as createBarcodeDetectorFrontEnd, i as QrDetection, n as BarcodeDetectorLike, o as RgbaImage, r as DetectedBarcodeLike, s as ToImageBitmapSource, t as BarcodeDetectorFrontEnd } from "../qr-frontend--fFX25mV.js";
|
|
2
|
+
export { BarcodeDetectorFrontEnd, BarcodeDetectorLike, DetectedBarcodeLike, QrDetection, QrFrontEnd, RgbaImage, ToImageBitmapSource, createBarcodeDetectorFrontEnd };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//#region ../src/ar/qr-frontend.ts
|
|
2
|
+
const defaultToImageData = (image) => new ImageData(new Uint8ClampedArray(image.data), image.width, image.height);
|
|
3
|
+
var BarcodeDetectorFrontEnd = class {
|
|
4
|
+
kind = "barcode-detector";
|
|
5
|
+
detector;
|
|
6
|
+
toSource;
|
|
7
|
+
constructor(detector, toSource = defaultToImageData) {
|
|
8
|
+
this.detector = detector;
|
|
9
|
+
this.toSource = toSource;
|
|
10
|
+
}
|
|
11
|
+
async detect(image) {
|
|
12
|
+
const results = await this.detector.detect(this.toSource(image));
|
|
13
|
+
for (const r of results) {
|
|
14
|
+
const corners = toQuad(r.cornerPoints);
|
|
15
|
+
if (corners && typeof r.rawValue === "string" && r.rawValue.length > 0) return {
|
|
16
|
+
corners,
|
|
17
|
+
text: r.rawValue
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Build a {@link BarcodeDetectorFrontEnd} if the runtime exposes a
|
|
25
|
+
* `BarcodeDetector` constructor; otherwise `null` (→ the caller must handle the
|
|
26
|
+
* unsupported-browser case; there is no OpenCV fallback). `ctor` is injectable
|
|
27
|
+
* for tests.
|
|
28
|
+
*/
|
|
29
|
+
function createBarcodeDetectorFrontEnd(ctor) {
|
|
30
|
+
const Ctor = ctor ?? globalThis.BarcodeDetector;
|
|
31
|
+
if (!Ctor) return null;
|
|
32
|
+
return new BarcodeDetectorFrontEnd(new Ctor({ formats: ["qr_code"] }));
|
|
33
|
+
}
|
|
34
|
+
function toQuad(points) {
|
|
35
|
+
if (points.length !== 4) return null;
|
|
36
|
+
const [a, b, c, d] = points;
|
|
37
|
+
if (!a || !b || !c || !d) return null;
|
|
38
|
+
const out = [
|
|
39
|
+
{
|
|
40
|
+
x: a.x,
|
|
41
|
+
y: a.y
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
x: b.x,
|
|
45
|
+
y: b.y
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
x: c.x,
|
|
49
|
+
y: c.y
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
x: d.x,
|
|
53
|
+
y: d.y
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
if (out.some((p) => !Number.isFinite(p.x) || !Number.isFinite(p.y))) return null;
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { BarcodeDetectorFrontEnd, createBarcodeDetectorFrontEnd };
|