reze-engine 0.2.18 → 0.3.0
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/README.md +67 -66
- package/dist/bezier-interpolate.d.ts +15 -0
- package/dist/bezier-interpolate.d.ts.map +1 -0
- package/dist/bezier-interpolate.js +40 -0
- package/dist/engine.d.ts +10 -9
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +284 -144
- package/dist/ik-solver.d.ts +26 -0
- package/dist/ik-solver.d.ts.map +1 -0
- package/dist/ik-solver.js +372 -0
- package/dist/math.d.ts +1 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +8 -0
- package/dist/model.d.ts +82 -3
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +357 -4
- package/dist/pmx-loader.d.ts +3 -1
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +218 -130
- package/dist/vmd-loader.d.ts +11 -1
- package/dist/vmd-loader.d.ts.map +1 -1
- package/dist/vmd-loader.js +91 -15
- package/package.json +1 -1
- package/src/bezier-interpolate.ts +47 -0
- package/src/camera.ts +358 -358
- package/src/engine.ts +308 -165
- package/src/ik-solver.ts +488 -0
- package/src/math.ts +555 -546
- package/src/model.ts +930 -421
- package/src/physics.ts +752 -752
- package/src/pmx-loader.ts +1173 -1054
- package/src/vmd-loader.ts +276 -179
package/dist/pmx-loader.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Model } from "./model";
|
|
1
|
+
import { Model, } from "./model";
|
|
2
2
|
import { Mat4, Vec3 } from "./math";
|
|
3
3
|
export class PmxLoader {
|
|
4
4
|
constructor(buffer) {
|
|
@@ -17,6 +17,8 @@ export class PmxLoader {
|
|
|
17
17
|
this.inverseBindMatrices = null;
|
|
18
18
|
this.joints0 = null;
|
|
19
19
|
this.weights0 = null;
|
|
20
|
+
this.morphs = [];
|
|
21
|
+
this.vertexCount = 0;
|
|
20
22
|
this.rigidbodies = [];
|
|
21
23
|
this.joints = [];
|
|
22
24
|
this.view = new DataView(buffer);
|
|
@@ -28,12 +30,13 @@ export class PmxLoader {
|
|
|
28
30
|
parse() {
|
|
29
31
|
this.parseHeader();
|
|
30
32
|
const { positions, normals, uvs } = this.parseVertices();
|
|
33
|
+
this.vertexCount = positions.length / 3;
|
|
31
34
|
const indices = this.parseIndices();
|
|
32
35
|
this.parseTextures();
|
|
33
36
|
this.parseMaterials();
|
|
34
37
|
this.parseBones();
|
|
35
|
-
//
|
|
36
|
-
this.
|
|
38
|
+
// Parse morphs and display frames before parsing rigidbodies
|
|
39
|
+
this.parseMorphs();
|
|
37
40
|
this.skipDisplayFrames();
|
|
38
41
|
this.parseRigidbodies();
|
|
39
42
|
this.parseJoints();
|
|
@@ -333,55 +336,76 @@ export class PmxLoader {
|
|
|
333
336
|
this.getInt32();
|
|
334
337
|
}
|
|
335
338
|
// IK block
|
|
339
|
+
let ikTargetIndex = undefined;
|
|
340
|
+
let ikIteration = undefined;
|
|
341
|
+
let ikLimitAngle = undefined;
|
|
342
|
+
let ikLinks = undefined;
|
|
336
343
|
if ((flags & FLAG_IK) !== 0) {
|
|
337
|
-
this.getNonVertexIndex(this.boneIndexSize); // target
|
|
338
|
-
this.getInt32(); // iteration
|
|
339
|
-
this.getFloat32(); // rotationConstraint
|
|
344
|
+
ikTargetIndex = this.getNonVertexIndex(this.boneIndexSize); // target
|
|
345
|
+
ikIteration = this.getInt32(); // iteration
|
|
346
|
+
ikLimitAngle = this.getFloat32(); // rotationConstraint
|
|
340
347
|
const linksCount = this.getInt32();
|
|
348
|
+
ikLinks = [];
|
|
341
349
|
for (let li = 0; li < linksCount; li++) {
|
|
342
|
-
this.getNonVertexIndex(this.boneIndexSize); // link target
|
|
350
|
+
const linkBoneIndex = this.getNonVertexIndex(this.boneIndexSize); // link target
|
|
343
351
|
const hasLimit = this.getUint8() === 1;
|
|
352
|
+
let minAngle = undefined;
|
|
353
|
+
let maxAngle = undefined;
|
|
344
354
|
if (hasLimit) {
|
|
345
355
|
// min and max angles (vec3 each)
|
|
346
|
-
this.getFloat32();
|
|
347
|
-
this.getFloat32();
|
|
348
|
-
this.getFloat32();
|
|
349
|
-
this.getFloat32();
|
|
350
|
-
this.getFloat32();
|
|
351
|
-
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);
|
|
352
364
|
}
|
|
365
|
+
ikLinks.push({
|
|
366
|
+
boneIndex: linkBoneIndex,
|
|
367
|
+
hasLimit,
|
|
368
|
+
minAngle,
|
|
369
|
+
maxAngle,
|
|
370
|
+
});
|
|
353
371
|
}
|
|
354
372
|
}
|
|
355
373
|
// Stash minimal bone info; append data will be merged later
|
|
356
|
-
abs[i] = {
|
|
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
|
+
};
|
|
357
389
|
}
|
|
358
390
|
for (let i = 0; i < count; i++) {
|
|
359
391
|
const a = abs[i];
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
bindTranslation: [a.x, a.y, a.z],
|
|
378
|
-
children: [], // Will be populated later when building skeleton
|
|
379
|
-
appendParentIndex: a.appendParent,
|
|
380
|
-
appendRatio: a.appendRatio,
|
|
381
|
-
appendRotate: a.appendRotate,
|
|
382
|
-
appendMove: a.appendMove,
|
|
383
|
-
});
|
|
384
|
-
}
|
|
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);
|
|
385
409
|
}
|
|
386
410
|
this.bones = bones;
|
|
387
411
|
}
|
|
@@ -390,114 +414,146 @@ export class PmxLoader {
|
|
|
390
414
|
this.bones = [];
|
|
391
415
|
}
|
|
392
416
|
}
|
|
393
|
-
|
|
417
|
+
parseMorphs() {
|
|
394
418
|
try {
|
|
395
419
|
// Check if we have enough bytes to read the count
|
|
396
420
|
if (this.offset + 4 > this.view.buffer.byteLength) {
|
|
397
|
-
|
|
421
|
+
this.morphs = [];
|
|
422
|
+
return;
|
|
398
423
|
}
|
|
399
424
|
const count = this.getInt32();
|
|
400
425
|
if (count < 0 || count > 100000) {
|
|
401
|
-
// Suspicious count, likely corrupted
|
|
402
|
-
|
|
403
|
-
|
|
426
|
+
// Suspicious count, likely corrupted
|
|
427
|
+
console.warn(`Suspicious morph count: ${count}`);
|
|
428
|
+
this.morphs = [];
|
|
429
|
+
return;
|
|
404
430
|
}
|
|
431
|
+
this.morphs = [];
|
|
405
432
|
for (let i = 0; i < count; i++) {
|
|
406
433
|
// Check bounds before reading each morph
|
|
407
434
|
if (this.offset >= this.view.buffer.byteLength) {
|
|
408
|
-
|
|
435
|
+
break;
|
|
409
436
|
}
|
|
410
437
|
try {
|
|
411
|
-
this.getText();
|
|
412
|
-
this.getText(); // englishName
|
|
413
|
-
this.getUint8(); // panelType
|
|
438
|
+
const name = this.getText();
|
|
439
|
+
this.getText(); // englishName (skip)
|
|
440
|
+
this.getUint8(); // panelType (skip)
|
|
414
441
|
const morphType = this.getUint8();
|
|
415
442
|
const offsetCount = this.getInt32();
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
this.getIndex(this.vertexIndexSize);
|
|
429
|
-
this.getFloat32();
|
|
430
|
-
this.getFloat32();
|
|
431
|
-
this.getFloat32();
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
this.getFloat32(); // z
|
|
439
|
-
this.getFloat32(); // rx
|
|
440
|
-
this.getFloat32(); // ry
|
|
441
|
-
this.getFloat32(); // rz
|
|
442
|
-
}
|
|
443
|
-
else if (morphType === 3) {
|
|
444
|
-
// UV morph
|
|
445
|
-
this.getIndex(this.vertexIndexSize); // vertexIndex
|
|
446
|
-
this.getFloat32(); // u
|
|
447
|
-
this.getFloat32(); // v
|
|
443
|
+
const morph = {
|
|
444
|
+
name,
|
|
445
|
+
type: morphType,
|
|
446
|
+
vertexOffsets: [],
|
|
447
|
+
groupReferences: [],
|
|
448
|
+
};
|
|
449
|
+
// Parse vertex morphs (type 1)
|
|
450
|
+
if (morphType === 1) {
|
|
451
|
+
for (let j = 0; j < offsetCount; j++) {
|
|
452
|
+
if (this.offset >= this.view.buffer.byteLength) {
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
const vertexIndex = this.getIndex(this.vertexIndexSize);
|
|
456
|
+
const x = this.getFloat32();
|
|
457
|
+
const y = this.getFloat32();
|
|
458
|
+
const z = this.getFloat32();
|
|
459
|
+
if (vertexIndex >= 0 && vertexIndex < this.vertexCount) {
|
|
460
|
+
morph.vertexOffsets.push({
|
|
461
|
+
vertexIndex,
|
|
462
|
+
positionOffset: [x, y, z],
|
|
463
|
+
});
|
|
464
|
+
}
|
|
448
465
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
this.
|
|
466
|
+
}
|
|
467
|
+
else if (morphType === 0) {
|
|
468
|
+
// Parse group morphs
|
|
469
|
+
for (let j = 0; j < offsetCount; j++) {
|
|
470
|
+
if (this.offset >= this.view.buffer.byteLength) {
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
const morphIndex = this.getNonVertexIndex(this.morphIndexSize);
|
|
474
|
+
const ratio = this.getFloat32();
|
|
475
|
+
if (morphIndex >= 0) {
|
|
476
|
+
morph.groupReferences.push({
|
|
477
|
+
morphIndex,
|
|
478
|
+
ratio,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
454
481
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
this.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
// Skip other morph types for now
|
|
485
|
+
for (let j = 0; j < offsetCount; j++) {
|
|
486
|
+
if (this.offset >= this.view.buffer.byteLength) {
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
if (morphType === 2) {
|
|
490
|
+
// Bone morph
|
|
491
|
+
this.getNonVertexIndex(this.boneIndexSize); // boneIndex
|
|
492
|
+
this.getFloat32(); // x
|
|
493
|
+
this.getFloat32(); // y
|
|
494
|
+
this.getFloat32(); // z
|
|
495
|
+
this.getFloat32(); // rx
|
|
496
|
+
this.getFloat32(); // ry
|
|
497
|
+
this.getFloat32(); // rz
|
|
498
|
+
}
|
|
499
|
+
else if (morphType === 3) {
|
|
500
|
+
// UV morph
|
|
501
|
+
this.getIndex(this.vertexIndexSize); // vertexIndex
|
|
502
|
+
this.getFloat32(); // u
|
|
503
|
+
this.getFloat32(); // v
|
|
504
|
+
}
|
|
505
|
+
else if (morphType === 4 || morphType === 5 || morphType === 6 || morphType === 7) {
|
|
506
|
+
// UV morph types 4-7 (additional UV channels)
|
|
507
|
+
this.getIndex(this.vertexIndexSize); // vertexIndex
|
|
508
|
+
this.getFloat32(); // u
|
|
509
|
+
this.getFloat32(); // v
|
|
510
|
+
}
|
|
511
|
+
else if (morphType === 8) {
|
|
512
|
+
// Material morph
|
|
513
|
+
this.getNonVertexIndex(this.materialIndexSize); // materialIndex
|
|
514
|
+
this.getUint8(); // offsetType
|
|
515
|
+
this.getFloat32(); // diffuse r
|
|
516
|
+
this.getFloat32(); // diffuse g
|
|
517
|
+
this.getFloat32(); // diffuse b
|
|
518
|
+
this.getFloat32(); // diffuse a
|
|
519
|
+
this.getFloat32(); // specular r
|
|
520
|
+
this.getFloat32(); // specular g
|
|
521
|
+
this.getFloat32(); // specular b
|
|
522
|
+
this.getFloat32(); // specular power
|
|
523
|
+
this.getFloat32(); // ambient r
|
|
524
|
+
this.getFloat32(); // ambient g
|
|
525
|
+
this.getFloat32(); // ambient b
|
|
526
|
+
this.getFloat32(); // edgeColor r
|
|
527
|
+
this.getFloat32(); // edgeColor g
|
|
528
|
+
this.getFloat32(); // edgeColor b
|
|
529
|
+
this.getFloat32(); // edgeColor a
|
|
530
|
+
this.getFloat32(); // edgeSize
|
|
531
|
+
this.getFloat32(); // textureCoeff r
|
|
532
|
+
this.getFloat32(); // textureCoeff g
|
|
533
|
+
this.getFloat32(); // textureCoeff b
|
|
534
|
+
this.getFloat32(); // textureCoeff a
|
|
535
|
+
this.getFloat32(); // sphereCoeff r
|
|
536
|
+
this.getFloat32(); // sphereCoeff g
|
|
537
|
+
this.getFloat32(); // sphereCoeff b
|
|
538
|
+
this.getFloat32(); // sphereCoeff a
|
|
539
|
+
this.getFloat32(); // toonCoeff r
|
|
540
|
+
this.getFloat32(); // toonCoeff g
|
|
541
|
+
this.getFloat32(); // toonCoeff b
|
|
542
|
+
this.getFloat32(); // toonCoeff a
|
|
543
|
+
}
|
|
487
544
|
}
|
|
488
545
|
}
|
|
546
|
+
this.morphs.push(morph);
|
|
489
547
|
}
|
|
490
548
|
catch (e) {
|
|
491
|
-
// If we fail to read a morph,
|
|
549
|
+
// If we fail to read a morph, skip it
|
|
492
550
|
console.warn(`Error reading morph ${i}:`, e);
|
|
493
|
-
return false;
|
|
494
551
|
}
|
|
495
552
|
}
|
|
496
|
-
return true;
|
|
497
553
|
}
|
|
498
554
|
catch (e) {
|
|
499
|
-
console.warn("Error
|
|
500
|
-
|
|
555
|
+
console.warn("Error parsing morphs:", e);
|
|
556
|
+
this.morphs = [];
|
|
501
557
|
}
|
|
502
558
|
}
|
|
503
559
|
skipDisplayFrames() {
|
|
@@ -748,9 +804,9 @@ export class PmxLoader {
|
|
|
748
804
|
}
|
|
749
805
|
toModel(positions, normals, uvs, indices) {
|
|
750
806
|
// Create indexed vertex buffer
|
|
751
|
-
|
|
752
|
-
const vertexData = new Float32Array(vertexCount * 8);
|
|
753
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
807
|
+
// Format: [x,y,z, nx,ny,nz, u,v, x,y,z, ...]
|
|
808
|
+
const vertexData = new Float32Array(this.vertexCount * 8);
|
|
809
|
+
for (let i = 0; i < this.vertexCount; i++) {
|
|
754
810
|
const pi = i * 3;
|
|
755
811
|
const ui = i * 2;
|
|
756
812
|
const vi = i * 8;
|
|
@@ -862,16 +918,48 @@ export class PmxLoader {
|
|
|
862
918
|
}
|
|
863
919
|
else {
|
|
864
920
|
// Create default skinning (single bone per vertex)
|
|
865
|
-
const
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
921
|
+
const joints = new Uint16Array(this.vertexCount * 4);
|
|
922
|
+
const weights = new Uint8Array(this.vertexCount * 4);
|
|
923
|
+
for (let i = 0; i < this.vertexCount; i++) {
|
|
869
924
|
joints[i * 4] = 0;
|
|
870
925
|
weights[i * 4] = 255;
|
|
871
926
|
}
|
|
872
927
|
skinning = { joints, weights };
|
|
873
928
|
}
|
|
874
|
-
|
|
929
|
+
// Create morphing data structure
|
|
930
|
+
const morphCount = this.morphs.length;
|
|
931
|
+
// Dense buffer: morphCount * vertexCount * 3 floats (one vec3 per morph per vertex)
|
|
932
|
+
const offsetsBuffer = new Float32Array(morphCount * this.vertexCount * 3);
|
|
933
|
+
// Initialize all offsets to zero
|
|
934
|
+
offsetsBuffer.fill(0);
|
|
935
|
+
// Fill in actual offsets for vertex morphs
|
|
936
|
+
for (let morphIdx = 0; morphIdx < morphCount; morphIdx++) {
|
|
937
|
+
const morph = this.morphs[morphIdx];
|
|
938
|
+
if (morph.type === 1) {
|
|
939
|
+
// Vertex morph
|
|
940
|
+
// Store offsets in dense buffer: [morph0_v0, morph0_v1, ..., morph1_v0, ...]
|
|
941
|
+
// Each vec3 is 3 consecutive floats
|
|
942
|
+
for (const offset of morph.vertexOffsets) {
|
|
943
|
+
// Calculate index in the dense buffer
|
|
944
|
+
// Layout: morphIdx * (vertexCount * 3) + vertexIndex * 3
|
|
945
|
+
// This gives us the starting float index for this morph's vertex offset
|
|
946
|
+
const bufferIdx = morphIdx * this.vertexCount * 3 + offset.vertexIndex * 3;
|
|
947
|
+
if (bufferIdx >= 0 &&
|
|
948
|
+
bufferIdx + 2 < offsetsBuffer.length &&
|
|
949
|
+
offset.vertexIndex >= 0 &&
|
|
950
|
+
offset.vertexIndex < this.vertexCount) {
|
|
951
|
+
offsetsBuffer[bufferIdx] = offset.positionOffset[0];
|
|
952
|
+
offsetsBuffer[bufferIdx + 1] = offset.positionOffset[1];
|
|
953
|
+
offsetsBuffer[bufferIdx + 2] = offset.positionOffset[2];
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const morphing = {
|
|
959
|
+
morphs: this.morphs,
|
|
960
|
+
offsetsBuffer,
|
|
961
|
+
};
|
|
962
|
+
return new Model(vertexData, indexData, this.textures, this.materials, skeleton, skinning, morphing, this.rigidbodies, this.joints);
|
|
875
963
|
}
|
|
876
964
|
getUint8() {
|
|
877
965
|
if (this.offset >= this.view.buffer.byteLength) {
|
package/dist/vmd-loader.d.ts
CHANGED
|
@@ -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;
|
package/dist/vmd-loader.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/vmd-loader.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Quat } from "./math";
|
|
1
|
+
import { Quat, Vec3 } from "./math";
|
|
2
2
|
export class VMDLoader {
|
|
3
3
|
constructor(buffer) {
|
|
4
4
|
this.offset = 0;
|
|
@@ -34,39 +34,67 @@ export class VMDLoader {
|
|
|
34
34
|
const allBoneFrames = [];
|
|
35
35
|
for (let i = 0; i < boneFrameCount; i++) {
|
|
36
36
|
const boneFrame = this.readBoneFrame();
|
|
37
|
-
// Convert frame number to time (
|
|
37
|
+
// Convert frame number to time (30 FPS)
|
|
38
38
|
const FRAME_RATE = 30.0;
|
|
39
39
|
const time = boneFrame.frame / FRAME_RATE;
|
|
40
40
|
allBoneFrames.push({ time, boneFrame });
|
|
41
41
|
}
|
|
42
|
+
// Read morph frame count (4 bytes, u32 little endian)
|
|
43
|
+
const morphFrameCount = this.getUint32();
|
|
44
|
+
// Read all morph frames
|
|
45
|
+
const allMorphFrames = [];
|
|
46
|
+
for (let i = 0; i < morphFrameCount; i++) {
|
|
47
|
+
const morphFrame = this.readMorphFrame();
|
|
48
|
+
// Convert frame number to time (30 FPS)
|
|
49
|
+
const FRAME_RATE = 30.0;
|
|
50
|
+
const time = morphFrame.frame / FRAME_RATE;
|
|
51
|
+
allMorphFrames.push({ time, morphFrame });
|
|
52
|
+
}
|
|
53
|
+
// Combine all frames and group by time
|
|
54
|
+
const allFrames = [];
|
|
55
|
+
for (const { time, boneFrame } of allBoneFrames) {
|
|
56
|
+
allFrames.push({ time, boneFrame });
|
|
57
|
+
}
|
|
58
|
+
for (const { time, morphFrame } of allMorphFrames) {
|
|
59
|
+
allFrames.push({ time, morphFrame });
|
|
60
|
+
}
|
|
61
|
+
// Sort by time
|
|
62
|
+
allFrames.sort((a, b) => a.time - b.time);
|
|
42
63
|
// Group by time and convert to VMDKeyFrame format
|
|
43
|
-
// Sort by time first
|
|
44
|
-
allBoneFrames.sort((a, b) => a.time - b.time);
|
|
45
64
|
const keyFrames = [];
|
|
46
65
|
let currentTime = -1.0;
|
|
47
66
|
let currentBoneFrames = [];
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
let currentMorphFrames = [];
|
|
68
|
+
for (const frame of allFrames) {
|
|
69
|
+
if (Math.abs(frame.time - currentTime) > 0.001) {
|
|
50
70
|
// New time frame
|
|
51
|
-
if (currentBoneFrames.length > 0) {
|
|
71
|
+
if (currentBoneFrames.length > 0 || currentMorphFrames.length > 0) {
|
|
52
72
|
keyFrames.push({
|
|
53
73
|
time: currentTime,
|
|
54
74
|
boneFrames: currentBoneFrames,
|
|
75
|
+
morphFrames: currentMorphFrames,
|
|
55
76
|
});
|
|
56
77
|
}
|
|
57
|
-
currentTime = time;
|
|
58
|
-
currentBoneFrames = [boneFrame];
|
|
78
|
+
currentTime = frame.time;
|
|
79
|
+
currentBoneFrames = frame.boneFrame ? [frame.boneFrame] : [];
|
|
80
|
+
currentMorphFrames = frame.morphFrame ? [frame.morphFrame] : [];
|
|
59
81
|
}
|
|
60
82
|
else {
|
|
61
83
|
// Same time frame
|
|
62
|
-
|
|
84
|
+
if (frame.boneFrame) {
|
|
85
|
+
currentBoneFrames.push(frame.boneFrame);
|
|
86
|
+
}
|
|
87
|
+
if (frame.morphFrame) {
|
|
88
|
+
currentMorphFrames.push(frame.morphFrame);
|
|
89
|
+
}
|
|
63
90
|
}
|
|
64
91
|
}
|
|
65
92
|
// Add the last frame
|
|
66
|
-
if (currentBoneFrames.length > 0) {
|
|
93
|
+
if (currentBoneFrames.length > 0 || currentMorphFrames.length > 0) {
|
|
67
94
|
keyFrames.push({
|
|
68
95
|
time: currentTime,
|
|
69
96
|
boneFrames: currentBoneFrames,
|
|
97
|
+
morphFrames: currentMorphFrames,
|
|
70
98
|
});
|
|
71
99
|
}
|
|
72
100
|
return keyFrames;
|
|
@@ -95,22 +123,70 @@ export class VMDLoader {
|
|
|
95
123
|
}
|
|
96
124
|
// Read frame number (4 bytes, little endian)
|
|
97
125
|
const frame = this.getUint32();
|
|
98
|
-
//
|
|
99
|
-
this.
|
|
126
|
+
// Read position/translation (12 bytes: 3 x f32, little endian)
|
|
127
|
+
const posX = this.getFloat32();
|
|
128
|
+
const posY = this.getFloat32();
|
|
129
|
+
const posZ = this.getFloat32();
|
|
130
|
+
const translation = new Vec3(posX, posY, posZ);
|
|
100
131
|
// Read rotation quaternion (16 bytes: 4 x f32, little endian)
|
|
101
132
|
const rotX = this.getFloat32();
|
|
102
133
|
const rotY = this.getFloat32();
|
|
103
134
|
const rotZ = this.getFloat32();
|
|
104
135
|
const rotW = this.getFloat32();
|
|
105
136
|
const rotation = new Quat(rotX, rotY, rotZ, rotW);
|
|
106
|
-
//
|
|
107
|
-
|
|
137
|
+
// Read interpolation parameters (64 bytes)
|
|
138
|
+
const interpolation = new Uint8Array(64);
|
|
139
|
+
for (let i = 0; i < 64; i++) {
|
|
140
|
+
interpolation[i] = this.getUint8();
|
|
141
|
+
}
|
|
108
142
|
return {
|
|
109
143
|
boneName,
|
|
110
144
|
frame,
|
|
111
145
|
rotation,
|
|
146
|
+
translation,
|
|
147
|
+
interpolation,
|
|
112
148
|
};
|
|
113
149
|
}
|
|
150
|
+
readMorphFrame() {
|
|
151
|
+
// Read morph name (15 bytes)
|
|
152
|
+
const nameBuffer = new Uint8Array(this.view.buffer, this.offset, 15);
|
|
153
|
+
this.offset += 15;
|
|
154
|
+
// Find the actual length of the morph name (stop at first null byte)
|
|
155
|
+
let nameLength = 15;
|
|
156
|
+
for (let i = 0; i < 15; i++) {
|
|
157
|
+
if (nameBuffer[i] === 0) {
|
|
158
|
+
nameLength = i;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Decode Shift-JIS morph name
|
|
163
|
+
let morphName;
|
|
164
|
+
try {
|
|
165
|
+
const nameSlice = nameBuffer.slice(0, nameLength);
|
|
166
|
+
morphName = this.decoder.decode(nameSlice);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Fallback to lossy decoding if there were encoding errors
|
|
170
|
+
morphName = String.fromCharCode(...nameBuffer.slice(0, nameLength));
|
|
171
|
+
}
|
|
172
|
+
// Read frame number (4 bytes, little endian)
|
|
173
|
+
const frame = this.getUint32();
|
|
174
|
+
// Read weight (4 bytes, f32, little endian)
|
|
175
|
+
const weight = this.getFloat32();
|
|
176
|
+
return {
|
|
177
|
+
morphName,
|
|
178
|
+
frame,
|
|
179
|
+
weight,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
getUint8() {
|
|
183
|
+
if (this.offset + 1 > this.view.buffer.byteLength) {
|
|
184
|
+
throw new RangeError(`Offset ${this.offset} + 1 exceeds buffer bounds ${this.view.buffer.byteLength}`);
|
|
185
|
+
}
|
|
186
|
+
const v = this.view.getUint8(this.offset);
|
|
187
|
+
this.offset += 1;
|
|
188
|
+
return v;
|
|
189
|
+
}
|
|
114
190
|
getUint32() {
|
|
115
191
|
if (this.offset + 4 > this.view.buffer.byteLength) {
|
|
116
192
|
throw new RangeError(`Offset ${this.offset} + 4 exceeds buffer bounds ${this.view.buffer.byteLength}`);
|