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