three-vat 2.0.0 → 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 CHANGED
@@ -81,6 +81,11 @@ Two things worth knowing the first time:
81
81
  - **Materials are never merged.** A 500-robot crowd with 3 materials is 3 draw
82
82
  calls — not 1, and not 500. VAT collapses instance count, not material count.
83
83
 
84
+ **A skinned character?** There is a second encoding: `{ encoding: 'rig' }` bakes
85
+ the posed rig instead of the posed vertices — two orders of magnitude less
86
+ texture, a bake in milliseconds, and faster on a phone, for an asset a rig can
87
+ express. [The rig encoding](./docs/usage.md#the-rig-encoding-encoding-rig).
88
+
84
89
  <details>
85
90
  <summary><b>Does it work with my model?</b></summary>
86
91
 
@@ -374,7 +374,9 @@ interface VATClip extends VATClipDefaults {
374
374
  /** Source clip duration in seconds. */
375
375
  duration: number;
376
376
  /**
377
- * Largest per-vertex position-delta magnitude (metres) across the clip.
377
+ * Largest displacement (metres) of any vertex from the rest pose across the
378
+ * clip — the same number under either encoding, because the rig bake skins
379
+ * every vertex on the CPU for the bounds anyway and measures it there.
378
380
  * Near-zero means the clip baked as a frozen pose — the diagnostic for a
379
381
  * mis-targeted or genuinely static clip.
380
382
  */
@@ -396,22 +398,14 @@ interface VATClip extends VATClipDefaults {
396
398
  * change it in a minor release. Move the buffer, hand it to {@link makeVATTexture};
397
399
  * do not read numbers out of it.
398
400
  */
399
- interface VAT {
400
- /** RGBA float texture of per-vertex position deltas (`x = vertex`, `y = frame`). */
401
- positionTexture: DataTexture;
402
- /**
403
- * RGBA float texture of per-vertex absolute normals (`x = vertex`, `y = frame`),
404
- * or `null` when the bake was told to skip it (`bakeNormals: false`) — halving
405
- * the VAT for a crowd that never reads a normal. Neither decode path samples
406
- * it when it is absent; a smooth-shaded lit material paired with such a VAT is
407
- * refused rather than lit by its rest pose.
408
- */
409
- normalTexture: DataTexture | null;
401
+ interface VATBase {
410
402
  /**
411
- * Merged, root-space rest-pose geometry. Its `position` is the delta
412
- * reference. Carries `normal` always, and `uv`, `color` and `tangent` when
413
- * every source mesh carried them; skinning attributes and morph targets are
414
- * dropped, the VAT having replaced them.
403
+ * Merged rest-pose geometry, in whichever space the encoding stores it: root
404
+ * space under the vertex encoding, where its `position` is the delta
405
+ * reference; each part's own local space under the rig encoding, where the
406
+ * slot carries the placement and also keeps `skinIndex` and `skinWeight`.
407
+ * Carries `normal` always, and `uv`, `color` and `tangent` when every source
408
+ * mesh carried them.
415
409
  */
416
410
  geometry: BufferGeometry;
417
411
  /** Source materials, indexed by `geometry.groups[].materialIndex`. */
@@ -424,9 +418,58 @@ interface VAT {
424
418
  vertexCount: number;
425
419
  /** Total frame rows across all clips (texture height). */
426
420
  totalFrames: number;
427
- /** Position encoding. Only `'delta'` in v1. */
421
+ }
422
+ /**
423
+ * A VAT under the **vertex encoding**: a row holds where every vertex ended up,
424
+ * as a position delta and, unless the bake was told to skip it, a normal. The
425
+ * source-agnostic encoding (ADR-0008) and the default; the member every bake
426
+ * produced before there was a second one (ADR-0018).
427
+ */
428
+ interface DeltaVAT extends VATBase {
429
+ /** Which encoding a row holds — the discriminant of {@link VAT}. */
428
430
  encoding: 'delta';
431
+ /** RGBA float texture of per-vertex position deltas (`x = vertex`, `y = frame`). */
432
+ positionTexture: DataTexture;
433
+ /**
434
+ * RGBA float texture of per-vertex absolute normals (`x = vertex`, `y = frame`),
435
+ * or `null` when the bake was told to skip it (`bakeNormals: false`) — halving
436
+ * the VAT for a crowd that never reads a normal. Neither decode path samples
437
+ * it when it is absent; a smooth-shaded lit material paired with such a VAT is
438
+ * refused rather than lit by its rest pose.
439
+ */
440
+ normalTexture: DataTexture | null;
429
441
  }
442
+ /**
443
+ * A VAT under the **rig encoding** (ADR-0018): a row holds the posed rig — one
444
+ * **slot** per bone, as a rotation, a translation and a uniform scale — and the
445
+ * vertex shader skins the rest-pose geometry from it. Two orders of magnitude
446
+ * smaller than the vertex encoding and bake-cheap, at the price of source
447
+ * agnosticism: what a rig cannot express is refused at the bake.
448
+ *
449
+ * No normal texture, and none missing: normals and tangents come out of the
450
+ * skin matrix, as in three's own skinning.
451
+ */
452
+ interface RigVAT extends VATBase {
453
+ /** Which encoding a row holds — the discriminant of {@link VAT}. */
454
+ encoding: 'rig';
455
+ /**
456
+ * RGBA float **rig texture**: two texels per slot — `(qx, qy, qz, qw)` then
457
+ * `(tx, ty, tz, s)` — laid out as `x = slot × 2 + texel`, `y = frame`, clips
458
+ * stacked as bands exactly as in the position texture. Consecutive rows of
459
+ * one slot sit on one quaternion hemisphere; the decode still flips the
460
+ * second row onto the first's side, because a looping clip blends a band's
461
+ * last row into its first and those are not neighbours.
462
+ */
463
+ rigTexture: DataTexture;
464
+ /** Slots in the rig — the texture is `slotCount × 2` texels wide. */
465
+ slotCount: number;
466
+ }
467
+ /**
468
+ * A baked VAT, discriminated on `encoding`. A decode that samples a texture
469
+ * narrows on `encoding` first, so an encoding it does not decode is refused by
470
+ * name rather than sampled as a texture the VAT does not have (ADR-0018).
471
+ */
472
+ type VAT = DeltaVAT | RigVAT;
430
473
  /**
431
474
  * The shared playback clock: one `{ value }` in seconds, read by every material
432
475
  * of every VAT mesh driven by it. Set it once per frame. Deliberately the
@@ -469,4 +512,4 @@ interface VATCrowd {
469
512
  */
470
513
  type VATCarrier = InstancedMesh | BatchedMesh;
471
514
 
472
- export { EndMode as E, INFINITE_REPETITIONS as I, LoopMode as L, MAX_FADE_DURATION as M, type VAT as V, type VATCarrier as a, type VATClip as b, type VATClipDefaults as c, type VATClock as d, type VATCrowd as e, type VATFadeFrom as f, type VATFrame as g, type VATInstance as h, type VATPlaybackTexture as i, createVATPlaybackTexture as j, endsAt as k, resolveVATFrame as r, setVATInstance as s };
515
+ export { type DeltaVAT as D, EndMode as E, INFINITE_REPETITIONS as I, LoopMode as L, MAX_FADE_DURATION as M, type RigVAT as R, type VAT as V, type VATBase as a, type VATCarrier as b, type VATClip as c, type VATClipDefaults as d, type VATClock as e, type VATCrowd as f, type VATFadeFrom as g, type VATFrame as h, type VATInstance as i, type VATPlaybackTexture as j, createVATPlaybackTexture as k, endsAt as l, resolveVATFrame as r, setVATInstance as s };
@@ -199,4 +199,13 @@ function endsAt(instance) {
199
199
  return instance.startTime + duration * repetitions / speed;
200
200
  }
201
201
 
202
- export { EndMode, FORWARD_ONLY_REASON, INFINITE_REPETITIONS, LIBRARY_PLAYBACK_DEFAULTS, LoopMode, MAX_FADE_DURATION, MAX_TEXTURE_SIZE, PACK_TEXELS, createVATPlaybackTexture, endsAt, makeVATTexture, resolveVATFrame, setVATInstance };
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 };
@@ -13,7 +13,7 @@ function needsBakedNormal(material) {
13
13
  return NORMAL_READING_FLAGS.some((flag) => flags[flag] === true);
14
14
  }
15
15
  function assertBakedNormal(vat, material) {
16
- if (vat.normalTexture !== null || !needsBakedNormal(material)) return;
16
+ if (vat.encoding !== "delta" || vat.normalTexture !== null || !needsBakedNormal(material)) return;
17
17
  throw new Error(
18
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
19
  );
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-BFCPmcQK.js';
3
- export { E as EndMode, I as INFINITE_REPETITIONS, L as LoopMode, M as MAX_FADE_DURATION, a as VATCarrier, b as VATClip, c as VATClipDefaults, d as VATClock, e as VATCrowd, f as VATFadeFrom, g as VATFrame, h as VATInstance, i as VATPlaybackTexture, j as createVATPlaybackTexture, k as endsAt, r as resolveVATFrame, s as setVATInstance } from './carrier-BFCPmcQK.js';
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
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[], { fps, maxTextureSize, bakeNormals }?: BakeOptions): VAT;
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 };