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/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-J5IEUGSB.js';
2
- export { EndMode, INFINITE_REPETITIONS, LoopMode, MAX_TEXTURE_SIZE, createVATPlaybackTexture, endsAt, makeVATTexture, resolveVATFrame, setVATInstance } from './chunk-J5IEUGSB.js';
3
- import { LoopRepeat, LoopOnce, LoopPingPong, Matrix4, AnimationMixer, Box3, Vector4, Vector3, Sphere, Quaternion, BufferGeometry, BufferAttribute, AdditiveAnimationBlendMode, PropertyBinding } from 'three';
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 collectParts(root) {
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
- if (!geometry.attributes.normal) geometry.computeVertexNormals();
83
- const material = mesh.material;
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: asAttribute(geometry.attributes.position, mesh, "position"),
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 want = optionalAttributes(parts);
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 ? asAttribute(geometry.attributes.color, part.mesh, "color") : null;
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
- _v.fromBufferAttribute(part.basePos, v).applyMatrix4(m);
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[(start + v) * 2] = srcUV.getX(v);
144
- uv[(start + v) * 2 + 1] = srcUV.getY(v);
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).transformDirection(m);
153
- const o4 = (start + v) * 4;
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 optionalAttributes(parts) {
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
- color: parts.every((p) => !!p.mesh.geometry.attributes.color),
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
- const { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE, bakeNormals = true, encoding = "delta" } = options;
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
- const vertexCount = parts.reduce((n, p) => n + p.vertexCount, 0);
224
- if (vertexCount > maxTextureSize) {
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: vertexCount ${vertexCount} exceeds maxTextureSize ${maxTextureSize}; row wrapping is not implemented`
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 Float32Array(vertexCount * totalFrames * 4);
237
- const nrmData = bakeNormals ? new Float32Array(vertexCount * totalFrames * 4) : null;
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
- let rowOffset = 0;
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
- for (let f = 0; f < frames; f++) {
263
- mixer.setTime(f / frames * clip.duration);
264
- root.updateMatrixWorld(true);
265
- const row = rowOffset + f;
266
- poseSkeletons(poses);
267
- if (!warnedNonUniformScale) {
268
- warnedNonUniformScale = warnOnNonUniformBoneScale(influencers);
269
- }
270
- for (const part of parts) {
271
- _partMatrix.multiplyMatrices(rootInverse, part.mesh.matrixWorld);
272
- const influences = part.mesh.morphTargetInfluences;
273
- const { morphPos, morphNrm, morphRelative, isSkinned, skinIndex, skinWeight } = part;
274
- const boneMatrices = part.pose?.matrices;
275
- const morphCount = morphCountOf(part);
276
- for (let v = 0; v < part.vertexCount; v++) {
277
- const vi = part.vertexStart + v;
278
- _p.fromBufferAttribute(part.basePos, v);
279
- if (bakeNormal) _n.fromBufferAttribute(part.baseNrm, v);
280
- if (influences && morphCount > 0) {
281
- if (!morphRelative) {
282
- _mb.fromBufferAttribute(part.basePos, v);
283
- if (bakeNormal) _mbn.fromBufferAttribute(part.baseNrm, v);
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);
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
- action.stop();
341
- clipTable.push({
342
- name: clip.name,
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, vertexCount, totalFrames),
363
- normalTexture: nrmData ? makeVATTexture(nrmData, vertexCount, totalFrames) : null,
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 USE_VERTEX_ENCODING = "bake this subtree with the vertex encoding (the default) instead";
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 Error(
558
- `three-vat: the rig encoding cannot bake this subtree: ${offences.join("; ")}. A slot stores a rotation, a translation and one scale, not a per-vertex delta \u2014 ${USE_VERTEX_ENCODING}`
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, morphPos, morphNrm, morphRelative } = part;
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
- for (let t = 0; t < statics.length; t++) {
575
- const w = statics[t];
576
- if (w === 0) continue;
577
- const targetPos = morphPos?.[t];
578
- if (targetPos) {
579
- _mt.fromBufferAttribute(targetPos, v);
580
- if (!morphRelative) _mt.sub(_mb.fromBufferAttribute(basePos, v));
581
- _p.addScaledVector(_mt, w);
582
- }
583
- const targetNrm = morphNrm?.[t];
584
- if (targetNrm) {
585
- _mt.fromBufferAttribute(targetNrm, v);
586
- if (!morphRelative) _mt.sub(_mb.fromBufferAttribute(baseNrm, v));
587
- _n.addScaledVector(_mt, w);
588
- }
589
- }
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 Error(
635
- `three-vat: ${label} animates with non-uniform scale in clip "${clip.name}", which the rig encoding cannot store \u2014 a slot is a rotation, a translation and one scale. ${USE_VERTEX_ENCODING}, or author it with a uniform scale.`
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 Error(
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 _q = new Quaternion();
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
- let rowOffset = 0;
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 (let f = 0; f < frames; f++) {
703
- mixer.setTime(f / frames * clip.duration);
704
- root.updateMatrixWorld(true);
705
- const row = rowOffset + f;
706
- poseSkeletons(poses);
707
- for (const [part, placement] of placements) placementOf(part, rootInverse, placement);
708
- for (let slot = 0; slot < slotCount; slot++) {
709
- const { rig, parts: readers } = slots[slot];
710
- const lead = readers[0];
711
- const placement = placements.get(lead);
712
- for (let pi = 1; pi < readers.length; pi++) {
713
- const other = readers[pi];
714
- if (!matricesClose(placement, placements.get(other))) {
715
- throw new Error(
716
- `three-vat: parts ${partName(lead)} and ${partName(other)} read the same slots but move apart in clip "${clip.name}"; the rig encoding gives them one set of slots, which cannot place them differently \u2014 ${USE_VERTEX_ENCODING}`
717
- );
718
- }
719
- }
720
- if (rig) {
721
- _slot.fromArray(rig.pose.matrices, rig.bone * BONE_STRIDE).multiply(rig.bindMatrix).premultiply(placement);
722
- } else {
723
- _slot.copy(placement);
724
- }
725
- slotMatrices.set(_slot.elements, slot * BONE_STRIDE);
726
- if (influenced[slot] && hasNonUniformScale(slotMatrices, slot)) {
727
- throw nonUniformScale(slotLabel(slots[slot]), clip);
728
- }
729
- _slot.decompose(_t, _q, _s);
730
- const p4 = slot * 4;
731
- if (previous[p4] * _q.x + previous[p4 + 1] * _q.y + previous[p4 + 2] * _q.z + previous[p4 + 3] * _q.w < 0) {
732
- _q.set(-_q.x, -_q.y, -_q.z, -_q.w);
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
- for (let v = 0; v < vertexCount; v++) {
751
- const o3 = v * 3;
752
- const o4 = v * 4;
753
- const x = localPosition[o3];
754
- const y = localPosition[o3 + 1];
755
- const z = localPosition[o3 + 2];
756
- let px = 0;
757
- let py = 0;
758
- let pz = 0;
759
- for (let i = 0; i < 4; i++) {
760
- const w = skinWeight[o4 + i];
761
- if (w === 0) continue;
762
- const m = skinIndex[o4 + i] * BONE_STRIDE;
763
- px += w * (slotMatrices[m] * x + slotMatrices[m + 4] * y + slotMatrices[m + 8] * z + slotMatrices[m + 12]);
764
- py += w * (slotMatrices[m + 1] * x + slotMatrices[m + 5] * y + slotMatrices[m + 9] * z + slotMatrices[m + 13]);
765
- pz += w * (slotMatrices[m + 2] * x + slotMatrices[m + 6] * y + slotMatrices[m + 10] * z + slotMatrices[m + 14]);
766
- }
767
- if (px < minX) minX = px;
768
- if (px > maxX) maxX = px;
769
- if (py < minY) minY = py;
770
- if (py > maxY) maxY = py;
771
- if (pz < minZ) minZ = pz;
772
- if (pz > maxZ) maxZ = pz;
773
- const dx = px - restRoot[o3];
774
- const dy = py - restRoot[o3 + 1];
775
- const dz = pz - restRoot[o3 + 2];
776
- const deltaSq = dx * dx + dy * dy + dz * dz;
777
- if (deltaSq > maxDeltaSq) maxDeltaSq = deltaSq;
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
- action.stop();
781
- clipTable.push({
782
- name: clip.name,
783
- startFrame: rowOffset,
784
- frames,
785
- fps: frames / clip.duration,
786
- duration: clip.duration,
787
- maxDelta: Math.sqrt(maxDeltaSq),
788
- ...resolved[ci].defaults
789
- });
790
- rowOffset += frames;
791
- });
792
- mixer.stopAllAction();
793
- mixer.setTime(0);
794
- root.updateMatrixWorld(true);
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 vi = start + v;
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
- if (uv) merged.setAttribute("uv", new BufferAttribute(uv, 2));
869
- if (color) merged.setAttribute("color", new BufferAttribute(color, 3));
870
- if (tangent) merged.setAttribute("tangent", new BufferAttribute(tangent, 4));
871
- indexParts(merged, parts);
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 };