reze-engine 0.34.0 → 0.35.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/README.md +6 -5
- package/dist/physics/contact.d.ts +3 -0
- package/dist/physics/contact.d.ts.map +1 -1
- package/dist/physics/contact.js +386 -19
- package/dist/physics/manifold.d.ts +13 -0
- package/dist/physics/manifold.d.ts.map +1 -0
- package/dist/physics/manifold.js +68 -0
- package/dist/physics/solver.d.ts.map +1 -1
- package/dist/physics/solver.js +64 -4
- package/package.json +1 -1
- package/src/physics/contact.ts +410 -15
- package/src/physics/solver.ts +64 -3
package/src/physics/solver.ts
CHANGED
|
@@ -19,6 +19,31 @@ import type { Contact, ContactPool } from "./contact"
|
|
|
19
19
|
|
|
20
20
|
const BOUNCE_THRESHOLD = 2.0
|
|
21
21
|
|
|
22
|
+
// Successive over-relaxation factor on CONTACT rows only (joints untouched).
|
|
23
|
+
// Each contact row independently drives the relative velocity at its own point
|
|
24
|
+
// to zero; when a dress panel carries up to 43 of them at once, they all
|
|
25
|
+
// correct the same motion and the body is over-braked, differently every
|
|
26
|
+
// substep. Scaling each row's step damps that without changing the fixed
|
|
27
|
+
// point — the accumulated impulse still converges to the same answer, just
|
|
28
|
+
// approached rather than overshot.
|
|
29
|
+
//
|
|
30
|
+
// The gain is PER CONTACT, scaled by how contended its two bodies are, not a
|
|
31
|
+
// flat constant. A flat factor was measured first and is wrong: it also slows
|
|
32
|
+
// the well-conditioned rows, and a body resting on a SINGLE contact can then
|
|
33
|
+
// no longer cancel its approach velocity within the iteration budget, so it
|
|
34
|
+
// creeps forever instead of settling (托特 peak speed at 15 s: 0.11 at gain
|
|
35
|
+
// 1.0, 0.46 at 0.5, 0.72 at 0.3 — a permanent limit cycle on a one-contact
|
|
36
|
+
// body). Over-constraint is a local property, so the remedy has to be local.
|
|
37
|
+
//
|
|
38
|
+
// gain = 1 / max(rows on A, rows on B), floored. One contact → 1.0, exact and
|
|
39
|
+
// settling preserved; a dress panel sharing 20 rows → 0.05, damped. This is
|
|
40
|
+
// the standard mass-splitting blend toward Jacobi in the contended cluster.
|
|
41
|
+
const CONTACT_SOR_MIN = 0.12
|
|
42
|
+
|
|
43
|
+
// Per-body contact-row counts for the gain above; grown on demand, refilled
|
|
44
|
+
// each substep. Module-level so the solve allocates nothing.
|
|
45
|
+
let _rowCount = new Int32Array(0)
|
|
46
|
+
|
|
22
47
|
// Ceilings on limit-correction velocity. In normal operation limit errors are
|
|
23
48
|
// tiny; a large error only appears after a discontinuity (teleport, stall,
|
|
24
49
|
// deep penetration), and feeding err·ERP/dt to the solver unclamped then
|
|
@@ -93,8 +118,23 @@ export function solveConstraints(
|
|
|
93
118
|
for (let c = 0; c < constraints.length; c++) {
|
|
94
119
|
setupConstraint(constraints[c], c, cache, store, dt, invDt)
|
|
95
120
|
}
|
|
121
|
+
// How many contact rows each body carries this substep — the per-contact
|
|
122
|
+
// relaxation gain below is a function of the more contended of its two
|
|
123
|
+
// bodies. Counting is O(contacts), done once, before any setup.
|
|
124
|
+
if (_rowCount.length < store.count) _rowCount = new Int32Array(store.count)
|
|
125
|
+
else _rowCount.fill(0, 0, store.count)
|
|
126
|
+
// Only DYNAMIC bodies are counted. A static body — above all the ground —
|
|
127
|
+
// collects a row from every body resting on it, and letting that inflate the
|
|
128
|
+
// count crushes the gain of each of those contacts to the floor, so nothing
|
|
129
|
+
// resting on the ground can build enough impulse and it creeps instead of
|
|
130
|
+
// settling. A body with invMass 0 cannot be over-braked in the first place.
|
|
96
131
|
for (let ci = 0; ci < contacts.count; ci++) {
|
|
97
|
-
|
|
132
|
+
const c = contacts.get(ci)
|
|
133
|
+
if (invMass[c.bodyA] > 0) _rowCount[c.bodyA]++
|
|
134
|
+
if (invMass[c.bodyB] > 0) _rowCount[c.bodyB]++
|
|
135
|
+
}
|
|
136
|
+
for (let ci = 0; ci < contacts.count; ci++) {
|
|
137
|
+
setupContactRow(contacts.get(ci), lv, av, invMass, W, invDt)
|
|
98
138
|
}
|
|
99
139
|
|
|
100
140
|
for (let iter = 0; iter < iterations; iter++) {
|
|
@@ -705,6 +745,7 @@ function setupContactRow(
|
|
|
705
745
|
av: Float32Array,
|
|
706
746
|
invMass: Float32Array,
|
|
707
747
|
W: Float32Array,
|
|
748
|
+
invDt: number,
|
|
708
749
|
): void {
|
|
709
750
|
const ai = c.bodyA * 3
|
|
710
751
|
const bi = c.bodyB * 3
|
|
@@ -748,6 +789,26 @@ function setupContactRow(
|
|
|
748
789
|
? -c.restitution * relVelN0
|
|
749
790
|
: 0
|
|
750
791
|
|
|
792
|
+
// Speculative rows (depth < 0 — the shapes are inside the margin band but
|
|
793
|
+
// NOT touching) must not brake a body that hasn't arrived yet. Their whole
|
|
794
|
+
// job is to stop it crossing the surface within this substep, so the
|
|
795
|
+
// approach speed they leave alone is exactly the one that closes the
|
|
796
|
+
// remaining gap in dt; only the excess above that is cancelled.
|
|
797
|
+
//
|
|
798
|
+
// Without this the row targets relVelN = 0 like a touching contact and
|
|
799
|
+
// stops approaching bodies dead up to CONTACT_MARGIN away from anything.
|
|
800
|
+
// The push-only clamp does NOT prevent that — it only forbids a negative
|
|
801
|
+
// (pulling) impulse, not a large positive one on a body in mid-air. On a
|
|
802
|
+
// dress rig half of all contact rows are speculative and 88% of them fire,
|
|
803
|
+
// which is the field of invisible brakes the cloth was shaking against.
|
|
804
|
+
c.allowedApproachVel = c.depth < 0 ? -c.depth * invDt : 0
|
|
805
|
+
|
|
806
|
+
// Relaxation gain, from the more contended of the two bodies (see
|
|
807
|
+
// CONTACT_SOR_MIN). A lone contact keeps gain 1.0 and stays exact.
|
|
808
|
+
const contended = _rowCount[c.bodyA] > _rowCount[c.bodyB] ? _rowCount[c.bodyA] : _rowCount[c.bodyB]
|
|
809
|
+
const gain = contended > 1 ? 1 / contended : 1
|
|
810
|
+
c.sorGain = gain < CONTACT_SOR_MIN ? CONTACT_SOR_MIN : gain
|
|
811
|
+
|
|
751
812
|
// Friction tangent basis. Pick the axis least aligned with n.
|
|
752
813
|
let t1x: number, t1y: number, t1z: number
|
|
753
814
|
if (Math.abs(nx) < 0.7071) { t1x = 0; t1y = -nz; t1z = ny }
|
|
@@ -837,7 +898,7 @@ function iterateContactRow(
|
|
|
837
898
|
if (jacInvN > 0) {
|
|
838
899
|
const nx = c.nx, ny = c.ny, nz = c.nz
|
|
839
900
|
const relVelN = dvx * nx + dvy * ny + dvz * nz
|
|
840
|
-
let dImpN = (c.bounceVel - relVelN) * jacInvN
|
|
901
|
+
let dImpN = (c.bounceVel - c.allowedApproachVel - relVelN) * jacInvN * c.sorGain
|
|
841
902
|
const oldN = c.appliedNormalImpulse
|
|
842
903
|
let newN = oldN + dImpN
|
|
843
904
|
if (newN < 0) { newN = 0; dImpN = -oldN }
|
|
@@ -907,7 +968,7 @@ function applyFrictionTangent(
|
|
|
907
968
|
): void {
|
|
908
969
|
if (jacInv <= 0) return
|
|
909
970
|
const relVel = dvx * tx + dvy * ty + dvz * tz
|
|
910
|
-
let dImp = -relVel * jacInv
|
|
971
|
+
let dImp = -relVel * jacInv * c.sorGain
|
|
911
972
|
const old = slot === 1 ? c.appliedFrictionImpulse1 : c.appliedFrictionImpulse2
|
|
912
973
|
let next = old + dImp
|
|
913
974
|
if (next < -muNormal) { next = -muNormal; dImp = next - old }
|