three-vat 4.1.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.
@@ -91,6 +91,7 @@ function crossfadeOf(instance) {
91
91
  if (!from || !asksToBlend(instance)) return null;
92
92
  return { from, duration: instance.fadeDuration };
93
93
  }
94
+ var fadeStartOf = (instance) => instance.fadeStart ?? instance.startTime;
94
95
  var asksToBlend = (instance) => (instance.fadeDuration ?? 0) > 0;
95
96
  function resolveVATFrame(instance, time) {
96
97
  const { clip } = instance;
@@ -98,16 +99,14 @@ function resolveVATFrame(instance, time) {
98
99
  const last = frames - 1;
99
100
  const duration = frames / clip.fps;
100
101
  const { loopMode, repetitions, endMode, speed } = resolvedPlaybackOf(instance);
101
- const local = (time - instance.startTime) * speed;
102
- const loops = local / duration;
102
+ const reversed = speed < 0;
103
+ const local = (time - instance.startTime) * Math.abs(speed);
103
104
  const started = local >= 0;
105
+ const loops = Math.max(local, 0) / duration;
104
106
  const finished = started && repetitions !== INFINITE_REPETITIONS && loops >= repetitions;
105
107
  let phase;
106
108
  let looping;
107
- if (!started) {
108
- phase = 0;
109
- looping = false;
110
- } else if (finished) {
109
+ if (finished) {
111
110
  phase = endMode === EndMode.Clamp ? 1 : 0;
112
111
  looping = false;
113
112
  } else if (loopMode === LoopMode.PingPong) {
@@ -118,15 +117,18 @@ function resolveVATFrame(instance, time) {
118
117
  phase = loops % 1;
119
118
  looping = true;
120
119
  }
121
- 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);
122
121
  const wraps = looping && !holds;
123
- 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;
124
126
  const f0 = Math.min(Math.floor(f), last);
125
127
  const next = f0 + 1;
126
128
  const f1 = wraps ? next >= frames ? 0 : next : Math.min(next, last);
127
129
  const row = clip.startFrame + f0;
128
130
  const crossfade = crossfadeOf(instance);
129
- const elapsed = crossfade ? (time - instance.startTime) / crossfade.duration : 0;
131
+ const elapsed = crossfade ? (time - fadeStartOf(instance)) / crossfade.duration : 0;
130
132
  return {
131
133
  row,
132
134
  rowNext: clip.startFrame + f1,
@@ -169,16 +171,8 @@ function createVATPlaybackTexture(instances, options = {}) {
169
171
  for (let i = 0; i < count; i++) writePack(data, i, instances[i] ?? RESERVED_ROW);
170
172
  return { texture: makeVATTexture(data, PACK_WIDTH, count), count };
171
173
  }
172
- 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";
173
174
  var rowStart = (index) => index * PACK_STRIDE;
174
175
  var texelStart = (index, field) => rowStart(index) + field * 4;
175
- function checkedPolicyOf(state, index, what) {
176
- const policy = resolvedPlaybackOf(state);
177
- if (policy.speed < 0) {
178
- throw new Error(`three-vat: ${what} ${index} has speed ${policy.speed}; ${FORWARD_ONLY_REASON}.`);
179
- }
180
- return policy;
181
- }
182
176
  function putBand(data, index, at, state, policy) {
183
177
  const clip = texelStart(index, at.clip);
184
178
  const playback = texelStart(index, at.playback);
@@ -200,18 +194,24 @@ function checkedCrossfadeOf(instance, index) {
200
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.`
201
195
  );
202
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
+ }
203
203
  return crossfadeOf(instance);
204
204
  }
205
205
  function writePack(data, index, instance) {
206
206
  const crossfade = checkedCrossfadeOf(instance, index);
207
- const live = checkedPolicyOf(instance, index, "instance");
208
- 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;
209
209
  const crossfadeTexel = texelStart(index, PACK_TEXELS.crossfade);
210
210
  putBand(data, index, LIVE_PAIR, instance, live);
211
211
  if (outgoing) putBand(data, index, OUTGOING_PAIR, outgoing.state, outgoing.policy);
212
212
  else clearTexels(data, index, OUTGOING_PAIR);
213
213
  data[crossfadeTexel] = crossfade ? crossfade.duration : 0;
214
- data[crossfadeTexel + 1] = 0;
214
+ data[crossfadeTexel + 1] = fadeStartOf(instance);
215
215
  data[crossfadeTexel + 2] = 0;
216
216
  data[crossfadeTexel + 3] = 0;
217
217
  }
@@ -219,9 +219,9 @@ function clearTexels(data, index, at) {
219
219
  data.fill(0, texelStart(index, at.clip), texelStart(index, at.clip) + 4);
220
220
  data.fill(0, texelStart(index, at.playback), texelStart(index, at.playback) + 4);
221
221
  }
222
- function readPack(data, index) {
223
- const clip = texelStart(index, PACK_TEXELS.clip);
224
- 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);
225
225
  return {
226
226
  clip: { startFrame: data[clip], frames: data[clip + 1], fps: data[clip + 2] },
227
227
  startTime: data[playback],
@@ -231,6 +231,13 @@ function readPack(data, index) {
231
231
  endMode: data[playback + 3]
232
232
  };
233
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
+ }
234
241
  function assertInstance(playback, index) {
235
242
  if (!Number.isInteger(index) || index < 0 || index >= playback.count) {
236
243
  throw new Error(`three-vat: instance ${index} is outside this crowd's ${playback.count} rows`);
@@ -241,14 +248,66 @@ function setVATInstance(playback, index, instance) {
241
248
  const data = playback.texture.image.data;
242
249
  const transitioning = instance.from === void 0 && asksToBlend(instance) ? { ...instance, from: readPack(data, index) } : instance;
243
250
  writePack(data, index, transitioning);
251
+ flagRow(playback, index);
252
+ }
253
+ function flagRow(playback, index) {
244
254
  playback.texture.addUpdateRange(rowStart(index), PACK_STRIDE);
245
255
  playback.texture.needsUpdate = true;
246
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;
247
305
  function endsAt(instance) {
248
306
  const { repetitions, speed } = resolvedPlaybackOf(instance);
249
- if (repetitions === INFINITE_REPETITIONS || speed <= 0) return null;
307
+ const rate = Math.abs(speed);
308
+ if (repetitions === INFINITE_REPETITIONS || rate === 0) return null;
250
309
  const duration = instance.clip.frames / instance.clip.fps;
251
- return instance.startTime + duration * repetitions / speed;
310
+ return instance.startTime + duration * repetitions / rate;
252
311
  }
253
312
 
254
313
  // src/rig-texture.ts
@@ -260,4 +319,4 @@ var RIG_TEXELS = {
260
319
  };
261
320
  var RIG_TEXELS_PER_SLOT = 2;
262
321
 
263
- 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, vertexLayoutFor, vertexWidthOf };
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 };