three-vat 4.0.0 → 4.2.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.
@@ -3,6 +3,20 @@ import { FloatType, DataTexture, RGBAFormat, RGFormat, UnsignedByteType, HalfFlo
3
3
  // src/vat-texture.ts
4
4
  var MAX_TEXTURE_SIZE = 16384;
5
5
  var HALF_FLOAT_MAX = 65504;
6
+ function vertexLayoutFor(vertexCount, totalFrames, maxTextureSize) {
7
+ const rowsPerFrame = Math.max(1, Math.ceil(vertexCount / maxTextureSize));
8
+ const width = vertexWidthOf({ vertexCount, rowsPerFrame });
9
+ const height = totalFrames * rowsPerFrame;
10
+ if (height > maxTextureSize) {
11
+ throw new Error(
12
+ `three-vat: totalFrames ${totalFrames} at ${rowsPerFrame} rows a frame is ${height} rows, which exceeds maxTextureSize ${maxTextureSize} \u2014 the vertex encoding spans each frame's ${vertexCount} vertices across ${rowsPerFrame} rows of ${width}; lower fps or bake fewer clips`
13
+ );
14
+ }
15
+ return { rowsPerFrame, width, height, frameStride: rowsPerFrame * width };
16
+ }
17
+ function vertexWidthOf({ vertexCount, rowsPerFrame }) {
18
+ return Math.ceil(vertexCount / rowsPerFrame);
19
+ }
6
20
  function sampledExactly(tex) {
7
21
  tex.minFilter = NearestFilter;
8
22
  tex.magFilter = NearestFilter;
@@ -77,6 +91,7 @@ function crossfadeOf(instance) {
77
91
  if (!from || !asksToBlend(instance)) return null;
78
92
  return { from, duration: instance.fadeDuration };
79
93
  }
94
+ var fadeStartOf = (instance) => instance.fadeStart ?? instance.startTime;
80
95
  var asksToBlend = (instance) => (instance.fadeDuration ?? 0) > 0;
81
96
  function resolveVATFrame(instance, time) {
82
97
  const { clip } = instance;
@@ -84,16 +99,14 @@ function resolveVATFrame(instance, time) {
84
99
  const last = frames - 1;
85
100
  const duration = frames / clip.fps;
86
101
  const { loopMode, repetitions, endMode, speed } = resolvedPlaybackOf(instance);
87
- const local = (time - instance.startTime) * speed;
88
- const loops = local / duration;
102
+ const reversed = speed < 0;
103
+ const local = (time - instance.startTime) * Math.abs(speed);
89
104
  const started = local >= 0;
105
+ const loops = Math.max(local, 0) / duration;
90
106
  const finished = started && repetitions !== INFINITE_REPETITIONS && loops >= repetitions;
91
107
  let phase;
92
108
  let looping;
93
- if (!started) {
94
- phase = 0;
95
- looping = false;
96
- } else if (finished) {
109
+ if (finished) {
97
110
  phase = endMode === EndMode.Clamp ? 1 : 0;
98
111
  looping = false;
99
112
  } else if (loopMode === LoopMode.PingPong) {
@@ -104,15 +117,18 @@ function resolveVATFrame(instance, time) {
104
117
  phase = loops % 1;
105
118
  looping = true;
106
119
  }
107
- const holds = looping && endMode === EndMode.Clamp && repetitions !== INFINITE_REPETITIONS && Math.floor(loops) + 1 >= repetitions;
120
+ const holds = looping && repetitions !== INFINITE_REPETITIONS && (reversed ? loops < 1 : endMode === EndMode.Clamp && Math.floor(loops) + 1 >= repetitions);
108
121
  const wraps = looping && !holds;
109
- const f = phase * (looping ? frames : last);
122
+ const mirrored = 1 - phase;
123
+ phase = reversed ? wraps && mirrored >= 1 ? 0 : mirrored : phase;
124
+ const spread = phase * (looping ? frames : last);
125
+ const f = holds ? Math.min(spread, last) : spread;
110
126
  const f0 = Math.min(Math.floor(f), last);
111
127
  const next = f0 + 1;
112
128
  const f1 = wraps ? next >= frames ? 0 : next : Math.min(next, last);
113
129
  const row = clip.startFrame + f0;
114
130
  const crossfade = crossfadeOf(instance);
115
- const elapsed = crossfade ? (time - instance.startTime) / crossfade.duration : 0;
131
+ const elapsed = crossfade ? (time - fadeStartOf(instance)) / crossfade.duration : 0;
116
132
  return {
117
133
  row,
118
134
  rowNext: clip.startFrame + f1,
@@ -155,16 +171,8 @@ function createVATPlaybackTexture(instances, options = {}) {
155
171
  for (let i = 0; i < count; i++) writePack(data, i, instances[i] ?? RESERVED_ROW);
156
172
  return { texture: makeVATTexture(data, PACK_WIDTH, count), count };
157
173
  }
158
- var FORWARD_ONLY_REASON = "a baked band plays forward from its own first row, so a negative speed would freeze it on that row rather than run it backwards \u2014 bake a reversed clip instead. A speed of 0 is a held first row, and is fine";
159
174
  var rowStart = (index) => index * PACK_STRIDE;
160
175
  var texelStart = (index, field) => rowStart(index) + field * 4;
161
- function checkedPolicyOf(state, index, what) {
162
- const policy = resolvedPlaybackOf(state);
163
- if (policy.speed < 0) {
164
- throw new Error(`three-vat: ${what} ${index} has speed ${policy.speed}; ${FORWARD_ONLY_REASON}.`);
165
- }
166
- return policy;
167
- }
168
176
  function putBand(data, index, at, state, policy) {
169
177
  const clip = texelStart(index, at.clip);
170
178
  const playback = texelStart(index, at.playback);
@@ -186,18 +194,24 @@ function checkedCrossfadeOf(instance, index) {
186
194
  `three-vat: instance ${index} has fadeDuration ${duration}; a transition lasts a finite number of seconds, and 0 (or no fadeDuration at all) is the cut.`
187
195
  );
188
196
  }
197
+ const start = instance.fadeStart;
198
+ if (start !== void 0 && !Number.isFinite(start)) {
199
+ throw new Error(
200
+ `three-vat: instance ${index} has fadeStart ${start}; a blend begins at a finite clock time, and no fadeStart at all begins it at startTime.`
201
+ );
202
+ }
189
203
  return crossfadeOf(instance);
190
204
  }
191
205
  function writePack(data, index, instance) {
192
206
  const crossfade = checkedCrossfadeOf(instance, index);
193
- const live = checkedPolicyOf(instance, index, "instance");
194
- const outgoing = crossfade ? { state: crossfade.from, policy: checkedPolicyOf(crossfade.from, index, "the outgoing band of instance") } : null;
207
+ const live = resolvedPlaybackOf(instance);
208
+ const outgoing = crossfade ? { state: crossfade.from, policy: resolvedPlaybackOf(crossfade.from) } : null;
195
209
  const crossfadeTexel = texelStart(index, PACK_TEXELS.crossfade);
196
210
  putBand(data, index, LIVE_PAIR, instance, live);
197
211
  if (outgoing) putBand(data, index, OUTGOING_PAIR, outgoing.state, outgoing.policy);
198
212
  else clearTexels(data, index, OUTGOING_PAIR);
199
213
  data[crossfadeTexel] = crossfade ? crossfade.duration : 0;
200
- data[crossfadeTexel + 1] = 0;
214
+ data[crossfadeTexel + 1] = fadeStartOf(instance);
201
215
  data[crossfadeTexel + 2] = 0;
202
216
  data[crossfadeTexel + 3] = 0;
203
217
  }
@@ -205,9 +219,9 @@ function clearTexels(data, index, at) {
205
219
  data.fill(0, texelStart(index, at.clip), texelStart(index, at.clip) + 4);
206
220
  data.fill(0, texelStart(index, at.playback), texelStart(index, at.playback) + 4);
207
221
  }
208
- function readPack(data, index) {
209
- const clip = texelStart(index, PACK_TEXELS.clip);
210
- const playback = texelStart(index, PACK_TEXELS.playback);
222
+ function readPack(data, index, at = LIVE_PAIR) {
223
+ const clip = texelStart(index, at.clip);
224
+ const playback = texelStart(index, at.playback);
211
225
  return {
212
226
  clip: { startFrame: data[clip], frames: data[clip + 1], fps: data[clip + 2] },
213
227
  startTime: data[playback],
@@ -217,6 +231,13 @@ function readPack(data, index) {
217
231
  endMode: data[playback + 3]
218
232
  };
219
233
  }
234
+ function readInstance(data, index) {
235
+ const live = readPack(data, index);
236
+ const crossfade = texelStart(index, PACK_TEXELS.crossfade);
237
+ const fadeDuration = data[crossfade];
238
+ if (fadeDuration === 0) return live;
239
+ return { ...live, from: readPack(data, index, OUTGOING_PAIR), fadeDuration, fadeStart: data[crossfade + 1] };
240
+ }
220
241
  function assertInstance(playback, index) {
221
242
  if (!Number.isInteger(index) || index < 0 || index >= playback.count) {
222
243
  throw new Error(`three-vat: instance ${index} is outside this crowd's ${playback.count} rows`);
@@ -227,14 +248,66 @@ function setVATInstance(playback, index, instance) {
227
248
  const data = playback.texture.image.data;
228
249
  const transitioning = instance.from === void 0 && asksToBlend(instance) ? { ...instance, from: readPack(data, index) } : instance;
229
250
  writePack(data, index, transitioning);
251
+ flagRow(playback, index);
252
+ }
253
+ function flagRow(playback, index) {
230
254
  playback.texture.addUpdateRange(rowStart(index), PACK_STRIDE);
231
255
  playback.texture.needsUpdate = true;
232
256
  }
257
+ function turnVATInstance(playback, index, time) {
258
+ assertInstance(playback, index);
259
+ const data = playback.texture.image.data;
260
+ const current = readInstance(data, index);
261
+ const { from, fadeDuration, fadeStart: _fadeStart, ...live } = current;
262
+ const outgoing = resolveVATFrame(current, time).outgoing;
263
+ const back = outgoing && outgoing.weight > 0 ? {
264
+ ...turnedAt(from, time, true),
265
+ from: turnedAt(live, time, true),
266
+ fadeDuration,
267
+ fadeStart: 2 * time - fadeStartOf(current) - fadeDuration
268
+ } : turnedAt(live, time);
269
+ writePack(data, index, back);
270
+ flagRow(playback, index);
271
+ return back;
272
+ }
273
+ function turnedAt(instance, time, retracesHold = false) {
274
+ const { loopMode, repetitions, endMode, speed } = resolvedPlaybackOf(instance);
275
+ const { startFrame, frames, fps } = instance.clip;
276
+ const clip = { startFrame, frames, fps };
277
+ const rate = Math.abs(speed);
278
+ if (rate === 0) return { clip, startTime: instance.startTime, speed, loopMode, repetitions, endMode };
279
+ const duration = frames / fps;
280
+ const shown = resolveVATFrame(instance, time);
281
+ const endless = repetitions === INFINITE_REPETITIONS;
282
+ const loops = (time - instance.startTime) * rate / duration;
283
+ const waiting = loops < 0;
284
+ const rewound = shown.finished && endMode === EndMode.Rewind;
285
+ const retraced = shown.finished ? repetitions : loops;
286
+ const play = (startTime, speed2, repetitions2) => ({
287
+ clip,
288
+ startTime,
289
+ speed: speed2,
290
+ loopMode,
291
+ repetitions: repetitions2,
292
+ endMode: EndMode.Clamp
293
+ });
294
+ if (waiting || rewound || !endless && retraced === 0) {
295
+ return play(time, shown.phase > 0.5 ? rate : -rate, 0);
296
+ }
297
+ const pingPong = loopMode === LoopMode.PingPong;
298
+ const lead = !pingPong && speed > 0 && endMode === EndMode.Rewind ? 1 : 0;
299
+ const count = endless ? INFINITE_REPETITIONS : pingPong ? 2 * Math.ceil((repetitions - 1) / 2) + 1 : Math.ceil(repetitions) + lead;
300
+ const retracedInBlend = retracesHold && shown.finished && count === repetitions ? loops : retraced;
301
+ const into = endless ? pingPong ? modulo(-retraced - 1, 2) : modulo(-retraced, 1) : count - retracedInBlend;
302
+ return play(time - into * duration / rate, -speed, count);
303
+ }
304
+ var modulo = (a, n) => (a % n + n) % n;
233
305
  function endsAt(instance) {
234
306
  const { repetitions, speed } = resolvedPlaybackOf(instance);
235
- if (repetitions === INFINITE_REPETITIONS || speed <= 0) return null;
307
+ const rate = Math.abs(speed);
308
+ if (repetitions === INFINITE_REPETITIONS || rate === 0) return null;
236
309
  const duration = instance.clip.frames / instance.clip.fps;
237
- return instance.startTime + duration * repetitions / speed;
310
+ return instance.startTime + duration * repetitions / rate;
238
311
  }
239
312
 
240
313
  // src/rig-texture.ts
@@ -246,4 +319,4 @@ var RIG_TEXELS = {
246
319
  };
247
320
  var RIG_TEXELS_PER_SLOT = 2;
248
321
 
249
- export { EndMode, FORWARD_ONLY_REASON, HALF_FLOAT_MAX, INFINITE_REPETITIONS, LIBRARY_PLAYBACK_DEFAULTS, LoopMode, MAX_TEXTURE_SIZE, PACK_TEXELS, RIG_TEXELS, RIG_TEXELS_PER_SLOT, createVATPlaybackTexture, endsAt, makeVATNormalTexture, makeVATTexture, resolveVATFrame, setVATInstance };
322
+ export { EndMode, HALF_FLOAT_MAX, INFINITE_REPETITIONS, LIBRARY_PLAYBACK_DEFAULTS, LoopMode, MAX_TEXTURE_SIZE, PACK_TEXELS, RIG_TEXELS, RIG_TEXELS_PER_SLOT, createVATPlaybackTexture, endsAt, makeVATNormalTexture, makeVATTexture, resolveVATFrame, setVATInstance, turnVATInstance, vertexLayoutFor, vertexWidthOf };
@@ -0,0 +1,263 @@
1
+ import { BAKED_FILE_EXTENSION, positionDigest, BAKED_FILE_VERSION, BAKED_LAYER_FORMATS, restSlotsOf } from './chunk-P6AFXEBT.js';
2
+ import { LoaderUtils, Mesh, BufferAttribute, Matrix4, Bone, SkinnedMesh, Skeleton, Group } from 'three';
3
+ import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
4
+
5
+ var IMAGE_EXTENSIONS = ["KHR_texture_basisu", "EXT_texture_webp", "EXT_texture_avif"];
6
+ var MIME_TYPES = {
7
+ png: "image/png",
8
+ jpg: "image/jpeg",
9
+ jpeg: "image/jpeg",
10
+ webp: "image/webp",
11
+ avif: "image/avif",
12
+ ktx2: "image/ktx2"
13
+ };
14
+ async function readSourceImages(parser, readFile = fetchBeside(parser)) {
15
+ const json = parser.json;
16
+ const read = /* @__PURE__ */ new Map();
17
+ const image = async (index) => {
18
+ const cached = read.get(index);
19
+ if (cached) return cached;
20
+ const def = json.images[index];
21
+ const bytes = def.bufferView !== void 0 ? new Uint8Array((await parser.getDependency("bufferView", def.bufferView)).slice(0)) : new Uint8Array(dataURI(def.uri) ?? await readFile(def.uri));
22
+ const mimeType = def.mimeType ?? mimeTypeOf(def.uri);
23
+ if (mimeType === void 0) {
24
+ const where = def.uri.startsWith("data:") ? "embedded" : def.uri;
25
+ throw new Error(`image ${index} (${where}) is of no type a baked file knows`);
26
+ }
27
+ const loaded = { bytes, mimeType, ...def.name ? { name: def.name } : {} };
28
+ read.set(index, loaded);
29
+ return loaded;
30
+ };
31
+ const images = /* @__PURE__ */ new Map();
32
+ for (const [object, reference] of parser.associations) {
33
+ const texture = object;
34
+ if (!texture.isTexture || reference.textures === void 0 || images.has(texture.source)) continue;
35
+ const def = json.textures[reference.textures];
36
+ const entry = { extensions: {}, required: [] };
37
+ if (def.source !== void 0) entry.source = await image(def.source);
38
+ for (const name of IMAGE_EXTENSIONS) {
39
+ const source = def.extensions?.[name]?.source;
40
+ if (source === void 0) continue;
41
+ entry.extensions[name] = await image(source);
42
+ if (json.extensionsRequired?.includes(name)) entry.required.push(name);
43
+ }
44
+ images.set(texture.source, entry);
45
+ }
46
+ return images;
47
+ }
48
+ function dataURI(uri) {
49
+ const data = /^data:([^,]*),(.*)$/s.exec(uri);
50
+ if (!data) return void 0;
51
+ if (/;base64$/.test(data[1])) return Uint8Array.from(atob(data[2]), (c) => c.charCodeAt(0)).buffer;
52
+ return Uint8Array.from(unescape(data[2]), (c) => c.charCodeAt(0)).buffer;
53
+ }
54
+ function fetchBeside(parser) {
55
+ const path = parser.options.path ?? "";
56
+ return async (uri) => {
57
+ const url = LoaderUtils.resolveURL(uri, path);
58
+ const response = await fetch(url);
59
+ if (!response.ok) throw new Error(`cannot fetch ${url}: ${response.status} ${response.statusText}`);
60
+ return response.arrayBuffer();
61
+ };
62
+ }
63
+ function mimeTypeOf(uri) {
64
+ const data = /^data:([^;,]+)/.exec(uri);
65
+ if (data) return data[1];
66
+ return MIME_TYPES[/\.([^./]+)$/.exec(uri)?.[1]?.toLowerCase() ?? ""];
67
+ }
68
+ var SLOTS = ["map", "normalMap", "emissiveMap", "aoMap", "metalnessMap", "roughnessMap"];
69
+ function strippedMaterials(materials, images) {
70
+ const originals = /* @__PURE__ */ new Map();
71
+ const stripped = materials.map((material) => {
72
+ const textured = material;
73
+ const keys = Object.keys(material).filter((key) => textured[key]?.isTexture);
74
+ if (keys.length === 0) return material;
75
+ for (const key of keys) refuseUncarriedTexture(material, key, textured[key], images);
76
+ const { metalnessMap, roughnessMap } = textured;
77
+ if (metalnessMap && roughnessMap && metalnessMap.source !== roughnessMap.source) {
78
+ throw new Error(
79
+ `three-vat: material "${material.name}" reads metalness and roughness from two images, which glTF packs into one; a baked file carries them as its source packed them`
80
+ );
81
+ }
82
+ const copy = material.clone();
83
+ for (const key of keys) copy[key] = null;
84
+ originals.set(copy, textured);
85
+ return copy;
86
+ });
87
+ return { stripped, originals };
88
+ }
89
+ function refuseUncarriedTexture(material, key, texture, images) {
90
+ if (!SLOTS.includes(key)) {
91
+ throw new Error(
92
+ `three-vat: material "${material.name}" carries a texture in ${key}, which a baked file does not carry; it carries ${SLOTS.join(", ")}`
93
+ );
94
+ }
95
+ if (!images.has(texture.source)) {
96
+ throw new Error(
97
+ `three-vat: material "${material.name}" carries a texture (${key}) whose image was not read from a glTF source, so there are no bytes to copy into a baked file`
98
+ );
99
+ }
100
+ }
101
+ var BakedMaterialsWriter = class {
102
+ constructor(writer, originals, sources) {
103
+ this.writer = writer;
104
+ this.originals = originals;
105
+ this.sources = sources;
106
+ this.name = "three-vat:materials";
107
+ this.images = /* @__PURE__ */ new Map();
108
+ this.textures = /* @__PURE__ */ new Map();
109
+ }
110
+ async writeMaterialAsync(material, def) {
111
+ const original = this.originals.get(material);
112
+ if (original === void 0) return;
113
+ const { map, normalMap, emissiveMap, aoMap, metalnessMap, roughnessMap } = original;
114
+ def.pbrMetallicRoughness ?? (def.pbrMetallicRoughness = {});
115
+ if (map) def.pbrMetallicRoughness.baseColorTexture = this.info(map);
116
+ const metalRough = metalnessMap ?? roughnessMap;
117
+ if (metalRough) def.pbrMetallicRoughness.metallicRoughnessTexture = this.info(metalRough);
118
+ if (normalMap) {
119
+ def.normalTexture = this.info(normalMap);
120
+ const scale = original.normalScale.x;
121
+ if (scale !== 1) def.normalTexture.scale = scale;
122
+ }
123
+ if (emissiveMap) def.emissiveTexture = this.info(emissiveMap);
124
+ if (aoMap) {
125
+ def.occlusionTexture = this.info(aoMap);
126
+ if (original.aoMapIntensity !== 1) def.occlusionTexture.strength = original.aoMapIntensity;
127
+ }
128
+ }
129
+ info(texture) {
130
+ const info = { index: this.texture(texture), texCoord: texture.channel };
131
+ this.writer.applyTextureTransform(info, texture);
132
+ return info;
133
+ }
134
+ /** A texture definition: the source's image layout, with this map's sampler. */
135
+ texture(texture) {
136
+ var _a;
137
+ const { writer } = this;
138
+ const { magFilter, minFilter, wrapS, wrapT } = texture;
139
+ const key = [texture.source.uuid, texture.name, magFilter, minFilter, wrapS, wrapT].join(":");
140
+ const cached = this.textures.get(key);
141
+ if (cached !== void 0) return cached;
142
+ const source = this.sources.get(texture.source);
143
+ const def = {
144
+ sampler: writer.processSampler(texture)
145
+ };
146
+ if (texture.name) def.name = texture.name;
147
+ if (source.source) def.source = this.image(source.source);
148
+ for (const [name, image] of Object.entries(source.extensions)) {
149
+ def.extensions = { ...def.extensions, [name]: { source: this.image(image) } };
150
+ writer.extensionsUsed[name] = true;
151
+ if (source.required.includes(name)) writer.extensionsRequired[name] = true;
152
+ }
153
+ const index = ((_a = writer.json).textures ?? (_a.textures = [])).push(def) - 1;
154
+ this.textures.set(key, index);
155
+ return index;
156
+ }
157
+ /**
158
+ * An image: its bytes as they were, in a buffer view of its own. Not the
159
+ * exporter's `processBufferViewImage`, whose view spans the padding to four
160
+ * bytes as well, so the image it holds would end in up to three zeros.
161
+ */
162
+ image(image) {
163
+ var _a, _b;
164
+ const cached = this.images.get(image);
165
+ if (cached !== void 0) return cached;
166
+ const { writer } = this;
167
+ const padded = new Uint8Array(Math.ceil(image.bytes.byteLength / 4) * 4);
168
+ padded.set(image.bytes);
169
+ const view = { buffer: writer.processBuffer(padded.buffer), byteOffset: writer.byteOffset, byteLength: image.bytes.byteLength };
170
+ writer.byteOffset += padded.byteLength;
171
+ const bufferView = ((_a = writer.json).bufferViews ?? (_a.bufferViews = [])).push(view) - 1;
172
+ const def = { mimeType: image.mimeType, bufferView };
173
+ if (image.name) def.name = image.name;
174
+ const index = ((_b = writer.json).images ?? (_b.images = [])).push(def) - 1;
175
+ this.images.set(image, index);
176
+ return index;
177
+ }
178
+ };
179
+ var KEPT = ["MeshStandardMaterial", "MeshPhysicalMaterial", "MeshBasicMaterial"];
180
+ var LOST = {
181
+ MeshPhongMaterial: ["specular", "shininess"]
182
+ };
183
+ function pbrConversions(materials) {
184
+ return materials.filter((material) => !KEPT.includes(material.type)).map((material) => ({ name: material.name, from: material.type, lost: LOST[material.type] ?? [] }));
185
+ }
186
+ async function writeBakedFile(vat, { images = /* @__PURE__ */ new Map() } = {}) {
187
+ const { stripped, originals } = strippedMaterials(vat.materials, images);
188
+ const mesh = vat.encoding === "rig" ? previewSkin(vat, stripped) : new Mesh(vat.geometry, stripped);
189
+ const exporter = new GLTFExporter().register((writer) => new VATExtensionWriter(writer, vat)).register((writer) => new BakedMaterialsWriter(writer, originals, images));
190
+ const glb = await exporter.parseAsync(mesh, { binary: true });
191
+ return new Uint8Array(glb);
192
+ }
193
+ var VATExtensionWriter = class {
194
+ constructor(writer, vat) {
195
+ this.writer = writer;
196
+ this.vat = vat;
197
+ this.name = BAKED_FILE_EXTENSION;
198
+ this.mesh = -1;
199
+ }
200
+ writeMesh() {
201
+ this.mesh = this.writer.json.meshes?.length ?? 0;
202
+ }
203
+ afterParse() {
204
+ const { vat, writer } = this;
205
+ const common = {
206
+ version: BAKED_FILE_VERSION,
207
+ mesh: this.mesh,
208
+ groups: vat.geometry.groups.map(({ start, count, materialIndex }) => ({ start, count, materialIndex })),
209
+ vertexCount: vat.vertexCount,
210
+ totalFrames: vat.totalFrames,
211
+ clips: vat.clips,
212
+ bounds: { min: vat.bounds.min.toArray(), max: vat.bounds.max.toArray() },
213
+ digest: positionDigest(vat.geometry.attributes.position)
214
+ };
215
+ let extension;
216
+ if (vat.encoding === "rig") {
217
+ extension = {
218
+ ...common,
219
+ encoding: "rig",
220
+ slotCount: vat.slotCount,
221
+ layers: { rig: this.layer(vat.rigTexture, "RGBA32F") }
222
+ };
223
+ } else {
224
+ extension = {
225
+ ...common,
226
+ encoding: "delta",
227
+ fallback: vat.fallback,
228
+ rowsPerFrame: vat.rowsPerFrame,
229
+ layers: { position: this.layer(vat.positionTexture, "RGBA16F") }
230
+ };
231
+ if (vat.normalTexture) extension.layers.normal = this.layer(vat.normalTexture, "RG8");
232
+ }
233
+ writer.json.extensions = { ...writer.json.extensions, [BAKED_FILE_EXTENSION]: extension };
234
+ writer.extensionsUsed[BAKED_FILE_EXTENSION] = true;
235
+ }
236
+ /** One texture's texels as a buffer view, written as the integers or floats they are. */
237
+ layer(texture, format) {
238
+ const data = texture.image.data;
239
+ const { componentType } = BAKED_LAYER_FORMATS[format];
240
+ const view = this.writer.processBufferView(new BufferAttribute(data, 1), componentType, 0, data.length);
241
+ return { bufferView: view.id, width: texture.image.width, height: texture.image.height, format };
242
+ }
243
+ };
244
+ function previewSkin(vat, materials) {
245
+ const rest = restSlotsOf(vat);
246
+ if (rest === void 0) {
247
+ throw new Error(
248
+ "three-vat: this rig-encoded VAT does not know its slots' rest matrices, which its baked file's preview skin is built from. Only a VAT bakeVAT or bakeVATInWorker returned carries them, not one loadVAT read; bake the asset and write that"
249
+ );
250
+ }
251
+ const matrix = new Matrix4();
252
+ const joints = Array.from({ length: vat.slotCount }, (_, slot) => {
253
+ const joint = new Bone();
254
+ joint.name = `slot ${slot}`;
255
+ matrix.fromArray(rest, slot * 16).decompose(joint.position, joint.quaternion, joint.scale);
256
+ return joint;
257
+ });
258
+ const mesh = new SkinnedMesh(vat.geometry, materials);
259
+ mesh.bind(new Skeleton(joints, joints.map(() => new Matrix4())), new Matrix4());
260
+ return new Group().add(mesh, ...joints);
261
+ }
262
+
263
+ export { dataURI, pbrConversions, readSourceImages, writeBakedFile };