@woosh/meep-engine 2.152.0 → 2.154.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 (99) hide show
  1. package/package.json +1 -1
  2. package/src/core/color/Color.d.ts +26 -6
  3. package/src/core/color/Color.d.ts.map +1 -1
  4. package/src/core/color/Color.js +38 -6
  5. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +112 -0
  6. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -0
  7. package/src/core/geom/3d/shape/ConvexHullShape3D.js +325 -0
  8. package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts +4 -0
  9. package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
  10. package/src/engine/graphics/ecs/trail2d/Trail2D.js +21 -0
  11. package/src/engine/physics/PLAN.md +4 -4
  12. package/src/engine/physics/body/BodyStorage.d.ts +3 -1
  13. package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
  14. package/src/engine/physics/body/BodyStorage.js +452 -450
  15. package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -1
  16. package/src/engine/physics/body/SolverBodyState.js +6 -5
  17. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  18. package/src/engine/physics/broadphase/generate_pairs.js +9 -1
  19. package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -1
  20. package/src/engine/physics/ccd/linear_sweep.js +237 -238
  21. package/src/engine/physics/computeInterceptPoint.d.ts.map +1 -1
  22. package/src/engine/physics/computeInterceptPoint.js +8 -3
  23. package/src/engine/physics/contact/ManifoldStore.d.ts +0 -16
  24. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  25. package/src/engine/physics/contact/ManifoldStore.js +1 -38
  26. package/src/engine/physics/ecs/BodyKind.d.ts +3 -2
  27. package/src/engine/physics/ecs/BodyKind.d.ts.map +1 -1
  28. package/src/engine/physics/ecs/BodyKind.js +25 -24
  29. package/src/engine/physics/ecs/PhysicsEvents.d.ts +4 -5
  30. package/src/engine/physics/ecs/PhysicsEvents.d.ts.map +1 -1
  31. package/src/engine/physics/ecs/PhysicsEvents.js +15 -16
  32. package/src/engine/physics/ecs/PhysicsSystem.d.ts +5 -30
  33. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  34. package/src/engine/physics/ecs/PhysicsSystem.js +13 -45
  35. package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts.map +1 -1
  36. package/src/engine/physics/ecs/RigidBodySerializationAdapter.js +85 -81
  37. package/src/engine/physics/ecs/is_sensor.d.ts +18 -0
  38. package/src/engine/physics/ecs/is_sensor.d.ts.map +1 -0
  39. package/src/engine/physics/ecs/is_sensor.js +27 -0
  40. package/src/engine/physics/events/ContactEventBuffer.d.ts +2 -1
  41. package/src/engine/physics/events/ContactEventBuffer.d.ts.map +1 -1
  42. package/src/engine/physics/events/ContactEventBuffer.js +84 -83
  43. package/src/engine/physics/gjk/gjk.d.ts +0 -26
  44. package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
  45. package/src/engine/physics/gjk/gjk.js +3 -52
  46. package/src/engine/physics/gjk/gjk_epa_penetration.d.ts +16 -0
  47. package/src/engine/physics/gjk/gjk_epa_penetration.d.ts.map +1 -0
  48. package/src/engine/physics/gjk/gjk_epa_penetration.js +255 -0
  49. package/src/engine/physics/gjk/minkowski_support.d.ts +4 -9
  50. package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -1
  51. package/src/engine/physics/gjk/minkowski_support.js +70 -75
  52. package/src/engine/physics/gjk/mpr.d.ts +1 -1
  53. package/src/engine/physics/gjk/mpr.d.ts.map +1 -1
  54. package/src/engine/physics/gjk/mpr.js +362 -344
  55. package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
  56. package/src/engine/physics/island/IslandBuilder.js +431 -428
  57. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  58. package/src/engine/physics/narrowphase/box_box_manifold.js +4 -81
  59. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
  60. package/src/engine/physics/narrowphase/box_triangle_contact.js +4 -39
  61. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  62. package/src/engine/physics/narrowphase/capsule_contacts.js +459 -462
  63. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +1 -1
  64. package/src/engine/physics/narrowphase/clip_against_axis_uv.js +4 -1
  65. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts +83 -0
  66. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -0
  67. package/src/engine/physics/narrowphase/convex_convex_manifold.js +425 -0
  68. package/src/engine/physics/narrowphase/convex_decomposition.d.ts +32 -0
  69. package/src/engine/physics/narrowphase/convex_decomposition.d.ts.map +1 -0
  70. package/src/engine/physics/narrowphase/convex_decomposition.js +293 -0
  71. package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts +41 -0
  72. package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts.map +1 -0
  73. package/src/engine/physics/narrowphase/mesh_convex_hull.js +106 -0
  74. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.d.ts +8 -0
  75. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.d.ts.map +1 -0
  76. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -0
  77. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  78. package/src/engine/physics/narrowphase/narrowphase_step.js +105 -102
  79. package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts +29 -0
  80. package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts.map +1 -0
  81. package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +69 -0
  82. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
  83. package/src/engine/physics/narrowphase/refine_ray_concave.js +152 -145
  84. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
  85. package/src/engine/physics/narrowphase/sphere_box_contact.js +132 -123
  86. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -1
  87. package/src/engine/physics/queries/overlap_shape.js +16 -17
  88. package/src/engine/physics/queries/raycast.d.ts +5 -0
  89. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  90. package/src/engine/physics/queries/raycast.js +16 -8
  91. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -1
  92. package/src/engine/physics/queries/shape_cast.js +13 -7
  93. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  94. package/src/engine/physics/solver/solve_contacts.js +8 -11
  95. package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -1
  96. package/src/engine/physics/vehicle/RaycastVehicle.js +339 -333
  97. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +0 -13
  98. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +0 -1
  99. package/src/engine/physics/gjk/expanding_polytope_algorithm.js +0 -399
@@ -3,10 +3,12 @@ import { Triangle3D } from "../../../core/geom/3d/shape/Triangle3D.js";
3
3
  import { body_id_index } from "../body/BodyStorage.js";
4
4
  import { combine_friction, combine_restitution } from "../contact/combine_material.js";
5
5
  import { CONTACT_STRIDE, MAX_CONTACTS_PER_MANIFOLD } from "../contact/ManifoldStore.js";
6
- import { expanding_polytope_algorithm } from "../gjk/expanding_polytope_algorithm.js";
7
- import { gjk_with_axis } from "../gjk/gjk.js";
6
+ import { gjk_epa_penetration } from "../gjk/gjk_epa_penetration.js";
8
7
  import { mpr } from "../gjk/mpr.js";
9
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";
10
12
  import { box_triangle_contact, BOX_TRIANGLE_OUT_LENGTH } from "./box_triangle_contact.js";
11
13
  import {
12
14
  CAPSULE_BOX_CONTACT_STRIDE,
@@ -31,17 +33,9 @@ import { sphere_triangle_contact } from "./sphere_triangle_contact.js";
31
33
  const posed_a = new PosedShape();
32
34
  const posed_b = new PosedShape();
33
35
 
34
- // Float64 simplex buffer. The simplex stores Minkowski-difference
35
- // support points that GJK accumulates across up to 64 iterations and
36
- // then hands to EPA for further refinement; precision loss here
37
- // compounds through every subsequent dot / cross / normal calculation
38
- // in the polytope expansion, so keep the full 53-bit mantissa.
39
- const simplex_buf = new Float64Array(12);
40
- const simplex_a = simplex_buf.subarray(0, 3);
41
- const simplex_b = simplex_buf.subarray(3, 6);
42
- const simplex_c = simplex_buf.subarray(6, 9);
43
- const simplex_d = simplex_buf.subarray(9, 12);
44
-
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).
45
39
  const epa_result = new Float64Array(3);
46
40
  const sphere_result = new Float64Array(4);
47
41
  const closed_form_result = new Float64Array(10);
@@ -49,6 +43,7 @@ const sphere_triangle_result = new Float64Array(10);
49
43
  const box_triangle_result = new Float64Array(BOX_TRIANGLE_OUT_LENGTH);
50
44
  const capsule_triangle_result = new Float64Array(CAPSULE_TRIANGLE_MAX_CONTACTS * CAPSULE_TRIANGLE_CONTACT_STRIDE);
51
45
  const box_manifold_result = new Float64Array(BOX_BOX_OUT_LENGTH);
46
+ const convex_manifold_result = new Float64Array(CONVEX_CONVEX_OUT_LENGTH);
52
47
  const capsule_box_multi_result = new Float64Array(CAPSULE_BOX_MAX_CONTACTS * CAPSULE_BOX_CONTACT_STRIDE);
53
48
 
54
49
  /**
@@ -369,31 +364,65 @@ function reduce_candidates(n) {
369
364
  return MAX_KEPT;
370
365
  }
371
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 prohibitive — then
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
+
372
413
  /**
373
414
  * Run pairwise narrowphase for one (colliderA, colliderB) tuple — dispatches
374
415
  * by shape type and appends 0..K contacts to the candidate buffer. Returns
375
416
  * the new candidate count.
376
417
  *
377
- * The optional `gjk_axis_buf` + `gjk_axis_off` arguments thread the
378
- * manifold's cached separating axis through to the GJK + EPA fallback
379
- * paths. Closed-form paths (sphere/box/capsule × sphere/box/capsule
380
- * and the three concave fast-paths) don't use GJK and ignore them. As
381
- * of P2.1 step 4 these are plumbed but not yet activated — the GJK
382
- * call sites still use the cache-less `gjk(simplex, A, B)`; step 5
383
- * flips one of them.
384
- *
385
418
  * @param {number} count
386
419
  * @param {Collider} colA
387
420
  * @param {Transform} trA
388
421
  * @param {Collider} colB
389
422
  * @param {Transform} trB
390
- * @param {Float64Array|null} [gjk_axis_buf] per-slot cached-axis buffer
391
- * (`manifolds.slot_axis_buffer`) or `null` for cold-start every call.
392
- * @param {number} [gjk_axis_off] offset into the buffer; pass
393
- * `manifolds.slot_axis_offset(slot)`.
394
423
  * @returns {number}
395
424
  */
396
- function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axis_off = 0) {
425
+ function dispatch_pair(count, colA, trA, colB, trB) {
397
426
  const shapeA = colA.shape;
398
427
  const shapeB = colB.shape;
399
428
 
@@ -616,6 +645,25 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
616
645
  return count;
617
646
  }
618
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
+ // mesh ↔ mesh. 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
+
619
667
  // ── Concave (non-convex) path ───────────────────────────────────────
620
668
  //
621
669
  // If either shape has `is_convex === false`, GJK on the whole shape
@@ -1110,30 +1158,20 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
1110
1158
  continue;
1111
1159
  }
1112
1160
 
1113
- // P2.1 step 6: pass the manifold's cached separating axis
1114
- // here too. Per-triangle iteration means the cache gets
1115
- // written multiple times per body-pair per frame (one per
1116
- // overlapping triangle); the final state reflects the
1117
- // last triangle's converged direction. Still a useful
1118
- // seed for next frame's first triangle.
1119
- if (!gjk_with_axis(simplex_buf, posed_a, posed_b, gjk_axis_buf, gjk_axis_off)) continue;
1120
- expanding_polytope_algorithm(
1121
- epa_result, 0,
1122
- simplex_a, simplex_b, simplex_c, simplex_d,
1123
- posed_a, posed_b
1124
- );
1125
-
1126
- let ex = epa_result[0], ey = epa_result[1], ez = epa_result[2];
1127
- let depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1128
- // P3.1: MPR fallback. EPA can return zero/negative/NaN
1129
- // depth on Triangle3D's degenerate face-normal support
1130
- // (the same precision pit the closed-form sphere/box/
1131
- // capsule paths above were added to dodge). MPR has
1132
- // different convergence properties and often succeeds
1133
- // here. Only kicks in for non-(sphere/box/capsule) convex
1134
- // shapes against the concave side — a narrow path now
1135
- // that closed-form covers the common cases.
1136
- if (!(depth > 0) || !Number.isFinite(depth)) {
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 {
1137
1175
  if (!mpr(epa_result, 0, posed_a, posed_b)) continue;
1138
1176
  ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1139
1177
  depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
@@ -1234,33 +1272,23 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
1234
1272
  return count;
1235
1273
  }
1236
1274
 
1237
- // GJK + EPA fallback for everything else. P2.1 step 5: pass the
1238
- // manifold's cached separating axis as GJK's initial search
1239
- // direction. Cold start (zero-axis) on first contact; subsequent
1240
- // frames reuse the converged direction, typically converging in
1241
- // 1–2 iterations instead of the ~6–10 cold-start cost.
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.
1242
1283
  posed_a.setup(colA.shape, trA.position, trA.rotation);
1243
1284
  posed_b.setup(colB.shape, trB.position, trB.rotation);
1244
1285
 
1245
- if (!gjk_with_axis(simplex_buf, posed_a, posed_b, gjk_axis_buf, gjk_axis_off)){
1246
- return count;
1247
- }
1248
-
1249
- expanding_polytope_algorithm(
1250
- epa_result, 0,
1251
- simplex_a, simplex_b, simplex_c, simplex_d,
1252
- posed_a, posed_b
1253
- );
1254
-
1255
- let ex = epa_result[0], ey = epa_result[1], ez = epa_result[2];
1256
- let depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1257
- // EPA can return zero-depth (touching), negative (impossible) or
1258
- // NaN/Inf (degenerate iteration-cap exit on smooth high-poly
1259
- // colliders). P3.1: fall back to MPR — different convergence
1260
- // properties (works portal-based rather than face-expansion), often
1261
- // succeeds where EPA fails on the same geometry. `shape_cast` and
1262
- // `compute_penetration` already use MPR for the same reason.
1263
- if (!(depth > 0) || !Number.isFinite(depth)) {
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 {
1264
1292
  if (!mpr(epa_result, 0, posed_a, posed_b)) return count;
1265
1293
  ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1266
1294
  depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
@@ -1331,14 +1359,6 @@ const _pp_colB = { shape: null };
1331
1359
  const _pp_trA = { position: null, rotation: null };
1332
1360
  const _pp_trB = { position: null, rotation: null };
1333
1361
 
1334
- /**
1335
- * Cold-start GJK seed for the one-shot penetration query. Re-zeroed before each
1336
- * call so every query is independent (gjk_with_axis treats a zero vector as a
1337
- * cold start) — no warm-start leakage between unrelated queries, which keeps
1338
- * the result a pure function of its inputs.
1339
- * @type {Float64Array}
1340
- */
1341
- const _pp_axis = new Float64Array(3);
1342
1362
 
1343
1363
  /**
1344
1364
  * Single-pair penetration query: the depth and world normal of the DEEPEST
@@ -1381,9 +1401,7 @@ export function deepest_pair_penetration(out_normal, shapeA, posA, rotA, shapeB,
1381
1401
  _pp_trB.position = posB;
1382
1402
  _pp_trB.rotation = rotB;
1383
1403
 
1384
- // Cold GJK seed one-shot query, not a warm-started per-frame manifold.
1385
- _pp_axis[0] = 0; _pp_axis[1] = 0; _pp_axis[2] = 0;
1386
- const n = dispatch_pair(0, _pp_colA, _pp_trA, _pp_colB, _pp_trB, _pp_axis, 0);
1404
+ const n = dispatch_pair(0, _pp_colA, _pp_trA, _pp_colB, _pp_trB);
1387
1405
  if (n === 0) {
1388
1406
  return 0;
1389
1407
  }
@@ -1455,15 +1473,6 @@ export function narrowphase_step(pair_list, manifolds, lists) {
1455
1473
  const la_len = list_a.length;
1456
1474
  const lb_len = list_b.length;
1457
1475
 
1458
- // Per-manifold cached GJK separating-axis seed. Threaded into
1459
- // dispatch_pair for the GJK fallback paths. For single-collider
1460
- // bodies (the common case) there's no contention across the
1461
- // inner loop; for compound bodies the cache reflects whichever
1462
- // collider-pair invoked GJK last, which is a noisy-but-useful
1463
- // seed (better than a cold (1, 0, 0) restart every frame).
1464
- const gjk_axis_buf = manifolds.slot_axis_buffer;
1465
- const gjk_axis_off = manifolds.slot_axis_offset(slot);
1466
-
1467
1476
  for (let a = 0; a < la_len; a++) {
1468
1477
  const ea = list_a[a];
1469
1478
 
@@ -1475,9 +1484,7 @@ export function narrowphase_step(pair_list, manifolds, lists) {
1475
1484
  ea.collider,
1476
1485
  ea.transform,
1477
1486
  eb.collider,
1478
- eb.transform,
1479
- gjk_axis_buf,
1480
- gjk_axis_off
1487
+ eb.transform
1481
1488
  );
1482
1489
 
1483
1490
  }
@@ -1630,16 +1637,12 @@ export function redetect_pair_geometry(manifolds, slot, list_a, list_b) {
1630
1637
  const count = manifolds.contact_count(slot);
1631
1638
  if (count === 0) return;
1632
1639
 
1633
- const gjk_axis_buf = manifolds.slot_axis_buffer;
1634
- const gjk_axis_off = manifolds.slot_axis_offset(slot);
1635
-
1636
1640
  let cc = 0;
1637
1641
  for (let a = 0; a < la_len; a++) {
1638
1642
  const ea = list_a[a];
1639
1643
  for (let b = 0; b < lb_len; b++) {
1640
1644
  const eb = list_b[b];
1641
- cc = dispatch_pair(cc, ea.collider, ea.transform, eb.collider, eb.transform,
1642
- gjk_axis_buf, gjk_axis_off);
1645
+ cc = dispatch_pair(cc, ea.collider, ea.transform, eb.collider, eb.transform);
1643
1646
  }
1644
1647
  }
1645
1648
  if (cc === 0) return; // nothing re-detected this substep — keep frozen geometry
@@ -0,0 +1,29 @@
1
+ /**
2
+ * In-place reduction of a strided contact-candidate buffer to at most `maxKept`
3
+ * points:
4
+ * 1. keep the deepest candidate (penetration depth at lane `stride - 1`) in
5
+ * slot 0;
6
+ * 2. then greedily pick the candidate whose MINIMUM squared distance to the
7
+ * already-kept set is largest (farthest-point sampling) — a behaviour-
8
+ * preserving max-spread approximation that avoids degenerate near-coincident
9
+ * manifolds.
10
+ *
11
+ * The contact position is lanes 0,1,2 of each `stride`-float record; the buffer
12
+ * is reordered in place and the caller reads the first `maxKept` records.
13
+ *
14
+ * Shared by {@link box_box_manifold} and {@link convex_convex_manifold} (both
15
+ * stride 4), which previously each carried a byte-identical copy. The stride-7
16
+ * (box_triangle) and stride-10 (narrowphase_step) variants differ only in stride
17
+ * and can adopt this too.
18
+ *
19
+ * @param {Float64Array} buf strided candidate buffer
20
+ * @param {number} n input candidate count
21
+ * @param {number} stride floats per candidate (depth at lane stride-1)
22
+ * @param {number} maxKept maximum candidates to keep
23
+ * @returns {number} kept count = min(n, maxKept)
24
+ *
25
+ * @author Alex Goldring
26
+ * @copyright Company Named Limited (c) 2026
27
+ */
28
+ export function reduce_manifold_contacts(buf: Float64Array, n: number, stride: number, maxKept: number): number;
29
+ //# sourceMappingURL=reduce_manifold_contacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reduce_manifold_contacts.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/reduce_manifold_contacts.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,8CATW,YAAY,KACZ,MAAM,UACN,MAAM,WACN,MAAM,GACJ,MAAM,CAqClB"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * In-place reduction of a strided contact-candidate buffer to at most `maxKept`
3
+ * points:
4
+ * 1. keep the deepest candidate (penetration depth at lane `stride - 1`) in
5
+ * slot 0;
6
+ * 2. then greedily pick the candidate whose MINIMUM squared distance to the
7
+ * already-kept set is largest (farthest-point sampling) — a behaviour-
8
+ * preserving max-spread approximation that avoids degenerate near-coincident
9
+ * manifolds.
10
+ *
11
+ * The contact position is lanes 0,1,2 of each `stride`-float record; the buffer
12
+ * is reordered in place and the caller reads the first `maxKept` records.
13
+ *
14
+ * Shared by {@link box_box_manifold} and {@link convex_convex_manifold} (both
15
+ * stride 4), which previously each carried a byte-identical copy. The stride-7
16
+ * (box_triangle) and stride-10 (narrowphase_step) variants differ only in stride
17
+ * and can adopt this too.
18
+ *
19
+ * @param {Float64Array} buf strided candidate buffer
20
+ * @param {number} n input candidate count
21
+ * @param {number} stride floats per candidate (depth at lane stride-1)
22
+ * @param {number} maxKept maximum candidates to keep
23
+ * @returns {number} kept count = min(n, maxKept)
24
+ *
25
+ * @author Alex Goldring
26
+ * @copyright Company Named Limited (c) 2026
27
+ */
28
+ export function reduce_manifold_contacts(buf, n, stride, maxKept) {
29
+ if (n <= maxKept) return n;
30
+
31
+ const depth_lane = stride - 1;
32
+
33
+ // Deepest into slot 0.
34
+ let deepest_idx = 0;
35
+ let deepest_val = buf[depth_lane];
36
+ for (let i = 1; i < n; i++) {
37
+ const v = buf[i * stride + depth_lane];
38
+ if (v > deepest_val) { deepest_val = v; deepest_idx = i; }
39
+ }
40
+ if (deepest_idx !== 0) swap(buf, stride, 0, deepest_idx);
41
+
42
+ // Greedy farthest-point for the remaining slots.
43
+ for (let k = 1; k < maxKept; k++) {
44
+ let best_score = -1;
45
+ let best_i = -1;
46
+ for (let i = k; i < n; i++) {
47
+ let min_d2 = Infinity;
48
+ for (let j = 0; j < k; j++) {
49
+ const dx = buf[i * stride] - buf[j * stride];
50
+ const dy = buf[i * stride + 1] - buf[j * stride + 1];
51
+ const dz = buf[i * stride + 2] - buf[j * stride + 2];
52
+ const d2 = dx * dx + dy * dy + dz * dz;
53
+ if (d2 < min_d2) min_d2 = d2;
54
+ }
55
+ if (min_d2 > best_score) { best_score = min_d2; best_i = i; }
56
+ }
57
+ if (best_i !== k) swap(buf, stride, k, best_i);
58
+ }
59
+ return maxKept;
60
+ }
61
+
62
+ function swap(buf, stride, a, b) {
63
+ const ao = a * stride, bo = b * stride;
64
+ for (let k = 0; k < stride; k++) {
65
+ const t = buf[ao + k];
66
+ buf[ao + k] = buf[bo + k];
67
+ buf[bo + k] = t;
68
+ }
69
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"refine_ray_concave.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/refine_ray_concave.js"],"names":[],"mappings":"AAsCA;;;;;;;;;GASG;AACH,qEARW;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,YAC5B,UAAU,MAAM,CAAC,MACjB,MAAM,MAAa,MAAM,MAAa,MAAM,MAC5C,MAAM,MAAa,MAAM,MAAa,MAAM,QAC5C,MAAM,aACN,YAAY,UAAQ,GAClB,MAAM,CAkGlB"}
1
+ {"version":3,"file":"refine_ray_concave.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/refine_ray_concave.js"],"names":[],"mappings":"AAsCA;;;;;;;;;GASG;AACH,qEARW;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,YAC5B,UAAU,MAAM,CAAC,MACjB,MAAM,MAAa,MAAM,MAAa,MAAM,MAC5C,MAAM,MAAa,MAAM,MAAa,MAAM,QAC5C,MAAM,aACN,YAAY,UAAQ,GAClB,MAAM,CAyGlB"}