reze-engine 0.3.9 → 0.3.11

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/model.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Mat4, Quat, Vec3, easeInOut, bezierInterpolate } from "./math";
1
+ import { Mat4, Quat, Vec3, bezierInterpolate } from "./math";
2
2
  import { Physics } from "./physics";
3
3
  import { IKSolverSystem } from "./ik-solver";
4
4
  import { VMDLoader } from "./vmd-loader";
@@ -58,25 +58,24 @@ export class Model {
58
58
  }
59
59
  initializeRuntimeSkeleton() {
60
60
  const boneCount = this.skeleton.bones.length;
61
+ // Pre-allocate object arrays for skeletal pose
62
+ const localRotations = new Array(boneCount);
63
+ const localTranslations = new Array(boneCount);
64
+ const worldMatrices = new Array(boneCount);
65
+ for (let i = 0; i < boneCount; i++) {
66
+ localRotations[i] = Quat.identity();
67
+ localTranslations[i] = Vec3.zeros();
68
+ worldMatrices[i] = Mat4.identity();
69
+ }
61
70
  this.runtimeSkeleton = {
62
- localRotations: new Float32Array(boneCount * 4),
63
- localTranslations: new Float32Array(boneCount * 3),
64
- worldMatrices: new Float32Array(boneCount * 16),
71
+ localRotations,
72
+ localTranslations,
73
+ worldMatrices,
65
74
  nameIndex: this.skeleton.bones.reduce((acc, bone, index) => {
66
75
  acc[bone.name] = index;
67
76
  return acc;
68
77
  }, {}),
69
78
  };
70
- const rotations = this.runtimeSkeleton.localRotations;
71
- for (let i = 0; i < this.skeleton.bones.length; i++) {
72
- const qi = i * 4;
73
- if (rotations[qi + 3] === 0) {
74
- rotations[qi] = 0;
75
- rotations[qi + 1] = 0;
76
- rotations[qi + 2] = 0;
77
- rotations[qi + 3] = 1;
78
- }
79
- }
80
79
  // Initialize IK runtime state
81
80
  this.initializeIKRuntime();
82
81
  }
@@ -87,8 +86,8 @@ export class Model {
87
86
  const ikChainInfo = new Array(boneCount);
88
87
  for (let i = 0; i < boneCount; i++) {
89
88
  ikChainInfo[i] = {
90
- ikRotation: new Quat(0, 0, 0, 1),
91
- localRotation: new Quat(0, 0, 0, 1),
89
+ ikRotation: Quat.identity(),
90
+ localRotation: Quat.identity(),
92
91
  };
93
92
  }
94
93
  // Build IK solvers from bone data
@@ -114,17 +113,28 @@ export class Model {
114
113
  initializeTweenBuffers() {
115
114
  const boneCount = this.skeleton.bones.length;
116
115
  const morphCount = this.morphing.morphs.length;
116
+ // Pre-allocate Quat and Vec3 arrays to avoid reallocation during tweens
117
+ const rotStartQuat = new Array(boneCount);
118
+ const rotTargetQuat = new Array(boneCount);
119
+ const transStartVec = new Array(boneCount);
120
+ const transTargetVec = new Array(boneCount);
121
+ for (let i = 0; i < boneCount; i++) {
122
+ rotStartQuat[i] = Quat.identity();
123
+ rotTargetQuat[i] = Quat.identity();
124
+ transStartVec[i] = Vec3.zeros();
125
+ transTargetVec[i] = Vec3.zeros();
126
+ }
117
127
  this.tweenState = {
118
128
  // Bone rotation tweens
119
129
  rotActive: new Uint8Array(boneCount),
120
- rotStartQuat: new Float32Array(boneCount * 4),
121
- rotTargetQuat: new Float32Array(boneCount * 4),
130
+ rotStartQuat,
131
+ rotTargetQuat,
122
132
  rotStartTimeMs: new Float32Array(boneCount),
123
133
  rotDurationMs: new Float32Array(boneCount),
124
134
  // Bone translation tweens
125
135
  transActive: new Uint8Array(boneCount),
126
- transStartVec: new Float32Array(boneCount * 3),
127
- transTargetVec: new Float32Array(boneCount * 3),
136
+ transStartVec,
137
+ transTargetVec,
128
138
  transStartTimeMs: new Float32Array(boneCount),
129
139
  transDurationMs: new Float32Array(boneCount),
130
140
  // Morph weight tweens
@@ -161,15 +171,9 @@ export class Model {
161
171
  const startMs = state.rotStartTimeMs[i];
162
172
  const durMs = Math.max(1, state.rotDurationMs[i]);
163
173
  const t = Math.max(0, Math.min(1, (now - startMs) / durMs));
164
- const e = easeInOut(t);
165
- const qi = i * 4;
166
- const startQuat = new Quat(state.rotStartQuat[qi], state.rotStartQuat[qi + 1], state.rotStartQuat[qi + 2], state.rotStartQuat[qi + 3]);
167
- const targetQuat = new Quat(state.rotTargetQuat[qi], state.rotTargetQuat[qi + 1], state.rotTargetQuat[qi + 2], state.rotTargetQuat[qi + 3]);
168
- const result = Quat.slerp(startQuat, targetQuat, e);
169
- rotations[qi] = result.x;
170
- rotations[qi + 1] = result.y;
171
- rotations[qi + 2] = result.z;
172
- rotations[qi + 3] = result.w;
174
+ const e = t; // Linear interpolation
175
+ const result = Quat.slerp(state.rotStartQuat[i], state.rotTargetQuat[i], e);
176
+ rotations[i].set(result);
173
177
  if (t >= 1) {
174
178
  state.rotActive[i] = 0;
175
179
  }
@@ -182,13 +186,12 @@ export class Model {
182
186
  const startMs = state.transStartTimeMs[i];
183
187
  const durMs = Math.max(1, state.transDurationMs[i]);
184
188
  const t = Math.max(0, Math.min(1, (now - startMs) / durMs));
185
- const e = easeInOut(t);
186
- const ti = i * 3;
187
- translations[ti] = state.transStartVec[ti] + (state.transTargetVec[ti] - state.transStartVec[ti]) * e;
188
- translations[ti + 1] =
189
- state.transStartVec[ti + 1] + (state.transTargetVec[ti + 1] - state.transStartVec[ti + 1]) * e;
190
- translations[ti + 2] =
191
- state.transStartVec[ti + 2] + (state.transTargetVec[ti + 2] - state.transStartVec[ti + 2]) * e;
189
+ const e = t; // Linear interpolation
190
+ const startVec = state.transStartVec[i];
191
+ const targetVec = state.transTargetVec[i];
192
+ translations[i].x = startVec.x + (targetVec.x - startVec.x) * e;
193
+ translations[i].y = startVec.y + (targetVec.y - startVec.y) * e;
194
+ translations[i].z = startVec.z + (targetVec.z - startVec.z) * e;
192
195
  if (t >= 1) {
193
196
  state.transActive[i] = 0;
194
197
  }
@@ -202,7 +205,7 @@ export class Model {
202
205
  const startMs = state.morphStartTimeMs[i];
203
206
  const durMs = Math.max(1, state.morphDurationMs[i]);
204
207
  const t = Math.max(0, Math.min(1, (now - startMs) / durMs));
205
- const e = easeInOut(t);
208
+ const e = t; // Linear interpolation
206
209
  const oldWeight = weights[i];
207
210
  weights[i] = state.morphStartWeight[i] + (state.morphTargetWeight[i] - state.morphStartWeight[i]) * e;
208
211
  // Check if weight actually changed (accounting for floating point precision)
@@ -253,7 +256,8 @@ export class Model {
253
256
  // ------- Bone helpers (public API) -------
254
257
  rotateBones(names, quats, durationMs) {
255
258
  const state = this.tweenState;
256
- const normalized = quats.map((q) => q.normalize());
259
+ // Clone and normalize to avoid mutating input
260
+ quats.forEach((q) => q.normalize());
257
261
  const now = this.tweenTimeMs;
258
262
  const dur = durationMs && durationMs > 0 ? durationMs : 0;
259
263
  for (let i = 0; i < names.length; i++) {
@@ -261,42 +265,34 @@ export class Model {
261
265
  const idx = this.runtimeSkeleton.nameIndex[name] ?? -1;
262
266
  if (idx < 0 || idx >= this.skeleton.bones.length)
263
267
  continue;
264
- const qi = idx * 4;
265
268
  const rotations = this.runtimeSkeleton.localRotations;
266
- const [tx, ty, tz, tw] = normalized[i].toArray();
269
+ const targetNorm = quats[i];
267
270
  if (dur === 0) {
268
- rotations[qi] = tx;
269
- rotations[qi + 1] = ty;
270
- rotations[qi + 2] = tz;
271
- rotations[qi + 3] = tw;
271
+ rotations[idx].set(targetNorm);
272
272
  state.rotActive[idx] = 0;
273
273
  continue;
274
274
  }
275
- let sx = rotations[qi];
276
- let sy = rotations[qi + 1];
277
- let sz = rotations[qi + 2];
278
- let sw = rotations[qi + 3];
275
+ const currentRot = rotations[idx];
276
+ let sx = currentRot.x;
277
+ let sy = currentRot.y;
278
+ let sz = currentRot.z;
279
+ let sw = currentRot.w;
279
280
  if (state.rotActive[idx] === 1) {
280
281
  const startMs = state.rotStartTimeMs[idx];
281
282
  const prevDur = Math.max(1, state.rotDurationMs[idx]);
282
283
  const t = Math.max(0, Math.min(1, (now - startMs) / prevDur));
283
- const e = easeInOut(t);
284
- const startQuat = new Quat(state.rotStartQuat[qi], state.rotStartQuat[qi + 1], state.rotStartQuat[qi + 2], state.rotStartQuat[qi + 3]);
285
- const targetQuat = new Quat(state.rotTargetQuat[qi], state.rotTargetQuat[qi + 1], state.rotTargetQuat[qi + 2], state.rotTargetQuat[qi + 3]);
286
- const result = Quat.slerp(startQuat, targetQuat, e);
284
+ const e = t; // Linear interpolation
285
+ const result = Quat.slerp(state.rotStartQuat[idx], state.rotTargetQuat[idx], e);
287
286
  sx = result.x;
288
287
  sy = result.y;
289
288
  sz = result.z;
290
289
  sw = result.w;
291
290
  }
292
- state.rotStartQuat[qi] = sx;
293
- state.rotStartQuat[qi + 1] = sy;
294
- state.rotStartQuat[qi + 2] = sz;
295
- state.rotStartQuat[qi + 3] = sw;
296
- state.rotTargetQuat[qi] = tx;
297
- state.rotTargetQuat[qi + 1] = ty;
298
- state.rotTargetQuat[qi + 2] = tz;
299
- state.rotTargetQuat[qi + 3] = tw;
291
+ state.rotStartQuat[idx].x = sx;
292
+ state.rotStartQuat[idx].y = sy;
293
+ state.rotStartQuat[idx].z = sz;
294
+ state.rotStartQuat[idx].w = sw;
295
+ state.rotTargetQuat[idx].set(targetNorm);
300
296
  state.rotStartTimeMs[idx] = now;
301
297
  state.rotDurationMs[idx] = dur;
302
298
  state.rotActive[idx] = 1;
@@ -308,7 +304,6 @@ export class Model {
308
304
  const state = this.tweenState;
309
305
  const now = this.tweenTimeMs;
310
306
  const dur = durationMs && durationMs > 0 ? durationMs : 0;
311
- const localRot = this.runtimeSkeleton.localRotations;
312
307
  // Compute bind pose world positions for all bones
313
308
  const skeleton = this.skeleton;
314
309
  const computeBindPoseWorldPosition = (idx) => {
@@ -328,9 +323,8 @@ export class Model {
328
323
  if (idx < 0 || idx >= this.skeleton.bones.length)
329
324
  continue;
330
325
  const bone = this.skeleton.bones[idx];
331
- const ti = idx * 3;
332
- const qi = idx * 4;
333
326
  const translations = this.runtimeSkeleton.localTranslations;
327
+ const localRot = this.runtimeSkeleton.localRotations;
334
328
  const vmdRelativeTranslation = relativeTranslations[i];
335
329
  // VMD translation is relative to bind pose world position
336
330
  // targetWorldPos = bindPoseWorldPos + vmdRelativeTranslation
@@ -343,7 +337,7 @@ export class Model {
343
337
  parentBindPoseWorldPos = computeBindPoseWorldPosition(bone.parentIndex);
344
338
  }
345
339
  else {
346
- parentBindPoseWorldPos = new Vec3(0, 0, 0);
340
+ parentBindPoseWorldPos = Vec3.zeros();
347
341
  }
348
342
  // Transform target world position to parent's local space
349
343
  // In bind pose, parent's world matrix is just a translation
@@ -351,44 +345,55 @@ export class Model {
351
345
  // Subtract bindTranslation to get position after bind translation
352
346
  const afterBindTranslation = parentSpacePos.subtract(new Vec3(bone.bindTranslation[0], bone.bindTranslation[1], bone.bindTranslation[2]));
353
347
  // Apply inverse rotation to get local translation
354
- const localRotation = new Quat(localRot[qi], localRot[qi + 1], localRot[qi + 2], localRot[qi + 3]);
355
- const invRotation = localRotation.conjugate().normalize();
348
+ const localRotation = localRot[idx];
349
+ // Clone to avoid mutating, then conjugate and normalize
350
+ const invRotation = localRotation.clone().conjugate().normalize();
356
351
  const rotationMat = Mat4.fromQuat(invRotation.x, invRotation.y, invRotation.z, invRotation.w);
357
352
  const rm = rotationMat.values;
358
353
  const localTranslation = new Vec3(rm[0] * afterBindTranslation.x + rm[4] * afterBindTranslation.y + rm[8] * afterBindTranslation.z, rm[1] * afterBindTranslation.x + rm[5] * afterBindTranslation.y + rm[9] * afterBindTranslation.z, rm[2] * afterBindTranslation.x + rm[6] * afterBindTranslation.y + rm[10] * afterBindTranslation.z);
359
354
  const [tx, ty, tz] = [localTranslation.x, localTranslation.y, localTranslation.z];
360
355
  if (dur === 0) {
361
- translations[ti] = tx;
362
- translations[ti + 1] = ty;
363
- translations[ti + 2] = tz;
356
+ translations[idx].x = tx;
357
+ translations[idx].y = ty;
358
+ translations[idx].z = tz;
364
359
  state.transActive[idx] = 0;
365
360
  continue;
366
361
  }
367
- let sx = translations[ti];
368
- let sy = translations[ti + 1];
369
- let sz = translations[ti + 2];
362
+ const currentTrans = translations[idx];
363
+ let sx = currentTrans.x;
364
+ let sy = currentTrans.y;
365
+ let sz = currentTrans.z;
370
366
  if (state.transActive[idx] === 1) {
371
367
  const startMs = state.transStartTimeMs[idx];
372
368
  const prevDur = Math.max(1, state.transDurationMs[idx]);
373
369
  const t = Math.max(0, Math.min(1, (now - startMs) / prevDur));
374
- const e = easeInOut(t);
375
- sx = state.transStartVec[ti] + (state.transTargetVec[ti] - state.transStartVec[ti]) * e;
376
- sy = state.transStartVec[ti + 1] + (state.transTargetVec[ti + 1] - state.transStartVec[ti + 1]) * e;
377
- sz = state.transStartVec[ti + 2] + (state.transTargetVec[ti + 2] - state.transStartVec[ti + 2]) * e;
370
+ const e = t; // Linear interpolation
371
+ const startVec = state.transStartVec[idx];
372
+ const targetVec = state.transTargetVec[idx];
373
+ sx = startVec.x + (targetVec.x - startVec.x) * e;
374
+ sy = startVec.y + (targetVec.y - startVec.y) * e;
375
+ sz = startVec.z + (targetVec.z - startVec.z) * e;
378
376
  }
379
- state.transStartVec[ti] = sx;
380
- state.transStartVec[ti + 1] = sy;
381
- state.transStartVec[ti + 2] = sz;
382
- state.transTargetVec[ti] = tx;
383
- state.transTargetVec[ti + 1] = ty;
384
- state.transTargetVec[ti + 2] = tz;
377
+ state.transStartVec[idx].x = sx;
378
+ state.transStartVec[idx].y = sy;
379
+ state.transStartVec[idx].z = sz;
380
+ state.transTargetVec[idx].x = tx;
381
+ state.transTargetVec[idx].y = ty;
382
+ state.transTargetVec[idx].z = tz;
385
383
  state.transStartTimeMs[idx] = now;
386
384
  state.transDurationMs[idx] = dur;
387
385
  state.transActive[idx] = 1;
388
386
  }
389
387
  }
390
388
  getBoneWorldMatrices() {
391
- return this.runtimeSkeleton.worldMatrices;
389
+ // Convert Mat4[] to Float32Array for WebGPU compatibility
390
+ const boneCount = this.skeleton.bones.length;
391
+ const worldMats = this.runtimeSkeleton.worldMatrices;
392
+ const result = new Float32Array(boneCount * 16);
393
+ for (let i = 0; i < boneCount; i++) {
394
+ result.set(worldMats[i].values, i * 16);
395
+ }
396
+ return result;
392
397
  }
393
398
  getBoneInverseBindMatrices() {
394
399
  return this.skeleton.inverseBindMatrices;
@@ -398,17 +403,16 @@ export class Model {
398
403
  const worldMats = this.runtimeSkeleton.worldMatrices;
399
404
  const invBindMats = this.skeleton.inverseBindMatrices;
400
405
  // Initialize cached array if needed or if bone count changed
401
- if (!this.cachedSkinMatrices || this.cachedSkinMatrices.length !== boneCount * 16) {
402
- this.cachedSkinMatrices = new Float32Array(boneCount * 16);
406
+ if (!this.skinMatricesArray || this.skinMatricesArray.length !== boneCount * 16) {
407
+ this.skinMatricesArray = new Float32Array(boneCount * 16);
403
408
  }
404
- const skinMatrices = this.cachedSkinMatrices;
409
+ const skinMatrices = this.skinMatricesArray;
405
410
  // Compute skin matrices: skinMatrix = worldMatrix × inverseBindMatrix
406
- // Use Mat4.multiplyArrays to avoid creating Mat4 objects
407
411
  for (let i = 0; i < boneCount; i++) {
408
- const worldOffset = i * 16;
412
+ const worldMat = worldMats[i];
409
413
  const invBindOffset = i * 16;
410
414
  const skinOffset = i * 16;
411
- Mat4.multiplyArrays(worldMats, worldOffset, invBindMats, invBindOffset, skinMatrices, skinOffset);
415
+ Mat4.multiplyArrays(worldMat.values, 0, invBindMats, invBindOffset, skinMatrices, skinOffset);
412
416
  }
413
417
  return skinMatrices;
414
418
  }
@@ -434,7 +438,7 @@ export class Model {
434
438
  const startMs = state.morphStartTimeMs[idx];
435
439
  const prevDur = Math.max(1, state.morphDurationMs[idx]);
436
440
  const t = Math.max(0, Math.min(1, (now - startMs) / prevDur));
437
- const e = easeInOut(t);
441
+ const e = t; // Linear interpolation
438
442
  startWeight = state.morphStartWeight[idx] + (state.morphTargetWeight[idx] - state.morphStartWeight[idx]) * e;
439
443
  }
440
444
  state.morphStartWeight[idx] = startWeight;
@@ -662,17 +666,12 @@ export class Model {
662
666
  const boneIdx = this.runtimeSkeleton.nameIndex[boneName];
663
667
  if (boneIdx === undefined)
664
668
  continue;
665
- const rotOffset = boneIdx * 4;
666
- const transOffset = boneIdx * 3;
669
+ const localRot = this.runtimeSkeleton.localRotations[boneIdx];
670
+ const localTrans = this.runtimeSkeleton.localTranslations[boneIdx];
667
671
  if (!frameB) {
668
672
  // No interpolation needed - direct assignment
669
- this.runtimeSkeleton.localRotations[rotOffset] = frameA.rotation.x;
670
- this.runtimeSkeleton.localRotations[rotOffset + 1] = frameA.rotation.y;
671
- this.runtimeSkeleton.localRotations[rotOffset + 2] = frameA.rotation.z;
672
- this.runtimeSkeleton.localRotations[rotOffset + 3] = frameA.rotation.w;
673
- this.runtimeSkeleton.localTranslations[transOffset] = frameA.translation.x;
674
- this.runtimeSkeleton.localTranslations[transOffset + 1] = frameA.translation.y;
675
- this.runtimeSkeleton.localTranslations[transOffset + 2] = frameA.translation.z;
673
+ localRot.set(frameA.rotation);
674
+ localTrans.set(frameA.translation);
676
675
  }
677
676
  else {
678
677
  const timeA = keyFrames[idx].time;
@@ -682,7 +681,7 @@ export class Model {
682
681
  const interp = frameB.interpolation;
683
682
  // Pre-normalize interpolation values (avoid division in bezierInterpolate)
684
683
  const rotT = bezierInterpolate(interp[0] * INV_127, interp[1] * INV_127, interp[2] * INV_127, interp[3] * INV_127, gradient);
685
- // Use Quat.slerp but extract components directly to avoid object allocation
684
+ // Use Quat.slerp to interpolate rotation
686
685
  const rotation = Quat.slerp(frameA.rotation, frameB.rotation, rotT);
687
686
  // Interpolate translation using bezier for each component
688
687
  // Inline getWeight to avoid function call overhead
@@ -690,17 +689,11 @@ export class Model {
690
689
  const txWeight = getWeight(0);
691
690
  const tyWeight = getWeight(16);
692
691
  const tzWeight = getWeight(32);
693
- // Direct array writes instead of Vec3 allocation
694
- this.runtimeSkeleton.localRotations[rotOffset] = rotation.x;
695
- this.runtimeSkeleton.localRotations[rotOffset + 1] = rotation.y;
696
- this.runtimeSkeleton.localRotations[rotOffset + 2] = rotation.z;
697
- this.runtimeSkeleton.localRotations[rotOffset + 3] = rotation.w;
698
- this.runtimeSkeleton.localTranslations[transOffset] =
699
- frameA.translation.x + (frameB.translation.x - frameA.translation.x) * txWeight;
700
- this.runtimeSkeleton.localTranslations[transOffset + 1] =
701
- frameA.translation.y + (frameB.translation.y - frameA.translation.y) * tyWeight;
702
- this.runtimeSkeleton.localTranslations[transOffset + 2] =
703
- frameA.translation.z + (frameB.translation.z - frameA.translation.z) * tzWeight;
692
+ // Direct property writes to avoid object allocation
693
+ localRot.set(rotation);
694
+ localTrans.x = frameA.translation.x + (frameB.translation.x - frameA.translation.x) * txWeight;
695
+ localTrans.y = frameA.translation.y + (frameB.translation.y - frameA.translation.y) * tyWeight;
696
+ localTrans.z = frameA.translation.z + (frameB.translation.z - frameA.translation.z) * tzWeight;
704
697
  }
705
698
  }
706
699
  // Process morph tracks
@@ -784,7 +777,7 @@ export class Model {
784
777
  const bones = this.skeleton.bones;
785
778
  const localRot = this.runtimeSkeleton.localRotations;
786
779
  const localTrans = this.runtimeSkeleton.localTranslations;
787
- const worldBuf = this.runtimeSkeleton.worldMatrices;
780
+ const worldMats = this.runtimeSkeleton.worldMatrices;
788
781
  const boneCount = bones.length;
789
782
  if (boneCount === 0)
790
783
  return;
@@ -797,8 +790,8 @@ export class Model {
797
790
  if (b.parentIndex >= boneCount) {
798
791
  console.warn(`[RZM] bone ${i} parent out of range: ${b.parentIndex}`);
799
792
  }
800
- const qi = i * 4;
801
- let rotateM = Mat4.fromQuat(localRot[qi], localRot[qi + 1], localRot[qi + 2], localRot[qi + 3]);
793
+ const boneRot = localRot[i];
794
+ let rotateM = Mat4.fromQuat(boneRot.x, boneRot.y, boneRot.z, boneRot.w);
802
795
  let addLocalTx = 0, addLocalTy = 0, addLocalTz = 0;
803
796
  // Optimized append rotation check - only check necessary conditions
804
797
  const appendParentIdx = b.appendParentIndex;
@@ -807,13 +800,12 @@ export class Model {
807
800
  const ratio = b.appendRatio === undefined ? 1 : Math.max(-1, Math.min(1, b.appendRatio));
808
801
  const hasRatio = Math.abs(ratio) > 1e-6;
809
802
  if (hasRatio) {
810
- const apQi = appendParentIdx * 4;
811
- const apTi = appendParentIdx * 3;
812
803
  if (b.appendRotate) {
813
- let ax = localRot[apQi];
814
- let ay = localRot[apQi + 1];
815
- let az = localRot[apQi + 2];
816
- const aw = localRot[apQi + 3];
804
+ const appendRot = localRot[appendParentIdx];
805
+ let ax = appendRot.x;
806
+ let ay = appendRot.y;
807
+ let az = appendRot.z;
808
+ const aw = appendRot.w;
817
809
  const absRatio = ratio < 0 ? -ratio : ratio;
818
810
  if (ratio < 0) {
819
811
  ax = -ax;
@@ -821,39 +813,39 @@ export class Model {
821
813
  az = -az;
822
814
  }
823
815
  const appendQuat = new Quat(ax, ay, az, aw);
824
- const result = Quat.slerp(new Quat(0, 0, 0, 1), appendQuat, absRatio);
816
+ const result = Quat.slerp(Quat.identity(), appendQuat, absRatio);
825
817
  rotateM = Mat4.fromQuat(result.x, result.y, result.z, result.w).multiply(rotateM);
826
818
  }
827
819
  if (b.appendMove) {
820
+ const appendTrans = localTrans[appendParentIdx];
828
821
  const appendRatio = b.appendRatio ?? 1;
829
- addLocalTx = localTrans[apTi] * appendRatio;
830
- addLocalTy = localTrans[apTi + 1] * appendRatio;
831
- addLocalTz = localTrans[apTi + 2] * appendRatio;
822
+ addLocalTx = appendTrans.x * appendRatio;
823
+ addLocalTy = appendTrans.y * appendRatio;
824
+ addLocalTz = appendTrans.z * appendRatio;
832
825
  }
833
826
  }
834
827
  }
835
828
  // Build local matrix: identity + bind translation, then rotation, then local translation, then append translation
836
- const ti = i * 3;
837
- const localTx = localTrans[ti] + addLocalTx;
838
- const localTy = localTrans[ti + 1] + addLocalTy;
839
- const localTz = localTrans[ti + 2] + addLocalTz;
829
+ const boneTrans = localTrans[i];
830
+ const localTx = boneTrans.x + addLocalTx;
831
+ const localTy = boneTrans.y + addLocalTy;
832
+ const localTz = boneTrans.z + addLocalTz;
840
833
  this.cachedIdentityMat1
841
834
  .setIdentity()
842
835
  .translateInPlace(b.bindTranslation[0], b.bindTranslation[1], b.bindTranslation[2]);
843
836
  this.cachedIdentityMat2.setIdentity().translateInPlace(localTx, localTy, localTz);
844
837
  const localM = this.cachedIdentityMat1.multiply(rotateM).multiply(this.cachedIdentityMat2);
845
- const worldOffset = i * 16;
838
+ const worldMat = worldMats[i];
846
839
  if (b.parentIndex >= 0) {
847
840
  const p = b.parentIndex;
848
841
  if (!computed[p])
849
842
  computeWorld(p);
850
- const parentOffset = p * 16;
851
- // Use cachedIdentityMat2 as temporary buffer for parent * local multiplication
852
- Mat4.multiplyArrays(worldBuf, parentOffset, localM.values, 0, this.cachedIdentityMat2.values, 0);
853
- worldBuf.subarray(worldOffset, worldOffset + 16).set(this.cachedIdentityMat2.values);
843
+ const parentMat = worldMats[p];
844
+ // Multiply parent world matrix by local matrix
845
+ Mat4.multiplyArrays(parentMat.values, 0, localM.values, 0, worldMat.values, 0);
854
846
  }
855
847
  else {
856
- worldBuf.subarray(worldOffset, worldOffset + 16).set(localM.values);
848
+ worldMat.values.set(localM.values);
857
849
  }
858
850
  computed[i] = true;
859
851
  };
package/dist/physics.d.ts CHANGED
@@ -72,8 +72,8 @@ export declare class Physics {
72
72
  private createAmmoRigidbodies;
73
73
  private createAmmoJoints;
74
74
  private normalizeAngle;
75
- reset(boneWorldMatrices: Float32Array, boneInverseBindMatrices: Float32Array): void;
76
- step(dt: number, boneWorldMatrices: Float32Array, boneInverseBindMatrices: Float32Array): void;
75
+ reset(boneWorldMatrices: Mat4[], boneInverseBindMatrices: Float32Array): void;
76
+ step(dt: number, boneWorldMatrices: Mat4[], boneInverseBindMatrices: Float32Array): void;
77
77
  private computeBodyOffsets;
78
78
  private positionBodiesFromBones;
79
79
  private syncFromBones;
@@ -1 +1 @@
1
- {"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../src/physics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAIzC,oBAAY,cAAc;IACxB,MAAM,IAAI;IACV,GAAG,IAAI;IACP,OAAO,IAAI;CACZ;AAED,oBAAY,aAAa;IACvB,MAAM,IAAI;IACV,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;IACnB,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;AAED,qBAAa,OAAO;IAClB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,WAAW,CAAqC;IACxD,OAAO,CAAC,IAAI,CAA4B;IAExC,OAAO,CAAC,aAAa,CAAY;IAEjC,OAAO,CAAC,eAAe,CAAY;IAEnC,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,sBAAsB,CAAQ;IACtC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,oCAAoC,CAAO;IAEnD,OAAO,CAAC,UAAU,CAAY;gBAElB,WAAW,EAAE,SAAS,EAAE,EAAE,MAAM,GAAE,KAAK,EAAO;YAM5C,QAAQ;IAatB,UAAU,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI;IAU/B,UAAU,IAAI,IAAI;IAIlB,cAAc,IAAI,SAAS,EAAE;IAI7B,SAAS,IAAI,KAAK,EAAE;IAIpB,sBAAsB,IAAI,KAAK,CAAC;QAAE,QAAQ,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAA;KAAE,CAAC;IA6CnE,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,qBAAqB;IA+F7B,OAAO,CAAC,gBAAgB;IA0KxB,OAAO,CAAC,cAAc;IAetB,KAAK,CAAC,iBAAiB,EAAE,YAAY,EAAE,uBAAuB,EAAE,YAAY,GAAG,IAAI;IAuEnF,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,iBAAiB,EAAE,YAAY,EAAE,uBAAuB,EAAE,YAAY,GAAG,IAAI;IAsC9F,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,uBAAuB;IAkD/B,OAAO,CAAC,aAAa;IAwDrB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,2BAA2B;CAqCpC"}
1
+ {"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../src/physics.ts"],"names":[],"mappings":"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"}
package/dist/physics.js CHANGED
@@ -336,7 +336,7 @@ export class Physics {
336
336
  if (!this.ammoInitialized || !this.ammo || !this.dynamicsWorld) {
337
337
  return;
338
338
  }
339
- const boneCount = boneWorldMatrices.length / 16;
339
+ const boneCount = boneWorldMatrices.length;
340
340
  const Ammo = this.ammo;
341
341
  // Ensure body offsets are computed
342
342
  if (!this.rigidbodiesInitialized) {
@@ -351,9 +351,8 @@ export class Physics {
351
351
  if (!ammoBody || rb.boneIndex < 0 || rb.boneIndex >= boneCount)
352
352
  continue;
353
353
  const boneIdx = rb.boneIndex;
354
- const worldMatIdx = boneIdx * 16;
355
354
  // Get bone world matrix
356
- const boneWorldMat = new Mat4(boneWorldMatrices.subarray(worldMatIdx, worldMatIdx + 16));
355
+ const boneWorldMat = boneWorldMatrices[boneIdx];
357
356
  // Compute body world matrix: bodyWorld = boneWorld × bodyOffsetMatrix
358
357
  // (like babylon-mmd: bodyWorldMatrix = bodyOffsetMatrix.multiplyToRef(bodyWorldMatrix))
359
358
  const bodyOffsetMatrix = rb.bodyOffsetMatrix || rb.bodyOffsetMatrixInverse.inverse();
@@ -394,7 +393,7 @@ export class Physics {
394
393
  if (!this.ammoInitialized || !this.ammo || !this.dynamicsWorld) {
395
394
  return;
396
395
  }
397
- const boneCount = boneWorldMatrices.length / 16;
396
+ const boneCount = boneWorldMatrices.length;
398
397
  if (this.firstFrame) {
399
398
  if (!this.rigidbodiesInitialized) {
400
399
  this.computeBodyOffsets(boneInverseBindMatrices, boneCount);
@@ -451,8 +450,7 @@ export class Physics {
451
450
  if (!ammoBody || rb.boneIndex < 0 || rb.boneIndex >= boneCount)
452
451
  continue;
453
452
  const boneIdx = rb.boneIndex;
454
- const worldMatIdx = boneIdx * 16;
455
- const boneWorldMat = new Mat4(boneWorldMatrices.subarray(worldMatIdx, worldMatIdx + 16));
453
+ const boneWorldMat = boneWorldMatrices[boneIdx];
456
454
  // nodeWorld = boneWorld × shapeLocal (not shapeLocal × boneWorld)
457
455
  const bodyOffsetMatrix = rb.bodyOffsetMatrix || rb.bodyOffsetMatrixInverse.inverse();
458
456
  const nodeWorldMatrix = boneWorldMat.multiply(bodyOffsetMatrix);
@@ -494,8 +492,7 @@ export class Physics {
494
492
  rb.boneIndex >= 0 &&
495
493
  rb.boneIndex < boneCount) {
496
494
  const boneIdx = rb.boneIndex;
497
- const worldMatIdx = boneIdx * 16;
498
- const boneWorldMat = new Mat4(boneWorldMatrices.subarray(worldMatIdx, worldMatIdx + 16));
495
+ const boneWorldMat = boneWorldMatrices[boneIdx];
499
496
  // nodeWorld = boneWorld × shapeLocal (not shapeLocal × boneWorld)
500
497
  const bodyOffsetMatrix = rb.bodyOffsetMatrix || rb.bodyOffsetMatrixInverse.inverse();
501
498
  const nodeWorldMatrix = boneWorldMat.multiply(bodyOffsetMatrix);
@@ -539,7 +536,6 @@ export class Physics {
539
536
  // Only dynamic rigidbodies drive bones (Static/Kinematic follow bones)
540
537
  if (rb.type === RigidbodyType.Dynamic && rb.boneIndex >= 0 && rb.boneIndex < boneCount) {
541
538
  const boneIdx = rb.boneIndex;
542
- const worldMatIdx = boneIdx * 16;
543
539
  const transform = ammoBody.getWorldTransform();
544
540
  const origin = transform.getOrigin();
545
541
  const rotation = transform.getRotation();
@@ -550,7 +546,7 @@ export class Physics {
550
546
  const boneWorldMat = nodeWorldMatrix.multiply(rb.bodyOffsetMatrixInverse);
551
547
  const values = boneWorldMat.values;
552
548
  if (!isNaN(values[0]) && !isNaN(values[15]) && Math.abs(values[0]) < 1e6 && Math.abs(values[15]) < 1e6) {
553
- boneWorldMatrices.set(values, worldMatIdx);
549
+ boneWorldMatrices[boneIdx].values.set(values);
554
550
  }
555
551
  else {
556
552
  console.warn(`[Physics] Invalid bone world matrix for rigidbody ${i} (${rb.name}), skipping update`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "A WebGPU-based MMD model renderer",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",