reze-engine 0.2.19 → 0.3.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/model.js CHANGED
@@ -1,5 +1,22 @@
1
- import { Mat4, Quat, easeInOut } from "./math";
1
+ import { Mat4, Quat, Vec3, easeInOut } from "./math";
2
+ import { IKSolverSystem } from "./ik-solver";
2
3
  const VERTEX_STRIDE = 8;
4
+ // Euler rotation order for angle constraints
5
+ export var EulerRotationOrder;
6
+ (function (EulerRotationOrder) {
7
+ EulerRotationOrder[EulerRotationOrder["YXZ"] = 0] = "YXZ";
8
+ EulerRotationOrder[EulerRotationOrder["ZYX"] = 1] = "ZYX";
9
+ EulerRotationOrder[EulerRotationOrder["XZY"] = 2] = "XZY";
10
+ })(EulerRotationOrder || (EulerRotationOrder = {}));
11
+ // Solve axis optimization
12
+ export var SolveAxis;
13
+ (function (SolveAxis) {
14
+ SolveAxis[SolveAxis["None"] = 0] = "None";
15
+ SolveAxis[SolveAxis["Fixed"] = 1] = "Fixed";
16
+ SolveAxis[SolveAxis["X"] = 2] = "X";
17
+ SolveAxis[SolveAxis["Y"] = 3] = "Y";
18
+ SolveAxis[SolveAxis["Z"] = 4] = "Z";
19
+ })(SolveAxis || (SolveAxis = {}));
3
20
  export class Model {
4
21
  constructor(vertexData, indexData, textures, materials, skeleton, skinning, morphing, rigidbodies = [], joints = []) {
5
22
  this.textures = [];
@@ -27,6 +44,7 @@ export class Model {
27
44
  }
28
45
  this.initializeRuntimeSkeleton();
29
46
  this.initializeRotTweenBuffers();
47
+ this.initializeTransTweenBuffers();
30
48
  this.initializeRuntimeMorph();
31
49
  this.initializeMorphTweenBuffers();
32
50
  this.applyMorphs(); // Apply initial morphs (all weights are 0, so no change)
@@ -53,6 +71,51 @@ export class Model {
53
71
  rotations[qi + 3] = 1;
54
72
  }
55
73
  }
74
+ // Initialize IK runtime state
75
+ this.initializeIKRuntime();
76
+ }
77
+ initializeIKRuntime() {
78
+ const boneCount = this.skeleton.bones.length;
79
+ const bones = this.skeleton.bones;
80
+ // Initialize IK chain info for all bones (will be populated for IK chain bones)
81
+ const ikChainInfo = new Array(boneCount);
82
+ for (let i = 0; i < boneCount; i++) {
83
+ ikChainInfo[i] = {
84
+ ikRotation: new Quat(0, 0, 0, 1),
85
+ localRotation: new Quat(0, 0, 0, 1),
86
+ };
87
+ }
88
+ // Build IK solvers from bone data
89
+ const ikSolvers = [];
90
+ let solverIndex = 0;
91
+ for (let i = 0; i < boneCount; i++) {
92
+ const bone = bones[i];
93
+ if (bone.ikTargetIndex !== undefined && bone.ikLinks && bone.ikLinks.length > 0) {
94
+ // Check if all links are affected by physics (for optimization)
95
+ let canSkipWhenPhysicsEnabled = true;
96
+ for (const link of bone.ikLinks) {
97
+ // For now, assume no bones are physics-controlled (can be enhanced later)
98
+ // If a bone has a rigidbody attached, it's physics-controlled
99
+ const hasPhysics = this.rigidbodies.some((rb) => rb.boneIndex === link.boneIndex);
100
+ if (!hasPhysics) {
101
+ canSkipWhenPhysicsEnabled = false;
102
+ break;
103
+ }
104
+ }
105
+ const solver = {
106
+ index: solverIndex++,
107
+ ikBoneIndex: i,
108
+ targetBoneIndex: bone.ikTargetIndex,
109
+ iterationCount: bone.ikIteration ?? 1,
110
+ limitAngle: bone.ikLimitAngle ?? Math.PI,
111
+ links: bone.ikLinks,
112
+ canSkipWhenPhysicsEnabled,
113
+ };
114
+ ikSolvers.push(solver);
115
+ }
116
+ }
117
+ this.runtimeSkeleton.ikChainInfo = ikChainInfo;
118
+ this.runtimeSkeleton.ikSolvers = ikSolvers;
56
119
  }
57
120
  initializeRotTweenBuffers() {
58
121
  const n = this.skeleton.bones.length;
@@ -64,6 +127,16 @@ export class Model {
64
127
  durationMs: new Float32Array(n),
65
128
  };
66
129
  }
130
+ initializeTransTweenBuffers() {
131
+ const n = this.skeleton.bones.length;
132
+ this.transTweenState = {
133
+ active: new Uint8Array(n),
134
+ startVec: new Float32Array(n * 3),
135
+ targetVec: new Float32Array(n * 3),
136
+ startTimeMs: new Float32Array(n),
137
+ durationMs: new Float32Array(n),
138
+ };
139
+ }
67
140
  initializeMorphTweenBuffers() {
68
141
  const n = this.morphing.morphs.length;
69
142
  this.morphTweenState = {
@@ -108,6 +181,26 @@ export class Model {
108
181
  state.active[i] = 0;
109
182
  }
110
183
  }
184
+ updateTranslationTweens() {
185
+ const state = this.transTweenState;
186
+ const now = performance.now();
187
+ const translations = this.runtimeSkeleton.localTranslations;
188
+ const boneCount = this.skeleton.bones.length;
189
+ for (let i = 0; i < boneCount; i++) {
190
+ if (state.active[i] !== 1)
191
+ continue;
192
+ const startMs = state.startTimeMs[i];
193
+ const durMs = Math.max(1, state.durationMs[i]);
194
+ const t = Math.max(0, Math.min(1, (now - startMs) / durMs));
195
+ const e = easeInOut(t);
196
+ const ti = i * 3;
197
+ translations[ti] = state.startVec[ti] + (state.targetVec[ti] - state.startVec[ti]) * e;
198
+ translations[ti + 1] = state.startVec[ti + 1] + (state.targetVec[ti + 1] - state.startVec[ti + 1]) * e;
199
+ translations[ti + 2] = state.startVec[ti + 2] + (state.targetVec[ti + 2] - state.startVec[ti + 2]) * e;
200
+ if (t >= 1)
201
+ state.active[i] = 0;
202
+ }
203
+ }
111
204
  updateMorphWeightTweens() {
112
205
  const state = this.morphTweenState;
113
206
  const now = performance.now();
@@ -231,6 +324,91 @@ export class Model {
231
324
  state.active[idx] = 1;
232
325
  }
233
326
  }
327
+ // Move bones using VMD-style relative translations (relative to bind pose world position)
328
+ // This is the default behavior for VMD animations
329
+ moveBones(names, relativeTranslations, durationMs) {
330
+ const state = this.transTweenState;
331
+ const now = performance.now();
332
+ const dur = durationMs && durationMs > 0 ? durationMs : 0;
333
+ const localRot = this.runtimeSkeleton.localRotations;
334
+ // Compute bind pose world positions for all bones
335
+ const skeleton = this.skeleton;
336
+ const computeBindPoseWorldPosition = (idx) => {
337
+ const bone = skeleton.bones[idx];
338
+ const bindPos = new Vec3(bone.bindTranslation[0], bone.bindTranslation[1], bone.bindTranslation[2]);
339
+ if (bone.parentIndex >= 0 && bone.parentIndex < skeleton.bones.length) {
340
+ const parentWorldPos = computeBindPoseWorldPosition(bone.parentIndex);
341
+ return parentWorldPos.add(bindPos);
342
+ }
343
+ else {
344
+ return bindPos;
345
+ }
346
+ };
347
+ for (let i = 0; i < names.length; i++) {
348
+ const name = names[i];
349
+ const idx = this.runtimeSkeleton.nameIndex[name] ?? -1;
350
+ if (idx < 0 || idx >= this.skeleton.bones.length)
351
+ continue;
352
+ const bone = this.skeleton.bones[idx];
353
+ const ti = idx * 3;
354
+ const qi = idx * 4;
355
+ const translations = this.runtimeSkeleton.localTranslations;
356
+ const vmdRelativeTranslation = relativeTranslations[i];
357
+ // VMD translation is relative to bind pose world position
358
+ // targetWorldPos = bindPoseWorldPos + vmdRelativeTranslation
359
+ const bindPoseWorldPos = computeBindPoseWorldPosition(idx);
360
+ const targetWorldPos = bindPoseWorldPos.add(vmdRelativeTranslation);
361
+ // Convert target world position to local translation
362
+ // We need parent's bind pose world position to transform to parent space
363
+ let parentBindPoseWorldPos;
364
+ if (bone.parentIndex >= 0) {
365
+ parentBindPoseWorldPos = computeBindPoseWorldPosition(bone.parentIndex);
366
+ }
367
+ else {
368
+ parentBindPoseWorldPos = new Vec3(0, 0, 0);
369
+ }
370
+ // Transform target world position to parent's local space
371
+ // In bind pose, parent's world matrix is just a translation
372
+ const parentSpacePos = targetWorldPos.subtract(parentBindPoseWorldPos);
373
+ // Subtract bindTranslation to get position after bind translation
374
+ const afterBindTranslation = parentSpacePos.subtract(new Vec3(bone.bindTranslation[0], bone.bindTranslation[1], bone.bindTranslation[2]));
375
+ // Apply inverse rotation to get local translation
376
+ const localRotation = new Quat(localRot[qi], localRot[qi + 1], localRot[qi + 2], localRot[qi + 3]);
377
+ const invRotation = localRotation.conjugate().normalize();
378
+ const rotationMat = Mat4.fromQuat(invRotation.x, invRotation.y, invRotation.z, invRotation.w);
379
+ const rm = rotationMat.values;
380
+ 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);
381
+ const [tx, ty, tz] = [localTranslation.x, localTranslation.y, localTranslation.z];
382
+ if (dur === 0) {
383
+ translations[ti] = tx;
384
+ translations[ti + 1] = ty;
385
+ translations[ti + 2] = tz;
386
+ state.active[idx] = 0;
387
+ continue;
388
+ }
389
+ let sx = translations[ti];
390
+ let sy = translations[ti + 1];
391
+ let sz = translations[ti + 2];
392
+ if (state.active[idx] === 1) {
393
+ const startMs = state.startTimeMs[idx];
394
+ const prevDur = Math.max(1, state.durationMs[idx]);
395
+ const t = Math.max(0, Math.min(1, (now - startMs) / prevDur));
396
+ const e = easeInOut(t);
397
+ sx = state.startVec[ti] + (state.targetVec[ti] - state.startVec[ti]) * e;
398
+ sy = state.startVec[ti + 1] + (state.targetVec[ti + 1] - state.startVec[ti + 1]) * e;
399
+ sz = state.startVec[ti + 2] + (state.targetVec[ti + 2] - state.startVec[ti + 2]) * e;
400
+ }
401
+ state.startVec[ti] = sx;
402
+ state.startVec[ti + 1] = sy;
403
+ state.startVec[ti + 2] = sz;
404
+ state.targetVec[ti] = tx;
405
+ state.targetVec[ti + 1] = ty;
406
+ state.targetVec[ti + 2] = tz;
407
+ state.startTimeMs[idx] = now;
408
+ state.durationMs[idx] = dur;
409
+ state.active[idx] = 1;
410
+ }
411
+ }
234
412
  getBoneWorldMatrices() {
235
413
  return this.runtimeSkeleton.worldMatrices;
236
414
  }
@@ -334,13 +512,29 @@ export class Model {
334
512
  }
335
513
  evaluatePose() {
336
514
  this.updateRotationTweens();
515
+ this.updateTranslationTweens();
337
516
  const hasActiveMorphTweens = this.updateMorphWeightTweens();
338
517
  if (hasActiveMorphTweens) {
339
518
  this.applyMorphs();
340
519
  }
520
+ // Compute initial world matrices (needed for IK solving)
521
+ this.computeWorldMatrices();
522
+ // Solve IK chains (modifies localRotations)
523
+ this.solveIKChains();
524
+ // Recompute world matrices with IK rotations applied
341
525
  this.computeWorldMatrices();
342
526
  return hasActiveMorphTweens;
343
527
  }
528
+ solveIKChains() {
529
+ const ikSolvers = this.runtimeSkeleton.ikSolvers;
530
+ if (!ikSolvers || ikSolvers.length === 0)
531
+ return;
532
+ const ikChainInfo = this.runtimeSkeleton.ikChainInfo;
533
+ if (!ikChainInfo)
534
+ return;
535
+ IKSolverSystem.solve(ikSolvers, this.skeleton.bones, this.runtimeSkeleton.localRotations, this.runtimeSkeleton.localTranslations, this.runtimeSkeleton.worldMatrices, ikChainInfo, false // usePhysics - can be enhanced later
536
+ );
537
+ }
344
538
  computeWorldMatrices() {
345
539
  const bones = this.skeleton.bones;
346
540
  const localRot = this.runtimeSkeleton.localRotations;
@@ -397,11 +591,15 @@ export class Model {
397
591
  }
398
592
  }
399
593
  }
400
- // Build local matrix: identity + bind translation, then rotation, then append translation
594
+ // Build local matrix: identity + bind translation, then rotation, then local translation, then append translation
595
+ const ti = i * 3;
596
+ const localTx = localTrans[ti] + addLocalTx;
597
+ const localTy = localTrans[ti + 1] + addLocalTy;
598
+ const localTz = localTrans[ti + 2] + addLocalTz;
401
599
  this.cachedIdentityMat1
402
600
  .setIdentity()
403
601
  .translateInPlace(b.bindTranslation[0], b.bindTranslation[1], b.bindTranslation[2]);
404
- this.cachedIdentityMat2.setIdentity().translateInPlace(addLocalTx, addLocalTy, addLocalTz);
602
+ this.cachedIdentityMat2.setIdentity().translateInPlace(localTx, localTy, localTz);
405
603
  const localM = this.cachedIdentityMat1.multiply(rotateM).multiply(this.cachedIdentityMat2);
406
604
  const worldOffset = i * 16;
407
605
  if (b.parentIndex >= 0) {
@@ -0,0 +1,100 @@
1
+ import { Quat, Vec3 } from "./math";
2
+ export interface AnimationPose {
3
+ boneRotations: Map<string, Quat>;
4
+ boneTranslations: Map<string, Vec3>;
5
+ morphWeights: Map<string, number>;
6
+ }
7
+ export interface AnimationProgress {
8
+ current: number;
9
+ duration: number;
10
+ percentage: number;
11
+ }
12
+ export declare class Player {
13
+ private frames;
14
+ private boneTracks;
15
+ private morphTracks;
16
+ private duration;
17
+ private isPlaying;
18
+ private isPaused;
19
+ private currentTime;
20
+ private startTime;
21
+ private pausedTime;
22
+ private pauseStartTime;
23
+ private audioElement?;
24
+ private audioUrl?;
25
+ private audioLoaded;
26
+ /**
27
+ * Load VMD animation file and optionally audio
28
+ */
29
+ loadVmd(vmdUrl: string, audioUrl?: string): Promise<void>;
30
+ /**
31
+ * Load audio file
32
+ */
33
+ loadAudio(url: string): Promise<void>;
34
+ /**
35
+ * Process frames into tracks
36
+ */
37
+ private processFrames;
38
+ /**
39
+ * Start or resume playback
40
+ */
41
+ play(): void;
42
+ /**
43
+ * Pause playback
44
+ */
45
+ pause(): void;
46
+ /**
47
+ * Stop playback and reset to beginning
48
+ */
49
+ stop(): void;
50
+ /**
51
+ * Seek to specific time
52
+ */
53
+ seek(time: number): void;
54
+ /**
55
+ * Update playback and return current pose
56
+ * Returns null if not playing, but returns current pose if paused
57
+ */
58
+ update(currentRealTime: number): AnimationPose | null;
59
+ /**
60
+ * Get pose at specific time (pure function)
61
+ */
62
+ getPoseAtTime(time: number): AnimationPose;
63
+ /**
64
+ * Get current playback progress
65
+ */
66
+ getProgress(): AnimationProgress;
67
+ /**
68
+ * Get current time
69
+ */
70
+ getCurrentTime(): number;
71
+ /**
72
+ * Get animation duration
73
+ */
74
+ getDuration(): number;
75
+ /**
76
+ * Check if playing
77
+ */
78
+ isPlayingState(): boolean;
79
+ /**
80
+ * Check if paused
81
+ */
82
+ isPausedState(): boolean;
83
+ /**
84
+ * Check if has audio
85
+ */
86
+ hasAudio(): boolean;
87
+ /**
88
+ * Set audio volume (0.0 to 1.0)
89
+ */
90
+ setVolume(volume: number): void;
91
+ /**
92
+ * Mute audio
93
+ */
94
+ mute(): void;
95
+ /**
96
+ * Unmute audio
97
+ */
98
+ unmute(): void;
99
+ }
100
+ //# sourceMappingURL=player.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAGnC,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAChC,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IACnC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,qBAAa,MAAM;IAEjB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,UAAU,CAAwE;IAC1F,OAAO,CAAC,WAAW,CAA0E;IAC7F,OAAO,CAAC,QAAQ,CAAY;IAG5B,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,WAAW,CAAY;IAG/B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,cAAc,CAAY;IAGlC,OAAO,CAAC,YAAY,CAAC,CAAkB;IACvC,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,WAAW,CAAiB;IAEpC;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW/D;;OAEG;IACG,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB3C;;OAEG;IACH,OAAO,CAAC,aAAa;IAwErB;;OAEG;IACH,IAAI,IAAI,IAAI;IAyBZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAYb;;OAEG;IACH,IAAI,IAAI,IAAI;IAcZ;;OAEG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAgBxB;;;OAGG;IACH,MAAM,CAAC,eAAe,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAiCrD;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa;IA+I1C;;OAEG;IACH,WAAW,IAAI,iBAAiB;IAQhC;;OAEG;IACH,cAAc,IAAI,MAAM;IAIxB;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;OAEG;IACH,cAAc,IAAI,OAAO;IAIzB;;OAEG;IACH,aAAa,IAAI,OAAO;IAIxB;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAM/B;;OAEG;IACH,IAAI,IAAI,IAAI;IAMZ;;OAEG;IACH,MAAM,IAAI,IAAI;CAKf"}