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/player.js ADDED
@@ -0,0 +1,409 @@
1
+ import { bezierInterpolate } from "./bezier-interpolate";
2
+ import { Quat, Vec3 } from "./math";
3
+ import { VMDLoader } from "./vmd-loader";
4
+ export class Player {
5
+ constructor() {
6
+ // Animation data
7
+ this.frames = [];
8
+ this.boneTracks = new Map();
9
+ this.morphTracks = new Map();
10
+ this.duration = 0;
11
+ // Playback state
12
+ this.isPlaying = false;
13
+ this.isPaused = false;
14
+ this.currentTime = 0;
15
+ // Timing
16
+ this.startTime = 0; // Real-time when playback started
17
+ this.pausedTime = 0; // Accumulated paused duration
18
+ this.pauseStartTime = 0;
19
+ this.audioLoaded = false;
20
+ }
21
+ /**
22
+ * Load VMD animation file and optionally audio
23
+ */
24
+ async loadVmd(vmdUrl, audioUrl) {
25
+ // Load animation
26
+ this.frames = await VMDLoader.load(vmdUrl);
27
+ this.processFrames();
28
+ // Load audio if provided
29
+ if (audioUrl) {
30
+ await this.loadAudio(audioUrl);
31
+ }
32
+ }
33
+ /**
34
+ * Load audio file
35
+ */
36
+ async loadAudio(url) {
37
+ this.audioUrl = url;
38
+ this.audioLoaded = false;
39
+ return new Promise((resolve, reject) => {
40
+ const audio = new Audio(url);
41
+ audio.preload = "auto";
42
+ audio.addEventListener("loadeddata", () => {
43
+ this.audioElement = audio;
44
+ this.audioLoaded = true;
45
+ resolve();
46
+ });
47
+ audio.addEventListener("error", (e) => {
48
+ console.warn("Failed to load audio:", url, e);
49
+ this.audioLoaded = false;
50
+ // Don't reject - animation should still work without audio
51
+ resolve();
52
+ });
53
+ audio.load();
54
+ });
55
+ }
56
+ /**
57
+ * Process frames into tracks
58
+ */
59
+ processFrames() {
60
+ // Process bone frames
61
+ const allBoneKeyFrames = [];
62
+ for (const keyFrame of this.frames) {
63
+ for (const boneFrame of keyFrame.boneFrames) {
64
+ allBoneKeyFrames.push({
65
+ boneFrame,
66
+ time: keyFrame.time,
67
+ });
68
+ }
69
+ }
70
+ const boneKeyFramesByBone = new Map();
71
+ for (const { boneFrame, time } of allBoneKeyFrames) {
72
+ if (!boneKeyFramesByBone.has(boneFrame.boneName)) {
73
+ boneKeyFramesByBone.set(boneFrame.boneName, []);
74
+ }
75
+ boneKeyFramesByBone.get(boneFrame.boneName).push({ boneFrame, time });
76
+ }
77
+ for (const keyFrames of boneKeyFramesByBone.values()) {
78
+ keyFrames.sort((a, b) => a.time - b.time);
79
+ }
80
+ // Process morph frames
81
+ const allMorphKeyFrames = [];
82
+ for (const keyFrame of this.frames) {
83
+ for (const morphFrame of keyFrame.morphFrames) {
84
+ allMorphKeyFrames.push({
85
+ morphFrame,
86
+ time: keyFrame.time,
87
+ });
88
+ }
89
+ }
90
+ const morphKeyFramesByMorph = new Map();
91
+ for (const { morphFrame, time } of allMorphKeyFrames) {
92
+ if (!morphKeyFramesByMorph.has(morphFrame.morphName)) {
93
+ morphKeyFramesByMorph.set(morphFrame.morphName, []);
94
+ }
95
+ morphKeyFramesByMorph.get(morphFrame.morphName).push({ morphFrame, time });
96
+ }
97
+ for (const keyFrames of morphKeyFramesByMorph.values()) {
98
+ keyFrames.sort((a, b) => a.time - b.time);
99
+ }
100
+ // Store tracks
101
+ this.boneTracks = boneKeyFramesByBone;
102
+ this.morphTracks = morphKeyFramesByMorph;
103
+ // Calculate animation duration from max frame time
104
+ let maxFrameTime = 0;
105
+ for (const keyFrames of this.boneTracks.values()) {
106
+ if (keyFrames.length > 0) {
107
+ const lastTime = keyFrames[keyFrames.length - 1].time;
108
+ if (lastTime > maxFrameTime) {
109
+ maxFrameTime = lastTime;
110
+ }
111
+ }
112
+ }
113
+ for (const keyFrames of this.morphTracks.values()) {
114
+ if (keyFrames.length > 0) {
115
+ const lastTime = keyFrames[keyFrames.length - 1].time;
116
+ if (lastTime > maxFrameTime) {
117
+ maxFrameTime = lastTime;
118
+ }
119
+ }
120
+ }
121
+ this.duration = maxFrameTime > 0 ? maxFrameTime : 0;
122
+ }
123
+ /**
124
+ * Start or resume playback
125
+ */
126
+ play() {
127
+ if (this.frames.length === 0)
128
+ return;
129
+ if (this.isPaused) {
130
+ // Resume from paused position - don't adjust time, just continue from where we paused
131
+ this.isPaused = false;
132
+ // Adjust start time so current time calculation continues smoothly
133
+ this.startTime = performance.now() - this.currentTime * 1000;
134
+ }
135
+ else {
136
+ // Start from beginning or current seek position
137
+ this.startTime = performance.now() - this.currentTime * 1000;
138
+ this.pausedTime = 0;
139
+ }
140
+ this.isPlaying = true;
141
+ // Play audio if available
142
+ if (this.audioElement && this.audioLoaded) {
143
+ this.audioElement.currentTime = this.currentTime;
144
+ this.audioElement.play().catch((error) => {
145
+ console.warn("Audio play failed:", error);
146
+ });
147
+ }
148
+ }
149
+ /**
150
+ * Pause playback
151
+ */
152
+ pause() {
153
+ if (!this.isPlaying || this.isPaused)
154
+ return;
155
+ this.isPaused = true;
156
+ this.pauseStartTime = performance.now();
157
+ // Pause audio if available
158
+ if (this.audioElement) {
159
+ this.audioElement.pause();
160
+ }
161
+ }
162
+ /**
163
+ * Stop playback and reset to beginning
164
+ */
165
+ stop() {
166
+ this.isPlaying = false;
167
+ this.isPaused = false;
168
+ this.currentTime = 0;
169
+ this.startTime = 0;
170
+ this.pausedTime = 0;
171
+ // Stop audio if available
172
+ if (this.audioElement) {
173
+ this.audioElement.pause();
174
+ this.audioElement.currentTime = 0;
175
+ }
176
+ }
177
+ /**
178
+ * Seek to specific time
179
+ */
180
+ seek(time) {
181
+ const clampedTime = Math.max(0, Math.min(time, this.duration));
182
+ this.currentTime = clampedTime;
183
+ // Adjust start time if playing
184
+ if (this.isPlaying && !this.isPaused) {
185
+ this.startTime = performance.now() - clampedTime * 1000;
186
+ this.pausedTime = 0;
187
+ }
188
+ // Seek audio if available
189
+ if (this.audioElement && this.audioLoaded) {
190
+ this.audioElement.currentTime = clampedTime;
191
+ }
192
+ }
193
+ /**
194
+ * Update playback and return current pose
195
+ * Returns null if not playing, but returns current pose if paused
196
+ */
197
+ update(currentRealTime) {
198
+ if (!this.isPlaying || this.frames.length === 0) {
199
+ return null;
200
+ }
201
+ // If paused, return current pose at paused time (no time update)
202
+ if (this.isPaused) {
203
+ return this.getPoseAtTime(this.currentTime);
204
+ }
205
+ // Calculate current animation time
206
+ const elapsedSeconds = (currentRealTime - this.startTime) / 1000;
207
+ this.currentTime = elapsedSeconds;
208
+ // Check if animation ended
209
+ if (this.currentTime >= this.duration) {
210
+ this.currentTime = this.duration;
211
+ this.pause(); // Auto-pause at end
212
+ return this.getPoseAtTime(this.currentTime);
213
+ }
214
+ // Sync audio if present (with tolerance)
215
+ if (this.audioElement && this.audioLoaded) {
216
+ const audioTime = this.audioElement.currentTime;
217
+ const syncTolerance = 0.1; // 100ms tolerance
218
+ if (Math.abs(audioTime - this.currentTime) > syncTolerance) {
219
+ this.audioElement.currentTime = this.currentTime;
220
+ }
221
+ }
222
+ return this.getPoseAtTime(this.currentTime);
223
+ }
224
+ /**
225
+ * Get pose at specific time (pure function)
226
+ */
227
+ getPoseAtTime(time) {
228
+ const pose = {
229
+ boneRotations: new Map(),
230
+ boneTranslations: new Map(),
231
+ morphWeights: new Map(),
232
+ };
233
+ // Helper to find upper bound index (binary search)
234
+ const upperBoundFrameIndex = (time, keyFrames) => {
235
+ let left = 0;
236
+ let right = keyFrames.length;
237
+ while (left < right) {
238
+ const mid = Math.floor((left + right) / 2);
239
+ if (keyFrames[mid].time <= time) {
240
+ left = mid + 1;
241
+ }
242
+ else {
243
+ right = mid;
244
+ }
245
+ }
246
+ return left;
247
+ };
248
+ // Process each bone track
249
+ for (const [boneName, keyFrames] of this.boneTracks.entries()) {
250
+ if (keyFrames.length === 0)
251
+ continue;
252
+ // Clamp frame time to track range
253
+ const startTime = keyFrames[0].time;
254
+ const endTime = keyFrames[keyFrames.length - 1].time;
255
+ const clampedFrameTime = Math.max(startTime, Math.min(endTime, time));
256
+ const upperBoundIndex = upperBoundFrameIndex(clampedFrameTime, keyFrames);
257
+ const upperBoundIndexMinusOne = upperBoundIndex - 1;
258
+ if (upperBoundIndexMinusOne < 0)
259
+ continue;
260
+ const timeB = keyFrames[upperBoundIndex]?.time;
261
+ const boneFrameA = keyFrames[upperBoundIndexMinusOne].boneFrame;
262
+ if (timeB === undefined) {
263
+ // Last keyframe or beyond - use the last keyframe value
264
+ pose.boneRotations.set(boneName, boneFrameA.rotation);
265
+ pose.boneTranslations.set(boneName, boneFrameA.translation);
266
+ }
267
+ else {
268
+ // Interpolate between two keyframes
269
+ const timeA = keyFrames[upperBoundIndexMinusOne].time;
270
+ const boneFrameB = keyFrames[upperBoundIndex].boneFrame;
271
+ const gradient = (clampedFrameTime - timeA) / (timeB - timeA);
272
+ // Interpolate rotation using Bezier
273
+ const interp = boneFrameB.interpolation;
274
+ const rotWeight = bezierInterpolate(interp[0] / 127, // x1
275
+ interp[1] / 127, // x2
276
+ interp[2] / 127, // y1
277
+ interp[3] / 127, // y2
278
+ gradient);
279
+ const interpolatedRotation = Quat.slerp(boneFrameA.rotation, boneFrameB.rotation, rotWeight);
280
+ // Interpolate translation using Bezier (separate curves for X, Y, Z)
281
+ const xWeight = bezierInterpolate(interp[0] / 127, // X_x1
282
+ interp[8] / 127, // X_x2
283
+ interp[4] / 127, // X_y1
284
+ interp[12] / 127, // X_y2
285
+ gradient);
286
+ const yWeight = bezierInterpolate(interp[16] / 127, // Y_x1
287
+ interp[24] / 127, // Y_x2
288
+ interp[20] / 127, // Y_y1
289
+ interp[28] / 127, // Y_y2
290
+ gradient);
291
+ const zWeight = bezierInterpolate(interp[32] / 127, // Z_x1
292
+ interp[40] / 127, // Z_x2
293
+ interp[36] / 127, // Z_y1
294
+ interp[44] / 127, // Z_y2
295
+ gradient);
296
+ const interpolatedTranslation = new Vec3(boneFrameA.translation.x + (boneFrameB.translation.x - boneFrameA.translation.x) * xWeight, boneFrameA.translation.y + (boneFrameB.translation.y - boneFrameA.translation.y) * yWeight, boneFrameA.translation.z + (boneFrameB.translation.z - boneFrameA.translation.z) * zWeight);
297
+ pose.boneRotations.set(boneName, interpolatedRotation);
298
+ pose.boneTranslations.set(boneName, interpolatedTranslation);
299
+ }
300
+ }
301
+ // Helper to find upper bound index for morph frames
302
+ const upperBoundMorphIndex = (time, keyFrames) => {
303
+ let left = 0;
304
+ let right = keyFrames.length;
305
+ while (left < right) {
306
+ const mid = Math.floor((left + right) / 2);
307
+ if (keyFrames[mid].time <= time) {
308
+ left = mid + 1;
309
+ }
310
+ else {
311
+ right = mid;
312
+ }
313
+ }
314
+ return left;
315
+ };
316
+ // Process each morph track
317
+ for (const [morphName, keyFrames] of this.morphTracks.entries()) {
318
+ if (keyFrames.length === 0)
319
+ continue;
320
+ // Clamp frame time to track range
321
+ const startTime = keyFrames[0].time;
322
+ const endTime = keyFrames[keyFrames.length - 1].time;
323
+ const clampedFrameTime = Math.max(startTime, Math.min(endTime, time));
324
+ const upperBoundIndex = upperBoundMorphIndex(clampedFrameTime, keyFrames);
325
+ const upperBoundIndexMinusOne = upperBoundIndex - 1;
326
+ if (upperBoundIndexMinusOne < 0)
327
+ continue;
328
+ const timeB = keyFrames[upperBoundIndex]?.time;
329
+ const morphFrameA = keyFrames[upperBoundIndexMinusOne].morphFrame;
330
+ if (timeB === undefined) {
331
+ // Last keyframe or beyond - use the last keyframe value
332
+ pose.morphWeights.set(morphName, morphFrameA.weight);
333
+ }
334
+ else {
335
+ // Linear interpolation between two keyframes
336
+ const timeA = keyFrames[upperBoundIndexMinusOne].time;
337
+ const morphFrameB = keyFrames[upperBoundIndex].morphFrame;
338
+ const gradient = (clampedFrameTime - timeA) / (timeB - timeA);
339
+ const interpolatedWeight = morphFrameA.weight + (morphFrameB.weight - morphFrameA.weight) * gradient;
340
+ pose.morphWeights.set(morphName, interpolatedWeight);
341
+ }
342
+ }
343
+ return pose;
344
+ }
345
+ /**
346
+ * Get current playback progress
347
+ */
348
+ getProgress() {
349
+ return {
350
+ current: this.currentTime,
351
+ duration: this.duration,
352
+ percentage: this.duration > 0 ? (this.currentTime / this.duration) * 100 : 0,
353
+ };
354
+ }
355
+ /**
356
+ * Get current time
357
+ */
358
+ getCurrentTime() {
359
+ return this.currentTime;
360
+ }
361
+ /**
362
+ * Get animation duration
363
+ */
364
+ getDuration() {
365
+ return this.duration;
366
+ }
367
+ /**
368
+ * Check if playing
369
+ */
370
+ isPlayingState() {
371
+ return this.isPlaying && !this.isPaused;
372
+ }
373
+ /**
374
+ * Check if paused
375
+ */
376
+ isPausedState() {
377
+ return this.isPaused;
378
+ }
379
+ /**
380
+ * Check if has audio
381
+ */
382
+ hasAudio() {
383
+ return this.audioElement !== undefined && this.audioLoaded;
384
+ }
385
+ /**
386
+ * Set audio volume (0.0 to 1.0)
387
+ */
388
+ setVolume(volume) {
389
+ if (this.audioElement) {
390
+ this.audioElement.volume = Math.max(0, Math.min(1, volume));
391
+ }
392
+ }
393
+ /**
394
+ * Mute audio
395
+ */
396
+ mute() {
397
+ if (this.audioElement) {
398
+ this.audioElement.muted = true;
399
+ }
400
+ }
401
+ /**
402
+ * Unmute audio
403
+ */
404
+ unmute() {
405
+ if (this.audioElement) {
406
+ this.audioElement.muted = false;
407
+ }
408
+ }
409
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAUN,MAAM,SAAS,CAAA;AAIhB,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,mBAAmB,CAAI;IAC/B,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,kBAAkB,CAAI;IAC9B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,mBAAmB,CAA4B;IACvD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAc;IAE5B,OAAO;WAIM,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAK9C,OAAO,CAAC,KAAK;IAiBb,OAAO,CAAC,WAAW;IA+CnB,OAAO,CAAC,aAAa;IA+FrB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,cAAc;IAyFtB,OAAO,CAAC,UAAU;IA2IlB,OAAO,CAAC,WAAW;IA+InB,OAAO,CAAC,iBAAiB;IAgDzB,OAAO,CAAC,gBAAgB;IAyFxB,OAAO,CAAC,WAAW;IAmGnB,OAAO,CAAC,kBAAkB;IAmC1B,OAAO,CAAC,OAAO;IAkLf,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,OAAO;IAmBf,OAAO,CAAC,QAAQ;CAIjB"}
1
+ {"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAWN,MAAM,SAAS,CAAA;AAIhB,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,mBAAmB,CAAI;IAC/B,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,kBAAkB,CAAI;IAC9B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,mBAAmB,CAA4B;IACvD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAc;IAE5B,OAAO;WAIM,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAK9C,OAAO,CAAC,KAAK;IAiBb,OAAO,CAAC,WAAW;IA+CnB,OAAO,CAAC,aAAa;IA+FrB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,cAAc;IAyFtB,OAAO,CAAC,UAAU;IAsKlB,OAAO,CAAC,WAAW;IA+InB,OAAO,CAAC,iBAAiB;IAgDzB,OAAO,CAAC,gBAAgB;IAyFxB,OAAO,CAAC,WAAW;IAmGnB,OAAO,CAAC,kBAAkB;IAmC1B,OAAO,CAAC,OAAO;IAkLf,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,OAAO;IAmBf,OAAO,CAAC,QAAQ;CAIjB"}
@@ -336,55 +336,76 @@ export class PmxLoader {
336
336
  this.getInt32();
337
337
  }
338
338
  // IK block
339
+ let ikTargetIndex = undefined;
340
+ let ikIteration = undefined;
341
+ let ikLimitAngle = undefined;
342
+ let ikLinks = undefined;
339
343
  if ((flags & FLAG_IK) !== 0) {
340
- this.getNonVertexIndex(this.boneIndexSize); // target
341
- this.getInt32(); // iteration
342
- this.getFloat32(); // rotationConstraint
344
+ ikTargetIndex = this.getNonVertexIndex(this.boneIndexSize); // target
345
+ ikIteration = this.getInt32(); // iteration
346
+ ikLimitAngle = this.getFloat32(); // rotationConstraint
343
347
  const linksCount = this.getInt32();
348
+ ikLinks = [];
344
349
  for (let li = 0; li < linksCount; li++) {
345
- this.getNonVertexIndex(this.boneIndexSize); // link target
350
+ const linkBoneIndex = this.getNonVertexIndex(this.boneIndexSize); // link target
346
351
  const hasLimit = this.getUint8() === 1;
352
+ let minAngle = undefined;
353
+ let maxAngle = undefined;
347
354
  if (hasLimit) {
348
355
  // min and max angles (vec3 each)
349
- this.getFloat32();
350
- this.getFloat32();
351
- this.getFloat32();
352
- this.getFloat32();
353
- this.getFloat32();
354
- this.getFloat32();
356
+ const minX = this.getFloat32();
357
+ const minY = this.getFloat32();
358
+ const minZ = this.getFloat32();
359
+ const maxX = this.getFloat32();
360
+ const maxY = this.getFloat32();
361
+ const maxZ = this.getFloat32();
362
+ minAngle = new Vec3(minX, minY, minZ);
363
+ maxAngle = new Vec3(maxX, maxY, maxZ);
355
364
  }
365
+ ikLinks.push({
366
+ boneIndex: linkBoneIndex,
367
+ hasLimit,
368
+ minAngle,
369
+ maxAngle,
370
+ });
356
371
  }
357
372
  }
358
373
  // Stash minimal bone info; append data will be merged later
359
- abs[i] = { name, parent: parentIndex, x, y, z, appendParent, appendRatio, appendRotate, appendMove };
374
+ abs[i] = {
375
+ name,
376
+ parent: parentIndex,
377
+ x,
378
+ y,
379
+ z,
380
+ appendParent,
381
+ appendRatio,
382
+ appendRotate,
383
+ appendMove,
384
+ ikTargetIndex,
385
+ ikIteration,
386
+ ikLimitAngle,
387
+ ikLinks,
388
+ };
360
389
  }
361
390
  for (let i = 0; i < count; i++) {
362
391
  const a = abs[i];
363
- if (a.parent >= 0 && a.parent < count) {
364
- const p = abs[a.parent];
365
- bones.push({
366
- name: a.name,
367
- parentIndex: a.parent,
368
- bindTranslation: [a.x - p.x, a.y - p.y, a.z - p.z],
369
- children: [], // Will be populated later when building skeleton
370
- appendParentIndex: a.appendParent,
371
- appendRatio: a.appendRatio,
372
- appendRotate: a.appendRotate,
373
- appendMove: a.appendMove,
374
- });
375
- }
376
- else {
377
- bones.push({
378
- name: a.name,
379
- parentIndex: a.parent,
380
- bindTranslation: [a.x, a.y, a.z],
381
- children: [], // Will be populated later when building skeleton
382
- appendParentIndex: a.appendParent,
383
- appendRatio: a.appendRatio,
384
- appendRotate: a.appendRotate,
385
- appendMove: a.appendMove,
386
- });
387
- }
392
+ const boneData = {
393
+ name: a.name,
394
+ parentIndex: a.parent,
395
+ bindTranslation: a.parent >= 0 && a.parent < count
396
+ ? [a.x - abs[a.parent].x, a.y - abs[a.parent].y, a.z - abs[a.parent].z]
397
+ : [a.x, a.y, a.z],
398
+ children: [], // Will be populated later when building skeleton
399
+ appendParentIndex: a.appendParent,
400
+ appendRatio: a.appendRatio,
401
+ appendRotate: a.appendRotate,
402
+ appendMove: a.appendMove,
403
+ ikTargetIndex: a.ikTargetIndex,
404
+ ikIteration: a.ikIteration,
405
+ ikLimitAngle: a.ikLimitAngle,
406
+ ikLinks: a.ikLinks,
407
+ };
408
+ bones.push(boneData);
388
409
  }
389
410
  this.bones = bones;
390
411
  }
@@ -1,12 +1,20 @@
1
- import { Quat } from "./math";
1
+ import { Quat, Vec3 } from "./math";
2
2
  export interface BoneFrame {
3
3
  boneName: string;
4
4
  frame: number;
5
5
  rotation: Quat;
6
+ translation: Vec3;
7
+ interpolation: Uint8Array;
8
+ }
9
+ export interface MorphFrame {
10
+ morphName: string;
11
+ frame: number;
12
+ weight: number;
6
13
  }
7
14
  export interface VMDKeyFrame {
8
15
  time: number;
9
16
  boneFrames: BoneFrame[];
17
+ morphFrames: MorphFrame[];
10
18
  }
11
19
  export declare class VMDLoader {
12
20
  private view;
@@ -17,6 +25,8 @@ export declare class VMDLoader {
17
25
  static loadFromBuffer(buffer: ArrayBuffer): VMDKeyFrame[];
18
26
  private parse;
19
27
  private readBoneFrame;
28
+ private readMorphFrame;
29
+ private getUint8;
20
30
  private getUint32;
21
31
  private getFloat32;
22
32
  private getString;
@@ -1 +1 @@
1
- {"version":3,"file":"vmd-loader.d.ts","sourceRoot":"","sources":["../src/vmd-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAE7B,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,IAAI,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,SAAS,EAAE,CAAA;CACxB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAa;IAE5B,OAAO;WAWM,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAKtD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,EAAE;IAKzD,OAAO,CAAC,KAAK;IA8Db,OAAO,CAAC,aAAa;IA+CrB,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,IAAI;CAMb"}
1
+ {"version":3,"file":"vmd-loader.d.ts","sourceRoot":"","sources":["../src/vmd-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAEnC,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,IAAI,CAAA;IACd,WAAW,EAAE,IAAI,CAAA;IACjB,aAAa,EAAE,UAAU,CAAA;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAa;IAE5B,OAAO;WAWM,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAKtD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,EAAE;IAKzD,OAAO,CAAC,KAAK;IAgGb,OAAO,CAAC,aAAa;IAuDrB,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,IAAI;CAMb"}