three-vat 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/{carrier-BFCPmcQK.d.ts → carrier-BxRuW47G.d.ts} +194 -100
- package/dist/{chunk-W2ZAFMPB.js → chunk-I4STYOD5.js} +1 -1
- package/dist/{chunk-2PGZ44TP.js → chunk-J5IEUGSB.js} +80 -56
- package/dist/index.d.ts +39 -4
- package/dist/index.js +499 -35
- package/dist/tsl.d.ts +107 -27
- package/dist/tsl.js +185 -69
- package/dist/webgl.d.ts +89 -7
- package/dist/webgl.js +370 -60
- package/package.json +1 -1
|
@@ -15,9 +15,11 @@ function makeVATTexture(data, width, height, type = FloatType) {
|
|
|
15
15
|
var PACK_TEXELS = {
|
|
16
16
|
clip: 0,
|
|
17
17
|
playback: 1,
|
|
18
|
-
|
|
18
|
+
crossfade: 2,
|
|
19
|
+
outgoingClip: 3,
|
|
20
|
+
outgoingPlayback: 4
|
|
19
21
|
};
|
|
20
|
-
var PACK_WIDTH =
|
|
22
|
+
var PACK_WIDTH = 5;
|
|
21
23
|
var PACK_STRIDE = PACK_WIDTH * 4;
|
|
22
24
|
var LoopMode = {
|
|
23
25
|
/** Play the clip end to end, forever (or `repetitions` times). */
|
|
@@ -34,7 +36,6 @@ var EndMode = {
|
|
|
34
36
|
Rewind: 1
|
|
35
37
|
};
|
|
36
38
|
var INFINITE_REPETITIONS = -1;
|
|
37
|
-
var MAX_FADE_DURATION = 0.25;
|
|
38
39
|
function defaultRepetitions(loopMode) {
|
|
39
40
|
return loopMode === LoopMode.Repeat ? INFINITE_REPETITIONS : 1;
|
|
40
41
|
}
|
|
@@ -56,15 +57,12 @@ function resolvedPlaybackOf(instance) {
|
|
|
56
57
|
speed: instance.speed ?? clip.speed ?? LIBRARY_PLAYBACK_DEFAULTS.speed
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
|
-
function
|
|
60
|
-
const duration = Math.min(instance.fadeDuration ?? 0, MAX_FADE_DURATION);
|
|
60
|
+
function crossfadeOf(instance) {
|
|
61
61
|
const from = instance.from;
|
|
62
|
-
if (!from ||
|
|
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);
|
|
62
|
+
if (!from || !asksToBlend(instance)) return null;
|
|
63
|
+
return { from, duration: instance.fadeDuration };
|
|
67
64
|
}
|
|
65
|
+
var asksToBlend = (instance) => (instance.fadeDuration ?? 0) > 0;
|
|
68
66
|
function resolveVATFrame(instance, time) {
|
|
69
67
|
const { clip } = instance;
|
|
70
68
|
const frames = clip.frames;
|
|
@@ -95,8 +93,8 @@ function resolveVATFrame(instance, time) {
|
|
|
95
93
|
const f0 = Math.min(Math.floor(f), last);
|
|
96
94
|
const f1 = wraps ? (f0 + 1) % frames : Math.min(f0 + 1, last);
|
|
97
95
|
const row = clip.startFrame + f0;
|
|
98
|
-
const
|
|
99
|
-
const elapsed =
|
|
96
|
+
const crossfade = crossfadeOf(instance);
|
|
97
|
+
const elapsed = crossfade ? (time - instance.startTime) / crossfade.duration : 0;
|
|
100
98
|
return {
|
|
101
99
|
row,
|
|
102
100
|
rowNext: clip.startFrame + f1,
|
|
@@ -104,50 +102,85 @@ function resolveVATFrame(instance, time) {
|
|
|
104
102
|
wraps,
|
|
105
103
|
finished,
|
|
106
104
|
phase,
|
|
107
|
-
|
|
108
|
-
fadeWeight: fading ? 1 - Math.min(Math.max(elapsed, 0), 1) : 0
|
|
105
|
+
outgoing: crossfade ? { ...resolveVATFrame(crossfade.from, time), weight: 1 - Math.min(Math.max(elapsed, 0), 1) } : null
|
|
109
106
|
};
|
|
110
107
|
}
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
var RESERVED_ROW = {
|
|
109
|
+
clip: { startFrame: 0, frames: 1, fps: 1 },
|
|
110
|
+
startTime: 0,
|
|
111
|
+
speed: 0
|
|
112
|
+
};
|
|
113
|
+
function createVATPlaybackTexture(instances, options = {}) {
|
|
114
|
+
const live = instances.length;
|
|
115
|
+
const count = options.capacity ?? live;
|
|
116
|
+
if (count < live) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`three-vat: a capacity of ${count} cannot hold the ${live} instances it was given \u2014 capacity is the crowd ceiling, so it is at least the crowd you start with.`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
113
121
|
if (count < 1) {
|
|
114
122
|
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"
|
|
123
|
+
"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. Pass a capacity to reserve rows for a crowd that has not spawned yet"
|
|
116
124
|
);
|
|
117
125
|
}
|
|
118
126
|
if (count > MAX_TEXTURE_SIZE) {
|
|
119
127
|
throw new Error(
|
|
120
|
-
`three-vat:
|
|
128
|
+
`three-vat: ${count} rows of playback texture is 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
129
|
);
|
|
122
130
|
}
|
|
123
131
|
const data = new Float32Array(count * PACK_STRIDE);
|
|
124
|
-
for (let i = 0; i < count; i++) writePack(data, i, instances[i]);
|
|
132
|
+
for (let i = 0; i < count; i++) writePack(data, i, instances[i] ?? RESERVED_ROW);
|
|
125
133
|
return { texture: makeVATTexture(data, PACK_WIDTH, count), count };
|
|
126
134
|
}
|
|
127
135
|
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
136
|
var rowStart = (index) => index * PACK_STRIDE;
|
|
129
137
|
var texelStart = (index, field) => rowStart(index) + field * 4;
|
|
130
|
-
function
|
|
131
|
-
const
|
|
132
|
-
const playback = texelStart(index, PACK_TEXELS.playback);
|
|
133
|
-
const fade = texelStart(index, PACK_TEXELS.fade);
|
|
134
|
-
const policy = resolvedPlaybackOf(instance);
|
|
138
|
+
function checkedPolicyOf(state, index, what) {
|
|
139
|
+
const policy = resolvedPlaybackOf(state);
|
|
135
140
|
if (policy.speed < 0) {
|
|
136
|
-
throw new Error(`three-vat:
|
|
141
|
+
throw new Error(`three-vat: ${what} ${index} has speed ${policy.speed}; ${FORWARD_ONLY_REASON}.`);
|
|
137
142
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
return policy;
|
|
144
|
+
}
|
|
145
|
+
function putBand(data, index, at, state, policy) {
|
|
146
|
+
const clip = texelStart(index, at.clip);
|
|
147
|
+
const playback = texelStart(index, at.playback);
|
|
148
|
+
data[clip] = state.clip.startFrame;
|
|
149
|
+
data[clip + 1] = state.clip.frames;
|
|
150
|
+
data[clip + 2] = state.clip.fps;
|
|
141
151
|
data[clip + 3] = policy.speed;
|
|
142
|
-
data[playback] =
|
|
152
|
+
data[playback] = state.startTime;
|
|
143
153
|
data[playback + 1] = policy.loopMode;
|
|
144
154
|
data[playback + 2] = policy.repetitions;
|
|
145
155
|
data[playback + 3] = policy.endMode;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
}
|
|
157
|
+
var LIVE_PAIR = { clip: PACK_TEXELS.clip, playback: PACK_TEXELS.playback };
|
|
158
|
+
var OUTGOING_PAIR = { clip: PACK_TEXELS.outgoingClip, playback: PACK_TEXELS.outgoingPlayback };
|
|
159
|
+
function checkedCrossfadeOf(instance, index) {
|
|
160
|
+
const duration = instance.fadeDuration;
|
|
161
|
+
if (duration !== void 0 && !(Number.isFinite(duration) && duration >= 0)) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`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.`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return crossfadeOf(instance);
|
|
167
|
+
}
|
|
168
|
+
function writePack(data, index, instance) {
|
|
169
|
+
const crossfade = checkedCrossfadeOf(instance, index);
|
|
170
|
+
const live = checkedPolicyOf(instance, index, "instance");
|
|
171
|
+
const outgoing = crossfade ? { state: crossfade.from, policy: checkedPolicyOf(crossfade.from, index, "the outgoing band of instance") } : null;
|
|
172
|
+
const crossfadeTexel = texelStart(index, PACK_TEXELS.crossfade);
|
|
173
|
+
putBand(data, index, LIVE_PAIR, instance, live);
|
|
174
|
+
if (outgoing) putBand(data, index, OUTGOING_PAIR, outgoing.state, outgoing.policy);
|
|
175
|
+
else clearTexels(data, index, OUTGOING_PAIR);
|
|
176
|
+
data[crossfadeTexel] = crossfade ? crossfade.duration : 0;
|
|
177
|
+
data[crossfadeTexel + 1] = 0;
|
|
178
|
+
data[crossfadeTexel + 2] = 0;
|
|
179
|
+
data[crossfadeTexel + 3] = 0;
|
|
180
|
+
}
|
|
181
|
+
function clearTexels(data, index, at) {
|
|
182
|
+
data.fill(0, texelStart(index, at.clip), texelStart(index, at.clip) + 4);
|
|
183
|
+
data.fill(0, texelStart(index, at.playback), texelStart(index, at.playback) + 4);
|
|
151
184
|
}
|
|
152
185
|
function readPack(data, index) {
|
|
153
186
|
const clip = texelStart(index, PACK_TEXELS.clip);
|
|
@@ -163,35 +196,17 @@ function readPack(data, index) {
|
|
|
163
196
|
}
|
|
164
197
|
function assertInstance(playback, index) {
|
|
165
198
|
if (!Number.isInteger(index) || index < 0 || index >= playback.count) {
|
|
166
|
-
throw new Error(`three-vat: instance ${index} is outside this crowd
|
|
199
|
+
throw new Error(`three-vat: instance ${index} is outside this crowd's ${playback.count} rows`);
|
|
167
200
|
}
|
|
168
201
|
}
|
|
169
202
|
function setVATInstance(playback, index, instance) {
|
|
170
203
|
assertInstance(playback, index);
|
|
171
204
|
const data = playback.texture.image.data;
|
|
172
|
-
const
|
|
173
|
-
writePack(data, index,
|
|
205
|
+
const transitioning = instance.from === void 0 && asksToBlend(instance) ? { ...instance, from: readPack(data, index) } : instance;
|
|
206
|
+
writePack(data, index, transitioning);
|
|
174
207
|
playback.texture.addUpdateRange(rowStart(index), PACK_STRIDE);
|
|
175
208
|
playback.texture.needsUpdate = true;
|
|
176
209
|
}
|
|
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
210
|
function endsAt(instance) {
|
|
196
211
|
const { repetitions, speed } = resolvedPlaybackOf(instance);
|
|
197
212
|
if (repetitions === INFINITE_REPETITIONS || speed <= 0) return null;
|
|
@@ -199,4 +214,13 @@ function endsAt(instance) {
|
|
|
199
214
|
return instance.startTime + duration * repetitions / speed;
|
|
200
215
|
}
|
|
201
216
|
|
|
202
|
-
|
|
217
|
+
// src/rig-texture.ts
|
|
218
|
+
var RIG_TEXELS = {
|
|
219
|
+
/** `(qx, qy, qz, qw)` — the slot's rotation, on one hemisphere with its neighbouring rows. */
|
|
220
|
+
rotation: 0,
|
|
221
|
+
/** `(tx, ty, tz, s)` — where the slot puts the origin, and its uniform scale in the spare component. */
|
|
222
|
+
placement: 1
|
|
223
|
+
};
|
|
224
|
+
var RIG_TEXELS_PER_SLOT = 2;
|
|
225
|
+
|
|
226
|
+
export { EndMode, FORWARD_ONLY_REASON, INFINITE_REPETITIONS, LIBRARY_PLAYBACK_DEFAULTS, LoopMode, MAX_TEXTURE_SIZE, PACK_TEXELS, RIG_TEXELS, RIG_TEXELS_PER_SLOT, createVATPlaybackTexture, endsAt, makeVATTexture, resolveVATFrame, setVATInstance };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnimationClip, AnimationAction, Object3D, TypedArray, TextureDataType, DataTexture } from 'three';
|
|
2
|
-
import { V as VAT } from './carrier-
|
|
3
|
-
export { E as EndMode, I as INFINITE_REPETITIONS, L as LoopMode,
|
|
2
|
+
import { D as DeltaVAT, R as RigVAT, V as VAT } from './carrier-BxRuW47G.js';
|
|
3
|
+
export { E as EndMode, I as INFINITE_REPETITIONS, L as LoopMode, a as VATBase, b as VATCarrier, c as VATClip, d as VATClipDefaults, e as VATClock, f as VATCrowd, g as VATFrame, h as VATInstance, i as VATOutgoingFrame, j as VATPlaybackState, k as VATPlaybackTexture, l as VATPlaybackTextureOptions, m as createVATPlaybackTexture, n as endsAt, r as resolveVATFrame, s as setVATInstance } from './carrier-BxRuW47G.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* What {@link bakeVAT} takes for each animation: the clip itself, or an
|
|
@@ -37,8 +37,29 @@ interface BakeOptions {
|
|
|
37
37
|
* Anything else that shades — a smooth-shaded lit material — would light the
|
|
38
38
|
* crowd by its rest-pose normals, which is visibly wrong (ADR-0002). Both
|
|
39
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.
|
|
40
44
|
*/
|
|
41
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';
|
|
42
63
|
}
|
|
43
64
|
/**
|
|
44
65
|
* Bake animations into a VAT by sampling the posed subtree frame by frame on
|
|
@@ -89,8 +110,22 @@ interface BakeOptions {
|
|
|
89
110
|
* approximation becomes visible, so the bake warns once, naming the bone.
|
|
90
111
|
* Renderer-agnostic — touches no WebGL/WebGPU context — so it runs identically
|
|
91
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.
|
|
92
121
|
*/
|
|
93
|
-
declare function bakeVAT(root: Object3D, animations: BakeInput[],
|
|
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;
|
|
94
129
|
|
|
95
130
|
/**
|
|
96
131
|
* The WebGL2 *spec floor for high-end desktop*, which two things lean on.
|
|
@@ -117,4 +152,4 @@ declare const MAX_TEXTURE_SIZE = 16384;
|
|
|
117
152
|
*/
|
|
118
153
|
declare function makeVATTexture(data: TypedArray, width: number, height: number, type?: TextureDataType): DataTexture;
|
|
119
154
|
|
|
120
|
-
export { type BakeInput, type BakeOptions, MAX_TEXTURE_SIZE, VAT, bakeVAT, makeVATTexture };
|
|
155
|
+
export { type BakeInput, type BakeOptions, DeltaVAT, MAX_TEXTURE_SIZE, RigVAT, VAT, bakeVAT, makeVATTexture };
|