@woosh/meep-engine 2.153.0 → 2.155.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.
- package/package.json +1 -1
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +112 -0
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/ConvexHullShape3D.js +325 -0
- package/src/core/geom/vec3/v3_array_copy.d.ts +3 -3
- package/src/core/geom/vec3/v3_array_copy.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_array_copy.js +2 -2
- package/src/core/geom/vec3/v3_cross.d.ts +17 -0
- package/src/core/geom/vec3/v3_cross.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_cross.js +20 -0
- package/src/core/geom/vec3/v3_subtract.d.ts +16 -0
- package/src/core/geom/vec3/v3_subtract.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_subtract.js +19 -0
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.js +8 -0
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts +4 -0
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2D.js +21 -0
- package/src/engine/physics/PLAN.md +4 -4
- package/src/engine/physics/body/BodyStorage.d.ts +3 -1
- package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
- package/src/engine/physics/body/BodyStorage.js +452 -450
- package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -1
- package/src/engine/physics/body/SolverBodyState.js +6 -5
- package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
- package/src/engine/physics/broadphase/generate_pairs.js +9 -1
- package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -1
- package/src/engine/physics/ccd/linear_sweep.js +237 -238
- package/src/engine/physics/computeInterceptPoint.d.ts.map +1 -1
- package/src/engine/physics/computeInterceptPoint.js +8 -3
- package/src/engine/physics/contact/ManifoldStore.d.ts +0 -16
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
- package/src/engine/physics/contact/ManifoldStore.js +1 -38
- package/src/engine/physics/ecs/BodyKind.d.ts +3 -2
- package/src/engine/physics/ecs/BodyKind.d.ts.map +1 -1
- package/src/engine/physics/ecs/BodyKind.js +25 -24
- package/src/engine/physics/ecs/PhysicsEvents.d.ts +4 -5
- package/src/engine/physics/ecs/PhysicsEvents.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsEvents.js +15 -16
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +5 -30
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +13 -45
- package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts.map +1 -1
- package/src/engine/physics/ecs/RigidBodySerializationAdapter.js +85 -81
- package/src/engine/physics/ecs/is_sensor.d.ts +18 -0
- package/src/engine/physics/ecs/is_sensor.d.ts.map +1 -0
- package/src/engine/physics/ecs/is_sensor.js +27 -0
- package/src/engine/physics/events/ContactEventBuffer.d.ts +2 -1
- package/src/engine/physics/events/ContactEventBuffer.d.ts.map +1 -1
- package/src/engine/physics/events/ContactEventBuffer.js +84 -83
- package/src/engine/physics/gjk/gjk.d.ts +0 -26
- package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
- package/src/engine/physics/gjk/gjk.js +3 -52
- package/src/engine/physics/gjk/gjk_epa_penetration.d.ts +20 -0
- package/src/engine/physics/gjk/gjk_epa_penetration.d.ts.map +1 -0
- package/src/engine/physics/gjk/gjk_epa_penetration.js +548 -0
- package/src/engine/physics/gjk/minkowski_support.d.ts +4 -9
- package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -1
- package/src/engine/physics/gjk/minkowski_support.js +70 -75
- package/src/engine/physics/gjk/mpr.d.ts +1 -1
- package/src/engine/physics/gjk/mpr.d.ts.map +1 -1
- package/src/engine/physics/gjk/mpr.js +362 -344
- package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
- package/src/engine/physics/island/IslandBuilder.js +431 -428
- package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_box_manifold.js +4 -81
- package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_triangle_contact.js +4 -39
- package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/capsule_contacts.js +459 -462
- package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/clip_against_axis_uv.js +4 -1
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts +83 -0
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/convex_convex_manifold.js +425 -0
- package/src/engine/physics/narrowphase/convex_decomposition.d.ts +32 -0
- package/src/engine/physics/narrowphase/convex_decomposition.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/convex_decomposition.js +293 -0
- package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts +41 -0
- package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/mesh_convex_hull.js +106 -0
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.d.ts +8 -0
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +105 -102
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts +29 -0
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +69 -0
- package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_concave.js +152 -145
- package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/sphere_box_contact.js +132 -123
- package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -1
- package/src/engine/physics/queries/overlap_shape.js +16 -17
- package/src/engine/physics/queries/raycast.d.ts +5 -0
- package/src/engine/physics/queries/raycast.d.ts.map +1 -1
- package/src/engine/physics/queries/raycast.js +16 -8
- package/src/engine/physics/queries/shape_cast.d.ts.map +1 -1
- package/src/engine/physics/queries/shape_cast.js +13 -7
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +8 -11
- package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -1
- package/src/engine/physics/vehicle/RaycastVehicle.js +339 -333
- package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +0 -13
- package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +0 -1
- 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 {
|
|
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
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
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
|
|
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
|
-
//
|
|
1114
|
-
//
|
|
1115
|
-
//
|
|
1116
|
-
//
|
|
1117
|
-
//
|
|
1118
|
-
//
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
|
1238
|
-
//
|
|
1239
|
-
//
|
|
1240
|
-
//
|
|
1241
|
-
//
|
|
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
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
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
|
-
|
|
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,
|
|
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"}
|