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.
- package/README.md +1 -1
- package/dist/engine.d.ts +3 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +32 -1
- package/dist/model.d.ts +36 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +156 -1
- package/dist/pmx-loader.d.ts +3 -1
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +161 -94
- package/package.json +1 -1
- package/src/engine.ts +33 -1
- package/src/model.ts +649 -421
- package/src/pmx-loader.ts +180 -89
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();
|
|
@@ -390,114 +393,146 @@ export class PmxLoader {
|
|
|
390
393
|
this.bones = [];
|
|
391
394
|
}
|
|
392
395
|
}
|
|
393
|
-
|
|
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
|
-
|
|
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
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
414
|
+
break;
|
|
409
415
|
}
|
|
410
416
|
try {
|
|
411
|
-
this.getText();
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
500
|
-
|
|
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
|
-
|
|
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
|
|
866
|
-
const
|
|
867
|
-
|
|
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
|
-
|
|
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
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
|
-
|
|
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) {
|