reze-engine 0.2.18 → 0.2.19

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();
@@ -390,114 +393,146 @@ export class PmxLoader {
390
393
  this.bones = [];
391
394
  }
392
395
  }
393
- skipMorphs() {
396
+ parseMorphs() {
394
397
  try {
395
398
  // Check if we have enough bytes to read the count
396
399
  if (this.offset + 4 > this.view.buffer.byteLength) {
397
- return false;
400
+ this.morphs = [];
401
+ return;
398
402
  }
399
403
  const count = this.getInt32();
400
404
  if (count < 0 || count > 100000) {
401
- // Suspicious count, likely corrupted - restore offset
402
- this.offset -= 4;
403
- return false;
405
+ // Suspicious count, likely corrupted
406
+ console.warn(`Suspicious morph count: ${count}`);
407
+ this.morphs = [];
408
+ return;
404
409
  }
410
+ this.morphs = [];
405
411
  for (let i = 0; i < count; i++) {
406
412
  // Check bounds before reading each morph
407
413
  if (this.offset >= this.view.buffer.byteLength) {
408
- return false;
414
+ break;
409
415
  }
410
416
  try {
411
- this.getText(); // name
412
- this.getText(); // englishName
413
- this.getUint8(); // panelType
417
+ const name = this.getText();
418
+ this.getText(); // englishName (skip)
419
+ this.getUint8(); // panelType (skip)
414
420
  const morphType = this.getUint8();
415
421
  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
422
+ const morph = {
423
+ name,
424
+ type: morphType,
425
+ vertexOffsets: [],
426
+ groupReferences: [],
427
+ };
428
+ // Parse vertex morphs (type 1)
429
+ if (morphType === 1) {
430
+ for (let j = 0; j < offsetCount; j++) {
431
+ if (this.offset >= this.view.buffer.byteLength) {
432
+ break;
433
+ }
434
+ const vertexIndex = this.getIndex(this.vertexIndexSize);
435
+ const x = this.getFloat32();
436
+ const y = this.getFloat32();
437
+ const z = this.getFloat32();
438
+ if (vertexIndex >= 0 && vertexIndex < this.vertexCount) {
439
+ morph.vertexOffsets.push({
440
+ vertexIndex,
441
+ positionOffset: [x, y, z],
442
+ });
443
+ }
448
444
  }
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
445
+ }
446
+ else if (morphType === 0) {
447
+ // Parse group morphs
448
+ for (let j = 0; j < offsetCount; j++) {
449
+ if (this.offset >= this.view.buffer.byteLength) {
450
+ break;
451
+ }
452
+ const morphIndex = this.getNonVertexIndex(this.morphIndexSize);
453
+ const ratio = this.getFloat32();
454
+ if (morphIndex >= 0) {
455
+ morph.groupReferences.push({
456
+ morphIndex,
457
+ ratio,
458
+ });
459
+ }
454
460
  }
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
461
+ }
462
+ else {
463
+ // Skip other morph types for now
464
+ for (let j = 0; j < offsetCount; j++) {
465
+ if (this.offset >= this.view.buffer.byteLength) {
466
+ break;
467
+ }
468
+ if (morphType === 2) {
469
+ // Bone morph
470
+ this.getNonVertexIndex(this.boneIndexSize); // boneIndex
471
+ this.getFloat32(); // x
472
+ this.getFloat32(); // y
473
+ this.getFloat32(); // z
474
+ this.getFloat32(); // rx
475
+ this.getFloat32(); // ry
476
+ this.getFloat32(); // rz
477
+ }
478
+ else if (morphType === 3) {
479
+ // UV morph
480
+ this.getIndex(this.vertexIndexSize); // vertexIndex
481
+ this.getFloat32(); // u
482
+ this.getFloat32(); // v
483
+ }
484
+ else if (morphType === 4 || morphType === 5 || morphType === 6 || morphType === 7) {
485
+ // UV morph types 4-7 (additional UV channels)
486
+ this.getIndex(this.vertexIndexSize); // vertexIndex
487
+ this.getFloat32(); // u
488
+ this.getFloat32(); // v
489
+ }
490
+ else if (morphType === 8) {
491
+ // Material morph
492
+ this.getNonVertexIndex(this.materialIndexSize); // materialIndex
493
+ this.getUint8(); // offsetType
494
+ this.getFloat32(); // diffuse r
495
+ this.getFloat32(); // diffuse g
496
+ this.getFloat32(); // diffuse b
497
+ this.getFloat32(); // diffuse a
498
+ this.getFloat32(); // specular r
499
+ this.getFloat32(); // specular g
500
+ this.getFloat32(); // specular b
501
+ this.getFloat32(); // specular power
502
+ this.getFloat32(); // ambient r
503
+ this.getFloat32(); // ambient g
504
+ this.getFloat32(); // ambient b
505
+ this.getFloat32(); // edgeColor r
506
+ this.getFloat32(); // edgeColor g
507
+ this.getFloat32(); // edgeColor b
508
+ this.getFloat32(); // edgeColor a
509
+ this.getFloat32(); // edgeSize
510
+ this.getFloat32(); // textureCoeff r
511
+ this.getFloat32(); // textureCoeff g
512
+ this.getFloat32(); // textureCoeff b
513
+ this.getFloat32(); // textureCoeff a
514
+ this.getFloat32(); // sphereCoeff r
515
+ this.getFloat32(); // sphereCoeff g
516
+ this.getFloat32(); // sphereCoeff b
517
+ this.getFloat32(); // sphereCoeff a
518
+ this.getFloat32(); // toonCoeff r
519
+ this.getFloat32(); // toonCoeff g
520
+ this.getFloat32(); // toonCoeff b
521
+ this.getFloat32(); // toonCoeff a
522
+ }
487
523
  }
488
524
  }
525
+ this.morphs.push(morph);
489
526
  }
490
527
  catch (e) {
491
- // If we fail to read a morph, stop skipping
528
+ // If we fail to read a morph, skip it
492
529
  console.warn(`Error reading morph ${i}:`, e);
493
- return false;
494
530
  }
495
531
  }
496
- return true;
497
532
  }
498
533
  catch (e) {
499
- console.warn("Error skipping morphs:", e);
500
- return false;
534
+ console.warn("Error parsing morphs:", e);
535
+ this.morphs = [];
501
536
  }
502
537
  }
503
538
  skipDisplayFrames() {
@@ -748,9 +783,9 @@ export class PmxLoader {
748
783
  }
749
784
  toModel(positions, normals, uvs, indices) {
750
785
  // 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++) {
786
+ // Format: [x,y,z, nx,ny,nz, u,v, x,y,z, ...]
787
+ const vertexData = new Float32Array(this.vertexCount * 8);
788
+ for (let i = 0; i < this.vertexCount; i++) {
754
789
  const pi = i * 3;
755
790
  const ui = i * 2;
756
791
  const vi = i * 8;
@@ -862,16 +897,48 @@ export class PmxLoader {
862
897
  }
863
898
  else {
864
899
  // 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++) {
900
+ const joints = new Uint16Array(this.vertexCount * 4);
901
+ const weights = new Uint8Array(this.vertexCount * 4);
902
+ for (let i = 0; i < this.vertexCount; i++) {
869
903
  joints[i * 4] = 0;
870
904
  weights[i * 4] = 255;
871
905
  }
872
906
  skinning = { joints, weights };
873
907
  }
874
- return new Model(vertexData, indexData, this.textures, this.materials, skeleton, skinning, this.rigidbodies, this.joints);
908
+ // Create morphing data structure
909
+ const morphCount = this.morphs.length;
910
+ // Dense buffer: morphCount * vertexCount * 3 floats (one vec3 per morph per vertex)
911
+ const offsetsBuffer = new Float32Array(morphCount * this.vertexCount * 3);
912
+ // Initialize all offsets to zero
913
+ offsetsBuffer.fill(0);
914
+ // Fill in actual offsets for vertex morphs
915
+ for (let morphIdx = 0; morphIdx < morphCount; morphIdx++) {
916
+ const morph = this.morphs[morphIdx];
917
+ if (morph.type === 1) {
918
+ // Vertex morph
919
+ // Store offsets in dense buffer: [morph0_v0, morph0_v1, ..., morph1_v0, ...]
920
+ // Each vec3 is 3 consecutive floats
921
+ for (const offset of morph.vertexOffsets) {
922
+ // Calculate index in the dense buffer
923
+ // Layout: morphIdx * (vertexCount * 3) + vertexIndex * 3
924
+ // This gives us the starting float index for this morph's vertex offset
925
+ const bufferIdx = morphIdx * this.vertexCount * 3 + offset.vertexIndex * 3;
926
+ if (bufferIdx >= 0 &&
927
+ bufferIdx + 2 < offsetsBuffer.length &&
928
+ offset.vertexIndex >= 0 &&
929
+ offset.vertexIndex < this.vertexCount) {
930
+ offsetsBuffer[bufferIdx] = offset.positionOffset[0];
931
+ offsetsBuffer[bufferIdx + 1] = offset.positionOffset[1];
932
+ offsetsBuffer[bufferIdx + 2] = offset.positionOffset[2];
933
+ }
934
+ }
935
+ }
936
+ }
937
+ const morphing = {
938
+ morphs: this.morphs,
939
+ offsetsBuffer,
940
+ };
941
+ return new Model(vertexData, indexData, this.textures, this.materials, skeleton, skinning, morphing, this.rigidbodies, this.joints);
875
942
  }
876
943
  getUint8() {
877
944
  if (this.offset >= 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.2.19",
4
4
  "description": "A WebGPU-based MMD model renderer",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/engine.ts CHANGED
@@ -116,6 +116,7 @@ export class Engine {
116
116
  private physics: Physics | null = null
117
117
  private materialSampler!: GPUSampler
118
118
  private textureCache = new Map<string, GPUTexture>()
119
+ private vertexBufferNeedsUpdate = false
119
120
  // Draw lists
120
121
  private opaqueDraws: DrawCall[] = []
121
122
  private eyeDraws: DrawCall[] = []
@@ -1577,6 +1578,21 @@ export class Engine {
1577
1578
  this.currentModel?.rotateBones(bones, rotations, durationMs)
1578
1579
  }
1579
1580
 
1581
+ public setMorphWeight(name: string, weight: number, durationMs?: number): void {
1582
+ if (!this.currentModel) return
1583
+ this.currentModel.setMorphWeight(name, weight, durationMs)
1584
+ if (!durationMs || durationMs === 0) {
1585
+ this.vertexBufferNeedsUpdate = true
1586
+ }
1587
+ }
1588
+
1589
+ private updateVertexBuffer(): void {
1590
+ if (!this.currentModel || !this.vertexBuffer) return
1591
+ const vertices = this.currentModel.getVertices()
1592
+ if (!vertices || vertices.length === 0) return
1593
+ this.device.queue.writeBuffer(this.vertexBuffer, 0, vertices)
1594
+ }
1595
+
1580
1596
  // Step 7: Create vertex, index, and joint buffers
1581
1597
  private async setupModelBuffers(model: Model) {
1582
1598
  this.currentModel = model
@@ -1992,6 +2008,21 @@ export class Engine {
1992
2008
  this.updateCameraUniforms()
1993
2009
  this.updateRenderTarget()
1994
2010
 
2011
+ // Update model pose first (this may update morph weights via tweens)
2012
+ // We need to do this before creating the encoder to ensure vertex buffer is ready
2013
+ if (this.currentModel) {
2014
+ const hasActiveMorphTweens = this.currentModel.evaluatePose()
2015
+ if (hasActiveMorphTweens) {
2016
+ this.vertexBufferNeedsUpdate = true
2017
+ }
2018
+ }
2019
+
2020
+ // Update vertex buffer if morphs changed
2021
+ if (this.vertexBufferNeedsUpdate) {
2022
+ this.updateVertexBuffer()
2023
+ this.vertexBufferNeedsUpdate = false
2024
+ }
2025
+
1995
2026
  // Use single encoder for both compute and render (reduces sync points)
1996
2027
  const encoder = this.device.createCommandEncoder()
1997
2028
 
@@ -2169,7 +2200,8 @@ export class Engine {
2169
2200
  }
2170
2201
 
2171
2202
  private updateModelPose(deltaTime: number, encoder: GPUCommandEncoder) {
2172
- this.currentModel!.evaluatePose()
2203
+ // Note: evaluatePose is called earlier in render() to update vertex buffer before encoder creation
2204
+ // Here we just get the matrices and update physics/compute
2173
2205
  const worldMats = this.currentModel!.getBoneWorldMatrices()
2174
2206
 
2175
2207
  if (this.physics) {