reze-engine 0.16.2 → 0.17.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.
Files changed (80) hide show
  1. package/dist/engine.d.ts +3 -1
  2. package/dist/engine.d.ts.map +1 -1
  3. package/dist/engine.js +165 -26
  4. package/dist/model.d.ts +5 -1
  5. package/dist/model.d.ts.map +1 -1
  6. package/dist/model.js +14 -1
  7. package/dist/physics/body.d.ts +4 -1
  8. package/dist/physics/body.d.ts.map +1 -1
  9. package/dist/physics/body.js +86 -20
  10. package/dist/physics/constraint.d.ts +15 -2
  11. package/dist/physics/constraint.d.ts.map +1 -1
  12. package/dist/physics/constraint.js +49 -2
  13. package/dist/physics/physics.d.ts +3 -0
  14. package/dist/physics/physics.d.ts.map +1 -1
  15. package/dist/physics/physics.js +69 -1
  16. package/dist/physics/solver.d.ts.map +1 -1
  17. package/dist/physics/solver.js +289 -113
  18. package/dist/physics/types.d.ts +2 -1
  19. package/dist/physics/types.d.ts.map +1 -1
  20. package/dist/physics/types.js +5 -0
  21. package/dist/physics/world.d.ts +3 -0
  22. package/dist/physics/world.d.ts.map +1 -1
  23. package/dist/physics/world.js +19 -3
  24. package/dist/pmx-loader.d.ts +2 -0
  25. package/dist/pmx-loader.d.ts.map +1 -1
  26. package/dist/pmx-loader.js +38 -20
  27. package/dist/shaders/materials/body.d.ts +1 -1
  28. package/dist/shaders/materials/body.d.ts.map +1 -1
  29. package/dist/shaders/materials/body.js +90 -85
  30. package/dist/shaders/materials/cloth_rough.d.ts +1 -1
  31. package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
  32. package/dist/shaders/materials/cloth_rough.js +1 -1
  33. package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
  34. package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
  35. package/dist/shaders/materials/cloth_smooth.js +2 -2
  36. package/dist/shaders/materials/common.d.ts +1 -1
  37. package/dist/shaders/materials/common.d.ts.map +1 -1
  38. package/dist/shaders/materials/common.js +137 -112
  39. package/dist/shaders/materials/default.d.ts +1 -1
  40. package/dist/shaders/materials/default.d.ts.map +1 -1
  41. package/dist/shaders/materials/default.js +37 -32
  42. package/dist/shaders/materials/eye.d.ts +1 -1
  43. package/dist/shaders/materials/eye.d.ts.map +1 -1
  44. package/dist/shaders/materials/eye.js +54 -35
  45. package/dist/shaders/materials/face.d.ts +1 -1
  46. package/dist/shaders/materials/face.d.ts.map +1 -1
  47. package/dist/shaders/materials/face.js +90 -85
  48. package/dist/shaders/materials/hair.d.ts +1 -1
  49. package/dist/shaders/materials/hair.d.ts.map +1 -1
  50. package/dist/shaders/materials/hair.js +85 -75
  51. package/dist/shaders/materials/metal.d.ts +1 -1
  52. package/dist/shaders/materials/metal.d.ts.map +1 -1
  53. package/dist/shaders/materials/metal.js +1 -1
  54. package/dist/shaders/materials/mmd_classic.d.ts +2 -0
  55. package/dist/shaders/materials/mmd_classic.d.ts.map +1 -0
  56. package/dist/shaders/materials/mmd_classic.js +66 -0
  57. package/dist/shaders/materials/stockings.d.ts +1 -1
  58. package/dist/shaders/materials/stockings.d.ts.map +1 -1
  59. package/dist/shaders/materials/stockings.js +1 -1
  60. package/package.json +1 -1
  61. package/src/engine.ts +4198 -4030
  62. package/src/model.ts +21 -1
  63. package/src/physics/body.ts +87 -24
  64. package/src/physics/constraint.ts +80 -6
  65. package/src/physics/physics.ts +68 -1
  66. package/src/physics/solver.ts +915 -744
  67. package/src/physics/types.ts +10 -2
  68. package/src/physics/world.ts +20 -3
  69. package/src/pmx-loader.ts +39 -20
  70. package/src/shaders/materials/body.ts +97 -92
  71. package/src/shaders/materials/cloth_rough.ts +1 -1
  72. package/src/shaders/materials/cloth_smooth.ts +2 -2
  73. package/src/shaders/materials/common.ts +204 -179
  74. package/src/shaders/materials/default.ts +45 -40
  75. package/src/shaders/materials/eye.ts +62 -43
  76. package/src/shaders/materials/face.ts +97 -92
  77. package/src/shaders/materials/hair.ts +92 -82
  78. package/src/shaders/materials/metal.ts +1 -1
  79. package/src/shaders/materials/mmd_classic.ts +68 -0
  80. package/src/shaders/materials/stockings.ts +1 -1
@@ -17,10 +17,12 @@ export class RigidBodyStore {
17
17
  this.linearVelocities = new Float32Array(N * 3);
18
18
  this.angularVelocities = new Float32Array(N * 3);
19
19
  this.invMass = new Float32Array(N);
20
- this.invInertia = new Float32Array(N);
20
+ this.invInertiaLocal = new Float32Array(N * 3);
21
+ this.invInertiaWorld = new Float32Array(N * 9);
21
22
  this.linearDamping = new Float32Array(N);
22
23
  this.angularDamping = new Float32Array(N);
23
24
  this.type = new Uint8Array(N);
25
+ this.aligned = new Uint8Array(N);
24
26
  this.boneIndex = new Int32Array(N);
25
27
  this.bodyOffsetMatrix = new Float32Array(N * 16);
26
28
  this.bodyOffsetInverse = new Float32Array(N * 16);
@@ -46,10 +48,12 @@ export class RigidBodyStore {
46
48
  this.orientations[i4 + 3] = q.w;
47
49
  const dynamic = rb.type === RigidbodyType.Dynamic && rb.mass > 0;
48
50
  this.invMass[i] = dynamic ? 1 / rb.mass : 0;
49
- this.invInertia[i] = dynamic ? computeInvInertia(rb) : 0;
51
+ if (dynamic)
52
+ computeLocalInvInertia(rb, this.invInertiaLocal, i * 3);
50
53
  this.linearDamping[i] = rb.linearDamping;
51
54
  this.angularDamping[i] = rb.angularDamping;
52
55
  this.type[i] = rb.type;
56
+ this.aligned[i] = rb.aligned ? 1 : 0;
53
57
  this.boneIndex[i] = rb.boneIndex;
54
58
  this.friction[i] = rb.friction;
55
59
  this.restitution[i] = rb.restitution;
@@ -61,6 +65,52 @@ export class RigidBodyStore {
61
65
  this.size[i * 3 + 2] = rb.size.z;
62
66
  }
63
67
  }
68
+ // Refresh I⁻¹_world = R·diag(invInertiaLocal)·Rᵀ for every dynamic body.
69
+ // Called once per substep before constraint setup (orientations are
70
+ // constant during a solve).
71
+ updateInvInertiaWorld() {
72
+ const N = this.count;
73
+ const ori = this.orientations;
74
+ const local = this.invInertiaLocal;
75
+ const W = this.invInertiaWorld;
76
+ const invMass = this.invMass;
77
+ for (let i = 0; i < N; i++) {
78
+ if (invMass[i] <= 0)
79
+ continue;
80
+ const i3 = i * 3;
81
+ const i4 = i * 4;
82
+ const i9 = i * 9;
83
+ const qx = ori[i4 + 0], qy = ori[i4 + 1], qz = ori[i4 + 2], qw = ori[i4 + 3];
84
+ const x2 = qx + qx, y2 = qy + qy, z2 = qz + qz;
85
+ const xx = qx * x2, yy = qy * y2, zz = qz * z2;
86
+ const xy = qx * y2, xz = qx * z2, yz = qy * z2;
87
+ const wx = qw * x2, wy = qw * y2, wz = qw * z2;
88
+ // R columns (column-major rotation matrix)
89
+ const r00 = 1 - (yy + zz), r01 = xy - wz, r02 = xz + wy;
90
+ const r10 = xy + wz, r11 = 1 - (xx + zz), r12 = yz - wx;
91
+ const r20 = xz - wy, r21 = yz + wx, r22 = 1 - (xx + yy);
92
+ const d0 = local[i3 + 0], d1 = local[i3 + 1], d2 = local[i3 + 2];
93
+ // W = R·diag·Rᵀ (symmetric)
94
+ const a0 = r00 * d0, a1 = r01 * d1, a2 = r02 * d2;
95
+ const b0 = r10 * d0, b1 = r11 * d1, b2 = r12 * d2;
96
+ const c0 = r20 * d0, c1 = r21 * d1, c2 = r22 * d2;
97
+ const w00 = a0 * r00 + a1 * r01 + a2 * r02;
98
+ const w01 = a0 * r10 + a1 * r11 + a2 * r12;
99
+ const w02 = a0 * r20 + a1 * r21 + a2 * r22;
100
+ const w11 = b0 * r10 + b1 * r11 + b2 * r12;
101
+ const w12 = b0 * r20 + b1 * r21 + b2 * r22;
102
+ const w22 = c0 * r20 + c1 * r21 + c2 * r22;
103
+ W[i9 + 0] = w00;
104
+ W[i9 + 1] = w01;
105
+ W[i9 + 2] = w02;
106
+ W[i9 + 3] = w01;
107
+ W[i9 + 4] = w11;
108
+ W[i9 + 5] = w12;
109
+ W[i9 + 6] = w02;
110
+ W[i9 + 7] = w12;
111
+ W[i9 + 8] = w22;
112
+ }
113
+ }
64
114
  // World-space AABBs for every body. Inflated by margin so contacts stay
65
115
  // paired across small velocity jitter without recomputing per iteration.
66
116
  updateAabbs(margin = 0.5) {
@@ -193,37 +243,53 @@ export class RigidBodyStore {
193
243
  const _scratchA = new Float32Array(16);
194
244
  const _scratchB = new Float32Array(16);
195
245
  const _scratchC = new Float32Array(16);
196
- // Scalar isotropic inverse inertia. Returns 0 for static bodies (mass = 0).
197
- // Sphere: I = (2/5)·m·r²
198
- // Box: I = (1/3)·m·max(a,b,c)²
199
- // Capsule: I = (1/12)·(3r² + h²) (cylinder transverse axis)
200
- function computeInvInertia(rb) {
246
+ // Diagonal local inverse inertia, matching Bullet's calculateLocalInertia
247
+ // per shape (so behavior tracks Ammo-based MMD engines):
248
+ // Sphere: I = (2/5)·m·r² on every axis
249
+ // Box: Ix = m/12·(ly²+lz²), with l = full extents (2·half)
250
+ // Capsule: Bullet's bounding-box approximation of the Y-axis capsule
251
+ // (lx = lz = 2r, ly = h + 2r)
252
+ function computeLocalInvInertia(rb, out, o) {
201
253
  const m = rb.mass;
202
254
  if (m <= 0)
203
- return 0;
204
- let I;
255
+ return;
256
+ let Ix, Iy, Iz;
205
257
  switch (rb.shape) {
206
258
  case RigidbodyShape.Sphere: {
207
- const r = rb.size.x;
208
- I = 0.4 * m * r * r;
259
+ const I = 0.4 * m * rb.size.x * rb.size.x;
260
+ Ix = I;
261
+ Iy = I;
262
+ Iz = I;
209
263
  break;
210
264
  }
211
265
  case RigidbodyShape.Box: {
212
- // Largest half-extent so the long axis isn't under-rotated.
213
- const a = Math.max(rb.size.x, rb.size.y, rb.size.z);
214
- I = (1 / 3) * m * a * a;
266
+ const lx2 = 4 * rb.size.x * rb.size.x;
267
+ const ly2 = 4 * rb.size.y * rb.size.y;
268
+ const lz2 = 4 * rb.size.z * rb.size.z;
269
+ Ix = (m / 12) * (ly2 + lz2);
270
+ Iy = (m / 12) * (lx2 + lz2);
271
+ Iz = (m / 12) * (lx2 + ly2);
215
272
  break;
216
273
  }
217
274
  case RigidbodyShape.Capsule: {
218
- const r = rb.size.x;
219
- const h = rb.size.y;
220
- I = (1 / 12) * m * (3 * r * r + h * h);
275
+ const lx = 2 * rb.size.x;
276
+ const ly = rb.size.y + 2 * rb.size.x;
277
+ const lx2 = lx * lx;
278
+ const ly2 = ly * ly;
279
+ Ix = (m / 12) * (ly2 + lx2);
280
+ Iy = (m / 12) * (lx2 + lx2);
281
+ Iz = (m / 12) * (lx2 + ly2);
221
282
  break;
222
283
  }
223
- default:
224
- I = m;
284
+ default: {
285
+ Ix = m;
286
+ Iy = m;
287
+ Iz = m;
288
+ }
225
289
  }
226
- return I > 0 ? 1 / I : 0;
290
+ out[o + 0] = Ix > 0 ? 1 / Ix : 0;
291
+ out[o + 1] = Iy > 0 ? 1 / Iy : 0;
292
+ out[o + 2] = Iz > 0 ? 1 / Iz : 0;
227
293
  }
228
294
  function identity16(out, offset) {
229
295
  out[offset + 0] = 1;
@@ -11,6 +11,7 @@ export interface SixDofSpringConstraint {
11
11
  springEnabled: Uint8Array;
12
12
  springStiffness: Float32Array;
13
13
  equilibriumPoint: Float32Array;
14
+ isLoop: boolean;
14
15
  cacheSkip: boolean;
15
16
  cacheLeverA: Float32Array;
16
17
  cacheLeverB: Float32Array;
@@ -22,16 +23,28 @@ export interface SixDofSpringConstraint {
22
23
  cacheLinActive: Uint8Array;
23
24
  cacheLinLimitImp: Float32Array;
24
25
  cacheLinSpringTarget: Float32Array;
26
+ cacheLinSpringMaxImp: Float32Array;
27
+ cacheLinSpringImp: Float32Array;
25
28
  cacheLinSpringActive: Uint8Array;
26
29
  cacheAngAxes: Float32Array;
27
30
  cacheAngTargetVel: Float32Array;
28
31
  cacheAngActive: Uint8Array;
29
- cacheAngJacInv: number;
32
+ cacheAngJacInv: Float32Array;
33
+ cacheAngSpringMaxImp: Float32Array;
34
+ cacheAngSpringImp: Float32Array;
35
+ cacheAngWA: Float32Array;
36
+ cacheAngWB: Float32Array;
30
37
  cacheAngLimAxis: Float32Array;
38
+ cacheAngLimWA: Float32Array;
39
+ cacheAngLimWB: Float32Array;
40
+ cacheAngLimJacInv: number;
31
41
  cacheAngLimTarget: number;
32
42
  cacheAngLimActive: number;
33
43
  cacheAngLimImp: number;
44
+ cacheAngPATarget: Float32Array;
45
+ cacheAngPAActive: Uint8Array;
46
+ cacheAngPAImp: Float32Array;
34
47
  }
35
- export declare const STOP_ERP = 0.475;
48
+ export declare const STOP_ERP = 0.45;
36
49
  export declare function buildConstraints(rigidbodies: Rigidbody[], joints: Joint[]): SixDofSpringConstraint[];
37
50
  //# sourceMappingURL=constraint.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constraint.d.ts","sourceRoot":"","sources":["../../src/physics/constraint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAW/C,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IAEb,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,EAAE,YAAY,CAAA;IAGpB,SAAS,EAAE,YAAY,CAAA;IACvB,SAAS,EAAE,YAAY,CAAA;IACvB,UAAU,EAAE,YAAY,CAAA;IACxB,UAAU,EAAE,YAAY,CAAA;IAExB,aAAa,EAAE,UAAU,CAAA;IACzB,eAAe,EAAE,YAAY,CAAA;IAC7B,gBAAgB,EAAE,YAAY,CAAA;IAK9B,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,EAAE,YAAY,CAAA;IACzB,WAAW,EAAE,YAAY,CAAA;IACzB,YAAY,EAAE,YAAY,CAAA;IAC1B,cAAc,EAAE,YAAY,CAAA;IAC5B,cAAc,EAAE,YAAY,CAAA;IAC5B,cAAc,EAAE,YAAY,CAAA;IAK5B,iBAAiB,EAAE,YAAY,CAAA;IAC/B,cAAc,EAAE,UAAU,CAAA;IAC1B,gBAAgB,EAAE,YAAY,CAAA;IAG9B,oBAAoB,EAAE,YAAY,CAAA;IAClC,oBAAoB,EAAE,UAAU,CAAA;IAChC,YAAY,EAAE,YAAY,CAAA;IAC1B,iBAAiB,EAAE,YAAY,CAAA;IAC/B,cAAc,EAAE,UAAU,CAAA;IAC1B,cAAc,EAAE,MAAM,CAAA;IAKtB,eAAe,EAAE,YAAY,CAAA;IAC7B,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,QAAQ,QAAQ,CAAA;AAM7B,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,SAAS,EAAE,EACxB,MAAM,EAAE,KAAK,EAAE,GACd,sBAAsB,EAAE,CAyF1B"}
1
+ {"version":3,"file":"constraint.d.ts","sourceRoot":"","sources":["../../src/physics/constraint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAY/C,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IAEb,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,EAAE,YAAY,CAAA;IAGpB,SAAS,EAAE,YAAY,CAAA;IACvB,SAAS,EAAE,YAAY,CAAA;IACvB,UAAU,EAAE,YAAY,CAAA;IACxB,UAAU,EAAE,YAAY,CAAA;IAExB,aAAa,EAAE,UAAU,CAAA;IACzB,eAAe,EAAE,YAAY,CAAA;IAC7B,gBAAgB,EAAE,YAAY,CAAA;IAK9B,MAAM,EAAE,OAAO,CAAA;IAKf,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,EAAE,YAAY,CAAA;IACzB,WAAW,EAAE,YAAY,CAAA;IACzB,YAAY,EAAE,YAAY,CAAA;IAC1B,cAAc,EAAE,YAAY,CAAA;IAC5B,cAAc,EAAE,YAAY,CAAA;IAC5B,cAAc,EAAE,YAAY,CAAA;IAK5B,iBAAiB,EAAE,YAAY,CAAA;IAC/B,cAAc,EAAE,UAAU,CAAA;IAC1B,gBAAgB,EAAE,YAAY,CAAA;IAK9B,oBAAoB,EAAE,YAAY,CAAA;IAClC,oBAAoB,EAAE,YAAY,CAAA;IAClC,iBAAiB,EAAE,YAAY,CAAA;IAC/B,oBAAoB,EAAE,UAAU,CAAA;IAChC,YAAY,EAAE,YAAY,CAAA;IAC1B,iBAAiB,EAAE,YAAY,CAAA;IAC/B,cAAc,EAAE,UAAU,CAAA;IAI1B,cAAc,EAAE,YAAY,CAAA;IAK5B,oBAAoB,EAAE,YAAY,CAAA;IAClC,iBAAiB,EAAE,YAAY,CAAA;IAC/B,UAAU,EAAE,YAAY,CAAA;IACxB,UAAU,EAAE,YAAY,CAAA;IAKxB,eAAe,EAAE,YAAY,CAAA;IAC7B,aAAa,EAAE,YAAY,CAAA;IAC3B,aAAa,EAAE,YAAY,CAAA;IAC3B,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IAKtB,gBAAgB,EAAE,YAAY,CAAA;IAC9B,gBAAgB,EAAE,UAAU,CAAA;IAC5B,aAAa,EAAE,YAAY,CAAA;CAC5B;AAOD,eAAO,MAAM,QAAQ,OAAO,CAAA;AAM5B,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,SAAS,EAAE,EACxB,MAAM,EAAE,KAAK,EAAE,GACd,sBAAsB,EAAE,CA+H1B"}
@@ -1,5 +1,11 @@
1
1
  import { Mat4 } from "../math";
2
- export const STOP_ERP = 0.475;
2
+ import { RigidbodyType } from "./types";
3
+ // Stop-limit ERP. PMX rigs are tuned against MMD's stiff limit response;
4
+ // lowering this makes cloth resting against its limits sink visibly deeper
5
+ // (equilibrium penetration scales with 1/ERP). Rest chatter at this
6
+ // stiffness was fixed at the source (spring double-drive, unilateral
7
+ // stops) — don't lower ERP to paper over jitter.
8
+ export const STOP_ERP = 0.45;
3
9
  // Build per-joint constraints from PMX data:
4
10
  // frameA = (bodyA_worldBind)^-1 · jointWorldBind
5
11
  // frameB = (bodyB_worldBind)^-1 · jointWorldBind
@@ -64,6 +70,7 @@ export function buildConstraints(rigidbodies, joints) {
64
70
  springEnabled,
65
71
  springStiffness,
66
72
  equilibriumPoint: new Float32Array(6),
73
+ isLoop: false,
67
74
  cacheSkip: false,
68
75
  cacheLeverA: new Float32Array(3),
69
76
  cacheLeverB: new Float32Array(3),
@@ -75,17 +82,57 @@ export function buildConstraints(rigidbodies, joints) {
75
82
  cacheLinActive: new Uint8Array(3),
76
83
  cacheLinLimitImp: new Float32Array(3),
77
84
  cacheLinSpringTarget: new Float32Array(3),
85
+ cacheLinSpringMaxImp: new Float32Array(3),
86
+ cacheLinSpringImp: new Float32Array(3),
78
87
  cacheLinSpringActive: new Uint8Array(3),
79
88
  cacheAngAxes: new Float32Array(9),
80
89
  cacheAngTargetVel: new Float32Array(3),
81
90
  cacheAngActive: new Uint8Array(3),
82
- cacheAngJacInv: 0,
91
+ cacheAngJacInv: new Float32Array(3),
92
+ cacheAngSpringMaxImp: new Float32Array(3),
93
+ cacheAngSpringImp: new Float32Array(3),
94
+ cacheAngWA: new Float32Array(9),
95
+ cacheAngWB: new Float32Array(9),
83
96
  cacheAngLimAxis: new Float32Array(3),
97
+ cacheAngLimWA: new Float32Array(3),
98
+ cacheAngLimWB: new Float32Array(3),
99
+ cacheAngLimJacInv: 0,
84
100
  cacheAngLimTarget: 0,
85
101
  cacheAngLimActive: 0,
86
102
  cacheAngLimImp: 0,
103
+ cacheAngPATarget: new Float32Array(3),
104
+ cacheAngPAActive: new Uint8Array(3),
105
+ cacheAngPAImp: new Float32Array(3),
87
106
  });
88
107
  }
108
+ // Mark loop-closing joints via union-find over the joint graph. All
109
+ // bone-follow bodies count as one "world" component (they're each anchored
110
+ // to the skeleton), so a joint bridging two separately anchored subtrees
111
+ // also closes a kinematic cycle. One edge per cycle gets softened, which
112
+ // releases the over-determination regardless of which edge it is.
113
+ const parent = new Int32Array(rigidbodies.length + 1);
114
+ for (let i = 0; i < parent.length; i++)
115
+ parent[i] = i;
116
+ const find = (x) => {
117
+ while (parent[x] !== x) {
118
+ parent[x] = parent[parent[x]];
119
+ x = parent[x];
120
+ }
121
+ return x;
122
+ };
123
+ const world = rigidbodies.length;
124
+ for (let i = 0; i < rigidbodies.length; i++) {
125
+ if (rigidbodies[i].type !== RigidbodyType.Dynamic)
126
+ parent[find(i)] = find(world);
127
+ }
128
+ for (const con of out) {
129
+ const ra = find(con.bodyA);
130
+ const rb = find(con.bodyB);
131
+ if (ra === rb)
132
+ con.isLoop = true;
133
+ else
134
+ parent[ra] = rb;
135
+ }
89
136
  return out;
90
137
  }
91
138
  // frame = bodyWorldBind^-1 · jointWorld. False if bodyWorldBind is singular.
@@ -20,6 +20,8 @@ export declare class RezePhysics {
20
20
  private kinTargetAngVel;
21
21
  private kinRoot;
22
22
  private teleportFlags;
23
+ private alignPinned;
24
+ teleportCount: number;
23
25
  constructor(rigidbodies: Rigidbody[], joints?: Joint[]);
24
26
  private buildKinematicRoots;
25
27
  private savePrevState;
@@ -39,6 +41,7 @@ export declare class RezePhysics {
39
41
  private advanceKinematicToTargets;
40
42
  private snapKinematicToTargets;
41
43
  private carryDynamicThroughTeleport;
44
+ private alignPinnedBodiesToBones;
42
45
  private restoreNonFiniteBodies;
43
46
  private applyDynamicsToBones;
44
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../../src/physics/physics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAYvC,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,KAAK,CAAO;IACpB,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAI;IAMhC,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,gBAAgB,CAAc;IAQtC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,eAAe,CAAc;IAMrC,OAAO,CAAC,OAAO,CAAY;IAE3B,OAAO,CAAC,aAAa,CAAY;gBAErB,WAAW,EAAE,SAAS,EAAE,EAAE,MAAM,GAAE,KAAK,EAAO;IAmB1D,OAAO,CAAC,mBAAmB;IA4B3B,OAAO,CAAC,aAAa;IAKrB,UAAU,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI;IAG/B,UAAU,IAAI,IAAI;IAGlB,cAAc,IAAI,SAAS,EAAE;IAG7B,SAAS,IAAI,KAAK,EAAE;IAGpB,QAAQ,IAAI,cAAc;IAI1B,sBAAsB,IAAI,KAAK,CAAC;QAAE,QAAQ,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAA;KAAE,CAAC;IAiBnE,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,GAAG,IAAI;IAOtC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,uBAAuB,EAAE,YAAY,GAAG,IAAI;IA4DxF,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,uBAAuB;IAqF/B,OAAO,CAAC,yBAAyB;IAgEjC,OAAO,CAAC,sBAAsB;IAmC9B,OAAO,CAAC,2BAA2B;IA8EnC,OAAO,CAAC,sBAAsB;IAqC9B,OAAO,CAAC,oBAAoB;CAoD7B"}
1
+ {"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../../src/physics/physics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAYvC,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,KAAK,CAAO;IACpB,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAI;IAMhC,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,gBAAgB,CAAc;IAQtC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,eAAe,CAAc;IAMrC,OAAO,CAAC,OAAO,CAAY;IAE3B,OAAO,CAAC,aAAa,CAAY;IAMjC,OAAO,CAAC,WAAW,CAAY;IAG/B,aAAa,SAAI;gBAEL,WAAW,EAAE,SAAS,EAAE,EAAE,MAAM,GAAE,KAAK,EAAO;IA8B1D,OAAO,CAAC,mBAAmB;IA4B3B,OAAO,CAAC,aAAa;IAKrB,UAAU,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI;IAG/B,UAAU,IAAI,IAAI;IAGlB,cAAc,IAAI,SAAS,EAAE;IAG7B,SAAS,IAAI,KAAK,EAAE;IAGpB,QAAQ,IAAI,cAAc;IAI1B,sBAAsB,IAAI,KAAK,CAAC;QAAE,QAAQ,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAA;KAAE,CAAC;IAiBnE,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,GAAG,IAAI;IActC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,uBAAuB,EAAE,YAAY,GAAG,IAAI;IAiExF,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,uBAAuB;IAqF/B,OAAO,CAAC,yBAAyB;IAgEjC,OAAO,CAAC,sBAAsB;IAmC9B,OAAO,CAAC,2BAA2B;IA6EnC,OAAO,CAAC,wBAAwB;IA2BhC,OAAO,CAAC,sBAAsB;IAqC9B,OAAO,CAAC,oBAAoB;CA6D7B"}
@@ -16,6 +16,9 @@ export class RezePhysics {
16
16
  this.timeAccum = 0;
17
17
  this.fixedTimeStep = 1 / 60;
18
18
  this.maxSubSteps = 6;
19
+ // Debug counter: frames on which a teleport (scrub/jump discontinuity)
20
+ // was detected and settled.
21
+ this.teleportCount = 0;
19
22
  this.rigidbodies = rigidbodies;
20
23
  this.joints = joints;
21
24
  this.store = new RigidBodyStore(rigidbodies);
@@ -30,6 +33,18 @@ export class RezePhysics {
30
33
  this.kinTargetAngVel = new Float32Array(this.store.count * 3);
31
34
  this.kinRoot = this.buildKinematicRoots();
32
35
  this.teleportFlags = new Uint8Array(this.store.count);
36
+ this.alignPinned = new Uint8Array(this.store.count);
37
+ const types = this.store.type;
38
+ for (const c of this.constraints) {
39
+ const tA = types[c.bodyA];
40
+ const tB = types[c.bodyB];
41
+ const aFollows = tA === RigidbodyType.Static || tA === RigidbodyType.Kinematic;
42
+ const bFollows = tB === RigidbodyType.Static || tB === RigidbodyType.Kinematic;
43
+ if (aFollows && this.store.aligned[c.bodyB])
44
+ this.alignPinned[c.bodyB] = 1;
45
+ if (bFollows && this.store.aligned[c.bodyA])
46
+ this.alignPinned[c.bodyA] = 1;
47
+ }
33
48
  }
34
49
  // BFS over the joint graph from every kinematic body, assigning each
35
50
  // reachable dynamic body the kinematic body it (transitively) hangs off.
@@ -101,6 +116,13 @@ export class RezePhysics {
101
116
  return;
102
117
  this.snapBodiesToBones(boneWorldMatrices);
103
118
  this.savePrevState(); // prev == curr after a snap, so no interpolation jump
119
+ // Reseed kinematic targets from the snapped pose — otherwise the next
120
+ // step derives the target-trajectory velocity against the pre-reset
121
+ // targets and feeds one frame of enormous anchor velocity to the solver.
122
+ this.kinTargetPos.set(this.store.positions);
123
+ this.kinTargetOri.set(this.store.orientations);
124
+ this.kinTargetVel.fill(0);
125
+ this.kinTargetAngVel.fill(0);
104
126
  this.timeAccum = 0;
105
127
  }
106
128
  step(dt, boneWorldMatrices, boneInverseBindMatrices) {
@@ -124,6 +146,7 @@ export class RezePhysics {
124
146
  // raw derived velocity is what used to explode the solver. Chains under
125
147
  // unaffected roots keep their momentum untouched.
126
148
  if (this.computeKinematicTargets(boneWorldMatrices, dt)) {
149
+ this.teleportCount++;
127
150
  this.carryDynamicThroughTeleport();
128
151
  this.snapKinematicToTargets(true);
129
152
  this.savePrevState(); // prev == curr so interpolation doesn't streak
@@ -155,6 +178,9 @@ export class RezePhysics {
155
178
  this.timeAccum = 0;
156
179
  this.snapKinematicToTargets(false);
157
180
  }
181
+ // MMD mode-2 bone alignment for pinned (depth-1) bodies: position
182
+ // re-pins to the animated bone, rotation stays simulated.
183
+ this.alignPinnedBodiesToBones(boneWorldMatrices);
158
184
  // Fraction into the next (not-yet-taken) step; always in [0, 1).
159
185
  const alpha = this.fixedTimeStep > 0 ? this.timeAccum / this.fixedTimeStep : 0;
160
186
  this.applyDynamicsToBones(boneWorldMatrices, alpha);
@@ -452,6 +478,32 @@ export class RezePhysics {
452
478
  av[i3 + 2] = 0;
453
479
  }
454
480
  }
481
+ // Overwrite pinned mode-2 bodies' positions with boneWorld × bodyOffset
482
+ // from the animated bone pose, keeping simulated orientation and
483
+ // velocities. prevPositions follows so interpolation doesn't streak.
484
+ alignPinnedBodiesToBones(boneWorldMatrices) {
485
+ const N = this.store.count;
486
+ const pinned = this.alignPinned;
487
+ const boneIdx = this.store.boneIndex;
488
+ const offsets = this.store.bodyOffsetMatrix;
489
+ const positions = this.store.positions;
490
+ const prevPos = this.prevPositions;
491
+ for (let i = 0; i < N; i++) {
492
+ if (!pinned[i])
493
+ continue;
494
+ const b = boneIdx[i];
495
+ if (b < 0 || b >= boneWorldMatrices.length)
496
+ continue;
497
+ Mat4.multiplyArrays(boneWorldMatrices[b].values, 0, offsets, i * 16, _bodyMat, 0);
498
+ const i3 = i * 3;
499
+ positions[i3 + 0] = _bodyMat[12];
500
+ positions[i3 + 1] = _bodyMat[13];
501
+ positions[i3 + 2] = _bodyMat[14];
502
+ prevPos[i3 + 0] = _bodyMat[12];
503
+ prevPos[i3 + 1] = _bodyMat[13];
504
+ prevPos[i3 + 2] = _bodyMat[14];
505
+ }
506
+ }
455
507
  // Backstop: if a dynamic body's state went non-finite despite the velocity
456
508
  // caps, restore its previous-substep pose with zero velocity instead of
457
509
  // letting NaNs spread through constraints and contacts. Runs after every
@@ -550,7 +602,23 @@ export class RezePhysics {
550
602
  Mat4.multiplyArrays(_bodyMat, 0, inv, i * 16, _boneMat, 0);
551
603
  // Sanity gate against NaN / extreme values — silently drop the update.
552
604
  if (Number.isFinite(_boneMat[0]) && Math.abs(_boneMat[0]) < 1e6) {
553
- boneWorldMatrices[b].values.set(_boneMat);
605
+ const v = boneWorldMatrices[b].values;
606
+ if (this.alignPinned[i]) {
607
+ // Pinned mode-2: the bone keeps its animated position, physics
608
+ // drives rotation only.
609
+ v[0] = _boneMat[0];
610
+ v[1] = _boneMat[1];
611
+ v[2] = _boneMat[2];
612
+ v[4] = _boneMat[4];
613
+ v[5] = _boneMat[5];
614
+ v[6] = _boneMat[6];
615
+ v[8] = _boneMat[8];
616
+ v[9] = _boneMat[9];
617
+ v[10] = _boneMat[10];
618
+ }
619
+ else {
620
+ v.set(_boneMat);
621
+ }
554
622
  }
555
623
  }
556
624
  }
@@ -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;AA6BrD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,cAAc,EACrB,WAAW,EAAE,sBAAsB,EAAE,EACrC,QAAQ,EAAE,WAAW,EACrB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,MAAM,GACjB,IAAI,CAyBN"}
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;AAyDrD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,cAAc,EACrB,WAAW,EAAE,sBAAsB,EAAE,EACrC,QAAQ,EAAE,WAAW,EACrB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,MAAM,GACjB,IAAI,CA4BN"}