three-vat 3.0.0 → 4.1.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 +20 -16
- package/dist/{carrier-BxRuW47G.d.ts → carrier-VLSJLgjX.d.ts} +70 -18
- package/dist/{chunk-J5IEUGSB.js → chunk-OEKKEHRQ.js} +51 -14
- package/dist/index.d.ts +199 -33
- package/dist/index.js +876 -303
- package/dist/tsl.d.ts +7 -1
- package/dist/tsl.js +46 -11
- package/dist/webgl.d.ts +7 -1
- package/dist/webgl.js +69 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,79 @@
|
|
|
1
|
-
import { LoopMode, MAX_TEXTURE_SIZE, makeVATTexture, RIG_TEXELS_PER_SLOT,
|
|
2
|
-
export { EndMode, INFINITE_REPETITIONS, LoopMode, MAX_TEXTURE_SIZE, createVATPlaybackTexture, endsAt, makeVATTexture, resolveVATFrame, setVATInstance } from './chunk-
|
|
3
|
-
import { LoopRepeat, LoopOnce, LoopPingPong,
|
|
1
|
+
import { LoopMode, MAX_TEXTURE_SIZE, vertexLayoutFor, makeVATNormalTexture, makeVATTexture, RIG_TEXELS_PER_SLOT, LIBRARY_PLAYBACK_DEFAULTS, FORWARD_ONLY_REASON, INFINITE_REPETITIONS, HALF_FLOAT_MAX, RIG_TEXELS } from './chunk-OEKKEHRQ.js';
|
|
2
|
+
export { EndMode, INFINITE_REPETITIONS, LoopMode, MAX_TEXTURE_SIZE, createVATPlaybackTexture, endsAt, makeVATNormalTexture, makeVATTexture, resolveVATFrame, setVATInstance } from './chunk-OEKKEHRQ.js';
|
|
3
|
+
import { LoopRepeat, LoopOnce, LoopPingPong, DataUtils, Quaternion, Matrix4, Box3, Vector4, Vector3, Sphere, HalfFloatType, BufferGeometry, BufferAttribute, AdditiveAnimationBlendMode, AnimationMixer, PropertyBinding, FloatType, Material, InterleavedBuffer, SkinnedMesh, Mesh, Bone, Object3D, Skeleton, AnimationClip, Color, InterleavedBufferAttribute, VectorKeyframeTrack, StringKeyframeTrack, QuaternionKeyframeTrack, NumberKeyframeTrack, ColorKeyframeTrack, BooleanKeyframeTrack, Float16BufferAttribute, Interpolant } from 'three';
|
|
4
4
|
|
|
5
|
+
var IGNORED = ["uuid", "name", "color", "userData", "metadata"];
|
|
6
|
+
function flatFacts(material) {
|
|
7
|
+
const m = material;
|
|
8
|
+
if (!m.color?.isColor || m.vertexColors || m.isShadowMaterial) return null;
|
|
9
|
+
const own = (key) => Object.prototype.hasOwnProperty.call(m, key);
|
|
10
|
+
if (own("onBeforeCompile") || own("customProgramCacheKey")) return null;
|
|
11
|
+
for (const value of Object.values(m)) {
|
|
12
|
+
const v = value;
|
|
13
|
+
if (v?.isTexture || v?.isNode) return null;
|
|
14
|
+
}
|
|
15
|
+
const json = m.toJSON();
|
|
16
|
+
for (const key of IGNORED) delete json[key];
|
|
17
|
+
return { key: JSON.stringify(json), color: [m.color.r, m.color.g, m.color.b] };
|
|
18
|
+
}
|
|
19
|
+
function mergedFlatMaterial(members) {
|
|
20
|
+
const merged = members[0].clone();
|
|
21
|
+
merged.color.setRGB(1, 1, 1);
|
|
22
|
+
merged.vertexColors = true;
|
|
23
|
+
merged.name = members.map((m) => m.name).filter(Boolean).join(" + ");
|
|
24
|
+
return merged;
|
|
25
|
+
}
|
|
26
|
+
var FLAT_MERGE = { facts: flatFacts, merge: mergedFlatMaterial };
|
|
27
|
+
function planFlatMerge(materials, hooks) {
|
|
28
|
+
const facts = /* @__PURE__ */ new Map();
|
|
29
|
+
const groups = /* @__PURE__ */ new Map();
|
|
30
|
+
for (const material of materials) {
|
|
31
|
+
if (facts.has(material)) continue;
|
|
32
|
+
const f = hooks.facts(material);
|
|
33
|
+
if (!f) continue;
|
|
34
|
+
facts.set(material, f);
|
|
35
|
+
groups.set(f.key, [...groups.get(f.key) ?? [], material]);
|
|
36
|
+
}
|
|
37
|
+
const targets = /* @__PURE__ */ new Map();
|
|
38
|
+
for (const members of groups.values()) {
|
|
39
|
+
if (members.length < 2) continue;
|
|
40
|
+
const merged = hooks.merge(members);
|
|
41
|
+
for (const m of members) targets.set(m, { material: merged, tint: new Color().fromArray(facts.get(m).color) });
|
|
42
|
+
}
|
|
43
|
+
return { targetOf: (material) => targets.get(material) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/octahedral.ts
|
|
47
|
+
var signNotZero = (v) => v >= 0 ? 1 : -1;
|
|
48
|
+
var toByte = (v) => Math.max(0, Math.min(255, Math.round((v * 0.5 + 0.5) * 255)));
|
|
49
|
+
function encodeOctahedral(x, y, z, out, offset) {
|
|
50
|
+
const l1 = Math.abs(x) + Math.abs(y) + Math.abs(z);
|
|
51
|
+
if (l1 === 0) {
|
|
52
|
+
out[offset] = toByte(0);
|
|
53
|
+
out[offset + 1] = toByte(0);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const px = x / l1;
|
|
57
|
+
const py = y / l1;
|
|
58
|
+
const fold = z <= 0;
|
|
59
|
+
out[offset] = toByte(fold ? (1 - Math.abs(py)) * signNotZero(px) : px);
|
|
60
|
+
out[offset + 1] = toByte(fold ? (1 - Math.abs(px)) * signNotZero(py) : py);
|
|
61
|
+
}
|
|
62
|
+
function decodeOctahedral(u, v, out = { x: 0, y: 0, z: 0 }) {
|
|
63
|
+
const ex = u / 255 * 2 - 1;
|
|
64
|
+
const ey = v / 255 * 2 - 1;
|
|
65
|
+
const z = 1 - Math.abs(ex) - Math.abs(ey);
|
|
66
|
+
const t = Math.max(-z, 0);
|
|
67
|
+
const x = ex - signNotZero(ex) * t;
|
|
68
|
+
const y = ey - signNotZero(ey) * t;
|
|
69
|
+
const length = Math.hypot(x, y, z) || 1;
|
|
70
|
+
out.x = x / length;
|
|
71
|
+
out.y = y / length;
|
|
72
|
+
out.z = z / length;
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/bake.ts
|
|
5
77
|
var LOOP_MODES = /* @__PURE__ */ new Map([
|
|
6
78
|
[LoopRepeat, LoopMode.Repeat],
|
|
7
79
|
[LoopOnce, LoopMode.Once],
|
|
@@ -53,6 +125,32 @@ function morphCountOf({ mesh, morphPos, morphNrm }) {
|
|
|
53
125
|
const influences = mesh.morphTargetInfluences;
|
|
54
126
|
return influences ? Math.min(influences.length, Math.max(morphPos?.length ?? 0, morphNrm?.length ?? 0)) : 0;
|
|
55
127
|
}
|
|
128
|
+
var _mt = /* @__PURE__ */ new Vector3();
|
|
129
|
+
var _mb = /* @__PURE__ */ new Vector3();
|
|
130
|
+
var _mbn = /* @__PURE__ */ new Vector3();
|
|
131
|
+
function morphVertex(part, v, weights, count, p, n) {
|
|
132
|
+
const { basePos, baseNrm, morphPos, morphNrm, morphRelative } = part;
|
|
133
|
+
if (!morphRelative) {
|
|
134
|
+
_mb.fromBufferAttribute(basePos, v);
|
|
135
|
+
if (n) _mbn.fromBufferAttribute(baseNrm, v);
|
|
136
|
+
}
|
|
137
|
+
for (let t = 0; t < count; t++) {
|
|
138
|
+
const w = weights[t];
|
|
139
|
+
if (w === 0) continue;
|
|
140
|
+
const targetPos = morphPos?.[t];
|
|
141
|
+
if (targetPos) {
|
|
142
|
+
_mt.fromBufferAttribute(targetPos, v);
|
|
143
|
+
if (!morphRelative) _mt.sub(_mb);
|
|
144
|
+
p.addScaledVector(_mt, w);
|
|
145
|
+
}
|
|
146
|
+
const targetNrm = n ? morphNrm?.[t] : void 0;
|
|
147
|
+
if (n && targetNrm) {
|
|
148
|
+
_mt.fromBufferAttribute(targetNrm, v);
|
|
149
|
+
if (!morphRelative) _mt.sub(_mbn);
|
|
150
|
+
n.addScaledVector(_mt, w);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
56
154
|
function asAttribute(value, mesh, name) {
|
|
57
155
|
if (!(value instanceof BufferAttribute)) {
|
|
58
156
|
throw new Error(
|
|
@@ -61,7 +159,14 @@ function asAttribute(value, mesh, name) {
|
|
|
61
159
|
}
|
|
62
160
|
return value;
|
|
63
161
|
}
|
|
64
|
-
function
|
|
162
|
+
function deriveNormals(position, index) {
|
|
163
|
+
const scratch = new BufferGeometry();
|
|
164
|
+
scratch.setAttribute("position", position);
|
|
165
|
+
scratch.setIndex(index);
|
|
166
|
+
scratch.computeVertexNormals();
|
|
167
|
+
return scratch.attributes.normal;
|
|
168
|
+
}
|
|
169
|
+
function collectParts(root, hooks) {
|
|
65
170
|
const found = [];
|
|
66
171
|
root.traverse((o) => {
|
|
67
172
|
const mesh = o;
|
|
@@ -70,6 +175,10 @@ function collectParts(root) {
|
|
|
70
175
|
if (found.length === 0) {
|
|
71
176
|
throw new Error("three-vat: no Mesh found under root; nothing to bake");
|
|
72
177
|
}
|
|
178
|
+
const flat = hooks ? planFlatMerge(
|
|
179
|
+
found.flatMap((mesh) => Array.isArray(mesh.material) ? [] : [mesh.material]),
|
|
180
|
+
hooks
|
|
181
|
+
) : null;
|
|
73
182
|
const materials = [];
|
|
74
183
|
const parts = [];
|
|
75
184
|
for (const mesh of found) {
|
|
@@ -79,11 +188,13 @@ function collectParts(root) {
|
|
|
79
188
|
);
|
|
80
189
|
}
|
|
81
190
|
const geometry = mesh.geometry;
|
|
82
|
-
|
|
83
|
-
const
|
|
191
|
+
const source = mesh.material;
|
|
192
|
+
const merged = flat?.targetOf(source);
|
|
193
|
+
const material = merged?.material ?? source;
|
|
84
194
|
let materialIndex = materials.indexOf(material);
|
|
85
195
|
if (materialIndex === -1) materialIndex = materials.push(material) - 1;
|
|
86
196
|
const skinned = mesh;
|
|
197
|
+
const basePos = asAttribute(geometry.attributes.position, mesh, "position");
|
|
87
198
|
parts.push({
|
|
88
199
|
mesh,
|
|
89
200
|
material,
|
|
@@ -91,8 +202,8 @@ function collectParts(root) {
|
|
|
91
202
|
vertexStart: 0,
|
|
92
203
|
// assigned below, after material sorting
|
|
93
204
|
vertexCount: geometry.attributes.position.count,
|
|
94
|
-
basePos
|
|
95
|
-
baseNrm: asAttribute(geometry.attributes.normal, mesh, "normal"),
|
|
205
|
+
basePos,
|
|
206
|
+
baseNrm: geometry.attributes.normal ? asAttribute(geometry.attributes.normal, mesh, "normal") : deriveNormals(basePos, geometry.index),
|
|
96
207
|
isSkinned: !!geometry.attributes.skinWeight && !!skinned.skeleton,
|
|
97
208
|
skeleton: skinned.skeleton,
|
|
98
209
|
pose: void 0,
|
|
@@ -101,7 +212,8 @@ function collectParts(root) {
|
|
|
101
212
|
skinWeight: geometry.attributes.skinWeight,
|
|
102
213
|
morphPos: geometry.morphAttributes.position,
|
|
103
214
|
morphNrm: geometry.morphAttributes.normal,
|
|
104
|
-
morphRelative: geometry.morphTargetsRelative
|
|
215
|
+
morphRelative: geometry.morphTargetsRelative,
|
|
216
|
+
tint: merged?.tint ?? null
|
|
105
217
|
});
|
|
106
218
|
}
|
|
107
219
|
parts.sort((a, b) => a.materialIndex - b.materialIndex);
|
|
@@ -115,42 +227,48 @@ function collectParts(root) {
|
|
|
115
227
|
function mergeGeometry(parts, restMatrices, total) {
|
|
116
228
|
const position = new Float32Array(total * 3);
|
|
117
229
|
const normal = new Float32Array(total * 3);
|
|
118
|
-
const
|
|
230
|
+
const _v = new Vector3();
|
|
231
|
+
const _n = new Vector3();
|
|
232
|
+
parts.forEach((part, pi) => {
|
|
233
|
+
const m = restMatrices[pi];
|
|
234
|
+
for (let v = 0; v < part.vertexCount; v++) {
|
|
235
|
+
_v.fromBufferAttribute(part.basePos, v).applyMatrix4(m);
|
|
236
|
+
_n.fromBufferAttribute(part.baseNrm, v).transformDirection(m);
|
|
237
|
+
_v.toArray(position, (part.vertexStart + v) * 3);
|
|
238
|
+
_n.toArray(normal, (part.vertexStart + v) * 3);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
const merged = new BufferGeometry();
|
|
242
|
+
merged.setAttribute("position", new BufferAttribute(position, 3));
|
|
243
|
+
merged.setAttribute("normal", new BufferAttribute(normal, 3));
|
|
244
|
+
mergeSurface(merged, parts, total, restMatrices);
|
|
245
|
+
return merged;
|
|
246
|
+
}
|
|
247
|
+
function mergeSurface(merged, parts, total, restMatrices) {
|
|
248
|
+
const merging = parts.some((p) => p.tint);
|
|
249
|
+
const want = optionalAttributes(parts, merging);
|
|
119
250
|
const uv = want.uv ? new Float32Array(total * 2) : null;
|
|
120
251
|
const color = want.color ? new Float32Array(total * 3) : null;
|
|
121
252
|
const tangent = want.tangent ? new Float32Array(total * 4) : null;
|
|
122
|
-
const _v = new Vector3();
|
|
123
|
-
const _n = new Vector3();
|
|
124
253
|
const _t = new Vector3();
|
|
125
254
|
parts.forEach((part, pi) => {
|
|
126
|
-
const m = restMatrices[pi];
|
|
127
255
|
const geometry = part.mesh.geometry;
|
|
128
256
|
const start = part.vertexStart;
|
|
129
257
|
const srcUV = uv ? asAttribute(geometry.attributes.uv, part.mesh, "uv") : null;
|
|
130
|
-
const srcColor = color ?
|
|
258
|
+
const srcColor = color ? colorSource(part, merging) : null;
|
|
131
259
|
const srcTangent = tangent ? geometry.attributes.tangent : null;
|
|
260
|
+
const m = restMatrices?.[pi];
|
|
132
261
|
for (let v = 0; v < part.vertexCount; v++) {
|
|
133
|
-
|
|
134
|
-
_n.fromBufferAttribute(part.baseNrm, v).transformDirection(m);
|
|
135
|
-
const o3 = (start + v) * 3;
|
|
136
|
-
position[o3] = _v.x;
|
|
137
|
-
position[o3 + 1] = _v.y;
|
|
138
|
-
position[o3 + 2] = _v.z;
|
|
139
|
-
normal[o3] = _n.x;
|
|
140
|
-
normal[o3 + 1] = _n.y;
|
|
141
|
-
normal[o3 + 2] = _n.z;
|
|
262
|
+
const vi = start + v;
|
|
142
263
|
if (uv && srcUV) {
|
|
143
|
-
uv[
|
|
144
|
-
uv[
|
|
145
|
-
}
|
|
146
|
-
if (color && srcColor) {
|
|
147
|
-
color[o3] = srcColor.getX(v);
|
|
148
|
-
color[o3 + 1] = srcColor.getY(v);
|
|
149
|
-
color[o3 + 2] = srcColor.getZ(v);
|
|
264
|
+
uv[vi * 2] = srcUV.getX(v);
|
|
265
|
+
uv[vi * 2 + 1] = srcUV.getY(v);
|
|
150
266
|
}
|
|
267
|
+
if (color) writeColor(color, vi * 3, part, srcColor, v);
|
|
151
268
|
if (tangent && srcTangent) {
|
|
152
|
-
_t.fromBufferAttribute(srcTangent, v)
|
|
153
|
-
|
|
269
|
+
_t.fromBufferAttribute(srcTangent, v);
|
|
270
|
+
if (m) _t.transformDirection(m);
|
|
271
|
+
const o4 = vi * 4;
|
|
154
272
|
tangent[o4] = _t.x;
|
|
155
273
|
tangent[o4 + 1] = _t.y;
|
|
156
274
|
tangent[o4 + 2] = _t.z;
|
|
@@ -158,19 +276,37 @@ function mergeGeometry(parts, restMatrices, total) {
|
|
|
158
276
|
}
|
|
159
277
|
}
|
|
160
278
|
});
|
|
161
|
-
const merged = new BufferGeometry();
|
|
162
|
-
merged.setAttribute("position", new BufferAttribute(position, 3));
|
|
163
|
-
merged.setAttribute("normal", new BufferAttribute(normal, 3));
|
|
164
279
|
if (uv) merged.setAttribute("uv", new BufferAttribute(uv, 2));
|
|
165
280
|
if (color) merged.setAttribute("color", new BufferAttribute(color, 3));
|
|
166
281
|
if (tangent) merged.setAttribute("tangent", new BufferAttribute(tangent, 4));
|
|
167
282
|
indexParts(merged, parts);
|
|
168
|
-
return merged;
|
|
169
283
|
}
|
|
170
|
-
function
|
|
284
|
+
function colorSource(part, merging) {
|
|
285
|
+
const source = part.mesh.geometry.attributes.color;
|
|
286
|
+
if (part.tint || !source) return null;
|
|
287
|
+
if (merging && source instanceof InterleavedBufferAttribute) return source;
|
|
288
|
+
return asAttribute(source, part.mesh, "color");
|
|
289
|
+
}
|
|
290
|
+
function writeColor(out, o3, part, source, v) {
|
|
291
|
+
if (part.tint) {
|
|
292
|
+
out[o3] = part.tint.r;
|
|
293
|
+
out[o3 + 1] = part.tint.g;
|
|
294
|
+
out[o3 + 2] = part.tint.b;
|
|
295
|
+
} else if (source) {
|
|
296
|
+
out[o3] = source.getX(v);
|
|
297
|
+
out[o3 + 1] = source.getY(v);
|
|
298
|
+
out[o3 + 2] = source.getZ(v);
|
|
299
|
+
} else {
|
|
300
|
+
out[o3] = out[o3 + 1] = out[o3 + 2] = 1;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function optionalAttributes(parts, merging) {
|
|
171
304
|
return {
|
|
172
305
|
uv: parts.every((p) => !!p.mesh.geometry.attributes.uv),
|
|
173
|
-
|
|
306
|
+
// A merge writes a colour for every part it touched, so the attribute is
|
|
307
|
+
// there whenever one happened; a part it did not touch keeps its own, or
|
|
308
|
+
// white, which leaves an untinted material's shading as it was.
|
|
309
|
+
color: merging || parts.every((p) => !!p.mesh.geometry.attributes.color),
|
|
174
310
|
tangent: parts.every((p) => {
|
|
175
311
|
const t = p.mesh.geometry.attributes.tangent;
|
|
176
312
|
return t instanceof BufferAttribute && t.itemSize === 4;
|
|
@@ -214,31 +350,96 @@ function frameCountsFor(clips, fps, maxTextureSize) {
|
|
|
214
350
|
return { frameCounts, totalFrames };
|
|
215
351
|
}
|
|
216
352
|
function bakeVAT(root, animations, options = {}) {
|
|
217
|
-
|
|
353
|
+
return bakeVATWith(root, animations, options, FLAT_MERGE);
|
|
354
|
+
}
|
|
355
|
+
function bakeVATWith(root, animations, options, hooks) {
|
|
356
|
+
const {
|
|
357
|
+
fps = 30,
|
|
358
|
+
maxTextureSize = MAX_TEXTURE_SIZE,
|
|
359
|
+
bakeNormals = true,
|
|
360
|
+
encoding = "auto",
|
|
361
|
+
mergeFlatMaterials = false
|
|
362
|
+
} = options;
|
|
218
363
|
const resolved = animations.map(resolveAnimation);
|
|
219
|
-
const clips = resolved.map((a) => a.clip);
|
|
220
364
|
root.updateMatrixWorld(true);
|
|
221
|
-
const parts = collectParts(root);
|
|
365
|
+
const parts = collectParts(root, mergeFlatMaterials ? hooks : null);
|
|
222
366
|
if (encoding === "rig") return bakeRig(root, parts, resolved, { fps, maxTextureSize });
|
|
223
|
-
|
|
224
|
-
|
|
367
|
+
if (encoding === "delta") {
|
|
368
|
+
return { ...bakeVertices(root, parts, resolved, { fps, maxTextureSize, bakeNormals }), fallback: null };
|
|
369
|
+
}
|
|
370
|
+
let refusal;
|
|
371
|
+
try {
|
|
372
|
+
return bakeRig(root, parts, resolved, { fps, maxTextureSize });
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (!(error instanceof RigRefusal)) throw error;
|
|
375
|
+
refusal = error;
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
return { ...bakeVertices(root, parts, resolved, { fps, maxTextureSize, bakeNormals }), fallback: refusal.message };
|
|
379
|
+
} catch (error) {
|
|
380
|
+
if (!(error instanceof Error)) throw error;
|
|
225
381
|
throw new Error(
|
|
226
|
-
`three-vat:
|
|
382
|
+
`three-vat: neither encoding can bake this subtree. The vertex encoding: ${unprefixed(error)}. The rig encoding, tried first: ${unprefixed(refusal)}`,
|
|
383
|
+
{ cause: refusal }
|
|
227
384
|
);
|
|
228
385
|
}
|
|
386
|
+
}
|
|
387
|
+
function unprefixed(error) {
|
|
388
|
+
return error.message.replace(/^three-vat: /, "");
|
|
389
|
+
}
|
|
390
|
+
function sampleClips(root, resolved, frameCounts, poses, writeFrame) {
|
|
391
|
+
const mixer = new AnimationMixer(root);
|
|
392
|
+
const clipTable = [];
|
|
393
|
+
let rowOffset = 0;
|
|
394
|
+
try {
|
|
395
|
+
for (let ci = 0; ci < resolved.length; ci++) {
|
|
396
|
+
const { clip, defaults } = resolved[ci];
|
|
397
|
+
const frames = frameCounts[ci];
|
|
398
|
+
const action = mixer.clipAction(clip);
|
|
399
|
+
action.play();
|
|
400
|
+
let maxDeltaSq = 0;
|
|
401
|
+
for (let f = 0; f < frames; f++) {
|
|
402
|
+
mixer.setTime(f / frames * clip.duration);
|
|
403
|
+
root.updateMatrixWorld(true);
|
|
404
|
+
poseSkeletons(poses);
|
|
405
|
+
maxDeltaSq = Math.max(maxDeltaSq, writeFrame(clip, f, rowOffset + f));
|
|
406
|
+
}
|
|
407
|
+
action.stop();
|
|
408
|
+
clipTable.push({
|
|
409
|
+
name: clip.name,
|
|
410
|
+
startFrame: rowOffset,
|
|
411
|
+
frames,
|
|
412
|
+
fps: frames / clip.duration,
|
|
413
|
+
duration: clip.duration,
|
|
414
|
+
maxDelta: Math.sqrt(maxDeltaSq),
|
|
415
|
+
// Declared once, here, rather than repeated at every instance that plays
|
|
416
|
+
// this band. An instance overrides any of them, field by field.
|
|
417
|
+
...defaults
|
|
418
|
+
});
|
|
419
|
+
rowOffset += frames;
|
|
420
|
+
}
|
|
421
|
+
} finally {
|
|
422
|
+
mixer.stopAllAction();
|
|
423
|
+
mixer.setTime(0);
|
|
424
|
+
root.updateMatrixWorld(true);
|
|
425
|
+
}
|
|
426
|
+
return clipTable;
|
|
427
|
+
}
|
|
428
|
+
function bakeVertices(root, parts, resolved, { fps, maxTextureSize, bakeNormals }) {
|
|
429
|
+
const clips = resolved.map((a) => a.clip);
|
|
430
|
+
const vertexCount = parts.reduce((n, p) => n + p.vertexCount, 0);
|
|
229
431
|
const { frameCounts, totalFrames } = frameCountsFor(clips, fps, maxTextureSize);
|
|
432
|
+
const { rowsPerFrame, width, height, frameStride } = vertexLayoutFor(vertexCount, totalFrames, maxTextureSize);
|
|
230
433
|
const rootInverse = root.matrixWorld.clone().invert();
|
|
231
434
|
const restMatrices = parts.map(
|
|
232
435
|
(p) => new Matrix4().multiplyMatrices(rootInverse, p.mesh.matrixWorld)
|
|
233
436
|
);
|
|
234
437
|
const geometry = mergeGeometry(parts, restMatrices, vertexCount);
|
|
235
438
|
const mergedBase = geometry.attributes.position;
|
|
236
|
-
const posData = new
|
|
237
|
-
const nrmData = bakeNormals ? new
|
|
439
|
+
const posData = new Uint16Array(frameStride * totalFrames * 4);
|
|
440
|
+
const nrmData = bakeNormals ? new Uint8Array(frameStride * totalFrames * 2) : null;
|
|
238
441
|
const bakeNormal = nrmData !== null;
|
|
239
|
-
const mixer = new AnimationMixer(root);
|
|
240
442
|
const bounds = new Box3();
|
|
241
|
-
const clipTable = [];
|
|
242
443
|
const _si = new Vector4();
|
|
243
444
|
const _sw = new Vector4();
|
|
244
445
|
const _acc = new Matrix4();
|
|
@@ -247,129 +448,86 @@ function bakeVAT(root, animations, options = {}) {
|
|
|
247
448
|
const _p = new Vector3();
|
|
248
449
|
const _bp = new Vector3();
|
|
249
450
|
const _n = new Vector3();
|
|
250
|
-
const _mt = new Vector3();
|
|
251
|
-
const _mb = new Vector3();
|
|
252
|
-
const _mbn = new Vector3();
|
|
253
451
|
const poses = attachPoseBuffers(parts);
|
|
254
452
|
const influencers = bakeNormals ? parts.filter((p) => p.pose).map(influencedBones) : [];
|
|
255
453
|
let warnedNonUniformScale = false;
|
|
256
|
-
|
|
257
|
-
clips.forEach((clip, ci) => {
|
|
258
|
-
const frames = frameCounts[ci];
|
|
259
|
-
const action = mixer.clipAction(clip);
|
|
260
|
-
action.play();
|
|
454
|
+
const writeFrame = (clip, f, row) => {
|
|
261
455
|
let maxDeltaSq = 0;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (w === 0) continue;
|
|
288
|
-
const targetPos = morphPos?.[t];
|
|
289
|
-
if (targetPos) {
|
|
290
|
-
_mt.fromBufferAttribute(targetPos, v);
|
|
291
|
-
if (!morphRelative) _mt.sub(_mb);
|
|
292
|
-
_p.addScaledVector(_mt, w);
|
|
293
|
-
}
|
|
294
|
-
const targetNrm = bakeNormal ? morphNrm?.[t] : void 0;
|
|
295
|
-
if (targetNrm) {
|
|
296
|
-
_mt.fromBufferAttribute(targetNrm, v);
|
|
297
|
-
if (!morphRelative) _mt.sub(_mbn);
|
|
298
|
-
_n.addScaledVector(_mt, w);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
if (isSkinned && boneMatrices) {
|
|
303
|
-
const skinned = part.mesh;
|
|
304
|
-
_si.fromBufferAttribute(skinIndex, v);
|
|
305
|
-
_sw.fromBufferAttribute(skinWeight, v);
|
|
306
|
-
const ae = _acc.elements;
|
|
307
|
-
ae.fill(0);
|
|
308
|
-
for (let i = 0; i < 4; i++) {
|
|
309
|
-
const w = _sw.getComponent(i);
|
|
310
|
-
if (w === 0) continue;
|
|
311
|
-
const b = _si.getComponent(i) * BONE_STRIDE;
|
|
312
|
-
for (let e = 0; e < 16; e++) ae[e] += boneMatrices[b + e] * w;
|
|
313
|
-
}
|
|
314
|
-
_skin.multiplyMatrices(_acc, skinned.bindMatrix).premultiply(skinned.bindMatrixInverse);
|
|
315
|
-
_p.applyMatrix4(_skin);
|
|
316
|
-
if (bakeNormal) _n.transformDirection(_skin);
|
|
317
|
-
}
|
|
318
|
-
_p.applyMatrix4(_partMatrix);
|
|
319
|
-
if (bakeNormal) _n.transformDirection(_partMatrix);
|
|
320
|
-
bounds.expandByPoint(_p);
|
|
321
|
-
_bp.fromBufferAttribute(mergedBase, vi);
|
|
322
|
-
const o = (row * vertexCount + vi) * 4;
|
|
323
|
-
const dx = _p.x - _bp.x;
|
|
324
|
-
const dy = _p.y - _bp.y;
|
|
325
|
-
const dz = _p.z - _bp.z;
|
|
326
|
-
maxDeltaSq = Math.max(maxDeltaSq, dx * dx + dy * dy + dz * dz);
|
|
327
|
-
posData[o] = dx;
|
|
328
|
-
posData[o + 1] = dy;
|
|
329
|
-
posData[o + 2] = dz;
|
|
330
|
-
posData[o + 3] = 1;
|
|
331
|
-
if (bakeNormal) {
|
|
332
|
-
nrmData[o] = _n.x;
|
|
333
|
-
nrmData[o + 1] = _n.y;
|
|
334
|
-
nrmData[o + 2] = _n.z;
|
|
335
|
-
nrmData[o + 3] = 1;
|
|
456
|
+
if (!warnedNonUniformScale) {
|
|
457
|
+
warnedNonUniformScale = warnOnNonUniformBoneScale(influencers);
|
|
458
|
+
}
|
|
459
|
+
for (const part of parts) {
|
|
460
|
+
_partMatrix.multiplyMatrices(rootInverse, part.mesh.matrixWorld);
|
|
461
|
+
const influences = part.mesh.morphTargetInfluences;
|
|
462
|
+
const { isSkinned, skinIndex, skinWeight } = part;
|
|
463
|
+
const boneMatrices = part.pose?.matrices;
|
|
464
|
+
const morphCount = morphCountOf(part);
|
|
465
|
+
for (let v = 0; v < part.vertexCount; v++) {
|
|
466
|
+
const vi = part.vertexStart + v;
|
|
467
|
+
_p.fromBufferAttribute(part.basePos, v);
|
|
468
|
+
if (bakeNormal) _n.fromBufferAttribute(part.baseNrm, v);
|
|
469
|
+
if (influences && morphCount > 0) morphVertex(part, v, influences, morphCount, _p, bakeNormal ? _n : null);
|
|
470
|
+
if (isSkinned && boneMatrices) {
|
|
471
|
+
const skinned = part.mesh;
|
|
472
|
+
_si.fromBufferAttribute(skinIndex, v);
|
|
473
|
+
_sw.fromBufferAttribute(skinWeight, v);
|
|
474
|
+
const ae = _acc.elements;
|
|
475
|
+
for (let e = 0; e < 16; e++) ae[e] = 0;
|
|
476
|
+
for (let i = 0; i < 4; i++) {
|
|
477
|
+
const w = _sw.getComponent(i);
|
|
478
|
+
if (w === 0) continue;
|
|
479
|
+
const b = _si.getComponent(i) * BONE_STRIDE;
|
|
480
|
+
for (let e = 0; e < 16; e++) ae[e] += boneMatrices[b + e] * w;
|
|
336
481
|
}
|
|
482
|
+
_skin.multiplyMatrices(_acc, skinned.bindMatrix).premultiply(skinned.bindMatrixInverse);
|
|
483
|
+
_p.applyMatrix4(_skin);
|
|
484
|
+
if (bakeNormal) _n.transformDirection(_skin);
|
|
485
|
+
}
|
|
486
|
+
_p.applyMatrix4(_partMatrix);
|
|
487
|
+
if (bakeNormal) _n.transformDirection(_partMatrix);
|
|
488
|
+
bounds.expandByPoint(_p);
|
|
489
|
+
_bp.fromBufferAttribute(mergedBase, vi);
|
|
490
|
+
const texel = row * frameStride + vi;
|
|
491
|
+
const o = texel * 4;
|
|
492
|
+
const dx = _p.x - _bp.x;
|
|
493
|
+
const dy = _p.y - _bp.y;
|
|
494
|
+
const dz = _p.z - _bp.z;
|
|
495
|
+
maxDeltaSq = Math.max(maxDeltaSq, dx * dx + dy * dy + dz * dz);
|
|
496
|
+
const largest = Math.max(Math.abs(dx), Math.abs(dy), Math.abs(dz));
|
|
497
|
+
if (largest > HALF_FLOAT_MAX) {
|
|
498
|
+
throw new Error(
|
|
499
|
+
`three-vat: position delta component ${largest} (clip "${clip.name}", frame ${f}, vertex ${vi}) exceeds the half-float limit of ${HALF_FLOAT_MAX} the position texture stores; a unit smaller than the metre is the usual cause`
|
|
500
|
+
);
|
|
337
501
|
}
|
|
502
|
+
posData[o] = toHalfFloat(dx);
|
|
503
|
+
posData[o + 1] = toHalfFloat(dy);
|
|
504
|
+
posData[o + 2] = toHalfFloat(dz);
|
|
505
|
+
posData[o + 3] = HALF_ONE;
|
|
506
|
+
if (bakeNormal) encodeOctahedral(_n.x, _n.y, _n.z, nrmData, texel * 2);
|
|
338
507
|
}
|
|
339
508
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
startFrame: rowOffset,
|
|
344
|
-
frames,
|
|
345
|
-
fps: frames / clip.duration,
|
|
346
|
-
duration: clip.duration,
|
|
347
|
-
maxDelta: Math.sqrt(maxDeltaSq),
|
|
348
|
-
// Declared once, here, rather than repeated at every instance that plays
|
|
349
|
-
// this band. An instance overrides any of them, field by field.
|
|
350
|
-
...resolved[ci].defaults
|
|
351
|
-
});
|
|
352
|
-
rowOffset += frames;
|
|
353
|
-
});
|
|
354
|
-
mixer.stopAllAction();
|
|
355
|
-
mixer.setTime(0);
|
|
356
|
-
root.updateMatrixWorld(true);
|
|
509
|
+
return maxDeltaSq;
|
|
510
|
+
};
|
|
511
|
+
const clipTable = sampleClips(root, resolved, frameCounts, poses, writeFrame);
|
|
357
512
|
geometry.boundingBox = bounds.clone();
|
|
358
513
|
geometry.boundingSphere = bounds.getBoundingSphere(new Sphere());
|
|
359
514
|
const materials = [];
|
|
360
515
|
for (const part of parts) materials[part.materialIndex] = part.material;
|
|
361
516
|
return {
|
|
362
|
-
positionTexture: makeVATTexture(posData,
|
|
363
|
-
normalTexture: nrmData ?
|
|
517
|
+
positionTexture: makeVATTexture(posData, width, height, HalfFloatType),
|
|
518
|
+
normalTexture: nrmData ? makeVATNormalTexture(nrmData, width, height) : null,
|
|
364
519
|
clips: clipTable,
|
|
365
520
|
bounds,
|
|
366
521
|
vertexCount,
|
|
367
522
|
totalFrames,
|
|
523
|
+
rowsPerFrame,
|
|
368
524
|
encoding: "delta",
|
|
369
525
|
geometry,
|
|
370
526
|
materials
|
|
371
527
|
};
|
|
372
528
|
}
|
|
529
|
+
var toHalfFloat = DataUtils.toHalfFloat;
|
|
530
|
+
var HALF_ONE = /* @__PURE__ */ toHalfFloat(1);
|
|
373
531
|
var BONE_STRIDE = 16;
|
|
374
532
|
var IDENTITY = /* @__PURE__ */ new Matrix4();
|
|
375
533
|
function attachPoseBuffers(parts) {
|
|
@@ -408,6 +566,7 @@ function influencedBones(part) {
|
|
|
408
566
|
return { pose: part.pose, bones: [...used] };
|
|
409
567
|
}
|
|
410
568
|
var SCALE_UNIFORMITY_EPSILON = 1e-4;
|
|
569
|
+
var MATRIX_BASIS = [0, 1, 2, 4, 5, 6, 8, 9, 10];
|
|
411
570
|
function hasNonUniformScale(matrices, b) {
|
|
412
571
|
const o = b * BONE_STRIDE;
|
|
413
572
|
return unevenSquares(
|
|
@@ -416,6 +575,13 @@ function hasNonUniformScale(matrices, b) {
|
|
|
416
575
|
matrices[o + 8] ** 2 + matrices[o + 9] ** 2 + matrices[o + 10] ** 2
|
|
417
576
|
);
|
|
418
577
|
}
|
|
578
|
+
function hasShear(matrices, b) {
|
|
579
|
+
const m = (i) => matrices[b * BONE_STRIDE + i];
|
|
580
|
+
const lengthSq = (a) => m(a) ** 2 + m(a + 1) ** 2 + m(a + 2) ** 2;
|
|
581
|
+
const dot = (a, c) => m(a) * m(c) + m(a + 1) * m(c + 1) + m(a + 2) * m(c + 2);
|
|
582
|
+
const square = (a, c) => dot(a, c) ** 2 <= SCALE_UNIFORMITY_EPSILON ** 2 * lengthSq(a) * lengthSq(c);
|
|
583
|
+
return !(square(0, 4) && square(0, 8) && square(4, 8));
|
|
584
|
+
}
|
|
419
585
|
function unevenSquares(x, y, z) {
|
|
420
586
|
const max = Math.max(x, y, z);
|
|
421
587
|
return max - Math.min(x, y, z) > SCALE_UNIFORMITY_EPSILON * max;
|
|
@@ -432,7 +598,9 @@ function warnOnNonUniformBoneScale(influencers) {
|
|
|
432
598
|
}
|
|
433
599
|
return false;
|
|
434
600
|
}
|
|
435
|
-
var
|
|
601
|
+
var VERTEX_ENCODING_STORES_IT = "the vertex encoding (`encoding: 'delta'`) stores what a rig cannot";
|
|
602
|
+
var RigRefusal = class extends Error {
|
|
603
|
+
};
|
|
436
604
|
function placementOf(part, rootInverse, out) {
|
|
437
605
|
out.multiplyMatrices(rootInverse, part.mesh.matrixWorld);
|
|
438
606
|
if (part.pose) out.multiply(part.mesh.bindMatrixInverse);
|
|
@@ -554,39 +722,22 @@ ${clips.join("\n")}`;
|
|
|
554
722
|
const its = parts.length === 1 ? "its" : "their";
|
|
555
723
|
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
724
|
});
|
|
557
|
-
return new
|
|
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
|
|
725
|
+
return new RigRefusal(
|
|
726
|
+
`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, and ${VERTEX_ENCODING_STORES_IT}`
|
|
559
727
|
);
|
|
560
728
|
}
|
|
561
729
|
function foldMorphs(part, statics) {
|
|
562
|
-
const { vertexCount, basePos, baseNrm
|
|
730
|
+
const { vertexCount, basePos, baseNrm } = part;
|
|
563
731
|
const position = new Float32Array(vertexCount * 3);
|
|
564
732
|
const normal = new Float32Array(vertexCount * 3);
|
|
565
733
|
const active = statics.some((w) => w !== 0);
|
|
566
734
|
const _p = new Vector3();
|
|
567
735
|
const _n = new Vector3();
|
|
568
|
-
const _mt = new Vector3();
|
|
569
|
-
const _mb = new Vector3();
|
|
570
736
|
for (let v = 0; v < vertexCount; v++) {
|
|
571
737
|
_p.fromBufferAttribute(basePos, v);
|
|
572
738
|
_n.fromBufferAttribute(baseNrm, v);
|
|
573
739
|
if (active) {
|
|
574
|
-
|
|
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
|
-
}
|
|
740
|
+
morphVertex(part, v, statics, statics.length, _p, _n);
|
|
590
741
|
_n.normalize();
|
|
591
742
|
}
|
|
592
743
|
_p.toArray(position, v * 3);
|
|
@@ -631,8 +782,8 @@ function refuseAnimatedNonUniformScale(slots, clips, root, influenced) {
|
|
|
631
782
|
}
|
|
632
783
|
}
|
|
633
784
|
function nonUniformScale(label, clip) {
|
|
634
|
-
return new
|
|
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
|
|
785
|
+
return new RigRefusal(
|
|
786
|
+
`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, and ${VERTEX_ENCODING_STORES_IT}. Authored with a uniform scale, it bakes as a rig`
|
|
636
787
|
);
|
|
637
788
|
}
|
|
638
789
|
function bakeRig(root, parts, resolved, { fps, maxTextureSize }) {
|
|
@@ -651,7 +802,7 @@ function bakeRig(root, parts, resolved, { fps, maxTextureSize }) {
|
|
|
651
802
|
}));
|
|
652
803
|
const width = slotCount * RIG_TEXELS_PER_SLOT;
|
|
653
804
|
if (width > maxTextureSize) {
|
|
654
|
-
throw new
|
|
805
|
+
throw new RigRefusal(
|
|
655
806
|
`three-vat: rig texture width ${width} (${slotCount} slots \xD7 ${RIG_TEXELS_PER_SLOT} texels) exceeds maxTextureSize ${maxTextureSize}`
|
|
656
807
|
);
|
|
657
808
|
}
|
|
@@ -670,7 +821,7 @@ function bakeRig(root, parts, resolved, { fps, maxTextureSize }) {
|
|
|
670
821
|
const _slot = new Matrix4();
|
|
671
822
|
const _partMatrix = new Matrix4();
|
|
672
823
|
const placements = new Map(parts.map((part) => [part, new Matrix4()]));
|
|
673
|
-
const
|
|
824
|
+
const _q2 = new Quaternion();
|
|
674
825
|
const _t = new Vector3();
|
|
675
826
|
const _s = new Vector3();
|
|
676
827
|
const restRoot = new Float64Array(vertexCount * 3);
|
|
@@ -684,114 +835,101 @@ function bakeRig(root, parts, resolved, { fps, maxTextureSize }) {
|
|
|
684
835
|
const data = new Float32Array(width * totalFrames * 4);
|
|
685
836
|
const slotMatrices = new Float64Array(slotCount * BONE_STRIDE);
|
|
686
837
|
const previous = new Float64Array(slotCount * 4);
|
|
687
|
-
const mixer = new AnimationMixer(root);
|
|
688
838
|
const bounds = new Box3();
|
|
689
|
-
const clipTable = [];
|
|
690
839
|
let minX = Infinity;
|
|
691
840
|
let minY = Infinity;
|
|
692
841
|
let minZ = Infinity;
|
|
693
842
|
let maxX = -Infinity;
|
|
694
843
|
let maxY = -Infinity;
|
|
695
844
|
let maxZ = -Infinity;
|
|
696
|
-
|
|
697
|
-
clips.forEach((clip, ci) => {
|
|
698
|
-
const frames = frameCounts[ci];
|
|
699
|
-
const action = mixer.clipAction(clip);
|
|
700
|
-
action.play();
|
|
845
|
+
const writeFrame = (clip, _f, row) => {
|
|
701
846
|
let maxDeltaSq = 0;
|
|
702
|
-
for (
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
for (
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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);
|
|
847
|
+
for (const [part, placement] of placements) placementOf(part, rootInverse, placement);
|
|
848
|
+
for (let slot = 0; slot < slotCount; slot++) {
|
|
849
|
+
const { rig, parts: readers } = slots[slot];
|
|
850
|
+
const lead = readers[0];
|
|
851
|
+
const placement = placements.get(lead);
|
|
852
|
+
for (let pi = 1; pi < readers.length; pi++) {
|
|
853
|
+
const other = readers[pi];
|
|
854
|
+
if (!matricesClose(placement, placements.get(other))) {
|
|
855
|
+
throw new RigRefusal(
|
|
856
|
+
`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, and ${VERTEX_ENCODING_STORES_IT}`
|
|
857
|
+
);
|
|
733
858
|
}
|
|
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
859
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
if (
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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;
|
|
860
|
+
if (rig) {
|
|
861
|
+
_slot.fromArray(rig.pose.matrices, rig.bone * BONE_STRIDE).multiply(rig.bindMatrix).premultiply(placement);
|
|
862
|
+
} else {
|
|
863
|
+
_slot.copy(placement);
|
|
864
|
+
}
|
|
865
|
+
slotMatrices.set(_slot.elements, slot * BONE_STRIDE);
|
|
866
|
+
if (influenced[slot] && (hasNonUniformScale(slotMatrices, slot) || hasShear(slotMatrices, slot))) {
|
|
867
|
+
throw nonUniformScale(slotLabel(slots[slot]), clip);
|
|
868
|
+
}
|
|
869
|
+
const p4 = slot * 4;
|
|
870
|
+
const det = _slot.determinant();
|
|
871
|
+
let scale;
|
|
872
|
+
if (det === 0) {
|
|
873
|
+
_t.setFromMatrixPosition(_slot);
|
|
874
|
+
_q2.fromArray(previous, p4);
|
|
875
|
+
if (_q2.lengthSq() === 0) _q2.identity();
|
|
876
|
+
scale = 0;
|
|
877
|
+
} else {
|
|
878
|
+
if (det < 0) for (const e of MATRIX_BASIS) _slot.elements[e] = -_slot.elements[e];
|
|
879
|
+
_slot.decompose(_t, _q2, _s);
|
|
880
|
+
scale = det < 0 ? -_s.x : _s.x;
|
|
881
|
+
}
|
|
882
|
+
if (previous[p4] * _q2.x + previous[p4 + 1] * _q2.y + previous[p4 + 2] * _q2.z + previous[p4 + 3] * _q2.w < 0) {
|
|
883
|
+
_q2.set(-_q2.x, -_q2.y, -_q2.z, -_q2.w);
|
|
778
884
|
}
|
|
885
|
+
previous[p4] = _q2.x;
|
|
886
|
+
previous[p4 + 1] = _q2.y;
|
|
887
|
+
previous[p4 + 2] = _q2.z;
|
|
888
|
+
previous[p4 + 3] = _q2.w;
|
|
889
|
+
const o = (row * width + slot * RIG_TEXELS_PER_SLOT) * 4;
|
|
890
|
+
const rotation = o + RIG_TEXELS.rotation * 4;
|
|
891
|
+
data[rotation] = _q2.x;
|
|
892
|
+
data[rotation + 1] = _q2.y;
|
|
893
|
+
data[rotation + 2] = _q2.z;
|
|
894
|
+
data[rotation + 3] = _q2.w;
|
|
895
|
+
const placementTexel = o + RIG_TEXELS.placement * 4;
|
|
896
|
+
data[placementTexel] = _t.x;
|
|
897
|
+
data[placementTexel + 1] = _t.y;
|
|
898
|
+
data[placementTexel + 2] = _t.z;
|
|
899
|
+
data[placementTexel + 3] = scale;
|
|
779
900
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
901
|
+
for (let v = 0; v < vertexCount; v++) {
|
|
902
|
+
const o3 = v * 3;
|
|
903
|
+
const o4 = v * 4;
|
|
904
|
+
const x = localPosition[o3];
|
|
905
|
+
const y = localPosition[o3 + 1];
|
|
906
|
+
const z = localPosition[o3 + 2];
|
|
907
|
+
let px = 0;
|
|
908
|
+
let py = 0;
|
|
909
|
+
let pz = 0;
|
|
910
|
+
for (let i = 0; i < 4; i++) {
|
|
911
|
+
const w = skinWeight[o4 + i];
|
|
912
|
+
if (w === 0) continue;
|
|
913
|
+
const m = skinIndex[o4 + i] * BONE_STRIDE;
|
|
914
|
+
px += w * (slotMatrices[m] * x + slotMatrices[m + 4] * y + slotMatrices[m + 8] * z + slotMatrices[m + 12]);
|
|
915
|
+
py += w * (slotMatrices[m + 1] * x + slotMatrices[m + 5] * y + slotMatrices[m + 9] * z + slotMatrices[m + 13]);
|
|
916
|
+
pz += w * (slotMatrices[m + 2] * x + slotMatrices[m + 6] * y + slotMatrices[m + 10] * z + slotMatrices[m + 14]);
|
|
917
|
+
}
|
|
918
|
+
if (px < minX) minX = px;
|
|
919
|
+
if (px > maxX) maxX = px;
|
|
920
|
+
if (py < minY) minY = py;
|
|
921
|
+
if (py > maxY) maxY = py;
|
|
922
|
+
if (pz < minZ) minZ = pz;
|
|
923
|
+
if (pz > maxZ) maxZ = pz;
|
|
924
|
+
const dx = px - restRoot[o3];
|
|
925
|
+
const dy = py - restRoot[o3 + 1];
|
|
926
|
+
const dz = pz - restRoot[o3 + 2];
|
|
927
|
+
const deltaSq = dx * dx + dy * dy + dz * dz;
|
|
928
|
+
if (deltaSq > maxDeltaSq) maxDeltaSq = deltaSq;
|
|
929
|
+
}
|
|
930
|
+
return maxDeltaSq;
|
|
931
|
+
};
|
|
932
|
+
const clipTable = sampleClips(root, resolved, frameCounts, poses, writeFrame);
|
|
795
933
|
bounds.min.set(minX, minY, minZ);
|
|
796
934
|
bounds.max.set(maxX, maxY, maxZ);
|
|
797
935
|
geometry.boundingBox = bounds.clone();
|
|
@@ -811,29 +949,18 @@ function bakeRig(root, parts, resolved, { fps, maxTextureSize }) {
|
|
|
811
949
|
};
|
|
812
950
|
}
|
|
813
951
|
function mergeRigGeometry(rigParts, total) {
|
|
814
|
-
const parts = rigParts.map((r) => r.part);
|
|
815
952
|
const position = new Float32Array(total * 3);
|
|
816
953
|
const normal = new Float32Array(total * 3);
|
|
817
954
|
const slotIndex = new Uint16Array(total * 4);
|
|
818
955
|
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
956
|
for (const { part, slotOf, position: restPos, normal: restNrm } of rigParts) {
|
|
824
|
-
const geometry = part.mesh.geometry;
|
|
825
957
|
const start = part.vertexStart;
|
|
826
958
|
const srcIndex = part.pose ? part.skinIndex : null;
|
|
827
959
|
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
960
|
position.set(restPos, start * 3);
|
|
832
961
|
normal.set(restNrm, start * 3);
|
|
833
962
|
for (let v = 0; v < part.vertexCount; v++) {
|
|
834
|
-
const
|
|
835
|
-
const o3 = vi * 3;
|
|
836
|
-
const o4 = vi * 4;
|
|
963
|
+
const o4 = (start + v) * 4;
|
|
837
964
|
if (srcIndex && srcWeight) {
|
|
838
965
|
for (let i = 0; i < 4; i++) {
|
|
839
966
|
slotIndex[o4 + i] = slotOf[srcIndex.getComponent(v, i)];
|
|
@@ -843,21 +970,6 @@ function mergeRigGeometry(rigParts, total) {
|
|
|
843
970
|
slotIndex[o4] = slotOf[0];
|
|
844
971
|
slotWeight[o4] = 1;
|
|
845
972
|
}
|
|
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
973
|
}
|
|
862
974
|
}
|
|
863
975
|
const merged = new BufferGeometry();
|
|
@@ -865,11 +977,472 @@ function mergeRigGeometry(rigParts, total) {
|
|
|
865
977
|
merged.setAttribute("normal", new BufferAttribute(normal, 3));
|
|
866
978
|
merged.setAttribute("skinIndex", new BufferAttribute(slotIndex, 4));
|
|
867
979
|
merged.setAttribute("skinWeight", new BufferAttribute(slotWeight, 4));
|
|
868
|
-
|
|
869
|
-
if (color) merged.setAttribute("color", new BufferAttribute(color, 3));
|
|
870
|
-
if (tangent) merged.setAttribute("tangent", new BufferAttribute(tangent, 4));
|
|
871
|
-
indexParts(merged, parts);
|
|
980
|
+
mergeSurface(merged, rigParts.map((r) => r.part), total, null);
|
|
872
981
|
return merged;
|
|
873
982
|
}
|
|
983
|
+
var REQUEST = "three-vat:bake";
|
|
984
|
+
var RESPONSE = "three-vat:baked";
|
|
985
|
+
function transferable(array, owned, transfer) {
|
|
986
|
+
const whole = array.byteOffset === 0 && array.byteLength === array.buffer.byteLength;
|
|
987
|
+
const out = owned && whole ? array : array.slice();
|
|
988
|
+
transfer.add(out.buffer);
|
|
989
|
+
return out;
|
|
990
|
+
}
|
|
991
|
+
function recordGeometry(geometry, owned, transfer, interleaved, interleavedRecords) {
|
|
992
|
+
const attribute = (a, what) => {
|
|
993
|
+
if (a instanceof InterleavedBufferAttribute) {
|
|
994
|
+
let buffer = interleaved.get(a.data);
|
|
995
|
+
if (buffer === void 0) {
|
|
996
|
+
buffer = interleavedRecords.push({ array: transferable(a.data.array, owned, transfer), stride: a.data.stride }) - 1;
|
|
997
|
+
interleaved.set(a.data, buffer);
|
|
998
|
+
}
|
|
999
|
+
return { kind: "interleaved", buffer, itemSize: a.itemSize, offset: a.offset, normalized: a.normalized, name: a.name };
|
|
1000
|
+
}
|
|
1001
|
+
if (a instanceof BufferAttribute) {
|
|
1002
|
+
return {
|
|
1003
|
+
kind: "plain",
|
|
1004
|
+
array: transferable(a.array, owned, transfer),
|
|
1005
|
+
itemSize: a.itemSize,
|
|
1006
|
+
normalized: a.normalized,
|
|
1007
|
+
name: a.name,
|
|
1008
|
+
...a instanceof Float16BufferAttribute ? { half: true } : {}
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
throw new Error(`three-vat: the "${what}" attribute is neither a BufferAttribute nor an interleaved one; a worker cannot carry it`);
|
|
1012
|
+
};
|
|
1013
|
+
const index = geometry.getIndex();
|
|
1014
|
+
return {
|
|
1015
|
+
attributes: Object.fromEntries(Object.entries(geometry.attributes).map(([name, a]) => [name, attribute(a, name)])),
|
|
1016
|
+
morphAttributes: Object.fromEntries(
|
|
1017
|
+
Object.entries(geometry.morphAttributes).map(([name, list]) => [name, list.map((a) => attribute(a, `morph ${name}`))])
|
|
1018
|
+
),
|
|
1019
|
+
morphTargetsRelative: geometry.morphTargetsRelative,
|
|
1020
|
+
index: index ? attribute(index, "index") : null,
|
|
1021
|
+
groups: geometry.groups.map((g) => ({ ...g })),
|
|
1022
|
+
drawRange: { ...geometry.drawRange }
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
function rebuildGeometry(record, interleaved) {
|
|
1026
|
+
const attribute = (a) => {
|
|
1027
|
+
const built = a.kind === "plain" ? a.half ? new Float16BufferAttribute(a.array, a.itemSize, a.normalized) : new BufferAttribute(a.array, a.itemSize, a.normalized) : new InterleavedBufferAttribute(interleaved[a.buffer], a.itemSize, a.offset, a.normalized);
|
|
1028
|
+
built.name = a.name;
|
|
1029
|
+
return built;
|
|
1030
|
+
};
|
|
1031
|
+
const geometry = new BufferGeometry();
|
|
1032
|
+
for (const [name, a] of Object.entries(record.attributes)) geometry.setAttribute(name, attribute(a));
|
|
1033
|
+
for (const [name, list] of Object.entries(record.morphAttributes)) {
|
|
1034
|
+
geometry.morphAttributes[name] = list.map(attribute);
|
|
1035
|
+
}
|
|
1036
|
+
geometry.morphTargetsRelative = record.morphTargetsRelative;
|
|
1037
|
+
if (record.index) geometry.setIndex(attribute(record.index));
|
|
1038
|
+
for (const g of record.groups) geometry.addGroup(g.start, g.count, g.materialIndex);
|
|
1039
|
+
geometry.setDrawRange(record.drawRange.start, record.drawRange.count);
|
|
1040
|
+
return geometry;
|
|
1041
|
+
}
|
|
1042
|
+
var TRACK_TYPES = {
|
|
1043
|
+
bool: BooleanKeyframeTrack,
|
|
1044
|
+
color: ColorKeyframeTrack,
|
|
1045
|
+
number: NumberKeyframeTrack,
|
|
1046
|
+
quaternion: QuaternionKeyframeTrack,
|
|
1047
|
+
string: StringKeyframeTrack,
|
|
1048
|
+
vector: VectorKeyframeTrack
|
|
1049
|
+
};
|
|
1050
|
+
var READ_PROPERTIES = /* @__PURE__ */ new Set(["position", "quaternion", "rotation", "scale", "morphTargetInfluences"]);
|
|
1051
|
+
function bakeReadsTrack(track, root, baked) {
|
|
1052
|
+
const { nodeName, objectName, propertyName } = PropertyBinding.parseTrackName(track.name);
|
|
1053
|
+
if (objectName && objectName !== "bones") return false;
|
|
1054
|
+
if (!READ_PROPERTIES.has(propertyName)) return false;
|
|
1055
|
+
if (propertyName !== "morphTargetInfluences" || objectName) return true;
|
|
1056
|
+
return baked.has(PropertyBinding.findNode(root, nodeName));
|
|
1057
|
+
}
|
|
1058
|
+
function recordTrack(track, clip, transfer) {
|
|
1059
|
+
const factory = track.createInterpolant;
|
|
1060
|
+
const interpolation = factory.isInterpolantFactoryMethodGLTFCubicSpline ? "gltf-cubic" : track.getInterpolation();
|
|
1061
|
+
if (interpolation === void 0) {
|
|
1062
|
+
throw new Error(
|
|
1063
|
+
`three-vat: track "${track.name}" of clip "${clip.name || "(unnamed)"}" uses a custom interpolant, which a worker cannot carry; bake this clip with bakeVAT on the main thread`
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
if (!(track.ValueTypeName in TRACK_TYPES)) {
|
|
1067
|
+
throw new Error(`three-vat: track "${track.name}" is a "${track.ValueTypeName}" track, which a worker cannot rebuild`);
|
|
1068
|
+
}
|
|
1069
|
+
const values = ArrayBuffer.isView(track.values) ? transferable(track.values, false, transfer) : Array.from(track.values);
|
|
1070
|
+
const settings = track.settings;
|
|
1071
|
+
return {
|
|
1072
|
+
name: track.name,
|
|
1073
|
+
type: track.ValueTypeName,
|
|
1074
|
+
times: transferable(track.times, false, transfer),
|
|
1075
|
+
values,
|
|
1076
|
+
interpolation,
|
|
1077
|
+
...settings?.inTangents && settings.outTangents ? { settings: { inTangents: settings.inTangents, outTangents: settings.outTangents } } : {}
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
var CubicSplineInterpolant = class extends Interpolant {
|
|
1081
|
+
copySampleValue_(index) {
|
|
1082
|
+
const result = this.resultBuffer;
|
|
1083
|
+
const values = this.sampleValues;
|
|
1084
|
+
const size = this.valueSize;
|
|
1085
|
+
const offset = index * size * 3 + size;
|
|
1086
|
+
for (let i = 0; i !== size; i++) result[i] = values[offset + i];
|
|
1087
|
+
return result;
|
|
1088
|
+
}
|
|
1089
|
+
interpolate_(i1, t0, t, t1) {
|
|
1090
|
+
const result = this.resultBuffer;
|
|
1091
|
+
const values = this.sampleValues;
|
|
1092
|
+
const stride = this.valueSize;
|
|
1093
|
+
const stride2 = stride * 2;
|
|
1094
|
+
const stride3 = stride * 3;
|
|
1095
|
+
const td = t1 - t0;
|
|
1096
|
+
const p = (t - t0) / td;
|
|
1097
|
+
const pp = p * p;
|
|
1098
|
+
const ppp = pp * p;
|
|
1099
|
+
const offset1 = i1 * stride3;
|
|
1100
|
+
const offset0 = offset1 - stride3;
|
|
1101
|
+
const s2 = -2 * ppp + 3 * pp;
|
|
1102
|
+
const s3 = ppp - pp;
|
|
1103
|
+
const s0 = 1 - s2;
|
|
1104
|
+
const s1 = s3 - pp + p;
|
|
1105
|
+
for (let i = 0; i !== stride; i++) {
|
|
1106
|
+
const p0 = values[offset0 + i + stride];
|
|
1107
|
+
const m0 = values[offset0 + i + stride2] * td;
|
|
1108
|
+
const p1 = values[offset1 + i + stride];
|
|
1109
|
+
const m1 = values[offset1 + i] * td;
|
|
1110
|
+
result[i] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
|
|
1111
|
+
}
|
|
1112
|
+
return result;
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
var _q = new Quaternion();
|
|
1116
|
+
var CubicSplineQuaternionInterpolant = class extends CubicSplineInterpolant {
|
|
1117
|
+
interpolate_(i1, t0, t, t1) {
|
|
1118
|
+
const result = super.interpolate_(i1, t0, t, t1);
|
|
1119
|
+
_q.fromArray(result).normalize().toArray(result);
|
|
1120
|
+
return result;
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
function rebuildTrack(record) {
|
|
1124
|
+
const Track = TRACK_TYPES[record.type];
|
|
1125
|
+
const track = new Track(record.name, record.times, record.values);
|
|
1126
|
+
if (record.interpolation === "gltf-cubic") {
|
|
1127
|
+
const quaternion = track instanceof QuaternionKeyframeTrack;
|
|
1128
|
+
const factory = function(result) {
|
|
1129
|
+
const Kind = quaternion ? CubicSplineQuaternionInterpolant : CubicSplineInterpolant;
|
|
1130
|
+
return new Kind(this.times, this.values, this.getValueSize() / 3, result);
|
|
1131
|
+
};
|
|
1132
|
+
factory.isInterpolantFactoryMethodGLTFCubicSpline = true;
|
|
1133
|
+
track.createInterpolant = factory;
|
|
1134
|
+
} else if (record.interpolation !== track.DefaultInterpolation) {
|
|
1135
|
+
track.setInterpolation(record.interpolation);
|
|
1136
|
+
}
|
|
1137
|
+
if (record.settings) Object.assign(track, { settings: record.settings });
|
|
1138
|
+
return track;
|
|
1139
|
+
}
|
|
1140
|
+
function isAction2(input) {
|
|
1141
|
+
return typeof input.getClip === "function";
|
|
1142
|
+
}
|
|
1143
|
+
function recordScene(root, animations, mergeFlat) {
|
|
1144
|
+
const transfer = /* @__PURE__ */ new Set();
|
|
1145
|
+
const nodes = [];
|
|
1146
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
1147
|
+
const geometries = /* @__PURE__ */ new Map();
|
|
1148
|
+
const geometryRecords = [];
|
|
1149
|
+
const interleaved = /* @__PURE__ */ new Map();
|
|
1150
|
+
const interleavedRecords = [];
|
|
1151
|
+
const materials = [];
|
|
1152
|
+
const skeletons = /* @__PURE__ */ new Map();
|
|
1153
|
+
const skinned = [];
|
|
1154
|
+
const baked = /* @__PURE__ */ new Set();
|
|
1155
|
+
const materialOf = (m) => {
|
|
1156
|
+
const i = materials.indexOf(m);
|
|
1157
|
+
return i === -1 ? materials.push(m) - 1 : i;
|
|
1158
|
+
};
|
|
1159
|
+
root.traverse((o) => {
|
|
1160
|
+
const mesh = o;
|
|
1161
|
+
const skin = o;
|
|
1162
|
+
const isBaked = mesh.isMesh && !!mesh.geometry;
|
|
1163
|
+
const record = {
|
|
1164
|
+
kind: isBaked ? skin.isSkinnedMesh ? "skinned" : "mesh" : o.isBone ? "bone" : "object",
|
|
1165
|
+
parent: o === root ? -1 : nodeIndex.get(o.parent),
|
|
1166
|
+
name: o.name,
|
|
1167
|
+
uuid: o.uuid,
|
|
1168
|
+
position: o.position.toArray(),
|
|
1169
|
+
quaternion: o.quaternion.toArray(),
|
|
1170
|
+
scale: o.scale.toArray(),
|
|
1171
|
+
rotationOrder: o.rotation.order,
|
|
1172
|
+
matrixAutoUpdate: o.matrixAutoUpdate,
|
|
1173
|
+
matrixWorldAutoUpdate: o.matrixWorldAutoUpdate,
|
|
1174
|
+
matrix: [...o.matrix.elements],
|
|
1175
|
+
matrixWorld: [...o.matrixWorld.elements]
|
|
1176
|
+
};
|
|
1177
|
+
if (o.pivot) record.pivot = o.pivot.toArray();
|
|
1178
|
+
nodeIndex.set(o, nodes.push(record) - 1);
|
|
1179
|
+
if (isBaked) {
|
|
1180
|
+
baked.add(o);
|
|
1181
|
+
let g = geometries.get(mesh.geometry);
|
|
1182
|
+
if (g === void 0) {
|
|
1183
|
+
g = geometryRecords.push(recordGeometry(mesh.geometry, false, transfer, interleaved, interleavedRecords)) - 1;
|
|
1184
|
+
geometries.set(mesh.geometry, g);
|
|
1185
|
+
}
|
|
1186
|
+
record.geometry = g;
|
|
1187
|
+
record.material = Array.isArray(mesh.material) ? mesh.material.map(materialOf) : materialOf(mesh.material);
|
|
1188
|
+
if (mesh.morphTargetInfluences) record.morphTargetInfluences = [...mesh.morphTargetInfluences];
|
|
1189
|
+
if (mesh.morphTargetDictionary) record.morphTargetDictionary = { ...mesh.morphTargetDictionary };
|
|
1190
|
+
if (skin.isSkinnedMesh) {
|
|
1191
|
+
record.bindMode = skin.bindMode;
|
|
1192
|
+
record.bindMatrix = [...skin.bindMatrix.elements];
|
|
1193
|
+
record.bindMatrixInverse = [...skin.bindMatrixInverse.elements];
|
|
1194
|
+
skinned.push([skin, record]);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
const skeletonRecords = [];
|
|
1199
|
+
for (const [mesh, record] of skinned) {
|
|
1200
|
+
const skeleton = mesh.skeleton;
|
|
1201
|
+
if (!skeleton) continue;
|
|
1202
|
+
let s = skeletons.get(skeleton);
|
|
1203
|
+
if (s === void 0) {
|
|
1204
|
+
const bones = skeleton.bones.map((bone) => {
|
|
1205
|
+
if (!bone) return -1;
|
|
1206
|
+
const i = nodeIndex.get(bone);
|
|
1207
|
+
if (i === void 0) {
|
|
1208
|
+
throw new Error(
|
|
1209
|
+
`three-vat: bone "${bone.name || "(unnamed)"}" of mesh "${mesh.name || "(unnamed)"}" is outside the subtree being baked; bake from an ancestor of the whole rig`
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
return i;
|
|
1213
|
+
});
|
|
1214
|
+
s = skeletonRecords.push({ bones, boneInverses: skeleton.boneInverses.map((m) => [...m.elements]) }) - 1;
|
|
1215
|
+
skeletons.set(skeleton, s);
|
|
1216
|
+
}
|
|
1217
|
+
record.skeleton = s;
|
|
1218
|
+
}
|
|
1219
|
+
const clips = [];
|
|
1220
|
+
const clipRecords = [];
|
|
1221
|
+
const clipOf = (clip) => {
|
|
1222
|
+
const i = clips.indexOf(clip);
|
|
1223
|
+
if (i !== -1) return i;
|
|
1224
|
+
clipRecords.push({
|
|
1225
|
+
name: clip.name,
|
|
1226
|
+
duration: clip.duration,
|
|
1227
|
+
blendMode: clip.blendMode,
|
|
1228
|
+
tracks: clip.tracks.filter((t) => bakeReadsTrack(t, root, baked)).map((t) => recordTrack(t, clip, transfer))
|
|
1229
|
+
});
|
|
1230
|
+
return clips.push(clip) - 1;
|
|
1231
|
+
};
|
|
1232
|
+
const inputs = animations.map(
|
|
1233
|
+
(input) => isAction2(input) ? {
|
|
1234
|
+
clip: clipOf(input.getClip()),
|
|
1235
|
+
action: {
|
|
1236
|
+
weight: input.weight,
|
|
1237
|
+
blendMode: input.blendMode,
|
|
1238
|
+
timeScale: input.timeScale,
|
|
1239
|
+
loop: input.loop,
|
|
1240
|
+
repetitions: input.repetitions,
|
|
1241
|
+
clampWhenFinished: input.clampWhenFinished
|
|
1242
|
+
}
|
|
1243
|
+
} : { clip: clipOf(input) }
|
|
1244
|
+
);
|
|
1245
|
+
return {
|
|
1246
|
+
scene: {
|
|
1247
|
+
parentWorld: root.parent ? [...root.parent.matrixWorld.elements] : null,
|
|
1248
|
+
nodes,
|
|
1249
|
+
geometries: geometryRecords,
|
|
1250
|
+
interleaved: interleavedRecords,
|
|
1251
|
+
skeletons: skeletonRecords,
|
|
1252
|
+
clips: clipRecords,
|
|
1253
|
+
inputs,
|
|
1254
|
+
materialCount: materials.length,
|
|
1255
|
+
flat: mergeFlat ? materials.map(flatFacts) : null
|
|
1256
|
+
},
|
|
1257
|
+
materials,
|
|
1258
|
+
transfer: [...transfer]
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
function rebuildScene(scene) {
|
|
1262
|
+
const stand = /* @__PURE__ */ new Map();
|
|
1263
|
+
const standIns = Array.from({ length: scene.materialCount }, (_, i) => {
|
|
1264
|
+
const m = new Material();
|
|
1265
|
+
stand.set(m, i);
|
|
1266
|
+
return m;
|
|
1267
|
+
});
|
|
1268
|
+
const hooks = {
|
|
1269
|
+
facts: (m) => scene.flat?.[stand.get(m)] ?? null,
|
|
1270
|
+
merge: (members) => {
|
|
1271
|
+
const merged = new Material();
|
|
1272
|
+
stand.set(merged, members.map((m) => stand.get(m)));
|
|
1273
|
+
return merged;
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
const interleaved = scene.interleaved.map(({ array, stride }) => new InterleavedBuffer(array, stride));
|
|
1277
|
+
const geometries = scene.geometries.map((g) => rebuildGeometry(g, interleaved));
|
|
1278
|
+
const nodes = scene.nodes.map((n) => {
|
|
1279
|
+
const material = n.material === void 0 ? void 0 : Array.isArray(n.material) ? n.material.map((i) => standIns[i]) : standIns[n.material];
|
|
1280
|
+
const geometry = n.geometry === void 0 ? void 0 : geometries[n.geometry];
|
|
1281
|
+
const o = n.kind === "skinned" ? new SkinnedMesh(geometry, material) : n.kind === "mesh" ? new Mesh(geometry, material) : n.kind === "bone" ? new Bone() : new Object3D();
|
|
1282
|
+
o.name = n.name;
|
|
1283
|
+
o.uuid = n.uuid;
|
|
1284
|
+
o.rotation.order = n.rotationOrder;
|
|
1285
|
+
o.position.fromArray(n.position);
|
|
1286
|
+
o.quaternion.fromArray(n.quaternion);
|
|
1287
|
+
o.scale.fromArray(n.scale);
|
|
1288
|
+
if (n.pivot) o.pivot = new Vector3().fromArray(n.pivot);
|
|
1289
|
+
o.matrixAutoUpdate = n.matrixAutoUpdate;
|
|
1290
|
+
o.matrixWorldAutoUpdate = n.matrixWorldAutoUpdate;
|
|
1291
|
+
o.matrix.fromArray(n.matrix);
|
|
1292
|
+
o.matrixWorld.fromArray(n.matrixWorld);
|
|
1293
|
+
const mesh = o;
|
|
1294
|
+
if (n.morphTargetInfluences) mesh.morphTargetInfluences = [...n.morphTargetInfluences];
|
|
1295
|
+
if (n.morphTargetDictionary) mesh.morphTargetDictionary = { ...n.morphTargetDictionary };
|
|
1296
|
+
return o;
|
|
1297
|
+
});
|
|
1298
|
+
scene.nodes.forEach((n, i) => {
|
|
1299
|
+
if (n.parent !== -1) nodes[n.parent].add(nodes[i]);
|
|
1300
|
+
});
|
|
1301
|
+
const skeletons = scene.skeletons.map(
|
|
1302
|
+
(s) => new Skeleton(
|
|
1303
|
+
s.bones.map((b) => b === -1 ? void 0 : nodes[b]),
|
|
1304
|
+
s.boneInverses.map((m) => new Matrix4().fromArray(m))
|
|
1305
|
+
)
|
|
1306
|
+
);
|
|
1307
|
+
scene.nodes.forEach((n, i) => {
|
|
1308
|
+
if (n.kind !== "skinned" || n.skeleton === void 0) return;
|
|
1309
|
+
const mesh = nodes[i];
|
|
1310
|
+
mesh.bindMode = n.bindMode;
|
|
1311
|
+
mesh.bind(skeletons[n.skeleton], new Matrix4().fromArray(n.bindMatrix));
|
|
1312
|
+
mesh.bindMatrixInverse.fromArray(n.bindMatrixInverse);
|
|
1313
|
+
});
|
|
1314
|
+
const root = nodes[0];
|
|
1315
|
+
if (scene.parentWorld) {
|
|
1316
|
+
const parent = new Object3D();
|
|
1317
|
+
parent.matrixWorld.fromArray(scene.parentWorld);
|
|
1318
|
+
parent.add(root);
|
|
1319
|
+
}
|
|
1320
|
+
const clips = scene.clips.map((c) => {
|
|
1321
|
+
return new AnimationClip(c.name, c.duration, c.tracks.map(rebuildTrack), c.blendMode);
|
|
1322
|
+
});
|
|
1323
|
+
const animations = scene.inputs.map(({ clip, action }) => {
|
|
1324
|
+
if (!action) return clips[clip];
|
|
1325
|
+
const a = new AnimationMixer(root).clipAction(clips[clip], void 0, action.blendMode);
|
|
1326
|
+
a.weight = action.weight;
|
|
1327
|
+
a.timeScale = action.timeScale;
|
|
1328
|
+
a.loop = action.loop;
|
|
1329
|
+
a.repetitions = action.repetitions;
|
|
1330
|
+
a.clampWhenFinished = action.clampWhenFinished;
|
|
1331
|
+
return a;
|
|
1332
|
+
});
|
|
1333
|
+
return { root, animations, stand, hooks };
|
|
1334
|
+
}
|
|
1335
|
+
function recordVAT(vat, stand) {
|
|
1336
|
+
const transfer = /* @__PURE__ */ new Set();
|
|
1337
|
+
const texture = vat.encoding === "delta" ? vat.positionTexture : vat.rigTexture;
|
|
1338
|
+
const normals = vat.encoding === "delta" && vat.normalTexture ? vat.normalTexture.image.data : null;
|
|
1339
|
+
const record = {
|
|
1340
|
+
encoding: vat.encoding,
|
|
1341
|
+
texels: transferable(texture.image.data, true, transfer),
|
|
1342
|
+
width: texture.image.width,
|
|
1343
|
+
height: texture.image.height,
|
|
1344
|
+
normals: normals ? transferable(normals, true, transfer) : null,
|
|
1345
|
+
slotCount: vat.encoding === "rig" ? vat.slotCount : 0,
|
|
1346
|
+
fallback: vat.encoding === "delta" ? vat.fallback : null,
|
|
1347
|
+
rowsPerFrame: vat.encoding === "delta" ? vat.rowsPerFrame : 1,
|
|
1348
|
+
geometry: recordGeometry(vat.geometry, true, transfer, /* @__PURE__ */ new Map(), []),
|
|
1349
|
+
materials: vat.materials.map((m) => stand.get(m)),
|
|
1350
|
+
clips: vat.clips,
|
|
1351
|
+
bounds: { min: vat.bounds.min.toArray(), max: vat.bounds.max.toArray() },
|
|
1352
|
+
vertexCount: vat.vertexCount,
|
|
1353
|
+
totalFrames: vat.totalFrames
|
|
1354
|
+
};
|
|
1355
|
+
return { vat: record, transfer: [...transfer] };
|
|
1356
|
+
}
|
|
1357
|
+
function rebuildVAT(record, materials) {
|
|
1358
|
+
const geometry = rebuildGeometry(record.geometry, []);
|
|
1359
|
+
const bounds = new Box3(new Vector3().fromArray(record.bounds.min), new Vector3().fromArray(record.bounds.max));
|
|
1360
|
+
geometry.boundingBox = bounds.clone();
|
|
1361
|
+
geometry.boundingSphere = bounds.getBoundingSphere(new Sphere());
|
|
1362
|
+
const base = {
|
|
1363
|
+
geometry,
|
|
1364
|
+
materials: record.materials.map(
|
|
1365
|
+
(i) => typeof i === "number" ? materials[i] : mergedFlatMaterial(i.map((j) => materials[j]))
|
|
1366
|
+
),
|
|
1367
|
+
clips: record.clips,
|
|
1368
|
+
bounds,
|
|
1369
|
+
vertexCount: record.vertexCount,
|
|
1370
|
+
totalFrames: record.totalFrames
|
|
1371
|
+
};
|
|
1372
|
+
if (record.encoding === "rig") {
|
|
1373
|
+
const vat2 = {
|
|
1374
|
+
encoding: "rig",
|
|
1375
|
+
rigTexture: makeVATTexture(record.texels, record.width, record.height, FloatType),
|
|
1376
|
+
slotCount: record.slotCount,
|
|
1377
|
+
...base
|
|
1378
|
+
};
|
|
1379
|
+
return vat2;
|
|
1380
|
+
}
|
|
1381
|
+
const vat = {
|
|
1382
|
+
positionTexture: makeVATTexture(record.texels, record.width, record.height, HalfFloatType),
|
|
1383
|
+
normalTexture: record.normals ? makeVATNormalTexture(record.normals, record.width, record.height) : null,
|
|
1384
|
+
encoding: "delta",
|
|
1385
|
+
fallback: record.fallback,
|
|
1386
|
+
rowsPerFrame: record.rowsPerFrame,
|
|
1387
|
+
...base
|
|
1388
|
+
};
|
|
1389
|
+
return vat;
|
|
1390
|
+
}
|
|
1391
|
+
var nextId = 0;
|
|
1392
|
+
function bakeVATInWorker(worker, root, animations, options = {}) {
|
|
1393
|
+
return new Promise((resolve, reject) => {
|
|
1394
|
+
root.updateMatrixWorld(true);
|
|
1395
|
+
const { scene, materials, transfer } = recordScene(root, animations, !!options.mergeFlatMaterials);
|
|
1396
|
+
const id = nextId++;
|
|
1397
|
+
const done = () => {
|
|
1398
|
+
worker.removeEventListener("message", onMessage);
|
|
1399
|
+
worker.removeEventListener("error", onError);
|
|
1400
|
+
};
|
|
1401
|
+
const onMessage = (event) => {
|
|
1402
|
+
const data = event.data;
|
|
1403
|
+
if (data?.type !== RESPONSE || data.id !== id) return;
|
|
1404
|
+
done();
|
|
1405
|
+
if ("error" in data) reject(new Error(data.error));
|
|
1406
|
+
else resolve(rebuildVAT(data.vat, materials));
|
|
1407
|
+
};
|
|
1408
|
+
const onError = (event) => {
|
|
1409
|
+
done();
|
|
1410
|
+
const message = event.message;
|
|
1411
|
+
reject(
|
|
1412
|
+
new Error(
|
|
1413
|
+
`three-vat: the bake worker failed${message ? ` (${message})` : ""}; its module must import and call serveVATBakes()`
|
|
1414
|
+
)
|
|
1415
|
+
);
|
|
1416
|
+
};
|
|
1417
|
+
worker.addEventListener("message", onMessage);
|
|
1418
|
+
worker.addEventListener("error", onError);
|
|
1419
|
+
const request = { type: REQUEST, id, scene, options };
|
|
1420
|
+
try {
|
|
1421
|
+
worker.postMessage(request, transfer);
|
|
1422
|
+
} catch (error) {
|
|
1423
|
+
done();
|
|
1424
|
+
reject(error);
|
|
1425
|
+
}
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
function serveVATBakes(scope = globalThis) {
|
|
1429
|
+
const onMessage = (event) => {
|
|
1430
|
+
const data = event.data;
|
|
1431
|
+
if (data?.type !== REQUEST) return;
|
|
1432
|
+
let response;
|
|
1433
|
+
let transfer = [];
|
|
1434
|
+
try {
|
|
1435
|
+
const { root, animations, stand, hooks } = rebuildScene(data.scene);
|
|
1436
|
+
const recorded = recordVAT(bakeVATWith(root, animations, data.options, hooks), stand);
|
|
1437
|
+
response = { type: RESPONSE, id: data.id, vat: recorded.vat };
|
|
1438
|
+
transfer = recorded.transfer;
|
|
1439
|
+
} catch (error) {
|
|
1440
|
+
response = { type: RESPONSE, id: data.id, error: error instanceof Error ? error.message : String(error) };
|
|
1441
|
+
}
|
|
1442
|
+
scope.postMessage(response, transfer);
|
|
1443
|
+
};
|
|
1444
|
+
scope.addEventListener("message", onMessage);
|
|
1445
|
+
return () => scope.removeEventListener("message", onMessage);
|
|
1446
|
+
}
|
|
874
1447
|
|
|
875
|
-
export { bakeVAT };
|
|
1448
|
+
export { bakeVAT, bakeVATInWorker, decodeOctahedral, encodeOctahedral, serveVATBakes };
|