gps-plus-slam-app-framework 1.4.0 → 1.8.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-BAxc15J6.d.ts +54 -0
- package/dist/{accuracy-circles-Pi2YeCrM.d.ts → accuracy-circles-DkWBkF5g.d.ts} +16 -6
- package/dist/{app-selectors-CrOucL00.d.ts → app-selectors-JqydaUMN.d.ts} +1 -1
- package/dist/ar/bresenham3d.d.ts +1 -1
- package/dist/ar/camera-blit-capture.d.ts +1 -1
- 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 +1 -1
- package/dist/ar/depth-occluder.d.ts +127 -0
- package/dist/ar/depth-occluder.js +299 -0
- package/dist/ar/depth-sampler.d.ts +2 -2
- package/dist/ar/depth-sampler.js +24 -4
- package/dist/ar/depth-unprojection.d.ts +1 -1
- package/dist/ar/depth-unprojection.js +52 -21
- package/dist/ar/enable-gps-ar.d.ts +1 -1
- package/dist/ar/enable-gps-ar.js +1 -1
- package/dist/ar/frame-loop.d.ts +1 -1
- package/dist/ar/frame-loop.js +12 -2
- 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 +169 -0
- package/dist/ar/index.d.ts +32 -30
- package/dist/ar/index.js +7 -5
- package/dist/ar/occlusion-mesh-worker.d.ts +58 -0
- package/dist/ar/occlusion-mesh-worker.js +129 -0
- package/dist/ar/occupancy-grid.d.ts +2 -2
- package/dist/ar/occupancy-grid.js +287 -31
- package/dist/ar/occupancy-mesher.d.ts +2 -0
- package/dist/ar/occupancy-mesher.js +638 -0
- package/dist/ar/planar-pnp.d.ts +1 -1
- package/dist/ar/qr-debug-view.d.ts +1 -1
- package/dist/ar/qr-derived-pose.d.ts +1 -1
- package/dist/ar/qr-detection-controller.d.ts +1 -1
- package/dist/ar/qr-frontend.d.ts +1 -1
- package/dist/ar/qr-pose.d.ts +1 -1
- package/dist/ar/qr-size-depth-context.d.ts +2 -2
- package/dist/ar/qr-size-measurer.d.ts +1 -1
- 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/xr-frame-loop.js +12 -2
- package/dist/{ar-Dc72Ksiu.js → ar-BqCudtqE.js} +4 -3
- package/dist/{ar-world-group-alignment-DDLkgNf_.d.ts → ar-world-group-alignment-BlvGcIAk.d.ts} +1 -1
- package/dist/capture-motion-gate-BGoAMkmf.d.ts +24 -0
- package/dist/capture-motion-gate-_NdTSwKi.js +106 -0
- package/dist/cell-key-DIB4rOTV.js +51 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/{create-slam-app-store-Ce2FMKCp.d.ts → create-slam-app-store-D3nzh9Wb.d.ts} +50 -40
- package/dist/create-slam-app-store-DXiAnJwR.js +156 -0
- package/dist/{depth-grid-lookup-vMDrGTk_.d.ts → depth-grid-lookup-D5atpNZ3.d.ts} +1 -1
- package/dist/{depth-sampler-DwfPYzS7.d.ts → depth-sampler-CwObAwEA.d.ts} +33 -1
- package/dist/{depth-unprojection-CK3b9htH.d.ts → depth-unprojection-DRdBU90S.d.ts} +1 -1
- package/dist/{enable-gps-ar-CjZ1nEdu.d.ts → enable-gps-ar-DvGzBOrp.d.ts} +4 -4
- package/dist/{frame-conversions-D_Af8Dt3.d.ts → frame-conversions-C4bnNOEY.d.ts} +1 -1
- package/dist/{fused-path-C0FHe67t.d.ts → fused-path-C5pp0PJU.d.ts} +1 -1
- package/dist/geo/h3-proximity.d.ts +1 -1
- package/dist/geo/index.d.ts +1 -1
- package/dist/geodesic-angle-BTldfFgs.js +39 -0
- package/dist/{gps-anchor-C6I9NDHA.d.ts → gps-anchor-BjECcYnD.d.ts} +1 -1
- package/dist/{gps-ar-pose-sampler-DaHg2aPZ.d.ts → gps-ar-pose-sampler-DEGNk2CI.d.ts} +1 -1
- package/dist/{gps-event-coordinator-C7hwHyLA.d.ts → gps-event-coordinator-CHtIkhb7.d.ts} +21 -11
- package/dist/{image-capture-C2Feywet.d.ts → image-capture-Bp6Pd4Md.d.ts} +103 -3
- package/dist/image-capture-DVcuqMtX.js +374 -0
- package/dist/image-quality-BMx2ko9-.d.ts +158 -0
- package/dist/index-D4vrgV4b.d.ts +2 -0
- package/dist/index-D74JTX4I.d.ts +22 -0
- package/dist/{index-m_LneUSi.d.ts → index-DlsOo2IB.d.ts} +1 -1
- package/dist/{index-BvLo2poy.d.ts → index-cCQWuYqy.d.ts} +3 -3
- package/dist/index.d.ts +82 -77
- package/dist/index.js +15 -10
- package/dist/{leaflet-map-overlay-bnb2U1NQ.d.ts → leaflet-map-overlay-BOZFW5kT.d.ts} +64 -10
- package/dist/leaflet-map-overlay-CBWPvaX9.js +452 -0
- package/dist/lerp-utils-Qs8OhumS.d.ts +42 -0
- package/dist/map-data-D7rpMC6f.js +120 -0
- package/dist/{map-data-DdjPiSav.d.ts → map-data-PNfGKGWx.d.ts} +22 -2
- package/dist/{map-overlay-draw-CPVdAsFq.d.ts → map-overlay-draw-DiroaJed.d.ts} +1 -1
- package/dist/{null-storage-backend-CuSdYFsM.d.ts → null-storage-backend-B1-jlpin.d.ts} +2 -2
- package/dist/occlusion-mesh-b28lRJ6S.d.ts +194 -0
- package/dist/occupancy-grid-BV7RhB7I.d.ts +194 -0
- package/dist/occupancy-mesher-C2ecWhNp.d.ts +100 -0
- package/dist/{opfs-storage-DMtlt57S.d.ts → opfs-storage-CTkxvxN4.d.ts} +5 -0
- package/dist/{opfs-storage-backend-CeQAb0SD.d.ts → opfs-storage-backend-D6Z03fML.d.ts} +2 -2
- package/dist/{persistence-middleware-5eearhSV.d.ts → persistence-middleware-CG-t08X4.d.ts} +1 -1
- package/dist/{planar-pnp-cjthe7Be.d.ts → planar-pnp-D7bc_rLX.d.ts} +1 -1
- package/dist/{qr-debug-view-B8gTzjMm.d.ts → qr-debug-view-BJooqbiP.d.ts} +1 -1
- package/dist/{qr-derived-pose-DinKE600.d.ts → qr-derived-pose-BwNWn8oV.d.ts} +2 -2
- package/dist/{qr-detected-slice-Cpv-dg82.d.ts → qr-detected-slice-TzVb8bij.d.ts} +4 -4
- package/dist/{qr-detection-controller-Cd2Ih4cC.d.ts → qr-detection-controller-Bo4XfQTP.d.ts} +3 -3
- package/dist/{qr-frontend-DufZp2xS.d.ts → qr-frontend-BPURlh3S.d.ts} +1 -1
- package/dist/{qr-pose-aggregation-CFlmLm6w.d.ts → qr-pose-aggregation-CypnmjA-.d.ts} +1 -1
- package/dist/{qr-pose-aggregation-DXR9STHs.js → qr-pose-aggregation-DBiaLW0O.js} +1 -10
- package/dist/{qr-size-depth-context-onu36E9s.d.ts → qr-size-depth-context-apAOmbrZ.d.ts} +2 -2
- package/dist/{qr-size-measurer-D7739ZpB.d.ts → qr-size-measurer-DxiMqRUw.d.ts} +3 -3
- package/dist/recording-options-vASCFzc5.d.ts +667 -0
- package/dist/{recording-replayer-CUEK82tM.d.ts → recording-replayer-D0V9WkmY.d.ts} +2 -2
- package/dist/{recording-slice-B-uCgGoh.d.ts → recording-slice-DTUtSVgd.d.ts} +2 -2
- package/dist/{replay-engine-BSVd7Zzb.d.ts → replay-engine-xdrUgWJi.d.ts} +1 -1
- package/dist/{replay-scene-CPt56pps.d.ts → replay-scene-zJ8SFDjE.d.ts} +3 -3
- package/dist/sensors/absolute-orientation.d.ts +2 -0
- package/dist/sensors/absolute-orientation.js +162 -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/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 -20
- package/dist/state/index.js +4 -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 +3 -3
- package/dist/state/qr-detected-slice.js +1 -1
- package/dist/state/recording-options.d.ts +2 -2
- package/dist/state/recording-options.js +286 -25
- 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/replay-engine.d.ts +1 -1
- package/dist/state/store-subscribers.d.ts +2 -2
- package/dist/state/store-subscribers.js +2 -1
- 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 +49 -128
- package/dist/state/tracking-slice.d.ts +1 -1
- package/dist/{state-IEw0ZsBu.js → state-BeKvn4B-.js} +1 -1
- package/dist/storage/file-system-utils.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/opfs-storage.js +25 -1
- package/dist/storage/storage-backend.d.ts +1 -1
- package/dist/storage/zip-coverage-embed.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-CTJHNdMp.d.ts → storage-backend-sEtmM4yW.d.ts} +1 -1
- package/dist/{store-subscribers-BZP81hjh.d.ts → store-subscribers-DScA7tdT.d.ts} +2 -2
- package/dist/{subscribe-to-selector-Bp3sLx8L.d.ts → subscribe-to-selector-B9ylM5Oh.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-DJZ9qeGg.d.ts → tracking-slice-DMkHlbny.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 +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 +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/accuracy-circles.js +17 -8
- 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 +1 -1
- package/dist/visualization/hit-test-reticle.d.ts +1 -1
- package/dist/visualization/index.d.ts +20 -19
- package/dist/visualization/index.js +4 -3
- package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
- package/dist/visualization/leaflet-map-overlay.js +1 -282
- package/dist/visualization/lerp-utils.d.ts +2 -2
- package/dist/visualization/lerp-utils.js +27 -3
- package/dist/visualization/map-data.d.ts +1 -1
- package/dist/visualization/map-data.js +1 -32
- package/dist/visualization/map-overlay-draw.d.ts +1 -1
- package/dist/visualization/map-overlay-draw.js +24 -2
- package/dist/visualization/map-overlay.d.ts +1 -1
- package/dist/visualization/occluder-mesh-driver.d.ts +130 -0
- package/dist/visualization/occluder-mesh-driver.js +219 -0
- package/dist/visualization/occlusion-mesh.d.ts +2 -0
- package/dist/visualization/occlusion-mesh.js +437 -0
- package/dist/visualization/three-dispose.d.ts +1 -1
- package/dist/visualization/vis-colors.d.ts +1 -1
- package/dist/{webxr-session-CJF9Nijp.js → webxr-session-BCP2XK7k.js} +110 -14
- package/dist/{webxr-session-COsOjJwn.d.ts → webxr-session-cRG2SYAQ.d.ts} +83 -10
- package/package.json +4 -4
- package/dist/index-eaI1Z1KR.d.ts +0 -2
- package/dist/lerp-utils-kLxMJ2Xj.d.ts +0 -20
- package/dist/occupancy-grid-Bvi2Gw1U.d.ts +0 -82
- package/dist/recording-options-D93kksJU.d.ts +0 -369
- /package/dist/{alignment-lerper-BUTsQ2yu.d.ts → alignment-lerper-gD70k7Fq.d.ts} +0 -0
- /package/dist/{ar-types-BckI1E-O.d.ts → ar-types-Cc5yvBwn.d.ts} +0 -0
- /package/dist/{bresenham3d-ByjcmEgM.d.ts → bresenham3d-DGc4nIb-.d.ts} +0 -0
- /package/dist/{camera-blit-capture-CqWe1G7s.d.ts → camera-blit-capture-BimSdyih.d.ts} +0 -0
- /package/dist/{camera-follower-BTb2Yp12.d.ts → camera-follower-DfBm2J5i.d.ts} +0 -0
- /package/dist/{capability-checker-CXqoJYtb.d.ts → capability-checker-BTTpygvZ.d.ts} +0 -0
- /package/dist/{capture-failure-tracker-BL5SoG2a.d.ts → capture-failure-tracker-_BVfhpxv.d.ts} +0 -0
- /package/dist/{chromium-camera-access-workaround-BNK189P0.d.ts → chromium-camera-access-workaround-C6yJLXz9.d.ts} +0 -0
- /package/dist/{concurrency-DqAlKX4j.d.ts → concurrency-DPeGBH0X.d.ts} +0 -0
- /package/dist/{css3d-renderer-manager-m-ab19vp.d.ts → css3d-renderer-manager-CPpVtvgs.d.ts} +0 -0
- /package/dist/{failure-tracker-Cnc_xWUw.d.ts → failure-tracker-CeKf5HIc.d.ts} +0 -0
- /package/dist/{file-system-utils-Bs7lpeVP.d.ts → file-system-utils-DueXHxo9.d.ts} +0 -0
- /package/dist/{format-file-size-BFUWvir1.d.ts → format-file-size-DxN-EBLc.d.ts} +0 -0
- /package/dist/{frame-loop-BEO9_q3k.d.ts → frame-loop-7GBMBu68.d.ts} +0 -0
- /package/dist/{frustum-visibility-D_RGmH98.d.ts → frustum-visibility-CokkgS_i.d.ts} +0 -0
- /package/dist/{geo-types-DqiBzPO9.d.ts → geo-types-DrGVu8nS.d.ts} +0 -0
- /package/dist/{gps-C5inWeNV.d.ts → gps-CDKDQokE.d.ts} +0 -0
- /package/dist/{gps-compass-cubes-BExxrWJ-.d.ts → gps-compass-cubes-1mooS3BS.d.ts} +0 -0
- /package/dist/{gps-error-handler-C2bS69oJ.d.ts → gps-error-handler-C34GQuTu.d.ts} +0 -0
- /package/dist/{gps-event-markers-Dd0G2bD0.d.ts → gps-event-markers-Di2nBzx-.d.ts} +0 -0
- /package/dist/{h3-proximity-B57C6rYL.d.ts → h3-proximity-D0GTHQmr.d.ts} +0 -0
- /package/dist/{hit-test-reticle-Dqak9hiR.d.ts → hit-test-reticle-CaQbrhbk.d.ts} +0 -0
- /package/dist/{list-formatter-ct8txwvR.d.ts → list-formatter-DLFDnS70.d.ts} +0 -0
- /package/dist/{logger-CzQrCe6C.d.ts → logger-CqTd5Kpk.d.ts} +0 -0
- /package/dist/{map-overlay-Dci4yvm3.d.ts → map-overlay-BCQgvIgU.d.ts} +0 -0
- /package/dist/{permission-checker-CtPtPQRq.d.ts → permission-checker-B-VtjnBA.d.ts} +0 -0
- /package/dist/{qr-pose-Bt2reSw7.d.ts → qr-pose-y3vaEd6x.d.ts} +0 -0
- /package/dist/{scene-node-names-CdNeQjOE.d.ts → scene-node-names-CGb-ylBd.d.ts} +0 -0
- /package/dist/{session-disposers-D8wnO-uW.js → session-disposers-BPrMplIC.js} +0 -0
- /package/dist/{three-dispose-BCZkSJp2.d.ts → three-dispose-BMOAmPhb.d.ts} +0 -0
- /package/dist/{vis-colors-BZsSLI4i.d.ts → vis-colors-C7HI0u9_.d.ts} +0 -0
- /package/dist/{webxr-nue-basis-CW1wsbm6.d.ts → webxr-nue-basis-CaCnYxuq.d.ts} +0 -0
- /package/dist/{xr-camera-texture-BmrP7iNZ.d.ts → xr-camera-texture-q0eIDSRg.d.ts} +0 -0
- /package/dist/{xr-error-handler-BEjRgZjt.d.ts → xr-error-handler-uVGDu9IX.d.ts} +0 -0
- /package/dist/{xr-frame-loop-K8ebdjLw.d.ts → xr-frame-loop-DX3JPpAU.d.ts} +0 -0
- /package/dist/{zip-coverage-embed-tFohI2J4.d.ts → zip-coverage-embed-ncVbaZAz.d.ts} +0 -0
- /package/dist/{zip-export-DDWXInTR.d.ts → zip-export-CtiUnAlJ.d.ts} +0 -0
- /package/dist/{zip-reader-Q2ue6zlC.d.ts → zip-reader-CvruaGvD.d.ts} +0 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { WEBXR_TO_NUE } from "../ar/webxr-nue-basis.js";
|
|
2
|
+
import { meshOccupiedCells } from "../ar/occupancy-mesher.js";
|
|
3
|
+
import * as THREE from "three";
|
|
4
|
+
//#region ../src/visualization/occlusion-mesh.ts
|
|
5
|
+
/**
|
|
6
|
+
* Persistent occlusion mesh — a depth-only `THREE.Mesh` of the occupancy grid.
|
|
7
|
+
*
|
|
8
|
+
* Wraps the pure {@link meshOccupiedCells} (face-culled voxel surface) into a
|
|
9
|
+
* THREE object that **writes depth but no color** (`colorWrite = false`,
|
|
10
|
+
* `depthWrite = true`), drawn before virtual content (low `renderOrder`) so real
|
|
11
|
+
* geometry the camera saw earlier hides virtual objects placed behind it —
|
|
12
|
+
* including out-of-view surfaces a single-frame live depth occluder cannot
|
|
13
|
+
* remember (2026-06-13-occupancy-mesh-options-plan.md §4; complements the live
|
|
14
|
+
* occluder in 2026-06-14-webxr-depth-occlusion-plan.md).
|
|
15
|
+
*
|
|
16
|
+
* Reusable across consumer apps (AnchorStarter / MinimalExample want occlusion
|
|
17
|
+
* too); the recorder only owns the off-by-default toggle + scene wiring.
|
|
18
|
+
*
|
|
19
|
+
* Coordinate space: the grid cells (and therefore the mesh positions) are **raw
|
|
20
|
+
* WebXR**, but the parent `arWorldGroup` is AR-odometry NUE. The mesh carries
|
|
21
|
+
* the constant `WEBXR_TO_NUE` basis change as its own local matrix — identical
|
|
22
|
+
* to `OccupancyCubesVisualizer` — so it rides the `alignment × WEBXR_TO_NUE`
|
|
23
|
+
* chain. The parent node is injected (no `getArWorldGroup()`) to stay testable.
|
|
24
|
+
*
|
|
25
|
+
* Scope: this is a full-rebuild occluder (re-mesh the whole snapshot on
|
|
26
|
+
* `update`). The chunked dirty-remesh perf layer (plan §7) is a follow-on.
|
|
27
|
+
*
|
|
28
|
+
* @see occlusion-mesh.ts.md for detailed documentation
|
|
29
|
+
*/
|
|
30
|
+
const MESH_NAME = "occupancy-occluder";
|
|
31
|
+
const DEBUG_MESH_NAME = "occupancy-occluder-debug";
|
|
32
|
+
const DEBUG_WIREFRAME_MESH_NAME = "occupancy-occluder-debug-wireframe";
|
|
33
|
+
/** Default render order — well before virtual content (which is ≥ 0). */
|
|
34
|
+
const DEFAULT_RENDER_ORDER = -1;
|
|
35
|
+
/** Opacity of the matcap debug skin — see-through enough to read the real scene
|
|
36
|
+
* behind it while the shape stays legible. */
|
|
37
|
+
const DEBUG_SKIN_OPACITY = .6;
|
|
38
|
+
/** Light-cyan GL lines of the wireframe skin — faint enough not to swamp the
|
|
39
|
+
* shaded skin in the combined style, visible enough to read triangle density. */
|
|
40
|
+
const WIREFRAME_COLOR = 11202303;
|
|
41
|
+
const WIREFRAME_OPACITY = .35;
|
|
42
|
+
/** Which styles shade with the matcap (and therefore need vertex normals —
|
|
43
|
+
* pure 'wireframe' is unlit and keeps the remesh path normal-free). */
|
|
44
|
+
function styleNeedsNormals(style) {
|
|
45
|
+
return style === "matcap" || style === "depth-shaded" || style === "depth-shaded-wireframe";
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Depth-shaded debug-skin constants — **module constants, not user settings**
|
|
49
|
+
* (tune in code after the first on-device look; 2026-07-02 debug-viz-styles
|
|
50
|
+
* plan). Exported so the unit/property tests of the GLSL-mirror curves below
|
|
51
|
+
* pin the same numbers the shader bakes in.
|
|
52
|
+
*/
|
|
53
|
+
const OCCLUDER_DEPTH_SHADE = {
|
|
54
|
+
/** Distance (m) where the fade starts — nearer stays full-bright cyan.
|
|
55
|
+
* Indoor-scale since the 2026-07-03 field pass (finding F1): the original
|
|
56
|
+
* 1.5 → 10 m range kept a whole room in the "near" band (94% brightness at
|
|
57
|
+
* 3 m), making depth-shaded indistinguishable from matcap on device. */
|
|
58
|
+
FADE_START_M: .75,
|
|
59
|
+
/** Distance (m) where the fade bottoms out at {@link FADE_MIN_BRIGHTNESS}.
|
|
60
|
+
* ~Room depth, so indoor scenes span the full near→far gradient; beyond it
|
|
61
|
+
* everything sits uniformly at the floor (still separated from near). */
|
|
62
|
+
FADE_END_M: 4.5,
|
|
63
|
+
/** Brightness floor far away — never fades to black, so distant mesh stays
|
|
64
|
+
* inspectable (the reason the fog-to-background idea was rejected). */
|
|
65
|
+
FADE_MIN_BRIGHTNESS: .3,
|
|
66
|
+
/** Fresnel exponent — higher hugs the silhouette tighter. */
|
|
67
|
+
RIM_POWER: 2.5,
|
|
68
|
+
/** White-rim contribution at a fully grazing view angle. */
|
|
69
|
+
RIM_STRENGTH: .6
|
|
70
|
+
};
|
|
71
|
+
/** Far-distance fade target (dark desaturated blue) — keeps the occluder's
|
|
72
|
+
* single-color cyan identity while clearly separating far from near. */
|
|
73
|
+
const FADE_TARGET_RGB = [
|
|
74
|
+
.03,
|
|
75
|
+
.07,
|
|
76
|
+
.12
|
|
77
|
+
];
|
|
78
|
+
/** GLSL `smoothstep` (clamped Hermite), mirrored exactly for the TS curves. */
|
|
79
|
+
function smoothstep(edge0, edge1, x) {
|
|
80
|
+
const t = Math.min(1, Math.max(0, (x - edge0) / (edge1 - edge0)));
|
|
81
|
+
return t * t * (3 - 2 * t);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Pure TS mirror of the shader's **distance fade**: 1 at/inside FADE_START_M,
|
|
85
|
+
* smoothly down to FADE_MIN_BRIGHTNESS at/after FADE_END_M. The fragment color
|
|
86
|
+
* is `mix(darkBlue, matcapColor, fade)` — near = bright cyan, far = dark blue.
|
|
87
|
+
* Mirrored so the curve is unit-testable without a GPU (the
|
|
88
|
+
* `buildFullscreenOcclusionShader` GLSL-mirror precedent).
|
|
89
|
+
*/
|
|
90
|
+
function occluderDepthFade(distanceM) {
|
|
91
|
+
const { FADE_START_M, FADE_END_M, FADE_MIN_BRIGHTNESS } = OCCLUDER_DEPTH_SHADE;
|
|
92
|
+
return 1 - (1 - FADE_MIN_BRIGHTNESS) * smoothstep(FADE_START_M, FADE_END_M, distanceM);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Pure TS mirror of the shader's **fresnel rim**: 0 where the surface faces
|
|
96
|
+
* the camera head-on (|cos| = 1), RIM_STRENGTH at a fully grazing angle
|
|
97
|
+
* (cos = 0) — silhouettes brighten, so overlapping shells read as separate.
|
|
98
|
+
*
|
|
99
|
+
* @param cosViewAngle `dot(normal, viewDir)` — cosine of the angle between the
|
|
100
|
+
* surface normal and the view direction, in [-1, 1].
|
|
101
|
+
*/
|
|
102
|
+
function occluderFresnelRim(cosViewAngle) {
|
|
103
|
+
const { RIM_POWER, RIM_STRENGTH } = OCCLUDER_DEPTH_SHADE;
|
|
104
|
+
return RIM_STRENGTH * Math.pow(1 - Math.abs(cosViewAngle), RIM_POWER);
|
|
105
|
+
}
|
|
106
|
+
/** A number as a GLSL float literal (fixed decimals so it never reads as int). */
|
|
107
|
+
function glslFloat(value) {
|
|
108
|
+
return value.toFixed(4);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The fragment-shader snippet the depth-shaded material injects before
|
|
112
|
+
* `#include <opaque_fragment>` in three.js's matcap fragment shader — the
|
|
113
|
+
* point where `outgoingLight` (matcap-lit color), `normal` and `viewDir` /
|
|
114
|
+
* `vViewPosition` are all in scope. Exported so tests can pin the exact
|
|
115
|
+
* injected code. Constants are baked as literals (no uniforms — they are
|
|
116
|
+
* module constants by design, see {@link OCCLUDER_DEPTH_SHADE}).
|
|
117
|
+
*/
|
|
118
|
+
function buildOccluderDepthShadeSnippet() {
|
|
119
|
+
const { FADE_START_M, FADE_END_M, FADE_MIN_BRIGHTNESS, RIM_POWER } = OCCLUDER_DEPTH_SHADE;
|
|
120
|
+
const { RIM_STRENGTH } = OCCLUDER_DEPTH_SHADE;
|
|
121
|
+
const [r, g, b] = FADE_TARGET_RGB;
|
|
122
|
+
return [
|
|
123
|
+
"// Occluder debug depth-shading: distance fade + fresnel rim (2026-07-02).",
|
|
124
|
+
`float dbgDistance = length( vViewPosition );`,
|
|
125
|
+
`float dbgFade = 1.0 - ( 1.0 - ${glslFloat(FADE_MIN_BRIGHTNESS)} ) * smoothstep( ${glslFloat(FADE_START_M)}, ${glslFloat(FADE_END_M)}, dbgDistance );`,
|
|
126
|
+
`outgoingLight = mix( vec3( ${glslFloat(r)}, ${glslFloat(g)}, ${glslFloat(b)} ), outgoingLight, dbgFade );`,
|
|
127
|
+
`float dbgRim = ${glslFloat(RIM_STRENGTH)} * pow( 1.0 - abs( dot( normal, viewDir ) ), ${glslFloat(RIM_POWER)} );`,
|
|
128
|
+
`outgoingLight += vec3( 1.0 ) * dbgRim;`
|
|
129
|
+
].join("\n ");
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Build a tiny procedural matcap texture (a shaded sphere with a specular
|
|
133
|
+
* highlight) so the debug skin reads as a **shiny** surface with **no scene
|
|
134
|
+
* lights** — `MeshMatcapMaterial` bakes the lighting into this lookup. Generated
|
|
135
|
+
* from a typed array (no canvas/WebGL), so it works headless in tests too.
|
|
136
|
+
*/
|
|
137
|
+
function createOccluderDebugMatcap() {
|
|
138
|
+
const size = 64;
|
|
139
|
+
const data = new Uint8Array(size * size * 4);
|
|
140
|
+
const lx = .4;
|
|
141
|
+
const ly = .5;
|
|
142
|
+
const lz = .75;
|
|
143
|
+
const llen = Math.hypot(lx, ly, lz);
|
|
144
|
+
for (let j = 0; j < size; j++) for (let i = 0; i < size; i++) {
|
|
145
|
+
const idx = (j * size + i) * 4;
|
|
146
|
+
const nx = (i + .5) / size * 2 - 1;
|
|
147
|
+
const ny = (j + .5) / size * 2 - 1;
|
|
148
|
+
const r2 = nx * nx + ny * ny;
|
|
149
|
+
let r = 12;
|
|
150
|
+
let g = 12;
|
|
151
|
+
let b = 14;
|
|
152
|
+
if (r2 <= 1) {
|
|
153
|
+
const nz = Math.sqrt(1 - r2);
|
|
154
|
+
const ndl = Math.max(0, (nx * lx + ny * ly + nz * lz) / llen);
|
|
155
|
+
const diff = .25 + .75 * ndl;
|
|
156
|
+
const spec = Math.pow(ndl, 32);
|
|
157
|
+
r = Math.min(255, 255 * (.15 * diff + spec));
|
|
158
|
+
g = Math.min(255, 255 * (.55 * diff + spec));
|
|
159
|
+
b = Math.min(255, 255 * (.78 * diff + spec));
|
|
160
|
+
}
|
|
161
|
+
data[idx] = r;
|
|
162
|
+
data[idx + 1] = g;
|
|
163
|
+
data[idx + 2] = b;
|
|
164
|
+
data[idx + 3] = 255;
|
|
165
|
+
}
|
|
166
|
+
const tex = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
|
|
167
|
+
tex.needsUpdate = true;
|
|
168
|
+
return tex;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* A depth-only occlusion mesh that rebuilds from an occupancy-grid snapshot.
|
|
172
|
+
* Mirrors {@link OccupancyCubesVisualizer}'s lifecycle (inject parent, `update`,
|
|
173
|
+
* `clear`, `dispose`) so the recorder can wire it the same way as the cubes.
|
|
174
|
+
*/
|
|
175
|
+
var OcclusionMesh = class {
|
|
176
|
+
arSpaceNode;
|
|
177
|
+
greedy;
|
|
178
|
+
mode;
|
|
179
|
+
material;
|
|
180
|
+
mesh;
|
|
181
|
+
geometry;
|
|
182
|
+
lastAabbs = [];
|
|
183
|
+
disposed = false;
|
|
184
|
+
debugStyle = "off";
|
|
185
|
+
shadedSkin = null;
|
|
186
|
+
wireframeSkin = null;
|
|
187
|
+
matcapTexture = null;
|
|
188
|
+
matcapMaterial = null;
|
|
189
|
+
depthShadedMaterial = null;
|
|
190
|
+
wireframeMaterial = null;
|
|
191
|
+
/**
|
|
192
|
+
* @param arSpaceNode the AR-odometry-NUE node that receives the alignment
|
|
193
|
+
* matrix (`arWorldGroup` live, `replaySceneState.arWorldGroup` in replay).
|
|
194
|
+
*/
|
|
195
|
+
constructor(arSpaceNode, options = {}) {
|
|
196
|
+
this.arSpaceNode = arSpaceNode;
|
|
197
|
+
this.greedy = options.greedy ?? true;
|
|
198
|
+
this.mode = options.mode;
|
|
199
|
+
this.geometry = new THREE.BufferGeometry();
|
|
200
|
+
this.material = new THREE.MeshBasicMaterial({
|
|
201
|
+
colorWrite: false,
|
|
202
|
+
depthWrite: true
|
|
203
|
+
});
|
|
204
|
+
this.mesh = new THREE.Mesh(this.geometry, this.material);
|
|
205
|
+
this.mesh.name = MESH_NAME;
|
|
206
|
+
this.mesh.renderOrder = options.renderOrder ?? DEFAULT_RENDER_ORDER;
|
|
207
|
+
this.mesh.frustumCulled = false;
|
|
208
|
+
this.mesh.matrixAutoUpdate = false;
|
|
209
|
+
this.mesh.matrix.copy(WEBXR_TO_NUE);
|
|
210
|
+
this.arSpaceNode.add(this.mesh);
|
|
211
|
+
}
|
|
212
|
+
/** The number of triangles currently drawn. */
|
|
213
|
+
getTriangleCount() {
|
|
214
|
+
const index = this.geometry.getIndex();
|
|
215
|
+
return index ? index.count / 3 : 0;
|
|
216
|
+
}
|
|
217
|
+
/** The AABB list from the most recent {@link update} (physics export hook). */
|
|
218
|
+
getAabbs() {
|
|
219
|
+
return this.lastAabbs;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Re-mesh from a fresh occupied-cell snapshot. Pass
|
|
223
|
+
* `grid.getOccupiedCells(occupancy.minConfidence)` so the occluder shares the
|
|
224
|
+
* same noise floor as the cubes and the COLMAP export.
|
|
225
|
+
*
|
|
226
|
+
* @param getCellPoint optional per-cell measured-centroid provider
|
|
227
|
+
* (`grid.getCellPoint`); only consumed when this occluder was constructed
|
|
228
|
+
* with `mode: 'smooth'` (otherwise ignored). When omitted under `'smooth'`,
|
|
229
|
+
* the surface nets falls back to cell centres.
|
|
230
|
+
*/
|
|
231
|
+
update(cells, cellSizeM, getCellPoint) {
|
|
232
|
+
if (this.disposed) return;
|
|
233
|
+
const { positions, indices, aabbs } = meshOccupiedCells(cells, cellSizeM, this.mode ? {
|
|
234
|
+
mode: this.mode,
|
|
235
|
+
getCellPoint
|
|
236
|
+
} : { greedy: this.greedy });
|
|
237
|
+
this.lastAabbs = aabbs;
|
|
238
|
+
this.swapGeometry(positions, indices);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Apply **precomputed** geometry (positions/indices) without meshing — the
|
|
242
|
+
* entry point for the Web Worker offload: the driver posts the occupied-cell
|
|
243
|
+
* snapshot to a worker (`packMeshRequest`/`runMeshRequest`), and this swaps in
|
|
244
|
+
* the returned typed arrays off the render-critical path. Byte-identical result
|
|
245
|
+
* to {@link update} for the same input.
|
|
246
|
+
*
|
|
247
|
+
* The AABB physics hook is NOT populated on this path (the worker returns
|
|
248
|
+
* geometry only; AABBs are an export hook unused by rendering) — {@link getAabbs}
|
|
249
|
+
* returns empty here. Use the synchronous {@link update} if AABBs are needed.
|
|
250
|
+
*/
|
|
251
|
+
applyMeshData(positions, indices) {
|
|
252
|
+
if (this.disposed) return;
|
|
253
|
+
this.lastAabbs = [];
|
|
254
|
+
this.swapGeometry(positions, indices);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Replace the geometry wholesale from typed arrays — a full rebuild is the
|
|
258
|
+
* simple first cut; dispose the old buffers to avoid leaking GPU memory across
|
|
259
|
+
* refreshes.
|
|
260
|
+
*/
|
|
261
|
+
swapGeometry(positions, indices) {
|
|
262
|
+
const next = new THREE.BufferGeometry();
|
|
263
|
+
next.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
|
264
|
+
next.setIndex(new THREE.BufferAttribute(indices, 1));
|
|
265
|
+
if (styleNeedsNormals(this.debugStyle)) next.computeVertexNormals();
|
|
266
|
+
this.geometry.dispose();
|
|
267
|
+
this.geometry = next;
|
|
268
|
+
this.mesh.geometry = next;
|
|
269
|
+
this.rebindSkinGeometry(next);
|
|
270
|
+
}
|
|
271
|
+
/** Point every active debug skin at the (new) shared geometry. */
|
|
272
|
+
rebindSkinGeometry(geometry) {
|
|
273
|
+
if (this.shadedSkin) this.shadedSkin.geometry = geometry;
|
|
274
|
+
if (this.wireframeSkin) this.wireframeSkin.geometry = geometry;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Select which **visible debug skin(s)** render the occluder mesh so its
|
|
278
|
+
* shape and structure can be judged on-device (2026-07-02 debug-viz-styles
|
|
279
|
+
* plan): the original `'matcap'` skin, a `'depth-shaded'` variant (distance
|
|
280
|
+
* fade + fresnel rim, separates overlapping layers), a `'wireframe'` overlay
|
|
281
|
+
* (the raw triangulation as GL lines), the combined
|
|
282
|
+
* `'depth-shaded-wireframe'`, or `'off'`.
|
|
283
|
+
*
|
|
284
|
+
* Additive by design: every style adds/removes separate skin meshes sharing
|
|
285
|
+
* the occluder's geometry and **never touches the invisible depth-only
|
|
286
|
+
* mesh**, so occlusion is byte-for-byte unchanged whichever style is active.
|
|
287
|
+
* The skins are `transparent` with `depthWrite:false` (the depth-only mesh
|
|
288
|
+
* already wrote the occluding depth), so they just paint where the occluder
|
|
289
|
+
* is the nearest geometry; the wireframe draws at renderOrder 1 so its lines
|
|
290
|
+
* overlay the shaded surface in the combined style.
|
|
291
|
+
*
|
|
292
|
+
* Vertex normals (the mesher emits none) are computed only for the
|
|
293
|
+
* matcap-based styles — `'wireframe'` is unlit, so like `'off'` it keeps the
|
|
294
|
+
* remesh path normal-free.
|
|
295
|
+
*
|
|
296
|
+
* Only meaningful when this occluder is actually meshing the grid (it is the
|
|
297
|
+
* persistent occluder's mesh); setting a style on an empty/disabled occluder
|
|
298
|
+
* is a harmless no-op until {@link update} feeds geometry.
|
|
299
|
+
*/
|
|
300
|
+
setDebugStyle(style) {
|
|
301
|
+
if (this.disposed || style === this.debugStyle) return;
|
|
302
|
+
this.debugStyle = style;
|
|
303
|
+
this.applyShadedSkin(this.shadedMaterialFor(style));
|
|
304
|
+
this.applyWireframeSkin(style === "wireframe" || style === "depth-shaded-wireframe");
|
|
305
|
+
}
|
|
306
|
+
/** Which matcap-based material the style shades with (null = no shaded skin). */
|
|
307
|
+
shadedMaterialFor(style) {
|
|
308
|
+
if (style === "matcap") return this.getMatcapMaterial();
|
|
309
|
+
if (style === "depth-shaded" || style === "depth-shaded-wireframe") return this.getDepthShadedMaterial();
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
/** Add/retarget/remove the single shaded skin node for the desired material. */
|
|
313
|
+
applyShadedSkin(material) {
|
|
314
|
+
if (material) {
|
|
315
|
+
this.geometry.computeVertexNormals();
|
|
316
|
+
if (this.shadedSkin) this.shadedSkin.material = material;
|
|
317
|
+
else {
|
|
318
|
+
this.shadedSkin = this.createSkinMesh(DEBUG_MESH_NAME, 0, material);
|
|
319
|
+
this.arSpaceNode.add(this.shadedSkin);
|
|
320
|
+
}
|
|
321
|
+
} else if (this.shadedSkin) {
|
|
322
|
+
this.arSpaceNode.remove(this.shadedSkin);
|
|
323
|
+
this.shadedSkin = null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/** Add/remove the wireframe skin — drawn after the shaded skin (renderOrder
|
|
327
|
+
* 1) so its lines sit on top of the surface in the combined style. */
|
|
328
|
+
applyWireframeSkin(want) {
|
|
329
|
+
if (want) {
|
|
330
|
+
if (!this.wireframeSkin) {
|
|
331
|
+
this.wireframeSkin = this.createSkinMesh(DEBUG_WIREFRAME_MESH_NAME, 1, this.getWireframeMaterial());
|
|
332
|
+
this.arSpaceNode.add(this.wireframeSkin);
|
|
333
|
+
}
|
|
334
|
+
} else if (this.wireframeSkin) {
|
|
335
|
+
this.arSpaceNode.remove(this.wireframeSkin);
|
|
336
|
+
this.wireframeSkin = null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Toggle the matcap debug rendering of the occluder mesh.
|
|
341
|
+
*
|
|
342
|
+
* @deprecated Superseded by {@link setDebugStyle} (2026-07-02) — this is a
|
|
343
|
+
* thin wrapper mapping `enabled ? 'matcap' : 'off'`, kept so existing
|
|
344
|
+
* consumers of the boolean API keep working unchanged.
|
|
345
|
+
*/
|
|
346
|
+
setDebugVisualization(enabled) {
|
|
347
|
+
this.setDebugStyle(enabled ? "matcap" : "off");
|
|
348
|
+
}
|
|
349
|
+
/** A debug skin mesh sharing the occluder's geometry, transform and culling
|
|
350
|
+
* behaviour — only name, renderOrder and material differ per skin. */
|
|
351
|
+
createSkinMesh(name, renderOrder, material) {
|
|
352
|
+
const skin = new THREE.Mesh(this.geometry, material);
|
|
353
|
+
skin.name = name;
|
|
354
|
+
skin.renderOrder = renderOrder;
|
|
355
|
+
skin.frustumCulled = false;
|
|
356
|
+
skin.matrixAutoUpdate = false;
|
|
357
|
+
skin.matrix.copy(WEBXR_TO_NUE);
|
|
358
|
+
return skin;
|
|
359
|
+
}
|
|
360
|
+
/** The procedural matcap texture, shared by both matcap-based materials. */
|
|
361
|
+
getMatcapTexture() {
|
|
362
|
+
if (!this.matcapTexture) this.matcapTexture = createOccluderDebugMatcap();
|
|
363
|
+
return this.matcapTexture;
|
|
364
|
+
}
|
|
365
|
+
getMatcapMaterial() {
|
|
366
|
+
if (!this.matcapMaterial) this.matcapMaterial = new THREE.MeshMatcapMaterial({
|
|
367
|
+
matcap: this.getMatcapTexture(),
|
|
368
|
+
transparent: true,
|
|
369
|
+
opacity: DEBUG_SKIN_OPACITY,
|
|
370
|
+
depthWrite: false
|
|
371
|
+
});
|
|
372
|
+
return this.matcapMaterial;
|
|
373
|
+
}
|
|
374
|
+
getDepthShadedMaterial() {
|
|
375
|
+
if (!this.depthShadedMaterial) {
|
|
376
|
+
const material = new THREE.MeshMatcapMaterial({
|
|
377
|
+
matcap: this.getMatcapTexture(),
|
|
378
|
+
transparent: true,
|
|
379
|
+
opacity: DEBUG_SKIN_OPACITY,
|
|
380
|
+
depthWrite: false
|
|
381
|
+
});
|
|
382
|
+
material.onBeforeCompile = (shader) => {
|
|
383
|
+
shader.fragmentShader = shader.fragmentShader.replace("#include <opaque_fragment>", `${buildOccluderDepthShadeSnippet()}\n\t#include <opaque_fragment>`);
|
|
384
|
+
};
|
|
385
|
+
material.customProgramCacheKey = () => "occluder-depth-shaded";
|
|
386
|
+
this.depthShadedMaterial = material;
|
|
387
|
+
}
|
|
388
|
+
return this.depthShadedMaterial;
|
|
389
|
+
}
|
|
390
|
+
getWireframeMaterial() {
|
|
391
|
+
if (!this.wireframeMaterial) this.wireframeMaterial = new THREE.MeshBasicMaterial({
|
|
392
|
+
wireframe: true,
|
|
393
|
+
color: WIREFRAME_COLOR,
|
|
394
|
+
transparent: true,
|
|
395
|
+
opacity: WIREFRAME_OPACITY,
|
|
396
|
+
depthWrite: false
|
|
397
|
+
});
|
|
398
|
+
return this.wireframeMaterial;
|
|
399
|
+
}
|
|
400
|
+
/** Empty the mesh (e.g. on store swap); the node stays in the scene. */
|
|
401
|
+
clear() {
|
|
402
|
+
if (this.disposed) return;
|
|
403
|
+
const next = new THREE.BufferGeometry();
|
|
404
|
+
this.geometry.dispose();
|
|
405
|
+
this.geometry = next;
|
|
406
|
+
this.mesh.geometry = next;
|
|
407
|
+
this.rebindSkinGeometry(next);
|
|
408
|
+
this.lastAabbs = [];
|
|
409
|
+
}
|
|
410
|
+
/** Remove the mesh from its parent and release GPU resources. */
|
|
411
|
+
dispose() {
|
|
412
|
+
if (this.disposed) return;
|
|
413
|
+
this.disposed = true;
|
|
414
|
+
this.arSpaceNode.remove(this.mesh);
|
|
415
|
+
if (this.shadedSkin) {
|
|
416
|
+
this.arSpaceNode.remove(this.shadedSkin);
|
|
417
|
+
this.shadedSkin = null;
|
|
418
|
+
}
|
|
419
|
+
if (this.wireframeSkin) {
|
|
420
|
+
this.arSpaceNode.remove(this.wireframeSkin);
|
|
421
|
+
this.wireframeSkin = null;
|
|
422
|
+
}
|
|
423
|
+
this.matcapTexture?.dispose();
|
|
424
|
+
this.matcapTexture = null;
|
|
425
|
+
this.matcapMaterial?.dispose();
|
|
426
|
+
this.matcapMaterial = null;
|
|
427
|
+
this.depthShadedMaterial?.dispose();
|
|
428
|
+
this.depthShadedMaterial = null;
|
|
429
|
+
this.wireframeMaterial?.dispose();
|
|
430
|
+
this.wireframeMaterial = null;
|
|
431
|
+
this.geometry.dispose();
|
|
432
|
+
this.material.dispose();
|
|
433
|
+
this.lastAabbs = [];
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
//#endregion
|
|
437
|
+
export { OCCLUDER_DEPTH_SHADE, OcclusionMesh, buildOccluderDepthShadeSnippet, occluderDepthFade, occluderFresnelRim };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as disposeMeshArray, r as disposeObject3D, t as DisposeOptions } from "../three-dispose-
|
|
1
|
+
import { n as disposeMeshArray, r as disposeObject3D, t as DisposeOptions } from "../three-dispose-BMOAmPhb.js";
|
|
2
2
|
export { DisposeOptions, disposeMeshArray, disposeObject3D };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as VIS_COLORS } from "../vis-colors-
|
|
1
|
+
import { t as VIS_COLORS } from "../vis-colors-C7HI0u9_.js";
|
|
2
2
|
export { VIS_COLORS };
|
|
@@ -5,10 +5,10 @@ import { DepthSampler, wrapXRDepthInfo } from "./ar/depth-sampler.js";
|
|
|
5
5
|
import { WEBXR_TO_NUE } from "./ar/webxr-nue-basis.js";
|
|
6
6
|
import { clearFrameUpdates, runFrameUpdates } from "./ar/frame-loop.js";
|
|
7
7
|
import { clearXrFrameUpdates, runXrFrameUpdates } from "./ar/xr-frame-loop.js";
|
|
8
|
-
import {
|
|
8
|
+
import { n as ImageCaptureManager, t as DEFAULT_CAPTURE_CONFIG } from "./image-capture-DVcuqMtX.js";
|
|
9
9
|
import { clearLastRestartedPayload, originReset, poseLost, poseReceived, resetTracking, selectLastRestartedPayload, selectTrackingPhase } from "./state/tracking-slice.js";
|
|
10
10
|
import { acquireCameraTexture } from "./ar/xr-camera-texture.js";
|
|
11
|
-
import { n as runSessionDisposers } from "./session-disposers-
|
|
11
|
+
import { n as runSessionDisposers } from "./session-disposers-BPrMplIC.js";
|
|
12
12
|
import { DEFAULT_RECORDING_OPTIONS, validateArCrashIsolationOptions } from "./state/recording-options.js";
|
|
13
13
|
import { SCENE_NODE } from "./ar/scene-node-names.js";
|
|
14
14
|
import { createCss3dRendererManager } from "./visualization/css3d-renderer-manager.js";
|
|
@@ -225,6 +225,8 @@ function resetWebXRState() {
|
|
|
225
225
|
onTrackingRestarted = null;
|
|
226
226
|
onTrackingLost = null;
|
|
227
227
|
onTrackingRecovered = null;
|
|
228
|
+
onSessionEnd = null;
|
|
229
|
+
endRequestedByApp = false;
|
|
228
230
|
depthSampler = null;
|
|
229
231
|
onDepthCaptured = null;
|
|
230
232
|
onDepthUnavailable = null;
|
|
@@ -290,6 +292,14 @@ let onSuspiciousImage = null;
|
|
|
290
292
|
*/
|
|
291
293
|
let getScreenRotation = null;
|
|
292
294
|
/**
|
|
295
|
+
* Off-thread image-quality analyzer (set via {@link setImageQualityAnalyzer}).
|
|
296
|
+
* Device-specific (a Web Worker in the recorder), so it is INJECTED rather than
|
|
297
|
+
* built here. Forwarded to `ImageCaptureManager` as its `analyzeFrame` callback;
|
|
298
|
+
* the manager only invokes it when `config.qualityFilter.enabled`. `null` ⇒ the
|
|
299
|
+
* legacy save-immediately path.
|
|
300
|
+
*/
|
|
301
|
+
let imageQualityAnalyzer = null;
|
|
302
|
+
/**
|
|
293
303
|
* Redux store injected by the host (`setTrackingStore`). When present
|
|
294
304
|
* together with `onTrackingRestarted`, `onXRFrame` dispatches
|
|
295
305
|
* `poseReceived`/`poseLost`, the XR reference-space reset listener
|
|
@@ -321,6 +331,20 @@ let onTrackingLost = null;
|
|
|
321
331
|
*/
|
|
322
332
|
let onTrackingRecovered = null;
|
|
323
333
|
/**
|
|
334
|
+
* Host callback fired exactly once whenever the XRSession ends — on BOTH the
|
|
335
|
+
* app-initiated and the system-initiated path (set via setSessionEndCallback).
|
|
336
|
+
* Cleared by resetWebXRState() like every other module-level callback.
|
|
337
|
+
*/
|
|
338
|
+
let onSessionEnd = null;
|
|
339
|
+
/**
|
|
340
|
+
* Set by endARSession() immediately before its `xrSession.end()` so the
|
|
341
|
+
* shared 'end' listener can tell the two trigger paths apart (the app's own
|
|
342
|
+
* end() fires the same 'end' event the system path does). Consumed (reset to
|
|
343
|
+
* false) by handleSessionEnded(); also cleared defensively in
|
|
344
|
+
* resetWebXRState() in case end() rejects without the event ever firing.
|
|
345
|
+
*/
|
|
346
|
+
let endRequestedByApp = false;
|
|
347
|
+
/**
|
|
324
348
|
* Depth sampler instance (created when AR session starts with depth callbacks)
|
|
325
349
|
*/
|
|
326
350
|
let depthSampler = null;
|
|
@@ -555,7 +579,7 @@ function getCurrentArPose() {
|
|
|
555
579
|
* @param isolationOptions - Crash-isolation diagnostic flags (DOM overlay,
|
|
556
580
|
* depth-sensing, camera-access)
|
|
557
581
|
* @param sessionFeatures - Opt-in standard WebXR features that are independent
|
|
558
|
-
* of crash isolation (
|
|
582
|
+
* of crash isolation (`requestHitTest`, `requestDepthOcclusion`)
|
|
559
583
|
* @returns XRSessionInit options
|
|
560
584
|
* @throws Error if rootElement is null
|
|
561
585
|
*/
|
|
@@ -568,7 +592,7 @@ function buildSessionOptions(rootElement, isolationOptions = {}, sessionFeatures
|
|
|
568
592
|
optionalFeatures.push("dom-overlay");
|
|
569
593
|
sessionOptions.domOverlay = { root: rootElement };
|
|
570
594
|
}
|
|
571
|
-
if (normalizedOptions.enableDepthSensingFeature) {
|
|
595
|
+
if (normalizedOptions.enableDepthSensingFeature || sessionFeatures.requestDepthOcclusion) {
|
|
572
596
|
optionalFeatures.push("depth-sensing");
|
|
573
597
|
Object.assign(sessionOptions, { depthSensing: {
|
|
574
598
|
usagePreference: ["cpu-optimized"],
|
|
@@ -592,6 +616,23 @@ async function isWebXRSupported() {
|
|
|
592
616
|
}
|
|
593
617
|
}
|
|
594
618
|
/**
|
|
619
|
+
* AR camera frustum constants — the single source of truth for live AR and
|
|
620
|
+
* replay (both build their camera via {@link createSceneHierarchy}).
|
|
621
|
+
*
|
|
622
|
+
* F2 (2026-07-04 user feedback): far raised 100 → 200 m so objects in the
|
|
623
|
+
* reported 100–200 m range are no longer frustum-culled. The far-plane
|
|
624
|
+
* distance itself is essentially free at this app's object counts; the real
|
|
625
|
+
* constraint is depth precision — far/near = 2×10⁴ is comfortable for a
|
|
626
|
+
* 24-bit depth buffer. Revisit the ratio if AR_CAMERA_NEAR ever shrinks.
|
|
627
|
+
*
|
|
628
|
+
* Note: these apply to WebGL content only. The CSS3D minimap is composited by
|
|
629
|
+
* the browser from the camera fov alone — near/far do not clip it (F1 in the
|
|
630
|
+
* same feedback doc).
|
|
631
|
+
*/
|
|
632
|
+
const AR_CAMERA_FOV = 70;
|
|
633
|
+
const AR_CAMERA_NEAR = .01;
|
|
634
|
+
const AR_CAMERA_FAR = 200;
|
|
635
|
+
/**
|
|
595
636
|
* Create the scene hierarchy with proper AR/GPS frame separation.
|
|
596
637
|
* This is a pure function for testability.
|
|
597
638
|
*
|
|
@@ -639,7 +680,7 @@ function createSceneHierarchy() {
|
|
|
639
680
|
const newArPose = new THREE.Object3D();
|
|
640
681
|
newArPose.name = "ar-pose";
|
|
641
682
|
newBasisChangeNode.add(newArPose);
|
|
642
|
-
const newCamera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight,
|
|
683
|
+
const newCamera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, AR_CAMERA_NEAR, 200);
|
|
643
684
|
newArPose.add(newCamera);
|
|
644
685
|
const ambientLight = new THREE.AmbientLight(16777215, .5);
|
|
645
686
|
newScene.add(ambientLight);
|
|
@@ -681,10 +722,7 @@ async function initAR(container, isolationOptions = {}, sessionFeatures = {}) {
|
|
|
681
722
|
const sessionOptions = buildSessionOptions(container, currentArCrashIsolationOptions, sessionFeatures);
|
|
682
723
|
xrSession = await navigator.xr.requestSession("immersive-ar", sessionOptions);
|
|
683
724
|
xrSession.addEventListener("end", () => {
|
|
684
|
-
|
|
685
|
-
if (trackingStore) trackingStore.dispatch(resetTracking());
|
|
686
|
-
xrSession = null;
|
|
687
|
-
latestArPose = null;
|
|
725
|
+
handleSessionEnded();
|
|
688
726
|
});
|
|
689
727
|
await renderer.xr.setSession(xrSession);
|
|
690
728
|
if (onTrackingRestarted && trackingStore) {
|
|
@@ -839,8 +877,15 @@ function onXRFrame(time, frame) {
|
|
|
839
877
|
if (currentArCrashIsolationOptions.enableCss3dRenderer && css3dManager) css3dManager.render(scene, camera);
|
|
840
878
|
}
|
|
841
879
|
/**
|
|
842
|
-
* Extract depth information from an XR frame.
|
|
843
|
-
*
|
|
880
|
+
* Extract depth information from an XR frame. Returns `null` if depth sensing is
|
|
881
|
+
* not available (no pose/view, no `getDepthInformation`, or the call throws).
|
|
882
|
+
*
|
|
883
|
+
* Exported so a consumer can feed the **live depth occluder** from a
|
|
884
|
+
* `registerXrFrameUpdate` callback — the callback has the live `frame` +
|
|
885
|
+
* `referenceSpace`, computes `pose = frame.getViewerPose(referenceSpace)`, and
|
|
886
|
+
* passes both here to obtain the per-frame {@link DepthInfo} (with the widened
|
|
887
|
+
* `data` / `rawValueToMeters` / `normDepthBufferFromNormView` / `projectionMatrix`
|
|
888
|
+
* the occluder needs). The same wrapped depth the sparse grid sampler consumes.
|
|
844
889
|
*/
|
|
845
890
|
function getDepthInfoFromFrame(frame, pose) {
|
|
846
891
|
const view = pose?.views[0];
|
|
@@ -976,6 +1021,41 @@ function nueQuaternionToWebXR$1(nue) {
|
|
|
976
1021
|
return nueQuaternionToWebXR(nue);
|
|
977
1022
|
}
|
|
978
1023
|
/**
|
|
1024
|
+
* Register a host callback fired exactly once whenever the XRSession ends —
|
|
1025
|
+
* app-initiated ({@link endARSession}) AND system-initiated (e.g. the Android
|
|
1026
|
+
* back gesture, which ends an immersive session directly and uncancelably).
|
|
1027
|
+
* `info.requestedByApp` discriminates the two. Fired AFTER the full teardown,
|
|
1028
|
+
* so the host can immediately start a fresh session from inside the callback.
|
|
1029
|
+
*
|
|
1030
|
+
* Cleared by resetWebXRState() (i.e. after every session end) like the other
|
|
1031
|
+
* module-level callbacks — re-register before/with each new session.
|
|
1032
|
+
* Pass `null` to unregister. F3, 2026-07-04 user feedback.
|
|
1033
|
+
*/
|
|
1034
|
+
function setSessionEndCallback(cb) {
|
|
1035
|
+
onSessionEnd = cb;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* The ONE teardown for a session that has ended, shared by both trigger
|
|
1039
|
+
* paths via the XRSession 'end' listener. Before F3 the system-initiated
|
|
1040
|
+
* path (back gesture) only nulled `xrSession`/`latestArPose`, leaving the
|
|
1041
|
+
* renderer compositing the 3D scene over a black camera background and the
|
|
1042
|
+
* host app never notified — the "haunted scene" from
|
|
1043
|
+
* docs/2026-02-15-lifecycle-orphans.md §1.
|
|
1044
|
+
*/
|
|
1045
|
+
function handleSessionEnded() {
|
|
1046
|
+
log.info("Session ended");
|
|
1047
|
+
const callback = onSessionEnd;
|
|
1048
|
+
const requestedByApp = endRequestedByApp;
|
|
1049
|
+
endRequestedByApp = false;
|
|
1050
|
+
if (trackingStore) trackingStore.dispatch(resetTracking());
|
|
1051
|
+
resetWebXRState();
|
|
1052
|
+
if (callback) try {
|
|
1053
|
+
callback({ requestedByApp });
|
|
1054
|
+
} catch (err) {
|
|
1055
|
+
log.error("Session-end callback threw:", err);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
979
1059
|
* End the current XR session and clean up all resources.
|
|
980
1060
|
*
|
|
981
1061
|
* Stops the animation loop, ends the XR session, then delegates the full
|
|
@@ -988,7 +1068,10 @@ function nueQuaternionToWebXR$1(nue) {
|
|
|
988
1068
|
async function endARSession() {
|
|
989
1069
|
if (renderer) renderer.setAnimationLoop(null);
|
|
990
1070
|
try {
|
|
991
|
-
if (xrSession)
|
|
1071
|
+
if (xrSession) {
|
|
1072
|
+
endRequestedByApp = true;
|
|
1073
|
+
await xrSession.end();
|
|
1074
|
+
}
|
|
992
1075
|
} finally {
|
|
993
1076
|
resetWebXRState();
|
|
994
1077
|
}
|
|
@@ -1009,6 +1092,18 @@ function setImageCaptureCallback(onCaptured, screenRotationGetter, onFailed, onS
|
|
|
1009
1092
|
onSuspiciousImage = onSuspicious ?? null;
|
|
1010
1093
|
}
|
|
1011
1094
|
/**
|
|
1095
|
+
* Inject (or clear) the off-thread image-quality analyzer used by the blur/
|
|
1096
|
+
* blackness drop+retry gate. Pass the recorder's Web Worker-backed analyzer to
|
|
1097
|
+
* enable the gate, or `null` to clear it (legacy save-immediately path). Must be
|
|
1098
|
+
* set BEFORE {@link startImageCapture} for the active recording — the manager
|
|
1099
|
+
* reads it once when constructed. The analyzer only runs when the capture
|
|
1100
|
+
* config's `qualityFilter.enabled` is true, so injecting it is harmless when the
|
|
1101
|
+
* gate is off. Ownership/disposal of the worker is the caller's responsibility.
|
|
1102
|
+
*/
|
|
1103
|
+
function setImageQualityAnalyzer(analyzer) {
|
|
1104
|
+
imageQualityAnalyzer = analyzer;
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1012
1107
|
* Start capturing images during recording.
|
|
1013
1108
|
* Must call setImageCaptureCallback first.
|
|
1014
1109
|
*
|
|
@@ -1036,7 +1131,8 @@ function startImageCapture(config) {
|
|
|
1036
1131
|
getScreenRotation,
|
|
1037
1132
|
onCaptured: onImageCaptured,
|
|
1038
1133
|
onCaptureFailed: onCaptureFailed ?? void 0,
|
|
1039
|
-
onSuspiciousImage: onSuspiciousImage ?? void 0
|
|
1134
|
+
onSuspiciousImage: onSuspiciousImage ?? void 0,
|
|
1135
|
+
analyzeFrame: imageQualityAnalyzer ?? void 0
|
|
1040
1136
|
};
|
|
1041
1137
|
const mergedConfig = {
|
|
1042
1138
|
...DEFAULT_CAPTURE_CONFIG,
|
|
@@ -1263,4 +1359,4 @@ function getLiveCss3dManager() {
|
|
|
1263
1359
|
return css3dManager;
|
|
1264
1360
|
}
|
|
1265
1361
|
//#endregion
|
|
1266
|
-
export {
|
|
1362
|
+
export { setArWorldGroup as A, setTrackingLostCallback as B, initAR as C, nueQuaternionToWebXR$1 as D, nuePositionToWebXR as E, setImageCaptureCallback as F, startDepthCapture as G, setTrackingStore as H, setImageQualityAnalyzer as I, stopDepthCapture as J, startImageCapture as K, setScene as L, setCameraFrameCallback as M, setDepthCaptureCallback as N, resetWebXRState as O, setFrameCallback as P, setSessionEndCallback as R, getXrCameraFromPose as S, isXRCameraLike as T, shouldLogCameraAccessDiagnostic as U, setTrackingRecoveredCallback as V, startCameraFrameCapture as W, CameraFrameSource as X, stopImageCapture as Y, getDepthInfoFromFrame as _, applyAlignmentMatrix as a, getLiveCss3dManager as b, endARSession as c, getArPose as d, getArWorldGroup as f, getCurrentArPose as g, getCameraFrameCount as h, DEFAULT_CAMERA_FRAME_CAPTURE_SIZE as i, setCamera as j, setArPose as k, extractPoseFromViewer as l, getCameraFrameCaptureSize as m, AR_CAMERA_FOV as n, buildSessionOptions as o, getCamera as p, stopCameraFrameCapture as q, AR_CAMERA_NEAR as r, createSceneHierarchy as s, AR_CAMERA_FAR as t, extractResetTransformData as u, getDepthSampleCount as v, isWebXRSupported as w, getScene as x, getImageCaptureFrameCount as y, setTrackingCallbacks as z };
|