@woosh/meep-engine 2.139.0 → 2.140.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) 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_to_matrix3.d.ts +54 -0
  9. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts.map +1 -0
  10. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -0
  11. package/src/core/geom/3d/shape/AbstractShape3D.d.ts +24 -2
  12. package/src/core/geom/3d/shape/AbstractShape3D.d.ts.map +1 -1
  13. package/src/core/geom/3d/shape/AbstractShape3D.js +24 -1
  14. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +148 -0
  15. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -0
  16. package/src/core/geom/3d/shape/HeightMapShape3D.js +451 -0
  17. package/src/core/geom/3d/shape/MeshShape3D.d.ts +210 -0
  18. package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -0
  19. package/src/core/geom/3d/shape/MeshShape3D.js +593 -0
  20. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  21. package/src/core/geom/3d/shape/TransformedShape3D.js +46 -2
  22. package/src/core/geom/3d/shape/Triangle3D.d.ts +95 -0
  23. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -0
  24. package/src/core/geom/3d/shape/Triangle3D.js +318 -0
  25. package/src/core/geom/3d/shape/UnionShape3D.js +13 -0
  26. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts +30 -0
  27. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts.map +1 -0
  28. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +64 -0
  29. package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.js +9 -11
  30. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts +28 -0
  31. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts.map +1 -0
  32. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -0
  33. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts.map +1 -1
  34. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +40 -18
  35. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts +9 -5
  36. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts.map +1 -1
  37. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +38 -10
  38. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts +14 -5
  39. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts.map +1 -1
  40. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +47 -5
  41. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +19 -0
  42. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
  43. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +75 -13
  44. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts +2 -2
  45. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts.map +1 -1
  46. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +1 -1
  47. package/src/core/geom/vec3/v3_dot_array_array.d.ts +3 -3
  48. package/src/core/geom/vec3/v3_dot_array_array.d.ts.map +1 -1
  49. package/src/core/geom/vec3/v3_dot_array_array.js +2 -2
  50. package/src/core/geom/vec3/v3_negate_array.d.ts +3 -3
  51. package/src/core/geom/vec3/v3_negate_array.d.ts.map +1 -1
  52. package/src/core/geom/vec3/v3_negate_array.js +2 -2
  53. package/src/core/geom/vec3/v3_quat3_apply.d.ts +29 -0
  54. package/src/core/geom/vec3/v3_quat3_apply.d.ts.map +1 -0
  55. package/src/core/geom/vec3/v3_quat3_apply.js +39 -0
  56. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +30 -0
  57. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts.map +1 -0
  58. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +41 -0
  59. package/src/core/geom/vec3/v3_triple_cross_product.d.ts +32 -0
  60. package/src/core/geom/vec3/v3_triple_cross_product.d.ts.map +1 -0
  61. package/src/core/geom/vec3/v3_triple_cross_product.js +45 -0
  62. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +16 -3
  63. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  64. package/src/engine/control/first-person/FirstPersonPlayerController.js +211 -211
  65. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +72 -8
  66. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  67. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +37 -5
  68. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +101 -3
  69. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  70. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1789 -1416
  71. package/src/engine/control/first-person/TODO.md +173 -127
  72. package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -1
  73. package/src/engine/control/first-person/abilities/Slide.js +9 -1
  74. package/src/engine/control/first-person/prototype_first_person_controller.js +88 -2
  75. package/src/engine/control/first-person/test/buildTestPlayer.d.ts.map +1 -1
  76. package/src/engine/control/first-person/test/buildTestPlayer.js +9 -1
  77. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts +42 -0
  78. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts.map +1 -0
  79. package/src/engine/graphics/geometry/CapsuleGeometry.js +171 -0
  80. package/src/engine/physics/BULLET_REVIEW.md +945 -0
  81. package/src/engine/physics/CANNON_REVIEW.md +1300 -0
  82. package/src/engine/physics/JOLT_REVIEW.md +913 -0
  83. package/src/engine/physics/PLAN.md +461 -236
  84. package/src/engine/physics/RAPIER_REVIEW.md +934 -0
  85. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -0
  86. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +2 -2
  87. package/src/engine/physics/contact/ManifoldStore.d.ts +83 -10
  88. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  89. package/src/engine/physics/contact/ManifoldStore.js +608 -499
  90. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +2 -2
  91. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -1
  92. package/src/engine/physics/ecs/PhysicsSystem.d.ts +128 -20
  93. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  94. package/src/engine/physics/ecs/PhysicsSystem.js +1301 -1159
  95. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  96. package/src/engine/physics/fluid/FluidSimulator.js +2 -1
  97. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +28 -6
  98. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
  99. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +39 -17
  100. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +6 -6
  101. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +1 -1
  102. package/src/engine/physics/gjk/expanding_polytope_algorithm.js +68 -22
  103. package/src/engine/physics/gjk/gjk.d.ts +28 -2
  104. package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
  105. package/src/engine/physics/gjk/gjk.js +421 -378
  106. package/src/engine/physics/gjk/minkowski_support.d.ts +37 -0
  107. package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -0
  108. package/src/engine/physics/gjk/minkowski_support.js +75 -0
  109. package/src/engine/physics/gjk/mpr.d.ts +56 -0
  110. package/src/engine/physics/gjk/mpr.d.ts.map +1 -0
  111. package/src/engine/physics/gjk/mpr.js +344 -0
  112. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +20 -5
  113. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  114. package/src/engine/physics/inertia/world_inverse_inertia.js +36 -38
  115. package/src/engine/physics/integration/integrate_position.d.ts +25 -7
  116. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
  117. package/src/engine/physics/integration/integrate_position.js +43 -12
  118. package/src/engine/physics/integration/integrate_velocity.d.ts +30 -0
  119. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  120. package/src/engine/physics/integration/integrate_velocity.js +82 -1
  121. package/src/engine/physics/narrowphase/PosedShape.d.ts +0 -8
  122. package/src/engine/physics/narrowphase/PosedShape.d.ts.map +1 -1
  123. package/src/engine/physics/narrowphase/PosedShape.js +28 -30
  124. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  125. package/src/engine/physics/narrowphase/box_box_manifold.js +113 -17
  126. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts +30 -0
  127. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -0
  128. package/src/engine/physics/narrowphase/box_triangle_contact.js +811 -0
  129. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  130. package/src/engine/physics/narrowphase/capsule_contacts.js +10 -56
  131. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts +71 -0
  132. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -0
  133. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +375 -0
  134. package/src/engine/physics/narrowphase/compute_penetration.d.ts +91 -0
  135. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -0
  136. package/src/engine/physics/narrowphase/compute_penetration.js +396 -0
  137. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts +35 -0
  138. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +1 -0
  139. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.js +80 -0
  140. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts +31 -0
  141. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts.map +1 -0
  142. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -0
  143. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +42 -0
  144. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -0
  145. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +204 -0
  146. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +42 -0
  147. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -0
  148. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +94 -0
  149. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts +37 -0
  150. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts.map +1 -0
  151. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -0
  152. package/src/engine/physics/narrowphase/narrowphase_step.d.ts +8 -2
  153. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  154. package/src/engine/physics/narrowphase/narrowphase_step.js +1422 -382
  155. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
  156. package/src/engine/physics/narrowphase/sphere_box_contact.js +16 -23
  157. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts +48 -0
  158. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts.map +1 -0
  159. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +143 -0
  160. package/src/engine/physics/queries/overlap_shape.d.ts +51 -0
  161. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -0
  162. package/src/engine/physics/queries/overlap_shape.js +183 -0
  163. package/src/engine/physics/queries/shape_cast.d.ts +56 -0
  164. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -0
  165. package/src/engine/physics/queries/shape_cast.js +387 -0
  166. package/src/engine/physics/solver/solve_contacts.d.ts +116 -30
  167. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  168. package/src/engine/physics/solver/solve_contacts.js +641 -223
  169. package/src/engine/physics/broadphase/aabb_transform_oriented.d.ts.map +0 -1
  170. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts +0 -20
  171. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts.map +0 -1
  172. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.js +0 -83
@@ -1,378 +1,421 @@
1
- import { v3_dot } from "../../../core/geom/vec3/v3_dot.js";
2
- import { v3_length } from "../../../core/geom/vec3/v3_length.js";
3
-
4
- const GJK_MAX_ITERATIONS = 64;
5
-
6
- /**
7
- * Module-level scratch buffers to avoid per-call allocations.
8
- */
9
- const scratch_support_a = new Float32Array(3);
10
- const scratch_support_b = new Float32Array(3);
11
- const scratch_dir = new Float32Array(3);
12
-
13
- /**
14
- * Compute the Minkowski difference support point for two shapes.
15
- * support(A-B, d) = support(A, d) - support(B, -d)
16
- *
17
- * The direction is normalized before being passed to the shape support
18
- * functions, since {@link AbstractShape3D#support} assumes a unit vector.
19
- *
20
- * @param {number[]|Float32Array} result
21
- * @param {number} result_offset
22
- * @param {AbstractShape3D} shape_a
23
- * @param {AbstractShape3D} shape_b
24
- * @param {number} dir_x
25
- * @param {number} dir_y
26
- * @param {number} dir_z
27
- */
28
- function minkowski_support(
29
- result, result_offset,
30
- shape_a,
31
- shape_b,
32
- dir_x, dir_y, dir_z
33
- ) {
34
- // Normalize direction support() contract requires a unit vector
35
- const len = v3_length(dir_x, dir_y, dir_z);
36
-
37
- if (len > 0) {
38
- const inv = 1 / len;
39
- dir_x *= inv;
40
- dir_y *= inv;
41
- dir_z *= inv;
42
- }
43
-
44
- shape_a.support(scratch_support_a, 0, dir_x, dir_y, dir_z);
45
- shape_b.support(scratch_support_b, 0, -dir_x, -dir_y, -dir_z);
46
-
47
- result[result_offset] = scratch_support_a[0] - scratch_support_b[0];
48
- result[result_offset + 1] = scratch_support_a[1] - scratch_support_b[1];
49
- result[result_offset + 2] = scratch_support_a[2] - scratch_support_b[2];
50
- }
51
-
52
- /**
53
- * GJK intersection test for two convex shapes.
54
- *
55
- * Determines whether two convex shapes overlap by iteratively building a simplex
56
- * in the Minkowski difference space. If the origin is enclosed by the simplex,
57
- * the shapes intersect.
58
- *
59
- * Adapted from https://github.com/kevinmoran/GJK/blob/master/GJK.h
60
- *
61
- * @param {number[]|Float32Array} simplex Working buffer for simplex vertices (4 vec3s). Must have length >= 12.
62
- * @param {AbstractShape3D} shape_a
63
- * @param {AbstractShape3D} shape_b
64
- * @returns {boolean} true if the shapes intersect
65
- */
66
- export function gjk(simplex, shape_a, shape_b) {
67
-
68
- const dir = scratch_dir;
69
-
70
- // Initial search direction arbitrary, (1,0,0)
71
-
72
- // Get initial support point (simplex point C)
73
- minkowski_support(
74
- simplex, 6,
75
- shape_a, shape_b,
76
- 1, 0, 0
77
- );
78
-
79
- // Search toward the origin from C
80
- dir[0] = -simplex[6];
81
- dir[1] = -simplex[7];
82
- dir[2] = -simplex[8];
83
-
84
- if (dir[0] === 0 && dir[1] === 0 && dir[2] === 0) {
85
- // Origin is exactly on the first support point — intersection
86
- return true;
87
- }
88
-
89
- // Get second support point (simplex point B)
90
- minkowski_support(simplex, 3, shape_a, shape_b, dir[0], dir[1], dir[2]);
91
-
92
- // B didn't pass the origin no intersection possible
93
- if (v3_dot(simplex[3], simplex[4], simplex[5], dir[0], dir[1], dir[2]) < 0) {
94
- return false;
95
- }
96
-
97
- // Line case: compute direction perpendicular to line segment BC toward origin
98
- // CB = C - B
99
- const cb_x = simplex[6] - simplex[3];
100
- const cb_y = simplex[7] - simplex[4];
101
- const cb_z = simplex[8] - simplex[5];
102
-
103
- // BO = -B (origin - B)
104
- const bo_x = -simplex[3];
105
- const bo_y = -simplex[4];
106
- const bo_z = -simplex[5];
107
-
108
- // dir = CB × BO × CB (triple cross product)
109
- triple_cross_product(dir, cb_x, cb_y, cb_z, bo_x, bo_y, bo_z);
110
-
111
- // Handle degenerate case where origin is on the line segment
112
- if (dir[0] === 0 && dir[1] === 0 && dir[2] === 0) {
113
- // Origin is on the line segment — pick any perpendicular direction
114
- // Use the cross product of CB with an arbitrary axis
115
- // CB × (1,0,0)
116
- dir[0] = cb_y;
117
- dir[1] = -cb_x;
118
- dir[2] = 0;
119
-
120
- if (dir[0] === 0 && dir[1] === 0 && dir[2] === 0) {
121
- // CB is parallel to (1,0,0), use (0,1,0)
122
- dir[0] = -cb_z;
123
- dir[1] = 0;
124
- dir[2] = cb_x;
125
- }
126
- }
127
-
128
- let simplex_size = 2; // we have B and C
129
-
130
- for (let iterations = 0; iterations < GJK_MAX_ITERATIONS; iterations++) {
131
- // Get new support point A
132
- minkowski_support(simplex, 0, shape_a, shape_b, dir[0], dir[1], dir[2]);
133
-
134
- const a_x = simplex[0];
135
- const a_y = simplex[1];
136
- const a_z = simplex[2];
137
-
138
- // Check if A passed the origin
139
- if (v3_dot(a_x, a_y, a_z, dir[0], dir[1], dir[2]) < 0) {
140
- return false; // no intersection
141
- }
142
-
143
- simplex_size++;
144
-
145
- if (simplex_size === 3) {
146
- // Triangle case
147
- update_simplex_triangle(dir, simplex);
148
- } else {
149
- // Tetrahedron case
150
- if (update_simplex_tetrahedron(dir, simplex)) {
151
- // Origin is inside the tetrahedron — intersection!
152
- return true;
153
- }
154
-
155
- // The simplex was reduced to a triangle, so keep size at 3
156
- simplex_size = 3;
157
- }
158
- }
159
-
160
- // Did not converge — assume no intersection
161
- return false;
162
- }
163
-
164
- /**
165
- * Compute the triple cross product: (a × b) × a
166
- *
167
- * @param {number[]|Float32Array} result output vec3
168
- * @param {number} ax
169
- * @param {number} ay
170
- * @param {number} az
171
- * @param {number} bx
172
- * @param {number} by
173
- * @param {number} bz
174
- */
175
- function triple_cross_product(result, ax, ay, az, bx, by, bz) {
176
- // First: t = a × b
177
- const tx = ay * bz - az * by;
178
- const ty = az * bx - ax * bz;
179
- const tz = ax * by - ay * bx;
180
-
181
- // Then: t × a
182
- result[0] = ty * az - tz * ay;
183
- result[1] = tz * ax - tx * az;
184
- result[2] = tx * ay - ty * ax;
185
- }
186
-
187
- /**
188
- * Update simplex for the triangle case (3 points: A, B, C).
189
- *
190
- * Determines the Voronoi region of the origin relative to triangle ABC
191
- * and updates the search direction accordingly.
192
- *
193
- * simplex layout: [A(0-2), B(3-5), C(6-8)]
194
- *
195
- * @param {number[]|Float32Array} search_dir output: next search direction
196
- * @param {Float32Array} simplex
197
- */
198
- function update_simplex_triangle(search_dir, simplex) {
199
- const a_x = simplex[0], a_y = simplex[1], a_z = simplex[2];
200
- const b_x = simplex[3], b_y = simplex[4], b_z = simplex[5];
201
- const c_x = simplex[6], c_y = simplex[7], c_z = simplex[8];
202
-
203
- // AB = B - A
204
- const ab_x = b_x - a_x;
205
- const ab_y = b_y - a_y;
206
- const ab_z = b_z - a_z;
207
-
208
- // AC = C - A
209
- const ac_x = c_x - a_x;
210
- const ac_y = c_y - a_y;
211
- const ac_z = c_z - a_z;
212
-
213
- // AO = -A (origin - A)
214
- const ao_x = -a_x;
215
- const ao_y = -a_y;
216
- const ao_z = -a_z;
217
-
218
- // Normal of triangle ABC
219
- const abc_x = ab_y * ac_z - ab_z * ac_y;
220
- const abc_y = ab_z * ac_x - ab_x * ac_z;
221
- const abc_z = ab_x * ac_y - ab_y * ac_x;
222
-
223
- // Test edge AC
224
- // ABC × AC
225
- const abc_cross_ac_x = abc_y * ac_z - abc_z * ac_y;
226
- const abc_cross_ac_y = abc_z * ac_x - abc_x * ac_z;
227
- const abc_cross_ac_z = abc_x * ac_y - abc_y * ac_x;
228
-
229
- if (v3_dot(abc_cross_ac_x, abc_cross_ac_y, abc_cross_ac_z, ao_x, ao_y, ao_z) > 0) {
230
- // Origin is on the AC side
231
- // Reduce simplex to line AC (A stays as A, C moves to B position)
232
- simplex[3] = c_x;
233
- simplex[4] = c_y;
234
- simplex[5] = c_z;
235
-
236
- // New direction: AC × AO × AC
237
- triple_cross_product(search_dir, ac_x, ac_y, ac_z, ao_x, ao_y, ao_z);
238
- return;
239
- }
240
-
241
- // Test edge AB
242
- // AB × ABC
243
- const ab_cross_abc_x = ab_y * abc_z - ab_z * abc_y;
244
- const ab_cross_abc_y = ab_z * abc_x - ab_x * abc_z;
245
- const ab_cross_abc_z = ab_x * abc_y - ab_y * abc_x;
246
-
247
- if (v3_dot(ab_cross_abc_x, ab_cross_abc_y, ab_cross_abc_z, ao_x, ao_y, ao_z) > 0) {
248
- // Origin is on the AB side
249
- // Keep simplex as line AB (C slot is cleared by not using it)
250
- simplex[6] = simplex[3];
251
- simplex[7] = simplex[4];
252
- simplex[8] = simplex[5];
253
-
254
- // New direction: AB × AO × AB
255
- triple_cross_product(search_dir, ab_x, ab_y, ab_z, ao_x, ao_y, ao_z);
256
- return;
257
- }
258
-
259
- // Origin is above or below the triangle
260
- if (v3_dot(abc_x, abc_y, abc_z, ao_x, ao_y, ao_z) > 0) {
261
- // Origin is above search in normal direction
262
- // Simplex stays as A, B, C
263
- search_dir[0] = abc_x;
264
- search_dir[1] = abc_y;
265
- search_dir[2] = abc_z;
266
- } else {
267
- // Origin is below swap B and C, search in -normal
268
- const tmp_x = simplex[3], tmp_y = simplex[4], tmp_z = simplex[5];
269
- simplex[3] = simplex[6];
270
- simplex[4] = simplex[7];
271
- simplex[5] = simplex[8];
272
- simplex[6] = tmp_x;
273
- simplex[7] = tmp_y;
274
- simplex[8] = tmp_z;
275
-
276
- search_dir[0] = -abc_x;
277
- search_dir[1] = -abc_y;
278
- search_dir[2] = -abc_z;
279
- }
280
- }
281
-
282
- /**
283
- * Update simplex for the tetrahedron case (4 points: A, B, C, D).
284
- *
285
- * Tests which face of the tetrahedron is closest to the origin and
286
- * reduces the simplex accordingly. Returns true if the origin is
287
- * inside the tetrahedron.
288
- *
289
- * simplex layout: [A(0-2), B(3-5), C(6-8), D(9-11)]
290
- *
291
- * @param {number[]|Float32Array} search_dir output: next search direction (only written when returning false)
292
- * @param {Float32Array} simplex
293
- * @returns {boolean} true if origin is inside the tetrahedron
294
- */
295
- function update_simplex_tetrahedron(search_dir, simplex) {
296
- const a_x = simplex[0], a_y = simplex[1], a_z = simplex[2];
297
- const b_x = simplex[3], b_y = simplex[4], b_z = simplex[5];
298
- const c_x = simplex[6], c_y = simplex[7], c_z = simplex[8];
299
- const d_x = simplex[9], d_y = simplex[10], d_z = simplex[11];
300
-
301
- // AB, AC, AD vectors
302
- const ab_x = b_x - a_x, ab_y = b_y - a_y, ab_z = b_z - a_z;
303
- const ac_x = c_x - a_x, ac_y = c_y - a_y, ac_z = c_z - a_z;
304
- const ad_x = d_x - a_x, ad_y = d_y - a_y, ad_z = d_z - a_z;
305
-
306
- // AO = origin - A = -A
307
- const ao_x = -a_x, ao_y = -a_y, ao_z = -a_z;
308
-
309
- // Face normals (outward-facing from A's perspective)
310
- // ABC normal
311
- const abc_x = ab_y * ac_z - ab_z * ac_y;
312
- const abc_y = ab_z * ac_x - ab_x * ac_z;
313
- const abc_z = ab_x * ac_y - ab_y * ac_x;
314
-
315
- // ACD normal
316
- const acd_x = ac_y * ad_z - ac_z * ad_y;
317
- const acd_y = ac_z * ad_x - ac_x * ad_z;
318
- const acd_z = ac_x * ad_y - ac_y * ad_x;
319
-
320
- // ADB normal
321
- const adb_x = ad_y * ab_z - ad_z * ab_y;
322
- const adb_y = ad_z * ab_x - ad_x * ab_z;
323
- const adb_z = ad_x * ab_y - ad_y * ab_x;
324
-
325
- // Test each face to see if the origin is on the outside
326
-
327
- // Check face ABC (opposite to D)
328
- const abc_test = v3_dot(abc_x, abc_y, abc_z, ao_x, ao_y, ao_z);
329
- // Make sure normal points away from D
330
- const abc_d_side = v3_dot(abc_x, abc_y, abc_z, ad_x, ad_y, ad_z);
331
-
332
- if (abc_test * abc_d_side < 0) {
333
- // Origin is on the opposite side of ABC from D
334
- // Reduce to triangle ABC: D is dropped
335
- // simplex = [A, B, C] — A(0), B(3), C(6) already in place
336
- update_simplex_triangle(search_dir, simplex);
337
- return false;
338
- }
339
-
340
- // Check face ACD (opposite to B)
341
- const acd_test = v3_dot(acd_x, acd_y, acd_z, ao_x, ao_y, ao_z);
342
- const acd_b_side = v3_dot(acd_x, acd_y, acd_z, ab_x, ab_y, ab_z);
343
-
344
- if (acd_test * acd_b_side < 0) {
345
- // Origin is outside face ACD
346
- // Reduce to triangle ACD: replace B with D
347
- simplex[3] = c_x;
348
- simplex[4] = c_y;
349
- simplex[5] = c_z;
350
- simplex[6] = d_x;
351
- simplex[7] = d_y;
352
- simplex[8] = d_z;
353
-
354
- update_simplex_triangle(search_dir, simplex);
355
- return false;
356
- }
357
-
358
- // Check face ADB (opposite to C)
359
- const adb_test = v3_dot(adb_x, adb_y, adb_z, ao_x, ao_y, ao_z);
360
- const adb_c_side = v3_dot(adb_x, adb_y, adb_z, ac_x, ac_y, ac_z);
361
-
362
- if (adb_test * adb_c_side < 0) {
363
- // Origin is outside face ADB
364
- // Reduce to triangle ADB: replace C with B, B with D
365
- simplex[6] = b_x;
366
- simplex[7] = b_y;
367
- simplex[8] = b_z;
368
- simplex[3] = d_x;
369
- simplex[4] = d_y;
370
- simplex[5] = d_z;
371
-
372
- update_simplex_triangle(search_dir, simplex);
373
- return false;
374
- }
375
-
376
- // Origin is inside the tetrahedron
377
- return true;
378
- }
1
+ import { v3_dot } from "../../../core/geom/vec3/v3_dot.js";
2
+ import { v3_triple_cross_product } from "../../../core/geom/vec3/v3_triple_cross_product.js";
3
+ import { minkowski_support } from "./minkowski_support.js";
4
+
5
+ const GJK_MAX_ITERATIONS = 64;
6
+
7
+ /**
8
+ * Search-direction scratch. The Minkowski-support call uses its own
9
+ * pair of scratch buffers from {@link minkowski_support}; this one is
10
+ * just for the per-iteration search direction the simplex algorithm
11
+ * mutates in place.
12
+ *
13
+ * Float64 — see the precision sweep that converted the rest of the
14
+ * solver-path arrays.
15
+ */
16
+ const scratch_dir = new Float64Array(3);
17
+
18
+ /**
19
+ * GJK intersection test for two convex shapes.
20
+ *
21
+ * Determines whether two convex shapes overlap by iteratively building a simplex
22
+ * in the Minkowski difference space. If the origin is enclosed by the simplex,
23
+ * the shapes intersect.
24
+ *
25
+ * Adapted from https://github.com/kevinmoran/GJK/blob/master/GJK.h
26
+ *
27
+ * @param {number[]|Float64Array} simplex Working buffer for simplex vertices (4 vec3s). Must have length >= 12.
28
+ * @param {AbstractShape3D} shape_a
29
+ * @param {AbstractShape3D} shape_b
30
+ * @returns {boolean} true if the shapes intersect
31
+ */
32
+ export function gjk(simplex, shape_a, shape_b) {
33
+ // Default initial search direction (1, 0, 0). gjk_core uses the
34
+ // caller's `dir` buffer as both the seed and the working
35
+ // direction, but for this wrapper the seed is fixed and the
36
+ // final direction is discarded.
37
+ scratch_dir[0] = 1;
38
+ scratch_dir[1] = 0;
39
+ scratch_dir[2] = 0;
40
+ return gjk_core(simplex, shape_a, shape_b, scratch_dir);
41
+ }
42
+
43
+ /**
44
+ * Separating-axis-cached variant of {@link gjk}. The 3 floats at
45
+ * `axis_buffer[axis_offset..+2]` are used as the initial search
46
+ * direction (seed) AND are overwritten with the final search direction
47
+ * on exit. The narrowphase keeps a per-manifold-slot cache of these
48
+ * three floats so quiescent contacts converge in 1–2 iterations next
49
+ * frame instead of ~6–10 from a cold `(1, 0, 0)` start (Bullet's
50
+ * `m_cachedSeparatingAxis` and Jolt's `ioV` use the same pattern).
51
+ *
52
+ * The same primitive doubles as a separating-axis cache for the
53
+ * "no-overlap" path: when GJK returns false, the final direction is
54
+ * roughly the separating axis, and next frame's first iteration
55
+ * detects separation immediately.
56
+ *
57
+ * Writeback is automatic — `gjk_core` mutates a `subarray` view of
58
+ * `axis_buffer`, so on return `axis_buffer[axis_offset..+2]` holds the
59
+ * final direction. No try/finally needed.
60
+ *
61
+ * @param {number[]|Float64Array} simplex
62
+ * @param {AbstractShape3D} shape_a
63
+ * @param {AbstractShape3D} shape_b
64
+ * @param {Float64Array} axis_buffer
65
+ * @param {number} axis_offset
66
+ * @returns {boolean}
67
+ */
68
+ export function gjk_with_axis(simplex, shape_a, shape_b, axis_buffer, axis_offset) {
69
+ // Subarray view: zero-copy in V8 once the JIT realises both views
70
+ // back the same buffer; the in-place writes inside gjk_core
71
+ // propagate to axis_buffer automatically.
72
+ const dir_view = axis_buffer.subarray(axis_offset, axis_offset + 3);
73
+
74
+ // Guard against a stale or degenerate cached axis. A zero vector
75
+ // (first-frame initial state, or a stuck cache) and NaN / Inf
76
+ // would crash the first support-point query; the (1, 0, 0)
77
+ // fallback is the cold-start default.
78
+ const x = dir_view[0], y = dir_view[1], z = dir_view[2];
79
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)
80
+ || (x === 0 && y === 0 && z === 0)) {
81
+ dir_view[0] = 1;
82
+ dir_view[1] = 0;
83
+ dir_view[2] = 0;
84
+ }
85
+
86
+ return gjk_core(simplex, shape_a, shape_b, dir_view);
87
+ }
88
+
89
+ /**
90
+ * Core GJK routine parameterised on the search-direction buffer.
91
+ * The caller seeds `dir[0..2]` with an initial direction (typically
92
+ * `(1, 0, 0)` for cold starts or a cached separating axis from a
93
+ * previous frame), and on return `dir` holds the FINAL search
94
+ * direction the algorithm settled on. The narrowphase separating-
95
+ * axis cache (P2.1) uses this to thread per-manifold axes through
96
+ * `gjk_with_axis` without a try/finally writeback dance.
97
+ *
98
+ * @param {number[]|Float64Array} simplex
99
+ * @param {AbstractShape3D} shape_a
100
+ * @param {AbstractShape3D} shape_b
101
+ * @param {Float64Array} dir 3 floats; seed direction on entry, final
102
+ * direction on exit. The buffer is mutated throughout the function.
103
+ * @returns {boolean}
104
+ */
105
+ function gjk_core(simplex, shape_a, shape_b, dir) {
106
+
107
+ // Get initial support point (simplex point C). Uses `dir` (the
108
+ // caller's seed) as the search direction.
109
+ minkowski_support(
110
+ simplex, 6,
111
+ shape_a, shape_b,
112
+ dir[0], dir[1], dir[2]
113
+ );
114
+
115
+ // Search toward the origin from C
116
+ dir[0] = -simplex[6];
117
+ dir[1] = -simplex[7];
118
+ dir[2] = -simplex[8];
119
+
120
+ if (dir[0] === 0 && dir[1] === 0 && dir[2] === 0) {
121
+ // Origin is exactly on the first support point — intersection
122
+ return true;
123
+ }
124
+
125
+ // Get second support point (simplex point B)
126
+ minkowski_support(simplex, 3, shape_a, shape_b, dir[0], dir[1], dir[2]);
127
+
128
+ // B didn't pass the origin no intersection possible
129
+ if (v3_dot(simplex[3], simplex[4], simplex[5], dir[0], dir[1], dir[2]) < 0) {
130
+ return false;
131
+ }
132
+
133
+ // Line case: compute direction perpendicular to line segment BC toward origin
134
+ // CB = C - B
135
+ const cb_x = simplex[6] - simplex[3];
136
+ const cb_y = simplex[7] - simplex[4];
137
+ const cb_z = simplex[8] - simplex[5];
138
+
139
+ // BO = -B (origin - B)
140
+ const bo_x = -simplex[3];
141
+ const bo_y = -simplex[4];
142
+ const bo_z = -simplex[5];
143
+
144
+ // dir = CB × BO × CB (triple cross product)
145
+ v3_triple_cross_product(dir, 0, cb_x, cb_y, cb_z, bo_x, bo_y, bo_z);
146
+
147
+ // Handle degenerate case where origin is on the line segment
148
+ if (dir[0] === 0 && dir[1] === 0 && dir[2] === 0) {
149
+ // Origin is on the line segment — pick any perpendicular direction
150
+ // Use the cross product of CB with an arbitrary axis
151
+ // CB × (1,0,0)
152
+ dir[0] = cb_y;
153
+ dir[1] = -cb_x;
154
+ dir[2] = 0;
155
+
156
+ if (dir[0] === 0 && dir[1] === 0 && dir[2] === 0) {
157
+ // CB is parallel to (1,0,0), use (0,1,0)
158
+ dir[0] = -cb_z;
159
+ dir[1] = 0;
160
+ dir[2] = cb_x;
161
+ }
162
+ }
163
+
164
+ let simplex_size = 2; // we have B and C
165
+
166
+ for (let iterations = 0; iterations < GJK_MAX_ITERATIONS; iterations++) {
167
+ // Get new support point A
168
+ minkowski_support(simplex, 0, shape_a, shape_b, dir[0], dir[1], dir[2]);
169
+
170
+ const a_x = simplex[0];
171
+ const a_y = simplex[1];
172
+ const a_z = simplex[2];
173
+
174
+ // Check if A passed the origin
175
+ if (v3_dot(a_x, a_y, a_z, dir[0], dir[1], dir[2]) < 0) {
176
+ return false; // no intersection
177
+ }
178
+
179
+ simplex_size++;
180
+
181
+ if (simplex_size === 3) {
182
+ // Triangle case may reduce to line (size 2) or keep as
183
+ // triangle (size 3) before shifting slots for the next iter.
184
+ simplex_size = update_simplex_triangle(dir, simplex);
185
+ } else {
186
+ // Tetrahedron case — 0 if origin enclosed, else new size
187
+ // after recursive face reduction.
188
+ const result = update_simplex_tetrahedron(dir, simplex);
189
+ if (result === 0) {
190
+ return true;
191
+ }
192
+ simplex_size = result;
193
+ }
194
+ }
195
+
196
+ // Did not converge — assume no intersection
197
+ return false;
198
+ }
199
+
200
+ /**
201
+ * Update simplex for the triangle case (3 points: A, B, C).
202
+ *
203
+ * Determines the Voronoi region of the origin relative to triangle ABC
204
+ * and updates the search direction accordingly. When the origin is
205
+ * closest to a triangle edge the simplex is reduced to a line (return
206
+ * 2). When the origin is above or below the triangle, the existing
207
+ * vertices are shifted (A → B, B → C, C → D for "above";
208
+ * B D, A → B for "below") so the next iteration's new support
209
+ * point can be written into slot 0 without losing simplex history,
210
+ * and the function returns 3 — the main loop's `simplex_size++` then
211
+ * promotes it to 4 (tetrahedron case) on the next pass.
212
+ *
213
+ * simplex layout: [A(0-2), B(3-5), C(6-8), D(9-11)]
214
+ *
215
+ * @param {number[]|Float64Array} search_dir output: next search direction
216
+ * @param {Float64Array} simplex
217
+ * @returns {number} new simplex size — 2 if reduced to a line,
218
+ * 3 if a triangle was retained (with vertices shifted toward higher
219
+ * slots so the next iteration grows into a tetrahedron)
220
+ */
221
+ function update_simplex_triangle(search_dir, simplex) {
222
+ const a_x = simplex[0], a_y = simplex[1], a_z = simplex[2];
223
+ const b_x = simplex[3], b_y = simplex[4], b_z = simplex[5];
224
+ const c_x = simplex[6], c_y = simplex[7], c_z = simplex[8];
225
+
226
+ // AB = B - A
227
+ const ab_x = b_x - a_x;
228
+ const ab_y = b_y - a_y;
229
+ const ab_z = b_z - a_z;
230
+
231
+ // AC = C - A
232
+ const ac_x = c_x - a_x;
233
+ const ac_y = c_y - a_y;
234
+ const ac_z = c_z - a_z;
235
+
236
+ // AO = -A (origin - A)
237
+ const ao_x = -a_x;
238
+ const ao_y = -a_y;
239
+ const ao_z = -a_z;
240
+
241
+ // Normal of triangle ABC
242
+ const abc_x = ab_y * ac_z - ab_z * ac_y;
243
+ const abc_y = ab_z * ac_x - ab_x * ac_z;
244
+ const abc_z = ab_x * ac_y - ab_y * ac_x;
245
+
246
+ // Test edge AC
247
+ // ABC × AC
248
+ const abc_cross_ac_x = abc_y * ac_z - abc_z * ac_y;
249
+ const abc_cross_ac_y = abc_z * ac_x - abc_x * ac_z;
250
+ const abc_cross_ac_z = abc_x * ac_y - abc_y * ac_x;
251
+
252
+ if (v3_dot(abc_cross_ac_x, abc_cross_ac_y, abc_cross_ac_z, ao_x, ao_y, ao_z) > 0) {
253
+ // Origin is on the AC side — reduce to line {A, C}, drop B.
254
+ //
255
+ // Slot management note: every main-loop iteration writes the
256
+ // newest support point into simplex[0..2] (the A slot),
257
+ // OVERWRITING the previous A. For "old A" to survive into the
258
+ // next iteration's triangle, it must be moved into another
259
+ // slot here. Kevin Moran's reference does `b = a;` — A's value
260
+ // is copied into the B slot. C stays where it is. Next iter's
261
+ // new A lands at slot 0; the simplex is then {new A, old A
262
+ // (in B slot), C} — a valid 3-vertex triangle.
263
+ //
264
+ // The previous implementation here wrote `simplex[3..5] = C`,
265
+ // which both duplicated C and discarded old A. Combined with
266
+ // the missing simplex-size accounting that kept the main loop
267
+ // calling `update_simplex_tetrahedron` with a stale D slot,
268
+ // this produced false-positive overlap for sphere-vs-sphere
269
+ // configurations placed off-axis. See `gjk.spec.js`'s
270
+ // documented regression block.
271
+ simplex[3] = a_x;
272
+ simplex[4] = a_y;
273
+ simplex[5] = a_z;
274
+
275
+ // New direction: AC × AO × AC
276
+ v3_triple_cross_product(search_dir, 0, ac_x, ac_y, ac_z, ao_x, ao_y, ao_z);
277
+ return 2;
278
+ }
279
+
280
+ // Test edge AB
281
+ // AB × ABC
282
+ const ab_cross_abc_x = ab_y * abc_z - ab_z * abc_y;
283
+ const ab_cross_abc_y = ab_z * abc_x - ab_x * abc_z;
284
+ const ab_cross_abc_z = ab_x * abc_y - ab_y * abc_x;
285
+
286
+ if (v3_dot(ab_cross_abc_x, ab_cross_abc_y, ab_cross_abc_z, ao_x, ao_y, ao_z) > 0) {
287
+ // Origin is on the AB side — reduce to line {A, B}, drop C.
288
+ // Kevin Moran: `c = a;` — A's value into C slot. B stays.
289
+ simplex[6] = a_x;
290
+ simplex[7] = a_y;
291
+ simplex[8] = a_z;
292
+
293
+ // New direction: AB × AO × AB
294
+ v3_triple_cross_product(search_dir, 0, ab_x, ab_y, ab_z, ao_x, ao_y, ao_z);
295
+ return 2;
296
+ }
297
+
298
+ // Origin is above or below the triangle. We're growing to a
299
+ // tetrahedron next iteration, so shift the existing vertices out
300
+ // of slot 0 to make room for the next minkowski_support.
301
+ if (v3_dot(abc_x, abc_y, abc_z, ao_x, ao_y, ao_z) > 0) {
302
+ // Origin above `d = c; c = b; b = a;` (Kevin Moran). The
303
+ // next iteration writes new A at slot 0; the tetrahedron then
304
+ // has all four vertices populated with valid simplex history.
305
+ simplex[9] = simplex[6]; simplex[10] = simplex[7]; simplex[11] = simplex[8];
306
+ simplex[6] = simplex[3]; simplex[7] = simplex[4]; simplex[8] = simplex[5];
307
+ simplex[3] = simplex[0]; simplex[4] = simplex[1]; simplex[5] = simplex[2];
308
+ search_dir[0] = abc_x;
309
+ search_dir[1] = abc_y;
310
+ search_dir[2] = abc_z;
311
+ } else {
312
+ // Origin below — `d = b; b = a;` (C stays). Inverted-winding
313
+ // tetrahedron; the face tests in `update_simplex_tetrahedron`
314
+ // multiply face_test by face_d_side and compare against 0, so
315
+ // they're winding-agnostic.
316
+ simplex[9] = simplex[3]; simplex[10] = simplex[4]; simplex[11] = simplex[5];
317
+ simplex[3] = simplex[0]; simplex[4] = simplex[1]; simplex[5] = simplex[2];
318
+ search_dir[0] = -abc_x;
319
+ search_dir[1] = -abc_y;
320
+ search_dir[2] = -abc_z;
321
+ }
322
+ return 3;
323
+ }
324
+
325
+ /**
326
+ * Update simplex for the tetrahedron case (4 points: A, B, C, D).
327
+ *
328
+ * Tests which face of the tetrahedron is closest to the origin and
329
+ * reduces the simplex accordingly. Returns 0 if the origin is inside
330
+ * the tetrahedron, otherwise returns the new simplex size (2 = line,
331
+ * 3 = triangle) so the main loop can dispatch the next iteration
332
+ * correctly. The size after reduction is determined by the recursive
333
+ * `update_simplex_triangle` call on the surviving face.
334
+ *
335
+ * simplex layout: [A(0-2), B(3-5), C(6-8), D(9-11)]
336
+ *
337
+ * @param {number[]|Float64Array} search_dir output: next search direction (only written on non-enclosure)
338
+ * @param {Float64Array} simplex
339
+ * @returns {number} 0 if origin is inside, else new simplex size (2 or 3)
340
+ */
341
+ function update_simplex_tetrahedron(search_dir, simplex) {
342
+ const a_x = simplex[0], a_y = simplex[1], a_z = simplex[2];
343
+ const b_x = simplex[3], b_y = simplex[4], b_z = simplex[5];
344
+ const c_x = simplex[6], c_y = simplex[7], c_z = simplex[8];
345
+ const d_x = simplex[9], d_y = simplex[10], d_z = simplex[11];
346
+
347
+ // AB, AC, AD vectors
348
+ const ab_x = b_x - a_x, ab_y = b_y - a_y, ab_z = b_z - a_z;
349
+ const ac_x = c_x - a_x, ac_y = c_y - a_y, ac_z = c_z - a_z;
350
+ const ad_x = d_x - a_x, ad_y = d_y - a_y, ad_z = d_z - a_z;
351
+
352
+ // AO = origin - A = -A
353
+ const ao_x = -a_x, ao_y = -a_y, ao_z = -a_z;
354
+
355
+ // Face normals (outward-facing from A's perspective)
356
+ // ABC normal
357
+ const abc_x = ab_y * ac_z - ab_z * ac_y;
358
+ const abc_y = ab_z * ac_x - ab_x * ac_z;
359
+ const abc_z = ab_x * ac_y - ab_y * ac_x;
360
+
361
+ // ACD normal
362
+ const acd_x = ac_y * ad_z - ac_z * ad_y;
363
+ const acd_y = ac_z * ad_x - ac_x * ad_z;
364
+ const acd_z = ac_x * ad_y - ac_y * ad_x;
365
+
366
+ // ADB normal
367
+ const adb_x = ad_y * ab_z - ad_z * ab_y;
368
+ const adb_y = ad_z * ab_x - ad_x * ab_z;
369
+ const adb_z = ad_x * ab_y - ad_y * ab_x;
370
+
371
+ // Test each face to see if the origin is on the outside
372
+
373
+ // Check face ABC (opposite to D)
374
+ const abc_test = v3_dot(abc_x, abc_y, abc_z, ao_x, ao_y, ao_z);
375
+ // Make sure normal points away from D
376
+ const abc_d_side = v3_dot(abc_x, abc_y, abc_z, ad_x, ad_y, ad_z);
377
+
378
+ if (abc_test * abc_d_side < 0) {
379
+ // Origin is on the opposite side of ABC from D
380
+ // Reduce to triangle ABC: D is dropped
381
+ // simplex = [A, B, C] — A(0), B(3), C(6) already in place
382
+ return update_simplex_triangle(search_dir, simplex);
383
+ }
384
+
385
+ // Check face ACD (opposite to B)
386
+ const acd_test = v3_dot(acd_x, acd_y, acd_z, ao_x, ao_y, ao_z);
387
+ const acd_b_side = v3_dot(acd_x, acd_y, acd_z, ab_x, ab_y, ab_z);
388
+
389
+ if (acd_test * acd_b_side < 0) {
390
+ // Origin is outside face ACD
391
+ // Reduce to triangle ACD: replace B with D
392
+ simplex[3] = c_x;
393
+ simplex[4] = c_y;
394
+ simplex[5] = c_z;
395
+ simplex[6] = d_x;
396
+ simplex[7] = d_y;
397
+ simplex[8] = d_z;
398
+
399
+ return update_simplex_triangle(search_dir, simplex);
400
+ }
401
+
402
+ // Check face ADB (opposite to C)
403
+ const adb_test = v3_dot(adb_x, adb_y, adb_z, ao_x, ao_y, ao_z);
404
+ const adb_c_side = v3_dot(adb_x, adb_y, adb_z, ac_x, ac_y, ac_z);
405
+
406
+ if (adb_test * adb_c_side < 0) {
407
+ // Origin is outside face ADB
408
+ // Reduce to triangle ADB: replace C with B, B with D
409
+ simplex[6] = b_x;
410
+ simplex[7] = b_y;
411
+ simplex[8] = b_z;
412
+ simplex[3] = d_x;
413
+ simplex[4] = d_y;
414
+ simplex[5] = d_z;
415
+
416
+ return update_simplex_triangle(search_dir, simplex);
417
+ }
418
+
419
+ // Origin is inside the tetrahedron
420
+ return 0;
421
+ }