reze-engine 0.12.3 → 0.13.1

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/dist/math.js CHANGED
@@ -52,6 +52,48 @@ export class Vec3 {
52
52
  this.z = other.z;
53
53
  return this;
54
54
  }
55
+ setXYZ(x, y, z) {
56
+ this.x = x;
57
+ this.y = y;
58
+ this.z = z;
59
+ return this;
60
+ }
61
+ // out = a - b (no allocation)
62
+ static subtractInto(a, b, out) {
63
+ out.x = a.x - b.x;
64
+ out.y = a.y - b.y;
65
+ out.z = a.z - b.z;
66
+ return out;
67
+ }
68
+ // out = a × b (no allocation). Safe when out === a or out === b.
69
+ static crossInto(a, b, out) {
70
+ const ax = a.x, ay = a.y, az = a.z;
71
+ const bx = b.x, by = b.y, bz = b.z;
72
+ out.x = ay * bz - az * by;
73
+ out.y = az * bx - ax * bz;
74
+ out.z = ax * by - ay * bx;
75
+ return out;
76
+ }
77
+ // Read translation from Mat4 values array (column-major) into out.
78
+ static setFromMat4Translation(m, out) {
79
+ out.x = m[12];
80
+ out.y = m[13];
81
+ out.z = m[14];
82
+ return out;
83
+ }
84
+ // Transform normal by the upper-left 3x3 of a Mat4 (column-major) into out.
85
+ // Safe when out === normal.
86
+ static transformMat4RotationInto(normal, m, out) {
87
+ const nx = normal.x, ny = normal.y, nz = normal.z;
88
+ out.x = m[0] * nx + m[4] * ny + m[8] * nz;
89
+ out.y = m[1] * nx + m[5] * ny + m[9] * nz;
90
+ out.z = m[2] * nx + m[6] * ny + m[10] * nz;
91
+ return out;
92
+ }
93
+ // In-place normalize returning length squared info via Vec3. Alias for normalize() but explicit.
94
+ normalizeInPlace() {
95
+ return this.normalize();
96
+ }
55
97
  }
56
98
  export class Quat {
57
99
  constructor(x, y, z, w) {
@@ -158,6 +200,78 @@ export class Quat {
158
200
  const s1 = Math.sin(theta) / sinTheta0;
159
201
  return new Quat(s0 * a.x + s1 * bx, s0 * a.y + s1 * by, s0 * a.z + s1 * bz, s0 * a.w + s1 * bw);
160
202
  }
203
+ // out = a * b (quaternion multiplication, rotation composition).
204
+ // Safe when out === a or out === b.
205
+ static multiplyInto(a, b, out) {
206
+ const ax = a.x, ay = a.y, az = a.z, aw = a.w;
207
+ const bx = b.x, by = b.y, bz = b.z, bw = b.w;
208
+ out.x = aw * bx + ax * bw + ay * bz - az * by;
209
+ out.y = aw * by - ax * bz + ay * bw + az * bx;
210
+ out.z = aw * bz + ax * by - ay * bx + az * bw;
211
+ out.w = aw * bw - ax * bx - ay * by - az * bz;
212
+ return out;
213
+ }
214
+ // out = quat from axis (unnormalized) and angle.
215
+ static fromAxisAngleInto(ax, ay, az, angle, out) {
216
+ const len = Math.sqrt(ax * ax + ay * ay + az * az);
217
+ const invLen = len > 0 ? 1 / len : 0;
218
+ const nx = ax * invLen, ny = ay * invLen, nz = az * invLen;
219
+ const half = angle * 0.5;
220
+ const s = Math.sin(half), c = Math.cos(half);
221
+ out.x = nx * s;
222
+ out.y = ny * s;
223
+ out.z = nz * s;
224
+ out.w = c;
225
+ return out;
226
+ }
227
+ // out = slerp(a, b, t). Safe when out === a or out === b.
228
+ static slerpInto(a, b, t, out) {
229
+ let cos = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
230
+ let bx = b.x, by = b.y, bz = b.z, bw = b.w;
231
+ if (cos < 0) {
232
+ cos = -cos;
233
+ bx = -bx;
234
+ by = -by;
235
+ bz = -bz;
236
+ bw = -bw;
237
+ }
238
+ if (cos > 0.9995) {
239
+ const x = a.x + t * (bx - a.x);
240
+ const y = a.y + t * (by - a.y);
241
+ const z = a.z + t * (bz - a.z);
242
+ const w = a.w + t * (bw - a.w);
243
+ const invLen = 1 / Math.hypot(x, y, z, w);
244
+ out.x = x * invLen;
245
+ out.y = y * invLen;
246
+ out.z = z * invLen;
247
+ out.w = w * invLen;
248
+ return out;
249
+ }
250
+ const theta0 = Math.acos(cos);
251
+ const sinTheta0 = Math.sin(theta0);
252
+ const theta = theta0 * t;
253
+ const s0 = Math.sin(theta0 - theta) / sinTheta0;
254
+ const s1 = Math.sin(theta) / sinTheta0;
255
+ out.x = s0 * a.x + s1 * bx;
256
+ out.y = s0 * a.y + s1 * by;
257
+ out.z = s0 * a.z + s1 * bz;
258
+ out.w = s0 * a.w + s1 * bw;
259
+ return out;
260
+ }
261
+ setXYZW(x, y, z, w) {
262
+ this.x = x;
263
+ this.y = y;
264
+ this.z = z;
265
+ this.w = w;
266
+ return this;
267
+ }
268
+ setIdentity() {
269
+ this.x = 0;
270
+ this.y = 0;
271
+ this.z = 0;
272
+ this.w = 1;
273
+ return this;
274
+ }
161
275
  // Convert Euler angles to quaternion (ZXY order, left-handed, PMX format)
162
276
  static fromEuler(rotX, rotY, rotZ) {
163
277
  const cx = Math.cos(rotX * 0.5);
@@ -297,6 +411,124 @@ export class Mat4 {
297
411
  clone() {
298
412
  return new Mat4(this.values.slice());
299
413
  }
414
+ // Write rotation matrix from quaternion into existing Float32Array (column-major).
415
+ static fromQuatInto(x, y, z, w, out, offset = 0) {
416
+ const x2 = x + x, y2 = y + y, z2 = z + z;
417
+ const xx = x * x2, xy = x * y2, xz = x * z2;
418
+ const yy = y * y2, yz = y * z2, zz = z * z2;
419
+ const wx = w * x2, wy = w * y2, wz = w * z2;
420
+ out[offset + 0] = 1 - (yy + zz);
421
+ out[offset + 1] = xy + wz;
422
+ out[offset + 2] = xz - wy;
423
+ out[offset + 3] = 0;
424
+ out[offset + 4] = xy - wz;
425
+ out[offset + 5] = 1 - (xx + zz);
426
+ out[offset + 6] = yz + wx;
427
+ out[offset + 7] = 0;
428
+ out[offset + 8] = xz + wy;
429
+ out[offset + 9] = yz - wx;
430
+ out[offset + 10] = 1 - (xx + yy);
431
+ out[offset + 11] = 0;
432
+ out[offset + 12] = 0;
433
+ out[offset + 13] = 0;
434
+ out[offset + 14] = 0;
435
+ out[offset + 15] = 1;
436
+ }
437
+ // Fused local transform: out = T(bindT) · R(quat) · T(localT).
438
+ // Result translation = bindT + R * localT; rotation column block = R.
439
+ // Column-major. Zero allocations.
440
+ static localTransformInto(bx, by, bz, qx, qy, qz, qw, lx, ly, lz, out) {
441
+ const x2 = qx + qx, y2 = qy + qy, z2 = qz + qz;
442
+ const xx = qx * x2, xy = qx * y2, xz = qx * z2;
443
+ const yy = qy * y2, yz = qy * z2, zz = qz * z2;
444
+ const wx = qw * x2, wy = qw * y2, wz = qw * z2;
445
+ const m00 = 1 - (yy + zz), m01 = xy + wz, m02 = xz - wy;
446
+ const m10 = xy - wz, m11 = 1 - (xx + zz), m12 = yz + wx;
447
+ const m20 = xz + wy, m21 = yz - wx, m22 = 1 - (xx + yy);
448
+ out[0] = m00;
449
+ out[1] = m01;
450
+ out[2] = m02;
451
+ out[3] = 0;
452
+ out[4] = m10;
453
+ out[5] = m11;
454
+ out[6] = m12;
455
+ out[7] = 0;
456
+ out[8] = m20;
457
+ out[9] = m21;
458
+ out[10] = m22;
459
+ out[11] = 0;
460
+ out[12] = bx + m00 * lx + m10 * ly + m20 * lz;
461
+ out[13] = by + m01 * lx + m11 * ly + m21 * lz;
462
+ out[14] = bz + m02 * lx + m12 * ly + m22 * lz;
463
+ out[15] = 1;
464
+ }
465
+ // Write position+rotation transform into existing Float32Array.
466
+ static fromPositionRotationInto(px, py, pz, qx, qy, qz, qw, out) {
467
+ Mat4.fromQuatInto(qx, qy, qz, qw, out, 0);
468
+ out[12] = px;
469
+ out[13] = py;
470
+ out[14] = pz;
471
+ }
472
+ // In-place 4x4 inverse into out array. Returns true on success, false if singular (out untouched).
473
+ static inverseInto(m, out) {
474
+ const a00 = m[0], a01 = m[1], a02 = m[2], a03 = m[3];
475
+ const a10 = m[4], a11 = m[5], a12 = m[6], a13 = m[7];
476
+ const a20 = m[8], a21 = m[9], a22 = m[10], a23 = m[11];
477
+ const a30 = m[12], a31 = m[13], a32 = m[14], a33 = m[15];
478
+ const b00 = a00 * a11 - a01 * a10;
479
+ const b01 = a00 * a12 - a02 * a10;
480
+ const b02 = a00 * a13 - a03 * a10;
481
+ const b03 = a01 * a12 - a02 * a11;
482
+ const b04 = a01 * a13 - a03 * a11;
483
+ const b05 = a02 * a13 - a03 * a12;
484
+ const b06 = a20 * a31 - a21 * a30;
485
+ const b07 = a20 * a32 - a22 * a30;
486
+ const b08 = a20 * a33 - a23 * a30;
487
+ const b09 = a21 * a32 - a22 * a31;
488
+ const b10 = a21 * a33 - a23 * a31;
489
+ const b11 = a22 * a33 - a23 * a32;
490
+ let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
491
+ if (Math.abs(det) < 1e-10)
492
+ return false;
493
+ det = 1.0 / det;
494
+ out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
495
+ out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
496
+ out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
497
+ out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
498
+ out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
499
+ out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
500
+ out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
501
+ out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
502
+ out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
503
+ out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
504
+ out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
505
+ out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
506
+ out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
507
+ out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
508
+ out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
509
+ out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
510
+ return true;
511
+ }
512
+ // Copy only the rotation (upper-left 3x3) of src into dst, zero out translation, identity w.
513
+ // Column-major in both.
514
+ static copyRotationInto(src, dst) {
515
+ dst[0] = src[0];
516
+ dst[1] = src[1];
517
+ dst[2] = src[2];
518
+ dst[3] = 0;
519
+ dst[4] = src[4];
520
+ dst[5] = src[5];
521
+ dst[6] = src[6];
522
+ dst[7] = 0;
523
+ dst[8] = src[8];
524
+ dst[9] = src[9];
525
+ dst[10] = src[10];
526
+ dst[11] = 0;
527
+ dst[12] = 0;
528
+ dst[13] = 0;
529
+ dst[14] = 0;
530
+ dst[15] = 1;
531
+ }
300
532
  static fromQuat(x, y, z, w) {
301
533
  // Column-major rotation matrix from quaternion (matches glMatrix/WGSL)
302
534
  const out = new Float32Array(16);
@@ -338,6 +570,48 @@ export class Mat4 {
338
570
  toQuat() {
339
571
  return Mat4.toQuatFromArray(this.values, 0);
340
572
  }
573
+ // Extract quaternion from matrix array into an existing Quat (no allocation).
574
+ static toQuatFromArrayInto(m, offset, out) {
575
+ const m00 = m[offset + 0], m01 = m[offset + 4], m02 = m[offset + 8];
576
+ const m10 = m[offset + 1], m11 = m[offset + 5], m12 = m[offset + 9];
577
+ const m20 = m[offset + 2], m21 = m[offset + 6], m22 = m[offset + 10];
578
+ const trace = m00 + m11 + m22;
579
+ let x = 0, y = 0, z = 0, w = 1;
580
+ if (trace > 0) {
581
+ const s = Math.sqrt(trace + 1.0) * 2;
582
+ w = 0.25 * s;
583
+ x = (m21 - m12) / s;
584
+ y = (m02 - m20) / s;
585
+ z = (m10 - m01) / s;
586
+ }
587
+ else if (m00 > m11 && m00 > m22) {
588
+ const s = Math.sqrt(1.0 + m00 - m11 - m22) * 2;
589
+ w = (m21 - m12) / s;
590
+ x = 0.25 * s;
591
+ y = (m01 + m10) / s;
592
+ z = (m02 + m20) / s;
593
+ }
594
+ else if (m11 > m22) {
595
+ const s = Math.sqrt(1.0 + m11 - m00 - m22) * 2;
596
+ w = (m02 - m20) / s;
597
+ x = (m01 + m10) / s;
598
+ y = 0.25 * s;
599
+ z = (m12 + m21) / s;
600
+ }
601
+ else {
602
+ const s = Math.sqrt(1.0 + m22 - m00 - m11) * 2;
603
+ w = (m10 - m01) / s;
604
+ x = (m02 + m20) / s;
605
+ y = (m12 + m21) / s;
606
+ z = 0.25 * s;
607
+ }
608
+ const invLen = 1 / Math.hypot(x, y, z, w);
609
+ out.x = x * invLen;
610
+ out.y = y * invLen;
611
+ out.z = z * invLen;
612
+ out.w = w * invLen;
613
+ return out;
614
+ }
341
615
  // Static method to extract quaternion from matrix array (avoids creating Mat4 object)
342
616
  static toQuatFromArray(m, offset) {
343
617
  const m00 = m[offset + 0], m01 = m[offset + 4], m02 = m[offset + 8];
@@ -452,3 +726,29 @@ export class Mat4 {
452
726
  return new Mat4(out);
453
727
  }
454
728
  }
729
+ // Preallocated scratch instances for hot paths. Each subsystem should use its own
730
+ // slot to avoid cross-call stomping. Bump the count if more call sites need scratch.
731
+ export const scratchMat4Values = [
732
+ new Float32Array(16),
733
+ new Float32Array(16),
734
+ new Float32Array(16),
735
+ new Float32Array(16),
736
+ new Float32Array(16),
737
+ new Float32Array(16),
738
+ ];
739
+ export const scratchVec3 = [
740
+ new Vec3(0, 0, 0),
741
+ new Vec3(0, 0, 0),
742
+ new Vec3(0, 0, 0),
743
+ new Vec3(0, 0, 0),
744
+ new Vec3(0, 0, 0),
745
+ new Vec3(0, 0, 0),
746
+ new Vec3(0, 0, 0),
747
+ new Vec3(0, 0, 0),
748
+ ];
749
+ export const scratchQuat = [
750
+ new Quat(0, 0, 0, 1),
751
+ new Quat(0, 0, 0, 1),
752
+ new Quat(0, 0, 0, 1),
753
+ new Quat(0, 0, 0, 1),
754
+ ];
package/dist/model.d.ts CHANGED
@@ -110,8 +110,6 @@ export declare class Model {
110
110
  private runtimeSkeleton;
111
111
  private runtimeMorph;
112
112
  private morphsDirty;
113
- private cachedIdentityMat1;
114
- private cachedIdentityMat2;
115
113
  private skinMatricesArray?;
116
114
  private tweenState;
117
115
  private tweenTimeMs;
@@ -1 +1 @@
1
- {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
package/dist/model.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Mat4, Quat, Vec3 } from "./math";
1
+ import { Mat4, Quat, Vec3, scratchMat4Values, scratchQuat } from "./math";
2
2
  import { Engine } from "./engine";
3
3
  import { joinAssetPath } from "./asset-reader";
4
4
  import { IKSolverSystem } from "./ik-solver";
@@ -26,9 +26,6 @@ export class Model {
26
26
  this.rigidbodies = [];
27
27
  this.joints = [];
28
28
  this.morphsDirty = false; // Flag indicating if morphs need to be applied
29
- // Cached identity matrices to avoid allocations in computeWorldMatrices
30
- this.cachedIdentityMat1 = Mat4.identity();
31
- this.cachedIdentityMat2 = Mat4.identity();
32
29
  this.tweenTimeMs = 0; // Time tracking for tweens (milliseconds)
33
30
  // Animation: state and multiple slots (idle, walk, attack, etc.); commit/rollback for action-game style
34
31
  this.animationState = new AnimationState();
@@ -864,15 +861,25 @@ export class Model {
864
861
  this.computeSingleBoneWorldMatrix(b.parentIndex, applyIK);
865
862
  }
866
863
  // Get base rotation
867
- let boneRot = localRot[boneIndex];
868
- // Apply IK rotation if requested
864
+ const baseRot = localRot[boneIndex];
865
+ let fx = baseRot.x, fy = baseRot.y, fz = baseRot.z, fw = baseRot.w;
866
+ // Apply IK rotation if requested: finalRot = ik * base, then normalize
869
867
  if (applyIK && ikChainInfo) {
870
868
  const chainInfo = ikChainInfo[boneIndex];
871
869
  if (chainInfo?.ikRotation) {
872
- boneRot = chainInfo.ikRotation.multiply(boneRot).normalize();
870
+ const ik = chainInfo.ikRotation;
871
+ const nx = ik.w * fx + ik.x * fw + ik.y * fz - ik.z * fy;
872
+ const ny = ik.w * fy - ik.x * fz + ik.y * fw + ik.z * fx;
873
+ const nz = ik.w * fz + ik.x * fy - ik.y * fx + ik.z * fw;
874
+ const nw = ik.w * fw - ik.x * fx - ik.y * fy - ik.z * fz;
875
+ const len = Math.sqrt(nx * nx + ny * ny + nz * nz + nw * nw);
876
+ const inv = len > 0 ? 1 / len : 0;
877
+ fx = nx * inv;
878
+ fy = ny * inv;
879
+ fz = nz * inv;
880
+ fw = nw * inv;
873
881
  }
874
882
  }
875
- let rotateM = Mat4.fromQuat(boneRot.x, boneRot.y, boneRot.z, boneRot.w);
876
883
  let addLocalTx = 0, addLocalTy = 0, addLocalTz = 0;
877
884
  // Handle append transformations (same logic as computeWorldMatrices)
878
885
  const appendParentIdx = b.appendParentIndex;
@@ -885,16 +892,11 @@ export class Model {
885
892
  const hasRatio = Math.abs(ratio) > 1e-6;
886
893
  if (hasRatio) {
887
894
  if (b.appendRotate) {
888
- // Get append parent's rotation
889
- // During IK solving, use only base local rotation (not IK rotations) to avoid
890
- // conflicts with IK rotations that are still being computed incrementally
891
- // IK rotations will be applied to localRotations after IK solving completes
895
+ // Recurse first (may touch scratch); all scratch use below happens after it unwinds
892
896
  if (appendParentIdx >= 0) {
893
- // Compute append parent's world matrix for dependency order, but use base rotation for append
894
897
  this.computeSingleBoneWorldMatrix(appendParentIdx, applyIK);
895
898
  }
896
- // Use append parent's base local rotation only (IK rotations are applied after solving)
897
- let appendRot = localRot[appendParentIdx];
899
+ const appendRot = localRot[appendParentIdx];
898
900
  let ax = appendRot.x, ay = appendRot.y, az = appendRot.z;
899
901
  const aw = appendRot.w;
900
902
  const absRatio = ratio < 0 ? -ratio : ratio;
@@ -903,9 +905,20 @@ export class Model {
903
905
  ay = -ay;
904
906
  az = -az;
905
907
  }
906
- const appendQuat = new Quat(ax, ay, az, aw);
907
- const result = Quat.slerp(Quat.identity(), appendQuat, absRatio);
908
- rotateM = Mat4.fromQuat(result.x, result.y, result.z, result.w).multiply(rotateM);
908
+ // slerp(identity, appendQuat, absRatio) into scratchQuat[1]
909
+ scratchQuat[0].setXYZW(ax, ay, az, aw);
910
+ scratchQuat[2].setIdentity();
911
+ Quat.slerpInto(scratchQuat[2], scratchQuat[0], absRatio, scratchQuat[1]);
912
+ // finalRot = slerpResult * finalRot (rotation composition as quat mul)
913
+ const sx = scratchQuat[1].x, sy = scratchQuat[1].y, sz = scratchQuat[1].z, sw = scratchQuat[1].w;
914
+ const nx = sw * fx + sx * fw + sy * fz - sz * fy;
915
+ const ny = sw * fy - sx * fz + sy * fw + sz * fx;
916
+ const nz = sw * fz + sx * fy - sy * fx + sz * fw;
917
+ const nw = sw * fw - sx * fx - sy * fy - sz * fz;
918
+ fx = nx;
919
+ fy = ny;
920
+ fz = nz;
921
+ fw = nw;
909
922
  }
910
923
  if (b.appendMove) {
911
924
  const appendTrans = localTrans[appendParentIdx];
@@ -919,18 +932,16 @@ export class Model {
919
932
  const localTx = boneTrans.x + addLocalTx;
920
933
  const localTy = boneTrans.y + addLocalTy;
921
934
  const localTz = boneTrans.z + addLocalTz;
922
- this.cachedIdentityMat1
923
- .setIdentity()
924
- .translateInPlace(b.bindTranslation[0], b.bindTranslation[1], b.bindTranslation[2]);
925
- this.cachedIdentityMat2.setIdentity().translateInPlace(localTx, localTy, localTz);
926
- const localM = this.cachedIdentityMat1.multiply(rotateM).multiply(this.cachedIdentityMat2);
935
+ // Fused local transform: T_bind · R(finalRot) · T_local → scratchMat4Values[0]
936
+ const localMVals = scratchMat4Values[0];
937
+ Mat4.localTransformInto(b.bindTranslation[0], b.bindTranslation[1], b.bindTranslation[2], fx, fy, fz, fw, localTx, localTy, localTz, localMVals);
927
938
  const worldMat = worldMats[boneIndex];
928
939
  if (b.parentIndex >= 0) {
929
940
  const parentMat = worldMats[b.parentIndex];
930
- Mat4.multiplyArrays(parentMat.values, 0, localM.values, 0, worldMat.values, 0);
941
+ Mat4.multiplyArrays(parentMat.values, 0, localMVals, 0, worldMat.values, 0);
931
942
  }
932
943
  else {
933
- worldMat.values.set(localM.values);
944
+ worldMat.values.set(localMVals);
934
945
  }
935
946
  }
936
947
  computeWorldMatrices() {
@@ -950,10 +961,14 @@ export class Model {
950
961
  if (b.parentIndex >= boneCount) {
951
962
  console.warn(`[RZM] bone ${i} parent out of range: ${b.parentIndex}`);
952
963
  }
964
+ // Ensure parent is computed FIRST, before we touch any scratch buffers.
965
+ // Recursion may itself use scratchMat4Values[0] / scratchQuat; doing it up
966
+ // front keeps the current frame's scratch slots untouched when we use them below.
967
+ if (b.parentIndex >= 0 && !computed[b.parentIndex])
968
+ computeWorld(b.parentIndex);
953
969
  const boneRot = localRot[i];
954
- let rotateM = Mat4.fromQuat(boneRot.x, boneRot.y, boneRot.z, boneRot.w);
970
+ let fx = boneRot.x, fy = boneRot.y, fz = boneRot.z, fw = boneRot.w;
955
971
  let addLocalTx = 0, addLocalTy = 0, addLocalTz = 0;
956
- // Optimized append rotation check - only check necessary conditions
957
972
  const appendParentIdx = b.appendParentIndex;
958
973
  const hasAppend = b.appendRotate && appendParentIdx !== undefined && appendParentIdx >= 0 && appendParentIdx < boneCount;
959
974
  if (hasAppend) {
@@ -962,9 +977,7 @@ export class Model {
962
977
  if (hasRatio) {
963
978
  if (b.appendRotate) {
964
979
  const appendRot = localRot[appendParentIdx];
965
- let ax = appendRot.x;
966
- let ay = appendRot.y;
967
- let az = appendRot.z;
980
+ let ax = appendRot.x, ay = appendRot.y, az = appendRot.z;
968
981
  const aw = appendRot.w;
969
982
  const absRatio = ratio < 0 ? -ratio : ratio;
970
983
  if (ratio < 0) {
@@ -972,9 +985,19 @@ export class Model {
972
985
  ay = -ay;
973
986
  az = -az;
974
987
  }
975
- const appendQuat = new Quat(ax, ay, az, aw);
976
- const result = Quat.slerp(Quat.identity(), appendQuat, absRatio);
977
- rotateM = Mat4.fromQuat(result.x, result.y, result.z, result.w).multiply(rotateM);
988
+ scratchQuat[0].setXYZW(ax, ay, az, aw);
989
+ scratchQuat[2].setIdentity();
990
+ Quat.slerpInto(scratchQuat[2], scratchQuat[0], absRatio, scratchQuat[1]);
991
+ // finalRot = slerpResult * finalRot (quat mul)
992
+ const sx = scratchQuat[1].x, sy = scratchQuat[1].y, sz = scratchQuat[1].z, sw = scratchQuat[1].w;
993
+ const nx = sw * fx + sx * fw + sy * fz - sz * fy;
994
+ const ny = sw * fy - sx * fz + sy * fw + sz * fx;
995
+ const nz = sw * fz + sx * fy - sy * fx + sz * fw;
996
+ const nw = sw * fw - sx * fx - sy * fy - sz * fz;
997
+ fx = nx;
998
+ fy = ny;
999
+ fz = nz;
1000
+ fw = nw;
978
1001
  }
979
1002
  if (b.appendMove) {
980
1003
  const appendTrans = localTrans[appendParentIdx];
@@ -985,27 +1008,19 @@ export class Model {
985
1008
  }
986
1009
  }
987
1010
  }
988
- // Build local matrix: identity + bind translation, then rotation, then local translation, then append translation
989
1011
  const boneTrans = localTrans[i];
990
1012
  const localTx = boneTrans.x + addLocalTx;
991
1013
  const localTy = boneTrans.y + addLocalTy;
992
1014
  const localTz = boneTrans.z + addLocalTz;
993
- this.cachedIdentityMat1
994
- .setIdentity()
995
- .translateInPlace(b.bindTranslation[0], b.bindTranslation[1], b.bindTranslation[2]);
996
- this.cachedIdentityMat2.setIdentity().translateInPlace(localTx, localTy, localTz);
997
- const localM = this.cachedIdentityMat1.multiply(rotateM).multiply(this.cachedIdentityMat2);
1015
+ const localMVals = scratchMat4Values[0];
1016
+ Mat4.localTransformInto(b.bindTranslation[0], b.bindTranslation[1], b.bindTranslation[2], fx, fy, fz, fw, localTx, localTy, localTz, localMVals);
998
1017
  const worldMat = worldMats[i];
999
1018
  if (b.parentIndex >= 0) {
1000
- const p = b.parentIndex;
1001
- if (!computed[p])
1002
- computeWorld(p);
1003
- const parentMat = worldMats[p];
1004
- // Multiply parent world matrix by local matrix
1005
- Mat4.multiplyArrays(parentMat.values, 0, localM.values, 0, worldMat.values, 0);
1019
+ const parentMat = worldMats[b.parentIndex];
1020
+ Mat4.multiplyArrays(parentMat.values, 0, localMVals, 0, worldMat.values, 0);
1006
1021
  }
1007
1022
  else {
1008
- worldMat.values.set(localM.values);
1023
+ worldMat.values.set(localMVals);
1009
1024
  }
1010
1025
  computed[i] = true;
1011
1026
  };
@@ -1 +1 @@
1
- {"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../src/physics.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../src/physics.ts"],"names":[],"mappings":"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"}
package/dist/physics.js CHANGED
@@ -1,4 +1,10 @@
1
1
  import { Quat, Vec3, Mat4 } from "./math";
2
+ // Physics-local scratch pool for per-frame sync (syncFromBones, applyAmmoRigidbodiesToBones).
3
+ // Each method uses only these slots and completes synchronously before the next is called.
4
+ const _physMat = [
5
+ new Float32Array(16), new Float32Array(16), new Float32Array(16),
6
+ ];
7
+ const _physQuat = new Quat(0, 0, 0, 1);
2
8
  import { loadAmmo } from "./ammo-loader";
3
9
  export var RigidbodyShape;
4
10
  (function (RigidbodyShape) {
@@ -358,9 +364,9 @@ export class Physics {
358
364
  this.firstFrame = false;
359
365
  }
360
366
  // Step order: 1) Sync Static/Kinematic from bones, 2) Step physics, 3) Apply dynamic to bones
361
- this.syncFromBones(boneWorldMatrices, boneInverseBindMatrices, boneCount);
367
+ this.syncFromBones(boneWorldMatrices, boneCount);
362
368
  this.stepAmmoPhysics(dt);
363
- this.applyAmmoRigidbodiesToBones(boneWorldMatrices, boneInverseBindMatrices, boneCount);
369
+ this.applyAmmoRigidbodiesToBones(boneWorldMatrices, boneCount);
364
370
  }
365
371
  // Compute bodyOffsetMatrixInverse for all rigidbodies (called once during initialization)
366
372
  computeBodyOffsets(boneInverseBindMatrices, boneCount) {
@@ -425,7 +431,7 @@ export class Physics {
425
431
  }
426
432
  }
427
433
  // Sync Static (FollowBone) and Kinematic rigidbodies to follow bone transforms
428
- syncFromBones(boneWorldMatrices, boneInverseBindMatrices, boneCount) {
434
+ syncFromBones(boneWorldMatrices, boneCount) {
429
435
  if (!this.ammo || !this.dynamicsWorld)
430
436
  return;
431
437
  const Ammo = this.ammo;
@@ -440,14 +446,17 @@ export class Physics {
440
446
  rb.boneIndex < boneCount) {
441
447
  const boneIdx = rb.boneIndex;
442
448
  const boneWorldMat = boneWorldMatrices[boneIdx];
443
- // nodeWorld = boneWorld × shapeLocal (not shapeLocal × boneWorld)
444
- const bodyOffsetMatrix = rb.bodyOffsetMatrix || rb.bodyOffsetMatrixInverse.inverse();
445
- const nodeWorldMatrix = boneWorldMat.multiply(bodyOffsetMatrix);
446
- const worldPos = nodeWorldMatrix.getPosition();
447
- const worldRot = nodeWorldMatrix.toQuat();
449
+ // Lazy-cache bodyOffsetMatrix on first hit (cold path).
450
+ if (!rb.bodyOffsetMatrix)
451
+ rb.bodyOffsetMatrix = rb.bodyOffsetMatrixInverse.inverse();
452
+ // nodeWorld = boneWorld × bodyOffsetMatrix → _physMat[0]
453
+ Mat4.multiplyArrays(boneWorldMat.values, 0, rb.bodyOffsetMatrix.values, 0, _physMat[0], 0);
454
+ const nodeVals = _physMat[0];
455
+ const wx = nodeVals[12], wy = nodeVals[13], wz = nodeVals[14];
456
+ Mat4.toQuatFromArrayInto(nodeVals, 0, _physQuat);
448
457
  const transform = new Ammo.btTransform();
449
- const pos = new Ammo.btVector3(worldPos.x, worldPos.y, worldPos.z);
450
- const quat = new Ammo.btQuaternion(worldRot.x, worldRot.y, worldRot.z, worldRot.w);
458
+ const pos = new Ammo.btVector3(wx, wy, wz);
459
+ const quat = new Ammo.btQuaternion(_physQuat.x, _physQuat.y, _physQuat.z, _physQuat.w);
451
460
  transform.setOrigin(pos);
452
461
  transform.setRotation(quat);
453
462
  ammoBody.setWorldTransform(transform);
@@ -472,7 +481,7 @@ export class Physics {
472
481
  this.dynamicsWorld.stepSimulation(dt, maxSubSteps, fixedTimeStep);
473
482
  }
474
483
  // Apply dynamic rigidbody world transforms to bone world matrices in-place
475
- applyAmmoRigidbodiesToBones(boneWorldMatrices, boneInverseBindMatrices, boneCount) {
484
+ applyAmmoRigidbodiesToBones(boneWorldMatrices, boneCount) {
476
485
  if (!this.ammo || !this.dynamicsWorld)
477
486
  return;
478
487
  for (let i = 0; i < this.rigidbodies.length; i++) {
@@ -486,14 +495,13 @@ export class Physics {
486
495
  const transform = ammoBody.getWorldTransform();
487
496
  const origin = transform.getOrigin();
488
497
  const rotation = transform.getRotation();
489
- const nodePos = new Vec3(origin.x(), origin.y(), origin.z());
490
- const nodeRot = new Quat(rotation.x(), rotation.y(), rotation.z(), rotation.w());
491
- const nodeWorldMatrix = Mat4.fromPositionRotation(nodePos, nodeRot);
492
- // boneWorld = nodeWorld × bodyOffsetMatrixInverse (not bodyOffsetMatrixInverse × nodeWorld)
493
- const boneWorldMat = nodeWorldMatrix.multiply(rb.bodyOffsetMatrixInverse);
494
- const values = boneWorldMat.values;
495
- if (!isNaN(values[0]) && !isNaN(values[15]) && Math.abs(values[0]) < 1e6 && Math.abs(values[15]) < 1e6) {
496
- boneWorldMatrices[boneIdx].values.set(values);
498
+ // nodeWorldMatrix _physMat[0] (from ammo position/rotation directly)
499
+ Mat4.fromPositionRotationInto(origin.x(), origin.y(), origin.z(), rotation.x(), rotation.y(), rotation.z(), rotation.w(), _physMat[0]);
500
+ // boneWorld = nodeWorld × bodyOffsetMatrixInverse → _physMat[1]
501
+ const boneVals = _physMat[1];
502
+ Mat4.multiplyArrays(_physMat[0], 0, rb.bodyOffsetMatrixInverse.values, 0, boneVals, 0);
503
+ if (!isNaN(boneVals[0]) && !isNaN(boneVals[15]) && Math.abs(boneVals[0]) < 1e6 && Math.abs(boneVals[15]) < 1e6) {
504
+ boneWorldMatrices[boneIdx].values.set(boneVals);
497
505
  }
498
506
  else {
499
507
  console.warn(`[Physics] Invalid bone world matrix for rigidbody ${i} (${rb.name}), skipping update`);