three-vat 2.0.0 → 3.0.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 +8 -4
- package/dist/{carrier-BFCPmcQK.d.ts → carrier-BxRuW47G.d.ts} +194 -100
- package/dist/{chunk-W2ZAFMPB.js → chunk-I4STYOD5.js} +1 -1
- package/dist/{chunk-2PGZ44TP.js → chunk-J5IEUGSB.js} +80 -56
- package/dist/index.d.ts +39 -4
- package/dist/index.js +499 -35
- package/dist/tsl.d.ts +107 -27
- package/dist/tsl.js +185 -69
- package/dist/webgl.d.ts +89 -7
- package/dist/webgl.js +370 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { LoopMode, MAX_TEXTURE_SIZE, makeVATTexture, LIBRARY_PLAYBACK_DEFAULTS, FORWARD_ONLY_REASON, INFINITE_REPETITIONS } from './chunk-
|
|
2
|
-
export { EndMode, INFINITE_REPETITIONS, LoopMode,
|
|
3
|
-
import { LoopRepeat, LoopOnce, LoopPingPong, Matrix4, AnimationMixer, Box3, Vector4, Vector3, Sphere,
|
|
1
|
+
import { LoopMode, MAX_TEXTURE_SIZE, makeVATTexture, RIG_TEXELS_PER_SLOT, RIG_TEXELS, LIBRARY_PLAYBACK_DEFAULTS, FORWARD_ONLY_REASON, INFINITE_REPETITIONS } from './chunk-J5IEUGSB.js';
|
|
2
|
+
export { EndMode, INFINITE_REPETITIONS, LoopMode, MAX_TEXTURE_SIZE, createVATPlaybackTexture, endsAt, makeVATTexture, resolveVATFrame, setVATInstance } from './chunk-J5IEUGSB.js';
|
|
3
|
+
import { LoopRepeat, LoopOnce, LoopPingPong, Matrix4, AnimationMixer, Box3, Vector4, Vector3, Sphere, Quaternion, BufferGeometry, BufferAttribute, AdditiveAnimationBlendMode, PropertyBinding } from 'three';
|
|
4
4
|
|
|
5
5
|
var LOOP_MODES = /* @__PURE__ */ new Map([
|
|
6
6
|
[LoopRepeat, LoopMode.Repeat],
|
|
@@ -49,6 +49,10 @@ function resolveAnimation(input) {
|
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
+
function morphCountOf({ mesh, morphPos, morphNrm }) {
|
|
53
|
+
const influences = mesh.morphTargetInfluences;
|
|
54
|
+
return influences ? Math.min(influences.length, Math.max(morphPos?.length ?? 0, morphNrm?.length ?? 0)) : 0;
|
|
55
|
+
}
|
|
52
56
|
function asAttribute(value, mesh, name) {
|
|
53
57
|
if (!(value instanceof BufferAttribute)) {
|
|
54
58
|
throw new Error(
|
|
@@ -111,17 +115,10 @@ function collectParts(root) {
|
|
|
111
115
|
function mergeGeometry(parts, restMatrices, total) {
|
|
112
116
|
const position = new Float32Array(total * 3);
|
|
113
117
|
const normal = new Float32Array(total * 3);
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
return t instanceof BufferAttribute && t.itemSize === 4;
|
|
119
|
-
});
|
|
120
|
-
const uv = wantUV ? new Float32Array(total * 2) : null;
|
|
121
|
-
const color = wantColor ? new Float32Array(total * 3) : null;
|
|
122
|
-
const tangent = wantTangent ? new Float32Array(total * 4) : null;
|
|
123
|
-
const indices = [];
|
|
124
|
-
const groups = [];
|
|
118
|
+
const want = optionalAttributes(parts);
|
|
119
|
+
const uv = want.uv ? new Float32Array(total * 2) : null;
|
|
120
|
+
const color = want.color ? new Float32Array(total * 3) : null;
|
|
121
|
+
const tangent = want.tangent ? new Float32Array(total * 4) : null;
|
|
125
122
|
const _v = new Vector3();
|
|
126
123
|
const _n = new Vector3();
|
|
127
124
|
const _t = new Vector3();
|
|
@@ -160,8 +157,33 @@ function mergeGeometry(parts, restMatrices, total) {
|
|
|
160
157
|
tangent[o4 + 3] = srcTangent.getW(v);
|
|
161
158
|
}
|
|
162
159
|
}
|
|
160
|
+
});
|
|
161
|
+
const merged = new BufferGeometry();
|
|
162
|
+
merged.setAttribute("position", new BufferAttribute(position, 3));
|
|
163
|
+
merged.setAttribute("normal", new BufferAttribute(normal, 3));
|
|
164
|
+
if (uv) merged.setAttribute("uv", new BufferAttribute(uv, 2));
|
|
165
|
+
if (color) merged.setAttribute("color", new BufferAttribute(color, 3));
|
|
166
|
+
if (tangent) merged.setAttribute("tangent", new BufferAttribute(tangent, 4));
|
|
167
|
+
indexParts(merged, parts);
|
|
168
|
+
return merged;
|
|
169
|
+
}
|
|
170
|
+
function optionalAttributes(parts) {
|
|
171
|
+
return {
|
|
172
|
+
uv: parts.every((p) => !!p.mesh.geometry.attributes.uv),
|
|
173
|
+
color: parts.every((p) => !!p.mesh.geometry.attributes.color),
|
|
174
|
+
tangent: parts.every((p) => {
|
|
175
|
+
const t = p.mesh.geometry.attributes.tangent;
|
|
176
|
+
return t instanceof BufferAttribute && t.itemSize === 4;
|
|
177
|
+
})
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function indexParts(merged, parts) {
|
|
181
|
+
const indices = [];
|
|
182
|
+
const groups = [];
|
|
183
|
+
for (const part of parts) {
|
|
184
|
+
const start = part.vertexStart;
|
|
163
185
|
const groupStart = indices.length;
|
|
164
|
-
const index = geometry.index;
|
|
186
|
+
const index = part.mesh.geometry.index;
|
|
165
187
|
if (index) {
|
|
166
188
|
for (let i = 0; i < index.count; i++) indices.push(start + index.getX(i));
|
|
167
189
|
} else {
|
|
@@ -177,35 +199,34 @@ function mergeGeometry(parts, restMatrices, total) {
|
|
|
177
199
|
materialIndex: part.materialIndex
|
|
178
200
|
});
|
|
179
201
|
}
|
|
180
|
-
}
|
|
181
|
-
const merged = new BufferGeometry();
|
|
182
|
-
merged.setAttribute("position", new BufferAttribute(position, 3));
|
|
183
|
-
merged.setAttribute("normal", new BufferAttribute(normal, 3));
|
|
184
|
-
if (uv) merged.setAttribute("uv", new BufferAttribute(uv, 2));
|
|
185
|
-
if (color) merged.setAttribute("color", new BufferAttribute(color, 3));
|
|
186
|
-
if (tangent) merged.setAttribute("tangent", new BufferAttribute(tangent, 4));
|
|
202
|
+
}
|
|
187
203
|
merged.setIndex(indices);
|
|
188
204
|
for (const g of groups) merged.addGroup(g.start, g.count, g.materialIndex);
|
|
189
|
-
return merged;
|
|
190
205
|
}
|
|
191
|
-
function
|
|
206
|
+
function frameCountsFor(clips, fps, maxTextureSize) {
|
|
207
|
+
const frameCounts = clips.map((c) => Math.max(2, Math.round(c.duration * fps)));
|
|
208
|
+
const totalFrames = frameCounts.reduce((a, b) => a + b, 0);
|
|
209
|
+
if (totalFrames > maxTextureSize) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`three-vat: totalFrames ${totalFrames} exceeds maxTextureSize ${maxTextureSize}; lower fps or bake fewer clips`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return { frameCounts, totalFrames };
|
|
215
|
+
}
|
|
216
|
+
function bakeVAT(root, animations, options = {}) {
|
|
217
|
+
const { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE, bakeNormals = true, encoding = "delta" } = options;
|
|
192
218
|
const resolved = animations.map(resolveAnimation);
|
|
193
219
|
const clips = resolved.map((a) => a.clip);
|
|
194
220
|
root.updateMatrixWorld(true);
|
|
195
221
|
const parts = collectParts(root);
|
|
222
|
+
if (encoding === "rig") return bakeRig(root, parts, resolved, { fps, maxTextureSize });
|
|
196
223
|
const vertexCount = parts.reduce((n, p) => n + p.vertexCount, 0);
|
|
197
224
|
if (vertexCount > maxTextureSize) {
|
|
198
225
|
throw new Error(
|
|
199
226
|
`three-vat: vertexCount ${vertexCount} exceeds maxTextureSize ${maxTextureSize}; row wrapping is not implemented`
|
|
200
227
|
);
|
|
201
228
|
}
|
|
202
|
-
const frameCounts
|
|
203
|
-
const totalFrames = frameCounts.reduce((a, b) => a + b, 0);
|
|
204
|
-
if (totalFrames > maxTextureSize) {
|
|
205
|
-
throw new Error(
|
|
206
|
-
`three-vat: totalFrames ${totalFrames} exceeds maxTextureSize ${maxTextureSize}; lower fps or bake fewer clips`
|
|
207
|
-
);
|
|
208
|
-
}
|
|
229
|
+
const { frameCounts, totalFrames } = frameCountsFor(clips, fps, maxTextureSize);
|
|
209
230
|
const rootInverse = root.matrixWorld.clone().invert();
|
|
210
231
|
const restMatrices = parts.map(
|
|
211
232
|
(p) => new Matrix4().multiplyMatrices(rootInverse, p.mesh.matrixWorld)
|
|
@@ -251,7 +272,7 @@ function bakeVAT(root, animations, { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE
|
|
|
251
272
|
const influences = part.mesh.morphTargetInfluences;
|
|
252
273
|
const { morphPos, morphNrm, morphRelative, isSkinned, skinIndex, skinWeight } = part;
|
|
253
274
|
const boneMatrices = part.pose?.matrices;
|
|
254
|
-
const morphCount =
|
|
275
|
+
const morphCount = morphCountOf(part);
|
|
255
276
|
for (let v = 0; v < part.vertexCount; v++) {
|
|
256
277
|
const vi = part.vertexStart + v;
|
|
257
278
|
_p.fromBufferAttribute(part.basePos, v);
|
|
@@ -389,9 +410,13 @@ function influencedBones(part) {
|
|
|
389
410
|
var SCALE_UNIFORMITY_EPSILON = 1e-4;
|
|
390
411
|
function hasNonUniformScale(matrices, b) {
|
|
391
412
|
const o = b * BONE_STRIDE;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
413
|
+
return unevenSquares(
|
|
414
|
+
matrices[o] ** 2 + matrices[o + 1] ** 2 + matrices[o + 2] ** 2,
|
|
415
|
+
matrices[o + 4] ** 2 + matrices[o + 5] ** 2 + matrices[o + 6] ** 2,
|
|
416
|
+
matrices[o + 8] ** 2 + matrices[o + 9] ** 2 + matrices[o + 10] ** 2
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
function unevenSquares(x, y, z) {
|
|
395
420
|
const max = Math.max(x, y, z);
|
|
396
421
|
return max - Math.min(x, y, z) > SCALE_UNIFORMITY_EPSILON * max;
|
|
397
422
|
}
|
|
@@ -407,5 +432,444 @@ function warnOnNonUniformBoneScale(influencers) {
|
|
|
407
432
|
}
|
|
408
433
|
return false;
|
|
409
434
|
}
|
|
435
|
+
var USE_VERTEX_ENCODING = "bake this subtree with the vertex encoding (the default) instead";
|
|
436
|
+
function placementOf(part, rootInverse, out) {
|
|
437
|
+
out.multiplyMatrices(rootInverse, part.mesh.matrixWorld);
|
|
438
|
+
if (part.pose) out.multiply(part.mesh.bindMatrixInverse);
|
|
439
|
+
return out;
|
|
440
|
+
}
|
|
441
|
+
var MATRIX_EPSILON = 1e-6;
|
|
442
|
+
function matricesClose(a, b) {
|
|
443
|
+
const ae = a.elements;
|
|
444
|
+
const be = b.elements;
|
|
445
|
+
for (let e = 0; e < 16; e++) if (Math.abs(ae[e] - be[e]) > MATRIX_EPSILON) return false;
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
function slotLabel(slot) {
|
|
449
|
+
const parts = slot.parts.map(partName).join(", ");
|
|
450
|
+
if (!slot.rig) return `rigid part ${parts}`;
|
|
451
|
+
const { pose, bone } = slot.rig;
|
|
452
|
+
return `bone "${pose.skeleton.bones[bone]?.name || `bone ${bone}`}" of ${parts}`;
|
|
453
|
+
}
|
|
454
|
+
function partName(part) {
|
|
455
|
+
return `"${part.mesh.name || "(unnamed)"}"`;
|
|
456
|
+
}
|
|
457
|
+
function slotReads(slot, bone, boneInverse, bindMatrix, placement) {
|
|
458
|
+
const { rig } = slot;
|
|
459
|
+
return rig !== void 0 && rig.pose.skeleton.bones[rig.bone] === bone && matricesClose(rig.pose.skeleton.boneInverses[rig.bone], boneInverse) && matricesClose(rig.bindMatrix, bindMatrix) && matricesClose(slot.restPlacement, placement);
|
|
460
|
+
}
|
|
461
|
+
function layoutSlots(parts, rootInverse) {
|
|
462
|
+
const slots = [];
|
|
463
|
+
const slotMaps = /* @__PURE__ */ new Map();
|
|
464
|
+
for (const part of parts) {
|
|
465
|
+
const restPlacement = placementOf(part, rootInverse, new Matrix4());
|
|
466
|
+
if (part.pose) {
|
|
467
|
+
const { pose } = part;
|
|
468
|
+
const { bones, boneInverses } = pose.skeleton;
|
|
469
|
+
const bindMatrix = part.mesh.bindMatrix.clone();
|
|
470
|
+
const map = [];
|
|
471
|
+
for (let b = 0; b < bones.length; b++) {
|
|
472
|
+
const bone = bones[b];
|
|
473
|
+
const shared = bone ? slots.findIndex((s) => slotReads(s, bone, boneInverses[b], bindMatrix, restPlacement)) : -1;
|
|
474
|
+
if (shared >= 0) {
|
|
475
|
+
const slot = slots[shared];
|
|
476
|
+
if (!slot.parts.includes(part)) slot.parts.push(part);
|
|
477
|
+
map.push(shared);
|
|
478
|
+
} else {
|
|
479
|
+
map.push(slots.length);
|
|
480
|
+
slots.push({ rig: { pose, bone: b, bindMatrix }, parts: [part], restPlacement });
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
slotMaps.set(part, map);
|
|
484
|
+
} else {
|
|
485
|
+
slotMaps.set(part, [slots.length]);
|
|
486
|
+
slots.push({ rig: void 0, parts: [part], restPlacement });
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return { slots, slotMaps };
|
|
490
|
+
}
|
|
491
|
+
function morphTracksOn(mesh, clip, root) {
|
|
492
|
+
const driven = /* @__PURE__ */ new Map();
|
|
493
|
+
for (const track of clip.tracks) {
|
|
494
|
+
const parsed = PropertyBinding.parseTrackName(track.name);
|
|
495
|
+
if (parsed.propertyName !== "morphTargetInfluences") continue;
|
|
496
|
+
if (PropertyBinding.findNode(root, parsed.nodeName) !== mesh) continue;
|
|
497
|
+
const size = track.getValueSize();
|
|
498
|
+
const keys = track.times.length;
|
|
499
|
+
const values = track.values;
|
|
500
|
+
const add = (t, read) => {
|
|
501
|
+
const list = driven.get(t) ?? [];
|
|
502
|
+
for (let k = 0; k < keys; k++) list.push(read(k));
|
|
503
|
+
driven.set(t, list);
|
|
504
|
+
};
|
|
505
|
+
if (parsed.propertyIndex === void 0) {
|
|
506
|
+
for (let t = 0; t < size; t++) add(t, (k) => values[k * size + t]);
|
|
507
|
+
} else {
|
|
508
|
+
const byIndex = Number(parsed.propertyIndex);
|
|
509
|
+
const t = Number.isInteger(byIndex) ? byIndex : mesh.morphTargetDictionary?.[parsed.propertyIndex];
|
|
510
|
+
if (t !== void 0) add(t, (k) => values[k]);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return driven;
|
|
514
|
+
}
|
|
515
|
+
function morphInfluences(part, clips, root) {
|
|
516
|
+
const { mesh } = part;
|
|
517
|
+
const influences = mesh.morphTargetInfluences;
|
|
518
|
+
const count = morphCountOf(part);
|
|
519
|
+
if (!influences || count === 0) return { statics: [], animated: [] };
|
|
520
|
+
const driven = clips.map((clip) => morphTracksOn(mesh, clip, root));
|
|
521
|
+
const statics = [];
|
|
522
|
+
const animated = [];
|
|
523
|
+
for (let t = 0; t < count; t++) {
|
|
524
|
+
const seen = /* @__PURE__ */ new Set();
|
|
525
|
+
const offending = [];
|
|
526
|
+
driven.forEach((tracks, ci) => {
|
|
527
|
+
const values = tracks.get(t);
|
|
528
|
+
if (!values) {
|
|
529
|
+
seen.add(influences[t]);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
offending.push(`"${clips[ci].name}"`);
|
|
533
|
+
for (const value of values) seen.add(value);
|
|
534
|
+
});
|
|
535
|
+
if (seen.size > 1) {
|
|
536
|
+
const name = Object.entries(mesh.morphTargetDictionary ?? {}).find(([, i]) => i === t)?.[0];
|
|
537
|
+
animated.push({ part, target: name ? `"${name}"` : String(t), clips: offending });
|
|
538
|
+
}
|
|
539
|
+
statics.push(seen.values().next().value);
|
|
540
|
+
}
|
|
541
|
+
return { statics, animated };
|
|
542
|
+
}
|
|
543
|
+
function refuseAnimatedMorphs(animated) {
|
|
544
|
+
const byDriver = /* @__PURE__ */ new Map();
|
|
545
|
+
for (const { part, target, clips } of animated) {
|
|
546
|
+
const key = `${target}
|
|
547
|
+
${clips.join("\n")}`;
|
|
548
|
+
const entry = byDriver.get(key) ?? { target, clips, parts: [] };
|
|
549
|
+
if (!entry.parts.includes(part)) entry.parts.push(part);
|
|
550
|
+
byDriver.set(key, entry);
|
|
551
|
+
}
|
|
552
|
+
const offences = [...byDriver.values()].map(({ target, clips, parts }) => {
|
|
553
|
+
const one = clips.length === 1;
|
|
554
|
+
const its = parts.length === 1 ? "its" : "their";
|
|
555
|
+
return `clip${one ? "" : "s"} ${clips.join(", ")} animate${one ? "s" : ""} morph target ${target} of part${parts.length === 1 ? "" : "s"} ${parts.map(partName).join(", ")}, ${its} vertices moving where no bone does`;
|
|
556
|
+
});
|
|
557
|
+
return new Error(
|
|
558
|
+
`three-vat: the rig encoding cannot bake this subtree: ${offences.join("; ")}. A slot stores a rotation, a translation and one scale, not a per-vertex delta \u2014 ${USE_VERTEX_ENCODING}`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
function foldMorphs(part, statics) {
|
|
562
|
+
const { vertexCount, basePos, baseNrm, morphPos, morphNrm, morphRelative } = part;
|
|
563
|
+
const position = new Float32Array(vertexCount * 3);
|
|
564
|
+
const normal = new Float32Array(vertexCount * 3);
|
|
565
|
+
const active = statics.some((w) => w !== 0);
|
|
566
|
+
const _p = new Vector3();
|
|
567
|
+
const _n = new Vector3();
|
|
568
|
+
const _mt = new Vector3();
|
|
569
|
+
const _mb = new Vector3();
|
|
570
|
+
for (let v = 0; v < vertexCount; v++) {
|
|
571
|
+
_p.fromBufferAttribute(basePos, v);
|
|
572
|
+
_n.fromBufferAttribute(baseNrm, v);
|
|
573
|
+
if (active) {
|
|
574
|
+
for (let t = 0; t < statics.length; t++) {
|
|
575
|
+
const w = statics[t];
|
|
576
|
+
if (w === 0) continue;
|
|
577
|
+
const targetPos = morphPos?.[t];
|
|
578
|
+
if (targetPos) {
|
|
579
|
+
_mt.fromBufferAttribute(targetPos, v);
|
|
580
|
+
if (!morphRelative) _mt.sub(_mb.fromBufferAttribute(basePos, v));
|
|
581
|
+
_p.addScaledVector(_mt, w);
|
|
582
|
+
}
|
|
583
|
+
const targetNrm = morphNrm?.[t];
|
|
584
|
+
if (targetNrm) {
|
|
585
|
+
_mt.fromBufferAttribute(targetNrm, v);
|
|
586
|
+
if (!morphRelative) _mt.sub(_mb.fromBufferAttribute(baseNrm, v));
|
|
587
|
+
_n.addScaledVector(_mt, w);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
_n.normalize();
|
|
591
|
+
}
|
|
592
|
+
_p.toArray(position, v * 3);
|
|
593
|
+
_n.toArray(normal, v * 3);
|
|
594
|
+
}
|
|
595
|
+
return { position, normal };
|
|
596
|
+
}
|
|
597
|
+
function scaleSensitiveNodes(slots, root, influenced) {
|
|
598
|
+
const sensitive = /* @__PURE__ */ new Map();
|
|
599
|
+
slots.forEach((slot, i) => {
|
|
600
|
+
if (!influenced[i]) return;
|
|
601
|
+
if (slot.rig) {
|
|
602
|
+
const { bones } = slot.rig.pose.skeleton;
|
|
603
|
+
for (let node = bones[slot.rig.bone] ?? null; node && bones.includes(node); node = node.parent) {
|
|
604
|
+
sensitive.set(node, slot);
|
|
605
|
+
}
|
|
606
|
+
} else {
|
|
607
|
+
for (let node = slot.parts[0].mesh; node && node !== root; node = node.parent) {
|
|
608
|
+
sensitive.set(node, slot);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
return sensitive;
|
|
613
|
+
}
|
|
614
|
+
function refuseAnimatedNonUniformScale(slots, clips, root, influenced) {
|
|
615
|
+
const sensitive = scaleSensitiveNodes(slots, root, influenced);
|
|
616
|
+
for (const clip of clips) {
|
|
617
|
+
for (const track of clip.tracks) {
|
|
618
|
+
const parsed = PropertyBinding.parseTrackName(track.name);
|
|
619
|
+
if (parsed.propertyName !== "scale" || track.getValueSize() !== 3) continue;
|
|
620
|
+
const node = PropertyBinding.findNode(root, parsed.nodeName);
|
|
621
|
+
const reached = node && sensitive.get(node);
|
|
622
|
+
if (!reached) continue;
|
|
623
|
+
const values = track.values;
|
|
624
|
+
for (let k = 0; k < track.times.length; k++) {
|
|
625
|
+
const o = k * 3;
|
|
626
|
+
if (unevenSquares(values[o] ** 2, values[o + 1] ** 2, values[o + 2] ** 2)) {
|
|
627
|
+
throw nonUniformScale(slotLabel(reached), clip);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function nonUniformScale(label, clip) {
|
|
634
|
+
return new Error(
|
|
635
|
+
`three-vat: ${label} animates with non-uniform scale in clip "${clip.name}", which the rig encoding cannot store \u2014 a slot is a rotation, a translation and one scale. ${USE_VERTEX_ENCODING}, or author it with a uniform scale.`
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
function bakeRig(root, parts, resolved, { fps, maxTextureSize }) {
|
|
639
|
+
const clips = resolved.map((a) => a.clip);
|
|
640
|
+
const rootInverse = root.matrixWorld.clone().invert();
|
|
641
|
+
const poses = attachPoseBuffers(parts);
|
|
642
|
+
const { slots, slotMaps } = layoutSlots(parts, rootInverse);
|
|
643
|
+
const slotCount = slots.length;
|
|
644
|
+
const morphs = parts.map((part) => morphInfluences(part, clips, root));
|
|
645
|
+
const animated = morphs.flatMap((m) => m.animated);
|
|
646
|
+
if (animated.length > 0) throw refuseAnimatedMorphs(animated);
|
|
647
|
+
const rigParts = parts.map((part, i) => ({
|
|
648
|
+
part,
|
|
649
|
+
slotOf: slotMaps.get(part),
|
|
650
|
+
...foldMorphs(part, morphs[i].statics)
|
|
651
|
+
}));
|
|
652
|
+
const width = slotCount * RIG_TEXELS_PER_SLOT;
|
|
653
|
+
if (width > maxTextureSize) {
|
|
654
|
+
throw new Error(
|
|
655
|
+
`three-vat: rig texture width ${width} (${slotCount} slots \xD7 ${RIG_TEXELS_PER_SLOT} texels) exceeds maxTextureSize ${maxTextureSize}`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
const { frameCounts, totalFrames } = frameCountsFor(clips, fps, maxTextureSize);
|
|
659
|
+
const vertexCount = parts.reduce((n, p) => n + p.vertexCount, 0);
|
|
660
|
+
const geometry = mergeRigGeometry(rigParts, vertexCount);
|
|
661
|
+
const localPosition = geometry.attributes.position.array;
|
|
662
|
+
const skinIndex = geometry.attributes.skinIndex.array;
|
|
663
|
+
const skinWeight = geometry.attributes.skinWeight.array;
|
|
664
|
+
const influenced = new Uint8Array(slotCount);
|
|
665
|
+
for (let i = 0; i < skinWeight.length; i++) {
|
|
666
|
+
if (skinWeight[i] !== 0) influenced[skinIndex[i]] = 1;
|
|
667
|
+
}
|
|
668
|
+
refuseAnimatedNonUniformScale(slots, clips, root, influenced);
|
|
669
|
+
const _p = new Vector3();
|
|
670
|
+
const _slot = new Matrix4();
|
|
671
|
+
const _partMatrix = new Matrix4();
|
|
672
|
+
const placements = new Map(parts.map((part) => [part, new Matrix4()]));
|
|
673
|
+
const _q = new Quaternion();
|
|
674
|
+
const _t = new Vector3();
|
|
675
|
+
const _s = new Vector3();
|
|
676
|
+
const restRoot = new Float64Array(vertexCount * 3);
|
|
677
|
+
for (const part of parts) {
|
|
678
|
+
_partMatrix.multiplyMatrices(rootInverse, part.mesh.matrixWorld);
|
|
679
|
+
for (let v = 0; v < part.vertexCount; v++) {
|
|
680
|
+
_p.fromBufferAttribute(part.basePos, v).applyMatrix4(_partMatrix);
|
|
681
|
+
_p.toArray(restRoot, (part.vertexStart + v) * 3);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const data = new Float32Array(width * totalFrames * 4);
|
|
685
|
+
const slotMatrices = new Float64Array(slotCount * BONE_STRIDE);
|
|
686
|
+
const previous = new Float64Array(slotCount * 4);
|
|
687
|
+
const mixer = new AnimationMixer(root);
|
|
688
|
+
const bounds = new Box3();
|
|
689
|
+
const clipTable = [];
|
|
690
|
+
let minX = Infinity;
|
|
691
|
+
let minY = Infinity;
|
|
692
|
+
let minZ = Infinity;
|
|
693
|
+
let maxX = -Infinity;
|
|
694
|
+
let maxY = -Infinity;
|
|
695
|
+
let maxZ = -Infinity;
|
|
696
|
+
let rowOffset = 0;
|
|
697
|
+
clips.forEach((clip, ci) => {
|
|
698
|
+
const frames = frameCounts[ci];
|
|
699
|
+
const action = mixer.clipAction(clip);
|
|
700
|
+
action.play();
|
|
701
|
+
let maxDeltaSq = 0;
|
|
702
|
+
for (let f = 0; f < frames; f++) {
|
|
703
|
+
mixer.setTime(f / frames * clip.duration);
|
|
704
|
+
root.updateMatrixWorld(true);
|
|
705
|
+
const row = rowOffset + f;
|
|
706
|
+
poseSkeletons(poses);
|
|
707
|
+
for (const [part, placement] of placements) placementOf(part, rootInverse, placement);
|
|
708
|
+
for (let slot = 0; slot < slotCount; slot++) {
|
|
709
|
+
const { rig, parts: readers } = slots[slot];
|
|
710
|
+
const lead = readers[0];
|
|
711
|
+
const placement = placements.get(lead);
|
|
712
|
+
for (let pi = 1; pi < readers.length; pi++) {
|
|
713
|
+
const other = readers[pi];
|
|
714
|
+
if (!matricesClose(placement, placements.get(other))) {
|
|
715
|
+
throw new Error(
|
|
716
|
+
`three-vat: parts ${partName(lead)} and ${partName(other)} read the same slots but move apart in clip "${clip.name}"; the rig encoding gives them one set of slots, which cannot place them differently \u2014 ${USE_VERTEX_ENCODING}`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (rig) {
|
|
721
|
+
_slot.fromArray(rig.pose.matrices, rig.bone * BONE_STRIDE).multiply(rig.bindMatrix).premultiply(placement);
|
|
722
|
+
} else {
|
|
723
|
+
_slot.copy(placement);
|
|
724
|
+
}
|
|
725
|
+
slotMatrices.set(_slot.elements, slot * BONE_STRIDE);
|
|
726
|
+
if (influenced[slot] && hasNonUniformScale(slotMatrices, slot)) {
|
|
727
|
+
throw nonUniformScale(slotLabel(slots[slot]), clip);
|
|
728
|
+
}
|
|
729
|
+
_slot.decompose(_t, _q, _s);
|
|
730
|
+
const p4 = slot * 4;
|
|
731
|
+
if (previous[p4] * _q.x + previous[p4 + 1] * _q.y + previous[p4 + 2] * _q.z + previous[p4 + 3] * _q.w < 0) {
|
|
732
|
+
_q.set(-_q.x, -_q.y, -_q.z, -_q.w);
|
|
733
|
+
}
|
|
734
|
+
previous[p4] = _q.x;
|
|
735
|
+
previous[p4 + 1] = _q.y;
|
|
736
|
+
previous[p4 + 2] = _q.z;
|
|
737
|
+
previous[p4 + 3] = _q.w;
|
|
738
|
+
const o = (row * width + slot * RIG_TEXELS_PER_SLOT) * 4;
|
|
739
|
+
const rotation = o + RIG_TEXELS.rotation * 4;
|
|
740
|
+
data[rotation] = _q.x;
|
|
741
|
+
data[rotation + 1] = _q.y;
|
|
742
|
+
data[rotation + 2] = _q.z;
|
|
743
|
+
data[rotation + 3] = _q.w;
|
|
744
|
+
const placementTexel = o + RIG_TEXELS.placement * 4;
|
|
745
|
+
data[placementTexel] = _t.x;
|
|
746
|
+
data[placementTexel + 1] = _t.y;
|
|
747
|
+
data[placementTexel + 2] = _t.z;
|
|
748
|
+
data[placementTexel + 3] = _s.x;
|
|
749
|
+
}
|
|
750
|
+
for (let v = 0; v < vertexCount; v++) {
|
|
751
|
+
const o3 = v * 3;
|
|
752
|
+
const o4 = v * 4;
|
|
753
|
+
const x = localPosition[o3];
|
|
754
|
+
const y = localPosition[o3 + 1];
|
|
755
|
+
const z = localPosition[o3 + 2];
|
|
756
|
+
let px = 0;
|
|
757
|
+
let py = 0;
|
|
758
|
+
let pz = 0;
|
|
759
|
+
for (let i = 0; i < 4; i++) {
|
|
760
|
+
const w = skinWeight[o4 + i];
|
|
761
|
+
if (w === 0) continue;
|
|
762
|
+
const m = skinIndex[o4 + i] * BONE_STRIDE;
|
|
763
|
+
px += w * (slotMatrices[m] * x + slotMatrices[m + 4] * y + slotMatrices[m + 8] * z + slotMatrices[m + 12]);
|
|
764
|
+
py += w * (slotMatrices[m + 1] * x + slotMatrices[m + 5] * y + slotMatrices[m + 9] * z + slotMatrices[m + 13]);
|
|
765
|
+
pz += w * (slotMatrices[m + 2] * x + slotMatrices[m + 6] * y + slotMatrices[m + 10] * z + slotMatrices[m + 14]);
|
|
766
|
+
}
|
|
767
|
+
if (px < minX) minX = px;
|
|
768
|
+
if (px > maxX) maxX = px;
|
|
769
|
+
if (py < minY) minY = py;
|
|
770
|
+
if (py > maxY) maxY = py;
|
|
771
|
+
if (pz < minZ) minZ = pz;
|
|
772
|
+
if (pz > maxZ) maxZ = pz;
|
|
773
|
+
const dx = px - restRoot[o3];
|
|
774
|
+
const dy = py - restRoot[o3 + 1];
|
|
775
|
+
const dz = pz - restRoot[o3 + 2];
|
|
776
|
+
const deltaSq = dx * dx + dy * dy + dz * dz;
|
|
777
|
+
if (deltaSq > maxDeltaSq) maxDeltaSq = deltaSq;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
action.stop();
|
|
781
|
+
clipTable.push({
|
|
782
|
+
name: clip.name,
|
|
783
|
+
startFrame: rowOffset,
|
|
784
|
+
frames,
|
|
785
|
+
fps: frames / clip.duration,
|
|
786
|
+
duration: clip.duration,
|
|
787
|
+
maxDelta: Math.sqrt(maxDeltaSq),
|
|
788
|
+
...resolved[ci].defaults
|
|
789
|
+
});
|
|
790
|
+
rowOffset += frames;
|
|
791
|
+
});
|
|
792
|
+
mixer.stopAllAction();
|
|
793
|
+
mixer.setTime(0);
|
|
794
|
+
root.updateMatrixWorld(true);
|
|
795
|
+
bounds.min.set(minX, minY, minZ);
|
|
796
|
+
bounds.max.set(maxX, maxY, maxZ);
|
|
797
|
+
geometry.boundingBox = bounds.clone();
|
|
798
|
+
geometry.boundingSphere = bounds.getBoundingSphere(new Sphere());
|
|
799
|
+
const materials = [];
|
|
800
|
+
for (const part of parts) materials[part.materialIndex] = part.material;
|
|
801
|
+
return {
|
|
802
|
+
encoding: "rig",
|
|
803
|
+
rigTexture: makeVATTexture(data, width, totalFrames),
|
|
804
|
+
slotCount,
|
|
805
|
+
clips: clipTable,
|
|
806
|
+
bounds,
|
|
807
|
+
vertexCount,
|
|
808
|
+
totalFrames,
|
|
809
|
+
geometry,
|
|
810
|
+
materials
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function mergeRigGeometry(rigParts, total) {
|
|
814
|
+
const parts = rigParts.map((r) => r.part);
|
|
815
|
+
const position = new Float32Array(total * 3);
|
|
816
|
+
const normal = new Float32Array(total * 3);
|
|
817
|
+
const slotIndex = new Uint16Array(total * 4);
|
|
818
|
+
const slotWeight = new Float32Array(total * 4);
|
|
819
|
+
const want = optionalAttributes(parts);
|
|
820
|
+
const uv = want.uv ? new Float32Array(total * 2) : null;
|
|
821
|
+
const color = want.color ? new Float32Array(total * 3) : null;
|
|
822
|
+
const tangent = want.tangent ? new Float32Array(total * 4) : null;
|
|
823
|
+
for (const { part, slotOf, position: restPos, normal: restNrm } of rigParts) {
|
|
824
|
+
const geometry = part.mesh.geometry;
|
|
825
|
+
const start = part.vertexStart;
|
|
826
|
+
const srcIndex = part.pose ? part.skinIndex : null;
|
|
827
|
+
const srcWeight = part.pose ? part.skinWeight : null;
|
|
828
|
+
const srcUV = uv ? asAttribute(geometry.attributes.uv, part.mesh, "uv") : null;
|
|
829
|
+
const srcColor = color ? asAttribute(geometry.attributes.color, part.mesh, "color") : null;
|
|
830
|
+
const srcTangent = tangent ? geometry.attributes.tangent : null;
|
|
831
|
+
position.set(restPos, start * 3);
|
|
832
|
+
normal.set(restNrm, start * 3);
|
|
833
|
+
for (let v = 0; v < part.vertexCount; v++) {
|
|
834
|
+
const vi = start + v;
|
|
835
|
+
const o3 = vi * 3;
|
|
836
|
+
const o4 = vi * 4;
|
|
837
|
+
if (srcIndex && srcWeight) {
|
|
838
|
+
for (let i = 0; i < 4; i++) {
|
|
839
|
+
slotIndex[o4 + i] = slotOf[srcIndex.getComponent(v, i)];
|
|
840
|
+
slotWeight[o4 + i] = srcWeight.getComponent(v, i);
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
slotIndex[o4] = slotOf[0];
|
|
844
|
+
slotWeight[o4] = 1;
|
|
845
|
+
}
|
|
846
|
+
if (uv && srcUV) {
|
|
847
|
+
uv[vi * 2] = srcUV.getX(v);
|
|
848
|
+
uv[vi * 2 + 1] = srcUV.getY(v);
|
|
849
|
+
}
|
|
850
|
+
if (color && srcColor) {
|
|
851
|
+
color[o3] = srcColor.getX(v);
|
|
852
|
+
color[o3 + 1] = srcColor.getY(v);
|
|
853
|
+
color[o3 + 2] = srcColor.getZ(v);
|
|
854
|
+
}
|
|
855
|
+
if (tangent && srcTangent) {
|
|
856
|
+
tangent[o4] = srcTangent.getX(v);
|
|
857
|
+
tangent[o4 + 1] = srcTangent.getY(v);
|
|
858
|
+
tangent[o4 + 2] = srcTangent.getZ(v);
|
|
859
|
+
tangent[o4 + 3] = srcTangent.getW(v);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
const merged = new BufferGeometry();
|
|
864
|
+
merged.setAttribute("position", new BufferAttribute(position, 3));
|
|
865
|
+
merged.setAttribute("normal", new BufferAttribute(normal, 3));
|
|
866
|
+
merged.setAttribute("skinIndex", new BufferAttribute(slotIndex, 4));
|
|
867
|
+
merged.setAttribute("skinWeight", new BufferAttribute(slotWeight, 4));
|
|
868
|
+
if (uv) merged.setAttribute("uv", new BufferAttribute(uv, 2));
|
|
869
|
+
if (color) merged.setAttribute("color", new BufferAttribute(color, 3));
|
|
870
|
+
if (tangent) merged.setAttribute("tangent", new BufferAttribute(tangent, 4));
|
|
871
|
+
indexParts(merged, parts);
|
|
872
|
+
return merged;
|
|
873
|
+
}
|
|
410
874
|
|
|
411
875
|
export { bakeVAT };
|