@woosh/meep-engine 2.155.0 → 2.157.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1098) hide show
  1. package/README.md +2 -4
  2. package/build/bundle-worker-image-decoder.js +1 -1
  3. package/build/bundle-worker-terrain.js +1 -1
  4. package/editor/view/ecs/ComponentControlView.d.ts +0 -9
  5. package/editor/view/ecs/ComponentControlView.js +2 -98
  6. package/editor/view/ecs/components/common/AutoCanvasView.js +100 -53
  7. package/editor/view/ecs/components/common/TextController.js +59 -0
  8. package/editor/view/node-graph/NodeGraphCamera.js +90 -0
  9. package/editor/view/node-graph/NodeGraphEditorView.js +121 -22
  10. package/editor/view/node-graph/NodeGraphSelection.js +89 -0
  11. package/editor/view/node-graph/NodeGraphView.js +669 -453
  12. package/editor/view/node-graph/NodeView.js +211 -135
  13. package/editor/view/node-graph/actions/ConnectionCreateAction.js +53 -0
  14. package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -0
  15. package/editor/view/node-graph/actions/NodeDeleteAction.js +88 -0
  16. package/editor/view/node-graph/actions/NodeParameterSetAction.js +52 -0
  17. package/editor/view/node-graph/actions/NodesMoveAction.js +41 -0
  18. package/editor/view/node-graph/actions/SelectionSetAction.js +60 -0
  19. package/editor/view/node-graph/connection_wire_geometry.js +107 -0
  20. package/package.json +1 -1
  21. package/samples/generation/SampleGenerator0.js +8 -1
  22. package/src/core/binary/32BitEncoder.js +1 -1
  23. package/src/core/binary/reinterpret_float32_as_uint32.d.ts +7 -0
  24. package/src/core/binary/reinterpret_float32_as_uint32.d.ts.map +1 -0
  25. package/src/core/binary/reinterpret_float32_as_uint32.js +13 -0
  26. package/src/core/binary/reinterpret_uint32_as_float32.d.ts +7 -0
  27. package/src/core/binary/reinterpret_uint32_as_float32.d.ts.map +1 -0
  28. package/src/core/binary/reinterpret_uint32_as_float32.js +14 -0
  29. package/src/core/binary/to_half_float_uint16.js +3 -3
  30. package/src/core/bvh2/bvh3/ebvh_build_for_geometry_incremental.d.ts.map +1 -1
  31. package/src/core/bvh2/bvh3/ebvh_build_for_geometry_incremental.js +1 -3
  32. package/src/core/bvh2/bvh3/ebvh_build_hierarchy_radix.d.ts.map +1 -1
  33. package/src/core/bvh2/bvh3/ebvh_build_hierarchy_radix.js +275 -253
  34. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_sphere.d.ts +12 -0
  35. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_sphere.d.ts.map +1 -0
  36. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_sphere.js +92 -0
  37. package/src/core/bvh8/BVH8.d.ts +127 -0
  38. package/src/core/bvh8/BVH8.d.ts.map +1 -0
  39. package/src/core/bvh8/BVH8.js +436 -0
  40. package/src/core/bvh8/NOTES.md +63 -0
  41. package/src/core/bvh8/build/BVH8Converter.d.ts +59 -0
  42. package/src/core/bvh8/build/BVH8Converter.d.ts.map +1 -0
  43. package/src/core/bvh8/build/BVH8Converter.js +588 -0
  44. package/src/core/bvh8/build/NodeProxy.d.ts +66 -0
  45. package/src/core/bvh8/build/NodeProxy.d.ts.map +1 -0
  46. package/src/core/bvh8/build/NodeProxy.js +308 -0
  47. package/src/core/bvh8/build/TriangleCluster.d.ts +29 -0
  48. package/src/core/bvh8/build/TriangleCluster.d.ts.map +1 -0
  49. package/src/core/bvh8/build/TriangleCluster.js +123 -0
  50. package/src/core/bvh8/build/aabb3_compute_merge_cost.d.ts +8 -0
  51. package/src/core/bvh8/build/aabb3_compute_merge_cost.d.ts.map +1 -0
  52. package/src/core/bvh8/build/aabb3_compute_merge_cost.js +29 -0
  53. package/src/core/bvh8/build/aabb3_from_triangle_by_index.d.ts +10 -0
  54. package/src/core/bvh8/build/aabb3_from_triangle_by_index.d.ts.map +1 -0
  55. package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +18 -0
  56. package/src/core/bvh8/build/bvh8_build_for_geometry.d.ts +10 -0
  57. package/src/core/bvh8/build/bvh8_build_for_geometry.d.ts.map +1 -0
  58. package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -0
  59. package/src/core/bvh8/build/bvh8_from_proxy.d.ts +9 -0
  60. package/src/core/bvh8/build/bvh8_from_proxy.d.ts.map +1 -0
  61. package/src/core/bvh8/build/bvh8_from_proxy.js +256 -0
  62. package/src/core/bvh8/build/byte.d.ts +7 -0
  63. package/src/core/bvh8/build/byte.d.ts.map +1 -0
  64. package/src/core/bvh8/build/byte.js +10 -0
  65. package/src/core/bvh8/build/encode_bounds_e.d.ts +9 -0
  66. package/src/core/bvh8/build/encode_bounds_e.d.ts.map +1 -0
  67. package/src/core/bvh8/build/encode_bounds_e.js +12 -0
  68. package/src/core/bvh8/bvh8_convert_to_dot.d.ts +11 -0
  69. package/src/core/bvh8/bvh8_convert_to_dot.d.ts.map +1 -0
  70. package/src/core/bvh8/bvh8_convert_to_dot.js +133 -0
  71. package/src/core/bvh8/bvh8_count_primitives.d.ts +22 -0
  72. package/src/core/bvh8/bvh8_count_primitives.d.ts.map +1 -0
  73. package/src/core/bvh8/bvh8_count_primitives.js +98 -0
  74. package/src/core/bvh8/bvh8_geometry_validate.d.ts +16 -0
  75. package/src/core/bvh8/bvh8_geometry_validate.d.ts.map +1 -0
  76. package/src/core/bvh8/bvh8_geometry_validate.js +149 -0
  77. package/src/core/bvh8/bvh8_geometry_validate_indirect.d.ts +16 -0
  78. package/src/core/bvh8/bvh8_geometry_validate_indirect.d.ts.map +1 -0
  79. package/src/core/bvh8/bvh8_geometry_validate_indirect.js +177 -0
  80. package/src/core/bvh8/bvh8_get_node_bounds.d.ts +9 -0
  81. package/src/core/bvh8/bvh8_get_node_bounds.d.ts.map +1 -0
  82. package/src/core/bvh8/bvh8_get_node_bounds.js +35 -0
  83. package/src/core/bvh8/bvh8_get_node_child_bounds.d.ts +10 -0
  84. package/src/core/bvh8/bvh8_get_node_child_bounds.d.ts.map +1 -0
  85. package/src/core/bvh8/bvh8_get_node_child_bounds.js +53 -0
  86. package/src/core/bvh8/bvh8_node_child_surface_area.d.ts +9 -0
  87. package/src/core/bvh8/bvh8_node_child_surface_area.d.ts.map +1 -0
  88. package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -0
  89. package/src/core/bvh8/bvh8_node_count_triangles.d.ts +8 -0
  90. package/src/core/bvh8/bvh8_node_count_triangles.d.ts.map +1 -0
  91. package/src/core/bvh8/bvh8_node_count_triangles.js +28 -0
  92. package/src/core/bvh8/bvh8_quality.d.ts +8 -0
  93. package/src/core/bvh8/bvh8_quality.d.ts.map +1 -0
  94. package/src/core/bvh8/bvh8_quality.js +73 -0
  95. package/src/core/bvh8/bvh8_validate_structure.d.ts +15 -0
  96. package/src/core/bvh8/bvh8_validate_structure.d.ts.map +1 -0
  97. package/src/core/bvh8/bvh8_validate_structure.js +87 -0
  98. package/src/core/cache/Cache.d.ts.map +1 -1
  99. package/src/core/cache/Cache.js +7 -0
  100. package/src/core/cache/FrequencySketch.d.ts.map +1 -1
  101. package/src/core/cache/FrequencySketch.js +8 -4
  102. package/src/core/clipboard/obtainClipBoard.d.ts +6 -0
  103. package/src/core/clipboard/obtainClipBoard.d.ts.map +1 -0
  104. package/src/core/clipboard/obtainClipBoard.js +29 -0
  105. package/src/core/clipboard/safeClipboardReadText.d.ts +6 -0
  106. package/src/core/clipboard/safeClipboardReadText.d.ts.map +1 -0
  107. package/src/core/clipboard/safeClipboardReadText.js +55 -0
  108. package/src/core/clipboard/safeClipboardWriteText.d.ts +8 -0
  109. package/src/core/clipboard/safeClipboardWriteText.d.ts.map +1 -0
  110. package/src/core/clipboard/safeClipboardWriteText.js +23 -0
  111. package/src/core/collection/Uint32MinHeap.d.ts +56 -0
  112. package/src/core/collection/Uint32MinHeap.d.ts.map +1 -0
  113. package/src/core/collection/Uint32MinHeap.js +109 -0
  114. package/src/core/collection/array/array_quick_sort_by_lookup_map.js +1 -1
  115. package/src/core/collection/array/array_set_diff_sorting.d.ts.map +1 -1
  116. package/src/core/collection/array/array_set_diff_sorting.js +4 -1
  117. package/src/core/collection/array/array_shuffle.d.ts.map +1 -1
  118. package/src/core/collection/array/array_shuffle.js +30 -27
  119. package/src/core/collection/array/binarySearchLowIndex.d.ts.map +1 -1
  120. package/src/core/collection/array/binarySearchLowIndex.js +4 -3
  121. package/src/core/collection/array/typed/array_buffer_hash.js +1 -1
  122. package/src/core/collection/array/typed/is_typed_array_equals.d.ts.map +1 -1
  123. package/src/core/collection/array/typed/is_typed_array_equals.js +12 -2
  124. package/src/core/collection/heap/BinaryHeap.d.ts.map +1 -1
  125. package/src/core/collection/heap/BinaryHeap.js +12 -2
  126. package/src/core/collection/list/FilteredListProjection.js +1 -1
  127. package/src/core/collection/queue/Deque.d.ts.map +1 -1
  128. package/src/core/collection/queue/Deque.js +10 -8
  129. package/src/core/collection/table/RowFirstTable.d.ts.map +1 -1
  130. package/src/core/collection/table/RowFirstTable.js +4 -2
  131. package/src/core/collection/table/RowFirstTableSpec.js +2 -2
  132. package/src/{engine/physics/island → core/collection/union-find}/union_find.d.ts +8 -5
  133. package/src/core/collection/union-find/union_find.d.ts.map +1 -0
  134. package/src/{engine/physics/island → core/collection/union-find}/union_find.js +8 -5
  135. package/src/core/color/operations/color_lerp.d.ts.map +1 -1
  136. package/src/core/color/operations/color_lerp.js +10 -3
  137. package/src/core/color/rgb2uint32.js +1 -1
  138. package/src/core/color/rgbe9995_to_rgb.js +1 -1
  139. package/src/core/dom/isImageBitmap.d.ts +7 -0
  140. package/src/core/dom/isImageBitmap.d.ts.map +1 -0
  141. package/src/core/dom/isImageBitmap.js +12 -0
  142. package/src/core/function/frameThrottle.d.ts +8 -0
  143. package/src/core/function/frameThrottle.d.ts.map +1 -0
  144. package/src/core/function/frameThrottle.js +23 -0
  145. package/src/core/function/objectsEqual.d.ts.map +1 -1
  146. package/src/core/function/objectsEqual.js +2 -1
  147. package/src/core/geom/2d/aabb/AABB2.d.ts.map +1 -1
  148. package/src/core/geom/2d/aabb/AABB2.js +12 -11
  149. package/src/core/geom/2d/convex-hull/convex_hull_jarvis_2d.d.ts.map +1 -1
  150. package/src/core/geom/2d/convex-hull/convex_hull_jarvis_2d.js +30 -4
  151. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.d.ts.map +1 -1
  152. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +6 -2
  153. package/src/core/geom/2d/hash-grid/SpatialHashGrid.d.ts.map +1 -1
  154. package/src/core/geom/2d/hash-grid/SpatialHashGrid.js +388 -386
  155. package/src/core/geom/2d/hash-grid/shg_query_elements_line.d.ts.map +1 -1
  156. package/src/core/geom/2d/hash-grid/shg_query_elements_line.js +8 -3
  157. package/src/{engine/physics/narrowphase/clip_against_axis_uv.d.ts → core/geom/2d/polygon/polygon2_clip_axis_halfplane.d.ts} +3 -3
  158. package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.d.ts.map +1 -0
  159. package/src/{engine/physics/narrowphase/clip_against_axis_uv.js → core/geom/2d/polygon/polygon2_clip_axis_halfplane.js} +51 -51
  160. package/src/core/geom/2d/quad-tree/QuadTreeDatum.d.ts.map +1 -1
  161. package/src/core/geom/2d/quad-tree/QuadTreeDatum.js +9 -1
  162. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.d.ts +3 -1
  163. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.d.ts.map +1 -1
  164. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +3 -1
  165. package/src/core/geom/2d/quad-tree-binary/QuadTree.js +714 -714
  166. package/src/core/geom/2d/r-tree/StaticR2Tree.d.ts.map +1 -1
  167. package/src/core/geom/2d/r-tree/StaticR2Tree.js +5 -4
  168. package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.d.ts.map +1 -1
  169. package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +33 -29
  170. package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.d.ts.map +1 -1
  171. package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +3 -1
  172. package/src/core/geom/3d/aabb/aabb3_signed_distance_to_aabb3.d.ts.map +1 -1
  173. package/src/core/geom/3d/aabb/aabb3_signed_distance_to_aabb3.js +10 -7
  174. package/src/{engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts → core/geom/3d/aabb/aabb3_transform_oriented_inverse.d.ts} +9 -7
  175. package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.d.ts.map +1 -0
  176. package/src/{engine/physics/narrowphase/decomposition/aabb_world_to_local.js → core/geom/3d/aabb/aabb3_transform_oriented_inverse.js} +9 -7
  177. package/src/core/geom/3d/aabb/aabb3_transformed_compute_plane_side.d.ts.map +1 -1
  178. package/src/core/geom/3d/aabb/aabb3_transformed_compute_plane_side.js +30 -9
  179. package/src/core/geom/3d/aabb/compute_aabb_from_points.js +3 -3
  180. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.d.ts +12 -0
  181. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.d.ts.map +1 -0
  182. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +46 -0
  183. package/src/core/geom/3d/box/box3_projected_half_extent.d.ts +28 -0
  184. package/src/core/geom/3d/box/box3_projected_half_extent.d.ts.map +1 -0
  185. package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -0
  186. package/src/core/geom/3d/box/box3_raycast.d.ts +37 -0
  187. package/src/core/geom/3d/box/box3_raycast.d.ts.map +1 -0
  188. package/src/core/geom/3d/box/box3_raycast.js +81 -0
  189. package/src/core/geom/3d/capsule/capsule_raycast.d.ts +35 -0
  190. package/src/core/geom/3d/capsule/capsule_raycast.d.ts.map +1 -0
  191. package/src/core/geom/3d/capsule/capsule_raycast.js +93 -0
  192. package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.d.ts.map +1 -1
  193. package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +4 -0
  194. package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +1 -1
  195. package/src/core/geom/3d/frustum/read_cluster_frustum_corners.js +1 -1
  196. package/src/core/geom/3d/frustum/read_frustum_corner.d.ts +9 -0
  197. package/src/core/geom/3d/frustum/read_frustum_corner.d.ts.map +1 -0
  198. package/src/core/geom/3d/frustum/read_frustum_corner.js +14 -0
  199. package/src/core/geom/3d/gjk/gjk.d.ts.map +1 -0
  200. package/src/{engine/physics → core/geom/3d}/gjk/gjk.js +430 -372
  201. package/src/{engine/physics → core/geom/3d}/gjk/gjk_epa_penetration.d.ts +8 -5
  202. package/src/core/geom/3d/gjk/gjk_epa_penetration.d.ts.map +1 -0
  203. package/src/{engine/physics → core/geom/3d}/gjk/gjk_epa_penetration.js +520 -548
  204. package/src/{engine/physics → core/geom/3d}/gjk/minkowski_support.d.ts +5 -4
  205. package/src/core/geom/3d/gjk/minkowski_support.d.ts.map +1 -0
  206. package/src/{engine/physics → core/geom/3d}/gjk/minkowski_support.js +71 -70
  207. package/src/{engine/physics → core/geom/3d}/gjk/mpr.d.ts +3 -3
  208. package/src/core/geom/3d/gjk/mpr.d.ts.map +1 -0
  209. package/src/{engine/physics → core/geom/3d}/gjk/mpr.js +368 -362
  210. package/src/core/geom/3d/line/line3_compute_segment_point_distance_eikonal.d.ts.map +1 -1
  211. package/src/core/geom/3d/line/line3_compute_segment_point_distance_eikonal.js +3 -2
  212. package/src/core/geom/3d/mat4/decompose_matrix_4_array.d.ts.map +1 -1
  213. package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +12 -2
  214. package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +2 -2
  215. package/src/core/geom/3d/mat4/m4_multiply_alphatensor.d.ts +1 -1
  216. package/src/core/geom/3d/mat4/m4_multiply_alphatensor.d.ts.map +1 -1
  217. package/src/core/geom/3d/mat4/m4_multiply_alphatensor.js +19 -13
  218. package/src/core/geom/3d/octahedra/octahedral_direction_to_uv.d.ts.map +1 -1
  219. package/src/core/geom/3d/octahedra/octahedral_direction_to_uv.js +3 -2
  220. package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +3 -2
  221. package/src/{engine/physics/integration/quat_integrate.d.ts → core/geom/3d/quaternion/quat3_integrate.d.ts} +2 -2
  222. package/src/core/geom/3d/quaternion/quat3_integrate.d.ts.map +1 -0
  223. package/src/{engine/physics/integration/quat_integrate.js → core/geom/3d/quaternion/quat3_integrate.js} +1 -1
  224. package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -1
  225. package/src/core/geom/3d/shape/MeshShape3D.js +7 -0
  226. package/src/{engine/physics/narrowphase/PosedShape.d.ts → core/geom/3d/shape/PosedShape3D.d.ts} +9 -8
  227. package/src/{engine/physics/narrowphase/PosedShape.d.ts.map → core/geom/3d/shape/PosedShape3D.d.ts.map} +1 -1
  228. package/src/{engine/physics/narrowphase/PosedShape.js → core/geom/3d/shape/PosedShape3D.js} +10 -9
  229. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  230. package/src/core/geom/3d/shape/TransformedShape3D.js +15 -11
  231. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  232. package/src/core/geom/3d/shape/UnionShape3D.js +3 -2
  233. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.d.ts.map +1 -1
  234. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +153 -148
  235. package/src/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.d.ts.map +1 -1
  236. package/src/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.js +7 -0
  237. package/src/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.d.ts.map +1 -1
  238. package/src/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.js +13 -10
  239. package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.d.ts +1 -1
  240. package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +2 -2
  241. package/src/core/geom/3d/sphere/sphere_raycast.d.ts +33 -0
  242. package/src/core/geom/3d/sphere/sphere_raycast.d.ts.map +1 -0
  243. package/src/core/geom/3d/sphere/sphere_raycast.js +47 -0
  244. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.d.ts +24 -0
  245. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.d.ts.map +1 -0
  246. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +39 -0
  247. package/src/core/geom/3d/tetrahedra/triangle/trace_triangular_depth_map.d.ts.map +1 -1
  248. package/src/core/geom/3d/tetrahedra/triangle/trace_triangular_depth_map.js +4 -2
  249. package/src/core/geom/3d/topology/bounds/computeTriangleClusterNormalBoundingCone.js +3 -3
  250. package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.js +1 -1
  251. package/src/core/geom/3d/topology/tm_vertex_compute_normal.d.ts.map +1 -1
  252. package/src/core/geom/3d/topology/tm_vertex_compute_normal.js +4 -2
  253. package/src/core/geom/3d/util/make_justified_point_grid.d.ts.map +1 -1
  254. package/src/core/geom/3d/util/make_justified_point_grid.js +18 -10
  255. package/src/core/geom/ConicRay.d.ts.map +1 -1
  256. package/src/core/geom/ConicRay.js +11 -13
  257. package/src/core/geom/packing/max-rect/removeRedundantBoxes.d.ts.map +1 -1
  258. package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +19 -4
  259. package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.d.ts.map +1 -0
  260. package/src/{engine/graphics/sh3/path_tracer/sampling → core/geom/vec3}/v3_orthonormal_matrix_from_normal.js +1 -1
  261. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +1 -1
  262. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +1 -1
  263. package/src/core/graph/coloring/colorizeGraph.js +2 -2
  264. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
  265. package/src/core/graph/csr/CSRGraph.js +325 -319
  266. package/src/core/graph/layout/CircleLayout.d.ts.map +1 -1
  267. package/src/core/graph/layout/CircleLayout.js +8 -6
  268. package/src/core/graph/metis/native/refine/compute_kway_params.d.ts.map +1 -1
  269. package/src/core/graph/metis/native/refine/compute_kway_params.js +139 -138
  270. package/src/core/graph/mn_graph_coarsen.d.ts.map +1 -1
  271. package/src/core/graph/mn_graph_coarsen.js +4 -2
  272. package/src/core/graph/v2/NodeContainer.js +7 -7
  273. package/src/core/localization/LocalizationEngine.js +1 -1
  274. package/src/core/math/bell_membership_function.d.ts.map +1 -1
  275. package/src/core/math/bell_membership_function.js +3 -1
  276. package/src/core/math/complex/complex_add.d.ts +4 -4
  277. package/src/core/math/complex/complex_add.d.ts.map +1 -1
  278. package/src/core/math/complex/complex_add.js +14 -5
  279. package/src/core/math/complex/complex_div.d.ts +4 -4
  280. package/src/core/math/complex/complex_div.d.ts.map +1 -1
  281. package/src/core/math/complex/complex_div.js +13 -6
  282. package/src/core/math/complex/complex_mul.d.ts +4 -4
  283. package/src/core/math/complex/complex_mul.d.ts.map +1 -1
  284. package/src/core/math/complex/complex_mul.js +12 -5
  285. package/src/core/math/complex/complex_sub.d.ts +4 -4
  286. package/src/core/math/complex/complex_sub.d.ts.map +1 -1
  287. package/src/core/math/complex/complex_sub.js +14 -5
  288. package/src/core/math/idct_1d.d.ts +4 -4
  289. package/src/core/math/idct_1d.d.ts.map +1 -1
  290. package/src/core/math/idct_1d.js +3 -3
  291. package/src/{engine/physics/fluid/solver/optimal_sor_omega.d.ts → core/math/linalg/sor_optimal_omega.d.ts} +4 -3
  292. package/src/core/math/linalg/sor_optimal_omega.d.ts.map +1 -0
  293. package/src/{engine/physics/fluid/solver/optimal_sor_omega.js → core/math/linalg/sor_optimal_omega.js} +4 -3
  294. package/src/core/math/lookup/ParameterLookupTable.d.ts +123 -0
  295. package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -0
  296. package/src/core/math/lookup/ParameterLookupTable.js +495 -0
  297. package/src/core/math/lookup/ParameterLookupTableFlags.d.ts +5 -0
  298. package/src/core/math/lookup/ParameterLookupTableFlags.d.ts.map +1 -0
  299. package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -0
  300. package/src/core/math/noise/create_simplex_noise_2d.d.ts.map +1 -1
  301. package/src/core/math/noise/create_simplex_noise_2d.js +4 -2
  302. package/src/core/math/noise/sdnoise.d.ts.map +1 -1
  303. package/src/core/math/noise/sdnoise.js +12 -9
  304. package/src/core/math/physics/kinematics/computeInterceptPoint.d.ts.map +1 -0
  305. package/src/{engine/physics → core/math/physics/kinematics}/computeInterceptPoint.js +79 -79
  306. package/src/core/math/physics/mie/compute_bhmie_optical_properties.d.ts.map +1 -1
  307. package/src/core/math/physics/mie/compute_bhmie_optical_properties.js +94 -50
  308. package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts +3 -6
  309. package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts.map +1 -1
  310. package/src/core/math/physics/mie/lorenz_mie_coefs.js +180 -157
  311. package/src/core/math/physics/mie/mie_ab_to_optical_properties.d.ts +3 -4
  312. package/src/core/math/physics/mie/mie_ab_to_optical_properties.d.ts.map +1 -1
  313. package/src/core/math/physics/mie/mie_ab_to_optical_properties.js +47 -21
  314. package/src/core/math/physics/mie/ri_air.d.ts.map +1 -1
  315. package/src/core/math/physics/mie/ri_air.js +1 -3
  316. package/src/core/math/physics/mie/ri_ammonium_sulfate.d.ts.map +1 -1
  317. package/src/core/math/physics/mie/ri_ammonium_sulfate.js +1 -3
  318. package/src/core/math/physics/mie/ri_brine.d.ts.map +1 -1
  319. package/src/core/math/physics/mie/ri_brine.js +1 -3
  320. package/src/core/math/physics/mie/ri_dust.d.ts.map +1 -1
  321. package/src/core/math/physics/mie/ri_dust.js +1 -3
  322. package/src/core/math/physics/mie/ri_pollen.d.ts.map +1 -1
  323. package/src/core/math/physics/mie/ri_pollen.js +1 -3
  324. package/src/core/math/physics/mie/ri_smoke.d.ts.map +1 -1
  325. package/src/core/math/physics/mie/ri_smoke.js +1 -3
  326. package/src/core/math/physics/mie/ri_soot.d.ts.map +1 -1
  327. package/src/core/math/physics/mie/ri_soot.js +1 -3
  328. package/src/core/math/physics/mie/ri_water.d.ts.map +1 -1
  329. package/src/core/math/physics/mie/ri_water.js +1 -3
  330. package/src/core/math/random/randomIntegerBetween.d.ts.map +1 -1
  331. package/src/core/math/random/randomIntegerBetween.js +4 -1
  332. package/src/core/math/random/random_pick_weighted_index.d.ts +10 -0
  333. package/src/core/math/random/random_pick_weighted_index.d.ts.map +1 -0
  334. package/src/core/math/random/random_pick_weighted_index.js +26 -0
  335. package/src/core/math/solveCubic.d.ts.map +1 -1
  336. package/src/core/math/solveCubic.js +95 -82
  337. package/src/core/math/spline/computeCatmullRomSplineUniformDistance.d.ts.map +1 -1
  338. package/src/core/math/spline/computeCatmullRomSplineUniformDistance.js +13 -0
  339. package/src/core/math/statistics/softmax.js +1 -1
  340. package/src/core/model/node-graph/NodeGraph.d.ts +9 -0
  341. package/src/core/model/node-graph/NodeGraph.d.ts.map +1 -1
  342. package/src/core/model/node-graph/NodeGraph.js +38 -0
  343. package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts +24 -0
  344. package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts.map +1 -1
  345. package/src/core/model/node-graph/visual/NodeGraphVisualData.js +56 -1
  346. package/src/core/model/node-graph/visual/NodeVisualData.js +1 -1
  347. package/src/core/model/object/ImmutableObjectPool.d.ts +7 -0
  348. package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
  349. package/src/core/model/object/ImmutableObjectPool.js +20 -10
  350. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.d.ts.map +1 -1
  351. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.js +39 -2
  352. package/src/core/model/reactive/model/terminal/ReactiveReference.d.ts.map +1 -1
  353. package/src/core/model/reactive/model/terminal/ReactiveReference.js +2 -0
  354. package/src/core/parser/simple/readHexToken.d.ts.map +1 -1
  355. package/src/core/parser/simple/readHexToken.js +6 -0
  356. package/src/core/path/convertPathToURL.d.ts +9 -0
  357. package/src/core/path/convertPathToURL.d.ts.map +1 -0
  358. package/src/core/path/convertPathToURL.js +107 -0
  359. package/src/core/primitives/numbers/number_pretty_print.d.ts.map +1 -1
  360. package/src/core/primitives/numbers/number_pretty_print.js +4 -1
  361. package/src/core/primitives/strings/string_jaro_winkler.js +1 -1
  362. package/src/core/process/CompositeProcess.js +1 -1
  363. package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
  364. package/src/core/process/executor/ConcurrentExecutor.js +3 -2
  365. package/src/core/process/task/util/randomCountTask.d.ts.map +1 -1
  366. package/src/core/process/task/util/randomCountTask.js +3 -1
  367. package/src/core/process/undo/ActionProcessor.d.ts.map +1 -1
  368. package/src/core/process/undo/ActionProcessor.js +5 -3
  369. package/src/core/process/worker/WorkerBuilder.js +4 -4
  370. package/src/core/process/worker/extractTransferables.js +1 -1
  371. package/src/engine/animation/curve/AnimationCurve.d.ts.map +1 -1
  372. package/src/engine/animation/curve/AnimationCurve.js +4 -2
  373. package/src/engine/animation/curve/draw/build_tangent_editor.d.ts.map +1 -1
  374. package/src/engine/animation/curve/draw/build_tangent_editor.js +8 -1
  375. package/src/engine/animation/curve/editor/createKeyframeDraggableAspect.d.ts.map +1 -1
  376. package/src/engine/animation/curve/editor/createKeyframeDraggableAspect.js +11 -5
  377. package/src/engine/asset/Asset.d.ts.map +1 -1
  378. package/src/engine/asset/Asset.js +16 -6
  379. package/src/engine/asset/AssetManager.d.ts +61 -52
  380. package/src/engine/asset/AssetManager.d.ts.map +1 -1
  381. package/src/engine/asset/AssetManager.js +1411 -1045
  382. package/src/engine/asset/AssetRequest.d.ts +1 -1
  383. package/src/engine/asset/AssetRequest.d.ts.map +1 -1
  384. package/src/engine/asset/AssetRequest.js +1 -1
  385. package/src/engine/asset/AssetRequestScope.d.ts.map +1 -1
  386. package/src/engine/asset/AssetRequestScope.js +7 -0
  387. package/src/engine/asset/PendingAsset.d.ts +32 -1
  388. package/src/engine/asset/PendingAsset.d.ts.map +1 -1
  389. package/src/engine/asset/PendingAsset.js +108 -61
  390. package/src/engine/asset/loaders/ArrayBufferLoader.js +2 -2
  391. package/src/engine/asset/loaders/AssetLoader.d.ts.map +1 -1
  392. package/src/engine/asset/loaders/AssetLoader.js +19 -2
  393. package/src/engine/asset/loaders/GLTFAssetLoader.d.ts.map +1 -1
  394. package/src/engine/asset/loaders/GLTFAssetLoader.js +123 -114
  395. package/src/engine/asset/loaders/JavascriptAssetLoader.d.ts +1 -1
  396. package/src/engine/asset/loaders/JavascriptAssetLoader.d.ts.map +1 -1
  397. package/src/engine/asset/loaders/JavascriptAssetLoader.js +31 -47
  398. package/src/engine/asset/loaders/JsonAssetLoader.js +1 -1
  399. package/src/engine/asset/loaders/SVGAssetLoader.js +2 -2
  400. package/src/engine/asset/loaders/SoundAssetLoader.js +1 -1
  401. package/src/engine/asset/loaders/TextAssetLoader.js +2 -2
  402. package/src/{core → engine/asset/loaders}/font/FontAsset.d.ts +1 -1
  403. package/src/engine/asset/loaders/font/FontAsset.d.ts.map +1 -0
  404. package/src/{core → engine/asset/loaders}/font/FontAsset.js +21 -21
  405. package/src/{core → engine/asset/loaders}/font/FontAssetLoader.d.ts +1 -1
  406. package/src/engine/asset/loaders/font/FontAssetLoader.d.ts.map +1 -0
  407. package/src/{core → engine/asset/loaders}/font/FontAssetLoader.js +20 -20
  408. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts +1 -1
  409. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts.map +1 -1
  410. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +11 -20
  411. package/src/engine/asset/loaders/texture/TextureAssetLoader.d.ts.map +1 -1
  412. package/src/engine/asset/loaders/texture/TextureAssetLoader.js +8 -2
  413. package/src/engine/asset/preloader/AssetPreloader.js +1 -1
  414. package/src/engine/control/first-person/DESIGN.md +1 -1
  415. package/src/engine/control/first-person/FirstPersonMotionPhase.d.ts +55 -0
  416. package/src/engine/control/first-person/FirstPersonMotionPhase.d.ts.map +1 -0
  417. package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -0
  418. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +23 -2
  419. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  420. package/src/engine/control/first-person/FirstPersonPlayerController.js +1 -1
  421. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +168 -0
  422. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  423. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +115 -0
  424. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +71 -0
  425. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  426. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +255 -55
  427. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +82 -43
  428. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
  429. package/src/engine/control/first-person/abilities/LedgeGrab.js +405 -213
  430. package/src/engine/control/first-person/abilities/Mantle.d.ts +6 -0
  431. package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
  432. package/src/engine/control/first-person/abilities/Mantle.js +104 -45
  433. package/src/engine/control/first-person/abilities/ScrambleUp.d.ts +61 -0
  434. package/src/engine/control/first-person/abilities/ScrambleUp.d.ts.map +1 -0
  435. package/src/engine/control/first-person/abilities/ScrambleUp.js +182 -0
  436. package/src/engine/control/first-person/math/jumpDynamics.d.ts +84 -0
  437. package/src/engine/control/first-person/math/jumpDynamics.d.ts.map +1 -0
  438. package/src/engine/control/first-person/math/jumpDynamics.js +108 -0
  439. package/src/engine/control/first-person/prototype_first_person_controller.js +45 -1
  440. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +1 -1
  441. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +1 -1
  442. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +19 -22
  443. package/src/engine/graphics/FrameThrottle.d.ts +1 -7
  444. package/src/engine/graphics/FrameThrottle.d.ts.map +1 -1
  445. package/src/engine/graphics/FrameThrottle.js +2 -24
  446. package/src/engine/graphics/camera/testClippingPlaneComputation.js +1 -1
  447. package/src/{core/geom/3d/shape/util → engine/graphics/debug}/shape_to_visual_entity.d.ts +1 -1
  448. package/src/engine/graphics/debug/shape_to_visual_entity.d.ts.map +1 -0
  449. package/src/{core/geom/3d/shape/util → engine/graphics/debug}/shape_to_visual_entity.js +159 -159
  450. package/src/{core/geom/3d/tetrahedra → engine/graphics/debug}/visualize_tetrahedral_mesh.d.ts +1 -1
  451. package/src/engine/graphics/debug/visualize_tetrahedral_mesh.d.ts.map +1 -0
  452. package/src/{core/geom/3d/tetrahedra → engine/graphics/debug}/visualize_tetrahedral_mesh.js +46 -46
  453. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.d.ts +1 -1
  454. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.d.ts.map +1 -1
  455. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -32
  456. package/src/engine/graphics/ecs/path/tube/prototypeAnimatedPathMask.js +1 -1
  457. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParameterLookupTableSerializationAdapter.d.ts.map +1 -1
  458. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParameterLookupTableSerializationAdapter.js +2 -76
  459. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.d.ts.map +1 -1
  460. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -427
  461. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.d.ts +1 -4
  462. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.d.ts.map +1 -1
  463. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -6
  464. package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +2 -2
  465. package/src/engine/graphics/render/buffer/buffers/prototypeNormalFrameBuffer.js +1 -1
  466. package/src/engine/graphics/render/forward_plus/plugin/ptototypeFPPlugin.js +1 -1
  467. package/src/engine/graphics/render/forward_plus/read_frustum_corner.d.ts +1 -8
  468. package/src/engine/graphics/render/forward_plus/read_frustum_corner.d.ts.map +1 -1
  469. package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -14
  470. package/src/engine/graphics/render/visibility/hiz/prototypeHiZ.js +1 -1
  471. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.d.ts +1 -11
  472. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.d.ts.map +1 -1
  473. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -46
  474. package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +1 -1
  475. package/src/engine/graphics/sh3/prototypeSH3Probe.js +1 -1
  476. package/src/engine/graphics/shadows/testShadowMapRendering.js +1 -1
  477. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.d.ts +27 -0
  478. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.d.ts.map +1 -0
  479. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -0
  480. package/src/engine/graphics/texture/isImageBitmap.d.ts +1 -6
  481. package/src/engine/graphics/texture/isImageBitmap.d.ts.map +1 -1
  482. package/src/engine/graphics/texture/isImageBitmap.js +2 -12
  483. package/src/{core/process/action → engine/intelligence/behavior/util}/AsynchronousDelayAction.d.ts +2 -2
  484. package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.d.ts.map +1 -0
  485. package/src/{core/process/action → engine/intelligence/behavior/util}/AsynchronousDelayAction.js +55 -52
  486. package/src/engine/network/NetworkSession.d.ts +12 -1
  487. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  488. package/src/engine/network/NetworkSession.js +52 -1
  489. package/src/engine/network/README.md +45 -0
  490. package/src/engine/network/convertPathToURL.d.ts +1 -8
  491. package/src/engine/network/convertPathToURL.d.ts.map +1 -1
  492. package/src/engine/network/convertPathToURL.js +2 -107
  493. package/src/engine/network/core/quantize/quantize_float.d.ts.map +1 -1
  494. package/src/engine/network/core/quantize/quantize_float.js +7 -0
  495. package/src/engine/network/core/quantize/quantize_position.d.ts.map +1 -1
  496. package/src/engine/network/core/quantize/quantize_position.js +12 -1
  497. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  498. package/src/engine/network/orchestrator/NetworkPeer.js +15 -1
  499. package/src/engine/network/replication/Replicator.d.ts +8 -0
  500. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  501. package/src/engine/network/replication/Replicator.js +48 -0
  502. package/src/engine/network/transport/Channel.d.ts.map +1 -1
  503. package/src/engine/network/transport/Channel.js +46 -12
  504. package/src/engine/network/transport/ReliableCommandPipeline.d.ts +16 -0
  505. package/src/engine/network/transport/ReliableCommandPipeline.d.ts.map +1 -1
  506. package/src/engine/network/transport/ReliableCommandPipeline.js +29 -0
  507. package/src/engine/network/transport/adapters/NodeUDPTransport.d.ts.map +1 -1
  508. package/src/engine/network/transport/adapters/NodeUDPTransport.js +7 -1
  509. package/src/engine/network/transport/fragments/packet_size.d.ts +5 -5
  510. package/src/engine/network/transport/fragments/packet_size.d.ts.map +1 -1
  511. package/src/engine/network/transport/fragments/packet_size.js +5 -5
  512. package/src/engine/physics/BULLET_REVIEW.md +1 -1
  513. package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -0
  514. package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -0
  515. package/src/engine/physics/JOLT_REVIEW.md +2 -2
  516. package/src/engine/physics/PLAN.md +1094 -944
  517. package/src/engine/physics/RAPIER_REVIEW.md +2 -2
  518. package/src/engine/physics/body/BodyStorage.d.ts +2 -12
  519. package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
  520. package/src/engine/physics/body/BodyStorage.js +406 -452
  521. package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -1
  522. package/src/engine/physics/body/SolverBodyState.js +12 -3
  523. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts +28 -3
  524. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts.map +1 -1
  525. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +60 -24
  526. package/src/engine/physics/broadphase/generate_pairs.d.ts +9 -5
  527. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  528. package/src/engine/physics/broadphase/generate_pairs.js +52 -37
  529. package/src/engine/physics/ccd/linear_sweep.d.ts +15 -5
  530. package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -1
  531. package/src/engine/physics/ccd/linear_sweep.js +122 -40
  532. package/src/engine/physics/constraint/solve_constraints.d.ts +4 -1
  533. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  534. package/src/engine/physics/constraint/solve_constraints.js +830 -691
  535. package/src/engine/physics/contact/ManifoldStore.d.ts +91 -16
  536. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  537. package/src/engine/physics/contact/ManifoldStore.js +204 -60
  538. package/src/engine/physics/ecs/BodyKind.d.ts +7 -3
  539. package/src/engine/physics/ecs/BodyKind.d.ts.map +1 -1
  540. package/src/engine/physics/ecs/BodyKind.js +29 -25
  541. package/src/engine/physics/ecs/Collider.d.ts +7 -0
  542. package/src/engine/physics/ecs/Collider.d.ts.map +1 -1
  543. package/src/engine/physics/ecs/Collider.js +7 -0
  544. package/src/engine/physics/ecs/ColliderSerializationAdapter.js +1 -1
  545. package/src/engine/physics/ecs/PhysicsSystem.d.ts +110 -6
  546. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  547. package/src/engine/physics/ecs/PhysicsSystem.js +2172 -1747
  548. package/src/engine/physics/ecs/RigidBody.d.ts +20 -5
  549. package/src/engine/physics/ecs/RigidBody.d.ts.map +1 -1
  550. package/src/engine/physics/ecs/RigidBody.js +307 -286
  551. package/src/engine/physics/ecs/RigidBodyFlags.d.ts +6 -3
  552. package/src/engine/physics/ecs/RigidBodyFlags.d.ts.map +1 -1
  553. package/src/engine/physics/ecs/RigidBodyFlags.js +31 -28
  554. package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts +12 -4
  555. package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts.map +1 -1
  556. package/src/engine/physics/ecs/RigidBodySerializationAdapter.js +19 -5
  557. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.d.ts +10 -0
  558. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.d.ts.map +1 -0
  559. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -0
  560. package/src/engine/physics/ecs/find_non_finite_physics_state.d.ts +28 -0
  561. package/src/engine/physics/ecs/find_non_finite_physics_state.d.ts.map +1 -0
  562. package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -0
  563. package/src/engine/physics/events/ContactEventBuffer.d.ts +11 -0
  564. package/src/engine/physics/events/ContactEventBuffer.d.ts.map +1 -1
  565. package/src/engine/physics/events/ContactEventBuffer.js +40 -0
  566. package/src/engine/physics/events/diff_manifolds.d.ts +30 -13
  567. package/src/engine/physics/events/diff_manifolds.d.ts.map +1 -1
  568. package/src/engine/physics/events/diff_manifolds.js +87 -50
  569. package/src/engine/physics/fluid/FluidField.d.ts +45 -17
  570. package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
  571. package/src/engine/physics/fluid/FluidField.js +53 -23
  572. package/src/engine/physics/fluid/FluidSimulator.d.ts +141 -5
  573. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  574. package/src/engine/physics/fluid/FluidSimulator.js +336 -43
  575. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +114 -0
  576. package/src/engine/physics/fluid/ecs/FluidComponent.d.ts +4 -3
  577. package/src/engine/physics/fluid/ecs/FluidComponent.d.ts.map +1 -1
  578. package/src/engine/physics/fluid/ecs/FluidComponent.js +4 -3
  579. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
  580. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts +41 -0
  581. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts.map +1 -0
  582. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.js +124 -0
  583. package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts +27 -8
  584. package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts.map +1 -1
  585. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +67 -18
  586. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.d.ts +42 -0
  587. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.d.ts.map +1 -0
  588. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +136 -0
  589. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.d.ts +37 -0
  590. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.d.ts.map +1 -0
  591. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +169 -0
  592. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.d.ts +36 -0
  593. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.d.ts.map +1 -0
  594. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +100 -0
  595. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.d.ts +6 -0
  596. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.d.ts.map +1 -1
  597. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.js +6 -0
  598. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.d.ts +7 -2
  599. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.d.ts.map +1 -1
  600. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.js +17 -12
  601. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.d.ts +42 -0
  602. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.d.ts.map +1 -0
  603. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -0
  604. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts +32 -22
  605. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts.map +1 -1
  606. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +43 -26
  607. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.d.ts +31 -0
  608. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.d.ts.map +1 -0
  609. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -0
  610. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts +26 -19
  611. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts.map +1 -1
  612. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +46 -42
  613. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts +38 -10
  614. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts.map +1 -1
  615. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +158 -75
  616. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +22 -17
  617. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
  618. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -96
  619. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +30 -1
  620. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  621. package/src/engine/physics/inertia/world_inverse_inertia.js +160 -116
  622. package/src/engine/physics/integration/integrate_position.js +97 -97
  623. package/src/engine/physics/island/IslandBuilder.d.ts +49 -8
  624. package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
  625. package/src/engine/physics/island/IslandBuilder.js +93 -14
  626. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  627. package/src/engine/physics/narrowphase/box_box_manifold.js +683 -673
  628. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
  629. package/src/engine/physics/narrowphase/box_triangle_contact.js +899 -749
  630. package/src/engine/physics/narrowphase/capsule_contacts.d.ts +27 -0
  631. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  632. package/src/engine/physics/narrowphase/capsule_contacts.js +624 -459
  633. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -1
  634. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +58 -38
  635. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
  636. package/src/engine/physics/narrowphase/compute_penetration.js +369 -325
  637. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts +3 -1
  638. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
  639. package/src/engine/physics/narrowphase/convex_convex_manifold.js +568 -425
  640. package/src/engine/physics/narrowphase/convex_decomposition.d.ts +32 -13
  641. package/src/engine/physics/narrowphase/convex_decomposition.d.ts.map +1 -1
  642. package/src/engine/physics/narrowphase/convex_decomposition.js +61 -65
  643. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +6 -3
  644. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -1
  645. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +66 -10
  646. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +4 -1
  647. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -1
  648. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +97 -94
  649. package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts.map +1 -1
  650. package/src/engine/physics/narrowphase/mesh_convex_hull.js +13 -8
  651. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
  652. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  653. package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1739
  654. package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts +14 -7
  655. package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts.map +1 -1
  656. package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -69
  657. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
  658. package/src/engine/physics/narrowphase/refine_ray_concave.js +5 -3
  659. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  660. package/src/engine/physics/narrowphase/refine_ray_hit.js +81 -78
  661. package/src/engine/physics/persistence/solver_caches.d.ts +20 -0
  662. package/src/engine/physics/persistence/solver_caches.d.ts.map +1 -0
  663. package/src/engine/physics/persistence/solver_caches.js +309 -0
  664. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -1
  665. package/src/engine/physics/queries/overlap_shape.js +187 -184
  666. package/src/engine/physics/queries/raycast.d.ts +3 -2
  667. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  668. package/src/engine/physics/queries/raycast.js +37 -11
  669. package/src/engine/physics/queries/shape_cast.d.ts +18 -5
  670. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -1
  671. package/src/engine/physics/queries/shape_cast.js +417 -393
  672. package/src/engine/physics/solver/solve_contacts.d.ts +22 -6
  673. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  674. package/src/engine/physics/solver/solve_contacts.js +1482 -1338
  675. package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -1
  676. package/src/engine/physics/vehicle/RaycastVehicle.js +344 -339
  677. package/src/engine/sound/SoundEngine.d.ts.map +1 -1
  678. package/src/engine/sound/SoundEngine.js +28 -0
  679. package/src/engine/sound/dB2Volume.d.ts +1 -1
  680. package/src/engine/sound/dB2Volume.d.ts.map +1 -1
  681. package/src/engine/sound/dB2Volume.js +1 -1
  682. package/src/engine/sound/ecs/SoundController.d.ts +4 -0
  683. package/src/engine/sound/ecs/SoundController.d.ts.map +1 -1
  684. package/src/engine/sound/ecs/SoundController.js +4 -0
  685. package/src/engine/sound/ecs/SoundControllerSystem.d.ts +5 -0
  686. package/src/engine/sound/ecs/SoundControllerSystem.d.ts.map +1 -1
  687. package/src/engine/sound/ecs/SoundControllerSystem.js +5 -0
  688. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts +69 -0
  689. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts.map +1 -0
  690. package/src/engine/sound/ecs/audio/AudioEmitter.js +83 -0
  691. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts +97 -0
  692. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts.map +1 -0
  693. package/src/engine/sound/ecs/audio/AudioEmitterSystem.js +238 -0
  694. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts +90 -0
  695. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts.map +1 -0
  696. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +324 -0
  697. package/src/engine/sound/ecs/audio/SpatialAudioIndex.d.ts +59 -0
  698. package/src/engine/sound/ecs/audio/SpatialAudioIndex.d.ts.map +1 -0
  699. package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -0
  700. package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts +16 -65
  701. package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts.map +1 -1
  702. package/src/engine/sound/ecs/emitter/SoundEmitter.js +19 -224
  703. package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.d.ts +26 -29
  704. package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.d.ts.map +1 -1
  705. package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.js +168 -135
  706. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts +36 -59
  707. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts.map +1 -1
  708. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.js +154 -390
  709. package/src/engine/sound/ecs/emitter/SoundTrack.d.ts +20 -23
  710. package/src/engine/sound/ecs/emitter/SoundTrack.d.ts.map +1 -1
  711. package/src/engine/sound/ecs/emitter/SoundTrack.js +34 -152
  712. package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +993 -0
  713. package/src/engine/sound/sopra/README.md +643 -7
  714. package/src/engine/sound/sopra/SopraEngine.d.ts +229 -0
  715. package/src/engine/sound/sopra/SopraEngine.d.ts.map +1 -0
  716. package/src/engine/sound/sopra/SopraEngine.js +423 -0
  717. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.d.ts +26 -0
  718. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.d.ts.map +1 -0
  719. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -0
  720. package/src/engine/sound/sopra/asset/BufferProvider.d.ts +24 -0
  721. package/src/engine/sound/sopra/asset/BufferProvider.d.ts.map +1 -0
  722. package/src/engine/sound/sopra/asset/BufferProvider.js +29 -0
  723. package/src/engine/sound/sopra/asset/StubBufferProvider.d.ts +31 -0
  724. package/src/engine/sound/sopra/asset/StubBufferProvider.d.ts.map +1 -0
  725. package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -0
  726. package/src/engine/sound/sopra/definition/BusDefinition.d.ts +83 -0
  727. package/src/engine/sound/sopra/definition/BusDefinition.d.ts.map +1 -0
  728. package/src/engine/sound/sopra/definition/BusDefinition.js +142 -0
  729. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts +17 -0
  730. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts.map +1 -0
  731. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -0
  732. package/src/engine/sound/sopra/definition/DuckingRule.d.ts +71 -0
  733. package/src/engine/sound/sopra/definition/DuckingRule.d.ts.map +1 -0
  734. package/src/engine/sound/sopra/definition/DuckingRule.js +106 -0
  735. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts +18 -0
  736. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts.map +1 -0
  737. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -0
  738. package/src/engine/sound/sopra/definition/EventDescription.d.ts +132 -0
  739. package/src/engine/sound/sopra/definition/EventDescription.d.ts.map +1 -0
  740. package/src/engine/sound/sopra/definition/EventDescription.js +259 -0
  741. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts +17 -0
  742. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts.map +1 -0
  743. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +71 -0
  744. package/src/engine/sound/sopra/definition/MixerSnapshot.d.ts +51 -0
  745. package/src/engine/sound/sopra/definition/MixerSnapshot.d.ts.map +1 -0
  746. package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -0
  747. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts +18 -0
  748. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts.map +1 -0
  749. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -0
  750. package/src/engine/sound/sopra/definition/ParameterDefinition.d.ts +72 -0
  751. package/src/engine/sound/sopra/definition/ParameterDefinition.d.ts.map +1 -0
  752. package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -0
  753. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts +18 -0
  754. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts.map +1 -0
  755. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -0
  756. package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts +14 -0
  757. package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts.map +1 -0
  758. package/src/engine/sound/sopra/definition/SopraPanningModel.js +20 -0
  759. package/src/engine/sound/sopra/definition/VoiceStealMode.d.ts +10 -0
  760. package/src/engine/sound/sopra/definition/VoiceStealMode.d.ts.map +1 -0
  761. package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -0
  762. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +93 -0
  763. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -0
  764. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -0
  765. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +80 -0
  766. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -0
  767. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +181 -0
  768. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts +17 -0
  769. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  770. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +74 -0
  771. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.d.ts +34 -0
  772. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.d.ts.map +1 -0
  773. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +100 -0
  774. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +101 -0
  775. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -0
  776. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +230 -0
  777. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts +17 -0
  778. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  779. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -0
  780. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +103 -0
  781. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -0
  782. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -0
  783. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts +18 -0
  784. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts.map +1 -0
  785. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -0
  786. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +40 -0
  787. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -0
  788. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +91 -0
  789. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts +17 -0
  790. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  791. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -0
  792. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.d.ts +44 -0
  793. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.d.ts.map +1 -0
  794. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -0
  795. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts +18 -0
  796. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts.map +1 -0
  797. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -0
  798. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +65 -0
  799. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -0
  800. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +131 -0
  801. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts +17 -0
  802. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  803. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -0
  804. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts +24 -0
  805. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts.map +1 -0
  806. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -0
  807. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts +70 -0
  808. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts.map +1 -0
  809. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -0
  810. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts +18 -0
  811. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts.map +1 -0
  812. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -0
  813. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts +74 -0
  814. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts.map +1 -0
  815. package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -0
  816. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts +18 -0
  817. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts.map +1 -0
  818. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -0
  819. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts +49 -0
  820. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts.map +1 -0
  821. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +101 -0
  822. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts +18 -0
  823. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts.map +1 -0
  824. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -0
  825. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts +31 -0
  826. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts.map +1 -0
  827. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.js +106 -0
  828. package/src/engine/sound/sopra/runtime/BusGraph.d.ts +79 -0
  829. package/src/engine/sound/sopra/runtime/BusGraph.d.ts.map +1 -0
  830. package/src/engine/sound/sopra/runtime/BusGraph.js +227 -0
  831. package/src/engine/sound/sopra/runtime/EventInstance.d.ts +144 -0
  832. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -0
  833. package/src/engine/sound/sopra/runtime/EventInstance.js +579 -0
  834. package/src/engine/sound/sopra/runtime/ParameterStore.d.ts +42 -0
  835. package/src/engine/sound/sopra/runtime/ParameterStore.d.ts.map +1 -0
  836. package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -0
  837. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.d.ts +42 -0
  838. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.d.ts.map +1 -0
  839. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -0
  840. package/src/engine/sound/sopra/runtime/Voice.d.ts +67 -0
  841. package/src/engine/sound/sopra/runtime/Voice.d.ts.map +1 -0
  842. package/src/engine/sound/sopra/runtime/Voice.js +145 -0
  843. package/src/engine/sound/sopra/runtime/VoiceManager.d.ts +38 -0
  844. package/src/engine/sound/sopra/runtime/VoiceManager.d.ts.map +1 -0
  845. package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -0
  846. package/src/engine/sound/sopra/runtime/VoicePool.d.ts +12 -0
  847. package/src/engine/sound/sopra/runtime/VoicePool.d.ts.map +1 -0
  848. package/src/engine/sound/sopra/runtime/VoicePool.js +17 -0
  849. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts +11 -0
  850. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts.map +1 -0
  851. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -0
  852. package/src/engine/sound/sopra/serialization/sopraJSON.d.ts +33 -0
  853. package/src/engine/sound/sopra/serialization/sopraJSON.d.ts.map +1 -0
  854. package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -0
  855. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts +27 -0
  856. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts.map +1 -0
  857. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -0
  858. package/src/engine/sound/sopra/util/MockAudioContext.d.ts +74 -0
  859. package/src/engine/sound/sopra/util/MockAudioContext.d.ts.map +1 -0
  860. package/src/engine/sound/sopra/util/MockAudioContext.js +215 -0
  861. package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts +15 -0
  862. package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts.map +1 -0
  863. package/src/engine/sound/sopra/util/buildAttenuationCurve.js +40 -0
  864. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts +34 -0
  865. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts.map +1 -0
  866. package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -0
  867. package/src/engine/sound/volume2dB.d.ts +1 -1
  868. package/src/engine/sound/volume2dB.d.ts.map +1 -1
  869. package/src/engine/sound/volume2dB.js +1 -1
  870. package/src/engine/ui/DraggableAspect.d.ts +12 -3
  871. package/src/engine/ui/DraggableAspect.d.ts.map +1 -1
  872. package/src/engine/ui/DraggableAspect.js +115 -83
  873. package/src/generation/COORDINATES.md +54 -0
  874. package/src/generation/GridTaskGroup.js +2 -2
  875. package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -0
  876. package/src/generation/automata/CaveGeneratorCellularAutomata.d.ts +9 -1
  877. package/src/generation/automata/CaveGeneratorCellularAutomata.d.ts.map +1 -1
  878. package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -59
  879. package/src/generation/automata/CellularAutomata.d.ts +6 -3
  880. package/src/generation/automata/CellularAutomata.d.ts.map +1 -1
  881. package/src/generation/automata/CellularAutomata.js +22 -19
  882. package/src/generation/filtering/CellFilter.d.ts +17 -0
  883. package/src/generation/filtering/CellFilter.d.ts.map +1 -1
  884. package/src/generation/filtering/CellFilter.js +117 -77
  885. package/src/generation/filtering/CellFilterCellMatcher.d.ts.map +1 -1
  886. package/src/generation/filtering/CellFilterCellMatcher.js +2 -0
  887. package/src/generation/filtering/boolean/CellFilterLiteralBoolean.d.ts +5 -0
  888. package/src/generation/filtering/boolean/CellFilterLiteralBoolean.d.ts.map +1 -1
  889. package/src/generation/filtering/boolean/CellFilterLiteralBoolean.js +15 -0
  890. package/src/generation/filtering/core/CellFilterBinaryOperation.d.ts +0 -1
  891. package/src/generation/filtering/core/CellFilterBinaryOperation.d.ts.map +1 -1
  892. package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -50
  893. package/src/generation/filtering/core/CellFilterOperationTertiary.d.ts +0 -1
  894. package/src/generation/filtering/core/CellFilterOperationTertiary.d.ts.map +1 -1
  895. package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -59
  896. package/src/generation/filtering/core/CellFilterUnaryOperation.d.ts +0 -1
  897. package/src/generation/filtering/core/CellFilterUnaryOperation.d.ts.map +1 -1
  898. package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -33
  899. package/src/generation/filtering/numeric/CellFilterCache.d.ts +1 -0
  900. package/src/generation/filtering/numeric/CellFilterCache.d.ts.map +1 -1
  901. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.d.ts +3 -2
  902. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.d.ts.map +1 -1
  903. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.js +9 -35
  904. package/src/generation/filtering/numeric/complex/CellFilterCurvature.d.ts +0 -1
  905. package/src/generation/filtering/numeric/complex/CellFilterCurvature.d.ts.map +1 -1
  906. package/src/generation/filtering/numeric/complex/CellFilterCurvature.js +19 -43
  907. package/src/generation/filtering/numeric/complex/CellFilterFXAA.d.ts +0 -1
  908. package/src/generation/filtering/numeric/complex/CellFilterFXAA.d.ts.map +1 -1
  909. package/src/generation/filtering/numeric/complex/CellFilterFXAA.js +2 -6
  910. package/src/generation/filtering/numeric/complex/CellFilterGaussianBlur.d.ts.map +1 -1
  911. package/src/generation/filtering/numeric/complex/CellFilterGaussianBlur.js +9 -12
  912. package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.d.ts.map +1 -1
  913. package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.js +2 -1
  914. package/src/generation/filtering/numeric/complex/CellFilterSobel.d.ts +0 -1
  915. package/src/generation/filtering/numeric/complex/CellFilterSobel.d.ts.map +1 -1
  916. package/src/generation/filtering/numeric/complex/CellFilterSobel.js +2 -6
  917. package/src/generation/filtering/numeric/math/CellFilterInverseLerp.d.ts +5 -4
  918. package/src/generation/filtering/numeric/math/CellFilterInverseLerp.d.ts.map +1 -1
  919. package/src/generation/filtering/numeric/math/CellFilterInverseLerp.js +5 -4
  920. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.d.ts +17 -0
  921. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.d.ts.map +1 -0
  922. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -0
  923. package/src/generation/filtering/numeric/sampling/AbstractCellFilterSampleGridLayer.d.ts.map +1 -1
  924. package/src/generation/filtering/numeric/sampling/AbstractCellFilterSampleGridLayer.js +7 -1
  925. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.d.ts.map +1 -1
  926. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +7 -10
  927. package/src/generation/filtering/numeric/util/sampler_from_filter.d.ts.map +1 -1
  928. package/src/generation/filtering/numeric/util/sampler_from_filter.js +2 -1
  929. package/src/generation/grid/GridData.d.ts.map +1 -1
  930. package/src/generation/grid/GridData.js +14 -1
  931. package/src/generation/grid/actions/ContinuousGridCellAction.d.ts +10 -3
  932. package/src/generation/grid/actions/ContinuousGridCellAction.d.ts.map +1 -1
  933. package/src/generation/grid/actions/ContinuousGridCellAction.js +18 -3
  934. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainHeight.d.ts +11 -1
  935. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainHeight.d.ts.map +1 -1
  936. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainHeight.js +13 -3
  937. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainObstacle.d.ts +1 -1
  938. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainObstacle.js +2 -2
  939. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.d.ts +1 -1
  940. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.d.ts.map +1 -1
  941. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.js +4 -6
  942. package/src/generation/grid/coords/grid_to_texel.d.ts +9 -0
  943. package/src/generation/grid/coords/grid_to_texel.d.ts.map +1 -0
  944. package/src/generation/grid/coords/grid_to_texel.js +10 -0
  945. package/src/generation/grid/coords/texel_to_grid.d.ts +9 -0
  946. package/src/generation/grid/coords/texel_to_grid.d.ts.map +1 -0
  947. package/src/generation/grid/coords/texel_to_grid.js +10 -0
  948. package/src/generation/grid/generation/GridTaskApplyActionToCells.d.ts +2 -2
  949. package/src/generation/grid/generation/GridTaskApplyActionToCells.d.ts.map +1 -1
  950. package/src/generation/grid/generation/GridTaskApplyActionToCells.js +10 -6
  951. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.d.ts.map +1 -1
  952. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +20 -21
  953. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.d.ts +7 -0
  954. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.d.ts.map +1 -1
  955. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +18 -10
  956. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.d.ts.map +1 -1
  957. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +16 -7
  958. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.d.ts +5 -3
  959. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.d.ts.map +1 -1
  960. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +26 -23
  961. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.d.ts.map +1 -1
  962. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +10 -1
  963. package/src/generation/grid/generation/grid/select/CellSupplierBestN.d.ts.map +1 -1
  964. package/src/generation/grid/generation/grid/select/CellSupplierBestN.js +4 -0
  965. package/src/generation/grid/generation/road/GridTaskGenerateRoads.d.ts +15 -8
  966. package/src/generation/grid/generation/road/GridTaskGenerateRoads.d.ts.map +1 -1
  967. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +89 -92
  968. package/src/generation/markers/GridActionRuleSet.d.ts.map +1 -1
  969. package/src/generation/markers/GridActionRuleSet.js +10 -2
  970. package/src/generation/markers/GridCellActionPlaceMarker.d.ts +11 -0
  971. package/src/generation/markers/GridCellActionPlaceMarker.d.ts.map +1 -1
  972. package/src/generation/markers/GridCellActionPlaceMarker.js +20 -3
  973. package/src/generation/markers/GridCellActionPlaceMarkerGroup.d.ts +3 -1
  974. package/src/generation/markers/GridCellActionPlaceMarkerGroup.d.ts.map +1 -1
  975. package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +9 -2
  976. package/src/generation/markers/MarkerNode.d.ts +8 -3
  977. package/src/generation/markers/MarkerNode.d.ts.map +1 -1
  978. package/src/generation/markers/MarkerNode.js +12 -5
  979. package/src/generation/markers/actions/MarkerNodeActionEntityPlacement.js +1 -1
  980. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessor.d.ts +1 -1
  981. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessor.d.ts.map +1 -1
  982. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessor.js +1 -1
  983. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorClingToTerrain.d.ts +1 -1
  984. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorClingToTerrain.d.ts.map +1 -1
  985. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorClingToTerrain.js +1 -1
  986. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.d.ts +1 -1
  987. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.d.ts.map +1 -1
  988. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.js +2 -2
  989. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorSequence.d.ts +1 -1
  990. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorSequence.d.ts.map +1 -1
  991. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorSequence.js +2 -2
  992. package/src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.d.ts.map +1 -1
  993. package/src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.js +6 -4
  994. package/src/generation/markers/actions/probability/MarkerNodeActionWeightedElement.d.ts.map +1 -1
  995. package/src/generation/markers/actions/probability/MarkerNodeActionWeightedElement.js +1 -3
  996. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.d.ts.map +1 -1
  997. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +12 -11
  998. package/src/generation/markers/matcher/MarkerNodeMatcherAnd.js +2 -2
  999. package/src/generation/markers/transform/MarkerNodeTransformer.d.ts +4 -1
  1000. package/src/generation/markers/transform/MarkerNodeTransformer.d.ts.map +1 -1
  1001. package/src/generation/markers/transform/MarkerNodeTransformer.js +4 -1
  1002. package/src/generation/markers/transform/MarkerNodeTransformerAddPositionYFromFilter.d.ts.map +1 -1
  1003. package/src/generation/markers/transform/MarkerNodeTransformerAddPositionYFromFilter.js +1 -3
  1004. package/src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.d.ts +5 -0
  1005. package/src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.d.ts.map +1 -1
  1006. package/src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.js +15 -0
  1007. package/src/generation/markers/transform/MarkerNodeTransformerRecordProperty.d.ts.map +1 -1
  1008. package/src/generation/markers/transform/MarkerNodeTransformerRecordProperty.js +1 -3
  1009. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilter.d.ts.map +1 -1
  1010. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilter.js +2 -4
  1011. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilterGradient.d.ts.map +1 -1
  1012. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilterGradient.js +1 -3
  1013. package/src/generation/placement/GridCellPlacementRule.d.ts.map +1 -1
  1014. package/src/generation/placement/GridCellPlacementRule.js +1 -3
  1015. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.d.ts.map +1 -1
  1016. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +8 -10
  1017. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.d.ts.map +1 -1
  1018. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +6 -4
  1019. package/src/generation/placement/action/random/weighted/WeightedGridCellAction.d.ts.map +1 -1
  1020. package/src/generation/placement/action/random/weighted/WeightedGridCellAction.js +1 -3
  1021. package/src/generation/rules/CellMatcher.d.ts +3 -1
  1022. package/src/generation/rules/CellMatcher.d.ts.map +1 -1
  1023. package/src/generation/rules/CellMatcher.js +3 -1
  1024. package/src/generation/rules/CellMatcherFromFilter.d.ts.map +1 -1
  1025. package/src/generation/rules/CellMatcherFromFilter.js +1 -3
  1026. package/src/generation/rules/CellMatcherLayerBitMaskTest.d.ts.map +1 -1
  1027. package/src/generation/rules/CellMatcherLayerBitMaskTest.js +6 -20
  1028. package/src/generation/test_support/executeTaskTreeSync.d.ts +9 -0
  1029. package/src/generation/test_support/executeTaskTreeSync.d.ts.map +1 -0
  1030. package/src/generation/test_support/executeTaskTreeSync.js +78 -0
  1031. package/src/generation/theme/TerrainLayerRuleAggregator.d.ts +2 -1
  1032. package/src/generation/theme/TerrainLayerRuleAggregator.d.ts.map +1 -1
  1033. package/src/generation/theme/TerrainLayerRuleAggregator.js +9 -6
  1034. package/src/generation/theme/Theme.d.ts +1 -1
  1035. package/src/generation/theme/Theme.d.ts.map +1 -1
  1036. package/src/generation/theme/Theme.js +2 -2
  1037. package/src/generation/theme/ThemeEngine.d.ts +3 -3
  1038. package/src/generation/theme/ThemeEngine.d.ts.map +1 -1
  1039. package/src/generation/theme/ThemeEngine.js +26 -16
  1040. package/src/generation/theme/cell/CellProcessingRule.d.ts +3 -3
  1041. package/src/generation/theme/cell/CellProcessingRule.d.ts.map +1 -1
  1042. package/src/generation/theme/cell/CellProcessingRule.js +6 -10
  1043. package/src/generation/theme/cell/CellProcessingRuleSet.d.ts +1 -1
  1044. package/src/generation/theme/cell/CellProcessingRuleSet.d.ts.map +1 -1
  1045. package/src/generation/theme/cell/CellProcessingRuleSet.js +2 -2
  1046. package/src/view/common/ListView.js +1 -1
  1047. package/src/view/elements/BottomLeftResizeHandleView.d.ts.map +1 -1
  1048. package/src/view/elements/BottomLeftResizeHandleView.js +13 -5
  1049. package/src/core/font/FontAsset.d.ts.map +0 -1
  1050. package/src/core/font/FontAssetLoader.d.ts.map +0 -1
  1051. package/src/core/geom/3d/shape/util/shape_to_visual_entity.d.ts.map +0 -1
  1052. package/src/core/geom/3d/tetrahedra/visualize_tetrahedral_mesh.d.ts.map +0 -1
  1053. package/src/core/process/action/AsynchronousDelayAction.d.ts.map +0 -1
  1054. package/src/engine/graphics/sh3/path_tracer/sampling/v3_orthonormal_matrix_from_normal.d.ts.map +0 -1
  1055. package/src/engine/physics/computeInterceptPoint.d.ts.map +0 -1
  1056. package/src/engine/physics/fluid/solver/optimal_sor_omega.d.ts.map +0 -1
  1057. package/src/engine/physics/gjk/gjk.d.ts.map +0 -1
  1058. package/src/engine/physics/gjk/gjk_epa_penetration.d.ts.map +0 -1
  1059. package/src/engine/physics/gjk/minkowski_support.d.ts.map +0 -1
  1060. package/src/engine/physics/gjk/mpr.d.ts.map +0 -1
  1061. package/src/engine/physics/integration/quat_integrate.d.ts.map +0 -1
  1062. package/src/engine/physics/island/union_find.d.ts.map +0 -1
  1063. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +0 -1
  1064. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +0 -1
  1065. package/src/engine/physics/narrowphase/ray_shapes.d.ts +0 -66
  1066. package/src/engine/physics/narrowphase/ray_shapes.d.ts.map +0 -1
  1067. package/src/engine/physics/narrowphase/ray_shapes.js +0 -187
  1068. package/src/engine/sound/ecs/emitter/SoundEmitterChannel.d.ts +0 -23
  1069. package/src/engine/sound/ecs/emitter/SoundEmitterChannel.d.ts.map +0 -1
  1070. package/src/engine/sound/ecs/emitter/SoundEmitterChannel.js +0 -32
  1071. package/src/engine/sound/ecs/emitter/SoundTrackNodes.d.ts +0 -18
  1072. package/src/engine/sound/ecs/emitter/SoundTrackNodes.d.ts.map +0 -1
  1073. package/src/engine/sound/ecs/emitter/SoundTrackNodes.js +0 -18
  1074. package/src/engine/sound/sopra/AbstractAudioClip.d.ts +0 -26
  1075. package/src/engine/sound/sopra/AbstractAudioClip.d.ts.map +0 -1
  1076. package/src/engine/sound/sopra/AbstractAudioClip.js +0 -29
  1077. package/src/engine/sound/sopra/ContainerAudioClip.d.ts +0 -12
  1078. package/src/engine/sound/sopra/ContainerAudioClip.d.ts.map +0 -1
  1079. package/src/engine/sound/sopra/ContainerAudioClip.js +0 -13
  1080. package/src/engine/sound/sopra/RandomContainerAudioClip.d.ts +0 -12
  1081. package/src/engine/sound/sopra/RandomContainerAudioClip.d.ts.map +0 -1
  1082. package/src/engine/sound/sopra/RandomContainerAudioClip.js +0 -15
  1083. package/src/engine/sound/sopra/SequenceContainerAudioClip.d.ts +0 -7
  1084. package/src/engine/sound/sopra/SequenceContainerAudioClip.d.ts.map +0 -1
  1085. package/src/engine/sound/sopra/SequenceContainerAudioClip.js +0 -8
  1086. package/src/engine/sound/sopra/SilenceAudioClip.d.ts +0 -13
  1087. package/src/engine/sound/sopra/SilenceAudioClip.d.ts.map +0 -1
  1088. package/src/engine/sound/sopra/SilenceAudioClip.js +0 -15
  1089. package/src/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.d.ts +0 -21
  1090. package/src/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.d.ts.map +0 -1
  1091. package/src/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.js +0 -68
  1092. package/src/generation/grid/generation/grid/GridTaskGridAlignedNodeGenerator.d.ts +0 -10
  1093. package/src/generation/grid/generation/grid/GridTaskGridAlignedNodeGenerator.d.ts.map +0 -1
  1094. package/src/generation/grid/generation/grid/GridTaskGridAlignedNodeGenerator.js +0 -17
  1095. /package/src/{engine/physics → core/geom/3d}/gjk/NOTES.md +0 -0
  1096. /package/src/{engine/physics → core/geom/3d}/gjk/gjk.d.ts +0 -0
  1097. /package/src/{engine/graphics/sh3/path_tracer/sampling → core/geom/vec3}/v3_orthonormal_matrix_from_normal.d.ts +0 -0
  1098. /package/src/{engine/physics → core/math/physics/kinematics}/computeInterceptPoint.d.ts +0 -0
@@ -1,1739 +1,1738 @@
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 { combine_friction, combine_restitution } from "../contact/combine_material.js";
5
- import { CONTACT_STRIDE, MAX_CONTACTS_PER_MANIFOLD } from "../contact/ManifoldStore.js";
6
- import { gjk_epa_penetration } from "../gjk/gjk_epa_penetration.js";
7
- import { mpr } from "../gjk/mpr.js";
8
- import { box_box_manifold, BOX_BOX_OUT_LENGTH } from "./box_box_manifold.js";
9
- import { convex_hull_clip, CONVEX_CONVEX_OUT_LENGTH } from "./convex_convex_manifold.js";
10
- import { get_mesh_convex_hull } from "./mesh_convex_hull.js";
11
- import { mesh_mesh_tet_contacts } from "./mesh_mesh_tet_manifold.js";
12
- import { box_triangle_contact, BOX_TRIANGLE_OUT_LENGTH } from "./box_triangle_contact.js";
13
- import {
14
- CAPSULE_BOX_CONTACT_STRIDE,
15
- CAPSULE_BOX_MAX_CONTACTS,
16
- capsule_box_multi_contacts,
17
- capsule_capsule_contact,
18
- capsule_sphere_contact,
19
- } from "./capsule_contacts.js";
20
- import {
21
- capsule_triangle_contact,
22
- CAPSULE_TRIANGLE_CONTACT_STRIDE,
23
- CAPSULE_TRIANGLE_MAX_CONTACTS,
24
- } from "./capsule_triangle_contact.js";
25
- import { aabb_world_to_local } from "./decomposition/aabb_world_to_local.js";
26
- import { decompose_to_triangles } from "./decomposition/decompose_to_triangles.js";
27
- import { TRIANGLE_FLOAT_STRIDE } from "./decomposition/triangle_buffer_layout.js";
28
- import { PosedShape } from "./PosedShape.js";
29
- import { sphere_box_contact } from "./sphere_box_contact.js";
30
- import { sphere_sphere_contact } from "./sphere_sphere_contact.js";
31
- import { sphere_triangle_contact } from "./sphere_triangle_contact.js";
32
-
33
- const posed_a = new PosedShape();
34
- const posed_b = new PosedShape();
35
-
36
- // Penetration axis + depth from the robust GJK + EPA query
37
- // (gjk_epa_penetration writes a unit normal here; it owns its own simplex
38
- // internally, so the narrowphase no longer keeps a simplex buffer).
39
- const epa_result = new Float64Array(3);
40
- const sphere_result = new Float64Array(4);
41
- const closed_form_result = new Float64Array(10);
42
- const sphere_triangle_result = new Float64Array(10);
43
- const box_triangle_result = new Float64Array(BOX_TRIANGLE_OUT_LENGTH);
44
- const capsule_triangle_result = new Float64Array(CAPSULE_TRIANGLE_MAX_CONTACTS * CAPSULE_TRIANGLE_CONTACT_STRIDE);
45
- const box_manifold_result = new Float64Array(BOX_BOX_OUT_LENGTH);
46
- const convex_manifold_result = new Float64Array(CONVEX_CONVEX_OUT_LENGTH);
47
- const capsule_box_multi_result = new Float64Array(CAPSULE_BOX_MAX_CONTACTS * CAPSULE_BOX_CONTACT_STRIDE);
48
-
49
- /**
50
- * Candidate-contact stride: wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth,
51
- * feature_id, friction, restitution.
52
- *
53
- * The `feature_id` (offset 10) is a stable cross-frame identifier of the
54
- * geometric feature pair that produced this contact — used by the
55
- * match-and-merge pass in {@link narrowphase_step} to carry warm-start
56
- * impulses from the previous frame's manifold to the slot index that
57
- * corresponds to the same physical contact. A value of 0 means
58
- * "no feature info, fall back to position matching".
59
- *
60
- * `friction` (offset 11) and `restitution` (offset 12) are the COMBINED
61
- * coefficients for the specific (colliderA, colliderB) pair that produced this
62
- * contact, combined here (the only place that knows the exact source collider
63
- * on each side) and carried into the manifold so a compound body's per-collider
64
- * materials are honoured per-contact.
65
- *
66
- * @type {number}
67
- */
68
- const CANDIDATE_STRIDE = 13;
69
-
70
- /**
71
- * Combined friction / restitution for the collider pair currently being
72
- * dispatched. Set once at the top of {@link dispatch_pair} (which is called
73
- * per collider pair) and written into every contact that call appends, so
74
- * each contact carries the material of its actual source colliders. Module
75
- * scratch rather than threaded through every `append_contact` call site.
76
- * @type {number}
77
- */
78
- let g_pair_friction = 0;
79
- let g_pair_restitution = 0;
80
-
81
- /**
82
- * Maximum number of contacts emitted into the per-pair manifold after the
83
- * reduction step. Mirrors {@link MAX_CONTACTS_PER_MANIFOLD} in ManifoldStore.
84
- * @type {number}
85
- */
86
- const MAX_KEPT = MAX_CONTACTS_PER_MANIFOLD;
87
-
88
- /**
89
- * Position-fallback tolerance for warm-start matching: when a candidate
90
- * contact has no feature id (or none of the previous-frame contacts shares
91
- * its id), match by closest world_a within this 3-D distance.
92
- *
93
- * 2 cm matches the original PLAN.md spec for "Persistent manifold cache"
94
- * — generous enough to follow small inter-frame contact migration on
95
- * curved surfaces, tight enough that distinct contacts on a single
96
- * manifold (typically >5 cm apart) don't get confused with each other.
97
- *
98
- * @type {number}
99
- */
100
- const MATCH_TOL_SQR = 0.02 * 0.02;
101
-
102
- /**
103
- * Scratch for the previous-frame contact snapshot taken at the top of the
104
- * match-and-merge pass. Sized for {@link MAX_CONTACTS_PER_MANIFOLD}
105
- * contacts, 7 floats per contact:
106
- * 0 : feature_id
107
- * 1, 2, 3 : world_a x, y, z (for position-fallback matching)
108
- * 4, 5, 6 : j_n, j_t1, j_t2 (carried forward to the matched candidate)
109
- *
110
- * Snapshotting upfront decouples the read (from the slot's previous state)
111
- * from the write (the new contact data + impulse copy), avoiding the
112
- * read-after-write hazard when the matching mapping shuffles indices.
113
- *
114
- * @type {Float64Array}
115
- */
116
- const prev_snapshot = new Float64Array(MAX_CONTACTS_PER_MANIFOLD * 7);
117
-
118
- /**
119
- * Per-prev-contact "already claimed by a candidate" flag.
120
- * @type {Uint8Array}
121
- */
122
- const prev_claimed = new Uint8Array(MAX_CONTACTS_PER_MANIFOLD);
123
-
124
- /**
125
- * For each kept candidate, the matched prev-contact index in
126
- * `[0, prev_count)` or `-1` if no match.
127
- * @type {Int32Array}
128
- */
129
- const cand_to_prev = new Int32Array(MAX_KEPT);
130
-
131
- /**
132
- * Per-candidate "already claimed" flags for {@link redetect_pair_geometry}'s
133
- * 1:1 existing-contact → fresh-candidate matching. Sized to the candidate
134
- * buffer capacity (64) so it covers any per-pair candidate count. Without it,
135
- * several existing contacts that share one triangle's `feature_id` (the
136
- * box/capsule-triangle paths emit multiple contacts per triangle) would all
137
- * match the same first candidate, collapsing the manifold to duplicate witness
138
- * points.
139
- * @type {Uint8Array}
140
- */
141
- const redetect_claimed = new Uint8Array(64);
142
-
143
- /**
144
- * Per body-pair scratch buffer for candidate contacts produced by the
145
- * cross-product of A's colliders × B's colliders. Sized generously for
146
- * typical compound bodies (each collider-pair contributes 1..4 contacts;
147
- * 64 covers up to 4 colliders per body × 4 colliders × 4 contacts = 64).
148
- * @type {Float64Array}
149
- */
150
- const candidates = new Float64Array(64 * CANDIDATE_STRIDE);
151
-
152
- /**
153
- * Maximum triangles a concave-side enumerator can produce per pair.
154
- * The query AABB is bounded by the broadphase's fattened envelope of
155
- * the convex-side body, so a single concave-vs-convex pair typically
156
- * yields tens of triangles, not thousands. 1024 is the safety cap.
157
- *
158
- * For a heightmap the per-cell triangle count scales O(N²) with the
159
- * shape's {@link HeightMapShape3D#tessellation} (a sub-cell quad is 2
160
- * triangles, and there are N×N sub-cells per sampler cell). The bounded
161
- * query AABB keeps the cell count small, so a typical pair stays well
162
- * inside 1024 at moderate tessellation; the silent-drop backstop below
163
- * covers any overflow at extreme values.
164
- *
165
- * If an enumerator's output would exceed this, the extra triangles are
166
- * silently dropped by the enumerator's bounds-check on the output
167
- * array the worst case is a missed contact on a far edge of the
168
- * filtered region, which the next-step rebroadphase corrects.
169
- *
170
- * @type {number}
171
- */
172
- const MAX_TRIANGLES_PER_PAIR = 1024;
173
-
174
- /**
175
- * Per-pair scratch for the concave-side triangle decomposition.
176
- * @type {Float64Array}
177
- */
178
- const triangle_buffer = new Float64Array(MAX_TRIANGLES_PER_PAIR * TRIANGLE_FLOAT_STRIDE);
179
-
180
- /**
181
- * Flyweight triangle shape — rebound to each successive triangle slice
182
- * of `triangle_buffer` during the concave-side dispatch loop. Zero
183
- * allocation per triangle.
184
- * @type {Triangle3D}
185
- */
186
- const triangle_shape = new Triangle3D();
187
-
188
- /**
189
- * Scratch AABB buffers used only by the concave-side dispatch:
190
- * - `concave_local_aabb` : convex shape's local AABB (input to oriented transform)
191
- * - `concave_world_aabb` : convex shape's world AABB
192
- * - `concave_query_aabb` : convex shape's AABB projected into concave's body-local frame
193
- * (what the triangle enumerator filters against)
194
- */
195
- const concave_local_aabb = new Float64Array(6);
196
- const concave_world_aabb = new Float64Array(6);
197
- const concave_query_aabb = new Float64Array(6);
198
-
199
- /**
200
- * Append one contact to the candidate buffer. Returns the new count.
201
- *
202
- * @param {number} count
203
- * @param {number} wax
204
- * @param {number} way
205
- * @param {number} waz
206
- * @param {number} wbx
207
- * @param {number} wby
208
- * @param {number} wbz
209
- * @param {number} nx
210
- * @param {number} ny
211
- * @param {number} nz
212
- * @param {number} depth
213
- * @param {number} feature_id stable cross-frame ID for warm-start matching;
214
- * `0` means no info — match-and-merge will fall back to position.
215
- * @returns {number}
216
- */
217
- function append_contact(count, wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth, feature_id) {
218
- if (count * CANDIDATE_STRIDE >= candidates.length){
219
- return count;
220
- }
221
-
222
- const off = count * CANDIDATE_STRIDE;
223
-
224
- candidates[off] = wax; candidates[off + 1] = way; candidates[off + 2] = waz;
225
- candidates[off + 3] = wbx; candidates[off + 4] = wby; candidates[off + 5] = wbz;
226
- candidates[off + 6] = nx; candidates[off + 7] = ny; candidates[off + 8] = nz;
227
- candidates[off + 9] = depth;
228
- candidates[off + 10] = feature_id;
229
- candidates[off + 11] = g_pair_friction;
230
- candidates[off + 12] = g_pair_restitution;
231
-
232
- return count + 1;
233
- }
234
-
235
- function swap_candidate(i, j) {
236
- if (i === j){
237
- return;
238
- }
239
-
240
- const oi = i * CANDIDATE_STRIDE;
241
- const oj = j * CANDIDATE_STRIDE;
242
-
243
- for (let k = 0; k < CANDIDATE_STRIDE; k++) {
244
- const t = candidates[oi + k];
245
- candidates[oi + k] = candidates[oj + k];
246
- candidates[oj + k] = t;
247
- }
248
- }
249
-
250
- /**
251
- * Compute a stable voronoi-region feature id for a sphere-vs-box contact.
252
- * Returns one of 27 values in `[1, 27]` based on which of the 27 voronoi
253
- * regions of the box the sphere centre lies in (8 corners + 12 edges + 6
254
- * faces + 1 interior). Stable across frames as long as the sphere stays
255
- * in the same region the typical case in steady-state contact.
256
- *
257
- * Encodes:
258
- * bucket_x {0, 1, 2} = sign(lx) bucket in box-local frame
259
- * bucket_y, bucket_z similarly
260
- * fid = 1 + bucket_x + 3·bucket_y + 9·bucket_z → 1..27
261
- *
262
- * The "+1" offset ensures the result is never 0, since 0 is reserved
263
- * for "no feature info, use position-fallback".
264
- *
265
- * @param {number} sx sphere centre x
266
- * @param {number} sy
267
- * @param {number} sz
268
- * @param {number} bx box centre x
269
- * @param {number} by
270
- * @param {number} bz
271
- * @param {number} bqx box quaternion x
272
- * @param {number} bqy
273
- * @param {number} bqz
274
- * @param {number} bqw
275
- * @param {number} hx box half-extent x
276
- * @param {number} hy
277
- * @param {number} hz
278
- * @returns {number}
279
- */
280
- function sphere_box_voronoi_fid(sx, sy, sz, bx, by, bz, bqx, bqy, bqz, bqw, hx, hy, hz) {
281
- // Inverse-rotate (sx - bx, sy - by, sz - bz) by the box's quaternion
282
- // to get the sphere centre in box-local frame. Inlined for the same
283
- // V8-inliner reason described in PosedShape.support — see
284
- // core/geom/vec3/v3_quat3_apply_inverse.js for the canonical form.
285
- const dx = sx - bx;
286
- const dy = sy - by;
287
- const dz = sz - bz;
288
-
289
- const tx0 = bqw * dx - bqy * dz + bqz * dy;
290
- const ty0 = bqw * dy - bqz * dx + bqx * dz;
291
- const tz0 = bqw * dz - bqx * dy + bqy * dx;
292
- const tw0 = bqx * dx + bqy * dy + bqz * dz;
293
-
294
- const lx = tx0 * bqw + tw0 * bqx + ty0 * bqz - tz0 * bqy;
295
- const ly = ty0 * bqw + tw0 * bqy + tz0 * bqx - tx0 * bqz;
296
- const lz = tz0 * bqw + tw0 * bqz + tx0 * bqy - ty0 * bqx;
297
-
298
- const bx_b = lx <= -hx ? 0 : (lx >= hx ? 2 : 1);
299
- const by_b = ly <= -hy ? 0 : (ly >= hy ? 2 : 1);
300
- const bz_b = lz <= -hz ? 0 : (lz >= hz ? 2 : 1);
301
-
302
- return 1 + bx_b + 3 * by_b + 9 * bz_b;
303
- }
304
-
305
- /**
306
- * Reduce an in-place candidate list to at most {@link MAX_KEPT} entries:
307
- * 1. Move the deepest to slot 0.
308
- * 2. For each subsequent slot, pick the remaining candidate whose minimum
309
- * distance to the already-kept set is largest (approximates max-area).
310
- *
311
- * @param {number} n
312
- * @returns {number} kept count, in [0, min(n, MAX_KEPT)]
313
- */
314
- function reduce_candidates(n) {
315
- if (n <= MAX_KEPT){
316
- return n;
317
- }
318
-
319
- // Move deepest to slot 0.
320
- let deepest_idx = 0;
321
- let deepest_val = candidates[9];
322
-
323
- for (let i = 1; i < n; i++) {
324
-
325
- const d = candidates[i * CANDIDATE_STRIDE + 9];
326
-
327
- if (d > deepest_val) {
328
- deepest_val = d; deepest_idx = i;
329
- }
330
- }
331
-
332
- swap_candidate(0, deepest_idx);
333
-
334
- for (let k = 1; k < MAX_KEPT; k++) {
335
-
336
- let best_score = -1;
337
- let best_i = -1;
338
-
339
- for (let i = k; i < n; i++) {
340
- let min_d2 = Infinity;
341
-
342
- for (let j = 0; j < k; j++) {
343
-
344
- const dx = candidates[i * CANDIDATE_STRIDE] - candidates[j * CANDIDATE_STRIDE];
345
- const dy = candidates[i * CANDIDATE_STRIDE + 1] - candidates[j * CANDIDATE_STRIDE + 1];
346
- const dz = candidates[i * CANDIDATE_STRIDE + 2] - candidates[j * CANDIDATE_STRIDE + 2];
347
- const d2 = dx * dx + dy * dy + dz * dz;
348
-
349
- if (d2 < min_d2){
350
- min_d2 = d2;
351
- }
352
- }
353
-
354
- if (min_d2 > best_score) {
355
- best_score = min_d2;
356
- best_i = i;
357
- }
358
-
359
- }
360
-
361
- swap_candidate(k, best_i);
362
- }
363
-
364
- return MAX_KEPT;
365
- }
366
-
367
- /**
368
- * Multi-point contact manifold between two convex polytopes (authored
369
- * ConvexHullShape3D, or a convex MeshShape3D represented as one hull). Robust
370
- * GJK + EPA finds the separating axis in O(support queries) — face-count
371
- * independent, where SAT's edge-pair term is prohibitivethen
372
- * {@link convex_hull_clip} clips the reference/incident faces into a face-on-face
373
- * patch around it (Bullet's clipHullAgainstHull). A single GJK+EPA contact can't
374
- * resist a stack's toppling torque; the clipped patch holds it. No cross-frame
375
- * state reset-and-resimulate determinism.
376
- *
377
- * @param {number} count
378
- * @param {{vertices,face_offsets,face_loops,support}} hullA in A's local frame
379
- * @param {Transform} trA
380
- * @param {{vertices,face_offsets,face_loops,support}} hullB in B's local frame
381
- * @param {Transform} trB
382
- * @param {Function} append_contact
383
- * @returns {number}
384
- */
385
- function hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact) {
386
- posed_a.setup(hullA, trA.position, trA.rotation);
387
- posed_b.setup(hullB, trB.position, trB.rotation);
388
- const depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
389
- if (!(depth > 0) || !Number.isFinite(depth)) return count;
390
-
391
- // convex_hull_clip orients the axis B→A robustly (via vertex centroids) and
392
- // writes the final normal into convex_manifold_result[0..2].
393
- convex_hull_clip(convex_manifold_result,
394
- hullA, trA.position, trA.rotation,
395
- hullB, trB.position, trB.rotation,
396
- epa_result[0], epa_result[1], epa_result[2]);
397
- const nx = convex_manifold_result[0], ny = convex_manifold_result[1], nz = convex_manifold_result[2];
398
- const cc = convex_manifold_result[3] | 0;
399
- // Clipped points migrate across features as the hulls rotate, so leave
400
- // fid = 0 and let the match-and-merge pass position-match (as box-box does).
401
- for (let k = 0; k < cc; k++) {
402
- const base = 4 + k * 7;
403
- count = append_contact(count,
404
- convex_manifold_result[base], convex_manifold_result[base + 1], convex_manifold_result[base + 2],
405
- convex_manifold_result[base + 3], convex_manifold_result[base + 4], convex_manifold_result[base + 5],
406
- nx, ny, nz,
407
- convex_manifold_result[base + 6],
408
- 0);
409
- }
410
- return count;
411
- }
412
-
413
- /**
414
- * Run pairwise narrowphase for one (colliderA, colliderB) tuple — dispatches
415
- * by shape type and appends 0..K contacts to the candidate buffer. Returns
416
- * the new candidate count.
417
- *
418
- * @param {number} count
419
- * @param {Collider} colA
420
- * @param {Transform} trA
421
- * @param {Collider} colB
422
- * @param {Transform} trB
423
- * @returns {number}
424
- */
425
- function dispatch_pair(count, colA, trA, colB, trB) {
426
- const shapeA = colA.shape;
427
- const shapeB = colB.shape;
428
-
429
- // Per-contact materials: combine the two source colliders' coefficients
430
- // once here (this is the only place that knows the exact collider on each
431
- // side) and stamp them onto every contact this dispatch appends. The
432
- // `deepest_pair_penetration` query passes bare `{shape}` adapters with no
433
- // material fields it never writes to a manifold, so 0 is fine there.
434
- const fa = colA.friction, fb = colB.friction;
435
-
436
- if (fa !== undefined && fb !== undefined) {
437
-
438
- g_pair_friction = combine_friction(fa, fb);
439
- g_pair_restitution = combine_restitution(colA.restitution, colB.restitution);
440
-
441
- } else {
442
- g_pair_friction = 0;
443
- g_pair_restitution = 0;
444
- }
445
-
446
- // isSphereShape3D covers both UnitSphereShape3D (fixed radius 1) and
447
- // SphereShape3D (arbitrary radius). Both expose `radius`.
448
- const isSphereA = shapeA.isSphereShape3D === true;
449
- const isSphereB = shapeB.isSphereShape3D === true;
450
- // isBoxShape3D covers both UnitCubeShape3D (fixed 0.5) and BoxShape3D
451
- // (arbitrary half-extents). Both expose `half_extents` as a Vector3.
452
- const isBoxA = shapeA.isBoxShape3D === true;
453
- const isBoxB = shapeB.isBoxShape3D === true;
454
- const isCapsuleA = shapeA.isCapsuleShape3D === true;
455
- const isCapsuleB = shapeB.isCapsuleShape3D === true;
456
-
457
- // sphere-sphere
458
- if (isSphereA && isSphereB) {
459
- const ra = shapeA.radius, rb = shapeB.radius;
460
-
461
- const ok = sphere_sphere_contact(
462
- sphere_result,
463
- trA.position.x, trA.position.y, trA.position.z,
464
- trB.position.x, trB.position.y, trB.position.z,
465
- ra, rb
466
- );
467
-
468
- if (!ok) return count;
469
-
470
- const nx = sphere_result[0], ny = sphere_result[1], nz = sphere_result[2];
471
-
472
- // Sphere-sphere produces exactly one contact per pair; fid = 1
473
- // identifies it as a real feature (distinguishes from "no info" = 0)
474
- // and is trivially stable across frames. Witnesses are the surface
475
- // points along the (unit) normal, scaled by each sphere's radius.
476
- return append_contact(count,
477
- trA.position.x - nx * ra, trA.position.y - ny * ra, trA.position.z - nz * ra,
478
- trB.position.x + nx * rb, trB.position.y + ny * rb, trB.position.z + nz * rb,
479
- nx, ny, nz, sphere_result[3], 1);
480
- }
481
-
482
- // sphere ↔ box
483
- if ((isSphereA && isBoxB) || (isBoxA && isSphereB)) {
484
- const sphereTr = isSphereA ? trA : trB;
485
- const sphereShape = isSphereA ? shapeA : shapeB;
486
- const boxTr = isSphereA ? trB : trA;
487
- const boxShape = isSphereA ? shapeB : shapeA;
488
- const bh = boxShape.half_extents;
489
-
490
- const ok = sphere_box_contact(
491
- closed_form_result,
492
- sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius,
493
- boxTr.position.x, boxTr.position.y, boxTr.position.z,
494
- boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
495
- bh.x, bh.y, bh.z
496
- );
497
-
498
- if (!ok) return count;
499
-
500
- let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
501
-
502
- const depth = closed_form_result[3];
503
- const wsx = closed_form_result[4], wsy = closed_form_result[5], wsz = closed_form_result[6];
504
- const wbx = closed_form_result[7], wby = closed_form_result[8], wbz = closed_form_result[9];
505
- // Feature id from the box-local voronoi region (1..27) — stable
506
- // while the sphere stays on the same face / edge / vertex of the
507
- // box, which is the steady-state case for resting / sliding contact.
508
-
509
- const fid = sphere_box_voronoi_fid(
510
- sphereTr.position.x, sphereTr.position.y, sphereTr.position.z,
511
- boxTr.position.x, boxTr.position.y, boxTr.position.z,
512
- boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
513
- bh.x, bh.y, bh.z
514
- );
515
-
516
- if (isSphereA) {
517
- return append_contact(count, wsx, wsy, wsz, wbx, wby, wbz, nx, ny, nz, depth, fid);
518
- } else {
519
- return append_contact(count, wbx, wby, wbz, wsx, wsy, wsz, -nx, -ny, -nz, depth, fid);
520
- }
521
- }
522
-
523
- // box-box multi-point
524
- if (isBoxA && isBoxB) {
525
- const ah = shapeA.half_extents;
526
- const bh = shapeB.half_extents;
527
- const ok = box_box_manifold(
528
- box_manifold_result,
529
- trA.position.x, trA.position.y, trA.position.z,
530
- trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
531
- ah.x, ah.y, ah.z,
532
- trB.position.x, trB.position.y, trB.position.z,
533
- trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
534
- bh.x, bh.y, bh.z
535
- );
536
- if (!ok) return count;
537
- const nx = box_manifold_result[0], ny = box_manifold_result[1], nz = box_manifold_result[2];
538
- const cc = box_manifold_result[3] | 0;
539
- // Box-box manifolds: closed-form clipping doesn't expose stable
540
- // per-contact feature ids (a contact migrates from "incident-vertex k"
541
- // to "clip-intersection on edge j" as the boxes rotate). Leave
542
- // fid = 0 so the match-and-merge pass uses position-fallback
543
- // the per-contact clipped points are spread by face geometry and
544
- // typically stay >>MATCH_TOL apart, so position matching is
545
- // unambiguous frame-to-frame.
546
- for (let k = 0; k < cc; k++) {
547
- const base = 4 + k * 7;
548
- count = append_contact(count,
549
- box_manifold_result[base], box_manifold_result[base + 1], box_manifold_result[base + 2],
550
- box_manifold_result[base + 3], box_manifold_result[base + 4], box_manifold_result[base + 5],
551
- nx, ny, nz,
552
- box_manifold_result[base + 6],
553
- 0);
554
- }
555
- return count;
556
- }
557
-
558
- // capsule-capsule
559
- if (isCapsuleA && isCapsuleB) {
560
- const a_shape = colA.shape, b_shape = colB.shape;
561
- const ok = capsule_capsule_contact(
562
- closed_form_result,
563
- trA.position.x, trA.position.y, trA.position.z,
564
- trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
565
- a_shape.radius, a_shape.height * 0.5,
566
- trB.position.x, trB.position.y, trB.position.z,
567
- trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
568
- b_shape.radius, b_shape.height * 0.5
569
- );
570
- if (!ok) return count;
571
- // Single contact per capsule-capsule pair; fid = 1 (stable, real feature).
572
- return append_contact(count,
573
- closed_form_result[4], closed_form_result[5], closed_form_result[6],
574
- closed_form_result[7], closed_form_result[8], closed_form_result[9],
575
- closed_form_result[0], closed_form_result[1], closed_form_result[2],
576
- closed_form_result[3], 1);
577
- }
578
-
579
- // capsule sphere
580
- if ((isCapsuleA && isSphereB) || (isSphereA && isCapsuleB)) {
581
- const capsuleTr = isCapsuleA ? trA : trB;
582
- const capsuleShape = isCapsuleA ? colA.shape : colB.shape;
583
- const sphereTr = isCapsuleA ? trB : trA;
584
- const sphereShape = isCapsuleA ? colB.shape : colA.shape;
585
- const ok = capsule_sphere_contact(
586
- closed_form_result,
587
- capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
588
- capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
589
- capsuleShape.radius, capsuleShape.height * 0.5,
590
- sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius
591
- );
592
- if (!ok) return count;
593
- let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
594
- const depth = closed_form_result[3];
595
- const cap_x = closed_form_result[4], cap_y = closed_form_result[5], cap_z = closed_form_result[6];
596
- const sph_x = closed_form_result[7], sph_y = closed_form_result[8], sph_z = closed_form_result[9];
597
- // Single contact per capsule-sphere pair; fid = 1.
598
- if (isCapsuleA) {
599
- return append_contact(count, cap_x, cap_y, cap_z, sph_x, sph_y, sph_z, nx, ny, nz, depth, 1);
600
- } else {
601
- return append_contact(count, sph_x, sph_y, sph_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, 1);
602
- }
603
- }
604
-
605
- // capsule ↔ box (multi-point — 1 closest-segment + up to 2 cap-centres)
606
- if ((isCapsuleA && isBoxB) || (isBoxA && isCapsuleB)) {
607
- const capsuleTr = isCapsuleA ? trA : trB;
608
- const capsuleShape = isCapsuleA ? shapeA : shapeB;
609
- const boxTr = isCapsuleA ? trB : trA;
610
- const boxShape = isCapsuleA ? shapeB : shapeA;
611
- const bh = boxShape.half_extents;
612
- const cc = capsule_box_multi_contacts(
613
- capsule_box_multi_result,
614
- capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
615
- capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
616
- capsuleShape.radius, capsuleShape.height * 0.5,
617
- boxTr.position.x, boxTr.position.y, boxTr.position.z,
618
- boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
619
- bh.x, bh.y, bh.z
620
- );
621
- if (cc === 0) return count;
622
- // multi_result layout per contact: cap_x/y/z (A side), box_x/y/z (B side), nx/ny/nz, depth.
623
- // Feature id per sub-contact: 1 = closest-segment, 2/3 = caps. The
624
- // emission order from capsule_box_multi_contacts is stable across
625
- // frames for the same geometric configuration.
626
- for (let k = 0; k < cc; k++) {
627
- const o = k * CAPSULE_BOX_CONTACT_STRIDE;
628
- const cap_x = capsule_box_multi_result[o];
629
- const cap_y = capsule_box_multi_result[o + 1];
630
- const cap_z = capsule_box_multi_result[o + 2];
631
- const box_x = capsule_box_multi_result[o + 3];
632
- const box_y = capsule_box_multi_result[o + 4];
633
- const box_z = capsule_box_multi_result[o + 5];
634
- const nx = capsule_box_multi_result[o + 6];
635
- const ny = capsule_box_multi_result[o + 7];
636
- const nz = capsule_box_multi_result[o + 8];
637
- const depth = capsule_box_multi_result[o + 9];
638
- const sub_fid = k + 1;
639
- if (isCapsuleA) {
640
- count = append_contact(count, cap_x, cap_y, cap_z, box_x, box_y, box_z, nx, ny, nz, depth, sub_fid);
641
- } else {
642
- count = append_contact(count, box_x, box_y, box_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, sub_fid);
643
- }
644
- }
645
- return count;
646
- }
647
-
648
- // convex hull ↔ convex hull (multi-point clipping via GJK + EPA + face-clip).
649
- if (shapeA.isConvexHullShape3D === true && shapeB.isConvexHullShape3D === true) {
650
- return hull_pair_contacts(count, shapeA, trA, shapeB, trB, append_contact);
651
- }
652
-
653
- // meshmesh. A globally-convex mesh collides as a single cached hull
654
- // (one GJK + EPA + clip) — the greedy decomposition fragments even a convex
655
- // mesh, so detecting convexity and routing through the hull path is the
656
- // scalable fast path. When BOTH meshes are convex, use it; otherwise fall to
657
- // the per-piece decomposed path (convex pieces vs convex pieces).
658
- if (shapeA.isMeshShape3D === true && shapeB.isMeshShape3D === true) {
659
- const hullA = get_mesh_convex_hull(shapeA);
660
- const hullB = get_mesh_convex_hull(shapeB);
661
- if (hullA !== null && hullB !== null) {
662
- return hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact);
663
- }
664
- return mesh_mesh_tet_contacts(count, shapeA, trA, shapeB, trB, append_contact);
665
- }
666
-
667
- // ── Concave (non-convex) path ───────────────────────────────────────
668
- //
669
- // If either shape has `is_convex === false`, GJK on the whole shape
670
- // produces incorrect results (Minkowski difference is not convex).
671
- // We decompose the concave side into triangles overlapping the
672
- // convex side's AABB, then run convex per-triangle GJK + EPA.
673
- //
674
- // Concave-vs-concave is intentionally NOT supported in v1: the
675
- // M×N triangle pairs would dominate runtime, and the physics
676
- // engine's design contract requires at least one side to be
677
- // static / kinematic for concave shapes anyway (the broadphase +
678
- // filter should keep such pairs out of the narrowphase entirely).
679
- // If one slips through, we skip rather than burn cycles.
680
- const isConcaveA = shapeA.is_convex === false;
681
- const isConcaveB = shapeB.is_convex === false;
682
- if (isConcaveA && isConcaveB) return count;
683
- if (isConcaveA || isConcaveB) {
684
- const concave_col = isConcaveA ? colA : colB;
685
- const concave_tr = isConcaveA ? trA : trB;
686
- const convex_col = isConcaveA ? colB : colA;
687
- const convex_tr = isConcaveA ? trB : trA;
688
-
689
- // 1. Convex shape's world AABB.
690
- convex_col.shape.compute_bounding_box(concave_local_aabb);
691
- aabb3_transform_oriented(
692
- concave_world_aabb, 0,
693
- concave_local_aabb[0], concave_local_aabb[1], concave_local_aabb[2],
694
- concave_local_aabb[3], concave_local_aabb[4], concave_local_aabb[5],
695
- convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
696
- convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w
697
- );
698
-
699
- // 2. Project into concave's body-local frame.
700
- aabb_world_to_local(
701
- concave_query_aabb, 0,
702
- concave_world_aabb,
703
- concave_tr.position.x, concave_tr.position.y, concave_tr.position.z,
704
- concave_tr.rotation.x, concave_tr.rotation.y, concave_tr.rotation.z, concave_tr.rotation.w
705
- );
706
-
707
- // 3. Decompose concave shape into triangles overlapping the query.
708
- const tri_count = decompose_to_triangles(
709
- triangle_buffer, 0, concave_col.shape,
710
- concave_query_aabb[0], concave_query_aabb[1], concave_query_aabb[2],
711
- concave_query_aabb[3], concave_query_aabb[4], concave_query_aabb[5]
712
- );
713
- if (tri_count === 0) return count;
714
-
715
- // 4. Set up the convex side once; the concave side gets a
716
- // Triangle3D rebound to each triangle in the loop. We keep
717
- // the concave side as our internal "A" so EPA's sign-check
718
- // convention matches the convex fallback below; the final
719
- // append_contact() swaps if the original A was the convex one.
720
- posed_b.setup(convex_col.shape, convex_tr.position, convex_tr.rotation);
721
- posed_a.shape = triangle_shape;
722
- posed_a.px = concave_tr.position.x;
723
- posed_a.py = concave_tr.position.y;
724
- posed_a.pz = concave_tr.position.z;
725
- posed_a.qx = concave_tr.rotation.x;
726
- posed_a.qy = concave_tr.rotation.y;
727
- posed_a.qz = concave_tr.rotation.z;
728
- posed_a.qw = concave_tr.rotation.w;
729
-
730
- // Pre-compute the centre-axis used by EPA's sign-check loop.
731
- // For convex-vs-convex this is `(B - A) = convex_centre − concave_centre`.
732
- const convex_wx = convex_tr.position.x;
733
- const convex_wy = convex_tr.position.y;
734
- const convex_wz = convex_tr.position.z;
735
-
736
- // Track the candidate-buffer index at the start of this
737
- // concave dispatch — the per-triangle dedup pass scans from
738
- // here to the current count, ignoring contacts from earlier
739
- // collider pairs in the same body pair.
740
- const pair_start_count = count;
741
-
742
- // Pre-compute concave's rotation components for the q · v · q⁻¹
743
- // rotations done per-triangle below (face normal + centroid).
744
- const cqx = concave_tr.rotation.x;
745
- const cqy = concave_tr.rotation.y;
746
- const cqz = concave_tr.rotation.z;
747
- const cqw = concave_tr.rotation.w;
748
- const c_pos_x = concave_tr.position.x;
749
- const c_pos_y = concave_tr.position.y;
750
- const c_pos_z = concave_tr.position.z;
751
-
752
- // Sphere fast-path: when the convex side is a sphere we bypass GJK+EPA
753
- // entirely per triangle and use the closed-form
754
- // {@link sphere_triangle_contact}. This avoids the EPA precision
755
- // wall on Triangle3D (whose support function is degenerate along
756
- // the face normal — all 3 vertices project to the same value),
757
- // which was producing noisy depths at small penetrations and
758
- // letting dropped spheres tunnel through heightmaps / meshes.
759
- const isSphereConvex = convex_col.shape.isSphereShape3D === true;
760
- const sphere_radius = isSphereConvex ? convex_col.shape.radius : 0;
761
-
762
- // Box fast-path: closed-form {@link box_triangle_contact} via SAT
763
- // over 13 axes + polygon clipping for face-vs-face contacts.
764
- // Same motivation as the sphere path Triangle3D's degenerate
765
- // face-normal support kills EPA precision and produces noisy
766
- // depths. The box path uses world-space triangle vertices.
767
- const isBoxConvex = convex_col.shape.isBoxShape3D === true;
768
- const box_half_extents = isBoxConvex ? convex_col.shape.half_extents : null;
769
-
770
- // Capsule fast-path: closed-form {@link capsule_triangle_contact}
771
- // via segment-vs-triangle closest-point + cap-centre sphere
772
- // queries for a multi-point manifold. Same motivation as the
773
- // sphere and box paths.
774
- const isCapsuleConvex = convex_col.shape.isCapsuleShape3D === true;
775
- const capsule_shape = isCapsuleConvex ? convex_col.shape : null;
776
-
777
- for (let i = 0; i < tri_count; i++) {
778
- const tri_offset = i * TRIANGLE_FLOAT_STRIDE;
779
- triangle_shape.bind(triangle_buffer, tri_offset);
780
-
781
- const ax = triangle_buffer[tri_offset ];
782
- const ay = triangle_buffer[tri_offset + 1];
783
- const az = triangle_buffer[tri_offset + 2];
784
-
785
- const bx = triangle_buffer[tri_offset + 3];
786
- const by = triangle_buffer[tri_offset + 4];
787
- const bz = triangle_buffer[tri_offset + 5];
788
-
789
- const cx_v = triangle_buffer[tri_offset + 6];
790
- const cy_v = triangle_buffer[tri_offset + 7];
791
- const cz_v = triangle_buffer[tri_offset + 8];
792
-
793
- // Triangle decomposition emits a stable per-triangle feature_id
794
- // at offset 9 (TRIANGLE_FEATURE_ID_OFFSET) — same triangle of
795
- // the same shape gets the same id across frames. This is the
796
- // gold-standard fid for the match-and-merge pass.
797
- const tri_fid = triangle_buffer[tri_offset + 9];
798
-
799
- // Triangle face normal in body-local frame: (B − A) × (C − A).
800
- // Winding convention (CCW from outside) gives an outward face
801
- // normal — the heightmap / mesh enumerators both promise this.
802
- const e1x_l = bx - ax, e1y_l = by - ay, e1z_l = bz - az;
803
- const e2x_l = cx_v - ax, e2y_l = cy_v - ay, e2z_l = cz_v - az;
804
-
805
- const fnx_l = e1y_l * e2z_l - e1z_l * e2y_l;
806
- const fny_l = e1z_l * e2x_l - e1x_l * e2z_l;
807
- const fnz_l = e1x_l * e2y_l - e1y_l * e2x_l;
808
-
809
- // Rotate face normal to world via concave's quaternion
810
- // (q · v · q⁻¹). Inlined for V8 inliner — see PosedShape.support.
811
- const fnix = cqw * fnx_l + cqy * fnz_l - cqz * fny_l;
812
- const fniy = cqw * fny_l + cqz * fnx_l - cqx * fnz_l;
813
- const fniz = cqw * fnz_l + cqx * fny_l - cqy * fnx_l;
814
- const fniw = -cqx * fnx_l - cqy * fny_l - cqz * fnz_l;
815
-
816
- const fnx_w = fnix * cqw - fniw * cqx - fniy * cqz + fniz * cqy;
817
- const fny_w = fniy * cqw - fniw * cqy - fniz * cqx + fnix * cqz;
818
- const fnz_w = fniz * cqw - fniw * cqz - fnix * cqy + fniy * cqx;
819
-
820
- // Sphere-vs-triangle closed-form fast-path.
821
- if (isSphereConvex) {
822
-
823
- // Rotate each triangle vertex from concave-local to world.
824
- // Per-vertex cost: ~21 flops × 3 vertices = 63 flops; vs.
825
- // the GJK + EPA cost we're replacing, this is essentially
826
- // free.
827
- // Inlined q · v · q* + translate per vertex see PosedShape.support.
828
- const axi = cqw * ax + cqy * az - cqz * ay;
829
- const ayi = cqw * ay + cqz * ax - cqx * az;
830
- const azi = cqw * az + cqx * ay - cqy * ax;
831
- const awi = -cqx * ax - cqy * ay - cqz * az;
832
-
833
- const ax_w = axi * cqw + awi * -cqx + ayi * -cqz - azi * -cqy + c_pos_x;
834
- const ay_w = ayi * cqw + awi * -cqy + azi * -cqx - axi * -cqz + c_pos_y;
835
- const az_w = azi * cqw + awi * -cqz + axi * -cqy - ayi * -cqx + c_pos_z;
836
-
837
- const bxi = cqw * bx + cqy * bz - cqz * by;
838
- const byi = cqw * by + cqz * bx - cqx * bz;
839
- const bzi = cqw * bz + cqx * by - cqy * bx;
840
- const bwi = -cqx * bx - cqy * by - cqz * bz;
841
-
842
- const bx_w = bxi * cqw + bwi * -cqx + byi * -cqz - bzi * -cqy + c_pos_x;
843
- const by_w = byi * cqw + bwi * -cqy + bzi * -cqx - bxi * -cqz + c_pos_y;
844
- const bz_w = bzi * cqw + bwi * -cqz + bxi * -cqy - byi * -cqx + c_pos_z;
845
-
846
- const cxi = cqw * cx_v + cqy * cz_v - cqz * cy_v;
847
- const cyi = cqw * cy_v + cqz * cx_v - cqx * cz_v;
848
- const czi = cqw * cz_v + cqx * cy_v - cqy * cx_v;
849
- const cwi = -cqx * cx_v - cqy * cy_v - cqz * cz_v;
850
-
851
- const cx_w = cxi * cqw + cwi * -cqx + cyi * -cqz - czi * -cqy + c_pos_x;
852
- const cy_w = cyi * cqw + cwi * -cqy + czi * -cqx - cxi * -cqz + c_pos_y;
853
- const cz_w = czi * cqw + cwi * -cqz + cxi * -cqy - cyi * -cqx + c_pos_z;
854
-
855
- const ok = sphere_triangle_contact(
856
- sphere_triangle_result,
857
- convex_wx, convex_wy, convex_wz, sphere_radius,
858
- ax_w, ay_w, az_w,
859
- bx_w, by_w, bz_w,
860
- cx_w, cy_w, cz_w
861
- );
862
-
863
- if (!ok) continue;
864
-
865
- // sphere_triangle_contact's normal points from the
866
- // triangle surface toward the sphere centre same
867
- // direction as the post-sign-check EPA MTV in the
868
- // fallback path below (concave A → convex B).
869
- const sd = sphere_triangle_result[3];
870
- const n_t2s_x = sphere_triangle_result[0];
871
- const n_t2s_y = sphere_triangle_result[1];
872
- const n_t2s_z = sphere_triangle_result[2];
873
-
874
- // One-sided rejection: the sphere must lie on the
875
- // outward side of the triangle. If the contact normal
876
- // opposes the face normal, the sphere is behind /
877
- // inside the solid — skip rather than push it deeper.
878
- if (n_t2s_x * fnx_w + n_t2s_y * fny_w + n_t2s_z * fnz_w <= 0) continue;
879
-
880
- // Stored normal convention is "B A". `nx, ny, nz` here
881
- // points convex → concave (which the existing EPA branch
882
- // also produces just before append). Dedup uses the
883
- // post-swap stored_n so adjacent-triangle duplicates
884
- // collapse the same way regardless of code path.
885
- const nx_s = -n_t2s_x;
886
- const ny_s = -n_t2s_y;
887
- const nz_s = -n_t2s_z;
888
-
889
- const stored_nx_s = isConcaveA ? nx_s : -nx_s;
890
- const stored_ny_s = isConcaveA ? ny_s : -ny_s;
891
- const stored_nz_s = isConcaveA ? nz_s : -nz_s;
892
- let is_duplicate_s = false;
893
- for (let k = pair_start_count; k < count; k++) {
894
- const ko = k * CANDIDATE_STRIDE;
895
- const dnx = candidates[ko + 6] - stored_nx_s;
896
- const dny = candidates[ko + 7] - stored_ny_s;
897
- const dnz = candidates[ko + 8] - stored_nz_s;
898
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
899
- is_duplicate_s = true;
900
- break;
901
- }
902
- }
903
- if (is_duplicate_s) continue;
904
-
905
- // Surface witnesses from the closed-form solver:
906
- // result[4..6] = sphere surface point (convex side)
907
- // result[7..9] = triangle closest point (concave side)
908
- // Use the actual witnesses rather than body centres —
909
- // unlike the EPA fallback (which uses body centres
910
- // because flat-faced support witnesses can be arbitrarily
911
- // far from the contact patch), closest-point-on-triangle
912
- // is exact and lies on the contact patch.
913
- const sphere_wx = sphere_triangle_result[4];
914
- const sphere_wy = sphere_triangle_result[5];
915
- const sphere_wz = sphere_triangle_result[6];
916
- const tri_wx = sphere_triangle_result[7];
917
- const tri_wy = sphere_triangle_result[8];
918
- const tri_wz = sphere_triangle_result[9];
919
- if (isConcaveA) {
920
- count = append_contact(count,
921
- tri_wx, tri_wy, tri_wz,
922
- sphere_wx, sphere_wy, sphere_wz,
923
- nx_s, ny_s, nz_s, sd, tri_fid);
924
- } else {
925
- count = append_contact(count,
926
- sphere_wx, sphere_wy, sphere_wz,
927
- tri_wx, tri_wy, tri_wz,
928
- -nx_s, -ny_s, -nz_s, sd, tri_fid);
929
- }
930
- continue;
931
- }
932
-
933
- // Box-vs-triangle closed-form fast-path.
934
- if (isBoxConvex) {
935
- // Rotate each triangle vertex from concave-local to world.
936
- // Inlined q · v · q* + translate per vertex — see PosedShape.support.
937
- const axi = cqw * ax + cqy * az - cqz * ay;
938
- const ayi = cqw * ay + cqz * ax - cqx * az;
939
- const azi = cqw * az + cqx * ay - cqy * ax;
940
- const awi = -cqx * ax - cqy * ay - cqz * az;
941
-
942
- const ax_w = axi * cqw + awi * -cqx + ayi * -cqz - azi * -cqy + c_pos_x;
943
- const ay_w = ayi * cqw + awi * -cqy + azi * -cqx - axi * -cqz + c_pos_y;
944
- const az_w = azi * cqw + awi * -cqz + axi * -cqy - ayi * -cqx + c_pos_z;
945
-
946
- const bxi = cqw * bx + cqy * bz - cqz * by;
947
- const byi = cqw * by + cqz * bx - cqx * bz;
948
- const bzi = cqw * bz + cqx * by - cqy * bx;
949
- const bwi = -cqx * bx - cqy * by - cqz * bz;
950
-
951
- const bx_w = bxi * cqw + bwi * -cqx + byi * -cqz - bzi * -cqy + c_pos_x;
952
- const by_w = byi * cqw + bwi * -cqy + bzi * -cqx - bxi * -cqz + c_pos_y;
953
- const bz_w = bzi * cqw + bwi * -cqz + bxi * -cqy - byi * -cqx + c_pos_z;
954
-
955
- const cxi = cqw * cx_v + cqy * cz_v - cqz * cy_v;
956
- const cyi = cqw * cy_v + cqz * cx_v - cqx * cz_v;
957
- const czi = cqw * cz_v + cqx * cy_v - cqy * cx_v;
958
- const cwi = -cqx * cx_v - cqy * cy_v - cqz * cz_v;
959
- const cx_w = cxi * cqw + cwi * -cqx + cyi * -cqz - czi * -cqy + c_pos_x;
960
- const cy_w = cyi * cqw + cwi * -cqy + czi * -cqx - cxi * -cqz + c_pos_y;
961
- const cz_w = czi * cqw + cwi * -cqz + cxi * -cqy - cyi * -cqx + c_pos_z;
962
-
963
- const ok = box_triangle_contact(
964
- box_triangle_result,
965
- convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
966
- convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
967
- box_half_extents.x, box_half_extents.y, box_half_extents.z,
968
- ax_w, ay_w, az_w,
969
- bx_w, by_w, bz_w,
970
- cx_w, cy_w, cz_w
971
- );
972
- if (!ok) continue;
973
-
974
- // Same convention as sphere_triangle: result normal points
975
- // from triangle surface toward box centre. To match the
976
- // EPA branch's `nx, ny, nz` (which points convex →
977
- // concave = box triangle), negate.
978
- const n_t2box_x = box_triangle_result[0];
979
- const n_t2box_y = box_triangle_result[1];
980
- const n_t2box_z = box_triangle_result[2];
981
-
982
- // One-sided rejection: the box must lie on the outward
983
- // side of the triangle. If the contact normal opposes
984
- // the face normal, the box is inside the solid → skip.
985
- if (n_t2box_x * fnx_w + n_t2box_y * fny_w + n_t2box_z * fnz_w <= 0) continue;
986
-
987
- const nx_b = -n_t2box_x;
988
- const ny_b = -n_t2box_y;
989
- const nz_b = -n_t2box_z;
990
- const stored_nx_b = isConcaveA ? nx_b : -nx_b;
991
- const stored_ny_b = isConcaveA ? ny_b : -ny_b;
992
- const stored_nz_b = isConcaveA ? nz_b : -nz_b;
993
-
994
- // Emit each clipped contact. The contact count from
995
- // box_triangle_contact is at most 4; combined with the
996
- // existing per-pair candidate cap (64 entries), tens of
997
- // overlapping triangles per pair are safely accommodated.
998
- const cc = box_triangle_result[3] | 0;
999
- for (let k = 0; k < cc; k++) {
1000
- const base = 4 + k * 7;
1001
- const t_wx = box_triangle_result[base];
1002
- const t_wy = box_triangle_result[base + 1];
1003
- const t_wz = box_triangle_result[base + 2];
1004
- const b_wx = box_triangle_result[base + 3];
1005
- const b_wy = box_triangle_result[base + 4];
1006
- const b_wz = box_triangle_result[base + 5];
1007
- const d_k = box_triangle_result[base + 6];
1008
-
1009
- // Dedup against earlier candidates in THIS dispatch
1010
- // (adjacent triangles can produce coincident contacts
1011
- // — same as the sphere and EPA paths).
1012
- let is_duplicate_k = false;
1013
- for (let q = pair_start_count; q < count; q++) {
1014
- const qo = q * CANDIDATE_STRIDE;
1015
- const dnx = candidates[qo + 6] - stored_nx_b;
1016
- const dny = candidates[qo + 7] - stored_ny_b;
1017
- const dnz = candidates[qo + 8] - stored_nz_b;
1018
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1019
- // Same normal; check positions too (a
1020
- // multi-point manifold has distinct points
1021
- // with the same normal — those should NOT
1022
- // be deduped). Compare the world-A side
1023
- // (triangle surface or box surface depending
1024
- // on dispatcher swap).
1025
- const pa_x = isConcaveA ? t_wx : b_wx;
1026
- const pa_y = isConcaveA ? t_wy : b_wy;
1027
- const pa_z = isConcaveA ? t_wz : b_wz;
1028
- const dpx = candidates[qo] - pa_x;
1029
- const dpy = candidates[qo + 1] - pa_y;
1030
- const dpz = candidates[qo + 2] - pa_z;
1031
- if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1032
- is_duplicate_k = true;
1033
- break;
1034
- }
1035
- }
1036
- }
1037
- if (is_duplicate_k) continue;
1038
-
1039
- if (isConcaveA) {
1040
- count = append_contact(count,
1041
- t_wx, t_wy, t_wz,
1042
- b_wx, b_wy, b_wz,
1043
- nx_b, ny_b, nz_b, d_k, tri_fid);
1044
- } else {
1045
- count = append_contact(count,
1046
- b_wx, b_wy, b_wz,
1047
- t_wx, t_wy, t_wz,
1048
- -nx_b, -ny_b, -nz_b, d_k, tri_fid);
1049
- }
1050
- }
1051
- continue;
1052
- }
1053
-
1054
- // Capsule-vs-triangle closed-form fast-path.
1055
- if (isCapsuleConvex) {
1056
- // Rotate each triangle vertex from concave-local to world.
1057
- // Inlined q · v · q* + translate per vertex — see PosedShape.support.
1058
- const axi = cqw * ax + cqy * az - cqz * ay;
1059
- const ayi = cqw * ay + cqz * ax - cqx * az;
1060
- const azi = cqw * az + cqx * ay - cqy * ax;
1061
- const awi = -cqx * ax - cqy * ay - cqz * az;
1062
- const ax_w = axi * cqw + awi * -cqx + ayi * -cqz - azi * -cqy + c_pos_x;
1063
- const ay_w = ayi * cqw + awi * -cqy + azi * -cqx - axi * -cqz + c_pos_y;
1064
- const az_w = azi * cqw + awi * -cqz + axi * -cqy - ayi * -cqx + c_pos_z;
1065
-
1066
- const bxi = cqw * bx + cqy * bz - cqz * by;
1067
- const byi = cqw * by + cqz * bx - cqx * bz;
1068
- const bzi = cqw * bz + cqx * by - cqy * bx;
1069
- const bwi = -cqx * bx - cqy * by - cqz * bz;
1070
- const bx_w = bxi * cqw + bwi * -cqx + byi * -cqz - bzi * -cqy + c_pos_x;
1071
- const by_w = byi * cqw + bwi * -cqy + bzi * -cqx - bxi * -cqz + c_pos_y;
1072
- const bz_w = bzi * cqw + bwi * -cqz + bxi * -cqy - byi * -cqx + c_pos_z;
1073
-
1074
- const cxi = cqw * cx_v + cqy * cz_v - cqz * cy_v;
1075
- const cyi = cqw * cy_v + cqz * cx_v - cqx * cz_v;
1076
- const czi = cqw * cz_v + cqx * cy_v - cqy * cx_v;
1077
- const cwi = -cqx * cx_v - cqy * cy_v - cqz * cz_v;
1078
- const cx_w = cxi * cqw + cwi * -cqx + cyi * -cqz - czi * -cqy + c_pos_x;
1079
- const cy_w = cyi * cqw + cwi * -cqy + czi * -cqx - cxi * -cqz + c_pos_y;
1080
- const cz_w = czi * cqw + cwi * -cqz + cxi * -cqy - cyi * -cqx + c_pos_z;
1081
-
1082
- const cap_cc = capsule_triangle_contact(
1083
- capsule_triangle_result,
1084
- convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
1085
- convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
1086
- capsule_shape.radius, capsule_shape.height * 0.5,
1087
- ax_w, ay_w, az_w,
1088
- bx_w, by_w, bz_w,
1089
- cx_w, cy_w, cz_w
1090
- );
1091
- if (cap_cc === 0) continue;
1092
-
1093
- // Each sub-contact's normal can be distinct (a flat
1094
- // capsule produces primary + endpoint contacts whose
1095
- // normals all point same direction, but an edge or
1096
- // vertex contact at a cap can deviate). Apply
1097
- // face-normal rejection per-contact.
1098
- for (let k = 0; k < cap_cc; k++) {
1099
- const base = k * CAPSULE_TRIANGLE_CONTACT_STRIDE;
1100
- const cap_x = capsule_triangle_result[base];
1101
- const cap_y = capsule_triangle_result[base + 1];
1102
- const cap_z = capsule_triangle_result[base + 2];
1103
- const ct_x = capsule_triangle_result[base + 3];
1104
- const ct_y = capsule_triangle_result[base + 4];
1105
- const ct_z = capsule_triangle_result[base + 5];
1106
- const n_t2cap_x = capsule_triangle_result[base + 6];
1107
- const n_t2cap_y = capsule_triangle_result[base + 7];
1108
- const n_t2cap_z = capsule_triangle_result[base + 8];
1109
- const d_c = capsule_triangle_result[base + 9];
1110
-
1111
- // One-sided face-normal rejection: capsule must be
1112
- // on the outward side of the triangle.
1113
- if (n_t2cap_x * fnx_w + n_t2cap_y * fny_w + n_t2cap_z * fnz_w <= 0) continue;
1114
-
1115
- // Stored normal "convex → concave" = "capsule →
1116
- // triangle" = negated t2cap.
1117
- const nx_c = -n_t2cap_x;
1118
- const ny_c = -n_t2cap_y;
1119
- const nz_c = -n_t2cap_z;
1120
- const stored_nx_c = isConcaveA ? nx_c : -nx_c;
1121
- const stored_ny_c = isConcaveA ? ny_c : -ny_c;
1122
- const stored_nz_c = isConcaveA ? nz_c : -nz_c;
1123
-
1124
- // Dedup against earlier candidates in THIS dispatch.
1125
- let is_duplicate_c = false;
1126
- for (let q = pair_start_count; q < count; q++) {
1127
- const qo = q * CANDIDATE_STRIDE;
1128
- const dnx = candidates[qo + 6] - stored_nx_c;
1129
- const dny = candidates[qo + 7] - stored_ny_c;
1130
- const dnz = candidates[qo + 8] - stored_nz_c;
1131
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1132
- const pa_x = isConcaveA ? ct_x : cap_x;
1133
- const pa_y = isConcaveA ? ct_y : cap_y;
1134
- const pa_z = isConcaveA ? ct_z : cap_z;
1135
- const dpx = candidates[qo] - pa_x;
1136
- const dpy = candidates[qo + 1] - pa_y;
1137
- const dpz = candidates[qo + 2] - pa_z;
1138
- if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1139
- is_duplicate_c = true;
1140
- break;
1141
- }
1142
- }
1143
- }
1144
- if (is_duplicate_c) continue;
1145
-
1146
- if (isConcaveA) {
1147
- count = append_contact(count,
1148
- ct_x, ct_y, ct_z,
1149
- cap_x, cap_y, cap_z,
1150
- nx_c, ny_c, nz_c, d_c, tri_fid);
1151
- } else {
1152
- count = append_contact(count,
1153
- cap_x, cap_y, cap_z,
1154
- ct_x, ct_y, ct_z,
1155
- -nx_c, -ny_c, -nz_c, d_c, tri_fid);
1156
- }
1157
- }
1158
- continue;
1159
- }
1160
-
1161
- // Robust GJK + EPA (see gjk/gjk_epa_penetration.js): returns the
1162
- // penetration depth and writes a UNIT axis into epa_result. We scale
1163
- // it to the MTV vector (ex,ey,ez) the tuned sign-check / one-sided
1164
- // rejection / dedup below already consume. The old gjk_with_axis +
1165
- // expanding_polytope_algorithm pair was replaced because it returned
1166
- // a non-minimal axis on degenerate simplices (see memory:
1167
- // feedback_epa_unreliable_polytopes); MPR is kept as a secondary
1168
- // safety net for the rare case the robust query reports no overlap.
1169
- let ex, ey, ez, depth;
1170
- const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1171
- if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1172
- ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1173
- depth = pen_depth;
1174
- } else {
1175
- if (!mpr(epa_result, 0, posed_a, posed_b)) continue;
1176
- ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1177
- depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1178
- if (!(depth > 0) || !Number.isFinite(depth)) continue;
1179
- }
1180
-
1181
- // Sign-check: validate MTV direction against (convex −
1182
- // triangle_centroid). EPA's polytope-closest-face normal
1183
- // can come out on the wrong side of the origin; this flip
1184
- // canonicalises MTV to point from triangle (A) toward the
1185
- // convex shape (B).
1186
- const cent_lx = (ax + bx + cx_v) / 3;
1187
- const cent_ly = (ay + by + cy_v) / 3;
1188
- const cent_lz = (az + bz + cz_v) / 3;
1189
- // Inlined q · v · q* + translate.
1190
- const cix = cqw * cent_lx + cqy * cent_lz - cqz * cent_ly;
1191
- const ciy = cqw * cent_ly + cqz * cent_lx - cqx * cent_lz;
1192
- const ciz = cqw * cent_lz + cqx * cent_ly - cqy * cent_lx;
1193
- const ciw = -cqx * cent_lx - cqy * cent_ly - cqz * cent_lz;
1194
- const cent_wx = cix * cqw + ciw * -cqx + ciy * -cqz - ciz * -cqy + c_pos_x;
1195
- const cent_wy = ciy * cqw + ciw * -cqy + ciz * -cqx - cix * -cqz + c_pos_y;
1196
- const cent_wz = ciz * cqw + ciw * -cqz + cix * -cqy - ciy * -cqx + c_pos_z;
1197
-
1198
- const tri_ab_x = convex_wx - cent_wx;
1199
- const tri_ab_y = convex_wy - cent_wy;
1200
- const tri_ab_z = convex_wz - cent_wz;
1201
- if (ex * tri_ab_x + ey * tri_ab_y + ez * tri_ab_z < 0) {
1202
- ex = -ex; ey = -ey; ez = -ez;
1203
- }
1204
-
1205
- // One-sided rejection (post-sign-check). MTV now points
1206
- // from triangle (A) toward convex shape (B). For a CCW
1207
- // outward-wound triangle, the convex shape is on the
1208
- // OUTWARD side iff MTV aligns with the face normal. If it
1209
- // opposes the face normal, the convex shape is on the
1210
- // back / inward side — invalid for heightmap/mesh (it's
1211
- // inside the solid). Skip the triangle rather than push
1212
- // the body deeper into the solid through the opposite
1213
- // face on the next step.
1214
- if (ex * fnx_w + ey * fny_w + ez * fnz_w <= 0) continue;
1215
-
1216
- // After the validated flip, MTV points from triangle
1217
- // (concave-side A) into convex (B). Stored normal is
1218
- // "B → A" so we negate. Final A/B order matches
1219
- // append_contact's contract: original A first, original B
1220
- // second, normal "from original B toward original A".
1221
- const inv = 1 / depth;
1222
- const nx = -ex * inv;
1223
- const ny = -ey * inv;
1224
- const nz = -ez * inv;
1225
-
1226
- // Dedup against earlier contacts emitted in THIS dispatch.
1227
- // Adjacent triangles (heightmap cells sharing a diagonal;
1228
- // mesh triangles sharing an edge or vertex) often report
1229
- // identical contacts (same normal, same body-centre
1230
- // application points), and feeding duplicates to the
1231
- // sequential-impulse solver makes it escalate the impulse
1232
- // across iterations without bound. We deduplicate by
1233
- // contact normal within a small angular threshold: same
1234
- // body pair + same normal + same body-centre positions =
1235
- // the same physical contact, only one copy belongs in the
1236
- // manifold.
1237
- const stored_nx = isConcaveA ? nx : -nx;
1238
- const stored_ny = isConcaveA ? ny : -ny;
1239
- const stored_nz = isConcaveA ? nz : -nz;
1240
- let is_duplicate = false;
1241
- for (let k = pair_start_count; k < count; k++) {
1242
- const ko = k * CANDIDATE_STRIDE;
1243
- const dnx = candidates[ko + 6] - stored_nx;
1244
- const dny = candidates[ko + 7] - stored_ny;
1245
- const dnz = candidates[ko + 8] - stored_nz;
1246
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1247
- is_duplicate = true;
1248
- break;
1249
- }
1250
- }
1251
- if (is_duplicate) continue;
1252
-
1253
- // Contact application points: body centres rather than
1254
- // the triangle's centroid. The existing GJK+EPA fallback
1255
- // (below) uses the same convention — for vertical contacts
1256
- // (sphere/box on a flat surface, the common case), the
1257
- // lever arm r × n vanishes and the impulse resolves
1258
- // cleanly.
1259
- if (isConcaveA) {
1260
- count = append_contact(count,
1261
- c_pos_x, c_pos_y, c_pos_z,
1262
- convex_wx, convex_wy, convex_wz,
1263
- nx, ny, nz, depth, tri_fid);
1264
- } else {
1265
- count = append_contact(count,
1266
- convex_wx, convex_wy, convex_wz,
1267
- c_pos_x, c_pos_y, c_pos_z,
1268
- -nx, -ny, -nz, depth, tri_fid);
1269
- }
1270
- }
1271
-
1272
- return count;
1273
- }
1274
-
1275
- // Robust GJK + EPA fallback for any convex pair without a closed form or a
1276
- // dedicated branch above (e.g. a primitive vs an authored ConvexHullShape3D,
1277
- // a cylinder/cone). gjk_epa_penetration returns the depth and writes a UNIT
1278
- // axis into epa_result; we scale to the MTV vector (ex,ey,ez) the body-centre
1279
- // sign-guard below consumes. Replaces the old gjk_with_axis +
1280
- // expanding_polytope_algorithm pair, which returned a non-minimal axis on the
1281
- // degenerate simplices its GJK produced (see memory:
1282
- // feedback_epa_unreliable_polytopes). MPR is kept as a secondary safety net.
1283
- posed_a.setup(colA.shape, trA.position, trA.rotation);
1284
- posed_b.setup(colB.shape, trB.position, trB.rotation);
1285
-
1286
- let ex, ey, ez, depth;
1287
- const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1288
- if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1289
- ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1290
- depth = pen_depth;
1291
- } else {
1292
- if (!mpr(epa_result, 0, posed_a, posed_b)) return count;
1293
- ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1294
- depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1295
- if (!(depth > 0) || !Number.isFinite(depth)) return count;
1296
- }
1297
-
1298
- // EPA's output vector should point along the minimum-translation
1299
- // axis from A into B (the direction you'd push B by to separate).
1300
- // On non-convergent EPA common on smooth high-poly colliders like
1301
- // a torus knot, where the polytope can't tighten onto the true
1302
- // closest face within the iteration cap the fallback returns
1303
- // *some* face's normal, and that face can be on either side of the
1304
- // origin. Resulting MTV occasionally points B *into* A instead of
1305
- // *away from* A; the solver then sees a "separating" relative
1306
- // velocity, clamps the normal impulse to zero, and the bodies pass
1307
- // straight through each other.
1308
- //
1309
- // Validate against the vector from body A's centre to body B's
1310
- // centre — EPA's direction must correlate positively with it. Flip
1311
- // if not. For convex bodies whose origin is inside the geometry,
1312
- // body-centre-to-body-centre is a strong proxy for the correct
1313
- // separation direction. For pathological cases (deeply
1314
- // interpenetrating bodies with coincident centres) the dot product
1315
- // is near zero and we leave EPA's guess intact.
1316
- const ab_x = trB.position.x - trA.position.x;
1317
- const ab_y = trB.position.y - trA.position.y;
1318
- const ab_z = trB.position.z - trA.position.z;
1319
- if (ex * ab_x + ey * ab_y + ez * ab_z < 0) {
1320
- ex = -ex; ey = -ey; ez = -ez;
1321
- }
1322
-
1323
- const inv = 1 / depth;
1324
- const nx = -ex * inv, ny = -ey * inv, nz = -ez * inv; // stored normal, B → A
1325
-
1326
- // Contact application points: body centres rather than support-function
1327
- // witnesses. The natural-sounding alternative `posed_a.support(+EPA_dir)`
1328
- // and `posed_b.support(-EPA_dir)` interacts badly with flat-faced
1329
- // supports: a large floor's support in `+Y` returns a *corner* (the box
1330
- // support is multi-valued along an axis-aligned direction and picks the
1331
- // sign-tied corner), which can be tens of metres from the actual
1332
- // contact patch. That blows up the solver's lever arm `r × n` and
1333
- // collapses `m_eff` to ~0, leaving the impulse vanishingly small.
1334
- //
1335
- // Body centres give `r n` for vertical contacts (sphere/knot landing
1336
- // on a floor the common case), so `r × n = 0` and `m_eff = invM`
1337
- // resolves the impact cleanly. For oblique contacts the application
1338
- // point lies on the line between centres, which is the right-ish lever
1339
- // arm for translational impulse and degrades gracefully on angular
1340
- // response. A future closed-form mesh-vs-box path will compute a
1341
- // proper face-projected witness; until then, body centres are the
1342
- // robust fallback for the GJK+EPA route.
1343
- // GJK+EPA fallback has no stable per-frame feature info; emit fid = 0
1344
- // so match-and-merge uses position-fallback. Single contact per pair.
1345
- return append_contact(count,
1346
- trA.position.x, trA.position.y, trA.position.z,
1347
- trB.position.x, trB.position.y, trB.position.z,
1348
- nx, ny, nz,
1349
- depth, 0
1350
- );
1351
- }
1352
-
1353
- // Reusable single-pair adapters for the penetration query below no per-call
1354
- // allocation. dispatch_pair only reads `.shape` off a collider and
1355
- // `.position` / `.rotation` off a transform, so these minimal stand-ins are
1356
- // all it needs.
1357
- const _pp_colA = { shape: null };
1358
- const _pp_colB = { shape: null };
1359
- const _pp_trA = { position: null, rotation: null };
1360
- const _pp_trB = { position: null, rotation: null };
1361
-
1362
-
1363
- /**
1364
- * Single-pair penetration query: the depth and world normal of the DEEPEST
1365
- * contact the narrowphase would generate for one posed shape pair.
1366
- *
1367
- * Routes through the exact same {@link dispatch_pair} the contact solver
1368
- * consumes closed-form for every sphere / box / capsule pair (box-box via
1369
- * SAT, so the true minimum-translation axis is found rather than the
1370
- * centroid-seeded portal MPR would pick), triangle decomposition + closed-form
1371
- * per triangle for convex-vs-concave, and GJK + EPA (+ MPR) for any other
1372
- * convex pair. The deepest contact's depth is the minimum-translation distance
1373
- * and its normal is the MTV axis, so the result is correct for every shape pair
1374
- * the engine can build and agrees bit-for-bit with what the solver acts on.
1375
- *
1376
- * The normal follows the narrowphase's stored convention: a unit vector
1377
- * pointing from B toward A — the direction to translate A to separate it.
1378
- *
1379
- * Concave-vs-concave is not dispatched (the narrowphase skips it) and returns
1380
- * 0; callers needing to reject that case must check `is_convex` themselves.
1381
- *
1382
- * Not re-entrant: shares the module-level candidate / scratch buffers with
1383
- * {@link narrowphase_step}. Intended for main-thread queries run outside a
1384
- * step (depenetration, overlap depth, tooling) — never from inside one.
1385
- *
1386
- * @param {Float64Array|number[]} out_normal length ≥ 3; receives the unit B→A
1387
- * normal on penetration (untouched when the return value is 0)
1388
- * @param {AbstractShape3D} shapeA
1389
- * @param {{x:number,y:number,z:number}} posA
1390
- * @param {{x:number,y:number,z:number,w:number}} rotA
1391
- * @param {AbstractShape3D} shapeB
1392
- * @param {{x:number,y:number,z:number}} posB
1393
- * @param {{x:number,y:number,z:number,w:number}} rotB
1394
- * @returns {number} deepest penetration depth (> 0) or 0 if separated
1395
- */
1396
- export function deepest_pair_penetration(out_normal, shapeA, posA, rotA, shapeB, posB, rotB) {
1397
- _pp_colA.shape = shapeA;
1398
- _pp_trA.position = posA;
1399
- _pp_trA.rotation = rotA;
1400
- _pp_colB.shape = shapeB;
1401
- _pp_trB.position = posB;
1402
- _pp_trB.rotation = rotB;
1403
-
1404
- const n = dispatch_pair(0, _pp_colA, _pp_trA, _pp_colB, _pp_trB);
1405
- if (n === 0) {
1406
- return 0;
1407
- }
1408
-
1409
- // Deepest contact = the minimum-translation depth; its stored normal is the
1410
- // separation axis. (For multi-point manifolds — box-box, capsule-box, a
1411
- // convex straddling several mesh triangles — every point shares the
1412
- // separating axis, so the max depth along it is the distance to separate.)
1413
- let best_depth = -1;
1414
- let best_off = 0;
1415
- for (let i = 0; i < n; i++) {
1416
- const off = i * CANDIDATE_STRIDE;
1417
- const d = candidates[off + 9];
1418
- if (d > best_depth) {
1419
- best_depth = d;
1420
- best_off = off;
1421
- }
1422
- }
1423
-
1424
- if (!(best_depth > 0) || !Number.isFinite(best_depth)) {
1425
- return 0;
1426
- }
1427
-
1428
- out_normal[0] = candidates[best_off + 6];
1429
- out_normal[1] = candidates[best_off + 7];
1430
- out_normal[2] = candidates[best_off + 8];
1431
-
1432
- return best_depth;
1433
- }
1434
-
1435
- /**
1436
- * For every pair in `pair_list`, do a cross-product over A's collider list ×
1437
- * B's collider list, accumulate candidate contacts, reduce to ≤4, and write
1438
- * to the manifold slot.
1439
- *
1440
- * @param {PairList} pair_list
1441
- * @param {ManifoldStore} manifolds
1442
- * @param {Array<Array<{collider: Collider, transform: Transform}>>} lists
1443
- * per-body collider lists, indexed by body-storage slot index. Typically
1444
- * `system.__body_collider_lists` — passed in directly so this helper
1445
- * has no dependency on `PhysicsSystem`.
1446
- */
1447
- export function narrowphase_step(pair_list, manifolds, lists) {
1448
- const count = pair_list.count;
1449
-
1450
- for (let i = 0; i < count; i++) {
1451
- const idA = pair_list.get_a(i);
1452
- const idB = pair_list.get_b(i);
1453
-
1454
- const idxA = body_id_index(idA);
1455
- const idxB = body_id_index(idB);
1456
-
1457
- const list_a = lists[idxA];
1458
- const list_b = lists[idxB];
1459
-
1460
- const slot = manifolds.find(idA, idB);
1461
-
1462
- if (list_a === undefined || list_b === undefined
1463
- || list_a.length === 0 || list_b.length === 0
1464
- ) {
1465
-
1466
- manifolds.clear_contacts(slot);
1467
- continue;
1468
-
1469
- }
1470
-
1471
- let cand_count = 0;
1472
-
1473
- const la_len = list_a.length;
1474
- const lb_len = list_b.length;
1475
-
1476
- for (let a = 0; a < la_len; a++) {
1477
- const ea = list_a[a];
1478
-
1479
- for (let b = 0; b < lb_len; b++) {
1480
- const eb = list_b[b];
1481
-
1482
- cand_count = dispatch_pair(
1483
- cand_count,
1484
- ea.collider,
1485
- ea.transform,
1486
- eb.collider,
1487
- eb.transform
1488
- );
1489
-
1490
- }
1491
- }
1492
-
1493
- if (cand_count === 0) {
1494
- // No contacts this frame for an existing manifold. Keep the
1495
- // slot in the cache (the grace window in advance_frame() will
1496
- // evict it if this persists), but zero out impulses and
1497
- // contact count — there's nothing for the solver to act on
1498
- // and stale impulses would mislead next frame's warm-start
1499
- // if contact re-establishes at a different feature.
1500
- manifolds.clear_contacts(slot);
1501
- continue;
1502
- }
1503
-
1504
- const kept = reduce_candidates(cand_count);
1505
-
1506
- // ── Match-and-merge: feature-id (with position fallback) ──────
1507
- //
1508
- // Snapshot prev-frame state for matching + impulse carry-over.
1509
- const data = manifolds.data_buffer;
1510
- const slot_off = manifolds.slot_data_offset(slot);
1511
- const prev_count_raw = manifolds.contact_count(slot);
1512
- const prev_count = prev_count_raw > MAX_CONTACTS_PER_MANIFOLD
1513
- ? MAX_CONTACTS_PER_MANIFOLD
1514
- : prev_count_raw;
1515
- for (let j = 0; j < prev_count; j++) {
1516
- const off = slot_off + j * CONTACT_STRIDE;
1517
- const snap_off = j * 7;
1518
- prev_snapshot[snap_off] = data[off + 13]; // feature_id
1519
- prev_snapshot[snap_off + 1] = data[off]; // world_a x
1520
- prev_snapshot[snap_off + 2] = data[off + 1]; // world_a y
1521
- prev_snapshot[snap_off + 3] = data[off + 2]; // world_a z
1522
- prev_snapshot[snap_off + 4] = data[off + 10]; // j_n
1523
- prev_snapshot[snap_off + 5] = data[off + 11]; // j_t1
1524
- prev_snapshot[snap_off + 6] = data[off + 12]; // j_t2
1525
- prev_claimed[j] = 0;
1526
- }
1527
-
1528
- // For each new candidate, find a matching prev contact.
1529
- // Step 1: feature-id match (only if BOTH sides have a non-zero
1530
- // feature_id fid = 0 means "no info").
1531
- // Step 2: position-fallback within MATCH_TOL_SQR.
1532
- for (let k = 0; k < kept; k++) {
1533
- const cand_off = k * CANDIDATE_STRIDE;
1534
- const cand_fid = candidates[cand_off + 10];
1535
- const cand_ax = candidates[cand_off];
1536
- const cand_ay = candidates[cand_off + 1];
1537
- const cand_az = candidates[cand_off + 2];
1538
-
1539
- let best_prev = -1;
1540
- if (cand_fid !== 0) {
1541
- for (let j = 0; j < prev_count; j++) {
1542
- if (prev_claimed[j]) continue;
1543
- if (prev_snapshot[j * 7] === cand_fid) {
1544
- best_prev = j;
1545
- break;
1546
- }
1547
- }
1548
- }
1549
- if (best_prev === -1) {
1550
- let best_d2 = MATCH_TOL_SQR;
1551
- for (let j = 0; j < prev_count; j++) {
1552
- if (prev_claimed[j]) continue;
1553
- const snap_off = j * 7;
1554
- const dx = prev_snapshot[snap_off + 1] - cand_ax;
1555
- const dy = prev_snapshot[snap_off + 2] - cand_ay;
1556
- const dz = prev_snapshot[snap_off + 3] - cand_az;
1557
- const d2 = dx * dx + dy * dy + dz * dz;
1558
- if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1559
- }
1560
- }
1561
- cand_to_prev[k] = best_prev;
1562
- if (best_prev !== -1) prev_claimed[best_prev] = 1;
1563
- }
1564
-
1565
- // Reset count without zeroing the data slab — set_contact below
1566
- // will overwrite geometry, and matched candidates will inherit
1567
- // the impulses we snapshotted above (written back at the new
1568
- // slot index after set_contact).
1569
- manifolds.begin_refill(slot);
1570
-
1571
- for (let k = 0; k < kept; k++) {
1572
- const off = k * CANDIDATE_STRIDE;
1573
- // Target slot index = k (we write candidates 0..kept-1 into
1574
- // slot indices 0..kept-1). The impulse carry-over below
1575
- // copies the matched prev contact's impulses into THIS k,
1576
- // so the geometric and warm-start state stay correlated
1577
- // even if the matching mapping permuted the order.
1578
- manifolds.set_contact(
1579
- slot, k,
1580
- candidates[off], candidates[off + 1], candidates[off + 2],
1581
- candidates[off + 3], candidates[off + 4], candidates[off + 5],
1582
- candidates[off + 6], candidates[off + 7], candidates[off + 8],
1583
- candidates[off + 9],
1584
- candidates[off + 10],
1585
- candidates[off + 11], candidates[off + 12]
1586
- );
1587
- const prev_j = cand_to_prev[k];
1588
- if (prev_j !== -1) {
1589
- // Copy prev_j's impulse to this slot index.
1590
- const dst_off = slot_off + k * CONTACT_STRIDE;
1591
- const src_off = prev_j * 7;
1592
- data[dst_off + 10] = prev_snapshot[src_off + 4];
1593
- data[dst_off + 11] = prev_snapshot[src_off + 5];
1594
- data[dst_off + 12] = prev_snapshot[src_off + 6];
1595
- } else {
1596
- // No match — explicitly zero the impulses, since this
1597
- // slot index may have stale data from an earlier frame's
1598
- // contact at the same index.
1599
- manifolds.clear_impulses(slot, k);
1600
- }
1601
- }
1602
- }
1603
- }
1604
-
1605
- /**
1606
- * Re-detect contact GEOMETRY for one existing manifold slot at the bodies'
1607
- * current poses, updating the witness points / normal / depth of the slot's
1608
- * existing contacts in place. Does NOT change the contact count, the
1609
- * feature ids, or the accumulated impulses it only refreshes geometry.
1610
- *
1611
- * This is the per-substep concave path (TGS): for a contact pair involving a
1612
- * concave body, the contact *feature* (which triangle is deepest, and its
1613
- * normal) genuinely changes as the body rocks, so the solver's cheap analytic
1614
- * refresh which freezes the feature for the whole outer step — pumps energy
1615
- * in. Re-running the narrowphase geometry each substep gives a fresh,
1616
- * correct normal so the body settles instead of rocking. Convex pairs keep
1617
- * the analytic refresh and never call this (their feature is stable).
1618
- *
1619
- * Matching is by feature id (stable per-triangle for the decomposition path),
1620
- * so a contact's geometry tracks the same triangle across substeps. A contact
1621
- * whose triangle isn't found this substep keeps its previous geometry (a rare
1622
- * transient; the once-per-frame {@link narrowphase_step} re-establishes the
1623
- * contact set next outer step). Count never changes here, so the solver's
1624
- * per-contact scratch (sized once at prepare) stays aligned.
1625
- *
1626
- * @param {ManifoldStore} manifolds
1627
- * @param {number} slot
1628
- * @param {Array<{collider: Collider, transform: Transform}>} list_a
1629
- * @param {Array<{collider: Collider, transform: Transform}>} list_b
1630
- */
1631
- export function redetect_pair_geometry(manifolds, slot, list_a, list_b) {
1632
- if (list_a === undefined || list_b === undefined) return;
1633
- const la_len = list_a.length;
1634
- const lb_len = list_b.length;
1635
- if (la_len === 0 || lb_len === 0) return;
1636
-
1637
- const count = manifolds.contact_count(slot);
1638
- if (count === 0) return;
1639
-
1640
- let cc = 0;
1641
- for (let a = 0; a < la_len; a++) {
1642
- const ea = list_a[a];
1643
- for (let b = 0; b < lb_len; b++) {
1644
- const eb = list_b[b];
1645
- cc = dispatch_pair(cc, ea.collider, ea.transform, eb.collider, eb.transform);
1646
- }
1647
- }
1648
- if (cc === 0) return; // nothing re-detected this substep — keep frozen geometry
1649
-
1650
- const data = manifolds.data_buffer;
1651
- const slot_off = manifolds.slot_data_offset(slot);
1652
-
1653
- // Match each existing contact to a DISTINCT fresh candidate. feature_id
1654
- // identifies the TRIANGLE, not the contact point the box/capsule-triangle
1655
- // paths emit several contacts for one triangle, all sharing that triangle's
1656
- // single fid (a flat box-on-heightmap cell yields fids like [6,6,6,7]). A
1657
- // plain first-match-by-fid therefore maps every same-fid existing contact
1658
- // onto the SAME candidate, collapsing the manifold to duplicate witness
1659
- // points a degenerate support polygon the solver can't damp (the
1660
- // box-on-heightmap rattle). So: gate by fid, disambiguate same-fid
1661
- // candidates by NEAREST previous witness (world-A) position, and claim each
1662
- // candidate so no two existing contacts take the same one. For the common
1663
- // unique-fid case (sphere/mesh: one contact per triangle) this picks that
1664
- // single candidate exactly once identical to the old behaviour.
1665
- for (let k = 0; k < cc; k++) redetect_claimed[k] = 0;
1666
-
1667
- for (let j = 0; j < count; j++) {
1668
- const off = slot_off + j * CONTACT_STRIDE;
1669
- const fid = data[off + 13];
1670
- if (fid === 0) continue; // no feature info to match on
1671
-
1672
- // Previous witness (world-A) of this existing contact — the anchor we
1673
- // disambiguate same-fid candidates against.
1674
- const pax = data[off];
1675
- const pay = data[off + 1];
1676
- const paz = data[off + 2];
1677
-
1678
- let best_k = -1;
1679
- let best_d2 = Infinity;
1680
- for (let k = 0; k < cc; k++) {
1681
- if (redetect_claimed[k] === 1) continue;
1682
- const co = k * CANDIDATE_STRIDE;
1683
- if (candidates[co + 10] !== fid) continue;
1684
- const dx = candidates[co] - pax;
1685
- const dy = candidates[co + 1] - pay;
1686
- const dz = candidates[co + 2] - paz;
1687
- const d2 = dx * dx + dy * dy + dz * dz;
1688
- if (d2 < best_d2) {
1689
- best_d2 = d2;
1690
- best_k = k;
1691
- }
1692
- }
1693
-
1694
- // Position fallback (mirrors narrowphase_step's match-and-merge). A
1695
- // single triangle's clipped contact count is NOT stable across sub-mm
1696
- // pose changes a box straddling a cell seam can have one triangle
1697
- // yield 4 points one substep and 3 the next — so an existing contact can
1698
- // outnumber this substep's same-fid candidates. Freezing its stale
1699
- // witness then duplicates a sibling contact's point, leaving a
1700
- // degenerate (sub-dimensional) manifold. Instead, claim the nearest
1701
- // unclaimed candidate of ANY fid: every contact keeps a DISTINCT live
1702
- // witness. The fid label is left intact and re-resolved by the next
1703
- // once-per-step narrowphase. (At a fixed pose the same-fid match always
1704
- // succeeds, so this never fires for the depth-equality guards or the
1705
- // one-contact-per-triangle sphere/mesh paths.)
1706
- if (best_k === -1) {
1707
- for (let k = 0; k < cc; k++) {
1708
- if (redetect_claimed[k] === 1) continue;
1709
- const co = k * CANDIDATE_STRIDE;
1710
- const dx = candidates[co] - pax;
1711
- const dy = candidates[co + 1] - pay;
1712
- const dz = candidates[co + 2] - paz;
1713
- const d2 = dx * dx + dy * dy + dz * dz;
1714
- if (d2 < best_d2) {
1715
- best_d2 = d2;
1716
- best_k = k;
1717
- }
1718
- }
1719
- }
1720
-
1721
- if (best_k === -1) continue; // no unclaimed candidate at all this substep — keep frozen geometry
1722
-
1723
- redetect_claimed[best_k] = 1;
1724
- const co = best_k * CANDIDATE_STRIDE;
1725
- // Overwrite geometry only: witnesses, normal, depth. (Count, feature
1726
- // ids and accumulated impulses are intentionally left untouched — see
1727
- // the function contract.)
1728
- data[off] = candidates[co];
1729
- data[off + 1] = candidates[co + 1];
1730
- data[off + 2] = candidates[co + 2];
1731
- data[off + 3] = candidates[co + 3];
1732
- data[off + 4] = candidates[co + 4];
1733
- data[off + 5] = candidates[co + 5];
1734
- data[off + 6] = candidates[co + 6];
1735
- data[off + 7] = candidates[co + 7];
1736
- data[off + 8] = candidates[co + 8];
1737
- data[off + 9] = candidates[co + 9];
1738
- }
1739
- }
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 { combine_friction, combine_restitution } from "../contact/combine_material.js";
5
+ import { CONTACT_STRIDE, MAX_CONTACTS_PER_MANIFOLD } from "../contact/ManifoldStore.js";
6
+ import { gjk_epa_penetration } from "../../../core/geom/3d/gjk/gjk_epa_penetration.js";
7
+ import { reduce_manifold_contacts } from "./reduce_manifold_contacts.js";
8
+ import { mpr } from "../../../core/geom/3d/gjk/mpr.js";
9
+ import { box_box_manifold, BOX_BOX_OUT_LENGTH } from "./box_box_manifold.js";
10
+ import { convex_hull_clip, CONVEX_CONVEX_OUT_LENGTH } from "./convex_convex_manifold.js";
11
+ import { get_mesh_convex_hull } from "./mesh_convex_hull.js";
12
+ import { mesh_mesh_tet_contacts } from "./mesh_mesh_tet_manifold.js";
13
+ import { box_triangle_contact, BOX_TRIANGLE_OUT_LENGTH } from "./box_triangle_contact.js";
14
+ import {
15
+ CAPSULE_BOX_CONTACT_STRIDE,
16
+ CAPSULE_BOX_MAX_CONTACTS,
17
+ capsule_box_multi_contacts,
18
+ capsule_capsule_contact,
19
+ CAPSULE_CAPSULE_CONTACT_STRIDE,
20
+ capsule_capsule_multi_contacts,
21
+ capsule_sphere_contact,
22
+ } from "./capsule_contacts.js";
23
+ import {
24
+ capsule_triangle_contact,
25
+ CAPSULE_TRIANGLE_CONTACT_STRIDE,
26
+ CAPSULE_TRIANGLE_MAX_CONTACTS,
27
+ } from "./capsule_triangle_contact.js";
28
+ import { aabb3_transform_oriented_inverse } from "../../../core/geom/3d/aabb/aabb3_transform_oriented_inverse.js";
29
+ import { decompose_to_triangles } from "./decomposition/decompose_to_triangles.js";
30
+ import { TRIANGLE_FLOAT_STRIDE } from "./decomposition/triangle_buffer_layout.js";
31
+ import { PosedShape3D } from "../../../core/geom/3d/shape/PosedShape3D.js";
32
+ import { sphere_box_contact } from "./sphere_box_contact.js";
33
+ import { sphere_sphere_contact } from "./sphere_sphere_contact.js";
34
+ import { sphere_triangle_contact } from "./sphere_triangle_contact.js";
35
+
36
+ const posed_a = new PosedShape3D();
37
+ const posed_b = new PosedShape3D();
38
+
39
+ // Penetration axis + depth from the robust GJK + EPA query
40
+ // (gjk_epa_penetration writes a unit normal here; it owns its own simplex
41
+ // internally, so the narrowphase no longer keeps a simplex buffer).
42
+ const epa_result = new Float64Array(3);
43
+ const sphere_result = new Float64Array(4);
44
+ const closed_form_result = new Float64Array(10);
45
+
46
+ /** Scratch for the capsule-capsule multi-contact manifold (2 contacts max). */
47
+ const capsule_capsule_result = new Float64Array(2 * CAPSULE_CAPSULE_CONTACT_STRIDE);
48
+ const sphere_triangle_result = new Float64Array(10);
49
+ const box_triangle_result = new Float64Array(BOX_TRIANGLE_OUT_LENGTH);
50
+ const capsule_triangle_result = new Float64Array(CAPSULE_TRIANGLE_MAX_CONTACTS * CAPSULE_TRIANGLE_CONTACT_STRIDE);
51
+ const box_manifold_result = new Float64Array(BOX_BOX_OUT_LENGTH);
52
+ const convex_manifold_result = new Float64Array(CONVEX_CONVEX_OUT_LENGTH);
53
+ const capsule_box_multi_result = new Float64Array(CAPSULE_BOX_MAX_CONTACTS * CAPSULE_BOX_CONTACT_STRIDE);
54
+
55
+ /**
56
+ * Candidate-contact stride: wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth,
57
+ * feature_id, friction, restitution.
58
+ *
59
+ * The `feature_id` (offset 10) is a stable cross-frame identifier of the
60
+ * geometric feature pair that produced this contact used by the
61
+ * match-and-merge pass in {@link narrowphase_step} to carry warm-start
62
+ * impulses from the previous frame's manifold to the slot index that
63
+ * corresponds to the same physical contact. A value of 0 means
64
+ * "no feature info, fall back to position matching".
65
+ *
66
+ * `friction` (offset 11) and `restitution` (offset 12) are the COMBINED
67
+ * coefficients for the specific (colliderA, colliderB) pair that produced this
68
+ * contact, combined here (the only place that knows the exact source collider
69
+ * on each side) and carried into the manifold so a compound body's per-collider
70
+ * materials are honoured per-contact.
71
+ *
72
+ * @type {number}
73
+ */
74
+ const CANDIDATE_STRIDE = 13;
75
+
76
+ /**
77
+ * Combined friction / restitution for the collider pair currently being
78
+ * dispatched. Set once at the top of {@link dispatch_pair} (which is called
79
+ * per collider pair) and written into every contact that call appends, so
80
+ * each contact carries the material of its actual source colliders. Module
81
+ * scratch rather than threaded through every `append_contact` call site.
82
+ * @type {number}
83
+ */
84
+ let g_pair_friction = 0;
85
+ let g_pair_restitution = 0;
86
+
87
+ /**
88
+ * Maximum number of contacts emitted into the per-pair manifold after the
89
+ * reduction step. Mirrors {@link MAX_CONTACTS_PER_MANIFOLD} in ManifoldStore.
90
+ * @type {number}
91
+ */
92
+ const MAX_KEPT = MAX_CONTACTS_PER_MANIFOLD;
93
+
94
+ /**
95
+ * Position-fallback tolerance for warm-start matching: when a candidate
96
+ * contact has no feature id (or none of the previous-frame contacts shares
97
+ * its id), match by closest world_a within this 3-D distance.
98
+ *
99
+ * 2 cm matches the original PLAN.md spec for "Persistent manifold cache"
100
+ * generous enough to follow small inter-frame contact migration on
101
+ * curved surfaces, tight enough that distinct contacts on a single
102
+ * manifold (typically >5 cm apart) don't get confused with each other.
103
+ *
104
+ * @type {number}
105
+ */
106
+ const MATCH_TOL_SQR = 0.02 * 0.02;
107
+
108
+ /**
109
+ * Per-(colliderA, colliderB) feature-id salt. Each emitter numbers its
110
+ * features in a small private range (sphere-box voronoi 1..27, capsule-box
111
+ * 1..k, triangles 1..N, …), so two SUB-PAIRS of a compound body can produce
112
+ * identical fids for unrelated features and warm-start impulses could be
113
+ * inherited across collider pairs. Salting by the sub-pair index keeps every
114
+ * fid unique across the body pair while staying exactly representable in f64
115
+ * (sub_pair · 2³² + fid stays far below 2⁵³ for any realistic compound; no
116
+ * single-pair emitter exceeds 2³²). Sub-pair 0 — the universal
117
+ * single-collider case — is left unsalted, so simple bodies see unchanged
118
+ * fids.
119
+ * @type {number}
120
+ */
121
+ const FID_PAIR_SALT = 0x100000000; // 2^32
122
+
123
+ /**
124
+ * Distance ceiling for a feature-id warm-start match. A fid match may track
125
+ * a fast-sliding contact far beyond {@link MATCH_TOL_SQR} that is its
126
+ * advantage over position matching but an impulse inherited across half a
127
+ * metre in one frame is stale by any standard (a teleported body re-deriving
128
+ * the same voronoi bucket, or a fid collision between unrelated features).
129
+ * The position fallback keeps its own 2 cm gate.
130
+ * @type {number}
131
+ */
132
+ const FID_MATCH_MAX_D2 = 0.5 * 0.5;
133
+
134
+ /**
135
+ * Scratch for the previous-frame contact snapshot taken at the top of the
136
+ * match-and-merge pass. Sized for {@link MAX_CONTACTS_PER_MANIFOLD}
137
+ * contacts, 7 floats per contact:
138
+ * 0 : feature_id
139
+ * 1, 2, 3 : world_a x, y, z (for position-fallback matching)
140
+ * 4, 5, 6 : j_n, j_t1, j_t2 (carried forward to the matched candidate)
141
+ *
142
+ * Snapshotting upfront decouples the read (from the slot's previous state)
143
+ * from the write (the new contact data + impulse copy), avoiding the
144
+ * read-after-write hazard when the matching mapping shuffles indices.
145
+ *
146
+ * @type {Float64Array}
147
+ */
148
+ const prev_snapshot = new Float64Array(MAX_CONTACTS_PER_MANIFOLD * 7);
149
+
150
+ /**
151
+ * Per-prev-contact "already claimed by a candidate" flag.
152
+ * @type {Uint8Array}
153
+ */
154
+ const prev_claimed = new Uint8Array(MAX_CONTACTS_PER_MANIFOLD);
155
+
156
+ /**
157
+ * For each kept candidate, the matched prev-contact index in
158
+ * `[0, prev_count)` or `-1` if no match.
159
+ * @type {Int32Array}
160
+ */
161
+ const cand_to_prev = new Int32Array(MAX_KEPT);
162
+
163
+ /**
164
+ * Per-candidate "already claimed" flags for {@link redetect_pair_geometry}'s
165
+ * 1:1 existing-contact fresh-candidate matching. Sized to the candidate
166
+ * buffer capacity (64) so it covers any per-pair candidate count. Without it,
167
+ * several existing contacts that share one triangle's `feature_id` (the
168
+ * box/capsule-triangle paths emit multiple contacts per triangle) would all
169
+ * match the same first candidate, collapsing the manifold to duplicate witness
170
+ * points.
171
+ * @type {Uint8Array}
172
+ */
173
+ const redetect_claimed = new Uint8Array(64);
174
+
175
+ /**
176
+ * Per body-pair scratch buffer for candidate contacts produced by the
177
+ * cross-product of A's colliders × B's colliders. Sized generously for
178
+ * typical compound bodies (each collider-pair contributes 1..4 contacts;
179
+ * 64 covers up to 4 colliders per body × 4 colliders × 4 contacts = 64).
180
+ * @type {Float64Array}
181
+ */
182
+ const candidates = new Float64Array(64 * CANDIDATE_STRIDE);
183
+
184
+ /**
185
+ * Maximum triangles a concave-side enumerator can produce per pair.
186
+ * The query AABB is bounded by the broadphase's fattened envelope of
187
+ * the convex-side body, so a single concave-vs-convex pair typically
188
+ * yields tens of triangles, not thousands. 1024 is the safety cap.
189
+ *
190
+ * For a heightmap the per-cell triangle count scales O(N²) with the
191
+ * shape's {@link HeightMapShape3D#tessellation} (a sub-cell quad is 2
192
+ * triangles, and there are N×N sub-cells per sampler cell). The bounded
193
+ * query AABB keeps the cell count small, so a typical pair stays well
194
+ * inside 1024 at moderate tessellation; the silent-drop backstop below
195
+ * covers any overflow at extreme values.
196
+ *
197
+ * Enumerators return the TRUE overlap count and have no bounds check of
198
+ * their own: writes past the scratch buffer are dropped (typed-array OOB
199
+ * semantics), so on overflow the consume loop MUST clamp the returned
200
+ * count to this cap or it reads `undefined` past the end and poisons the
201
+ * manifold with NaN. The worst case after clamping is a missed contact
202
+ * on a far edge of the filtered region, which the next-step
203
+ * rebroadphase corrects.
204
+ *
205
+ * @type {number}
206
+ */
207
+ const MAX_TRIANGLES_PER_PAIR = 1024;
208
+
209
+ /**
210
+ * Per-pair scratch for the concave-side triangle decomposition.
211
+ * @type {Float64Array}
212
+ */
213
+ const triangle_buffer = new Float64Array(MAX_TRIANGLES_PER_PAIR * TRIANGLE_FLOAT_STRIDE);
214
+
215
+ /**
216
+ * Flyweight triangle shape — rebound to each successive triangle slice
217
+ * of `triangle_buffer` during the concave-side dispatch loop. Zero
218
+ * allocation per triangle.
219
+ * @type {Triangle3D}
220
+ */
221
+ const triangle_shape = new Triangle3D();
222
+
223
+ /**
224
+ * Scratch AABB buffers used only by the concave-side dispatch:
225
+ * - `concave_local_aabb` : convex shape's local AABB (input to oriented transform)
226
+ * - `concave_world_aabb` : convex shape's world AABB
227
+ * - `concave_query_aabb` : convex shape's AABB projected into concave's body-local frame
228
+ * (what the triangle enumerator filters against)
229
+ */
230
+ const concave_local_aabb = new Float64Array(6);
231
+ const concave_world_aabb = new Float64Array(6);
232
+ const concave_query_aabb = new Float64Array(6);
233
+
234
+ /**
235
+ * Rotate + translate a triangle's three vertices from a concave body's
236
+ * local frame into world space: writes 9 floats `(A_w, B_w, C_w)` into
237
+ * `out`. Inlined `q · v · q*` + translate per vertex (see
238
+ * PosedShape3D.support for the V8-inliner rationale); the operation order
239
+ * matches the blocks this replaced bit-exactly. Per-vertex cost ~21 flops
240
+ * × 3 essentially free next to any narrowphase kernel it feeds.
241
+ *
242
+ * Shared by the sphere / box / capsule closed-form triangle fast-paths in
243
+ * the concave dispatch, which each carried an identical inlined copy.
244
+ */
245
+ function triangle_vertices_to_world(out, ax, ay, az, bx, by, bz, cx, cy, cz, qx, qy, qz, qw, px, py, pz) {
246
+ const axi = qw * ax + qy * az - qz * ay;
247
+ const ayi = qw * ay + qz * ax - qx * az;
248
+ const azi = qw * az + qx * ay - qy * ax;
249
+ const awi = -qx * ax - qy * ay - qz * az;
250
+
251
+ out[0] = axi * qw + awi * -qx + ayi * -qz - azi * -qy + px;
252
+ out[1] = ayi * qw + awi * -qy + azi * -qx - axi * -qz + py;
253
+ out[2] = azi * qw + awi * -qz + axi * -qy - ayi * -qx + pz;
254
+
255
+ const bxi = qw * bx + qy * bz - qz * by;
256
+ const byi = qw * by + qz * bx - qx * bz;
257
+ const bzi = qw * bz + qx * by - qy * bx;
258
+ const bwi = -qx * bx - qy * by - qz * bz;
259
+
260
+ out[3] = bxi * qw + bwi * -qx + byi * -qz - bzi * -qy + px;
261
+ out[4] = byi * qw + bwi * -qy + bzi * -qx - bxi * -qz + py;
262
+ out[5] = bzi * qw + bwi * -qz + bxi * -qy - byi * -qx + pz;
263
+
264
+ const cxi = qw * cx + qy * cz - qz * cy;
265
+ const cyi = qw * cy + qz * cx - qx * cz;
266
+ const czi = qw * cz + qx * cy - qy * cx;
267
+ const cwi = -qx * cx - qy * cy - qz * cz;
268
+
269
+ out[6] = cxi * qw + cwi * -qx + cyi * -qz - czi * -qy + px;
270
+ out[7] = cyi * qw + cwi * -qy + czi * -qx - cxi * -qz + py;
271
+ out[8] = czi * qw + cwi * -qz + cxi * -qy - cyi * -qx + pz;
272
+ }
273
+
274
+ /** Destination for {@link triangle_vertices_to_world}. */
275
+ const scratch_tri_world = new Float64Array(9);
276
+
277
+ /**
278
+ * Append one contact to the candidate buffer. Returns the new count.
279
+ *
280
+ * @param {number} count
281
+ * @param {number} wax
282
+ * @param {number} way
283
+ * @param {number} waz
284
+ * @param {number} wbx
285
+ * @param {number} wby
286
+ * @param {number} wbz
287
+ * @param {number} nx
288
+ * @param {number} ny
289
+ * @param {number} nz
290
+ * @param {number} depth
291
+ * @param {number} feature_id stable cross-frame ID for warm-start matching;
292
+ * `0` means no info match-and-merge will fall back to position.
293
+ * @returns {number}
294
+ */
295
+ function append_contact(count, wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth, feature_id) {
296
+ if (count * CANDIDATE_STRIDE >= candidates.length){
297
+ return count;
298
+ }
299
+
300
+ const off = count * CANDIDATE_STRIDE;
301
+
302
+ candidates[off] = wax; candidates[off + 1] = way; candidates[off + 2] = waz;
303
+ candidates[off + 3] = wbx; candidates[off + 4] = wby; candidates[off + 5] = wbz;
304
+ candidates[off + 6] = nx; candidates[off + 7] = ny; candidates[off + 8] = nz;
305
+ candidates[off + 9] = depth;
306
+ candidates[off + 10] = feature_id;
307
+ candidates[off + 11] = g_pair_friction;
308
+ candidates[off + 12] = g_pair_restitution;
309
+
310
+ return count + 1;
311
+ }
312
+
313
+ /**
314
+ * Compute a stable voronoi-region feature id for a sphere-vs-box contact.
315
+ * Returns one of 27 values in `[1, 27]` based on which of the 27 voronoi
316
+ * regions of the box the sphere centre lies in (8 corners + 12 edges + 6
317
+ * faces + 1 interior). Stable across frames as long as the sphere stays
318
+ * in the same region — the typical case in steady-state contact.
319
+ *
320
+ * Encodes:
321
+ * bucket_x ∈ {0, 1, 2} = sign(lx) bucket in box-local frame
322
+ * bucket_y, bucket_z similarly
323
+ * fid = 1 + bucket_x + 3·bucket_y + 9·bucket_z → 1..27
324
+ *
325
+ * The "+1" offset ensures the result is never 0, since 0 is reserved
326
+ * for "no feature info, use position-fallback".
327
+ *
328
+ * @param {number} sx sphere centre x
329
+ * @param {number} sy
330
+ * @param {number} sz
331
+ * @param {number} bx box centre x
332
+ * @param {number} by
333
+ * @param {number} bz
334
+ * @param {number} bqx box quaternion x
335
+ * @param {number} bqy
336
+ * @param {number} bqz
337
+ * @param {number} bqw
338
+ * @param {number} hx box half-extent x
339
+ * @param {number} hy
340
+ * @param {number} hz
341
+ * @returns {number}
342
+ */
343
+ function sphere_box_voronoi_fid(sx, sy, sz, bx, by, bz, bqx, bqy, bqz, bqw, hx, hy, hz) {
344
+ // Inverse-rotate (sx - bx, sy - by, sz - bz) by the box's quaternion
345
+ // to get the sphere centre in box-local frame. Inlined for the same
346
+ // V8-inliner reason described in PosedShape3D.support see
347
+ // core/geom/vec3/v3_quat3_apply_inverse.js for the canonical form.
348
+ const dx = sx - bx;
349
+ const dy = sy - by;
350
+ const dz = sz - bz;
351
+
352
+ const tx0 = bqw * dx - bqy * dz + bqz * dy;
353
+ const ty0 = bqw * dy - bqz * dx + bqx * dz;
354
+ const tz0 = bqw * dz - bqx * dy + bqy * dx;
355
+ const tw0 = bqx * dx + bqy * dy + bqz * dz;
356
+
357
+ const lx = tx0 * bqw + tw0 * bqx + ty0 * bqz - tz0 * bqy;
358
+ const ly = ty0 * bqw + tw0 * bqy + tz0 * bqx - tx0 * bqz;
359
+ const lz = tz0 * bqw + tw0 * bqz + tx0 * bqy - ty0 * bqx;
360
+
361
+ const bx_b = lx <= -hx ? 0 : (lx >= hx ? 2 : 1);
362
+ const by_b = ly <= -hy ? 0 : (ly >= hy ? 2 : 1);
363
+ const bz_b = lz <= -hz ? 0 : (lz >= hz ? 2 : 1);
364
+
365
+ return 1 + bx_b + 3 * by_b + 9 * bz_b;
366
+ }
367
+
368
+ /**
369
+ * Multi-point contact manifold between two convex polytopes (authored
370
+ * ConvexHullShape3D, or a convex MeshShape3D represented as one hull). Robust
371
+ * GJK + EPA finds the separating axis in O(support queries) face-count
372
+ * independent, where SAT's edge-pair term is prohibitive then
373
+ * {@link convex_hull_clip} clips the reference/incident faces into a face-on-face
374
+ * patch around it (Bullet's clipHullAgainstHull). A single GJK+EPA contact can't
375
+ * resist a stack's toppling torque; the clipped patch holds it. No cross-frame
376
+ * state → reset-and-resimulate determinism.
377
+ *
378
+ * @param {number} count
379
+ * @param {{vertices,face_offsets,face_loops,support}} hullA in A's local frame
380
+ * @param {Transform} trA
381
+ * @param {{vertices,face_offsets,face_loops,support}} hullB in B's local frame
382
+ * @param {Transform} trB
383
+ * @param {Function} append_contact
384
+ * @returns {number}
385
+ */
386
+ function hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact) {
387
+ posed_a.setup(hullA, trA.position, trA.rotation);
388
+ posed_b.setup(hullB, trB.position, trB.rotation);
389
+ const depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
390
+ if (!(depth > 0) || !Number.isFinite(depth)) return count;
391
+
392
+ // convex_hull_clip orients the axis B→A robustly (via vertex centroids) and
393
+ // writes the final normal into convex_manifold_result[0..2].
394
+ convex_hull_clip(convex_manifold_result,
395
+ hullA, trA.position, trA.rotation,
396
+ hullB, trB.position, trB.rotation,
397
+ epa_result[0], epa_result[1], epa_result[2]);
398
+ const nx = convex_manifold_result[0], ny = convex_manifold_result[1], nz = convex_manifold_result[2];
399
+ const cc = convex_manifold_result[3] | 0;
400
+ // Clipped points migrate across features as the hulls rotate, so leave
401
+ // fid = 0 and let the match-and-merge pass position-match (as box-box does).
402
+ for (let k = 0; k < cc; k++) {
403
+ const base = 4 + k * 7;
404
+ count = append_contact(count,
405
+ convex_manifold_result[base], convex_manifold_result[base + 1], convex_manifold_result[base + 2],
406
+ convex_manifold_result[base + 3], convex_manifold_result[base + 4], convex_manifold_result[base + 5],
407
+ nx, ny, nz,
408
+ convex_manifold_result[base + 6],
409
+ 0);
410
+ }
411
+ return count;
412
+ }
413
+
414
+ /**
415
+ * Run pairwise narrowphase for one (colliderA, colliderB) tuple dispatches
416
+ * by shape type and appends 0..K contacts to the candidate buffer. Returns
417
+ * the new candidate count.
418
+ *
419
+ * @param {number} count
420
+ * @param {Collider} colA
421
+ * @param {Transform} trA
422
+ * @param {Collider} colB
423
+ * @param {Transform} trB
424
+ * @returns {number}
425
+ */
426
+ function dispatch_pair(count, colA, trA, colB, trB) {
427
+ const shapeA = colA.shape;
428
+ const shapeB = colB.shape;
429
+
430
+ // Per-contact materials: combine the two source colliders' coefficients
431
+ // once here (this is the only place that knows the exact collider on each
432
+ // side) and stamp them onto every contact this dispatch appends. The
433
+ // `deepest_pair_penetration` query passes bare `{shape}` adapters with no
434
+ // material fields — it never writes to a manifold, so 0 is fine there.
435
+ const fa = colA.friction, fb = colB.friction;
436
+
437
+ if (fa !== undefined && fb !== undefined) {
438
+
439
+ g_pair_friction = combine_friction(fa, fb);
440
+ g_pair_restitution = combine_restitution(colA.restitution, colB.restitution);
441
+
442
+ } else {
443
+ g_pair_friction = 0;
444
+ g_pair_restitution = 0;
445
+ }
446
+
447
+ // isSphereShape3D covers both UnitSphereShape3D (fixed radius 1) and
448
+ // SphereShape3D (arbitrary radius). Both expose `radius`.
449
+ const isSphereA = shapeA.isSphereShape3D === true;
450
+ const isSphereB = shapeB.isSphereShape3D === true;
451
+ // isBoxShape3D covers both UnitCubeShape3D (fixed 0.5) and BoxShape3D
452
+ // (arbitrary half-extents). Both expose `half_extents` as a Vector3.
453
+ const isBoxA = shapeA.isBoxShape3D === true;
454
+ const isBoxB = shapeB.isBoxShape3D === true;
455
+ const isCapsuleA = shapeA.isCapsuleShape3D === true;
456
+ const isCapsuleB = shapeB.isCapsuleShape3D === true;
457
+
458
+ // sphere-sphere
459
+ if (isSphereA && isSphereB) {
460
+ const ra = shapeA.radius, rb = shapeB.radius;
461
+
462
+ const ok = sphere_sphere_contact(
463
+ sphere_result,
464
+ trA.position.x, trA.position.y, trA.position.z,
465
+ trB.position.x, trB.position.y, trB.position.z,
466
+ ra, rb
467
+ );
468
+
469
+ if (!ok) return count;
470
+
471
+ const nx = sphere_result[0], ny = sphere_result[1], nz = sphere_result[2];
472
+
473
+ // Sphere-sphere produces exactly one contact per pair; fid = 1
474
+ // identifies it as a real feature (distinguishes from "no info" = 0)
475
+ // and is trivially stable across frames. Witnesses are the surface
476
+ // points along the (unit) normal, scaled by each sphere's radius.
477
+ return append_contact(count,
478
+ trA.position.x - nx * ra, trA.position.y - ny * ra, trA.position.z - nz * ra,
479
+ trB.position.x + nx * rb, trB.position.y + ny * rb, trB.position.z + nz * rb,
480
+ nx, ny, nz, sphere_result[3], 1);
481
+ }
482
+
483
+ // sphere box
484
+ if ((isSphereA && isBoxB) || (isBoxA && isSphereB)) {
485
+ const sphereTr = isSphereA ? trA : trB;
486
+ const sphereShape = isSphereA ? shapeA : shapeB;
487
+ const boxTr = isSphereA ? trB : trA;
488
+ const boxShape = isSphereA ? shapeB : shapeA;
489
+ const bh = boxShape.half_extents;
490
+
491
+ const ok = sphere_box_contact(
492
+ closed_form_result,
493
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius,
494
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
495
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
496
+ bh.x, bh.y, bh.z
497
+ );
498
+
499
+ if (!ok) return count;
500
+
501
+ let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
502
+
503
+ const depth = closed_form_result[3];
504
+ const wsx = closed_form_result[4], wsy = closed_form_result[5], wsz = closed_form_result[6];
505
+ const wbx = closed_form_result[7], wby = closed_form_result[8], wbz = closed_form_result[9];
506
+ // Feature id from the box-local voronoi region (1..27) stable
507
+ // while the sphere stays on the same face / edge / vertex of the
508
+ // box, which is the steady-state case for resting / sliding contact.
509
+
510
+ const fid = sphere_box_voronoi_fid(
511
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z,
512
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
513
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
514
+ bh.x, bh.y, bh.z
515
+ );
516
+
517
+ if (isSphereA) {
518
+ return append_contact(count, wsx, wsy, wsz, wbx, wby, wbz, nx, ny, nz, depth, fid);
519
+ } else {
520
+ return append_contact(count, wbx, wby, wbz, wsx, wsy, wsz, -nx, -ny, -nz, depth, fid);
521
+ }
522
+ }
523
+
524
+ // box-box multi-point
525
+ if (isBoxA && isBoxB) {
526
+ const ah = shapeA.half_extents;
527
+ const bh = shapeB.half_extents;
528
+ const ok = box_box_manifold(
529
+ box_manifold_result,
530
+ trA.position.x, trA.position.y, trA.position.z,
531
+ trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
532
+ ah.x, ah.y, ah.z,
533
+ trB.position.x, trB.position.y, trB.position.z,
534
+ trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
535
+ bh.x, bh.y, bh.z
536
+ );
537
+ if (!ok) return count;
538
+ const nx = box_manifold_result[0], ny = box_manifold_result[1], nz = box_manifold_result[2];
539
+ const cc = box_manifold_result[3] | 0;
540
+ // Box-box manifolds: closed-form clipping doesn't expose stable
541
+ // per-contact feature ids (a contact migrates from "incident-vertex k"
542
+ // to "clip-intersection on edge j" as the boxes rotate). Leave
543
+ // fid = 0 so the match-and-merge pass uses position-fallback
544
+ // the per-contact clipped points are spread by face geometry and
545
+ // typically stay >>MATCH_TOL apart, so position matching is
546
+ // unambiguous frame-to-frame.
547
+ for (let k = 0; k < cc; k++) {
548
+ const base = 4 + k * 7;
549
+ count = append_contact(count,
550
+ box_manifold_result[base], box_manifold_result[base + 1], box_manifold_result[base + 2],
551
+ box_manifold_result[base + 3], box_manifold_result[base + 4], box_manifold_result[base + 5],
552
+ nx, ny, nz,
553
+ box_manifold_result[base + 6],
554
+ 0);
555
+ }
556
+ return count;
557
+ }
558
+
559
+ // capsule-capsule (multi-point: near-parallel overlap emits both ends of
560
+ // the overlap interval; everything else the single closest-point contact)
561
+ if (isCapsuleA && isCapsuleB) {
562
+ const a_shape = colA.shape, b_shape = colB.shape;
563
+ const n_cc = capsule_capsule_multi_contacts(
564
+ capsule_capsule_result,
565
+ trA.position.x, trA.position.y, trA.position.z,
566
+ trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
567
+ a_shape.radius, a_shape.height * 0.5,
568
+ trB.position.x, trB.position.y, trB.position.z,
569
+ trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
570
+ b_shape.radius, b_shape.height * 0.5
571
+ );
572
+ for (let k = 0; k < n_cc; k++) {
573
+ const off = k * CAPSULE_CAPSULE_CONTACT_STRIDE;
574
+ // fid = k + 1: stable per interval end (1-based; 0 = no info).
575
+ count = append_contact(count,
576
+ capsule_capsule_result[off], capsule_capsule_result[off + 1], capsule_capsule_result[off + 2],
577
+ capsule_capsule_result[off + 3], capsule_capsule_result[off + 4], capsule_capsule_result[off + 5],
578
+ capsule_capsule_result[off + 6], capsule_capsule_result[off + 7], capsule_capsule_result[off + 8],
579
+ capsule_capsule_result[off + 9], k + 1);
580
+ }
581
+ return count;
582
+ }
583
+
584
+ // capsule sphere
585
+ if ((isCapsuleA && isSphereB) || (isSphereA && isCapsuleB)) {
586
+ const capsuleTr = isCapsuleA ? trA : trB;
587
+ const capsuleShape = isCapsuleA ? colA.shape : colB.shape;
588
+ const sphereTr = isCapsuleA ? trB : trA;
589
+ const sphereShape = isCapsuleA ? colB.shape : colA.shape;
590
+ const ok = capsule_sphere_contact(
591
+ closed_form_result,
592
+ capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
593
+ capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
594
+ capsuleShape.radius, capsuleShape.height * 0.5,
595
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius
596
+ );
597
+ if (!ok) return count;
598
+ let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
599
+ const depth = closed_form_result[3];
600
+ const cap_x = closed_form_result[4], cap_y = closed_form_result[5], cap_z = closed_form_result[6];
601
+ const sph_x = closed_form_result[7], sph_y = closed_form_result[8], sph_z = closed_form_result[9];
602
+ // Single contact per capsule-sphere pair; fid = 1.
603
+ if (isCapsuleA) {
604
+ return append_contact(count, cap_x, cap_y, cap_z, sph_x, sph_y, sph_z, nx, ny, nz, depth, 1);
605
+ } else {
606
+ return append_contact(count, sph_x, sph_y, sph_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, 1);
607
+ }
608
+ }
609
+
610
+ // capsule box (multi-point — 1 closest-segment + up to 2 cap-centres)
611
+ if ((isCapsuleA && isBoxB) || (isBoxA && isCapsuleB)) {
612
+ const capsuleTr = isCapsuleA ? trA : trB;
613
+ const capsuleShape = isCapsuleA ? shapeA : shapeB;
614
+ const boxTr = isCapsuleA ? trB : trA;
615
+ const boxShape = isCapsuleA ? shapeB : shapeA;
616
+ const bh = boxShape.half_extents;
617
+ const cc = capsule_box_multi_contacts(
618
+ capsule_box_multi_result,
619
+ capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
620
+ capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
621
+ capsuleShape.radius, capsuleShape.height * 0.5,
622
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
623
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
624
+ bh.x, bh.y, bh.z
625
+ );
626
+ if (cc === 0) return count;
627
+ // multi_result layout per contact: cap_x/y/z (A side), box_x/y/z (B side), nx/ny/nz, depth.
628
+ // Feature id per sub-contact: 1 = closest-segment, 2/3 = caps. The
629
+ // emission order from capsule_box_multi_contacts is stable across
630
+ // frames for the same geometric configuration.
631
+ for (let k = 0; k < cc; k++) {
632
+ const o = k * CAPSULE_BOX_CONTACT_STRIDE;
633
+ const cap_x = capsule_box_multi_result[o];
634
+ const cap_y = capsule_box_multi_result[o + 1];
635
+ const cap_z = capsule_box_multi_result[o + 2];
636
+ const box_x = capsule_box_multi_result[o + 3];
637
+ const box_y = capsule_box_multi_result[o + 4];
638
+ const box_z = capsule_box_multi_result[o + 5];
639
+ const nx = capsule_box_multi_result[o + 6];
640
+ const ny = capsule_box_multi_result[o + 7];
641
+ const nz = capsule_box_multi_result[o + 8];
642
+ const depth = capsule_box_multi_result[o + 9];
643
+ const sub_fid = k + 1;
644
+ if (isCapsuleA) {
645
+ count = append_contact(count, cap_x, cap_y, cap_z, box_x, box_y, box_z, nx, ny, nz, depth, sub_fid);
646
+ } else {
647
+ count = append_contact(count, box_x, box_y, box_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, sub_fid);
648
+ }
649
+ }
650
+ return count;
651
+ }
652
+
653
+ // convex hull convex hull (multi-point clipping via GJK + EPA + face-clip).
654
+ if (shapeA.isConvexHullShape3D === true && shapeB.isConvexHullShape3D === true) {
655
+ return hull_pair_contacts(count, shapeA, trA, shapeB, trB, append_contact);
656
+ }
657
+
658
+ // mesh ↔ mesh. A globally-convex mesh collides as a single cached hull
659
+ // (one GJK + EPA + clip) — the greedy decomposition fragments even a convex
660
+ // mesh, so detecting convexity and routing through the hull path is the
661
+ // scalable fast path. When BOTH meshes are convex, use it; otherwise fall to
662
+ // the per-piece decomposed path (convex pieces vs convex pieces).
663
+ if (shapeA.isMeshShape3D === true && shapeB.isMeshShape3D === true) {
664
+ const hullA = get_mesh_convex_hull(shapeA);
665
+ const hullB = get_mesh_convex_hull(shapeB);
666
+ if (hullA !== null && hullB !== null) {
667
+ return hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact);
668
+ }
669
+ return mesh_mesh_tet_contacts(count, shapeA, trA, shapeB, trB, append_contact);
670
+ }
671
+
672
+ // ── Concave (non-convex) path ───────────────────────────────────────
673
+ //
674
+ // If either shape has `is_convex === false`, GJK on the whole shape
675
+ // produces incorrect results (Minkowski difference is not convex).
676
+ // We decompose the concave side into triangles overlapping the
677
+ // convex side's AABB, then run convex per-triangle GJK + EPA.
678
+ //
679
+ // Concave-vs-concave is intentionally NOT supported in v1: the
680
+ // M×N triangle pairs would dominate runtime, and the physics
681
+ // engine's design contract requires at least one side to be
682
+ // static / kinematic for concave shapes anyway (the broadphase +
683
+ // filter should keep such pairs out of the narrowphase entirely).
684
+ // If one slips through, we skip rather than burn cycles.
685
+ const isConcaveA = shapeA.is_convex === false;
686
+ const isConcaveB = shapeB.is_convex === false;
687
+ if (isConcaveA && isConcaveB) return count;
688
+ if (isConcaveA || isConcaveB) {
689
+ const concave_col = isConcaveA ? colA : colB;
690
+ const concave_tr = isConcaveA ? trA : trB;
691
+ const convex_col = isConcaveA ? colB : colA;
692
+ const convex_tr = isConcaveA ? trB : trA;
693
+
694
+ // 1. Convex shape's world AABB.
695
+ convex_col.shape.compute_bounding_box(concave_local_aabb);
696
+ aabb3_transform_oriented(
697
+ concave_world_aabb, 0,
698
+ concave_local_aabb[0], concave_local_aabb[1], concave_local_aabb[2],
699
+ concave_local_aabb[3], concave_local_aabb[4], concave_local_aabb[5],
700
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
701
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w
702
+ );
703
+
704
+ // 2. Project into concave's body-local frame.
705
+ aabb3_transform_oriented_inverse(
706
+ concave_query_aabb, 0,
707
+ concave_world_aabb,
708
+ concave_tr.position.x, concave_tr.position.y, concave_tr.position.z,
709
+ concave_tr.rotation.x, concave_tr.rotation.y, concave_tr.rotation.z, concave_tr.rotation.w
710
+ );
711
+
712
+ // 3. Decompose concave shape into triangles overlapping the query.
713
+ // The enumerator returns the TRUE overlap count — clamp to the
714
+ // scratch capacity (overflow writes were dropped; reading past the
715
+ // buffer would poison the manifold with NaN).
716
+ let tri_count = decompose_to_triangles(
717
+ triangle_buffer, 0, concave_col.shape,
718
+ concave_query_aabb[0], concave_query_aabb[1], concave_query_aabb[2],
719
+ concave_query_aabb[3], concave_query_aabb[4], concave_query_aabb[5]
720
+ );
721
+ if (tri_count > MAX_TRIANGLES_PER_PAIR) tri_count = MAX_TRIANGLES_PER_PAIR;
722
+ if (tri_count === 0) return count;
723
+
724
+ // 4. Set up the convex side once; the concave side gets a
725
+ // Triangle3D rebound to each triangle in the loop. We keep
726
+ // the concave side as our internal "A" so EPA's sign-check
727
+ // convention matches the convex fallback below; the final
728
+ // append_contact() swaps if the original A was the convex one.
729
+ posed_b.setup(convex_col.shape, convex_tr.position, convex_tr.rotation);
730
+ posed_a.shape = triangle_shape;
731
+ posed_a.px = concave_tr.position.x;
732
+ posed_a.py = concave_tr.position.y;
733
+ posed_a.pz = concave_tr.position.z;
734
+ posed_a.qx = concave_tr.rotation.x;
735
+ posed_a.qy = concave_tr.rotation.y;
736
+ posed_a.qz = concave_tr.rotation.z;
737
+ posed_a.qw = concave_tr.rotation.w;
738
+
739
+ // Pre-compute the centre-axis used by EPA's sign-check loop.
740
+ // For convex-vs-convex this is `(B - A) = convex_centre − concave_centre`.
741
+ const convex_wx = convex_tr.position.x;
742
+ const convex_wy = convex_tr.position.y;
743
+ const convex_wz = convex_tr.position.z;
744
+
745
+ // Track the candidate-buffer index at the start of this
746
+ // concave dispatch — the per-triangle dedup pass scans from
747
+ // here to the current count, ignoring contacts from earlier
748
+ // collider pairs in the same body pair.
749
+ const pair_start_count = count;
750
+
751
+ // Pre-compute concave's rotation components for the q · v · q⁻¹
752
+ // rotations done per-triangle below (face normal + centroid).
753
+ const cqx = concave_tr.rotation.x;
754
+ const cqy = concave_tr.rotation.y;
755
+ const cqz = concave_tr.rotation.z;
756
+ const cqw = concave_tr.rotation.w;
757
+ const c_pos_x = concave_tr.position.x;
758
+ const c_pos_y = concave_tr.position.y;
759
+ const c_pos_z = concave_tr.position.z;
760
+
761
+ // Sphere fast-path: when the convex side is a sphere we bypass GJK+EPA
762
+ // entirely per triangle and use the closed-form
763
+ // {@link sphere_triangle_contact}. This avoids the EPA precision
764
+ // wall on Triangle3D (whose support function is degenerate along
765
+ // the face normal all 3 vertices project to the same value),
766
+ // which was producing noisy depths at small penetrations and
767
+ // letting dropped spheres tunnel through heightmaps / meshes.
768
+ const isSphereConvex = convex_col.shape.isSphereShape3D === true;
769
+ const sphere_radius = isSphereConvex ? convex_col.shape.radius : 0;
770
+
771
+ // Box fast-path: closed-form {@link box_triangle_contact} via SAT
772
+ // over 13 axes + polygon clipping for face-vs-face contacts.
773
+ // Same motivation as the sphere path Triangle3D's degenerate
774
+ // face-normal support kills EPA precision and produces noisy
775
+ // depths. The box path uses world-space triangle vertices.
776
+ const isBoxConvex = convex_col.shape.isBoxShape3D === true;
777
+ const box_half_extents = isBoxConvex ? convex_col.shape.half_extents : null;
778
+
779
+ // Capsule fast-path: closed-form {@link capsule_triangle_contact}
780
+ // via segment-vs-triangle closest-point + cap-centre sphere
781
+ // queries for a multi-point manifold. Same motivation as the
782
+ // sphere and box paths.
783
+ const isCapsuleConvex = convex_col.shape.isCapsuleShape3D === true;
784
+ const capsule_shape = isCapsuleConvex ? convex_col.shape : null;
785
+
786
+ for (let i = 0; i < tri_count; i++) {
787
+ const tri_offset = i * TRIANGLE_FLOAT_STRIDE;
788
+ triangle_shape.bind(triangle_buffer, tri_offset);
789
+
790
+ const ax = triangle_buffer[tri_offset ];
791
+ const ay = triangle_buffer[tri_offset + 1];
792
+ const az = triangle_buffer[tri_offset + 2];
793
+
794
+ const bx = triangle_buffer[tri_offset + 3];
795
+ const by = triangle_buffer[tri_offset + 4];
796
+ const bz = triangle_buffer[tri_offset + 5];
797
+
798
+ const cx_v = triangle_buffer[tri_offset + 6];
799
+ const cy_v = triangle_buffer[tri_offset + 7];
800
+ const cz_v = triangle_buffer[tri_offset + 8];
801
+
802
+ // Triangle decomposition emits a stable per-triangle feature_id
803
+ // at offset 9 (TRIANGLE_FEATURE_ID_OFFSET) same triangle of
804
+ // the same shape gets the same id across frames. This is the
805
+ // gold-standard fid for the match-and-merge pass.
806
+ const tri_fid = triangle_buffer[tri_offset + 9];
807
+
808
+ // Triangle face normal in body-local frame: (B − A) × (C − A).
809
+ // Winding convention (CCW from outside) gives an outward face
810
+ // normal the heightmap / mesh enumerators both promise this.
811
+ const e1x_l = bx - ax, e1y_l = by - ay, e1z_l = bz - az;
812
+ const e2x_l = cx_v - ax, e2y_l = cy_v - ay, e2z_l = cz_v - az;
813
+
814
+ const fnx_l = e1y_l * e2z_l - e1z_l * e2y_l;
815
+ const fny_l = e1z_l * e2x_l - e1x_l * e2z_l;
816
+ const fnz_l = e1x_l * e2y_l - e1y_l * e2x_l;
817
+
818
+ // Rotate face normal to world via concave's quaternion
819
+ // (q · v · q⁻¹). Inlined for V8 inliner — see PosedShape3D.support.
820
+ const fnix = cqw * fnx_l + cqy * fnz_l - cqz * fny_l;
821
+ const fniy = cqw * fny_l + cqz * fnx_l - cqx * fnz_l;
822
+ const fniz = cqw * fnz_l + cqx * fny_l - cqy * fnx_l;
823
+ const fniw = -cqx * fnx_l - cqy * fny_l - cqz * fnz_l;
824
+
825
+ const fnx_w = fnix * cqw - fniw * cqx - fniy * cqz + fniz * cqy;
826
+ const fny_w = fniy * cqw - fniw * cqy - fniz * cqx + fnix * cqz;
827
+ const fnz_w = fniz * cqw - fniw * cqz - fnix * cqy + fniy * cqx;
828
+
829
+ // Sphere-vs-triangle closed-form fast-path.
830
+ if (isSphereConvex) {
831
+
832
+ // Rotate each triangle vertex from concave-local to world
833
+ // via the shared helper.
834
+ triangle_vertices_to_world(
835
+ scratch_tri_world,
836
+ ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
837
+ cqx, cqy, cqz, cqw,
838
+ c_pos_x, c_pos_y, c_pos_z
839
+ );
840
+ const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
841
+ const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
842
+ const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
843
+
844
+ const ok = sphere_triangle_contact(
845
+ sphere_triangle_result,
846
+ convex_wx, convex_wy, convex_wz, sphere_radius,
847
+ ax_w, ay_w, az_w,
848
+ bx_w, by_w, bz_w,
849
+ cx_w, cy_w, cz_w
850
+ );
851
+
852
+ if (!ok) continue;
853
+
854
+ // sphere_triangle_contact's normal points from the
855
+ // triangle surface toward the sphere centre — same
856
+ // direction as the post-sign-check EPA MTV in the
857
+ // fallback path below (concave A → convex B).
858
+ const sd = sphere_triangle_result[3];
859
+ const n_t2s_x = sphere_triangle_result[0];
860
+ const n_t2s_y = sphere_triangle_result[1];
861
+ const n_t2s_z = sphere_triangle_result[2];
862
+
863
+ // One-sided rejection: the sphere must lie on the
864
+ // outward side of the triangle. If the contact normal
865
+ // opposes the face normal, the sphere is behind /
866
+ // inside the solid skip rather than push it deeper.
867
+ if (n_t2s_x * fnx_w + n_t2s_y * fny_w + n_t2s_z * fnz_w <= 0) continue;
868
+
869
+ // Stored normal convention is "B → A". `nx, ny, nz` here
870
+ // points convex → concave (which the existing EPA branch
871
+ // also produces just before append). Dedup uses the
872
+ // post-swap stored_n so adjacent-triangle duplicates
873
+ // collapse the same way regardless of code path.
874
+ const nx_s = -n_t2s_x;
875
+ const ny_s = -n_t2s_y;
876
+ const nz_s = -n_t2s_z;
877
+
878
+ const stored_nx_s = isConcaveA ? nx_s : -nx_s;
879
+ const stored_ny_s = isConcaveA ? ny_s : -ny_s;
880
+ const stored_nz_s = isConcaveA ? nz_s : -nz_s;
881
+ let is_duplicate_s = false;
882
+ for (let k = pair_start_count; k < count; k++) {
883
+ const ko = k * CANDIDATE_STRIDE;
884
+ const dnx = candidates[ko + 6] - stored_nx_s;
885
+ const dny = candidates[ko + 7] - stored_ny_s;
886
+ const dnz = candidates[ko + 8] - stored_nz_s;
887
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
888
+ is_duplicate_s = true;
889
+ break;
890
+ }
891
+ }
892
+ if (is_duplicate_s) continue;
893
+
894
+ // Surface witnesses from the closed-form solver:
895
+ // result[4..6] = sphere surface point (convex side)
896
+ // result[7..9] = triangle closest point (concave side)
897
+ // Use the actual witnesses rather than body centres —
898
+ // unlike the EPA fallback (which uses body centres
899
+ // because flat-faced support witnesses can be arbitrarily
900
+ // far from the contact patch), closest-point-on-triangle
901
+ // is exact and lies on the contact patch.
902
+ const sphere_wx = sphere_triangle_result[4];
903
+ const sphere_wy = sphere_triangle_result[5];
904
+ const sphere_wz = sphere_triangle_result[6];
905
+ const tri_wx = sphere_triangle_result[7];
906
+ const tri_wy = sphere_triangle_result[8];
907
+ const tri_wz = sphere_triangle_result[9];
908
+ if (isConcaveA) {
909
+ count = append_contact(count,
910
+ tri_wx, tri_wy, tri_wz,
911
+ sphere_wx, sphere_wy, sphere_wz,
912
+ nx_s, ny_s, nz_s, sd, tri_fid);
913
+ } else {
914
+ count = append_contact(count,
915
+ sphere_wx, sphere_wy, sphere_wz,
916
+ tri_wx, tri_wy, tri_wz,
917
+ -nx_s, -ny_s, -nz_s, sd, tri_fid);
918
+ }
919
+ continue;
920
+ }
921
+
922
+ // Box-vs-triangle closed-form fast-path.
923
+ if (isBoxConvex) {
924
+ // Rotate each triangle vertex from concave-local to world
925
+ // via the shared helper.
926
+ triangle_vertices_to_world(
927
+ scratch_tri_world,
928
+ ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
929
+ cqx, cqy, cqz, cqw,
930
+ c_pos_x, c_pos_y, c_pos_z
931
+ );
932
+ const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
933
+ const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
934
+ const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
935
+
936
+ const ok = box_triangle_contact(
937
+ box_triangle_result,
938
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
939
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
940
+ box_half_extents.x, box_half_extents.y, box_half_extents.z,
941
+ ax_w, ay_w, az_w,
942
+ bx_w, by_w, bz_w,
943
+ cx_w, cy_w, cz_w
944
+ );
945
+ if (!ok) continue;
946
+
947
+ // Same convention as sphere_triangle: result normal points
948
+ // from triangle surface toward box centre. To match the
949
+ // EPA branch's `nx, ny, nz` (which points convex
950
+ // concave = box → triangle), negate.
951
+ const n_t2box_x = box_triangle_result[0];
952
+ const n_t2box_y = box_triangle_result[1];
953
+ const n_t2box_z = box_triangle_result[2];
954
+
955
+ // One-sided rejection: the box must lie on the outward
956
+ // side of the triangle. If the contact normal opposes
957
+ // the face normal, the box is inside the solid skip.
958
+ if (n_t2box_x * fnx_w + n_t2box_y * fny_w + n_t2box_z * fnz_w <= 0) continue;
959
+
960
+ const nx_b = -n_t2box_x;
961
+ const ny_b = -n_t2box_y;
962
+ const nz_b = -n_t2box_z;
963
+ const stored_nx_b = isConcaveA ? nx_b : -nx_b;
964
+ const stored_ny_b = isConcaveA ? ny_b : -ny_b;
965
+ const stored_nz_b = isConcaveA ? nz_b : -nz_b;
966
+
967
+ // Emit each clipped contact. The contact count from
968
+ // box_triangle_contact is at most 4; combined with the
969
+ // existing per-pair candidate cap (64 entries), tens of
970
+ // overlapping triangles per pair are safely accommodated.
971
+ const cc = box_triangle_result[3] | 0;
972
+ for (let k = 0; k < cc; k++) {
973
+ const base = 4 + k * 7;
974
+ const t_wx = box_triangle_result[base];
975
+ const t_wy = box_triangle_result[base + 1];
976
+ const t_wz = box_triangle_result[base + 2];
977
+ const b_wx = box_triangle_result[base + 3];
978
+ const b_wy = box_triangle_result[base + 4];
979
+ const b_wz = box_triangle_result[base + 5];
980
+ const d_k = box_triangle_result[base + 6];
981
+
982
+ // Dedup against earlier candidates in THIS dispatch
983
+ // (adjacent triangles can produce coincident contacts
984
+ // same as the sphere and EPA paths).
985
+ let is_duplicate_k = false;
986
+ for (let q = pair_start_count; q < count; q++) {
987
+ const qo = q * CANDIDATE_STRIDE;
988
+ const dnx = candidates[qo + 6] - stored_nx_b;
989
+ const dny = candidates[qo + 7] - stored_ny_b;
990
+ const dnz = candidates[qo + 8] - stored_nz_b;
991
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
992
+ // Same normal; check positions too (a
993
+ // multi-point manifold has distinct points
994
+ // with the same normal those should NOT
995
+ // be deduped). Compare the world-A side
996
+ // (triangle surface or box surface depending
997
+ // on dispatcher swap).
998
+ const pa_x = isConcaveA ? t_wx : b_wx;
999
+ const pa_y = isConcaveA ? t_wy : b_wy;
1000
+ const pa_z = isConcaveA ? t_wz : b_wz;
1001
+ const dpx = candidates[qo] - pa_x;
1002
+ const dpy = candidates[qo + 1] - pa_y;
1003
+ const dpz = candidates[qo + 2] - pa_z;
1004
+ if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1005
+ is_duplicate_k = true;
1006
+ break;
1007
+ }
1008
+ }
1009
+ }
1010
+ if (is_duplicate_k) continue;
1011
+
1012
+ if (isConcaveA) {
1013
+ count = append_contact(count,
1014
+ t_wx, t_wy, t_wz,
1015
+ b_wx, b_wy, b_wz,
1016
+ nx_b, ny_b, nz_b, d_k, tri_fid);
1017
+ } else {
1018
+ count = append_contact(count,
1019
+ b_wx, b_wy, b_wz,
1020
+ t_wx, t_wy, t_wz,
1021
+ -nx_b, -ny_b, -nz_b, d_k, tri_fid);
1022
+ }
1023
+ }
1024
+ continue;
1025
+ }
1026
+
1027
+ // Capsule-vs-triangle closed-form fast-path.
1028
+ if (isCapsuleConvex) {
1029
+ // Rotate each triangle vertex from concave-local to world
1030
+ // via the shared helper.
1031
+ triangle_vertices_to_world(
1032
+ scratch_tri_world,
1033
+ ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
1034
+ cqx, cqy, cqz, cqw,
1035
+ c_pos_x, c_pos_y, c_pos_z
1036
+ );
1037
+ const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
1038
+ const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
1039
+ const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
1040
+
1041
+ const cap_cc = capsule_triangle_contact(
1042
+ capsule_triangle_result,
1043
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
1044
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
1045
+ capsule_shape.radius, capsule_shape.height * 0.5,
1046
+ ax_w, ay_w, az_w,
1047
+ bx_w, by_w, bz_w,
1048
+ cx_w, cy_w, cz_w
1049
+ );
1050
+ if (cap_cc === 0) continue;
1051
+
1052
+ // Each sub-contact's normal can be distinct (a flat
1053
+ // capsule produces primary + endpoint contacts whose
1054
+ // normals all point ≈ same direction, but an edge or
1055
+ // vertex contact at a cap can deviate). Apply
1056
+ // face-normal rejection per-contact.
1057
+ for (let k = 0; k < cap_cc; k++) {
1058
+ const base = k * CAPSULE_TRIANGLE_CONTACT_STRIDE;
1059
+ const cap_x = capsule_triangle_result[base];
1060
+ const cap_y = capsule_triangle_result[base + 1];
1061
+ const cap_z = capsule_triangle_result[base + 2];
1062
+ const ct_x = capsule_triangle_result[base + 3];
1063
+ const ct_y = capsule_triangle_result[base + 4];
1064
+ const ct_z = capsule_triangle_result[base + 5];
1065
+ const n_t2cap_x = capsule_triangle_result[base + 6];
1066
+ const n_t2cap_y = capsule_triangle_result[base + 7];
1067
+ const n_t2cap_z = capsule_triangle_result[base + 8];
1068
+ const d_c = capsule_triangle_result[base + 9];
1069
+
1070
+ // One-sided face-normal rejection: capsule must be
1071
+ // on the outward side of the triangle.
1072
+ if (n_t2cap_x * fnx_w + n_t2cap_y * fny_w + n_t2cap_z * fnz_w <= 0) continue;
1073
+
1074
+ // Stored normal "convex concave" = "capsule
1075
+ // triangle" = negated t2cap.
1076
+ const nx_c = -n_t2cap_x;
1077
+ const ny_c = -n_t2cap_y;
1078
+ const nz_c = -n_t2cap_z;
1079
+ const stored_nx_c = isConcaveA ? nx_c : -nx_c;
1080
+ const stored_ny_c = isConcaveA ? ny_c : -ny_c;
1081
+ const stored_nz_c = isConcaveA ? nz_c : -nz_c;
1082
+
1083
+ // Dedup against earlier candidates in THIS dispatch.
1084
+ let is_duplicate_c = false;
1085
+ for (let q = pair_start_count; q < count; q++) {
1086
+ const qo = q * CANDIDATE_STRIDE;
1087
+ const dnx = candidates[qo + 6] - stored_nx_c;
1088
+ const dny = candidates[qo + 7] - stored_ny_c;
1089
+ const dnz = candidates[qo + 8] - stored_nz_c;
1090
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1091
+ const pa_x = isConcaveA ? ct_x : cap_x;
1092
+ const pa_y = isConcaveA ? ct_y : cap_y;
1093
+ const pa_z = isConcaveA ? ct_z : cap_z;
1094
+ const dpx = candidates[qo] - pa_x;
1095
+ const dpy = candidates[qo + 1] - pa_y;
1096
+ const dpz = candidates[qo + 2] - pa_z;
1097
+ if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1098
+ is_duplicate_c = true;
1099
+ break;
1100
+ }
1101
+ }
1102
+ }
1103
+ if (is_duplicate_c) continue;
1104
+
1105
+ if (isConcaveA) {
1106
+ count = append_contact(count,
1107
+ ct_x, ct_y, ct_z,
1108
+ cap_x, cap_y, cap_z,
1109
+ nx_c, ny_c, nz_c, d_c, tri_fid);
1110
+ } else {
1111
+ count = append_contact(count,
1112
+ cap_x, cap_y, cap_z,
1113
+ ct_x, ct_y, ct_z,
1114
+ -nx_c, -ny_c, -nz_c, d_c, tri_fid);
1115
+ }
1116
+ }
1117
+ continue;
1118
+ }
1119
+
1120
+ // Robust GJK + EPA (see gjk/gjk_epa_penetration.js): returns the
1121
+ // penetration depth and writes a UNIT axis into epa_result. We scale
1122
+ // it to the MTV vector (ex,ey,ez) the tuned sign-check / one-sided
1123
+ // rejection / dedup below already consume. The old gjk_with_axis +
1124
+ // expanding_polytope_algorithm pair was replaced because it returned
1125
+ // a non-minimal axis on degenerate simplices (see memory:
1126
+ // feedback_epa_unreliable_polytopes); MPR is kept as a secondary
1127
+ // safety net for the rare case the robust query reports no overlap.
1128
+ let ex, ey, ez, depth;
1129
+ const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1130
+ if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1131
+ ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1132
+ depth = pen_depth;
1133
+ } else {
1134
+ if (!mpr(epa_result, 0, posed_a, posed_b)) continue;
1135
+ ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1136
+ depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1137
+ if (!(depth > 0) || !Number.isFinite(depth)) continue;
1138
+ }
1139
+
1140
+ // Sign-check: validate MTV direction against (convex −
1141
+ // triangle_centroid). EPA's polytope-closest-face normal
1142
+ // can come out on the wrong side of the origin; this flip
1143
+ // canonicalises MTV to point from triangle (A) toward the
1144
+ // convex shape (B).
1145
+ const cent_lx = (ax + bx + cx_v) / 3;
1146
+ const cent_ly = (ay + by + cy_v) / 3;
1147
+ const cent_lz = (az + bz + cz_v) / 3;
1148
+ // Inlined q · v · q* + translate.
1149
+ const cix = cqw * cent_lx + cqy * cent_lz - cqz * cent_ly;
1150
+ const ciy = cqw * cent_ly + cqz * cent_lx - cqx * cent_lz;
1151
+ const ciz = cqw * cent_lz + cqx * cent_ly - cqy * cent_lx;
1152
+ const ciw = -cqx * cent_lx - cqy * cent_ly - cqz * cent_lz;
1153
+ const cent_wx = cix * cqw + ciw * -cqx + ciy * -cqz - ciz * -cqy + c_pos_x;
1154
+ const cent_wy = ciy * cqw + ciw * -cqy + ciz * -cqx - cix * -cqz + c_pos_y;
1155
+ const cent_wz = ciz * cqw + ciw * -cqz + cix * -cqy - ciy * -cqx + c_pos_z;
1156
+
1157
+ const tri_ab_x = convex_wx - cent_wx;
1158
+ const tri_ab_y = convex_wy - cent_wy;
1159
+ const tri_ab_z = convex_wz - cent_wz;
1160
+ if (ex * tri_ab_x + ey * tri_ab_y + ez * tri_ab_z < 0) {
1161
+ ex = -ex; ey = -ey; ez = -ez;
1162
+ }
1163
+
1164
+ // One-sided rejection (post-sign-check). MTV now points
1165
+ // from triangle (A) toward convex shape (B). For a CCW
1166
+ // outward-wound triangle, the convex shape is on the
1167
+ // OUTWARD side iff MTV aligns with the face normal. If it
1168
+ // opposes the face normal, the convex shape is on the
1169
+ // back / inward side — invalid for heightmap/mesh (it's
1170
+ // inside the solid). Skip the triangle rather than push
1171
+ // the body deeper into the solid through the opposite
1172
+ // face on the next step.
1173
+ if (ex * fnx_w + ey * fny_w + ez * fnz_w <= 0) continue;
1174
+
1175
+ // After the validated flip, MTV points from triangle
1176
+ // (concave-side A) into convex (B). Stored normal is
1177
+ // "B A" so we negate. Final A/B order matches
1178
+ // append_contact's contract: original A first, original B
1179
+ // second, normal "from original B toward original A".
1180
+ const inv = 1 / depth;
1181
+ const nx = -ex * inv;
1182
+ const ny = -ey * inv;
1183
+ const nz = -ez * inv;
1184
+
1185
+ // Dedup against earlier contacts emitted in THIS dispatch.
1186
+ // Adjacent triangles (heightmap cells sharing a diagonal;
1187
+ // mesh triangles sharing an edge or vertex) often report
1188
+ // identical contacts (same normal, same body-centre
1189
+ // application points), and feeding duplicates to the
1190
+ // sequential-impulse solver makes it escalate the impulse
1191
+ // across iterations without bound. We deduplicate by
1192
+ // contact normal within a small angular threshold: same
1193
+ // body pair + same normal + same body-centre positions =
1194
+ // the same physical contact, only one copy belongs in the
1195
+ // manifold.
1196
+ const stored_nx = isConcaveA ? nx : -nx;
1197
+ const stored_ny = isConcaveA ? ny : -ny;
1198
+ const stored_nz = isConcaveA ? nz : -nz;
1199
+ let is_duplicate = false;
1200
+ for (let k = pair_start_count; k < count; k++) {
1201
+ const ko = k * CANDIDATE_STRIDE;
1202
+ const dnx = candidates[ko + 6] - stored_nx;
1203
+ const dny = candidates[ko + 7] - stored_ny;
1204
+ const dnz = candidates[ko + 8] - stored_nz;
1205
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1206
+ is_duplicate = true;
1207
+ break;
1208
+ }
1209
+ }
1210
+ if (is_duplicate) continue;
1211
+
1212
+ // Contact application points: body centres rather than
1213
+ // the triangle's centroid. The existing GJK+EPA fallback
1214
+ // (below) uses the same convention for vertical contacts
1215
+ // (sphere/box on a flat surface, the common case), the
1216
+ // lever arm r × n vanishes and the impulse resolves
1217
+ // cleanly.
1218
+ if (isConcaveA) {
1219
+ count = append_contact(count,
1220
+ c_pos_x, c_pos_y, c_pos_z,
1221
+ convex_wx, convex_wy, convex_wz,
1222
+ nx, ny, nz, depth, tri_fid);
1223
+ } else {
1224
+ count = append_contact(count,
1225
+ convex_wx, convex_wy, convex_wz,
1226
+ c_pos_x, c_pos_y, c_pos_z,
1227
+ -nx, -ny, -nz, depth, tri_fid);
1228
+ }
1229
+ }
1230
+
1231
+ return count;
1232
+ }
1233
+
1234
+ // Robust GJK + EPA fallback for any convex pair without a closed form or a
1235
+ // dedicated branch above (e.g. a primitive vs an authored ConvexHullShape3D,
1236
+ // a cylinder/cone). gjk_epa_penetration returns the depth and writes a UNIT
1237
+ // axis into epa_result; we scale to the MTV vector (ex,ey,ez) the body-centre
1238
+ // sign-guard below consumes. Replaces the old gjk_with_axis +
1239
+ // expanding_polytope_algorithm pair, which returned a non-minimal axis on the
1240
+ // degenerate simplices its GJK produced (see memory:
1241
+ // feedback_epa_unreliable_polytopes). MPR is kept as a secondary safety net.
1242
+ posed_a.setup(colA.shape, trA.position, trA.rotation);
1243
+ posed_b.setup(colB.shape, trB.position, trB.rotation);
1244
+
1245
+ let ex, ey, ez, depth;
1246
+ const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1247
+ if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1248
+ ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1249
+ depth = pen_depth;
1250
+ } else {
1251
+ if (!mpr(epa_result, 0, posed_a, posed_b)) return count;
1252
+ ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1253
+ depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1254
+ if (!(depth > 0) || !Number.isFinite(depth)) return count;
1255
+ }
1256
+
1257
+ // EPA's output vector should point along the minimum-translation
1258
+ // axis from A into B (the direction you'd push B by to separate).
1259
+ // On non-convergent EPA — common on smooth high-poly colliders like
1260
+ // a torus knot, where the polytope can't tighten onto the true
1261
+ // closest face within the iteration cap — the fallback returns
1262
+ // *some* face's normal, and that face can be on either side of the
1263
+ // origin. Resulting MTV occasionally points B *into* A instead of
1264
+ // *away from* A; the solver then sees a "separating" relative
1265
+ // velocity, clamps the normal impulse to zero, and the bodies pass
1266
+ // straight through each other.
1267
+ //
1268
+ // Validate against the vector from body A's centre to body B's
1269
+ // centre — EPA's direction must correlate positively with it. Flip
1270
+ // if not. For convex bodies whose origin is inside the geometry,
1271
+ // body-centre-to-body-centre is a strong proxy for the correct
1272
+ // separation direction. For pathological cases (deeply
1273
+ // interpenetrating bodies with coincident centres) the dot product
1274
+ // is near zero and we leave EPA's guess intact.
1275
+ const ab_x = trB.position.x - trA.position.x;
1276
+ const ab_y = trB.position.y - trA.position.y;
1277
+ const ab_z = trB.position.z - trA.position.z;
1278
+ if (ex * ab_x + ey * ab_y + ez * ab_z < 0) {
1279
+ ex = -ex; ey = -ey; ez = -ez;
1280
+ }
1281
+
1282
+ const inv = 1 / depth;
1283
+ const nx = -ex * inv, ny = -ey * inv, nz = -ez * inv; // stored normal, B → A
1284
+
1285
+ // Contact application points: body centres rather than support-function
1286
+ // witnesses. The natural-sounding alternative — `posed_a.support(+EPA_dir)`
1287
+ // and `posed_b.support(-EPA_dir)` interacts badly with flat-faced
1288
+ // supports: a large floor's support in `+Y` returns a *corner* (the box
1289
+ // support is multi-valued along an axis-aligned direction and picks the
1290
+ // sign-tied corner), which can be tens of metres from the actual
1291
+ // contact patch. That blows up the solver's lever arm `r × n` and
1292
+ // collapses `m_eff` to ~0, leaving the impulse vanishingly small.
1293
+ //
1294
+ // Body centres give `r n` for vertical contacts (sphere/knot landing
1295
+ // on a floor the common case), so `r × n = 0` and `m_eff = invM`
1296
+ // resolves the impact cleanly. For oblique contacts the application
1297
+ // point lies on the line between centres, which is the right-ish lever
1298
+ // arm for translational impulse and degrades gracefully on angular
1299
+ // response. A future closed-form mesh-vs-box path will compute a
1300
+ // proper face-projected witness; until then, body centres are the
1301
+ // robust fallback for the GJK+EPA route.
1302
+ // GJK+EPA fallback has no stable per-frame feature info; emit fid = 0
1303
+ // so match-and-merge uses position-fallback. Single contact per pair.
1304
+ return append_contact(count,
1305
+ trA.position.x, trA.position.y, trA.position.z,
1306
+ trB.position.x, trB.position.y, trB.position.z,
1307
+ nx, ny, nz,
1308
+ depth, 0
1309
+ );
1310
+ }
1311
+
1312
+ // Reusable single-pair adapters for the penetration query below — no per-call
1313
+ // allocation. dispatch_pair only reads `.shape` off a collider and
1314
+ // `.position` / `.rotation` off a transform, so these minimal stand-ins are
1315
+ // all it needs.
1316
+ const _pp_colA = { shape: null };
1317
+ const _pp_colB = { shape: null };
1318
+ const _pp_trA = { position: null, rotation: null };
1319
+ const _pp_trB = { position: null, rotation: null };
1320
+
1321
+
1322
+ /**
1323
+ * Single-pair penetration query: the depth and world normal of the DEEPEST
1324
+ * contact the narrowphase would generate for one posed shape pair.
1325
+ *
1326
+ * Routes through the exact same {@link dispatch_pair} the contact solver
1327
+ * consumes — closed-form for every sphere / box / capsule pair (box-box via
1328
+ * SAT, so the true minimum-translation axis is found rather than the
1329
+ * centroid-seeded portal MPR would pick), triangle decomposition + closed-form
1330
+ * per triangle for convex-vs-concave, and GJK + EPA (+ MPR) for any other
1331
+ * convex pair. The deepest contact's depth is the minimum-translation distance
1332
+ * and its normal is the MTV axis, so the result is correct for every shape pair
1333
+ * the engine can build and agrees bit-for-bit with what the solver acts on.
1334
+ *
1335
+ * The normal follows the narrowphase's stored convention: a unit vector
1336
+ * pointing from B toward A the direction to translate A to separate it.
1337
+ *
1338
+ * Concave-vs-concave is not dispatched (the narrowphase skips it) and returns
1339
+ * 0; callers needing to reject that case must check `is_convex` themselves.
1340
+ *
1341
+ * Not re-entrant: shares the module-level candidate / scratch buffers with
1342
+ * {@link narrowphase_step}. Intended for main-thread queries run outside a
1343
+ * step (depenetration, overlap depth, tooling) never from inside one.
1344
+ *
1345
+ * @param {Float64Array|number[]} out_normal length ≥ 3; receives the unit B→A
1346
+ * normal on penetration (untouched when the return value is 0)
1347
+ * @param {AbstractShape3D} shapeA
1348
+ * @param {{x:number,y:number,z:number}} posA
1349
+ * @param {{x:number,y:number,z:number,w:number}} rotA
1350
+ * @param {AbstractShape3D} shapeB
1351
+ * @param {{x:number,y:number,z:number}} posB
1352
+ * @param {{x:number,y:number,z:number,w:number}} rotB
1353
+ * @returns {number} deepest penetration depth (> 0) or 0 if separated
1354
+ */
1355
+ export function deepest_pair_penetration(out_normal, shapeA, posA, rotA, shapeB, posB, rotB) {
1356
+ _pp_colA.shape = shapeA;
1357
+ _pp_trA.position = posA;
1358
+ _pp_trA.rotation = rotA;
1359
+ _pp_colB.shape = shapeB;
1360
+ _pp_trB.position = posB;
1361
+ _pp_trB.rotation = rotB;
1362
+
1363
+ const n = dispatch_pair(0, _pp_colA, _pp_trA, _pp_colB, _pp_trB);
1364
+ if (n === 0) {
1365
+ return 0;
1366
+ }
1367
+
1368
+ // Deepest contact = the minimum-translation depth; its stored normal is the
1369
+ // separation axis. (For multi-point manifolds box-box, capsule-box, a
1370
+ // convex straddling several mesh triangles every point shares the
1371
+ // separating axis, so the max depth along it is the distance to separate.)
1372
+ let best_depth = -1;
1373
+ let best_off = 0;
1374
+ for (let i = 0; i < n; i++) {
1375
+ const off = i * CANDIDATE_STRIDE;
1376
+ const d = candidates[off + 9];
1377
+ if (d > best_depth) {
1378
+ best_depth = d;
1379
+ best_off = off;
1380
+ }
1381
+ }
1382
+
1383
+ if (!(best_depth > 0) || !Number.isFinite(best_depth)) {
1384
+ return 0;
1385
+ }
1386
+
1387
+ out_normal[0] = candidates[best_off + 6];
1388
+ out_normal[1] = candidates[best_off + 7];
1389
+ out_normal[2] = candidates[best_off + 8];
1390
+
1391
+ return best_depth;
1392
+ }
1393
+
1394
+ /**
1395
+ * For every pair in `pair_list`, do a cross-product over A's collider list ×
1396
+ * B's collider list, accumulate candidate contacts, reduce to ≤4, and write
1397
+ * to the manifold slot.
1398
+ *
1399
+ * @param {PairList} pair_list
1400
+ * @param {ManifoldStore} manifolds
1401
+ * @param {Array<Array<{collider: Collider, transform: Transform}>>} lists
1402
+ * per-body collider lists, indexed by body-storage slot index. Typically
1403
+ * `system.__body_collider_lists` — passed in directly so this helper
1404
+ * has no dependency on `PhysicsSystem`.
1405
+ */
1406
+ export function narrowphase_step(pair_list, manifolds, lists) {
1407
+ const count = pair_list.count;
1408
+
1409
+ for (let i = 0; i < count; i++) {
1410
+ const idA = pair_list.get_a(i);
1411
+ const idB = pair_list.get_b(i);
1412
+
1413
+ const idxA = body_id_index(idA);
1414
+ const idxB = body_id_index(idB);
1415
+
1416
+ const list_a = lists[idxA];
1417
+ const list_b = lists[idxB];
1418
+
1419
+ const slot = manifolds.find(idA, idB);
1420
+
1421
+ if (list_a === undefined || list_b === undefined
1422
+ || list_a.length === 0 || list_b.length === 0
1423
+ ) {
1424
+
1425
+ manifolds.clear_contacts(slot);
1426
+ continue;
1427
+
1428
+ }
1429
+
1430
+ let cand_count = 0;
1431
+
1432
+ const la_len = list_a.length;
1433
+ const lb_len = list_b.length;
1434
+
1435
+ let sub_pair = 0;
1436
+ for (let a = 0; a < la_len; a++) {
1437
+ const ea = list_a[a];
1438
+
1439
+ for (let b = 0; b < lb_len; b++) {
1440
+ const eb = list_b[b];
1441
+
1442
+ const before = cand_count;
1443
+ cand_count = dispatch_pair(
1444
+ cand_count,
1445
+ ea.collider,
1446
+ ea.transform,
1447
+ eb.collider,
1448
+ eb.transform
1449
+ );
1450
+
1451
+ // Salt this sub-pair's fids ({@link FID_PAIR_SALT}) so a
1452
+ // compound body cannot fid-match — and inherit warm-start
1453
+ // impulses — across different collider pairs. fid 0 stays 0.
1454
+ if (sub_pair !== 0) {
1455
+ const salt = sub_pair * FID_PAIR_SALT;
1456
+ for (let c = before; c < cand_count; c++) {
1457
+ const co = c * CANDIDATE_STRIDE;
1458
+ if (candidates[co + 10] !== 0) candidates[co + 10] += salt;
1459
+ }
1460
+ }
1461
+ sub_pair++;
1462
+ }
1463
+ }
1464
+
1465
+ if (cand_count === 0) {
1466
+ // No contacts this frame for an existing manifold. Keep the
1467
+ // slot in the cache (the grace window in advance_frame() will
1468
+ // evict it if this persists), but zero out impulses and
1469
+ // contact count — there's nothing for the solver to act on
1470
+ // and stale impulses would mislead next frame's warm-start
1471
+ // if contact re-establishes at a different feature.
1472
+ manifolds.clear_contacts(slot);
1473
+ continue;
1474
+ }
1475
+
1476
+ const kept = reduce_manifold_contacts(candidates, cand_count, CANDIDATE_STRIDE, MAX_KEPT, 9);
1477
+
1478
+ // ── Match-and-merge: feature-id (with position fallback) ──────
1479
+ //
1480
+ // Snapshot prev-frame state for matching + impulse carry-over.
1481
+ const data = manifolds.data_buffer;
1482
+ const slot_off = manifolds.slot_data_offset(slot);
1483
+ const prev_count_raw = manifolds.contact_count(slot);
1484
+ const prev_count = prev_count_raw > MAX_CONTACTS_PER_MANIFOLD
1485
+ ? MAX_CONTACTS_PER_MANIFOLD
1486
+ : prev_count_raw;
1487
+ for (let j = 0; j < prev_count; j++) {
1488
+ const off = slot_off + j * CONTACT_STRIDE;
1489
+ const snap_off = j * 7;
1490
+ prev_snapshot[snap_off] = data[off + 13]; // feature_id
1491
+ prev_snapshot[snap_off + 1] = data[off]; // world_a x
1492
+ prev_snapshot[snap_off + 2] = data[off + 1]; // world_a y
1493
+ prev_snapshot[snap_off + 3] = data[off + 2]; // world_a z
1494
+ prev_snapshot[snap_off + 4] = data[off + 10]; // j_n
1495
+ prev_snapshot[snap_off + 5] = data[off + 11]; // j_t1
1496
+ prev_snapshot[snap_off + 6] = data[off + 12]; // j_t2
1497
+ prev_claimed[j] = 0;
1498
+ }
1499
+
1500
+ // For each new candidate, find a matching prev contact.
1501
+ // Step 1: feature-id match (only if BOTH sides have a non-zero
1502
+ // feature_id — fid = 0 means "no info"). Same-fid prev
1503
+ // contacts are disambiguated by NEAREST previous witness:
1504
+ // a fid identifies the FEATURE (e.g. a triangle), not the
1505
+ // contact point — the box/capsule-triangle paths emit up
1506
+ // to 4 contacts sharing one triangle's fid, and the
1507
+ // reducer reorders them frame to frame, so first-match
1508
+ // would shuffle warm-start impulses between a face's own
1509
+ // contact points. (redetect_pair_geometry documents and
1510
+ // fixes the same failure for the per-substep path.)
1511
+ // Step 2: position-fallback within MATCH_TOL_SQR.
1512
+ for (let k = 0; k < kept; k++) {
1513
+ const cand_off = k * CANDIDATE_STRIDE;
1514
+ const cand_fid = candidates[cand_off + 10];
1515
+ const cand_ax = candidates[cand_off];
1516
+ const cand_ay = candidates[cand_off + 1];
1517
+ const cand_az = candidates[cand_off + 2];
1518
+
1519
+ let best_prev = -1;
1520
+ if (cand_fid !== 0) {
1521
+ // Gated by FID_MATCH_MAX_D2: a fid recomputed half a metre
1522
+ // away (teleport, fid collision) must not inherit impulses.
1523
+ let best_d2 = FID_MATCH_MAX_D2;
1524
+ for (let j = 0; j < prev_count; j++) {
1525
+ if (prev_claimed[j]) continue;
1526
+ if (prev_snapshot[j * 7] !== cand_fid) continue;
1527
+ const snap_off = j * 7;
1528
+ const dx = prev_snapshot[snap_off + 1] - cand_ax;
1529
+ const dy = prev_snapshot[snap_off + 2] - cand_ay;
1530
+ const dz = prev_snapshot[snap_off + 3] - cand_az;
1531
+ const d2 = dx * dx + dy * dy + dz * dz;
1532
+ if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1533
+ }
1534
+ }
1535
+ if (best_prev === -1) {
1536
+ let best_d2 = MATCH_TOL_SQR;
1537
+ for (let j = 0; j < prev_count; j++) {
1538
+ if (prev_claimed[j]) continue;
1539
+ const snap_off = j * 7;
1540
+ const dx = prev_snapshot[snap_off + 1] - cand_ax;
1541
+ const dy = prev_snapshot[snap_off + 2] - cand_ay;
1542
+ const dz = prev_snapshot[snap_off + 3] - cand_az;
1543
+ const d2 = dx * dx + dy * dy + dz * dz;
1544
+ if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1545
+ }
1546
+ }
1547
+ cand_to_prev[k] = best_prev;
1548
+ if (best_prev !== -1) prev_claimed[best_prev] = 1;
1549
+ }
1550
+
1551
+ // Reset count without zeroing the data slab set_contact below
1552
+ // will overwrite geometry, and matched candidates will inherit
1553
+ // the impulses we snapshotted above (written back at the new
1554
+ // slot index after set_contact).
1555
+ manifolds.begin_refill(slot);
1556
+
1557
+ for (let k = 0; k < kept; k++) {
1558
+ const off = k * CANDIDATE_STRIDE;
1559
+ // Target slot index = k (we write candidates 0..kept-1 into
1560
+ // slot indices 0..kept-1). The impulse carry-over below
1561
+ // copies the matched prev contact's impulses into THIS k,
1562
+ // so the geometric and warm-start state stay correlated
1563
+ // even if the matching mapping permuted the order.
1564
+ manifolds.set_contact(
1565
+ slot, k,
1566
+ candidates[off], candidates[off + 1], candidates[off + 2],
1567
+ candidates[off + 3], candidates[off + 4], candidates[off + 5],
1568
+ candidates[off + 6], candidates[off + 7], candidates[off + 8],
1569
+ candidates[off + 9],
1570
+ candidates[off + 10],
1571
+ candidates[off + 11], candidates[off + 12]
1572
+ );
1573
+ const prev_j = cand_to_prev[k];
1574
+ if (prev_j !== -1) {
1575
+ // Copy prev_j's impulse to this slot index.
1576
+ const dst_off = slot_off + k * CONTACT_STRIDE;
1577
+ const src_off = prev_j * 7;
1578
+ data[dst_off + 10] = prev_snapshot[src_off + 4];
1579
+ data[dst_off + 11] = prev_snapshot[src_off + 5];
1580
+ data[dst_off + 12] = prev_snapshot[src_off + 6];
1581
+ } else {
1582
+ // No match explicitly zero the impulses, since this
1583
+ // slot index may have stale data from an earlier frame's
1584
+ // contact at the same index.
1585
+ manifolds.clear_impulses(slot, k);
1586
+ }
1587
+ }
1588
+ }
1589
+ }
1590
+
1591
+ /**
1592
+ * Re-detect contact GEOMETRY for one existing manifold slot at the bodies'
1593
+ * current poses, updating the witness points / normal / depth of the slot's
1594
+ * existing contacts in place. Does NOT change the contact count, the
1595
+ * feature ids, or the accumulated impulses — it only refreshes geometry.
1596
+ *
1597
+ * This is the per-substep concave path (TGS): for a contact pair involving a
1598
+ * concave body, the contact *feature* (which triangle is deepest, and its
1599
+ * normal) genuinely changes as the body rocks, so the solver's cheap analytic
1600
+ * refresh — which freezes the feature for the whole outer step — pumps energy
1601
+ * in. Re-running the narrowphase geometry each substep gives a fresh,
1602
+ * correct normal so the body settles instead of rocking. Convex pairs keep
1603
+ * the analytic refresh and never call this (their feature is stable).
1604
+ *
1605
+ * Matching is by feature id (stable per-triangle for the decomposition path),
1606
+ * so a contact's geometry tracks the same triangle across substeps. A contact
1607
+ * whose triangle isn't found this substep keeps its previous geometry (a rare
1608
+ * transient; the once-per-frame {@link narrowphase_step} re-establishes the
1609
+ * contact set next outer step). Count never changes here, so the solver's
1610
+ * per-contact scratch (sized once at prepare) stays aligned.
1611
+ *
1612
+ * @param {ManifoldStore} manifolds
1613
+ * @param {number} slot
1614
+ * @param {Array<{collider: Collider, transform: Transform}>} list_a
1615
+ * @param {Array<{collider: Collider, transform: Transform}>} list_b
1616
+ */
1617
+ export function redetect_pair_geometry(manifolds, slot, list_a, list_b) {
1618
+ if (list_a === undefined || list_b === undefined) return;
1619
+ const la_len = list_a.length;
1620
+ const lb_len = list_b.length;
1621
+ if (la_len === 0 || lb_len === 0) return;
1622
+
1623
+ const count = manifolds.contact_count(slot);
1624
+ if (count === 0) return;
1625
+
1626
+ let cc = 0;
1627
+ let sub_pair = 0;
1628
+ for (let a = 0; a < la_len; a++) {
1629
+ const ea = list_a[a];
1630
+ for (let b = 0; b < lb_len; b++) {
1631
+ const eb = list_b[b];
1632
+ const before = cc;
1633
+ cc = dispatch_pair(cc, ea.collider, ea.transform, eb.collider, eb.transform);
1634
+ // Stored fids are sub-pair-salted (see narrowphase_step's dispatch
1635
+ // loop) salt the redetected candidates identically or no fid
1636
+ // would ever match for a compound body's non-first sub-pair.
1637
+ if (sub_pair !== 0) {
1638
+ const salt = sub_pair * FID_PAIR_SALT;
1639
+ for (let c = before; c < cc; c++) {
1640
+ const co = c * CANDIDATE_STRIDE;
1641
+ if (candidates[co + 10] !== 0) candidates[co + 10] += salt;
1642
+ }
1643
+ }
1644
+ sub_pair++;
1645
+ }
1646
+ }
1647
+ if (cc === 0) return; // nothing re-detected this substep — keep frozen geometry
1648
+
1649
+ const data = manifolds.data_buffer;
1650
+ const slot_off = manifolds.slot_data_offset(slot);
1651
+
1652
+ // Match each existing contact to a DISTINCT fresh candidate. feature_id
1653
+ // identifies the TRIANGLE, not the contact point the box/capsule-triangle
1654
+ // paths emit several contacts for one triangle, all sharing that triangle's
1655
+ // single fid (a flat box-on-heightmap cell yields fids like [6,6,6,7]). A
1656
+ // plain first-match-by-fid therefore maps every same-fid existing contact
1657
+ // onto the SAME candidate, collapsing the manifold to duplicate witness
1658
+ // points a degenerate support polygon the solver can't damp (the
1659
+ // box-on-heightmap rattle). So: gate by fid, disambiguate same-fid
1660
+ // candidates by NEAREST previous witness (world-A) position, and claim each
1661
+ // candidate so no two existing contacts take the same one. For the common
1662
+ // unique-fid case (sphere/mesh: one contact per triangle) this picks that
1663
+ // single candidate exactly once identical to the old behaviour.
1664
+ for (let k = 0; k < cc; k++) redetect_claimed[k] = 0;
1665
+
1666
+ for (let j = 0; j < count; j++) {
1667
+ const off = slot_off + j * CONTACT_STRIDE;
1668
+ const fid = data[off + 13];
1669
+ if (fid === 0) continue; // no feature info to match on
1670
+
1671
+ // Previous witness (world-A) of this existing contact — the anchor we
1672
+ // disambiguate same-fid candidates against.
1673
+ const pax = data[off];
1674
+ const pay = data[off + 1];
1675
+ const paz = data[off + 2];
1676
+
1677
+ let best_k = -1;
1678
+ let best_d2 = Infinity;
1679
+ for (let k = 0; k < cc; k++) {
1680
+ if (redetect_claimed[k] === 1) continue;
1681
+ const co = k * CANDIDATE_STRIDE;
1682
+ if (candidates[co + 10] !== fid) continue;
1683
+ const dx = candidates[co] - pax;
1684
+ const dy = candidates[co + 1] - pay;
1685
+ const dz = candidates[co + 2] - paz;
1686
+ const d2 = dx * dx + dy * dy + dz * dz;
1687
+ if (d2 < best_d2) {
1688
+ best_d2 = d2;
1689
+ best_k = k;
1690
+ }
1691
+ }
1692
+
1693
+ // Position fallback (mirrors narrowphase_step's match-and-merge). A
1694
+ // single triangle's clipped contact count is NOT stable across sub-mm
1695
+ // pose changes a box straddling a cell seam can have one triangle
1696
+ // yield 4 points one substep and 3 the next so an existing contact can
1697
+ // outnumber this substep's same-fid candidates. Freezing its stale
1698
+ // witness then duplicates a sibling contact's point, leaving a
1699
+ // degenerate (sub-dimensional) manifold. Instead, claim the nearest
1700
+ // unclaimed candidate of ANY fid: every contact keeps a DISTINCT live
1701
+ // witness. The fid label is left intact and re-resolved by the next
1702
+ // once-per-step narrowphase. (At a fixed pose the same-fid match always
1703
+ // succeeds, so this never fires for the depth-equality guards or the
1704
+ // one-contact-per-triangle sphere/mesh paths.)
1705
+ if (best_k === -1) {
1706
+ for (let k = 0; k < cc; k++) {
1707
+ if (redetect_claimed[k] === 1) continue;
1708
+ const co = k * CANDIDATE_STRIDE;
1709
+ const dx = candidates[co] - pax;
1710
+ const dy = candidates[co + 1] - pay;
1711
+ const dz = candidates[co + 2] - paz;
1712
+ const d2 = dx * dx + dy * dy + dz * dz;
1713
+ if (d2 < best_d2) {
1714
+ best_d2 = d2;
1715
+ best_k = k;
1716
+ }
1717
+ }
1718
+ }
1719
+
1720
+ if (best_k === -1) continue; // no unclaimed candidate at all this substep — keep frozen geometry
1721
+
1722
+ redetect_claimed[best_k] = 1;
1723
+ const co = best_k * CANDIDATE_STRIDE;
1724
+ // Overwrite geometry only: witnesses, normal, depth. (Count, feature
1725
+ // ids and accumulated impulses are intentionally left untouched — see
1726
+ // the function contract.)
1727
+ data[off] = candidates[co];
1728
+ data[off + 1] = candidates[co + 1];
1729
+ data[off + 2] = candidates[co + 2];
1730
+ data[off + 3] = candidates[co + 3];
1731
+ data[off + 4] = candidates[co + 4];
1732
+ data[off + 5] = candidates[co + 5];
1733
+ data[off + 6] = candidates[co + 6];
1734
+ data[off + 7] = candidates[co + 7];
1735
+ data[off + 8] = candidates[co + 8];
1736
+ data[off + 9] = candidates[co + 9];
1737
+ }
1738
+ }