gps-plus-slam-app-framework 1.7.0 → 1.9.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/{accuracy-circles-BtqOP_v1.d.ts → accuracy-circles-DkWBkF5g.d.ts} +16 -6
- package/dist/{app-selectors-ikTUQZpe.d.ts → app-selectors-BbAiCRUT.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 +1 -1
- package/dist/ar/image-capture.js +1 -1
- package/dist/ar/image-quality.d.ts +1 -1
- package/dist/ar/image-quality.js +2 -1
- package/dist/ar/index.d.ts +32 -31
- package/dist/ar/index.js +5 -4
- 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-MFpFlV7j.js → ar-BqCudtqE.js} +3 -2
- package/dist/{ar-world-group-alignment-69fgnB39.d.ts → ar-world-group-alignment-Cfg0trC3.d.ts} +1 -1
- package/dist/{capture-motion-gate-4EscNZCw.js → capture-motion-gate-_NdTSwKi.js} +17 -5
- 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-n_VF63D5.d.ts → create-slam-app-store-D1QSMuKu.d.ts} +8 -9
- package/dist/{create-slam-app-store-DkCHOpvb.js → create-slam-app-store-DXiAnJwR.js} +9 -2
- package/dist/{depth-grid-lookup-DQ-IYhO3.d.ts → depth-grid-lookup-D5atpNZ3.d.ts} +1 -1
- package/dist/{depth-sampler-asc8exHt.d.ts → depth-sampler-CwObAwEA.d.ts} +33 -1
- package/dist/{depth-unprojection-DjsbJW9L.d.ts → depth-unprojection-DRdBU90S.d.ts} +1 -1
- package/dist/{enable-gps-ar-DkNhFv4Y.d.ts → enable-gps-ar-BBtRO2FJ.d.ts} +4 -4
- package/dist/{frame-conversions-DObhj8Wf.d.ts → frame-conversions-DDcNHkTb.d.ts} +1 -1
- package/dist/{fused-path-v6eOmLl3.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/{gps-anchor-BeFO2RYa.d.ts → gps-anchor-CEjGqEVD.d.ts} +1 -1
- package/dist/{gps-ar-pose-sampler-DHMZmYAc.d.ts → gps-ar-pose-sampler-DEGNk2CI.d.ts} +1 -1
- package/dist/{gps-event-coordinator-CjWW636x.d.ts → gps-event-coordinator-BWpgBrHq.d.ts} +4 -4
- package/dist/{image-capture-CNZDrQXG.d.ts → image-capture-Bp6Pd4Md.d.ts} +3 -3
- package/dist/{image-capture-B2-b6tKK.js → image-capture-DVcuqMtX.js} +1 -1
- package/dist/{index-BODDUTvD.d.ts → index-B6iBnO_K.d.ts} +1 -1
- package/dist/index-DXPVzOpP.d.ts +2 -0
- package/dist/{index-BneSj4_5.d.ts → index-cCQWuYqy.d.ts} +3 -3
- package/dist/index.d.ts +82 -80
- package/dist/index.js +10 -8
- package/dist/{leaflet-map-overlay-CmIyiE8n.d.ts → leaflet-map-overlay-C8GS4cUy.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-1tF5gcYV.js +124 -0
- package/dist/map-data-CUnTzRIC.d.ts +78 -0
- package/dist/{map-overlay-draw-ymz1s7fG.d.ts → map-overlay-draw-6WJeq0Kg.d.ts} +1 -1
- package/dist/{null-storage-backend-B9rtyDfG.d.ts → null-storage-backend-B1-jlpin.d.ts} +2 -2
- package/dist/occlusion-mesh-kXCQ2FM1.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-3VZh-qH6.d.ts → opfs-storage-CTkxvxN4.d.ts} +5 -0
- package/dist/{opfs-storage-backend-JJiUJo-3.d.ts → opfs-storage-backend-D6Z03fML.d.ts} +2 -2
- package/dist/{persistence-middleware-DAPTTwB2.d.ts → persistence-middleware-CG-t08X4.d.ts} +1 -1
- package/dist/{planar-pnp-my9bqULS.d.ts → planar-pnp-D7bc_rLX.d.ts} +1 -1
- package/dist/{qr-debug-view-Brl73HGO.d.ts → qr-debug-view-BJooqbiP.d.ts} +1 -1
- package/dist/{qr-derived-pose-CmxTP2m4.d.ts → qr-derived-pose-BwNWn8oV.d.ts} +2 -2
- package/dist/{qr-detected-slice-VQssZmQJ.d.ts → qr-detected-slice-TzVb8bij.d.ts} +4 -4
- package/dist/{qr-detection-controller-CDurHv8E.d.ts → qr-detection-controller-Bo4XfQTP.d.ts} +3 -3
- package/dist/{qr-frontend--fFX25mV.d.ts → qr-frontend-BPURlh3S.d.ts} +1 -1
- package/dist/{qr-pose-aggregation-B7tWJq7j.d.ts → qr-pose-aggregation-CypnmjA-.d.ts} +1 -1
- package/dist/{qr-size-depth-context-CeAG53fy.d.ts → qr-size-depth-context-apAOmbrZ.d.ts} +2 -2
- package/dist/{qr-size-measurer-DQdAsid1.d.ts → qr-size-measurer-DxiMqRUw.d.ts} +3 -3
- package/dist/{recording-options-BPCpMHLK.d.ts → recording-options-DYKWVI93.d.ts} +203 -24
- package/dist/{recording-replayer-DQKpAGyQ.d.ts → recording-replayer-BJ8Lf0QY.d.ts} +2 -2
- package/dist/{recording-slice-DaxHZvNu.d.ts → recording-slice-BW8SiLo1.d.ts} +2 -2
- package/dist/{replay-engine-CmKh_4wf.d.ts → replay-engine-DmCAc7WM.d.ts} +1 -1
- package/dist/{replay-scene-DBlDrV_x.d.ts → replay-scene-zJ8SFDjE.d.ts} +3 -3
- package/dist/sensors/absolute-orientation.d.ts +1 -1
- package/dist/sensors/absolute-orientation.js +23 -6
- package/dist/sensors/gps-error-handler.d.ts +1 -1
- package/dist/sensors/gps.d.ts +1 -1
- package/dist/sensors/index.d.ts +4 -4
- 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 -1
- package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
- package/dist/state/gps-event-coordinator.d.ts +1 -1
- package/dist/state/index.d.ts +19 -19
- package/dist/state/index.js +2 -2
- package/dist/state/persistence-middleware.d.ts +1 -1
- package/dist/state/qr-detected-slice.d.ts +3 -3
- package/dist/state/recording-options.d.ts +2 -2
- package/dist/state/recording-options.js +150 -19
- 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 +26 -12
- package/dist/state/subscribe-to-selector.d.ts +1 -1
- package/dist/state/tracking-quality.d.ts +1 -1
- package/dist/state/tracking-quality.js +14 -6
- package/dist/state/tracking-slice.d.ts +1 -1
- package/dist/{state-E51QT_-j.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 +30 -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 +2 -2
- package/dist/storage/zip-reader.js +20 -7
- package/dist/{storage-backend-BdVLD0dU.d.ts → storage-backend-sEtmM4yW.d.ts} +1 -1
- package/dist/{store-subscribers-CA6AJH_8.d.ts → store-subscribers-DJUErW-o.d.ts} +6 -3
- package/dist/{subscribe-to-selector-B6EUmQ3I.d.ts → subscribe-to-selector-dQV8SpsO.d.ts} +1 -1
- package/dist/test-utils/browser-mocks.d.ts +1 -1
- package/dist/test-utils/browser-mocks.js +2 -0
- package/dist/test-utils/zip-round-trip-helpers.d.ts +1 -1
- package/dist/{tracking-slice-BuAZxXZ5.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 +7 -7
- 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-DG-eNfny.js → webxr-session-BCP2XK7k.js} +88 -13
- package/dist/{webxr-session-Do5L6G3B.d.ts → webxr-session-BWCpGd-2.d.ts} +73 -10
- package/dist/{zip-reader-D9iPQwdQ.d.ts → zip-reader-BzENzMH6.d.ts} +36 -10
- package/package.json +9 -5
- package/dist/index-_Rhgoqkh.d.ts +0 -2
- package/dist/lerp-utils-BLmHhkVe.d.ts +0 -20
- package/dist/map-data-B4oEehUh.d.ts +0 -49
- package/dist/occupancy-grid-DnOc-6PU.d.ts +0 -82
- /package/dist/{absolute-orientation-CAEh840t.d.ts → absolute-orientation-BAxc15J6.d.ts} +0 -0
- /package/dist/{alignment-lerper-Bil6vbVw.d.ts → alignment-lerper-gD70k7Fq.d.ts} +0 -0
- /package/dist/{ar-types-BFX2r1wk.d.ts → ar-types-Cc5yvBwn.d.ts} +0 -0
- /package/dist/{bresenham3d-CU1Gv2W4.d.ts → bresenham3d-DGc4nIb-.d.ts} +0 -0
- /package/dist/{camera-blit-capture-NKT9H5-e.d.ts → camera-blit-capture-BimSdyih.d.ts} +0 -0
- /package/dist/{camera-follower-CqJJ7zRj.d.ts → camera-follower-DfBm2J5i.d.ts} +0 -0
- /package/dist/{capability-checker-DUQLg6gq.d.ts → capability-checker-BTTpygvZ.d.ts} +0 -0
- /package/dist/{capture-failure-tracker-gsm8q7yi.d.ts → capture-failure-tracker-_BVfhpxv.d.ts} +0 -0
- /package/dist/{capture-motion-gate-0Yl2NpQG.d.ts → capture-motion-gate-BGoAMkmf.d.ts} +0 -0
- /package/dist/{chromium-camera-access-workaround-D4K2uwwQ.d.ts → chromium-camera-access-workaround-C6yJLXz9.d.ts} +0 -0
- /package/dist/{concurrency-By0oOTZt.d.ts → concurrency-DPeGBH0X.d.ts} +0 -0
- /package/dist/{css3d-renderer-manager-B3kGNtcn.d.ts → css3d-renderer-manager-CPpVtvgs.d.ts} +0 -0
- /package/dist/{failure-tracker-BO7M7jF_.d.ts → failure-tracker-CeKf5HIc.d.ts} +0 -0
- /package/dist/{file-system-utils-DOajnMm-.d.ts → file-system-utils-DueXHxo9.d.ts} +0 -0
- /package/dist/{format-file-size-Dc98ulzB.d.ts → format-file-size-DxN-EBLc.d.ts} +0 -0
- /package/dist/{frame-loop-CpVjQ1dH.d.ts → frame-loop-7GBMBu68.d.ts} +0 -0
- /package/dist/{frustum-visibility-CeoCZchO.d.ts → frustum-visibility-CokkgS_i.d.ts} +0 -0
- /package/dist/{geo-types-3Ip1NzVR.d.ts → geo-types-DrGVu8nS.d.ts} +0 -0
- /package/dist/{gps-BBO3hKfT.d.ts → gps-CDKDQokE.d.ts} +0 -0
- /package/dist/{gps-compass-cubes-DSN87L8p.d.ts → gps-compass-cubes-1mooS3BS.d.ts} +0 -0
- /package/dist/{gps-error-handler-BbPsfw-_.d.ts → gps-error-handler-C34GQuTu.d.ts} +0 -0
- /package/dist/{gps-event-markers-Cz9RMA2k.d.ts → gps-event-markers-Di2nBzx-.d.ts} +0 -0
- /package/dist/{h3-proximity-C-j0pw1j.d.ts → h3-proximity-D0GTHQmr.d.ts} +0 -0
- /package/dist/{hit-test-reticle-DV-2TpB9.d.ts → hit-test-reticle-CaQbrhbk.d.ts} +0 -0
- /package/dist/{image-quality-smCBoFj1.d.ts → image-quality-BMx2ko9-.d.ts} +0 -0
- /package/dist/{index-BAPokxMb.d.ts → index-D74JTX4I.d.ts} +0 -0
- /package/dist/{list-formatter-KNMS0ay2.d.ts → list-formatter-DLFDnS70.d.ts} +0 -0
- /package/dist/{logger-CLnPrHiD.d.ts → logger-CqTd5Kpk.d.ts} +0 -0
- /package/dist/{map-overlay-Dh2B97MP.d.ts → map-overlay-BCQgvIgU.d.ts} +0 -0
- /package/dist/{permission-checker-CDuI8BwC.d.ts → permission-checker-B-VtjnBA.d.ts} +0 -0
- /package/dist/{qr-pose-D2rfu5DA.d.ts → qr-pose-y3vaEd6x.d.ts} +0 -0
- /package/dist/{scene-node-names-BiShOQDB.d.ts → scene-node-names-CGb-ylBd.d.ts} +0 -0
- /package/dist/{session-disposers-zv9OGXPB.js → session-disposers-BPrMplIC.js} +0 -0
- /package/dist/{three-dispose-Bt1k7WKh.d.ts → three-dispose-BMOAmPhb.d.ts} +0 -0
- /package/dist/{vis-colors-Obw2pLqT.d.ts → vis-colors-C7HI0u9_.d.ts} +0 -0
- /package/dist/{webxr-nue-basis-3nfjDJJp.d.ts → webxr-nue-basis-CaCnYxuq.d.ts} +0 -0
- /package/dist/{xr-camera-texture-Cva1q3Qj.d.ts → xr-camera-texture-q0eIDSRg.d.ts} +0 -0
- /package/dist/{xr-error-handler-CtvHpjfm.d.ts → xr-error-handler-uVGDu9IX.d.ts} +0 -0
- /package/dist/{xr-frame-loop-DIYtRcUa.d.ts → xr-frame-loop-DX3JPpAU.d.ts} +0 -0
- /package/dist/{zip-coverage-embed-DCAREVmw.d.ts → zip-coverage-embed-ncVbaZAz.d.ts} +0 -0
- /package/dist/{zip-export-ol0S_Ofd.d.ts → zip-export-CtiUnAlJ.d.ts} +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { packMeshRequest, runMeshRequest } from "../ar/occlusion-mesh-worker.js";
|
|
2
|
+
//#region ../src/visualization/occluder-mesh-driver.ts
|
|
3
|
+
/**
|
|
4
|
+
* Occluder mesh driver — main-thread orchestration for the Web Worker offload.
|
|
5
|
+
*
|
|
6
|
+
* Drives {@link packMeshRequest} / {@link runMeshRequest} (see
|
|
7
|
+
* `2026-07-01-occluder-worker-and-chunked-remesh-plan.md`): the occluder wiring
|
|
8
|
+
* calls {@link OccluderMeshDriver.request} on each refresh with the occupied-cell
|
|
9
|
+
* snapshot; the driver packs it, posts it to a worker, and invokes a callback
|
|
10
|
+
* with the meshed geometry when it returns — so the expensive mesh runs
|
|
11
|
+
* off-thread and never stalls the render.
|
|
12
|
+
*
|
|
13
|
+
* Policy (locked 2026-07-01):
|
|
14
|
+
* - **Coalesce to latest** — at most one job in flight; a request made while busy
|
|
15
|
+
* is remembered (newest wins) and posted when the current one returns, so work
|
|
16
|
+
* never queues up behind a growing grid.
|
|
17
|
+
* - **Synchronous fallback** — constructed with `poster = null` (no worker
|
|
18
|
+
* support), it meshes inline and calls back immediately (today's behaviour).
|
|
19
|
+
* - **Error recovery (2026-07-01 Phase 1 gap fix)** — a worker that never replies
|
|
20
|
+
* (an uncaught throw in `runMeshRequest`, or a module that fails to load) must
|
|
21
|
+
* not wedge the in-flight slot forever. On a worker error the driver clears the
|
|
22
|
+
* slot and re-posts the pending snapshot (or lets the next refresh post) so the
|
|
23
|
+
* occluder never silently freezes. If the worker errors **before ever producing
|
|
24
|
+
* a mesh** — almost always a load failure — the driver declares it unusable
|
|
25
|
+
* ({@link OccluderMeshDriverOptions.onWorkerUnusable}) and switches to
|
|
26
|
+
* synchronous main-thread meshing, since further posts would vanish into a dead
|
|
27
|
+
* worker.
|
|
28
|
+
*
|
|
29
|
+
* Framework-owned + THREE-free: a consumer supplies a {@link MeshWorkerPoster}
|
|
30
|
+
* adapting a real `Worker`, and applies the result (e.g. `OcclusionMesh.applyMeshData`).
|
|
31
|
+
*/
|
|
32
|
+
/** Cell count of a snapshot in either representation. */
|
|
33
|
+
function cellCountOf(cells) {
|
|
34
|
+
return cells instanceof Int32Array ? cells.length / 3 : cells.length;
|
|
35
|
+
}
|
|
36
|
+
var OccluderMeshDriver = class {
|
|
37
|
+
poster;
|
|
38
|
+
onWorkerUnusable;
|
|
39
|
+
onError;
|
|
40
|
+
onMeshStats;
|
|
41
|
+
now;
|
|
42
|
+
nextId = 1;
|
|
43
|
+
inFlightId = null;
|
|
44
|
+
inFlightCallback = null;
|
|
45
|
+
/** Timing/size context of the in-flight job; cleared on completion/failure. */
|
|
46
|
+
inFlightStats = null;
|
|
47
|
+
pending = null;
|
|
48
|
+
disposed = false;
|
|
49
|
+
/** True ⇒ mesh inline (no worker, or the worker was declared unusable). */
|
|
50
|
+
syncMode;
|
|
51
|
+
/** Set once the worker returns a mesh — distinguishes a transient error from a load failure. */
|
|
52
|
+
hasSucceeded = false;
|
|
53
|
+
/**
|
|
54
|
+
* @param poster a worker adapter, or `null` for synchronous main-thread meshing.
|
|
55
|
+
* @param options optional {@link OccluderMeshDriverOptions.onWorkerUnusable} /
|
|
56
|
+
* {@link OccluderMeshDriverOptions.onError} callbacks.
|
|
57
|
+
*/
|
|
58
|
+
constructor(poster, options = {}) {
|
|
59
|
+
this.poster = poster;
|
|
60
|
+
this.syncMode = poster === null;
|
|
61
|
+
this.onWorkerUnusable = options.onWorkerUnusable;
|
|
62
|
+
this.onError = options.onError;
|
|
63
|
+
this.onMeshStats = options.onMeshStats;
|
|
64
|
+
this.now = options.now ?? (() => performance.now());
|
|
65
|
+
if (poster) {
|
|
66
|
+
poster.onmessage = (event) => this.handleResponse(event.data);
|
|
67
|
+
poster.onerror = (error) => this.handleWorkerError(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** True while a mesh job is being computed (worker path only). */
|
|
71
|
+
get busy() {
|
|
72
|
+
return this.inFlightId !== null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Request a mesh of `cells` (tuple array, or a flat Int32Array snapshot —
|
|
76
|
+
* see `packMeshRequest`; a flat snapshot's buffer is transferred/detached,
|
|
77
|
+
* so pass a fresh array). If a job is already in flight, this becomes the
|
|
78
|
+
* (single) pending job — the newest request wins; intermediates are dropped.
|
|
79
|
+
* `getCellPoint` is invoked with a transient cell tuple that may be a reused
|
|
80
|
+
* scratch — it must not retain the tuple beyond the call (see
|
|
81
|
+
* `MeshOccupiedCellsOptions.getCellPoint`).
|
|
82
|
+
*/
|
|
83
|
+
request(cells, cellSizeM, mode, getCellPoint, onMesh) {
|
|
84
|
+
if (this.disposed) return;
|
|
85
|
+
const job = {
|
|
86
|
+
cells,
|
|
87
|
+
cellSizeM,
|
|
88
|
+
mode,
|
|
89
|
+
getCellPoint,
|
|
90
|
+
onMesh
|
|
91
|
+
};
|
|
92
|
+
if (this.inFlightId !== null) {
|
|
93
|
+
this.pending = job;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.post(job);
|
|
97
|
+
}
|
|
98
|
+
post(job) {
|
|
99
|
+
const id = this.nextId++;
|
|
100
|
+
this.inFlightId = id;
|
|
101
|
+
this.inFlightCallback = job.onMesh;
|
|
102
|
+
this.inFlightStats = {
|
|
103
|
+
startedMs: this.now(),
|
|
104
|
+
cellCount: cellCountOf(job.cells),
|
|
105
|
+
mode: job.mode,
|
|
106
|
+
synchronous: this.syncMode || this.poster === null
|
|
107
|
+
};
|
|
108
|
+
let packed;
|
|
109
|
+
try {
|
|
110
|
+
packed = packMeshRequest(id, job.cells, job.cellSizeM, job.mode, job.getCellPoint);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
this.failInFlight(id, error, { workerAtFault: false });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const { request, transfer } = packed;
|
|
116
|
+
if (this.poster && !this.syncMode) try {
|
|
117
|
+
this.poster.postMessage(request, transfer);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
this.failInFlight(id, error);
|
|
120
|
+
}
|
|
121
|
+
else try {
|
|
122
|
+
const { response } = runMeshRequest(request);
|
|
123
|
+
this.handleResponse(response);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (this.inFlightId === id) this.failInFlight(id, error);
|
|
126
|
+
else this.onError?.(error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
handleResponse(response) {
|
|
130
|
+
if (this.disposed || response.id !== this.inFlightId) return;
|
|
131
|
+
this.hasSucceeded = true;
|
|
132
|
+
const callback = this.inFlightCallback;
|
|
133
|
+
const stats = this.inFlightStats;
|
|
134
|
+
try {
|
|
135
|
+
if (stats && this.onMeshStats) {
|
|
136
|
+
const { startedMs, ...rest } = stats;
|
|
137
|
+
this.onMeshStats({
|
|
138
|
+
...rest,
|
|
139
|
+
durationMs: this.now() - startedMs
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
callback?.(response.positions, response.indices);
|
|
143
|
+
} finally {
|
|
144
|
+
this.inFlightId = null;
|
|
145
|
+
this.inFlightCallback = null;
|
|
146
|
+
this.inFlightStats = null;
|
|
147
|
+
if (!this.disposed && this.pending) {
|
|
148
|
+
const next = this.pending;
|
|
149
|
+
this.pending = null;
|
|
150
|
+
this.post(next);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* A worker error event. If a job is in flight, recover it (only one ever is).
|
|
156
|
+
* If none is — the realistic ordering for a **module-load failure**, which
|
|
157
|
+
* errors within a few ms of construction, before the first (throttled)
|
|
158
|
+
* `request` posts anything — the worker is dead: posting to it would be
|
|
159
|
+
* silently dropped (no second error) and wedge the in-flight slot forever. So
|
|
160
|
+
* a pre-first-post error on a worker that has **never** succeeded is treated as
|
|
161
|
+
* a load failure right here — declare it unusable and switch to synchronous
|
|
162
|
+
* meshing so the first `request` never touches the dead worker. A stray/late
|
|
163
|
+
* error on a worker that has already meshed once is transient — kept, ignored.
|
|
164
|
+
*/
|
|
165
|
+
handleWorkerError(error) {
|
|
166
|
+
if (this.disposed) return;
|
|
167
|
+
if (this.inFlightId !== null) {
|
|
168
|
+
this.failInFlight(this.inFlightId, error);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!this.syncMode && !this.hasSucceeded) {
|
|
172
|
+
if (error !== void 0) this.onError?.(error);
|
|
173
|
+
this.syncMode = true;
|
|
174
|
+
this.onWorkerUnusable?.();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Clear a failed in-flight job so the driver never wedges. If the worker had
|
|
179
|
+
* never produced a mesh (almost always a module load failure) fall back to
|
|
180
|
+
* synchronous meshing — unless `workerAtFault` is false (a pack-time error in
|
|
181
|
+
* the caller's `getCellPoint`, which never reached the worker). Then re-post
|
|
182
|
+
* the pending snapshot if one is queued. We deliberately do NOT re-post the
|
|
183
|
+
* *failed* job: if it failed on deterministic bad data, re-posting would
|
|
184
|
+
* loop — the next refresh brings a fresh snapshot.
|
|
185
|
+
*/
|
|
186
|
+
failInFlight(id, error, options = {}) {
|
|
187
|
+
if (this.disposed || id !== this.inFlightId) return;
|
|
188
|
+
try {
|
|
189
|
+
if (error !== void 0) this.onError?.(error);
|
|
190
|
+
} finally {
|
|
191
|
+
this.inFlightId = null;
|
|
192
|
+
this.inFlightCallback = null;
|
|
193
|
+
this.inFlightStats = null;
|
|
194
|
+
if ((options.workerAtFault ?? true) && !this.syncMode && !this.hasSucceeded) {
|
|
195
|
+
this.syncMode = true;
|
|
196
|
+
this.onWorkerUnusable?.();
|
|
197
|
+
}
|
|
198
|
+
if (this.pending) {
|
|
199
|
+
const next = this.pending;
|
|
200
|
+
this.pending = null;
|
|
201
|
+
this.post(next);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/** Stop delivering results (detaches the worker handlers; drops any pending). */
|
|
206
|
+
dispose() {
|
|
207
|
+
this.disposed = true;
|
|
208
|
+
this.pending = null;
|
|
209
|
+
this.inFlightCallback = null;
|
|
210
|
+
this.inFlightId = null;
|
|
211
|
+
this.inFlightStats = null;
|
|
212
|
+
if (this.poster) {
|
|
213
|
+
this.poster.onmessage = null;
|
|
214
|
+
this.poster.onerror = null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
//#endregion
|
|
219
|
+
export { OccluderMeshDriver };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as occluderDepthFade, i as buildOccluderDepthShadeSnippet, n as OcclusionMesh, o as occluderFresnelRim, r as OcclusionMeshOptions, t as OCCLUDER_DEPTH_SHADE } from "../occlusion-mesh-kXCQ2FM1.js";
|
|
2
|
+
export { OCCLUDER_DEPTH_SHADE, OcclusionMesh, OcclusionMeshOptions, buildOccluderDepthShadeSnippet, occluderDepthFade, occluderFresnelRim };
|
|
@@ -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 };
|