three-vat 1.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -4
- package/dist/carrier-BXaLAPRO.d.ts +515 -0
- package/dist/chunk-3PWAY6MD.js +211 -0
- package/dist/chunk-I4STYOD5.js +53 -0
- package/dist/index.d.ts +113 -14
- package/dist/index.js +622 -68
- package/dist/tsl.d.ts +82 -47
- package/dist/tsl.js +176 -40
- package/dist/webgl.d.ts +31 -26
- package/dist/webgl.js +295 -35
- package/package.json +20 -14
- package/dist/chunk-SXTVASKG.js +0 -40
- package/dist/instance-playback-BrGBIKLe.d.ts +0 -109
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { FloatType, DataTexture, RGBAFormat, NearestFilter } from 'three';
|
|
2
|
+
|
|
3
|
+
// src/vat-texture.ts
|
|
4
|
+
var MAX_TEXTURE_SIZE = 16384;
|
|
5
|
+
function makeVATTexture(data, width, height, type = FloatType) {
|
|
6
|
+
const tex = new DataTexture(data, width, height, RGBAFormat, type);
|
|
7
|
+
tex.minFilter = NearestFilter;
|
|
8
|
+
tex.magFilter = NearestFilter;
|
|
9
|
+
tex.generateMipmaps = false;
|
|
10
|
+
tex.needsUpdate = true;
|
|
11
|
+
return tex;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/instance-playback.ts
|
|
15
|
+
var PACK_TEXELS = {
|
|
16
|
+
clip: 0,
|
|
17
|
+
playback: 1,
|
|
18
|
+
fade: 2
|
|
19
|
+
};
|
|
20
|
+
var PACK_WIDTH = 3;
|
|
21
|
+
var PACK_STRIDE = PACK_WIDTH * 4;
|
|
22
|
+
var LoopMode = {
|
|
23
|
+
/** Play the clip end to end, forever (or `repetitions` times). */
|
|
24
|
+
Repeat: 0,
|
|
25
|
+
/** Play the clip through once. */
|
|
26
|
+
Once: 1,
|
|
27
|
+
/** Play forward, then backward, without baking the reversed frames. */
|
|
28
|
+
PingPong: 2
|
|
29
|
+
};
|
|
30
|
+
var EndMode = {
|
|
31
|
+
/** Hold the last frame. */
|
|
32
|
+
Clamp: 0,
|
|
33
|
+
/** Return to the first frame. */
|
|
34
|
+
Rewind: 1
|
|
35
|
+
};
|
|
36
|
+
var INFINITE_REPETITIONS = -1;
|
|
37
|
+
var MAX_FADE_DURATION = 0.25;
|
|
38
|
+
function defaultRepetitions(loopMode) {
|
|
39
|
+
return loopMode === LoopMode.Repeat ? INFINITE_REPETITIONS : 1;
|
|
40
|
+
}
|
|
41
|
+
var LIBRARY_PLAYBACK_DEFAULTS = {
|
|
42
|
+
loopMode: LoopMode.Repeat,
|
|
43
|
+
repetitions: defaultRepetitions(LoopMode.Repeat),
|
|
44
|
+
endMode: EndMode.Clamp,
|
|
45
|
+
speed: 1
|
|
46
|
+
};
|
|
47
|
+
function resolvedPlaybackOf(instance) {
|
|
48
|
+
const { clip } = instance;
|
|
49
|
+
const clipLoopMode = clip.loopMode ?? LIBRARY_PLAYBACK_DEFAULTS.loopMode;
|
|
50
|
+
const loopMode = instance.loopMode ?? clipLoopMode;
|
|
51
|
+
const clipRepetitions = loopMode === clipLoopMode ? clip.repetitions : void 0;
|
|
52
|
+
return {
|
|
53
|
+
loopMode,
|
|
54
|
+
repetitions: instance.repetitions ?? clipRepetitions ?? defaultRepetitions(loopMode),
|
|
55
|
+
endMode: instance.endMode ?? clip.endMode ?? LIBRARY_PLAYBACK_DEFAULTS.endMode,
|
|
56
|
+
speed: instance.speed ?? clip.speed ?? LIBRARY_PLAYBACK_DEFAULTS.speed
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function fadeOf(instance) {
|
|
60
|
+
const duration = Math.min(instance.fadeDuration ?? 0, MAX_FADE_DURATION);
|
|
61
|
+
const from = instance.from;
|
|
62
|
+
if (!from || duration <= 0 || from.frames <= 0) return null;
|
|
63
|
+
return { from, duration };
|
|
64
|
+
}
|
|
65
|
+
function fadeRowOf(from) {
|
|
66
|
+
return from.startFrame + Math.max(Math.min(Math.floor(from.phase * from.frames), from.frames - 1), 0);
|
|
67
|
+
}
|
|
68
|
+
function resolveVATFrame(instance, time) {
|
|
69
|
+
const { clip } = instance;
|
|
70
|
+
const frames = clip.frames;
|
|
71
|
+
const last = frames - 1;
|
|
72
|
+
const duration = frames / clip.fps;
|
|
73
|
+
const { loopMode, repetitions, endMode, speed } = resolvedPlaybackOf(instance);
|
|
74
|
+
const local = (time - instance.startTime) * speed;
|
|
75
|
+
const loops = local / duration;
|
|
76
|
+
const started = local >= 0;
|
|
77
|
+
const finished = started && repetitions !== INFINITE_REPETITIONS && loops >= repetitions;
|
|
78
|
+
let phase;
|
|
79
|
+
let wraps;
|
|
80
|
+
if (!started) {
|
|
81
|
+
phase = 0;
|
|
82
|
+
wraps = false;
|
|
83
|
+
} else if (finished) {
|
|
84
|
+
phase = endMode === EndMode.Clamp ? 1 : 0;
|
|
85
|
+
wraps = false;
|
|
86
|
+
} else if (loopMode === LoopMode.PingPong) {
|
|
87
|
+
const m = loops % 2;
|
|
88
|
+
phase = m < 1 ? m : 2 - m;
|
|
89
|
+
wraps = false;
|
|
90
|
+
} else {
|
|
91
|
+
phase = loops % 1;
|
|
92
|
+
wraps = true;
|
|
93
|
+
}
|
|
94
|
+
const f = phase * (wraps ? frames : last);
|
|
95
|
+
const f0 = Math.min(Math.floor(f), last);
|
|
96
|
+
const f1 = wraps ? (f0 + 1) % frames : Math.min(f0 + 1, last);
|
|
97
|
+
const row = clip.startFrame + f0;
|
|
98
|
+
const fading = fadeOf(instance);
|
|
99
|
+
const elapsed = fading ? (time - instance.startTime) / fading.duration : 0;
|
|
100
|
+
return {
|
|
101
|
+
row,
|
|
102
|
+
rowNext: clip.startFrame + f1,
|
|
103
|
+
mix: f - f0,
|
|
104
|
+
wraps,
|
|
105
|
+
finished,
|
|
106
|
+
phase,
|
|
107
|
+
fadeRow: fading ? fadeRowOf(fading.from) : row,
|
|
108
|
+
fadeWeight: fading ? 1 - Math.min(Math.max(elapsed, 0), 1) : 0
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function createVATPlaybackTexture(instances) {
|
|
112
|
+
const count = instances.length;
|
|
113
|
+
if (count < 1) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
"three-vat: a crowd needs at least one instance \u2014 a playback texture is one row per instance, and there is no zero-row texture to carry none"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (count > MAX_TEXTURE_SIZE) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`three-vat: a crowd of ${count} instances needs ${count} rows of playback texture, past the ${MAX_TEXTURE_SIZE}-row ceiling \u2014 the playback texture holds one row per instance, so MAX_TEXTURE_SIZE is the instance ceiling.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const data = new Float32Array(count * PACK_STRIDE);
|
|
124
|
+
for (let i = 0; i < count; i++) writePack(data, i, instances[i]);
|
|
125
|
+
return { texture: makeVATTexture(data, PACK_WIDTH, count), count };
|
|
126
|
+
}
|
|
127
|
+
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";
|
|
128
|
+
var rowStart = (index) => index * PACK_STRIDE;
|
|
129
|
+
var texelStart = (index, field) => rowStart(index) + field * 4;
|
|
130
|
+
function writePack(data, index, instance) {
|
|
131
|
+
const clip = texelStart(index, PACK_TEXELS.clip);
|
|
132
|
+
const playback = texelStart(index, PACK_TEXELS.playback);
|
|
133
|
+
const fade = texelStart(index, PACK_TEXELS.fade);
|
|
134
|
+
const policy = resolvedPlaybackOf(instance);
|
|
135
|
+
if (policy.speed < 0) {
|
|
136
|
+
throw new Error(`three-vat: instance ${index} has speed ${policy.speed}; ${FORWARD_ONLY_REASON}.`);
|
|
137
|
+
}
|
|
138
|
+
data[clip] = instance.clip.startFrame;
|
|
139
|
+
data[clip + 1] = instance.clip.frames;
|
|
140
|
+
data[clip + 2] = instance.clip.fps;
|
|
141
|
+
data[clip + 3] = policy.speed;
|
|
142
|
+
data[playback] = instance.startTime;
|
|
143
|
+
data[playback + 1] = policy.loopMode;
|
|
144
|
+
data[playback + 2] = policy.repetitions;
|
|
145
|
+
data[playback + 3] = policy.endMode;
|
|
146
|
+
const fading = fadeOf(instance);
|
|
147
|
+
data[fade] = fading ? fading.from.startFrame : 0;
|
|
148
|
+
data[fade + 1] = fading ? fading.from.frames : 0;
|
|
149
|
+
data[fade + 2] = fading ? fading.from.phase : 0;
|
|
150
|
+
data[fade + 3] = fading ? fading.duration : 0;
|
|
151
|
+
}
|
|
152
|
+
function readPack(data, index) {
|
|
153
|
+
const clip = texelStart(index, PACK_TEXELS.clip);
|
|
154
|
+
const playback = texelStart(index, PACK_TEXELS.playback);
|
|
155
|
+
return {
|
|
156
|
+
clip: { startFrame: data[clip], frames: data[clip + 1], fps: data[clip + 2] },
|
|
157
|
+
startTime: data[playback],
|
|
158
|
+
speed: data[clip + 3],
|
|
159
|
+
loopMode: data[playback + 1],
|
|
160
|
+
repetitions: data[playback + 2],
|
|
161
|
+
endMode: data[playback + 3]
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function assertInstance(playback, index) {
|
|
165
|
+
if (!Number.isInteger(index) || index < 0 || index >= playback.count) {
|
|
166
|
+
throw new Error(`three-vat: instance ${index} is outside this crowd of ${playback.count}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function setVATInstance(playback, index, instance) {
|
|
170
|
+
assertInstance(playback, index);
|
|
171
|
+
const data = playback.texture.image.data;
|
|
172
|
+
const fading = instance.from === void 0 && (instance.fadeDuration ?? 0) > 0 ? { ...instance, from: freezeOf(data, index, instance.startTime) } : instance;
|
|
173
|
+
writePack(data, index, fading);
|
|
174
|
+
playback.texture.addUpdateRange(rowStart(index), PACK_STRIDE);
|
|
175
|
+
playback.texture.needsUpdate = true;
|
|
176
|
+
}
|
|
177
|
+
function freezeOf(data, index, time) {
|
|
178
|
+
const outgoing = readPack(data, index);
|
|
179
|
+
const { row } = resolveVATFrame(outgoing, time);
|
|
180
|
+
return {
|
|
181
|
+
startFrame: outgoing.clip.startFrame,
|
|
182
|
+
frames: outgoing.clip.frames,
|
|
183
|
+
// The row the instance is actually displaying at that moment — so a fade
|
|
184
|
+
// out of a finished one-shot freezes the end pose it was holding, not the
|
|
185
|
+
// first row of a clip it stopped playing seconds ago.
|
|
186
|
+
//
|
|
187
|
+
// Named as the phase at the *centre* of that row rather than the playback
|
|
188
|
+
// phase itself, because {@link fadeRowOf} is what reads it back and the two
|
|
189
|
+
// do not spread a phase the same way: a bouncing ping-pong is up to a row
|
|
190
|
+
// apart between them, and rounding in a shader could cost another. Half a
|
|
191
|
+
// row of slack costs nothing and lands all three decodes on this row.
|
|
192
|
+
phase: (row - outgoing.clip.startFrame + 0.5) / outgoing.clip.frames
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function endsAt(instance) {
|
|
196
|
+
const { repetitions, speed } = resolvedPlaybackOf(instance);
|
|
197
|
+
if (repetitions === INFINITE_REPETITIONS || speed <= 0) return null;
|
|
198
|
+
const duration = instance.clip.frames / instance.clip.fps;
|
|
199
|
+
return instance.startTime + duration * repetitions / speed;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/rig-texture.ts
|
|
203
|
+
var RIG_TEXELS = {
|
|
204
|
+
/** `(qx, qy, qz, qw)` — the slot's rotation, on one hemisphere with its neighbouring rows. */
|
|
205
|
+
rotation: 0,
|
|
206
|
+
/** `(tx, ty, tz, s)` — where the slot puts the origin, and its uniform scale in the spare component. */
|
|
207
|
+
placement: 1
|
|
208
|
+
};
|
|
209
|
+
var RIG_TEXELS_PER_SLOT = 2;
|
|
210
|
+
|
|
211
|
+
export { EndMode, FORWARD_ONLY_REASON, INFINITE_REPETITIONS, LIBRARY_PLAYBACK_DEFAULTS, LoopMode, MAX_FADE_DURATION, MAX_TEXTURE_SIZE, PACK_TEXELS, RIG_TEXELS, RIG_TEXELS_PER_SLOT, createVATPlaybackTexture, endsAt, makeVATTexture, resolveVATFrame, setVATInstance };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/baked-normals.ts
|
|
2
|
+
var NORMAL_READING_FLAGS = [
|
|
3
|
+
"isMeshStandardMaterial",
|
|
4
|
+
"isMeshPhongMaterial",
|
|
5
|
+
"isMeshLambertMaterial",
|
|
6
|
+
"isMeshToonMaterial",
|
|
7
|
+
"isMeshNormalMaterial",
|
|
8
|
+
"isMeshMatcapMaterial"
|
|
9
|
+
];
|
|
10
|
+
function needsBakedNormal(material) {
|
|
11
|
+
if (material.flatShading === true) return false;
|
|
12
|
+
const flags = material;
|
|
13
|
+
return NORMAL_READING_FLAGS.some((flag) => flags[flag] === true);
|
|
14
|
+
}
|
|
15
|
+
function assertBakedNormal(vat, material) {
|
|
16
|
+
if (vat.encoding !== "delta" || vat.normalTexture !== null || !needsBakedNormal(material)) return;
|
|
17
|
+
throw new Error(
|
|
18
|
+
`three-vat: material "${material.name || "(unnamed)"}" (${material.type}) shades from a normal, but this VAT was baked with \`bakeNormals: false\` and carries none \u2014 the crowd would be lit by its rest pose. Set \`flatShading: true\` on the material (three then derives the normal from the deformed position, per fragment, which is the right normal for a posed mesh), or use an unlit material such as MeshBasicMaterial, or bake with \`bakeNormals: true\`.`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/carrier.ts
|
|
23
|
+
function isBatchedCarrier(carrier) {
|
|
24
|
+
return carrier?.isBatchedMesh === true;
|
|
25
|
+
}
|
|
26
|
+
function rangeOf(batch, geometryId) {
|
|
27
|
+
try {
|
|
28
|
+
return batch.getGeometryRangeAt(geometryId);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function assertVATCarrier(carrier, vat) {
|
|
34
|
+
if (!isBatchedCarrier(carrier)) return;
|
|
35
|
+
if (rangeOf(carrier, 1)) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"three-vat: a BatchedMesh carrier must hold exactly one geometry \u2014 the VAT\u2019s own. Both decode paths index the VAT by the vertex index, which on a batch is the geometry\u2019s vertexStart plus the vertex, so a second geometry\u2019s instances read another character\u2019s rows. One VAT, one geometry, N instances: mixing characters in one draw needs one VAT texture each, and a sampler is a uniform per draw call."
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const range = rangeOf(carrier, 0);
|
|
41
|
+
if (!range) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
"three-vat: this BatchedMesh holds no geometry \u2014 add `vat.geometry` with `addGeometry` before patching a material for it."
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (range.vertexStart !== 0 || range.vertexCount !== vat.vertexCount) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`three-vat: this BatchedMesh\u2019s geometry spans ${range.vertexCount} vertices from ${range.vertexStart}, and the VAT has ${vat.vertexCount} from 0. The decode reads the VAT at the batch\u2019s own vertex index, so the batch must hold \`vat.geometry\` and nothing before it.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { assertBakedNormal, assertVATCarrier, isBatchedCarrier };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { V as VAT } from './
|
|
3
|
-
export { a as
|
|
1
|
+
import { AnimationClip, AnimationAction, Object3D, TypedArray, TextureDataType, DataTexture } from 'three';
|
|
2
|
+
import { D as DeltaVAT, R as RigVAT, V as VAT } from './carrier-BXaLAPRO.js';
|
|
3
|
+
export { E as EndMode, I as INFINITE_REPETITIONS, L as LoopMode, M as MAX_FADE_DURATION, a as VATBase, b as VATCarrier, c as VATClip, d as VATClipDefaults, e as VATClock, f as VATCrowd, g as VATFadeFrom, h as VATFrame, i as VATInstance, j as VATPlaybackTexture, k as createVATPlaybackTexture, l as endsAt, r as resolveVATFrame, s as setVATInstance } from './carrier-BXaLAPRO.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* What {@link bakeVAT} takes for each animation: the clip itself, or an
|
|
7
|
+
* `AnimationAction` already configured the way three taught you.
|
|
8
|
+
*
|
|
9
|
+
* An action costs the baker nothing — it builds an `AnimationMixer` to pose the
|
|
10
|
+
* mesh either way — and buys the caller per-clip defaults every instance of
|
|
11
|
+
* that clip inherits ({@link VATClipDefaults}).
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
type BakeInput = AnimationClip | AnimationAction;
|
|
14
14
|
interface BakeOptions {
|
|
15
15
|
/** Sample rate in frames per second. Default `30`. */
|
|
16
16
|
fps?: number;
|
|
@@ -21,10 +21,76 @@ interface BakeOptions {
|
|
|
21
21
|
* VAT that allocates on your desktop and fails on a phone.
|
|
22
22
|
*/
|
|
23
23
|
maxTextureSize?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Bake the normal texture. Default `true`.
|
|
26
|
+
*
|
|
27
|
+
* Turning it off halves the VAT — `verts x frames x 16 B x 2` becomes `x 1` —
|
|
28
|
+
* and is correct for exactly two material setups:
|
|
29
|
+
*
|
|
30
|
+
* - **Unlit** (`MeshBasicMaterial`, and its node twin), which never reads a
|
|
31
|
+
* normal, so the texture was pure waste.
|
|
32
|
+
* - **`flatShading: true`**, where three derives the normal from screen-space
|
|
33
|
+
* derivatives of the *deformed* position in the fragment stage. That is the
|
|
34
|
+
* correct normal for the posed mesh, computed for free — the baked one is
|
|
35
|
+
* not merely unnecessary there, it is redundant work.
|
|
36
|
+
*
|
|
37
|
+
* Anything else that shades — a smooth-shaded lit material — would light the
|
|
38
|
+
* crowd by its rest-pose normals, which is visibly wrong (ADR-0002). Both
|
|
39
|
+
* decode paths refuse that pairing loudly rather than render it.
|
|
40
|
+
*
|
|
41
|
+
* Accepted and ignored under the rig encoding, which has no normal texture to
|
|
42
|
+
* drop: normals come out of the skin matrix there. Refusing a no-op would
|
|
43
|
+
* punish the caller who switched encodings and left their options alone.
|
|
44
|
+
*/
|
|
45
|
+
bakeNormals?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* What a frame row holds (ADR-0018). Default `'delta'`.
|
|
48
|
+
*
|
|
49
|
+
* - **`'delta'`** — the vertex encoding: where every vertex ended up, as a
|
|
50
|
+
* position delta and a normal. Source-agnostic — skinning, morph targets
|
|
51
|
+
* and node animation alike — and the default.
|
|
52
|
+
* - **`'rig'`** — the rig encoding: the posed rig, one **slot** per bone as a
|
|
53
|
+
* rotation, a translation and a uniform scale, skinned in the vertex shader
|
|
54
|
+
* from the rest-pose geometry. Two orders of magnitude smaller, no vertex
|
|
55
|
+
* ceiling, and unable to store what a rig cannot express — refused at the
|
|
56
|
+
* bake by name, before a frame is sampled: a morph target whose influence a
|
|
57
|
+
* baked clip animates, or a bone (or rigid part) scaled unevenly. A morph
|
|
58
|
+
* influence no clip animates is folded into the rest pose; a rigid,
|
|
59
|
+
* node-animated part is one slot of weight one; parts reading the same
|
|
60
|
+
* bones through the same bind matrix share slots.
|
|
61
|
+
*/
|
|
62
|
+
encoding?: 'delta' | 'rig';
|
|
24
63
|
}
|
|
25
64
|
/**
|
|
26
|
-
* Bake
|
|
27
|
-
*
|
|
65
|
+
* Bake animations into a VAT by sampling the posed subtree frame by frame on
|
|
66
|
+
* the CPU.
|
|
67
|
+
*
|
|
68
|
+
* Each entry of `animations` is an `AnimationClip`, or an `AnimationAction`
|
|
69
|
+
* already configured the way three taught you:
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* const action = mixer.clipAction(deathClip)
|
|
73
|
+
* action.loop = THREE.LoopOnce
|
|
74
|
+
*
|
|
75
|
+
* const vat = bakeVAT(gltf.scene, [walkAction, action, idleClip])
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* Every instance that plays `death` then inherits "once, clamped" without the
|
|
79
|
+
* caller saying so again, and may still override any of it. An action costs the
|
|
80
|
+
* bake nothing — it builds an `AnimationMixer` to pose the mesh either way — and
|
|
81
|
+
* a plain clip carries no configuration, so the simple case still needs no
|
|
82
|
+
* mixer at all and takes the library defaults ({@link VATClipDefaults}).
|
|
83
|
+
*
|
|
84
|
+
* `loop`, `repetitions` and `timeScale` are read. **`clampWhenFinished` is
|
|
85
|
+
* not**: it is `false` on every untouched action, so a crowd reads it as the
|
|
86
|
+
* silence it usually is and clamps either way — an instance names
|
|
87
|
+
* `endMode: EndMode.Rewind` to get three's behaviour back.
|
|
88
|
+
* **`time` and `paused` are ignored**: they say where a playhead is sitting,
|
|
89
|
+
* not how the animation is meant to play, and a VAT has no playhead of its own
|
|
90
|
+
* to seed — every instance's position is a function of the shared clock and its
|
|
91
|
+
* own `startTime`. A non-unit `weight` and an additive `blendMode` are refused
|
|
92
|
+
* outright; both describe several actions blended at once, which one baked band
|
|
93
|
+
* cannot be.
|
|
28
94
|
*
|
|
29
95
|
* The unit of a bake is the whole subtree under `root`, merged into one vertex
|
|
30
96
|
* set and recorded in root space (ADR-0008) — so it handles a single
|
|
@@ -44,8 +110,41 @@ interface BakeOptions {
|
|
|
44
110
|
* approximation becomes visible, so the bake warns once, naming the bone.
|
|
45
111
|
* Renderer-agnostic — touches no WebGL/WebGPU context — so it runs identically
|
|
46
112
|
* at runtime, in a Web Worker, and in Node.
|
|
113
|
+
*
|
|
114
|
+
* All of that is the **vertex encoding**, the default. `encoding: 'rig'`
|
|
115
|
+
* stores the posed rig instead — one slot per bone, two texels, skinned in the
|
|
116
|
+
* vertex shader from the rest-pose geometry (ADR-0018) — under the same clip
|
|
117
|
+
* table and the same playback contract, at a fraction of the memory; see
|
|
118
|
+
* {@link BakeOptions.encoding} for what it refuses. Overloaded on the option's
|
|
119
|
+
* literal, so a call that names no encoding, or the vertex encoding, still
|
|
120
|
+
* holds the narrow {@link DeltaVAT} it always did.
|
|
47
121
|
*/
|
|
48
|
-
declare function bakeVAT(root: Object3D,
|
|
122
|
+
declare function bakeVAT(root: Object3D, animations: BakeInput[], options?: BakeOptions & {
|
|
123
|
+
encoding?: 'delta';
|
|
124
|
+
}): DeltaVAT;
|
|
125
|
+
declare function bakeVAT(root: Object3D, animations: BakeInput[], options: BakeOptions & {
|
|
126
|
+
encoding: 'rig';
|
|
127
|
+
}): RigVAT;
|
|
128
|
+
declare function bakeVAT(root: Object3D, animations: BakeInput[], options?: BakeOptions): VAT;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The WebGL2 *spec floor for high-end desktop*, which two things lean on.
|
|
132
|
+
*
|
|
133
|
+
* - **The bake's fallback cap**, used when the caller does not pass
|
|
134
|
+
* `maxTextureSize`. The baker is renderer-agnostic by design (it runs in
|
|
135
|
+
* Node, and in a Web Worker) so it cannot query the real limit itself: pass
|
|
136
|
+
* `getMaxTextureSize(renderer)` from `three-vat/webgl` or `three-vat/tsl`
|
|
137
|
+
* whenever a renderer exists.
|
|
138
|
+
* - **The instance ceiling**, because the playback texture is one row per
|
|
139
|
+
* instance (`createVATPlaybackTexture`, ADR-0016). That one takes no
|
|
140
|
+
* override, the crowd being built long after the bake was sized.
|
|
141
|
+
*
|
|
142
|
+
* Not a guarantee either way: plenty of mobile GPUs report 4096 or 8192, and
|
|
143
|
+
* a crowd between that and this number is refused by the driver at upload
|
|
144
|
+
* rather than here. The bake is where the real limit is worth passing, because
|
|
145
|
+
* it is where the numbers get large.
|
|
146
|
+
*/
|
|
147
|
+
declare const MAX_TEXTURE_SIZE = 16384;
|
|
49
148
|
/**
|
|
50
149
|
* Build a VAT `DataTexture` with the fixed sampling flags every path relies on:
|
|
51
150
|
* RGBA, nearest filtering, no mipmaps. Frame interpolation is done manually in
|
|
@@ -53,4 +152,4 @@ declare function bakeVAT(root: Object3D, clips: AnimationClip[], { fps, maxTextu
|
|
|
53
152
|
*/
|
|
54
153
|
declare function makeVATTexture(data: TypedArray, width: number, height: number, type?: TextureDataType): DataTexture;
|
|
55
154
|
|
|
56
|
-
export { type BakeOptions, MAX_TEXTURE_SIZE, VAT, bakeVAT, makeVATTexture };
|
|
155
|
+
export { type BakeInput, type BakeOptions, DeltaVAT, MAX_TEXTURE_SIZE, RigVAT, VAT, bakeVAT, makeVATTexture };
|