gps-plus-slam-app-framework 1.11.0 → 1.13.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.
Files changed (248) hide show
  1. package/README.md +13 -14
  2. package/dist/{app-selectors-DG0ZqaeI.d.ts → app-selectors-QM0ZpNA4.d.ts} +1 -1
  3. package/dist/ar/ar-crash-isolation.d.ts +1 -1
  4. package/dist/ar/bresenham3d.d.ts +1 -1
  5. package/dist/ar/camera-blit-capture.d.ts +1 -1
  6. package/dist/ar/capability-checker.d.ts +1 -1
  7. package/dist/ar/capture-failure-tracker.d.ts +1 -1
  8. package/dist/ar/capture-motion-gate.d.ts +1 -1
  9. package/dist/ar/chromium-camera-access-workaround.d.ts +1 -1
  10. package/dist/ar/depth-grid-lookup.d.ts +1 -1
  11. package/dist/ar/depth-occluder.d.ts +1 -1
  12. package/dist/ar/depth-sampler.d.ts +3 -3
  13. package/dist/ar/depth-sampler.js +28 -1
  14. package/dist/ar/depth-unprojection.d.ts +1 -1
  15. package/dist/ar/enable-gps-ar.d.ts +1 -1
  16. package/dist/ar/enable-gps-ar.js +20 -5
  17. package/dist/ar/frame-loop.d.ts +1 -1
  18. package/dist/ar/hit-test-reticle-driver.d.ts +2 -0
  19. package/dist/ar/hit-test-reticle-driver.js +122 -0
  20. package/dist/ar/image-capture.d.ts +1 -1
  21. package/dist/ar/image-capture.js +1 -1
  22. package/dist/ar/image-quality.d.ts +1 -1
  23. package/dist/ar/index.d.ts +35 -34
  24. package/dist/ar/index.js +5 -4
  25. package/dist/ar/occlusion-mesh-worker.d.ts +2 -2
  26. package/dist/ar/occlusion-mesh-worker.js +6 -4
  27. package/dist/ar/occupancy-grid.d.ts +2 -2
  28. package/dist/ar/occupancy-grid.js +191 -11
  29. package/dist/ar/occupancy-mesher.d.ts +1 -1
  30. package/dist/ar/occupancy-mesher.js +1 -636
  31. package/dist/ar/qr/index.d.ts +10 -10
  32. package/dist/ar/qr/planar-pnp.d.ts +1 -1
  33. package/dist/ar/qr/qr-debug-view.d.ts +1 -1
  34. package/dist/ar/qr/qr-derived-pose.d.ts +1 -1
  35. package/dist/ar/qr/qr-detection-controller.d.ts +1 -1
  36. package/dist/ar/qr/qr-frontend.d.ts +1 -1
  37. package/dist/ar/qr/qr-pose.d.ts +1 -1
  38. package/dist/ar/qr/qr-size-depth-context.d.ts +2 -2
  39. package/dist/ar/qr/qr-size-measurer.d.ts +1 -1
  40. package/dist/ar/replay-scene.d.ts +1 -1
  41. package/dist/ar/replay-scene.js +1 -1
  42. package/dist/ar/scene-node-names.d.ts +1 -1
  43. package/dist/ar/webxr-nue-basis.d.ts +1 -1
  44. package/dist/ar/webxr-session.d.ts +3 -3
  45. package/dist/ar/webxr-session.js +1 -1
  46. package/dist/ar/xr-camera-texture.d.ts +1 -1
  47. package/dist/ar/xr-error-handler.d.ts +1 -1
  48. package/dist/ar/xr-frame-loop.d.ts +1 -1
  49. package/dist/{ar-world-group-alignment-BdS_h8Nf.d.ts → ar-world-group-alignment-CvvfACfx.d.ts} +1 -1
  50. package/dist/{cell-key-Bj-eYTLA.js → cell-key-D1b_Ix-p.js} +12 -1
  51. package/dist/core/index.d.ts +1 -1
  52. package/dist/{create-slam-app-store-DFm5K9Ob.d.ts → create-slam-app-store-HNe1PBtx.d.ts} +26 -4
  53. package/dist/{create-slam-app-store-oK9bnqyI.js → create-slam-app-store-wYN7YkB5.js} +13 -4
  54. package/dist/{depth-grid-lookup-CCuzlqUo.d.ts → depth-grid-lookup-PcJTdRLL.d.ts} +1 -1
  55. package/dist/{depth-sampler-CS2jAFw4.d.ts → depth-sampler-BcvuxgCS.d.ts} +29 -2
  56. package/dist/{depth-unprojection-ET3Gwedf.d.ts → depth-unprojection-DbGipFkA.d.ts} +1 -1
  57. package/dist/{enable-gps-ar-D_xEHzh5.d.ts → enable-gps-ar-xjz3jNqm.d.ts} +4 -4
  58. package/dist/{frame-conversions-CH7E2Iv_.d.ts → frame-conversions-CzPQDYpM.d.ts} +1 -1
  59. package/dist/{fused-path-DOKzVMiO.d.ts → fused-path-CBAttcVm.d.ts} +1 -1
  60. package/dist/geo/h3-proximity.d.ts +1 -1
  61. package/dist/geo/index.d.ts +1 -1
  62. package/dist/{gps-anchor-BaqyRsPC.d.ts → gps-anchor-Gv4NDwH4.d.ts} +1 -1
  63. package/dist/{gps-ar-pose-sampler-BuuUuK-D.d.ts → gps-ar-pose-sampler-DBEYDFY5.d.ts} +1 -1
  64. package/dist/{gps-event-coordinator-Cruoe5I-.d.ts → gps-event-coordinator-BKwB4RyQ.d.ts} +4 -4
  65. package/dist/{gps-event-markers-y2b2aVl6.d.ts → gps-event-markers-212h7j3D.d.ts} +4 -4
  66. package/dist/hit-test-reticle-driver-CfKiDgsa.d.ts +40 -0
  67. package/dist/{image-capture-BevgKpoO.d.ts → image-capture-BXmx1YvD.d.ts} +3 -3
  68. package/dist/{index-Dxb6ATNK.d.ts → index-DVmgFIgm.d.ts} +1 -1
  69. package/dist/{index-BWVwd9Zw.d.ts → index-DX9VC257.d.ts} +3 -3
  70. package/dist/{index-ClmnNJ6-.d.ts → index-qtZ2muFd.d.ts} +1 -1
  71. package/dist/index.d.ts +89 -82
  72. package/dist/index.js +17 -10
  73. package/dist/{leaflet-map-overlay-CksetpTt.d.ts → leaflet-map-overlay-BkHmzvpD.d.ts} +1 -1
  74. package/dist/{map-data-D8cCqpOM.d.ts → map-data-BQXPtRz7.d.ts} +1 -1
  75. package/dist/{map-overlay-draw-Cy-klqoA.d.ts → map-overlay-draw-sTM3wug_.d.ts} +1 -1
  76. package/dist/{null-storage-backend-DXXuwG8T.d.ts → null-storage-backend-Dtn3TBfT.d.ts} +2 -2
  77. package/dist/{occlusion-mesh-BWe2PrVi.d.ts → occlusion-mesh-BY2aMej3.d.ts} +22 -4
  78. package/dist/occupancy-cubes-visualizer-soN0Sizd.d.ts +146 -0
  79. package/dist/{occupancy-grid-CYBdmMOo.d.ts → occupancy-grid-DKzIdCSZ.d.ts} +113 -3
  80. package/dist/{occupancy-mesher-B4Bv4BTk.d.ts → occupancy-mesher-Co80tOpl.d.ts} +22 -1
  81. package/dist/occupancy-mesher-DzNeJdQf.js +837 -0
  82. package/dist/{opfs-storage-backend-D1ZpeNEL.d.ts → opfs-storage-backend-CZ-ECudx.d.ts} +2 -2
  83. package/dist/perf-stats-overlay-BMEQrzx6.d.ts +68 -0
  84. package/dist/{persistence-middleware-Cun4aF5M.d.ts → persistence-middleware-Byo_Fs4n.d.ts} +1 -1
  85. package/dist/{planar-pnp-ByxwDEyM.d.ts → planar-pnp-CkcMN3o3.d.ts} +1 -1
  86. package/dist/pointer-picking-BATHHTlT.d.ts +41 -0
  87. package/dist/{qr-debug-view-CGlITU09.d.ts → qr-debug-view-BMjNVhV2.d.ts} +1 -1
  88. package/dist/{qr-derived-pose-DZPxZd9C.d.ts → qr-derived-pose-Dq7mPK2s.d.ts} +2 -2
  89. package/dist/{qr-detected-slice-mfyxD2-M.d.ts → qr-detected-slice-8t_laDR9.d.ts} +4 -4
  90. package/dist/{qr-detection-controller-C5NII-vs.d.ts → qr-detection-controller-Dh4Paymb.d.ts} +3 -3
  91. package/dist/{qr-frontend-QzKZSqB-.d.ts → qr-frontend-BnLlVjKu.d.ts} +1 -1
  92. package/dist/{qr-pose-aggregation-mABSN0AW.d.ts → qr-pose-aggregation-Bpv4uARJ.d.ts} +1 -1
  93. package/dist/{qr-size-depth-context-CMDv6T29.d.ts → qr-size-depth-context-Bzbx98nS.d.ts} +2 -2
  94. package/dist/{qr-size-measurer-rVFf4AdM.d.ts → qr-size-measurer-18_W3INY.d.ts} +3 -3
  95. package/dist/{recording-replayer-qT2EM1Qs.d.ts → recording-replayer-BKmVuchg.d.ts} +2 -2
  96. package/dist/{recording-slice-DXw5-UvO.d.ts → recording-slice-zMedT0ot.d.ts} +1 -1
  97. package/dist/{replay-engine-B1H_kLhK.d.ts → replay-engine-D-cbT0Fw.d.ts} +1 -1
  98. package/dist/{replay-scene-BJeO36YT.d.ts → replay-scene-D5qzY0_x.d.ts} +3 -3
  99. package/dist/sensors/absolute-orientation.d.ts +1 -1
  100. package/dist/sensors/gps-error-handler.d.ts +1 -1
  101. package/dist/sensors/gps.d.ts +1 -1
  102. package/dist/sensors/index.d.ts +4 -4
  103. package/dist/sensors/permission-checker.d.ts +1 -1
  104. package/dist/state/app-selectors.d.ts +1 -1
  105. package/dist/state/combined-root-state.d.ts +1 -1
  106. package/dist/state/create-slam-app-store.d.ts +1 -1
  107. package/dist/state/create-slam-app-store.js +1 -1
  108. package/dist/state/gps-ar-pose-sampler.d.ts +2 -2
  109. package/dist/state/gps-event-coordinator.d.ts +2 -2
  110. package/dist/state/gps-event-coordinator.js +1 -1
  111. package/dist/state/index.d.ts +18 -18
  112. package/dist/state/index.js +3 -3
  113. package/dist/state/persistence-middleware.d.ts +1 -1
  114. package/dist/state/qr-detected-slice.d.ts +3 -3
  115. package/dist/state/recording-replayer.d.ts +2 -2
  116. package/dist/state/recording-replayer.js +1 -1
  117. package/dist/state/recording-slice.d.ts +1 -1
  118. package/dist/state/replay-engine.d.ts +1 -1
  119. package/dist/state/replay-occupancy-subscriber.d.ts +51 -0
  120. package/dist/state/replay-occupancy-subscriber.js +85 -0
  121. package/dist/state/replay-session.d.ts +88 -0
  122. package/dist/state/replay-session.js +165 -0
  123. package/dist/state/store-subscribers.d.ts +2 -2
  124. package/dist/state/store-subscribers.js +1 -1
  125. package/dist/state/subscribe-to-selector.d.ts +1 -1
  126. package/dist/state/tracking-quality.d.ts +1 -1
  127. package/dist/state/tracking-slice.d.ts +1 -1
  128. package/dist/{state-CDF5LvsO.js → state-DxL3T1Id.js} +1 -1
  129. package/dist/storage/file-system-utils.d.ts +1 -1
  130. package/dist/storage/index.d.ts +8 -8
  131. package/dist/storage/null-storage-backend.d.ts +1 -1
  132. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  133. package/dist/storage/opfs-storage.d.ts +1 -1
  134. package/dist/storage/storage-backend.d.ts +1 -1
  135. package/dist/storage/zip-coverage-embed.d.ts +1 -1
  136. package/dist/storage/zip-export.d.ts +1 -1
  137. package/dist/storage/zip-reader.d.ts +1 -1
  138. package/dist/{storage-backend-BX7egQKi.d.ts → storage-backend-DCYbOvTw.d.ts} +1 -1
  139. package/dist/{store-subscribers-CWHToX6N.d.ts → store-subscribers-BbRW35u5.d.ts} +2 -2
  140. package/dist/{subscribe-to-selector-j-eOYybQ.d.ts → subscribe-to-selector-B_lm_euO.d.ts} +1 -1
  141. package/dist/test-utils/browser-mocks.d.ts +1 -1
  142. package/dist/test-utils/zip-round-trip-helpers.d.ts +1 -1
  143. package/dist/text-sprite-C6VcC2mo.d.ts +53 -0
  144. package/dist/{tracking-slice-BAbcZK-Y.d.ts → tracking-slice-D5LDL_y4.d.ts} +1 -1
  145. package/dist/types/ar-types.d.ts +1 -1
  146. package/dist/types/geo-types.d.ts +1 -1
  147. package/dist/types/index.d.ts +2 -2
  148. package/dist/utils/concurrency.d.ts +1 -1
  149. package/dist/utils/failure-tracker.d.ts +1 -1
  150. package/dist/utils/format-file-size.d.ts +1 -1
  151. package/dist/utils/fused-path.d.ts +1 -1
  152. package/dist/utils/index.d.ts +7 -7
  153. package/dist/utils/list-formatter.d.ts +1 -1
  154. package/dist/utils/logger.d.ts +1 -1
  155. package/dist/visualization/accuracy-circles.d.ts +1 -1
  156. package/dist/visualization/alignment-lerper.d.ts +1 -1
  157. package/dist/visualization/ar-world-group-alignment.d.ts +1 -1
  158. package/dist/visualization/ar-world-group-alignment.js +1 -1
  159. package/dist/visualization/camera-follower.d.ts +1 -1
  160. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  161. package/dist/visualization/frame-conversions.d.ts +1 -1
  162. package/dist/visualization/frustum-visibility.d.ts +1 -1
  163. package/dist/visualization/gps-anchor.d.ts +1 -1
  164. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  165. package/dist/visualization/gps-compass-cubes.js +6 -22
  166. package/dist/visualization/gps-event-markers.d.ts +1 -1
  167. package/dist/visualization/gps-event-markers.js +1 -1
  168. package/dist/visualization/hit-test-reticle.d.ts +1 -1
  169. package/dist/visualization/hit-test-reticle.js +3 -2
  170. package/dist/visualization/index.d.ts +25 -19
  171. package/dist/visualization/index.js +10 -4
  172. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  173. package/dist/visualization/leaflet-map-overlay.js +1 -1
  174. package/dist/visualization/lerp-utils.d.ts +1 -1
  175. package/dist/visualization/map-data.d.ts +1 -1
  176. package/dist/visualization/map-data.js +1 -1
  177. package/dist/visualization/map-overlay-draw.d.ts +1 -1
  178. package/dist/visualization/occluder-mesh-driver.d.ts +2 -2
  179. package/dist/visualization/occlusion-mesh.d.ts +1 -1
  180. package/dist/visualization/occlusion-mesh.js +26 -1
  181. package/dist/visualization/occupancy-cubes-visualizer.d.ts +2 -0
  182. package/dist/visualization/occupancy-cubes-visualizer.js +221 -0
  183. package/dist/visualization/perf-stats-overlay.d.ts +2 -0
  184. package/dist/visualization/perf-stats-overlay.js +85 -0
  185. package/dist/visualization/pointer-picking.d.ts +2 -0
  186. package/dist/visualization/pointer-picking.js +58 -0
  187. package/dist/visualization/text-sprite.d.ts +2 -0
  188. package/dist/visualization/text-sprite.js +105 -0
  189. package/dist/visualization/three-dispose.d.ts +1 -1
  190. package/dist/visualization/vis-colors.d.ts +1 -1
  191. package/dist/visualization/wayfinding-hud.d.ts +2 -0
  192. package/dist/visualization/wayfinding-hud.js +289 -0
  193. package/dist/visualization/wayfinding-placement.d.ts +2 -0
  194. package/dist/visualization/wayfinding-placement.js +157 -0
  195. package/dist/wayfinding-hud-DVun2b69.d.ts +82 -0
  196. package/dist/wayfinding-placement-dnHzfYlT.d.ts +97 -0
  197. package/dist/{webxr-session-OyR3YbTq.js → webxr-session-BBlgzrqY.js} +3 -3
  198. package/dist/{webxr-session-DefwObIu.d.ts → webxr-session-DurXRnl_.d.ts} +7 -7
  199. package/package.json +3 -3
  200. /package/dist/{absolute-orientation-Dj1One3y.d.ts → absolute-orientation-0WIsqJnu.d.ts} +0 -0
  201. /package/dist/{accuracy-circles-BHz7yVIL.d.ts → accuracy-circles-CFnKGfsD.d.ts} +0 -0
  202. /package/dist/{alignment-lerper-DAHicRON.d.ts → alignment-lerper-CK1TYLIL.d.ts} +0 -0
  203. /package/dist/{ar-crash-isolation-DlXCGzMf.d.ts → ar-crash-isolation-BSFw_beO.d.ts} +0 -0
  204. /package/dist/{ar-types-D6PYS4bc.d.ts → ar-types-BprL3nKv.d.ts} +0 -0
  205. /package/dist/{bresenham3d-D-t9i_b5.d.ts → bresenham3d-bt61R-Qa.d.ts} +0 -0
  206. /package/dist/{camera-blit-capture-BlC-arRb.d.ts → camera-blit-capture-CgNJ9grH.d.ts} +0 -0
  207. /package/dist/{camera-follower-CO_ibF2o.d.ts → camera-follower-OnnXsbHd.d.ts} +0 -0
  208. /package/dist/{capability-checker-BZ3_x1YL.d.ts → capability-checker-DrWimJ_t.d.ts} +0 -0
  209. /package/dist/{capture-failure-tracker-BAUKjtXU.d.ts → capture-failure-tracker-bUXJeMvQ.d.ts} +0 -0
  210. /package/dist/{capture-motion-gate-CPwLekNf.d.ts → capture-motion-gate-C60Qrilh.d.ts} +0 -0
  211. /package/dist/{chromium-camera-access-workaround-BHR4clTE.d.ts → chromium-camera-access-workaround-BRs6gkI4.d.ts} +0 -0
  212. /package/dist/{concurrency-DW0jQo3G.d.ts → concurrency-CMhbkxBk.d.ts} +0 -0
  213. /package/dist/{css3d-renderer-manager-CJjHtCMT.d.ts → css3d-renderer-manager-CE6xN7XM.d.ts} +0 -0
  214. /package/dist/{device-orientation-cache-j-kLP8he.js → device-orientation-cache-BjEaEAmh.js} +0 -0
  215. /package/dist/{failure-tracker-B7fzPfZo.d.ts → failure-tracker-BdaFulDA.d.ts} +0 -0
  216. /package/dist/{file-system-utils-BHI0rXr9.d.ts → file-system-utils-DE3BViuq.d.ts} +0 -0
  217. /package/dist/{format-file-size-Da8rZgfs.d.ts → format-file-size-B2CJdRqD.d.ts} +0 -0
  218. /package/dist/{frame-loop-7GBMBu68.d.ts → frame-loop-CUEIdlhS.d.ts} +0 -0
  219. /package/dist/{frustum-visibility-COPfzruF.d.ts → frustum-visibility-BTx1Sqfo.d.ts} +0 -0
  220. /package/dist/{geo-types-BfDK8kJu.d.ts → geo-types-DGe5UKgx.d.ts} +0 -0
  221. /package/dist/{gps-CnhYrJCa.d.ts → gps-CC0l9lCp.d.ts} +0 -0
  222. /package/dist/{gps-compass-cubes-CT3TWctJ.d.ts → gps-compass-cubes-QQJgpRXR.d.ts} +0 -0
  223. /package/dist/{gps-error-handler-DjVuLXve.d.ts → gps-error-handler-da4DCcs5.d.ts} +0 -0
  224. /package/dist/{h3-proximity-BaAulrnM.d.ts → h3-proximity-CAEk3osq.d.ts} +0 -0
  225. /package/dist/{hit-test-reticle-DhnCMOsc.d.ts → hit-test-reticle-DcOyqo3B.d.ts} +0 -0
  226. /package/dist/{image-capture-C4DwxC10.js → image-capture-Dn8uLIP2.js} +0 -0
  227. /package/dist/{image-quality--HLuyJ2r.d.ts → image-quality-G7_qT4aj.d.ts} +0 -0
  228. /package/dist/{index-CjxbDm8W.d.ts → index-T8Fxvzka.d.ts} +0 -0
  229. /package/dist/{index-Nr3qg4IH.d.ts → index-Y_RNWbQX.d.ts} +0 -0
  230. /package/dist/{leaflet-map-overlay-CTcTVmL1.js → leaflet-map-overlay-DjWfRT8a.js} +0 -0
  231. /package/dist/{lerp-utils-bwMwVCpU.d.ts → lerp-utils-B-ecvLq2.d.ts} +0 -0
  232. /package/dist/{list-formatter-1ttG18MQ.d.ts → list-formatter-C45u3Vgv.d.ts} +0 -0
  233. /package/dist/{logger-B4StJUDb.d.ts → logger-BQGNXi9W.d.ts} +0 -0
  234. /package/dist/{map-data-DkhsYDGM.js → map-data-vL8jb5Z9.js} +0 -0
  235. /package/dist/{opfs-storage-S_odkCk1.d.ts → opfs-storage-CrhVauNL.d.ts} +0 -0
  236. /package/dist/{permission-checker-B-VtjnBA.d.ts → permission-checker-BDrCOwQF.d.ts} +0 -0
  237. /package/dist/{qr-pose-21fRDcFO.d.ts → qr-pose-_dInl6di.d.ts} +0 -0
  238. /package/dist/{scene-node-names-DWzhE7Ro.d.ts → scene-node-names-Ct6ATrfM.d.ts} +0 -0
  239. /package/dist/{session-disposers-B7hUN90w.js → session-disposers-BJM5U62W.js} +0 -0
  240. /package/dist/{three-dispose-C3mnKQR5.d.ts → three-dispose-B5-XiECx.d.ts} +0 -0
  241. /package/dist/{vis-colors-CoSpbZEO.d.ts → vis-colors-16Pht13u.d.ts} +0 -0
  242. /package/dist/{webxr-nue-basis-C3vTqkE2.d.ts → webxr-nue-basis-B8ZEetVb.d.ts} +0 -0
  243. /package/dist/{xr-camera-texture-q0eIDSRg.d.ts → xr-camera-texture-BA4xiaQ4.d.ts} +0 -0
  244. /package/dist/{xr-error-handler-D9y5r8ok.d.ts → xr-error-handler-BeyxXCE_.d.ts} +0 -0
  245. /package/dist/{xr-frame-loop-BiZvgqP7.d.ts → xr-frame-loop-A1mGDFHI.d.ts} +0 -0
  246. /package/dist/{zip-coverage-embed-CyoX7XL7.d.ts → zip-coverage-embed-BC3O2-8b.d.ts} +0 -0
  247. /package/dist/{zip-export-CuBimX1J.d.ts → zip-export-Cfl_kNQ7.d.ts} +0 -0
  248. /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 };