reze-engine 0.16.1 → 0.16.2
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/physics/constraint.d.ts +7 -0
- package/dist/physics/constraint.d.ts.map +1 -1
- package/dist/physics/constraint.js +7 -0
- package/dist/physics/physics.d.ts +12 -1
- package/dist/physics/physics.d.ts.map +1 -1
- package/dist/physics/physics.js +344 -47
- package/dist/physics/solver.d.ts.map +1 -1
- package/dist/physics/solver.js +215 -39
- package/dist/physics/world.d.ts.map +1 -1
- package/dist/physics/world.js +12 -1
- 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/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/physics/constraint.ts +29 -5
- package/src/physics/physics.ts +355 -55
- package/src/physics/solver.ts +205 -38
- package/src/physics/world.ts +13 -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,34 @@ 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
|
+
if (con.springEnabled[i] && denom > 0) {
|
|
170
|
+
// Clamp k to the deadbeat limit: an explicit spring with k·dt² > 1
|
|
171
|
+
// overshoots equilibrium every step and pumps energy — the classic
|
|
172
|
+
// pre-Spring2 Bullet 6dof instability this port inherited. (¼ margin
|
|
173
|
+
// for Gauss-Seidel coupling in chains.)
|
|
174
|
+
const k = Math.min(con.springStiffness[i], 0.25 * invDt * invDt);
|
|
175
|
+
const serr = curr - con.equilibriumPoint[i];
|
|
176
|
+
con.cacheLinSpringTarget[i] = -k * serr * dt;
|
|
177
|
+
con.cacheLinSpringActive[i] = 1;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
con.cacheLinSpringActive[i] = 0;
|
|
181
|
+
}
|
|
145
182
|
}
|
|
146
183
|
// Angular: TA^T · TB → Euler XYZ; axes from TA.col2 × TB.col0.
|
|
147
184
|
const r00 = _TA[0] * _TB[0] + _TA[1] * _TB[1] + _TA[2] * _TB[2];
|
|
@@ -196,34 +233,103 @@ function setupConstraint(con, store, dt, invDt) {
|
|
|
196
233
|
angAxes[8] = zz;
|
|
197
234
|
const angDenom = iiA + iiB;
|
|
198
235
|
con.cacheAngJacInv = angDenom > 0 ? 1 / angDenom : 0;
|
|
236
|
+
// Per-axis rows carry only the springs. Sign flip vs linear:
|
|
237
|
+
// d(angDiff)/dt = −(ω_B − ω_A)·ax.
|
|
199
238
|
const angTgt = con.cacheAngTargetVel;
|
|
200
239
|
const angAct = con.cacheAngActive;
|
|
201
240
|
for (let i = 0; i < 3; i++) {
|
|
202
241
|
const idx = i + 3;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
242
|
+
if (con.springEnabled[idx] && angDenom > 0) {
|
|
243
|
+
// Same deadbeat clamp as the linear springs.
|
|
244
|
+
const k = Math.min(con.springStiffness[idx], 0.25 * invDt * invDt);
|
|
245
|
+
const serr = _angDiffScratch[i] - con.equilibriumPoint[idx];
|
|
246
|
+
angTgt[i] = k * serr * dt;
|
|
247
|
+
angAct[i] = 1;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
angTgt[i] = 0;
|
|
251
|
+
angAct[i] = 0;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Angular limit handling is hybrid. Small violations (the resting-cloth
|
|
255
|
+
// regime) use per-axis euler rows — they converge cleanly and keep resting
|
|
256
|
+
// cloth dead still. Large violations switch to a single geodesic row toward
|
|
257
|
+
// the euler-clamped target: per-axis euler rows (the Bullet-2.7x approach
|
|
258
|
+
// this port used) become geometrically inconsistent for large errors — near
|
|
259
|
+
// the asin singularity they chase phantom errors and pump angular velocity
|
|
260
|
+
// into the chain instead of converging.
|
|
261
|
+
con.cacheAngLimActive = 0;
|
|
262
|
+
if (angDenom > 0) {
|
|
263
|
+
const ex = _angDiffScratch[0], ey = _angDiffScratch[1], ez = _angDiffScratch[2];
|
|
264
|
+
// Free axes (min > max) follow the current angle, i.e. no correction.
|
|
265
|
+
let tx = ex, ty = ey, tz = ez;
|
|
266
|
+
if (con.angularMin[0] <= con.angularMax[0])
|
|
267
|
+
tx = ex < con.angularMin[0] ? con.angularMin[0] : ex > con.angularMax[0] ? con.angularMax[0] : ex;
|
|
268
|
+
if (con.angularMin[1] <= con.angularMax[1])
|
|
269
|
+
ty = ey < con.angularMin[1] ? con.angularMin[1] : ey > con.angularMax[1] ? con.angularMax[1] : ey;
|
|
270
|
+
if (con.angularMin[2] <= con.angularMax[2])
|
|
271
|
+
tz = ez < con.angularMin[2] ? con.angularMin[2] : ez > con.angularMax[2] ? con.angularMax[2] : ez;
|
|
272
|
+
const errX = ex - tx, errY = ey - ty, errZ = ez - tz;
|
|
273
|
+
const maxErr = Math.max(Math.abs(errX), Math.abs(errY), Math.abs(errZ));
|
|
274
|
+
if (maxErr > 0 && maxErr < GEODESIC_THRESHOLD) {
|
|
275
|
+
// Per-axis euler limit rows, folded into the per-axis row set (the
|
|
276
|
+
// spring impulse clamp must not bound a limit row — lift it).
|
|
277
|
+
for (let i = 0; i < 3; i++) {
|
|
278
|
+
const err = i === 0 ? errX : i === 1 ? errY : errZ;
|
|
279
|
+
if (err === 0)
|
|
280
|
+
continue;
|
|
281
|
+
let target = err * STOP_ERP * invDt;
|
|
282
|
+
if (target > MAX_ANGULAR_CORRECTION_VEL)
|
|
283
|
+
target = MAX_ANGULAR_CORRECTION_VEL;
|
|
284
|
+
else if (target < -MAX_ANGULAR_CORRECTION_VEL)
|
|
285
|
+
target = -MAX_ANGULAR_CORRECTION_VEL;
|
|
286
|
+
angTgt[i] += target;
|
|
287
|
+
angAct[i] = 1;
|
|
218
288
|
}
|
|
219
289
|
}
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
290
|
+
else if (maxErr > 0) {
|
|
291
|
+
// Bilateral (equality) if any violated axis is locked — a locked axis
|
|
292
|
+
// is a joint, not a stop. Unilateral otherwise.
|
|
293
|
+
const bilateral = (tx !== ex && con.angularMin[0] === con.angularMax[0]) ||
|
|
294
|
+
(ty !== ey && con.angularMin[1] === con.angularMax[1]) ||
|
|
295
|
+
(tz !== ez && con.angularMin[2] === con.angularMax[2]);
|
|
296
|
+
// The decomposition above satisfies R_rel^T = Rx(x)·Ry(y)·Rz(z), so
|
|
297
|
+
// u = qx·qy·qz is conj(q_rel) and the error rotation (current →
|
|
298
|
+
// clamped target, expressed in TA's frame) is q_E = conj(u_t) ⊗ u.
|
|
299
|
+
eulerXYZQuatInto(ex, ey, ez, _quatScratchA);
|
|
300
|
+
eulerXYZQuatInto(tx, ty, tz, _quatScratchB);
|
|
301
|
+
const ux = _quatScratchA[0], uy = _quatScratchA[1], uz = _quatScratchA[2], uw = _quatScratchA[3];
|
|
302
|
+
const vx = _quatScratchB[0], vy = _quatScratchB[1], vz = _quatScratchB[2], vw = _quatScratchB[3];
|
|
303
|
+
// q_E = conj(v) ⊗ u
|
|
304
|
+
let qex = vw * ux - vx * uw - vy * uz + vz * uy;
|
|
305
|
+
let qey = vw * uy + vx * uz - vy * uw - vz * ux;
|
|
306
|
+
let qez = vw * uz - vx * uy + vy * ux - vz * uw;
|
|
307
|
+
let qew = vw * uw + vx * ux + vy * uy + vz * uz;
|
|
308
|
+
if (qew < 0) {
|
|
309
|
+
qex = -qex;
|
|
310
|
+
qey = -qey;
|
|
311
|
+
qez = -qez;
|
|
312
|
+
qew = -qew;
|
|
313
|
+
}
|
|
314
|
+
const sinHalf = Math.sqrt(qex * qex + qey * qey + qez * qez);
|
|
315
|
+
if (sinHalf > 1e-6) {
|
|
316
|
+
const angle = 2 * Math.atan2(sinHalf, qew);
|
|
317
|
+
const invS = 1 / sinHalf;
|
|
318
|
+
const axx = qex * invS, axy = qey * invS, axz = qez * invS;
|
|
319
|
+
// Axis lives in TA's frame; TA's basis columns map it to world.
|
|
320
|
+
const lim = con.cacheAngLimAxis;
|
|
321
|
+
lim[0] = _TA[0] * axx + _TA[4] * axy + _TA[8] * axz;
|
|
322
|
+
lim[1] = _TA[1] * axx + _TA[5] * axy + _TA[9] * axz;
|
|
323
|
+
lim[2] = _TA[2] * axx + _TA[6] * axy + _TA[10] * axz;
|
|
324
|
+
let target = angle * STOP_ERP * invDt;
|
|
325
|
+
if (target > MAX_ANGULAR_CORRECTION_VEL)
|
|
326
|
+
target = MAX_ANGULAR_CORRECTION_VEL;
|
|
327
|
+
con.cacheAngLimTarget = target;
|
|
328
|
+
con.cacheAngLimActive = bilateral ? 1 : 2;
|
|
329
|
+
}
|
|
223
330
|
}
|
|
224
|
-
angTgt[i] = target;
|
|
225
|
-
angAct[i] = angDenom > 0 ? active : 0;
|
|
226
331
|
}
|
|
332
|
+
con.cacheAngLimImp = 0;
|
|
227
333
|
}
|
|
228
334
|
// ITER: read cache, compute relVel from current lv/av, apply impulse.
|
|
229
335
|
function iterateConstraint(con, lv, av, invMass, invInertia) {
|
|
@@ -257,13 +363,39 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
|
|
|
257
363
|
const dvx = vBx - vAx;
|
|
258
364
|
const dvy = vBy - vAy;
|
|
259
365
|
const dvz = vBz - vAz;
|
|
366
|
+
const sprAct = con.cacheLinSpringActive;
|
|
367
|
+
const sprTgt = con.cacheLinSpringTarget;
|
|
368
|
+
const limImp = con.cacheLinLimitImp;
|
|
260
369
|
for (let i = 0; i < 3; i++) {
|
|
261
|
-
if (!act[i])
|
|
370
|
+
if (!act[i] && !sprAct[i])
|
|
262
371
|
continue;
|
|
263
372
|
const o = i * 3;
|
|
264
373
|
const axx = axes[o + 0], axy = axes[o + 1], axz = axes[o + 2];
|
|
265
374
|
const relVel = dvx * axx + dvy * axy + dvz * axz;
|
|
266
|
-
|
|
375
|
+
let j = 0;
|
|
376
|
+
// Limit row. Locked axes (act 1) are bilateral equality joints; ranged
|
|
377
|
+
// axes in violation (act 2) are unilateral stops — accumulated impulse
|
|
378
|
+
// clamped to the corrective sign, so the stop pushes back into range but
|
|
379
|
+
// never pulls deeper or brakes natural recovery (a bilateral stop acts
|
|
380
|
+
// as a motor and pumps energy into swinging cloth).
|
|
381
|
+
if (act[i]) {
|
|
382
|
+
const target = tgt[i];
|
|
383
|
+
let dImp = LIMIT_SOFTNESS_LINEAR * (target - relVel) * jac[i];
|
|
384
|
+
if (act[i] === 2) {
|
|
385
|
+
const old = limImp[i];
|
|
386
|
+
let next = old + dImp;
|
|
387
|
+
if (target > 0 ? next < 0 : next > 0)
|
|
388
|
+
next = 0;
|
|
389
|
+
dImp = next - old;
|
|
390
|
+
limImp[i] = next;
|
|
391
|
+
}
|
|
392
|
+
j += dImp;
|
|
393
|
+
}
|
|
394
|
+
// Spring row: velocity-target drive; the deadbeat k-clamp at setup
|
|
395
|
+
// bounds its aggression.
|
|
396
|
+
if (sprAct[i]) {
|
|
397
|
+
j += (sprTgt[i] - relVel) * jac[i];
|
|
398
|
+
}
|
|
267
399
|
if (j === 0)
|
|
268
400
|
continue;
|
|
269
401
|
if (imA > 0) {
|
|
@@ -299,6 +431,8 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
|
|
|
299
431
|
const o = i * 3;
|
|
300
432
|
const axx = angAxes[o + 0], axy = angAxes[o + 1], axz = angAxes[o + 2];
|
|
301
433
|
const relAv = dax * axx + day * axy + daz * axz;
|
|
434
|
+
// Spring drive (deadbeat-clamped at setup) plus, for small violations,
|
|
435
|
+
// the folded per-axis limit correction.
|
|
302
436
|
const j = (angTgt[i] - relAv) * angJacInv;
|
|
303
437
|
if (j === 0)
|
|
304
438
|
continue;
|
|
@@ -313,6 +447,38 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
|
|
|
313
447
|
av[bi + 2] += j * iiB * axz;
|
|
314
448
|
}
|
|
315
449
|
}
|
|
450
|
+
// Geodesic limit row: drive (ω_B − ω_A)·axis toward the correction target.
|
|
451
|
+
// Unilateral — the accumulated impulse can only push toward the legal
|
|
452
|
+
// region (target is always ≥ 0 along the corrective axis).
|
|
453
|
+
if (con.cacheAngLimActive) {
|
|
454
|
+
const lim = con.cacheAngLimAxis;
|
|
455
|
+
const nx = lim[0], ny = lim[1], nz = lim[2];
|
|
456
|
+
// Re-read relAv — the spring rows above may have changed av.
|
|
457
|
+
const relAv = (av[bi + 0] - av[ai + 0]) * nx +
|
|
458
|
+
(av[bi + 1] - av[ai + 1]) * ny +
|
|
459
|
+
(av[bi + 2] - av[ai + 2]) * nz;
|
|
460
|
+
let j = LIMIT_SOFTNESS_ANGULAR * (con.cacheAngLimTarget - relAv) * angJacInv;
|
|
461
|
+
if (con.cacheAngLimActive === 2) {
|
|
462
|
+
const old = con.cacheAngLimImp;
|
|
463
|
+
let next = old + j;
|
|
464
|
+
if (next < 0)
|
|
465
|
+
next = 0;
|
|
466
|
+
j = next - old;
|
|
467
|
+
con.cacheAngLimImp = next;
|
|
468
|
+
}
|
|
469
|
+
if (j !== 0) {
|
|
470
|
+
if (iiA > 0) {
|
|
471
|
+
av[ai + 0] -= j * iiA * nx;
|
|
472
|
+
av[ai + 1] -= j * iiA * ny;
|
|
473
|
+
av[ai + 2] -= j * iiA * nz;
|
|
474
|
+
}
|
|
475
|
+
if (iiB > 0) {
|
|
476
|
+
av[bi + 0] += j * iiB * nx;
|
|
477
|
+
av[bi + 1] += j * iiB * ny;
|
|
478
|
+
av[bi + 2] += j * iiB * nz;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
316
482
|
}
|
|
317
483
|
// SETUP: pre-compute Jacobians, friction basis, and the bounce reference
|
|
318
484
|
// from the *initial* closing velocity (Bullet's pattern — captures restitution
|
|
@@ -537,6 +703,16 @@ function buildBodyMat(store, i, out) {
|
|
|
537
703
|
const i3 = i * 3, i4 = i * 4;
|
|
538
704
|
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
705
|
}
|
|
706
|
+
// Quaternion of qx(x) ⊗ qy(y) ⊗ qz(z) (three.js 'XYZ' order).
|
|
707
|
+
function eulerXYZQuatInto(x, y, z, out) {
|
|
708
|
+
const sx = Math.sin(x * 0.5), cx = Math.cos(x * 0.5);
|
|
709
|
+
const sy = Math.sin(y * 0.5), cy = Math.cos(y * 0.5);
|
|
710
|
+
const sz = Math.sin(z * 0.5), cz = Math.cos(z * 0.5);
|
|
711
|
+
out[0] = sx * cy * cz + cx * sy * sz;
|
|
712
|
+
out[1] = cx * sy * cz - sx * cy * sz;
|
|
713
|
+
out[2] = cx * cy * sz + sx * sy * cz;
|
|
714
|
+
out[3] = cx * cy * cz - sx * sy * sz;
|
|
715
|
+
}
|
|
540
716
|
// Euler XYZ from a 3×3 rotation matrix (row-major elements).
|
|
541
717
|
function matrixToEulerXYZ(r00, r01, r10, r11, r20, r21, r22, out) {
|
|
542
718
|
if (r20 < 1) {
|
|
@@ -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;gBAET,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;
|
|
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;gBAET,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;CAyI5G"}
|
package/dist/physics/world.js
CHANGED
|
@@ -95,13 +95,24 @@ export class World {
|
|
|
95
95
|
}
|
|
96
96
|
// 5. Integrate. Cap angular velocity at π/2 per step — a high-impulse
|
|
97
97
|
// contact spike on a low-inertia body would otherwise spin past π
|
|
98
|
-
// in one step and trash the quaternion integration.
|
|
98
|
+
// in one step and trash the quaternion integration. Linear velocity
|
|
99
|
+
// is capped at 5 units per step for the same reason: an explosion
|
|
100
|
+
// guard far above what legit cloth motion ever reaches.
|
|
99
101
|
const MAX_ANGVEL_DT = Math.PI * 0.5;
|
|
102
|
+
const MAX_LINVEL_DT = 5;
|
|
100
103
|
for (let i = 0; i < N; i++) {
|
|
101
104
|
if (types[i] !== RigidbodyType.Dynamic || invMass[i] <= 0)
|
|
102
105
|
continue;
|
|
103
106
|
const i3 = i * 3;
|
|
104
107
|
const i4 = i * 4;
|
|
108
|
+
const vx = lv[i3 + 0], vy = lv[i3 + 1], vz = lv[i3 + 2];
|
|
109
|
+
const vmag = Math.sqrt(vx * vx + vy * vy + vz * vz);
|
|
110
|
+
if (vmag * dt > MAX_LINVEL_DT) {
|
|
111
|
+
const scale = MAX_LINVEL_DT / (vmag * dt);
|
|
112
|
+
lv[i3 + 0] = vx * scale;
|
|
113
|
+
lv[i3 + 1] = vy * scale;
|
|
114
|
+
lv[i3 + 2] = vz * scale;
|
|
115
|
+
}
|
|
105
116
|
pos[i3 + 0] += lv[i3 + 0] * dt;
|
|
106
117
|
pos[i3 + 1] += lv[i3 + 1] * dt;
|
|
107
118
|
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"}
|