reze-engine 0.36.0 → 0.37.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 +4 -3
- package/dist/camera.d.ts +17 -0
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +25 -0
- package/dist/engine.d.ts +94 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +250 -14
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/model.d.ts +81 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +192 -0
- package/dist/physics/autofit.d.ts +147 -0
- package/dist/physics/autofit.d.ts.map +1 -0
- package/dist/physics/autofit.js +501 -0
- package/dist/physics/contact.d.ts +12 -3
- package/dist/physics/contact.d.ts.map +1 -1
- package/dist/physics/contact.js +5 -2
- package/dist/physics/manifold.d.ts +32 -11
- package/dist/physics/manifold.d.ts.map +1 -1
- package/dist/physics/manifold.js +137 -57
- package/dist/physics/solver.d.ts +7 -1
- package/dist/physics/solver.d.ts.map +1 -1
- package/dist/physics/solver.js +536 -182
- package/dist/physics/world.d.ts +2 -0
- package/dist/physics/world.d.ts.map +1 -1
- package/dist/physics/world.js +16 -43
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +84 -44
- package/package.json +1 -1
- package/src/camera.ts +26 -0
- package/src/engine.ts +305 -18
- package/src/index.ts +11 -1
- package/src/model.ts +231 -1
- package/src/physics/contact.ts +17 -7
- package/src/physics/manifold.ts +151 -0
- package/src/physics/solver.ts +520 -184
- package/src/physics/world.ts +17 -43
- package/src/pmx-loader.ts +84 -44
package/dist/physics/manifold.js
CHANGED
|
@@ -1,68 +1,148 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Persistent contact manifolds — the cache that makes warm starting possible.
|
|
2
2
|
//
|
|
3
|
-
// Bullet keeps a btPersistentManifold per body pair
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// it
|
|
8
|
-
// account for ALL of the visible shake under animation (25 shaking bodies with
|
|
9
|
-
// contacts on, 0 with them off), so this is the mechanism that matters.
|
|
3
|
+
// Bullet 2.75 keeps a btPersistentManifold per body pair holding up to 4 points,
|
|
4
|
+
// each carrying the impulse it converged to last step. At setup the solver seeds
|
|
5
|
+
// each row with `cp.m_appliedImpulse * m_warmstartingFactor` (0.85) and applies
|
|
6
|
+
// it immediately, so a resting stack starts the substep already holding roughly
|
|
7
|
+
// the load it needs instead of rediscovering it from zero every time.
|
|
10
8
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
// That is not a nicety here: penetration recovery rides in the contact velocity
|
|
10
|
+
// row as a Baumgarte term, and a row rebuilt from zero each substep overshoots
|
|
11
|
+
// it. Warm starting on its own was measured WORSE on this engine — but that was
|
|
12
|
+
// against a solver with no bias term and a position-correction pass, which is a
|
|
13
|
+
// different system. The two are one design in Bullet and are ported as one.
|
|
14
|
+
//
|
|
15
|
+
// Points are matched by proximity in each body's own local frame, which is what
|
|
16
|
+
// btPersistentManifold does — a world-space match would drift with the body.
|
|
17
|
+
const MAX_POINTS = 4;
|
|
18
|
+
/** Match radius, in model units. Bullet's gContactBreakingThreshold is 0.02;
|
|
19
|
+
* ours is the contact margin, so a point that merely slid along a face is
|
|
20
|
+
* still recognised as the same point rather than dropped and rebuilt. */
|
|
21
|
+
const MATCH_DIST_SQ = 0.04 * 0.04;
|
|
22
|
+
export class ManifoldCache {
|
|
19
23
|
constructor() {
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
24
|
+
this.pairs = new Map();
|
|
25
|
+
this.touched = new Set();
|
|
22
26
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
static key(a, b) {
|
|
28
|
+
return a < b ? a * 65536 + b : b * 65536 + a;
|
|
29
|
+
}
|
|
30
|
+
/** Look up what this contact point converged to last substep. Returns null
|
|
31
|
+
* when it is new. */
|
|
32
|
+
find(a, b, lax, lay, laz) {
|
|
33
|
+
const list = this.pairs.get(ManifoldCache.key(a, b));
|
|
34
|
+
if (list === undefined)
|
|
35
|
+
return null;
|
|
36
|
+
let best = null;
|
|
37
|
+
let bestD = MATCH_DIST_SQ;
|
|
38
|
+
for (let i = 0; i < list.length; i++) {
|
|
39
|
+
const p = list[i];
|
|
40
|
+
const dx = p.lax - lax, dy = p.lay - lay, dz = p.laz - laz;
|
|
41
|
+
const d = dx * dx + dy * dy + dz * dz;
|
|
42
|
+
if (d < bestD) {
|
|
43
|
+
bestD = d;
|
|
44
|
+
best = p;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return best;
|
|
48
|
+
}
|
|
49
|
+
/** Record what this point converged to, for the next substep to start from. */
|
|
50
|
+
store(a, b, lax, lay, laz, lbx, lby, lbz, normalImpulse, frictionImpulse1, frictionImpulse2, age) {
|
|
51
|
+
const k = ManifoldCache.key(a, b);
|
|
52
|
+
this.touched.add(k);
|
|
53
|
+
let list = this.pairs.get(k);
|
|
54
|
+
if (list === undefined) {
|
|
55
|
+
list = [];
|
|
56
|
+
this.pairs.set(k, list);
|
|
57
|
+
}
|
|
58
|
+
// Replace the nearest existing point, else append; past MAX_POINTS drop the
|
|
59
|
+
// shallowest-held one so the manifold keeps the load-bearing corners.
|
|
60
|
+
let best = -1;
|
|
61
|
+
let bestD = MATCH_DIST_SQ;
|
|
62
|
+
for (let i = 0; i < list.length; i++) {
|
|
63
|
+
const p = list[i];
|
|
64
|
+
const dx = p.lax - lax, dy = p.lay - lay, dz = p.laz - laz;
|
|
65
|
+
const d = dx * dx + dy * dy + dz * dz;
|
|
66
|
+
if (d < bestD) {
|
|
67
|
+
bestD = d;
|
|
68
|
+
best = i;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (best < 0) {
|
|
72
|
+
if (list.length < MAX_POINTS) {
|
|
73
|
+
list.push({ lax, lay, laz, lbx, lby, lbz, normalImpulse, frictionImpulse1, frictionImpulse2, age, seen: true });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// btPersistentManifold::sortCachedPoints — when a 5th point arrives, drop
|
|
77
|
+
// whichever of the 5 leaves the largest quadrilateral. Area is what keeps
|
|
78
|
+
// a resting box from pivoting; dropping the shallowest instead can leave
|
|
79
|
+
// four nearly-collinear points that pin position but not orientation.
|
|
80
|
+
best = worstAreaIndex(list, lax, lay, laz);
|
|
43
81
|
}
|
|
82
|
+
const p = list[best];
|
|
83
|
+
p.lax = lax;
|
|
84
|
+
p.lay = lay;
|
|
85
|
+
p.laz = laz;
|
|
86
|
+
p.lbx = lbx;
|
|
87
|
+
p.lby = lby;
|
|
88
|
+
p.lbz = lbz;
|
|
89
|
+
p.normalImpulse = normalImpulse;
|
|
90
|
+
p.frictionImpulse1 = frictionImpulse1;
|
|
91
|
+
p.frictionImpulse2 = frictionImpulse2;
|
|
92
|
+
p.age = age;
|
|
93
|
+
p.seen = true;
|
|
44
94
|
}
|
|
45
|
-
/**
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const c = pool.get(i);
|
|
52
|
-
if (c.manifoldSlot >= MAX_PER_PAIR)
|
|
95
|
+
/** Drop every point not re-seen this substep, and every pair left empty.
|
|
96
|
+
* Without this a separated pair keeps handing back a stale impulse. */
|
|
97
|
+
endStep() {
|
|
98
|
+
for (const [k, list] of this.pairs) {
|
|
99
|
+
if (!this.touched.has(k)) {
|
|
100
|
+
this.pairs.delete(k);
|
|
53
101
|
continue;
|
|
54
|
-
let arr = this.b.get(c.manifoldKey);
|
|
55
|
-
if (!arr) {
|
|
56
|
-
arr = new Float32Array(MAX_PER_PAIR * STRIDE);
|
|
57
|
-
this.b.set(c.manifoldKey, arr);
|
|
58
102
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
103
|
+
let w = 0;
|
|
104
|
+
for (let i = 0; i < list.length; i++) {
|
|
105
|
+
const p = list[i];
|
|
106
|
+
if (!p.seen)
|
|
107
|
+
continue;
|
|
108
|
+
p.seen = false;
|
|
109
|
+
list[w++] = p;
|
|
110
|
+
}
|
|
111
|
+
list.length = w;
|
|
112
|
+
if (w === 0)
|
|
113
|
+
this.pairs.delete(k);
|
|
114
|
+
}
|
|
115
|
+
this.touched.clear();
|
|
116
|
+
}
|
|
117
|
+
clear() {
|
|
118
|
+
this.pairs.clear();
|
|
119
|
+
this.touched.clear();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Which of the 4 cached points to replace so the surviving quad keeps the most
|
|
123
|
+
* area once the new point joins it. */
|
|
124
|
+
function worstAreaIndex(list, nx, ny, nz) {
|
|
125
|
+
let bestIdx = 0;
|
|
126
|
+
let bestArea = -1;
|
|
127
|
+
for (let drop = 0; drop < list.length; drop++) {
|
|
128
|
+
// The quad is: the new point plus the three survivors.
|
|
129
|
+
const pts = [[nx, ny, nz]];
|
|
130
|
+
for (let i = 0; i < list.length; i++)
|
|
131
|
+
if (i !== drop)
|
|
132
|
+
pts.push([list[i].lax, list[i].lay, list[i].laz]);
|
|
133
|
+
if (pts.length < 4)
|
|
134
|
+
continue;
|
|
135
|
+
// |d0 × d1| over the diagonals — Bullet's area proxy.
|
|
136
|
+
const d0x = pts[0][0] - pts[2][0], d0y = pts[0][1] - pts[2][1], d0z = pts[0][2] - pts[2][2];
|
|
137
|
+
const d1x = pts[1][0] - pts[3][0], d1y = pts[1][1] - pts[3][1], d1z = pts[1][2] - pts[3][2];
|
|
138
|
+
const cx = d0y * d1z - d0z * d1y;
|
|
139
|
+
const cy = d0z * d1x - d0x * d1z;
|
|
140
|
+
const cz = d0x * d1y - d0y * d1x;
|
|
141
|
+
const area = cx * cx + cy * cy + cz * cz;
|
|
142
|
+
if (area > bestArea) {
|
|
143
|
+
bestArea = area;
|
|
144
|
+
bestIdx = drop;
|
|
63
145
|
}
|
|
64
|
-
const t = this.a;
|
|
65
|
-
this.a = this.b;
|
|
66
|
-
this.b = t;
|
|
67
146
|
}
|
|
147
|
+
return bestIdx;
|
|
68
148
|
}
|
package/dist/physics/solver.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { RigidBodyStore } from "./body";
|
|
2
2
|
import type { SixDofSpringConstraint } from "./constraint";
|
|
3
3
|
import type { ContactPool } from "./contact";
|
|
4
|
-
|
|
4
|
+
import type { ManifoldCache } from "./manifold";
|
|
5
|
+
export declare function solveConstraints(store: RigidBodyStore, constraints: SixDofSpringConstraint[], cache: SolverCache, contacts: ContactPool, dt: number, iterations: number, manifolds?: ManifoldCache | null): void;
|
|
6
|
+
/** Integrate the accumulated push/turn velocity straight into the transform —
|
|
7
|
+
* btSolverBody::writebackVelocity(timeStep). Never touches real momentum. */
|
|
8
|
+
export declare function applySplitImpulsePush(store: RigidBodyStore, dt: number): void;
|
|
5
9
|
export declare class SolverCache {
|
|
6
10
|
readonly F: Float32Array;
|
|
7
11
|
readonly I: Int32Array;
|
|
@@ -10,4 +14,6 @@ export declare class SolverCache {
|
|
|
10
14
|
readonly D: Float64Array;
|
|
11
15
|
constructor(constraints: SixDofSpringConstraint[]);
|
|
12
16
|
}
|
|
17
|
+
/** Store every solved row back into the manifold for the next substep. */
|
|
18
|
+
export declare function saveContactImpulses(store: RigidBodyStore, contacts: ContactPool, manifolds: ManifoldCache): void;
|
|
13
19
|
//# sourceMappingURL=solver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solver.d.ts","sourceRoot":"","sources":["../../src/physics/solver.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAC5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"solver.d.ts","sourceRoot":"","sources":["../../src/physics/solver.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAC5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,WAAW,CAAA;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAmJ/C,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,cAAc,EACrB,WAAW,EAAE,sBAAsB,EAAE,EACrC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,WAAW,EACrB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,aAAa,GAAG,IAAW,GACrC,IAAI,CA+FN;AAgDD;8EAC8E;AAC9E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CA8B7E;AAyCD,qBAAa,WAAW;IACtB,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAA;IACxB,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAA;IACtB;sEACkE;IAClE,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAA;gBACZ,WAAW,EAAE,sBAAsB,EAAE;CAUlD;AAs6BD,0EAA0E;AAC1E,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,WAAW,EACrB,SAAS,EAAE,aAAa,GACvB,IAAI,CAcN"}
|