@woosh/meep-engine 2.138.20 → 2.140.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 (584) hide show
  1. package/package.json +1 -1
  2. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts +3 -3
  3. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts.map +1 -1
  4. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js +4 -4
  5. package/src/core/collection/PairUint32Map.d.ts +100 -0
  6. package/src/core/collection/PairUint32Map.d.ts.map +1 -0
  7. package/src/core/collection/PairUint32Map.js +321 -0
  8. package/src/core/collection/Uint32Map.d.ts +119 -0
  9. package/src/core/collection/Uint32Map.d.ts.map +1 -0
  10. package/src/core/collection/Uint32Map.js +345 -0
  11. package/src/core/collection/array/array_shuffle.d.ts +10 -3
  12. package/src/core/collection/array/array_shuffle.d.ts.map +1 -1
  13. package/src/core/collection/array/array_shuffle.js +27 -22
  14. package/src/core/collection/heap/FibonacciHeap.d.ts +195 -0
  15. package/src/core/collection/heap/FibonacciHeap.d.ts.map +1 -0
  16. package/src/core/collection/heap/FibonacciHeap.js +586 -0
  17. package/src/core/collection/heap/Uint32Heap.js +1 -1
  18. package/src/core/collection/heap/Uint32Heap4.d.ts +169 -0
  19. package/src/core/collection/heap/Uint32Heap4.d.ts.map +1 -0
  20. package/src/core/collection/heap/Uint32Heap4.js +490 -0
  21. package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts +30 -0
  22. package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts.map +1 -0
  23. package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +93 -0
  24. package/src/core/geom/3d/line/line3_closest_points_segment_segment.d.ts +27 -0
  25. package/src/core/geom/3d/line/line3_closest_points_segment_segment.d.ts.map +1 -0
  26. package/src/core/geom/3d/line/line3_closest_points_segment_segment.js +88 -0
  27. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts +54 -0
  28. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts.map +1 -0
  29. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -0
  30. package/src/core/geom/3d/shape/AbstractShape3D.d.ts +24 -2
  31. package/src/core/geom/3d/shape/AbstractShape3D.d.ts.map +1 -1
  32. package/src/core/geom/3d/shape/AbstractShape3D.js +24 -1
  33. package/src/core/geom/3d/shape/BoxShape3D.d.ts +61 -0
  34. package/src/core/geom/3d/shape/BoxShape3D.d.ts.map +1 -0
  35. package/src/core/geom/3d/shape/BoxShape3D.js +158 -0
  36. package/src/core/geom/3d/shape/CapsuleShape3D.d.ts +11 -0
  37. package/src/core/geom/3d/shape/CapsuleShape3D.d.ts.map +1 -1
  38. package/src/core/geom/3d/shape/CapsuleShape3D.js +12 -0
  39. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +148 -0
  40. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -0
  41. package/src/core/geom/3d/shape/HeightMapShape3D.js +451 -0
  42. package/src/core/geom/3d/shape/MeshShape3D.d.ts +210 -0
  43. package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -0
  44. package/src/core/geom/3d/shape/MeshShape3D.js +593 -0
  45. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  46. package/src/core/geom/3d/shape/TransformedShape3D.js +46 -2
  47. package/src/core/geom/3d/shape/Triangle3D.d.ts +95 -0
  48. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -0
  49. package/src/core/geom/3d/shape/Triangle3D.js +318 -0
  50. package/src/core/geom/3d/shape/UnionShape3D.js +13 -0
  51. package/src/core/geom/3d/shape/UnitCubeShape3D.d.ts +37 -9
  52. package/src/core/geom/3d/shape/UnitCubeShape3D.d.ts.map +1 -1
  53. package/src/core/geom/3d/shape/UnitCubeShape3D.js +45 -98
  54. package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts +10 -0
  55. package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts.map +1 -1
  56. package/src/core/geom/3d/shape/UnitSphereShape3D.js +11 -0
  57. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts +30 -0
  58. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts.map +1 -0
  59. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +64 -0
  60. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.d.ts +61 -0
  61. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.d.ts.map +1 -0
  62. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +148 -0
  63. package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.d.ts +39 -0
  64. package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.d.ts.map +1 -0
  65. package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.js +147 -0
  66. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.d.ts +15 -0
  67. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.d.ts.map +1 -0
  68. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.js +22 -0
  69. package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.d.ts +2 -0
  70. package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.d.ts.map +1 -0
  71. package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.js +671 -0
  72. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts +28 -0
  73. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts.map +1 -0
  74. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -0
  75. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.d.ts +26 -0
  76. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.d.ts.map +1 -0
  77. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +222 -0
  78. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.d.ts +34 -0
  79. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.d.ts.map +1 -0
  80. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.js +146 -0
  81. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.d.ts +36 -0
  82. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.d.ts.map +1 -0
  83. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.js +232 -0
  84. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.d.ts +33 -0
  85. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.d.ts.map +1 -0
  86. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.js +255 -0
  87. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts +68 -0
  88. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts.map +1 -0
  89. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +387 -0
  90. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts +35 -0
  91. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts.map +1 -0
  92. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +140 -0
  93. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts +31 -0
  94. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts.map +1 -0
  95. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +97 -0
  96. package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.d.ts +32 -0
  97. package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.d.ts.map +1 -0
  98. package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.js +66 -0
  99. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +41 -0
  100. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
  101. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +124 -13
  102. package/src/core/geom/3d/topology/struct/binary/BinaryTopology.d.ts +134 -0
  103. package/src/core/geom/3d/topology/struct/binary/BinaryTopology.d.ts.map +1 -1
  104. package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +276 -3
  105. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.d.ts +17 -0
  106. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.d.ts.map +1 -0
  107. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +135 -0
  108. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.d.ts +14 -0
  109. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.d.ts.map +1 -0
  110. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js +177 -0
  111. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.d.ts.map +1 -1
  112. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.js +20 -4
  113. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
  114. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +5 -3
  115. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.d.ts.map +1 -1
  116. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +9 -0
  117. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.d.ts.map +1 -1
  118. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.js +21 -45
  119. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill.d.ts.map +1 -1
  120. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill.js +7 -1
  121. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.d.ts +8 -6
  122. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.d.ts.map +1 -1
  123. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +8 -6
  124. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.d.ts +22 -0
  125. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.d.ts.map +1 -0
  126. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js +73 -0
  127. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_replace.d.ts.map +1 -1
  128. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_replace.js +51 -1
  129. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.d.ts +10 -0
  130. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.d.ts.map +1 -0
  131. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.js +42 -0
  132. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.d.ts +28 -0
  133. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.d.ts.map +1 -0
  134. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +227 -0
  135. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.d.ts +13 -0
  136. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.d.ts.map +1 -0
  137. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.js +108 -0
  138. package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.d.ts +11 -0
  139. package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.d.ts.map +1 -0
  140. package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.js +20 -0
  141. package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.d.ts +20 -0
  142. package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.d.ts.map +1 -0
  143. package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.js +38 -0
  144. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts +2 -2
  145. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts.map +1 -1
  146. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +1 -1
  147. package/src/core/geom/vec3/v3_dot_array_array.d.ts +3 -3
  148. package/src/core/geom/vec3/v3_dot_array_array.d.ts.map +1 -1
  149. package/src/core/geom/vec3/v3_dot_array_array.js +2 -2
  150. package/src/core/geom/vec3/v3_negate_array.d.ts +3 -3
  151. package/src/core/geom/vec3/v3_negate_array.d.ts.map +1 -1
  152. package/src/core/geom/vec3/v3_negate_array.js +2 -2
  153. package/src/core/geom/vec3/v3_quat3_apply.d.ts +29 -0
  154. package/src/core/geom/vec3/v3_quat3_apply.d.ts.map +1 -0
  155. package/src/core/geom/vec3/v3_quat3_apply.js +39 -0
  156. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +30 -0
  157. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts.map +1 -0
  158. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +41 -0
  159. package/src/core/geom/vec3/v3_triple_cross_product.d.ts +32 -0
  160. package/src/core/geom/vec3/v3_triple_cross_product.d.ts.map +1 -0
  161. package/src/core/geom/vec3/v3_triple_cross_product.js +45 -0
  162. package/src/core/graph/csr/CSRGraph.d.ts +168 -0
  163. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -0
  164. package/src/core/graph/csr/CSRGraph.js +319 -0
  165. package/src/core/graph/metis/cluster_mesh_metis.d.ts +12 -0
  166. package/src/core/graph/metis/cluster_mesh_metis.d.ts.map +1 -1
  167. package/src/core/graph/metis/cluster_mesh_metis.js +12 -0
  168. package/src/core/graph/metis/metis.d.ts +19 -0
  169. package/src/core/graph/metis/metis.d.ts.map +1 -1
  170. package/src/core/graph/metis/metis.js +20 -0
  171. package/src/core/graph/metis/metis_cluster_bs.d.ts +11 -0
  172. package/src/core/graph/metis/metis_cluster_bs.d.ts.map +1 -1
  173. package/src/core/graph/metis/metis_cluster_bs.js +11 -0
  174. package/src/core/graph/metis/metis_options.d.ts +17 -2
  175. package/src/core/graph/metis/metis_options.d.ts.map +1 -1
  176. package/src/core/graph/metis/metis_options.js +17 -2
  177. package/src/core/graph/metis/native/MetisGraph.d.ts +144 -0
  178. package/src/core/graph/metis/native/MetisGraph.d.ts.map +1 -0
  179. package/src/core/graph/metis/native/MetisGraph.js +212 -0
  180. package/src/core/graph/metis/native/bisection/BisectionScratch.d.ts +72 -0
  181. package/src/core/graph/metis/native/bisection/BisectionScratch.d.ts.map +1 -0
  182. package/src/core/graph/metis/native/bisection/BisectionScratch.js +101 -0
  183. package/src/core/graph/metis/native/bisection/bisect_graph.d.ts +37 -0
  184. package/src/core/graph/metis/native/bisection/bisect_graph.d.ts.map +1 -0
  185. package/src/core/graph/metis/native/bisection/bisect_graph.js +100 -0
  186. package/src/core/graph/metis/native/bisection/compute_2way_params.d.ts +15 -0
  187. package/src/core/graph/metis/native/bisection/compute_2way_params.d.ts.map +1 -0
  188. package/src/core/graph/metis/native/bisection/compute_2way_params.js +84 -0
  189. package/src/core/graph/metis/native/bisection/fm_2way.d.ts +30 -0
  190. package/src/core/graph/metis/native/bisection/fm_2way.d.ts.map +1 -0
  191. package/src/core/graph/metis/native/bisection/fm_2way.js +290 -0
  192. package/src/core/graph/metis/native/bisection/grow_bisection.d.ts +23 -0
  193. package/src/core/graph/metis/native/bisection/grow_bisection.d.ts.map +1 -0
  194. package/src/core/graph/metis/native/bisection/grow_bisection.js +137 -0
  195. package/src/core/graph/metis/native/bisection/split_graph_two_way.d.ts +28 -0
  196. package/src/core/graph/metis/native/bisection/split_graph_two_way.d.ts.map +1 -0
  197. package/src/core/graph/metis/native/bisection/split_graph_two_way.js +119 -0
  198. package/src/core/graph/metis/native/coarsen/coarsen_graph.d.ts +20 -0
  199. package/src/core/graph/metis/native/coarsen/coarsen_graph.d.ts.map +1 -0
  200. package/src/core/graph/metis/native/coarsen/coarsen_graph.js +94 -0
  201. package/src/core/graph/metis/native/coarsen/create_coarse_graph.d.ts +24 -0
  202. package/src/core/graph/metis/native/coarsen/create_coarse_graph.d.ts.map +1 -0
  203. package/src/core/graph/metis/native/coarsen/create_coarse_graph.js +158 -0
  204. package/src/core/graph/metis/native/coarsen/match_shem.d.ts +41 -0
  205. package/src/core/graph/metis/native/coarsen/match_shem.d.ts.map +1 -0
  206. package/src/core/graph/metis/native/coarsen/match_shem.js +175 -0
  207. package/src/core/graph/metis/native/initial/initial_kway_bfs.d.ts +24 -0
  208. package/src/core/graph/metis/native/initial/initial_kway_bfs.d.ts.map +1 -0
  209. package/src/core/graph/metis/native/initial/initial_kway_bfs.js +122 -0
  210. package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.d.ts +29 -0
  211. package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.d.ts.map +1 -0
  212. package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.js +170 -0
  213. package/src/core/graph/metis/native/metis_partition_kway.d.ts +41 -0
  214. package/src/core/graph/metis/native/metis_partition_kway.d.ts.map +1 -0
  215. package/src/core/graph/metis/native/metis_partition_kway.js +126 -0
  216. package/src/core/graph/metis/native/refine/IndexedFloatMaxHeap.d.ts +62 -0
  217. package/src/core/graph/metis/native/refine/IndexedFloatMaxHeap.d.ts.map +1 -0
  218. package/src/core/graph/metis/native/refine/IndexedFloatMaxHeap.js +261 -0
  219. package/src/core/graph/metis/native/refine/RefinementScratch.d.ts +45 -0
  220. package/src/core/graph/metis/native/refine/RefinementScratch.d.ts.map +1 -0
  221. package/src/core/graph/metis/native/refine/RefinementScratch.js +53 -0
  222. package/src/core/graph/metis/native/refine/compute_kway_params.d.ts +18 -0
  223. package/src/core/graph/metis/native/refine/compute_kway_params.d.ts.map +1 -0
  224. package/src/core/graph/metis/native/refine/compute_kway_params.js +138 -0
  225. package/src/core/graph/metis/native/refine/fm_kway.d.ts +63 -0
  226. package/src/core/graph/metis/native/refine/fm_kway.d.ts.map +1 -0
  227. package/src/core/graph/metis/native/refine/fm_kway.js +462 -0
  228. package/src/core/graph/metis/native/refine/project_kway.d.ts +22 -0
  229. package/src/core/graph/metis/native/refine/project_kway.d.ts.map +1 -0
  230. package/src/core/graph/metis/native/refine/project_kway.js +43 -0
  231. package/src/core/graph/metis/native/refine/refine_kway.d.ts +34 -0
  232. package/src/core/graph/metis/native/refine/refine_kway.d.ts.map +1 -0
  233. package/src/core/graph/metis/native/refine/refine_kway.js +43 -0
  234. package/src/core/math/linalg/eigen/matrix_householder_in_place.d.ts +2 -2
  235. package/src/core/math/linalg/eigen/matrix_householder_in_place.js +2 -2
  236. package/src/core/math/linalg/eigen/matrix_qr_in_place.d.ts +6 -4
  237. package/src/core/math/linalg/eigen/matrix_qr_in_place.d.ts.map +1 -1
  238. package/src/core/math/linalg/eigen/matrix_qr_in_place.js +69 -23
  239. package/src/engine/EngineHarness.d.ts +3 -1
  240. package/src/engine/EngineHarness.d.ts.map +1 -1
  241. package/src/engine/EngineHarness.js +3 -0
  242. package/src/engine/control/first-person/DESIGN.md +30 -6
  243. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +563 -0
  244. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +115 -9
  245. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  246. package/src/engine/control/first-person/FirstPersonPlayerController.js +211 -176
  247. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +601 -8
  248. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  249. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +349 -8
  250. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +319 -23
  251. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  252. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1789 -799
  253. package/src/engine/control/first-person/TODO.md +173 -0
  254. package/src/engine/control/first-person/abilities/Ability.d.ts +101 -0
  255. package/src/engine/control/first-person/abilities/Ability.d.ts.map +1 -0
  256. package/src/engine/control/first-person/abilities/Ability.js +119 -0
  257. package/src/engine/control/first-person/abilities/AbilitySet.d.ts +86 -0
  258. package/src/engine/control/first-person/abilities/AbilitySet.d.ts.map +1 -0
  259. package/src/engine/control/first-person/abilities/AbilitySet.js +185 -0
  260. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +62 -0
  261. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -0
  262. package/src/engine/control/first-person/abilities/LedgeGrab.js +199 -0
  263. package/src/engine/control/first-person/abilities/Mantle.d.ts +45 -0
  264. package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -0
  265. package/src/engine/control/first-person/abilities/Mantle.js +188 -0
  266. package/src/engine/control/first-person/abilities/Slide.d.ts +33 -0
  267. package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -0
  268. package/src/engine/control/first-person/abilities/Slide.js +166 -0
  269. package/src/engine/control/first-person/abilities/WallJump.d.ts +45 -0
  270. package/src/engine/control/first-person/abilities/WallJump.d.ts.map +1 -0
  271. package/src/engine/control/first-person/abilities/WallJump.js +131 -0
  272. package/src/engine/control/first-person/abilities/WallRun.d.ts +44 -0
  273. package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -0
  274. package/src/engine/control/first-person/abilities/WallRun.js +180 -0
  275. package/src/engine/control/first-person/composer/EyeOffsetStack.d.ts +49 -0
  276. package/src/engine/control/first-person/composer/EyeOffsetStack.d.ts.map +1 -0
  277. package/src/engine/control/first-person/composer/EyeOffsetStack.js +60 -0
  278. package/src/engine/control/first-person/mastery/BreathRhythmEvaluator.d.ts +100 -0
  279. package/src/engine/control/first-person/mastery/BreathRhythmEvaluator.d.ts.map +1 -0
  280. package/src/engine/control/first-person/mastery/BreathRhythmEvaluator.js +133 -0
  281. package/src/engine/control/first-person/mastery/DecisionPoint.d.ts +10 -0
  282. package/src/engine/control/first-person/mastery/DecisionPoint.d.ts.map +1 -0
  283. package/src/engine/control/first-person/mastery/DecisionPoint.js +30 -0
  284. package/src/engine/control/first-person/mastery/FootAsymmetryTurnEvaluator.d.ts +61 -0
  285. package/src/engine/control/first-person/mastery/FootAsymmetryTurnEvaluator.d.ts.map +1 -0
  286. package/src/engine/control/first-person/mastery/FootAsymmetryTurnEvaluator.js +109 -0
  287. package/src/engine/control/first-person/mastery/MasteryEvaluator.d.ts +40 -0
  288. package/src/engine/control/first-person/mastery/MasteryEvaluator.d.ts.map +1 -0
  289. package/src/engine/control/first-person/mastery/MasteryEvaluator.js +45 -0
  290. package/src/engine/control/first-person/mastery/MasteryScore.d.ts +68 -0
  291. package/src/engine/control/first-person/mastery/MasteryScore.d.ts.map +1 -0
  292. package/src/engine/control/first-person/mastery/MasteryScore.js +100 -0
  293. package/src/engine/control/first-person/mastery/MasterySet.d.ts +60 -0
  294. package/src/engine/control/first-person/mastery/MasterySet.d.ts.map +1 -0
  295. package/src/engine/control/first-person/mastery/MasterySet.js +86 -0
  296. package/src/engine/control/first-person/mastery/SlideInitiationTimingEvaluator.d.ts +58 -0
  297. package/src/engine/control/first-person/mastery/SlideInitiationTimingEvaluator.d.ts.map +1 -0
  298. package/src/engine/control/first-person/mastery/SlideInitiationTimingEvaluator.js +83 -0
  299. package/src/engine/control/first-person/mastery/StrideTimingJumpEvaluator.d.ts +69 -0
  300. package/src/engine/control/first-person/mastery/StrideTimingJumpEvaluator.d.ts.map +1 -0
  301. package/src/engine/control/first-person/mastery/StrideTimingJumpEvaluator.js +109 -0
  302. package/src/engine/control/first-person/math/Spring.d.ts +56 -0
  303. package/src/engine/control/first-person/math/Spring.d.ts.map +1 -0
  304. package/src/engine/control/first-person/math/Spring.js +71 -0
  305. package/src/engine/control/first-person/math/computeLRCBreathRate.d.ts +26 -0
  306. package/src/engine/control/first-person/math/computeLRCBreathRate.d.ts.map +1 -0
  307. package/src/engine/control/first-person/math/computeLRCBreathRate.js +41 -0
  308. package/src/engine/control/first-person/math/computeMassRatios.d.ts +35 -0
  309. package/src/engine/control/first-person/math/computeMassRatios.d.ts.map +1 -0
  310. package/src/engine/control/first-person/math/computeMassRatios.js +44 -0
  311. package/src/engine/control/first-person/pose/FirstPersonPose.d.ts +31 -1
  312. package/src/engine/control/first-person/pose/FirstPersonPose.d.ts.map +1 -1
  313. package/src/engine/control/first-person/pose/FirstPersonPose.js +49 -3
  314. package/src/engine/control/first-person/pose/FirstPersonPosture.d.ts +7 -0
  315. package/src/engine/control/first-person/pose/FirstPersonPosture.d.ts.map +1 -0
  316. package/src/engine/control/first-person/pose/FirstPersonPosture.js +27 -0
  317. package/src/engine/control/first-person/prototype_first_person_controller.js +637 -120
  318. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts +58 -0
  319. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts.map +1 -0
  320. package/src/engine/control/first-person/sensors/FirstPersonSensors.js +77 -0
  321. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts +80 -0
  322. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -0
  323. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +196 -0
  324. package/src/engine/control/first-person/test/buildTestPlayer.d.ts +20 -0
  325. package/src/engine/control/first-person/test/buildTestPlayer.d.ts.map +1 -0
  326. package/src/engine/control/first-person/test/buildTestPlayer.js +36 -0
  327. package/src/engine/graphics/camera/testClippingPlaneComputation.js +0 -2
  328. package/src/engine/graphics/ecs/light/Light.d.ts.map +1 -1
  329. package/src/engine/graphics/ecs/light/Light.js +27 -0
  330. package/src/engine/graphics/ecs/light/LightSystem.js +1 -1
  331. package/src/engine/graphics/ecs/path/PathDisplaySystem.d.ts.map +1 -1
  332. package/src/engine/graphics/ecs/path/testPathDisplaySystem.js +0 -2
  333. package/src/engine/graphics/ecs/path/tube/prototypeAnimatedPathMask.js +0 -2
  334. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts +42 -0
  335. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts.map +1 -0
  336. package/src/engine/graphics/geometry/CapsuleGeometry.js +171 -0
  337. package/src/engine/graphics/render/buffer/buffers/prototypeNormalFrameBuffer.js +0 -2
  338. package/src/engine/graphics/render/forward_plus/plugin/ptototypeFPPlugin.js +0 -2
  339. package/src/engine/graphics/render/visibility/hiz/prototypeHiZ.js +0 -2
  340. package/src/engine/navigation/grid/find_path_on_grid_astar.d.ts.map +1 -1
  341. package/src/engine/navigation/grid/find_path_on_grid_astar.js +11 -2
  342. package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts.map +1 -1
  343. package/src/engine/navigation/mesh/bt_mesh_face_find_path.js +11 -1
  344. package/src/engine/physics/BULLET_REVIEW.md +945 -0
  345. package/src/engine/physics/CANNON_REVIEW.md +1300 -0
  346. package/src/engine/physics/JOLT_REVIEW.md +913 -0
  347. package/src/engine/physics/PLAN.md +461 -0
  348. package/src/engine/physics/RAPIER_REVIEW.md +934 -0
  349. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -0
  350. package/src/engine/physics/body/BodyStorage.d.ts +187 -0
  351. package/src/engine/physics/body/BodyStorage.d.ts.map +1 -0
  352. package/src/engine/physics/body/BodyStorage.js +427 -0
  353. package/src/engine/physics/broadphase/PairList.d.ts +62 -0
  354. package/src/engine/physics/broadphase/PairList.d.ts.map +1 -0
  355. package/src/engine/physics/broadphase/PairList.js +97 -0
  356. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts +16 -0
  357. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts.map +1 -0
  358. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +61 -0
  359. package/src/engine/physics/broadphase/generate_pairs.d.ts +38 -0
  360. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -0
  361. package/src/engine/physics/broadphase/generate_pairs.js +101 -0
  362. package/src/engine/physics/contact/ManifoldStore.d.ts +299 -0
  363. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -0
  364. package/src/engine/physics/contact/ManifoldStore.js +608 -0
  365. package/src/engine/physics/ecs/BodyKind.d.ts +23 -0
  366. package/src/engine/physics/ecs/BodyKind.d.ts.map +1 -0
  367. package/src/engine/physics/ecs/BodyKind.js +24 -0
  368. package/src/engine/physics/ecs/Collider.d.ts +98 -0
  369. package/src/engine/physics/ecs/Collider.d.ts.map +1 -0
  370. package/src/engine/physics/ecs/Collider.js +136 -0
  371. package/src/engine/physics/ecs/ColliderFlags.d.ts +14 -0
  372. package/src/engine/physics/ecs/ColliderFlags.d.ts.map +1 -0
  373. package/src/engine/physics/ecs/ColliderFlags.js +15 -0
  374. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +58 -0
  375. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -0
  376. package/src/engine/physics/ecs/ColliderObserverSystem.js +103 -0
  377. package/src/engine/physics/ecs/ColliderSerializationAdapter.d.ts +25 -0
  378. package/src/engine/physics/ecs/ColliderSerializationAdapter.d.ts.map +1 -0
  379. package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -0
  380. package/src/engine/physics/ecs/PhysicsEvents.d.ts +15 -0
  381. package/src/engine/physics/ecs/PhysicsEvents.d.ts.map +1 -0
  382. package/src/engine/physics/ecs/PhysicsEvents.js +16 -0
  383. package/src/engine/physics/ecs/PhysicsSystem.d.ts +628 -0
  384. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -0
  385. package/src/engine/physics/ecs/PhysicsSystem.js +1301 -0
  386. package/src/engine/physics/ecs/RigidBody.d.ts +197 -0
  387. package/src/engine/physics/ecs/RigidBody.d.ts.map +1 -0
  388. package/src/engine/physics/ecs/RigidBody.js +240 -0
  389. package/src/engine/physics/ecs/RigidBodyFlags.d.ts +21 -0
  390. package/src/engine/physics/ecs/RigidBodyFlags.d.ts.map +1 -0
  391. package/src/engine/physics/ecs/RigidBodyFlags.js +22 -0
  392. package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts +28 -0
  393. package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts.map +1 -0
  394. package/src/engine/physics/ecs/RigidBodySerializationAdapter.js +81 -0
  395. package/src/engine/physics/ecs/SleepState.d.ts +11 -0
  396. package/src/engine/physics/ecs/SleepState.d.ts.map +1 -0
  397. package/src/engine/physics/ecs/SleepState.js +12 -0
  398. package/src/engine/physics/events/ContactEventBuffer.d.ts +46 -0
  399. package/src/engine/physics/events/ContactEventBuffer.d.ts.map +1 -0
  400. package/src/engine/physics/events/ContactEventBuffer.js +83 -0
  401. package/src/engine/physics/events/diff_manifolds.d.ts +25 -0
  402. package/src/engine/physics/events/diff_manifolds.d.ts.map +1 -0
  403. package/src/engine/physics/events/diff_manifolds.js +50 -0
  404. package/src/engine/physics/fluid/FluidField.d.ts +294 -16
  405. package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
  406. package/src/engine/physics/fluid/FluidField.js +510 -66
  407. package/src/engine/physics/fluid/FluidSimulator.d.ts +188 -5
  408. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  409. package/src/engine/physics/fluid/FluidSimulator.js +456 -95
  410. package/src/engine/physics/fluid/SliceVisualiser.d.ts +29 -6
  411. package/src/engine/physics/fluid/SliceVisualiser.d.ts.map +1 -1
  412. package/src/engine/physics/fluid/SliceVisualiser.js +190 -165
  413. package/src/engine/physics/fluid/ecs/FluidComponent.d.ts +154 -0
  414. package/src/engine/physics/fluid/ecs/FluidComponent.d.ts.map +1 -0
  415. package/src/engine/physics/fluid/ecs/FluidComponent.js +238 -0
  416. package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.d.ts +45 -0
  417. package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.d.ts.map +1 -0
  418. package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -0
  419. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +107 -0
  420. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts.map +1 -0
  421. package/src/engine/physics/fluid/ecs/FluidSystem.js +278 -0
  422. package/src/engine/physics/fluid/effector/AbstractFluidEffector.d.ts +62 -1
  423. package/src/engine/physics/fluid/effector/AbstractFluidEffector.d.ts.map +1 -1
  424. package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -6
  425. package/src/engine/physics/fluid/effector/GlobalFluidEffector.d.ts +17 -4
  426. package/src/engine/physics/fluid/effector/GlobalFluidEffector.d.ts.map +1 -1
  427. package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +105 -12
  428. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.d.ts +43 -0
  429. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.d.ts.map +1 -0
  430. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +210 -0
  431. package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts +62 -1
  432. package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts.map +1 -1
  433. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +302 -8
  434. package/src/engine/physics/fluid/prototype.js +102 -91
  435. package/src/engine/physics/fluid/solver/optimal_sor_omega.d.ts +33 -0
  436. package/src/engine/physics/fluid/solver/optimal_sor_omega.d.ts.map +1 -0
  437. package/src/engine/physics/fluid/solver/optimal_sor_omega.js +41 -0
  438. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.d.ts +20 -5
  439. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.d.ts.map +1 -1
  440. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.js +60 -38
  441. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.d.ts +25 -4
  442. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.d.ts.map +1 -1
  443. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.js +93 -73
  444. package/src/engine/physics/fluid/solver/v3_grid_apply_scalar_advection.d.ts +23 -0
  445. package/src/engine/physics/fluid/solver/v3_grid_apply_scalar_advection.d.ts.map +1 -0
  446. package/src/engine/physics/fluid/solver/v3_grid_apply_scalar_advection.js +60 -0
  447. package/src/engine/physics/fluid/solver/v3_grid_compute_divergence.d.ts +23 -0
  448. package/src/engine/physics/fluid/solver/v3_grid_compute_divergence.d.ts.map +1 -0
  449. package/src/engine/physics/fluid/solver/v3_grid_compute_divergence.js +68 -0
  450. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts +30 -0
  451. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts.map +1 -0
  452. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +66 -0
  453. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.d.ts +26 -0
  454. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.d.ts.map +1 -0
  455. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -0
  456. package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.d.ts +30 -0
  457. package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.d.ts.map +1 -0
  458. package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -0
  459. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts +49 -0
  460. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts.map +1 -0
  461. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +126 -0
  462. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts +93 -0
  463. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts.map +1 -0
  464. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +424 -0
  465. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +48 -0
  466. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -0
  467. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +92 -0
  468. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +6 -6
  469. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +1 -1
  470. package/src/engine/physics/gjk/expanding_polytope_algorithm.js +76 -32
  471. package/src/engine/physics/gjk/gjk.d.ts +28 -2
  472. package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
  473. package/src/engine/physics/gjk/gjk.js +421 -378
  474. package/src/engine/physics/gjk/minkowski_support.d.ts +37 -0
  475. package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -0
  476. package/src/engine/physics/gjk/minkowski_support.js +75 -0
  477. package/src/engine/physics/gjk/mpr.d.ts +56 -0
  478. package/src/engine/physics/gjk/mpr.d.ts.map +1 -0
  479. package/src/engine/physics/gjk/mpr.js +344 -0
  480. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +44 -0
  481. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -0
  482. package/src/engine/physics/inertia/world_inverse_inertia.js +77 -0
  483. package/src/engine/physics/integration/integrate_position.d.ts +34 -0
  484. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -0
  485. package/src/engine/physics/integration/integrate_position.js +79 -0
  486. package/src/engine/physics/integration/integrate_velocity.d.ts +55 -0
  487. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -0
  488. package/src/engine/physics/integration/integrate_velocity.js +160 -0
  489. package/src/engine/physics/integration/quat_integrate.d.ts +27 -0
  490. package/src/engine/physics/integration/quat_integrate.d.ts.map +1 -0
  491. package/src/engine/physics/integration/quat_integrate.js +62 -0
  492. package/src/engine/physics/island/IslandBuilder.d.ts +167 -0
  493. package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -0
  494. package/src/engine/physics/island/IslandBuilder.js +411 -0
  495. package/src/engine/physics/island/union_find.d.ts +51 -0
  496. package/src/engine/physics/island/union_find.d.ts.map +1 -0
  497. package/src/engine/physics/island/union_find.js +76 -0
  498. package/src/engine/physics/narrowphase/PosedShape.d.ts +51 -0
  499. package/src/engine/physics/narrowphase/PosedShape.d.ts.map +1 -0
  500. package/src/engine/physics/narrowphase/PosedShape.js +108 -0
  501. package/src/engine/physics/narrowphase/box_box_manifold.d.ts +32 -0
  502. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -0
  503. package/src/engine/physics/narrowphase/box_box_manifold.js +639 -0
  504. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts +30 -0
  505. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -0
  506. package/src/engine/physics/narrowphase/box_triangle_contact.js +811 -0
  507. package/src/engine/physics/narrowphase/capsule_contacts.d.ts +122 -0
  508. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -0
  509. package/src/engine/physics/narrowphase/capsule_contacts.js +462 -0
  510. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts +71 -0
  511. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -0
  512. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +375 -0
  513. package/src/engine/physics/narrowphase/compute_penetration.d.ts +91 -0
  514. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -0
  515. package/src/engine/physics/narrowphase/compute_penetration.js +396 -0
  516. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts +35 -0
  517. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +1 -0
  518. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.js +80 -0
  519. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts +31 -0
  520. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts.map +1 -0
  521. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -0
  522. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +42 -0
  523. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -0
  524. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +204 -0
  525. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +42 -0
  526. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -0
  527. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +94 -0
  528. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts +37 -0
  529. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts.map +1 -0
  530. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -0
  531. package/src/engine/physics/narrowphase/narrowphase_step.d.ts +17 -0
  532. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -0
  533. package/src/engine/physics/narrowphase/narrowphase_step.js +1422 -0
  534. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts +38 -0
  535. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -0
  536. package/src/engine/physics/narrowphase/sphere_box_contact.js +123 -0
  537. package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts +26 -0
  538. package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts.map +1 -0
  539. package/src/engine/physics/narrowphase/sphere_sphere_contact.js +51 -0
  540. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts +48 -0
  541. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts.map +1 -0
  542. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +143 -0
  543. package/src/engine/physics/queries/PhysicsSurfacePoint.d.ts +83 -0
  544. package/src/engine/physics/queries/PhysicsSurfacePoint.d.ts.map +1 -0
  545. package/src/engine/physics/queries/PhysicsSurfacePoint.js +100 -0
  546. package/src/engine/physics/queries/overlap_shape.d.ts +51 -0
  547. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -0
  548. package/src/engine/physics/queries/overlap_shape.js +183 -0
  549. package/src/engine/physics/queries/raycast.d.ts +20 -0
  550. package/src/engine/physics/queries/raycast.d.ts.map +1 -0
  551. package/src/engine/physics/queries/raycast.js +249 -0
  552. package/src/engine/physics/queries/shape_cast.d.ts +56 -0
  553. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -0
  554. package/src/engine/physics/queries/shape_cast.js +387 -0
  555. package/src/engine/physics/solver/friction_cone.d.ts +16 -0
  556. package/src/engine/physics/solver/friction_cone.d.ts.map +1 -0
  557. package/src/engine/physics/solver/friction_cone.js +37 -0
  558. package/src/engine/physics/solver/solve_contacts.d.ts +122 -0
  559. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -0
  560. package/src/engine/physics/solver/solve_contacts.js +1016 -0
  561. package/src/core/geom/3d/topology/struct/binary/io/edge/OrderedEdge.d.ts +0 -34
  562. package/src/core/geom/3d/topology/struct/binary/io/edge/OrderedEdge.d.ts.map +0 -1
  563. package/src/core/geom/3d/topology/struct/binary/io/edge/OrderedEdge.js +0 -66
  564. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_calc_edges.d.ts +0 -2
  565. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_calc_edges.d.ts.map +0 -1
  566. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_calc_edges.js +0 -54
  567. package/src/core/geom/3d/topology/struct/binary/io/edge/get_or_create_edge_map.d.ts +0 -2
  568. package/src/core/geom/3d/topology/struct/binary/io/edge/get_or_create_edge_map.d.ts.map +0 -1
  569. package/src/core/geom/3d/topology/struct/binary/io/edge/get_or_create_edge_map.js +0 -26
  570. package/src/engine/ecs/components/Motion.d.ts +0 -21
  571. package/src/engine/ecs/components/Motion.d.ts.map +0 -1
  572. package/src/engine/ecs/components/Motion.js +0 -27
  573. package/src/engine/ecs/components/MotionSerializationAdapter.d.ts +0 -20
  574. package/src/engine/ecs/components/MotionSerializationAdapter.d.ts.map +0 -1
  575. package/src/engine/ecs/components/MotionSerializationAdapter.js +0 -26
  576. package/src/engine/ecs/systems/MotionSystem.d.ts +0 -9
  577. package/src/engine/ecs/systems/MotionSystem.d.ts.map +0 -1
  578. package/src/engine/ecs/systems/MotionSystem.js +0 -29
  579. package/src/engine/physics/fluid/Fluid.d.ts +0 -26
  580. package/src/engine/physics/fluid/Fluid.d.ts.map +0 -1
  581. package/src/engine/physics/fluid/Fluid.js +0 -221
  582. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_reverse.d.ts +0 -7
  583. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_reverse.d.ts.map +0 -1
  584. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_reverse.js +0 -8
@@ -0,0 +1,1422 @@
1
+ import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
2
+ import { Triangle3D } from "../../../core/geom/3d/shape/Triangle3D.js";
3
+ import { body_id_index } from "../body/BodyStorage.js";
4
+ import { CONTACT_STRIDE, MAX_CONTACTS_PER_MANIFOLD } from "../contact/ManifoldStore.js";
5
+ import { expanding_polytope_algorithm } from "../gjk/expanding_polytope_algorithm.js";
6
+ import { gjk_with_axis } from "../gjk/gjk.js";
7
+ import { mpr } from "../gjk/mpr.js";
8
+ import { box_box_manifold, BOX_BOX_OUT_LENGTH } from "./box_box_manifold.js";
9
+ import { box_triangle_contact, BOX_TRIANGLE_OUT_LENGTH } from "./box_triangle_contact.js";
10
+ import {
11
+ CAPSULE_BOX_CONTACT_STRIDE,
12
+ CAPSULE_BOX_MAX_CONTACTS,
13
+ capsule_box_multi_contacts,
14
+ capsule_capsule_contact,
15
+ capsule_sphere_contact,
16
+ } from "./capsule_contacts.js";
17
+ import {
18
+ capsule_triangle_contact,
19
+ CAPSULE_TRIANGLE_CONTACT_STRIDE,
20
+ CAPSULE_TRIANGLE_MAX_CONTACTS,
21
+ } from "./capsule_triangle_contact.js";
22
+ import { aabb_world_to_local } from "./decomposition/aabb_world_to_local.js";
23
+ import { decompose_to_triangles } from "./decomposition/decompose_to_triangles.js";
24
+ import { TRIANGLE_FLOAT_STRIDE } from "./decomposition/triangle_buffer_layout.js";
25
+ import { PosedShape } from "./PosedShape.js";
26
+ import { sphere_box_contact } from "./sphere_box_contact.js";
27
+ import { sphere_sphere_contact } from "./sphere_sphere_contact.js";
28
+ import { sphere_triangle_contact } from "./sphere_triangle_contact.js";
29
+
30
+ const posed_a = new PosedShape();
31
+ const posed_b = new PosedShape();
32
+
33
+ // Float64 simplex buffer. The simplex stores Minkowski-difference
34
+ // support points that GJK accumulates across up to 64 iterations and
35
+ // then hands to EPA for further refinement; precision loss here
36
+ // compounds through every subsequent dot / cross / normal calculation
37
+ // in the polytope expansion, so keep the full 53-bit mantissa.
38
+ const simplex_buf = new Float64Array(12);
39
+ const simplex_a = simplex_buf.subarray(0, 3);
40
+ const simplex_b = simplex_buf.subarray(3, 6);
41
+ const simplex_c = simplex_buf.subarray(6, 9);
42
+ const simplex_d = simplex_buf.subarray(9, 12);
43
+
44
+ const epa_result = new Float64Array(3);
45
+ const sphere_result = new Float64Array(4);
46
+ const closed_form_result = new Float64Array(10);
47
+ const sphere_triangle_result = new Float64Array(10);
48
+ const box_triangle_result = new Float64Array(BOX_TRIANGLE_OUT_LENGTH);
49
+ const capsule_triangle_result = new Float64Array(CAPSULE_TRIANGLE_MAX_CONTACTS * CAPSULE_TRIANGLE_CONTACT_STRIDE);
50
+ const box_manifold_result = new Float64Array(BOX_BOX_OUT_LENGTH);
51
+ const capsule_box_multi_result = new Float64Array(CAPSULE_BOX_MAX_CONTACTS * CAPSULE_BOX_CONTACT_STRIDE);
52
+
53
+ /**
54
+ * Candidate-contact stride: wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth,
55
+ * feature_id.
56
+ *
57
+ * The `feature_id` (offset 10) is a stable cross-frame identifier of the
58
+ * geometric feature pair that produced this contact — used by the
59
+ * match-and-merge pass in {@link narrowphase_step} to carry warm-start
60
+ * impulses from the previous frame's manifold to the slot index that
61
+ * corresponds to the same physical contact. A value of 0 means
62
+ * "no feature info, fall back to position matching".
63
+ *
64
+ * @type {number}
65
+ */
66
+ const CANDIDATE_STRIDE = 11;
67
+
68
+ /**
69
+ * Maximum number of contacts emitted into the per-pair manifold after the
70
+ * reduction step. Mirrors {@link MAX_CONTACTS_PER_MANIFOLD} in ManifoldStore.
71
+ * @type {number}
72
+ */
73
+ const MAX_KEPT = MAX_CONTACTS_PER_MANIFOLD;
74
+
75
+ /**
76
+ * Position-fallback tolerance for warm-start matching: when a candidate
77
+ * contact has no feature id (or none of the previous-frame contacts shares
78
+ * its id), match by closest world_a within this 3-D distance.
79
+ *
80
+ * 2 cm matches the original PLAN.md spec for "Persistent manifold cache"
81
+ * — generous enough to follow small inter-frame contact migration on
82
+ * curved surfaces, tight enough that distinct contacts on a single
83
+ * manifold (typically >5 cm apart) don't get confused with each other.
84
+ *
85
+ * @type {number}
86
+ */
87
+ const MATCH_TOL_SQR = 0.02 * 0.02;
88
+
89
+ /**
90
+ * Scratch for the previous-frame contact snapshot taken at the top of the
91
+ * match-and-merge pass. Sized for {@link MAX_CONTACTS_PER_MANIFOLD}
92
+ * contacts, 7 floats per contact:
93
+ * 0 : feature_id
94
+ * 1, 2, 3 : world_a x, y, z (for position-fallback matching)
95
+ * 4, 5, 6 : j_n, j_t1, j_t2 (carried forward to the matched candidate)
96
+ *
97
+ * Snapshotting upfront decouples the read (from the slot's previous state)
98
+ * from the write (the new contact data + impulse copy), avoiding the
99
+ * read-after-write hazard when the matching mapping shuffles indices.
100
+ *
101
+ * @type {Float64Array}
102
+ */
103
+ const prev_snapshot = new Float64Array(MAX_CONTACTS_PER_MANIFOLD * 7);
104
+
105
+ /**
106
+ * Per-prev-contact "already claimed by a candidate" flag.
107
+ * @type {Uint8Array}
108
+ */
109
+ const prev_claimed = new Uint8Array(MAX_CONTACTS_PER_MANIFOLD);
110
+
111
+ /**
112
+ * For each kept candidate, the matched prev-contact index in
113
+ * `[0, prev_count)` or `-1` if no match.
114
+ * @type {Int32Array}
115
+ */
116
+ const cand_to_prev = new Int32Array(MAX_KEPT);
117
+
118
+ /**
119
+ * Per body-pair scratch buffer for candidate contacts produced by the
120
+ * cross-product of A's colliders × B's colliders. Sized generously for
121
+ * typical compound bodies (each collider-pair contributes 1..4 contacts;
122
+ * 64 covers up to 4 colliders per body × 4 colliders × 4 contacts = 64).
123
+ * @type {Float64Array}
124
+ */
125
+ const candidates = new Float64Array(64 * CANDIDATE_STRIDE);
126
+
127
+ /**
128
+ * Maximum triangles a concave-side enumerator can produce per pair.
129
+ * The query AABB is bounded by the broadphase's fattened envelope of
130
+ * the convex-side body, so a single concave-vs-convex pair typically
131
+ * yields tens of triangles, not thousands. 1024 is the safety cap.
132
+ *
133
+ * If an enumerator's output would exceed this, the extra triangles are
134
+ * silently dropped by the enumerator's bounds-check on the output
135
+ * array — the worst case is a missed contact on a far edge of the
136
+ * filtered region, which the next-step rebroadphase corrects.
137
+ *
138
+ * @type {number}
139
+ */
140
+ const MAX_TRIANGLES_PER_PAIR = 1024;
141
+
142
+ /**
143
+ * Per-pair scratch for the concave-side triangle decomposition.
144
+ * @type {Float64Array}
145
+ */
146
+ const triangle_buffer = new Float64Array(MAX_TRIANGLES_PER_PAIR * TRIANGLE_FLOAT_STRIDE);
147
+
148
+ /**
149
+ * Flyweight triangle shape — rebound to each successive triangle slice
150
+ * of `triangle_buffer` during the concave-side dispatch loop. Zero
151
+ * allocation per triangle.
152
+ * @type {Triangle3D}
153
+ */
154
+ const triangle_shape = new Triangle3D();
155
+
156
+ /**
157
+ * Scratch AABB buffers used only by the concave-side dispatch:
158
+ * - `concave_local_aabb` : convex shape's local AABB (input to oriented transform)
159
+ * - `concave_world_aabb` : convex shape's world AABB
160
+ * - `concave_query_aabb` : convex shape's AABB projected into concave's body-local frame
161
+ * (what the triangle enumerator filters against)
162
+ */
163
+ const concave_local_aabb = new Float64Array(6);
164
+ const concave_world_aabb = new Float64Array(6);
165
+ const concave_query_aabb = new Float64Array(6);
166
+
167
+ /**
168
+ * Append one contact to the candidate buffer. Returns the new count.
169
+ *
170
+ * @param {number} count
171
+ * @param {number} wax
172
+ * @param {number} way
173
+ * @param {number} waz
174
+ * @param {number} wbx
175
+ * @param {number} wby
176
+ * @param {number} wbz
177
+ * @param {number} nx
178
+ * @param {number} ny
179
+ * @param {number} nz
180
+ * @param {number} depth
181
+ * @param {number} feature_id stable cross-frame ID for warm-start matching;
182
+ * `0` means no info — match-and-merge will fall back to position.
183
+ * @returns {number}
184
+ */
185
+ function append_contact(count, wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth, feature_id) {
186
+ if (count * CANDIDATE_STRIDE >= candidates.length){
187
+ return count;
188
+ }
189
+
190
+ const off = count * CANDIDATE_STRIDE;
191
+
192
+ candidates[off] = wax; candidates[off + 1] = way; candidates[off + 2] = waz;
193
+ candidates[off + 3] = wbx; candidates[off + 4] = wby; candidates[off + 5] = wbz;
194
+ candidates[off + 6] = nx; candidates[off + 7] = ny; candidates[off + 8] = nz;
195
+ candidates[off + 9] = depth;
196
+ candidates[off + 10] = feature_id;
197
+
198
+ return count + 1;
199
+ }
200
+
201
+ function swap_candidate(i, j) {
202
+ if (i === j){
203
+ return;
204
+ }
205
+
206
+ const oi = i * CANDIDATE_STRIDE;
207
+ const oj = j * CANDIDATE_STRIDE;
208
+
209
+ for (let k = 0; k < CANDIDATE_STRIDE; k++) {
210
+ const t = candidates[oi + k];
211
+ candidates[oi + k] = candidates[oj + k];
212
+ candidates[oj + k] = t;
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Compute a stable voronoi-region feature id for a sphere-vs-box contact.
218
+ * Returns one of 27 values in `[1, 27]` based on which of the 27 voronoi
219
+ * regions of the box the sphere centre lies in (8 corners + 12 edges + 6
220
+ * faces + 1 interior). Stable across frames as long as the sphere stays
221
+ * in the same region — the typical case in steady-state contact.
222
+ *
223
+ * Encodes:
224
+ * bucket_x ∈ {0, 1, 2} = sign(lx) bucket in box-local frame
225
+ * bucket_y, bucket_z similarly
226
+ * fid = 1 + bucket_x + 3·bucket_y + 9·bucket_z → 1..27
227
+ *
228
+ * The "+1" offset ensures the result is never 0, since 0 is reserved
229
+ * for "no feature info, use position-fallback".
230
+ *
231
+ * @param {number} sx sphere centre x
232
+ * @param {number} sy
233
+ * @param {number} sz
234
+ * @param {number} bx box centre x
235
+ * @param {number} by
236
+ * @param {number} bz
237
+ * @param {number} bqx box quaternion x
238
+ * @param {number} bqy
239
+ * @param {number} bqz
240
+ * @param {number} bqw
241
+ * @param {number} hx box half-extent x
242
+ * @param {number} hy
243
+ * @param {number} hz
244
+ * @returns {number}
245
+ */
246
+ function sphere_box_voronoi_fid(sx, sy, sz, bx, by, bz, bqx, bqy, bqz, bqw, hx, hy, hz) {
247
+ // Inverse-rotate (sx - bx, sy - by, sz - bz) by the box's quaternion
248
+ // to get the sphere centre in box-local frame. Inlined for the same
249
+ // V8-inliner reason described in PosedShape.support — see
250
+ // core/geom/vec3/v3_quat3_apply_inverse.js for the canonical form.
251
+ const dx = sx - bx;
252
+ const dy = sy - by;
253
+ const dz = sz - bz;
254
+
255
+ const tx0 = bqw * dx - bqy * dz + bqz * dy;
256
+ const ty0 = bqw * dy - bqz * dx + bqx * dz;
257
+ const tz0 = bqw * dz - bqx * dy + bqy * dx;
258
+ const tw0 = bqx * dx + bqy * dy + bqz * dz;
259
+
260
+ const lx = tx0 * bqw + tw0 * bqx + ty0 * bqz - tz0 * bqy;
261
+ const ly = ty0 * bqw + tw0 * bqy + tz0 * bqx - tx0 * bqz;
262
+ const lz = tz0 * bqw + tw0 * bqz + tx0 * bqy - ty0 * bqx;
263
+
264
+ const bx_b = lx <= -hx ? 0 : (lx >= hx ? 2 : 1);
265
+ const by_b = ly <= -hy ? 0 : (ly >= hy ? 2 : 1);
266
+ const bz_b = lz <= -hz ? 0 : (lz >= hz ? 2 : 1);
267
+
268
+ return 1 + bx_b + 3 * by_b + 9 * bz_b;
269
+ }
270
+
271
+ /**
272
+ * Reduce an in-place candidate list to at most {@link MAX_KEPT} entries:
273
+ * 1. Move the deepest to slot 0.
274
+ * 2. For each subsequent slot, pick the remaining candidate whose minimum
275
+ * distance to the already-kept set is largest (approximates max-area).
276
+ *
277
+ * @param {number} n
278
+ * @returns {number} kept count, in [0, min(n, MAX_KEPT)]
279
+ */
280
+ function reduce_candidates(n) {
281
+ if (n <= MAX_KEPT){
282
+ return n;
283
+ }
284
+
285
+ // Move deepest to slot 0.
286
+ let deepest_idx = 0;
287
+ let deepest_val = candidates[9];
288
+
289
+ for (let i = 1; i < n; i++) {
290
+
291
+ const d = candidates[i * CANDIDATE_STRIDE + 9];
292
+
293
+ if (d > deepest_val) {
294
+ deepest_val = d; deepest_idx = i;
295
+ }
296
+ }
297
+
298
+ swap_candidate(0, deepest_idx);
299
+
300
+ for (let k = 1; k < MAX_KEPT; k++) {
301
+
302
+ let best_score = -1;
303
+ let best_i = -1;
304
+
305
+ for (let i = k; i < n; i++) {
306
+ let min_d2 = Infinity;
307
+
308
+ for (let j = 0; j < k; j++) {
309
+
310
+ const dx = candidates[i * CANDIDATE_STRIDE] - candidates[j * CANDIDATE_STRIDE];
311
+ const dy = candidates[i * CANDIDATE_STRIDE + 1] - candidates[j * CANDIDATE_STRIDE + 1];
312
+ const dz = candidates[i * CANDIDATE_STRIDE + 2] - candidates[j * CANDIDATE_STRIDE + 2];
313
+ const d2 = dx * dx + dy * dy + dz * dz;
314
+
315
+ if (d2 < min_d2){
316
+ min_d2 = d2;
317
+ }
318
+ }
319
+
320
+ if (min_d2 > best_score) {
321
+ best_score = min_d2;
322
+ best_i = i;
323
+ }
324
+
325
+ }
326
+
327
+ swap_candidate(k, best_i);
328
+ }
329
+
330
+ return MAX_KEPT;
331
+ }
332
+
333
+ /**
334
+ * Run pairwise narrowphase for one (colliderA, colliderB) tuple — dispatches
335
+ * by shape type and appends 0..K contacts to the candidate buffer. Returns
336
+ * the new candidate count.
337
+ *
338
+ * The optional `gjk_axis_buf` + `gjk_axis_off` arguments thread the
339
+ * manifold's cached separating axis through to the GJK + EPA fallback
340
+ * paths. Closed-form paths (sphere/box/capsule × sphere/box/capsule
341
+ * and the three concave fast-paths) don't use GJK and ignore them. As
342
+ * of P2.1 step 4 these are plumbed but not yet activated — the GJK
343
+ * call sites still use the cache-less `gjk(simplex, A, B)`; step 5
344
+ * flips one of them.
345
+ *
346
+ * @param {number} count
347
+ * @param {Collider} colA
348
+ * @param {Transform} trA
349
+ * @param {Collider} colB
350
+ * @param {Transform} trB
351
+ * @param {Float64Array|null} [gjk_axis_buf] per-slot cached-axis buffer
352
+ * (`manifolds.slot_axis_buffer`) or `null` for cold-start every call.
353
+ * @param {number} [gjk_axis_off] offset into the buffer; pass
354
+ * `manifolds.slot_axis_offset(slot)`.
355
+ * @returns {number}
356
+ */
357
+ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axis_off = 0) {
358
+ const shapeA = colA.shape;
359
+ const shapeB = colB.shape;
360
+
361
+ const isSphereA = shapeA.isUnitSphereShape3D === true;
362
+ const isSphereB = shapeB.isUnitSphereShape3D === true;
363
+ // isBoxShape3D covers both UnitCubeShape3D (fixed 0.5) and BoxShape3D
364
+ // (arbitrary half-extents). Both expose `half_extents` as a Vector3.
365
+ const isBoxA = shapeA.isBoxShape3D === true;
366
+ const isBoxB = shapeB.isBoxShape3D === true;
367
+ const isCapsuleA = shapeA.isCapsuleShape3D === true;
368
+ const isCapsuleB = shapeB.isCapsuleShape3D === true;
369
+
370
+ // sphere-sphere
371
+ if (isSphereA && isSphereB) {
372
+
373
+ const ok = sphere_sphere_contact(
374
+ sphere_result,
375
+ trA.position.x, trA.position.y, trA.position.z,
376
+ trB.position.x, trB.position.y, trB.position.z,
377
+ 1, 1
378
+ );
379
+
380
+ if (!ok) return count;
381
+
382
+ const nx = sphere_result[0], ny = sphere_result[1], nz = sphere_result[2];
383
+
384
+ // Sphere-sphere produces exactly one contact per pair; fid = 1
385
+ // identifies it as a real feature (distinguishes from "no info" = 0)
386
+ // and is trivially stable across frames.
387
+ return append_contact(count,
388
+ trA.position.x - nx, trA.position.y - ny, trA.position.z - nz,
389
+ trB.position.x + nx, trB.position.y + ny, trB.position.z + nz,
390
+ nx, ny, nz, sphere_result[3], 1);
391
+ }
392
+
393
+ // sphere ↔ box
394
+ if ((isSphereA && isBoxB) || (isBoxA && isSphereB)) {
395
+ const sphereTr = isSphereA ? trA : trB;
396
+ const boxTr = isSphereA ? trB : trA;
397
+ const boxShape = isSphereA ? shapeB : shapeA;
398
+ const bh = boxShape.half_extents;
399
+
400
+ const ok = sphere_box_contact(
401
+ closed_form_result,
402
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, 1,
403
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
404
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
405
+ bh.x, bh.y, bh.z
406
+ );
407
+
408
+ if (!ok) return count;
409
+
410
+ let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
411
+
412
+ const depth = closed_form_result[3];
413
+ const wsx = closed_form_result[4], wsy = closed_form_result[5], wsz = closed_form_result[6];
414
+ const wbx = closed_form_result[7], wby = closed_form_result[8], wbz = closed_form_result[9];
415
+ // Feature id from the box-local voronoi region (1..27) — stable
416
+ // while the sphere stays on the same face / edge / vertex of the
417
+ // box, which is the steady-state case for resting / sliding contact.
418
+
419
+ const fid = sphere_box_voronoi_fid(
420
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z,
421
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
422
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
423
+ bh.x, bh.y, bh.z
424
+ );
425
+
426
+ if (isSphereA) {
427
+ return append_contact(count, wsx, wsy, wsz, wbx, wby, wbz, nx, ny, nz, depth, fid);
428
+ } else {
429
+ return append_contact(count, wbx, wby, wbz, wsx, wsy, wsz, -nx, -ny, -nz, depth, fid);
430
+ }
431
+ }
432
+
433
+ // box-box multi-point
434
+ if (isBoxA && isBoxB) {
435
+ const ah = shapeA.half_extents;
436
+ const bh = shapeB.half_extents;
437
+ const ok = box_box_manifold(
438
+ box_manifold_result,
439
+ trA.position.x, trA.position.y, trA.position.z,
440
+ trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
441
+ ah.x, ah.y, ah.z,
442
+ trB.position.x, trB.position.y, trB.position.z,
443
+ trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
444
+ bh.x, bh.y, bh.z
445
+ );
446
+ if (!ok) return count;
447
+ const nx = box_manifold_result[0], ny = box_manifold_result[1], nz = box_manifold_result[2];
448
+ const cc = box_manifold_result[3] | 0;
449
+ // Box-box manifolds: closed-form clipping doesn't expose stable
450
+ // per-contact feature ids (a contact migrates from "incident-vertex k"
451
+ // to "clip-intersection on edge j" as the boxes rotate). Leave
452
+ // fid = 0 so the match-and-merge pass uses position-fallback —
453
+ // the per-contact clipped points are spread by face geometry and
454
+ // typically stay >>MATCH_TOL apart, so position matching is
455
+ // unambiguous frame-to-frame.
456
+ for (let k = 0; k < cc; k++) {
457
+ const base = 4 + k * 7;
458
+ count = append_contact(count,
459
+ box_manifold_result[base], box_manifold_result[base + 1], box_manifold_result[base + 2],
460
+ box_manifold_result[base + 3], box_manifold_result[base + 4], box_manifold_result[base + 5],
461
+ nx, ny, nz,
462
+ box_manifold_result[base + 6],
463
+ 0);
464
+ }
465
+ return count;
466
+ }
467
+
468
+ // capsule-capsule
469
+ if (isCapsuleA && isCapsuleB) {
470
+ const a_shape = colA.shape, b_shape = colB.shape;
471
+ const ok = capsule_capsule_contact(
472
+ closed_form_result,
473
+ trA.position.x, trA.position.y, trA.position.z,
474
+ trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
475
+ a_shape.radius, a_shape.height * 0.5,
476
+ trB.position.x, trB.position.y, trB.position.z,
477
+ trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
478
+ b_shape.radius, b_shape.height * 0.5
479
+ );
480
+ if (!ok) return count;
481
+ // Single contact per capsule-capsule pair; fid = 1 (stable, real feature).
482
+ return append_contact(count,
483
+ closed_form_result[4], closed_form_result[5], closed_form_result[6],
484
+ closed_form_result[7], closed_form_result[8], closed_form_result[9],
485
+ closed_form_result[0], closed_form_result[1], closed_form_result[2],
486
+ closed_form_result[3], 1);
487
+ }
488
+
489
+ // capsule ↔ sphere
490
+ if ((isCapsuleA && isSphereB) || (isSphereA && isCapsuleB)) {
491
+ const capsuleTr = isCapsuleA ? trA : trB;
492
+ const capsuleShape = isCapsuleA ? colA.shape : colB.shape;
493
+ const sphereTr = isCapsuleA ? trB : trA;
494
+ const ok = capsule_sphere_contact(
495
+ closed_form_result,
496
+ capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
497
+ capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
498
+ capsuleShape.radius, capsuleShape.height * 0.5,
499
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, 1
500
+ );
501
+ if (!ok) return count;
502
+ let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
503
+ const depth = closed_form_result[3];
504
+ const cap_x = closed_form_result[4], cap_y = closed_form_result[5], cap_z = closed_form_result[6];
505
+ const sph_x = closed_form_result[7], sph_y = closed_form_result[8], sph_z = closed_form_result[9];
506
+ // Single contact per capsule-sphere pair; fid = 1.
507
+ if (isCapsuleA) {
508
+ return append_contact(count, cap_x, cap_y, cap_z, sph_x, sph_y, sph_z, nx, ny, nz, depth, 1);
509
+ } else {
510
+ return append_contact(count, sph_x, sph_y, sph_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, 1);
511
+ }
512
+ }
513
+
514
+ // capsule ↔ box (multi-point — 1 closest-segment + up to 2 cap-centres)
515
+ if ((isCapsuleA && isBoxB) || (isBoxA && isCapsuleB)) {
516
+ const capsuleTr = isCapsuleA ? trA : trB;
517
+ const capsuleShape = isCapsuleA ? shapeA : shapeB;
518
+ const boxTr = isCapsuleA ? trB : trA;
519
+ const boxShape = isCapsuleA ? shapeB : shapeA;
520
+ const bh = boxShape.half_extents;
521
+ const cc = capsule_box_multi_contacts(
522
+ capsule_box_multi_result,
523
+ capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
524
+ capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
525
+ capsuleShape.radius, capsuleShape.height * 0.5,
526
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
527
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
528
+ bh.x, bh.y, bh.z
529
+ );
530
+ if (cc === 0) return count;
531
+ // multi_result layout per contact: cap_x/y/z (A side), box_x/y/z (B side), nx/ny/nz, depth.
532
+ // Feature id per sub-contact: 1 = closest-segment, 2/3 = caps. The
533
+ // emission order from capsule_box_multi_contacts is stable across
534
+ // frames for the same geometric configuration.
535
+ for (let k = 0; k < cc; k++) {
536
+ const o = k * CAPSULE_BOX_CONTACT_STRIDE;
537
+ const cap_x = capsule_box_multi_result[o];
538
+ const cap_y = capsule_box_multi_result[o + 1];
539
+ const cap_z = capsule_box_multi_result[o + 2];
540
+ const box_x = capsule_box_multi_result[o + 3];
541
+ const box_y = capsule_box_multi_result[o + 4];
542
+ const box_z = capsule_box_multi_result[o + 5];
543
+ const nx = capsule_box_multi_result[o + 6];
544
+ const ny = capsule_box_multi_result[o + 7];
545
+ const nz = capsule_box_multi_result[o + 8];
546
+ const depth = capsule_box_multi_result[o + 9];
547
+ const sub_fid = k + 1;
548
+ if (isCapsuleA) {
549
+ count = append_contact(count, cap_x, cap_y, cap_z, box_x, box_y, box_z, nx, ny, nz, depth, sub_fid);
550
+ } else {
551
+ count = append_contact(count, box_x, box_y, box_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, sub_fid);
552
+ }
553
+ }
554
+ return count;
555
+ }
556
+
557
+ // ── Concave (non-convex) path ───────────────────────────────────────
558
+ //
559
+ // If either shape has `is_convex === false`, GJK on the whole shape
560
+ // produces incorrect results (Minkowski difference is not convex).
561
+ // We decompose the concave side into triangles overlapping the
562
+ // convex side's AABB, then run convex per-triangle GJK + EPA.
563
+ //
564
+ // Concave-vs-concave is intentionally NOT supported in v1: the
565
+ // M×N triangle pairs would dominate runtime, and the physics
566
+ // engine's design contract requires at least one side to be
567
+ // static / kinematic for concave shapes anyway (the broadphase +
568
+ // filter should keep such pairs out of the narrowphase entirely).
569
+ // If one slips through, we skip rather than burn cycles.
570
+ const isConcaveA = shapeA.is_convex === false;
571
+ const isConcaveB = shapeB.is_convex === false;
572
+ if (isConcaveA && isConcaveB) return count;
573
+ if (isConcaveA || isConcaveB) {
574
+ const concave_col = isConcaveA ? colA : colB;
575
+ const concave_tr = isConcaveA ? trA : trB;
576
+ const convex_col = isConcaveA ? colB : colA;
577
+ const convex_tr = isConcaveA ? trB : trA;
578
+
579
+ // 1. Convex shape's world AABB.
580
+ convex_col.shape.compute_bounding_box(concave_local_aabb);
581
+ aabb3_transform_oriented(
582
+ concave_world_aabb, 0,
583
+ concave_local_aabb[0], concave_local_aabb[1], concave_local_aabb[2],
584
+ concave_local_aabb[3], concave_local_aabb[4], concave_local_aabb[5],
585
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
586
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w
587
+ );
588
+
589
+ // 2. Project into concave's body-local frame.
590
+ aabb_world_to_local(
591
+ concave_query_aabb, 0,
592
+ concave_world_aabb,
593
+ concave_tr.position.x, concave_tr.position.y, concave_tr.position.z,
594
+ concave_tr.rotation.x, concave_tr.rotation.y, concave_tr.rotation.z, concave_tr.rotation.w
595
+ );
596
+
597
+ // 3. Decompose concave shape into triangles overlapping the query.
598
+ const tri_count = decompose_to_triangles(
599
+ triangle_buffer, 0, concave_col.shape,
600
+ concave_query_aabb[0], concave_query_aabb[1], concave_query_aabb[2],
601
+ concave_query_aabb[3], concave_query_aabb[4], concave_query_aabb[5]
602
+ );
603
+ if (tri_count === 0) return count;
604
+
605
+ // 4. Set up the convex side once; the concave side gets a
606
+ // Triangle3D rebound to each triangle in the loop. We keep
607
+ // the concave side as our internal "A" so EPA's sign-check
608
+ // convention matches the convex fallback below; the final
609
+ // append_contact() swaps if the original A was the convex one.
610
+ posed_b.setup(convex_col.shape, convex_tr.position, convex_tr.rotation);
611
+ posed_a.shape = triangle_shape;
612
+ posed_a.px = concave_tr.position.x;
613
+ posed_a.py = concave_tr.position.y;
614
+ posed_a.pz = concave_tr.position.z;
615
+ posed_a.qx = concave_tr.rotation.x;
616
+ posed_a.qy = concave_tr.rotation.y;
617
+ posed_a.qz = concave_tr.rotation.z;
618
+ posed_a.qw = concave_tr.rotation.w;
619
+
620
+ // Pre-compute the centre-axis used by EPA's sign-check loop.
621
+ // For convex-vs-convex this is `(B - A) = convex_centre − concave_centre`.
622
+ const convex_wx = convex_tr.position.x;
623
+ const convex_wy = convex_tr.position.y;
624
+ const convex_wz = convex_tr.position.z;
625
+
626
+ // Track the candidate-buffer index at the start of this
627
+ // concave dispatch — the per-triangle dedup pass scans from
628
+ // here to the current count, ignoring contacts from earlier
629
+ // collider pairs in the same body pair.
630
+ const pair_start_count = count;
631
+
632
+ // Pre-compute concave's rotation components for the q · v · q⁻¹
633
+ // rotations done per-triangle below (face normal + centroid).
634
+ const cqx = concave_tr.rotation.x;
635
+ const cqy = concave_tr.rotation.y;
636
+ const cqz = concave_tr.rotation.z;
637
+ const cqw = concave_tr.rotation.w;
638
+ const c_pos_x = concave_tr.position.x;
639
+ const c_pos_y = concave_tr.position.y;
640
+ const c_pos_z = concave_tr.position.z;
641
+
642
+ // Sphere fast-path: when the convex side is a UnitSphereShape3D we
643
+ // bypass GJK+EPA entirely per triangle and use the closed-form
644
+ // {@link sphere_triangle_contact}. This avoids the EPA precision
645
+ // wall on Triangle3D (whose support function is degenerate along
646
+ // the face normal — all 3 vertices project to the same value),
647
+ // which was producing noisy depths at small penetrations and
648
+ // letting dropped spheres tunnel through heightmaps / meshes.
649
+ const isSphereConvex = convex_col.shape.isUnitSphereShape3D === true;
650
+
651
+ // Box fast-path: closed-form {@link box_triangle_contact} via SAT
652
+ // over 13 axes + polygon clipping for face-vs-face contacts.
653
+ // Same motivation as the sphere path — Triangle3D's degenerate
654
+ // face-normal support kills EPA precision and produces noisy
655
+ // depths. The box path uses world-space triangle vertices.
656
+ const isBoxConvex = convex_col.shape.isBoxShape3D === true;
657
+ const box_half_extents = isBoxConvex ? convex_col.shape.half_extents : null;
658
+
659
+ // Capsule fast-path: closed-form {@link capsule_triangle_contact}
660
+ // via segment-vs-triangle closest-point + cap-centre sphere
661
+ // queries for a multi-point manifold. Same motivation as the
662
+ // sphere and box paths.
663
+ const isCapsuleConvex = convex_col.shape.isCapsuleShape3D === true;
664
+ const capsule_shape = isCapsuleConvex ? convex_col.shape : null;
665
+
666
+ for (let i = 0; i < tri_count; i++) {
667
+ const tri_offset = i * TRIANGLE_FLOAT_STRIDE;
668
+ triangle_shape.bind(triangle_buffer, tri_offset);
669
+
670
+ const ax = triangle_buffer[tri_offset ];
671
+ const ay = triangle_buffer[tri_offset + 1];
672
+ const az = triangle_buffer[tri_offset + 2];
673
+
674
+ const bx = triangle_buffer[tri_offset + 3];
675
+ const by = triangle_buffer[tri_offset + 4];
676
+ const bz = triangle_buffer[tri_offset + 5];
677
+
678
+ const cx_v = triangle_buffer[tri_offset + 6];
679
+ const cy_v = triangle_buffer[tri_offset + 7];
680
+ const cz_v = triangle_buffer[tri_offset + 8];
681
+
682
+ // Triangle decomposition emits a stable per-triangle feature_id
683
+ // at offset 9 (TRIANGLE_FEATURE_ID_OFFSET) — same triangle of
684
+ // the same shape gets the same id across frames. This is the
685
+ // gold-standard fid for the match-and-merge pass.
686
+ const tri_fid = triangle_buffer[tri_offset + 9];
687
+
688
+ // Triangle face normal in body-local frame: (B − A) × (C − A).
689
+ // Winding convention (CCW from outside) gives an outward face
690
+ // normal — the heightmap / mesh enumerators both promise this.
691
+ const e1x_l = bx - ax, e1y_l = by - ay, e1z_l = bz - az;
692
+ const e2x_l = cx_v - ax, e2y_l = cy_v - ay, e2z_l = cz_v - az;
693
+
694
+ const fnx_l = e1y_l * e2z_l - e1z_l * e2y_l;
695
+ const fny_l = e1z_l * e2x_l - e1x_l * e2z_l;
696
+ const fnz_l = e1x_l * e2y_l - e1y_l * e2x_l;
697
+
698
+ // Rotate face normal to world via concave's quaternion
699
+ // (q · v · q⁻¹). Inlined for V8 inliner — see PosedShape.support.
700
+ const fnix = cqw * fnx_l + cqy * fnz_l - cqz * fny_l;
701
+ const fniy = cqw * fny_l + cqz * fnx_l - cqx * fnz_l;
702
+ const fniz = cqw * fnz_l + cqx * fny_l - cqy * fnx_l;
703
+ const fniw = -cqx * fnx_l - cqy * fny_l - cqz * fnz_l;
704
+
705
+ const fnx_w = fnix * cqw - fniw * cqx - fniy * cqz + fniz * cqy;
706
+ const fny_w = fniy * cqw - fniw * cqy - fniz * cqx + fnix * cqz;
707
+ const fnz_w = fniz * cqw - fniw * cqz - fnix * cqy + fniy * cqx;
708
+
709
+ // Sphere-vs-triangle closed-form fast-path.
710
+ if (isSphereConvex) {
711
+
712
+ // Rotate each triangle vertex from concave-local to world.
713
+ // Per-vertex cost: ~21 flops × 3 vertices = 63 flops; vs.
714
+ // the GJK + EPA cost we're replacing, this is essentially
715
+ // free.
716
+ // Inlined q · v · q* + translate per vertex — see PosedShape.support.
717
+ const axi = cqw * ax + cqy * az - cqz * ay;
718
+ const ayi = cqw * ay + cqz * ax - cqx * az;
719
+ const azi = cqw * az + cqx * ay - cqy * ax;
720
+ const awi = -cqx * ax - cqy * ay - cqz * az;
721
+
722
+ const ax_w = axi * cqw + awi * -cqx + ayi * -cqz - azi * -cqy + c_pos_x;
723
+ const ay_w = ayi * cqw + awi * -cqy + azi * -cqx - axi * -cqz + c_pos_y;
724
+ const az_w = azi * cqw + awi * -cqz + axi * -cqy - ayi * -cqx + c_pos_z;
725
+
726
+ const bxi = cqw * bx + cqy * bz - cqz * by;
727
+ const byi = cqw * by + cqz * bx - cqx * bz;
728
+ const bzi = cqw * bz + cqx * by - cqy * bx;
729
+ const bwi = -cqx * bx - cqy * by - cqz * bz;
730
+
731
+ const bx_w = bxi * cqw + bwi * -cqx + byi * -cqz - bzi * -cqy + c_pos_x;
732
+ const by_w = byi * cqw + bwi * -cqy + bzi * -cqx - bxi * -cqz + c_pos_y;
733
+ const bz_w = bzi * cqw + bwi * -cqz + bxi * -cqy - byi * -cqx + c_pos_z;
734
+
735
+ const cxi = cqw * cx_v + cqy * cz_v - cqz * cy_v;
736
+ const cyi = cqw * cy_v + cqz * cx_v - cqx * cz_v;
737
+ const czi = cqw * cz_v + cqx * cy_v - cqy * cx_v;
738
+ const cwi = -cqx * cx_v - cqy * cy_v - cqz * cz_v;
739
+
740
+ const cx_w = cxi * cqw + cwi * -cqx + cyi * -cqz - czi * -cqy + c_pos_x;
741
+ const cy_w = cyi * cqw + cwi * -cqy + czi * -cqx - cxi * -cqz + c_pos_y;
742
+ const cz_w = czi * cqw + cwi * -cqz + cxi * -cqy - cyi * -cqx + c_pos_z;
743
+
744
+ const ok = sphere_triangle_contact(
745
+ sphere_triangle_result,
746
+ convex_wx, convex_wy, convex_wz, 1,
747
+ ax_w, ay_w, az_w,
748
+ bx_w, by_w, bz_w,
749
+ cx_w, cy_w, cz_w
750
+ );
751
+
752
+ if (!ok) continue;
753
+
754
+ // sphere_triangle_contact's normal points from the
755
+ // triangle surface toward the sphere centre — same
756
+ // direction as the post-sign-check EPA MTV in the
757
+ // fallback path below (concave A → convex B).
758
+ const sd = sphere_triangle_result[3];
759
+ const n_t2s_x = sphere_triangle_result[0];
760
+ const n_t2s_y = sphere_triangle_result[1];
761
+ const n_t2s_z = sphere_triangle_result[2];
762
+
763
+ // One-sided rejection: the sphere must lie on the
764
+ // outward side of the triangle. If the contact normal
765
+ // opposes the face normal, the sphere is behind /
766
+ // inside the solid — skip rather than push it deeper.
767
+ if (n_t2s_x * fnx_w + n_t2s_y * fny_w + n_t2s_z * fnz_w <= 0) continue;
768
+
769
+ // Stored normal convention is "B → A". `nx, ny, nz` here
770
+ // points convex → concave (which the existing EPA branch
771
+ // also produces just before append). Dedup uses the
772
+ // post-swap stored_n so adjacent-triangle duplicates
773
+ // collapse the same way regardless of code path.
774
+ const nx_s = -n_t2s_x;
775
+ const ny_s = -n_t2s_y;
776
+ const nz_s = -n_t2s_z;
777
+
778
+ const stored_nx_s = isConcaveA ? nx_s : -nx_s;
779
+ const stored_ny_s = isConcaveA ? ny_s : -ny_s;
780
+ const stored_nz_s = isConcaveA ? nz_s : -nz_s;
781
+ let is_duplicate_s = false;
782
+ for (let k = pair_start_count; k < count; k++) {
783
+ const ko = k * CANDIDATE_STRIDE;
784
+ const dnx = candidates[ko + 6] - stored_nx_s;
785
+ const dny = candidates[ko + 7] - stored_ny_s;
786
+ const dnz = candidates[ko + 8] - stored_nz_s;
787
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
788
+ is_duplicate_s = true;
789
+ break;
790
+ }
791
+ }
792
+ if (is_duplicate_s) continue;
793
+
794
+ // Surface witnesses from the closed-form solver:
795
+ // result[4..6] = sphere surface point (convex side)
796
+ // result[7..9] = triangle closest point (concave side)
797
+ // Use the actual witnesses rather than body centres —
798
+ // unlike the EPA fallback (which uses body centres
799
+ // because flat-faced support witnesses can be arbitrarily
800
+ // far from the contact patch), closest-point-on-triangle
801
+ // is exact and lies on the contact patch.
802
+ const sphere_wx = sphere_triangle_result[4];
803
+ const sphere_wy = sphere_triangle_result[5];
804
+ const sphere_wz = sphere_triangle_result[6];
805
+ const tri_wx = sphere_triangle_result[7];
806
+ const tri_wy = sphere_triangle_result[8];
807
+ const tri_wz = sphere_triangle_result[9];
808
+ if (isConcaveA) {
809
+ count = append_contact(count,
810
+ tri_wx, tri_wy, tri_wz,
811
+ sphere_wx, sphere_wy, sphere_wz,
812
+ nx_s, ny_s, nz_s, sd, tri_fid);
813
+ } else {
814
+ count = append_contact(count,
815
+ sphere_wx, sphere_wy, sphere_wz,
816
+ tri_wx, tri_wy, tri_wz,
817
+ -nx_s, -ny_s, -nz_s, sd, tri_fid);
818
+ }
819
+ continue;
820
+ }
821
+
822
+ // Box-vs-triangle closed-form fast-path.
823
+ if (isBoxConvex) {
824
+ // Rotate each triangle vertex from concave-local to world.
825
+ // Inlined q · v · q* + translate per vertex — see PosedShape.support.
826
+ const axi = cqw * ax + cqy * az - cqz * ay;
827
+ const ayi = cqw * ay + cqz * ax - cqx * az;
828
+ const azi = cqw * az + cqx * ay - cqy * ax;
829
+ const awi = -cqx * ax - cqy * ay - cqz * az;
830
+
831
+ const ax_w = axi * cqw + awi * -cqx + ayi * -cqz - azi * -cqy + c_pos_x;
832
+ const ay_w = ayi * cqw + awi * -cqy + azi * -cqx - axi * -cqz + c_pos_y;
833
+ const az_w = azi * cqw + awi * -cqz + axi * -cqy - ayi * -cqx + c_pos_z;
834
+
835
+ const bxi = cqw * bx + cqy * bz - cqz * by;
836
+ const byi = cqw * by + cqz * bx - cqx * bz;
837
+ const bzi = cqw * bz + cqx * by - cqy * bx;
838
+ const bwi = -cqx * bx - cqy * by - cqz * bz;
839
+
840
+ const bx_w = bxi * cqw + bwi * -cqx + byi * -cqz - bzi * -cqy + c_pos_x;
841
+ const by_w = byi * cqw + bwi * -cqy + bzi * -cqx - bxi * -cqz + c_pos_y;
842
+ const bz_w = bzi * cqw + bwi * -cqz + bxi * -cqy - byi * -cqx + c_pos_z;
843
+
844
+ const cxi = cqw * cx_v + cqy * cz_v - cqz * cy_v;
845
+ const cyi = cqw * cy_v + cqz * cx_v - cqx * cz_v;
846
+ const czi = cqw * cz_v + cqx * cy_v - cqy * cx_v;
847
+ const cwi = -cqx * cx_v - cqy * cy_v - cqz * cz_v;
848
+ const cx_w = cxi * cqw + cwi * -cqx + cyi * -cqz - czi * -cqy + c_pos_x;
849
+ const cy_w = cyi * cqw + cwi * -cqy + czi * -cqx - cxi * -cqz + c_pos_y;
850
+ const cz_w = czi * cqw + cwi * -cqz + cxi * -cqy - cyi * -cqx + c_pos_z;
851
+
852
+ const ok = box_triangle_contact(
853
+ box_triangle_result,
854
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
855
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
856
+ box_half_extents.x, box_half_extents.y, box_half_extents.z,
857
+ ax_w, ay_w, az_w,
858
+ bx_w, by_w, bz_w,
859
+ cx_w, cy_w, cz_w
860
+ );
861
+ if (!ok) continue;
862
+
863
+ // Same convention as sphere_triangle: result normal points
864
+ // from triangle surface toward box centre. To match the
865
+ // EPA branch's `nx, ny, nz` (which points convex →
866
+ // concave = box → triangle), negate.
867
+ const n_t2box_x = box_triangle_result[0];
868
+ const n_t2box_y = box_triangle_result[1];
869
+ const n_t2box_z = box_triangle_result[2];
870
+
871
+ // One-sided rejection: the box must lie on the outward
872
+ // side of the triangle. If the contact normal opposes
873
+ // the face normal, the box is inside the solid → skip.
874
+ if (n_t2box_x * fnx_w + n_t2box_y * fny_w + n_t2box_z * fnz_w <= 0) continue;
875
+
876
+ const nx_b = -n_t2box_x;
877
+ const ny_b = -n_t2box_y;
878
+ const nz_b = -n_t2box_z;
879
+ const stored_nx_b = isConcaveA ? nx_b : -nx_b;
880
+ const stored_ny_b = isConcaveA ? ny_b : -ny_b;
881
+ const stored_nz_b = isConcaveA ? nz_b : -nz_b;
882
+
883
+ // Emit each clipped contact. The contact count from
884
+ // box_triangle_contact is at most 4; combined with the
885
+ // existing per-pair candidate cap (64 entries), tens of
886
+ // overlapping triangles per pair are safely accommodated.
887
+ const cc = box_triangle_result[3] | 0;
888
+ for (let k = 0; k < cc; k++) {
889
+ const base = 4 + k * 7;
890
+ const t_wx = box_triangle_result[base];
891
+ const t_wy = box_triangle_result[base + 1];
892
+ const t_wz = box_triangle_result[base + 2];
893
+ const b_wx = box_triangle_result[base + 3];
894
+ const b_wy = box_triangle_result[base + 4];
895
+ const b_wz = box_triangle_result[base + 5];
896
+ const d_k = box_triangle_result[base + 6];
897
+
898
+ // Dedup against earlier candidates in THIS dispatch
899
+ // (adjacent triangles can produce coincident contacts
900
+ // — same as the sphere and EPA paths).
901
+ let is_duplicate_k = false;
902
+ for (let q = pair_start_count; q < count; q++) {
903
+ const qo = q * CANDIDATE_STRIDE;
904
+ const dnx = candidates[qo + 6] - stored_nx_b;
905
+ const dny = candidates[qo + 7] - stored_ny_b;
906
+ const dnz = candidates[qo + 8] - stored_nz_b;
907
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
908
+ // Same normal; check positions too (a
909
+ // multi-point manifold has distinct points
910
+ // with the same normal — those should NOT
911
+ // be deduped). Compare the world-A side
912
+ // (triangle surface or box surface depending
913
+ // on dispatcher swap).
914
+ const pa_x = isConcaveA ? t_wx : b_wx;
915
+ const pa_y = isConcaveA ? t_wy : b_wy;
916
+ const pa_z = isConcaveA ? t_wz : b_wz;
917
+ const dpx = candidates[qo] - pa_x;
918
+ const dpy = candidates[qo + 1] - pa_y;
919
+ const dpz = candidates[qo + 2] - pa_z;
920
+ if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
921
+ is_duplicate_k = true;
922
+ break;
923
+ }
924
+ }
925
+ }
926
+ if (is_duplicate_k) continue;
927
+
928
+ if (isConcaveA) {
929
+ count = append_contact(count,
930
+ t_wx, t_wy, t_wz,
931
+ b_wx, b_wy, b_wz,
932
+ nx_b, ny_b, nz_b, d_k, tri_fid);
933
+ } else {
934
+ count = append_contact(count,
935
+ b_wx, b_wy, b_wz,
936
+ t_wx, t_wy, t_wz,
937
+ -nx_b, -ny_b, -nz_b, d_k, tri_fid);
938
+ }
939
+ }
940
+ continue;
941
+ }
942
+
943
+ // Capsule-vs-triangle closed-form fast-path.
944
+ if (isCapsuleConvex) {
945
+ // Rotate each triangle vertex from concave-local to world.
946
+ // Inlined q · v · q* + translate per vertex — see PosedShape.support.
947
+ const axi = cqw * ax + cqy * az - cqz * ay;
948
+ const ayi = cqw * ay + cqz * ax - cqx * az;
949
+ const azi = cqw * az + cqx * ay - cqy * ax;
950
+ const awi = -cqx * ax - cqy * ay - cqz * az;
951
+ const ax_w = axi * cqw + awi * -cqx + ayi * -cqz - azi * -cqy + c_pos_x;
952
+ const ay_w = ayi * cqw + awi * -cqy + azi * -cqx - axi * -cqz + c_pos_y;
953
+ const az_w = azi * cqw + awi * -cqz + axi * -cqy - ayi * -cqx + c_pos_z;
954
+
955
+ const bxi = cqw * bx + cqy * bz - cqz * by;
956
+ const byi = cqw * by + cqz * bx - cqx * bz;
957
+ const bzi = cqw * bz + cqx * by - cqy * bx;
958
+ const bwi = -cqx * bx - cqy * by - cqz * bz;
959
+ const bx_w = bxi * cqw + bwi * -cqx + byi * -cqz - bzi * -cqy + c_pos_x;
960
+ const by_w = byi * cqw + bwi * -cqy + bzi * -cqx - bxi * -cqz + c_pos_y;
961
+ const bz_w = bzi * cqw + bwi * -cqz + bxi * -cqy - byi * -cqx + c_pos_z;
962
+
963
+ const cxi = cqw * cx_v + cqy * cz_v - cqz * cy_v;
964
+ const cyi = cqw * cy_v + cqz * cx_v - cqx * cz_v;
965
+ const czi = cqw * cz_v + cqx * cy_v - cqy * cx_v;
966
+ const cwi = -cqx * cx_v - cqy * cy_v - cqz * cz_v;
967
+ const cx_w = cxi * cqw + cwi * -cqx + cyi * -cqz - czi * -cqy + c_pos_x;
968
+ const cy_w = cyi * cqw + cwi * -cqy + czi * -cqx - cxi * -cqz + c_pos_y;
969
+ const cz_w = czi * cqw + cwi * -cqz + cxi * -cqy - cyi * -cqx + c_pos_z;
970
+
971
+ const cap_cc = capsule_triangle_contact(
972
+ capsule_triangle_result,
973
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
974
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
975
+ capsule_shape.radius, capsule_shape.height * 0.5,
976
+ ax_w, ay_w, az_w,
977
+ bx_w, by_w, bz_w,
978
+ cx_w, cy_w, cz_w
979
+ );
980
+ if (cap_cc === 0) continue;
981
+
982
+ // Each sub-contact's normal can be distinct (a flat
983
+ // capsule produces primary + endpoint contacts whose
984
+ // normals all point ≈ same direction, but an edge or
985
+ // vertex contact at a cap can deviate). Apply
986
+ // face-normal rejection per-contact.
987
+ for (let k = 0; k < cap_cc; k++) {
988
+ const base = k * CAPSULE_TRIANGLE_CONTACT_STRIDE;
989
+ const cap_x = capsule_triangle_result[base];
990
+ const cap_y = capsule_triangle_result[base + 1];
991
+ const cap_z = capsule_triangle_result[base + 2];
992
+ const ct_x = capsule_triangle_result[base + 3];
993
+ const ct_y = capsule_triangle_result[base + 4];
994
+ const ct_z = capsule_triangle_result[base + 5];
995
+ const n_t2cap_x = capsule_triangle_result[base + 6];
996
+ const n_t2cap_y = capsule_triangle_result[base + 7];
997
+ const n_t2cap_z = capsule_triangle_result[base + 8];
998
+ const d_c = capsule_triangle_result[base + 9];
999
+
1000
+ // One-sided face-normal rejection: capsule must be
1001
+ // on the outward side of the triangle.
1002
+ if (n_t2cap_x * fnx_w + n_t2cap_y * fny_w + n_t2cap_z * fnz_w <= 0) continue;
1003
+
1004
+ // Stored normal "convex → concave" = "capsule →
1005
+ // triangle" = negated t2cap.
1006
+ const nx_c = -n_t2cap_x;
1007
+ const ny_c = -n_t2cap_y;
1008
+ const nz_c = -n_t2cap_z;
1009
+ const stored_nx_c = isConcaveA ? nx_c : -nx_c;
1010
+ const stored_ny_c = isConcaveA ? ny_c : -ny_c;
1011
+ const stored_nz_c = isConcaveA ? nz_c : -nz_c;
1012
+
1013
+ // Dedup against earlier candidates in THIS dispatch.
1014
+ let is_duplicate_c = false;
1015
+ for (let q = pair_start_count; q < count; q++) {
1016
+ const qo = q * CANDIDATE_STRIDE;
1017
+ const dnx = candidates[qo + 6] - stored_nx_c;
1018
+ const dny = candidates[qo + 7] - stored_ny_c;
1019
+ const dnz = candidates[qo + 8] - stored_nz_c;
1020
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1021
+ const pa_x = isConcaveA ? ct_x : cap_x;
1022
+ const pa_y = isConcaveA ? ct_y : cap_y;
1023
+ const pa_z = isConcaveA ? ct_z : cap_z;
1024
+ const dpx = candidates[qo] - pa_x;
1025
+ const dpy = candidates[qo + 1] - pa_y;
1026
+ const dpz = candidates[qo + 2] - pa_z;
1027
+ if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1028
+ is_duplicate_c = true;
1029
+ break;
1030
+ }
1031
+ }
1032
+ }
1033
+ if (is_duplicate_c) continue;
1034
+
1035
+ if (isConcaveA) {
1036
+ count = append_contact(count,
1037
+ ct_x, ct_y, ct_z,
1038
+ cap_x, cap_y, cap_z,
1039
+ nx_c, ny_c, nz_c, d_c, tri_fid);
1040
+ } else {
1041
+ count = append_contact(count,
1042
+ cap_x, cap_y, cap_z,
1043
+ ct_x, ct_y, ct_z,
1044
+ -nx_c, -ny_c, -nz_c, d_c, tri_fid);
1045
+ }
1046
+ }
1047
+ continue;
1048
+ }
1049
+
1050
+ // P2.1 step 6: pass the manifold's cached separating axis
1051
+ // here too. Per-triangle iteration means the cache gets
1052
+ // written multiple times per body-pair per frame (one per
1053
+ // overlapping triangle); the final state reflects the
1054
+ // last triangle's converged direction. Still a useful
1055
+ // seed for next frame's first triangle.
1056
+ if (!gjk_with_axis(simplex_buf, posed_a, posed_b, gjk_axis_buf, gjk_axis_off)) continue;
1057
+ expanding_polytope_algorithm(
1058
+ epa_result, 0,
1059
+ simplex_a, simplex_b, simplex_c, simplex_d,
1060
+ posed_a, posed_b
1061
+ );
1062
+
1063
+ let ex = epa_result[0], ey = epa_result[1], ez = epa_result[2];
1064
+ let depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1065
+ // P3.1: MPR fallback. EPA can return zero/negative/NaN
1066
+ // depth on Triangle3D's degenerate face-normal support
1067
+ // (the same precision pit the closed-form sphere/box/
1068
+ // capsule paths above were added to dodge). MPR has
1069
+ // different convergence properties and often succeeds
1070
+ // here. Only kicks in for non-(sphere/box/capsule) convex
1071
+ // shapes against the concave side — a narrow path now
1072
+ // that closed-form covers the common cases.
1073
+ if (!(depth > 0) || !Number.isFinite(depth)) {
1074
+ if (!mpr(epa_result, 0, posed_a, posed_b)) continue;
1075
+ ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1076
+ depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1077
+ if (!(depth > 0) || !Number.isFinite(depth)) continue;
1078
+ }
1079
+
1080
+ // Sign-check: validate MTV direction against (convex −
1081
+ // triangle_centroid). EPA's polytope-closest-face normal
1082
+ // can come out on the wrong side of the origin; this flip
1083
+ // canonicalises MTV to point from triangle (A) toward the
1084
+ // convex shape (B).
1085
+ const cent_lx = (ax + bx + cx_v) / 3;
1086
+ const cent_ly = (ay + by + cy_v) / 3;
1087
+ const cent_lz = (az + bz + cz_v) / 3;
1088
+ // Inlined q · v · q* + translate.
1089
+ const cix = cqw * cent_lx + cqy * cent_lz - cqz * cent_ly;
1090
+ const ciy = cqw * cent_ly + cqz * cent_lx - cqx * cent_lz;
1091
+ const ciz = cqw * cent_lz + cqx * cent_ly - cqy * cent_lx;
1092
+ const ciw = -cqx * cent_lx - cqy * cent_ly - cqz * cent_lz;
1093
+ const cent_wx = cix * cqw + ciw * -cqx + ciy * -cqz - ciz * -cqy + c_pos_x;
1094
+ const cent_wy = ciy * cqw + ciw * -cqy + ciz * -cqx - cix * -cqz + c_pos_y;
1095
+ const cent_wz = ciz * cqw + ciw * -cqz + cix * -cqy - ciy * -cqx + c_pos_z;
1096
+
1097
+ const tri_ab_x = convex_wx - cent_wx;
1098
+ const tri_ab_y = convex_wy - cent_wy;
1099
+ const tri_ab_z = convex_wz - cent_wz;
1100
+ if (ex * tri_ab_x + ey * tri_ab_y + ez * tri_ab_z < 0) {
1101
+ ex = -ex; ey = -ey; ez = -ez;
1102
+ }
1103
+
1104
+ // One-sided rejection (post-sign-check). MTV now points
1105
+ // from triangle (A) toward convex shape (B). For a CCW
1106
+ // outward-wound triangle, the convex shape is on the
1107
+ // OUTWARD side iff MTV aligns with the face normal. If it
1108
+ // opposes the face normal, the convex shape is on the
1109
+ // back / inward side — invalid for heightmap/mesh (it's
1110
+ // inside the solid). Skip the triangle rather than push
1111
+ // the body deeper into the solid through the opposite
1112
+ // face on the next step.
1113
+ if (ex * fnx_w + ey * fny_w + ez * fnz_w <= 0) continue;
1114
+
1115
+ // After the validated flip, MTV points from triangle
1116
+ // (concave-side A) into convex (B). Stored normal is
1117
+ // "B → A" so we negate. Final A/B order matches
1118
+ // append_contact's contract: original A first, original B
1119
+ // second, normal "from original B toward original A".
1120
+ const inv = 1 / depth;
1121
+ const nx = -ex * inv;
1122
+ const ny = -ey * inv;
1123
+ const nz = -ez * inv;
1124
+
1125
+ // Dedup against earlier contacts emitted in THIS dispatch.
1126
+ // Adjacent triangles (heightmap cells sharing a diagonal;
1127
+ // mesh triangles sharing an edge or vertex) often report
1128
+ // identical contacts (same normal, same body-centre
1129
+ // application points), and feeding duplicates to the
1130
+ // sequential-impulse solver makes it escalate the impulse
1131
+ // across iterations without bound. We deduplicate by
1132
+ // contact normal within a small angular threshold: same
1133
+ // body pair + same normal + same body-centre positions =
1134
+ // the same physical contact, only one copy belongs in the
1135
+ // manifold.
1136
+ const stored_nx = isConcaveA ? nx : -nx;
1137
+ const stored_ny = isConcaveA ? ny : -ny;
1138
+ const stored_nz = isConcaveA ? nz : -nz;
1139
+ let is_duplicate = false;
1140
+ for (let k = pair_start_count; k < count; k++) {
1141
+ const ko = k * CANDIDATE_STRIDE;
1142
+ const dnx = candidates[ko + 6] - stored_nx;
1143
+ const dny = candidates[ko + 7] - stored_ny;
1144
+ const dnz = candidates[ko + 8] - stored_nz;
1145
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1146
+ is_duplicate = true;
1147
+ break;
1148
+ }
1149
+ }
1150
+ if (is_duplicate) continue;
1151
+
1152
+ // Contact application points: body centres rather than
1153
+ // the triangle's centroid. The existing GJK+EPA fallback
1154
+ // (below) uses the same convention — for vertical contacts
1155
+ // (sphere/box on a flat surface, the common case), the
1156
+ // lever arm r × n vanishes and the impulse resolves
1157
+ // cleanly.
1158
+ if (isConcaveA) {
1159
+ count = append_contact(count,
1160
+ c_pos_x, c_pos_y, c_pos_z,
1161
+ convex_wx, convex_wy, convex_wz,
1162
+ nx, ny, nz, depth, tri_fid);
1163
+ } else {
1164
+ count = append_contact(count,
1165
+ convex_wx, convex_wy, convex_wz,
1166
+ c_pos_x, c_pos_y, c_pos_z,
1167
+ -nx, -ny, -nz, depth, tri_fid);
1168
+ }
1169
+ }
1170
+
1171
+ return count;
1172
+ }
1173
+
1174
+ // GJK + EPA fallback for everything else. P2.1 step 5: pass the
1175
+ // manifold's cached separating axis as GJK's initial search
1176
+ // direction. Cold start (zero-axis) on first contact; subsequent
1177
+ // frames reuse the converged direction, typically converging in
1178
+ // 1–2 iterations instead of the ~6–10 cold-start cost.
1179
+ posed_a.setup(colA.shape, trA.position, trA.rotation);
1180
+ posed_b.setup(colB.shape, trB.position, trB.rotation);
1181
+
1182
+ if (!gjk_with_axis(simplex_buf, posed_a, posed_b, gjk_axis_buf, gjk_axis_off)){
1183
+ return count;
1184
+ }
1185
+
1186
+ expanding_polytope_algorithm(
1187
+ epa_result, 0,
1188
+ simplex_a, simplex_b, simplex_c, simplex_d,
1189
+ posed_a, posed_b
1190
+ );
1191
+
1192
+ let ex = epa_result[0], ey = epa_result[1], ez = epa_result[2];
1193
+ let depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1194
+ // EPA can return zero-depth (touching), negative (impossible) or
1195
+ // NaN/Inf (degenerate iteration-cap exit on smooth high-poly
1196
+ // colliders). P3.1: fall back to MPR — different convergence
1197
+ // properties (works portal-based rather than face-expansion), often
1198
+ // succeeds where EPA fails on the same geometry. `shape_cast` and
1199
+ // `compute_penetration` already use MPR for the same reason.
1200
+ if (!(depth > 0) || !Number.isFinite(depth)) {
1201
+ if (!mpr(epa_result, 0, posed_a, posed_b)) return count;
1202
+ ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1203
+ depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1204
+ if (!(depth > 0) || !Number.isFinite(depth)) return count;
1205
+ }
1206
+
1207
+ // EPA's output vector should point along the minimum-translation
1208
+ // axis from A into B (the direction you'd push B by to separate).
1209
+ // On non-convergent EPA — common on smooth high-poly colliders like
1210
+ // a torus knot, where the polytope can't tighten onto the true
1211
+ // closest face within the iteration cap — the fallback returns
1212
+ // *some* face's normal, and that face can be on either side of the
1213
+ // origin. Resulting MTV occasionally points B *into* A instead of
1214
+ // *away from* A; the solver then sees a "separating" relative
1215
+ // velocity, clamps the normal impulse to zero, and the bodies pass
1216
+ // straight through each other.
1217
+ //
1218
+ // Validate against the vector from body A's centre to body B's
1219
+ // centre — EPA's direction must correlate positively with it. Flip
1220
+ // if not. For convex bodies whose origin is inside the geometry,
1221
+ // body-centre-to-body-centre is a strong proxy for the correct
1222
+ // separation direction. For pathological cases (deeply
1223
+ // interpenetrating bodies with coincident centres) the dot product
1224
+ // is near zero and we leave EPA's guess intact.
1225
+ const ab_x = trB.position.x - trA.position.x;
1226
+ const ab_y = trB.position.y - trA.position.y;
1227
+ const ab_z = trB.position.z - trA.position.z;
1228
+ if (ex * ab_x + ey * ab_y + ez * ab_z < 0) {
1229
+ ex = -ex; ey = -ey; ez = -ez;
1230
+ }
1231
+
1232
+ const inv = 1 / depth;
1233
+ const nx = -ex * inv, ny = -ey * inv, nz = -ez * inv; // stored normal, B → A
1234
+
1235
+ // Contact application points: body centres rather than support-function
1236
+ // witnesses. The natural-sounding alternative — `posed_a.support(+EPA_dir)`
1237
+ // and `posed_b.support(-EPA_dir)` — interacts badly with flat-faced
1238
+ // supports: a large floor's support in `+Y` returns a *corner* (the box
1239
+ // support is multi-valued along an axis-aligned direction and picks the
1240
+ // sign-tied corner), which can be tens of metres from the actual
1241
+ // contact patch. That blows up the solver's lever arm `r × n` and
1242
+ // collapses `m_eff` to ~0, leaving the impulse vanishingly small.
1243
+ //
1244
+ // Body centres give `r ‖ n` for vertical contacts (sphere/knot landing
1245
+ // on a floor — the common case), so `r × n = 0` and `m_eff = invM`
1246
+ // resolves the impact cleanly. For oblique contacts the application
1247
+ // point lies on the line between centres, which is the right-ish lever
1248
+ // arm for translational impulse and degrades gracefully on angular
1249
+ // response. A future closed-form mesh-vs-box path will compute a
1250
+ // proper face-projected witness; until then, body centres are the
1251
+ // robust fallback for the GJK+EPA route.
1252
+ // GJK+EPA fallback has no stable per-frame feature info; emit fid = 0
1253
+ // so match-and-merge uses position-fallback. Single contact per pair.
1254
+ return append_contact(count,
1255
+ trA.position.x, trA.position.y, trA.position.z,
1256
+ trB.position.x, trB.position.y, trB.position.z,
1257
+ nx, ny, nz,
1258
+ depth, 0
1259
+ );
1260
+ }
1261
+
1262
+ /**
1263
+ * For every pair in `pair_list`, do a cross-product over A's collider list ×
1264
+ * B's collider list, accumulate candidate contacts, reduce to ≤4, and write
1265
+ * to the manifold slot.
1266
+ *
1267
+ * @param {PairList} pair_list
1268
+ * @param {ManifoldStore} manifolds
1269
+ * @param {Array<Array<{collider: Collider, transform: Transform}>>} lists
1270
+ * per-body collider lists, indexed by body-storage slot index. Typically
1271
+ * `system.__body_collider_lists` — passed in directly so this helper
1272
+ * has no dependency on `PhysicsSystem`.
1273
+ */
1274
+ export function narrowphase_step(pair_list, manifolds, lists) {
1275
+ const count = pair_list.count;
1276
+
1277
+ for (let i = 0; i < count; i++) {
1278
+ const idA = pair_list.get_a(i);
1279
+ const idB = pair_list.get_b(i);
1280
+ const idxA = body_id_index(idA);
1281
+ const idxB = body_id_index(idB);
1282
+
1283
+ const list_a = lists[idxA];
1284
+ const list_b = lists[idxB];
1285
+ const slot = manifolds.find(idA, idB);
1286
+
1287
+ if (list_a === undefined || list_b === undefined
1288
+ || list_a.length === 0 || list_b.length === 0) {
1289
+ manifolds.clear_contacts(slot);
1290
+ continue;
1291
+ }
1292
+
1293
+ let cand_count = 0;
1294
+ const la_len = list_a.length;
1295
+ const lb_len = list_b.length;
1296
+ // Per-manifold cached GJK separating-axis seed. Threaded into
1297
+ // dispatch_pair for the GJK fallback paths. For single-collider
1298
+ // bodies (the common case) there's no contention across the
1299
+ // inner loop; for compound bodies the cache reflects whichever
1300
+ // collider-pair invoked GJK last, which is a noisy-but-useful
1301
+ // seed (better than a cold (1, 0, 0) restart every frame).
1302
+ const gjk_axis_buf = manifolds.slot_axis_buffer;
1303
+ const gjk_axis_off = manifolds.slot_axis_offset(slot);
1304
+ for (let a = 0; a < la_len; a++) {
1305
+ const ea = list_a[a];
1306
+ for (let b = 0; b < lb_len; b++) {
1307
+ const eb = list_b[b];
1308
+ cand_count = dispatch_pair(cand_count, ea.collider, ea.transform, eb.collider, eb.transform,
1309
+ gjk_axis_buf, gjk_axis_off);
1310
+ }
1311
+ }
1312
+
1313
+ if (cand_count === 0) {
1314
+ // No contacts this frame for an existing manifold. Keep the
1315
+ // slot in the cache (the grace window in advance_frame() will
1316
+ // evict it if this persists), but zero out impulses and
1317
+ // contact count — there's nothing for the solver to act on
1318
+ // and stale impulses would mislead next frame's warm-start
1319
+ // if contact re-establishes at a different feature.
1320
+ manifolds.clear_contacts(slot);
1321
+ continue;
1322
+ }
1323
+
1324
+ const kept = reduce_candidates(cand_count);
1325
+
1326
+ // ── Match-and-merge: feature-id (with position fallback) ──────
1327
+ //
1328
+ // Snapshot prev-frame state for matching + impulse carry-over.
1329
+ const data = manifolds.data_buffer;
1330
+ const slot_off = manifolds.slot_data_offset(slot);
1331
+ const prev_count_raw = manifolds.contact_count(slot);
1332
+ const prev_count = prev_count_raw > MAX_CONTACTS_PER_MANIFOLD
1333
+ ? MAX_CONTACTS_PER_MANIFOLD
1334
+ : prev_count_raw;
1335
+ for (let j = 0; j < prev_count; j++) {
1336
+ const off = slot_off + j * CONTACT_STRIDE;
1337
+ const snap_off = j * 7;
1338
+ prev_snapshot[snap_off] = data[off + 13]; // feature_id
1339
+ prev_snapshot[snap_off + 1] = data[off]; // world_a x
1340
+ prev_snapshot[snap_off + 2] = data[off + 1]; // world_a y
1341
+ prev_snapshot[snap_off + 3] = data[off + 2]; // world_a z
1342
+ prev_snapshot[snap_off + 4] = data[off + 10]; // j_n
1343
+ prev_snapshot[snap_off + 5] = data[off + 11]; // j_t1
1344
+ prev_snapshot[snap_off + 6] = data[off + 12]; // j_t2
1345
+ prev_claimed[j] = 0;
1346
+ }
1347
+
1348
+ // For each new candidate, find a matching prev contact.
1349
+ // Step 1: feature-id match (only if BOTH sides have a non-zero
1350
+ // feature_id — fid = 0 means "no info").
1351
+ // Step 2: position-fallback within MATCH_TOL_SQR.
1352
+ for (let k = 0; k < kept; k++) {
1353
+ const cand_off = k * CANDIDATE_STRIDE;
1354
+ const cand_fid = candidates[cand_off + 10];
1355
+ const cand_ax = candidates[cand_off];
1356
+ const cand_ay = candidates[cand_off + 1];
1357
+ const cand_az = candidates[cand_off + 2];
1358
+
1359
+ let best_prev = -1;
1360
+ if (cand_fid !== 0) {
1361
+ for (let j = 0; j < prev_count; j++) {
1362
+ if (prev_claimed[j]) continue;
1363
+ if (prev_snapshot[j * 7] === cand_fid) {
1364
+ best_prev = j;
1365
+ break;
1366
+ }
1367
+ }
1368
+ }
1369
+ if (best_prev === -1) {
1370
+ let best_d2 = MATCH_TOL_SQR;
1371
+ for (let j = 0; j < prev_count; j++) {
1372
+ if (prev_claimed[j]) continue;
1373
+ const snap_off = j * 7;
1374
+ const dx = prev_snapshot[snap_off + 1] - cand_ax;
1375
+ const dy = prev_snapshot[snap_off + 2] - cand_ay;
1376
+ const dz = prev_snapshot[snap_off + 3] - cand_az;
1377
+ const d2 = dx * dx + dy * dy + dz * dz;
1378
+ if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1379
+ }
1380
+ }
1381
+ cand_to_prev[k] = best_prev;
1382
+ if (best_prev !== -1) prev_claimed[best_prev] = 1;
1383
+ }
1384
+
1385
+ // Reset count without zeroing the data slab — set_contact below
1386
+ // will overwrite geometry, and matched candidates will inherit
1387
+ // the impulses we snapshotted above (written back at the new
1388
+ // slot index after set_contact).
1389
+ manifolds.begin_refill(slot);
1390
+
1391
+ for (let k = 0; k < kept; k++) {
1392
+ const off = k * CANDIDATE_STRIDE;
1393
+ // Target slot index = k (we write candidates 0..kept-1 into
1394
+ // slot indices 0..kept-1). The impulse carry-over below
1395
+ // copies the matched prev contact's impulses into THIS k,
1396
+ // so the geometric and warm-start state stay correlated
1397
+ // even if the matching mapping permuted the order.
1398
+ manifolds.set_contact(
1399
+ slot, k,
1400
+ candidates[off], candidates[off + 1], candidates[off + 2],
1401
+ candidates[off + 3], candidates[off + 4], candidates[off + 5],
1402
+ candidates[off + 6], candidates[off + 7], candidates[off + 8],
1403
+ candidates[off + 9],
1404
+ candidates[off + 10]
1405
+ );
1406
+ const prev_j = cand_to_prev[k];
1407
+ if (prev_j !== -1) {
1408
+ // Copy prev_j's impulse to this slot index.
1409
+ const dst_off = slot_off + k * CONTACT_STRIDE;
1410
+ const src_off = prev_j * 7;
1411
+ data[dst_off + 10] = prev_snapshot[src_off + 4];
1412
+ data[dst_off + 11] = prev_snapshot[src_off + 5];
1413
+ data[dst_off + 12] = prev_snapshot[src_off + 6];
1414
+ } else {
1415
+ // No match — explicitly zero the impulses, since this
1416
+ // slot index may have stale data from an earlier frame's
1417
+ // contact at the same index.
1418
+ manifolds.clear_impulses(slot, k);
1419
+ }
1420
+ }
1421
+ }
1422
+ }