@woosh/meep-engine 2.139.0 → 2.141.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 (199) hide show
  1. package/package.json +1 -1
  2. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts +3 -3
  3. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts.map +1 -1
  4. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js +4 -4
  5. package/src/{engine/physics/broadphase/aabb_transform_oriented.d.ts → core/geom/3d/aabb/aabb3_transform_oriented.d.ts} +2 -2
  6. package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts.map +1 -0
  7. package/src/{engine/physics/broadphase/aabb_transform_oriented.js → core/geom/3d/aabb/aabb3_transform_oriented.js} +1 -1
  8. package/src/core/geom/3d/quaternion/quat3_multiply.d.ts +21 -0
  9. package/src/core/geom/3d/quaternion/quat3_multiply.d.ts.map +1 -0
  10. package/src/core/geom/3d/quaternion/quat3_multiply.js +25 -0
  11. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts +54 -0
  12. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts.map +1 -0
  13. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -0
  14. package/src/core/geom/3d/shape/AbstractShape3D.d.ts +24 -2
  15. package/src/core/geom/3d/shape/AbstractShape3D.d.ts.map +1 -1
  16. package/src/core/geom/3d/shape/AbstractShape3D.js +24 -1
  17. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +148 -0
  18. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -0
  19. package/src/core/geom/3d/shape/HeightMapShape3D.js +451 -0
  20. package/src/core/geom/3d/shape/MeshShape3D.d.ts +210 -0
  21. package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -0
  22. package/src/core/geom/3d/shape/MeshShape3D.js +593 -0
  23. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  24. package/src/core/geom/3d/shape/TransformedShape3D.js +46 -2
  25. package/src/core/geom/3d/shape/Triangle3D.d.ts +95 -0
  26. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -0
  27. package/src/core/geom/3d/shape/Triangle3D.js +318 -0
  28. package/src/core/geom/3d/shape/UnionShape3D.js +13 -0
  29. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts +30 -0
  30. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts.map +1 -0
  31. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +64 -0
  32. package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.js +9 -11
  33. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts +28 -0
  34. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts.map +1 -0
  35. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -0
  36. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts.map +1 -1
  37. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +40 -18
  38. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts +9 -5
  39. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts.map +1 -1
  40. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +38 -10
  41. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts +14 -5
  42. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts.map +1 -1
  43. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +47 -5
  44. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +19 -0
  45. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
  46. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +75 -13
  47. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts +2 -2
  48. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts.map +1 -1
  49. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +1 -1
  50. package/src/core/geom/vec3/v3_dot_array_array.d.ts +3 -3
  51. package/src/core/geom/vec3/v3_dot_array_array.d.ts.map +1 -1
  52. package/src/core/geom/vec3/v3_dot_array_array.js +2 -2
  53. package/src/core/geom/vec3/v3_negate_array.d.ts +3 -3
  54. package/src/core/geom/vec3/v3_negate_array.d.ts.map +1 -1
  55. package/src/core/geom/vec3/v3_negate_array.js +2 -2
  56. package/src/core/geom/vec3/v3_quat3_apply.d.ts +29 -0
  57. package/src/core/geom/vec3/v3_quat3_apply.d.ts.map +1 -0
  58. package/src/core/geom/vec3/v3_quat3_apply.js +39 -0
  59. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +30 -0
  60. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts.map +1 -0
  61. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +41 -0
  62. package/src/core/geom/vec3/v3_triple_cross_product.d.ts +32 -0
  63. package/src/core/geom/vec3/v3_triple_cross_product.d.ts.map +1 -0
  64. package/src/core/geom/vec3/v3_triple_cross_product.js +45 -0
  65. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +16 -3
  66. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  67. package/src/engine/control/first-person/FirstPersonPlayerController.js +211 -211
  68. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +72 -8
  69. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  70. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +37 -5
  71. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +101 -3
  72. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  73. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1789 -1416
  74. package/src/engine/control/first-person/TODO.md +173 -127
  75. package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -1
  76. package/src/engine/control/first-person/abilities/Slide.js +9 -1
  77. package/src/engine/control/first-person/prototype_first_person_controller.js +88 -2
  78. package/src/engine/control/first-person/test/buildTestPlayer.d.ts.map +1 -1
  79. package/src/engine/control/first-person/test/buildTestPlayer.js +9 -1
  80. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts +42 -0
  81. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts.map +1 -0
  82. package/src/engine/graphics/geometry/CapsuleGeometry.js +171 -0
  83. package/src/engine/physics/BULLET_REVIEW.md +945 -0
  84. package/src/engine/physics/CANNON_REVIEW.md +1300 -0
  85. package/src/engine/physics/JOLT_REVIEW.md +913 -0
  86. package/src/engine/physics/PLAN.md +578 -236
  87. package/src/engine/physics/RAPIER_REVIEW.md +934 -0
  88. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -0
  89. package/src/engine/physics/REVIEW_002.md +151 -0
  90. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +2 -2
  91. package/src/engine/physics/constraint/DofMode.d.ts +28 -0
  92. package/src/engine/physics/constraint/DofMode.d.ts.map +1 -0
  93. package/src/engine/physics/constraint/DofMode.js +35 -0
  94. package/src/engine/physics/constraint/solve_constraints.d.ts +16 -0
  95. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -0
  96. package/src/engine/physics/constraint/solve_constraints.js +436 -0
  97. package/src/engine/physics/contact/ManifoldStore.d.ts +83 -10
  98. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  99. package/src/engine/physics/contact/ManifoldStore.js +608 -499
  100. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +2 -2
  101. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -1
  102. package/src/engine/physics/ecs/Joint.d.ts +179 -0
  103. package/src/engine/physics/ecs/Joint.d.ts.map +1 -0
  104. package/src/engine/physics/ecs/Joint.js +234 -0
  105. package/src/engine/physics/ecs/PhysicsSystem.d.ts +180 -20
  106. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  107. package/src/engine/physics/ecs/PhysicsSystem.js +1423 -1159
  108. package/src/engine/physics/fluid/FluidField.d.ts +14 -10
  109. package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
  110. package/src/engine/physics/fluid/FluidField.js +14 -10
  111. package/src/engine/physics/fluid/FluidSimulator.js +1 -1
  112. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts +17 -10
  113. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts.map +1 -1
  114. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +18 -11
  115. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts +13 -10
  116. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts.map +1 -1
  117. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +18 -13
  118. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts +4 -3
  119. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts.map +1 -1
  120. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +15 -11
  121. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +30 -6
  122. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
  123. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +44 -18
  124. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +6 -6
  125. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +1 -1
  126. package/src/engine/physics/gjk/expanding_polytope_algorithm.js +68 -22
  127. package/src/engine/physics/gjk/gjk.d.ts +28 -2
  128. package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
  129. package/src/engine/physics/gjk/gjk.js +421 -378
  130. package/src/engine/physics/gjk/minkowski_support.d.ts +37 -0
  131. package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -0
  132. package/src/engine/physics/gjk/minkowski_support.js +75 -0
  133. package/src/engine/physics/gjk/mpr.d.ts +56 -0
  134. package/src/engine/physics/gjk/mpr.d.ts.map +1 -0
  135. package/src/engine/physics/gjk/mpr.js +344 -0
  136. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +20 -5
  137. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  138. package/src/engine/physics/inertia/world_inverse_inertia.js +36 -38
  139. package/src/engine/physics/integration/integrate_position.d.ts +25 -7
  140. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
  141. package/src/engine/physics/integration/integrate_position.js +43 -12
  142. package/src/engine/physics/integration/integrate_velocity.d.ts +30 -0
  143. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  144. package/src/engine/physics/integration/integrate_velocity.js +82 -1
  145. package/src/engine/physics/island/IslandBuilder.d.ts +4 -1
  146. package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
  147. package/src/engine/physics/island/IslandBuilder.js +33 -16
  148. package/src/engine/physics/narrowphase/PosedShape.d.ts +0 -8
  149. package/src/engine/physics/narrowphase/PosedShape.d.ts.map +1 -1
  150. package/src/engine/physics/narrowphase/PosedShape.js +28 -30
  151. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  152. package/src/engine/physics/narrowphase/box_box_manifold.js +140 -18
  153. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts +30 -0
  154. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -0
  155. package/src/engine/physics/narrowphase/box_triangle_contact.js +811 -0
  156. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  157. package/src/engine/physics/narrowphase/capsule_contacts.js +10 -56
  158. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts +71 -0
  159. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -0
  160. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +375 -0
  161. package/src/engine/physics/narrowphase/compute_penetration.d.ts +91 -0
  162. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -0
  163. package/src/engine/physics/narrowphase/compute_penetration.js +396 -0
  164. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts +35 -0
  165. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +1 -0
  166. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.js +80 -0
  167. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts +31 -0
  168. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts.map +1 -0
  169. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -0
  170. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +42 -0
  171. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -0
  172. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +204 -0
  173. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +42 -0
  174. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -0
  175. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +94 -0
  176. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts +37 -0
  177. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts.map +1 -0
  178. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -0
  179. package/src/engine/physics/narrowphase/narrowphase_step.d.ts +41 -2
  180. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  181. package/src/engine/physics/narrowphase/narrowphase_step.js +1497 -382
  182. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
  183. package/src/engine/physics/narrowphase/sphere_box_contact.js +16 -23
  184. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts +48 -0
  185. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts.map +1 -0
  186. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +143 -0
  187. package/src/engine/physics/queries/overlap_shape.d.ts +51 -0
  188. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -0
  189. package/src/engine/physics/queries/overlap_shape.js +183 -0
  190. package/src/engine/physics/queries/shape_cast.d.ts +56 -0
  191. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -0
  192. package/src/engine/physics/queries/shape_cast.js +387 -0
  193. package/src/engine/physics/solver/solve_contacts.d.ts +146 -32
  194. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  195. package/src/engine/physics/solver/solve_contacts.js +809 -223
  196. package/src/engine/physics/broadphase/aabb_transform_oriented.d.ts.map +0 -1
  197. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts +0 -20
  198. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts.map +0 -1
  199. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.js +0 -83
@@ -1 +1 @@
1
- {"version":3,"file":"capsule_contacts.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/capsule_contacts.js"],"names":[],"mappings":"AA0EA;;;;;;;;;;;;;;;GAeG;AACH,2CAVW,MAAM,EAAE,GAAC,YAAY,MACrB,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,eACN,MAAM,QAOhB;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,4CAhBW,MAAM,EAAE,GAAC,YAAY,QACrB,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,YACN,MAAM,YACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,YACN,MAAM,GACJ,OAAO,CA6CnB;AAMD;;;;;GAKG;AACH,6CAHW,MAAM,EAAE,GAAC,YAAY,yNACnB,OAAO,CAuEnB;AAOD;;;;;;;;;;;;;GAaG;AACH,yCAHW,MAAM,EAAE,GAAC,YAAY,4NACnB,OAAO,CA8FnB;AAyCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,gDAtBW,YAAY,QACZ,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,YACN,MAAM,YACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,GACJ,MAAM,CAuElB;AApJD;;;;;;;;;;GAUG;AACH,yCAFU,MAAM,CAE6B;AAE7C;;;;;GAKG;AACH,uCAFU,MAAM,CAE0B"}
1
+ {"version":3,"file":"capsule_contacts.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/capsule_contacts.js"],"names":[],"mappings":"AA4BA;;;;;;;;;;;;;;;GAeG;AACH,2CAVW,MAAM,EAAE,GAAC,YAAY,MACrB,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,eACN,MAAM,QAOhB;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,4CAhBW,MAAM,EAAE,GAAC,YAAY,QACrB,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,YACN,MAAM,YACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,YACN,MAAM,GACJ,OAAO,CA6CnB;AAMD;;;;;GAKG;AACH,6CAHW,MAAM,EAAE,GAAC,YAAY,yNACnB,OAAO,CAuEnB;AAOD;;;;;;;;;;;;;GAaG;AACH,yCAHW,MAAM,EAAE,GAAC,YAAY,4NACnB,OAAO,CA8FnB;AAyCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,gDAtBW,YAAY,QACZ,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,YACN,MAAM,YACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,GACJ,MAAM,CAuElB;AApJD;;;;;;;;;;GAUG;AACH,yCAFU,MAAM,CAE6B;AAE7C;;;;;GAKG;AACH,uCAFU,MAAM,CAE0B"}
@@ -1,5 +1,7 @@
1
1
  import { line3_closest_points_segment_segment } from "../../../core/geom/3d/line/line3_closest_points_segment_segment.js";
2
2
  import { line3_compute_segment_nearest_point_to_point_t } from "../../../core/geom/3d/line/line3_compute_segment_nearest_point_to_point_t.js";
3
+ import { v3_quat3_apply } from "../../../core/geom/vec3/v3_quat3_apply.js";
4
+ import { v3_quat3_apply_inverse } from "../../../core/geom/vec3/v3_quat3_apply_inverse.js";
3
5
  import { sphere_box_contact } from "./sphere_box_contact.js";
4
6
 
5
7
  /**
@@ -22,54 +24,6 @@ import { sphere_box_contact } from "./sphere_box_contact.js";
22
24
 
23
25
  const scratch_st = new Float64Array(2);
24
26
 
25
- /**
26
- * Rotate a body-local vector into world space via the quaternion identity
27
- * `v' = q · v · q*`. Writes (rx, ry, rz) into the destination at offset.
28
- *
29
- * @param {number[]|Float64Array} out
30
- * @param {number} off
31
- * @param {number} vx
32
- * @param {number} vy
33
- * @param {number} vz
34
- * @param {number} qx
35
- * @param {number} qy
36
- * @param {number} qz
37
- * @param {number} qw
38
- */
39
- function rotate_local_to_world(out, off, vx, vy, vz, qx, qy, qz, qw) {
40
- const ix = qw * vx + qy * vz - qz * vy;
41
- const iy = qw * vy + qz * vx - qx * vz;
42
- const iz = qw * vz + qx * vy - qy * vx;
43
- const iw = -qx * vx - qy * vy - qz * vz;
44
- out[off] = ix * qw + iw * (-qx) + iy * (-qz) - iz * (-qy);
45
- out[off + 1] = iy * qw + iw * (-qy) + iz * (-qx) - ix * (-qz);
46
- out[off + 2] = iz * qw + iw * (-qz) + ix * (-qy) - iy * (-qx);
47
- }
48
-
49
- /**
50
- * Inverse-rotate a world vector into body space via the conjugate quaternion.
51
- *
52
- * @param {number[]|Float64Array} out
53
- * @param {number} off
54
- * @param {number} vx
55
- * @param {number} vy
56
- * @param {number} vz
57
- * @param {number} qx
58
- * @param {number} qy
59
- * @param {number} qz
60
- * @param {number} qw
61
- */
62
- function rotate_world_to_local(out, off, vx, vy, vz, qx, qy, qz, qw) {
63
- const cx = -qx, cy = -qy, cz = -qz;
64
- const tx = qw * vx + cy * vz - cz * vy;
65
- const ty = qw * vy + cz * vx - cx * vz;
66
- const tz = qw * vz + cx * vy - cy * vx;
67
- const tw = -cx * vx - cy * vy - cz * vz;
68
- out[off] = tx * qw + tw * qx + ty * qz - tz * qy;
69
- out[off + 1] = ty * qw + tw * qy + tz * qx - tx * qz;
70
- out[off + 2] = tz * qw + tw * qz + tx * qy - ty * qx;
71
- }
72
-
73
27
  const scratch_seg = new Float64Array(6);
74
28
 
75
29
  /**
@@ -89,8 +43,8 @@ const scratch_seg = new Float64Array(6);
89
43
  * @param {number} half_height
90
44
  */
91
45
  export function capsule_world_segment(out, cx, cy, cz, qx, qy, qz, qw, half_height) {
92
- rotate_local_to_world(out, 0, 0, -half_height, 0, qx, qy, qz, qw);
93
- rotate_local_to_world(out, 3, 0, half_height, 0, qx, qy, qz, qw);
46
+ v3_quat3_apply(out, 0, 0, -half_height, 0, qx, qy, qz, qw);
47
+ v3_quat3_apply(out, 3, 0, half_height, 0, qx, qy, qz, qw);
94
48
  out[0] += cx; out[1] += cy; out[2] += cz;
95
49
  out[3] += cx; out[4] += cy; out[5] += cz;
96
50
  }
@@ -267,7 +221,7 @@ export function capsule_box_contact(
267
221
  b_cx, b_cy, b_cz, b_qx, b_qy, b_qz, b_qw, b_hx, b_hy, b_hz
268
222
  ) {
269
223
  // Bring the capsule's segment endpoints into box-local space.
270
- rotate_world_to_local(scratch_a0_local, 0, -0, -a_half_h, 0, a_qx, a_qy, a_qz, a_qw);
224
+ v3_quat3_apply_inverse(scratch_a0_local, 0, -0, -a_half_h, 0, a_qx, a_qy, a_qz, a_qw);
271
225
  // Note: we constructed the segment ends in body frame as (0, ±h, 0). We
272
226
  // need them in world first, then box-local. Build world here directly
273
227
  // for clarity.
@@ -275,8 +229,8 @@ export function capsule_box_contact(
275
229
  // World → box-local: subtract box centre, then rotate by conjugate of box quat.
276
230
  const w_a0x = scratch_seg[0] - b_cx, w_a0y = scratch_seg[1] - b_cy, w_a0z = scratch_seg[2] - b_cz;
277
231
  const w_a1x = scratch_seg[3] - b_cx, w_a1y = scratch_seg[4] - b_cy, w_a1z = scratch_seg[5] - b_cz;
278
- rotate_world_to_local(scratch_a0_local, 0, w_a0x, w_a0y, w_a0z, b_qx, b_qy, b_qz, b_qw);
279
- rotate_world_to_local(scratch_a1_local, 0, w_a1x, w_a1y, w_a1z, b_qx, b_qy, b_qz, b_qw);
232
+ v3_quat3_apply_inverse(scratch_a0_local, 0, w_a0x, w_a0y, w_a0z, b_qx, b_qy, b_qz, b_qw);
233
+ v3_quat3_apply_inverse(scratch_a1_local, 0, w_a1x, w_a1y, w_a1z, b_qx, b_qy, b_qz, b_qw);
280
234
 
281
235
  const a0lx = scratch_a0_local[0], a0ly = scratch_a0_local[1], a0lz = scratch_a0_local[2];
282
236
  const a1lx = scratch_a1_local[0], a1ly = scratch_a1_local[1], a1lz = scratch_a1_local[2];
@@ -336,18 +290,18 @@ export function capsule_box_contact(
336
290
  }
337
291
 
338
292
  // Normal (box → capsule axis point) into world.
339
- rotate_local_to_world(out, 0, nlx, nly, nlz, b_qx, b_qy, b_qz, b_qw);
293
+ v3_quat3_apply(out, 0, nlx, nly, nlz, b_qx, b_qy, b_qz, b_qw);
340
294
  const nx = out[0], ny = out[1], nz = out[2];
341
295
  out[3] = a_radius - dist;
342
296
 
343
297
  // Capsule-side contact: world segment point pushed back by radius.
344
- rotate_local_to_world(out, 4, plx, ply, plz, b_qx, b_qy, b_qz, b_qw);
298
+ v3_quat3_apply(out, 4, plx, ply, plz, b_qx, b_qy, b_qz, b_qw);
345
299
  out[4] += b_cx - nx * a_radius;
346
300
  out[5] += b_cy - ny * a_radius;
347
301
  out[6] += b_cz - nz * a_radius;
348
302
 
349
303
  // Box-side contact: world projection of the box-surface point.
350
- rotate_local_to_world(out, 7, qlx, qly, qlz, b_qx, b_qy, b_qz, b_qw);
304
+ v3_quat3_apply(out, 7, qlx, qly, qlz, b_qx, b_qy, b_qz, b_qw);
351
305
  out[7] += b_cx;
352
306
  out[8] += b_cy;
353
307
  out[9] += b_cz;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @param {number[]|Float64Array} out length >= CAPSULE_TRIANGLE_MAX_CONTACTS *
3
+ * CAPSULE_TRIANGLE_CONTACT_STRIDE
4
+ * @param {number} c_cx capsule centre x
5
+ * @param {number} c_cy
6
+ * @param {number} c_cz
7
+ * @param {number} c_qx capsule rotation
8
+ * @param {number} c_qy
9
+ * @param {number} c_qz
10
+ * @param {number} c_qw
11
+ * @param {number} c_radius
12
+ * @param {number} c_half_h capsule half-height (not including caps)
13
+ * @param {number} ax triangle vertex A x (world)
14
+ * @param {number} ay
15
+ * @param {number} az
16
+ * @param {number} bx triangle vertex B x (world)
17
+ * @param {number} by
18
+ * @param {number} bz
19
+ * @param {number} cx triangle vertex C x (world)
20
+ * @param {number} cy
21
+ * @param {number} cz
22
+ * @returns {number} number of contacts emitted, in `[0, CAPSULE_TRIANGLE_MAX_CONTACTS]`
23
+ */
24
+ export function capsule_triangle_contact(out: number[] | Float64Array, c_cx: number, c_cy: number, c_cz: number, c_qx: number, c_qy: number, c_qz: number, c_qw: number, c_radius: number, c_half_h: number, ax: number, ay: number, az: number, bx: number, by: number, bz: number, cx: number, cy: number, cz: number): number;
25
+ /**
26
+ * Multi-point capsule-vs-triangle contact generation.
27
+ *
28
+ * A capsule is the Minkowski sum of a line segment and a sphere of
29
+ * radius `r`, so capsule-vs-triangle reduces to:
30
+ *
31
+ * 1. Find the closest pair of points between the capsule's central
32
+ * segment and the triangle (segment ↔ triangle distance).
33
+ * 2. If distance ≥ radius, no overlap.
34
+ * 3. Otherwise emit a primary contact at the segment-triangle
35
+ * closest pair, then additionally treat each cap centre (segment
36
+ * endpoint) as a sphere of cap-radius and run
37
+ * {@link sphere_triangle_contact} to produce up to 2 endpoint
38
+ * contacts. Spatial dedup against the primary so a capsule
39
+ * touching with one cap doesn't emit two contacts at the same
40
+ * point.
41
+ *
42
+ * The multi-point manifold (1 primary + 2 endpoints) is what allows a
43
+ * capsule resting flat on a triangle face to be stable — a single
44
+ * contact at the segment's closest point is positionally ambiguous
45
+ * (every interior segment point is equidistant from the face), and
46
+ * the body wobbles around whichever point the iterative closest-point
47
+ * solver happened to converge on. The endpoint contacts pin the
48
+ * capsule's rotation about its long axis.
49
+ *
50
+ * Output layout per contact (matches
51
+ * {@link CAPSULE_BOX_CONTACT_STRIDE} so the same downstream wiring
52
+ * works):
53
+ * 0..2 : world contact on capsule side
54
+ * 3..5 : world contact on triangle side
55
+ * 6..8 : normal from triangle (B) toward capsule (A)
56
+ * 9 : depth (positive = penetration)
57
+ *
58
+ * @author Alex Goldring
59
+ * @copyright Company Named Limited (c) 2026
60
+ */
61
+ /**
62
+ * Output stride per contact.
63
+ * @type {number}
64
+ */
65
+ export const CAPSULE_TRIANGLE_CONTACT_STRIDE: number;
66
+ /**
67
+ * Maximum contacts emitted: 1 primary + 2 endpoint sphere contacts.
68
+ * @type {number}
69
+ */
70
+ export const CAPSULE_TRIANGLE_MAX_CONTACTS: number;
71
+ //# sourceMappingURL=capsule_triangle_contact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capsule_triangle_contact.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/capsule_triangle_contact.js"],"names":[],"mappings":"AAgPA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,8CAtBW,MAAM,EAAE,GAAC,YAAY,QAErB,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,YACN,MAAM,YACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,GACJ,MAAM,CAqElB;AArUD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;GAGG;AACH,8CAFU,MAAM,CAEkC;AAElD;;;GAGG;AACH,4CAFU,MAAM,CAE+B"}
@@ -0,0 +1,375 @@
1
+ import { computeTriangleClosestPointToPointBarycentric } from "../../../core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js";
2
+ import { line3_closest_points_segment_segment } from "../../../core/geom/3d/line/line3_closest_points_segment_segment.js";
3
+ import { capsule_world_segment } from "./capsule_contacts.js";
4
+ import { sphere_triangle_contact } from "./sphere_triangle_contact.js";
5
+
6
+ /**
7
+ * Multi-point capsule-vs-triangle contact generation.
8
+ *
9
+ * A capsule is the Minkowski sum of a line segment and a sphere of
10
+ * radius `r`, so capsule-vs-triangle reduces to:
11
+ *
12
+ * 1. Find the closest pair of points between the capsule's central
13
+ * segment and the triangle (segment ↔ triangle distance).
14
+ * 2. If distance ≥ radius, no overlap.
15
+ * 3. Otherwise emit a primary contact at the segment-triangle
16
+ * closest pair, then additionally treat each cap centre (segment
17
+ * endpoint) as a sphere of cap-radius and run
18
+ * {@link sphere_triangle_contact} to produce up to 2 endpoint
19
+ * contacts. Spatial dedup against the primary so a capsule
20
+ * touching with one cap doesn't emit two contacts at the same
21
+ * point.
22
+ *
23
+ * The multi-point manifold (1 primary + 2 endpoints) is what allows a
24
+ * capsule resting flat on a triangle face to be stable — a single
25
+ * contact at the segment's closest point is positionally ambiguous
26
+ * (every interior segment point is equidistant from the face), and
27
+ * the body wobbles around whichever point the iterative closest-point
28
+ * solver happened to converge on. The endpoint contacts pin the
29
+ * capsule's rotation about its long axis.
30
+ *
31
+ * Output layout per contact (matches
32
+ * {@link CAPSULE_BOX_CONTACT_STRIDE} so the same downstream wiring
33
+ * works):
34
+ * 0..2 : world contact on capsule side
35
+ * 3..5 : world contact on triangle side
36
+ * 6..8 : normal from triangle (B) toward capsule (A)
37
+ * 9 : depth (positive = penetration)
38
+ *
39
+ * @author Alex Goldring
40
+ * @copyright Company Named Limited (c) 2026
41
+ */
42
+
43
+ /**
44
+ * Output stride per contact.
45
+ * @type {number}
46
+ */
47
+ export const CAPSULE_TRIANGLE_CONTACT_STRIDE = 10;
48
+
49
+ /**
50
+ * Maximum contacts emitted: 1 primary + 2 endpoint sphere contacts.
51
+ * @type {number}
52
+ */
53
+ export const CAPSULE_TRIANGLE_MAX_CONTACTS = 3;
54
+
55
+ /**
56
+ * Two world positions are treated as the same contact when their
57
+ * squared distance is below this threshold. Same value as the box
58
+ * variant — looser than the manifold's warm-start match tolerance
59
+ * (≈0.02) because we specifically want to dedupe coincident
60
+ * primary/endpoint contacts; small lateral shifts produce distinct
61
+ * contacts that should both be kept.
62
+ * @type {number}
63
+ */
64
+ const DEDUPE_DIST_SQR = 1e-6;
65
+
66
+ /**
67
+ * Numerical floor for "segment direction roughly parallel to triangle
68
+ * plane". Used to bail out of the case-0 (segment-plane intersection)
69
+ * branch before dividing by `d · n`.
70
+ * @type {number}
71
+ */
72
+ const PARALLEL_EPS = 1e-12;
73
+
74
+ /**
75
+ * Tolerance for "reconstructed point matches input" in the inside-
76
+ * triangle check during case-0.
77
+ * @type {number}
78
+ */
79
+ const INSIDE_TRIANGLE_EPS_SQR = 1e-10;
80
+
81
+ // --- scratch (allocation-free across calls) ---------------------------------
82
+
83
+ const scratch_seg = new Float64Array(6);
84
+ const scratch_bary = new Float64Array(2);
85
+ const scratch_pair_st = new Float64Array(2);
86
+ const scratch_seg_tri = new Float64Array(6); // closest segment / triangle world points
87
+ const scratch_sphere = new Float64Array(10);
88
+
89
+ /**
90
+ * Compute the closest pair of points between a 3D line segment
91
+ * `(P1, P2)` and a triangle `(A, B, C)`. Writes the closest segment
92
+ * point into `out[0..2]` and the closest triangle point into
93
+ * `out[3..5]`, and returns the (positive) distance between them.
94
+ *
95
+ * Implementation: take the minimum of 6 candidate distances —
96
+ * - Case 0: segment crosses triangle's plane inside the face
97
+ * → distance 0; both points coincide on the face.
98
+ * - Cases 1, 2: each segment endpoint vs its closest point on the
99
+ * triangle (via {@link computeTriangleClosestPointToPointBarycentric}).
100
+ * Subsumes the "endpoint vs triangle vertex / edge / face" cases.
101
+ * - Cases 3, 4, 5: segment vs each of the triangle's 3 edges, via
102
+ * {@link line3_closest_points_segment_segment}. Covers the
103
+ * "segment interior closest to triangle edge" case that the
104
+ * endpoint queries miss.
105
+ *
106
+ * @param {number[]|Float64Array} out length >= 6
107
+ */
108
+ function segment_triangle_closest(
109
+ out,
110
+ p1x, p1y, p1z, p2x, p2y, p2z,
111
+ ax, ay, az, bx, by, bz, cx, cy, cz
112
+ ) {
113
+ let best_dist_sqr = Infinity;
114
+ let best_sx = 0, best_sy = 0, best_sz = 0;
115
+ let best_tx = 0, best_ty = 0, best_tz = 0;
116
+
117
+ // --- Case 0: segment intersects triangle plane INSIDE the face -----------
118
+ const dx_seg = p2x - p1x, dy_seg = p2y - p1y, dz_seg = p2z - p1z;
119
+ const e1x = bx - ax, e1y = by - ay, e1z = bz - az;
120
+ const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
121
+ const tnx = e1y * e2z - e1z * e2y;
122
+ const tny = e1z * e2x - e1x * e2z;
123
+ const tnz = e1x * e2y - e1y * e2x;
124
+ const d_dot_n = dx_seg * tnx + dy_seg * tny + dz_seg * tnz;
125
+ if (Math.abs(d_dot_n) > PARALLEL_EPS) {
126
+ const t = ((ax - p1x) * tnx + (ay - p1y) * tny + (az - p1z) * tnz) / d_dot_n;
127
+ if (t >= 0 && t <= 1) {
128
+ const ix = p1x + dx_seg * t;
129
+ const iy = p1y + dy_seg * t;
130
+ const iz = p1z + dz_seg * t;
131
+ // Inside-triangle test: feed the intersection through the
132
+ // closest-point algorithm; it returns the BARYCENTRIC of the
133
+ // closest point on the triangle. If the closest point equals
134
+ // the input (within INSIDE_TRIANGLE_EPS_SQR), the intersection
135
+ // is inside the face.
136
+ computeTriangleClosestPointToPointBarycentric(
137
+ scratch_bary, 0,
138
+ ix, iy, iz,
139
+ ax, ay, az,
140
+ bx, by, bz,
141
+ cx, cy, cz
142
+ );
143
+ const alpha = scratch_bary[0];
144
+ const beta = scratch_bary[1];
145
+ const gamma = 1 - alpha - beta;
146
+ const rx = alpha * ax + beta * bx + gamma * cx;
147
+ const ry = alpha * ay + beta * by + gamma * cy;
148
+ const rz = alpha * az + beta * bz + gamma * cz;
149
+ const diff_x = rx - ix, diff_y = ry - iy, diff_z = rz - iz;
150
+ if (diff_x * diff_x + diff_y * diff_y + diff_z * diff_z < INSIDE_TRIANGLE_EPS_SQR) {
151
+ out[0] = ix; out[1] = iy; out[2] = iz;
152
+ out[3] = ix; out[4] = iy; out[5] = iz;
153
+ return 0;
154
+ }
155
+ }
156
+ }
157
+
158
+ // --- Case 1: P1 vs closest-on-triangle -----------------------------------
159
+ computeTriangleClosestPointToPointBarycentric(
160
+ scratch_bary, 0,
161
+ p1x, p1y, p1z,
162
+ ax, ay, az,
163
+ bx, by, bz,
164
+ cx, cy, cz
165
+ );
166
+ {
167
+ const alpha = scratch_bary[0];
168
+ const beta = scratch_bary[1];
169
+ const gamma = 1 - alpha - beta;
170
+ const qx = alpha * ax + beta * bx + gamma * cx;
171
+ const qy = alpha * ay + beta * by + gamma * cy;
172
+ const qz = alpha * az + beta * bz + gamma * cz;
173
+ const dx = p1x - qx, dy = p1y - qy, dz = p1z - qz;
174
+ const d_sqr = dx * dx + dy * dy + dz * dz;
175
+ if (d_sqr < best_dist_sqr) {
176
+ best_dist_sqr = d_sqr;
177
+ best_sx = p1x; best_sy = p1y; best_sz = p1z;
178
+ best_tx = qx; best_ty = qy; best_tz = qz;
179
+ }
180
+ }
181
+
182
+ // --- Case 2: P2 vs closest-on-triangle -----------------------------------
183
+ computeTriangleClosestPointToPointBarycentric(
184
+ scratch_bary, 0,
185
+ p2x, p2y, p2z,
186
+ ax, ay, az,
187
+ bx, by, bz,
188
+ cx, cy, cz
189
+ );
190
+ {
191
+ const alpha = scratch_bary[0];
192
+ const beta = scratch_bary[1];
193
+ const gamma = 1 - alpha - beta;
194
+ const qx = alpha * ax + beta * bx + gamma * cx;
195
+ const qy = alpha * ay + beta * by + gamma * cy;
196
+ const qz = alpha * az + beta * bz + gamma * cz;
197
+ const dx = p2x - qx, dy = p2y - qy, dz = p2z - qz;
198
+ const d_sqr = dx * dx + dy * dy + dz * dz;
199
+ if (d_sqr < best_dist_sqr) {
200
+ best_dist_sqr = d_sqr;
201
+ best_sx = p2x; best_sy = p2y; best_sz = p2z;
202
+ best_tx = qx; best_ty = qy; best_tz = qz;
203
+ }
204
+ }
205
+
206
+ // --- Cases 3..5: segment vs each triangle edge ---------------------------
207
+ // Helper inline to avoid array allocation.
208
+ function test_edge(e_p1x, e_p1y, e_p1z, e_p2x, e_p2y, e_p2z) {
209
+ line3_closest_points_segment_segment(
210
+ scratch_pair_st,
211
+ p1x, p1y, p1z, p2x, p2y, p2z,
212
+ e_p1x, e_p1y, e_p1z, e_p2x, e_p2y, e_p2z
213
+ );
214
+ const s = scratch_pair_st[0];
215
+ const t = scratch_pair_st[1];
216
+ const sx = p1x + s * (p2x - p1x);
217
+ const sy = p1y + s * (p2y - p1y);
218
+ const sz = p1z + s * (p2z - p1z);
219
+ const tx = e_p1x + t * (e_p2x - e_p1x);
220
+ const ty = e_p1y + t * (e_p2y - e_p1y);
221
+ const tz = e_p1z + t * (e_p2z - e_p1z);
222
+ const dx = sx - tx, dy = sy - ty, dz = sz - tz;
223
+ const d_sqr = dx * dx + dy * dy + dz * dz;
224
+ if (d_sqr < best_dist_sqr) {
225
+ best_dist_sqr = d_sqr;
226
+ best_sx = sx; best_sy = sy; best_sz = sz;
227
+ best_tx = tx; best_ty = ty; best_tz = tz;
228
+ }
229
+ }
230
+ test_edge(ax, ay, az, bx, by, bz);
231
+ test_edge(bx, by, bz, cx, cy, cz);
232
+ test_edge(cx, cy, cz, ax, ay, az);
233
+
234
+ out[0] = best_sx; out[1] = best_sy; out[2] = best_sz;
235
+ out[3] = best_tx; out[4] = best_ty; out[5] = best_tz;
236
+ return Math.sqrt(best_dist_sqr);
237
+ }
238
+
239
+ // --- main --------------------------------------------------------------------
240
+
241
+ /**
242
+ * @param {number[]|Float64Array} out length >= CAPSULE_TRIANGLE_MAX_CONTACTS *
243
+ * CAPSULE_TRIANGLE_CONTACT_STRIDE
244
+ * @param {number} c_cx capsule centre x
245
+ * @param {number} c_cy
246
+ * @param {number} c_cz
247
+ * @param {number} c_qx capsule rotation
248
+ * @param {number} c_qy
249
+ * @param {number} c_qz
250
+ * @param {number} c_qw
251
+ * @param {number} c_radius
252
+ * @param {number} c_half_h capsule half-height (not including caps)
253
+ * @param {number} ax triangle vertex A x (world)
254
+ * @param {number} ay
255
+ * @param {number} az
256
+ * @param {number} bx triangle vertex B x (world)
257
+ * @param {number} by
258
+ * @param {number} bz
259
+ * @param {number} cx triangle vertex C x (world)
260
+ * @param {number} cy
261
+ * @param {number} cz
262
+ * @returns {number} number of contacts emitted, in `[0, CAPSULE_TRIANGLE_MAX_CONTACTS]`
263
+ */
264
+ export function capsule_triangle_contact(
265
+ out,
266
+ c_cx, c_cy, c_cz, c_qx, c_qy, c_qz, c_qw, c_radius, c_half_h,
267
+ ax, ay, az, bx, by, bz, cx, cy, cz
268
+ ) {
269
+ // Capsule's world-space segment endpoints.
270
+ capsule_world_segment(scratch_seg, c_cx, c_cy, c_cz, c_qx, c_qy, c_qz, c_qw, c_half_h);
271
+ const sax = scratch_seg[0], say = scratch_seg[1], saz = scratch_seg[2];
272
+ const sbx = scratch_seg[3], sby = scratch_seg[4], sbz = scratch_seg[5];
273
+
274
+ // Primary contact: closest segment point ↔ closest triangle point.
275
+ const dist = segment_triangle_closest(
276
+ scratch_seg_tri,
277
+ sax, say, saz, sbx, sby, sbz,
278
+ ax, ay, az, bx, by, bz, cx, cy, cz
279
+ );
280
+ if (dist >= c_radius) return 0;
281
+
282
+ const seg_pt_x = scratch_seg_tri[0];
283
+ const seg_pt_y = scratch_seg_tri[1];
284
+ const seg_pt_z = scratch_seg_tri[2];
285
+ const tri_pt_x = scratch_seg_tri[3];
286
+ const tri_pt_y = scratch_seg_tri[4];
287
+ const tri_pt_z = scratch_seg_tri[5];
288
+
289
+ // Normal points from the triangle closest point toward the segment.
290
+ let nx, ny, nz;
291
+ if (dist > 0) {
292
+ const inv = 1 / dist;
293
+ nx = (seg_pt_x - tri_pt_x) * inv;
294
+ ny = (seg_pt_y - tri_pt_y) * inv;
295
+ nz = (seg_pt_z - tri_pt_z) * inv;
296
+ } else {
297
+ // Segment crosses or touches the triangle plane on the face —
298
+ // pick the triangle's outward face normal as a deterministic
299
+ // tie-break, same convention as sphere_triangle_contact.
300
+ const e1x = bx - ax, e1y = by - ay, e1z = bz - az;
301
+ const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
302
+ const fnx = e1y * e2z - e1z * e2y;
303
+ const fny = e1z * e2x - e1x * e2z;
304
+ const fnz = e1x * e2y - e1y * e2x;
305
+ const fn_mag = Math.sqrt(fnx * fnx + fny * fny + fnz * fnz);
306
+ if (fn_mag === 0) return 0; // degenerate triangle
307
+ nx = fnx / fn_mag;
308
+ ny = fny / fn_mag;
309
+ nz = fnz / fn_mag;
310
+ }
311
+
312
+ const depth = c_radius - dist;
313
+
314
+ // Primary contact emission.
315
+ // Capsule surface point: segment point pushed -radius along the normal
316
+ // (back toward the triangle).
317
+ out[0] = seg_pt_x - nx * c_radius;
318
+ out[1] = seg_pt_y - ny * c_radius;
319
+ out[2] = seg_pt_z - nz * c_radius;
320
+ out[3] = tri_pt_x; out[4] = tri_pt_y; out[5] = tri_pt_z;
321
+ out[6] = nx; out[7] = ny; out[8] = nz;
322
+ out[9] = depth;
323
+ let count = 1;
324
+
325
+ // Endpoint sphere contacts. Inline the 2 endpoint queries to keep
326
+ // allocation-free.
327
+ count = try_endpoint_contact(out, count, sax, say, saz, c_radius, ax, ay, az, bx, by, bz, cx, cy, cz);
328
+ count = try_endpoint_contact(out, count, sbx, sby, sbz, c_radius, ax, ay, az, bx, by, bz, cx, cy, cz);
329
+
330
+ return count;
331
+ }
332
+
333
+ /**
334
+ * Run sphere_triangle_contact at one cap centre and append the
335
+ * resulting contact to `out` (with spatial dedup against the
336
+ * already-emitted entries). Returns the new contact count.
337
+ *
338
+ * @returns {number}
339
+ */
340
+ function try_endpoint_contact(
341
+ out, count,
342
+ cap_x, cap_y, cap_z, radius,
343
+ ax, ay, az, bx, by, bz, cx, cy, cz
344
+ ) {
345
+ const ok = sphere_triangle_contact(
346
+ scratch_sphere,
347
+ cap_x, cap_y, cap_z, radius,
348
+ ax, ay, az, bx, by, bz, cx, cy, cz
349
+ );
350
+ if (!ok) return count;
351
+
352
+ const e_nx = scratch_sphere[0], e_ny = scratch_sphere[1], e_nz = scratch_sphere[2];
353
+ const e_depth = scratch_sphere[3];
354
+ const e_cap_x = scratch_sphere[4], e_cap_y = scratch_sphere[5], e_cap_z = scratch_sphere[6];
355
+ const e_tri_x = scratch_sphere[7], e_tri_y = scratch_sphere[8], e_tri_z = scratch_sphere[9];
356
+
357
+ // Dedup: compare capsule-side world position against every prior
358
+ // contact. Two distinct triangle-surface points always have
359
+ // distinct capsule-side projections along the cap-radius vector,
360
+ // so capsule-side position uniquely identifies a contact.
361
+ for (let k = 0; k < count; k++) {
362
+ const ko = k * CAPSULE_TRIANGLE_CONTACT_STRIDE;
363
+ const dx = e_cap_x - out[ko];
364
+ const dy = e_cap_y - out[ko + 1];
365
+ const dz = e_cap_z - out[ko + 2];
366
+ if (dx * dx + dy * dy + dz * dz < DEDUPE_DIST_SQR) return count;
367
+ }
368
+
369
+ const o = count * CAPSULE_TRIANGLE_CONTACT_STRIDE;
370
+ out[o] = e_cap_x; out[o + 1] = e_cap_y; out[o + 2] = e_cap_z;
371
+ out[o + 3] = e_tri_x; out[o + 4] = e_tri_y; out[o + 5] = e_tri_z;
372
+ out[o + 6] = e_nx; out[o + 7] = e_ny; out[o + 8] = e_nz;
373
+ out[o + 9] = e_depth;
374
+ return count + 1;
375
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Compute the penetration depth between two shapes at world poses,
3
+ * returning the depth and writing the separation direction.
4
+ *
5
+ * If the shapes overlap, the return value is the positive distance
6
+ * along `out_direction` you would have to translate `shape_a` to
7
+ * separate it from `shape_b`. `out_direction` is the unit vector
8
+ * pointing from `shape_b` toward `shape_a` — applying
9
+ * `out_direction * return_value` to `position_a` (or equivalently
10
+ * `-out_direction * return_value` to `position_b`) is the minimum
11
+ * translation that produces separation.
12
+ *
13
+ * If the shapes do not overlap (or EPA degenerates on a tangent
14
+ * contact), the return value is `0` and `out_direction` is left
15
+ * untouched. Callers should treat 0 as "no penetration".
16
+ *
17
+ * Sign convention matches the narrowphase's stored contact normal:
18
+ * - `out_direction` ≡ "B → A" direction
19
+ * - Negative of cast direction in shape_cast at the kiss point
20
+ * - The outward normal of B's surface at the contact, pointing
21
+ * toward A
22
+ *
23
+ * Built on the existing GJK + EPA + PosedShape primitives — same
24
+ * precision characteristics (essentially exact for sign-based supports
25
+ * like cubes; asymptotic on curved supports like spheres near tangent,
26
+ * where the polytope iteration cap leaves a small angular residual on
27
+ * the direction).
28
+ *
29
+ * ## Non-convex support
30
+ *
31
+ * Exactly **one** of the two shapes may be non-convex (heightmap,
32
+ * mesh). The non-convex shape is decomposed into triangles overlapping
33
+ * the convex shape's AABB (via the same machinery the narrowphase
34
+ * uses), and per-triangle GJK + EPA is run; the deepest contact's
35
+ * direction and depth are reported. Concave-vs-concave throws — the
36
+ * M×N triangle-pair cost is out of scope for this primitive (and is
37
+ * also refused by the narrowphase for dynamic pairs).
38
+ *
39
+ * ## Non-convex precision limits
40
+ *
41
+ * The concave path is a per-triangle half-space test (convex's deepest
42
+ * point along −face_normal, compared to the triangle's plane). This
43
+ * is exact for **heightmaps** — adjacent triangles cover the
44
+ * boundary cases, and the face normal IS the contact direction.
45
+ *
46
+ * For **closed meshes** the half-space test extrapolates each triangle
47
+ * as an infinite plane, which can over-report depth on side faces
48
+ * when the convex shape extends past a face's 2D extent. The
49
+ * deepest-wins aggregation then picks a "false-deepest" face whose
50
+ * direction may not be the geometrically optimal one. A closed-form
51
+ * triangle-vs-X solver per primitive shape would fix this; until
52
+ * then, the function reports *some* outward direction with positive
53
+ * depth, which still resolves penetration over multiple iterations.
54
+ *
55
+ * Bodies fully inside the concave solid (or below a heightmap
56
+ * surface) are correctly recovered: every face's deepest-inward
57
+ * support point lands on the inward side, so the half-space test
58
+ * fires, and the deepest face wins. The reported direction pushes
59
+ * the body outward through that face.
60
+ *
61
+ * @param {Float64Array|number[]} out_direction length ≥ 3; receives
62
+ * the unit separation direction (B → A) on penetration
63
+ * @param {AbstractShape3D} shape_a in shape_a's local frame; may be concave
64
+ * @param {{x:number,y:number,z:number}} position_a world position of A
65
+ * @param {{x:number,y:number,z:number,w:number}} rotation_a world rotation of A
66
+ * @param {AbstractShape3D} shape_b in shape_b's local frame; may be concave
67
+ * @param {{x:number,y:number,z:number}} position_b world position of B
68
+ * @param {{x:number,y:number,z:number,w:number}} rotation_b world rotation of B
69
+ * @returns {number} penetration depth (positive) on overlap, 0 otherwise
70
+ * @throws {Error} if both shapes have `is_convex === false`
71
+ */
72
+ export function compute_penetration(out_direction: Float64Array | number[], shape_a: AbstractShape3D, position_a: {
73
+ x: number;
74
+ y: number;
75
+ z: number;
76
+ }, rotation_a: {
77
+ x: number;
78
+ y: number;
79
+ z: number;
80
+ w: number;
81
+ }, shape_b: AbstractShape3D, position_b: {
82
+ x: number;
83
+ y: number;
84
+ z: number;
85
+ }, rotation_b: {
86
+ x: number;
87
+ y: number;
88
+ z: number;
89
+ w: number;
90
+ }): number;
91
+ //# sourceMappingURL=compute_penetration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute_penetration.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/compute_penetration.js"],"names":[],"mappings":"AAkEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AACH,mDAXW,YAAY,GAAC,MAAM,EAAE,wCAGrB;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,cAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,wCAErC;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,cAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,GACnC,MAAM,CA4BlB"}