@woosh/meep-engine 2.142.0 → 2.144.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/CapsuleShape3D.d.ts +1 -1
- package/src/core/geom/3d/shape/CapsuleShape3D.js +1 -1
- package/src/core/geom/3d/shape/PointShape3D.d.ts +1 -0
- package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PointShape3D.js +11 -0
- package/src/core/geom/3d/shape/SphereShape3D.d.ts +48 -0
- package/src/core/geom/3d/shape/SphereShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/SphereShape3D.js +131 -0
- package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts +30 -18
- package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -92
- package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/shape_to_type.js +4 -2
- package/src/core/geom/3d/shape/json/type_adapters.d.ts +12 -3
- package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/type_adapters.js +16 -4
- package/src/core/geom/3d/shape/util/shape_to_visual_entity.js +2 -2
- package/src/engine/control/first-person/DESIGN_COLLISION.md +302 -0
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +91 -58
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1814 -1789
- package/src/engine/control/first-person/TODO.md +17 -32
- package/src/engine/control/first-person/collision/KinematicMover.d.ts +176 -0
- package/src/engine/control/first-person/collision/KinematicMover.d.ts.map +1 -0
- package/src/engine/control/first-person/collision/KinematicMover.js +424 -0
- package/src/engine/control/first-person/prototype_first_person_controller.js +65 -0
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +3 -1
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +30 -16
- package/src/engine/physics/PLAN.md +94 -32
- package/src/engine/physics/contact/ManifoldStore.d.ts +28 -2
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
- package/src/engine/physics/contact/ManifoldStore.js +37 -3
- package/src/engine/physics/contact/combine_material.d.ts +30 -0
- package/src/engine/physics/contact/combine_material.d.ts.map +1 -0
- package/src/engine/physics/contact/combine_material.js +35 -0
- package/src/engine/physics/ecs/Collider.d.ts +15 -0
- package/src/engine/physics/ecs/Collider.d.ts.map +1 -1
- package/src/engine/physics/ecs/Collider.js +34 -0
- package/src/engine/physics/ecs/Joint.d.ts +18 -0
- package/src/engine/physics/ecs/Joint.d.ts.map +1 -1
- package/src/engine/physics/ecs/Joint.js +70 -0
- package/src/engine/physics/ecs/JointSerializationAdapter.d.ts +29 -0
- package/src/engine/physics/ecs/JointSerializationAdapter.d.ts.map +1 -0
- package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +9 -4
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +9 -4
- package/src/engine/physics/ecs/RigidBody.d.ts +15 -0
- package/src/engine/physics/ecs/RigidBody.d.ts.map +1 -1
- package/src/engine/physics/ecs/RigidBody.js +46 -0
- package/src/engine/physics/narrowphase/compute_penetration.d.ts +41 -41
- package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/compute_penetration.js +96 -169
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts +52 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +150 -16
- package/src/engine/physics/narrowphase/refine_ray_hit.js +2 -2
- package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts +8 -7
- package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/sphere_sphere_contact.js +8 -7
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +10 -21
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
|
|
2
2
|
import { Triangle3D } from "../../../core/geom/3d/shape/Triangle3D.js";
|
|
3
3
|
import { body_id_index } from "../body/BodyStorage.js";
|
|
4
|
+
import { combine_friction, combine_restitution } from "../contact/combine_material.js";
|
|
4
5
|
import { CONTACT_STRIDE, MAX_CONTACTS_PER_MANIFOLD } from "../contact/ManifoldStore.js";
|
|
5
6
|
import { expanding_polytope_algorithm } from "../gjk/expanding_polytope_algorithm.js";
|
|
6
7
|
import { gjk_with_axis } from "../gjk/gjk.js";
|
|
@@ -52,7 +53,7 @@ const capsule_box_multi_result = new Float64Array(CAPSULE_BOX_MAX_CONTACTS * CAP
|
|
|
52
53
|
|
|
53
54
|
/**
|
|
54
55
|
* Candidate-contact stride: wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth,
|
|
55
|
-
* feature_id.
|
|
56
|
+
* feature_id, friction, restitution.
|
|
56
57
|
*
|
|
57
58
|
* The `feature_id` (offset 10) is a stable cross-frame identifier of the
|
|
58
59
|
* geometric feature pair that produced this contact — used by the
|
|
@@ -61,9 +62,26 @@ const capsule_box_multi_result = new Float64Array(CAPSULE_BOX_MAX_CONTACTS * CAP
|
|
|
61
62
|
* corresponds to the same physical contact. A value of 0 means
|
|
62
63
|
* "no feature info, fall back to position matching".
|
|
63
64
|
*
|
|
65
|
+
* `friction` (offset 11) and `restitution` (offset 12) are the COMBINED
|
|
66
|
+
* coefficients for the specific (colliderA, colliderB) pair that produced this
|
|
67
|
+
* contact, combined here (the only place that knows the exact source collider
|
|
68
|
+
* on each side) and carried into the manifold so a compound body's per-collider
|
|
69
|
+
* materials are honoured per-contact.
|
|
70
|
+
*
|
|
71
|
+
* @type {number}
|
|
72
|
+
*/
|
|
73
|
+
const CANDIDATE_STRIDE = 13;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Combined friction / restitution for the collider pair currently being
|
|
77
|
+
* dispatched. Set once at the top of {@link dispatch_pair} (which is called
|
|
78
|
+
* per collider pair) and written into every contact that call appends, so
|
|
79
|
+
* each contact carries the material of its actual source colliders. Module
|
|
80
|
+
* scratch rather than threaded through every `append_contact` call site.
|
|
64
81
|
* @type {number}
|
|
65
82
|
*/
|
|
66
|
-
|
|
83
|
+
let g_pair_friction = 0;
|
|
84
|
+
let g_pair_restitution = 0;
|
|
67
85
|
|
|
68
86
|
/**
|
|
69
87
|
* Maximum number of contacts emitted into the per-pair manifold after the
|
|
@@ -194,6 +212,8 @@ function append_contact(count, wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth,
|
|
|
194
212
|
candidates[off + 6] = nx; candidates[off + 7] = ny; candidates[off + 8] = nz;
|
|
195
213
|
candidates[off + 9] = depth;
|
|
196
214
|
candidates[off + 10] = feature_id;
|
|
215
|
+
candidates[off + 11] = g_pair_friction;
|
|
216
|
+
candidates[off + 12] = g_pair_restitution;
|
|
197
217
|
|
|
198
218
|
return count + 1;
|
|
199
219
|
}
|
|
@@ -358,8 +378,24 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
|
|
|
358
378
|
const shapeA = colA.shape;
|
|
359
379
|
const shapeB = colB.shape;
|
|
360
380
|
|
|
361
|
-
|
|
362
|
-
|
|
381
|
+
// Per-contact materials: combine the two source colliders' coefficients
|
|
382
|
+
// once here (this is the only place that knows the exact collider on each
|
|
383
|
+
// side) and stamp them onto every contact this dispatch appends. The
|
|
384
|
+
// `deepest_pair_penetration` query passes bare `{shape}` adapters with no
|
|
385
|
+
// material fields — it never writes to a manifold, so 0 is fine there.
|
|
386
|
+
const fa = colA.friction, fb = colB.friction;
|
|
387
|
+
if (fa !== undefined && fb !== undefined) {
|
|
388
|
+
g_pair_friction = combine_friction(fa, fb);
|
|
389
|
+
g_pair_restitution = combine_restitution(colA.restitution, colB.restitution);
|
|
390
|
+
} else {
|
|
391
|
+
g_pair_friction = 0;
|
|
392
|
+
g_pair_restitution = 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// isSphereShape3D covers both UnitSphereShape3D (fixed radius 1) and
|
|
396
|
+
// SphereShape3D (arbitrary radius). Both expose `radius`.
|
|
397
|
+
const isSphereA = shapeA.isSphereShape3D === true;
|
|
398
|
+
const isSphereB = shapeB.isSphereShape3D === true;
|
|
363
399
|
// isBoxShape3D covers both UnitCubeShape3D (fixed 0.5) and BoxShape3D
|
|
364
400
|
// (arbitrary half-extents). Both expose `half_extents` as a Vector3.
|
|
365
401
|
const isBoxA = shapeA.isBoxShape3D === true;
|
|
@@ -369,12 +405,13 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
|
|
|
369
405
|
|
|
370
406
|
// sphere-sphere
|
|
371
407
|
if (isSphereA && isSphereB) {
|
|
408
|
+
const ra = shapeA.radius, rb = shapeB.radius;
|
|
372
409
|
|
|
373
410
|
const ok = sphere_sphere_contact(
|
|
374
411
|
sphere_result,
|
|
375
412
|
trA.position.x, trA.position.y, trA.position.z,
|
|
376
413
|
trB.position.x, trB.position.y, trB.position.z,
|
|
377
|
-
|
|
414
|
+
ra, rb
|
|
378
415
|
);
|
|
379
416
|
|
|
380
417
|
if (!ok) return count;
|
|
@@ -383,23 +420,25 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
|
|
|
383
420
|
|
|
384
421
|
// Sphere-sphere produces exactly one contact per pair; fid = 1
|
|
385
422
|
// identifies it as a real feature (distinguishes from "no info" = 0)
|
|
386
|
-
// and is trivially stable across frames.
|
|
423
|
+
// and is trivially stable across frames. Witnesses are the surface
|
|
424
|
+
// points along the (unit) normal, scaled by each sphere's radius.
|
|
387
425
|
return append_contact(count,
|
|
388
|
-
trA.position.x - nx, trA.position.y - ny, trA.position.z - nz,
|
|
389
|
-
trB.position.x + nx, trB.position.y + ny, trB.position.z + nz,
|
|
426
|
+
trA.position.x - nx * ra, trA.position.y - ny * ra, trA.position.z - nz * ra,
|
|
427
|
+
trB.position.x + nx * rb, trB.position.y + ny * rb, trB.position.z + nz * rb,
|
|
390
428
|
nx, ny, nz, sphere_result[3], 1);
|
|
391
429
|
}
|
|
392
430
|
|
|
393
431
|
// sphere ↔ box
|
|
394
432
|
if ((isSphereA && isBoxB) || (isBoxA && isSphereB)) {
|
|
395
|
-
const sphereTr
|
|
433
|
+
const sphereTr = isSphereA ? trA : trB;
|
|
434
|
+
const sphereShape = isSphereA ? shapeA : shapeB;
|
|
396
435
|
const boxTr = isSphereA ? trB : trA;
|
|
397
436
|
const boxShape = isSphereA ? shapeB : shapeA;
|
|
398
437
|
const bh = boxShape.half_extents;
|
|
399
438
|
|
|
400
439
|
const ok = sphere_box_contact(
|
|
401
440
|
closed_form_result,
|
|
402
|
-
sphereTr.position.x, sphereTr.position.y, sphereTr.position.z,
|
|
441
|
+
sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius,
|
|
403
442
|
boxTr.position.x, boxTr.position.y, boxTr.position.z,
|
|
404
443
|
boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
|
|
405
444
|
bh.x, bh.y, bh.z
|
|
@@ -491,12 +530,13 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
|
|
|
491
530
|
const capsuleTr = isCapsuleA ? trA : trB;
|
|
492
531
|
const capsuleShape = isCapsuleA ? colA.shape : colB.shape;
|
|
493
532
|
const sphereTr = isCapsuleA ? trB : trA;
|
|
533
|
+
const sphereShape = isCapsuleA ? colB.shape : colA.shape;
|
|
494
534
|
const ok = capsule_sphere_contact(
|
|
495
535
|
closed_form_result,
|
|
496
536
|
capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
|
|
497
537
|
capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
|
|
498
538
|
capsuleShape.radius, capsuleShape.height * 0.5,
|
|
499
|
-
sphereTr.position.x, sphereTr.position.y, sphereTr.position.z,
|
|
539
|
+
sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius
|
|
500
540
|
);
|
|
501
541
|
if (!ok) return count;
|
|
502
542
|
let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
|
|
@@ -639,14 +679,15 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
|
|
|
639
679
|
const c_pos_y = concave_tr.position.y;
|
|
640
680
|
const c_pos_z = concave_tr.position.z;
|
|
641
681
|
|
|
642
|
-
// Sphere fast-path: when the convex side is a
|
|
643
|
-
//
|
|
682
|
+
// Sphere fast-path: when the convex side is a sphere we bypass GJK+EPA
|
|
683
|
+
// entirely per triangle and use the closed-form
|
|
644
684
|
// {@link sphere_triangle_contact}. This avoids the EPA precision
|
|
645
685
|
// wall on Triangle3D (whose support function is degenerate along
|
|
646
686
|
// the face normal — all 3 vertices project to the same value),
|
|
647
687
|
// which was producing noisy depths at small penetrations and
|
|
648
688
|
// letting dropped spheres tunnel through heightmaps / meshes.
|
|
649
|
-
const isSphereConvex = convex_col.shape.
|
|
689
|
+
const isSphereConvex = convex_col.shape.isSphereShape3D === true;
|
|
690
|
+
const sphere_radius = isSphereConvex ? convex_col.shape.radius : 0;
|
|
650
691
|
|
|
651
692
|
// Box fast-path: closed-form {@link box_triangle_contact} via SAT
|
|
652
693
|
// over 13 axes + polygon clipping for face-vs-face contacts.
|
|
@@ -743,7 +784,7 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
|
|
|
743
784
|
|
|
744
785
|
const ok = sphere_triangle_contact(
|
|
745
786
|
sphere_triangle_result,
|
|
746
|
-
convex_wx, convex_wy, convex_wz,
|
|
787
|
+
convex_wx, convex_wy, convex_wz, sphere_radius,
|
|
747
788
|
ax_w, ay_w, az_w,
|
|
748
789
|
bx_w, by_w, bz_w,
|
|
749
790
|
cx_w, cy_w, cz_w
|
|
@@ -1259,6 +1300,98 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
|
|
|
1259
1300
|
);
|
|
1260
1301
|
}
|
|
1261
1302
|
|
|
1303
|
+
// Reusable single-pair adapters for the penetration query below — no per-call
|
|
1304
|
+
// allocation. dispatch_pair only reads `.shape` off a collider and
|
|
1305
|
+
// `.position` / `.rotation` off a transform, so these minimal stand-ins are
|
|
1306
|
+
// all it needs.
|
|
1307
|
+
const _pp_colA = { shape: null };
|
|
1308
|
+
const _pp_colB = { shape: null };
|
|
1309
|
+
const _pp_trA = { position: null, rotation: null };
|
|
1310
|
+
const _pp_trB = { position: null, rotation: null };
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* Cold-start GJK seed for the one-shot penetration query. Re-zeroed before each
|
|
1314
|
+
* call so every query is independent (gjk_with_axis treats a zero vector as a
|
|
1315
|
+
* cold start) — no warm-start leakage between unrelated queries, which keeps
|
|
1316
|
+
* the result a pure function of its inputs.
|
|
1317
|
+
* @type {Float64Array}
|
|
1318
|
+
*/
|
|
1319
|
+
const _pp_axis = new Float64Array(3);
|
|
1320
|
+
|
|
1321
|
+
/**
|
|
1322
|
+
* Single-pair penetration query: the depth and world normal of the DEEPEST
|
|
1323
|
+
* contact the narrowphase would generate for one posed shape pair.
|
|
1324
|
+
*
|
|
1325
|
+
* Routes through the exact same {@link dispatch_pair} the contact solver
|
|
1326
|
+
* consumes — closed-form for every sphere / box / capsule pair (box-box via
|
|
1327
|
+
* SAT, so the true minimum-translation axis is found rather than the
|
|
1328
|
+
* centroid-seeded portal MPR would pick), triangle decomposition + closed-form
|
|
1329
|
+
* per triangle for convex-vs-concave, and GJK + EPA (+ MPR) for any other
|
|
1330
|
+
* convex pair. The deepest contact's depth is the minimum-translation distance
|
|
1331
|
+
* and its normal is the MTV axis, so the result is correct for every shape pair
|
|
1332
|
+
* the engine can build and agrees bit-for-bit with what the solver acts on.
|
|
1333
|
+
*
|
|
1334
|
+
* The normal follows the narrowphase's stored convention: a unit vector
|
|
1335
|
+
* pointing from B toward A — the direction to translate A to separate it.
|
|
1336
|
+
*
|
|
1337
|
+
* Concave-vs-concave is not dispatched (the narrowphase skips it) and returns
|
|
1338
|
+
* 0; callers needing to reject that case must check `is_convex` themselves.
|
|
1339
|
+
*
|
|
1340
|
+
* Not re-entrant: shares the module-level candidate / scratch buffers with
|
|
1341
|
+
* {@link narrowphase_step}. Intended for main-thread queries run outside a
|
|
1342
|
+
* step (depenetration, overlap depth, tooling) — never from inside one.
|
|
1343
|
+
*
|
|
1344
|
+
* @param {Float64Array|number[]} out_normal length ≥ 3; receives the unit B→A
|
|
1345
|
+
* normal on penetration (untouched when the return value is 0)
|
|
1346
|
+
* @param {AbstractShape3D} shapeA
|
|
1347
|
+
* @param {{x:number,y:number,z:number}} posA
|
|
1348
|
+
* @param {{x:number,y:number,z:number,w:number}} rotA
|
|
1349
|
+
* @param {AbstractShape3D} shapeB
|
|
1350
|
+
* @param {{x:number,y:number,z:number}} posB
|
|
1351
|
+
* @param {{x:number,y:number,z:number,w:number}} rotB
|
|
1352
|
+
* @returns {number} deepest penetration depth (> 0) or 0 if separated
|
|
1353
|
+
*/
|
|
1354
|
+
export function deepest_pair_penetration(out_normal, shapeA, posA, rotA, shapeB, posB, rotB) {
|
|
1355
|
+
_pp_colA.shape = shapeA;
|
|
1356
|
+
_pp_trA.position = posA;
|
|
1357
|
+
_pp_trA.rotation = rotA;
|
|
1358
|
+
_pp_colB.shape = shapeB;
|
|
1359
|
+
_pp_trB.position = posB;
|
|
1360
|
+
_pp_trB.rotation = rotB;
|
|
1361
|
+
|
|
1362
|
+
// Cold GJK seed — one-shot query, not a warm-started per-frame manifold.
|
|
1363
|
+
_pp_axis[0] = 0; _pp_axis[1] = 0; _pp_axis[2] = 0;
|
|
1364
|
+
const n = dispatch_pair(0, _pp_colA, _pp_trA, _pp_colB, _pp_trB, _pp_axis, 0);
|
|
1365
|
+
if (n === 0) {
|
|
1366
|
+
return 0;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// Deepest contact = the minimum-translation depth; its stored normal is the
|
|
1370
|
+
// separation axis. (For multi-point manifolds — box-box, capsule-box, a
|
|
1371
|
+
// convex straddling several mesh triangles — every point shares the
|
|
1372
|
+
// separating axis, so the max depth along it is the distance to separate.)
|
|
1373
|
+
let best_depth = -1;
|
|
1374
|
+
let best_off = 0;
|
|
1375
|
+
for (let i = 0; i < n; i++) {
|
|
1376
|
+
const off = i * CANDIDATE_STRIDE;
|
|
1377
|
+
const d = candidates[off + 9];
|
|
1378
|
+
if (d > best_depth) {
|
|
1379
|
+
best_depth = d;
|
|
1380
|
+
best_off = off;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
if (!(best_depth > 0) || !Number.isFinite(best_depth)) {
|
|
1385
|
+
return 0;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
out_normal[0] = candidates[best_off + 6];
|
|
1389
|
+
out_normal[1] = candidates[best_off + 7];
|
|
1390
|
+
out_normal[2] = candidates[best_off + 8];
|
|
1391
|
+
|
|
1392
|
+
return best_depth;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1262
1395
|
/**
|
|
1263
1396
|
* For every pair in `pair_list`, do a cross-product over A's collider list ×
|
|
1264
1397
|
* B's collider list, accumulate candidate contacts, reduce to ≤4, and write
|
|
@@ -1401,7 +1534,8 @@ export function narrowphase_step(pair_list, manifolds, lists) {
|
|
|
1401
1534
|
candidates[off + 3], candidates[off + 4], candidates[off + 5],
|
|
1402
1535
|
candidates[off + 6], candidates[off + 7], candidates[off + 8],
|
|
1403
1536
|
candidates[off + 9],
|
|
1404
|
-
candidates[off + 10]
|
|
1537
|
+
candidates[off + 10],
|
|
1538
|
+
candidates[off + 11], candidates[off + 12]
|
|
1405
1539
|
);
|
|
1406
1540
|
const prev_j = cand_to_prev[k];
|
|
1407
1541
|
if (prev_j !== -1) {
|
|
@@ -41,10 +41,10 @@ const ln = new Float64Array(3); // surface normal in shape-local frame
|
|
|
41
41
|
* {@link RAY_REFINE_UNSUPPORTED} when the shape has no exact ray test here
|
|
42
42
|
*/
|
|
43
43
|
export function refine_ray_hit(shape, position, rotation, ox, oy, oz, dx, dy, dz, tMax, outNormal) {
|
|
44
|
-
if (shape.
|
|
44
|
+
if (shape.isSphereShape3D === true) {
|
|
45
45
|
// Rotation-invariant: translate into the sphere's frame; the local
|
|
46
46
|
// normal is already the world normal.
|
|
47
|
-
return ray_sphere_local(outNormal, ox - position.x, oy - position.y, oz - position.z, dx, dy, dz, tMax,
|
|
47
|
+
return ray_sphere_local(outNormal, ox - position.x, oy - position.y, oz - position.z, dx, dy, dz, tMax, shape.radius);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
if (shape.isBoxShape3D === true || shape.isCapsuleShape3D === true) {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Closed-form contact generation for two
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `depth` is the positive penetration distance.
|
|
2
|
+
* Closed-form contact generation for two spheres positioned in world space.
|
|
3
|
+
* Returns whether the spheres overlap. On overlap, `out` is populated with
|
|
4
|
+
* `[nx, ny, nz, depth]` where the normal (unit length) points from B toward A
|
|
5
|
+
* and `depth` is the positive penetration distance.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Each sphere's radius is passed explicitly (`radius_a` / `radius_b`) — the
|
|
8
|
+
* caller reads it from the shape (`SphereShape3D.radius`; 1 for the
|
|
9
|
+
* {@link UnitSphereShape3D} special case). Any scaling on the body's transform
|
|
10
|
+
* is irrelevant under our "no scale on physics transforms" assumption.
|
|
10
11
|
*
|
|
11
12
|
* Centres-coincident is a singular case for the general normal but is
|
|
12
13
|
* resolved here by picking +X as a deterministic tie-break direction.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sphere_sphere_contact.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/sphere_sphere_contact.js"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sphere_sphere_contact.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/sphere_sphere_contact.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,2CAXW,MAAM,EAAE,GAAC,YAAY,MACrB,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,YACN,MAAM,YACN,MAAM,GACJ,OAAO,CA4BnB"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Closed-form contact generation for two
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `depth` is the positive penetration distance.
|
|
2
|
+
* Closed-form contact generation for two spheres positioned in world space.
|
|
3
|
+
* Returns whether the spheres overlap. On overlap, `out` is populated with
|
|
4
|
+
* `[nx, ny, nz, depth]` where the normal (unit length) points from B toward A
|
|
5
|
+
* and `depth` is the positive penetration distance.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Each sphere's radius is passed explicitly (`radius_a` / `radius_b`) — the
|
|
8
|
+
* caller reads it from the shape (`SphereShape3D.radius`; 1 for the
|
|
9
|
+
* {@link UnitSphereShape3D} special case). Any scaling on the body's transform
|
|
10
|
+
* is irrelevant under our "no scale on physics transforms" assumption.
|
|
10
11
|
*
|
|
11
12
|
* Centres-coincident is a singular case for the general normal but is
|
|
12
13
|
* resolved here by picking +X as a deterministic tie-break direction.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solve_contacts.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/solver/solve_contacts.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"solve_contacts.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/solver/solve_contacts.js"],"names":[],"mappings":"AAkbA;;;;;;;;;;;;;GAaG;AACH,0FAHW,MAAM,GACJ,MAAM,CA+JlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,2FAuCC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wFAgEC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,iGAmFC;AAED;;;;;;;;GAQG;AACH,uFAFW,MAAM,QA2GhB;AAED;;;;;;;;;;;GAWG;AACH,yFA4DC;AAED;;;;;;;;;;;;;;GAcG;AACH,2FAFW,MAAM,QA4EhB;AAED;;;;;;;;;;;;;;;GAeG;AACH,oFAJW,MAAM,UACN,MAAM,cACN,MAAM,QAahB;AA7kCD;;;;;GAKG;AACH,0CAFU,MAAM,CAEuB;AAEvC;;;GAGG;AACH,0CAFU,MAAM,CAEsB"}
|
|
@@ -286,25 +286,11 @@ function inv_mass_of(rb) {
|
|
|
286
286
|
return rb.mass > 0 ? 1 / rb.mass : 0;
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
*/
|
|
295
|
-
function combine_friction(a, b) {
|
|
296
|
-
return Math.sqrt(a * b);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Combine two restitution coefficients (maximum — Unity / common default).
|
|
301
|
-
* @param {number} a
|
|
302
|
-
* @param {number} b
|
|
303
|
-
* @returns {number}
|
|
304
|
-
*/
|
|
305
|
-
function combine_restitution(a, b) {
|
|
306
|
-
return a > b ? a : b;
|
|
307
|
-
}
|
|
289
|
+
// Friction / restitution are no longer combined here: the narrowphase combines
|
|
290
|
+
// the specific source-collider pair's coefficients per contact (so compound
|
|
291
|
+
// bodies with mixed-material colliders are honoured) and stores the result in
|
|
292
|
+
// the manifold (CONTACT_STRIDE offsets 14 / 15). prepare_contacts reads them
|
|
293
|
+
// per contact. See engine/physics/contact/combine_material.js.
|
|
308
294
|
|
|
309
295
|
const scratch_clamp = new Float64Array(2);
|
|
310
296
|
const scratch_inertia_a = new Float64Array(3);
|
|
@@ -520,8 +506,6 @@ export function prepare_contacts(manifolds, system, dt_sub) {
|
|
|
520
506
|
|
|
521
507
|
const cc = manifolds.contact_count(slot);
|
|
522
508
|
const slot_off = manifolds.slot_data_offset(slot);
|
|
523
|
-
const restitution_combined = combine_restitution(colA.restitution, colB.restitution);
|
|
524
|
-
const friction_combined = combine_friction(colA.friction, colB.friction);
|
|
525
509
|
|
|
526
510
|
const pAx = trA.position.x, pAy = trA.position.y, pAz = trA.position.z;
|
|
527
511
|
const pBx = trB.position.x, pBy = trB.position.y, pBz = trB.position.z;
|
|
@@ -530,6 +514,11 @@ export function prepare_contacts(manifolds, system, dt_sub) {
|
|
|
530
514
|
for (let k = 0; k < cc; k++) {
|
|
531
515
|
const off = slot_off + k * CONTACT_STRIDE;
|
|
532
516
|
|
|
517
|
+
// Per-contact combined materials (stamped by the narrowphase from
|
|
518
|
+
// this contact's specific source-collider pair).
|
|
519
|
+
const friction_combined = data[off + 14];
|
|
520
|
+
const restitution_combined = data[off + 15];
|
|
521
|
+
|
|
533
522
|
const wax = data[off], way = data[off + 1], waz = data[off + 2];
|
|
534
523
|
const wbx = data[off + 3], wby = data[off + 4], wbz = data[off + 5];
|
|
535
524
|
const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
|