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.
@@ -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
- // Skip morphs and display frames before parsing rigidbodies
36
- this.skipMorphs();
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] = { 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
+ };
357
389
  }
358
390
  for (let i = 0; i < count; i++) {
359
391
  const a = abs[i];
360
- if (a.parent >= 0 && a.parent < count) {
361
- const p = abs[a.parent];
362
- bones.push({
363
- name: a.name,
364
- parentIndex: a.parent,
365
- bindTranslation: [a.x - p.x, a.y - p.y, a.z - p.z],
366
- children: [], // Will be populated later when building skeleton
367
- appendParentIndex: a.appendParent,
368
- appendRatio: a.appendRatio,
369
- appendRotate: a.appendRotate,
370
- appendMove: a.appendMove,
371
- });
372
- }
373
- else {
374
- bones.push({
375
- name: a.name,
376
- parentIndex: a.parent,
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
- skipMorphs() {
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
- return false;
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 - restore offset
402
- this.offset -= 4;
403
- return false;
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
- return false;
435
+ break;
409
436
  }
410
437
  try {
411
- this.getText(); // name
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
- // Skip offsets based on morph type
417
- for (let j = 0; j < offsetCount; j++) {
418
- if (this.offset >= this.view.buffer.byteLength) {
419
- return false;
420
- }
421
- if (morphType === 0) {
422
- // Group morph
423
- this.getNonVertexIndex(this.morphIndexSize); // morphIndex
424
- this.getFloat32(); // ratio
425
- }
426
- else if (morphType === 1) {
427
- // Vertex morph
428
- this.getIndex(this.vertexIndexSize); // vertexIndex
429
- this.getFloat32(); // x
430
- this.getFloat32(); // y
431
- this.getFloat32(); // z
432
- }
433
- else if (morphType === 2) {
434
- // Bone morph
435
- this.getNonVertexIndex(this.boneIndexSize); // boneIndex
436
- this.getFloat32(); // x
437
- this.getFloat32(); // y
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
- else if (morphType === 4 || morphType === 5 || morphType === 6 || morphType === 7) {
450
- // UV morph types 4-7 (additional UV channels)
451
- this.getIndex(this.vertexIndexSize); // vertexIndex
452
- this.getFloat32(); // u
453
- this.getFloat32(); // v
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
- else if (morphType === 8) {
456
- // Material morph
457
- this.getNonVertexIndex(this.materialIndexSize); // materialIndex
458
- this.getUint8(); // offsetType
459
- this.getFloat32(); // diffuse r
460
- this.getFloat32(); // diffuse g
461
- this.getFloat32(); // diffuse b
462
- this.getFloat32(); // diffuse a
463
- this.getFloat32(); // specular r
464
- this.getFloat32(); // specular g
465
- this.getFloat32(); // specular b
466
- this.getFloat32(); // specular power
467
- this.getFloat32(); // ambient r
468
- this.getFloat32(); // ambient g
469
- this.getFloat32(); // ambient b
470
- this.getFloat32(); // edgeColor r
471
- this.getFloat32(); // edgeColor g
472
- this.getFloat32(); // edgeColor b
473
- this.getFloat32(); // edgeColor a
474
- this.getFloat32(); // edgeSize
475
- this.getFloat32(); // textureCoeff r
476
- this.getFloat32(); // textureCoeff g
477
- this.getFloat32(); // textureCoeff b
478
- this.getFloat32(); // textureCoeff a
479
- this.getFloat32(); // sphereCoeff r
480
- this.getFloat32(); // sphereCoeff g
481
- this.getFloat32(); // sphereCoeff b
482
- this.getFloat32(); // sphereCoeff a
483
- this.getFloat32(); // toonCoeff r
484
- this.getFloat32(); // toonCoeff g
485
- this.getFloat32(); // toonCoeff b
486
- this.getFloat32(); // toonCoeff a
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, stop skipping
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 skipping morphs:", e);
500
- return false;
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
- const vertexCount = positions.length / 3;
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 vertexCount = positions.length / 3;
866
- const joints = new Uint16Array(vertexCount * 4);
867
- const weights = new Uint8Array(vertexCount * 4);
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
- return new Model(vertexData, indexData, this.textures, this.materials, skeleton, skinning, this.rigidbodies, this.joints);
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) {
@@ -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"}
@@ -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 (assuming 30 FPS like the Rust code)
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
- for (const { time, boneFrame } of allBoneFrames) {
49
- if (Math.abs(time - currentTime) > 0.001) {
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
- currentBoneFrames.push(boneFrame);
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
- // Skip position (12 bytes: 3 x f32, little endian)
99
- this.skip(12);
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
- // Skip interpolation parameters (64 bytes)
107
- this.skip(64);
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}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.2.18",
3
+ "version": "0.3.0",
4
4
  "description": "A WebGPU-based MMD model renderer",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",