reze-engine 0.16.1 → 0.16.3
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 +139 -284
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +5 -5
- package/dist/model.d.ts +4 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +14 -1
- package/dist/physics/body.d.ts +1 -0
- package/dist/physics/body.d.ts.map +1 -1
- package/dist/physics/body.js +2 -0
- package/dist/physics/constraint.d.ts +8 -1
- package/dist/physics/constraint.d.ts.map +1 -1
- package/dist/physics/constraint.js +13 -1
- package/dist/physics/physics.d.ts +15 -1
- package/dist/physics/physics.d.ts.map +1 -1
- package/dist/physics/physics.js +413 -48
- package/dist/physics/solver.d.ts.map +1 -1
- package/dist/physics/solver.js +223 -39
- package/dist/physics/types.d.ts +2 -1
- package/dist/physics/types.d.ts.map +1 -1
- package/dist/physics/types.js +5 -0
- package/dist/physics/world.d.ts +3 -0
- package/dist/physics/world.d.ts.map +1 -1
- package/dist/physics/world.js +31 -4
- package/dist/physics-debug.d.ts +30 -0
- package/dist/physics-debug.d.ts.map +1 -0
- package/dist/physics-debug.js +526 -0
- package/dist/pmx-loader.d.ts +2 -0
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +37 -20
- package/dist/shaders/materials/body.d.ts +1 -1
- package/dist/shaders/materials/body.d.ts.map +1 -1
- package/dist/shaders/materials/body.js +90 -85
- package/dist/shaders/materials/cloth_rough.d.ts +1 -1
- package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_rough.js +1 -1
- package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
- package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_smooth.js +2 -2
- package/dist/shaders/materials/common.d.ts +1 -1
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +10 -0
- package/dist/shaders/materials/default.d.ts +1 -1
- package/dist/shaders/materials/default.d.ts.map +1 -1
- package/dist/shaders/materials/default.js +37 -32
- package/dist/shaders/materials/eye.d.ts +1 -1
- package/dist/shaders/materials/eye.d.ts.map +1 -1
- package/dist/shaders/materials/eye.js +40 -35
- package/dist/shaders/materials/face.d.ts +1 -1
- package/dist/shaders/materials/face.d.ts.map +1 -1
- package/dist/shaders/materials/face.js +90 -85
- package/dist/shaders/materials/hair.d.ts +1 -1
- package/dist/shaders/materials/hair.d.ts.map +1 -1
- package/dist/shaders/materials/hair.js +85 -75
- package/dist/shaders/materials/metal.d.ts +1 -1
- package/dist/shaders/materials/metal.d.ts.map +1 -1
- package/dist/shaders/materials/metal.js +1 -1
- package/dist/shaders/materials/stockings.d.ts +1 -1
- package/dist/shaders/materials/stockings.d.ts.map +1 -1
- package/dist/shaders/materials/stockings.js +1 -1
- package/dist/shaders/passes/physics-debug.d.ts +2 -0
- package/dist/shaders/passes/physics-debug.d.ts.map +1 -0
- package/dist/shaders/passes/physics-debug.js +69 -0
- package/package.json +1 -1
- package/src/engine.ts +36 -21
- package/src/model.ts +18 -1
- package/src/physics/body.ts +5 -0
- package/src/physics/constraint.ts +35 -6
- package/src/physics/physics.ts +423 -56
- package/src/physics/solver.ts +213 -38
- package/src/physics/types.ts +10 -2
- package/src/physics/world.ts +33 -4
- package/src/pmx-loader.ts +38 -20
- package/src/shaders/materials/body.ts +97 -92
- package/src/shaders/materials/cloth_rough.ts +1 -1
- package/src/shaders/materials/cloth_smooth.ts +2 -2
- package/src/shaders/materials/common.ts +10 -0
- package/src/shaders/materials/default.ts +45 -40
- package/src/shaders/materials/eye.ts +48 -43
- package/src/shaders/materials/face.ts +97 -92
- package/src/shaders/materials/hair.ts +92 -82
- package/src/shaders/materials/metal.ts +1 -1
- package/src/shaders/materials/stockings.ts +1 -1
- package/dist/gpu-profile.d.ts +0 -19
- package/dist/gpu-profile.d.ts.map +0 -1
- package/dist/gpu-profile.js +0 -120
- package/dist/physics/profile.d.ts +0 -18
- package/dist/physics/profile.d.ts.map +0 -1
- package/dist/physics/profile.js +0 -44
package/dist/physics/solver.js
CHANGED
|
@@ -13,12 +13,28 @@
|
|
|
13
13
|
import { Mat4 } from "../math";
|
|
14
14
|
import { STOP_ERP } from "./constraint";
|
|
15
15
|
const BOUNCE_THRESHOLD = 2.0;
|
|
16
|
+
// Ceilings on limit-correction velocity. In normal operation limit errors are
|
|
17
|
+
// tiny; a large error only appears after a discontinuity (teleport, stall,
|
|
18
|
+
// deep penetration), and feeding err·ERP/dt to the solver unclamped then
|
|
19
|
+
// injects explosion-scale impulses into the chain.
|
|
20
|
+
const MAX_LINEAR_CORRECTION_VEL = 120; // units/s
|
|
21
|
+
const MAX_ANGULAR_CORRECTION_VEL = 30; // rad/s
|
|
22
|
+
// Bullet's limit-motor softness defaults (0.7 translational, 0.5 rotational):
|
|
23
|
+
// scale each iteration's limit impulse so the stop engages progressively
|
|
24
|
+
// instead of as a hard velocity snap.
|
|
25
|
+
const LIMIT_SOFTNESS_LINEAR = 0.7;
|
|
26
|
+
const LIMIT_SOFTNESS_ANGULAR = 0.5;
|
|
27
|
+
// Angular limit violations below this switch to per-axis euler rows; above
|
|
28
|
+
// it, the single geodesic row takes over (see setupConstraint).
|
|
29
|
+
const GEODESIC_THRESHOLD = 0.5; // rad
|
|
16
30
|
// Module-level scratch (no per-iter allocations).
|
|
17
31
|
const _TA = new Float32Array(16);
|
|
18
32
|
const _TB = new Float32Array(16);
|
|
19
33
|
const _bodyMatA = new Float32Array(16);
|
|
20
34
|
const _bodyMatB = new Float32Array(16);
|
|
21
35
|
const _angDiffScratch = new Float32Array(3);
|
|
36
|
+
const _quatScratchA = new Float32Array(4);
|
|
37
|
+
const _quatScratchB = new Float32Array(4);
|
|
22
38
|
export function solveConstraints(store, constraints, contacts, dt, iterations) {
|
|
23
39
|
if (dt <= 0)
|
|
24
40
|
return;
|
|
@@ -60,21 +76,23 @@ function setupConstraint(con, store, dt, invDt) {
|
|
|
60
76
|
buildBodyMat(store, b, _bodyMatB);
|
|
61
77
|
Mat4.multiplyArrays(_bodyMatA, 0, con.frameA, 0, _TA, 0);
|
|
62
78
|
Mat4.multiplyArrays(_bodyMatB, 0, con.frameB, 0, _TB, 0);
|
|
63
|
-
//
|
|
79
|
+
// Per-body pivots at each body's own joint-frame origin (Spring2-style).
|
|
80
|
+
// Bullet 2.7x's shared mass-weighted anchor (m_AnchorPos) degenerates when
|
|
81
|
+
// the joint is violated by a large distance: the midpoint sits far from
|
|
82
|
+
// both bodies, the lever arms grow with the separation, the Jacobian
|
|
83
|
+
// denominator blows up as err²·invInertia, and the row applies torque
|
|
84
|
+
// instead of closing velocity — the joint "breaks" and the error runs
|
|
85
|
+
// away. Per-body pivots keep the levers bounded by the frame offsets, so
|
|
86
|
+
// the row stays effective no matter how large the violation is.
|
|
64
87
|
const pos = store.positions;
|
|
65
88
|
const ai = a * 3;
|
|
66
89
|
const bi = b * 3;
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const rAy = anchorY - pos[ai + 1];
|
|
74
|
-
const rAz = anchorZ - pos[ai + 2];
|
|
75
|
-
const rBx = anchorX - pos[bi + 0];
|
|
76
|
-
const rBy = anchorY - pos[bi + 1];
|
|
77
|
-
const rBz = anchorZ - pos[bi + 2];
|
|
90
|
+
const rAx = _TA[12] - pos[ai + 0];
|
|
91
|
+
const rAy = _TA[13] - pos[ai + 1];
|
|
92
|
+
const rAz = _TA[14] - pos[ai + 2];
|
|
93
|
+
const rBx = _TB[12] - pos[bi + 0];
|
|
94
|
+
const rBy = _TB[13] - pos[bi + 1];
|
|
95
|
+
const rBz = _TB[14] - pos[bi + 2];
|
|
78
96
|
const lA = con.cacheLeverA;
|
|
79
97
|
const lB = con.cacheLeverB;
|
|
80
98
|
lA[0] = rAx;
|
|
@@ -123,6 +141,8 @@ function setupConstraint(con, store, dt, invDt) {
|
|
|
123
141
|
const lo = con.linearMin[i];
|
|
124
142
|
const hi = con.linearMax[i];
|
|
125
143
|
const curr = i === 0 ? linDiff0 : i === 1 ? linDiff1 : linDiff2;
|
|
144
|
+
// active: 1 = bilateral equality (locked axis — a joint, always on),
|
|
145
|
+
// 2 = unilateral stop (ranged axis in violation).
|
|
126
146
|
let target = 0;
|
|
127
147
|
let active = 0;
|
|
128
148
|
if (lo <= hi) {
|
|
@@ -131,17 +151,37 @@ function setupConstraint(con, store, dt, invDt) {
|
|
|
131
151
|
err = curr - lo;
|
|
132
152
|
else if (curr > hi)
|
|
133
153
|
err = curr - hi;
|
|
154
|
+
if (lo === hi)
|
|
155
|
+
active = 1;
|
|
156
|
+
else if (err !== 0)
|
|
157
|
+
active = 2;
|
|
134
158
|
if (err !== 0) {
|
|
135
159
|
target = -err * STOP_ERP * invDt;
|
|
136
|
-
|
|
160
|
+
if (target > MAX_LINEAR_CORRECTION_VEL)
|
|
161
|
+
target = MAX_LINEAR_CORRECTION_VEL;
|
|
162
|
+
else if (target < -MAX_LINEAR_CORRECTION_VEL)
|
|
163
|
+
target = -MAX_LINEAR_CORRECTION_VEL;
|
|
137
164
|
}
|
|
138
165
|
}
|
|
139
|
-
if (con.springEnabled[i]) {
|
|
140
|
-
target += -con.springStiffness[i] * (curr - con.equilibriumPoint[i]) * dt;
|
|
141
|
-
active = 1;
|
|
142
|
-
}
|
|
143
166
|
tgt[i] = target;
|
|
144
167
|
act[i] = denom > 0 ? active : 0;
|
|
168
|
+
con.cacheLinLimitImp[i] = 0;
|
|
169
|
+
// A spring on a locked axis is redundant — the bilateral limit row
|
|
170
|
+
// already welds the DOF, and driving it twice overshoots every
|
|
171
|
+
// iteration (PMX rigs routinely put k=100000 springs on locked axes,
|
|
172
|
+
// which turned welded weight-bodies into energy pumps).
|
|
173
|
+
if (con.springEnabled[i] && denom > 0 && lo !== hi) {
|
|
174
|
+
// Clamp k to the deadbeat limit: an explicit spring with k·dt² > 1
|
|
175
|
+
// overshoots equilibrium every step and pumps energy — the classic
|
|
176
|
+
// pre-Spring2 Bullet 6dof instability this port inherited.
|
|
177
|
+
const k = Math.min(con.springStiffness[i], invDt * invDt);
|
|
178
|
+
const serr = curr - con.equilibriumPoint[i];
|
|
179
|
+
con.cacheLinSpringTarget[i] = -k * serr * dt;
|
|
180
|
+
con.cacheLinSpringActive[i] = 1;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
con.cacheLinSpringActive[i] = 0;
|
|
184
|
+
}
|
|
145
185
|
}
|
|
146
186
|
// Angular: TA^T · TB → Euler XYZ; axes from TA.col2 × TB.col0.
|
|
147
187
|
const r00 = _TA[0] * _TB[0] + _TA[1] * _TB[1] + _TA[2] * _TB[2];
|
|
@@ -196,34 +236,105 @@ function setupConstraint(con, store, dt, invDt) {
|
|
|
196
236
|
angAxes[8] = zz;
|
|
197
237
|
const angDenom = iiA + iiB;
|
|
198
238
|
con.cacheAngJacInv = angDenom > 0 ? 1 / angDenom : 0;
|
|
239
|
+
// Per-axis rows carry only the springs. Sign flip vs linear:
|
|
240
|
+
// d(angDiff)/dt = −(ω_B − ω_A)·ax.
|
|
199
241
|
const angTgt = con.cacheAngTargetVel;
|
|
200
242
|
const angAct = con.cacheAngActive;
|
|
201
243
|
for (let i = 0; i < 3; i++) {
|
|
202
244
|
const idx = i + 3;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
245
|
+
// Springs on locked axes are skipped — the limit row welds those, and
|
|
246
|
+
// double-driving a DOF overshoots every iteration (see the linear loop).
|
|
247
|
+
if (con.springEnabled[idx] && angDenom > 0 && con.angularMin[i] !== con.angularMax[i]) {
|
|
248
|
+
// Same deadbeat clamp as the linear springs.
|
|
249
|
+
const k = Math.min(con.springStiffness[idx], invDt * invDt);
|
|
250
|
+
const serr = _angDiffScratch[i] - con.equilibriumPoint[idx];
|
|
251
|
+
angTgt[i] = k * serr * dt;
|
|
252
|
+
angAct[i] = 1;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
angTgt[i] = 0;
|
|
256
|
+
angAct[i] = 0;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Angular limit handling is hybrid. Small violations (the resting-cloth
|
|
260
|
+
// regime) use per-axis euler rows — they converge cleanly and keep resting
|
|
261
|
+
// cloth dead still. Large violations switch to a single geodesic row toward
|
|
262
|
+
// the euler-clamped target: per-axis euler rows (the Bullet-2.7x approach
|
|
263
|
+
// this port used) become geometrically inconsistent for large errors — near
|
|
264
|
+
// the asin singularity they chase phantom errors and pump angular velocity
|
|
265
|
+
// into the chain instead of converging.
|
|
266
|
+
con.cacheAngLimActive = 0;
|
|
267
|
+
if (angDenom > 0) {
|
|
268
|
+
const ex = _angDiffScratch[0], ey = _angDiffScratch[1], ez = _angDiffScratch[2];
|
|
269
|
+
// Free axes (min > max) follow the current angle, i.e. no correction.
|
|
270
|
+
let tx = ex, ty = ey, tz = ez;
|
|
271
|
+
if (con.angularMin[0] <= con.angularMax[0])
|
|
272
|
+
tx = ex < con.angularMin[0] ? con.angularMin[0] : ex > con.angularMax[0] ? con.angularMax[0] : ex;
|
|
273
|
+
if (con.angularMin[1] <= con.angularMax[1])
|
|
274
|
+
ty = ey < con.angularMin[1] ? con.angularMin[1] : ey > con.angularMax[1] ? con.angularMax[1] : ey;
|
|
275
|
+
if (con.angularMin[2] <= con.angularMax[2])
|
|
276
|
+
tz = ez < con.angularMin[2] ? con.angularMin[2] : ez > con.angularMax[2] ? con.angularMax[2] : ez;
|
|
277
|
+
const errX = ex - tx, errY = ey - ty, errZ = ez - tz;
|
|
278
|
+
const maxErr = Math.max(Math.abs(errX), Math.abs(errY), Math.abs(errZ));
|
|
279
|
+
if (maxErr > 0 && maxErr < GEODESIC_THRESHOLD) {
|
|
280
|
+
// Per-axis euler limit rows, folded into the per-axis row set (the
|
|
281
|
+
// spring impulse clamp must not bound a limit row — lift it).
|
|
282
|
+
for (let i = 0; i < 3; i++) {
|
|
283
|
+
const err = i === 0 ? errX : i === 1 ? errY : errZ;
|
|
284
|
+
if (err === 0)
|
|
285
|
+
continue;
|
|
286
|
+
let target = err * STOP_ERP * invDt;
|
|
287
|
+
if (target > MAX_ANGULAR_CORRECTION_VEL)
|
|
288
|
+
target = MAX_ANGULAR_CORRECTION_VEL;
|
|
289
|
+
else if (target < -MAX_ANGULAR_CORRECTION_VEL)
|
|
290
|
+
target = -MAX_ANGULAR_CORRECTION_VEL;
|
|
291
|
+
angTgt[i] += target;
|
|
292
|
+
angAct[i] = 1;
|
|
218
293
|
}
|
|
219
294
|
}
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
295
|
+
else if (maxErr > 0) {
|
|
296
|
+
// Bilateral (equality) if any violated axis is locked — a locked axis
|
|
297
|
+
// is a joint, not a stop. Unilateral otherwise.
|
|
298
|
+
const bilateral = (tx !== ex && con.angularMin[0] === con.angularMax[0]) ||
|
|
299
|
+
(ty !== ey && con.angularMin[1] === con.angularMax[1]) ||
|
|
300
|
+
(tz !== ez && con.angularMin[2] === con.angularMax[2]);
|
|
301
|
+
// The decomposition above satisfies R_rel^T = Rx(x)·Ry(y)·Rz(z), so
|
|
302
|
+
// u = qx·qy·qz is conj(q_rel) and the error rotation (current →
|
|
303
|
+
// clamped target, expressed in TA's frame) is q_E = conj(u_t) ⊗ u.
|
|
304
|
+
eulerXYZQuatInto(ex, ey, ez, _quatScratchA);
|
|
305
|
+
eulerXYZQuatInto(tx, ty, tz, _quatScratchB);
|
|
306
|
+
const ux = _quatScratchA[0], uy = _quatScratchA[1], uz = _quatScratchA[2], uw = _quatScratchA[3];
|
|
307
|
+
const vx = _quatScratchB[0], vy = _quatScratchB[1], vz = _quatScratchB[2], vw = _quatScratchB[3];
|
|
308
|
+
// q_E = conj(v) ⊗ u
|
|
309
|
+
let qex = vw * ux - vx * uw - vy * uz + vz * uy;
|
|
310
|
+
let qey = vw * uy + vx * uz - vy * uw - vz * ux;
|
|
311
|
+
let qez = vw * uz - vx * uy + vy * ux - vz * uw;
|
|
312
|
+
let qew = vw * uw + vx * ux + vy * uy + vz * uz;
|
|
313
|
+
if (qew < 0) {
|
|
314
|
+
qex = -qex;
|
|
315
|
+
qey = -qey;
|
|
316
|
+
qez = -qez;
|
|
317
|
+
qew = -qew;
|
|
318
|
+
}
|
|
319
|
+
const sinHalf = Math.sqrt(qex * qex + qey * qey + qez * qez);
|
|
320
|
+
if (sinHalf > 1e-6) {
|
|
321
|
+
const angle = 2 * Math.atan2(sinHalf, qew);
|
|
322
|
+
const invS = 1 / sinHalf;
|
|
323
|
+
const axx = qex * invS, axy = qey * invS, axz = qez * invS;
|
|
324
|
+
// Axis lives in TA's frame; TA's basis columns map it to world.
|
|
325
|
+
const lim = con.cacheAngLimAxis;
|
|
326
|
+
lim[0] = _TA[0] * axx + _TA[4] * axy + _TA[8] * axz;
|
|
327
|
+
lim[1] = _TA[1] * axx + _TA[5] * axy + _TA[9] * axz;
|
|
328
|
+
lim[2] = _TA[2] * axx + _TA[6] * axy + _TA[10] * axz;
|
|
329
|
+
let target = angle * STOP_ERP * invDt;
|
|
330
|
+
if (target > MAX_ANGULAR_CORRECTION_VEL)
|
|
331
|
+
target = MAX_ANGULAR_CORRECTION_VEL;
|
|
332
|
+
con.cacheAngLimTarget = target;
|
|
333
|
+
con.cacheAngLimActive = bilateral ? 1 : 2;
|
|
334
|
+
}
|
|
223
335
|
}
|
|
224
|
-
angTgt[i] = target;
|
|
225
|
-
angAct[i] = angDenom > 0 ? active : 0;
|
|
226
336
|
}
|
|
337
|
+
con.cacheAngLimImp = 0;
|
|
227
338
|
}
|
|
228
339
|
// ITER: read cache, compute relVel from current lv/av, apply impulse.
|
|
229
340
|
function iterateConstraint(con, lv, av, invMass, invInertia) {
|
|
@@ -257,13 +368,42 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
|
|
|
257
368
|
const dvx = vBx - vAx;
|
|
258
369
|
const dvy = vBy - vAy;
|
|
259
370
|
const dvz = vBz - vAz;
|
|
371
|
+
const sprAct = con.cacheLinSpringActive;
|
|
372
|
+
const sprTgt = con.cacheLinSpringTarget;
|
|
373
|
+
const limImp = con.cacheLinLimitImp;
|
|
260
374
|
for (let i = 0; i < 3; i++) {
|
|
261
|
-
if (!act[i])
|
|
375
|
+
if (!act[i] && !sprAct[i])
|
|
262
376
|
continue;
|
|
263
377
|
const o = i * 3;
|
|
264
378
|
const axx = axes[o + 0], axy = axes[o + 1], axz = axes[o + 2];
|
|
265
379
|
const relVel = dvx * axx + dvy * axy + dvz * axz;
|
|
266
|
-
|
|
380
|
+
let j = 0;
|
|
381
|
+
// Limit row. Locked axes (act 1) are bilateral equality joints; ranged
|
|
382
|
+
// axes in violation (act 2) are unilateral stops — accumulated impulse
|
|
383
|
+
// clamped to the corrective sign, so the stop pushes back into range but
|
|
384
|
+
// never pulls deeper or brakes natural recovery (a bilateral stop acts
|
|
385
|
+
// as a motor and pumps energy into swinging cloth).
|
|
386
|
+
if (act[i]) {
|
|
387
|
+
const target = tgt[i];
|
|
388
|
+
let dImp = LIMIT_SOFTNESS_LINEAR * (target - relVel) * jac[i];
|
|
389
|
+
if (act[i] === 2) {
|
|
390
|
+
const old = limImp[i];
|
|
391
|
+
let next = old + dImp;
|
|
392
|
+
if (target > 0 ? next < 0 : next > 0)
|
|
393
|
+
next = 0;
|
|
394
|
+
dImp = next - old;
|
|
395
|
+
limImp[i] = next;
|
|
396
|
+
}
|
|
397
|
+
j += dImp;
|
|
398
|
+
}
|
|
399
|
+
// Spring row: velocity-target drive; the deadbeat k-clamp at setup
|
|
400
|
+
// bounds its aggression. relVel is refreshed with the limit impulse
|
|
401
|
+
// applied just above (j·denom = j / jac) — driving the spring off the
|
|
402
|
+
// stale value double-corrects the DOF and overshoots every iteration.
|
|
403
|
+
if (sprAct[i]) {
|
|
404
|
+
const relVelNow = j !== 0 ? relVel + j / jac[i] : relVel;
|
|
405
|
+
j += (sprTgt[i] - relVelNow) * jac[i];
|
|
406
|
+
}
|
|
267
407
|
if (j === 0)
|
|
268
408
|
continue;
|
|
269
409
|
if (imA > 0) {
|
|
@@ -299,6 +439,8 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
|
|
|
299
439
|
const o = i * 3;
|
|
300
440
|
const axx = angAxes[o + 0], axy = angAxes[o + 1], axz = angAxes[o + 2];
|
|
301
441
|
const relAv = dax * axx + day * axy + daz * axz;
|
|
442
|
+
// Spring drive (deadbeat-clamped at setup) plus, for small violations,
|
|
443
|
+
// the folded per-axis limit correction.
|
|
302
444
|
const j = (angTgt[i] - relAv) * angJacInv;
|
|
303
445
|
if (j === 0)
|
|
304
446
|
continue;
|
|
@@ -313,6 +455,38 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
|
|
|
313
455
|
av[bi + 2] += j * iiB * axz;
|
|
314
456
|
}
|
|
315
457
|
}
|
|
458
|
+
// Geodesic limit row: drive (ω_B − ω_A)·axis toward the correction target.
|
|
459
|
+
// Unilateral — the accumulated impulse can only push toward the legal
|
|
460
|
+
// region (target is always ≥ 0 along the corrective axis).
|
|
461
|
+
if (con.cacheAngLimActive) {
|
|
462
|
+
const lim = con.cacheAngLimAxis;
|
|
463
|
+
const nx = lim[0], ny = lim[1], nz = lim[2];
|
|
464
|
+
// Re-read relAv — the spring rows above may have changed av.
|
|
465
|
+
const relAv = (av[bi + 0] - av[ai + 0]) * nx +
|
|
466
|
+
(av[bi + 1] - av[ai + 1]) * ny +
|
|
467
|
+
(av[bi + 2] - av[ai + 2]) * nz;
|
|
468
|
+
let j = LIMIT_SOFTNESS_ANGULAR * (con.cacheAngLimTarget - relAv) * angJacInv;
|
|
469
|
+
if (con.cacheAngLimActive === 2) {
|
|
470
|
+
const old = con.cacheAngLimImp;
|
|
471
|
+
let next = old + j;
|
|
472
|
+
if (next < 0)
|
|
473
|
+
next = 0;
|
|
474
|
+
j = next - old;
|
|
475
|
+
con.cacheAngLimImp = next;
|
|
476
|
+
}
|
|
477
|
+
if (j !== 0) {
|
|
478
|
+
if (iiA > 0) {
|
|
479
|
+
av[ai + 0] -= j * iiA * nx;
|
|
480
|
+
av[ai + 1] -= j * iiA * ny;
|
|
481
|
+
av[ai + 2] -= j * iiA * nz;
|
|
482
|
+
}
|
|
483
|
+
if (iiB > 0) {
|
|
484
|
+
av[bi + 0] += j * iiB * nx;
|
|
485
|
+
av[bi + 1] += j * iiB * ny;
|
|
486
|
+
av[bi + 2] += j * iiB * nz;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
316
490
|
}
|
|
317
491
|
// SETUP: pre-compute Jacobians, friction basis, and the bounce reference
|
|
318
492
|
// from the *initial* closing velocity (Bullet's pattern — captures restitution
|
|
@@ -537,6 +711,16 @@ function buildBodyMat(store, i, out) {
|
|
|
537
711
|
const i3 = i * 3, i4 = i * 4;
|
|
538
712
|
Mat4.fromPositionRotationInto(store.positions[i3 + 0], store.positions[i3 + 1], store.positions[i3 + 2], store.orientations[i4 + 0], store.orientations[i4 + 1], store.orientations[i4 + 2], store.orientations[i4 + 3], out);
|
|
539
713
|
}
|
|
714
|
+
// Quaternion of qx(x) ⊗ qy(y) ⊗ qz(z) (three.js 'XYZ' order).
|
|
715
|
+
function eulerXYZQuatInto(x, y, z, out) {
|
|
716
|
+
const sx = Math.sin(x * 0.5), cx = Math.cos(x * 0.5);
|
|
717
|
+
const sy = Math.sin(y * 0.5), cy = Math.cos(y * 0.5);
|
|
718
|
+
const sz = Math.sin(z * 0.5), cz = Math.cos(z * 0.5);
|
|
719
|
+
out[0] = sx * cy * cz + cx * sy * sz;
|
|
720
|
+
out[1] = cx * sy * cz - sx * cy * sz;
|
|
721
|
+
out[2] = cx * cy * sz + sx * sy * cz;
|
|
722
|
+
out[3] = cx * cy * cz - sx * sy * sz;
|
|
723
|
+
}
|
|
540
724
|
// Euler XYZ from a 3×3 rotation matrix (row-major elements).
|
|
541
725
|
function matrixToEulerXYZ(r00, r01, r10, r11, r20, r21, r22, out) {
|
|
542
726
|
if (r20 < 1) {
|
package/dist/physics/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare enum RigidbodyShape {
|
|
|
5
5
|
Capsule = 2
|
|
6
6
|
}
|
|
7
7
|
export declare enum RigidbodyType {
|
|
8
|
-
Static = 0
|
|
8
|
+
Static = 0,// follows bone (anchor)
|
|
9
9
|
Dynamic = 1,
|
|
10
10
|
Kinematic = 2
|
|
11
11
|
}
|
|
@@ -25,6 +25,7 @@ export interface Rigidbody {
|
|
|
25
25
|
restitution: number;
|
|
26
26
|
friction: number;
|
|
27
27
|
type: RigidbodyType;
|
|
28
|
+
aligned?: boolean;
|
|
28
29
|
bodyOffsetMatrixInverse: Mat4;
|
|
29
30
|
bodyOffsetMatrix?: Mat4;
|
|
30
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/physics/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAEpC,oBAAY,cAAc;IACxB,MAAM,IAAI;IACV,GAAG,IAAI;IACP,OAAO,IAAI;CACZ;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/physics/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAEpC,oBAAY,cAAc;IACxB,MAAM,IAAI;IACV,GAAG,IAAI;IACP,OAAO,IAAI;CACZ;AAOD,oBAAY,aAAa;IACvB,MAAM,IAAI,CAAE,wBAAwB;IACpC,OAAO,IAAI;IACX,SAAS,IAAI;CACd;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,cAAc,CAAA;IACrB,IAAI,EAAE,IAAI,CAAA;IACV,aAAa,EAAE,IAAI,CAAA;IACnB,aAAa,EAAE,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,aAAa,CAAA;IAGnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,uBAAuB,EAAE,IAAI,CAAA;IAC7B,gBAAgB,CAAC,EAAE,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,IAAI,CAAA;IACd,QAAQ,EAAE,IAAI,CAAA;IACd,WAAW,EAAE,IAAI,CAAA;IACjB,WAAW,EAAE,IAAI,CAAA;IACjB,WAAW,EAAE,IAAI,CAAA;IACjB,WAAW,EAAE,IAAI,CAAA;IACjB,cAAc,EAAE,IAAI,CAAA;IACpB,cAAc,EAAE,IAAI,CAAA;CACrB"}
|
package/dist/physics/types.js
CHANGED
|
@@ -4,6 +4,11 @@ export var RigidbodyShape;
|
|
|
4
4
|
RigidbodyShape[RigidbodyShape["Box"] = 1] = "Box";
|
|
5
5
|
RigidbodyShape[RigidbodyShape["Capsule"] = 2] = "Capsule";
|
|
6
6
|
})(RigidbodyShape || (RigidbodyShape = {}));
|
|
7
|
+
// PMX physics mode: 0 = follow bone, 1 = physics, 2 = physics + bone-position
|
|
8
|
+
// alignment. Mode 2 is DYNAMIC — the loader maps it to Dynamic with
|
|
9
|
+
// `aligned: true` (bone gets rotation only, body position re-pins to the
|
|
10
|
+
// animated bone each frame). Mapping the raw byte 1:1 onto this enum froze
|
|
11
|
+
// mode-2 bodies (most modern cloth rigs, and every 胸_回転 breast body).
|
|
7
12
|
export var RigidbodyType;
|
|
8
13
|
(function (RigidbodyType) {
|
|
9
14
|
RigidbodyType[RigidbodyType["Static"] = 0] = "Static";
|
package/dist/physics/world.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ import { type ContactPool } from "./contact";
|
|
|
5
5
|
export declare class World {
|
|
6
6
|
readonly gravity: Vec3;
|
|
7
7
|
solverIterations: number;
|
|
8
|
+
private dampCacheDt;
|
|
9
|
+
private linDampFactor;
|
|
10
|
+
private angDampFactor;
|
|
8
11
|
constructor(gravity: Vec3);
|
|
9
12
|
setGravity(g: Vec3): void;
|
|
10
13
|
step(store: RigidBodyStore, constraints: SixDofSpringConstraint[], contacts: ContactPool, dt: number): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../../src/physics/world.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAE5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAO1D,qBAAa,KAAK;IAChB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;IACtB,gBAAgB,SAAK;
|
|
1
|
+
{"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../../src/physics/world.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAE5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAO1D,qBAAa,KAAK;IAChB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;IACtB,gBAAgB,SAAK;IAIrB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,aAAa,CAA4B;gBAErC,OAAO,EAAE,IAAI;IAIzB,UAAU,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI;IAMzB,IAAI,CAAC,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,sBAAsB,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CAoJ5G"}
|
package/dist/physics/world.js
CHANGED
|
@@ -10,6 +10,11 @@ import { findContacts } from "./contact";
|
|
|
10
10
|
export class World {
|
|
11
11
|
constructor(gravity) {
|
|
12
12
|
this.solverIterations = 10;
|
|
13
|
+
// Per-body damping factors pow(1−damping, dt), cached because damping and
|
|
14
|
+
// the fixed dt never change — two Math.pow per body per substep otherwise.
|
|
15
|
+
this.dampCacheDt = -1;
|
|
16
|
+
this.linDampFactor = null;
|
|
17
|
+
this.angDampFactor = null;
|
|
13
18
|
this.gravity = new Vec3(gravity.x, gravity.y, gravity.z);
|
|
14
19
|
}
|
|
15
20
|
setGravity(g) {
|
|
@@ -34,7 +39,18 @@ export class World {
|
|
|
34
39
|
const gz = this.gravity.z;
|
|
35
40
|
// 1. Predict — gravity + damping. The pow form (vs the linear
|
|
36
41
|
// 1−damping·dt approximation) stays stable at high PMX damping
|
|
37
|
-
// values like 0.99.
|
|
42
|
+
// values like 0.99. Factors are cached (damping and dt are constant).
|
|
43
|
+
if (this.dampCacheDt !== dt || !this.linDampFactor || this.linDampFactor.length !== N) {
|
|
44
|
+
this.dampCacheDt = dt;
|
|
45
|
+
this.linDampFactor = new Float32Array(N);
|
|
46
|
+
this.angDampFactor = new Float32Array(N);
|
|
47
|
+
for (let i = 0; i < N; i++) {
|
|
48
|
+
this.linDampFactor[i] = Math.pow(Math.max(0, 1 - ldamp[i]), dt);
|
|
49
|
+
this.angDampFactor[i] = Math.pow(Math.max(0, 1 - adamp[i]), dt);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const linDamp = this.linDampFactor;
|
|
53
|
+
const angDamp = this.angDampFactor;
|
|
38
54
|
for (let i = 0; i < N; i++) {
|
|
39
55
|
if (types[i] !== RigidbodyType.Dynamic || invMass[i] <= 0)
|
|
40
56
|
continue;
|
|
@@ -42,8 +58,8 @@ export class World {
|
|
|
42
58
|
lv[i3 + 0] += gx * dt;
|
|
43
59
|
lv[i3 + 1] += gy * dt;
|
|
44
60
|
lv[i3 + 2] += gz * dt;
|
|
45
|
-
const ld =
|
|
46
|
-
const ad =
|
|
61
|
+
const ld = linDamp[i];
|
|
62
|
+
const ad = angDamp[i];
|
|
47
63
|
lv[i3 + 0] *= ld;
|
|
48
64
|
lv[i3 + 1] *= ld;
|
|
49
65
|
lv[i3 + 2] *= ld;
|
|
@@ -95,13 +111,24 @@ export class World {
|
|
|
95
111
|
}
|
|
96
112
|
// 5. Integrate. Cap angular velocity at π/2 per step — a high-impulse
|
|
97
113
|
// contact spike on a low-inertia body would otherwise spin past π
|
|
98
|
-
// in one step and trash the quaternion integration.
|
|
114
|
+
// in one step and trash the quaternion integration. Linear velocity
|
|
115
|
+
// is capped at 5 units per step for the same reason: an explosion
|
|
116
|
+
// guard far above what legit cloth motion ever reaches.
|
|
99
117
|
const MAX_ANGVEL_DT = Math.PI * 0.5;
|
|
118
|
+
const MAX_LINVEL_DT = 5;
|
|
100
119
|
for (let i = 0; i < N; i++) {
|
|
101
120
|
if (types[i] !== RigidbodyType.Dynamic || invMass[i] <= 0)
|
|
102
121
|
continue;
|
|
103
122
|
const i3 = i * 3;
|
|
104
123
|
const i4 = i * 4;
|
|
124
|
+
const vx = lv[i3 + 0], vy = lv[i3 + 1], vz = lv[i3 + 2];
|
|
125
|
+
const vmag = Math.sqrt(vx * vx + vy * vy + vz * vz);
|
|
126
|
+
if (vmag * dt > MAX_LINVEL_DT) {
|
|
127
|
+
const scale = MAX_LINVEL_DT / (vmag * dt);
|
|
128
|
+
lv[i3 + 0] = vx * scale;
|
|
129
|
+
lv[i3 + 1] = vy * scale;
|
|
130
|
+
lv[i3 + 2] = vz * scale;
|
|
131
|
+
}
|
|
105
132
|
pos[i3 + 0] += lv[i3 + 0] * dt;
|
|
106
133
|
pos[i3 + 1] += lv[i3 + 1] * dt;
|
|
107
134
|
pos[i3 + 2] += lv[i3 + 2] * dt;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { RezePhysics } from "./physics";
|
|
2
|
+
export declare class PhysicsDebugRenderer {
|
|
3
|
+
private device;
|
|
4
|
+
private bindGroup;
|
|
5
|
+
private wirePipelineSphere;
|
|
6
|
+
private wirePipelineBox;
|
|
7
|
+
private wirePipelineCapsule;
|
|
8
|
+
private wireSphereBuffer;
|
|
9
|
+
private wireBoxBuffer;
|
|
10
|
+
private wireCapsuleBuffer;
|
|
11
|
+
private wireSphereCount;
|
|
12
|
+
private wireBoxCount;
|
|
13
|
+
private wireCapsuleCount;
|
|
14
|
+
private solidPipelineSphere;
|
|
15
|
+
private solidPipelineBox;
|
|
16
|
+
private solidPipelineCapsule;
|
|
17
|
+
private solidSphereBuffer;
|
|
18
|
+
private solidBoxBuffer;
|
|
19
|
+
private solidCapsuleBuffer;
|
|
20
|
+
private solidSphereCount;
|
|
21
|
+
private solidBoxCount;
|
|
22
|
+
private solidCapsuleCount;
|
|
23
|
+
private instanceBuffer;
|
|
24
|
+
private instanceData;
|
|
25
|
+
private instanceCapacity;
|
|
26
|
+
constructor(device: GPUDevice, cameraUniformBuffer: GPUBuffer, presentationFormat: GPUTextureFormat);
|
|
27
|
+
render(pass: GPURenderPassEncoder, physics: RezePhysics): void;
|
|
28
|
+
destroy(): void;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=physics-debug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"physics-debug.d.ts","sourceRoot":"","sources":["../src/physics-debug.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAmB5C,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,SAAS,CAAc;IAG/B,OAAO,CAAC,kBAAkB,CAAmB;IAC7C,OAAO,CAAC,eAAe,CAAmB;IAC1C,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,gBAAgB,CAAW;IACnC,OAAO,CAAC,aAAa,CAAW;IAChC,OAAO,CAAC,iBAAiB,CAAW;IACpC,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,gBAAgB,CAAQ;IAIhC,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,oBAAoB,CAAmB;IAC/C,OAAO,CAAC,iBAAiB,CAAW;IACpC,OAAO,CAAC,cAAc,CAAW;IACjC,OAAO,CAAC,kBAAkB,CAAW;IACrC,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,iBAAiB,CAAQ;IAEjC,OAAO,CAAC,cAAc,CAAW;IACjC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,gBAAgB,CAAQ;gBAG9B,MAAM,EAAE,SAAS,EACjB,mBAAmB,EAAE,SAAS,EAC9B,kBAAkB,EAAE,gBAAgB;IA2HtC,MAAM,CAAC,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI;IAmI9D,OAAO,IAAI,IAAI;CAShB"}
|