@woosh/meep-engine 2.156.0 → 2.157.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 (654) hide show
  1. package/README.md +1 -3
  2. package/editor/view/ecs/components/common/AutoCanvasView.js +100 -53
  3. package/editor/view/ecs/components/common/TextController.js +59 -0
  4. package/editor/view/node-graph/NodeGraphCamera.js +90 -0
  5. package/editor/view/node-graph/NodeGraphEditorView.js +121 -22
  6. package/editor/view/node-graph/NodeGraphSelection.js +89 -0
  7. package/editor/view/node-graph/NodeGraphView.js +669 -453
  8. package/editor/view/node-graph/NodeView.js +211 -135
  9. package/editor/view/node-graph/actions/ConnectionCreateAction.js +53 -0
  10. package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -0
  11. package/editor/view/node-graph/actions/NodeDeleteAction.js +88 -0
  12. package/editor/view/node-graph/actions/NodeParameterSetAction.js +52 -0
  13. package/editor/view/node-graph/actions/NodesMoveAction.js +41 -0
  14. package/editor/view/node-graph/actions/SelectionSetAction.js +60 -0
  15. package/editor/view/node-graph/connection_wire_geometry.js +107 -0
  16. package/package.json +1 -1
  17. package/samples/generation/SampleGenerator0.js +8 -1
  18. package/src/core/binary/reinterpret_float32_as_uint32.d.ts +7 -0
  19. package/src/core/binary/reinterpret_float32_as_uint32.d.ts.map +1 -0
  20. package/src/core/binary/reinterpret_float32_as_uint32.js +13 -0
  21. package/src/core/binary/reinterpret_uint32_as_float32.d.ts +7 -0
  22. package/src/core/binary/reinterpret_uint32_as_float32.d.ts.map +1 -0
  23. package/src/core/binary/reinterpret_uint32_as_float32.js +14 -0
  24. package/src/core/bvh2/bvh3/ebvh_build_for_geometry_incremental.d.ts.map +1 -1
  25. package/src/core/bvh2/bvh3/ebvh_build_for_geometry_incremental.js +1 -3
  26. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_sphere.d.ts +12 -0
  27. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_sphere.d.ts.map +1 -0
  28. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_sphere.js +92 -0
  29. package/src/core/bvh8/BVH8.d.ts +127 -0
  30. package/src/core/bvh8/BVH8.d.ts.map +1 -0
  31. package/src/core/bvh8/BVH8.js +436 -0
  32. package/src/core/bvh8/NOTES.md +63 -0
  33. package/src/core/bvh8/build/BVH8Converter.d.ts +59 -0
  34. package/src/core/bvh8/build/BVH8Converter.d.ts.map +1 -0
  35. package/src/core/bvh8/build/BVH8Converter.js +588 -0
  36. package/src/core/bvh8/build/NodeProxy.d.ts +66 -0
  37. package/src/core/bvh8/build/NodeProxy.d.ts.map +1 -0
  38. package/src/core/bvh8/build/NodeProxy.js +308 -0
  39. package/src/core/bvh8/build/TriangleCluster.d.ts +29 -0
  40. package/src/core/bvh8/build/TriangleCluster.d.ts.map +1 -0
  41. package/src/core/bvh8/build/TriangleCluster.js +123 -0
  42. package/src/core/bvh8/build/aabb3_compute_merge_cost.d.ts +8 -0
  43. package/src/core/bvh8/build/aabb3_compute_merge_cost.d.ts.map +1 -0
  44. package/src/core/bvh8/build/aabb3_compute_merge_cost.js +29 -0
  45. package/src/core/bvh8/build/aabb3_from_triangle_by_index.d.ts +10 -0
  46. package/src/core/bvh8/build/aabb3_from_triangle_by_index.d.ts.map +1 -0
  47. package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +18 -0
  48. package/src/core/bvh8/build/bvh8_build_for_geometry.d.ts +10 -0
  49. package/src/core/bvh8/build/bvh8_build_for_geometry.d.ts.map +1 -0
  50. package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -0
  51. package/src/core/bvh8/build/bvh8_from_proxy.d.ts +9 -0
  52. package/src/core/bvh8/build/bvh8_from_proxy.d.ts.map +1 -0
  53. package/src/core/bvh8/build/bvh8_from_proxy.js +256 -0
  54. package/src/core/bvh8/build/byte.d.ts +7 -0
  55. package/src/core/bvh8/build/byte.d.ts.map +1 -0
  56. package/src/core/bvh8/build/byte.js +10 -0
  57. package/src/core/bvh8/build/encode_bounds_e.d.ts +9 -0
  58. package/src/core/bvh8/build/encode_bounds_e.d.ts.map +1 -0
  59. package/src/core/bvh8/build/encode_bounds_e.js +12 -0
  60. package/src/core/bvh8/bvh8_convert_to_dot.d.ts +11 -0
  61. package/src/core/bvh8/bvh8_convert_to_dot.d.ts.map +1 -0
  62. package/src/core/bvh8/bvh8_convert_to_dot.js +133 -0
  63. package/src/core/bvh8/bvh8_count_primitives.d.ts +22 -0
  64. package/src/core/bvh8/bvh8_count_primitives.d.ts.map +1 -0
  65. package/src/core/bvh8/bvh8_count_primitives.js +98 -0
  66. package/src/core/bvh8/bvh8_geometry_validate.d.ts +16 -0
  67. package/src/core/bvh8/bvh8_geometry_validate.d.ts.map +1 -0
  68. package/src/core/bvh8/bvh8_geometry_validate.js +149 -0
  69. package/src/core/bvh8/bvh8_geometry_validate_indirect.d.ts +16 -0
  70. package/src/core/bvh8/bvh8_geometry_validate_indirect.d.ts.map +1 -0
  71. package/src/core/bvh8/bvh8_geometry_validate_indirect.js +177 -0
  72. package/src/core/bvh8/bvh8_get_node_bounds.d.ts +9 -0
  73. package/src/core/bvh8/bvh8_get_node_bounds.d.ts.map +1 -0
  74. package/src/core/bvh8/bvh8_get_node_bounds.js +35 -0
  75. package/src/core/bvh8/bvh8_get_node_child_bounds.d.ts +10 -0
  76. package/src/core/bvh8/bvh8_get_node_child_bounds.d.ts.map +1 -0
  77. package/src/core/bvh8/bvh8_get_node_child_bounds.js +53 -0
  78. package/src/core/bvh8/bvh8_node_child_surface_area.d.ts +9 -0
  79. package/src/core/bvh8/bvh8_node_child_surface_area.d.ts.map +1 -0
  80. package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -0
  81. package/src/core/bvh8/bvh8_node_count_triangles.d.ts +8 -0
  82. package/src/core/bvh8/bvh8_node_count_triangles.d.ts.map +1 -0
  83. package/src/core/bvh8/bvh8_node_count_triangles.js +28 -0
  84. package/src/core/bvh8/bvh8_quality.d.ts +8 -0
  85. package/src/core/bvh8/bvh8_quality.d.ts.map +1 -0
  86. package/src/core/bvh8/bvh8_quality.js +73 -0
  87. package/src/core/bvh8/bvh8_validate_structure.d.ts +15 -0
  88. package/src/core/bvh8/bvh8_validate_structure.d.ts.map +1 -0
  89. package/src/core/bvh8/bvh8_validate_structure.js +87 -0
  90. package/src/core/collection/Uint32MinHeap.d.ts +56 -0
  91. package/src/core/collection/Uint32MinHeap.d.ts.map +1 -0
  92. package/src/core/collection/Uint32MinHeap.js +109 -0
  93. package/src/core/collection/list/FilteredListProjection.js +1 -1
  94. package/src/{engine/physics/island → core/collection/union-find}/union_find.d.ts +8 -5
  95. package/src/core/collection/union-find/union_find.d.ts.map +1 -0
  96. package/src/{engine/physics/island → core/collection/union-find}/union_find.js +8 -5
  97. package/src/core/dom/isImageBitmap.d.ts +7 -0
  98. package/src/core/dom/isImageBitmap.d.ts.map +1 -0
  99. package/src/core/dom/isImageBitmap.js +12 -0
  100. package/src/core/function/frameThrottle.d.ts +8 -0
  101. package/src/core/function/frameThrottle.d.ts.map +1 -0
  102. package/src/core/function/frameThrottle.js +23 -0
  103. package/src/{engine/physics/narrowphase/clip_against_axis_uv.d.ts → core/geom/2d/polygon/polygon2_clip_axis_halfplane.d.ts} +3 -3
  104. package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.d.ts.map +1 -0
  105. package/src/{engine/physics/narrowphase/clip_against_axis_uv.js → core/geom/2d/polygon/polygon2_clip_axis_halfplane.js} +51 -51
  106. package/src/{engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts → core/geom/3d/aabb/aabb3_transform_oriented_inverse.d.ts} +9 -7
  107. package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.d.ts.map +1 -0
  108. package/src/{engine/physics/narrowphase/decomposition/aabb_world_to_local.js → core/geom/3d/aabb/aabb3_transform_oriented_inverse.js} +9 -7
  109. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.d.ts +12 -0
  110. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.d.ts.map +1 -0
  111. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +46 -0
  112. package/src/core/geom/3d/box/box3_projected_half_extent.d.ts +28 -0
  113. package/src/core/geom/3d/box/box3_projected_half_extent.d.ts.map +1 -0
  114. package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -0
  115. package/src/core/geom/3d/frustum/read_cluster_frustum_corners.js +1 -1
  116. package/src/core/geom/3d/frustum/read_frustum_corner.d.ts +9 -0
  117. package/src/core/geom/3d/frustum/read_frustum_corner.d.ts.map +1 -0
  118. package/src/core/geom/3d/frustum/read_frustum_corner.js +14 -0
  119. package/src/core/geom/3d/gjk/gjk.d.ts.map +1 -0
  120. package/src/{engine/physics → core/geom/3d}/gjk/gjk.js +430 -372
  121. package/src/{engine/physics → core/geom/3d}/gjk/gjk_epa_penetration.d.ts +8 -5
  122. package/src/core/geom/3d/gjk/gjk_epa_penetration.d.ts.map +1 -0
  123. package/src/{engine/physics → core/geom/3d}/gjk/gjk_epa_penetration.js +520 -544
  124. package/src/{engine/physics → core/geom/3d}/gjk/minkowski_support.d.ts +5 -4
  125. package/src/core/geom/3d/gjk/minkowski_support.d.ts.map +1 -0
  126. package/src/{engine/physics → core/geom/3d}/gjk/minkowski_support.js +71 -70
  127. package/src/{engine/physics → core/geom/3d}/gjk/mpr.d.ts +3 -3
  128. package/src/core/geom/3d/gjk/mpr.d.ts.map +1 -0
  129. package/src/{engine/physics → core/geom/3d}/gjk/mpr.js +368 -362
  130. package/src/{engine/physics/integration/quat_integrate.d.ts → core/geom/3d/quaternion/quat3_integrate.d.ts} +2 -2
  131. package/src/core/geom/3d/quaternion/quat3_integrate.d.ts.map +1 -0
  132. package/src/{engine/physics/integration/quat_integrate.js → core/geom/3d/quaternion/quat3_integrate.js} +1 -1
  133. package/src/{engine/physics/narrowphase/PosedShape.d.ts → core/geom/3d/shape/PosedShape3D.d.ts} +9 -8
  134. package/src/{engine/physics/narrowphase/PosedShape.d.ts.map → core/geom/3d/shape/PosedShape3D.d.ts.map} +1 -1
  135. package/src/{engine/physics/narrowphase/PosedShape.js → core/geom/3d/shape/PosedShape3D.js} +10 -9
  136. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  137. package/src/core/geom/3d/shape/TransformedShape3D.js +15 -11
  138. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +1 -1
  139. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +1 -1
  140. package/src/core/math/complex/complex_add.d.ts +1 -1
  141. package/src/core/math/complex/complex_add.d.ts.map +1 -1
  142. package/src/core/math/complex/complex_add.js +12 -3
  143. package/src/core/math/complex/complex_div.d.ts +1 -1
  144. package/src/core/math/complex/complex_div.d.ts.map +1 -1
  145. package/src/core/math/complex/complex_div.js +11 -4
  146. package/src/core/math/complex/complex_mul.d.ts +1 -1
  147. package/src/core/math/complex/complex_mul.d.ts.map +1 -1
  148. package/src/core/math/complex/complex_mul.js +10 -3
  149. package/src/core/math/complex/complex_sub.d.ts +1 -1
  150. package/src/core/math/complex/complex_sub.d.ts.map +1 -1
  151. package/src/core/math/complex/complex_sub.js +12 -3
  152. package/src/{engine/physics/fluid/solver/optimal_sor_omega.d.ts → core/math/linalg/sor_optimal_omega.d.ts} +4 -3
  153. package/src/core/math/linalg/sor_optimal_omega.d.ts.map +1 -0
  154. package/src/{engine/physics/fluid/solver/optimal_sor_omega.js → core/math/linalg/sor_optimal_omega.js} +4 -3
  155. package/src/core/math/lookup/ParameterLookupTable.d.ts +123 -0
  156. package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -0
  157. package/src/core/math/lookup/ParameterLookupTable.js +495 -0
  158. package/src/core/math/lookup/ParameterLookupTableFlags.d.ts +5 -0
  159. package/src/core/math/lookup/ParameterLookupTableFlags.d.ts.map +1 -0
  160. package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -0
  161. package/src/core/math/physics/kinematics/computeInterceptPoint.d.ts.map +1 -0
  162. package/src/{engine/physics → core/math/physics/kinematics}/computeInterceptPoint.js +79 -79
  163. package/src/core/math/physics/mie/ri_air.d.ts.map +1 -1
  164. package/src/core/math/physics/mie/ri_air.js +1 -3
  165. package/src/core/math/physics/mie/ri_ammonium_sulfate.d.ts.map +1 -1
  166. package/src/core/math/physics/mie/ri_ammonium_sulfate.js +1 -3
  167. package/src/core/math/physics/mie/ri_brine.d.ts.map +1 -1
  168. package/src/core/math/physics/mie/ri_brine.js +1 -3
  169. package/src/core/math/physics/mie/ri_dust.d.ts.map +1 -1
  170. package/src/core/math/physics/mie/ri_dust.js +1 -3
  171. package/src/core/math/physics/mie/ri_pollen.d.ts.map +1 -1
  172. package/src/core/math/physics/mie/ri_pollen.js +1 -3
  173. package/src/core/math/physics/mie/ri_smoke.d.ts.map +1 -1
  174. package/src/core/math/physics/mie/ri_smoke.js +1 -3
  175. package/src/core/math/physics/mie/ri_soot.d.ts.map +1 -1
  176. package/src/core/math/physics/mie/ri_soot.js +1 -3
  177. package/src/core/math/physics/mie/ri_water.d.ts.map +1 -1
  178. package/src/core/math/physics/mie/ri_water.js +1 -3
  179. package/src/core/math/random/random_pick_weighted_index.d.ts +10 -0
  180. package/src/core/math/random/random_pick_weighted_index.d.ts.map +1 -0
  181. package/src/core/math/random/random_pick_weighted_index.js +26 -0
  182. package/src/core/model/node-graph/NodeGraph.d.ts +9 -0
  183. package/src/core/model/node-graph/NodeGraph.d.ts.map +1 -1
  184. package/src/core/model/node-graph/NodeGraph.js +38 -0
  185. package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts +23 -0
  186. package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts.map +1 -1
  187. package/src/core/model/node-graph/visual/NodeGraphVisualData.js +54 -0
  188. package/src/core/path/convertPathToURL.d.ts +9 -0
  189. package/src/core/path/convertPathToURL.d.ts.map +1 -0
  190. package/src/core/path/convertPathToURL.js +107 -0
  191. package/src/core/process/worker/WorkerBuilder.js +1 -1
  192. package/src/core/process/worker/extractTransferables.js +1 -1
  193. package/src/engine/animation/curve/draw/build_tangent_editor.d.ts.map +1 -1
  194. package/src/engine/animation/curve/draw/build_tangent_editor.js +8 -1
  195. package/src/engine/animation/curve/editor/createKeyframeDraggableAspect.d.ts.map +1 -1
  196. package/src/engine/animation/curve/editor/createKeyframeDraggableAspect.js +11 -5
  197. package/src/engine/asset/Asset.d.ts.map +1 -1
  198. package/src/engine/asset/Asset.js +16 -6
  199. package/src/engine/asset/AssetManager.d.ts +61 -52
  200. package/src/engine/asset/AssetManager.d.ts.map +1 -1
  201. package/src/engine/asset/AssetManager.js +1411 -1045
  202. package/src/engine/asset/AssetRequest.d.ts +1 -1
  203. package/src/engine/asset/AssetRequest.d.ts.map +1 -1
  204. package/src/engine/asset/AssetRequest.js +1 -1
  205. package/src/engine/asset/AssetRequestScope.d.ts.map +1 -1
  206. package/src/engine/asset/AssetRequestScope.js +7 -0
  207. package/src/engine/asset/PendingAsset.d.ts +32 -1
  208. package/src/engine/asset/PendingAsset.d.ts.map +1 -1
  209. package/src/engine/asset/PendingAsset.js +108 -61
  210. package/src/engine/asset/loaders/ArrayBufferLoader.js +2 -2
  211. package/src/engine/asset/loaders/AssetLoader.d.ts.map +1 -1
  212. package/src/engine/asset/loaders/AssetLoader.js +19 -2
  213. package/src/engine/asset/loaders/GLTFAssetLoader.d.ts.map +1 -1
  214. package/src/engine/asset/loaders/GLTFAssetLoader.js +123 -114
  215. package/src/engine/asset/loaders/JavascriptAssetLoader.d.ts +1 -1
  216. package/src/engine/asset/loaders/JavascriptAssetLoader.d.ts.map +1 -1
  217. package/src/engine/asset/loaders/JavascriptAssetLoader.js +31 -47
  218. package/src/engine/asset/loaders/JsonAssetLoader.js +1 -1
  219. package/src/engine/asset/loaders/SVGAssetLoader.js +2 -2
  220. package/src/engine/asset/loaders/SoundAssetLoader.js +1 -1
  221. package/src/engine/asset/loaders/TextAssetLoader.js +2 -2
  222. package/src/{core → engine/asset/loaders}/font/FontAsset.d.ts +1 -1
  223. package/src/engine/asset/loaders/font/FontAsset.d.ts.map +1 -0
  224. package/src/{core → engine/asset/loaders}/font/FontAsset.js +21 -21
  225. package/src/{core → engine/asset/loaders}/font/FontAssetLoader.d.ts +1 -1
  226. package/src/engine/asset/loaders/font/FontAssetLoader.d.ts.map +1 -0
  227. package/src/{core → engine/asset/loaders}/font/FontAssetLoader.js +20 -20
  228. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts +1 -1
  229. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts.map +1 -1
  230. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +11 -20
  231. package/src/engine/asset/loaders/texture/TextureAssetLoader.d.ts.map +1 -1
  232. package/src/engine/asset/loaders/texture/TextureAssetLoader.js +8 -2
  233. package/src/engine/asset/preloader/AssetPreloader.js +1 -1
  234. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +1 -1
  235. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +1 -1
  236. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +19 -22
  237. package/src/engine/graphics/FrameThrottle.d.ts +1 -7
  238. package/src/engine/graphics/FrameThrottle.d.ts.map +1 -1
  239. package/src/engine/graphics/FrameThrottle.js +2 -24
  240. package/src/{core/geom/3d/shape/util → engine/graphics/debug}/shape_to_visual_entity.d.ts +1 -1
  241. package/src/engine/graphics/debug/shape_to_visual_entity.d.ts.map +1 -0
  242. package/src/{core/geom/3d/shape/util → engine/graphics/debug}/shape_to_visual_entity.js +159 -159
  243. package/src/{core/geom/3d/tetrahedra → engine/graphics/debug}/visualize_tetrahedral_mesh.d.ts +1 -1
  244. package/src/engine/graphics/debug/visualize_tetrahedral_mesh.d.ts.map +1 -0
  245. package/src/{core/geom/3d/tetrahedra → engine/graphics/debug}/visualize_tetrahedral_mesh.js +46 -46
  246. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.d.ts +1 -1
  247. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.d.ts.map +1 -1
  248. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -32
  249. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParameterLookupTableSerializationAdapter.d.ts.map +1 -1
  250. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParameterLookupTableSerializationAdapter.js +2 -76
  251. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.d.ts.map +1 -1
  252. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -427
  253. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.d.ts +1 -4
  254. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.d.ts.map +1 -1
  255. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -6
  256. package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +1 -1
  257. package/src/engine/graphics/render/forward_plus/read_frustum_corner.d.ts +1 -8
  258. package/src/engine/graphics/render/forward_plus/read_frustum_corner.d.ts.map +1 -1
  259. package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -14
  260. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.d.ts +1 -11
  261. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.d.ts.map +1 -1
  262. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -46
  263. package/src/engine/graphics/sh3/prototypeSH3Probe.js +1 -1
  264. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.d.ts +27 -0
  265. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.d.ts.map +1 -0
  266. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -0
  267. package/src/engine/graphics/texture/isImageBitmap.d.ts +1 -6
  268. package/src/engine/graphics/texture/isImageBitmap.d.ts.map +1 -1
  269. package/src/engine/graphics/texture/isImageBitmap.js +2 -12
  270. package/src/{core/process/action → engine/intelligence/behavior/util}/AsynchronousDelayAction.d.ts +2 -2
  271. package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.d.ts.map +1 -0
  272. package/src/{core/process/action → engine/intelligence/behavior/util}/AsynchronousDelayAction.js +55 -55
  273. package/src/engine/network/NetworkSession.d.ts +12 -1
  274. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  275. package/src/engine/network/NetworkSession.js +52 -1
  276. package/src/engine/network/README.md +45 -0
  277. package/src/engine/network/convertPathToURL.d.ts +1 -8
  278. package/src/engine/network/convertPathToURL.d.ts.map +1 -1
  279. package/src/engine/network/convertPathToURL.js +2 -107
  280. package/src/engine/network/core/quantize/quantize_float.d.ts.map +1 -1
  281. package/src/engine/network/core/quantize/quantize_float.js +7 -0
  282. package/src/engine/network/core/quantize/quantize_position.d.ts.map +1 -1
  283. package/src/engine/network/core/quantize/quantize_position.js +12 -1
  284. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  285. package/src/engine/network/orchestrator/NetworkPeer.js +15 -1
  286. package/src/engine/network/replication/Replicator.d.ts +8 -0
  287. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  288. package/src/engine/network/replication/Replicator.js +48 -0
  289. package/src/engine/network/transport/Channel.d.ts.map +1 -1
  290. package/src/engine/network/transport/Channel.js +46 -12
  291. package/src/engine/network/transport/ReliableCommandPipeline.d.ts +16 -0
  292. package/src/engine/network/transport/ReliableCommandPipeline.d.ts.map +1 -1
  293. package/src/engine/network/transport/ReliableCommandPipeline.js +29 -0
  294. package/src/engine/network/transport/adapters/NodeUDPTransport.d.ts.map +1 -1
  295. package/src/engine/network/transport/adapters/NodeUDPTransport.js +7 -1
  296. package/src/engine/network/transport/fragments/packet_size.d.ts +5 -5
  297. package/src/engine/network/transport/fragments/packet_size.d.ts.map +1 -1
  298. package/src/engine/network/transport/fragments/packet_size.js +5 -5
  299. package/src/engine/physics/BULLET_REVIEW.md +1 -1
  300. package/src/engine/physics/JOLT_REVIEW.md +2 -2
  301. package/src/engine/physics/PLAN.md +1094 -945
  302. package/src/engine/physics/RAPIER_REVIEW.md +2 -2
  303. package/src/engine/physics/body/BodyStorage.d.ts +2 -12
  304. package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
  305. package/src/engine/physics/body/BodyStorage.js +406 -452
  306. package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -1
  307. package/src/engine/physics/body/SolverBodyState.js +12 -3
  308. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts +28 -3
  309. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts.map +1 -1
  310. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +60 -24
  311. package/src/engine/physics/broadphase/generate_pairs.d.ts +9 -5
  312. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  313. package/src/engine/physics/broadphase/generate_pairs.js +52 -37
  314. package/src/engine/physics/ccd/linear_sweep.d.ts +15 -5
  315. package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -1
  316. package/src/engine/physics/ccd/linear_sweep.js +122 -40
  317. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  318. package/src/engine/physics/constraint/solve_constraints.js +830 -805
  319. package/src/engine/physics/contact/ManifoldStore.d.ts +91 -16
  320. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  321. package/src/engine/physics/contact/ManifoldStore.js +204 -60
  322. package/src/engine/physics/ecs/BodyKind.d.ts +7 -3
  323. package/src/engine/physics/ecs/BodyKind.d.ts.map +1 -1
  324. package/src/engine/physics/ecs/BodyKind.js +29 -25
  325. package/src/engine/physics/ecs/Collider.d.ts +7 -0
  326. package/src/engine/physics/ecs/Collider.d.ts.map +1 -1
  327. package/src/engine/physics/ecs/Collider.js +7 -0
  328. package/src/engine/physics/ecs/ColliderSerializationAdapter.js +1 -1
  329. package/src/engine/physics/ecs/PhysicsSystem.d.ts +110 -6
  330. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  331. package/src/engine/physics/ecs/PhysicsSystem.js +467 -45
  332. package/src/engine/physics/ecs/RigidBody.d.ts +20 -5
  333. package/src/engine/physics/ecs/RigidBody.d.ts.map +1 -1
  334. package/src/engine/physics/ecs/RigidBody.js +307 -286
  335. package/src/engine/physics/ecs/RigidBodyFlags.d.ts +6 -3
  336. package/src/engine/physics/ecs/RigidBodyFlags.d.ts.map +1 -1
  337. package/src/engine/physics/ecs/RigidBodyFlags.js +31 -28
  338. package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts +12 -4
  339. package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts.map +1 -1
  340. package/src/engine/physics/ecs/RigidBodySerializationAdapter.js +19 -5
  341. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.d.ts +10 -0
  342. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.d.ts.map +1 -0
  343. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -0
  344. package/src/engine/physics/ecs/find_non_finite_physics_state.d.ts +28 -0
  345. package/src/engine/physics/ecs/find_non_finite_physics_state.d.ts.map +1 -0
  346. package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -0
  347. package/src/engine/physics/events/ContactEventBuffer.d.ts +11 -0
  348. package/src/engine/physics/events/ContactEventBuffer.d.ts.map +1 -1
  349. package/src/engine/physics/events/ContactEventBuffer.js +40 -0
  350. package/src/engine/physics/events/diff_manifolds.d.ts +30 -13
  351. package/src/engine/physics/events/diff_manifolds.d.ts.map +1 -1
  352. package/src/engine/physics/events/diff_manifolds.js +87 -50
  353. package/src/engine/physics/fluid/FluidField.d.ts +45 -17
  354. package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
  355. package/src/engine/physics/fluid/FluidField.js +53 -23
  356. package/src/engine/physics/fluid/FluidSimulator.d.ts +141 -5
  357. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  358. package/src/engine/physics/fluid/FluidSimulator.js +336 -43
  359. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +114 -0
  360. package/src/engine/physics/fluid/ecs/FluidComponent.d.ts +4 -3
  361. package/src/engine/physics/fluid/ecs/FluidComponent.d.ts.map +1 -1
  362. package/src/engine/physics/fluid/ecs/FluidComponent.js +4 -3
  363. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
  364. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts +41 -0
  365. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts.map +1 -0
  366. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.js +124 -0
  367. package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts +27 -8
  368. package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts.map +1 -1
  369. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +67 -18
  370. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.d.ts +42 -0
  371. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.d.ts.map +1 -0
  372. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +136 -0
  373. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.d.ts +37 -0
  374. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.d.ts.map +1 -0
  375. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +169 -0
  376. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.d.ts +36 -0
  377. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.d.ts.map +1 -0
  378. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +100 -0
  379. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.d.ts +6 -0
  380. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.d.ts.map +1 -1
  381. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.js +6 -0
  382. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.d.ts +7 -2
  383. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.d.ts.map +1 -1
  384. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.js +17 -12
  385. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.d.ts +42 -0
  386. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.d.ts.map +1 -0
  387. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -0
  388. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts +32 -22
  389. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts.map +1 -1
  390. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +43 -26
  391. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.d.ts +31 -0
  392. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.d.ts.map +1 -0
  393. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -0
  394. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts +26 -19
  395. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts.map +1 -1
  396. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +46 -42
  397. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts +38 -10
  398. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts.map +1 -1
  399. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +158 -75
  400. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +22 -17
  401. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
  402. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -96
  403. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +30 -1
  404. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  405. package/src/engine/physics/inertia/world_inverse_inertia.js +160 -116
  406. package/src/engine/physics/integration/integrate_position.js +97 -97
  407. package/src/engine/physics/island/IslandBuilder.d.ts +49 -8
  408. package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
  409. package/src/engine/physics/island/IslandBuilder.js +93 -14
  410. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  411. package/src/engine/physics/narrowphase/box_box_manifold.js +683 -673
  412. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
  413. package/src/engine/physics/narrowphase/box_triangle_contact.js +899 -749
  414. package/src/engine/physics/narrowphase/capsule_contacts.d.ts +27 -0
  415. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  416. package/src/engine/physics/narrowphase/capsule_contacts.js +624 -459
  417. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -1
  418. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +58 -38
  419. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
  420. package/src/engine/physics/narrowphase/compute_penetration.js +369 -325
  421. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts +3 -1
  422. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
  423. package/src/engine/physics/narrowphase/convex_convex_manifold.js +568 -422
  424. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +6 -3
  425. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -1
  426. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +66 -10
  427. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +4 -1
  428. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -1
  429. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +97 -94
  430. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
  431. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  432. package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1739
  433. package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts +14 -7
  434. package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts.map +1 -1
  435. package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -69
  436. package/src/engine/physics/persistence/solver_caches.d.ts +20 -0
  437. package/src/engine/physics/persistence/solver_caches.d.ts.map +1 -0
  438. package/src/engine/physics/persistence/solver_caches.js +309 -0
  439. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -1
  440. package/src/engine/physics/queries/overlap_shape.js +187 -184
  441. package/src/engine/physics/queries/raycast.d.ts +3 -2
  442. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  443. package/src/engine/physics/queries/raycast.js +37 -11
  444. package/src/engine/physics/queries/shape_cast.d.ts +18 -5
  445. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -1
  446. package/src/engine/physics/queries/shape_cast.js +417 -393
  447. package/src/engine/physics/solver/solve_contacts.d.ts +22 -6
  448. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  449. package/src/engine/physics/solver/solve_contacts.js +1482 -1338
  450. package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -1
  451. package/src/engine/physics/vehicle/RaycastVehicle.js +344 -339
  452. package/src/engine/ui/DraggableAspect.d.ts +12 -3
  453. package/src/engine/ui/DraggableAspect.d.ts.map +1 -1
  454. package/src/engine/ui/DraggableAspect.js +115 -83
  455. package/src/generation/COORDINATES.md +54 -0
  456. package/src/generation/GridTaskGroup.js +2 -2
  457. package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -0
  458. package/src/generation/automata/CaveGeneratorCellularAutomata.d.ts +9 -1
  459. package/src/generation/automata/CaveGeneratorCellularAutomata.d.ts.map +1 -1
  460. package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -59
  461. package/src/generation/automata/CellularAutomata.d.ts +6 -3
  462. package/src/generation/automata/CellularAutomata.d.ts.map +1 -1
  463. package/src/generation/automata/CellularAutomata.js +22 -19
  464. package/src/generation/filtering/CellFilter.d.ts +17 -0
  465. package/src/generation/filtering/CellFilter.d.ts.map +1 -1
  466. package/src/generation/filtering/CellFilter.js +117 -77
  467. package/src/generation/filtering/CellFilterCellMatcher.d.ts.map +1 -1
  468. package/src/generation/filtering/CellFilterCellMatcher.js +2 -0
  469. package/src/generation/filtering/boolean/CellFilterLiteralBoolean.d.ts +5 -0
  470. package/src/generation/filtering/boolean/CellFilterLiteralBoolean.d.ts.map +1 -1
  471. package/src/generation/filtering/boolean/CellFilterLiteralBoolean.js +15 -0
  472. package/src/generation/filtering/core/CellFilterBinaryOperation.d.ts +0 -1
  473. package/src/generation/filtering/core/CellFilterBinaryOperation.d.ts.map +1 -1
  474. package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -50
  475. package/src/generation/filtering/core/CellFilterOperationTertiary.d.ts +0 -1
  476. package/src/generation/filtering/core/CellFilterOperationTertiary.d.ts.map +1 -1
  477. package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -59
  478. package/src/generation/filtering/core/CellFilterUnaryOperation.d.ts +0 -1
  479. package/src/generation/filtering/core/CellFilterUnaryOperation.d.ts.map +1 -1
  480. package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -33
  481. package/src/generation/filtering/numeric/CellFilterCache.d.ts +1 -0
  482. package/src/generation/filtering/numeric/CellFilterCache.d.ts.map +1 -1
  483. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.d.ts +3 -2
  484. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.d.ts.map +1 -1
  485. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.js +9 -35
  486. package/src/generation/filtering/numeric/complex/CellFilterCurvature.d.ts +0 -1
  487. package/src/generation/filtering/numeric/complex/CellFilterCurvature.d.ts.map +1 -1
  488. package/src/generation/filtering/numeric/complex/CellFilterCurvature.js +19 -43
  489. package/src/generation/filtering/numeric/complex/CellFilterFXAA.d.ts +0 -1
  490. package/src/generation/filtering/numeric/complex/CellFilterFXAA.d.ts.map +1 -1
  491. package/src/generation/filtering/numeric/complex/CellFilterFXAA.js +2 -6
  492. package/src/generation/filtering/numeric/complex/CellFilterGaussianBlur.d.ts.map +1 -1
  493. package/src/generation/filtering/numeric/complex/CellFilterGaussianBlur.js +9 -12
  494. package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.d.ts.map +1 -1
  495. package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.js +2 -1
  496. package/src/generation/filtering/numeric/complex/CellFilterSobel.d.ts +0 -1
  497. package/src/generation/filtering/numeric/complex/CellFilterSobel.d.ts.map +1 -1
  498. package/src/generation/filtering/numeric/complex/CellFilterSobel.js +2 -6
  499. package/src/generation/filtering/numeric/math/CellFilterInverseLerp.d.ts +5 -4
  500. package/src/generation/filtering/numeric/math/CellFilterInverseLerp.d.ts.map +1 -1
  501. package/src/generation/filtering/numeric/math/CellFilterInverseLerp.js +5 -4
  502. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.d.ts +17 -0
  503. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.d.ts.map +1 -0
  504. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -0
  505. package/src/generation/filtering/numeric/sampling/AbstractCellFilterSampleGridLayer.d.ts.map +1 -1
  506. package/src/generation/filtering/numeric/sampling/AbstractCellFilterSampleGridLayer.js +7 -1
  507. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.d.ts.map +1 -1
  508. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +7 -10
  509. package/src/generation/filtering/numeric/util/sampler_from_filter.d.ts.map +1 -1
  510. package/src/generation/filtering/numeric/util/sampler_from_filter.js +2 -1
  511. package/src/generation/grid/GridData.d.ts.map +1 -1
  512. package/src/generation/grid/GridData.js +14 -1
  513. package/src/generation/grid/actions/ContinuousGridCellAction.d.ts +10 -3
  514. package/src/generation/grid/actions/ContinuousGridCellAction.d.ts.map +1 -1
  515. package/src/generation/grid/actions/ContinuousGridCellAction.js +18 -3
  516. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainHeight.d.ts +11 -1
  517. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainHeight.d.ts.map +1 -1
  518. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainHeight.js +13 -3
  519. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainObstacle.d.ts +1 -1
  520. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainObstacle.js +2 -2
  521. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.d.ts +1 -1
  522. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.d.ts.map +1 -1
  523. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.js +4 -6
  524. package/src/generation/grid/coords/grid_to_texel.d.ts +9 -0
  525. package/src/generation/grid/coords/grid_to_texel.d.ts.map +1 -0
  526. package/src/generation/grid/coords/grid_to_texel.js +10 -0
  527. package/src/generation/grid/coords/texel_to_grid.d.ts +9 -0
  528. package/src/generation/grid/coords/texel_to_grid.d.ts.map +1 -0
  529. package/src/generation/grid/coords/texel_to_grid.js +10 -0
  530. package/src/generation/grid/generation/GridTaskApplyActionToCells.d.ts +2 -2
  531. package/src/generation/grid/generation/GridTaskApplyActionToCells.d.ts.map +1 -1
  532. package/src/generation/grid/generation/GridTaskApplyActionToCells.js +10 -6
  533. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.d.ts.map +1 -1
  534. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +20 -21
  535. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.d.ts +7 -0
  536. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.d.ts.map +1 -1
  537. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +18 -10
  538. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.d.ts.map +1 -1
  539. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +16 -7
  540. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.d.ts +5 -3
  541. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.d.ts.map +1 -1
  542. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +26 -23
  543. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.d.ts.map +1 -1
  544. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +10 -1
  545. package/src/generation/grid/generation/grid/select/CellSupplierBestN.d.ts.map +1 -1
  546. package/src/generation/grid/generation/grid/select/CellSupplierBestN.js +4 -0
  547. package/src/generation/grid/generation/road/GridTaskGenerateRoads.d.ts +15 -8
  548. package/src/generation/grid/generation/road/GridTaskGenerateRoads.d.ts.map +1 -1
  549. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +89 -92
  550. package/src/generation/markers/GridActionRuleSet.d.ts.map +1 -1
  551. package/src/generation/markers/GridActionRuleSet.js +10 -2
  552. package/src/generation/markers/GridCellActionPlaceMarker.d.ts +11 -0
  553. package/src/generation/markers/GridCellActionPlaceMarker.d.ts.map +1 -1
  554. package/src/generation/markers/GridCellActionPlaceMarker.js +20 -3
  555. package/src/generation/markers/GridCellActionPlaceMarkerGroup.d.ts +3 -1
  556. package/src/generation/markers/GridCellActionPlaceMarkerGroup.d.ts.map +1 -1
  557. package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +9 -2
  558. package/src/generation/markers/MarkerNode.d.ts +8 -3
  559. package/src/generation/markers/MarkerNode.d.ts.map +1 -1
  560. package/src/generation/markers/MarkerNode.js +12 -5
  561. package/src/generation/markers/actions/MarkerNodeActionEntityPlacement.js +1 -1
  562. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessor.d.ts +1 -1
  563. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessor.d.ts.map +1 -1
  564. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessor.js +1 -1
  565. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorClingToTerrain.d.ts +1 -1
  566. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorClingToTerrain.d.ts.map +1 -1
  567. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorClingToTerrain.js +1 -1
  568. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.d.ts +1 -1
  569. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.d.ts.map +1 -1
  570. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.js +2 -2
  571. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorSequence.d.ts +1 -1
  572. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorSequence.d.ts.map +1 -1
  573. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorSequence.js +2 -2
  574. package/src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.d.ts.map +1 -1
  575. package/src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.js +6 -4
  576. package/src/generation/markers/actions/probability/MarkerNodeActionWeightedElement.d.ts.map +1 -1
  577. package/src/generation/markers/actions/probability/MarkerNodeActionWeightedElement.js +1 -3
  578. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.d.ts.map +1 -1
  579. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +12 -11
  580. package/src/generation/markers/matcher/MarkerNodeMatcherAnd.js +2 -2
  581. package/src/generation/markers/transform/MarkerNodeTransformer.d.ts +4 -1
  582. package/src/generation/markers/transform/MarkerNodeTransformer.d.ts.map +1 -1
  583. package/src/generation/markers/transform/MarkerNodeTransformer.js +4 -1
  584. package/src/generation/markers/transform/MarkerNodeTransformerAddPositionYFromFilter.d.ts.map +1 -1
  585. package/src/generation/markers/transform/MarkerNodeTransformerAddPositionYFromFilter.js +1 -3
  586. package/src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.d.ts +5 -0
  587. package/src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.d.ts.map +1 -1
  588. package/src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.js +15 -0
  589. package/src/generation/markers/transform/MarkerNodeTransformerRecordProperty.d.ts.map +1 -1
  590. package/src/generation/markers/transform/MarkerNodeTransformerRecordProperty.js +1 -3
  591. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilter.d.ts.map +1 -1
  592. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilter.js +2 -4
  593. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilterGradient.d.ts.map +1 -1
  594. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilterGradient.js +1 -3
  595. package/src/generation/placement/GridCellPlacementRule.d.ts.map +1 -1
  596. package/src/generation/placement/GridCellPlacementRule.js +1 -3
  597. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.d.ts.map +1 -1
  598. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +8 -10
  599. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.d.ts.map +1 -1
  600. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +6 -4
  601. package/src/generation/placement/action/random/weighted/WeightedGridCellAction.d.ts.map +1 -1
  602. package/src/generation/placement/action/random/weighted/WeightedGridCellAction.js +1 -3
  603. package/src/generation/rules/CellMatcher.d.ts +3 -1
  604. package/src/generation/rules/CellMatcher.d.ts.map +1 -1
  605. package/src/generation/rules/CellMatcher.js +3 -1
  606. package/src/generation/rules/CellMatcherFromFilter.d.ts.map +1 -1
  607. package/src/generation/rules/CellMatcherFromFilter.js +1 -3
  608. package/src/generation/rules/CellMatcherLayerBitMaskTest.d.ts.map +1 -1
  609. package/src/generation/rules/CellMatcherLayerBitMaskTest.js +6 -20
  610. package/src/generation/test_support/executeTaskTreeSync.d.ts +9 -0
  611. package/src/generation/test_support/executeTaskTreeSync.d.ts.map +1 -0
  612. package/src/generation/test_support/executeTaskTreeSync.js +78 -0
  613. package/src/generation/theme/TerrainLayerRuleAggregator.d.ts +2 -1
  614. package/src/generation/theme/TerrainLayerRuleAggregator.d.ts.map +1 -1
  615. package/src/generation/theme/TerrainLayerRuleAggregator.js +9 -6
  616. package/src/generation/theme/Theme.d.ts +1 -1
  617. package/src/generation/theme/Theme.d.ts.map +1 -1
  618. package/src/generation/theme/Theme.js +2 -2
  619. package/src/generation/theme/ThemeEngine.d.ts +3 -3
  620. package/src/generation/theme/ThemeEngine.d.ts.map +1 -1
  621. package/src/generation/theme/ThemeEngine.js +26 -16
  622. package/src/generation/theme/cell/CellProcessingRule.d.ts +3 -3
  623. package/src/generation/theme/cell/CellProcessingRule.d.ts.map +1 -1
  624. package/src/generation/theme/cell/CellProcessingRule.js +6 -10
  625. package/src/generation/theme/cell/CellProcessingRuleSet.d.ts +1 -1
  626. package/src/generation/theme/cell/CellProcessingRuleSet.d.ts.map +1 -1
  627. package/src/generation/theme/cell/CellProcessingRuleSet.js +2 -2
  628. package/src/view/common/ListView.js +1 -1
  629. package/src/view/elements/BottomLeftResizeHandleView.d.ts.map +1 -1
  630. package/src/view/elements/BottomLeftResizeHandleView.js +13 -5
  631. package/src/core/font/FontAsset.d.ts.map +0 -1
  632. package/src/core/font/FontAssetLoader.d.ts.map +0 -1
  633. package/src/core/geom/3d/shape/util/shape_to_visual_entity.d.ts.map +0 -1
  634. package/src/core/geom/3d/tetrahedra/visualize_tetrahedral_mesh.d.ts.map +0 -1
  635. package/src/core/process/action/AsynchronousDelayAction.d.ts.map +0 -1
  636. package/src/engine/physics/computeInterceptPoint.d.ts.map +0 -1
  637. package/src/engine/physics/fluid/solver/optimal_sor_omega.d.ts.map +0 -1
  638. package/src/engine/physics/gjk/gjk.d.ts.map +0 -1
  639. package/src/engine/physics/gjk/gjk_epa_penetration.d.ts.map +0 -1
  640. package/src/engine/physics/gjk/minkowski_support.d.ts.map +0 -1
  641. package/src/engine/physics/gjk/mpr.d.ts.map +0 -1
  642. package/src/engine/physics/integration/quat_integrate.d.ts.map +0 -1
  643. package/src/engine/physics/island/union_find.d.ts.map +0 -1
  644. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +0 -1
  645. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +0 -1
  646. package/src/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.d.ts +0 -21
  647. package/src/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.d.ts.map +0 -1
  648. package/src/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.js +0 -68
  649. package/src/generation/grid/generation/grid/GridTaskGridAlignedNodeGenerator.d.ts +0 -10
  650. package/src/generation/grid/generation/grid/GridTaskGridAlignedNodeGenerator.d.ts.map +0 -1
  651. package/src/generation/grid/generation/grid/GridTaskGridAlignedNodeGenerator.js +0 -17
  652. /package/src/{engine/physics → core/geom/3d}/gjk/NOTES.md +0 -0
  653. /package/src/{engine/physics → core/geom/3d}/gjk/gjk.d.ts +0 -0
  654. /package/src/{engine/physics → core/math/physics/kinematics}/computeInterceptPoint.d.ts +0 -0
@@ -1,749 +1,899 @@
1
- import {
2
- line3_closest_points_segment_segment
3
- } from "../../../core/geom/3d/line/line3_closest_points_segment_segment.js";
4
- import { quat3_to_matrix3 } from "../../../core/geom/3d/quaternion/quat3_to_matrix3.js";
5
- import { clip_against_axis_uv } from "./clip_against_axis_uv.js";
6
- import { reduce_manifold_contacts } from "./reduce_manifold_contacts.js";
7
-
8
- /**
9
- * Multi-point manifold construction for an oriented box vs. a triangle.
10
- *
11
- * Algorithm (Parry's `contact_manifolds_cuboid_triangle.rs` as the
12
- * blueprint):
13
- *
14
- * 1. SAT over 13 candidate axes — 3 box face normals + 1 triangle face
15
- * normal + 9 edge-edge cross products. The triangle's projection
16
- * onto an axis is NOT symmetric around its centroid (unlike the
17
- * box's), so the per-axis MTV is computed via the asymmetric
18
- * `min(push_pos, push_neg)` form rather than the box-box-style
19
- * `(rA + rB) - dist`.
20
- *
21
- * 2. Branch by winning-axis source:
22
- *
23
- * a. Box face axis (sources 0..2) — reference face = box face along
24
- * winning axis, incident polygon = triangle. Project the triangle
25
- * into the reference face's (u, v) basis, clip against the
26
- * rectangle |u| half_u, |v| half_v via Sutherland-Hodgman
27
- * with 4 axis-aligned passes. For each surviving (u, v) point,
28
- * recover its world position on the triangle plane.
29
- *
30
- * b. Triangle face axis (source 3) — reference = triangle, incident
31
- * = box face most antiparallel to the contact normal. Project
32
- * the box face's 4 corners into a 2D basis on the triangle
33
- * plane, clip against the triangle's 3 edges (3 general
34
- * half-plane passes). Recover world positions on the box face
35
- * plane.
36
- *
37
- * c. Edge-cross axis (sources 4..12) — single contact at the
38
- * closest pair of points on the relevant box edge and triangle
39
- * edge (via {@link line3_closest_points_segment_segment}).
40
- *
41
- * 3. Reduce surviving contacts to at most {@link MAX_CONTACTS} by
42
- * deepest-first then perimeter expansion.
43
- *
44
- * Contact convention: `out[0..2]` is the world normal pointing from the
45
- * triangle's surface toward the box's centre (the direction the box
46
- * should be pushed to separate from the triangle). The caller in
47
- * `narrowphase_step.js` swaps to the "B A" convention as needed.
48
- *
49
- * Output layout (mirrors {@link box_box_manifold}):
50
- * out[0..2] : world normal (triangle box)
51
- * out[3] : contact count
52
- * out[4 + k*7 + 0..2] : world contact on triangle surface
53
- * out[4 + k*7 + 3..5] : world contact on box surface
54
- * out[4 + k*7 + 6] : penetration depth (positive)
55
- *
56
- * @author Alex Goldring
57
- * @copyright Company Named Limited (c) 2026
58
- */
59
-
60
- const MAX_CONTACTS = 4;
61
- const CONTACT_STRIDE = 7;
62
- const PARALLEL_EPS_SQR = 1e-8;
63
-
64
- /**
65
- * Length of `out` required by {@link box_triangle_contact}.
66
- * @type {number}
67
- */
68
- export const BOX_TRIANGLE_OUT_LENGTH = 4 + MAX_CONTACTS * CONTACT_STRIDE;
69
-
70
- // --- scratch storage (allocation-free across calls) ----------------------------
71
-
72
- const scratch_axes = new Float64Array(9);
73
-
74
- // Triangle projected to the box face's uv basis (start with 3 points;
75
- // after axis-aligned clipping can grow to at most 7 points).
76
- const tri_uv_in = new Float64Array(8 * 2);
77
- const tri_uv_out = new Float64Array(8 * 2);
78
-
79
- // Box face corners (for the triangle-axis-winner branch).
80
- const box_face_corners_uv_in = new Float64Array(8 * 2);
81
- const box_face_corners_uv_out = new Float64Array(8 * 2);
82
-
83
- // Surviving contact candidates: stride 7 (3 world-tri + 3 world-box + 1 depth).
84
- const candidates = new Float64Array(8 * 7);
85
-
86
- // Closest-points scratch for the edge-cross-winner branch.
87
- const closest_pair_st = new Float64Array(2);
88
-
89
- // --- helpers -----------------------------------------------------------------
90
-
91
- /**
92
- * Half-extent of the box projected onto a unit world-axis `(ux, uy, uz)`,
93
- * given the box's world-space axes and half-extents.
94
- */
95
- function projected_box_half_extent(axes, hx, hy, hz, ux, uy, uz) {
96
- const px = ux * axes[0] + uy * axes[1] + uz * axes[2];
97
- const py = ux * axes[3] + uy * axes[4] + uz * axes[5];
98
- const pz = ux * axes[6] + uy * axes[7] + uz * axes[8];
99
- return (px < 0 ? -px : px) * hx
100
- + (py < 0 ? -py : py) * hy
101
- + (pz < 0 ? -pz : pz) * hz;
102
- }
103
-
104
- /**
105
- * Sutherland-Hodgman clip of `points_in` against the general half-plane
106
- * `(p - (line_ox, line_oy)) · (line_nx, line_ny) ≤ 0`. 2D stride 2;
107
- * result to `points_out`. Returns surviving vertex count.
108
- */
109
- function clip_against_half_plane_uv(points_in, point_count, points_out, line_ox, line_oy, line_nx, line_ny) {
110
- let out_count = 0;
111
- for (let i = 0; i < point_count; i++) {
112
- const j = (i + 1) % point_count;
113
- const ax = points_in[i * 2], ay = points_in[i * 2 + 1];
114
- const bx = points_in[j * 2], by = points_in[j * 2 + 1];
115
- const a_val = (ax - line_ox) * line_nx + (ay - line_oy) * line_ny;
116
- const b_val = (bx - line_ox) * line_nx + (by - line_oy) * line_ny;
117
- const a_inside = a_val <= 0;
118
- const b_inside = b_val <= 0;
119
-
120
- if (a_inside) {
121
- points_out[out_count * 2] = ax;
122
- points_out[out_count * 2 + 1] = ay;
123
- out_count++;
124
- }
125
- if (a_inside !== b_inside) {
126
- const denom = a_val - b_val;
127
- const t = denom !== 0 ? a_val / denom : 0;
128
- points_out[out_count * 2] = ax + (bx - ax) * t;
129
- points_out[out_count * 2 + 1] = ay + (by - ay) * t;
130
- out_count++;
131
- }
132
- }
133
- return out_count;
134
- }
135
-
136
- /**
137
- * In-place reduction of `candidates` (stride 7: x, y, z on triangle,
138
- * x, y, z on box, depth) to at most {@link MAX_CONTACTS} entries.
139
- * Strategy: keep the deepest, then iteratively pick the candidate
140
- * whose minimum distance to the already-kept set (in 3D space) is
141
- * largest approximates max-perimeter / max-area selection.
142
- */
143
- // Contact reduction (deepest + farthest-point spread) is shared via
144
- // reduce_manifold_contacts (stride 7 here).
145
- function reduce_contacts(n) {
146
- return reduce_manifold_contacts(candidates, n, CONTACT_STRIDE, MAX_CONTACTS);
147
- }
148
-
149
- // --- main --------------------------------------------------------------------
150
-
151
- /**
152
- * @param {number[]|Float64Array} out length >= {@link BOX_TRIANGLE_OUT_LENGTH}
153
- * @param {number} bcx box centre x (world)
154
- * @param {number} bcy
155
- * @param {number} bcz
156
- * @param {number} bqx box quaternion x
157
- * @param {number} bqy
158
- * @param {number} bqz
159
- * @param {number} bqw
160
- * @param {number} bhx box half-extent x (body frame)
161
- * @param {number} bhy
162
- * @param {number} bhz
163
- * @param {number} ax triangle vertex A x (world)
164
- * @param {number} ay
165
- * @param {number} az
166
- * @param {number} bx triangle vertex B x (world)
167
- * @param {number} by
168
- * @param {number} bz
169
- * @param {number} cx triangle vertex C x (world)
170
- * @param {number} cy
171
- * @param {number} cz
172
- * @returns {boolean} true if box and triangle overlap
173
- */
174
- export function box_triangle_contact(
175
- out,
176
- bcx, bcy, bcz, bqx, bqy, bqz, bqw, bhx, bhy, bhz,
177
- ax, ay, az, bx, by, bz, cx, cy, cz
178
- ) {
179
- quat3_to_matrix3(scratch_axes, 0, bqx, bqy, bqz, bqw);
180
- const axes = scratch_axes;
181
-
182
- // Triangle edges in world.
183
- const ab_x = bx - ax, ab_y = by - ay, ab_z = bz - az;
184
- const bc_x = cx - bx, bc_y = cy - by, bc_z = cz - bz;
185
- const ca_x = ax - cx, ca_y = ay - cy, ca_z = az - cz;
186
-
187
- // Triangle face normal in world via (B-A) × (C-A).
188
- const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
189
- const tnx_raw = ab_y * e2z - ab_z * e2y;
190
- const tny_raw = ab_z * e2x - ab_x * e2z;
191
- const tnz_raw = ab_x * e2y - ab_y * e2x;
192
- const tn_mag_sqr = tnx_raw * tnx_raw + tny_raw * tny_raw + tnz_raw * tnz_raw;
193
- if (tn_mag_sqr <= PARALLEL_EPS_SQR) {
194
- // Degenerate triangle (zero area). No sensible contact.
195
- out[3] = 0;
196
- return false;
197
- }
198
- const tn_inv = 1 / Math.sqrt(tn_mag_sqr);
199
- const tnx = tnx_raw * tn_inv;
200
- const tny = tny_raw * tn_inv;
201
- const tnz = tnz_raw * tn_inv;
202
-
203
- // Triangle centroid for sign-canonicalisation of the contact normal.
204
- const tcx = (ax + bx + cx) / 3;
205
- const tcy = (ay + by + cy) / 3;
206
- const tcz = (az + bz + cz) / 3;
207
- // Vector from triangle centroid toward box centre used to flip the
208
- // SAT axis so the stored normal consistently points triangle box.
209
- const d_t2b_x = bcx - tcx;
210
- const d_t2b_y = bcy - tcy;
211
- const d_t2b_z = bcz - tcz;
212
-
213
- // SAT state.
214
- let best_overlap = Infinity;
215
- let best_nx = 0, best_ny = 0, best_nz = 0; // points triangle → box
216
- let best_source = -1;
217
- // Box-edge index (0..2) and triangle-edge index (0..2) for edge-cross sources.
218
- let best_box_edge_idx = -1;
219
- let best_tri_edge_idx = -1;
220
-
221
- /**
222
- * Test a candidate separating axis `(lx, ly, lz)` (need not be unit-length).
223
- * Returns true if a separating axis is found (overlap < 0); otherwise
224
- * updates `best_*` if this axis has the smallest overlap so far.
225
- *
226
- * Source:
227
- * 0..2 : box face normal (axis index 0..2)
228
- * 3 : triangle face normal
229
- * 4..12 : edge-cross (box_edge_idx * 3 + tri_edge_idx, offset +4)
230
- */
231
- function test_axis(lx, ly, lz, source, box_e_idx, tri_e_idx) {
232
- const len_sqr = lx * lx + ly * ly + lz * lz;
233
- if (len_sqr <= PARALLEL_EPS_SQR) return false;
234
- const inv_len = 1 / Math.sqrt(len_sqr);
235
- const ux = lx * inv_len, uy = ly * inv_len, uz = lz * inv_len;
236
-
237
- // Box projection.
238
- const box_centre_proj = ux * bcx + uy * bcy + uz * bcz;
239
- const r_box = projected_box_half_extent(axes, bhx, bhy, bhz, ux, uy, uz);
240
- const bmin = box_centre_proj - r_box;
241
- const bmax = box_centre_proj + r_box;
242
-
243
- // Triangle projection NOT symmetric around centroid, so we take
244
- // the per-vertex extreme values.
245
- const da = ux * ax + uy * ay + uz * az;
246
- const db = ux * bx + uy * by + uz * bz;
247
- const dc = ux * cx + uy * cy + uz * cz;
248
- const tmin = da < db ? (da < dc ? da : dc) : (db < dc ? db : dc);
249
- const tmax = da > db ? (da > dc ? da : dc) : (db > dc ? db : dc);
250
-
251
- // Separation test.
252
- if (bmax < tmin || tmax < bmin) return true;
253
-
254
- // Compute MTV magnitude as the smaller of the two "push directions".
255
- // push_pos: how far we'd push the triangle in +u to escape.
256
- // push_neg: how far in -u.
257
- // For asymmetric intervals these are not equal in general.
258
- const push_pos = bmax - tmin;
259
- const push_neg = tmax - bmin;
260
- const overlap = push_pos < push_neg ? push_pos : push_neg;
261
-
262
- if (overlap < best_overlap) {
263
- best_overlap = overlap;
264
- // Canonical normal direction: triangle box. Use the
265
- // box-centre-vs-triangle-centroid sign as the tiebreaker so
266
- // the SAT axis is canonically oriented before we negate.
267
- const center_dot = d_t2b_x * ux + d_t2b_y * uy + d_t2b_z * uz;
268
- const sign = center_dot >= 0 ? 1 : -1;
269
- best_nx = ux * sign;
270
- best_ny = uy * sign;
271
- best_nz = uz * sign;
272
- best_source = source;
273
- best_box_edge_idx = box_e_idx;
274
- best_tri_edge_idx = tri_e_idx;
275
- }
276
- return false;
277
- }
278
-
279
- // 1. Box face normals (3 axes).
280
- if (test_axis(axes[0], axes[1], axes[2], 0, -1, -1)) { out[3] = 0; return false; }
281
- if (test_axis(axes[3], axes[4], axes[5], 1, -1, -1)) { out[3] = 0; return false; }
282
- if (test_axis(axes[6], axes[7], axes[8], 2, -1, -1)) { out[3] = 0; return false; }
283
-
284
- // 2. Triangle face normal (1 axis).
285
- if (test_axis(tnx, tny, tnz, 3, -1, -1)) { out[3] = 0; return false; }
286
-
287
- // 3. Edge-edge cross products (9 axes).
288
- for (let i = 0; i < 3; i++) {
289
- const aix = axes[i * 3], aiy = axes[i * 3 + 1], aiz = axes[i * 3 + 2];
290
- for (let j = 0; j < 3; j++) {
291
- let ex, ey, ez;
292
- if (j === 0) { ex = ab_x; ey = ab_y; ez = ab_z; }
293
- else if (j === 1) { ex = bc_x; ey = bc_y; ez = bc_z; }
294
- else { ex = ca_x; ey = ca_y; ez = ca_z; }
295
- const cx_ax = aiy * ez - aiz * ey;
296
- const cy_ax = aiz * ex - aix * ez;
297
- const cz_ax = aix * ey - aiy * ex;
298
- if (test_axis(cx_ax, cy_ax, cz_ax, 4 + i * 3 + j, i, j)) { out[3] = 0; return false; }
299
- }
300
- }
301
-
302
- // We have overlap. Output the contact normal (triangle → box).
303
- const nx = best_nx, ny = best_ny, nz = best_nz;
304
- out[0] = nx; out[1] = ny; out[2] = nz;
305
-
306
- // --- Contact manifold construction ---
307
-
308
- if (best_source < 3) {
309
- // Box face axis winner: reference = box face, incident = triangle.
310
- return emit_box_face_manifold(out, best_source, axes, bcx, bcy, bcz, bhx, bhy, bhz,
311
- ax, ay, az, bx, by, bz, cx, cy, cz, tnx, tny, tnz, nx, ny, nz);
312
- } else if (best_source === 3) {
313
- // Triangle face axis winner: reference = triangle, incident = box face.
314
- return emit_triangle_face_manifold(out, axes, bcx, bcy, bcz, bhx, bhy, bhz,
315
- ax, ay, az, bx, by, bz, cx, cy, cz, tnx, tny, tnz, nx, ny, nz);
316
- } else {
317
- // Edge-cross winner: single contact at closest pair on the two edges.
318
- return emit_edge_cross_contact(out, axes, bcx, bcy, bcz, bhx, bhy, bhz,
319
- ax, ay, az, bx, by, bz, cx, cy, cz,
320
- best_box_edge_idx, best_tri_edge_idx,
321
- nx, ny, nz, best_overlap);
322
- }
323
- }
324
-
325
- // --- Manifold builders ------------------------------------------------------
326
-
327
- /**
328
- * Box-face SAT winner: clip the triangle against the box's reference face
329
- * rectangle, then for each surviving (u, v) recover the world contact on
330
- * the triangle plane.
331
- */
332
- function emit_box_face_manifold(
333
- out, ref_axis_idx, axes,
334
- bcx, bcy, bcz, bhx, bhy, bhz,
335
- ax, ay, az, bx, by, bz, cx, cy, cz,
336
- tnx, tny, tnz,
337
- nx, ny, nz
338
- ) {
339
- // The contact normal `n` points triangle → box. The box's outward
340
- // face normal at the reference face points box → triangle = -n.
341
- const face_out_nx = -nx;
342
- const face_out_ny = -ny;
343
- const face_out_nz = -nz;
344
-
345
- // Sign of the reference axis aligned with the outward normal.
346
- const ref_axis_x = axes[ref_axis_idx * 3];
347
- const ref_axis_y = axes[ref_axis_idx * 3 + 1];
348
- const ref_axis_z = axes[ref_axis_idx * 3 + 2];
349
- const ref_axis_dot = ref_axis_x * face_out_nx + ref_axis_y * face_out_ny + ref_axis_z * face_out_nz;
350
- const ref_axis_sign = ref_axis_dot >= 0 ? 1 : -1;
351
- const ref_h_along = ref_axis_idx === 0 ? bhx : (ref_axis_idx === 1 ? bhy : bhz);
352
-
353
- // Reference face origin (centre of the face on the +/- side of the box).
354
- const ref_face_ox = bcx + ref_axis_x * ref_axis_sign * ref_h_along;
355
- const ref_face_oy = bcy + ref_axis_y * ref_axis_sign * ref_h_along;
356
- const ref_face_oz = bcz + ref_axis_z * ref_axis_sign * ref_h_along;
357
-
358
- // Reference face's two tangent axes (u, v) and their half-extents.
359
- let u_axis_idx, v_axis_idx;
360
- if (ref_axis_idx === 0) { u_axis_idx = 1; v_axis_idx = 2; }
361
- else if (ref_axis_idx === 1) { u_axis_idx = 2; v_axis_idx = 0; }
362
- else { u_axis_idx = 0; v_axis_idx = 1; }
363
- const ux = axes[u_axis_idx * 3], uy = axes[u_axis_idx * 3 + 1], uz = axes[u_axis_idx * 3 + 2];
364
- const vx = axes[v_axis_idx * 3], vy = axes[v_axis_idx * 3 + 1], vz = axes[v_axis_idx * 3 + 2];
365
- const half_u = u_axis_idx === 0 ? bhx : (u_axis_idx === 1 ? bhy : bhz);
366
- const half_v = v_axis_idx === 0 ? bhx : (v_axis_idx === 1 ? bhy : bhz);
367
-
368
- // Project the triangle's 3 vertices into the (u, v) basis on the
369
- // reference plane.
370
- const da_x = ax - ref_face_ox, da_y = ay - ref_face_oy, da_z = az - ref_face_oz;
371
- const db_x = bx - ref_face_ox, db_y = by - ref_face_oy, db_z = bz - ref_face_oz;
372
- const dc_x = cx - ref_face_ox, dc_y = cy - ref_face_oy, dc_z = cz - ref_face_oz;
373
- tri_uv_in[0] = da_x * ux + da_y * uy + da_z * uz;
374
- tri_uv_in[1] = da_x * vx + da_y * vy + da_z * vz;
375
- tri_uv_in[2] = db_x * ux + db_y * uy + db_z * uz;
376
- tri_uv_in[3] = db_x * vx + db_y * vy + db_z * vz;
377
- tri_uv_in[4] = dc_x * ux + dc_y * uy + dc_z * uz;
378
- tri_uv_in[5] = dc_x * vx + dc_y * vy + dc_z * vz;
379
-
380
- // Clip against rectangle |u| ≤ half_u, |v| ≤ half_v via 4 passes.
381
- let n = 3;
382
- n = clip_against_axis_uv(tri_uv_in, n, tri_uv_out, 0, half_u, true);
383
- if (n === 0) { out[3] = 0; return false; }
384
- n = clip_against_axis_uv(tri_uv_out, n, tri_uv_in, 0, -half_u, false);
385
- if (n === 0) { out[3] = 0; return false; }
386
- n = clip_against_axis_uv(tri_uv_in, n, tri_uv_out, 1, half_v, true);
387
- if (n === 0) { out[3] = 0; return false; }
388
- n = clip_against_axis_uv(tri_uv_out, n, tri_uv_in, 1, -half_v, false);
389
- if (n === 0) { out[3] = 0; return false; }
390
-
391
- // The clipped polygon is in tri_uv_in. For each surviving (u, v):
392
- // X = ref_face_origin + u * u_axis + v * v_axis (point on ref plane)
393
- // Find t such that (X + t * face_out_n) is on the triangle plane:
394
- // (X + t * face_out_n - A) · tn = 0
395
- // t = ((A - X) · tn) / (face_out_n · tn)
396
- // World point on triangle plane = X + t * face_out_n.
397
- // Depth = -t (positive when triangle penetrates the box).
398
- const denom = face_out_nx * tnx + face_out_ny * tny + face_out_nz * tnz;
399
- if (Math.abs(denom) < PARALLEL_EPS_SQR) {
400
- // Triangle plane parallel to ref outward degenerate. No contacts.
401
- out[3] = 0;
402
- return false;
403
- }
404
- const clipped_uv = tri_uv_in;
405
-
406
- let cand_count = 0;
407
- for (let i = 0; i < n; i++) {
408
- const u = clipped_uv[i * 2];
409
- const v = clipped_uv[i * 2 + 1];
410
- const xpx = ref_face_ox + u * ux + v * vx;
411
- const xpy = ref_face_oy + u * uy + v * vy;
412
- const xpz = ref_face_oz + u * uz + v * vz;
413
-
414
- const t = ((ax - xpx) * tnx + (ay - xpy) * tny + (az - xpz) * tnz) / denom;
415
- const world_tri_x = xpx + face_out_nx * t;
416
- const world_tri_y = xpy + face_out_ny * t;
417
- const world_tri_z = xpz + face_out_nz * t;
418
- const depth = -t;
419
- if (depth <= 0) continue;
420
-
421
- const off = cand_count * 7;
422
- candidates[off] = world_tri_x;
423
- candidates[off + 1] = world_tri_y;
424
- candidates[off + 2] = world_tri_z;
425
- // Box-side contact = same (u, v) on ref plane.
426
- candidates[off + 3] = xpx;
427
- candidates[off + 4] = xpy;
428
- candidates[off + 5] = xpz;
429
- candidates[off + 6] = depth;
430
- cand_count++;
431
- }
432
-
433
- if (cand_count === 0) { out[3] = 0; return false; }
434
- const kept = reduce_contacts(cand_count);
435
- out[3] = kept;
436
- for (let k = 0; k < kept; k++) {
437
- const src = k * 7;
438
- const base = 4 + k * CONTACT_STRIDE;
439
- out[base] = candidates[src];
440
- out[base + 1] = candidates[src + 1];
441
- out[base + 2] = candidates[src + 2];
442
- out[base + 3] = candidates[src + 3];
443
- out[base + 4] = candidates[src + 4];
444
- out[base + 5] = candidates[src + 5];
445
- out[base + 6] = candidates[src + 6];
446
- }
447
- return true;
448
- }
449
-
450
- /**
451
- * Triangle-face SAT winner: reference = triangle, incident = the box face
452
- * most antiparallel to the contact normal. Clip the box face quad against
453
- * the triangle's 3 edges, then recover world contacts on the box face plane.
454
- */
455
- function emit_triangle_face_manifold(
456
- out, axes,
457
- bcx, bcy, bcz, bhx, bhy, bhz,
458
- ax, ay, az, bx, by, bz, cx, cy, cz,
459
- tnx, tny, tnz,
460
- nx, ny, nz
461
- ) {
462
- // Reference = triangle. Its outward face normal points from the triangle
463
- // surface toward the box — i.e. the contact normal `n` (triangle → box),
464
- // which the SAT already sign-canonicalised. The incident box face is then
465
- // the one whose outward normal is most antiparallel to this (the box face
466
- // actually facing the triangle), and the per-point depth below measures the
467
- // penetration of THAT face — not the box's far face.
468
- //
469
- // (The earlier `-n` here pointed the reference normal into the terrain, so
470
- // the incident picker selected the box's FAR face and the plane-to-plane
471
- // depth ballooned to ~the box's thickness for any tilted box — catapulting
472
- // rocking boxes off heightmaps. See box_triangle_contact.spec.js
473
- // "tilted box, shallow corner".)
474
- const ref_out_x = nx;
475
- const ref_out_y = ny;
476
- const ref_out_z = nz;
477
-
478
- // Pick the incident box face: the one whose outward axis is most
479
- // antiparallel to ref_out.
480
- let inc_axis_idx = 0;
481
- let inc_axis_sign = 1;
482
- let max_anti = -Infinity;
483
- for (let i = 0; i < 3; i++) {
484
- const iax = axes[i * 3], iay = axes[i * 3 + 1], iaz = axes[i * 3 + 2];
485
- const d_pos = iax * ref_out_x + iay * ref_out_y + iaz * ref_out_z;
486
- if (-d_pos > max_anti) { max_anti = -d_pos; inc_axis_idx = i; inc_axis_sign = -1; }
487
- if ( d_pos > max_anti) { max_anti = d_pos; inc_axis_idx = i; inc_axis_sign = 1; }
488
- }
489
- const inc_outward_sign = -inc_axis_sign;
490
- const inc_h_along = inc_axis_idx === 0 ? bhx : (inc_axis_idx === 1 ? bhy : bhz);
491
- const inc_face_cx = bcx + axes[inc_axis_idx * 3] * inc_outward_sign * inc_h_along;
492
- const inc_face_cy = bcy + axes[inc_axis_idx * 3 + 1] * inc_outward_sign * inc_h_along;
493
- const inc_face_cz = bcz + axes[inc_axis_idx * 3 + 2] * inc_outward_sign * inc_h_along;
494
-
495
- // Box face's two tangent axes in world.
496
- let i_u_idx, i_v_idx;
497
- if (inc_axis_idx === 0) { i_u_idx = 1; i_v_idx = 2; }
498
- else if (inc_axis_idx === 1) { i_u_idx = 2; i_v_idx = 0; }
499
- else { i_u_idx = 0; i_v_idx = 1; }
500
- const iux = axes[i_u_idx * 3], iuy = axes[i_u_idx * 3 + 1], iuz = axes[i_u_idx * 3 + 2];
501
- const ivx = axes[i_v_idx * 3], ivy = axes[i_v_idx * 3 + 1], ivz = axes[i_v_idx * 3 + 2];
502
- const i_half_u = i_u_idx === 0 ? bhx : (i_u_idx === 1 ? bhy : bhz);
503
- const i_half_v = i_v_idx === 0 ? bhx : (i_v_idx === 1 ? bhy : bhz);
504
-
505
- // Build a 2D basis on the triangle plane. Use AB direction as `u`,
506
- // and `n_uv = tn × u` as `v` so the basis is orthonormal.
507
- const ab_x_w = bx - ax, ab_y_w = by - ay, ab_z_w = bz - az;
508
- const ab_len_sqr = ab_x_w * ab_x_w + ab_y_w * ab_y_w + ab_z_w * ab_z_w;
509
- if (ab_len_sqr <= PARALLEL_EPS_SQR) { out[3] = 0; return false; }
510
- const ab_inv = 1 / Math.sqrt(ab_len_sqr);
511
- const tux = ab_x_w * ab_inv, tuy = ab_y_w * ab_inv, tuz = ab_z_w * ab_inv;
512
- // v = tn × u (using the geometric face normal as the "up").
513
- const tvx = tny * tuz - tnz * tuy;
514
- const tvy = tnz * tux - tnx * tuz;
515
- const tvz = tnx * tuy - tny * tux;
516
-
517
- // Project triangle vertices to (u, v). A is the origin of the
518
- // 2D basis A_uv = (0, 0); B is along the u axis by construction
519
- // so B_uv = (|AB|, 0); C is computed normally.
520
- const ac_x_w = cx - ax, ac_y_w = cy - ay, ac_z_w = cz - az;
521
- const b_u = ab_x_w * tux + ab_y_w * tuy + ab_z_w * tuz; // = |AB|
522
- const c_u = ac_x_w * tux + ac_y_w * tuy + ac_z_w * tuz;
523
- const c_v = ac_x_w * tvx + ac_y_w * tvy + ac_z_w * tvz;
524
-
525
- // Triangle's 3 edges as 2D half-planes in (u, v). For each edge, the
526
- // outward normal must point AWAY from the opposite vertex.
527
- // Edge AB: from A=(0,0) to B=(b_u, 0). Direction = (1, 0). Outward
528
- // normal perpendicular = (0, 1) or (0, -1). Choose sign so it
529
- // points away from C (= sign(-c_v) gives 1 if c_v < 0).
530
- const ab_n_v = c_v >= 0 ? -1 : 1;
531
- // Edge BC: from B=(b_u, 0) to C=(c_u, c_v). Direction = (c_u - b_u, c_v).
532
- // Outward perpendicular: rotate by ±90° (depending on triangle winding).
533
- // Outward direction must point away from A=(0,0).
534
- const bc_dir_u = c_u - b_u, bc_dir_v = c_v - 0;
535
- // Two perpendiculars: (-bc_dir_v, bc_dir_u) and (bc_dir_v, -bc_dir_u).
536
- // Pick the one with positive dot vs (A - midpoint of BC) negated.
537
- const bc_mid_u = (b_u + c_u) * 0.5;
538
- const bc_mid_v = c_v * 0.5;
539
- const to_a_u_from_bc = 0 - bc_mid_u;
540
- const to_a_v_from_bc = 0 - bc_mid_v;
541
- // We want outward = -direction_to_A.
542
- let bc_n_u = -bc_dir_v, bc_n_v = bc_dir_u;
543
- if (bc_n_u * to_a_u_from_bc + bc_n_v * to_a_v_from_bc > 0) {
544
- bc_n_u = -bc_n_u; bc_n_v = -bc_n_v;
545
- }
546
- // Normalise so the clip threshold is "distance ≤ 0" in 2D.
547
- const bc_n_len = Math.sqrt(bc_n_u * bc_n_u + bc_n_v * bc_n_v);
548
- if (bc_n_len > 0) { bc_n_u /= bc_n_len; bc_n_v /= bc_n_len; }
549
- // Edge CA: from C to A. Direction = (-c_u, -c_v). Outward must point
550
- // away from B.
551
- const ca_dir_u = 0 - c_u, ca_dir_v = 0 - c_v;
552
- const ca_mid_u = c_u * 0.5;
553
- const ca_mid_v = c_v * 0.5;
554
- const to_b_u_from_ca = b_u - ca_mid_u;
555
- const to_b_v_from_ca = 0 - ca_mid_v;
556
- let ca_n_u = -ca_dir_v, ca_n_v = ca_dir_u;
557
- if (ca_n_u * to_b_u_from_ca + ca_n_v * to_b_v_from_ca > 0) {
558
- ca_n_u = -ca_n_u; ca_n_v = -ca_n_v;
559
- }
560
- const ca_n_len = Math.sqrt(ca_n_u * ca_n_u + ca_n_v * ca_n_v);
561
- if (ca_n_len > 0) { ca_n_u /= ca_n_len; ca_n_v /= ca_n_len; }
562
-
563
- // Build the box-face polygon (4 corners) and project to (u, v) on
564
- // the triangle plane.
565
- const signs_u = [ 1, 1, -1, -1];
566
- const signs_v = [-1, 1, 1, -1];
567
- for (let i = 0; i < 4; i++) {
568
- const su = signs_u[i] * i_half_u;
569
- const sv = signs_v[i] * i_half_v;
570
- const wx = inc_face_cx + iux * su + ivx * sv;
571
- const wy = inc_face_cy + iuy * su + ivy * sv;
572
- const wz = inc_face_cz + iuz * su + ivz * sv;
573
- const dx = wx - ax, dy = wy - ay, dz = wz - az;
574
- box_face_corners_uv_in[i * 2] = dx * tux + dy * tuy + dz * tuz;
575
- box_face_corners_uv_in[i * 2 + 1] = dx * tvx + dy * tvy + dz * tvz;
576
- }
577
-
578
- // Clip the quad against the 3 triangle-edge half-planes.
579
- let np = 4;
580
- // Edge AB: half-plane (p.v - 0) * ab_n_v <= 0 → coord_idx = 1, bound = 0, keep_below if ab_n_v=+1.
581
- np = clip_against_half_plane_uv(box_face_corners_uv_in, np, box_face_corners_uv_out, 0, 0, 0, ab_n_v);
582
- if (np === 0) { out[3] = 0; return false; }
583
- np = clip_against_half_plane_uv(box_face_corners_uv_out, np, box_face_corners_uv_in, b_u, 0, bc_n_u, bc_n_v);
584
- if (np === 0) { out[3] = 0; return false; }
585
- np = clip_against_half_plane_uv(box_face_corners_uv_in, np, box_face_corners_uv_out, c_u, c_v, ca_n_u, ca_n_v);
586
- if (np === 0) { out[3] = 0; return false; }
587
-
588
- // Survivors are in box_face_corners_uv_out. For each:
589
- // X = A + u * tu + v * tv (point on triangle plane)
590
- // Find t such that X + t * ref_out is on box face plane:
591
- // (X + t * ref_out - inc_face_c) · inc_out = 0
592
- // Where inc_out = inc_axes[inc_axis_idx] * inc_outward_sign.
593
- const inc_out_x = axes[inc_axis_idx * 3] * inc_outward_sign;
594
- const inc_out_y = axes[inc_axis_idx * 3 + 1] * inc_outward_sign;
595
- const inc_out_z = axes[inc_axis_idx * 3 + 2] * inc_outward_sign;
596
- const denom = ref_out_x * inc_out_x + ref_out_y * inc_out_y + ref_out_z * inc_out_z;
597
- if (Math.abs(denom) < PARALLEL_EPS_SQR) { out[3] = 0; return false; }
598
- const clipped_uv = box_face_corners_uv_out;
599
-
600
- let cand_count = 0;
601
- for (let i = 0; i < np; i++) {
602
- const u = clipped_uv[i * 2];
603
- const v = clipped_uv[i * 2 + 1];
604
- const xpx = ax + u * tux + v * tvx;
605
- const xpy = ay + u * tuy + v * tvy;
606
- const xpz = az + u * tuz + v * tvz;
607
-
608
- const num_x = inc_face_cx - xpx;
609
- const num_y = inc_face_cy - xpy;
610
- const num_z = inc_face_cz - xpz;
611
- const t = (num_x * inc_out_x + num_y * inc_out_y + num_z * inc_out_z) / denom;
612
- const world_box_x = xpx + ref_out_x * t;
613
- const world_box_y = xpy + ref_out_y * t;
614
- const world_box_z = xpz + ref_out_z * t;
615
- // Depth: how far the box face has penetrated past the triangle
616
- // plane, measured along ref_out. depth = -t.
617
- const depth = -t;
618
- if (depth <= 0) continue;
619
-
620
- const off = cand_count * 7;
621
- // Triangle-side contact: the clipped (u, v) point on the
622
- // triangle plane.
623
- candidates[off] = xpx;
624
- candidates[off + 1] = xpy;
625
- candidates[off + 2] = xpz;
626
- candidates[off + 3] = world_box_x;
627
- candidates[off + 4] = world_box_y;
628
- candidates[off + 5] = world_box_z;
629
- candidates[off + 6] = depth;
630
- cand_count++;
631
- }
632
-
633
- if (cand_count === 0) { out[3] = 0; return false; }
634
- const kept = reduce_contacts(cand_count);
635
- out[3] = kept;
636
- for (let k = 0; k < kept; k++) {
637
- const src = k * 7;
638
- const base = 4 + k * CONTACT_STRIDE;
639
- out[base] = candidates[src];
640
- out[base + 1] = candidates[src + 1];
641
- out[base + 2] = candidates[src + 2];
642
- out[base + 3] = candidates[src + 3];
643
- out[base + 4] = candidates[src + 4];
644
- out[base + 5] = candidates[src + 5];
645
- out[base + 6] = candidates[src + 6];
646
- }
647
- return true;
648
- }
649
-
650
- /**
651
- * Edge-cross SAT winner: emit a single contact at the closest pair of
652
- * points on (a parallel-translated) box edge × the triangle edge.
653
- *
654
- * The box has 12 edges, but along the winning box-axis direction `i`
655
- * there are 4 parallel edges (the four lines on the box surface
656
- * parallel to box-axis i). The "right" edge for the contact is the
657
- * one whose endpoints span the triangle edge — practically, pick the
658
- * box edge most aligned with the SAT separation: the one whose centre
659
- * (when translated perpendicular to the SAT axis) is closest to the
660
- * triangle edge.
661
- *
662
- * Procedure:
663
- * 1. Construct the box edge as the line through box centre +
664
- * (the +/- of the other two box half-extents pushing to the
665
- * "near" corner determined by the SAT-axis sign).
666
- * 2. Construct the triangle edge as the segment.
667
- * 3. Closest-pair via `line3_closest_points_segment_segment`.
668
- * 4. Contact = midpoint, depth = best_overlap.
669
- */
670
- function emit_edge_cross_contact(
671
- out, axes,
672
- bcx, bcy, bcz, bhx, bhy, bhz,
673
- ax, ay, az, bx, by, bz, cx, cy, cz,
674
- box_edge_idx, tri_edge_idx,
675
- nx, ny, nz,
676
- overlap
677
- ) {
678
- // Box edge direction: axis index `box_edge_idx`.
679
- const edx = axes[box_edge_idx * 3];
680
- const edy = axes[box_edge_idx * 3 + 1];
681
- const edz = axes[box_edge_idx * 3 + 2];
682
- const edge_half = box_edge_idx === 0 ? bhx : (box_edge_idx === 1 ? bhy : bhz);
683
-
684
- // The OTHER two box axes determine which of the four parallel edges
685
- // we pick. Use the sign of the contact normal projected onto each
686
- // perpendicular axis to choose the edge closest to the contact.
687
- let u_idx, v_idx;
688
- if (box_edge_idx === 0) { u_idx = 1; v_idx = 2; }
689
- else if (box_edge_idx === 1) { u_idx = 2; v_idx = 0; }
690
- else { u_idx = 0; v_idx = 1; }
691
- const u_ax_x = axes[u_idx * 3], u_ax_y = axes[u_idx * 3 + 1], u_ax_z = axes[u_idx * 3 + 2];
692
- const v_ax_x = axes[v_idx * 3], v_ax_y = axes[v_idx * 3 + 1], v_ax_z = axes[v_idx * 3 + 2];
693
- const half_u = u_idx === 0 ? bhx : (u_idx === 1 ? bhy : bhz);
694
- const half_v = v_idx === 0 ? bhx : (v_idx === 1 ? bhy : bhz);
695
-
696
- // The contact normal `n` points triangle → box. The box edge
697
- // closest to the triangle is the one whose corner (in u, v) is in
698
- // the -n direction. Decompose -n into the u and v components.
699
- const minus_n_dot_u = -(nx * u_ax_x + ny * u_ax_y + nz * u_ax_z);
700
- const minus_n_dot_v = -(nx * v_ax_x + ny * v_ax_y + nz * v_ax_z);
701
- const u_sign = minus_n_dot_u >= 0 ? 1 : -1;
702
- const v_sign = minus_n_dot_v >= 0 ? 1 : -1;
703
-
704
- // Box edge endpoints in world: from corner_lo to corner_hi along
705
- // the chosen box axis.
706
- const corner_cx = bcx + u_ax_x * u_sign * half_u + v_ax_x * v_sign * half_v;
707
- const corner_cy = bcy + u_ax_y * u_sign * half_u + v_ax_y * v_sign * half_v;
708
- const corner_cz = bcz + u_ax_z * u_sign * half_u + v_ax_z * v_sign * half_v;
709
- const box_p1x = corner_cx - edx * edge_half, box_p1y = corner_cy - edy * edge_half, box_p1z = corner_cz - edz * edge_half;
710
- const box_p2x = corner_cx + edx * edge_half, box_p2y = corner_cy + edy * edge_half, box_p2z = corner_cz + edz * edge_half;
711
-
712
- // Triangle edge endpoints.
713
- let tri_p1x, tri_p1y, tri_p1z, tri_p2x, tri_p2y, tri_p2z;
714
- if (tri_edge_idx === 0) {
715
- tri_p1x = ax; tri_p1y = ay; tri_p1z = az;
716
- tri_p2x = bx; tri_p2y = by; tri_p2z = bz;
717
- } else if (tri_edge_idx === 1) {
718
- tri_p1x = bx; tri_p1y = by; tri_p1z = bz;
719
- tri_p2x = cx; tri_p2y = cy; tri_p2z = cz;
720
- } else {
721
- tri_p1x = cx; tri_p1y = cy; tri_p1z = cz;
722
- tri_p2x = ax; tri_p2y = ay; tri_p2z = az;
723
- }
724
-
725
- // Closest points on the two segments.
726
- line3_closest_points_segment_segment(
727
- closest_pair_st,
728
- box_p1x, box_p1y, box_p1z, box_p2x, box_p2y, box_p2z,
729
- tri_p1x, tri_p1y, tri_p1z, tri_p2x, tri_p2y, tri_p2z
730
- );
731
- const s = closest_pair_st[0], t = closest_pair_st[1];
732
- const closest_box_x = box_p1x + s * (box_p2x - box_p1x);
733
- const closest_box_y = box_p1y + s * (box_p2y - box_p1y);
734
- const closest_box_z = box_p1z + s * (box_p2z - box_p1z);
735
- const closest_tri_x = tri_p1x + t * (tri_p2x - tri_p1x);
736
- const closest_tri_y = tri_p1y + t * (tri_p2y - tri_p1y);
737
- const closest_tri_z = tri_p1z + t * (tri_p2z - tri_p1z);
738
-
739
- out[3] = 1;
740
- const base = 4;
741
- out[base] = closest_tri_x;
742
- out[base + 1] = closest_tri_y;
743
- out[base + 2] = closest_tri_z;
744
- out[base + 3] = closest_box_x;
745
- out[base + 4] = closest_box_y;
746
- out[base + 5] = closest_box_z;
747
- out[base + 6] = overlap;
748
- return true;
749
- }
1
+ import {
2
+ line3_closest_points_segment_segment
3
+ } from "../../../core/geom/3d/line/line3_closest_points_segment_segment.js";
4
+ import { quat3_to_matrix3 } from "../../../core/geom/3d/quaternion/quat3_to_matrix3.js";
5
+ import { polygon2_clip_axis_halfplane } from "../../../core/geom/2d/polygon/polygon2_clip_axis_halfplane.js";
6
+ import { reduce_manifold_contacts } from "./reduce_manifold_contacts.js";
7
+ import { box3_projected_half_extent } from "../../../core/geom/3d/box/box3_projected_half_extent.js";
8
+
9
+ /**
10
+ * Multi-point manifold construction for an oriented box vs. a triangle.
11
+ *
12
+ * Algorithm (Parry's `contact_manifolds_cuboid_triangle.rs` as the
13
+ * blueprint):
14
+ *
15
+ * 1. SAT over 13 candidate axes 3 box face normals + 1 triangle face
16
+ * normal + 9 edge-edge cross products. The triangle's projection
17
+ * onto an axis is NOT symmetric around its centroid (unlike the
18
+ * box's), so the per-axis MTV is computed via the asymmetric
19
+ * `min(push_pos, push_neg)` form rather than the box-box-style
20
+ * `(rA + rB) - dist`.
21
+ *
22
+ * 2. Branch by winning-axis source:
23
+ *
24
+ * a. Box face axis (sources 0..2) — reference face = box face along
25
+ * winning axis, incident polygon = triangle. Project the triangle
26
+ * into the reference face's (u, v) basis, clip against the
27
+ * rectangle |u| half_u, |v| half_v via Sutherland-Hodgman
28
+ * with 4 axis-aligned passes. For each surviving (u, v) point,
29
+ * recover its world position on the triangle plane.
30
+ *
31
+ * b. Triangle face axis (source 3) reference = triangle, incident
32
+ * = box face most antiparallel to the contact normal. Project
33
+ * the box face's 4 corners into a 2D basis on the triangle
34
+ * plane, clip against the triangle's 3 edges (3 general
35
+ * half-plane passes). Recover world positions on the box face
36
+ * plane.
37
+ *
38
+ * c. Edge-cross axis (sources 4..12) single contact at the
39
+ * closest pair of points on the relevant box edge and triangle
40
+ * edge (via {@link line3_closest_points_segment_segment}).
41
+ *
42
+ * 3. Reduce surviving contacts to at most {@link MAX_CONTACTS} by
43
+ * deepest-first then perimeter expansion.
44
+ *
45
+ * Contact convention: `out[0..2]` is the world normal pointing from the
46
+ * triangle's surface toward the box's centre (the direction the box
47
+ * should be pushed to separate from the triangle). The caller in
48
+ * `narrowphase_step.js` swaps to the "B → A" convention as needed.
49
+ *
50
+ * Output layout (mirrors {@link box_box_manifold}):
51
+ * out[0..2] : world normal (triangle → box)
52
+ * out[3] : contact count
53
+ * out[4 + k*7 + 0..2] : world contact on triangle surface
54
+ * out[4 + k*7 + 3..5] : world contact on box surface
55
+ * out[4 + k*7 + 6] : penetration depth (positive)
56
+ *
57
+ * @author Alex Goldring
58
+ * @copyright Company Named Limited (c) 2026
59
+ */
60
+
61
+ const MAX_CONTACTS = 4;
62
+ const CONTACT_STRIDE = 7;
63
+ const PARALLEL_EPS_SQR = 1e-8;
64
+
65
+ /**
66
+ * Angular near-parallel threshold for the manifold builders' plane-recovery
67
+ * denominators. Those denominators are dots of two UNIT vectors (cosines),
68
+ * so the guard must be an ANGULAR epsilon: the old squared-length epsilon
69
+ * (1e-8) left a window (cos in 1e-8..1e-6) where 1/denom amplified the plane
70
+ * recovery by up to 1e8 - absurd "deepest" candidates that won the manifold
71
+ * reduction - and below it dropped SAT-PROVEN overlaps outright. At 1e-4 the
72
+ * amplification is bounded by 1e4 and the per-candidate depth cap (depth
73
+ * cannot exceed the SAT overlap along the winning axis) rejects the
74
+ * remainder; near-parallel cases emit the deepest-vertex fallback instead of
75
+ * dropping the pair.
76
+ * @type {number}
77
+ */
78
+ const PARALLEL_COS_EPS = 1e-4;
79
+
80
+ /**
81
+ * Length of `out` required by {@link box_triangle_contact}.
82
+ * @type {number}
83
+ */
84
+ export const BOX_TRIANGLE_OUT_LENGTH = 4 + MAX_CONTACTS * CONTACT_STRIDE;
85
+
86
+ // --- scratch storage (allocation-free across calls) ----------------------------
87
+
88
+ const scratch_axes = new Float64Array(9);
89
+
90
+ // Triangle projected to the box face's uv basis (start with 3 points;
91
+ // after axis-aligned clipping can grow to at most 7 points).
92
+ const tri_uv_in = new Float64Array(8 * 2);
93
+ const tri_uv_out = new Float64Array(8 * 2);
94
+
95
+ // Box face corners (for the triangle-axis-winner branch).
96
+ const box_face_corners_uv_in = new Float64Array(8 * 2);
97
+ const box_face_corners_uv_out = new Float64Array(8 * 2);
98
+
99
+ // Surviving contact candidates: stride 7 (3 world-tri + 3 world-box + 1 depth).
100
+ const candidates = new Float64Array(8 * 7);
101
+
102
+ // Closest-points scratch for the edge-cross-winner branch.
103
+ const closest_pair_st = new Float64Array(2);
104
+
105
+ // --- helpers -----------------------------------------------------------------
106
+
107
+ /**
108
+ * Sutherland-Hodgman clip of `points_in` against the general half-plane
109
+ * `(p - (line_ox, line_oy)) · (line_nx, line_ny) ≤ 0`. 2D stride 2;
110
+ * result to `points_out`. Returns surviving vertex count.
111
+ */
112
+ function clip_against_half_plane_uv(points_in, point_count, points_out, line_ox, line_oy, line_nx, line_ny) {
113
+ let out_count = 0;
114
+ for (let i = 0; i < point_count; i++) {
115
+ const j = (i + 1) % point_count;
116
+ const ax = points_in[i * 2], ay = points_in[i * 2 + 1];
117
+ const bx = points_in[j * 2], by = points_in[j * 2 + 1];
118
+ const a_val = (ax - line_ox) * line_nx + (ay - line_oy) * line_ny;
119
+ const b_val = (bx - line_ox) * line_nx + (by - line_oy) * line_ny;
120
+ const a_inside = a_val <= 0;
121
+ const b_inside = b_val <= 0;
122
+
123
+ if (a_inside) {
124
+ points_out[out_count * 2] = ax;
125
+ points_out[out_count * 2 + 1] = ay;
126
+ out_count++;
127
+ }
128
+ if (a_inside !== b_inside) {
129
+ const denom = a_val - b_val;
130
+ const t = denom !== 0 ? a_val / denom : 0;
131
+ points_out[out_count * 2] = ax + (bx - ax) * t;
132
+ points_out[out_count * 2 + 1] = ay + (by - ay) * t;
133
+ out_count++;
134
+ }
135
+ }
136
+ return out_count;
137
+ }
138
+
139
+ /**
140
+ * In-place reduction of `candidates` (stride 7: x, y, z on triangle,
141
+ * x, y, z on box, depth) to at most {@link MAX_CONTACTS} entries.
142
+ * Strategy: keep the deepest, then iteratively pick the candidate
143
+ * whose minimum distance to the already-kept set (in 3D space) is
144
+ * largest approximates max-perimeter / max-area selection.
145
+ */
146
+ // Contact reduction (deepest + farthest-point spread) is shared via
147
+ // reduce_manifold_contacts (stride 7 here).
148
+ function reduce_contacts(n) {
149
+ return reduce_manifold_contacts(candidates, n, CONTACT_STRIDE, MAX_CONTACTS, CONTACT_STRIDE - 1);
150
+ }
151
+
152
+ // --- main --------------------------------------------------------------------
153
+
154
+ // --- hoisted SAT axis test (D3) ----------------------------------------------
155
+ //
156
+ // `test_axis` used to be a per-call closure — allocated for every
157
+ // box-triangle candidate because it captures and MUTATES the running
158
+ // best-axis accumulator. Hoisted to module scope; the per-call inputs are
159
+ // staged in the g_bt_* slots (written once per call, read by all 13 axis
160
+ // tests). Same arithmetic, same order — bit-identical results.
161
+
162
+ let g_bt_bcx = 0, g_bt_bcy = 0, g_bt_bcz = 0;
163
+ let g_bt_bhx = 0, g_bt_bhy = 0, g_bt_bhz = 0;
164
+ let g_bt_ax = 0, g_bt_ay = 0, g_bt_az = 0;
165
+ let g_bt_bx = 0, g_bt_by = 0, g_bt_bz = 0;
166
+ let g_bt_cx = 0, g_bt_cy = 0, g_bt_cz = 0;
167
+ let g_bt_d_t2b_x = 0, g_bt_d_t2b_y = 0, g_bt_d_t2b_z = 0;
168
+ let g_bt_best_overlap = Infinity;
169
+ let g_bt_best_nx = 0, g_bt_best_ny = 0, g_bt_best_nz = 0;
170
+ let g_bt_best_source = -1;
171
+ let g_bt_best_box_edge_idx = -1;
172
+ let g_bt_best_tri_edge_idx = -1;
173
+
174
+ /**
175
+ * Test a candidate separating axis `(lx, ly, lz)` (need not be unit-length).
176
+ * Returns true if a separating axis is found (overlap < 0); otherwise
177
+ * updates the g_bt_best_* slots if this axis has the smallest overlap so far.
178
+ *
179
+ * Source:
180
+ * 0..2 : box face normal (axis index 0..2)
181
+ * 3 : triangle face normal
182
+ * 4..12 : edge-cross (box_edge_idx * 3 + tri_edge_idx, offset +4)
183
+ */
184
+ function test_axis(lx, ly, lz, source, box_e_idx, tri_e_idx) {
185
+ const len_sqr = lx * lx + ly * ly + lz * lz;
186
+ if (len_sqr <= PARALLEL_EPS_SQR) return false;
187
+ const inv_len = 1 / Math.sqrt(len_sqr);
188
+ const ux = lx * inv_len, uy = ly * inv_len, uz = lz * inv_len;
189
+
190
+ // Box projection.
191
+ const box_centre_proj = ux * g_bt_bcx + uy * g_bt_bcy + uz * g_bt_bcz;
192
+ const r_box = box3_projected_half_extent(scratch_axes, 0, g_bt_bhx, g_bt_bhy, g_bt_bhz, ux, uy, uz);
193
+ const bmin = box_centre_proj - r_box;
194
+ const bmax = box_centre_proj + r_box;
195
+
196
+ // Triangle projection — NOT symmetric around centroid, so we take
197
+ // the per-vertex extreme values.
198
+ const da = ux * g_bt_ax + uy * g_bt_ay + uz * g_bt_az;
199
+ const db = ux * g_bt_bx + uy * g_bt_by + uz * g_bt_bz;
200
+ const dc = ux * g_bt_cx + uy * g_bt_cy + uz * g_bt_cz;
201
+ const tmin = da < db ? (da < dc ? da : dc) : (db < dc ? db : dc);
202
+ const tmax = da > db ? (da > dc ? da : dc) : (db > dc ? db : dc);
203
+
204
+ // Separation test.
205
+ if (bmax < tmin || tmax < bmin) return true;
206
+
207
+ // Compute MTV magnitude as the smaller of the two "push directions".
208
+ // push_pos: how far we'd push the triangle in +u to escape.
209
+ // push_neg: how far in -u.
210
+ // For asymmetric intervals these are not equal in general.
211
+ const push_pos = bmax - tmin;
212
+ const push_neg = tmax - bmin;
213
+ const overlap = push_pos < push_neg ? push_pos : push_neg;
214
+
215
+ if (overlap < g_bt_best_overlap) {
216
+ g_bt_best_overlap = overlap;
217
+ // Canonical normal direction: triangle -> box. Use the
218
+ // box-centre-vs-triangle-centroid sign as the tiebreaker so
219
+ // the SAT axis is canonically oriented before we negate.
220
+ const center_dot = g_bt_d_t2b_x * ux + g_bt_d_t2b_y * uy + g_bt_d_t2b_z * uz;
221
+ const sign = center_dot >= 0 ? 1 : -1;
222
+ g_bt_best_nx = ux * sign;
223
+ g_bt_best_ny = uy * sign;
224
+ g_bt_best_nz = uz * sign;
225
+ g_bt_best_source = source;
226
+ g_bt_best_box_edge_idx = box_e_idx;
227
+ g_bt_best_tri_edge_idx = tri_e_idx;
228
+ }
229
+ return false;
230
+ }
231
+
232
+ /**
233
+ * @param {number[]|Float64Array} out length >= {@link BOX_TRIANGLE_OUT_LENGTH}
234
+ * @param {number} bcx box centre x (world)
235
+ * @param {number} bcy
236
+ * @param {number} bcz
237
+ * @param {number} bqx box quaternion x
238
+ * @param {number} bqy
239
+ * @param {number} bqz
240
+ * @param {number} bqw
241
+ * @param {number} bhx box half-extent x (body frame)
242
+ * @param {number} bhy
243
+ * @param {number} bhz
244
+ * @param {number} ax triangle vertex A x (world)
245
+ * @param {number} ay
246
+ * @param {number} az
247
+ * @param {number} bx triangle vertex B x (world)
248
+ * @param {number} by
249
+ * @param {number} bz
250
+ * @param {number} cx triangle vertex C x (world)
251
+ * @param {number} cy
252
+ * @param {number} cz
253
+ * @returns {boolean} true if box and triangle overlap
254
+ */
255
+ export function box_triangle_contact(
256
+ out,
257
+ bcx, bcy, bcz, bqx, bqy, bqz, bqw, bhx, bhy, bhz,
258
+ ax, ay, az, bx, by, bz, cx, cy, cz
259
+ ) {
260
+ quat3_to_matrix3(scratch_axes, 0, bqx, bqy, bqz, bqw);
261
+ const axes = scratch_axes;
262
+
263
+ // Triangle edges in world.
264
+ const ab_x = bx - ax, ab_y = by - ay, ab_z = bz - az;
265
+ const bc_x = cx - bx, bc_y = cy - by, bc_z = cz - bz;
266
+ const ca_x = ax - cx, ca_y = ay - cy, ca_z = az - cz;
267
+
268
+ // Triangle face normal in world via (B-A) × (C-A).
269
+ const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
270
+ const tnx_raw = ab_y * e2z - ab_z * e2y;
271
+ const tny_raw = ab_z * e2x - ab_x * e2z;
272
+ const tnz_raw = ab_x * e2y - ab_y * e2x;
273
+ const tn_mag_sqr = tnx_raw * tnx_raw + tny_raw * tny_raw + tnz_raw * tnz_raw;
274
+ if (tn_mag_sqr <= PARALLEL_EPS_SQR) {
275
+ // Degenerate triangle (zero area). No sensible contact.
276
+ out[3] = 0;
277
+ return false;
278
+ }
279
+ const tn_inv = 1 / Math.sqrt(tn_mag_sqr);
280
+ const tnx = tnx_raw * tn_inv;
281
+ const tny = tny_raw * tn_inv;
282
+ const tnz = tnz_raw * tn_inv;
283
+
284
+ // Triangle centroid for sign-canonicalisation of the contact normal.
285
+ const tcx = (ax + bx + cx) / 3;
286
+ const tcy = (ay + by + cy) / 3;
287
+ const tcz = (az + bz + cz) / 3;
288
+ // Vector from triangle centroid toward box centre used to flip the
289
+ // SAT axis so the stored normal consistently points triangle box.
290
+ const d_t2b_x = bcx - tcx;
291
+ const d_t2b_y = bcy - tcy;
292
+ const d_t2b_z = bcz - tcz;
293
+
294
+ // Stage the per-call inputs + reset the best-axis accumulator for the
295
+ // hoisted test_axis (see the D3 note above it).
296
+ g_bt_bcx = bcx; g_bt_bcy = bcy; g_bt_bcz = bcz;
297
+ g_bt_bhx = bhx; g_bt_bhy = bhy; g_bt_bhz = bhz;
298
+ g_bt_ax = ax; g_bt_ay = ay; g_bt_az = az;
299
+ g_bt_bx = bx; g_bt_by = by; g_bt_bz = bz;
300
+ g_bt_cx = cx; g_bt_cy = cy; g_bt_cz = cz;
301
+ g_bt_d_t2b_x = d_t2b_x; g_bt_d_t2b_y = d_t2b_y; g_bt_d_t2b_z = d_t2b_z;
302
+ g_bt_best_overlap = Infinity;
303
+ g_bt_best_nx = 0; g_bt_best_ny = 0; g_bt_best_nz = 0;
304
+ g_bt_best_source = -1;
305
+ g_bt_best_box_edge_idx = -1;
306
+ g_bt_best_tri_edge_idx = -1;
307
+
308
+ // 1. Box face normals (3 axes).
309
+ if (test_axis(axes[0], axes[1], axes[2], 0, -1, -1)) { out[3] = 0; return false; }
310
+ if (test_axis(axes[3], axes[4], axes[5], 1, -1, -1)) { out[3] = 0; return false; }
311
+ if (test_axis(axes[6], axes[7], axes[8], 2, -1, -1)) { out[3] = 0; return false; }
312
+
313
+ // 2. Triangle face normal (1 axis).
314
+ if (test_axis(tnx, tny, tnz, 3, -1, -1)) { out[3] = 0; return false; }
315
+
316
+ // 3. Edge-edge cross products (9 axes).
317
+ for (let i = 0; i < 3; i++) {
318
+ const aix = axes[i * 3], aiy = axes[i * 3 + 1], aiz = axes[i * 3 + 2];
319
+ for (let j = 0; j < 3; j++) {
320
+ let ex, ey, ez;
321
+ if (j === 0) { ex = ab_x; ey = ab_y; ez = ab_z; }
322
+ else if (j === 1) { ex = bc_x; ey = bc_y; ez = bc_z; }
323
+ else { ex = ca_x; ey = ca_y; ez = ca_z; }
324
+ const cx_ax = aiy * ez - aiz * ey;
325
+ const cy_ax = aiz * ex - aix * ez;
326
+ const cz_ax = aix * ey - aiy * ex;
327
+ if (test_axis(cx_ax, cy_ax, cz_ax, 4 + i * 3 + j, i, j)) { out[3] = 0; return false; }
328
+ }
329
+ }
330
+
331
+ // We have overlap. Output the contact normal (triangle → box).
332
+ const best_overlap = g_bt_best_overlap;
333
+ const best_source = g_bt_best_source;
334
+ const best_box_edge_idx = g_bt_best_box_edge_idx;
335
+ const best_tri_edge_idx = g_bt_best_tri_edge_idx;
336
+ const nx = g_bt_best_nx, ny = g_bt_best_ny, nz = g_bt_best_nz;
337
+ out[0] = nx; out[1] = ny; out[2] = nz;
338
+
339
+ // --- Contact manifold construction ---
340
+
341
+ if (best_source < 3) {
342
+ // Box face axis winner: reference = box face, incident = triangle.
343
+ return emit_box_face_manifold(out, best_source, axes, bcx, bcy, bcz, bhx, bhy, bhz,
344
+ ax, ay, az, bx, by, bz, cx, cy, cz, tnx, tny, tnz, nx, ny, nz, best_overlap);
345
+ } else if (best_source === 3) {
346
+ // Triangle face axis winner: reference = triangle, incident = box face.
347
+ return emit_triangle_face_manifold(out, axes, bcx, bcy, bcz, bhx, bhy, bhz,
348
+ ax, ay, az, bx, by, bz, cx, cy, cz, tnx, tny, tnz, nx, ny, nz, best_overlap);
349
+ } else {
350
+ // Edge-cross winner: single contact at closest pair on the two edges.
351
+ return emit_edge_cross_contact(out, axes, bcx, bcy, bcz, bhx, bhy, bhz,
352
+ ax, ay, az, bx, by, bz, cx, cy, cz,
353
+ best_box_edge_idx, best_tri_edge_idx,
354
+ nx, ny, nz, best_overlap);
355
+ }
356
+ }
357
+
358
+ // --- Manifold builders ------------------------------------------------------
359
+
360
+ /**
361
+ * Box-face SAT winner: clip the triangle against the box's reference face
362
+ * rectangle, then for each surviving (u, v) recover the world contact on
363
+ * the triangle plane.
364
+ */
365
+ function emit_box_face_manifold(
366
+ out, ref_axis_idx, axes,
367
+ bcx, bcy, bcz, bhx, bhy, bhz,
368
+ ax, ay, az, bx, by, bz, cx, cy, cz,
369
+ tnx, tny, tnz,
370
+ nx, ny, nz,
371
+ best_overlap
372
+ ) {
373
+ // The contact normal `n` points triangle box. The box's outward
374
+ // face normal at the reference face points box triangle = -n.
375
+ const face_out_nx = -nx;
376
+ const face_out_ny = -ny;
377
+ const face_out_nz = -nz;
378
+
379
+ // Sign of the reference axis aligned with the outward normal.
380
+ const ref_axis_x = axes[ref_axis_idx * 3];
381
+ const ref_axis_y = axes[ref_axis_idx * 3 + 1];
382
+ const ref_axis_z = axes[ref_axis_idx * 3 + 2];
383
+ const ref_axis_dot = ref_axis_x * face_out_nx + ref_axis_y * face_out_ny + ref_axis_z * face_out_nz;
384
+ const ref_axis_sign = ref_axis_dot >= 0 ? 1 : -1;
385
+ const ref_h_along = ref_axis_idx === 0 ? bhx : (ref_axis_idx === 1 ? bhy : bhz);
386
+
387
+ // Reference face origin (centre of the face on the +/- side of the box).
388
+ const ref_face_ox = bcx + ref_axis_x * ref_axis_sign * ref_h_along;
389
+ const ref_face_oy = bcy + ref_axis_y * ref_axis_sign * ref_h_along;
390
+ const ref_face_oz = bcz + ref_axis_z * ref_axis_sign * ref_h_along;
391
+
392
+ // Reference face's two tangent axes (u, v) and their half-extents.
393
+ let u_axis_idx, v_axis_idx;
394
+ if (ref_axis_idx === 0) { u_axis_idx = 1; v_axis_idx = 2; }
395
+ else if (ref_axis_idx === 1) { u_axis_idx = 2; v_axis_idx = 0; }
396
+ else { u_axis_idx = 0; v_axis_idx = 1; }
397
+ const ux = axes[u_axis_idx * 3], uy = axes[u_axis_idx * 3 + 1], uz = axes[u_axis_idx * 3 + 2];
398
+ const vx = axes[v_axis_idx * 3], vy = axes[v_axis_idx * 3 + 1], vz = axes[v_axis_idx * 3 + 2];
399
+ const half_u = u_axis_idx === 0 ? bhx : (u_axis_idx === 1 ? bhy : bhz);
400
+ const half_v = v_axis_idx === 0 ? bhx : (v_axis_idx === 1 ? bhy : bhz);
401
+
402
+ // Project the triangle's 3 vertices into the (u, v) basis on the
403
+ // reference plane.
404
+ const da_x = ax - ref_face_ox, da_y = ay - ref_face_oy, da_z = az - ref_face_oz;
405
+ const db_x = bx - ref_face_ox, db_y = by - ref_face_oy, db_z = bz - ref_face_oz;
406
+ const dc_x = cx - ref_face_ox, dc_y = cy - ref_face_oy, dc_z = cz - ref_face_oz;
407
+ tri_uv_in[0] = da_x * ux + da_y * uy + da_z * uz;
408
+ tri_uv_in[1] = da_x * vx + da_y * vy + da_z * vz;
409
+ tri_uv_in[2] = db_x * ux + db_y * uy + db_z * uz;
410
+ tri_uv_in[3] = db_x * vx + db_y * vy + db_z * vz;
411
+ tri_uv_in[4] = dc_x * ux + dc_y * uy + dc_z * uz;
412
+ tri_uv_in[5] = dc_x * vx + dc_y * vy + dc_z * vz;
413
+
414
+ // Clip against rectangle |u| half_u, |v| half_v via 4 passes.
415
+ let n = 3;
416
+ n = polygon2_clip_axis_halfplane(tri_uv_in, n, tri_uv_out, 0, half_u, true);
417
+ if (n === 0) { out[3] = 0; return false; }
418
+ n = polygon2_clip_axis_halfplane(tri_uv_out, n, tri_uv_in, 0, -half_u, false);
419
+ if (n === 0) { out[3] = 0; return false; }
420
+ n = polygon2_clip_axis_halfplane(tri_uv_in, n, tri_uv_out, 1, half_v, true);
421
+ if (n === 0) { out[3] = 0; return false; }
422
+ n = polygon2_clip_axis_halfplane(tri_uv_out, n, tri_uv_in, 1, -half_v, false);
423
+ if (n === 0) { out[3] = 0; return false; }
424
+
425
+ // The clipped polygon is in tri_uv_in. For each surviving (u, v):
426
+ // X = ref_face_origin + u * u_axis + v * v_axis (point on ref plane)
427
+ // Find t such that (X + t * face_out_n) is on the triangle plane:
428
+ // (X + t * face_out_n - A) · tn = 0
429
+ // t = ((A - X) · tn) / (face_out_n · tn)
430
+ // World point on triangle plane = X + t * face_out_n.
431
+ // Depth = -t (positive when triangle penetrates the box).
432
+ // The recovery below divides by this cosine; a depth recovered through a
433
+ // near-zero denominator is a projection artefact, never real penetration:
434
+ // along the WINNING SAT axis no contact can be deeper than the SAT
435
+ // overlap itself, so candidates past that cap are rejected, and a
436
+ // near-parallel plane (or an emptied candidate set) falls back to the
437
+ // deepest triangle vertex with the exact SAT depth - a SAT-proven
438
+ // overlap must never be dropped.
439
+ const depth_cap = best_overlap * 1.0001 + 1e-9;
440
+ const denom = face_out_nx * tnx + face_out_ny * tny + face_out_nz * tnz;
441
+ if (Math.abs(denom) < PARALLEL_COS_EPS) {
442
+ return emit_box_face_deepest_vertex(out,
443
+ ref_face_ox, ref_face_oy, ref_face_oz,
444
+ face_out_nx, face_out_ny, face_out_nz,
445
+ ux, uy, uz, vx, vy, vz, half_u, half_v,
446
+ ax, ay, az, bx, by, bz, cx, cy, cz,
447
+ best_overlap);
448
+ }
449
+ const clipped_uv = tri_uv_in;
450
+
451
+ let cand_count = 0;
452
+ for (let i = 0; i < n; i++) {
453
+ const u = clipped_uv[i * 2];
454
+ const v = clipped_uv[i * 2 + 1];
455
+ const xpx = ref_face_ox + u * ux + v * vx;
456
+ const xpy = ref_face_oy + u * uy + v * vy;
457
+ const xpz = ref_face_oz + u * uz + v * vz;
458
+
459
+ const t = ((ax - xpx) * tnx + (ay - xpy) * tny + (az - xpz) * tnz) / denom;
460
+ const world_tri_x = xpx + face_out_nx * t;
461
+ const world_tri_y = xpy + face_out_ny * t;
462
+ const world_tri_z = xpz + face_out_nz * t;
463
+ const depth = -t;
464
+ if (depth <= 0 || depth > depth_cap) continue;
465
+
466
+ const off = cand_count * 7;
467
+ candidates[off] = world_tri_x;
468
+ candidates[off + 1] = world_tri_y;
469
+ candidates[off + 2] = world_tri_z;
470
+ // Box-side contact = same (u, v) on ref plane.
471
+ candidates[off + 3] = xpx;
472
+ candidates[off + 4] = xpy;
473
+ candidates[off + 5] = xpz;
474
+ candidates[off + 6] = depth;
475
+ cand_count++;
476
+ }
477
+
478
+ if (cand_count === 0) {
479
+ return emit_box_face_deepest_vertex(out,
480
+ ref_face_ox, ref_face_oy, ref_face_oz,
481
+ face_out_nx, face_out_ny, face_out_nz,
482
+ ux, uy, uz, vx, vy, vz, half_u, half_v,
483
+ ax, ay, az, bx, by, bz, cx, cy, cz,
484
+ best_overlap);
485
+ }
486
+ const kept = reduce_contacts(cand_count);
487
+ out[3] = kept;
488
+ for (let k = 0; k < kept; k++) {
489
+ const src = k * 7;
490
+ const base = 4 + k * CONTACT_STRIDE;
491
+ out[base] = candidates[src];
492
+ out[base + 1] = candidates[src + 1];
493
+ out[base + 2] = candidates[src + 2];
494
+ out[base + 3] = candidates[src + 3];
495
+ out[base + 4] = candidates[src + 4];
496
+ out[base + 5] = candidates[src + 5];
497
+ out[base + 6] = candidates[src + 6];
498
+ }
499
+ return true;
500
+ }
501
+
502
+ /**
503
+ * Box-face fallback for configurations the plane recovery cannot serve (the
504
+ * triangle plane near-parallel to the projection direction, or every clipped
505
+ * candidate rejected): one contact at the triangle vertex deepest below the
506
+ * reference face, with the exact SAT overlap as depth. The box witness is
507
+ * the vertex projected onto the face and clamped into the face rectangle.
508
+ */
509
+ function emit_box_face_deepest_vertex(
510
+ out,
511
+ ref_face_ox, ref_face_oy, ref_face_oz,
512
+ face_out_nx, face_out_ny, face_out_nz,
513
+ ux, uy, uz, vx, vy, vz, half_u, half_v,
514
+ ax, ay, az, bx, by, bz, cx, cy, cz,
515
+ best_overlap
516
+ ) {
517
+ let px = ax, py = ay, pz = az;
518
+ let best_d = (ref_face_ox - ax) * face_out_nx + (ref_face_oy - ay) * face_out_ny + (ref_face_oz - az) * face_out_nz;
519
+ const d_b = (ref_face_ox - bx) * face_out_nx + (ref_face_oy - by) * face_out_ny + (ref_face_oz - bz) * face_out_nz;
520
+ if (d_b > best_d) { best_d = d_b; px = bx; py = by; pz = bz; }
521
+ const d_c = (ref_face_ox - cx) * face_out_nx + (ref_face_oy - cy) * face_out_ny + (ref_face_oz - cz) * face_out_nz;
522
+ if (d_c > best_d) { best_d = d_c; px = cx; py = cy; pz = cz; }
523
+
524
+ // Project onto the face plane, clamped into the face rectangle.
525
+ let u = (px - ref_face_ox) * ux + (py - ref_face_oy) * uy + (pz - ref_face_oz) * uz;
526
+ let v = (px - ref_face_ox) * vx + (py - ref_face_oy) * vy + (pz - ref_face_oz) * vz;
527
+ u = u < -half_u ? -half_u : (u > half_u ? half_u : u);
528
+ v = v < -half_v ? -half_v : (v > half_v ? half_v : v);
529
+
530
+ out[3] = 1;
531
+ out[4] = px;
532
+ out[5] = py;
533
+ out[6] = pz;
534
+ out[7] = ref_face_ox + u * ux + v * vx;
535
+ out[8] = ref_face_oy + u * uy + v * vy;
536
+ out[9] = ref_face_oz + u * uz + v * vz;
537
+ out[10] = best_overlap;
538
+ return true;
539
+ }
540
+
541
+ /**
542
+ * Triangle-face SAT winner: reference = triangle, incident = the box face
543
+ * most antiparallel to the contact normal. Clip the box face quad against
544
+ * the triangle's 3 edges, then recover world contacts on the box face plane.
545
+ */
546
+ function emit_triangle_face_manifold(
547
+ out, axes,
548
+ bcx, bcy, bcz, bhx, bhy, bhz,
549
+ ax, ay, az, bx, by, bz, cx, cy, cz,
550
+ tnx, tny, tnz,
551
+ nx, ny, nz,
552
+ best_overlap
553
+ ) {
554
+ // Reference = triangle. Its outward face normal points from the triangle
555
+ // surface toward the box — i.e. the contact normal `n` (triangle → box),
556
+ // which the SAT already sign-canonicalised. The incident box face is then
557
+ // the one whose outward normal is most antiparallel to this (the box face
558
+ // actually facing the triangle), and the per-point depth below measures the
559
+ // penetration of THAT face — not the box's far face.
560
+ //
561
+ // (The earlier `-n` here pointed the reference normal into the terrain, so
562
+ // the incident picker selected the box's FAR face and the plane-to-plane
563
+ // depth ballooned to ~the box's thickness for any tilted box catapulting
564
+ // rocking boxes off heightmaps. See box_triangle_contact.spec.js
565
+ // "tilted box, shallow corner".)
566
+ const ref_out_x = nx;
567
+ const ref_out_y = ny;
568
+ const ref_out_z = nz;
569
+
570
+ // Pick the incident box face: the one whose outward axis is most
571
+ // antiparallel to ref_out.
572
+ let inc_axis_idx = 0;
573
+ let inc_axis_sign = 1;
574
+ let max_anti = -Infinity;
575
+ for (let i = 0; i < 3; i++) {
576
+ const iax = axes[i * 3], iay = axes[i * 3 + 1], iaz = axes[i * 3 + 2];
577
+ const d_pos = iax * ref_out_x + iay * ref_out_y + iaz * ref_out_z;
578
+ if (-d_pos > max_anti) { max_anti = -d_pos; inc_axis_idx = i; inc_axis_sign = -1; }
579
+ if ( d_pos > max_anti) { max_anti = d_pos; inc_axis_idx = i; inc_axis_sign = 1; }
580
+ }
581
+ const inc_outward_sign = -inc_axis_sign;
582
+ const inc_h_along = inc_axis_idx === 0 ? bhx : (inc_axis_idx === 1 ? bhy : bhz);
583
+ const inc_face_cx = bcx + axes[inc_axis_idx * 3] * inc_outward_sign * inc_h_along;
584
+ const inc_face_cy = bcy + axes[inc_axis_idx * 3 + 1] * inc_outward_sign * inc_h_along;
585
+ const inc_face_cz = bcz + axes[inc_axis_idx * 3 + 2] * inc_outward_sign * inc_h_along;
586
+
587
+ // Box face's two tangent axes in world.
588
+ let i_u_idx, i_v_idx;
589
+ if (inc_axis_idx === 0) { i_u_idx = 1; i_v_idx = 2; }
590
+ else if (inc_axis_idx === 1) { i_u_idx = 2; i_v_idx = 0; }
591
+ else { i_u_idx = 0; i_v_idx = 1; }
592
+ const iux = axes[i_u_idx * 3], iuy = axes[i_u_idx * 3 + 1], iuz = axes[i_u_idx * 3 + 2];
593
+ const ivx = axes[i_v_idx * 3], ivy = axes[i_v_idx * 3 + 1], ivz = axes[i_v_idx * 3 + 2];
594
+ const i_half_u = i_u_idx === 0 ? bhx : (i_u_idx === 1 ? bhy : bhz);
595
+ const i_half_v = i_v_idx === 0 ? bhx : (i_v_idx === 1 ? bhy : bhz);
596
+
597
+ // Build a 2D basis on the triangle plane. Use AB direction as `u`,
598
+ // and `n_uv = tn × u` as `v` so the basis is orthonormal.
599
+ const ab_x_w = bx - ax, ab_y_w = by - ay, ab_z_w = bz - az;
600
+ const ab_len_sqr = ab_x_w * ab_x_w + ab_y_w * ab_y_w + ab_z_w * ab_z_w;
601
+ if (ab_len_sqr <= PARALLEL_EPS_SQR) { out[3] = 0; return false; }
602
+ const ab_inv = 1 / Math.sqrt(ab_len_sqr);
603
+ const tux = ab_x_w * ab_inv, tuy = ab_y_w * ab_inv, tuz = ab_z_w * ab_inv;
604
+ // v = tn × u (using the geometric face normal as the "up").
605
+ const tvx = tny * tuz - tnz * tuy;
606
+ const tvy = tnz * tux - tnx * tuz;
607
+ const tvz = tnx * tuy - tny * tux;
608
+
609
+ // Project triangle vertices to (u, v). A is the origin of the
610
+ // 2D basis — A_uv = (0, 0); B is along the u axis by construction
611
+ // so B_uv = (|AB|, 0); C is computed normally.
612
+ const ac_x_w = cx - ax, ac_y_w = cy - ay, ac_z_w = cz - az;
613
+ const b_u = ab_x_w * tux + ab_y_w * tuy + ab_z_w * tuz; // = |AB|
614
+ const c_u = ac_x_w * tux + ac_y_w * tuy + ac_z_w * tuz;
615
+ const c_v = ac_x_w * tvx + ac_y_w * tvy + ac_z_w * tvz;
616
+
617
+ // Triangle's 3 edges as 2D half-planes in (u, v). For each edge, the
618
+ // outward normal must point AWAY from the opposite vertex.
619
+ // Edge AB: from A=(0,0) to B=(b_u, 0). Direction = (1, 0). Outward
620
+ // normal perpendicular = (0, 1) or (0, -1). Choose sign so it
621
+ // points away from C (= sign(-c_v) gives 1 if c_v < 0).
622
+ const ab_n_v = c_v >= 0 ? -1 : 1;
623
+ // Edge BC: from B=(b_u, 0) to C=(c_u, c_v). Direction = (c_u - b_u, c_v).
624
+ // Outward perpendicular: rotate by ±90° (depending on triangle winding).
625
+ // Outward direction must point away from A=(0,0).
626
+ const bc_dir_u = c_u - b_u, bc_dir_v = c_v - 0;
627
+ // Two perpendiculars: (-bc_dir_v, bc_dir_u) and (bc_dir_v, -bc_dir_u).
628
+ // Pick the one with positive dot vs (A - midpoint of BC) negated.
629
+ const bc_mid_u = (b_u + c_u) * 0.5;
630
+ const bc_mid_v = c_v * 0.5;
631
+ const to_a_u_from_bc = 0 - bc_mid_u;
632
+ const to_a_v_from_bc = 0 - bc_mid_v;
633
+ // We want outward = -direction_to_A.
634
+ let bc_n_u = -bc_dir_v, bc_n_v = bc_dir_u;
635
+ if (bc_n_u * to_a_u_from_bc + bc_n_v * to_a_v_from_bc > 0) {
636
+ bc_n_u = -bc_n_u; bc_n_v = -bc_n_v;
637
+ }
638
+ // Normalise so the clip threshold is "distance ≤ 0" in 2D.
639
+ const bc_n_len = Math.sqrt(bc_n_u * bc_n_u + bc_n_v * bc_n_v);
640
+ if (bc_n_len > 0) { bc_n_u /= bc_n_len; bc_n_v /= bc_n_len; }
641
+ // Edge CA: from C to A. Direction = (-c_u, -c_v). Outward must point
642
+ // away from B.
643
+ const ca_dir_u = 0 - c_u, ca_dir_v = 0 - c_v;
644
+ const ca_mid_u = c_u * 0.5;
645
+ const ca_mid_v = c_v * 0.5;
646
+ const to_b_u_from_ca = b_u - ca_mid_u;
647
+ const to_b_v_from_ca = 0 - ca_mid_v;
648
+ let ca_n_u = -ca_dir_v, ca_n_v = ca_dir_u;
649
+ if (ca_n_u * to_b_u_from_ca + ca_n_v * to_b_v_from_ca > 0) {
650
+ ca_n_u = -ca_n_u; ca_n_v = -ca_n_v;
651
+ }
652
+ const ca_n_len = Math.sqrt(ca_n_u * ca_n_u + ca_n_v * ca_n_v);
653
+ if (ca_n_len > 0) { ca_n_u /= ca_n_len; ca_n_v /= ca_n_len; }
654
+
655
+ // Build the box-face polygon (4 corners) and project to (u, v) on
656
+ // the triangle plane.
657
+ const signs_u = [ 1, 1, -1, -1];
658
+ const signs_v = [-1, 1, 1, -1];
659
+ for (let i = 0; i < 4; i++) {
660
+ const su = signs_u[i] * i_half_u;
661
+ const sv = signs_v[i] * i_half_v;
662
+ const wx = inc_face_cx + iux * su + ivx * sv;
663
+ const wy = inc_face_cy + iuy * su + ivy * sv;
664
+ const wz = inc_face_cz + iuz * su + ivz * sv;
665
+ const dx = wx - ax, dy = wy - ay, dz = wz - az;
666
+ box_face_corners_uv_in[i * 2] = dx * tux + dy * tuy + dz * tuz;
667
+ box_face_corners_uv_in[i * 2 + 1] = dx * tvx + dy * tvy + dz * tvz;
668
+ }
669
+
670
+ // Clip the quad against the 3 triangle-edge half-planes.
671
+ let np = 4;
672
+ // Edge AB: half-plane (p.v - 0) * ab_n_v <= 0 → coord_idx = 1, bound = 0, keep_below if ab_n_v=+1.
673
+ np = clip_against_half_plane_uv(box_face_corners_uv_in, np, box_face_corners_uv_out, 0, 0, 0, ab_n_v);
674
+ if (np === 0) { out[3] = 0; return false; }
675
+ np = clip_against_half_plane_uv(box_face_corners_uv_out, np, box_face_corners_uv_in, b_u, 0, bc_n_u, bc_n_v);
676
+ if (np === 0) { out[3] = 0; return false; }
677
+ np = clip_against_half_plane_uv(box_face_corners_uv_in, np, box_face_corners_uv_out, c_u, c_v, ca_n_u, ca_n_v);
678
+ if (np === 0) { out[3] = 0; return false; }
679
+
680
+ // Survivors are in box_face_corners_uv_out. For each:
681
+ // X = A + u * tu + v * tv (point on triangle plane)
682
+ // Find t such that X + t * ref_out is on box face plane:
683
+ // (X + t * ref_out - inc_face_c) · inc_out = 0
684
+ // Where inc_out = inc_axes[inc_axis_idx] * inc_outward_sign.
685
+ const inc_out_x = axes[inc_axis_idx * 3] * inc_outward_sign;
686
+ const inc_out_y = axes[inc_axis_idx * 3 + 1] * inc_outward_sign;
687
+ const inc_out_z = axes[inc_axis_idx * 3 + 2] * inc_outward_sign;
688
+ // Same recovery-degeneracy protocol as emit_box_face_manifold: angular
689
+ // epsilon on the cosine, per-candidate depth cap at the SAT overlap, and
690
+ // a deepest-corner fallback so a SAT-proven overlap is never dropped.
691
+ const depth_cap = best_overlap * 1.0001 + 1e-9;
692
+ const denom = ref_out_x * inc_out_x + ref_out_y * inc_out_y + ref_out_z * inc_out_z;
693
+ if (Math.abs(denom) < PARALLEL_COS_EPS) {
694
+ return emit_triangle_face_deepest_corner(out,
695
+ inc_face_cx, inc_face_cy, inc_face_cz,
696
+ iux, iuy, iuz, ivx, ivy, ivz, i_half_u, i_half_v,
697
+ ax, ay, az,
698
+ ref_out_x, ref_out_y, ref_out_z,
699
+ best_overlap);
700
+ }
701
+ const clipped_uv = box_face_corners_uv_out;
702
+
703
+ let cand_count = 0;
704
+ for (let i = 0; i < np; i++) {
705
+ const u = clipped_uv[i * 2];
706
+ const v = clipped_uv[i * 2 + 1];
707
+ const xpx = ax + u * tux + v * tvx;
708
+ const xpy = ay + u * tuy + v * tvy;
709
+ const xpz = az + u * tuz + v * tvz;
710
+
711
+ const num_x = inc_face_cx - xpx;
712
+ const num_y = inc_face_cy - xpy;
713
+ const num_z = inc_face_cz - xpz;
714
+ const t = (num_x * inc_out_x + num_y * inc_out_y + num_z * inc_out_z) / denom;
715
+ const world_box_x = xpx + ref_out_x * t;
716
+ const world_box_y = xpy + ref_out_y * t;
717
+ const world_box_z = xpz + ref_out_z * t;
718
+ // Depth: how far the box face has penetrated past the triangle
719
+ // plane, measured along ref_out. depth = -t.
720
+ const depth = -t;
721
+ if (depth <= 0 || depth > depth_cap) continue;
722
+
723
+ const off = cand_count * 7;
724
+ // Triangle-side contact: the clipped (u, v) point on the
725
+ // triangle plane.
726
+ candidates[off] = xpx;
727
+ candidates[off + 1] = xpy;
728
+ candidates[off + 2] = xpz;
729
+ candidates[off + 3] = world_box_x;
730
+ candidates[off + 4] = world_box_y;
731
+ candidates[off + 5] = world_box_z;
732
+ candidates[off + 6] = depth;
733
+ cand_count++;
734
+ }
735
+
736
+ if (cand_count === 0) {
737
+ return emit_triangle_face_deepest_corner(out,
738
+ inc_face_cx, inc_face_cy, inc_face_cz,
739
+ iux, iuy, iuz, ivx, ivy, ivz, i_half_u, i_half_v,
740
+ ax, ay, az,
741
+ ref_out_x, ref_out_y, ref_out_z,
742
+ best_overlap);
743
+ }
744
+ const kept = reduce_contacts(cand_count);
745
+ out[3] = kept;
746
+ for (let k = 0; k < kept; k++) {
747
+ const src = k * 7;
748
+ const base = 4 + k * CONTACT_STRIDE;
749
+ out[base] = candidates[src];
750
+ out[base + 1] = candidates[src + 1];
751
+ out[base + 2] = candidates[src + 2];
752
+ out[base + 3] = candidates[src + 3];
753
+ out[base + 4] = candidates[src + 4];
754
+ out[base + 5] = candidates[src + 5];
755
+ out[base + 6] = candidates[src + 6];
756
+ }
757
+ return true;
758
+ }
759
+
760
+ /**
761
+ * Edge-cross SAT winner: emit a single contact at the closest pair of
762
+ * points on (a parallel-translated) box edge × the triangle edge.
763
+ *
764
+ * The box has 12 edges, but along the winning box-axis direction `i`
765
+ * there are 4 parallel edges (the four lines on the box surface
766
+ * parallel to box-axis i). The "right" edge for the contact is the
767
+ * one whose endpoints span the triangle edge — practically, pick the
768
+ * box edge most aligned with the SAT separation: the one whose centre
769
+ * (when translated perpendicular to the SAT axis) is closest to the
770
+ * triangle edge.
771
+ *
772
+ * Procedure:
773
+ * 1. Construct the box edge as the line through box centre +
774
+ * (the +/- of the other two box half-extents pushing to the
775
+ * "near" corner determined by the SAT-axis sign).
776
+ * 2. Construct the triangle edge as the segment.
777
+ * 3. Closest-pair via `line3_closest_points_segment_segment`.
778
+ * 4. Contact = midpoint, depth = best_overlap.
779
+ */
780
+ /**
781
+ * Triangle-face fallback for configurations the plane recovery cannot serve:
782
+ * one contact at the incident box-face corner deepest below the triangle
783
+ * plane, with the exact SAT overlap as depth. The triangle witness is the
784
+ * corner pushed back onto the triangle plane along the contact normal.
785
+ */
786
+ function emit_triangle_face_deepest_corner(
787
+ out,
788
+ inc_face_cx, inc_face_cy, inc_face_cz,
789
+ iux, iuy, iuz, ivx, ivy, ivz, i_half_u, i_half_v,
790
+ ax, ay, az,
791
+ ref_out_x, ref_out_y, ref_out_z,
792
+ best_overlap
793
+ ) {
794
+ let best_d = -Infinity;
795
+ let px = inc_face_cx, py = inc_face_cy, pz = inc_face_cz;
796
+ for (let i = 0; i < 4; i++) {
797
+ const su = (i === 0 || i === 1 ? 1 : -1) * i_half_u;
798
+ const sv = (i === 1 || i === 2 ? 1 : -1) * i_half_v;
799
+ const wx = inc_face_cx + iux * su + ivx * sv;
800
+ const wy = inc_face_cy + iuy * su + ivy * sv;
801
+ const wz = inc_face_cz + iuz * su + ivz * sv;
802
+ // Depth of this corner below the triangle plane along ref_out.
803
+ const d = -((wx - ax) * ref_out_x + (wy - ay) * ref_out_y + (wz - az) * ref_out_z);
804
+ if (d > best_d) { best_d = d; px = wx; py = wy; pz = wz; }
805
+ }
806
+
807
+ out[3] = 1;
808
+ // Triangle witness: the corner pushed back onto the triangle plane.
809
+ out[4] = px + ref_out_x * best_d;
810
+ out[5] = py + ref_out_y * best_d;
811
+ out[6] = pz + ref_out_z * best_d;
812
+ // Box witness: the deepest corner itself.
813
+ out[7] = px;
814
+ out[8] = py;
815
+ out[9] = pz;
816
+ out[10] = best_overlap;
817
+ return true;
818
+ }
819
+
820
+ function emit_edge_cross_contact(
821
+ out, axes,
822
+ bcx, bcy, bcz, bhx, bhy, bhz,
823
+ ax, ay, az, bx, by, bz, cx, cy, cz,
824
+ box_edge_idx, tri_edge_idx,
825
+ nx, ny, nz,
826
+ overlap
827
+ ) {
828
+ // Box edge direction: axis index `box_edge_idx`.
829
+ const edx = axes[box_edge_idx * 3];
830
+ const edy = axes[box_edge_idx * 3 + 1];
831
+ const edz = axes[box_edge_idx * 3 + 2];
832
+ const edge_half = box_edge_idx === 0 ? bhx : (box_edge_idx === 1 ? bhy : bhz);
833
+
834
+ // The OTHER two box axes determine which of the four parallel edges
835
+ // we pick. Use the sign of the contact normal projected onto each
836
+ // perpendicular axis to choose the edge closest to the contact.
837
+ let u_idx, v_idx;
838
+ if (box_edge_idx === 0) { u_idx = 1; v_idx = 2; }
839
+ else if (box_edge_idx === 1) { u_idx = 2; v_idx = 0; }
840
+ else { u_idx = 0; v_idx = 1; }
841
+ const u_ax_x = axes[u_idx * 3], u_ax_y = axes[u_idx * 3 + 1], u_ax_z = axes[u_idx * 3 + 2];
842
+ const v_ax_x = axes[v_idx * 3], v_ax_y = axes[v_idx * 3 + 1], v_ax_z = axes[v_idx * 3 + 2];
843
+ const half_u = u_idx === 0 ? bhx : (u_idx === 1 ? bhy : bhz);
844
+ const half_v = v_idx === 0 ? bhx : (v_idx === 1 ? bhy : bhz);
845
+
846
+ // The contact normal `n` points triangle → box. The box edge
847
+ // closest to the triangle is the one whose corner (in u, v) is in
848
+ // the -n direction. Decompose -n into the u and v components.
849
+ const minus_n_dot_u = -(nx * u_ax_x + ny * u_ax_y + nz * u_ax_z);
850
+ const minus_n_dot_v = -(nx * v_ax_x + ny * v_ax_y + nz * v_ax_z);
851
+ const u_sign = minus_n_dot_u >= 0 ? 1 : -1;
852
+ const v_sign = minus_n_dot_v >= 0 ? 1 : -1;
853
+
854
+ // Box edge endpoints in world: from corner_lo to corner_hi along
855
+ // the chosen box axis.
856
+ const corner_cx = bcx + u_ax_x * u_sign * half_u + v_ax_x * v_sign * half_v;
857
+ const corner_cy = bcy + u_ax_y * u_sign * half_u + v_ax_y * v_sign * half_v;
858
+ const corner_cz = bcz + u_ax_z * u_sign * half_u + v_ax_z * v_sign * half_v;
859
+ const box_p1x = corner_cx - edx * edge_half, box_p1y = corner_cy - edy * edge_half, box_p1z = corner_cz - edz * edge_half;
860
+ const box_p2x = corner_cx + edx * edge_half, box_p2y = corner_cy + edy * edge_half, box_p2z = corner_cz + edz * edge_half;
861
+
862
+ // Triangle edge endpoints.
863
+ let tri_p1x, tri_p1y, tri_p1z, tri_p2x, tri_p2y, tri_p2z;
864
+ if (tri_edge_idx === 0) {
865
+ tri_p1x = ax; tri_p1y = ay; tri_p1z = az;
866
+ tri_p2x = bx; tri_p2y = by; tri_p2z = bz;
867
+ } else if (tri_edge_idx === 1) {
868
+ tri_p1x = bx; tri_p1y = by; tri_p1z = bz;
869
+ tri_p2x = cx; tri_p2y = cy; tri_p2z = cz;
870
+ } else {
871
+ tri_p1x = cx; tri_p1y = cy; tri_p1z = cz;
872
+ tri_p2x = ax; tri_p2y = ay; tri_p2z = az;
873
+ }
874
+
875
+ // Closest points on the two segments.
876
+ line3_closest_points_segment_segment(
877
+ closest_pair_st,
878
+ box_p1x, box_p1y, box_p1z, box_p2x, box_p2y, box_p2z,
879
+ tri_p1x, tri_p1y, tri_p1z, tri_p2x, tri_p2y, tri_p2z
880
+ );
881
+ const s = closest_pair_st[0], t = closest_pair_st[1];
882
+ const closest_box_x = box_p1x + s * (box_p2x - box_p1x);
883
+ const closest_box_y = box_p1y + s * (box_p2y - box_p1y);
884
+ const closest_box_z = box_p1z + s * (box_p2z - box_p1z);
885
+ const closest_tri_x = tri_p1x + t * (tri_p2x - tri_p1x);
886
+ const closest_tri_y = tri_p1y + t * (tri_p2y - tri_p1y);
887
+ const closest_tri_z = tri_p1z + t * (tri_p2z - tri_p1z);
888
+
889
+ out[3] = 1;
890
+ const base = 4;
891
+ out[base] = closest_tri_x;
892
+ out[base + 1] = closest_tri_y;
893
+ out[base + 2] = closest_tri_z;
894
+ out[base + 3] = closest_box_x;
895
+ out[base + 4] = closest_box_y;
896
+ out[base + 5] = closest_box_z;
897
+ out[base + 6] = overlap;
898
+ return true;
899
+ }