@woosh/meep-engine 2.168.0 → 2.169.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 (295) hide show
  1. package/README.md +2 -2
  2. package/build/bundle-worker-image-decoder.js +1 -1
  3. package/package.json +1 -1
  4. package/src/REVIEW_2026_08_06/adv-core.md +511 -0
  5. package/src/REVIEW_2026_08_06/adv-gfx.md +480 -0
  6. package/src/REVIEW_2026_08_06/adv-view.md +446 -0
  7. package/src/REVIEW_2026_08_06/api-core.md +296 -0
  8. package/src/REVIEW_2026_08_06/api-engine.md +195 -0
  9. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -0
  10. package/src/REVIEW_2026_08_06/core-binary.md +348 -0
  11. package/src/REVIEW_2026_08_06/core-collection.md +376 -0
  12. package/src/REVIEW_2026_08_06/core-math.md +221 -0
  13. package/src/REVIEW_2026_08_06/core-process.md +266 -0
  14. package/src/REVIEW_2026_08_06/core-science.md +223 -0
  15. package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -0
  16. package/src/REVIEW_2026_08_06/engine-animation.md +167 -0
  17. package/src/REVIEW_2026_08_06/engine-asset.md +166 -0
  18. package/src/REVIEW_2026_08_06/engine-ecs.md +920 -0
  19. package/src/REVIEW_2026_08_06/engine-input.md +197 -0
  20. package/src/REVIEW_2026_08_06/engine-misc.md +248 -0
  21. package/src/REVIEW_2026_08_06/engine-terrain.md +52 -0
  22. package/src/REVIEW_2026_08_06/generation.md +176 -0
  23. package/src/REVIEW_2026_08_06/geom-2d.md +434 -0
  24. package/src/REVIEW_2026_08_06/geom-3d.md +117 -0
  25. package/src/REVIEW_2026_08_06/gfx-gi.md +143 -0
  26. package/src/REVIEW_2026_08_06/gfx-render.md +740 -0
  27. package/src/REVIEW_2026_08_06/gfx-texture.md +690 -0
  28. package/src/REVIEW_2026_08_06/prior-criticals.md +49 -0
  29. package/src/REVIEW_2026_08_06/prior-highs.md +138 -0
  30. package/src/REVIEW_2026_08_06/test-gaps.md +364 -0
  31. package/src/REVIEW_2026_08_06/view.md +667 -0
  32. package/src/REVIEW_2026_08_06.md +571 -0
  33. package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
  34. package/src/core/binary/BinaryBuffer.js +12 -1
  35. package/src/core/binary/BitImage2.d.ts +5 -1
  36. package/src/core/binary/BitImage2.d.ts.map +1 -1
  37. package/src/core/binary/BitImage2.js +5 -1
  38. package/src/core/bvh8/build/NodeProxy.d.ts.map +1 -1
  39. package/src/core/bvh8/build/NodeProxy.js +314 -308
  40. package/src/core/collection/list/List.d.ts +15 -0
  41. package/src/core/collection/list/List.d.ts.map +1 -1
  42. package/src/core/collection/list/List.js +34 -0
  43. package/src/core/collection/list/SortedListProjection.d.ts.map +1 -1
  44. package/src/core/collection/list/SortedListProjection.js +4 -1
  45. package/src/core/collection/map/BiMap.d.ts +25 -2
  46. package/src/core/collection/map/BiMap.d.ts.map +1 -1
  47. package/src/core/collection/map/BiMap.js +57 -3
  48. package/src/core/debug/matchers/IsAnything.d.ts.map +1 -1
  49. package/src/core/debug/matchers/IsAnything.js +5 -1
  50. package/src/core/events/signal/Signal.d.ts.map +1 -1
  51. package/src/core/events/signal/Signal.js +3 -1
  52. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -0
  53. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.d.ts +31 -0
  54. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.d.ts.map +1 -0
  55. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -0
  56. package/src/core/geom/2d/polygon/polygon2_signed_area.d.ts +24 -2
  57. package/src/core/geom/2d/polygon/polygon2_signed_area.d.ts.map +1 -1
  58. package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -34
  59. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.d.ts +28 -0
  60. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.d.ts.map +1 -0
  61. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -0
  62. package/src/core/geom/2d/polygon/polygon2_triangulate.d.ts +40 -0
  63. package/src/core/geom/2d/polygon/polygon2_triangulate.d.ts.map +1 -0
  64. package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -0
  65. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.d.ts +28 -0
  66. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.d.ts.map +1 -0
  67. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -0
  68. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.d.ts +6 -3
  69. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.d.ts.map +1 -1
  70. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +26 -13
  71. package/src/core/geom/2d/triangle/tri2_signed_area.d.ts +17 -1
  72. package/src/core/geom/2d/triangle/tri2_signed_area.d.ts.map +1 -1
  73. package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -23
  74. package/src/core/geom/2d/v2_morton_encode.d.ts +20 -0
  75. package/src/core/geom/2d/v2_morton_encode.d.ts.map +1 -0
  76. package/src/core/geom/2d/v2_morton_encode.js +23 -0
  77. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.d.ts.map +1 -1
  78. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +4 -1
  79. package/src/core/geom/3d/polygon/polygon3_compute_normal.d.ts +27 -0
  80. package/src/core/geom/3d/polygon/polygon3_compute_normal.d.ts.map +1 -0
  81. package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -0
  82. package/src/core/geom/3d/polygon/polygon3_triangulate.d.ts +34 -0
  83. package/src/core/geom/3d/polygon/polygon3_triangulate.d.ts.map +1 -0
  84. package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -0
  85. package/src/core/geom/3d/quaternion/quat3_multiply.d.ts +8 -2
  86. package/src/core/geom/3d/quaternion/quat3_multiply.d.ts.map +1 -1
  87. package/src/core/geom/3d/quaternion/quat3_multiply.js +8 -2
  88. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  89. package/src/core/geom/3d/shape/ConvexHullShape3D.js +32 -2
  90. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.d.ts +23 -6
  91. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.d.ts.map +1 -1
  92. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -124
  93. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.d.ts +17 -0
  94. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.d.ts.map +1 -0
  95. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -0
  96. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.d.ts +20 -0
  97. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.d.ts.map +1 -0
  98. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.js +40 -0
  99. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.d.ts +19 -0
  100. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.d.ts.map +1 -0
  101. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.js +34 -0
  102. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.d.ts +22 -0
  103. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.d.ts.map +1 -0
  104. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.js +37 -0
  105. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.d.ts +87 -0
  106. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.d.ts.map +1 -0
  107. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -0
  108. package/src/core/geom/3d/triangle/tri3_compute_signed_volume.d.ts +32 -0
  109. package/src/core/geom/3d/triangle/tri3_compute_signed_volume.d.ts.map +1 -1
  110. package/src/core/geom/3d/triangle/tri3_compute_signed_volume.js +66 -7
  111. package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.d.ts +22 -1
  112. package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.d.ts.map +1 -1
  113. package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.js +83 -38
  114. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -0
  115. package/src/core/geom/vec3/v3_cotangent.d.ts +6 -2
  116. package/src/core/geom/vec3/v3_cotangent.d.ts.map +1 -1
  117. package/src/core/geom/vec3/v3_cotangent.js +6 -2
  118. package/src/core/geom/vec3/v3_cross.d.ts +6 -2
  119. package/src/core/geom/vec3/v3_cross.d.ts.map +1 -1
  120. package/src/core/geom/vec3/v3_cross.js +6 -2
  121. package/src/core/geom/vec3/v3_multiply.d.ts +6 -2
  122. package/src/core/geom/vec3/v3_multiply.d.ts.map +1 -1
  123. package/src/core/geom/vec3/v3_multiply.js +6 -2
  124. package/src/core/geom/vec3/v3_subtract.d.ts +6 -2
  125. package/src/core/geom/vec3/v3_subtract.d.ts.map +1 -1
  126. package/src/core/geom/vec3/v3_subtract.js +6 -2
  127. package/src/core/localization/Localization.js +1 -1
  128. package/src/core/math/random/randomSeed.d.ts +13 -0
  129. package/src/core/math/random/randomSeed.d.ts.map +1 -0
  130. package/src/core/math/random/randomSeed.js +16 -0
  131. package/src/core/math/spline/spline3_hermite_apply_transform.d.ts +50 -0
  132. package/src/core/math/spline/spline3_hermite_apply_transform.d.ts.map +1 -0
  133. package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -0
  134. package/src/core/math/spline/spline3_hermite_reverse.d.ts +34 -0
  135. package/src/core/math/spline/spline3_hermite_reverse.d.ts.map +1 -0
  136. package/src/core/math/spline/spline3_hermite_reverse.js +49 -0
  137. package/src/core/math/statistics/hammersley_sequence.d.ts +17 -4
  138. package/src/core/math/statistics/hammersley_sequence.d.ts.map +1 -1
  139. package/src/core/math/statistics/hammersley_sequence.js +25 -16
  140. package/src/core/process/Future.d.ts.map +1 -1
  141. package/src/core/process/Future.js +12 -0
  142. package/src/core/process/PromiseWatcher.d.ts.map +1 -1
  143. package/src/core/process/PromiseWatcher.js +9 -2
  144. package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
  145. package/src/core/process/executor/ConcurrentExecutor.js +12 -2
  146. package/src/core/process/worker/WorkerProxy.d.ts.map +1 -1
  147. package/src/core/process/worker/WorkerProxy.js +5 -2
  148. package/src/core/process/worker/extractTransferables.d.ts.map +1 -1
  149. package/src/core/process/worker/extractTransferables.js +4 -1
  150. package/src/engine/Engine.d.ts +12 -12
  151. package/src/engine/Engine.d.ts.map +1 -1
  152. package/src/engine/Engine.js +149 -10
  153. package/src/engine/EngineHarness.d.ts.map +1 -1
  154. package/src/engine/EngineHarness.js +4 -1
  155. package/src/engine/asset/loaders/ArrayBufferLoader.d.ts.map +1 -1
  156. package/src/engine/asset/loaders/ArrayBufferLoader.js +7 -0
  157. package/src/engine/ecs/speaker/lines/sets/LineSetDescription.d.ts.map +1 -1
  158. package/src/engine/ecs/speaker/lines/sets/LineSetDescription.js +3 -1
  159. package/src/engine/ecs/terrain/ecs/TerrainSystem.d.ts.map +1 -1
  160. package/src/engine/ecs/terrain/ecs/TerrainSystem.js +4 -1
  161. package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts +29 -0
  162. package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts.map +1 -1
  163. package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +84 -23
  164. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -0
  165. package/src/engine/graphics/GraphicsEngine.d.ts +56 -7
  166. package/src/engine/graphics/GraphicsEngine.d.ts.map +1 -1
  167. package/src/engine/graphics/GraphicsEngine.js +196 -24
  168. package/src/engine/graphics/context/WebGLContextFailureReason.d.ts +9 -0
  169. package/src/engine/graphics/context/WebGLContextFailureReason.d.ts.map +1 -0
  170. package/src/engine/graphics/context/WebGLContextFailureReason.js +17 -0
  171. package/src/engine/graphics/context/WebGLContextMonitor.d.ts +106 -0
  172. package/src/engine/graphics/context/WebGLContextMonitor.d.ts.map +1 -0
  173. package/src/engine/graphics/context/WebGLContextMonitor.js +341 -0
  174. package/src/engine/graphics/context/WebGLContextState.d.ts +14 -0
  175. package/src/engine/graphics/context/WebGLContextState.d.ts.map +1 -0
  176. package/src/engine/graphics/context/WebGLContextState.js +24 -0
  177. package/src/engine/graphics/context/testWebGLContextLoss.d.ts +2 -0
  178. package/src/engine/graphics/context/testWebGLContextLoss.d.ts.map +1 -0
  179. package/src/engine/graphics/context/testWebGLContextLoss.js +696 -0
  180. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.d.ts.map +1 -1
  181. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -32
  182. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.d.ts +14 -0
  183. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.d.ts.map +1 -0
  184. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.js +41 -0
  185. package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.js +1 -1
  186. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.d.ts.map +1 -1
  187. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +5 -0
  188. package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts +5 -1
  189. package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
  190. package/src/engine/graphics/ecs/trail2d/Trail2D.js +15 -0
  191. package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts +1 -1
  192. package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts.map +1 -1
  193. package/src/engine/graphics/ecs/trail2d/Trail2DSystem.js +2 -1
  194. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts +4 -1
  195. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts.map +1 -1
  196. package/src/engine/graphics/ecs/trail3d/Trail3D.js +488 -476
  197. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts +1 -1
  198. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts.map +1 -1
  199. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.js +280 -253
  200. package/src/engine/graphics/geometry/MikkT/BuildNeighborsFast.d.ts.map +1 -1
  201. package/src/engine/graphics/geometry/MikkT/BuildNeighborsFast.js +3 -2
  202. package/src/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.d.ts.map +1 -1
  203. package/src/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.js +3 -1
  204. package/src/engine/graphics/geometry/computeMeshSurfaceArea.js +1 -1
  205. package/src/engine/graphics/load_and_set_cubemap_v0.d.ts +5 -0
  206. package/src/engine/graphics/load_and_set_cubemap_v0.d.ts.map +1 -1
  207. package/src/engine/graphics/load_and_set_cubemap_v0.js +26 -21
  208. package/src/engine/graphics/particles/particular/group/ParticleGroup.d.ts.map +1 -1
  209. package/src/engine/graphics/particles/particular/group/ParticleGroup.js +19 -8
  210. package/src/engine/graphics/particles/particular/group/optimizeCommandQueue.js +1 -1
  211. package/src/engine/graphics/texture/atlas/AtlasLookupTexture.d.ts.map +1 -1
  212. package/src/engine/graphics/texture/atlas/AtlasLookupTexture.js +10 -3
  213. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
  214. package/src/engine/graphics/texture/atlas/TextureAtlas.js +4 -1
  215. package/src/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.d.ts.map +1 -1
  216. package/src/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.js +11 -0
  217. package/src/engine/graphics/texture/cubemap/load_environment_map.d.ts.map +1 -1
  218. package/src/engine/graphics/texture/cubemap/load_environment_map.js +5 -2
  219. package/src/engine/graphics/texture/sampler/distance/computeSignedDistanceField_Chamfer.d.ts.map +1 -1
  220. package/src/engine/graphics/texture/sampler/distance/computeSignedDistanceField_Chamfer.js +26 -18
  221. package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.d.ts +15 -2
  222. package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.d.ts.map +1 -1
  223. package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.js +29 -3
  224. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.d.ts +11 -1
  225. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.d.ts.map +1 -1
  226. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +158 -171
  227. package/src/engine/graphics/texture/sampler/sampler2d_paint.js +1 -1
  228. package/src/engine/graphics/texture/sampler/util/bitSet2Sampler2D.js +2 -2
  229. package/src/engine/input/ecs/util/TopDownCameraControllerHelper.d.ts.map +1 -1
  230. package/src/engine/input/ecs/util/TopDownCameraControllerHelper.js +10 -3
  231. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.d.ts +14 -0
  232. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.d.ts.map +1 -0
  233. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.js +38 -0
  234. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  235. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +433 -425
  236. package/src/engine/physics/constraint/solve_constraints.d.ts +4 -1
  237. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  238. package/src/engine/physics/constraint/solve_constraints.js +38 -13
  239. package/src/engine/physics/contact/ManifoldStore.d.ts +15 -3
  240. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  241. package/src/engine/physics/contact/ManifoldStore.js +15 -3
  242. package/src/engine/physics/ecs/Joint.d.ts +7 -4
  243. package/src/engine/physics/ecs/Joint.d.ts.map +1 -1
  244. package/src/engine/physics/ecs/Joint.js +7 -4
  245. package/src/engine/physics/ecs/PhysicsSystem.d.ts +14 -0
  246. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  247. package/src/engine/physics/ecs/PhysicsSystem.js +16 -0
  248. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  249. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
  250. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts +15 -7
  251. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
  252. package/src/engine/physics/narrowphase/convex_convex_manifold.js +33 -10
  253. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts +6 -2
  254. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
  255. package/src/engine/physics/narrowphase/refine_ray_concave.js +6 -2
  256. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +6 -2
  257. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  258. package/src/engine/physics/narrowphase/refine_ray_hit.js +6 -2
  259. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  260. package/src/engine/physics/queries/raycast.js +11 -4
  261. package/src/engine/save/storage/IndexedDBStorage.d.ts.map +1 -1
  262. package/src/engine/save/storage/IndexedDBStorage.js +21 -3
  263. package/src/engine/simulation/Ticker.d.ts +12 -0
  264. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  265. package/src/engine/simulation/Ticker.js +18 -0
  266. package/src/engine/sound/simulation/AcousticSimulator.d.ts.map +1 -1
  267. package/src/engine/sound/simulation/AcousticSimulator.js +9 -1
  268. package/src/engine/sound/simulation/core/VolumeField.d.ts +6 -2
  269. package/src/engine/sound/simulation/core/VolumeField.d.ts.map +1 -1
  270. package/src/engine/sound/simulation/core/VolumeField.js +6 -2
  271. package/src/engine/sound/simulation/probe/AcousticProbeField.d.ts +6 -2
  272. package/src/engine/sound/simulation/probe/AcousticProbeField.d.ts.map +1 -1
  273. package/src/engine/sound/simulation/probe/AcousticProbeField.js +6 -2
  274. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.d.ts +6 -2
  275. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.d.ts.map +1 -1
  276. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +6 -2
  277. package/src/engine/sound/simulation/probe/bakeProbeReflectors.d.ts +3 -1
  278. package/src/engine/sound/simulation/probe/bakeProbeReflectors.d.ts.map +1 -1
  279. package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +3 -1
  280. package/src/engine/ui/GUIEngine.d.ts.map +1 -1
  281. package/src/engine/ui/GUIEngine.js +8 -1
  282. package/src/generation/theme/TerrainTheme.d.ts.map +1 -1
  283. package/src/generation/theme/TerrainTheme.js +2 -1
  284. package/src/generation/theme/ThemeEngine.d.ts.map +1 -1
  285. package/src/generation/theme/ThemeEngine.js +6 -5
  286. package/src/view/ViewGroup.d.ts.map +1 -1
  287. package/src/view/ViewGroup.js +9 -7
  288. package/src/view/graphics/WebGLContextFailureView.d.ts +19 -0
  289. package/src/view/graphics/WebGLContextFailureView.d.ts.map +1 -0
  290. package/src/view/graphics/WebGLContextFailureView.js +76 -0
  291. package/src/view/minimap/Minimap.d.ts +8 -0
  292. package/src/view/minimap/Minimap.d.ts.map +1 -1
  293. package/src/view/minimap/Minimap.js +16 -3
  294. package/src/view/minimap/dom/MinimapMarkerView.d.ts.map +1 -1
  295. package/src/view/minimap/dom/MinimapMarkerView.js +3 -1
@@ -0,0 +1,1280 @@
1
+ import { assert } from "../../../assert.js";
2
+ import { UINT32_MAX } from "../../../binary/UINT32_MAX.js";
3
+ import { orient2d_indexed } from "../convex-hull/orient2d_indexed.js";
4
+ import { v2_morton_encode } from "../v2_morton_encode.js";
5
+ import { polygon2_is_counter_clockwise } from "./polygon2_is_counter_clockwise.js";
6
+
7
+ /**
8
+ * Sentinel for "no node", matching the codebase-wide typed-array pointer
9
+ * convention (`NULL_POINTER` in {@link BinaryTopology} is the same value).
10
+ * @type {number}
11
+ */
12
+ const NULL_NODE = UINT32_MAX;
13
+
14
+ /**
15
+ * Vertex count above which the point-in-ear test switches from a full ring walk
16
+ * to a Morton-code range walk. Below it the ring is short enough that the walk is
17
+ * cheaper than maintaining the Z-order list; above it the walk is the entire cost
18
+ * of the algorithm.
19
+ *
20
+ * Not a public option: both paths compute the same triangulation, so exposing the
21
+ * choice would be a knob with no semantics. Tests assert the two agree.
22
+ * @type {number}
23
+ */
24
+ const HASH_THRESHOLD = 80;
25
+
26
+ /**
27
+ * Largest quantised coordinate fed to {@link v2_morton_encode}. 15 bits per axis,
28
+ * matching the resolution the Z-order range test can usefully distinguish.
29
+ * @type {number}
30
+ */
31
+ const MORTON_SCALE = 32767;
32
+
33
+ // --------------------------------------------------------------------------
34
+ // Ring storage.
35
+ //
36
+ // A vertex ring is a doubly-linked list held in flat typed arrays with integer
37
+ // handles -- the same shape as every other meep structure of this kind
38
+ // (`BinaryElementPool`, `BinaryTopology`, `bvh8`), and unlike the object-node
39
+ // lists the reference implementations of this algorithm use. Nodes are never
40
+ // compacted: removal unlinks, so handles stay stable for the whole call.
41
+ //
42
+ // Coordinates are copied in rather than read back through `points`, for three
43
+ // reasons: the bridge nodes created by hole elimination have no entry in
44
+ // `points`; `orient2d_indexed` wants exactly this interleaved layout; and it
45
+ // keeps no reference to caller memory alive.
46
+ //
47
+ // Module-static and grown on demand, so a call allocates nothing in the steady
48
+ // state. The consequence -- documented on the public function -- is that
49
+ // `polygon2_triangulate` is NOT re-entrant, same as `bt_mesh_simplify_by_error`.
50
+ // --------------------------------------------------------------------------
51
+
52
+ /** Interleaved (x, y) per node. The array `orient2d_indexed` is applied to. */
53
+ let _node_xy = new Float64Array(0);
54
+ /** Index into the caller's `points` that this node reports on output. */
55
+ let _node_point = new Uint32Array(0);
56
+ let _node_next = new Uint32Array(0);
57
+ let _node_prev = new Uint32Array(0);
58
+ /** Morton code, only meaningful while the Z-order list is live. */
59
+ let _node_z = new Uint32Array(0);
60
+ let _node_znext = new Uint32Array(0);
61
+ let _node_zprev = new Uint32Array(0);
62
+ /**
63
+ * 1 for a node introduced by hole elimination rather than by the caller. Steiner
64
+ * nodes are exempt from the degenerate-point filter: they are deliberately
65
+ * coincident with another node and removing them would undo the bridge.
66
+ */
67
+ let _node_steiner = new Uint8Array(0);
68
+
69
+ /** Allocation cursor into the node arrays. */
70
+ let _node_count = 0;
71
+
72
+ /** Output buffer and write cursor for the current call. */
73
+ let _out = null;
74
+ let _out_cursor = 0;
75
+
76
+ /** Morton quantisation state for the current call. */
77
+ let _min_x = 0;
78
+ let _min_y = 0;
79
+ let _inv_size = 0;
80
+ /** True while the Z-order accelerated ear test is in use. */
81
+ let _hashed = false;
82
+
83
+ /**
84
+ * @param {number} capacity
85
+ */
86
+ function ensure_node_capacity(capacity) {
87
+ if (_node_point.length >= capacity) {
88
+ return;
89
+ }
90
+
91
+ _node_xy = new Float64Array(capacity * 2);
92
+ _node_point = new Uint32Array(capacity);
93
+ _node_next = new Uint32Array(capacity);
94
+ _node_prev = new Uint32Array(capacity);
95
+ _node_z = new Uint32Array(capacity);
96
+ _node_znext = new Uint32Array(capacity);
97
+ _node_zprev = new Uint32Array(capacity);
98
+ _node_steiner = new Uint8Array(capacity);
99
+ }
100
+
101
+ /**
102
+ * @param {number} point_index
103
+ * @param {number} x
104
+ * @param {number} y
105
+ * @returns {number} node
106
+ */
107
+ function create_node(point_index, x, y) {
108
+ const node = _node_count++;
109
+
110
+ _node_xy[node * 2] = x;
111
+ _node_xy[node * 2 + 1] = y;
112
+ _node_point[node] = point_index;
113
+ _node_next[node] = NULL_NODE;
114
+ _node_prev[node] = NULL_NODE;
115
+ _node_z[node] = 0;
116
+ _node_znext[node] = NULL_NODE;
117
+ _node_zprev[node] = NULL_NODE;
118
+ _node_steiner[node] = 0;
119
+
120
+ return node;
121
+ }
122
+
123
+ /**
124
+ * Append a node after `last` in the ring, or start a new ring when `last` is
125
+ * {@link NULL_NODE}.
126
+ *
127
+ * @param {number} point_index
128
+ * @param {number} x
129
+ * @param {number} y
130
+ * @param {number} last
131
+ * @returns {number} the new node
132
+ */
133
+ function insert_node(point_index, x, y, last) {
134
+ const node = create_node(point_index, x, y);
135
+
136
+ if (last === NULL_NODE) {
137
+ _node_next[node] = node;
138
+ _node_prev[node] = node;
139
+
140
+ return node;
141
+ }
142
+
143
+ const after = _node_next[last];
144
+
145
+ _node_next[node] = after;
146
+ _node_prev[node] = last;
147
+ _node_prev[after] = node;
148
+ _node_next[last] = node;
149
+
150
+ return node;
151
+ }
152
+
153
+ /**
154
+ * Unlink `node` from the ring and from the Z-order list.
155
+ * @param {number} node
156
+ */
157
+ function remove_node(node) {
158
+ const next = _node_next[node];
159
+ const prev = _node_prev[node];
160
+
161
+ _node_prev[next] = prev;
162
+ _node_next[prev] = next;
163
+
164
+ const zprev = _node_zprev[node];
165
+ const znext = _node_znext[node];
166
+
167
+ if (zprev !== NULL_NODE) {
168
+ _node_znext[zprev] = znext;
169
+ }
170
+
171
+ if (znext !== NULL_NODE) {
172
+ _node_zprev[znext] = zprev;
173
+ }
174
+ }
175
+
176
+ function x_of(node) {
177
+ return _node_xy[node * 2];
178
+ }
179
+
180
+ function y_of(node) {
181
+ return _node_xy[node * 2 + 1];
182
+ }
183
+
184
+ /**
185
+ * Exact orientation of the node triple: **positive = counter-clockwise**, zero =
186
+ * collinear. Every combinatorial decision in this file routes through here, so
187
+ * the triangulation is decided exactly regardless of coordinate magnitude.
188
+ *
189
+ * Note the negation. Shewchuk's `orient2d` -- and therefore `robust-predicates`
190
+ * and {@link orient2d_indexed} -- returns a *negative* value for a
191
+ * counter-clockwise triple with y up (`orient2d(0,0, 1,0, 0,1) === -1`), and
192
+ * `-0` for collinear. Negating a double flips the sign exactly, so this keeps the
193
+ * predicate exact while giving this file the conventional sign. Reading the
194
+ * inverted convention into every comparison below would be a standing invitation
195
+ * to get one of them backwards.
196
+ *
197
+ * @param {number} a
198
+ * @param {number} b
199
+ * @param {number} c
200
+ * @returns {number}
201
+ */
202
+ function orient(a, b, c) {
203
+ return -orient2d_indexed(_node_xy, a, b, c);
204
+ }
205
+
206
+ /**
207
+ * @param {number} a
208
+ * @param {number} b
209
+ * @returns {boolean} true when the two nodes sit at exactly the same point
210
+ */
211
+ function nodes_equal(a, b) {
212
+ return _node_xy[a * 2] === _node_xy[b * 2] && _node_xy[a * 2 + 1] === _node_xy[b * 2 + 1];
213
+ }
214
+
215
+ /**
216
+ * Build a ring from `points[from .. to)` (vertex indices), oriented so that its
217
+ * signed area has the sign `want_positive` asks for -- counter-clockwise for the
218
+ * outer contour, clockwise for holes.
219
+ *
220
+ * Normalising here rather than demanding it of the caller is deliberate: winding
221
+ * is derivable from the data, so requiring it would be redundant state the caller
222
+ * could contradict.
223
+ *
224
+ * @param {ArrayLike<number>} points
225
+ * @param {number} from first vertex index, inclusive
226
+ * @param {number} to last vertex index, exclusive
227
+ * @param {boolean} want_positive
228
+ * @returns {number} the last node of the ring, or {@link NULL_NODE} when empty
229
+ */
230
+ function link_contour(points, from, to, want_positive) {
231
+ let last = NULL_NODE;
232
+
233
+ const count = to - from;
234
+
235
+ if (count <= 0) {
236
+ return NULL_NODE;
237
+ }
238
+
239
+ // Exactly, via one orientation predicate at a hull vertex. The shoelace sum
240
+ // cannot be used here: for a small contour, or one far from the origin, its
241
+ // sign is rounding noise, and getting the winding backwards makes every corner
242
+ // look reflex and the whole contour untriangulatable.
243
+ const is_ccw = polygon2_is_counter_clockwise(points, from, count);
244
+
245
+ if (is_ccw === want_positive) {
246
+ for (let i = from; i < to; i++) {
247
+ last = insert_node(i, points[i * 2], points[i * 2 + 1], last);
248
+ }
249
+ } else {
250
+ for (let i = to - 1; i >= from; i--) {
251
+ last = insert_node(i, points[i * 2], points[i * 2 + 1], last);
252
+ }
253
+ }
254
+
255
+ if (last !== NULL_NODE && nodes_equal(last, _node_next[last])) {
256
+ // the contour repeats its first point as its last
257
+ remove_node(last);
258
+ last = _node_next[last];
259
+ }
260
+
261
+ return last;
262
+ }
263
+
264
+ /**
265
+ * Drop nodes that are coincident with their successor or collinear with both
266
+ * neighbours -- neither can contribute a triangle, and both stall the ear search.
267
+ * Steiner nodes are kept.
268
+ *
269
+ * @param {number} start
270
+ * @param {number} end node to finish at; pass {@link NULL_NODE} for `start`
271
+ * @returns {number} a surviving node, or {@link NULL_NODE} if the ring collapsed
272
+ */
273
+ function filter_points(start, end) {
274
+ if (start === NULL_NODE) {
275
+ return NULL_NODE;
276
+ }
277
+
278
+ let stop = end === NULL_NODE ? start : end;
279
+
280
+ let p = start;
281
+ let again;
282
+
283
+ do {
284
+ again = false;
285
+
286
+ const next = _node_next[p];
287
+ const prev = _node_prev[p];
288
+
289
+ if (_node_steiner[p] === 0 && (nodes_equal(p, next) || orient(prev, p, next) === 0)) {
290
+ remove_node(p);
291
+
292
+ p = prev;
293
+ stop = prev;
294
+
295
+ if (p === _node_next[p]) {
296
+ return NULL_NODE;
297
+ }
298
+
299
+ again = true;
300
+ } else {
301
+ p = next;
302
+ }
303
+ } while (again || p !== stop);
304
+
305
+ return p;
306
+ }
307
+
308
+ /**
309
+ * Inclusive point-in-triangle for a counter-clockwise triangle (a, b, c).
310
+ *
311
+ * @param {number} a
312
+ * @param {number} b
313
+ * @param {number} c
314
+ * @param {number} p
315
+ * @returns {boolean}
316
+ */
317
+ function point_in_triangle(a, b, c, p) {
318
+ return orient(a, b, p) >= 0 && orient(b, c, p) >= 0 && orient(c, a, p) >= 0;
319
+ }
320
+
321
+ /**
322
+ * As {@link point_in_triangle}, but a point coincident with `a` does not count.
323
+ *
324
+ * Hole elimination bridges a hole into the outer ring by duplicating a vertex, so
325
+ * a point sitting exactly on `a` is an expected, legitimate configuration. Letting
326
+ * it block would make every bridged hole untriangulatable.
327
+ *
328
+ * @param {number} a
329
+ * @param {number} b
330
+ * @param {number} c
331
+ * @param {number} p
332
+ * @returns {boolean}
333
+ */
334
+ function point_in_triangle_except_first(a, b, c, p) {
335
+ return !nodes_equal(a, p) && point_in_triangle(a, b, c, p);
336
+ }
337
+
338
+ /**
339
+ * Is the segment `a -> b` inside the polygon in the immediate neighbourhood of
340
+ * `a`? Splits on whether `a` is a convex or a reflex corner.
341
+ *
342
+ * @param {number} a
343
+ * @param {number} b
344
+ * @returns {boolean}
345
+ */
346
+ function locally_inside(a, b) {
347
+ const prev = _node_prev[a];
348
+ const next = _node_next[a];
349
+
350
+ if (orient(prev, a, next) > 0) {
351
+ // convex corner: b must be inside the wedge
352
+ return orient(a, b, next) <= 0 && orient(a, prev, b) <= 0;
353
+ }
354
+
355
+ // reflex corner: b must be outside the (smaller) reflex wedge
356
+ return orient(a, b, prev) > 0 || orient(a, next, b) > 0;
357
+ }
358
+
359
+ /**
360
+ * Is the midpoint of `a -> b` inside the ring? Even-odd ray cast along +x.
361
+ *
362
+ * @param {number} a
363
+ * @param {number} b
364
+ * @returns {boolean}
365
+ */
366
+ function middle_inside(a, b) {
367
+ const px = (x_of(a) + x_of(b)) / 2;
368
+ const py = (y_of(a) + y_of(b)) / 2;
369
+
370
+ let p = a;
371
+ let inside = false;
372
+
373
+ do {
374
+ const next = _node_next[p];
375
+
376
+ const py0 = y_of(p);
377
+ const py1 = y_of(next);
378
+
379
+ if (((py0 > py) !== (py1 > py))
380
+ && py1 !== py0
381
+ && (px < (x_of(next) - x_of(p)) * (py - py0) / (py1 - py0) + x_of(p))) {
382
+ inside = !inside;
383
+ }
384
+
385
+ p = next;
386
+ } while (p !== a);
387
+
388
+ return inside;
389
+ }
390
+
391
+ /**
392
+ * Does `q` lie on the segment `p -> r`, given the three are already known to be
393
+ * collinear?
394
+ *
395
+ * @param {number} p
396
+ * @param {number} q
397
+ * @param {number} r
398
+ * @returns {boolean}
399
+ */
400
+ function on_segment(p, q, r) {
401
+ return x_of(q) <= Math.max(x_of(p), x_of(r))
402
+ && x_of(q) >= Math.min(x_of(p), x_of(r))
403
+ && y_of(q) <= Math.max(y_of(p), y_of(r))
404
+ && y_of(q) >= Math.min(y_of(p), y_of(r));
405
+ }
406
+
407
+ /**
408
+ * @param {number} value
409
+ * @returns {number} -1, 0 or 1
410
+ */
411
+ function sign_of(value) {
412
+ return value > 0 ? 1 : value < 0 ? -1 : 0;
413
+ }
414
+
415
+ /**
416
+ * Do the segments `p1 -> q1` and `p2 -> q2` intersect, including collinear
417
+ * touching?
418
+ *
419
+ * @param {number} p1
420
+ * @param {number} q1
421
+ * @param {number} p2
422
+ * @param {number} q2
423
+ * @returns {boolean}
424
+ */
425
+ function segments_intersect(p1, q1, p2, q2) {
426
+ const o1 = sign_of(orient(p1, q1, p2));
427
+ const o2 = sign_of(orient(p1, q1, q2));
428
+ const o3 = sign_of(orient(p2, q2, p1));
429
+ const o4 = sign_of(orient(p2, q2, q1));
430
+
431
+ if (o1 !== o2 && o3 !== o4) {
432
+ return true;
433
+ }
434
+
435
+ if (o1 === 0 && on_segment(p1, p2, q1)) {
436
+ return true;
437
+ }
438
+
439
+ if (o2 === 0 && on_segment(p1, q2, q1)) {
440
+ return true;
441
+ }
442
+
443
+ if (o3 === 0 && on_segment(p2, p1, q2)) {
444
+ return true;
445
+ }
446
+
447
+ if (o4 === 0 && on_segment(p2, q1, q2)) {
448
+ return true;
449
+ }
450
+
451
+ return false;
452
+ }
453
+
454
+ /**
455
+ * Does the candidate diagonal `a -> b` cross any edge of the ring?
456
+ *
457
+ * @param {number} a
458
+ * @param {number} b
459
+ * @returns {boolean}
460
+ */
461
+ function intersects_polygon(a, b) {
462
+ let p = a;
463
+
464
+ do {
465
+ const next = _node_next[p];
466
+
467
+ if (_node_point[p] !== _node_point[a]
468
+ && _node_point[next] !== _node_point[a]
469
+ && _node_point[p] !== _node_point[b]
470
+ && _node_point[next] !== _node_point[b]
471
+ && segments_intersect(p, next, a, b)) {
472
+ return true;
473
+ }
474
+
475
+ p = next;
476
+ } while (p !== a);
477
+
478
+ return false;
479
+ }
480
+
481
+ /**
482
+ * Is `a -> b` a diagonal that splits the ring into two simple sub-rings?
483
+ *
484
+ * @param {number} a
485
+ * @param {number} b
486
+ * @returns {boolean}
487
+ */
488
+ function is_valid_diagonal(a, b) {
489
+ if (_node_point[_node_next[a]] === _node_point[b] || _node_point[_node_prev[a]] === _node_point[b]) {
490
+ return false;
491
+ }
492
+
493
+ if (intersects_polygon(a, b)) {
494
+ return false;
495
+ }
496
+
497
+ const locally_visible = locally_inside(a, b)
498
+ && locally_inside(b, a)
499
+ && middle_inside(a, b)
500
+ // and does not produce two opposite-facing sectors
501
+ && (orient(_node_prev[a], a, _node_prev[b]) !== 0 || orient(a, _node_prev[b], b) !== 0);
502
+
503
+ if (locally_visible) {
504
+ return true;
505
+ }
506
+
507
+ // zero-length diagonal between two coincident reflex corners still separates
508
+ return nodes_equal(a, b)
509
+ && orient(_node_prev[a], a, _node_next[a]) < 0
510
+ && orient(_node_prev[b], b, _node_next[b]) < 0;
511
+ }
512
+
513
+ /**
514
+ * Cut the ring in two along the diagonal `a -> b`, duplicating both endpoints.
515
+ *
516
+ * @param {number} a
517
+ * @param {number} b
518
+ * @returns {number} a node of the second ring
519
+ */
520
+ function split_ring(a, b) {
521
+ const a2 = create_node(_node_point[a], x_of(a), y_of(a));
522
+ const b2 = create_node(_node_point[b], x_of(b), y_of(b));
523
+
524
+ const an = _node_next[a];
525
+ const bp = _node_prev[b];
526
+
527
+ _node_next[a] = b;
528
+ _node_prev[b] = a;
529
+
530
+ _node_next[a2] = an;
531
+ _node_prev[an] = a2;
532
+
533
+ _node_next[b2] = a2;
534
+ _node_prev[a2] = b2;
535
+
536
+ _node_next[bp] = b2;
537
+ _node_prev[b2] = bp;
538
+
539
+ return b2;
540
+ }
541
+
542
+ /**
543
+ * Quantise a coordinate pair into the Morton grid for the current call.
544
+ *
545
+ * @param {number} x
546
+ * @param {number} y
547
+ * @returns {number}
548
+ */
549
+ function morton_of(x, y) {
550
+ return v2_morton_encode(
551
+ ((x - _min_x) * _inv_size) | 0,
552
+ ((y - _min_y) * _inv_size) | 0
553
+ );
554
+ }
555
+
556
+ /**
557
+ * (Re)build the Z-order doubly-linked list over the ring starting at `start`.
558
+ *
559
+ * @param {number} start
560
+ */
561
+ function index_curve(start) {
562
+ let p = start;
563
+
564
+ do {
565
+ _node_z[p] = morton_of(x_of(p), y_of(p));
566
+ _node_zprev[p] = _node_prev[p];
567
+ _node_znext[p] = _node_next[p];
568
+
569
+ p = _node_next[p];
570
+ } while (p !== start);
571
+
572
+ const last = _node_zprev[p];
573
+
574
+ _node_znext[last] = NULL_NODE;
575
+ _node_zprev[p] = NULL_NODE;
576
+
577
+ sort_z_linked(p);
578
+ }
579
+
580
+ /**
581
+ * Bottom-up merge sort of the Z-order list by Morton code. Operates on links, so
582
+ * it moves no data and needs no scratch array.
583
+ *
584
+ * @param {number} list head node
585
+ * @returns {number} the new head
586
+ */
587
+ function sort_z_linked(list) {
588
+ let in_size = 1;
589
+ let num_merges;
590
+
591
+ do {
592
+ let p = list;
593
+ let tail = NULL_NODE;
594
+
595
+ list = NULL_NODE;
596
+ num_merges = 0;
597
+
598
+ while (p !== NULL_NODE) {
599
+ num_merges++;
600
+
601
+ let q = p;
602
+ let p_size = 0;
603
+
604
+ for (let i = 0; i < in_size; i++) {
605
+ p_size++;
606
+ q = _node_znext[q];
607
+
608
+ if (q === NULL_NODE) {
609
+ break;
610
+ }
611
+ }
612
+
613
+ let q_size = in_size;
614
+
615
+ while (p_size > 0 || (q_size > 0 && q !== NULL_NODE)) {
616
+ let e;
617
+
618
+ if (p_size !== 0 && (q_size === 0 || q === NULL_NODE || _node_z[p] <= _node_z[q])) {
619
+ e = p;
620
+ p = _node_znext[p];
621
+ p_size--;
622
+ } else {
623
+ e = q;
624
+ q = _node_znext[q];
625
+ q_size--;
626
+ }
627
+
628
+ if (tail !== NULL_NODE) {
629
+ _node_znext[tail] = e;
630
+ } else {
631
+ list = e;
632
+ }
633
+
634
+ _node_zprev[e] = tail;
635
+ tail = e;
636
+ }
637
+
638
+ p = q;
639
+ }
640
+
641
+ _node_znext[tail] = NULL_NODE;
642
+ in_size *= 2;
643
+ } while (num_merges > 1);
644
+
645
+ return list;
646
+ }
647
+
648
+ /**
649
+ * Is `ear` an ear -- a convex corner whose triangle contains no other reflex
650
+ * corner of the ring? Linear scan; used below {@link HASH_THRESHOLD}.
651
+ *
652
+ * @param {number} ear
653
+ * @returns {boolean}
654
+ */
655
+ function is_ear(ear) {
656
+ const a = _node_prev[ear];
657
+ const c = _node_next[ear];
658
+
659
+ if (orient(a, ear, c) <= 0) {
660
+ // reflex or straight -- cannot be an ear
661
+ return false;
662
+ }
663
+
664
+ const x0 = Math.min(x_of(a), x_of(ear), x_of(c));
665
+ const y0 = Math.min(y_of(a), y_of(ear), y_of(c));
666
+ const x1 = Math.max(x_of(a), x_of(ear), x_of(c));
667
+ const y1 = Math.max(y_of(a), y_of(ear), y_of(c));
668
+
669
+ let p = _node_next[c];
670
+
671
+ while (p !== a) {
672
+ const px = x_of(p);
673
+ const py = y_of(p);
674
+
675
+ if (px >= x0 && px <= x1 && py >= y0 && py <= y1
676
+ && point_in_triangle_except_first(a, ear, c, p)
677
+ && orient(_node_prev[p], p, _node_next[p]) <= 0) {
678
+ return false;
679
+ }
680
+
681
+ p = _node_next[p];
682
+ }
683
+
684
+ return true;
685
+ }
686
+
687
+ /**
688
+ * Does node `p` block the ear (a, ear, c)? Shared by the two Z-order walks.
689
+ *
690
+ * @param {number} p
691
+ * @param {number} a
692
+ * @param {number} ear
693
+ * @param {number} c
694
+ * @param {number} x0
695
+ * @param {number} y0
696
+ * @param {number} x1
697
+ * @param {number} y1
698
+ * @returns {boolean}
699
+ */
700
+ function blocks_ear(p, a, ear, c, x0, y0, x1, y1) {
701
+ if (p === a || p === c) {
702
+ return false;
703
+ }
704
+
705
+ const px = x_of(p);
706
+ const py = y_of(p);
707
+
708
+ if (px < x0 || px > x1 || py < y0 || py > y1) {
709
+ return false;
710
+ }
711
+
712
+ if (!point_in_triangle(a, ear, c, p)) {
713
+ return false;
714
+ }
715
+
716
+ return orient(_node_prev[p], p, _node_next[p]) <= 0;
717
+ }
718
+
719
+ /**
720
+ * {@link is_ear} restricted to nodes whose Morton code falls in the ear's
721
+ * bounding-box code range -- the walk that keeps large rings out of O(n^2).
722
+ *
723
+ * @param {number} ear
724
+ * @returns {boolean}
725
+ */
726
+ function is_ear_hashed(ear) {
727
+ const a = _node_prev[ear];
728
+ const c = _node_next[ear];
729
+
730
+ if (orient(a, ear, c) <= 0) {
731
+ return false;
732
+ }
733
+
734
+ const x0 = Math.min(x_of(a), x_of(ear), x_of(c));
735
+ const y0 = Math.min(y_of(a), y_of(ear), y_of(c));
736
+ const x1 = Math.max(x_of(a), x_of(ear), x_of(c));
737
+ const y1 = Math.max(y_of(a), y_of(ear), y_of(c));
738
+
739
+ const min_z = morton_of(x0, y0);
740
+ const max_z = morton_of(x1, y1);
741
+
742
+ let p = _node_zprev[ear];
743
+ let n = _node_znext[ear];
744
+
745
+ while (p !== NULL_NODE && _node_z[p] >= min_z && n !== NULL_NODE && _node_z[n] <= max_z) {
746
+ if (blocks_ear(p, a, ear, c, x0, y0, x1, y1)) {
747
+ return false;
748
+ }
749
+
750
+ p = _node_zprev[p];
751
+
752
+ if (blocks_ear(n, a, ear, c, x0, y0, x1, y1)) {
753
+ return false;
754
+ }
755
+
756
+ n = _node_znext[n];
757
+ }
758
+
759
+ while (p !== NULL_NODE && _node_z[p] >= min_z) {
760
+ if (blocks_ear(p, a, ear, c, x0, y0, x1, y1)) {
761
+ return false;
762
+ }
763
+
764
+ p = _node_zprev[p];
765
+ }
766
+
767
+ while (n !== NULL_NODE && _node_z[n] <= max_z) {
768
+ if (blocks_ear(n, a, ear, c, x0, y0, x1, y1)) {
769
+ return false;
770
+ }
771
+
772
+ n = _node_znext[n];
773
+ }
774
+
775
+ return true;
776
+ }
777
+
778
+ /**
779
+ * @param {number} a
780
+ * @param {number} b
781
+ * @param {number} c
782
+ */
783
+ function emit_triangle(a, b, c) {
784
+ _out[_out_cursor] = _node_point[a];
785
+ _out[_out_cursor + 1] = _node_point[b];
786
+ _out[_out_cursor + 2] = _node_point[c];
787
+ _out_cursor += 3;
788
+ }
789
+
790
+ /**
791
+ * Is the ring through `node` down to fewer than three nodes -- i.e. is there no
792
+ * triangle left in it at all?
793
+ *
794
+ * @param {number} node
795
+ * @returns {boolean}
796
+ */
797
+ function ring_is_spent(node) {
798
+ return _node_next[node] === node || _node_next[_node_next[node]] === node;
799
+ }
800
+
801
+ /**
802
+ * Second-chance pass: when the ring has no ear left, look for a pair of adjacent
803
+ * edges that cross and cut the offending vertex pair out as one triangle.
804
+ *
805
+ * Takes the output of {@link filter_points} directly, so it accepts and
806
+ * propagates that function's collapsed-ring sentinel.
807
+ *
808
+ * @param {number} start a node, or {@link NULL_NODE}
809
+ * @returns {number} a node of the repaired ring, or {@link NULL_NODE}
810
+ */
811
+ function cure_local_intersections(start) {
812
+ if (start === NULL_NODE) {
813
+ return NULL_NODE;
814
+ }
815
+
816
+ let p = start;
817
+
818
+ do {
819
+ const a = _node_prev[p];
820
+ const next = _node_next[p];
821
+ const b = _node_next[next];
822
+
823
+ if (!nodes_equal(a, b)
824
+ && segments_intersect(a, p, next, b)
825
+ && locally_inside(a, b)
826
+ && locally_inside(b, a)) {
827
+ emit_triangle(a, p, b);
828
+
829
+ remove_node(p);
830
+ remove_node(next);
831
+
832
+ // Two nodes just left the ring. Once fewer than three remain there is
833
+ // no triangle to cure and the walk has nothing left to walk: `p` and
834
+ // `start` would both be chasing unlinked nodes.
835
+ if (ring_is_spent(b)) {
836
+ return NULL_NODE;
837
+ }
838
+
839
+ p = b;
840
+ start = b;
841
+ }
842
+
843
+ p = _node_next[p];
844
+ } while (p !== start);
845
+
846
+ return filter_points(p, NULL_NODE);
847
+ }
848
+
849
+ /**
850
+ * Last-chance pass: split the ring along any valid diagonal and triangulate the
851
+ * two halves independently.
852
+ *
853
+ * @param {number} start
854
+ */
855
+ function split_ear_clip(start) {
856
+ let a = start;
857
+
858
+ do {
859
+ let b = _node_next[_node_next[a]];
860
+
861
+ while (b !== _node_prev[a]) {
862
+ if (_node_point[a] !== _node_point[b] && is_valid_diagonal(a, b)) {
863
+ let c = split_ring(a, b);
864
+
865
+ a = filter_points(a, _node_next[a]);
866
+ c = filter_points(c, _node_next[c]);
867
+
868
+ ear_clip(a, 0);
869
+ ear_clip(c, 0);
870
+
871
+ return;
872
+ }
873
+
874
+ b = _node_next[b];
875
+ }
876
+
877
+ a = _node_next[a];
878
+ } while (a !== start);
879
+ }
880
+
881
+ /**
882
+ * Slice ears off the ring until it is a single triangle.
883
+ *
884
+ * `pass` records how many recovery tiers have been tried on this ring: 0 = plain,
885
+ * 1 = after removing degenerate points, 2 = after curing local self-intersections.
886
+ * Reaching the end of tier 2 without an ear means the ring is not triangulatable
887
+ * and its remaining area is dropped -- reported to the caller as a short triangle
888
+ * count.
889
+ *
890
+ * @param {number} ear
891
+ * @param {number} pass
892
+ */
893
+ function ear_clip(ear, pass) {
894
+ if (ear === NULL_NODE) {
895
+ return;
896
+ }
897
+
898
+ if (pass === 0 && _hashed) {
899
+ index_curve(ear);
900
+ }
901
+
902
+ let stop = ear;
903
+
904
+ while (_node_prev[ear] !== _node_next[ear]) {
905
+ const prev = _node_prev[ear];
906
+ const next = _node_next[ear];
907
+
908
+ if (_hashed ? is_ear_hashed(ear) : is_ear(ear)) {
909
+ emit_triangle(prev, ear, next);
910
+
911
+ remove_node(ear);
912
+
913
+ // skipping the next vertex leads to fewer sliver triangles
914
+ ear = _node_next[next];
915
+ stop = _node_next[next];
916
+
917
+ continue;
918
+ }
919
+
920
+ ear = next;
921
+
922
+ if (ear !== stop) {
923
+ continue;
924
+ }
925
+
926
+ if (pass === 0) {
927
+ ear_clip(filter_points(ear, NULL_NODE), 1);
928
+ } else if (pass === 1) {
929
+ const cured = cure_local_intersections(filter_points(ear, NULL_NODE));
930
+
931
+ ear_clip(cured, 2);
932
+ } else if (pass === 2) {
933
+ split_ear_clip(ear);
934
+ }
935
+
936
+ return;
937
+ }
938
+ }
939
+
940
+ /**
941
+ * Leftmost (then lowest) node of a ring.
942
+ *
943
+ * @param {number} start
944
+ * @returns {number}
945
+ */
946
+ function get_leftmost(start) {
947
+ let p = start;
948
+ let leftmost = start;
949
+
950
+ do {
951
+ if (x_of(p) < x_of(leftmost) || (x_of(p) === x_of(leftmost) && y_of(p) < y_of(leftmost))) {
952
+ leftmost = p;
953
+ }
954
+
955
+ p = _node_next[p];
956
+ } while (p !== start);
957
+
958
+ return leftmost;
959
+ }
960
+
961
+ /**
962
+ * Find the outer-ring node that the hole's leftmost node can see, so a bridge
963
+ * between them stays inside the polygon.
964
+ *
965
+ * Casts a ray in -x from the hole vertex, takes the closest outer edge it hits,
966
+ * then -- if any reflex outer vertex lies in the triangle formed by the hit -- picks
967
+ * the one at the smallest angle to the ray instead.
968
+ *
969
+ * @param {number} hole
970
+ * @param {number} outer
971
+ * @returns {number} the bridge node, or {@link NULL_NODE} when none is visible
972
+ */
973
+ function find_hole_bridge(hole, outer) {
974
+ const hx = x_of(hole);
975
+ const hy = y_of(hole);
976
+
977
+ let qx = -Infinity;
978
+ let m = NULL_NODE;
979
+
980
+ let p = outer;
981
+
982
+ if (nodes_equal(hole, p)) {
983
+ return p;
984
+ }
985
+
986
+ do {
987
+ const next = _node_next[p];
988
+
989
+ if (nodes_equal(hole, next)) {
990
+ return next;
991
+ }
992
+
993
+ const py = y_of(p);
994
+ const ny = y_of(next);
995
+
996
+ if (hy <= py && hy >= ny && ny !== py) {
997
+ const x = x_of(p) + (hy - py) * (x_of(next) - x_of(p)) / (ny - py);
998
+
999
+ if (x <= hx && x > qx) {
1000
+ qx = x;
1001
+ m = x_of(p) < x_of(next) ? p : next;
1002
+
1003
+ if (x === hx) {
1004
+ // the hole touches the outer contour exactly here
1005
+ return m;
1006
+ }
1007
+ }
1008
+ }
1009
+
1010
+ p = next;
1011
+ } while (p !== outer);
1012
+
1013
+ if (m === NULL_NODE) {
1014
+ return NULL_NODE;
1015
+ }
1016
+
1017
+ // Search the sector between the ray hit and the chosen endpoint for a reflex
1018
+ // vertex that would be occluded by the naive bridge.
1019
+ const stop = m;
1020
+ const mx = x_of(m);
1021
+ const my = y_of(m);
1022
+
1023
+ let tan_min = Infinity;
1024
+
1025
+ p = m;
1026
+
1027
+ do {
1028
+ const px = x_of(p);
1029
+ const py = y_of(p);
1030
+
1031
+ if (hx >= px && px >= mx && hx !== px
1032
+ && point_in_triangle_xy(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, px, py)) {
1033
+ const tan = Math.abs(hy - py) / (hx - px);
1034
+
1035
+ if (locally_inside(p, hole)
1036
+ && (tan < tan_min
1037
+ || (tan === tan_min
1038
+ && (px > x_of(m) || (px === x_of(m) && sector_contains_sector(m, p)))))) {
1039
+ m = p;
1040
+ tan_min = tan;
1041
+ }
1042
+ }
1043
+
1044
+ p = _node_next[p];
1045
+ } while (p !== stop);
1046
+
1047
+ return m;
1048
+ }
1049
+
1050
+ /**
1051
+ * Point-in-triangle on raw coordinates. The bridge search builds a triangle from
1052
+ * the ray hit, which is not a ring node, so it cannot use the node-indexed test.
1053
+ *
1054
+ * Inexact by necessity (`qx` is an intersection, computed with a division) -- but
1055
+ * this only selects *which* legal bridge to use, never whether the result is
1056
+ * valid, so an unlucky rounding costs triangle quality and nothing else.
1057
+ *
1058
+ * @returns {boolean}
1059
+ */
1060
+ function point_in_triangle_xy(ax, ay, bx, by, cx, cy, px, py) {
1061
+ return (cx - px) * (ay - py) >= (ax - px) * (cy - py)
1062
+ && (ax - px) * (by - py) >= (bx - px) * (ay - py)
1063
+ && (bx - px) * (cy - py) >= (cx - px) * (by - py);
1064
+ }
1065
+
1066
+ /**
1067
+ * Whether m's interior sector contains p's -- the tie-break when two candidate
1068
+ * bridges sit at the same angle.
1069
+ *
1070
+ * @param {number} m
1071
+ * @param {number} p
1072
+ * @returns {boolean}
1073
+ */
1074
+ function sector_contains_sector(m, p) {
1075
+ return orient(_node_prev[m], m, _node_prev[p]) > 0 && orient(_node_next[p], m, _node_next[m]) > 0;
1076
+ }
1077
+
1078
+ /**
1079
+ * Splice one hole into the outer ring through a doubled bridge edge.
1080
+ *
1081
+ * @param {number} hole leftmost node of the hole ring
1082
+ * @param {number} outer
1083
+ * @returns {number} the outer ring node to continue from
1084
+ */
1085
+ function eliminate_hole(hole, outer) {
1086
+ if (outer === NULL_NODE) {
1087
+ return NULL_NODE;
1088
+ }
1089
+
1090
+ const bridge = find_hole_bridge(hole, outer);
1091
+
1092
+ if (bridge === NULL_NODE) {
1093
+ return outer;
1094
+ }
1095
+
1096
+ const bridge_reverse = split_ring(bridge, hole);
1097
+
1098
+ filter_points(bridge_reverse, _node_next[bridge_reverse]);
1099
+
1100
+ return filter_points(bridge, _node_next[bridge]);
1101
+ }
1102
+
1103
+ /**
1104
+ * Compare two hole rings by their leftmost node, so bridges are cut left to right
1105
+ * and therefore never cross one another.
1106
+ *
1107
+ * @param {number} a leftmost node of one hole
1108
+ * @param {number} b leftmost node of another
1109
+ * @returns {number}
1110
+ */
1111
+ function compare_hole_leftmost(a, b) {
1112
+ let result = x_of(a) - x_of(b);
1113
+
1114
+ if (result !== 0) {
1115
+ return result;
1116
+ }
1117
+
1118
+ result = y_of(a) - y_of(b);
1119
+
1120
+ if (result !== 0) {
1121
+ return result;
1122
+ }
1123
+
1124
+ // Two holes meeting at one point: order them by outgoing slope so the bridge
1125
+ // is always cut at the vertex where they meet.
1126
+ const a_slope = (y_of(_node_next[a]) - y_of(a)) / (x_of(_node_next[a]) - x_of(a));
1127
+ const b_slope = (y_of(_node_next[b]) - y_of(b)) / (x_of(_node_next[b]) - x_of(b));
1128
+
1129
+ return a_slope - b_slope;
1130
+ }
1131
+
1132
+ /**
1133
+ * Splice every hole contour into the outer ring.
1134
+ *
1135
+ * @param {ArrayLike<number>} points
1136
+ * @param {ArrayLike<number>} contour_ends
1137
+ * @param {number} contour_count
1138
+ * @param {number} outer
1139
+ * @returns {number} the merged ring
1140
+ */
1141
+ function eliminate_holes(points, contour_ends, contour_count, outer) {
1142
+ const queue = [];
1143
+
1144
+ for (let c = 1; c < contour_count; c++) {
1145
+ const from = contour_ends[c - 1];
1146
+ const to = contour_ends[c];
1147
+
1148
+ const last = link_contour(points, from, to, false);
1149
+
1150
+ if (last === NULL_NODE) {
1151
+ continue;
1152
+ }
1153
+
1154
+ if (last === _node_next[last]) {
1155
+ // the hole collapsed to a single point; keep it as a Steiner vertex
1156
+ _node_steiner[last] = 1;
1157
+ }
1158
+
1159
+ queue.push(get_leftmost(last));
1160
+ }
1161
+
1162
+ queue.sort(compare_hole_leftmost);
1163
+
1164
+ for (let i = 0; i < queue.length; i++) {
1165
+ outer = eliminate_hole(queue[i], outer);
1166
+ }
1167
+
1168
+ return outer;
1169
+ }
1170
+
1171
+ /**
1172
+ * Triangulate a 2D polygon with holes.
1173
+ *
1174
+ * Ear clipping over a doubly-linked vertex ring, with every combinatorial
1175
+ * decision made by the exact `orient2d` predicate already used elsewhere in
1176
+ * `core/geom` -- so the result does not depend on coordinate magnitude, and
1177
+ * collinear or coincident input is decided rather than guessed. Above
1178
+ * {@link HASH_THRESHOLD} vertices the point-in-ear test is restricted to a
1179
+ * Morton-code range, which is what keeps large rings off the quadratic path.
1180
+ *
1181
+ * Contour `i` spans `points[(i === 0 ? 0 : contour_ends[i - 1]) .. contour_ends[i])`
1182
+ * in vertex units. Contour 0 is the outer boundary and the rest are holes.
1183
+ * Winding is normalised internally, so callers cannot get it subtly wrong.
1184
+ *
1185
+ * Output is triples of indices into `points`, in ear-cut order, each triangle
1186
+ * counter-clockwise.
1187
+ *
1188
+ * A well-formed polygon yields exactly `V + 2H - 2` triangles, where `V` is the
1189
+ * total vertex count and `H` the hole count. **A smaller return value is how this
1190
+ * function reports that the input could not be fully triangulated** -- it is
1191
+ * self-intersecting, or partly degenerate. It never throws for that: a partial
1192
+ * triangulation is still the best available answer, and the caller is told how
1193
+ * much of one it got. {@link polygon2_triangulation_deviation} measures the
1194
+ * shortfall in area terms.
1195
+ *
1196
+ * Not re-entrant: the vertex ring lives in module-static buffers so a call
1197
+ * allocates nothing in the steady state.
1198
+ *
1199
+ * @param {Uint32Array} result receives 3 point indices per triangle
1200
+ * @param {number} result_offset first index written in `result`
1201
+ * @param {ArrayLike<number>} points flat (x, y) pairs, stride 2
1202
+ * @param {ArrayLike<number>} contour_ends exclusive end vertex index per contour, non-decreasing
1203
+ * @param {number} contour_count number of contours, at least 1
1204
+ * @returns {number} number of triangles written
1205
+ *
1206
+ * @author Alex Goldring
1207
+ * @copyright Company Named Limited (c) 2026
1208
+ */
1209
+ export function polygon2_triangulate(result, result_offset, points, contour_ends, contour_count) {
1210
+ assert.defined(result, "result");
1211
+ assert.isNonNegativeInteger(result_offset, "result_offset");
1212
+ assert.isArrayLike(points, "points");
1213
+ assert.isArrayLike(contour_ends, "contour_ends");
1214
+ assert.isPositiveInteger(contour_count, "contour_count");
1215
+
1216
+ const vertex_count = contour_ends[contour_count - 1];
1217
+
1218
+ assert.isNonNegativeInteger(vertex_count, "contour_ends[contour_count - 1]");
1219
+ assert.greaterThanOrEqual(points.length, vertex_count * 2, "points.length");
1220
+
1221
+ if (vertex_count < 3) {
1222
+ return 0;
1223
+ }
1224
+
1225
+ // Hole elimination duplicates two vertices per bridge, and each recovery split
1226
+ // duplicates two more. Splits only ever happen on a ring that already failed to
1227
+ // clip, and each consumes at least one vertex, so 2 per vertex bounds both.
1228
+ ensure_node_capacity(vertex_count * 3 + contour_count * 2);
1229
+
1230
+ _node_count = 0;
1231
+ _out = result;
1232
+ _out_cursor = result_offset;
1233
+
1234
+ let outer = link_contour(points, 0, contour_ends[0], true);
1235
+
1236
+ if (outer === NULL_NODE || _node_next[outer] === _node_prev[outer]) {
1237
+ _out = null;
1238
+
1239
+ return 0;
1240
+ }
1241
+
1242
+ if (contour_count > 1) {
1243
+ outer = eliminate_holes(points, contour_ends, contour_count, outer);
1244
+ }
1245
+
1246
+ _hashed = vertex_count > HASH_THRESHOLD;
1247
+
1248
+ if (_hashed) {
1249
+ let min_x = Infinity;
1250
+ let min_y = Infinity;
1251
+ let max_x = -Infinity;
1252
+ let max_y = -Infinity;
1253
+
1254
+ for (let i = 0; i < vertex_count; i++) {
1255
+ const x = points[i * 2];
1256
+ const y = points[i * 2 + 1];
1257
+
1258
+ if (x < min_x) min_x = x;
1259
+ if (y < min_y) min_y = y;
1260
+ if (x > max_x) max_x = x;
1261
+ if (y > max_y) max_y = y;
1262
+ }
1263
+
1264
+ _min_x = min_x;
1265
+ _min_y = min_y;
1266
+
1267
+ const extent = Math.max(max_x - min_x, max_y - min_y);
1268
+
1269
+ // A zero-extent polygon has no area to triangulate; any finite scale works.
1270
+ _inv_size = extent > 0 ? MORTON_SCALE / extent : 0;
1271
+ }
1272
+
1273
+ ear_clip(outer, 0);
1274
+
1275
+ const triangles = (_out_cursor - result_offset) / 3;
1276
+
1277
+ _out = null;
1278
+
1279
+ return triangles;
1280
+ }