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/model.js
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
|
-
import { Mat4, Quat, easeInOut } from "./math";
|
|
1
|
+
import { Mat4, Quat, Vec3, easeInOut } from "./math";
|
|
2
|
+
import { IKSolverSystem } from "./ik-solver";
|
|
2
3
|
const VERTEX_STRIDE = 8;
|
|
4
|
+
// Euler rotation order for angle constraints
|
|
5
|
+
export var EulerRotationOrder;
|
|
6
|
+
(function (EulerRotationOrder) {
|
|
7
|
+
EulerRotationOrder[EulerRotationOrder["YXZ"] = 0] = "YXZ";
|
|
8
|
+
EulerRotationOrder[EulerRotationOrder["ZYX"] = 1] = "ZYX";
|
|
9
|
+
EulerRotationOrder[EulerRotationOrder["XZY"] = 2] = "XZY";
|
|
10
|
+
})(EulerRotationOrder || (EulerRotationOrder = {}));
|
|
11
|
+
// Solve axis optimization
|
|
12
|
+
export var SolveAxis;
|
|
13
|
+
(function (SolveAxis) {
|
|
14
|
+
SolveAxis[SolveAxis["None"] = 0] = "None";
|
|
15
|
+
SolveAxis[SolveAxis["Fixed"] = 1] = "Fixed";
|
|
16
|
+
SolveAxis[SolveAxis["X"] = 2] = "X";
|
|
17
|
+
SolveAxis[SolveAxis["Y"] = 3] = "Y";
|
|
18
|
+
SolveAxis[SolveAxis["Z"] = 4] = "Z";
|
|
19
|
+
})(SolveAxis || (SolveAxis = {}));
|
|
3
20
|
export class Model {
|
|
4
|
-
constructor(vertexData, indexData, textures, materials, skeleton, skinning, rigidbodies = [], joints = []) {
|
|
21
|
+
constructor(vertexData, indexData, textures, materials, skeleton, skinning, morphing, rigidbodies = [], joints = []) {
|
|
5
22
|
this.textures = [];
|
|
6
23
|
this.materials = [];
|
|
7
24
|
// Physics data from PMX
|
|
@@ -10,6 +27,8 @@ export class Model {
|
|
|
10
27
|
// Cached identity matrices to avoid allocations in computeWorldMatrices
|
|
11
28
|
this.cachedIdentityMat1 = Mat4.identity();
|
|
12
29
|
this.cachedIdentityMat2 = Mat4.identity();
|
|
30
|
+
// Store base vertex data (original positions before morphing)
|
|
31
|
+
this.baseVertexData = new Float32Array(vertexData);
|
|
13
32
|
this.vertexData = vertexData;
|
|
14
33
|
this.vertexCount = vertexData.length / VERTEX_STRIDE;
|
|
15
34
|
this.indexData = indexData;
|
|
@@ -17,6 +36,7 @@ export class Model {
|
|
|
17
36
|
this.materials = materials;
|
|
18
37
|
this.skeleton = skeleton;
|
|
19
38
|
this.skinning = skinning;
|
|
39
|
+
this.morphing = morphing;
|
|
20
40
|
this.rigidbodies = rigidbodies;
|
|
21
41
|
this.joints = joints;
|
|
22
42
|
if (this.skeleton.bones.length == 0) {
|
|
@@ -24,6 +44,10 @@ export class Model {
|
|
|
24
44
|
}
|
|
25
45
|
this.initializeRuntimeSkeleton();
|
|
26
46
|
this.initializeRotTweenBuffers();
|
|
47
|
+
this.initializeTransTweenBuffers();
|
|
48
|
+
this.initializeRuntimeMorph();
|
|
49
|
+
this.initializeMorphTweenBuffers();
|
|
50
|
+
this.applyMorphs(); // Apply initial morphs (all weights are 0, so no change)
|
|
27
51
|
}
|
|
28
52
|
initializeRuntimeSkeleton() {
|
|
29
53
|
const boneCount = this.skeleton.bones.length;
|
|
@@ -47,6 +71,51 @@ export class Model {
|
|
|
47
71
|
rotations[qi + 3] = 1;
|
|
48
72
|
}
|
|
49
73
|
}
|
|
74
|
+
// Initialize IK runtime state
|
|
75
|
+
this.initializeIKRuntime();
|
|
76
|
+
}
|
|
77
|
+
initializeIKRuntime() {
|
|
78
|
+
const boneCount = this.skeleton.bones.length;
|
|
79
|
+
const bones = this.skeleton.bones;
|
|
80
|
+
// Initialize IK chain info for all bones (will be populated for IK chain bones)
|
|
81
|
+
const ikChainInfo = new Array(boneCount);
|
|
82
|
+
for (let i = 0; i < boneCount; i++) {
|
|
83
|
+
ikChainInfo[i] = {
|
|
84
|
+
ikRotation: new Quat(0, 0, 0, 1),
|
|
85
|
+
localRotation: new Quat(0, 0, 0, 1),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Build IK solvers from bone data
|
|
89
|
+
const ikSolvers = [];
|
|
90
|
+
let solverIndex = 0;
|
|
91
|
+
for (let i = 0; i < boneCount; i++) {
|
|
92
|
+
const bone = bones[i];
|
|
93
|
+
if (bone.ikTargetIndex !== undefined && bone.ikLinks && bone.ikLinks.length > 0) {
|
|
94
|
+
// Check if all links are affected by physics (for optimization)
|
|
95
|
+
let canSkipWhenPhysicsEnabled = true;
|
|
96
|
+
for (const link of bone.ikLinks) {
|
|
97
|
+
// For now, assume no bones are physics-controlled (can be enhanced later)
|
|
98
|
+
// If a bone has a rigidbody attached, it's physics-controlled
|
|
99
|
+
const hasPhysics = this.rigidbodies.some((rb) => rb.boneIndex === link.boneIndex);
|
|
100
|
+
if (!hasPhysics) {
|
|
101
|
+
canSkipWhenPhysicsEnabled = false;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const solver = {
|
|
106
|
+
index: solverIndex++,
|
|
107
|
+
ikBoneIndex: i,
|
|
108
|
+
targetBoneIndex: bone.ikTargetIndex,
|
|
109
|
+
iterationCount: bone.ikIteration ?? 1,
|
|
110
|
+
limitAngle: bone.ikLimitAngle ?? Math.PI,
|
|
111
|
+
links: bone.ikLinks,
|
|
112
|
+
canSkipWhenPhysicsEnabled,
|
|
113
|
+
};
|
|
114
|
+
ikSolvers.push(solver);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.runtimeSkeleton.ikChainInfo = ikChainInfo;
|
|
118
|
+
this.runtimeSkeleton.ikSolvers = ikSolvers;
|
|
50
119
|
}
|
|
51
120
|
initializeRotTweenBuffers() {
|
|
52
121
|
const n = this.skeleton.bones.length;
|
|
@@ -58,6 +127,36 @@ export class Model {
|
|
|
58
127
|
durationMs: new Float32Array(n),
|
|
59
128
|
};
|
|
60
129
|
}
|
|
130
|
+
initializeTransTweenBuffers() {
|
|
131
|
+
const n = this.skeleton.bones.length;
|
|
132
|
+
this.transTweenState = {
|
|
133
|
+
active: new Uint8Array(n),
|
|
134
|
+
startVec: new Float32Array(n * 3),
|
|
135
|
+
targetVec: new Float32Array(n * 3),
|
|
136
|
+
startTimeMs: new Float32Array(n),
|
|
137
|
+
durationMs: new Float32Array(n),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
initializeMorphTweenBuffers() {
|
|
141
|
+
const n = this.morphing.morphs.length;
|
|
142
|
+
this.morphTweenState = {
|
|
143
|
+
active: new Uint8Array(n),
|
|
144
|
+
startWeight: new Float32Array(n),
|
|
145
|
+
targetWeight: new Float32Array(n),
|
|
146
|
+
startTimeMs: new Float32Array(n),
|
|
147
|
+
durationMs: new Float32Array(n),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
initializeRuntimeMorph() {
|
|
151
|
+
const morphCount = this.morphing.morphs.length;
|
|
152
|
+
this.runtimeMorph = {
|
|
153
|
+
nameIndex: this.morphing.morphs.reduce((acc, morph, index) => {
|
|
154
|
+
acc[morph.name] = index;
|
|
155
|
+
return acc;
|
|
156
|
+
}, {}),
|
|
157
|
+
weights: new Float32Array(morphCount),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
61
160
|
updateRotationTweens() {
|
|
62
161
|
const state = this.rotTweenState;
|
|
63
162
|
const now = performance.now();
|
|
@@ -82,6 +181,48 @@ export class Model {
|
|
|
82
181
|
state.active[i] = 0;
|
|
83
182
|
}
|
|
84
183
|
}
|
|
184
|
+
updateTranslationTweens() {
|
|
185
|
+
const state = this.transTweenState;
|
|
186
|
+
const now = performance.now();
|
|
187
|
+
const translations = this.runtimeSkeleton.localTranslations;
|
|
188
|
+
const boneCount = this.skeleton.bones.length;
|
|
189
|
+
for (let i = 0; i < boneCount; i++) {
|
|
190
|
+
if (state.active[i] !== 1)
|
|
191
|
+
continue;
|
|
192
|
+
const startMs = state.startTimeMs[i];
|
|
193
|
+
const durMs = Math.max(1, state.durationMs[i]);
|
|
194
|
+
const t = Math.max(0, Math.min(1, (now - startMs) / durMs));
|
|
195
|
+
const e = easeInOut(t);
|
|
196
|
+
const ti = i * 3;
|
|
197
|
+
translations[ti] = state.startVec[ti] + (state.targetVec[ti] - state.startVec[ti]) * e;
|
|
198
|
+
translations[ti + 1] = state.startVec[ti + 1] + (state.targetVec[ti + 1] - state.startVec[ti + 1]) * e;
|
|
199
|
+
translations[ti + 2] = state.startVec[ti + 2] + (state.targetVec[ti + 2] - state.startVec[ti + 2]) * e;
|
|
200
|
+
if (t >= 1)
|
|
201
|
+
state.active[i] = 0;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
updateMorphWeightTweens() {
|
|
205
|
+
const state = this.morphTweenState;
|
|
206
|
+
const now = performance.now();
|
|
207
|
+
const weights = this.runtimeMorph.weights;
|
|
208
|
+
const morphCount = this.morphing.morphs.length;
|
|
209
|
+
let hasActiveTweens = false;
|
|
210
|
+
for (let i = 0; i < morphCount; i++) {
|
|
211
|
+
if (state.active[i] !== 1)
|
|
212
|
+
continue;
|
|
213
|
+
hasActiveTweens = true;
|
|
214
|
+
const startMs = state.startTimeMs[i];
|
|
215
|
+
const durMs = Math.max(1, state.durationMs[i]);
|
|
216
|
+
const t = Math.max(0, Math.min(1, (now - startMs) / durMs));
|
|
217
|
+
const e = easeInOut(t);
|
|
218
|
+
weights[i] = state.startWeight[i] + (state.targetWeight[i] - state.startWeight[i]) * e;
|
|
219
|
+
if (t >= 1) {
|
|
220
|
+
weights[i] = state.targetWeight[i];
|
|
221
|
+
state.active[i] = 0;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return hasActiveTweens;
|
|
225
|
+
}
|
|
85
226
|
// Get interleaved vertex data for GPU upload
|
|
86
227
|
// Format: [x,y,z, nx,ny,nz, u,v, x,y,z, nx,ny,nz, u,v, ...]
|
|
87
228
|
getVertices() {
|
|
@@ -117,6 +258,13 @@ export class Model {
|
|
|
117
258
|
getJoints() {
|
|
118
259
|
return this.joints;
|
|
119
260
|
}
|
|
261
|
+
// Accessors for morphing
|
|
262
|
+
getMorphing() {
|
|
263
|
+
return this.morphing;
|
|
264
|
+
}
|
|
265
|
+
getMorphWeights() {
|
|
266
|
+
return this.runtimeMorph.weights;
|
|
267
|
+
}
|
|
120
268
|
// ------- Bone helpers (public API) -------
|
|
121
269
|
getBoneNames() {
|
|
122
270
|
return this.skeleton.bones.map((b) => b.name);
|
|
@@ -176,15 +324,216 @@ export class Model {
|
|
|
176
324
|
state.active[idx] = 1;
|
|
177
325
|
}
|
|
178
326
|
}
|
|
327
|
+
// Move bones using VMD-style relative translations (relative to bind pose world position)
|
|
328
|
+
// This is the default behavior for VMD animations
|
|
329
|
+
moveBones(names, relativeTranslations, durationMs) {
|
|
330
|
+
const state = this.transTweenState;
|
|
331
|
+
const now = performance.now();
|
|
332
|
+
const dur = durationMs && durationMs > 0 ? durationMs : 0;
|
|
333
|
+
const localRot = this.runtimeSkeleton.localRotations;
|
|
334
|
+
// Compute bind pose world positions for all bones
|
|
335
|
+
const skeleton = this.skeleton;
|
|
336
|
+
const computeBindPoseWorldPosition = (idx) => {
|
|
337
|
+
const bone = skeleton.bones[idx];
|
|
338
|
+
const bindPos = new Vec3(bone.bindTranslation[0], bone.bindTranslation[1], bone.bindTranslation[2]);
|
|
339
|
+
if (bone.parentIndex >= 0 && bone.parentIndex < skeleton.bones.length) {
|
|
340
|
+
const parentWorldPos = computeBindPoseWorldPosition(bone.parentIndex);
|
|
341
|
+
return parentWorldPos.add(bindPos);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
return bindPos;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
for (let i = 0; i < names.length; i++) {
|
|
348
|
+
const name = names[i];
|
|
349
|
+
const idx = this.runtimeSkeleton.nameIndex[name] ?? -1;
|
|
350
|
+
if (idx < 0 || idx >= this.skeleton.bones.length)
|
|
351
|
+
continue;
|
|
352
|
+
const bone = this.skeleton.bones[idx];
|
|
353
|
+
const ti = idx * 3;
|
|
354
|
+
const qi = idx * 4;
|
|
355
|
+
const translations = this.runtimeSkeleton.localTranslations;
|
|
356
|
+
const vmdRelativeTranslation = relativeTranslations[i];
|
|
357
|
+
// VMD translation is relative to bind pose world position
|
|
358
|
+
// targetWorldPos = bindPoseWorldPos + vmdRelativeTranslation
|
|
359
|
+
const bindPoseWorldPos = computeBindPoseWorldPosition(idx);
|
|
360
|
+
const targetWorldPos = bindPoseWorldPos.add(vmdRelativeTranslation);
|
|
361
|
+
// Convert target world position to local translation
|
|
362
|
+
// We need parent's bind pose world position to transform to parent space
|
|
363
|
+
let parentBindPoseWorldPos;
|
|
364
|
+
if (bone.parentIndex >= 0) {
|
|
365
|
+
parentBindPoseWorldPos = computeBindPoseWorldPosition(bone.parentIndex);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
parentBindPoseWorldPos = new Vec3(0, 0, 0);
|
|
369
|
+
}
|
|
370
|
+
// Transform target world position to parent's local space
|
|
371
|
+
// In bind pose, parent's world matrix is just a translation
|
|
372
|
+
const parentSpacePos = targetWorldPos.subtract(parentBindPoseWorldPos);
|
|
373
|
+
// Subtract bindTranslation to get position after bind translation
|
|
374
|
+
const afterBindTranslation = parentSpacePos.subtract(new Vec3(bone.bindTranslation[0], bone.bindTranslation[1], bone.bindTranslation[2]));
|
|
375
|
+
// Apply inverse rotation to get local translation
|
|
376
|
+
const localRotation = new Quat(localRot[qi], localRot[qi + 1], localRot[qi + 2], localRot[qi + 3]);
|
|
377
|
+
const invRotation = localRotation.conjugate().normalize();
|
|
378
|
+
const rotationMat = Mat4.fromQuat(invRotation.x, invRotation.y, invRotation.z, invRotation.w);
|
|
379
|
+
const rm = rotationMat.values;
|
|
380
|
+
const localTranslation = new Vec3(rm[0] * afterBindTranslation.x + rm[4] * afterBindTranslation.y + rm[8] * afterBindTranslation.z, rm[1] * afterBindTranslation.x + rm[5] * afterBindTranslation.y + rm[9] * afterBindTranslation.z, rm[2] * afterBindTranslation.x + rm[6] * afterBindTranslation.y + rm[10] * afterBindTranslation.z);
|
|
381
|
+
const [tx, ty, tz] = [localTranslation.x, localTranslation.y, localTranslation.z];
|
|
382
|
+
if (dur === 0) {
|
|
383
|
+
translations[ti] = tx;
|
|
384
|
+
translations[ti + 1] = ty;
|
|
385
|
+
translations[ti + 2] = tz;
|
|
386
|
+
state.active[idx] = 0;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
let sx = translations[ti];
|
|
390
|
+
let sy = translations[ti + 1];
|
|
391
|
+
let sz = translations[ti + 2];
|
|
392
|
+
if (state.active[idx] === 1) {
|
|
393
|
+
const startMs = state.startTimeMs[idx];
|
|
394
|
+
const prevDur = Math.max(1, state.durationMs[idx]);
|
|
395
|
+
const t = Math.max(0, Math.min(1, (now - startMs) / prevDur));
|
|
396
|
+
const e = easeInOut(t);
|
|
397
|
+
sx = state.startVec[ti] + (state.targetVec[ti] - state.startVec[ti]) * e;
|
|
398
|
+
sy = state.startVec[ti + 1] + (state.targetVec[ti + 1] - state.startVec[ti + 1]) * e;
|
|
399
|
+
sz = state.startVec[ti + 2] + (state.targetVec[ti + 2] - state.startVec[ti + 2]) * e;
|
|
400
|
+
}
|
|
401
|
+
state.startVec[ti] = sx;
|
|
402
|
+
state.startVec[ti + 1] = sy;
|
|
403
|
+
state.startVec[ti + 2] = sz;
|
|
404
|
+
state.targetVec[ti] = tx;
|
|
405
|
+
state.targetVec[ti + 1] = ty;
|
|
406
|
+
state.targetVec[ti + 2] = tz;
|
|
407
|
+
state.startTimeMs[idx] = now;
|
|
408
|
+
state.durationMs[idx] = dur;
|
|
409
|
+
state.active[idx] = 1;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
179
412
|
getBoneWorldMatrices() {
|
|
180
413
|
return this.runtimeSkeleton.worldMatrices;
|
|
181
414
|
}
|
|
182
415
|
getBoneInverseBindMatrices() {
|
|
183
416
|
return this.skeleton.inverseBindMatrices;
|
|
184
417
|
}
|
|
418
|
+
getMorphNames() {
|
|
419
|
+
return this.morphing.morphs.map((m) => m.name);
|
|
420
|
+
}
|
|
421
|
+
setMorphWeight(name, weight, durationMs) {
|
|
422
|
+
const idx = this.runtimeMorph.nameIndex[name] ?? -1;
|
|
423
|
+
if (idx < 0 || idx >= this.runtimeMorph.weights.length)
|
|
424
|
+
return;
|
|
425
|
+
const clampedWeight = Math.max(0, Math.min(1, weight));
|
|
426
|
+
const dur = durationMs && durationMs > 0 ? durationMs : 0;
|
|
427
|
+
if (dur === 0) {
|
|
428
|
+
// Instant change
|
|
429
|
+
this.runtimeMorph.weights[idx] = clampedWeight;
|
|
430
|
+
this.morphTweenState.active[idx] = 0;
|
|
431
|
+
this.applyMorphs();
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
// Animated change
|
|
435
|
+
const state = this.morphTweenState;
|
|
436
|
+
const now = performance.now();
|
|
437
|
+
const currentWeight = this.runtimeMorph.weights[idx];
|
|
438
|
+
// If already tweening, start from current interpolated value
|
|
439
|
+
let startWeight = currentWeight;
|
|
440
|
+
if (state.active[idx] === 1) {
|
|
441
|
+
const startMs = state.startTimeMs[idx];
|
|
442
|
+
const prevDur = Math.max(1, state.durationMs[idx]);
|
|
443
|
+
const t = Math.max(0, Math.min(1, (now - startMs) / prevDur));
|
|
444
|
+
const e = easeInOut(t);
|
|
445
|
+
startWeight = state.startWeight[idx] + (state.targetWeight[idx] - state.startWeight[idx]) * e;
|
|
446
|
+
}
|
|
447
|
+
state.startWeight[idx] = startWeight;
|
|
448
|
+
state.targetWeight[idx] = clampedWeight;
|
|
449
|
+
state.startTimeMs[idx] = now;
|
|
450
|
+
state.durationMs[idx] = dur;
|
|
451
|
+
state.active[idx] = 1;
|
|
452
|
+
// Immediately apply morphs with current weight
|
|
453
|
+
this.runtimeMorph.weights[idx] = startWeight;
|
|
454
|
+
this.applyMorphs();
|
|
455
|
+
}
|
|
456
|
+
applyMorphs() {
|
|
457
|
+
// Reset vertex data to base positions
|
|
458
|
+
this.vertexData.set(this.baseVertexData);
|
|
459
|
+
const vertexCount = this.vertexCount;
|
|
460
|
+
const morphCount = this.morphing.morphs.length;
|
|
461
|
+
const weights = this.runtimeMorph.weights;
|
|
462
|
+
// First pass: Compute effective weights for all morphs (handling group morphs)
|
|
463
|
+
const effectiveWeights = new Float32Array(morphCount);
|
|
464
|
+
effectiveWeights.set(weights); // Start with direct weights
|
|
465
|
+
// Apply group morphs: group morph weight * ratio affects referenced morphs
|
|
466
|
+
for (let morphIdx = 0; morphIdx < morphCount; morphIdx++) {
|
|
467
|
+
const morph = this.morphing.morphs[morphIdx];
|
|
468
|
+
if (morph.type === 0 && morph.groupReferences) {
|
|
469
|
+
const groupWeight = weights[morphIdx];
|
|
470
|
+
if (groupWeight > 0.0001) {
|
|
471
|
+
for (const ref of morph.groupReferences) {
|
|
472
|
+
if (ref.morphIndex >= 0 && ref.morphIndex < morphCount) {
|
|
473
|
+
// Add group morph's contribution to the referenced morph
|
|
474
|
+
effectiveWeights[ref.morphIndex] += groupWeight * ref.ratio;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// Clamp effective weights to [0, 1]
|
|
481
|
+
for (let i = 0; i < morphCount; i++) {
|
|
482
|
+
effectiveWeights[i] = Math.max(0, Math.min(1, effectiveWeights[i]));
|
|
483
|
+
}
|
|
484
|
+
// Second pass: Apply vertex morphs with their effective weights
|
|
485
|
+
for (let morphIdx = 0; morphIdx < morphCount; morphIdx++) {
|
|
486
|
+
const effectiveWeight = effectiveWeights[morphIdx];
|
|
487
|
+
if (effectiveWeight === 0 || effectiveWeight < 0.0001)
|
|
488
|
+
continue;
|
|
489
|
+
const morph = this.morphing.morphs[morphIdx];
|
|
490
|
+
if (morph.type !== 1)
|
|
491
|
+
continue; // Only process vertex morphs
|
|
492
|
+
// For vertex morphs, iterate through vertices that have offsets
|
|
493
|
+
for (const vertexOffset of morph.vertexOffsets) {
|
|
494
|
+
const vIdx = vertexOffset.vertexIndex;
|
|
495
|
+
if (vIdx < 0 || vIdx >= vertexCount)
|
|
496
|
+
continue;
|
|
497
|
+
// Get morph offset for this vertex
|
|
498
|
+
const offsetX = vertexOffset.positionOffset[0];
|
|
499
|
+
const offsetY = vertexOffset.positionOffset[1];
|
|
500
|
+
const offsetZ = vertexOffset.positionOffset[2];
|
|
501
|
+
// Skip if offset is zero
|
|
502
|
+
if (Math.abs(offsetX) < 0.0001 && Math.abs(offsetY) < 0.0001 && Math.abs(offsetZ) < 0.0001) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
// Apply weighted offset to vertex position (positions are at stride 0, 8, 16, ...)
|
|
506
|
+
const vertexIdx = vIdx * VERTEX_STRIDE;
|
|
507
|
+
this.vertexData[vertexIdx] += offsetX * effectiveWeight;
|
|
508
|
+
this.vertexData[vertexIdx + 1] += offsetY * effectiveWeight;
|
|
509
|
+
this.vertexData[vertexIdx + 2] += offsetZ * effectiveWeight;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
185
513
|
evaluatePose() {
|
|
186
514
|
this.updateRotationTweens();
|
|
515
|
+
this.updateTranslationTweens();
|
|
516
|
+
const hasActiveMorphTweens = this.updateMorphWeightTweens();
|
|
517
|
+
if (hasActiveMorphTweens) {
|
|
518
|
+
this.applyMorphs();
|
|
519
|
+
}
|
|
520
|
+
// Compute initial world matrices (needed for IK solving)
|
|
521
|
+
this.computeWorldMatrices();
|
|
522
|
+
// Solve IK chains (modifies localRotations)
|
|
523
|
+
this.solveIKChains();
|
|
524
|
+
// Recompute world matrices with IK rotations applied
|
|
187
525
|
this.computeWorldMatrices();
|
|
526
|
+
return hasActiveMorphTweens;
|
|
527
|
+
}
|
|
528
|
+
solveIKChains() {
|
|
529
|
+
const ikSolvers = this.runtimeSkeleton.ikSolvers;
|
|
530
|
+
if (!ikSolvers || ikSolvers.length === 0)
|
|
531
|
+
return;
|
|
532
|
+
const ikChainInfo = this.runtimeSkeleton.ikChainInfo;
|
|
533
|
+
if (!ikChainInfo)
|
|
534
|
+
return;
|
|
535
|
+
IKSolverSystem.solve(ikSolvers, this.skeleton.bones, this.runtimeSkeleton.localRotations, this.runtimeSkeleton.localTranslations, this.runtimeSkeleton.worldMatrices, ikChainInfo, false // usePhysics - can be enhanced later
|
|
536
|
+
);
|
|
188
537
|
}
|
|
189
538
|
computeWorldMatrices() {
|
|
190
539
|
const bones = this.skeleton.bones;
|
|
@@ -242,11 +591,15 @@ export class Model {
|
|
|
242
591
|
}
|
|
243
592
|
}
|
|
244
593
|
}
|
|
245
|
-
// Build local matrix: identity + bind translation, then rotation, then append translation
|
|
594
|
+
// Build local matrix: identity + bind translation, then rotation, then local translation, then append translation
|
|
595
|
+
const ti = i * 3;
|
|
596
|
+
const localTx = localTrans[ti] + addLocalTx;
|
|
597
|
+
const localTy = localTrans[ti + 1] + addLocalTy;
|
|
598
|
+
const localTz = localTrans[ti + 2] + addLocalTz;
|
|
246
599
|
this.cachedIdentityMat1
|
|
247
600
|
.setIdentity()
|
|
248
601
|
.translateInPlace(b.bindTranslation[0], b.bindTranslation[1], b.bindTranslation[2]);
|
|
249
|
-
this.cachedIdentityMat2.setIdentity().translateInPlace(
|
|
602
|
+
this.cachedIdentityMat2.setIdentity().translateInPlace(localTx, localTy, localTz);
|
|
250
603
|
const localM = this.cachedIdentityMat1.multiply(rotateM).multiply(this.cachedIdentityMat2);
|
|
251
604
|
const worldOffset = i * 16;
|
|
252
605
|
if (b.parentIndex >= 0) {
|
package/dist/pmx-loader.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare class PmxLoader {
|
|
|
17
17
|
private inverseBindMatrices;
|
|
18
18
|
private joints0;
|
|
19
19
|
private weights0;
|
|
20
|
+
private morphs;
|
|
21
|
+
private vertexCount;
|
|
20
22
|
private rigidbodies;
|
|
21
23
|
private joints;
|
|
22
24
|
private constructor();
|
|
@@ -28,7 +30,7 @@ export declare class PmxLoader {
|
|
|
28
30
|
private parseTextures;
|
|
29
31
|
private parseMaterials;
|
|
30
32
|
private parseBones;
|
|
31
|
-
private
|
|
33
|
+
private parseMorphs;
|
|
32
34
|
private skipDisplayFrames;
|
|
33
35
|
private parseRigidbodies;
|
|
34
36
|
private parseJoints;
|
package/dist/pmx-loader.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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"}
|