reze-engine 0.15.1 → 0.16.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/dist/camera.d.ts +2 -0
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +8 -3
- package/dist/engine.d.ts +19 -4
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +294 -27
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/ik-solver.js +13 -7
- package/dist/math.d.ts +2 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +65 -46
- package/dist/model.d.ts +28 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +259 -75
- package/dist/physics/physics.d.ts +3 -0
- package/dist/physics/physics.d.ts.map +1 -1
- package/dist/physics/physics.js +55 -4
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +2 -30
- package/dist/shaders/materials/body.d.ts +1 -1
- package/dist/shaders/materials/body.d.ts.map +1 -1
- package/dist/shaders/materials/body.js +3 -1
- package/dist/shaders/materials/cloth_rough.d.ts +1 -1
- package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_rough.js +3 -1
- package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
- package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
- package/dist/shaders/materials/common.d.ts +2 -2
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +8 -0
- package/dist/shaders/materials/default.d.ts +1 -1
- package/dist/shaders/materials/default.d.ts.map +1 -1
- package/dist/shaders/materials/eye.d.ts +1 -1
- package/dist/shaders/materials/eye.d.ts.map +1 -1
- package/dist/shaders/materials/face.d.ts +1 -1
- package/dist/shaders/materials/face.d.ts.map +1 -1
- package/dist/shaders/materials/face.js +4 -1
- package/dist/shaders/materials/hair.d.ts +1 -1
- package/dist/shaders/materials/hair.d.ts.map +1 -1
- package/dist/shaders/materials/metal.d.ts +1 -1
- package/dist/shaders/materials/metal.d.ts.map +1 -1
- package/dist/shaders/materials/stockings.d.ts +1 -1
- package/dist/shaders/materials/stockings.d.ts.map +1 -1
- package/dist/shaders/materials/stockings.js +13 -9
- package/dist/shaders/passes/composite.d.ts +1 -1
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +18 -12
- package/dist/shaders/passes/morph.d.ts +2 -0
- package/dist/shaders/passes/morph.d.ts.map +1 -0
- package/dist/shaders/passes/morph.js +49 -0
- package/package.json +1 -1
- package/src/camera.ts +9 -3
- package/src/engine.ts +336 -29
- package/src/ik-solver.ts +14 -7
- package/src/math.ts +41 -51
- package/src/model.ts +288 -91
- package/src/physics/physics.ts +56 -13
- package/src/pmx-loader.ts +2 -35
- package/src/shaders/materials/body.ts +3 -1
- package/src/shaders/materials/cloth_rough.ts +3 -1
- package/src/shaders/materials/common.ts +8 -0
- package/src/shaders/materials/face.ts +4 -1
- package/src/shaders/materials/stockings.ts +13 -9
- package/src/shaders/passes/composite.ts +18 -12
- package/src/shaders/passes/morph.ts +50 -0
package/dist/model.js
CHANGED
|
@@ -6,6 +6,13 @@ import { VMDLoader } from "./vmd-loader";
|
|
|
6
6
|
import { VMDWriter } from "./vmd-writer";
|
|
7
7
|
import { AnimationState, interpolateControlPoints, rawInterpolationToBoneInterpolation, } from "./animation";
|
|
8
8
|
const VERTEX_STRIDE = 8;
|
|
9
|
+
// Animation-sampling scratch (applyPoseFromClip → convertVMDTranslationToLocal). These
|
|
10
|
+
// run sequentially and are not reentrant with the world-matrix scratch in math.ts, so
|
|
11
|
+
// plain module singletons are safe and let the per-bone sample path allocate nothing.
|
|
12
|
+
const _animSlerp = new Quat(0, 0, 0, 1);
|
|
13
|
+
const _animInterpT = new Vec3(0, 0, 0);
|
|
14
|
+
const _convOut = new Vec3(0, 0, 0);
|
|
15
|
+
const _convMat = new Float32Array(16);
|
|
9
16
|
export class Model {
|
|
10
17
|
get name() {
|
|
11
18
|
return this._name;
|
|
@@ -37,6 +44,15 @@ export class Model {
|
|
|
37
44
|
}
|
|
38
45
|
constructor(vertexData, indexData, textures, materials, skeleton, skinning, morphing, rigidbodies = [], joints = []) {
|
|
39
46
|
this._name = "";
|
|
47
|
+
this.morphPrevMinVert = -1; // vertices touched by last applyMorphs (reset to base this pass)
|
|
48
|
+
this.morphPrevMaxVert = -1;
|
|
49
|
+
this.morphPendingMinVert = -1; // accumulated range awaiting a GPU upload
|
|
50
|
+
this.morphPendingMaxVert = -1;
|
|
51
|
+
this.morphUploadFull = true; // first upload after load pushes the whole buffer once
|
|
52
|
+
// GPU morph path: when enabled (engine set up the compute buffers), applyMorphs only
|
|
53
|
+
// resolves effective weights and flags them dirty — the compute pass does the vertex work.
|
|
54
|
+
this.gpuMorphEnabled = false;
|
|
55
|
+
this.morphWeightsDirty = false;
|
|
40
56
|
this.textures = [];
|
|
41
57
|
this.materials = [];
|
|
42
58
|
// Physics data from PMX
|
|
@@ -141,6 +157,58 @@ export class Model {
|
|
|
141
157
|
}
|
|
142
158
|
this.runtimeSkeleton.ikChainInfo = ikChainInfo;
|
|
143
159
|
this.runtimeSkeleton.ikSolvers = ikSolvers;
|
|
160
|
+
this.buildDeformOrder();
|
|
161
|
+
}
|
|
162
|
+
// Precompute the bone order that computeWorldMatrices iterates every frame: every
|
|
163
|
+
// bone appears after its parent. This is the exact finishing order the previous
|
|
164
|
+
// recursive computeWorld() produced (walk up the not-yet-emitted ancestor chain,
|
|
165
|
+
// then emit it top-down; ties broken by ascending index) — so behavior is identical,
|
|
166
|
+
// minus the per-frame closure + visited-array allocation and the recursion overhead.
|
|
167
|
+
buildDeformOrder() {
|
|
168
|
+
const bones = this.skeleton.bones;
|
|
169
|
+
const n = bones.length;
|
|
170
|
+
const order = new Int32Array(n);
|
|
171
|
+
const done = new Uint8Array(n);
|
|
172
|
+
const stack = [];
|
|
173
|
+
let k = 0;
|
|
174
|
+
for (let i = 0; i < n; i++) {
|
|
175
|
+
if (done[i])
|
|
176
|
+
continue;
|
|
177
|
+
stack.length = 0;
|
|
178
|
+
let cur = i;
|
|
179
|
+
// Collect the chain of not-yet-emitted ancestors up to the root (or a done one).
|
|
180
|
+
while (cur >= 0 && cur < n && !done[cur]) {
|
|
181
|
+
stack.push(cur);
|
|
182
|
+
cur = bones[cur].parentIndex;
|
|
183
|
+
}
|
|
184
|
+
// Emit from the topmost ancestor down so parents precede children.
|
|
185
|
+
for (let s = stack.length - 1; s >= 0; s--) {
|
|
186
|
+
const b = stack[s];
|
|
187
|
+
done[b] = 1;
|
|
188
|
+
order[k++] = b;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
this.deformOrder = order;
|
|
192
|
+
// Accumulate bind-pose world positions in the same parent-before-child order and
|
|
193
|
+
// with the same add order as the old recursive computeBindPoseWorldPosition, so the
|
|
194
|
+
// downstream arithmetic stays bit-identical.
|
|
195
|
+
const bindWorld = new Float32Array(n * 3);
|
|
196
|
+
for (let idx = 0; idx < n; idx++) {
|
|
197
|
+
const i = order[idx];
|
|
198
|
+
const bt = bones[i].bindTranslation;
|
|
199
|
+
const p = bones[i].parentIndex;
|
|
200
|
+
if (p >= 0 && p < n) {
|
|
201
|
+
bindWorld[i * 3 + 0] = bindWorld[p * 3 + 0] + bt[0];
|
|
202
|
+
bindWorld[i * 3 + 1] = bindWorld[p * 3 + 1] + bt[1];
|
|
203
|
+
bindWorld[i * 3 + 2] = bindWorld[p * 3 + 2] + bt[2];
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
bindWorld[i * 3 + 0] = bt[0];
|
|
207
|
+
bindWorld[i * 3 + 1] = bt[1];
|
|
208
|
+
bindWorld[i * 3 + 2] = bt[2];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
this.bindWorldPos = bindWorld;
|
|
144
212
|
}
|
|
145
213
|
initializeTweenBuffers() {
|
|
146
214
|
const boneCount = this.skeleton.bones.length;
|
|
@@ -411,51 +479,50 @@ export class Model {
|
|
|
411
479
|
}
|
|
412
480
|
}
|
|
413
481
|
// VMD translation (world delta from bind pose) → bone local space; optional rotation for animation vs IK
|
|
482
|
+
// Returns a REUSED scratch Vec3 (_convOut) — callers must copy immediately (they do:
|
|
483
|
+
// .set() / destructure). Zero allocation; result is bit-identical to the previous
|
|
484
|
+
// recursive implementation (verified numerically).
|
|
414
485
|
convertVMDTranslationToLocal(boneIdx, vmdRelativeTranslation, rotation) {
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
//
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
let
|
|
438
|
-
if (
|
|
439
|
-
|
|
486
|
+
const bone = this.skeleton.bones[boneIdx];
|
|
487
|
+
const bindWorld = this.bindWorldPos;
|
|
488
|
+
const bt = bone.bindTranslation;
|
|
489
|
+
const p = bone.parentIndex;
|
|
490
|
+
// afterBindTranslation = (bindWorld[bone] + vmd − bindWorld[parent]) − bindTranslation.
|
|
491
|
+
// (Algebraically this reduces to vmd, since bindWorld[bone] = bindWorld[parent] +
|
|
492
|
+
// bindTranslation, but the explicit form keeps the result bit-identical to before.)
|
|
493
|
+
const bi3 = boneIdx * 3;
|
|
494
|
+
const targetX = bindWorld[bi3 + 0] + vmdRelativeTranslation.x;
|
|
495
|
+
const targetY = bindWorld[bi3 + 1] + vmdRelativeTranslation.y;
|
|
496
|
+
const targetZ = bindWorld[bi3 + 2] + vmdRelativeTranslation.z;
|
|
497
|
+
const px = p >= 0 ? bindWorld[p * 3 + 0] : 0;
|
|
498
|
+
const py = p >= 0 ? bindWorld[p * 3 + 1] : 0;
|
|
499
|
+
const pz = p >= 0 ? bindWorld[p * 3 + 2] : 0;
|
|
500
|
+
const abx = targetX - px - bt[0];
|
|
501
|
+
const aby = targetY - py - bt[1];
|
|
502
|
+
const abz = targetZ - pz - bt[2];
|
|
503
|
+
// Inverse rotation = conjugate + normalize (matches localRotation.clone().conjugate()
|
|
504
|
+
// .normalize()), applied via a rotation matrix (matches Mat4.fromQuat). Uses animation
|
|
505
|
+
// rotation when provided so IK-modified localRot doesn't perturb the conversion.
|
|
506
|
+
const q = rotation ?? this.runtimeSkeleton.localRotations[boneIdx];
|
|
507
|
+
const qlen = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
|
|
508
|
+
let ix, iy, iz, iw;
|
|
509
|
+
if (qlen === 0) {
|
|
510
|
+
ix = 0;
|
|
511
|
+
iy = 0;
|
|
512
|
+
iz = 0;
|
|
513
|
+
iw = 1;
|
|
440
514
|
}
|
|
441
515
|
else {
|
|
442
|
-
|
|
516
|
+
const inv = 1 / qlen;
|
|
517
|
+
ix = -q.x * inv;
|
|
518
|
+
iy = -q.y * inv;
|
|
519
|
+
iz = -q.z * inv;
|
|
520
|
+
iw = q.w * inv;
|
|
443
521
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const afterBindTranslation = parentSpacePos.subtract(new Vec3(bone.bindTranslation[0], bone.bindTranslation[1], bone.bindTranslation[2]));
|
|
449
|
-
// Apply inverse rotation to get local translation
|
|
450
|
-
// Use provided rotation (animation rotation) or fall back to current localRotation
|
|
451
|
-
// Using animation rotation prevents conflicts when IK modifies the rotation
|
|
452
|
-
const localRotation = rotation ?? localRot[boneIdx];
|
|
453
|
-
// Clone to avoid mutating, then conjugate and normalize
|
|
454
|
-
const invRotation = localRotation.clone().conjugate().normalize();
|
|
455
|
-
const rotationMat = Mat4.fromQuat(invRotation.x, invRotation.y, invRotation.z, invRotation.w);
|
|
456
|
-
const rm = rotationMat.values;
|
|
457
|
-
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);
|
|
458
|
-
return localTranslation;
|
|
522
|
+
Mat4.fromQuatInto(ix, iy, iz, iw, _convMat, 0);
|
|
523
|
+
const rm = _convMat;
|
|
524
|
+
_convOut.setXYZ(rm[0] * abx + rm[4] * aby + rm[8] * abz, rm[1] * abx + rm[5] * aby + rm[9] * abz, rm[2] * abx + rm[6] * aby + rm[10] * abz);
|
|
525
|
+
return _convOut;
|
|
459
526
|
}
|
|
460
527
|
getWorldMatrices() {
|
|
461
528
|
return this.runtimeSkeleton.worldMatrices;
|
|
@@ -552,13 +619,15 @@ export class Model {
|
|
|
552
619
|
this.applyMorphs();
|
|
553
620
|
}
|
|
554
621
|
applyMorphs() {
|
|
555
|
-
// Reset vertex data to base positions
|
|
556
|
-
this.vertexData.set(this.baseVertexData);
|
|
557
622
|
const vertexCount = this.vertexCount;
|
|
558
623
|
const morphCount = this.morphing.morphs.length;
|
|
559
624
|
const weights = this.runtimeMorph.weights;
|
|
560
|
-
//
|
|
561
|
-
|
|
625
|
+
// Effective weights (group-morph resolution + clamp). Both paths need these: the GPU
|
|
626
|
+
// path uploads them for the compute pass; the CPU path applies them below. Reused buffer.
|
|
627
|
+
if (!this.morphEffectiveWeights || this.morphEffectiveWeights.length !== morphCount) {
|
|
628
|
+
this.morphEffectiveWeights = new Float32Array(morphCount);
|
|
629
|
+
}
|
|
630
|
+
const effectiveWeights = this.morphEffectiveWeights;
|
|
562
631
|
effectiveWeights.set(weights); // Start with direct weights
|
|
563
632
|
// Apply group morphs: group morph weight * ratio affects referenced morphs
|
|
564
633
|
for (let morphIdx = 0; morphIdx < morphCount; morphIdx++) {
|
|
@@ -568,18 +637,30 @@ export class Model {
|
|
|
568
637
|
if (groupWeight > 0.0001) {
|
|
569
638
|
for (const ref of morph.groupReferences) {
|
|
570
639
|
if (ref.morphIndex >= 0 && ref.morphIndex < morphCount) {
|
|
571
|
-
// Add group morph's contribution to the referenced morph
|
|
572
640
|
effectiveWeights[ref.morphIndex] += groupWeight * ref.ratio;
|
|
573
641
|
}
|
|
574
642
|
}
|
|
575
643
|
}
|
|
576
644
|
}
|
|
577
645
|
}
|
|
578
|
-
// Clamp effective weights to [0, 1]
|
|
579
646
|
for (let i = 0; i < morphCount; i++) {
|
|
580
647
|
effectiveWeights[i] = Math.max(0, Math.min(1, effectiveWeights[i]));
|
|
581
648
|
}
|
|
582
|
-
//
|
|
649
|
+
// GPU path: the compute pass applies the vertex offsets from these weights.
|
|
650
|
+
if (this.gpuMorphEnabled) {
|
|
651
|
+
this.morphWeightsDirty = true;
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
// ── CPU path ── Reset only the vertices morphed by the previous pass back to base
|
|
655
|
+
// (targeted reset; vertexData never diverges from base outside that range).
|
|
656
|
+
if (this.morphPrevMaxVert >= 0) {
|
|
657
|
+
const s = this.morphPrevMinVert * VERTEX_STRIDE;
|
|
658
|
+
const e = (this.morphPrevMaxVert + 1) * VERTEX_STRIDE;
|
|
659
|
+
this.vertexData.set(this.baseVertexData.subarray(s, e), s);
|
|
660
|
+
}
|
|
661
|
+
// Apply vertex morphs, tracking the touched vertex-index range for partial upload.
|
|
662
|
+
let curMinVert = -1;
|
|
663
|
+
let curMaxVert = -1;
|
|
583
664
|
for (let morphIdx = 0; morphIdx < morphCount; morphIdx++) {
|
|
584
665
|
const effectiveWeight = effectiveWeights[morphIdx];
|
|
585
666
|
if (effectiveWeight === 0 || effectiveWeight < 0.0001)
|
|
@@ -587,26 +668,136 @@ export class Model {
|
|
|
587
668
|
const morph = this.morphing.morphs[morphIdx];
|
|
588
669
|
if (morph.type !== 1)
|
|
589
670
|
continue; // Only process vertex morphs
|
|
590
|
-
// For vertex morphs, iterate through vertices that have offsets
|
|
591
671
|
for (const vertexOffset of morph.vertexOffsets) {
|
|
592
672
|
const vIdx = vertexOffset.vertexIndex;
|
|
593
673
|
if (vIdx < 0 || vIdx >= vertexCount)
|
|
594
674
|
continue;
|
|
595
|
-
// Get morph offset for this vertex
|
|
596
675
|
const offsetX = vertexOffset.positionOffset[0];
|
|
597
676
|
const offsetY = vertexOffset.positionOffset[1];
|
|
598
677
|
const offsetZ = vertexOffset.positionOffset[2];
|
|
599
|
-
// Skip if offset is zero
|
|
600
678
|
if (Math.abs(offsetX) < 0.0001 && Math.abs(offsetY) < 0.0001 && Math.abs(offsetZ) < 0.0001) {
|
|
601
679
|
continue;
|
|
602
680
|
}
|
|
603
|
-
// Apply weighted offset to vertex position (positions are at stride 0, 8, 16, ...)
|
|
604
681
|
const vertexIdx = vIdx * VERTEX_STRIDE;
|
|
605
682
|
this.vertexData[vertexIdx] += offsetX * effectiveWeight;
|
|
606
683
|
this.vertexData[vertexIdx + 1] += offsetY * effectiveWeight;
|
|
607
684
|
this.vertexData[vertexIdx + 2] += offsetZ * effectiveWeight;
|
|
685
|
+
if (curMinVert < 0 || vIdx < curMinVert)
|
|
686
|
+
curMinVert = vIdx;
|
|
687
|
+
if (vIdx > curMaxVert)
|
|
688
|
+
curMaxVert = vIdx;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
let dirtyMin = curMinVert;
|
|
692
|
+
let dirtyMax = curMaxVert;
|
|
693
|
+
if (this.morphPrevMaxVert >= 0) {
|
|
694
|
+
if (dirtyMin < 0 || this.morphPrevMinVert < dirtyMin)
|
|
695
|
+
dirtyMin = this.morphPrevMinVert;
|
|
696
|
+
if (this.morphPrevMaxVert > dirtyMax)
|
|
697
|
+
dirtyMax = this.morphPrevMaxVert;
|
|
698
|
+
}
|
|
699
|
+
if (dirtyMin >= 0 && dirtyMax >= 0) {
|
|
700
|
+
this.morphPendingMinVert =
|
|
701
|
+
this.morphPendingMinVert < 0 ? dirtyMin : Math.min(this.morphPendingMinVert, dirtyMin);
|
|
702
|
+
this.morphPendingMaxVert =
|
|
703
|
+
this.morphPendingMaxVert < 0 ? dirtyMax : Math.max(this.morphPendingMaxVert, dirtyMax);
|
|
704
|
+
}
|
|
705
|
+
this.morphPrevMinVert = curMinVert;
|
|
706
|
+
this.morphPrevMaxVert = curMaxVert;
|
|
707
|
+
}
|
|
708
|
+
// ── GPU morph path support ──
|
|
709
|
+
// Called by the engine once it has created the compute buffers for this model; switches
|
|
710
|
+
// applyMorphs to the weights-only branch.
|
|
711
|
+
enableGpuMorphs() {
|
|
712
|
+
this.gpuMorphEnabled = true;
|
|
713
|
+
}
|
|
714
|
+
// True (once) when morph weights changed since the last check — the engine then uploads
|
|
715
|
+
// getEffectiveMorphWeights() and dispatches the compute pass.
|
|
716
|
+
consumeMorphWeightsDirty() {
|
|
717
|
+
const d = this.morphWeightsDirty;
|
|
718
|
+
this.morphWeightsDirty = false;
|
|
719
|
+
return d;
|
|
720
|
+
}
|
|
721
|
+
// Effective (group-resolved, clamped) morph weights for GPU upload. Ensures they're
|
|
722
|
+
// computed at least once even before the first weight change.
|
|
723
|
+
getEffectiveMorphWeights() {
|
|
724
|
+
if (!this.morphEffectiveWeights)
|
|
725
|
+
this.applyMorphs();
|
|
726
|
+
return this.morphEffectiveWeights ?? new Float32Array(0);
|
|
727
|
+
}
|
|
728
|
+
// Build the CSR inversion of vertex-morph offsets for the GPU compute pass. Returns null
|
|
729
|
+
// when the model has no vertex-morph offsets (no GPU path needed). Entries for each vertex
|
|
730
|
+
// are emitted in ascending morph-index order, matching the CPU accumulation order.
|
|
731
|
+
buildMorphComputeData() {
|
|
732
|
+
const V = this.vertexCount;
|
|
733
|
+
const M = this.morphing.morphs.length;
|
|
734
|
+
const morphs = this.morphing.morphs;
|
|
735
|
+
const EPS = 0.0001;
|
|
736
|
+
const isLive = (o) => o.vertexIndex >= 0 &&
|
|
737
|
+
o.vertexIndex < V &&
|
|
738
|
+
(Math.abs(o.positionOffset[0]) >= EPS ||
|
|
739
|
+
Math.abs(o.positionOffset[1]) >= EPS ||
|
|
740
|
+
Math.abs(o.positionOffset[2]) >= EPS);
|
|
741
|
+
const counts = new Uint32Array(V);
|
|
742
|
+
for (let m = 0; m < M; m++) {
|
|
743
|
+
const morph = morphs[m];
|
|
744
|
+
if (morph.type !== 1)
|
|
745
|
+
continue;
|
|
746
|
+
for (const o of morph.vertexOffsets)
|
|
747
|
+
if (isLive(o))
|
|
748
|
+
counts[o.vertexIndex]++;
|
|
749
|
+
}
|
|
750
|
+
const rowStart = new Uint32Array(V + 1);
|
|
751
|
+
let acc = 0;
|
|
752
|
+
for (let v = 0; v < V; v++) {
|
|
753
|
+
rowStart[v] = acc;
|
|
754
|
+
acc += counts[v];
|
|
755
|
+
}
|
|
756
|
+
rowStart[V] = acc;
|
|
757
|
+
const E = acc;
|
|
758
|
+
if (E === 0)
|
|
759
|
+
return null;
|
|
760
|
+
const colMorph = new Uint32Array(E);
|
|
761
|
+
const colOffset = new Float32Array(E * 3);
|
|
762
|
+
const fill = new Uint32Array(V);
|
|
763
|
+
for (let m = 0; m < M; m++) {
|
|
764
|
+
const morph = morphs[m];
|
|
765
|
+
if (morph.type !== 1)
|
|
766
|
+
continue;
|
|
767
|
+
for (const o of morph.vertexOffsets) {
|
|
768
|
+
if (!isLive(o))
|
|
769
|
+
continue;
|
|
770
|
+
const v = o.vertexIndex;
|
|
771
|
+
const p = rowStart[v] + fill[v]++;
|
|
772
|
+
colMorph[p] = m;
|
|
773
|
+
colOffset[p * 3] = o.positionOffset[0];
|
|
774
|
+
colOffset[p * 3 + 1] = o.positionOffset[1];
|
|
775
|
+
colOffset[p * 3 + 2] = o.positionOffset[2];
|
|
608
776
|
}
|
|
609
777
|
}
|
|
778
|
+
const basePositions = new Float32Array(V * 3);
|
|
779
|
+
for (let v = 0; v < V; v++) {
|
|
780
|
+
const vi = v * VERTEX_STRIDE;
|
|
781
|
+
basePositions[v * 3] = this.baseVertexData[vi];
|
|
782
|
+
basePositions[v * 3 + 1] = this.baseVertexData[vi + 1];
|
|
783
|
+
basePositions[v * 3 + 2] = this.baseVertexData[vi + 2];
|
|
784
|
+
}
|
|
785
|
+
return { basePositions, rowStart, colMorph, colOffset, morphCount: M, vertexCount: V, entryCount: E };
|
|
786
|
+
}
|
|
787
|
+
// Consume the pending morph vertex-upload range for the engine. Returns null when a
|
|
788
|
+
// full upload is needed (first time after load, or nothing tracked), else the inclusive
|
|
789
|
+
// [minVert, maxVert] slice that changed. Resets pending state.
|
|
790
|
+
consumeVertexUploadRange() {
|
|
791
|
+
if (this.morphUploadFull || this.morphPendingMaxVert < 0) {
|
|
792
|
+
this.morphUploadFull = false;
|
|
793
|
+
this.morphPendingMinVert = -1;
|
|
794
|
+
this.morphPendingMaxVert = -1;
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
const range = { minVert: this.morphPendingMinVert, maxVert: this.morphPendingMaxVert };
|
|
798
|
+
this.morphPendingMinVert = -1;
|
|
799
|
+
this.morphPendingMaxVert = -1;
|
|
800
|
+
return range;
|
|
610
801
|
}
|
|
611
802
|
buildClipFromVmdKeyFrames(vmdKeyFrames) {
|
|
612
803
|
const boneTracksByBone = {};
|
|
@@ -835,11 +1026,11 @@ export class Model {
|
|
|
835
1026
|
const gradient = frameDelta > 0 ? (clampedFrame - frameA.frame) / frameDelta : 0;
|
|
836
1027
|
const interp = frameB.interpolation;
|
|
837
1028
|
const rotT = interpolateControlPoints(interp.rotation, gradient);
|
|
838
|
-
const rotation = Quat.
|
|
1029
|
+
const rotation = Quat.slerpInto(frameA.rotation, frameB.rotation, rotT, _animSlerp);
|
|
839
1030
|
const txWeight = interpolateControlPoints(interp.translationX, gradient);
|
|
840
1031
|
const tyWeight = interpolateControlPoints(interp.translationY, gradient);
|
|
841
1032
|
const tzWeight = interpolateControlPoints(interp.translationZ, gradient);
|
|
842
|
-
const interpolatedVMDTranslation =
|
|
1033
|
+
const interpolatedVMDTranslation = _animInterpT.setXYZ(frameA.translation.x + (frameB.translation.x - frameA.translation.x) * txWeight, frameA.translation.y + (frameB.translation.y - frameA.translation.y) * tyWeight, frameA.translation.z + (frameB.translation.z - frameA.translation.z) * tzWeight);
|
|
843
1034
|
const localTranslation = this.convertVMDTranslationToLocal(boneIdx, interpolatedVMDTranslation, rotation);
|
|
844
1035
|
localRot.set(rotation);
|
|
845
1036
|
localTrans.set(localTranslation);
|
|
@@ -906,10 +1097,14 @@ export class Model {
|
|
|
906
1097
|
if (!ikChainInfo)
|
|
907
1098
|
return;
|
|
908
1099
|
// Solve each IK solver sequentially, ensuring consistent state between solvers
|
|
1100
|
+
let firstSolver = true;
|
|
909
1101
|
for (const solver of ikSolvers) {
|
|
910
|
-
//
|
|
911
|
-
//
|
|
912
|
-
|
|
1102
|
+
// Each solver must see the effects of previous solvers on localRotations, so
|
|
1103
|
+
// recompute world matrices between solvers. The first solver is skipped: the
|
|
1104
|
+
// caller (update) already computed them and nothing has changed localRotations yet.
|
|
1105
|
+
if (!firstSolver)
|
|
1106
|
+
this.computeWorldMatrices();
|
|
1107
|
+
firstSolver = false;
|
|
913
1108
|
// Clear computed set for this solver's pass
|
|
914
1109
|
this.ikComputedSet.clear();
|
|
915
1110
|
// Solve this IK chain
|
|
@@ -1033,20 +1228,13 @@ export class Model {
|
|
|
1033
1228
|
const boneCount = bones.length;
|
|
1034
1229
|
if (boneCount === 0)
|
|
1035
1230
|
return;
|
|
1036
|
-
//
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1231
|
+
// Flat traversal in precomputed order: every bone's parent is already done, so no
|
|
1232
|
+
// per-bone visited check, no recursion, and no per-call allocation. Same per-bone
|
|
1233
|
+
// math as before. Scratch slots are safe to reuse since there's no reentrancy now.
|
|
1234
|
+
const order = this.deformOrder;
|
|
1235
|
+
for (let k = 0; k < boneCount; k++) {
|
|
1236
|
+
const i = order[k];
|
|
1041
1237
|
const b = bones[i];
|
|
1042
|
-
if (b.parentIndex >= boneCount) {
|
|
1043
|
-
console.warn(`[RZM] bone ${i} parent out of range: ${b.parentIndex}`);
|
|
1044
|
-
}
|
|
1045
|
-
// Ensure parent is computed FIRST, before we touch any scratch buffers.
|
|
1046
|
-
// Recursion may itself use scratchMat4Values[0] / scratchQuat; doing it up
|
|
1047
|
-
// front keeps the current frame's scratch slots untouched when we use them below.
|
|
1048
|
-
if (b.parentIndex >= 0 && !computed[b.parentIndex])
|
|
1049
|
-
computeWorld(b.parentIndex);
|
|
1050
1238
|
const boneRot = localRot[i];
|
|
1051
1239
|
let fx = boneRot.x, fy = boneRot.y, fz = boneRot.z, fw = boneRot.w;
|
|
1052
1240
|
let addLocalTx = 0, addLocalTy = 0, addLocalTz = 0;
|
|
@@ -1103,10 +1291,6 @@ export class Model {
|
|
|
1103
1291
|
else {
|
|
1104
1292
|
worldMat.values.set(localMVals);
|
|
1105
1293
|
}
|
|
1106
|
-
|
|
1107
|
-
};
|
|
1108
|
-
// Process all bones (recursion handles dependencies automatically)
|
|
1109
|
-
for (let i = 0; i < boneCount; i++)
|
|
1110
|
-
computeWorld(i);
|
|
1294
|
+
}
|
|
1111
1295
|
}
|
|
1112
1296
|
}
|
|
@@ -12,7 +12,10 @@ export declare class RezePhysics {
|
|
|
12
12
|
private timeAccum;
|
|
13
13
|
private readonly fixedTimeStep;
|
|
14
14
|
private readonly maxSubSteps;
|
|
15
|
+
private prevPositions;
|
|
16
|
+
private prevOrientations;
|
|
15
17
|
constructor(rigidbodies: Rigidbody[], joints?: Joint[]);
|
|
18
|
+
private savePrevState;
|
|
16
19
|
setGravity(gravity: Vec3): void;
|
|
17
20
|
getGravity(): Vec3;
|
|
18
21
|
getRigidbodies(): Rigidbody[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../../src/physics/physics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAYvC,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,KAAK,CAAO;IACpB,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAI;
|
|
1
|
+
{"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../../src/physics/physics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAYvC,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,KAAK,CAAO;IACpB,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAI;IAMhC,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,gBAAgB,CAAc;gBAE1B,WAAW,EAAE,SAAS,EAAE,EAAE,MAAM,GAAE,KAAK,EAAO;IAY1D,OAAO,CAAC,aAAa;IAKrB,UAAU,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI;IAG/B,UAAU,IAAI,IAAI;IAGlB,cAAc,IAAI,SAAS,EAAE;IAG7B,SAAS,IAAI,KAAK,EAAE;IAGpB,QAAQ,IAAI,cAAc;IAI1B,sBAAsB,IAAI,KAAK,CAAC;QAAE,QAAQ,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAA;KAAE,CAAC;IAiBnE,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,GAAG,IAAI;IAOtC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,uBAAuB,EAAE,YAAY,GAAG,IAAI;IAkCxF,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,aAAa;IA+ErB,OAAO,CAAC,oBAAoB;CAoD7B"}
|
package/dist/physics/physics.js
CHANGED
|
@@ -22,6 +22,13 @@ export class RezePhysics {
|
|
|
22
22
|
this.world = new World(new Vec3(0, -98, 0));
|
|
23
23
|
this.constraints = buildConstraints(rigidbodies, joints);
|
|
24
24
|
this.contacts = new ContactPool();
|
|
25
|
+
this.prevPositions = new Float32Array(this.store.count * 3);
|
|
26
|
+
this.prevOrientations = new Float32Array(this.store.count * 4);
|
|
27
|
+
}
|
|
28
|
+
// Snapshot the current body pose as the interpolation "previous" state.
|
|
29
|
+
savePrevState() {
|
|
30
|
+
this.prevPositions.set(this.store.positions);
|
|
31
|
+
this.prevOrientations.set(this.store.orientations);
|
|
25
32
|
}
|
|
26
33
|
setGravity(gravity) {
|
|
27
34
|
this.world.setGravity(gravity);
|
|
@@ -58,6 +65,8 @@ export class RezePhysics {
|
|
|
58
65
|
if (this.firstFrame)
|
|
59
66
|
return;
|
|
60
67
|
this.snapBodiesToBones(boneWorldMatrices);
|
|
68
|
+
this.savePrevState(); // prev == curr after a snap, so no interpolation jump
|
|
69
|
+
this.timeAccum = 0;
|
|
61
70
|
}
|
|
62
71
|
step(dt, boneWorldMatrices, boneInverseBindMatrices) {
|
|
63
72
|
if (this.firstFrame) {
|
|
@@ -65,6 +74,7 @@ export class RezePhysics {
|
|
|
65
74
|
// Start at current bone pose, not the PMX bind pose, so animations
|
|
66
75
|
// that skip frame 0 don't pop bodies on first step.
|
|
67
76
|
this.snapBodiesToBones(boneWorldMatrices);
|
|
77
|
+
this.savePrevState();
|
|
68
78
|
this.firstFrame = false;
|
|
69
79
|
}
|
|
70
80
|
// Sync once per render frame; kinematic targets don't change between
|
|
@@ -72,17 +82,21 @@ export class RezePhysics {
|
|
|
72
82
|
// bone-pose delta so joints feel the kinematic motion.
|
|
73
83
|
this.syncFromBones(boneWorldMatrices, dt);
|
|
74
84
|
// Fixed-timestep substeps. The maxSubSteps cap prevents runaway after
|
|
75
|
-
// a long stall (tab backgrounded, etc.).
|
|
85
|
+
// a long stall (tab backgrounded, etc.). Snapshot the pose before each step so
|
|
86
|
+
// after the loop prevState is one substep behind the live (current) state.
|
|
76
87
|
this.timeAccum += dt;
|
|
77
88
|
let sub = 0;
|
|
78
89
|
while (this.timeAccum >= this.fixedTimeStep && sub < this.maxSubSteps) {
|
|
90
|
+
this.savePrevState();
|
|
79
91
|
this.world.step(this.store, this.constraints, this.contacts, this.fixedTimeStep);
|
|
80
92
|
this.timeAccum -= this.fixedTimeStep;
|
|
81
93
|
sub++;
|
|
82
94
|
}
|
|
83
95
|
if (sub === this.maxSubSteps)
|
|
84
96
|
this.timeAccum = 0;
|
|
85
|
-
|
|
97
|
+
// Fraction into the next (not-yet-taken) step; always in [0, 1).
|
|
98
|
+
const alpha = this.fixedTimeStep > 0 ? this.timeAccum / this.fixedTimeStep : 0;
|
|
99
|
+
this.applyDynamicsToBones(boneWorldMatrices, alpha);
|
|
86
100
|
}
|
|
87
101
|
// Snap all bone-bound bodies to boneWorld × bodyOffset, zero velocities.
|
|
88
102
|
snapBodiesToBones(boneWorldMatrices) {
|
|
@@ -183,13 +197,18 @@ export class RezePhysics {
|
|
|
183
197
|
}
|
|
184
198
|
// Dynamic bodies write their transform back to the bone matrix:
|
|
185
199
|
// boneWorld = bodyWorld × bodyOffsetInverse.
|
|
186
|
-
|
|
200
|
+
// The body pose is the render-interpolated pose between the previous and current
|
|
201
|
+
// substep states (alpha = fraction into the next step), which removes fixed-step judder.
|
|
202
|
+
applyDynamicsToBones(boneWorldMatrices, alpha) {
|
|
187
203
|
const N = this.store.count;
|
|
188
204
|
const inv = this.store.bodyOffsetInverse;
|
|
189
205
|
const positions = this.store.positions;
|
|
190
206
|
const orientations = this.store.orientations;
|
|
207
|
+
const prevPos = this.prevPositions;
|
|
208
|
+
const prevOri = this.prevOrientations;
|
|
191
209
|
const types = this.store.type;
|
|
192
210
|
const boneIdx = this.store.boneIndex;
|
|
211
|
+
const oneMinus = 1 - alpha;
|
|
193
212
|
for (let i = 0; i < N; i++) {
|
|
194
213
|
if (types[i] !== RigidbodyType.Dynamic)
|
|
195
214
|
continue;
|
|
@@ -198,7 +217,39 @@ export class RezePhysics {
|
|
|
198
217
|
continue;
|
|
199
218
|
const i3 = i * 3;
|
|
200
219
|
const i4 = i * 4;
|
|
201
|
-
|
|
220
|
+
// Position: straight lerp.
|
|
221
|
+
const px = prevPos[i3 + 0] * oneMinus + positions[i3 + 0] * alpha;
|
|
222
|
+
const py = prevPos[i3 + 1] * oneMinus + positions[i3 + 1] * alpha;
|
|
223
|
+
const pz = prevPos[i3 + 2] * oneMinus + positions[i3 + 2] * alpha;
|
|
224
|
+
// Orientation: shortest-arc nlerp (bodies rotate little per fixed step, so nlerp
|
|
225
|
+
// tracks slerp closely and avoids the trig).
|
|
226
|
+
const ax = prevOri[i4 + 0], ay = prevOri[i4 + 1], az = prevOri[i4 + 2], aw = prevOri[i4 + 3];
|
|
227
|
+
let bx = orientations[i4 + 0], by = orientations[i4 + 1], bz = orientations[i4 + 2], bw = orientations[i4 + 3];
|
|
228
|
+
if (ax * bx + ay * by + az * bz + aw * bw < 0) {
|
|
229
|
+
bx = -bx;
|
|
230
|
+
by = -by;
|
|
231
|
+
bz = -bz;
|
|
232
|
+
bw = -bw;
|
|
233
|
+
}
|
|
234
|
+
let qx = ax * oneMinus + bx * alpha;
|
|
235
|
+
let qy = ay * oneMinus + by * alpha;
|
|
236
|
+
let qz = az * oneMinus + bz * alpha;
|
|
237
|
+
let qw = aw * oneMinus + bw * alpha;
|
|
238
|
+
const ql = Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw);
|
|
239
|
+
if (ql > 0) {
|
|
240
|
+
const invL = 1 / ql;
|
|
241
|
+
qx *= invL;
|
|
242
|
+
qy *= invL;
|
|
243
|
+
qz *= invL;
|
|
244
|
+
qw *= invL;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
qx = 0;
|
|
248
|
+
qy = 0;
|
|
249
|
+
qz = 0;
|
|
250
|
+
qw = 1;
|
|
251
|
+
}
|
|
252
|
+
Mat4.fromPositionRotationInto(px, py, pz, qx, qy, qz, qw, _bodyMat);
|
|
202
253
|
Mat4.multiplyArrays(_bodyMat, 0, inv, i * 16, _boneMat, 0);
|
|
203
254
|
// Sanity gate against NaN / extreme values — silently drop the update.
|
|
204
255
|
if (Number.isFinite(_boneMat[0]) && Math.abs(_boneMat[0]) < 1e6) {
|
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,EACL,KAAK,EAWN,MAAM,SAAS,CAAA;AAGhB,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEzE,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;IAI9C,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK;WAIpC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAKxF,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;IAoEtB,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;
|
|
1
|
+
{"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAWN,MAAM,SAAS,CAAA;AAGhB,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEzE,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;IAI9C,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK;WAIpC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAKxF,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;IAoEtB,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;IAiJf,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"}
|
package/dist/pmx-loader.js
CHANGED
|
@@ -916,38 +916,10 @@ export class PmxLoader {
|
|
|
916
916
|
}
|
|
917
917
|
skinning = { joints, weights };
|
|
918
918
|
}
|
|
919
|
-
//
|
|
920
|
-
|
|
921
|
-
// Dense buffer: morphCount * vertexCount * 3 floats (one vec3 per morph per vertex)
|
|
922
|
-
const offsetsBuffer = new Float32Array(morphCount * this.vertexCount * 3);
|
|
923
|
-
// Initialize all offsets to zero
|
|
924
|
-
offsetsBuffer.fill(0);
|
|
925
|
-
// Fill in actual offsets for vertex morphs
|
|
926
|
-
for (let morphIdx = 0; morphIdx < morphCount; morphIdx++) {
|
|
927
|
-
const morph = this.morphs[morphIdx];
|
|
928
|
-
if (morph.type === 1) {
|
|
929
|
-
// Vertex morph
|
|
930
|
-
// Store offsets in dense buffer: [morph0_v0, morph0_v1, ..., morph1_v0, ...]
|
|
931
|
-
// Each vec3 is 3 consecutive floats
|
|
932
|
-
for (const offset of morph.vertexOffsets) {
|
|
933
|
-
// Calculate index in the dense buffer
|
|
934
|
-
// Layout: morphIdx * (vertexCount * 3) + vertexIndex * 3
|
|
935
|
-
// This gives us the starting float index for this morph's vertex offset
|
|
936
|
-
const bufferIdx = morphIdx * this.vertexCount * 3 + offset.vertexIndex * 3;
|
|
937
|
-
if (bufferIdx >= 0 &&
|
|
938
|
-
bufferIdx + 2 < offsetsBuffer.length &&
|
|
939
|
-
offset.vertexIndex >= 0 &&
|
|
940
|
-
offset.vertexIndex < this.vertexCount) {
|
|
941
|
-
offsetsBuffer[bufferIdx] = offset.positionOffset[0];
|
|
942
|
-
offsetsBuffer[bufferIdx + 1] = offset.positionOffset[1];
|
|
943
|
-
offsetsBuffer[bufferIdx + 2] = offset.positionOffset[2];
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
}
|
|
919
|
+
// Morphs are applied from the sparse per-morph vertexOffsets lists (see
|
|
920
|
+
// Model.applyMorphs); no dense morphCount*vertexCount buffer is needed.
|
|
948
921
|
const morphing = {
|
|
949
922
|
morphs: this.morphs,
|
|
950
|
-
offsetsBuffer,
|
|
951
923
|
};
|
|
952
924
|
return new Model(vertexData, indexData, this.textures, this.materials, skeleton, skinning, morphing, this.rigidbodies, this.joints);
|
|
953
925
|
}
|