@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,554 @@
1
+ # meep `core/geom` vs. `elalish/manifold` — comparative review
2
+
3
+ Date: 2026-08-01 · Reviewer: Claude (Opus 5) · Upstream reviewed: `elalish/manifold@master`
4
+ (`src/`, `include/manifold/`, `test/`, `bindings/wasm/`)
5
+
6
+ Motivating question: *an agent working on an offline computational-geometry task
7
+ abandoned meep and pulled in `manifold` instead. Why, and what would make that
8
+ unlikely next time?* Performance was **not** the reason.
9
+
10
+ ---
11
+
12
+ ## 0. TL;DR
13
+
14
+ **meep and manifold are not competitors — they barely overlap.** manifold is a
15
+ *solid modelling kernel*: its entire reason to exist is "given two watertight
16
+ solids, produce a third watertight solid", plus the ~40 operations that orbit
17
+ that guarantee. meep's `core/geom` is a *geometry-processing and simulation
18
+ toolbox*: half-edge topology surgery, BVHs, decimation, UV atlasing, LSCM/BFF
19
+ parameterisation, tetrahedralisation, navmesh, collision shapes.
20
+
21
+ The agent did not switch because meep's geometry was worse. It switched because
22
+ **meep has no solid modelling at all** — no mesh boolean, no polygon boolean,
23
+ no offset, no convex hull, no isosurfacing, no extrude/revolve, and no
24
+ manifoldness contract. Any task phrased as "subtract this from that and give me
25
+ a clean mesh" has a zero-line answer in manifold and no answer at all in meep.
26
+
27
+ Concretely, the review found:
28
+
29
+ - **3 gaps that alone justify reaching for another library** (§4.1–4.3): mesh
30
+ boolean, 2D polygon boolean/offset, and general polygon triangulation.
31
+ - **6 further "A and C but not B" gaps** (§4.4) where meep has the hard parts
32
+ either side of a missing cheap middle — most glaringly, a full SDF shape
33
+ algebra (`AbstractShape3D.signed_distance_at_point`, `UnionShape3D`) with
34
+ **no way to turn a field into a mesh**, and no `IntersectionShape3D` /
35
+ `DifferenceShape3D` to go with `UnionShape3D`.
36
+ - **1 hard numerical defect with a reproduced failure** (§3.1): meep's signed
37
+ volume returns `256` for a unit cube at `(1e6, 1e6, 1e6)` and `−262144` at
38
+ `1e7`. Correct answer: `1`.
39
+ - **A missing structural invariant checker** for `BinaryTopology` (§3.4), which
40
+ is the single biggest reason manifold *feels* trustworthy and meep's topology
41
+ layer does not.
42
+
43
+ Where meep leads, it leads decisively (§5): UV atlasing, parameterisation,
44
+ BVH8, geodesic/navmesh queries, tetrahedralisation, physics shapes. manifold has
45
+ none of it and never will.
46
+
47
+ ---
48
+
49
+ ## 1. Why the agent switched — the direct answer
50
+
51
+ For an offline computational-geometry task, the decision tree an agent walks is
52
+ roughly:
53
+
54
+ | Question the task asks | manifold | meep |
55
+ | --- | --- | --- |
56
+ | "union / subtract / intersect these meshes" | `a + b`, `a - b`, `a ^ b` | — |
57
+ | "give me the result as a *valid* mesh" | guaranteed manifold, or `Status()` says why not | no manifoldness contract anywhere |
58
+ | "extrude this outline / revolve this profile" | `Manifold::Extrude` / `Revolve` | — |
59
+ | "triangulate this polygon with holes" | `Triangulate(polys, ε)` | — (only a convex fan, §3.3) |
60
+ | "offset this outline by 2mm, round joins" | `CrossSection::Offset` | — |
61
+ | "convex hull of these points" | `Manifold::Hull(pts)` | — (`ConvexHullShape3D` *consumes* a hull, does not compute one) |
62
+ | "mesh this SDF" | `Manifold::LevelSet(sdf, bounds, edgeLength)` | — (can voxelize, cannot isosurface) |
63
+ | "what is its volume / genus / is it closed?" | `Volume()`, `Genus()`, `Status()` | volume only, and see §3.1 |
64
+ | "slice it at z=0 / project its silhouette" | `Slice()`, `Project()` | — |
65
+
66
+ That is nine consecutive misses on the operations that define the domain. The
67
+ switch was not a judgement call; it was the absence of an alternative.
68
+
69
+ The second-order reason is the **contract**. manifold's central promise is that
70
+ every `Manifold` value is a closed, oriented, ε-valid 2-manifold, or is empty
71
+ with an `Error` explaining why. Every operation preserves that. An agent can
72
+ chain twenty operations and trust the twentieth. meep's `BinaryTopology` is a
73
+ BMesh-style structure that *permits* wire edges, n-gons, non-manifold radial
74
+ cycles and pinched vertices by design — which is right for a modelling/authoring
75
+ substrate, but means no operation can state a post-condition, and there is no
76
+ validator to check one (§3.4).
77
+
78
+ ---
79
+
80
+ ## 2. Capability map
81
+
82
+ Legend: **✔** present and comparable · **~** partial / narrower · **✘** absent.
83
+
84
+ ### 2.1 Solid modelling (manifold's core)
85
+
86
+ | manifold | meep | Notes |
87
+ | --- | --- | --- |
88
+ | `Boolean` / `BatchBoolean` (`src/boolean3.cpp`, `boolean_result.cpp`) | ✘ | The flagship. Smith & Dodgson robust boolean over a halfedge mesh. |
89
+ | `Split`, `SplitByPlane`, `TrimByPlane` | ✘ | `clip_soup_against_overhangs.js` clips a *soup* against a height field only. |
90
+ | `MinkowskiSum` / `MinkowskiDifference` (`src/minkowski.cpp`) | ✘ | meep has `gjk/minkowski_support.js` — the support function only, not the sum. |
91
+ | `Hull()` (3D quickhull, `src/quickhull.cpp`) | ✘ | See §4.3. |
92
+ | `Decompose()` / `Compose()` (connected components) | ~ | `bt_mesh_compute_face_islands`, `bt_mesh_face_decouple_islands` cover component splitting on a topology mesh. |
93
+ | `LevelSet` (marching tetrahedra over an SDF, `src/sdf.cpp`) | ✘ | See §4.4-a. |
94
+ | `Extrude`, `Revolve` | ✘ | `TubeX` sweeps a circle along a path — not a general profile extrude. |
95
+ | `Slice`, `Project` (`src/face_op.cpp`) | ✘ | |
96
+ | `Cube/Sphere/Cylinder/Tetrahedron` as *solids* | ~ | meep has these as `AbstractShape3D` colliders and as three.js geometry, not as topology solids. |
97
+ | `Genus()`, `IsConvex()`, `Status()/Error` | ✘ | `mesh_convex_hull.js` has a bespoke convexity test for tet meshes. |
98
+ | `Volume()`, `SurfaceArea()` | ~ | Present but see §3.1/§3.2. |
99
+ | `MinGap(other, searchLength)` | ✘ | Distance queries exist per-primitive; no mesh-mesh minimum gap. |
100
+ | `RayCast`, `WindingNumber` | ~ | Raycast: **meep is ahead** (bvh2/bvh3/bvh8, `refine_ray_hit`). Winding number: ✘. |
101
+ | `epsilon_` / `tolerance_` model, `SetTolerance`, `Simplify(tol)` | ~ | meep passes ad-hoc `eps` per function; no per-mesh tolerance carried with the data. See §4.5. |
102
+
103
+ ### 2.2 2D
104
+
105
+ | manifold | meep | Notes |
106
+ | --- | --- | --- |
107
+ | `CrossSection` boolean (`src/boolean2*.cpp` — own sweep-line engine, replaced Clipper2) | ✘ | meep has `polygon2_clip_halfplane` / `_axis_halfplane` only. |
108
+ | `CrossSection::Offset` (Square/Round/Miter joins) | ✘ | See §4.2. |
109
+ | `CrossSection::Hull` | ✔ | `convex_hull_monotone_2d`, `convex_hull_jarvis_2d`, plus fixed-count variants meep has and manifold lacks. |
110
+ | `TriangulateIdx` (monotone sweep, arbitrary hole nesting) | ✘ | See §3.3/§4.1. |
111
+ | `Simplify` (2D contour) | ✘ | |
112
+ | `Rect`, `Area`, `Bounds`, `Decompose` | ~ | `Rectangle`, `compute_polygon_area_2d`, `polygon2_signed_area` cover the scalars. |
113
+
114
+ ### 2.3 Mesh processing
115
+
116
+ | Area | manifold | meep | Verdict |
117
+ | --- | --- | --- | --- |
118
+ | Halfedge structure | compact triangle-only halfedge (`src/impl.h`) | BMesh-style vertex/edge/loop/face pools with disk + radial cycles (`BinaryTopology.js`, 984 lines) | **meep is more general** (n-gons, wire edges, per-element data pools); manifold is more constrained *on purpose*. |
119
+ | Decimation / simplify | `SimplifyTopology` = dedupe + collapse-short + collapse-colinear + swap-degenerates (`src/edge_op.cpp`) | QEM half-edge decimation to a face target (`bt_mesh_simplify`) and error-bounded cleanup (`bt_mesh_simplify_by_error`) | **meep is ahead on quality** (true quadrics + Fibonacci heap + fold guard); manifold's is a robustness pass, not an LOD tool. |
120
+ | Vertex welding | `Merge` + Morton sort + union-find (`src/sort.cpp`, `disjoint_sets.h`) | `bt_merge_vertices_by_distance` (open-addressed spatial hash, 27-cell fallback) | Comparable. meep's is order-dependent; manifold's is deterministic by construction. |
121
+ | Degenerate removal | `RemoveDegenerates`, `SwapDegenerates`, `RemoveIfFolded`, `SplitPinchedVerts`, `DedupeEdges` | `collapse_all_degenerate_edges`, `bt_mesh_kill_short_edges`, `bt_mesh_fuse_duplicate_edges`, `bt_face_is_degenerate` | Roughly comparable coverage. manifold's `SplitPinchedVerts` has no meep equivalent. |
122
+ | T-junctions | handled inside the boolean | `bt_mesh_resolve_t_junctions` | **meep has an explicit tool manifold lacks.** |
123
+ | Hole filling | ✘ | `bt_mesh_close_boundary_holes`, `bt_mesh_fill_small_holes` | **meep only.** |
124
+ | Refine / subdivide / smooth | `Refine`, `RefineToLength`, `RefineToTolerance`, `SmoothByNormals`, `SmoothOut`, `Smooth` (Bézier, `src/subdivision.cpp`, `smoothing.cpp` — 70KB combined) | ✘ | **manifold only.** See §4.4-c. |
125
+ | Normals | `CalculateNormals(minSharpAngle)`, angle-weighted pseudo-normals, sharp-edge splitting | `bt_mesh_compute_face_normals`, `tm_vertex_compute_normal` | manifold's sharp-angle-aware version has no meep equivalent. |
126
+ | Curvature | `CalculateCurvature(gaussian, mean)` | ✘ | |
127
+ | Self-intersection test | `IsSelfIntersecting()` (BVH + tri-tri distance, ε-tolerant) | ✘ | Note: `tri_dist.h` (`DistanceTriangleTriangleSquared`) has no meep equivalent either. |
128
+ | Structural validation | `IsManifold()`, `Is2Manifold()`, `IsFinite()`, `IsIndexInBounds()`, `MatchesTriNormals()`, `NumDegenerateTris()` | `topology_find_broken_links` / `debugValidateMesh` — **legacy `TopoMesh` only** | See §3.4. |
129
+ | UV atlas / parameterisation | ✘ | `atlas/` — charts, LSCM, BFF, stretch metric, bitmap + skyline packing, multi-page | **meep only, decisively.** |
130
+ | Tetrahedralisation | ✘ | `compute_delaunay_tetrahedral_mesh`, `compute_tetrahedral_mesh_from_surface` | **meep only.** |
131
+ | Geodesic / path queries on a mesh | ✘ | `bt_mesh_face_find_path`, `_polyanya`, eikonal distance fields, `funnel_string_pull` | **meep only.** |
132
+ | BVH | `src/collider.h` — linear BVH, Morton-ordered | `bvh2`, `bvh3`, `bvh8` (wide, compressed, `imask`), binary-serialisable | **meep is well ahead.** |
133
+
134
+ ---
135
+
136
+ ## 3. Implementation defects found in meep
137
+
138
+ ### 3.1 `tri3_compute_signed_volume` catastrophically cancels away from the origin — **HIGH**
139
+
140
+ `core/geom/3d/triangle/tri3_compute_signed_volume.js` evaluates the *fully
141
+ expanded* 3×3 determinant of **absolute** coordinates:
142
+
143
+ ```js
144
+ const v321 = cx * by * az; // … six triple products, each O(|p|³)
145
+ return ONE_OVER_SIX * (-v321 + v231 + v312 - v132 - v213 + v123);
146
+ ```
147
+
148
+ Every term is `O(|p|³)` while the result is `O(1)`, so all the significant
149
+ digits cancel. Reproduced (unit cube, 12 triangles, `double`):
150
+
151
+ | translation applied to every vertex | meep result | correct |
152
+ | --- | --- | --- |
153
+ | `(0, 0, 0)` | `1.00000000000` | `1` |
154
+ | `(1e5, 1e5, 1e5)` | `0.999999999985` | `1` |
155
+ | `(1e6, 1e6, 1e6)` | **`256.000000000`** | `1` |
156
+ | `(1e7, 1e7, 1e7)` | **`−262144.000000`** | `1` |
157
+
158
+ manifold computes the same quantity as `dot(cross(b − v, c − v), v) / 6`
159
+ (`src/properties.cpp:265`), forming the *differences first*. With the identical
160
+ inputs that form returns `1.00000000012` at `1e6` and `0.999999999069` at `1e7`
161
+ — i.e. the defect is entirely in the formulation, not in double precision.
162
+
163
+ The existing spec does test translation invariance
164
+ (`triangle_mesh_compute_signed_volume.spec.js:48`) but only by `(5, −3, 17)`,
165
+ which is ~5 orders of magnitude too small to expose it.
166
+
167
+ **Fix.** Replace the expanded determinant with the edge-difference form. For the
168
+ mesh-level wrapper, additionally subtract a reference point (bbox centre, or
169
+ simply `positions[0..2]`) from every vertex once — signed volume is
170
+ translation-invariant, so this is free and removes the failure mode entirely
171
+ rather than merely postponing it. Extend the spec to `1e6`/`1e7` offsets.
172
+
173
+ The same class of formulation appears in `tri3_compute_signed_volume`'s only
174
+ sibling, `tetrahedron_compute_signed_volume.js`, which delegates to it.
175
+
176
+ ### 3.2 `ConvexHullShape3D.recompute()` repeats the pattern — **LOW**
177
+
178
+ `core/geom/3d/shape/ConvexHullShape3D.js:133`:
179
+
180
+ ```js
181
+ vol6 += ax * (by * cz - bz * cy) + ay * (bz * cx - bx * cz) + az * (bx * cy - by * cx);
182
+ ```
183
+
184
+ Same absolute-coordinate triple products. Blast radius is small — collider
185
+ vertices are in body-local space near the origin — but the fix is the same
186
+ one-liner and worth taking while the area is open.
187
+
188
+ ### 3.3 No mesh-level compensated summation — **LOW**
189
+
190
+ `tri3_mesh_compute_signed_volume.js:22` accumulates with a plain `+=`.
191
+ manifold's `GetProperty` (`src/properties.cpp:265`) uses explicit Kahan
192
+ summation for **both** volume and surface area, precisely because these are
193
+ sums of signed terms over millions of triangles. Given that meep's own atlas
194
+ benchmark already builds ~4M-triangle scenes, this is worth matching. It is
195
+ strictly secondary to §3.1 — fix the formulation first, then the summation.
196
+
197
+ ### 3.4 `BinaryTopology` has no structural invariant checker — **HIGH (process defect)**
198
+
199
+ `topology_find_broken_links.js` and `debugValidateMesh.js` both operate on the
200
+ legacy object-based `TopoMesh`. The primary structure — `BinaryTopology`, the
201
+ one every `bt_*` operation mutates — has **no validator at all**: no
202
+ `bt_mesh_validate`, no `bt_mesh_is_manifold`, no disk/radial-cycle consistency
203
+ check.
204
+
205
+ This matters more than any individual algorithm. manifold's operations are
206
+ trusted because `ManifoldParams().intermediateChecks` re-runs `IsManifold()`
207
+ between stages and the test suite calls `CheckStrictly()` on nearly every
208
+ result. meep has ~40 `bt_*` mutators (`bt_edge_collapse`, `bt_vert_splice`,
209
+ `bt_radial_loop_remove`, `bt_face_poke`, …) each of which can corrupt a disk or
210
+ radial cycle, and nothing that would notice.
211
+
212
+ It also explains a real behavioural difference in decimation. When a collapse
213
+ would violate the link condition, manifold **splits the mesh topologically**
214
+ (`FormLoop` / `RemoveIfFolded`, `src/edge_op.cpp:492`) so the result stays
215
+ 2-manifold and the genus change is explicit. meep instead collapses first and
216
+ repairs after, via `bt_edge_kill_parallels` + `bt_vertex_fuse_duplicate_edges`
217
+ (`bt_mesh_simplify_by_error.js:582`). That may well be fine — but with no
218
+ invariant checker there is no way to know, and `bt_mesh_simplify.spec.js:522`
219
+ ("handles a non-manifold T-junction edge without throwing") asserts only that it
220
+ doesn't throw, not that the output is well-formed.
221
+
222
+ ### 3.5 `bt_face_triangulate` is a convex fan applied unconditionally — **MEDIUM**
223
+
224
+ `bt_face_triangulate.js` fans from the first loop vertex and documents
225
+ "Assumes that each face is a convex polygon". `bt_mesh_triangulate.js` then
226
+ applies it to **every** face in the mesh with no convexity test. For a concave
227
+ n-gon this emits inverted and overlapping triangles, silently.
228
+
229
+ This is a documented precondition rather than a coding error, so I am not
230
+ proposing a guard — the right response is that meep should have a real
231
+ triangulator (§4.1) and `bt_mesh_triangulate` should call it. As it stands,
232
+ "triangulate my mesh" is a public API that quietly produces garbage on the
233
+ input a caller is most likely to hand it (an imported OBJ/FBX with n-gons).
234
+
235
+ `bt_mesh_close_boundary_holes.js:131` has the same shape: it fans from the
236
+ loop's centroid, which for a non-convex or strongly non-planar boundary lies
237
+ outside the hole. It is scoped ("downstream tetrahedralization is
238
+ position-only"), but it is the second place where a real triangulator is the
239
+ missing dependency.
240
+
241
+ ---
242
+
243
+ ## 4. Gaps — where manifold is better with no trade-off
244
+
245
+ ### 4.1 General polygon triangulation (with holes) — the highest-leverage single gap
246
+
247
+ `src/polygon.cpp` (38KB) + `polygon_internal.h` + `tree2d.h` implement a
248
+ monotone sweep triangulator with:
249
+
250
+ - arbitrary depth of nested holes and any number of separate polygons;
251
+ - an explicit ε-validity contract (documented on `SimplePolygonIdx`);
252
+ - a self-checking mode (`intermediateChecks`) that verifies the halfedge
253
+ triangulation it produced;
254
+ - a **fuzz harness** (`test/polygon_fuzz.cpp`, FuzzTest + timeout + signal
255
+ handling) and a **regression corpus** of real-world failures
256
+ (`test/polygons/polygon_corpus.txt`, `sponge.txt`, `zebra.txt`,
257
+ `zebra3.txt` — 2.9MB of recorded failing inputs, replayed as tests).
258
+
259
+ meep has no polygon triangulator. This is the dependency under `Extrude`,
260
+ `Revolve`, `Slice`, `Project`, n-gon import, hole filling, planar chart
261
+ re-meshing, and the face-splitting half of any future boolean. **Building this
262
+ first unblocks more of the list than anything else on it.**
263
+
264
+ Note the corpus approach specifically: manifold's triangulator got robust by
265
+ recording every polygon that ever broke it and replaying them forever. That is
266
+ directly transferable and cheap.
267
+
268
+ ### 4.2 2D polygon boolean + offset
269
+
270
+ `src/boolean2.cpp` / `boolean2_sweep.cpp` / `boolean2_offset.cpp` /
271
+ `boolean2_predicates.cpp` (~70KB) — manifold wrote its own sweep-line
272
+ replacement for Clipper2, with `JoinType::{Square, Round, Miter}` offsetting and
273
+ a dedicated offset corpus test (`test/cross_section_offset_corpus_test.cpp`,
274
+ `test/models/Offset{1..4}.obj`).
275
+
276
+ meep has half-plane clipping only. Anything phrased as "union these footprints",
277
+ "inset this outline by the agent radius", "subtract these obstacle polygons" has
278
+ no answer. This is also the natural way to build navmesh obstacle carving —
279
+ which meep currently does by voxel/soup clipping instead.
280
+
281
+ ### 4.3 3D convex hull
282
+
283
+ `src/quickhull.cpp` (30KB, derived from Kuukka's public-domain quickhull) with a
284
+ dedicated test file covering the cases that break naive implementations:
285
+ `Hull.Degenerate2D`, `Hull.Degenerate1D`, `Hull.NotEnoughPoints`, `Hull.Hollow`,
286
+ `Hull.MengerSponge`, plus two recorded real-world failures.
287
+
288
+ meep's `ConvexHullShape3D` *consumes* a hull (`static from(vertices, indices)`)
289
+ and `mesh_convex_hull.js` only *detects* whether a tet mesh is already convex.
290
+ There is no way to compute a hull from a point set. That blocks: authoring
291
+ colliders from arbitrary meshes, convex decomposition quality (the greedy
292
+ decomposer in `convex_decomposition.js` explicitly notes it fragments convex
293
+ input), bounding-volume construction, and `Hull()`-based modelling
294
+ (rounded frames, sweeps between poses — manifold's `Hull(vector<Manifold>)` is
295
+ one of its most-used operations in practice).
296
+
297
+ ### 4.4 "A and C but not B" — cheap middles missing between expensive ends
298
+
299
+ **a) SDF → mesh.** meep has a complete implicit-shape algebra:
300
+ `AbstractShape3D.signed_distance_at_point`, `signed_distance_gradient_at_point`,
301
+ `contains_point`, `support`, `raycast`, plus `shape3_voxelize_to_grid`, plus
302
+ `probe_sdf_grid` / `probe_place_sdf_cover` in the sound simulator. It has
303
+ world-class mesh *processing* downstream. It has **no isosurface extraction** —
304
+ no marching cubes, no marching tetrahedra, no dual contouring. So a field can be
305
+ sampled but never turned into geometry. manifold's `LevelSet(sdf, bounds,
306
+ edgeLength, level, tolerance)` is ~22KB and is the single cheapest way for meep
307
+ to acquire *approximate* boolean modelling: union/intersect/subtract of SDFs is
308
+ `min`/`max`/`max(−)`, and the mesher does the rest.
309
+
310
+ **b) `UnionShape3D` with no siblings.** `core/geom/3d/shape/` has
311
+ `UnionShape3D` and no `IntersectionShape3D`, no `DifferenceShape3D`, no
312
+ `TransformedShape3D`-style negation. This is the literal shape of the
313
+ user's concern: a caller who finds `UnionShape3D` reasonably expects the other
314
+ two Boolean combinators to exist. Both are ~30 lines each given the existing
315
+ `AbstractShape3D` interface (`max`, and `max(a, −b)` for signed distance;
316
+ intersect/complement for `contains_point`; bounds from the intersection /
317
+ from `a`).
318
+
319
+ **c) Refine / smooth / subdivide.** manifold ships `Refine(n)`,
320
+ `RefineToLength`, `RefineToTolerance`, `SmoothByNormals`, `SmoothOut`, and
321
+ `Smooth(mesh, sharpenedEdges)` — a full Bézier-triangle subdivision surface
322
+ with per-halfedge tangents, sharp-edge control, and 18 dedicated tests.
323
+ meep can *decimate* well and *parameterise* excellently but cannot refine at
324
+ all. For offline work (the stated context) refinement is as common as
325
+ decimation.
326
+
327
+ **d) Genus / Euler characteristic.** meep has connected components, boundary
328
+ loop walking, and manifoldness predicates on edges — every ingredient — but no
329
+ `genus()`. manifold's is three lines (`χ = V − E + F`, `genus = 1 − χ/2`,
330
+ `src/properties.cpp:241`).
331
+
332
+ **e) Winding number.** meep has BVH raycasting and `bt_mesh_segment_penetrates_surface`
333
+ but no point-in-mesh / winding number for arbitrary point sets. manifold's
334
+ `WindingNumber(points)` is ray-parity over its collider, with tests specifically
335
+ for the watertight-vertex and silhouette-edge cases that break naive parity
336
+ counting (`measurement_test.cpp`).
337
+
338
+ **f) Mesh-mesh minimum distance.** meep has per-pair narrowphase distance for
339
+ collision shapes but no `MinGap(other, searchLength)` for triangle meshes, and
340
+ no triangle-triangle distance primitive (manifold's `src/tri_dist.h`). That
341
+ primitive is also the missing piece for a self-intersection test (§2.3).
342
+
343
+ ### 4.5 API-level: the tolerance model
344
+
345
+ manifold carries `epsilon_` (numerical validity floor, derived from the bounding
346
+ box) and `tolerance_` (user-facing simplification budget) **on the mesh**, sets
347
+ them once (`SetEpsilon`, `src/impl.cpp:684`), and threads them through every
348
+ operation. `MeshGL.tolerance` round-trips through serialisation. There are four
349
+ dedicated tests (`Properties.Epsilon`, `Epsilon2`, `Tolerance`,
350
+ `ToleranceSphere`) asserting the propagation rules.
351
+
352
+ meep passes tolerances ad hoc: `max_error` to `bt_mesh_simplify_by_error`,
353
+ `tolerance` to `tm_weld_duplicate_vertices`, `EPSILON` defaults scattered per
354
+ call site. Nothing carries a tolerance with the data, so a pipeline of five
355
+ operations has five independently-chosen epsilons and no way to reason about
356
+ accumulated drift. manifold's design is strictly better here and costs one
357
+ field on `BinaryTopology`.
358
+
359
+ Two related API notes:
360
+
361
+ - **Error reporting.** `Manifold::Status()` returns a 15-value `Error` enum
362
+ (`NotManifold`, `NonFiniteVertex`, `VertexOutOfBounds`, `ResultTooLarge`, …)
363
+ and operations propagate it rather than throwing — with an entire test family
364
+ (`Manifold.ErrorPropagation*`, 13 tests) verifying that an invalid input stays
365
+ invalid through every operation. meep's topology layer generally asserts (and
366
+ asserts compile out in production).
367
+ - **Provenance.** `MeshGL`'s `runIndex` / `runOriginalID` / `runTransform` /
368
+ `faceID` let a caller recover, for every output triangle, which input mesh and
369
+ which original face it came from — through booleans. That is what makes
370
+ "re-apply the original materials to the boolean result" a solved problem.
371
+ meep's `BinaryTopology` has per-element data pools that could carry the same
372
+ information but no convention for doing so.
373
+
374
+ ---
375
+
376
+ ## 5. Where meep leads
377
+
378
+ Not a courtesy section — these are areas where manifold has nothing and the
379
+ right advice is "do not migrate away from meep for this":
380
+
381
+ - **UV atlasing** (`core/geom/3d/atlas/`): chart segmentation, region-atomic
382
+ segmentation, LSCM *and* BFF parameterisation, stretch metrics, chart merging,
383
+ piecewise re-segmentation, bitmap + skyline packing, multi-page elastic
384
+ density. manifold has no UV pipeline at all.
385
+ - **Acceleration structures**: `bvh8` (wide, compressed, `imask` authority,
386
+ binary-serialisable) vs manifold's single linear Morton BVH.
387
+ - **Tetrahedralisation** and volumetric meshing.
388
+ - **Geodesic / navigation queries**: Polyanya, funnel string-pull, eikonal
389
+ boundary distance fields, face islands.
390
+ - **Physics-grade shape queries**: GJK/EPA, exact SAT manifolds, convex
391
+ decomposition, capsule/box/heightmap primitives with exact closed forms.
392
+ - **Half-edge generality**: n-gons, wire edges, per-element attribute pools,
393
+ T-junction resolution, hole closing — manifold's triangle-only halfedge
394
+ cannot represent most of this.
395
+ - **Error-bounded decimation with a true quadric heap** — manifold's
396
+ `SimplifyTopology` is a cleanup pass, not an LOD tool.
397
+
398
+ ---
399
+
400
+ ## 6. Recommendations
401
+
402
+ Ordered by (unblocks-other-work × confidence) ÷ effort.
403
+
404
+ ### P0 — correctness, small
405
+
406
+ 1. **Fix `tri3_compute_signed_volume`** (§3.1). Edge-difference form; subtract a
407
+ reference point at the mesh level. Add specs at `1e6`/`1e7` offsets. Same fix
408
+ in `ConvexHullShape3D.recompute()`. *(~1h)*
409
+ 2. **Add Kahan summation to `tri3_mesh_compute_signed_volume`** and to any
410
+ surface-area accumulator. *(~30m)*
411
+ 3. **Write `bt_mesh_validate(mesh)`** for `BinaryTopology` (§3.4): disk-cycle
412
+ closure and slot convention, radial-cycle closure, loop `next`/`prev`
413
+ inverse, loop→face→loop reachability, edge→loop→edge agreement, allocation
414
+ consistency, no dangling `NULL_POINTER`. Then a `bt_mesh_is_manifold` /
415
+ `bt_mesh_is_2_manifold` pair on top. Call it from every `bt_*` spec's
416
+ arrange/assert. This is the highest-value item in the whole document: it
417
+ converts ~40 mutators from "probably fine" to "checked", and it is the
418
+ prerequisite for trusting anything built later. *(~1–2 days incl. retrofitting specs)*
419
+
420
+ ### P1 — the unblocking primitive
421
+
422
+ 4. **Port a polygon triangulator with hole support** (§4.1). Monotone sweep is
423
+ the right choice (manifold's, and it's the one that survives fuzzing). Ship
424
+ it with: an ε parameter, a self-check mode, a fuzz spec, and a *corpus file*
425
+ of recorded failures replayed as tests. Then rewire `bt_face_triangulate` /
426
+ `bt_mesh_triangulate` and `bt_mesh_close_boundary_holes` onto it. *(~1 week)*
427
+
428
+ ### P2 — the cheap middles
429
+
430
+ 5. **`IntersectionShape3D` + `DifferenceShape3D`** to complete `UnionShape3D`
431
+ (§4.4-b). *(~half a day, including specs)*
432
+ 6. **Isosurface extraction** (`shape3_to_topology` / marching tetrahedra over
433
+ `AbstractShape3D.signed_distance_at_point`) (§4.4-a). With #5 this gives meep
434
+ approximate CSG immediately, over the shape algebra it already has, and feeds
435
+ straight into the existing decimation + atlas pipeline. Follow manifold's
436
+ `sdf.cpp` in using marching **tetrahedra** rather than cubes — no ambiguous
437
+ cases, guaranteed manifold output. *(~3–4 days)*
438
+ 7. **3D convex hull (quickhull)** (§4.3). Improves `convex_decomposition`
439
+ quality, unblocks `ConvexHullShape3D.from(points)`, and is the basis of
440
+ `Hull`-based modelling. Copy manifold's degenerate-case test list verbatim —
441
+ collinear points, coplanar points, <4 points, duplicate points. *(~3–4 days)*
442
+ 8. **`bt_mesh_genus` / `bt_mesh_euler_characteristic`** (§4.4-d) and
443
+ **`bt_mesh_winding_number`** (§4.4-e). *(~1 day together)*
444
+
445
+ ### P3 — the tolerance model
446
+
447
+ 9. **Carry `epsilon` / `tolerance` on `BinaryTopology`** (§4.5), derived from the
448
+ bounding box, settable, propagated by operations, serialised. Retrofit the
449
+ `bt_*` operations that currently take an `eps` argument to default to it.
450
+ *(~2–3 days, mostly mechanical)*
451
+ 10. **A `Status()`-style error channel** for topology operations, so a pipeline
452
+ can carry "this mesh is invalid because X" instead of asserting (and
453
+ asserts compile out in production, so today the failure is silent).
454
+
455
+ ### P4 — larger, only if the domain demands it
456
+
457
+ 11. **2D polygon boolean + offset** (§4.2). Big, but it is the correct
458
+ foundation for navmesh obstacle carving, footprint union, and inset/outset.
459
+ If the appetite isn't there, note that a well-specified subset (union of
460
+ simple polygons + inset/outset with miter joins) covers most in-engine uses.
461
+ 12. **Refine / subdivide** (§4.4-c). Comparatively self-contained; loop or
462
+ Bézier-triangle subdivision over `BinaryTopology`.
463
+ 13. **Full 3D mesh boolean.** Deliberately last. It is the largest single piece
464
+ of work in the comparison (manifold spends ~90KB across `boolean3.cpp` +
465
+ `boolean_result.cpp` + `boolean2*.cpp` and years of corpus-driven hardening),
466
+ and #6 (SDF level-set CSG) covers a large fraction of the practical demand
467
+ at a fraction of the cost. **Recommendation: do not build this.** If exact
468
+ boolean is genuinely required, depending on manifold's WASM build for that
469
+ one operation is the honest answer — the review found no evidence meep should
470
+ own it.
471
+
472
+ ### The meta-recommendation
473
+
474
+ The reason an agent left is not that any one of these was missing — it is that
475
+ **the first three questions it asked all missed**. The cheapest way to change
476
+ that outcome is P1 + P2 (#4–#8): a triangulator, the two missing shape
477
+ combinators, an isosurfacer, and a hull. That is roughly two to three weeks and
478
+ it converts meep from "cannot do solid modelling" to "can do solid modelling
479
+ approximately, over its own shape algebra, feeding its own best-in-class
480
+ downstream pipeline". Combined with P0 #3 (the validator), it also makes the
481
+ result trustworthy enough to chain — which is the other half of why manifold
482
+ gets picked.
483
+
484
+ ---
485
+
486
+ ## 7. Test-design lessons worth stealing
487
+
488
+ manifold's suite is unusually good and several patterns transfer directly:
489
+
490
+ 1. **Universal invariant assertions.** `test/test.h` defines `CheckStrictly()`,
491
+ `CheckGL()`, `CheckFinite()`, `Identical()`, `RelatedGL()` and nearly every
492
+ test calls one. The assertion is on *the invariant*, not on specific values —
493
+ which is exactly meep's stated black-box testing preference.
494
+ 2. **Recorded-failure corpora.** `test/polygons/*.txt` (2.9MB) and
495
+ `test/models/*.obj` are literally the inputs that once broke the library,
496
+ replayed forever. Cheap to build, permanently valuable. meep's atlas suite
497
+ already hints at this (the retracted "sphere over-fragmentation" finding
498
+ turned out to be a fixture bug) — formalising it as a corpus would have
499
+ caught that earlier.
500
+ 3. **Fuzzing the fragile primitive.** `polygon_fuzz.cpp` / `manifold_fuzz.cpp`
501
+ fuzz the triangulator and the boolean specifically, with timeouts, because
502
+ those are the two things that hang rather than fail.
503
+ 4. **Determinism as a first-class test family.** `Boolean.DeterminismSimple{Subtract,
504
+ Union,Intersect}`, `Manifold.MeshDeterminism`. meep already does this for
505
+ physics (`determinism.spec.js`); the topology layer should too, especially
506
+ given order-dependent welding.
507
+ 5. **Error-propagation as a test family.** 13 `Manifold.ErrorPropagation*` tests
508
+ assert that an invalid input remains invalid through every single operation.
509
+ Pairs with recommendation #10.
510
+ 6. **Named real-world regression tests.** `BooleanComplex.GenericTwinBooleanTest7081`,
511
+ `Havocglass8Bool`, `CraycloudBool`, `OpenscadCrash` — each one a bug report
512
+ frozen into a test with the model checked in.
513
+
514
+ ---
515
+
516
+ ## Appendix A — manifold source map
517
+
518
+ | File | Size | What it is |
519
+ | --- | --- | --- |
520
+ | `src/boolean3.cpp` + `boolean_result.cpp` | 60KB | 3D mesh boolean (Smith & Dodgson) |
521
+ | `src/boolean2*.cpp` | 70KB | 2D polygon boolean + offset (own sweep-line, replaced Clipper2) |
522
+ | `src/polygon.cpp` + `polygon_internal.h` + `tree2d.cpp` | 45KB | Monotone-sweep triangulator with holes |
523
+ | `src/quickhull.cpp` | 30KB | 3D convex hull |
524
+ | `src/sdf.cpp` | 22KB | `LevelSet` — marching tetrahedra |
525
+ | `src/subdivision.cpp` + `smoothing.cpp` | 75KB | Refine + Bézier-triangle smoothing |
526
+ | `src/edge_op.cpp` | 35KB | Collapse / swap / dedupe / pinched-vert splitting |
527
+ | `src/impl.cpp` + `impl.h` | 60KB | Halfedge mesh, epsilon/tolerance, normals |
528
+ | `src/properties.cpp` | 16KB | Volume, area, curvature, genus, manifoldness, self-intersection |
529
+ | `src/csg_tree.cpp` | 36KB | Lazy CSG evaluation + batch boolean scheduling |
530
+ | `src/minkowski.cpp` | 8KB | Minkowski sum/difference via hull + boolean |
531
+ | `src/sort.cpp` | 24KB | Morton ordering, vertex merge, halfedge reordering |
532
+ | `src/collider.h` | 12KB | Linear BVH |
533
+ | `src/face_op.cpp` | 17KB | `Face2Tri`, `Slice`, `Project` |
534
+ | `test/` | ~450KB + 5MB models | 9 gtest files, 2 fuzz harnesses, polygon corpus, 17 regression OBJs |
535
+
536
+ ## Appendix B — reproduction of §3.1
537
+
538
+ ```js
539
+ // meep form: fully expanded determinant of absolute coordinates
540
+ function meep(a, b, c) {
541
+ const [ax,ay,az]=a, [bx,by,bz]=b, [cx,cy,cz]=c;
542
+ return (-(cx*by*az) + bx*cy*az + cx*ay*bz - ax*cy*bz - bx*ay*cz + ax*by*cz) / 6;
543
+ }
544
+ // manifold form: differences first
545
+ function manifoldForm(a, b, c) {
546
+ const u = [b[0]-a[0], b[1]-a[1], b[2]-a[2]];
547
+ const w = [c[0]-a[0], c[1]-a[1], c[2]-a[2]];
548
+ const cr = [u[1]*w[2]-u[2]*w[1], u[2]*w[0]-u[0]*w[2], u[0]*w[1]-u[1]*w[0]];
549
+ return (cr[0]*a[0] + cr[1]*a[1] + cr[2]*a[2]) / 6;
550
+ }
551
+ // Summed over the 12 triangles of a unit cube translated by (1e7, 1e7, 1e7):
552
+ // meep → -262144
553
+ // manifoldForm → 0.999999999069
554
+ ```
@@ -12,8 +12,12 @@
12
12
  * non-finite — callers exclude degenerate faces upstream rather than guarding
13
13
  * here.
14
14
  *
15
- * @param {number} ax @param {number} ay @param {number} az
16
- * @param {number} bx @param {number} by @param {number} bz
15
+ * @param {number} ax
16
+ * @param {number} ay
17
+ * @param {number} az
18
+ * @param {number} bx
19
+ * @param {number} by
20
+ * @param {number} bz
17
21
  * @returns {number}
18
22
  */
19
23
  export function v3_cotangent(ax: number, ay: number, az: number, bx: number, by: number, bz: number): number;
@@ -1 +1 @@
1
- {"version":3,"file":"v3_cotangent.d.ts","sourceRoot":"","sources":["../../../../../src/core/geom/vec3/v3_cotangent.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,iCAJW,MAAM,MAAa,MAAM,MAAa,MAAM,MAC5C,MAAM,MAAa,MAAM,MAAa,MAAM,GAC1C,MAAM,CAUlB"}
1
+ {"version":3,"file":"v3_cotangent.d.ts","sourceRoot":"","sources":["../../../../../src/core/geom/vec3/v3_cotangent.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,iCARW,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,GACJ,MAAM,CAUlB"}
@@ -14,8 +14,12 @@ import { v3_dot } from "./v3_dot.js";
14
14
  * non-finite — callers exclude degenerate faces upstream rather than guarding
15
15
  * here.
16
16
  *
17
- * @param {number} ax @param {number} ay @param {number} az
18
- * @param {number} bx @param {number} by @param {number} bz
17
+ * @param {number} ax
18
+ * @param {number} ay
19
+ * @param {number} az
20
+ * @param {number} bx
21
+ * @param {number} by
22
+ * @param {number} bz
19
23
  * @returns {number}
20
24
  */
21
25
  export function v3_cotangent(ax, ay, az, bx, by, bz) {
@@ -7,8 +7,12 @@
7
7
  *
8
8
  * @param {number[]|Float32Array|Float64Array} result destination
9
9
  * @param {number} result_offset offset into `result`
10
- * @param {number} ax @param {number} ay @param {number} az first vector
11
- * @param {number} bx @param {number} by @param {number} bz second vector
10
+ * @param {number} ax first vector x
11
+ * @param {number} ay first vector y
12
+ * @param {number} az first vector z
13
+ * @param {number} bx second vector x
14
+ * @param {number} by second vector y
15
+ * @param {number} bz second vector z
12
16
  *
13
17
  * @author Alex Goldring
14
18
  * @copyright Company Named Limited (c) 2026
@@ -1 +1 @@
1
- {"version":3,"file":"v3_cross.d.ts","sourceRoot":"","sources":["../../../../../src/core/geom/vec3/v3_cross.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,iCARW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,iBAClC,MAAM,MACN,MAAM,MAAa,MAAM,MAAa,MAAM,MAC5C,MAAM,MAAa,MAAM,MAAa,MAAM,QAStD"}
1
+ {"version":3,"file":"v3_cross.d.ts","sourceRoot":"","sources":["../../../../../src/core/geom/vec3/v3_cross.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,iCAZW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,iBAClC,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAShB"}
@@ -7,8 +7,12 @@
7
7
  *
8
8
  * @param {number[]|Float32Array|Float64Array} result destination
9
9
  * @param {number} result_offset offset into `result`
10
- * @param {number} ax @param {number} ay @param {number} az first vector
11
- * @param {number} bx @param {number} by @param {number} bz second vector
10
+ * @param {number} ax first vector x
11
+ * @param {number} ay first vector y
12
+ * @param {number} az first vector z
13
+ * @param {number} bx second vector x
14
+ * @param {number} by second vector y
15
+ * @param {number} bz second vector z
12
16
  *
13
17
  * @author Alex Goldring
14
18
  * @copyright Company Named Limited (c) 2026
@@ -6,8 +6,12 @@
6
6
  *
7
7
  * @param {number[]|Float32Array|Float64Array} result destination
8
8
  * @param {number} result_offset offset into `result`
9
- * @param {number} ax @param {number} ay @param {number} az first factor
10
- * @param {number} bx @param {number} by @param {number} bz second factor
9
+ * @param {number} ax first factor x
10
+ * @param {number} ay first factor y
11
+ * @param {number} az first factor z
12
+ * @param {number} bx second factor x
13
+ * @param {number} by second factor y
14
+ * @param {number} bz second factor z
11
15
  *
12
16
  * @author Alex Goldring
13
17
  * @copyright Company Named Limited (c) 2026
@@ -1 +1 @@
1
- {"version":3,"file":"v3_multiply.d.ts","sourceRoot":"","sources":["../../../../../src/core/geom/vec3/v3_multiply.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,oCARW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,iBAClC,MAAM,MACN,MAAM,MAAa,MAAM,MAAa,MAAM,MAC5C,MAAM,MAAa,MAAM,MAAa,MAAM,QAStD"}
1
+ {"version":3,"file":"v3_multiply.d.ts","sourceRoot":"","sources":["../../../../../src/core/geom/vec3/v3_multiply.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,oCAZW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,iBAClC,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAShB"}