gps-plus-slam-app-framework 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/dist/{accuracy-circles-BtqOP_v1.d.ts → accuracy-circles-DkWBkF5g.d.ts} +16 -6
  2. package/dist/{app-selectors-ikTUQZpe.d.ts → app-selectors-BbAiCRUT.d.ts} +1 -1
  3. package/dist/ar/bresenham3d.d.ts +1 -1
  4. package/dist/ar/camera-blit-capture.d.ts +1 -1
  5. package/dist/ar/capability-checker.d.ts +1 -1
  6. package/dist/ar/capture-failure-tracker.d.ts +1 -1
  7. package/dist/ar/chromium-camera-access-workaround.d.ts +1 -1
  8. package/dist/ar/depth-grid-lookup.d.ts +1 -1
  9. package/dist/ar/depth-occluder.d.ts +127 -0
  10. package/dist/ar/depth-occluder.js +299 -0
  11. package/dist/ar/depth-sampler.d.ts +2 -2
  12. package/dist/ar/depth-sampler.js +24 -4
  13. package/dist/ar/depth-unprojection.d.ts +1 -1
  14. package/dist/ar/depth-unprojection.js +52 -21
  15. package/dist/ar/enable-gps-ar.d.ts +1 -1
  16. package/dist/ar/enable-gps-ar.js +1 -1
  17. package/dist/ar/frame-loop.d.ts +1 -1
  18. package/dist/ar/frame-loop.js +12 -2
  19. package/dist/ar/image-capture.d.ts +1 -1
  20. package/dist/ar/image-capture.js +1 -1
  21. package/dist/ar/image-quality.d.ts +1 -1
  22. package/dist/ar/image-quality.js +2 -1
  23. package/dist/ar/index.d.ts +32 -31
  24. package/dist/ar/index.js +5 -4
  25. package/dist/ar/occlusion-mesh-worker.d.ts +58 -0
  26. package/dist/ar/occlusion-mesh-worker.js +129 -0
  27. package/dist/ar/occupancy-grid.d.ts +2 -2
  28. package/dist/ar/occupancy-grid.js +287 -31
  29. package/dist/ar/occupancy-mesher.d.ts +2 -0
  30. package/dist/ar/occupancy-mesher.js +638 -0
  31. package/dist/ar/planar-pnp.d.ts +1 -1
  32. package/dist/ar/qr-debug-view.d.ts +1 -1
  33. package/dist/ar/qr-derived-pose.d.ts +1 -1
  34. package/dist/ar/qr-detection-controller.d.ts +1 -1
  35. package/dist/ar/qr-frontend.d.ts +1 -1
  36. package/dist/ar/qr-pose.d.ts +1 -1
  37. package/dist/ar/qr-size-depth-context.d.ts +2 -2
  38. package/dist/ar/qr-size-measurer.d.ts +1 -1
  39. package/dist/ar/replay-scene.d.ts +1 -1
  40. package/dist/ar/replay-scene.js +1 -1
  41. package/dist/ar/scene-node-names.d.ts +1 -1
  42. package/dist/ar/webxr-nue-basis.d.ts +1 -1
  43. package/dist/ar/webxr-session.d.ts +4 -4
  44. package/dist/ar/webxr-session.js +2 -2
  45. package/dist/ar/xr-camera-texture.d.ts +1 -1
  46. package/dist/ar/xr-error-handler.d.ts +1 -1
  47. package/dist/ar/xr-frame-loop.d.ts +1 -1
  48. package/dist/ar/xr-frame-loop.js +12 -2
  49. package/dist/{ar-MFpFlV7j.js → ar-BqCudtqE.js} +3 -2
  50. package/dist/{ar-world-group-alignment-69fgnB39.d.ts → ar-world-group-alignment-Cfg0trC3.d.ts} +1 -1
  51. package/dist/{capture-motion-gate-4EscNZCw.js → capture-motion-gate-_NdTSwKi.js} +17 -5
  52. package/dist/cell-key-DIB4rOTV.js +51 -0
  53. package/dist/core/index.d.ts +2 -2
  54. package/dist/core/index.js +2 -2
  55. package/dist/{create-slam-app-store-n_VF63D5.d.ts → create-slam-app-store-D1QSMuKu.d.ts} +8 -9
  56. package/dist/{create-slam-app-store-DkCHOpvb.js → create-slam-app-store-DXiAnJwR.js} +9 -2
  57. package/dist/{depth-grid-lookup-DQ-IYhO3.d.ts → depth-grid-lookup-D5atpNZ3.d.ts} +1 -1
  58. package/dist/{depth-sampler-asc8exHt.d.ts → depth-sampler-CwObAwEA.d.ts} +33 -1
  59. package/dist/{depth-unprojection-DjsbJW9L.d.ts → depth-unprojection-DRdBU90S.d.ts} +1 -1
  60. package/dist/{enable-gps-ar-DkNhFv4Y.d.ts → enable-gps-ar-BBtRO2FJ.d.ts} +4 -4
  61. package/dist/{frame-conversions-DObhj8Wf.d.ts → frame-conversions-DDcNHkTb.d.ts} +1 -1
  62. package/dist/{fused-path-v6eOmLl3.d.ts → fused-path-C5pp0PJU.d.ts} +1 -1
  63. package/dist/geo/h3-proximity.d.ts +1 -1
  64. package/dist/geo/index.d.ts +1 -1
  65. package/dist/{gps-anchor-BeFO2RYa.d.ts → gps-anchor-CEjGqEVD.d.ts} +1 -1
  66. package/dist/{gps-ar-pose-sampler-DHMZmYAc.d.ts → gps-ar-pose-sampler-DEGNk2CI.d.ts} +1 -1
  67. package/dist/{gps-event-coordinator-CjWW636x.d.ts → gps-event-coordinator-BWpgBrHq.d.ts} +4 -4
  68. package/dist/{image-capture-CNZDrQXG.d.ts → image-capture-Bp6Pd4Md.d.ts} +3 -3
  69. package/dist/{image-capture-B2-b6tKK.js → image-capture-DVcuqMtX.js} +1 -1
  70. package/dist/{index-BODDUTvD.d.ts → index-B6iBnO_K.d.ts} +1 -1
  71. package/dist/index-DXPVzOpP.d.ts +2 -0
  72. package/dist/{index-BneSj4_5.d.ts → index-cCQWuYqy.d.ts} +3 -3
  73. package/dist/index.d.ts +82 -80
  74. package/dist/index.js +10 -8
  75. package/dist/{leaflet-map-overlay-CmIyiE8n.d.ts → leaflet-map-overlay-C8GS4cUy.d.ts} +64 -10
  76. package/dist/leaflet-map-overlay-CBWPvaX9.js +452 -0
  77. package/dist/lerp-utils-Qs8OhumS.d.ts +42 -0
  78. package/dist/map-data-1tF5gcYV.js +124 -0
  79. package/dist/map-data-CUnTzRIC.d.ts +78 -0
  80. package/dist/{map-overlay-draw-ymz1s7fG.d.ts → map-overlay-draw-6WJeq0Kg.d.ts} +1 -1
  81. package/dist/{null-storage-backend-B9rtyDfG.d.ts → null-storage-backend-B1-jlpin.d.ts} +2 -2
  82. package/dist/occlusion-mesh-kXCQ2FM1.d.ts +194 -0
  83. package/dist/occupancy-grid-BV7RhB7I.d.ts +194 -0
  84. package/dist/occupancy-mesher-C2ecWhNp.d.ts +100 -0
  85. package/dist/{opfs-storage-3VZh-qH6.d.ts → opfs-storage-CTkxvxN4.d.ts} +5 -0
  86. package/dist/{opfs-storage-backend-JJiUJo-3.d.ts → opfs-storage-backend-D6Z03fML.d.ts} +2 -2
  87. package/dist/{persistence-middleware-DAPTTwB2.d.ts → persistence-middleware-CG-t08X4.d.ts} +1 -1
  88. package/dist/{planar-pnp-my9bqULS.d.ts → planar-pnp-D7bc_rLX.d.ts} +1 -1
  89. package/dist/{qr-debug-view-Brl73HGO.d.ts → qr-debug-view-BJooqbiP.d.ts} +1 -1
  90. package/dist/{qr-derived-pose-CmxTP2m4.d.ts → qr-derived-pose-BwNWn8oV.d.ts} +2 -2
  91. package/dist/{qr-detected-slice-VQssZmQJ.d.ts → qr-detected-slice-TzVb8bij.d.ts} +4 -4
  92. package/dist/{qr-detection-controller-CDurHv8E.d.ts → qr-detection-controller-Bo4XfQTP.d.ts} +3 -3
  93. package/dist/{qr-frontend--fFX25mV.d.ts → qr-frontend-BPURlh3S.d.ts} +1 -1
  94. package/dist/{qr-pose-aggregation-B7tWJq7j.d.ts → qr-pose-aggregation-CypnmjA-.d.ts} +1 -1
  95. package/dist/{qr-size-depth-context-CeAG53fy.d.ts → qr-size-depth-context-apAOmbrZ.d.ts} +2 -2
  96. package/dist/{qr-size-measurer-DQdAsid1.d.ts → qr-size-measurer-DxiMqRUw.d.ts} +3 -3
  97. package/dist/{recording-options-BPCpMHLK.d.ts → recording-options-DYKWVI93.d.ts} +203 -24
  98. package/dist/{recording-replayer-DQKpAGyQ.d.ts → recording-replayer-BJ8Lf0QY.d.ts} +2 -2
  99. package/dist/{recording-slice-DaxHZvNu.d.ts → recording-slice-BW8SiLo1.d.ts} +2 -2
  100. package/dist/{replay-engine-CmKh_4wf.d.ts → replay-engine-DmCAc7WM.d.ts} +1 -1
  101. package/dist/{replay-scene-DBlDrV_x.d.ts → replay-scene-zJ8SFDjE.d.ts} +3 -3
  102. package/dist/sensors/absolute-orientation.d.ts +1 -1
  103. package/dist/sensors/absolute-orientation.js +23 -6
  104. package/dist/sensors/gps-error-handler.d.ts +1 -1
  105. package/dist/sensors/gps.d.ts +1 -1
  106. package/dist/sensors/index.d.ts +4 -4
  107. package/dist/sensors/permission-checker.d.ts +1 -1
  108. package/dist/state/app-selectors.d.ts +1 -1
  109. package/dist/state/combined-root-state.d.ts +1 -1
  110. package/dist/state/create-slam-app-store.d.ts +1 -1
  111. package/dist/state/create-slam-app-store.js +1 -1
  112. package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
  113. package/dist/state/gps-event-coordinator.d.ts +1 -1
  114. package/dist/state/index.d.ts +19 -19
  115. package/dist/state/index.js +2 -2
  116. package/dist/state/persistence-middleware.d.ts +1 -1
  117. package/dist/state/qr-detected-slice.d.ts +3 -3
  118. package/dist/state/recording-options.d.ts +2 -2
  119. package/dist/state/recording-options.js +150 -19
  120. package/dist/state/recording-replayer.d.ts +2 -2
  121. package/dist/state/recording-replayer.js +1 -1
  122. package/dist/state/recording-slice.d.ts +1 -1
  123. package/dist/state/replay-engine.d.ts +1 -1
  124. package/dist/state/store-subscribers.d.ts +2 -2
  125. package/dist/state/store-subscribers.js +26 -12
  126. package/dist/state/subscribe-to-selector.d.ts +1 -1
  127. package/dist/state/tracking-quality.d.ts +1 -1
  128. package/dist/state/tracking-quality.js +14 -6
  129. package/dist/state/tracking-slice.d.ts +1 -1
  130. package/dist/{state-E51QT_-j.js → state-BeKvn4B-.js} +1 -1
  131. package/dist/storage/file-system-utils.d.ts +1 -1
  132. package/dist/storage/index.d.ts +8 -8
  133. package/dist/storage/null-storage-backend.d.ts +1 -1
  134. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  135. package/dist/storage/opfs-storage.d.ts +1 -1
  136. package/dist/storage/opfs-storage.js +30 -1
  137. package/dist/storage/storage-backend.d.ts +1 -1
  138. package/dist/storage/zip-coverage-embed.d.ts +1 -1
  139. package/dist/storage/zip-export.d.ts +1 -1
  140. package/dist/storage/zip-reader.d.ts +2 -2
  141. package/dist/storage/zip-reader.js +20 -7
  142. package/dist/{storage-backend-BdVLD0dU.d.ts → storage-backend-sEtmM4yW.d.ts} +1 -1
  143. package/dist/{store-subscribers-CA6AJH_8.d.ts → store-subscribers-DJUErW-o.d.ts} +6 -3
  144. package/dist/{subscribe-to-selector-B6EUmQ3I.d.ts → subscribe-to-selector-dQV8SpsO.d.ts} +1 -1
  145. package/dist/test-utils/browser-mocks.d.ts +1 -1
  146. package/dist/test-utils/browser-mocks.js +2 -0
  147. package/dist/test-utils/zip-round-trip-helpers.d.ts +1 -1
  148. package/dist/{tracking-slice-BuAZxXZ5.d.ts → tracking-slice-DMkHlbny.d.ts} +1 -1
  149. package/dist/types/ar-types.d.ts +1 -1
  150. package/dist/types/geo-types.d.ts +1 -1
  151. package/dist/types/index.d.ts +2 -2
  152. package/dist/utils/concurrency.d.ts +1 -1
  153. package/dist/utils/failure-tracker.d.ts +1 -1
  154. package/dist/utils/format-file-size.d.ts +1 -1
  155. package/dist/utils/fused-path.d.ts +1 -1
  156. package/dist/utils/index.d.ts +7 -7
  157. package/dist/utils/list-formatter.d.ts +1 -1
  158. package/dist/utils/logger.d.ts +1 -1
  159. package/dist/visualization/accuracy-circles.d.ts +1 -1
  160. package/dist/visualization/accuracy-circles.js +17 -8
  161. package/dist/visualization/alignment-lerper.d.ts +1 -1
  162. package/dist/visualization/ar-world-group-alignment.d.ts +1 -1
  163. package/dist/visualization/ar-world-group-alignment.js +1 -1
  164. package/dist/visualization/camera-follower.d.ts +1 -1
  165. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  166. package/dist/visualization/frame-conversions.d.ts +1 -1
  167. package/dist/visualization/frustum-visibility.d.ts +1 -1
  168. package/dist/visualization/gps-anchor.d.ts +1 -1
  169. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  170. package/dist/visualization/gps-event-markers.d.ts +1 -1
  171. package/dist/visualization/gps-event-markers.js +1 -1
  172. package/dist/visualization/hit-test-reticle.d.ts +1 -1
  173. package/dist/visualization/index.d.ts +20 -19
  174. package/dist/visualization/index.js +4 -3
  175. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  176. package/dist/visualization/leaflet-map-overlay.js +1 -282
  177. package/dist/visualization/lerp-utils.d.ts +2 -2
  178. package/dist/visualization/lerp-utils.js +27 -3
  179. package/dist/visualization/map-data.d.ts +1 -1
  180. package/dist/visualization/map-data.js +1 -32
  181. package/dist/visualization/map-overlay-draw.d.ts +1 -1
  182. package/dist/visualization/map-overlay-draw.js +24 -2
  183. package/dist/visualization/map-overlay.d.ts +1 -1
  184. package/dist/visualization/occluder-mesh-driver.d.ts +130 -0
  185. package/dist/visualization/occluder-mesh-driver.js +219 -0
  186. package/dist/visualization/occlusion-mesh.d.ts +2 -0
  187. package/dist/visualization/occlusion-mesh.js +437 -0
  188. package/dist/visualization/three-dispose.d.ts +1 -1
  189. package/dist/visualization/vis-colors.d.ts +1 -1
  190. package/dist/{webxr-session-DG-eNfny.js → webxr-session-BCP2XK7k.js} +88 -13
  191. package/dist/{webxr-session-Do5L6G3B.d.ts → webxr-session-BWCpGd-2.d.ts} +73 -10
  192. package/dist/{zip-reader-D9iPQwdQ.d.ts → zip-reader-BzENzMH6.d.ts} +36 -10
  193. package/package.json +9 -5
  194. package/dist/index-_Rhgoqkh.d.ts +0 -2
  195. package/dist/lerp-utils-BLmHhkVe.d.ts +0 -20
  196. package/dist/map-data-B4oEehUh.d.ts +0 -49
  197. package/dist/occupancy-grid-DnOc-6PU.d.ts +0 -82
  198. /package/dist/{absolute-orientation-CAEh840t.d.ts → absolute-orientation-BAxc15J6.d.ts} +0 -0
  199. /package/dist/{alignment-lerper-Bil6vbVw.d.ts → alignment-lerper-gD70k7Fq.d.ts} +0 -0
  200. /package/dist/{ar-types-BFX2r1wk.d.ts → ar-types-Cc5yvBwn.d.ts} +0 -0
  201. /package/dist/{bresenham3d-CU1Gv2W4.d.ts → bresenham3d-DGc4nIb-.d.ts} +0 -0
  202. /package/dist/{camera-blit-capture-NKT9H5-e.d.ts → camera-blit-capture-BimSdyih.d.ts} +0 -0
  203. /package/dist/{camera-follower-CqJJ7zRj.d.ts → camera-follower-DfBm2J5i.d.ts} +0 -0
  204. /package/dist/{capability-checker-DUQLg6gq.d.ts → capability-checker-BTTpygvZ.d.ts} +0 -0
  205. /package/dist/{capture-failure-tracker-gsm8q7yi.d.ts → capture-failure-tracker-_BVfhpxv.d.ts} +0 -0
  206. /package/dist/{capture-motion-gate-0Yl2NpQG.d.ts → capture-motion-gate-BGoAMkmf.d.ts} +0 -0
  207. /package/dist/{chromium-camera-access-workaround-D4K2uwwQ.d.ts → chromium-camera-access-workaround-C6yJLXz9.d.ts} +0 -0
  208. /package/dist/{concurrency-By0oOTZt.d.ts → concurrency-DPeGBH0X.d.ts} +0 -0
  209. /package/dist/{css3d-renderer-manager-B3kGNtcn.d.ts → css3d-renderer-manager-CPpVtvgs.d.ts} +0 -0
  210. /package/dist/{failure-tracker-BO7M7jF_.d.ts → failure-tracker-CeKf5HIc.d.ts} +0 -0
  211. /package/dist/{file-system-utils-DOajnMm-.d.ts → file-system-utils-DueXHxo9.d.ts} +0 -0
  212. /package/dist/{format-file-size-Dc98ulzB.d.ts → format-file-size-DxN-EBLc.d.ts} +0 -0
  213. /package/dist/{frame-loop-CpVjQ1dH.d.ts → frame-loop-7GBMBu68.d.ts} +0 -0
  214. /package/dist/{frustum-visibility-CeoCZchO.d.ts → frustum-visibility-CokkgS_i.d.ts} +0 -0
  215. /package/dist/{geo-types-3Ip1NzVR.d.ts → geo-types-DrGVu8nS.d.ts} +0 -0
  216. /package/dist/{gps-BBO3hKfT.d.ts → gps-CDKDQokE.d.ts} +0 -0
  217. /package/dist/{gps-compass-cubes-DSN87L8p.d.ts → gps-compass-cubes-1mooS3BS.d.ts} +0 -0
  218. /package/dist/{gps-error-handler-BbPsfw-_.d.ts → gps-error-handler-C34GQuTu.d.ts} +0 -0
  219. /package/dist/{gps-event-markers-Cz9RMA2k.d.ts → gps-event-markers-Di2nBzx-.d.ts} +0 -0
  220. /package/dist/{h3-proximity-C-j0pw1j.d.ts → h3-proximity-D0GTHQmr.d.ts} +0 -0
  221. /package/dist/{hit-test-reticle-DV-2TpB9.d.ts → hit-test-reticle-CaQbrhbk.d.ts} +0 -0
  222. /package/dist/{image-quality-smCBoFj1.d.ts → image-quality-BMx2ko9-.d.ts} +0 -0
  223. /package/dist/{index-BAPokxMb.d.ts → index-D74JTX4I.d.ts} +0 -0
  224. /package/dist/{list-formatter-KNMS0ay2.d.ts → list-formatter-DLFDnS70.d.ts} +0 -0
  225. /package/dist/{logger-CLnPrHiD.d.ts → logger-CqTd5Kpk.d.ts} +0 -0
  226. /package/dist/{map-overlay-Dh2B97MP.d.ts → map-overlay-BCQgvIgU.d.ts} +0 -0
  227. /package/dist/{permission-checker-CDuI8BwC.d.ts → permission-checker-B-VtjnBA.d.ts} +0 -0
  228. /package/dist/{qr-pose-D2rfu5DA.d.ts → qr-pose-y3vaEd6x.d.ts} +0 -0
  229. /package/dist/{scene-node-names-BiShOQDB.d.ts → scene-node-names-CGb-ylBd.d.ts} +0 -0
  230. /package/dist/{session-disposers-zv9OGXPB.js → session-disposers-BPrMplIC.js} +0 -0
  231. /package/dist/{three-dispose-Bt1k7WKh.d.ts → three-dispose-BMOAmPhb.d.ts} +0 -0
  232. /package/dist/{vis-colors-Obw2pLqT.d.ts → vis-colors-C7HI0u9_.d.ts} +0 -0
  233. /package/dist/{webxr-nue-basis-3nfjDJJp.d.ts → webxr-nue-basis-CaCnYxuq.d.ts} +0 -0
  234. /package/dist/{xr-camera-texture-Cva1q3Qj.d.ts → xr-camera-texture-q0eIDSRg.d.ts} +0 -0
  235. /package/dist/{xr-error-handler-CtvHpjfm.d.ts → xr-error-handler-uVGDu9IX.d.ts} +0 -0
  236. /package/dist/{xr-frame-loop-DIYtRcUa.d.ts → xr-frame-loop-DX3JPpAU.d.ts} +0 -0
  237. /package/dist/{zip-coverage-embed-DCAREVmw.d.ts → zip-coverage-embed-ncVbaZAz.d.ts} +0 -0
  238. /package/dist/{zip-export-ol0S_Ofd.d.ts → zip-export-CtiUnAlJ.d.ts} +0 -0
@@ -0,0 +1,219 @@
1
+ import { packMeshRequest, runMeshRequest } from "../ar/occlusion-mesh-worker.js";
2
+ //#region ../src/visualization/occluder-mesh-driver.ts
3
+ /**
4
+ * Occluder mesh driver — main-thread orchestration for the Web Worker offload.
5
+ *
6
+ * Drives {@link packMeshRequest} / {@link runMeshRequest} (see
7
+ * `2026-07-01-occluder-worker-and-chunked-remesh-plan.md`): the occluder wiring
8
+ * calls {@link OccluderMeshDriver.request} on each refresh with the occupied-cell
9
+ * snapshot; the driver packs it, posts it to a worker, and invokes a callback
10
+ * with the meshed geometry when it returns — so the expensive mesh runs
11
+ * off-thread and never stalls the render.
12
+ *
13
+ * Policy (locked 2026-07-01):
14
+ * - **Coalesce to latest** — at most one job in flight; a request made while busy
15
+ * is remembered (newest wins) and posted when the current one returns, so work
16
+ * never queues up behind a growing grid.
17
+ * - **Synchronous fallback** — constructed with `poster = null` (no worker
18
+ * support), it meshes inline and calls back immediately (today's behaviour).
19
+ * - **Error recovery (2026-07-01 Phase 1 gap fix)** — a worker that never replies
20
+ * (an uncaught throw in `runMeshRequest`, or a module that fails to load) must
21
+ * not wedge the in-flight slot forever. On a worker error the driver clears the
22
+ * slot and re-posts the pending snapshot (or lets the next refresh post) so the
23
+ * occluder never silently freezes. If the worker errors **before ever producing
24
+ * a mesh** — almost always a load failure — the driver declares it unusable
25
+ * ({@link OccluderMeshDriverOptions.onWorkerUnusable}) and switches to
26
+ * synchronous main-thread meshing, since further posts would vanish into a dead
27
+ * worker.
28
+ *
29
+ * Framework-owned + THREE-free: a consumer supplies a {@link MeshWorkerPoster}
30
+ * adapting a real `Worker`, and applies the result (e.g. `OcclusionMesh.applyMeshData`).
31
+ */
32
+ /** Cell count of a snapshot in either representation. */
33
+ function cellCountOf(cells) {
34
+ return cells instanceof Int32Array ? cells.length / 3 : cells.length;
35
+ }
36
+ var OccluderMeshDriver = class {
37
+ poster;
38
+ onWorkerUnusable;
39
+ onError;
40
+ onMeshStats;
41
+ now;
42
+ nextId = 1;
43
+ inFlightId = null;
44
+ inFlightCallback = null;
45
+ /** Timing/size context of the in-flight job; cleared on completion/failure. */
46
+ inFlightStats = null;
47
+ pending = null;
48
+ disposed = false;
49
+ /** True ⇒ mesh inline (no worker, or the worker was declared unusable). */
50
+ syncMode;
51
+ /** Set once the worker returns a mesh — distinguishes a transient error from a load failure. */
52
+ hasSucceeded = false;
53
+ /**
54
+ * @param poster a worker adapter, or `null` for synchronous main-thread meshing.
55
+ * @param options optional {@link OccluderMeshDriverOptions.onWorkerUnusable} /
56
+ * {@link OccluderMeshDriverOptions.onError} callbacks.
57
+ */
58
+ constructor(poster, options = {}) {
59
+ this.poster = poster;
60
+ this.syncMode = poster === null;
61
+ this.onWorkerUnusable = options.onWorkerUnusable;
62
+ this.onError = options.onError;
63
+ this.onMeshStats = options.onMeshStats;
64
+ this.now = options.now ?? (() => performance.now());
65
+ if (poster) {
66
+ poster.onmessage = (event) => this.handleResponse(event.data);
67
+ poster.onerror = (error) => this.handleWorkerError(error);
68
+ }
69
+ }
70
+ /** True while a mesh job is being computed (worker path only). */
71
+ get busy() {
72
+ return this.inFlightId !== null;
73
+ }
74
+ /**
75
+ * Request a mesh of `cells` (tuple array, or a flat Int32Array snapshot —
76
+ * see `packMeshRequest`; a flat snapshot's buffer is transferred/detached,
77
+ * so pass a fresh array). If a job is already in flight, this becomes the
78
+ * (single) pending job — the newest request wins; intermediates are dropped.
79
+ * `getCellPoint` is invoked with a transient cell tuple that may be a reused
80
+ * scratch — it must not retain the tuple beyond the call (see
81
+ * `MeshOccupiedCellsOptions.getCellPoint`).
82
+ */
83
+ request(cells, cellSizeM, mode, getCellPoint, onMesh) {
84
+ if (this.disposed) return;
85
+ const job = {
86
+ cells,
87
+ cellSizeM,
88
+ mode,
89
+ getCellPoint,
90
+ onMesh
91
+ };
92
+ if (this.inFlightId !== null) {
93
+ this.pending = job;
94
+ return;
95
+ }
96
+ this.post(job);
97
+ }
98
+ post(job) {
99
+ const id = this.nextId++;
100
+ this.inFlightId = id;
101
+ this.inFlightCallback = job.onMesh;
102
+ this.inFlightStats = {
103
+ startedMs: this.now(),
104
+ cellCount: cellCountOf(job.cells),
105
+ mode: job.mode,
106
+ synchronous: this.syncMode || this.poster === null
107
+ };
108
+ let packed;
109
+ try {
110
+ packed = packMeshRequest(id, job.cells, job.cellSizeM, job.mode, job.getCellPoint);
111
+ } catch (error) {
112
+ this.failInFlight(id, error, { workerAtFault: false });
113
+ return;
114
+ }
115
+ const { request, transfer } = packed;
116
+ if (this.poster && !this.syncMode) try {
117
+ this.poster.postMessage(request, transfer);
118
+ } catch (error) {
119
+ this.failInFlight(id, error);
120
+ }
121
+ else try {
122
+ const { response } = runMeshRequest(request);
123
+ this.handleResponse(response);
124
+ } catch (error) {
125
+ if (this.inFlightId === id) this.failInFlight(id, error);
126
+ else this.onError?.(error);
127
+ }
128
+ }
129
+ handleResponse(response) {
130
+ if (this.disposed || response.id !== this.inFlightId) return;
131
+ this.hasSucceeded = true;
132
+ const callback = this.inFlightCallback;
133
+ const stats = this.inFlightStats;
134
+ try {
135
+ if (stats && this.onMeshStats) {
136
+ const { startedMs, ...rest } = stats;
137
+ this.onMeshStats({
138
+ ...rest,
139
+ durationMs: this.now() - startedMs
140
+ });
141
+ }
142
+ callback?.(response.positions, response.indices);
143
+ } finally {
144
+ this.inFlightId = null;
145
+ this.inFlightCallback = null;
146
+ this.inFlightStats = null;
147
+ if (!this.disposed && this.pending) {
148
+ const next = this.pending;
149
+ this.pending = null;
150
+ this.post(next);
151
+ }
152
+ }
153
+ }
154
+ /**
155
+ * A worker error event. If a job is in flight, recover it (only one ever is).
156
+ * If none is — the realistic ordering for a **module-load failure**, which
157
+ * errors within a few ms of construction, before the first (throttled)
158
+ * `request` posts anything — the worker is dead: posting to it would be
159
+ * silently dropped (no second error) and wedge the in-flight slot forever. So
160
+ * a pre-first-post error on a worker that has **never** succeeded is treated as
161
+ * a load failure right here — declare it unusable and switch to synchronous
162
+ * meshing so the first `request` never touches the dead worker. A stray/late
163
+ * error on a worker that has already meshed once is transient — kept, ignored.
164
+ */
165
+ handleWorkerError(error) {
166
+ if (this.disposed) return;
167
+ if (this.inFlightId !== null) {
168
+ this.failInFlight(this.inFlightId, error);
169
+ return;
170
+ }
171
+ if (!this.syncMode && !this.hasSucceeded) {
172
+ if (error !== void 0) this.onError?.(error);
173
+ this.syncMode = true;
174
+ this.onWorkerUnusable?.();
175
+ }
176
+ }
177
+ /**
178
+ * Clear a failed in-flight job so the driver never wedges. If the worker had
179
+ * never produced a mesh (almost always a module load failure) fall back to
180
+ * synchronous meshing — unless `workerAtFault` is false (a pack-time error in
181
+ * the caller's `getCellPoint`, which never reached the worker). Then re-post
182
+ * the pending snapshot if one is queued. We deliberately do NOT re-post the
183
+ * *failed* job: if it failed on deterministic bad data, re-posting would
184
+ * loop — the next refresh brings a fresh snapshot.
185
+ */
186
+ failInFlight(id, error, options = {}) {
187
+ if (this.disposed || id !== this.inFlightId) return;
188
+ try {
189
+ if (error !== void 0) this.onError?.(error);
190
+ } finally {
191
+ this.inFlightId = null;
192
+ this.inFlightCallback = null;
193
+ this.inFlightStats = null;
194
+ if ((options.workerAtFault ?? true) && !this.syncMode && !this.hasSucceeded) {
195
+ this.syncMode = true;
196
+ this.onWorkerUnusable?.();
197
+ }
198
+ if (this.pending) {
199
+ const next = this.pending;
200
+ this.pending = null;
201
+ this.post(next);
202
+ }
203
+ }
204
+ }
205
+ /** Stop delivering results (detaches the worker handlers; drops any pending). */
206
+ dispose() {
207
+ this.disposed = true;
208
+ this.pending = null;
209
+ this.inFlightCallback = null;
210
+ this.inFlightId = null;
211
+ this.inFlightStats = null;
212
+ if (this.poster) {
213
+ this.poster.onmessage = null;
214
+ this.poster.onerror = null;
215
+ }
216
+ }
217
+ };
218
+ //#endregion
219
+ export { OccluderMeshDriver };
@@ -0,0 +1,2 @@
1
+ import { a as occluderDepthFade, i as buildOccluderDepthShadeSnippet, n as OcclusionMesh, o as occluderFresnelRim, r as OcclusionMeshOptions, t as OCCLUDER_DEPTH_SHADE } from "../occlusion-mesh-kXCQ2FM1.js";
2
+ export { OCCLUDER_DEPTH_SHADE, OcclusionMesh, OcclusionMeshOptions, buildOccluderDepthShadeSnippet, occluderDepthFade, occluderFresnelRim };
@@ -0,0 +1,437 @@
1
+ import { WEBXR_TO_NUE } from "../ar/webxr-nue-basis.js";
2
+ import { meshOccupiedCells } from "../ar/occupancy-mesher.js";
3
+ import * as THREE from "three";
4
+ //#region ../src/visualization/occlusion-mesh.ts
5
+ /**
6
+ * Persistent occlusion mesh — a depth-only `THREE.Mesh` of the occupancy grid.
7
+ *
8
+ * Wraps the pure {@link meshOccupiedCells} (face-culled voxel surface) into a
9
+ * THREE object that **writes depth but no color** (`colorWrite = false`,
10
+ * `depthWrite = true`), drawn before virtual content (low `renderOrder`) so real
11
+ * geometry the camera saw earlier hides virtual objects placed behind it —
12
+ * including out-of-view surfaces a single-frame live depth occluder cannot
13
+ * remember (2026-06-13-occupancy-mesh-options-plan.md §4; complements the live
14
+ * occluder in 2026-06-14-webxr-depth-occlusion-plan.md).
15
+ *
16
+ * Reusable across consumer apps (AnchorStarter / MinimalExample want occlusion
17
+ * too); the recorder only owns the off-by-default toggle + scene wiring.
18
+ *
19
+ * Coordinate space: the grid cells (and therefore the mesh positions) are **raw
20
+ * WebXR**, but the parent `arWorldGroup` is AR-odometry NUE. The mesh carries
21
+ * the constant `WEBXR_TO_NUE` basis change as its own local matrix — identical
22
+ * to `OccupancyCubesVisualizer` — so it rides the `alignment × WEBXR_TO_NUE`
23
+ * chain. The parent node is injected (no `getArWorldGroup()`) to stay testable.
24
+ *
25
+ * Scope: this is a full-rebuild occluder (re-mesh the whole snapshot on
26
+ * `update`). The chunked dirty-remesh perf layer (plan §7) is a follow-on.
27
+ *
28
+ * @see occlusion-mesh.ts.md for detailed documentation
29
+ */
30
+ const MESH_NAME = "occupancy-occluder";
31
+ const DEBUG_MESH_NAME = "occupancy-occluder-debug";
32
+ const DEBUG_WIREFRAME_MESH_NAME = "occupancy-occluder-debug-wireframe";
33
+ /** Default render order — well before virtual content (which is ≥ 0). */
34
+ const DEFAULT_RENDER_ORDER = -1;
35
+ /** Opacity of the matcap debug skin — see-through enough to read the real scene
36
+ * behind it while the shape stays legible. */
37
+ const DEBUG_SKIN_OPACITY = .6;
38
+ /** Light-cyan GL lines of the wireframe skin — faint enough not to swamp the
39
+ * shaded skin in the combined style, visible enough to read triangle density. */
40
+ const WIREFRAME_COLOR = 11202303;
41
+ const WIREFRAME_OPACITY = .35;
42
+ /** Which styles shade with the matcap (and therefore need vertex normals —
43
+ * pure 'wireframe' is unlit and keeps the remesh path normal-free). */
44
+ function styleNeedsNormals(style) {
45
+ return style === "matcap" || style === "depth-shaded" || style === "depth-shaded-wireframe";
46
+ }
47
+ /**
48
+ * Depth-shaded debug-skin constants — **module constants, not user settings**
49
+ * (tune in code after the first on-device look; 2026-07-02 debug-viz-styles
50
+ * plan). Exported so the unit/property tests of the GLSL-mirror curves below
51
+ * pin the same numbers the shader bakes in.
52
+ */
53
+ const OCCLUDER_DEPTH_SHADE = {
54
+ /** Distance (m) where the fade starts — nearer stays full-bright cyan.
55
+ * Indoor-scale since the 2026-07-03 field pass (finding F1): the original
56
+ * 1.5 → 10 m range kept a whole room in the "near" band (94% brightness at
57
+ * 3 m), making depth-shaded indistinguishable from matcap on device. */
58
+ FADE_START_M: .75,
59
+ /** Distance (m) where the fade bottoms out at {@link FADE_MIN_BRIGHTNESS}.
60
+ * ~Room depth, so indoor scenes span the full near→far gradient; beyond it
61
+ * everything sits uniformly at the floor (still separated from near). */
62
+ FADE_END_M: 4.5,
63
+ /** Brightness floor far away — never fades to black, so distant mesh stays
64
+ * inspectable (the reason the fog-to-background idea was rejected). */
65
+ FADE_MIN_BRIGHTNESS: .3,
66
+ /** Fresnel exponent — higher hugs the silhouette tighter. */
67
+ RIM_POWER: 2.5,
68
+ /** White-rim contribution at a fully grazing view angle. */
69
+ RIM_STRENGTH: .6
70
+ };
71
+ /** Far-distance fade target (dark desaturated blue) — keeps the occluder's
72
+ * single-color cyan identity while clearly separating far from near. */
73
+ const FADE_TARGET_RGB = [
74
+ .03,
75
+ .07,
76
+ .12
77
+ ];
78
+ /** GLSL `smoothstep` (clamped Hermite), mirrored exactly for the TS curves. */
79
+ function smoothstep(edge0, edge1, x) {
80
+ const t = Math.min(1, Math.max(0, (x - edge0) / (edge1 - edge0)));
81
+ return t * t * (3 - 2 * t);
82
+ }
83
+ /**
84
+ * Pure TS mirror of the shader's **distance fade**: 1 at/inside FADE_START_M,
85
+ * smoothly down to FADE_MIN_BRIGHTNESS at/after FADE_END_M. The fragment color
86
+ * is `mix(darkBlue, matcapColor, fade)` — near = bright cyan, far = dark blue.
87
+ * Mirrored so the curve is unit-testable without a GPU (the
88
+ * `buildFullscreenOcclusionShader` GLSL-mirror precedent).
89
+ */
90
+ function occluderDepthFade(distanceM) {
91
+ const { FADE_START_M, FADE_END_M, FADE_MIN_BRIGHTNESS } = OCCLUDER_DEPTH_SHADE;
92
+ return 1 - (1 - FADE_MIN_BRIGHTNESS) * smoothstep(FADE_START_M, FADE_END_M, distanceM);
93
+ }
94
+ /**
95
+ * Pure TS mirror of the shader's **fresnel rim**: 0 where the surface faces
96
+ * the camera head-on (|cos| = 1), RIM_STRENGTH at a fully grazing angle
97
+ * (cos = 0) — silhouettes brighten, so overlapping shells read as separate.
98
+ *
99
+ * @param cosViewAngle `dot(normal, viewDir)` — cosine of the angle between the
100
+ * surface normal and the view direction, in [-1, 1].
101
+ */
102
+ function occluderFresnelRim(cosViewAngle) {
103
+ const { RIM_POWER, RIM_STRENGTH } = OCCLUDER_DEPTH_SHADE;
104
+ return RIM_STRENGTH * Math.pow(1 - Math.abs(cosViewAngle), RIM_POWER);
105
+ }
106
+ /** A number as a GLSL float literal (fixed decimals so it never reads as int). */
107
+ function glslFloat(value) {
108
+ return value.toFixed(4);
109
+ }
110
+ /**
111
+ * The fragment-shader snippet the depth-shaded material injects before
112
+ * `#include <opaque_fragment>` in three.js's matcap fragment shader — the
113
+ * point where `outgoingLight` (matcap-lit color), `normal` and `viewDir` /
114
+ * `vViewPosition` are all in scope. Exported so tests can pin the exact
115
+ * injected code. Constants are baked as literals (no uniforms — they are
116
+ * module constants by design, see {@link OCCLUDER_DEPTH_SHADE}).
117
+ */
118
+ function buildOccluderDepthShadeSnippet() {
119
+ const { FADE_START_M, FADE_END_M, FADE_MIN_BRIGHTNESS, RIM_POWER } = OCCLUDER_DEPTH_SHADE;
120
+ const { RIM_STRENGTH } = OCCLUDER_DEPTH_SHADE;
121
+ const [r, g, b] = FADE_TARGET_RGB;
122
+ return [
123
+ "// Occluder debug depth-shading: distance fade + fresnel rim (2026-07-02).",
124
+ `float dbgDistance = length( vViewPosition );`,
125
+ `float dbgFade = 1.0 - ( 1.0 - ${glslFloat(FADE_MIN_BRIGHTNESS)} ) * smoothstep( ${glslFloat(FADE_START_M)}, ${glslFloat(FADE_END_M)}, dbgDistance );`,
126
+ `outgoingLight = mix( vec3( ${glslFloat(r)}, ${glslFloat(g)}, ${glslFloat(b)} ), outgoingLight, dbgFade );`,
127
+ `float dbgRim = ${glslFloat(RIM_STRENGTH)} * pow( 1.0 - abs( dot( normal, viewDir ) ), ${glslFloat(RIM_POWER)} );`,
128
+ `outgoingLight += vec3( 1.0 ) * dbgRim;`
129
+ ].join("\n ");
130
+ }
131
+ /**
132
+ * Build a tiny procedural matcap texture (a shaded sphere with a specular
133
+ * highlight) so the debug skin reads as a **shiny** surface with **no scene
134
+ * lights** — `MeshMatcapMaterial` bakes the lighting into this lookup. Generated
135
+ * from a typed array (no canvas/WebGL), so it works headless in tests too.
136
+ */
137
+ function createOccluderDebugMatcap() {
138
+ const size = 64;
139
+ const data = new Uint8Array(size * size * 4);
140
+ const lx = .4;
141
+ const ly = .5;
142
+ const lz = .75;
143
+ const llen = Math.hypot(lx, ly, lz);
144
+ for (let j = 0; j < size; j++) for (let i = 0; i < size; i++) {
145
+ const idx = (j * size + i) * 4;
146
+ const nx = (i + .5) / size * 2 - 1;
147
+ const ny = (j + .5) / size * 2 - 1;
148
+ const r2 = nx * nx + ny * ny;
149
+ let r = 12;
150
+ let g = 12;
151
+ let b = 14;
152
+ if (r2 <= 1) {
153
+ const nz = Math.sqrt(1 - r2);
154
+ const ndl = Math.max(0, (nx * lx + ny * ly + nz * lz) / llen);
155
+ const diff = .25 + .75 * ndl;
156
+ const spec = Math.pow(ndl, 32);
157
+ r = Math.min(255, 255 * (.15 * diff + spec));
158
+ g = Math.min(255, 255 * (.55 * diff + spec));
159
+ b = Math.min(255, 255 * (.78 * diff + spec));
160
+ }
161
+ data[idx] = r;
162
+ data[idx + 1] = g;
163
+ data[idx + 2] = b;
164
+ data[idx + 3] = 255;
165
+ }
166
+ const tex = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
167
+ tex.needsUpdate = true;
168
+ return tex;
169
+ }
170
+ /**
171
+ * A depth-only occlusion mesh that rebuilds from an occupancy-grid snapshot.
172
+ * Mirrors {@link OccupancyCubesVisualizer}'s lifecycle (inject parent, `update`,
173
+ * `clear`, `dispose`) so the recorder can wire it the same way as the cubes.
174
+ */
175
+ var OcclusionMesh = class {
176
+ arSpaceNode;
177
+ greedy;
178
+ mode;
179
+ material;
180
+ mesh;
181
+ geometry;
182
+ lastAabbs = [];
183
+ disposed = false;
184
+ debugStyle = "off";
185
+ shadedSkin = null;
186
+ wireframeSkin = null;
187
+ matcapTexture = null;
188
+ matcapMaterial = null;
189
+ depthShadedMaterial = null;
190
+ wireframeMaterial = null;
191
+ /**
192
+ * @param arSpaceNode the AR-odometry-NUE node that receives the alignment
193
+ * matrix (`arWorldGroup` live, `replaySceneState.arWorldGroup` in replay).
194
+ */
195
+ constructor(arSpaceNode, options = {}) {
196
+ this.arSpaceNode = arSpaceNode;
197
+ this.greedy = options.greedy ?? true;
198
+ this.mode = options.mode;
199
+ this.geometry = new THREE.BufferGeometry();
200
+ this.material = new THREE.MeshBasicMaterial({
201
+ colorWrite: false,
202
+ depthWrite: true
203
+ });
204
+ this.mesh = new THREE.Mesh(this.geometry, this.material);
205
+ this.mesh.name = MESH_NAME;
206
+ this.mesh.renderOrder = options.renderOrder ?? DEFAULT_RENDER_ORDER;
207
+ this.mesh.frustumCulled = false;
208
+ this.mesh.matrixAutoUpdate = false;
209
+ this.mesh.matrix.copy(WEBXR_TO_NUE);
210
+ this.arSpaceNode.add(this.mesh);
211
+ }
212
+ /** The number of triangles currently drawn. */
213
+ getTriangleCount() {
214
+ const index = this.geometry.getIndex();
215
+ return index ? index.count / 3 : 0;
216
+ }
217
+ /** The AABB list from the most recent {@link update} (physics export hook). */
218
+ getAabbs() {
219
+ return this.lastAabbs;
220
+ }
221
+ /**
222
+ * Re-mesh from a fresh occupied-cell snapshot. Pass
223
+ * `grid.getOccupiedCells(occupancy.minConfidence)` so the occluder shares the
224
+ * same noise floor as the cubes and the COLMAP export.
225
+ *
226
+ * @param getCellPoint optional per-cell measured-centroid provider
227
+ * (`grid.getCellPoint`); only consumed when this occluder was constructed
228
+ * with `mode: 'smooth'` (otherwise ignored). When omitted under `'smooth'`,
229
+ * the surface nets falls back to cell centres.
230
+ */
231
+ update(cells, cellSizeM, getCellPoint) {
232
+ if (this.disposed) return;
233
+ const { positions, indices, aabbs } = meshOccupiedCells(cells, cellSizeM, this.mode ? {
234
+ mode: this.mode,
235
+ getCellPoint
236
+ } : { greedy: this.greedy });
237
+ this.lastAabbs = aabbs;
238
+ this.swapGeometry(positions, indices);
239
+ }
240
+ /**
241
+ * Apply **precomputed** geometry (positions/indices) without meshing — the
242
+ * entry point for the Web Worker offload: the driver posts the occupied-cell
243
+ * snapshot to a worker (`packMeshRequest`/`runMeshRequest`), and this swaps in
244
+ * the returned typed arrays off the render-critical path. Byte-identical result
245
+ * to {@link update} for the same input.
246
+ *
247
+ * The AABB physics hook is NOT populated on this path (the worker returns
248
+ * geometry only; AABBs are an export hook unused by rendering) — {@link getAabbs}
249
+ * returns empty here. Use the synchronous {@link update} if AABBs are needed.
250
+ */
251
+ applyMeshData(positions, indices) {
252
+ if (this.disposed) return;
253
+ this.lastAabbs = [];
254
+ this.swapGeometry(positions, indices);
255
+ }
256
+ /**
257
+ * Replace the geometry wholesale from typed arrays — a full rebuild is the
258
+ * simple first cut; dispose the old buffers to avoid leaking GPU memory across
259
+ * refreshes.
260
+ */
261
+ swapGeometry(positions, indices) {
262
+ const next = new THREE.BufferGeometry();
263
+ next.setAttribute("position", new THREE.BufferAttribute(positions, 3));
264
+ next.setIndex(new THREE.BufferAttribute(indices, 1));
265
+ if (styleNeedsNormals(this.debugStyle)) next.computeVertexNormals();
266
+ this.geometry.dispose();
267
+ this.geometry = next;
268
+ this.mesh.geometry = next;
269
+ this.rebindSkinGeometry(next);
270
+ }
271
+ /** Point every active debug skin at the (new) shared geometry. */
272
+ rebindSkinGeometry(geometry) {
273
+ if (this.shadedSkin) this.shadedSkin.geometry = geometry;
274
+ if (this.wireframeSkin) this.wireframeSkin.geometry = geometry;
275
+ }
276
+ /**
277
+ * Select which **visible debug skin(s)** render the occluder mesh so its
278
+ * shape and structure can be judged on-device (2026-07-02 debug-viz-styles
279
+ * plan): the original `'matcap'` skin, a `'depth-shaded'` variant (distance
280
+ * fade + fresnel rim, separates overlapping layers), a `'wireframe'` overlay
281
+ * (the raw triangulation as GL lines), the combined
282
+ * `'depth-shaded-wireframe'`, or `'off'`.
283
+ *
284
+ * Additive by design: every style adds/removes separate skin meshes sharing
285
+ * the occluder's geometry and **never touches the invisible depth-only
286
+ * mesh**, so occlusion is byte-for-byte unchanged whichever style is active.
287
+ * The skins are `transparent` with `depthWrite:false` (the depth-only mesh
288
+ * already wrote the occluding depth), so they just paint where the occluder
289
+ * is the nearest geometry; the wireframe draws at renderOrder 1 so its lines
290
+ * overlay the shaded surface in the combined style.
291
+ *
292
+ * Vertex normals (the mesher emits none) are computed only for the
293
+ * matcap-based styles — `'wireframe'` is unlit, so like `'off'` it keeps the
294
+ * remesh path normal-free.
295
+ *
296
+ * Only meaningful when this occluder is actually meshing the grid (it is the
297
+ * persistent occluder's mesh); setting a style on an empty/disabled occluder
298
+ * is a harmless no-op until {@link update} feeds geometry.
299
+ */
300
+ setDebugStyle(style) {
301
+ if (this.disposed || style === this.debugStyle) return;
302
+ this.debugStyle = style;
303
+ this.applyShadedSkin(this.shadedMaterialFor(style));
304
+ this.applyWireframeSkin(style === "wireframe" || style === "depth-shaded-wireframe");
305
+ }
306
+ /** Which matcap-based material the style shades with (null = no shaded skin). */
307
+ shadedMaterialFor(style) {
308
+ if (style === "matcap") return this.getMatcapMaterial();
309
+ if (style === "depth-shaded" || style === "depth-shaded-wireframe") return this.getDepthShadedMaterial();
310
+ return null;
311
+ }
312
+ /** Add/retarget/remove the single shaded skin node for the desired material. */
313
+ applyShadedSkin(material) {
314
+ if (material) {
315
+ this.geometry.computeVertexNormals();
316
+ if (this.shadedSkin) this.shadedSkin.material = material;
317
+ else {
318
+ this.shadedSkin = this.createSkinMesh(DEBUG_MESH_NAME, 0, material);
319
+ this.arSpaceNode.add(this.shadedSkin);
320
+ }
321
+ } else if (this.shadedSkin) {
322
+ this.arSpaceNode.remove(this.shadedSkin);
323
+ this.shadedSkin = null;
324
+ }
325
+ }
326
+ /** Add/remove the wireframe skin — drawn after the shaded skin (renderOrder
327
+ * 1) so its lines sit on top of the surface in the combined style. */
328
+ applyWireframeSkin(want) {
329
+ if (want) {
330
+ if (!this.wireframeSkin) {
331
+ this.wireframeSkin = this.createSkinMesh(DEBUG_WIREFRAME_MESH_NAME, 1, this.getWireframeMaterial());
332
+ this.arSpaceNode.add(this.wireframeSkin);
333
+ }
334
+ } else if (this.wireframeSkin) {
335
+ this.arSpaceNode.remove(this.wireframeSkin);
336
+ this.wireframeSkin = null;
337
+ }
338
+ }
339
+ /**
340
+ * Toggle the matcap debug rendering of the occluder mesh.
341
+ *
342
+ * @deprecated Superseded by {@link setDebugStyle} (2026-07-02) — this is a
343
+ * thin wrapper mapping `enabled ? 'matcap' : 'off'`, kept so existing
344
+ * consumers of the boolean API keep working unchanged.
345
+ */
346
+ setDebugVisualization(enabled) {
347
+ this.setDebugStyle(enabled ? "matcap" : "off");
348
+ }
349
+ /** A debug skin mesh sharing the occluder's geometry, transform and culling
350
+ * behaviour — only name, renderOrder and material differ per skin. */
351
+ createSkinMesh(name, renderOrder, material) {
352
+ const skin = new THREE.Mesh(this.geometry, material);
353
+ skin.name = name;
354
+ skin.renderOrder = renderOrder;
355
+ skin.frustumCulled = false;
356
+ skin.matrixAutoUpdate = false;
357
+ skin.matrix.copy(WEBXR_TO_NUE);
358
+ return skin;
359
+ }
360
+ /** The procedural matcap texture, shared by both matcap-based materials. */
361
+ getMatcapTexture() {
362
+ if (!this.matcapTexture) this.matcapTexture = createOccluderDebugMatcap();
363
+ return this.matcapTexture;
364
+ }
365
+ getMatcapMaterial() {
366
+ if (!this.matcapMaterial) this.matcapMaterial = new THREE.MeshMatcapMaterial({
367
+ matcap: this.getMatcapTexture(),
368
+ transparent: true,
369
+ opacity: DEBUG_SKIN_OPACITY,
370
+ depthWrite: false
371
+ });
372
+ return this.matcapMaterial;
373
+ }
374
+ getDepthShadedMaterial() {
375
+ if (!this.depthShadedMaterial) {
376
+ const material = new THREE.MeshMatcapMaterial({
377
+ matcap: this.getMatcapTexture(),
378
+ transparent: true,
379
+ opacity: DEBUG_SKIN_OPACITY,
380
+ depthWrite: false
381
+ });
382
+ material.onBeforeCompile = (shader) => {
383
+ shader.fragmentShader = shader.fragmentShader.replace("#include <opaque_fragment>", `${buildOccluderDepthShadeSnippet()}\n\t#include <opaque_fragment>`);
384
+ };
385
+ material.customProgramCacheKey = () => "occluder-depth-shaded";
386
+ this.depthShadedMaterial = material;
387
+ }
388
+ return this.depthShadedMaterial;
389
+ }
390
+ getWireframeMaterial() {
391
+ if (!this.wireframeMaterial) this.wireframeMaterial = new THREE.MeshBasicMaterial({
392
+ wireframe: true,
393
+ color: WIREFRAME_COLOR,
394
+ transparent: true,
395
+ opacity: WIREFRAME_OPACITY,
396
+ depthWrite: false
397
+ });
398
+ return this.wireframeMaterial;
399
+ }
400
+ /** Empty the mesh (e.g. on store swap); the node stays in the scene. */
401
+ clear() {
402
+ if (this.disposed) return;
403
+ const next = new THREE.BufferGeometry();
404
+ this.geometry.dispose();
405
+ this.geometry = next;
406
+ this.mesh.geometry = next;
407
+ this.rebindSkinGeometry(next);
408
+ this.lastAabbs = [];
409
+ }
410
+ /** Remove the mesh from its parent and release GPU resources. */
411
+ dispose() {
412
+ if (this.disposed) return;
413
+ this.disposed = true;
414
+ this.arSpaceNode.remove(this.mesh);
415
+ if (this.shadedSkin) {
416
+ this.arSpaceNode.remove(this.shadedSkin);
417
+ this.shadedSkin = null;
418
+ }
419
+ if (this.wireframeSkin) {
420
+ this.arSpaceNode.remove(this.wireframeSkin);
421
+ this.wireframeSkin = null;
422
+ }
423
+ this.matcapTexture?.dispose();
424
+ this.matcapTexture = null;
425
+ this.matcapMaterial?.dispose();
426
+ this.matcapMaterial = null;
427
+ this.depthShadedMaterial?.dispose();
428
+ this.depthShadedMaterial = null;
429
+ this.wireframeMaterial?.dispose();
430
+ this.wireframeMaterial = null;
431
+ this.geometry.dispose();
432
+ this.material.dispose();
433
+ this.lastAabbs = [];
434
+ }
435
+ };
436
+ //#endregion
437
+ export { OCCLUDER_DEPTH_SHADE, OcclusionMesh, buildOccluderDepthShadeSnippet, occluderDepthFade, occluderFresnelRim };
@@ -1,2 +1,2 @@
1
- import { n as disposeMeshArray, r as disposeObject3D, t as DisposeOptions } from "../three-dispose-Bt1k7WKh.js";
1
+ import { n as disposeMeshArray, r as disposeObject3D, t as DisposeOptions } from "../three-dispose-BMOAmPhb.js";
2
2
  export { DisposeOptions, disposeMeshArray, disposeObject3D };
@@ -1,2 +1,2 @@
1
- import { t as VIS_COLORS } from "../vis-colors-Obw2pLqT.js";
1
+ import { t as VIS_COLORS } from "../vis-colors-C7HI0u9_.js";
2
2
  export { VIS_COLORS };