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/ik-solver.d.ts +2 -3
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/ik-solver.js +16 -32
- package/dist/math.d.ts +4 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +72 -13
- package/dist/model.d.ts +5 -5
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +126 -134
- package/dist/physics.d.ts +2 -2
- package/dist/physics.d.ts.map +1 -1
- package/dist/physics.js +6 -10
- package/package.json +1 -1
- package/src/ik-solver.ts +31 -51
- package/src/math.ts +75 -11
- package/src/model.ts +136 -165
- package/src/physics.ts +11 -21
- package/dist/bezier-interpolate.d.ts +0 -15
- package/dist/bezier-interpolate.d.ts.map +0 -1
- package/dist/bezier-interpolate.js +0 -40
- package/dist/engine_r.d.ts +0 -132
- package/dist/engine_r.d.ts.map +0 -1
- package/dist/engine_r.js +0 -1489
package/src/model.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Mat4, Quat, Vec3,
|
|
1
|
+
import { Mat4, Quat, Vec3, bezierInterpolate } from "./math"
|
|
2
2
|
import { Rigidbody, Joint, Physics } from "./physics"
|
|
3
3
|
import { IKSolverSystem } from "./ik-solver"
|
|
4
4
|
import { VMDKeyFrame, VMDLoader, BoneFrame, MorphFrame } from "./vmd-loader"
|
|
@@ -107,9 +107,9 @@ export interface Morphing {
|
|
|
107
107
|
// Runtime skeleton pose state (updated each frame)
|
|
108
108
|
export interface SkeletonRuntime {
|
|
109
109
|
nameIndex: Record<string, number> // Cached lookup: bone name -> bone index (built on initialization)
|
|
110
|
-
localRotations:
|
|
111
|
-
localTranslations:
|
|
112
|
-
worldMatrices:
|
|
110
|
+
localRotations: Quat[] // quat per bone
|
|
111
|
+
localTranslations: Vec3[] // vec3 per bone
|
|
112
|
+
worldMatrices: Mat4[] // mat4 per bone
|
|
113
113
|
ikChainInfo?: IKChainInfo[] // IK chain info per bone (only for IK chain bones)
|
|
114
114
|
ikSolvers?: IKSolver[] // All IK solvers in the model
|
|
115
115
|
}
|
|
@@ -125,15 +125,15 @@ export interface MorphRuntime {
|
|
|
125
125
|
interface TweenState {
|
|
126
126
|
// Bone rotation tweens
|
|
127
127
|
rotActive: Uint8Array // 0/1 per bone
|
|
128
|
-
rotStartQuat:
|
|
129
|
-
rotTargetQuat:
|
|
128
|
+
rotStartQuat: Quat[]
|
|
129
|
+
rotTargetQuat: Quat[]
|
|
130
130
|
rotStartTimeMs: Float32Array // one float per bone (ms)
|
|
131
131
|
rotDurationMs: Float32Array // one float per bone (ms)
|
|
132
132
|
|
|
133
133
|
// Bone translation tweens
|
|
134
134
|
transActive: Uint8Array // 0/1 per bone
|
|
135
|
-
transStartVec:
|
|
136
|
-
transTargetVec:
|
|
135
|
+
transStartVec: Vec3[] // vec3 per bone (x,y,z)
|
|
136
|
+
transTargetVec: Vec3[] // vec3 per bone (x,y,z)
|
|
137
137
|
transStartTimeMs: Float32Array // one float per bone (ms)
|
|
138
138
|
transDurationMs: Float32Array // one float per bone (ms)
|
|
139
139
|
|
|
@@ -175,7 +175,7 @@ export class Model {
|
|
|
175
175
|
private cachedIdentityMat2 = Mat4.identity()
|
|
176
176
|
|
|
177
177
|
// Cached skin matrices array to avoid allocations in getSkinMatrices
|
|
178
|
-
private
|
|
178
|
+
private skinMatricesArray?: Float32Array
|
|
179
179
|
|
|
180
180
|
private tweenState!: TweenState
|
|
181
181
|
private tweenTimeMs: number = 0 // Time tracking for tweens (milliseconds)
|
|
@@ -241,27 +241,26 @@ export class Model {
|
|
|
241
241
|
private initializeRuntimeSkeleton(): void {
|
|
242
242
|
const boneCount = this.skeleton.bones.length
|
|
243
243
|
|
|
244
|
+
// Pre-allocate object arrays for skeletal pose
|
|
245
|
+
const localRotations: Quat[] = new Array(boneCount)
|
|
246
|
+
const localTranslations: Vec3[] = new Array(boneCount)
|
|
247
|
+
const worldMatrices: Mat4[] = new Array(boneCount)
|
|
248
|
+
for (let i = 0; i < boneCount; i++) {
|
|
249
|
+
localRotations[i] = Quat.identity()
|
|
250
|
+
localTranslations[i] = Vec3.zeros()
|
|
251
|
+
worldMatrices[i] = Mat4.identity()
|
|
252
|
+
}
|
|
253
|
+
|
|
244
254
|
this.runtimeSkeleton = {
|
|
245
|
-
localRotations
|
|
246
|
-
localTranslations
|
|
247
|
-
worldMatrices
|
|
255
|
+
localRotations,
|
|
256
|
+
localTranslations,
|
|
257
|
+
worldMatrices,
|
|
248
258
|
nameIndex: this.skeleton.bones.reduce((acc, bone, index) => {
|
|
249
259
|
acc[bone.name] = index
|
|
250
260
|
return acc
|
|
251
261
|
}, {} as Record<string, number>),
|
|
252
262
|
}
|
|
253
263
|
|
|
254
|
-
const rotations = this.runtimeSkeleton.localRotations
|
|
255
|
-
for (let i = 0; i < this.skeleton.bones.length; i++) {
|
|
256
|
-
const qi = i * 4
|
|
257
|
-
if (rotations[qi + 3] === 0) {
|
|
258
|
-
rotations[qi] = 0
|
|
259
|
-
rotations[qi + 1] = 0
|
|
260
|
-
rotations[qi + 2] = 0
|
|
261
|
-
rotations[qi + 3] = 1
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
264
|
// Initialize IK runtime state
|
|
266
265
|
this.initializeIKRuntime()
|
|
267
266
|
}
|
|
@@ -274,8 +273,8 @@ export class Model {
|
|
|
274
273
|
const ikChainInfo: IKChainInfo[] = new Array(boneCount)
|
|
275
274
|
for (let i = 0; i < boneCount; i++) {
|
|
276
275
|
ikChainInfo[i] = {
|
|
277
|
-
ikRotation:
|
|
278
|
-
localRotation:
|
|
276
|
+
ikRotation: Quat.identity(),
|
|
277
|
+
localRotation: Quat.identity(),
|
|
279
278
|
}
|
|
280
279
|
}
|
|
281
280
|
|
|
@@ -306,18 +305,30 @@ export class Model {
|
|
|
306
305
|
const boneCount = this.skeleton.bones.length
|
|
307
306
|
const morphCount = this.morphing.morphs.length
|
|
308
307
|
|
|
308
|
+
// Pre-allocate Quat and Vec3 arrays to avoid reallocation during tweens
|
|
309
|
+
const rotStartQuat: Quat[] = new Array(boneCount)
|
|
310
|
+
const rotTargetQuat: Quat[] = new Array(boneCount)
|
|
311
|
+
const transStartVec: Vec3[] = new Array(boneCount)
|
|
312
|
+
const transTargetVec: Vec3[] = new Array(boneCount)
|
|
313
|
+
for (let i = 0; i < boneCount; i++) {
|
|
314
|
+
rotStartQuat[i] = Quat.identity()
|
|
315
|
+
rotTargetQuat[i] = Quat.identity()
|
|
316
|
+
transStartVec[i] = Vec3.zeros()
|
|
317
|
+
transTargetVec[i] = Vec3.zeros()
|
|
318
|
+
}
|
|
319
|
+
|
|
309
320
|
this.tweenState = {
|
|
310
321
|
// Bone rotation tweens
|
|
311
322
|
rotActive: new Uint8Array(boneCount),
|
|
312
|
-
rotStartQuat
|
|
313
|
-
rotTargetQuat
|
|
323
|
+
rotStartQuat,
|
|
324
|
+
rotTargetQuat,
|
|
314
325
|
rotStartTimeMs: new Float32Array(boneCount),
|
|
315
326
|
rotDurationMs: new Float32Array(boneCount),
|
|
316
327
|
|
|
317
328
|
// Bone translation tweens
|
|
318
329
|
transActive: new Uint8Array(boneCount),
|
|
319
|
-
transStartVec
|
|
320
|
-
transTargetVec
|
|
330
|
+
transStartVec,
|
|
331
|
+
transTargetVec,
|
|
321
332
|
transStartTimeMs: new Float32Array(boneCount),
|
|
322
333
|
transDurationMs: new Float32Array(boneCount),
|
|
323
334
|
|
|
@@ -358,27 +369,10 @@ export class Model {
|
|
|
358
369
|
const startMs = state.rotStartTimeMs[i]
|
|
359
370
|
const durMs = Math.max(1, state.rotDurationMs[i])
|
|
360
371
|
const t = Math.max(0, Math.min(1, (now - startMs) / durMs))
|
|
361
|
-
const e =
|
|
362
|
-
|
|
363
|
-
const qi = i * 4
|
|
364
|
-
const startQuat = new Quat(
|
|
365
|
-
state.rotStartQuat[qi],
|
|
366
|
-
state.rotStartQuat[qi + 1],
|
|
367
|
-
state.rotStartQuat[qi + 2],
|
|
368
|
-
state.rotStartQuat[qi + 3]
|
|
369
|
-
)
|
|
370
|
-
const targetQuat = new Quat(
|
|
371
|
-
state.rotTargetQuat[qi],
|
|
372
|
-
state.rotTargetQuat[qi + 1],
|
|
373
|
-
state.rotTargetQuat[qi + 2],
|
|
374
|
-
state.rotTargetQuat[qi + 3]
|
|
375
|
-
)
|
|
376
|
-
const result = Quat.slerp(startQuat, targetQuat, e)
|
|
372
|
+
const e = t // Linear interpolation
|
|
377
373
|
|
|
378
|
-
|
|
379
|
-
rotations[
|
|
380
|
-
rotations[qi + 2] = result.z
|
|
381
|
-
rotations[qi + 3] = result.w
|
|
374
|
+
const result = Quat.slerp(state.rotStartQuat[i], state.rotTargetQuat[i], e)
|
|
375
|
+
rotations[i].set(result)
|
|
382
376
|
|
|
383
377
|
if (t >= 1) {
|
|
384
378
|
state.rotActive[i] = 0
|
|
@@ -393,14 +387,13 @@ export class Model {
|
|
|
393
387
|
const startMs = state.transStartTimeMs[i]
|
|
394
388
|
const durMs = Math.max(1, state.transDurationMs[i])
|
|
395
389
|
const t = Math.max(0, Math.min(1, (now - startMs) / durMs))
|
|
396
|
-
const e =
|
|
390
|
+
const e = t // Linear interpolation
|
|
397
391
|
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
translations[
|
|
401
|
-
|
|
402
|
-
translations[
|
|
403
|
-
state.transStartVec[ti + 2] + (state.transTargetVec[ti + 2] - state.transStartVec[ti + 2]) * e
|
|
392
|
+
const startVec = state.transStartVec[i]
|
|
393
|
+
const targetVec = state.transTargetVec[i]
|
|
394
|
+
translations[i].x = startVec.x + (targetVec.x - startVec.x) * e
|
|
395
|
+
translations[i].y = startVec.y + (targetVec.y - startVec.y) * e
|
|
396
|
+
translations[i].z = startVec.z + (targetVec.z - startVec.z) * e
|
|
404
397
|
|
|
405
398
|
if (t >= 1) {
|
|
406
399
|
state.transActive[i] = 0
|
|
@@ -416,7 +409,7 @@ export class Model {
|
|
|
416
409
|
const startMs = state.morphStartTimeMs[i]
|
|
417
410
|
const durMs = Math.max(1, state.morphDurationMs[i])
|
|
418
411
|
const t = Math.max(0, Math.min(1, (now - startMs) / durMs))
|
|
419
|
-
const e =
|
|
412
|
+
const e = t // Linear interpolation
|
|
420
413
|
|
|
421
414
|
const oldWeight = weights[i]
|
|
422
415
|
weights[i] = state.morphStartWeight[i] + (state.morphTargetWeight[i] - state.morphStartWeight[i]) * e
|
|
@@ -483,7 +476,8 @@ export class Model {
|
|
|
483
476
|
|
|
484
477
|
rotateBones(names: string[], quats: Quat[], durationMs?: number): void {
|
|
485
478
|
const state = this.tweenState
|
|
486
|
-
|
|
479
|
+
// Clone and normalize to avoid mutating input
|
|
480
|
+
quats.forEach((q) => q.normalize())
|
|
487
481
|
const now = this.tweenTimeMs
|
|
488
482
|
const dur = durationMs && durationMs > 0 ? durationMs : 0
|
|
489
483
|
|
|
@@ -492,56 +486,38 @@ export class Model {
|
|
|
492
486
|
const idx = this.runtimeSkeleton.nameIndex[name] ?? -1
|
|
493
487
|
if (idx < 0 || idx >= this.skeleton.bones.length) continue
|
|
494
488
|
|
|
495
|
-
const qi = idx * 4
|
|
496
489
|
const rotations = this.runtimeSkeleton.localRotations
|
|
497
|
-
const
|
|
490
|
+
const targetNorm = quats[i]
|
|
498
491
|
|
|
499
492
|
if (dur === 0) {
|
|
500
|
-
rotations[
|
|
501
|
-
rotations[qi + 1] = ty
|
|
502
|
-
rotations[qi + 2] = tz
|
|
503
|
-
rotations[qi + 3] = tw
|
|
493
|
+
rotations[idx].set(targetNorm)
|
|
504
494
|
state.rotActive[idx] = 0
|
|
505
495
|
continue
|
|
506
496
|
}
|
|
507
497
|
|
|
508
|
-
|
|
509
|
-
let
|
|
510
|
-
let
|
|
511
|
-
let
|
|
498
|
+
const currentRot = rotations[idx]
|
|
499
|
+
let sx = currentRot.x
|
|
500
|
+
let sy = currentRot.y
|
|
501
|
+
let sz = currentRot.z
|
|
502
|
+
let sw = currentRot.w
|
|
512
503
|
|
|
513
504
|
if (state.rotActive[idx] === 1) {
|
|
514
505
|
const startMs = state.rotStartTimeMs[idx]
|
|
515
506
|
const prevDur = Math.max(1, state.rotDurationMs[idx])
|
|
516
507
|
const t = Math.max(0, Math.min(1, (now - startMs) / prevDur))
|
|
517
|
-
const e =
|
|
518
|
-
const
|
|
519
|
-
state.rotStartQuat[qi],
|
|
520
|
-
state.rotStartQuat[qi + 1],
|
|
521
|
-
state.rotStartQuat[qi + 2],
|
|
522
|
-
state.rotStartQuat[qi + 3]
|
|
523
|
-
)
|
|
524
|
-
const targetQuat = new Quat(
|
|
525
|
-
state.rotTargetQuat[qi],
|
|
526
|
-
state.rotTargetQuat[qi + 1],
|
|
527
|
-
state.rotTargetQuat[qi + 2],
|
|
528
|
-
state.rotTargetQuat[qi + 3]
|
|
529
|
-
)
|
|
530
|
-
const result = Quat.slerp(startQuat, targetQuat, e)
|
|
508
|
+
const e = t // Linear interpolation
|
|
509
|
+
const result = Quat.slerp(state.rotStartQuat[idx], state.rotTargetQuat[idx], e)
|
|
531
510
|
sx = result.x
|
|
532
511
|
sy = result.y
|
|
533
512
|
sz = result.z
|
|
534
513
|
sw = result.w
|
|
535
514
|
}
|
|
536
515
|
|
|
537
|
-
state.rotStartQuat[
|
|
538
|
-
state.rotStartQuat[
|
|
539
|
-
state.rotStartQuat[
|
|
540
|
-
state.rotStartQuat[
|
|
541
|
-
state.rotTargetQuat[
|
|
542
|
-
state.rotTargetQuat[qi + 1] = ty
|
|
543
|
-
state.rotTargetQuat[qi + 2] = tz
|
|
544
|
-
state.rotTargetQuat[qi + 3] = tw
|
|
516
|
+
state.rotStartQuat[idx].x = sx
|
|
517
|
+
state.rotStartQuat[idx].y = sy
|
|
518
|
+
state.rotStartQuat[idx].z = sz
|
|
519
|
+
state.rotStartQuat[idx].w = sw
|
|
520
|
+
state.rotTargetQuat[idx].set(targetNorm)
|
|
545
521
|
state.rotStartTimeMs[idx] = now
|
|
546
522
|
state.rotDurationMs[idx] = dur
|
|
547
523
|
state.rotActive[idx] = 1
|
|
@@ -554,7 +530,6 @@ export class Model {
|
|
|
554
530
|
const state = this.tweenState
|
|
555
531
|
const now = this.tweenTimeMs
|
|
556
532
|
const dur = durationMs && durationMs > 0 ? durationMs : 0
|
|
557
|
-
const localRot = this.runtimeSkeleton.localRotations
|
|
558
533
|
|
|
559
534
|
// Compute bind pose world positions for all bones
|
|
560
535
|
const skeleton = this.skeleton
|
|
@@ -575,9 +550,8 @@ export class Model {
|
|
|
575
550
|
if (idx < 0 || idx >= this.skeleton.bones.length) continue
|
|
576
551
|
|
|
577
552
|
const bone = this.skeleton.bones[idx]
|
|
578
|
-
const ti = idx * 3
|
|
579
|
-
const qi = idx * 4
|
|
580
553
|
const translations = this.runtimeSkeleton.localTranslations
|
|
554
|
+
const localRot = this.runtimeSkeleton.localRotations
|
|
581
555
|
const vmdRelativeTranslation = relativeTranslations[i]
|
|
582
556
|
|
|
583
557
|
// VMD translation is relative to bind pose world position
|
|
@@ -591,7 +565,7 @@ export class Model {
|
|
|
591
565
|
if (bone.parentIndex >= 0) {
|
|
592
566
|
parentBindPoseWorldPos = computeBindPoseWorldPosition(bone.parentIndex)
|
|
593
567
|
} else {
|
|
594
|
-
parentBindPoseWorldPos =
|
|
568
|
+
parentBindPoseWorldPos = Vec3.zeros()
|
|
595
569
|
}
|
|
596
570
|
|
|
597
571
|
// Transform target world position to parent's local space
|
|
@@ -604,8 +578,9 @@ export class Model {
|
|
|
604
578
|
)
|
|
605
579
|
|
|
606
580
|
// Apply inverse rotation to get local translation
|
|
607
|
-
const localRotation =
|
|
608
|
-
|
|
581
|
+
const localRotation = localRot[idx]
|
|
582
|
+
// Clone to avoid mutating, then conjugate and normalize
|
|
583
|
+
const invRotation = localRotation.clone().conjugate().normalize()
|
|
609
584
|
const rotationMat = Mat4.fromQuat(invRotation.x, invRotation.y, invRotation.z, invRotation.w)
|
|
610
585
|
const rm = rotationMat.values
|
|
611
586
|
const localTranslation = new Vec3(
|
|
@@ -617,33 +592,36 @@ export class Model {
|
|
|
617
592
|
const [tx, ty, tz] = [localTranslation.x, localTranslation.y, localTranslation.z]
|
|
618
593
|
|
|
619
594
|
if (dur === 0) {
|
|
620
|
-
translations[
|
|
621
|
-
translations[
|
|
622
|
-
translations[
|
|
595
|
+
translations[idx].x = tx
|
|
596
|
+
translations[idx].y = ty
|
|
597
|
+
translations[idx].z = tz
|
|
623
598
|
state.transActive[idx] = 0
|
|
624
599
|
continue
|
|
625
600
|
}
|
|
626
601
|
|
|
627
|
-
|
|
628
|
-
let
|
|
629
|
-
let
|
|
602
|
+
const currentTrans = translations[idx]
|
|
603
|
+
let sx = currentTrans.x
|
|
604
|
+
let sy = currentTrans.y
|
|
605
|
+
let sz = currentTrans.z
|
|
630
606
|
|
|
631
607
|
if (state.transActive[idx] === 1) {
|
|
632
608
|
const startMs = state.transStartTimeMs[idx]
|
|
633
609
|
const prevDur = Math.max(1, state.transDurationMs[idx])
|
|
634
610
|
const t = Math.max(0, Math.min(1, (now - startMs) / prevDur))
|
|
635
|
-
const e =
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
611
|
+
const e = t // Linear interpolation
|
|
612
|
+
const startVec = state.transStartVec[idx]
|
|
613
|
+
const targetVec = state.transTargetVec[idx]
|
|
614
|
+
sx = startVec.x + (targetVec.x - startVec.x) * e
|
|
615
|
+
sy = startVec.y + (targetVec.y - startVec.y) * e
|
|
616
|
+
sz = startVec.z + (targetVec.z - startVec.z) * e
|
|
639
617
|
}
|
|
640
618
|
|
|
641
|
-
state.transStartVec[
|
|
642
|
-
state.transStartVec[
|
|
643
|
-
state.transStartVec[
|
|
644
|
-
state.transTargetVec[
|
|
645
|
-
state.transTargetVec[
|
|
646
|
-
state.transTargetVec[
|
|
619
|
+
state.transStartVec[idx].x = sx
|
|
620
|
+
state.transStartVec[idx].y = sy
|
|
621
|
+
state.transStartVec[idx].z = sz
|
|
622
|
+
state.transTargetVec[idx].x = tx
|
|
623
|
+
state.transTargetVec[idx].y = ty
|
|
624
|
+
state.transTargetVec[idx].z = tz
|
|
647
625
|
state.transStartTimeMs[idx] = now
|
|
648
626
|
state.transDurationMs[idx] = dur
|
|
649
627
|
state.transActive[idx] = 1
|
|
@@ -651,7 +629,14 @@ export class Model {
|
|
|
651
629
|
}
|
|
652
630
|
|
|
653
631
|
getBoneWorldMatrices(): Float32Array {
|
|
654
|
-
|
|
632
|
+
// Convert Mat4[] to Float32Array for WebGPU compatibility
|
|
633
|
+
const boneCount = this.skeleton.bones.length
|
|
634
|
+
const worldMats = this.runtimeSkeleton.worldMatrices
|
|
635
|
+
const result = new Float32Array(boneCount * 16)
|
|
636
|
+
for (let i = 0; i < boneCount; i++) {
|
|
637
|
+
result.set(worldMats[i].values, i * 16)
|
|
638
|
+
}
|
|
639
|
+
return result
|
|
655
640
|
}
|
|
656
641
|
|
|
657
642
|
getBoneInverseBindMatrices(): Float32Array {
|
|
@@ -664,19 +649,18 @@ export class Model {
|
|
|
664
649
|
const invBindMats = this.skeleton.inverseBindMatrices
|
|
665
650
|
|
|
666
651
|
// Initialize cached array if needed or if bone count changed
|
|
667
|
-
if (!this.
|
|
668
|
-
this.
|
|
652
|
+
if (!this.skinMatricesArray || this.skinMatricesArray.length !== boneCount * 16) {
|
|
653
|
+
this.skinMatricesArray = new Float32Array(boneCount * 16)
|
|
669
654
|
}
|
|
670
655
|
|
|
671
|
-
const skinMatrices = this.
|
|
656
|
+
const skinMatrices = this.skinMatricesArray
|
|
672
657
|
|
|
673
658
|
// Compute skin matrices: skinMatrix = worldMatrix × inverseBindMatrix
|
|
674
|
-
// Use Mat4.multiplyArrays to avoid creating Mat4 objects
|
|
675
659
|
for (let i = 0; i < boneCount; i++) {
|
|
676
|
-
const
|
|
660
|
+
const worldMat = worldMats[i]
|
|
677
661
|
const invBindOffset = i * 16
|
|
678
662
|
const skinOffset = i * 16
|
|
679
|
-
Mat4.multiplyArrays(
|
|
663
|
+
Mat4.multiplyArrays(worldMat.values, 0, invBindMats, invBindOffset, skinMatrices, skinOffset)
|
|
680
664
|
}
|
|
681
665
|
|
|
682
666
|
return skinMatrices
|
|
@@ -707,7 +691,7 @@ export class Model {
|
|
|
707
691
|
const startMs = state.morphStartTimeMs[idx]
|
|
708
692
|
const prevDur = Math.max(1, state.morphDurationMs[idx])
|
|
709
693
|
const t = Math.max(0, Math.min(1, (now - startMs) / prevDur))
|
|
710
|
-
const e =
|
|
694
|
+
const e = t // Linear interpolation
|
|
711
695
|
startWeight = state.morphStartWeight[idx] + (state.morphTargetWeight[idx] - state.morphStartWeight[idx]) * e
|
|
712
696
|
}
|
|
713
697
|
|
|
@@ -973,18 +957,13 @@ export class Model {
|
|
|
973
957
|
const boneIdx = this.runtimeSkeleton.nameIndex[boneName]
|
|
974
958
|
if (boneIdx === undefined) continue
|
|
975
959
|
|
|
976
|
-
const
|
|
977
|
-
const
|
|
960
|
+
const localRot = this.runtimeSkeleton.localRotations[boneIdx]
|
|
961
|
+
const localTrans = this.runtimeSkeleton.localTranslations[boneIdx]
|
|
978
962
|
|
|
979
963
|
if (!frameB) {
|
|
980
964
|
// No interpolation needed - direct assignment
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
this.runtimeSkeleton.localRotations[rotOffset + 2] = frameA.rotation.z
|
|
984
|
-
this.runtimeSkeleton.localRotations[rotOffset + 3] = frameA.rotation.w
|
|
985
|
-
this.runtimeSkeleton.localTranslations[transOffset] = frameA.translation.x
|
|
986
|
-
this.runtimeSkeleton.localTranslations[transOffset + 1] = frameA.translation.y
|
|
987
|
-
this.runtimeSkeleton.localTranslations[transOffset + 2] = frameA.translation.z
|
|
965
|
+
localRot.set(frameA.rotation)
|
|
966
|
+
localTrans.set(frameA.translation)
|
|
988
967
|
} else {
|
|
989
968
|
const timeA = keyFrames[idx].time
|
|
990
969
|
const timeB = keyFrames[idx + 1].time
|
|
@@ -1001,7 +980,7 @@ export class Model {
|
|
|
1001
980
|
gradient
|
|
1002
981
|
)
|
|
1003
982
|
|
|
1004
|
-
// Use Quat.slerp
|
|
983
|
+
// Use Quat.slerp to interpolate rotation
|
|
1005
984
|
const rotation = Quat.slerp(frameA.rotation, frameB.rotation, rotT)
|
|
1006
985
|
|
|
1007
986
|
// Interpolate translation using bezier for each component
|
|
@@ -1019,17 +998,11 @@ export class Model {
|
|
|
1019
998
|
const tyWeight = getWeight(16)
|
|
1020
999
|
const tzWeight = getWeight(32)
|
|
1021
1000
|
|
|
1022
|
-
// Direct
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
this.runtimeSkeleton.localTranslations[transOffset] =
|
|
1028
|
-
frameA.translation.x + (frameB.translation.x - frameA.translation.x) * txWeight
|
|
1029
|
-
this.runtimeSkeleton.localTranslations[transOffset + 1] =
|
|
1030
|
-
frameA.translation.y + (frameB.translation.y - frameA.translation.y) * tyWeight
|
|
1031
|
-
this.runtimeSkeleton.localTranslations[transOffset + 2] =
|
|
1032
|
-
frameA.translation.z + (frameB.translation.z - frameA.translation.z) * tzWeight
|
|
1001
|
+
// Direct property writes to avoid object allocation
|
|
1002
|
+
localRot.set(rotation)
|
|
1003
|
+
localTrans.x = frameA.translation.x + (frameB.translation.x - frameA.translation.x) * txWeight
|
|
1004
|
+
localTrans.y = frameA.translation.y + (frameB.translation.y - frameA.translation.y) * tyWeight
|
|
1005
|
+
localTrans.z = frameA.translation.z + (frameB.translation.z - frameA.translation.z) * tzWeight
|
|
1033
1006
|
}
|
|
1034
1007
|
}
|
|
1035
1008
|
|
|
@@ -1137,7 +1110,7 @@ export class Model {
|
|
|
1137
1110
|
const bones = this.skeleton.bones
|
|
1138
1111
|
const localRot = this.runtimeSkeleton.localRotations
|
|
1139
1112
|
const localTrans = this.runtimeSkeleton.localTranslations
|
|
1140
|
-
const
|
|
1113
|
+
const worldMats = this.runtimeSkeleton.worldMatrices
|
|
1141
1114
|
const boneCount = bones.length
|
|
1142
1115
|
|
|
1143
1116
|
if (boneCount === 0) return
|
|
@@ -1153,8 +1126,8 @@ export class Model {
|
|
|
1153
1126
|
console.warn(`[RZM] bone ${i} parent out of range: ${b.parentIndex}`)
|
|
1154
1127
|
}
|
|
1155
1128
|
|
|
1156
|
-
const
|
|
1157
|
-
let rotateM = Mat4.fromQuat(
|
|
1129
|
+
const boneRot = localRot[i]
|
|
1130
|
+
let rotateM = Mat4.fromQuat(boneRot.x, boneRot.y, boneRot.z, boneRot.w)
|
|
1158
1131
|
let addLocalTx = 0,
|
|
1159
1132
|
addLocalTy = 0,
|
|
1160
1133
|
addLocalTz = 0
|
|
@@ -1169,14 +1142,12 @@ export class Model {
|
|
|
1169
1142
|
const hasRatio = Math.abs(ratio) > 1e-6
|
|
1170
1143
|
|
|
1171
1144
|
if (hasRatio) {
|
|
1172
|
-
const apQi = appendParentIdx * 4
|
|
1173
|
-
const apTi = appendParentIdx * 3
|
|
1174
|
-
|
|
1175
1145
|
if (b.appendRotate) {
|
|
1176
|
-
|
|
1177
|
-
let
|
|
1178
|
-
let
|
|
1179
|
-
|
|
1146
|
+
const appendRot = localRot[appendParentIdx]
|
|
1147
|
+
let ax = appendRot.x
|
|
1148
|
+
let ay = appendRot.y
|
|
1149
|
+
let az = appendRot.z
|
|
1150
|
+
const aw = appendRot.w
|
|
1180
1151
|
const absRatio = ratio < 0 ? -ratio : ratio
|
|
1181
1152
|
if (ratio < 0) {
|
|
1182
1153
|
ax = -ax
|
|
@@ -1184,40 +1155,40 @@ export class Model {
|
|
|
1184
1155
|
az = -az
|
|
1185
1156
|
}
|
|
1186
1157
|
const appendQuat = new Quat(ax, ay, az, aw)
|
|
1187
|
-
const result = Quat.slerp(
|
|
1158
|
+
const result = Quat.slerp(Quat.identity(), appendQuat, absRatio)
|
|
1188
1159
|
rotateM = Mat4.fromQuat(result.x, result.y, result.z, result.w).multiply(rotateM)
|
|
1189
1160
|
}
|
|
1190
1161
|
|
|
1191
1162
|
if (b.appendMove) {
|
|
1163
|
+
const appendTrans = localTrans[appendParentIdx]
|
|
1192
1164
|
const appendRatio = b.appendRatio ?? 1
|
|
1193
|
-
addLocalTx =
|
|
1194
|
-
addLocalTy =
|
|
1195
|
-
addLocalTz =
|
|
1165
|
+
addLocalTx = appendTrans.x * appendRatio
|
|
1166
|
+
addLocalTy = appendTrans.y * appendRatio
|
|
1167
|
+
addLocalTz = appendTrans.z * appendRatio
|
|
1196
1168
|
}
|
|
1197
1169
|
}
|
|
1198
1170
|
}
|
|
1199
1171
|
|
|
1200
1172
|
// Build local matrix: identity + bind translation, then rotation, then local translation, then append translation
|
|
1201
|
-
const
|
|
1202
|
-
const localTx =
|
|
1203
|
-
const localTy =
|
|
1204
|
-
const localTz =
|
|
1173
|
+
const boneTrans = localTrans[i]
|
|
1174
|
+
const localTx = boneTrans.x + addLocalTx
|
|
1175
|
+
const localTy = boneTrans.y + addLocalTy
|
|
1176
|
+
const localTz = boneTrans.z + addLocalTz
|
|
1205
1177
|
this.cachedIdentityMat1
|
|
1206
1178
|
.setIdentity()
|
|
1207
1179
|
.translateInPlace(b.bindTranslation[0], b.bindTranslation[1], b.bindTranslation[2])
|
|
1208
1180
|
this.cachedIdentityMat2.setIdentity().translateInPlace(localTx, localTy, localTz)
|
|
1209
1181
|
const localM = this.cachedIdentityMat1.multiply(rotateM).multiply(this.cachedIdentityMat2)
|
|
1210
1182
|
|
|
1211
|
-
const
|
|
1183
|
+
const worldMat = worldMats[i]
|
|
1212
1184
|
if (b.parentIndex >= 0) {
|
|
1213
1185
|
const p = b.parentIndex
|
|
1214
1186
|
if (!computed[p]) computeWorld(p)
|
|
1215
|
-
const
|
|
1216
|
-
//
|
|
1217
|
-
Mat4.multiplyArrays(
|
|
1218
|
-
worldBuf.subarray(worldOffset, worldOffset + 16).set(this.cachedIdentityMat2.values)
|
|
1187
|
+
const parentMat = worldMats[p]
|
|
1188
|
+
// Multiply parent world matrix by local matrix
|
|
1189
|
+
Mat4.multiplyArrays(parentMat.values, 0, localM.values, 0, worldMat.values, 0)
|
|
1219
1190
|
} else {
|
|
1220
|
-
|
|
1191
|
+
worldMat.values.set(localM.values)
|
|
1221
1192
|
}
|
|
1222
1193
|
computed[i] = true
|
|
1223
1194
|
}
|
package/src/physics.ts
CHANGED
|
@@ -460,12 +460,12 @@ export class Physics {
|
|
|
460
460
|
// Reset physics state (reposition bodies, clear velocities)
|
|
461
461
|
// Following babylon-mmd pattern: initialize all rigid body positions from current bone poses
|
|
462
462
|
// Call this when starting a new animation to prevent physics instability from sudden pose changes
|
|
463
|
-
reset(boneWorldMatrices:
|
|
463
|
+
reset(boneWorldMatrices: Mat4[], boneInverseBindMatrices: Float32Array): void {
|
|
464
464
|
if (!this.ammoInitialized || !this.ammo || !this.dynamicsWorld) {
|
|
465
465
|
return
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
const boneCount = boneWorldMatrices.length
|
|
468
|
+
const boneCount = boneWorldMatrices.length
|
|
469
469
|
const Ammo = this.ammo
|
|
470
470
|
|
|
471
471
|
// Ensure body offsets are computed
|
|
@@ -482,10 +482,9 @@ export class Physics {
|
|
|
482
482
|
if (!ammoBody || rb.boneIndex < 0 || rb.boneIndex >= boneCount) continue
|
|
483
483
|
|
|
484
484
|
const boneIdx = rb.boneIndex
|
|
485
|
-
const worldMatIdx = boneIdx * 16
|
|
486
485
|
|
|
487
486
|
// Get bone world matrix
|
|
488
|
-
const boneWorldMat =
|
|
487
|
+
const boneWorldMat = boneWorldMatrices[boneIdx]
|
|
489
488
|
|
|
490
489
|
// Compute body world matrix: bodyWorld = boneWorld × bodyOffsetMatrix
|
|
491
490
|
// (like babylon-mmd: bodyWorldMatrix = bodyOffsetMatrix.multiplyToRef(bodyWorldMatrix))
|
|
@@ -531,13 +530,13 @@ export class Physics {
|
|
|
531
530
|
|
|
532
531
|
// Syncs bones to rigidbodies, simulates dynamics, solves constraints
|
|
533
532
|
// Modifies boneWorldMatrices in-place for dynamic rigidbodies that drive bones
|
|
534
|
-
step(dt: number, boneWorldMatrices:
|
|
533
|
+
step(dt: number, boneWorldMatrices: Mat4[], boneInverseBindMatrices: Float32Array): void {
|
|
535
534
|
// Wait for Ammo to initialize
|
|
536
535
|
if (!this.ammoInitialized || !this.ammo || !this.dynamicsWorld) {
|
|
537
536
|
return
|
|
538
537
|
}
|
|
539
538
|
|
|
540
|
-
const boneCount = boneWorldMatrices.length
|
|
539
|
+
const boneCount = boneWorldMatrices.length
|
|
541
540
|
|
|
542
541
|
if (this.firstFrame) {
|
|
543
542
|
if (!this.rigidbodiesInitialized) {
|
|
@@ -596,7 +595,7 @@ export class Physics {
|
|
|
596
595
|
}
|
|
597
596
|
|
|
598
597
|
// Position bodies based on current bone transforms (called on first frame only)
|
|
599
|
-
private positionBodiesFromBones(boneWorldMatrices:
|
|
598
|
+
private positionBodiesFromBones(boneWorldMatrices: Mat4[], boneCount: number): void {
|
|
600
599
|
if (!this.ammo || !this.dynamicsWorld) return
|
|
601
600
|
|
|
602
601
|
const Ammo = this.ammo
|
|
@@ -607,9 +606,7 @@ export class Physics {
|
|
|
607
606
|
if (!ammoBody || rb.boneIndex < 0 || rb.boneIndex >= boneCount) continue
|
|
608
607
|
|
|
609
608
|
const boneIdx = rb.boneIndex
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
const boneWorldMat = new Mat4(boneWorldMatrices.subarray(worldMatIdx, worldMatIdx + 16))
|
|
609
|
+
const boneWorldMat = boneWorldMatrices[boneIdx]
|
|
613
610
|
|
|
614
611
|
// nodeWorld = boneWorld × shapeLocal (not shapeLocal × boneWorld)
|
|
615
612
|
const bodyOffsetMatrix = rb.bodyOffsetMatrix || rb.bodyOffsetMatrixInverse.inverse()
|
|
@@ -646,11 +643,7 @@ export class Physics {
|
|
|
646
643
|
}
|
|
647
644
|
|
|
648
645
|
// Sync Static (FollowBone) and Kinematic rigidbodies to follow bone transforms
|
|
649
|
-
private syncFromBones(
|
|
650
|
-
boneWorldMatrices: Float32Array,
|
|
651
|
-
boneInverseBindMatrices: Float32Array,
|
|
652
|
-
boneCount: number
|
|
653
|
-
): void {
|
|
646
|
+
private syncFromBones(boneWorldMatrices: Mat4[], boneInverseBindMatrices: Float32Array, boneCount: number): void {
|
|
654
647
|
if (!this.ammo || !this.dynamicsWorld) return
|
|
655
648
|
|
|
656
649
|
const Ammo = this.ammo
|
|
@@ -667,9 +660,7 @@ export class Physics {
|
|
|
667
660
|
rb.boneIndex < boneCount
|
|
668
661
|
) {
|
|
669
662
|
const boneIdx = rb.boneIndex
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
const boneWorldMat = new Mat4(boneWorldMatrices.subarray(worldMatIdx, worldMatIdx + 16))
|
|
663
|
+
const boneWorldMat = boneWorldMatrices[boneIdx]
|
|
673
664
|
|
|
674
665
|
// nodeWorld = boneWorld × shapeLocal (not shapeLocal × boneWorld)
|
|
675
666
|
const bodyOffsetMatrix = rb.bodyOffsetMatrix || rb.bodyOffsetMatrixInverse.inverse()
|
|
@@ -713,7 +704,7 @@ export class Physics {
|
|
|
713
704
|
|
|
714
705
|
// Apply dynamic rigidbody world transforms to bone world matrices in-place
|
|
715
706
|
private applyAmmoRigidbodiesToBones(
|
|
716
|
-
boneWorldMatrices:
|
|
707
|
+
boneWorldMatrices: Mat4[],
|
|
717
708
|
boneInverseBindMatrices: Float32Array,
|
|
718
709
|
boneCount: number
|
|
719
710
|
): void {
|
|
@@ -727,7 +718,6 @@ export class Physics {
|
|
|
727
718
|
// Only dynamic rigidbodies drive bones (Static/Kinematic follow bones)
|
|
728
719
|
if (rb.type === RigidbodyType.Dynamic && rb.boneIndex >= 0 && rb.boneIndex < boneCount) {
|
|
729
720
|
const boneIdx = rb.boneIndex
|
|
730
|
-
const worldMatIdx = boneIdx * 16
|
|
731
721
|
|
|
732
722
|
const transform = ammoBody.getWorldTransform()
|
|
733
723
|
const origin = transform.getOrigin()
|
|
@@ -742,7 +732,7 @@ export class Physics {
|
|
|
742
732
|
|
|
743
733
|
const values = boneWorldMat.values
|
|
744
734
|
if (!isNaN(values[0]) && !isNaN(values[15]) && Math.abs(values[0]) < 1e6 && Math.abs(values[15]) < 1e6) {
|
|
745
|
-
boneWorldMatrices.set(values
|
|
735
|
+
boneWorldMatrices[boneIdx].values.set(values)
|
|
746
736
|
} else {
|
|
747
737
|
console.warn(`[Physics] Invalid bone world matrix for rigidbody ${i} (${rb.name}), skipping update`)
|
|
748
738
|
}
|