gps-plus-slam-app-framework 1.11.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -14
- package/dist/{app-selectors-DG0ZqaeI.d.ts → app-selectors-DBUgwomp.d.ts} +1 -1
- package/dist/ar/ar-crash-isolation.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/capture-motion-gate.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 +1 -1
- package/dist/ar/depth-sampler.d.ts +3 -3
- package/dist/ar/depth-sampler.js +28 -1
- package/dist/ar/depth-unprojection.d.ts +1 -1
- package/dist/ar/enable-gps-ar.d.ts +1 -1
- package/dist/ar/enable-gps-ar.js +20 -5
- package/dist/ar/frame-loop.d.ts +1 -1
- package/dist/ar/hit-test-reticle-driver.d.ts +2 -0
- package/dist/ar/hit-test-reticle-driver.js +122 -0
- 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/index.d.ts +35 -34
- package/dist/ar/index.js +5 -4
- package/dist/ar/occlusion-mesh-worker.d.ts +2 -2
- package/dist/ar/occlusion-mesh-worker.js +6 -4
- package/dist/ar/occupancy-grid.d.ts +2 -2
- package/dist/ar/occupancy-grid.js +191 -11
- package/dist/ar/occupancy-mesher.d.ts +1 -1
- package/dist/ar/occupancy-mesher.js +1 -636
- package/dist/ar/qr/index.d.ts +10 -10
- package/dist/ar/qr/planar-pnp.d.ts +1 -1
- package/dist/ar/qr/qr-debug-view.d.ts +1 -1
- package/dist/ar/qr/qr-derived-pose.d.ts +1 -1
- package/dist/ar/qr/qr-detection-controller.d.ts +1 -1
- package/dist/ar/qr/qr-frontend.d.ts +1 -1
- package/dist/ar/qr/qr-pose.d.ts +1 -1
- package/dist/ar/qr/qr-size-depth-context.d.ts +2 -2
- package/dist/ar/qr/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 +3 -3
- package/dist/ar/webxr-session.js +1 -1
- 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-world-group-alignment-BdS_h8Nf.d.ts → ar-world-group-alignment-Dn_UiRy1.d.ts} +1 -1
- package/dist/{cell-key-Bj-eYTLA.js → cell-key-D1b_Ix-p.js} +12 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/{create-slam-app-store-oK9bnqyI.js → create-slam-app-store-B62K8_Qr.js} +17 -4
- package/dist/{create-slam-app-store-DFm5K9Ob.d.ts → create-slam-app-store-sxb1-esK.d.ts} +34 -4
- package/dist/{depth-grid-lookup-CCuzlqUo.d.ts → depth-grid-lookup-PcJTdRLL.d.ts} +1 -1
- package/dist/{depth-sampler-CS2jAFw4.d.ts → depth-sampler-BcvuxgCS.d.ts} +29 -2
- package/dist/{depth-unprojection-ET3Gwedf.d.ts → depth-unprojection-DbGipFkA.d.ts} +1 -1
- package/dist/{enable-gps-ar-D_xEHzh5.d.ts → enable-gps-ar-xjz3jNqm.d.ts} +4 -4
- package/dist/{frame-conversions-CH7E2Iv_.d.ts → frame-conversions-CzPQDYpM.d.ts} +1 -1
- package/dist/{fused-path-DOKzVMiO.d.ts → fused-path-CBAttcVm.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-BaqyRsPC.d.ts → gps-anchor-Gv4NDwH4.d.ts} +1 -1
- package/dist/{gps-ar-pose-sampler-BuuUuK-D.d.ts → gps-ar-pose-sampler-DBEYDFY5.d.ts} +1 -1
- package/dist/{gps-event-coordinator-Cruoe5I-.d.ts → gps-event-coordinator-BAy56wYd.d.ts} +4 -4
- package/dist/{gps-event-markers-y2b2aVl6.d.ts → gps-event-markers-212h7j3D.d.ts} +4 -4
- package/dist/hit-test-reticle-driver-CfKiDgsa.d.ts +40 -0
- package/dist/{image-capture-BevgKpoO.d.ts → image-capture-BXmx1YvD.d.ts} +3 -3
- package/dist/{index-Dxb6ATNK.d.ts → index-DVmgFIgm.d.ts} +1 -1
- package/dist/{index-BWVwd9Zw.d.ts → index-DX9VC257.d.ts} +3 -3
- package/dist/{index-ClmnNJ6-.d.ts → index-qYJ5-SBC.d.ts} +1 -1
- package/dist/index.d.ts +89 -82
- package/dist/index.js +17 -10
- package/dist/{leaflet-map-overlay-CksetpTt.d.ts → leaflet-map-overlay-BkHmzvpD.d.ts} +1 -1
- package/dist/{map-data-D8cCqpOM.d.ts → map-data-BQXPtRz7.d.ts} +1 -1
- package/dist/{map-overlay-draw-Cy-klqoA.d.ts → map-overlay-draw-sTM3wug_.d.ts} +1 -1
- package/dist/{null-storage-backend-DXXuwG8T.d.ts → null-storage-backend-Dtn3TBfT.d.ts} +2 -2
- package/dist/{occlusion-mesh-BWe2PrVi.d.ts → occlusion-mesh-BY2aMej3.d.ts} +22 -4
- package/dist/occupancy-cubes-visualizer-soN0Sizd.d.ts +146 -0
- package/dist/{occupancy-grid-CYBdmMOo.d.ts → occupancy-grid-DKzIdCSZ.d.ts} +113 -3
- package/dist/{occupancy-mesher-B4Bv4BTk.d.ts → occupancy-mesher-Co80tOpl.d.ts} +22 -1
- package/dist/occupancy-mesher-DzNeJdQf.js +837 -0
- package/dist/{opfs-storage-backend-D1ZpeNEL.d.ts → opfs-storage-backend-CZ-ECudx.d.ts} +2 -2
- package/dist/perf-stats-overlay-BMEQrzx6.d.ts +68 -0
- package/dist/{persistence-middleware-Cun4aF5M.d.ts → persistence-middleware-Byo_Fs4n.d.ts} +1 -1
- package/dist/{planar-pnp-ByxwDEyM.d.ts → planar-pnp-CkcMN3o3.d.ts} +1 -1
- package/dist/pointer-picking-BATHHTlT.d.ts +41 -0
- package/dist/{qr-debug-view-CGlITU09.d.ts → qr-debug-view-BMjNVhV2.d.ts} +1 -1
- package/dist/{qr-derived-pose-DZPxZd9C.d.ts → qr-derived-pose-Dq7mPK2s.d.ts} +2 -2
- package/dist/{qr-detected-slice-mfyxD2-M.d.ts → qr-detected-slice-8t_laDR9.d.ts} +4 -4
- package/dist/{qr-detection-controller-C5NII-vs.d.ts → qr-detection-controller-Dh4Paymb.d.ts} +3 -3
- package/dist/{qr-frontend-QzKZSqB-.d.ts → qr-frontend-BnLlVjKu.d.ts} +1 -1
- package/dist/{qr-pose-aggregation-mABSN0AW.d.ts → qr-pose-aggregation-Bpv4uARJ.d.ts} +1 -1
- package/dist/{qr-size-depth-context-CMDv6T29.d.ts → qr-size-depth-context-Bzbx98nS.d.ts} +2 -2
- package/dist/{qr-size-measurer-rVFf4AdM.d.ts → qr-size-measurer-18_W3INY.d.ts} +3 -3
- package/dist/{recording-replayer-qT2EM1Qs.d.ts → recording-replayer-CpFiYczh.d.ts} +2 -2
- package/dist/{recording-slice-DXw5-UvO.d.ts → recording-slice-zMedT0ot.d.ts} +1 -1
- package/dist/{replay-engine-B1H_kLhK.d.ts → replay-engine-DI7uknkv.d.ts} +1 -1
- package/dist/{replay-scene-BJeO36YT.d.ts → replay-scene-D5qzY0_x.d.ts} +3 -3
- package/dist/sensors/absolute-orientation.d.ts +1 -1
- 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 +2 -2
- package/dist/state/gps-event-coordinator.d.ts +2 -2
- package/dist/state/gps-event-coordinator.js +1 -1
- package/dist/state/index.d.ts +18 -18
- package/dist/state/index.js +3 -3
- package/dist/state/persistence-middleware.d.ts +1 -1
- package/dist/state/qr-detected-slice.d.ts +3 -3
- 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/replay-occupancy-subscriber.d.ts +51 -0
- package/dist/state/replay-occupancy-subscriber.js +85 -0
- package/dist/state/replay-session.d.ts +88 -0
- package/dist/state/replay-session.js +165 -0
- package/dist/state/store-subscribers.d.ts +2 -2
- package/dist/state/store-subscribers.js +1 -1
- package/dist/state/subscribe-to-selector.d.ts +1 -1
- package/dist/state/tracking-quality.d.ts +1 -1
- package/dist/state/tracking-slice.d.ts +1 -1
- package/dist/{state-CDF5LvsO.js → state-BECcnNRW.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/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-BX7egQKi.d.ts → storage-backend-DCYbOvTw.d.ts} +1 -1
- package/dist/{store-subscribers-CWHToX6N.d.ts → store-subscribers-BenHBQhD.d.ts} +2 -2
- package/dist/{subscribe-to-selector-j-eOYybQ.d.ts → subscribe-to-selector-hitZwSj8.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/text-sprite-C6VcC2mo.d.ts +53 -0
- package/dist/{tracking-slice-BAbcZK-Y.d.ts → tracking-slice-D5LDL_y4.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/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-compass-cubes.js +6 -22
- 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/hit-test-reticle.js +3 -2
- package/dist/visualization/index.d.ts +25 -19
- package/dist/visualization/index.js +10 -4
- package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
- package/dist/visualization/leaflet-map-overlay.js +1 -1
- package/dist/visualization/lerp-utils.d.ts +1 -1
- package/dist/visualization/map-data.d.ts +1 -1
- package/dist/visualization/map-data.js +1 -1
- package/dist/visualization/map-overlay-draw.d.ts +1 -1
- package/dist/visualization/occluder-mesh-driver.d.ts +2 -2
- package/dist/visualization/occlusion-mesh.d.ts +1 -1
- package/dist/visualization/occlusion-mesh.js +26 -1
- package/dist/visualization/occupancy-cubes-visualizer.d.ts +2 -0
- package/dist/visualization/occupancy-cubes-visualizer.js +221 -0
- package/dist/visualization/perf-stats-overlay.d.ts +2 -0
- package/dist/visualization/perf-stats-overlay.js +85 -0
- package/dist/visualization/pointer-picking.d.ts +2 -0
- package/dist/visualization/pointer-picking.js +58 -0
- package/dist/visualization/text-sprite.d.ts +2 -0
- package/dist/visualization/text-sprite.js +105 -0
- package/dist/visualization/three-dispose.d.ts +1 -1
- package/dist/visualization/vis-colors.d.ts +1 -1
- package/dist/visualization/wayfinding-hud.d.ts +2 -0
- package/dist/visualization/wayfinding-hud.js +289 -0
- package/dist/visualization/wayfinding-placement.d.ts +2 -0
- package/dist/visualization/wayfinding-placement.js +157 -0
- package/dist/wayfinding-hud-DVun2b69.d.ts +82 -0
- package/dist/wayfinding-placement-dnHzfYlT.d.ts +97 -0
- package/dist/{webxr-session-OyR3YbTq.js → webxr-session-BBlgzrqY.js} +3 -3
- package/dist/{webxr-session-DefwObIu.d.ts → webxr-session-DurXRnl_.d.ts} +7 -7
- package/package.json +3 -3
- /package/dist/{absolute-orientation-Dj1One3y.d.ts → absolute-orientation-0WIsqJnu.d.ts} +0 -0
- /package/dist/{accuracy-circles-BHz7yVIL.d.ts → accuracy-circles-CFnKGfsD.d.ts} +0 -0
- /package/dist/{alignment-lerper-DAHicRON.d.ts → alignment-lerper-CK1TYLIL.d.ts} +0 -0
- /package/dist/{ar-crash-isolation-DlXCGzMf.d.ts → ar-crash-isolation-BSFw_beO.d.ts} +0 -0
- /package/dist/{ar-types-D6PYS4bc.d.ts → ar-types-BprL3nKv.d.ts} +0 -0
- /package/dist/{bresenham3d-D-t9i_b5.d.ts → bresenham3d-bt61R-Qa.d.ts} +0 -0
- /package/dist/{camera-blit-capture-BlC-arRb.d.ts → camera-blit-capture-CgNJ9grH.d.ts} +0 -0
- /package/dist/{camera-follower-CO_ibF2o.d.ts → camera-follower-OnnXsbHd.d.ts} +0 -0
- /package/dist/{capability-checker-BZ3_x1YL.d.ts → capability-checker-DrWimJ_t.d.ts} +0 -0
- /package/dist/{capture-failure-tracker-BAUKjtXU.d.ts → capture-failure-tracker-bUXJeMvQ.d.ts} +0 -0
- /package/dist/{capture-motion-gate-CPwLekNf.d.ts → capture-motion-gate-C60Qrilh.d.ts} +0 -0
- /package/dist/{chromium-camera-access-workaround-BHR4clTE.d.ts → chromium-camera-access-workaround-BRs6gkI4.d.ts} +0 -0
- /package/dist/{concurrency-DW0jQo3G.d.ts → concurrency-CMhbkxBk.d.ts} +0 -0
- /package/dist/{css3d-renderer-manager-CJjHtCMT.d.ts → css3d-renderer-manager-CE6xN7XM.d.ts} +0 -0
- /package/dist/{device-orientation-cache-j-kLP8he.js → device-orientation-cache-BjEaEAmh.js} +0 -0
- /package/dist/{failure-tracker-B7fzPfZo.d.ts → failure-tracker-BdaFulDA.d.ts} +0 -0
- /package/dist/{file-system-utils-BHI0rXr9.d.ts → file-system-utils-DE3BViuq.d.ts} +0 -0
- /package/dist/{format-file-size-Da8rZgfs.d.ts → format-file-size-B2CJdRqD.d.ts} +0 -0
- /package/dist/{frame-loop-7GBMBu68.d.ts → frame-loop-CUEIdlhS.d.ts} +0 -0
- /package/dist/{frustum-visibility-COPfzruF.d.ts → frustum-visibility-BTx1Sqfo.d.ts} +0 -0
- /package/dist/{geo-types-BfDK8kJu.d.ts → geo-types-DGe5UKgx.d.ts} +0 -0
- /package/dist/{gps-CnhYrJCa.d.ts → gps-CC0l9lCp.d.ts} +0 -0
- /package/dist/{gps-compass-cubes-CT3TWctJ.d.ts → gps-compass-cubes-QQJgpRXR.d.ts} +0 -0
- /package/dist/{gps-error-handler-DjVuLXve.d.ts → gps-error-handler-da4DCcs5.d.ts} +0 -0
- /package/dist/{h3-proximity-BaAulrnM.d.ts → h3-proximity-CAEk3osq.d.ts} +0 -0
- /package/dist/{hit-test-reticle-DhnCMOsc.d.ts → hit-test-reticle-DcOyqo3B.d.ts} +0 -0
- /package/dist/{image-capture-C4DwxC10.js → image-capture-Dn8uLIP2.js} +0 -0
- /package/dist/{image-quality--HLuyJ2r.d.ts → image-quality-G7_qT4aj.d.ts} +0 -0
- /package/dist/{index-CjxbDm8W.d.ts → index-T8Fxvzka.d.ts} +0 -0
- /package/dist/{index-Nr3qg4IH.d.ts → index-Y_RNWbQX.d.ts} +0 -0
- /package/dist/{leaflet-map-overlay-CTcTVmL1.js → leaflet-map-overlay-DjWfRT8a.js} +0 -0
- /package/dist/{lerp-utils-bwMwVCpU.d.ts → lerp-utils-B-ecvLq2.d.ts} +0 -0
- /package/dist/{list-formatter-1ttG18MQ.d.ts → list-formatter-C45u3Vgv.d.ts} +0 -0
- /package/dist/{logger-B4StJUDb.d.ts → logger-BQGNXi9W.d.ts} +0 -0
- /package/dist/{map-data-DkhsYDGM.js → map-data-vL8jb5Z9.js} +0 -0
- /package/dist/{opfs-storage-S_odkCk1.d.ts → opfs-storage-CrhVauNL.d.ts} +0 -0
- /package/dist/{permission-checker-B-VtjnBA.d.ts → permission-checker-BDrCOwQF.d.ts} +0 -0
- /package/dist/{qr-pose-21fRDcFO.d.ts → qr-pose-_dInl6di.d.ts} +0 -0
- /package/dist/{scene-node-names-DWzhE7Ro.d.ts → scene-node-names-Ct6ATrfM.d.ts} +0 -0
- /package/dist/{session-disposers-B7hUN90w.js → session-disposers-BJM5U62W.js} +0 -0
- /package/dist/{three-dispose-C3mnKQR5.d.ts → three-dispose-B5-XiECx.d.ts} +0 -0
- /package/dist/{vis-colors-CoSpbZEO.d.ts → vis-colors-16Pht13u.d.ts} +0 -0
- /package/dist/{webxr-nue-basis-C3vTqkE2.d.ts → webxr-nue-basis-B8ZEetVb.d.ts} +0 -0
- /package/dist/{xr-camera-texture-q0eIDSRg.d.ts → xr-camera-texture-BA4xiaQ4.d.ts} +0 -0
- /package/dist/{xr-error-handler-D9y5r8ok.d.ts → xr-error-handler-BeyxXCE_.d.ts} +0 -0
- /package/dist/{xr-frame-loop-BiZvgqP7.d.ts → xr-frame-loop-A1mGDFHI.d.ts} +0 -0
- /package/dist/{zip-coverage-embed-CyoX7XL7.d.ts → zip-coverage-embed-BC3O2-8b.d.ts} +0 -0
- /package/dist/{zip-export-CuBimX1J.d.ts → zip-export-Cfl_kNQ7.d.ts} +0 -0
- /package/dist/{zip-reader-BHc_b_iA.d.ts → zip-reader-DwkuvlGo.d.ts} +0 -0
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
import { n as CELL_KEY_STRIDE_X, r as CELL_KEY_STRIDE_Y, s as packCellKey } from "./cell-key-D1b_Ix-p.js";
|
|
2
|
+
//#region ../src/ar/packed-key-hash.ts
|
|
3
|
+
/**
|
|
4
|
+
* Open-addressed hash map for packed cell keys — the mesher hot-loop
|
|
5
|
+
* replacement for `Map<number, number>` / `Set<number>`.
|
|
6
|
+
*
|
|
7
|
+
* WHY (2026-07-17 perf loop, iteration 2): profiling the production-default
|
|
8
|
+
* 'smooth' surface-nets mesher showed ~65% of its time inside Map/Set
|
|
9
|
+
* operations keyed by packed cell keys (occupied-membership tests, dual-vertex
|
|
10
|
+
* welding, centroid memo). This flat typed-array table answers the same
|
|
11
|
+
* lookups without per-entry heap boxes or hash-object overhead — together
|
|
12
|
+
* with the smooth builder's ordinal/incremental-key rewrite it took the
|
|
13
|
+
* production re-mesh (`runMeshRequest`, 100k-cell corpus regime) from 331 to
|
|
14
|
+
* 101 ms, −69% (byte-identical output, see occupancy-mesher.ts.md).
|
|
15
|
+
*
|
|
16
|
+
* CONTRACT (deliberately narrow — this is a fast path, not a general map):
|
|
17
|
+
* - Keys: non-negative finite numbers ≤ 2^53 (packed cell keys are ≤ ~2^51).
|
|
18
|
+
* `-1` is the internal empty sentinel, so negative keys are unsupported.
|
|
19
|
+
* - Values: integers ≥ 0 (Int32). `get` returns `-1` for absent keys — the
|
|
20
|
+
* caller-friendly miss signal that avoids an `undefined` check in hot loops.
|
|
21
|
+
* Storing a negative value throws (it would read back as a miss).
|
|
22
|
+
* - No delete (the mesher builds tables once per mesh and drops them).
|
|
23
|
+
*
|
|
24
|
+
* Implementation: linear probing over power-of-two capacity, grown at ~2/3
|
|
25
|
+
* load by rehashing (growth is CORRECTNESS, not tuning — a full table would
|
|
26
|
+
* probe forever). The 51-bit key is mixed to 32 bits via lo³²⊕hi²¹ and a
|
|
27
|
+
* golden-ratio `Math.imul` spread; packed keys are highly structured
|
|
28
|
+
* (base-2^17 positional), and the multiplicative mix breaks that structure —
|
|
29
|
+
* verified by the property test driving adjacent-coordinate workloads.
|
|
30
|
+
*
|
|
31
|
+
* @see packed-key-hash.ts.md for detailed documentation
|
|
32
|
+
*/
|
|
33
|
+
const EMPTY = -1;
|
|
34
|
+
const TWO_32 = 4294967296;
|
|
35
|
+
var PackedKeyHash = class {
|
|
36
|
+
keys;
|
|
37
|
+
vals;
|
|
38
|
+
mask;
|
|
39
|
+
used = 0;
|
|
40
|
+
/** Grow when `used` exceeds this (2/3 of capacity). */
|
|
41
|
+
growAt;
|
|
42
|
+
/**
|
|
43
|
+
* @param expectedEntries sizing hint — the table starts at the next power
|
|
44
|
+
* of two ≥ 2× the hint (≥ 16) and grows transparently beyond it.
|
|
45
|
+
*/
|
|
46
|
+
constructor(expectedEntries) {
|
|
47
|
+
let cap = 16;
|
|
48
|
+
const target = Math.max(0, expectedEntries) * 2;
|
|
49
|
+
while (cap < target) cap *= 2;
|
|
50
|
+
this.keys = new Float64Array(cap).fill(EMPTY);
|
|
51
|
+
this.vals = new Int32Array(cap);
|
|
52
|
+
this.mask = cap - 1;
|
|
53
|
+
this.growAt = cap * 2 / 3;
|
|
54
|
+
}
|
|
55
|
+
/** Number of distinct keys stored. */
|
|
56
|
+
get size() {
|
|
57
|
+
return this.used;
|
|
58
|
+
}
|
|
59
|
+
/** The slot holding `key`, or the empty slot where it would be inserted. */
|
|
60
|
+
slot(key) {
|
|
61
|
+
const hi = Math.floor(key / TWO_32);
|
|
62
|
+
let h = key - hi * TWO_32 ^ Math.imul(hi, 2654435761);
|
|
63
|
+
h ^= h >>> 16;
|
|
64
|
+
h = Math.imul(h, 2246822507);
|
|
65
|
+
h ^= h >>> 13;
|
|
66
|
+
h = Math.imul(h, 3266489909);
|
|
67
|
+
h ^= h >>> 16;
|
|
68
|
+
let i = h >>> 0 & this.mask;
|
|
69
|
+
const keys = this.keys;
|
|
70
|
+
while (keys[i] !== EMPTY && keys[i] !== key) i = i + 1 & this.mask;
|
|
71
|
+
return i;
|
|
72
|
+
}
|
|
73
|
+
/** Value stored for `key`, or -1 when absent. */
|
|
74
|
+
get(key) {
|
|
75
|
+
const i = this.slot(key);
|
|
76
|
+
return this.keys[i] === key ? this.vals[i] : -1;
|
|
77
|
+
}
|
|
78
|
+
/** True iff `key` is stored. */
|
|
79
|
+
has(key) {
|
|
80
|
+
return this.keys[this.slot(key)] === key;
|
|
81
|
+
}
|
|
82
|
+
/** Insert or overwrite. `value` must be an integer ≥ 0 (see contract). */
|
|
83
|
+
set(key, value) {
|
|
84
|
+
if (value < 0) throw new RangeError(`PackedKeyHash values must be ≥ 0 (got ${value}); -1 is the miss sentinel`);
|
|
85
|
+
const i = this.slot(key);
|
|
86
|
+
if (this.keys[i] === EMPTY) {
|
|
87
|
+
if (this.used >= this.growAt) {
|
|
88
|
+
this.grow();
|
|
89
|
+
this.setPresized(key, value);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.used++;
|
|
93
|
+
}
|
|
94
|
+
this.keys[i] = key;
|
|
95
|
+
this.vals[i] = value;
|
|
96
|
+
}
|
|
97
|
+
/** `set` after `grow()` — capacity is guaranteed, no growth check needed. */
|
|
98
|
+
setPresized(key, value) {
|
|
99
|
+
const i = this.slot(key);
|
|
100
|
+
if (this.keys[i] === EMPTY) this.used++;
|
|
101
|
+
this.keys[i] = key;
|
|
102
|
+
this.vals[i] = value;
|
|
103
|
+
}
|
|
104
|
+
grow() {
|
|
105
|
+
const oldKeys = this.keys;
|
|
106
|
+
const oldVals = this.vals;
|
|
107
|
+
const cap = (this.mask + 1) * 2;
|
|
108
|
+
this.keys = new Float64Array(cap).fill(EMPTY);
|
|
109
|
+
this.vals = new Int32Array(cap);
|
|
110
|
+
this.mask = cap - 1;
|
|
111
|
+
this.growAt = cap * 2 / 3;
|
|
112
|
+
this.used = 0;
|
|
113
|
+
for (let i = 0; i < oldKeys.length; i++) {
|
|
114
|
+
const k = oldKeys[i];
|
|
115
|
+
if (k !== EMPTY) this.setPresized(k, oldVals[i]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region ../src/ar/occupancy-mesher.ts
|
|
121
|
+
/** Resolve the effective mesher mode from the options. */
|
|
122
|
+
function resolveMode(options) {
|
|
123
|
+
return options?.mode ?? "per-face";
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Right-handed cyclic axis assignment per face-normal axis `d`: `(d, u, v)`
|
|
127
|
+
* with `eu × ev = ed`, so a `(uMin,vMin)→(uMax,vMin)→(uMax,vMax)→(uMin,vMax)`
|
|
128
|
+
* quad has the `+d` outward normal (and the reverse order has `−d`). Used by the
|
|
129
|
+
* greedy mesher to keep merged-quad winding consistent with the per-face path.
|
|
130
|
+
*/
|
|
131
|
+
const GREEDY_DIRS = [
|
|
132
|
+
{
|
|
133
|
+
d: 0,
|
|
134
|
+
u: 1,
|
|
135
|
+
v: 2
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
d: 1,
|
|
139
|
+
u: 2,
|
|
140
|
+
v: 0
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
d: 2,
|
|
144
|
+
u: 0,
|
|
145
|
+
v: 1
|
|
146
|
+
}
|
|
147
|
+
];
|
|
148
|
+
/**
|
|
149
|
+
* The six cube faces with outward (CCW-from-outside) winding. Corner signs are
|
|
150
|
+
* ±1 multipliers of the half-cell extent. Triangulated as (0,1,2)+(0,2,3).
|
|
151
|
+
*/
|
|
152
|
+
const FACES = [
|
|
153
|
+
{
|
|
154
|
+
neighbour: [
|
|
155
|
+
1,
|
|
156
|
+
0,
|
|
157
|
+
0
|
|
158
|
+
],
|
|
159
|
+
corners: [
|
|
160
|
+
[
|
|
161
|
+
1,
|
|
162
|
+
-1,
|
|
163
|
+
-1
|
|
164
|
+
],
|
|
165
|
+
[
|
|
166
|
+
1,
|
|
167
|
+
1,
|
|
168
|
+
-1
|
|
169
|
+
],
|
|
170
|
+
[
|
|
171
|
+
1,
|
|
172
|
+
1,
|
|
173
|
+
1
|
|
174
|
+
],
|
|
175
|
+
[
|
|
176
|
+
1,
|
|
177
|
+
-1,
|
|
178
|
+
1
|
|
179
|
+
]
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
neighbour: [
|
|
184
|
+
-1,
|
|
185
|
+
0,
|
|
186
|
+
0
|
|
187
|
+
],
|
|
188
|
+
corners: [
|
|
189
|
+
[
|
|
190
|
+
-1,
|
|
191
|
+
-1,
|
|
192
|
+
-1
|
|
193
|
+
],
|
|
194
|
+
[
|
|
195
|
+
-1,
|
|
196
|
+
-1,
|
|
197
|
+
1
|
|
198
|
+
],
|
|
199
|
+
[
|
|
200
|
+
-1,
|
|
201
|
+
1,
|
|
202
|
+
1
|
|
203
|
+
],
|
|
204
|
+
[
|
|
205
|
+
-1,
|
|
206
|
+
1,
|
|
207
|
+
-1
|
|
208
|
+
]
|
|
209
|
+
]
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
neighbour: [
|
|
213
|
+
0,
|
|
214
|
+
1,
|
|
215
|
+
0
|
|
216
|
+
],
|
|
217
|
+
corners: [
|
|
218
|
+
[
|
|
219
|
+
-1,
|
|
220
|
+
1,
|
|
221
|
+
-1
|
|
222
|
+
],
|
|
223
|
+
[
|
|
224
|
+
-1,
|
|
225
|
+
1,
|
|
226
|
+
1
|
|
227
|
+
],
|
|
228
|
+
[
|
|
229
|
+
1,
|
|
230
|
+
1,
|
|
231
|
+
1
|
|
232
|
+
],
|
|
233
|
+
[
|
|
234
|
+
1,
|
|
235
|
+
1,
|
|
236
|
+
-1
|
|
237
|
+
]
|
|
238
|
+
]
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
neighbour: [
|
|
242
|
+
0,
|
|
243
|
+
-1,
|
|
244
|
+
0
|
|
245
|
+
],
|
|
246
|
+
corners: [
|
|
247
|
+
[
|
|
248
|
+
-1,
|
|
249
|
+
-1,
|
|
250
|
+
-1
|
|
251
|
+
],
|
|
252
|
+
[
|
|
253
|
+
1,
|
|
254
|
+
-1,
|
|
255
|
+
-1
|
|
256
|
+
],
|
|
257
|
+
[
|
|
258
|
+
1,
|
|
259
|
+
-1,
|
|
260
|
+
1
|
|
261
|
+
],
|
|
262
|
+
[
|
|
263
|
+
-1,
|
|
264
|
+
-1,
|
|
265
|
+
1
|
|
266
|
+
]
|
|
267
|
+
]
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
neighbour: [
|
|
271
|
+
0,
|
|
272
|
+
0,
|
|
273
|
+
1
|
|
274
|
+
],
|
|
275
|
+
corners: [
|
|
276
|
+
[
|
|
277
|
+
-1,
|
|
278
|
+
-1,
|
|
279
|
+
1
|
|
280
|
+
],
|
|
281
|
+
[
|
|
282
|
+
1,
|
|
283
|
+
-1,
|
|
284
|
+
1
|
|
285
|
+
],
|
|
286
|
+
[
|
|
287
|
+
1,
|
|
288
|
+
1,
|
|
289
|
+
1
|
|
290
|
+
],
|
|
291
|
+
[
|
|
292
|
+
-1,
|
|
293
|
+
1,
|
|
294
|
+
1
|
|
295
|
+
]
|
|
296
|
+
]
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
neighbour: [
|
|
300
|
+
0,
|
|
301
|
+
0,
|
|
302
|
+
-1
|
|
303
|
+
],
|
|
304
|
+
corners: [
|
|
305
|
+
[
|
|
306
|
+
-1,
|
|
307
|
+
-1,
|
|
308
|
+
-1
|
|
309
|
+
],
|
|
310
|
+
[
|
|
311
|
+
-1,
|
|
312
|
+
1,
|
|
313
|
+
-1
|
|
314
|
+
],
|
|
315
|
+
[
|
|
316
|
+
1,
|
|
317
|
+
1,
|
|
318
|
+
-1
|
|
319
|
+
],
|
|
320
|
+
[
|
|
321
|
+
1,
|
|
322
|
+
-1,
|
|
323
|
+
-1
|
|
324
|
+
]
|
|
325
|
+
]
|
|
326
|
+
}
|
|
327
|
+
];
|
|
328
|
+
/** Finite, integer, and within the packable key range on every axis.
|
|
329
|
+
* `Number.isInteger` subsumes the finiteness check (NaN/±Infinity are not
|
|
330
|
+
* integers) and rejects fractional coordinates, which the packed-key algebra
|
|
331
|
+
* cannot key safely (neighbour ±1 and half-lattice `2·coord ± 1` keys only
|
|
332
|
+
* coincide for integer cells). */
|
|
333
|
+
function isPackableCell(cell) {
|
|
334
|
+
for (let i = 0; i < 3; i++) {
|
|
335
|
+
const c = cell[i];
|
|
336
|
+
if (!Number.isInteger(c) || Math.abs(c) > 32767) return false;
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
/** True iff all three components are finite. A non-finite measured centroid
|
|
341
|
+
* from `getCellPoint` (an upstream tracking/accumulation glitch) must degrade
|
|
342
|
+
* to the geometric fallback exactly like a `null` one — otherwise it poisons
|
|
343
|
+
* every welded vertex / shared corner that averages it, and it also breaks
|
|
344
|
+
* `runMeshRequest`'s byte-identical parity with a direct mesh (the worker wire
|
|
345
|
+
* protocol packs "no centroid" as NaN, so NaN already falls back off-thread). */
|
|
346
|
+
function isFiniteVector3(v) {
|
|
347
|
+
return Number.isFinite(v[0]) && Number.isFinite(v[1]) && Number.isFinite(v[2]);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Mesh a snapshot of occupied cells into a face-culled surface + AABB list.
|
|
351
|
+
*
|
|
352
|
+
* Only faces whose neighbour cell is **not** in the occupied set are emitted
|
|
353
|
+
* (interior faces are dropped), so the triangle count scales with the surface
|
|
354
|
+
* area of the occupied set. Duplicate cells in `cells` are de-duplicated;
|
|
355
|
+
* cells with a non-finite or non-integer coordinate are skipped defensively (a
|
|
356
|
+
* tracking glitch upstream must not poison the mesh, and the packed cell keys
|
|
357
|
+
* are only collision-safe for integer coordinates).
|
|
358
|
+
*
|
|
359
|
+
* @param cells occupied cells (e.g. `grid.getOccupiedCells(minConfidence)`).
|
|
360
|
+
* @param cellSizeM cube edge length in metres (must be a positive finite number).
|
|
361
|
+
* @returns positions/indices (raw-WebXR metres) + one AABB per unique cell.
|
|
362
|
+
*/
|
|
363
|
+
function meshOccupiedCells(cells, cellSizeM, options) {
|
|
364
|
+
if (!Number.isFinite(cellSizeM) || cellSizeM <= 0) throw new RangeError(`cellSizeM must be a positive number, got ${cellSizeM}`);
|
|
365
|
+
const half = cellSizeM / 2;
|
|
366
|
+
const mode = resolveMode(options);
|
|
367
|
+
const centroids = mode === "smooth" ? options?.centroids ?? null : null;
|
|
368
|
+
const occupied = new PackedKeyHash(Array.isArray(cells) ? cells.length : 64);
|
|
369
|
+
const uniqueCells = [];
|
|
370
|
+
const inputIndices = centroids ? [] : null;
|
|
371
|
+
let inputIndex = -1;
|
|
372
|
+
for (const cell of cells) {
|
|
373
|
+
inputIndex++;
|
|
374
|
+
if (!isPackableCell(cell)) continue;
|
|
375
|
+
const key = packCellKey(cell[0], cell[1], cell[2]);
|
|
376
|
+
if (occupied.has(key)) continue;
|
|
377
|
+
occupied.set(key, uniqueCells.length);
|
|
378
|
+
uniqueCells.push(cell);
|
|
379
|
+
inputIndices?.push(inputIndex);
|
|
380
|
+
}
|
|
381
|
+
const sharedHalfExtents = Object.freeze([
|
|
382
|
+
half,
|
|
383
|
+
half,
|
|
384
|
+
half
|
|
385
|
+
]);
|
|
386
|
+
const aabbs = options?.includeAabbs === false ? [] : uniqueCells.map(([x, y, z]) => ({
|
|
387
|
+
center: [
|
|
388
|
+
x * cellSizeM,
|
|
389
|
+
y * cellSizeM,
|
|
390
|
+
z * cellSizeM
|
|
391
|
+
],
|
|
392
|
+
halfExtents: sharedHalfExtents
|
|
393
|
+
}));
|
|
394
|
+
if (mode === "smooth") {
|
|
395
|
+
const geometry = buildSmooth(occupied, uniqueCells, cellSizeM, centroids ? resolveCellPointsFromCentroids(uniqueCells, inputIndices, centroids, cellSizeM) : resolveCellPoints(uniqueCells, cellSizeM, options?.getCellPoint));
|
|
396
|
+
return {
|
|
397
|
+
positions: geometry.positions,
|
|
398
|
+
indices: geometry.indices,
|
|
399
|
+
aabbs
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
const positions = [];
|
|
403
|
+
const indices = [];
|
|
404
|
+
if (mode === "greedy") buildGreedy(occupied, uniqueCells, cellSizeM, positions, indices);
|
|
405
|
+
else if (mode === "corner-fit") buildCornerFit(occupied, uniqueCells, cellSizeM, options?.getCellPoint, positions, indices);
|
|
406
|
+
else buildCulled(occupied, uniqueCells, cellSizeM, positions, indices);
|
|
407
|
+
return {
|
|
408
|
+
positions: new Float32Array(positions),
|
|
409
|
+
indices: new Uint32Array(indices),
|
|
410
|
+
aabbs
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/** Push a quad (4 corners, already ordered) as two triangles. */
|
|
414
|
+
/** Per-face culling: emit each exposed unit face as its own quad. */
|
|
415
|
+
function buildCulled(occupied, uniqueCells, cellSizeM, positions, indices) {
|
|
416
|
+
const half = cellSizeM / 2;
|
|
417
|
+
for (const [x, y, z] of uniqueCells) {
|
|
418
|
+
const cx = x * cellSizeM;
|
|
419
|
+
const cy = y * cellSizeM;
|
|
420
|
+
const cz = z * cellSizeM;
|
|
421
|
+
for (const face of FACES) {
|
|
422
|
+
const nx = x + face.neighbour[0];
|
|
423
|
+
const ny = y + face.neighbour[1];
|
|
424
|
+
const nz = z + face.neighbour[2];
|
|
425
|
+
if (occupied.has(packCellKey(nx, ny, nz))) continue;
|
|
426
|
+
const base = positions.length / 3;
|
|
427
|
+
const corners = face.corners;
|
|
428
|
+
for (let i = 0; i < 4; i++) {
|
|
429
|
+
const c = corners[i];
|
|
430
|
+
positions.push(cx + c[0] * half, cy + c[1] * half, cz + c[2] * half);
|
|
431
|
+
}
|
|
432
|
+
indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* 'smooth' mode — **standard Naive Surface Nets (dual contouring)** over the
|
|
438
|
+
* occupancy field, consuming the per-cell measured centroids the cube meshers
|
|
439
|
+
* discard.
|
|
440
|
+
*
|
|
441
|
+
* Treats occupancy as a binary field sampled at integer cell coordinates and
|
|
442
|
+
* contours the occupied/empty boundary:
|
|
443
|
+
* - **Vertices** — one welded vertex per "dual cell" (a unit cube whose 8
|
|
444
|
+
* corners are the cells `b … b+1`) that **straddles** the boundary (≥1
|
|
445
|
+
* occupied AND ≥1 empty corner), placed at the **mean of its occupied
|
|
446
|
+
* corners' `getCellPoint()`** (the measured surface points; the corners'
|
|
447
|
+
* geometric centres without a provider). Welding by dual-cell key makes the
|
|
448
|
+
* surface crack-free.
|
|
449
|
+
* - **Quads** — one per occupied↔empty **crossing**: for every occupied-cell
|
|
450
|
+
* face whose neighbour is empty (the SAME set the cube mesher emits), a quad
|
|
451
|
+
* joins the 4 dual cells sharing that edge, wound to face the empty side.
|
|
452
|
+
*
|
|
453
|
+
* Because there is one quad per crossing, **coverage matches the cubes** — unlike
|
|
454
|
+
* the previous 2×2-fully-occupied-patch heuristic, which only meshed flat solid
|
|
455
|
+
* blocks and so missed 80–90 % of a real, ragged depth surface (the reported
|
|
456
|
+
* "barely any surfaces" bug; 2026-06-30 rewrite). The result is smooth (welded
|
|
457
|
+
* vertices pulled onto the measured surface) and watertight for closed regions;
|
|
458
|
+
* over a thin feature (a one-cell floor) the top and bottom dual vertices average
|
|
459
|
+
* the same cells and coincide, so it reads as a single smooth sheet — the
|
|
460
|
+
* smoothest of the modes. Exception: a dual cell with exactly ONE occupied
|
|
461
|
+
* corner is nudged toward its dual-cell centre ({@link SINGLE_CORNER_NUDGE_K}),
|
|
462
|
+
* so features thin in ≥2 dimensions (isolated voxels, line/pillar ends) keep a
|
|
463
|
+
* non-zero area instead of collapsing onto a single point; on a thin floor this
|
|
464
|
+
* puffs only the perimeter-corner vertices by ±0.25·cell.
|
|
465
|
+
*/
|
|
466
|
+
/**
|
|
467
|
+
* 'smooth' single-occupied-corner fallback strength: a dual cell with exactly
|
|
468
|
+
* one occupied corner places its vertex ON that corner's cell point, so every
|
|
469
|
+
* dual cell around a feature thin in ≥2 dimensions (an isolated voxel, the end
|
|
470
|
+
* of a 1-cell line/pillar) coincided with its neighbours → all-degenerate
|
|
471
|
+
* (zero-area) triangles → thin features were invisible to the occluder despite
|
|
472
|
+
* a full per-face triangle count. Nudging the `n === 1` vertex toward the
|
|
473
|
+
* dual-cell centre by this fraction of the corner→centre distance (0.5 ⇒
|
|
474
|
+
* ±0.25·cell per axis) keeps it a pure function of the dual cell, so welding /
|
|
475
|
+
* watertightness and the measured-offset invariant are preserved. Trade-off
|
|
476
|
+
* (accepted 2026-07-02): the `n === 1` perimeter corners of a thin floor (and
|
|
477
|
+
* of a solid box) puff by ±0.25·cell — imperceptible for AR occlusion. Known
|
|
478
|
+
* residual: the `n === 2` shaft rings of a long 1×1×N feature still collapse
|
|
479
|
+
* (locally indistinguishable from a thin floor's intentionally-flat edges).
|
|
480
|
+
* 0.25 was rejected as too close to imperceptibly-non-zero; 1.0 discards the
|
|
481
|
+
* measured centroid exactly where data is sparsest. See
|
|
482
|
+
* 2026-07-01-1455-smooth-mesher-single-corner-degeneracy-followup.md.
|
|
483
|
+
*/
|
|
484
|
+
const SINGLE_CORNER_NUDGE_K = .5;
|
|
485
|
+
/**
|
|
486
|
+
* Resolve every unique cell's surface point (measured centroid via
|
|
487
|
+
* `getCellPoint`, or the geometric centre when the provider is absent /
|
|
488
|
+
* returns null / returns a non-finite triple) EAGERLY into ordinal-aligned
|
|
489
|
+
* typed arrays — `buildSmooth`'s dual-vertex loop then reads points by the
|
|
490
|
+
* ordinal stored in the occupied hash, with no per-key memo Map. The lazy
|
|
491
|
+
* memo this replaces resolved the same points for every surface cell anyway
|
|
492
|
+
* (interior cells of a solid are the only extra work, and reconstruction
|
|
493
|
+
* grids are shells).
|
|
494
|
+
*/
|
|
495
|
+
function resolveCellPoints(uniqueCells, cellSizeM, getCellPoint) {
|
|
496
|
+
const n = uniqueCells.length;
|
|
497
|
+
const px = new Float64Array(n);
|
|
498
|
+
const py = new Float64Array(n);
|
|
499
|
+
const pz = new Float64Array(n);
|
|
500
|
+
const scratch = [
|
|
501
|
+
0,
|
|
502
|
+
0,
|
|
503
|
+
0
|
|
504
|
+
];
|
|
505
|
+
for (let i = 0; i < n; i++) {
|
|
506
|
+
const cell = uniqueCells[i];
|
|
507
|
+
const x = cell[0];
|
|
508
|
+
const y = cell[1];
|
|
509
|
+
const z = cell[2];
|
|
510
|
+
let wx = x * cellSizeM;
|
|
511
|
+
let wy = y * cellSizeM;
|
|
512
|
+
let wz = z * cellSizeM;
|
|
513
|
+
if (getCellPoint) {
|
|
514
|
+
scratch[0] = x;
|
|
515
|
+
scratch[1] = y;
|
|
516
|
+
scratch[2] = z;
|
|
517
|
+
const cp = getCellPoint(scratch);
|
|
518
|
+
if (cp && isFiniteVector3(cp)) {
|
|
519
|
+
wx = cp[0];
|
|
520
|
+
wy = cp[1];
|
|
521
|
+
wz = cp[2];
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
px[i] = wx;
|
|
525
|
+
py[i] = wy;
|
|
526
|
+
pz[i] = wz;
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
px,
|
|
530
|
+
py,
|
|
531
|
+
pz
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* The `centroids`-array twin of {@link resolveCellPoints} (the mesh-worker
|
|
536
|
+
* fast path): centroids arrive input-order-aligned over the wire, so each
|
|
537
|
+
* unique cell's point is `centroids[inputIndices[i]·3 …]` when that triple is
|
|
538
|
+
* finite, else the geometric centre — the exact fallback rule of the callback
|
|
539
|
+
* path (NaN is the wire's "no centroid" sentinel; a non-finite value from a
|
|
540
|
+
* poisoned provider must also degrade, never propagate).
|
|
541
|
+
*/
|
|
542
|
+
function resolveCellPointsFromCentroids(uniqueCells, inputIndices, centroids, cellSizeM) {
|
|
543
|
+
const n = uniqueCells.length;
|
|
544
|
+
const px = new Float64Array(n);
|
|
545
|
+
const py = new Float64Array(n);
|
|
546
|
+
const pz = new Float64Array(n);
|
|
547
|
+
for (let i = 0; i < n; i++) {
|
|
548
|
+
const j = inputIndices[i] * 3;
|
|
549
|
+
const cx = centroids[j];
|
|
550
|
+
const cy = centroids[j + 1];
|
|
551
|
+
const cz = centroids[j + 2];
|
|
552
|
+
if (Number.isFinite(cx) && Number.isFinite(cy) && Number.isFinite(cz)) {
|
|
553
|
+
px[i] = cx;
|
|
554
|
+
py[i] = cy;
|
|
555
|
+
pz[i] = cz;
|
|
556
|
+
} else {
|
|
557
|
+
const cell = uniqueCells[i];
|
|
558
|
+
px[i] = cell[0] * cellSizeM;
|
|
559
|
+
py[i] = cell[1] * cellSizeM;
|
|
560
|
+
pz[i] = cell[2] * cellSizeM;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
px,
|
|
565
|
+
py,
|
|
566
|
+
pz
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
/** Growth step for the smooth builder's typed output buffers. */
|
|
570
|
+
function grownCopy(buffer, Ctor) {
|
|
571
|
+
const grown = new Ctor(buffer.length * 2);
|
|
572
|
+
grown.set(buffer);
|
|
573
|
+
return grown;
|
|
574
|
+
}
|
|
575
|
+
function buildSmooth(occupied, uniqueCells, cellSizeM, points) {
|
|
576
|
+
const n = uniqueCells.length;
|
|
577
|
+
const { px, py, pz } = points;
|
|
578
|
+
let positions = new Float32Array(4096 * 3);
|
|
579
|
+
let posLen = 0;
|
|
580
|
+
let indices = new Uint32Array(8192 * 3);
|
|
581
|
+
let idxLen = 0;
|
|
582
|
+
const vertexIndex = new PackedKeyHash(n * 2);
|
|
583
|
+
const nudge = cellSizeM * SINGLE_CORNER_NUDGE_K;
|
|
584
|
+
const dualVertex = (dkey) => {
|
|
585
|
+
const existing = vertexIndex.get(dkey);
|
|
586
|
+
if (existing !== -1) return existing;
|
|
587
|
+
let sx = 0;
|
|
588
|
+
let sy = 0;
|
|
589
|
+
let sz = 0;
|
|
590
|
+
let count = 0;
|
|
591
|
+
let odx = 0;
|
|
592
|
+
let ody = 0;
|
|
593
|
+
let odz = 0;
|
|
594
|
+
for (let dx = 0; dx <= 1; dx++) for (let dy = 0; dy <= 1; dy++) for (let dz = 0; dz <= 1; dz++) {
|
|
595
|
+
const ord = occupied.get(dkey + dx * CELL_KEY_STRIDE_X + dy * CELL_KEY_STRIDE_Y + dz);
|
|
596
|
+
if (ord === -1) continue;
|
|
597
|
+
sx += px[ord];
|
|
598
|
+
sy += py[ord];
|
|
599
|
+
sz += pz[ord];
|
|
600
|
+
count += 1;
|
|
601
|
+
odx = dx;
|
|
602
|
+
ody = dy;
|
|
603
|
+
odz = dz;
|
|
604
|
+
}
|
|
605
|
+
let vx = sx / count;
|
|
606
|
+
let vy = sy / count;
|
|
607
|
+
let vz = sz / count;
|
|
608
|
+
if (count === 1) {
|
|
609
|
+
vx += (.5 - odx) * nudge;
|
|
610
|
+
vy += (.5 - ody) * nudge;
|
|
611
|
+
vz += (.5 - odz) * nudge;
|
|
612
|
+
}
|
|
613
|
+
if (posLen + 3 > positions.length) positions = grownCopy(positions, Float32Array);
|
|
614
|
+
const idx = posLen / 3;
|
|
615
|
+
positions[posLen] = vx;
|
|
616
|
+
positions[posLen + 1] = vy;
|
|
617
|
+
positions[posLen + 2] = vz;
|
|
618
|
+
posLen += 3;
|
|
619
|
+
vertexIndex.set(dkey, idx);
|
|
620
|
+
return idx;
|
|
621
|
+
};
|
|
622
|
+
const STRIDES = [
|
|
623
|
+
CELL_KEY_STRIDE_X,
|
|
624
|
+
CELL_KEY_STRIDE_Y,
|
|
625
|
+
1
|
|
626
|
+
];
|
|
627
|
+
for (const cell of uniqueCells) {
|
|
628
|
+
const cKey = packCellKey(cell[0], cell[1], cell[2]);
|
|
629
|
+
for (const { d, u, v } of GREEDY_DIRS) {
|
|
630
|
+
const sd = STRIDES[d];
|
|
631
|
+
const su = STRIDES[u];
|
|
632
|
+
const sv = STRIDES[v];
|
|
633
|
+
for (let sgn = 1; sgn >= -1; sgn -= 2) {
|
|
634
|
+
if (occupied.has(cKey + sgn * sd)) continue;
|
|
635
|
+
const keyA = (sgn > 0 ? cKey : cKey - sd) - su - sv;
|
|
636
|
+
const iA = dualVertex(keyA);
|
|
637
|
+
const iB = dualVertex(keyA + su);
|
|
638
|
+
const iC = dualVertex(keyA + su + sv);
|
|
639
|
+
const iD = dualVertex(keyA + sv);
|
|
640
|
+
if (idxLen + 6 > indices.length) indices = grownCopy(indices, Uint32Array);
|
|
641
|
+
indices[idxLen] = iA;
|
|
642
|
+
indices[idxLen + 3] = iA;
|
|
643
|
+
indices[idxLen + 4] = iC;
|
|
644
|
+
if (sgn > 0) {
|
|
645
|
+
indices[idxLen + 1] = iB;
|
|
646
|
+
indices[idxLen + 2] = iC;
|
|
647
|
+
indices[idxLen + 5] = iD;
|
|
648
|
+
} else {
|
|
649
|
+
indices[idxLen + 1] = iD;
|
|
650
|
+
indices[idxLen + 2] = iC;
|
|
651
|
+
indices[idxLen + 5] = iB;
|
|
652
|
+
}
|
|
653
|
+
idxLen += 6;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
positions: positions.slice(0, posLen),
|
|
659
|
+
indices: indices.slice(0, idxLen)
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* 'corner-fit' mode — the per-face cube mesher with **displaced shared corners**.
|
|
664
|
+
*
|
|
665
|
+
* Keeps {@link buildCulled}'s exact face topology (same exposed faces), but each
|
|
666
|
+
* lattice corner — identified by its integer half-lattice key `(2x±1, 2y±1,
|
|
667
|
+
* 2z±1)` so every cell sharing it produces the SAME key — is **nudged by the mean
|
|
668
|
+
* sub-cell offset** (`getCellPoint() − cellCentre`) of the occupied cells
|
|
669
|
+
* touching it. Vertices are welded by corner key, so adjacent faces reference the
|
|
670
|
+
* identical displaced position: seams stay coincident ⇒ the surface deforms to
|
|
671
|
+
* hug the measured points yet stays **watertight** (the even-edge-cover invariant
|
|
672
|
+
* `'smooth'` gives up). Without a `getCellPoint` provider every corner falls back
|
|
673
|
+
* to the geometric corner `key · cellSize/2`, i.e. plain cubes.
|
|
674
|
+
*
|
|
675
|
+
* Why the **offset**, not the absolute centroid mean (2026-06-30 fix): moving a
|
|
676
|
+
* corner onto the absolute mean collapsed thin features — a one-cell-thick floor's
|
|
677
|
+
* top and bottom corners average the SAME cells, so they coincided into a flat
|
|
678
|
+
* sheet visually indistinguishable from `'smooth'`. Adding the offset to each
|
|
679
|
+
* corner's OWN geometric position keeps the cube's thickness, so `'corner-fit'`
|
|
680
|
+
* stays a distinct, cube-like, watertight option.
|
|
681
|
+
*
|
|
682
|
+
* Tradeoffs vs `'smooth'`: watertight and exact-cube topology, but corners are
|
|
683
|
+
* 8-way averages (so geometry only *approaches* the measured points, never lands
|
|
684
|
+
* on them) and the per-face O(surface-area) triangle cost is unchanged. Greedy
|
|
685
|
+
* merging does not apply (displaced corners are non-coplanar).
|
|
686
|
+
*/
|
|
687
|
+
function buildCornerFit(occupied, uniqueCells, cellSizeM, getCellPoint, positions, indices) {
|
|
688
|
+
const half = cellSizeM / 2;
|
|
689
|
+
const cornerSum = /* @__PURE__ */ new Map();
|
|
690
|
+
const addCornerOffset = (key, ox, oy, oz) => {
|
|
691
|
+
let acc = cornerSum.get(key);
|
|
692
|
+
if (!acc) {
|
|
693
|
+
acc = {
|
|
694
|
+
x: 0,
|
|
695
|
+
y: 0,
|
|
696
|
+
z: 0,
|
|
697
|
+
n: 0
|
|
698
|
+
};
|
|
699
|
+
cornerSum.set(key, acc);
|
|
700
|
+
}
|
|
701
|
+
acc.x += ox;
|
|
702
|
+
acc.y += oy;
|
|
703
|
+
acc.z += oz;
|
|
704
|
+
acc.n += 1;
|
|
705
|
+
};
|
|
706
|
+
for (const cell of uniqueCells) {
|
|
707
|
+
const cp = getCellPoint ? getCellPoint(cell) : null;
|
|
708
|
+
if (!cp || !isFiniteVector3(cp)) continue;
|
|
709
|
+
const ox = cp[0] - cell[0] * cellSizeM;
|
|
710
|
+
const oy = cp[1] - cell[1] * cellSizeM;
|
|
711
|
+
const oz = cp[2] - cell[2] * cellSizeM;
|
|
712
|
+
for (let sx = -1; sx <= 1; sx += 2) for (let sy = -1; sy <= 1; sy += 2) for (let sz = -1; sz <= 1; sz += 2) addCornerOffset(packCellKey(2 * cell[0] + sx, 2 * cell[1] + sy, 2 * cell[2] + sz), ox, oy, oz);
|
|
713
|
+
}
|
|
714
|
+
const vertexIndex = /* @__PURE__ */ new Map();
|
|
715
|
+
const cornerVertex = (kx, ky, kz) => {
|
|
716
|
+
const key = packCellKey(kx, ky, kz);
|
|
717
|
+
const existing = vertexIndex.get(key);
|
|
718
|
+
if (existing !== void 0) return existing;
|
|
719
|
+
const acc = cornerSum.get(key);
|
|
720
|
+
const px = kx * half + (acc ? acc.x / acc.n : 0);
|
|
721
|
+
const py = ky * half + (acc ? acc.y / acc.n : 0);
|
|
722
|
+
const pz = kz * half + (acc ? acc.z / acc.n : 0);
|
|
723
|
+
const idx = positions.length / 3;
|
|
724
|
+
positions.push(px, py, pz);
|
|
725
|
+
vertexIndex.set(key, idx);
|
|
726
|
+
return idx;
|
|
727
|
+
};
|
|
728
|
+
for (const [x, y, z] of uniqueCells) for (const face of FACES) {
|
|
729
|
+
const nx = x + face.neighbour[0];
|
|
730
|
+
const ny = y + face.neighbour[1];
|
|
731
|
+
const nz = z + face.neighbour[2];
|
|
732
|
+
if (occupied.has(packCellKey(nx, ny, nz))) continue;
|
|
733
|
+
const corners = face.corners;
|
|
734
|
+
const x2 = 2 * x;
|
|
735
|
+
const y2 = 2 * y;
|
|
736
|
+
const z2 = 2 * z;
|
|
737
|
+
const c0 = corners[0];
|
|
738
|
+
const c1 = corners[1];
|
|
739
|
+
const c2 = corners[2];
|
|
740
|
+
const c3 = corners[3];
|
|
741
|
+
const v0 = cornerVertex(x2 + c0[0], y2 + c0[1], z2 + c0[2]);
|
|
742
|
+
const v1 = cornerVertex(x2 + c1[0], y2 + c1[1], z2 + c1[2]);
|
|
743
|
+
const v2 = cornerVertex(x2 + c2[0], y2 + c2[1], z2 + c2[2]);
|
|
744
|
+
const v3 = cornerVertex(x2 + c3[0], y2 + c3[1], z2 + c3[2]);
|
|
745
|
+
indices.push(v0, v1, v2, v0, v2, v3);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Greedy meshing: for each face-normal axis and side, sweep slices and merge
|
|
750
|
+
* adjacent coplanar exposed faces into maximal rectangles, emitting one quad
|
|
751
|
+
* per rectangle. The covered unit faces are identical to {@link buildCulled};
|
|
752
|
+
* only the triangle count drops.
|
|
753
|
+
*/
|
|
754
|
+
function buildGreedy(occupied, uniqueCells, cellSizeM, positions, indices) {
|
|
755
|
+
const half = cellSizeM / 2;
|
|
756
|
+
const neighbour = [
|
|
757
|
+
0,
|
|
758
|
+
0,
|
|
759
|
+
0
|
|
760
|
+
];
|
|
761
|
+
for (const { d, u, v } of GREEDY_DIRS) for (let sign = 1; sign >= -1; sign -= 2) {
|
|
762
|
+
const slices = /* @__PURE__ */ new Map();
|
|
763
|
+
for (const cell of uniqueCells) {
|
|
764
|
+
neighbour[0] = cell[0];
|
|
765
|
+
neighbour[1] = cell[1];
|
|
766
|
+
neighbour[2] = cell[2];
|
|
767
|
+
neighbour[d] += sign;
|
|
768
|
+
if (occupied.has(packCellKey(neighbour[0], neighbour[1], neighbour[2]))) continue;
|
|
769
|
+
const k = cell[d];
|
|
770
|
+
const iu = cell[u];
|
|
771
|
+
const iv = cell[v];
|
|
772
|
+
let slice = slices.get(k);
|
|
773
|
+
if (!slice) {
|
|
774
|
+
slice = /* @__PURE__ */ new Map();
|
|
775
|
+
slices.set(k, slice);
|
|
776
|
+
}
|
|
777
|
+
slice.set(packCellKey(iu, iv, 0), [iu, iv]);
|
|
778
|
+
}
|
|
779
|
+
for (const [k, slice] of [...slices.entries()].sort((a, b) => a[0] - b[0])) greedyMergeSlice(slice, half, cellSizeM, d, u, v, k, sign, positions, indices);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
/** Greedy-merge one slice's exposed (iu,iv) mask into maximal rectangles. */
|
|
783
|
+
function greedyMergeSlice(slice, half, cellSizeM, d, u, v, k, sign, positions, indices) {
|
|
784
|
+
const has = (iu, iv) => slice.has(packCellKey(iu, iv, 0));
|
|
785
|
+
const used = /* @__PURE__ */ new Set();
|
|
786
|
+
const isUsed = (iu, iv) => used.has(packCellKey(iu, iv, 0));
|
|
787
|
+
const p = [
|
|
788
|
+
0,
|
|
789
|
+
0,
|
|
790
|
+
0
|
|
791
|
+
];
|
|
792
|
+
const cells = [...slice.values()].sort((a, b) => a[1] !== b[1] ? a[1] - b[1] : a[0] - b[0]);
|
|
793
|
+
for (const [iu, iv] of cells) {
|
|
794
|
+
if (isUsed(iu, iv)) continue;
|
|
795
|
+
let w = 1;
|
|
796
|
+
while (has(iu + w, iv) && !isUsed(iu + w, iv)) w++;
|
|
797
|
+
let h = 1;
|
|
798
|
+
let canGrow = true;
|
|
799
|
+
while (canGrow) {
|
|
800
|
+
for (let du = 0; du < w; du++) {
|
|
801
|
+
if (has(iu + du, iv + h) && !isUsed(iu + du, iv + h)) continue;
|
|
802
|
+
canGrow = false;
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
if (canGrow) h++;
|
|
806
|
+
}
|
|
807
|
+
for (let dv = 0; dv < h; dv++) for (let du = 0; du < w; du++) used.add(packCellKey(iu + du, iv + dv, 0));
|
|
808
|
+
const plane = k * cellSizeM + sign * half;
|
|
809
|
+
const uMin = iu * cellSizeM - half;
|
|
810
|
+
const uMax = (iu + w - 1) * cellSizeM + half;
|
|
811
|
+
const vMin = iv * cellSizeM - half;
|
|
812
|
+
const vMax = (iv + h - 1) * cellSizeM + half;
|
|
813
|
+
const base = positions.length / 3;
|
|
814
|
+
p[d] = plane;
|
|
815
|
+
p[u] = uMin;
|
|
816
|
+
p[v] = vMin;
|
|
817
|
+
positions.push(p[0], p[1], p[2]);
|
|
818
|
+
if (sign > 0) {
|
|
819
|
+
p[u] = uMax;
|
|
820
|
+
positions.push(p[0], p[1], p[2]);
|
|
821
|
+
p[v] = vMax;
|
|
822
|
+
positions.push(p[0], p[1], p[2]);
|
|
823
|
+
p[u] = uMin;
|
|
824
|
+
positions.push(p[0], p[1], p[2]);
|
|
825
|
+
} else {
|
|
826
|
+
p[v] = vMax;
|
|
827
|
+
positions.push(p[0], p[1], p[2]);
|
|
828
|
+
p[u] = uMax;
|
|
829
|
+
positions.push(p[0], p[1], p[2]);
|
|
830
|
+
p[v] = vMin;
|
|
831
|
+
positions.push(p[0], p[1], p[2]);
|
|
832
|
+
}
|
|
833
|
+
indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
//#endregion
|
|
837
|
+
export { meshOccupiedCells as t };
|