@viggle/splat-engine-three 0.0.0-stage → 0.1.1

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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +450 -2
  3. package/dist/chunks/chunk-WN5WBKM4.js +8606 -0
  4. package/dist/chunks/chunk-WN5WBKM4.js.map +7 -0
  5. package/dist/index.js +56 -0
  6. package/dist/index.js.map +7 -0
  7. package/dist/types/anim/animation-conversion.d.ts +25 -0
  8. package/dist/types/anim/bind-pose.d.ts +15 -0
  9. package/dist/types/anim/keyframe-splines.d.ts +59 -0
  10. package/dist/types/anim/rest-relative.d.ts +32 -0
  11. package/dist/types/anim/spline.d.ts +14 -0
  12. package/dist/types/animation.d.ts +216 -0
  13. package/dist/types/armature.d.ts +524 -0
  14. package/dist/types/character.d.ts +582 -0
  15. package/dist/types/engine.d.ts +166 -0
  16. package/dist/types/environment.d.ts +53 -0
  17. package/dist/types/index.d.ts +40 -0
  18. package/dist/types/lighting/color.d.ts +13 -0
  19. package/dist/types/lighting/point-light.d.ts +33 -0
  20. package/dist/types/lighting/sky-light.d.ts +22 -0
  21. package/dist/types/lighting/sun-light.d.ts +34 -0
  22. package/dist/types/loader/animation/animation-glb.d.ts +117 -0
  23. package/dist/types/loader/character/index.d.ts +33 -0
  24. package/dist/types/loader/character/legacy/index.d.ts +34 -0
  25. package/dist/types/loader/character/legacy/interleaved-splat.d.ts +101 -0
  26. package/dist/types/loader/character/legacy/planar-splat.d.ts +139 -0
  27. package/dist/types/loader/character/load.d.ts +33 -0
  28. package/dist/types/loader/character/types.d.ts +129 -0
  29. package/dist/types/loader/character/v1/decode.d.ts +32 -0
  30. package/dist/types/loader/environment/ply.d.ts +16 -0
  31. package/dist/types/loader/shared/bin-cache.d.ts +19 -0
  32. package/dist/types/loader/skeleton/index.d.ts +19 -0
  33. package/dist/types/loader/skeleton/skeleton-data-441.d.ts +5 -0
  34. package/dist/types/loader/skeleton/skeleton-data.d.ts +6 -0
  35. package/dist/types/math.d.ts +82 -0
  36. package/dist/types/skeletal/animation-player.d.ts +185 -0
  37. package/dist/types/skeletal/bone-mask.d.ts +27 -0
  38. package/dist/types/skeletal/bone-stretch.d.ts +65 -0
  39. package/dist/types/skeletal/pose.d.ts +27 -0
  40. package/dist/types/skeletal/skeleton.d.ts +57 -0
  41. package/dist/types/skeletal/splat-skinning.d.ts +69 -0
  42. package/dist/types/splat/backend.d.ts +57 -0
  43. package/dist/types/splat/bone-binding.d.ts +38 -0
  44. package/dist/types/splat/depth-sort.d.ts +76 -0
  45. package/dist/types/splat/importance.d.ts +60 -0
  46. package/dist/types/splat/packed-splats.d.ts +36 -0
  47. package/dist/types/splat/prune.d.ts +60 -0
  48. package/dist/types/splat/quality.d.ts +175 -0
  49. package/dist/types/splat/splat-mesh.d.ts +71 -0
  50. package/dist/types/splat/splat.d.ts +252 -0
  51. package/dist/types/splat/transform-palette.d.ts +42 -0
  52. package/dist/types/splat/webgl/backend.d.ts +17 -0
  53. package/dist/types/splat/webgl/shaders.d.ts +2 -0
  54. package/dist/types/splat/webgl/splat-mesh.d.ts +54 -0
  55. package/dist/types/splat/webgpu/backend.d.ts +25 -0
  56. package/dist/types/splat/webgpu/lighting.d.ts +34 -0
  57. package/dist/types/splat/webgpu/radix-sort.d.ts +46 -0
  58. package/dist/types/splat/webgpu/splat-mesh.d.ts +73 -0
  59. package/dist/types/three/loaders.d.ts +77 -0
  60. package/dist/types/three/splat-character.d.ts +220 -0
  61. package/dist/types/types.d.ts +30 -0
  62. package/dist/types/webgpu.d.ts +5 -0
  63. package/dist/webgpu.js +843 -0
  64. package/dist/webgpu.js.map +7 -0
  65. package/package.json +46 -4
@@ -0,0 +1,582 @@
1
+ import { Vector3 } from 'three';
2
+ import type { Object3D } from 'three';
3
+ import { Armature } from './armature';
4
+ import type { SplatEngine } from './engine';
5
+ import type { CharacterBundle } from './loader/character/load';
6
+ import type { AnimEventCallback } from './skeletal/animation-player';
7
+ import type { BodyRegion, BoneMask } from './skeletal/bone-mask';
8
+ import { Splat } from './splat/splat';
9
+ /**
10
+ * Options for {@link Character.load}.
11
+ *
12
+ * @group Characters & Animation
13
+ */
14
+ export interface LoadCharacterOptions {
15
+ /**
16
+ * Name used for the armature and any embedded animation registered on the
17
+ * engine. Defaults to the file basename (stripped of extension).
18
+ */
19
+ name?: string;
20
+ /** Initial position, `y` at GROUND CONTACT - same convention as
21
+ * {@link Character.setPosition}. Defaults to the origin. */
22
+ position?: {
23
+ x: number;
24
+ y: number;
25
+ z: number;
26
+ };
27
+ /**
28
+ * Which rest translations pose the rig.
29
+ *
30
+ * - `'auto'` (default) - `'bundled'` for a bundle carrying a correction
31
+ * receipt, `'builtin'` for one without. Apps should pass nothing: the
32
+ * discriminator lives in the file, so the decision belongs here rather than
33
+ * in each consuming app, where the copies would otherwise drift apart.
34
+ * - `'builtin'` - the canonical std-male tables from the skeleton library.
35
+ * Every character is posed on one shared skeleton.
36
+ * - `'bundled'` - the per-character `joints` array for BODY bones, canonical
37
+ * for `FACIAL_*` ones and for toes. See `keepsCanonicalRest` for the
38
+ * measurements behind both exclusions.
39
+ *
40
+ * The explicit values exist for the comparison scenes, which show one
41
+ * character both ways on purpose.
42
+ *
43
+ * The body tables disagree, and not subtly: on a sample character 81 of 86
44
+ * body bones differ by more than 1 mm, worst 36 mm at `foot_r`, with the
45
+ * canonical limbs uniformly SHORTER (thigh -14.7 mm, calf -36.4 mm). That
46
+ * leaves the canonical knee ~36 mm high and the canonical foot floating
47
+ * ~72 mm off the ground on a character whose own skeleton reaches it.
48
+ *
49
+ * At rest the choice is invisible - skinning cancels `worldPose · inverseBind`
50
+ * to identity either way. It only shows once a joint ROTATES, because the
51
+ * rotation happens about the chosen pivot: a splat at the true knee swung
52
+ * about a centre 36 mm away lands ~51 mm off through a 90° bend.
53
+ *
54
+ * Clips are authored on the canonical rig - their body translations match
55
+ * `REST_TRANSLATIONS_441` to 0.0007 mm - which USED to make a canonical bind
56
+ * the only one that agreed with the pose, and `'bundled'` a diagnostic.
57
+ * That is no longer so: a bone whose translation never changes across a clip
58
+ * is now read as a rest declaration and left to the character
59
+ * (`Animation`'s `translationMask`, derived for `.anim`, GLB and the
60
+ * `.bin`-embedded clip alike). Playback is effectively rotation-only for
61
+ * every bone the clip does not actually translate - 341 of 342 on the body
62
+ * rig, 829 of 1185 on the full one - so `'bundled'` now inherits the
63
+ * character's own lengths instead of having them overwritten.
64
+ *
65
+ * Bundles carry translations only, never rotations, so `'bundled'` yields
66
+ * per-character bone LENGTHS with canonical joint ORIENTATIONS - a hybrid.
67
+ * Falls back to `'builtin'` when the bundle has no joints (older files) or
68
+ * the array length disagrees with the bone count. A 119-bone expression
69
+ * bundle keeps its first 86 body rest translations when posed on Body86;
70
+ * its 33 facial influences are attached to the head separately.
71
+ */
72
+ restSource?: 'auto' | 'builtin' | 'bundled';
73
+ }
74
+ export declare class Character {
75
+ /** Render-LOD level pyramid - when more than one entry, only one is
76
+ * enabled at a time (the active level). `splats[0]` is always the
77
+ * highest-fidelity (L0). Single-LOD characters have `splats.length === 1`. */
78
+ splats: Splat[];
79
+ /**
80
+ * The currently-rendered splat. For single-bin characters this is
81
+ * always `splats[0]`; for multi-LOD characters it tracks the active
82
+ * render-LOD level and flips with `setRenderLod` / `setRenderLevel`.
83
+ *
84
+ * Safe for read-only queries (`.numSplats`, `.object3D`, `.isInFrustum`,
85
+ * etc.). For **state mutations** that should persist across LOD swaps
86
+ * (tint, sort, visibility) prefer the Character-level helpers
87
+ * ({@link setTint}, {@link requestSort}) - writing to a single splat
88
+ * here only affects whichever level is active right now and the change
89
+ * vanishes when the active level swaps.
90
+ */
91
+ get splat(): Splat;
92
+ private _countLodEnabled;
93
+ private _countLodNearDist;
94
+ private _countLodFarDist;
95
+ private _countLodNearFraction;
96
+ private _countLodFarFraction;
97
+ private _renderLodEnabled;
98
+ private _renderLodNearDist;
99
+ private _renderLodFarDist;
100
+ private _activeLevel;
101
+ private _variantSplats;
102
+ /** Whether this character is live (ticking + rendering). Toggled by
103
+ * {@link setActive} for object pooling - a deactivated character keeps all
104
+ * its GPU resources (splats, textures, compiled shaders) but stops
105
+ * updating and rendering, so it can be reactivated at a new position with
106
+ * none of the ~tens-of-ms `new Splat` setup cost. */
107
+ private _active;
108
+ /** Set by {@link destroy}. Async loaders that resolve afterwards (LOD
109
+ * upgrades, variant splats) check this so they don't re-add splats that
110
+ * nothing owns or ticks anymore - without it, a character destroyed while
111
+ * a finer LOD is still downloading leaks that splat into the scene as a
112
+ * frozen, orphaned figure. */
113
+ private _destroyed;
114
+ /** True once {@link destroy} has run. Progressive-load choreography
115
+ * (coarse-first upgrade loops) should stop streaming when this flips. */
116
+ get destroyed(): boolean;
117
+ private _bakedGroundShiftY;
118
+ /**
119
+ * The ground shift a `VIGGCHAR` v1 bake folded into this character, in
120
+ * metres. `0` for legacy `.bin` bundles and for anything without one.
121
+ *
122
+ * Diagnostics and ground-snap tooling only. {@link setPosition},
123
+ * {@link getPosition} and {@link Character.load}'s `position` option already
124
+ * apply it, so game code should not need to read this - and code that
125
+ * measures ground contact empirically (a {@link getLowestWorldY} probe) must
126
+ * NOT apply it, because such a probe observes the shift's result already and
127
+ * would double-correct.
128
+ */
129
+ get bakedGroundShiftY(): number;
130
+ /**
131
+ * Convert a ground-contact Y - the convention {@link setPosition} takes -
132
+ * into the raw Y to write on `armature.object3D`.
133
+ *
134
+ * For hot paths that must bypass {@link setPosition} and write the pivot
135
+ * directly (a per-frame sync with a pooled `Vec3`, say). Everything else
136
+ * should use {@link setPosition} and never see this.
137
+ */
138
+ groundToEntityY(groundY: number): number;
139
+ /**
140
+ * Exact inverse of {@link groundToEntityY}: a raw `armature.object3D` Y back
141
+ * into the ground-contact convention.
142
+ *
143
+ * Read positions back through this (or through {@link getPosition}) so that a
144
+ * read/modify/write round-trip - a motor integrating velocity, a tween
145
+ * sampling its own start value - can never apply the shift twice.
146
+ */
147
+ entityToGroundY(entityY: number): number;
148
+ private _updateHandler;
149
+ /** Skeleton + animation controller. */
150
+ armature: Armature;
151
+ private _engine;
152
+ /**
153
+ * The three.js object for the whole character - the armature's pivot, with
154
+ * the character's splats parented under it. Already added to the engine's
155
+ * content root; reparent it if you need the character under something else.
156
+ */
157
+ get object3D(): Object3D;
158
+ constructor(armature: Armature, splats: Splat[], engine: SplatEngine);
159
+ /**
160
+ * Load a character bundle and add it to the engine's content root.
161
+ *
162
+ * The bundle's bone count selects the rig: 441 bones = full body + facial
163
+ * rig, otherwise the 86-bone standard-male body rig. If the bundle contains
164
+ * an embedded animation, it is registered on the engine under
165
+ * {@link LoadCharacterOptions.name} (or the file basename).
166
+ *
167
+ * @param engine - Target engine.
168
+ * @param urlOrUrls - One bundle URL, or an array of LOD levels for the same
169
+ * character, finest first. Every level shares the armature and animation
170
+ * from the FIRST entry; see {@link setRenderLod} for how a level is picked
171
+ * at runtime and {@link addLodLevel} for adding one later.
172
+ * @param opts - See {@link LoadCharacterOptions}.
173
+ * @returns A fully-wired character whose armature drives the splat every frame.
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * const character = await Character.load(engine, '/assets/hero.vsplat');
178
+ * character.playAnimation('idle', { loop: true });
179
+ * ```
180
+ */
181
+ static load(engine: SplatEngine, urlOrUrls: string | string[], opts?: LoadCharacterOptions): Promise<Character>;
182
+ /**
183
+ * Load a `.bin` character bundle from in-memory bytes.
184
+ *
185
+ * Same shape as {@link Character.load} but consumes bytes directly. Use for
186
+ * runtime-generated content (an offline generation pipeline's output, hot
187
+ * swaps, tests) where there is no fetchable URL.
188
+ *
189
+ * @param engine - Target engine.
190
+ * @param bytes - Raw `.bin` bundle bytes.
191
+ * @param opts - See {@link LoadCharacterOptions}. `opts.name` is required for
192
+ * anim registration since there is no URL basename to fall back on.
193
+ * @returns A fully-wired character whose armature drives the splat every frame.
194
+ *
195
+ * @example
196
+ * ```ts
197
+ * const character = await Character.loadFromBytes(engine, generatedBundle, { name: 'hero' });
198
+ * character.playAnimation('idle', { loop: true });
199
+ * ```
200
+ */
201
+ static loadFromBytes(engine: SplatEngine, bytes: Uint8Array, opts?: LoadCharacterOptions): Promise<Character>;
202
+ /**
203
+ * Load a `.vsplat` bundle.
204
+ *
205
+ * The extension is advisory: the bytes go to the same magic-dispatching
206
+ * reader {@link Character.load} uses, so a `.vsplat` may be a `VIGGCHAR`
207
+ * version 1 bundle or one of the legacy planar containers. None of those
208
+ * carries an animation chunk, so the character comes up in its rest pose;
209
+ * register clips separately with {@link Animation.loadGlb} if you need motion.
210
+ *
211
+ * Differs from {@link Character.load} only in skipping the in-memory
212
+ * `ArrayBuffer` cache - see `loader/shared/asset-loader.ts`.
213
+ *
214
+ * @param engine - Target engine.
215
+ * @param source - URL, or raw `.vsplat` bytes.
216
+ * @param opts - See {@link LoadCharacterOptions}.
217
+ */
218
+ static loadVsplat(engine: SplatEngine, source: string | Uint8Array, opts?: LoadCharacterOptions): Promise<Character>;
219
+ /**
220
+ * Build a character from bundles that are already decoded. The bundles are
221
+ * read, never modified, so the same ones can back any number of characters.
222
+ * @internal
223
+ */
224
+ static fromBundles(engine: SplatEngine, bundles: CharacterBundle[], defaultName: string, opts?: LoadCharacterOptions): Character;
225
+ private static _buildFromLoadResult;
226
+ /** Build + register the per-frame update handler. Called once at
227
+ * construction; kept separate so the handler can close over `this`. */
228
+ private _wireUpdate;
229
+ /** Pick the render-LOD level for this frame based on camera distance.
230
+ * near band → L0; far band → last; in between → intermediate level. */
231
+ private _pickRenderLevel;
232
+ /**
233
+ * Enable distance-based render LOD. Per frame, the character switches
234
+ * between the loaded LOD splats (`splats[0..N-1]`, from finest to
235
+ * coarsest). Near the camera → L0 (full); beyond `farDist` → last level;
236
+ * in between → intermediate levels split evenly across the band.
237
+ *
238
+ * Composes with {@link Armature.setAnimationLod} / {@link Armature.setSortLod}:
239
+ * render LOD throws away splats at distance, the other two throttle
240
+ * animation / sort work - stack all three for maximum CPU savings.
241
+ *
242
+ * No-op if only one LOD level was loaded.
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * const c = await Character.load(engine, ['hero.l0.vsplat', 'hero.l1.vsplat', 'hero.l2.vsplat']);
247
+ * c.setRenderLod({ nearDist: 8, farDist: 20 });
248
+ * ```
249
+ */
250
+ setRenderLod(opts: {
251
+ nearDist: number;
252
+ farDist: number;
253
+ }): void;
254
+ /** Disable render LOD - pins the character to the highest-fidelity level
255
+ * (L0). Other loaded LOD splats remain in memory but are not rendered. */
256
+ disableRenderLod(): void;
257
+ /** Force a specific render-LOD level, bypassing distance bands. Useful
258
+ * for debugging / A-B comparisons in the profiling scene. */
259
+ setRenderLevel(level: number): void;
260
+ /**
261
+ * Thin the character with distance by processing fewer of its splats, rather
262
+ * than by switching to a different file.
263
+ *
264
+ * At `nearDist` and closer, `nearFraction` of the splats are skinned, sorted
265
+ * and drawn; at `farDist` and beyond, `farFraction` of them; in between, the
266
+ * fraction is interpolated. Which splats those are is decided by importance
267
+ * inside each 4 cm cell (see {@link Splat.setCountLod}), so the character
268
+ * thins evenly instead of losing a region.
269
+ *
270
+ * The dynamic counterpart of {@link SplatQuality.pruneRatio}, and the one to
271
+ * reach for when the splats are still wanted close up. It composes with
272
+ * everything else: {@link Character.setRenderLod} switches between loaded
273
+ * files, {@link Armature.setAnimationLod} and {@link Armature.setSortLod}
274
+ * drop frames of pose and sort work, and this one changes how much of the
275
+ * character each of those frames covers.
276
+ *
277
+ * `WebGLRenderer` only - see {@link Splat.setCountLod} for why.
278
+ *
279
+ * ```ts
280
+ * character.setSplatCountLod({ nearDist: 4, farDist: 16, farFraction: 0.25 });
281
+ * ```
282
+ */
283
+ setSplatCountLod(opts: {
284
+ nearDist: number;
285
+ farDist: number;
286
+ /** Fraction kept at `nearDist` and closer. Default 1. */
287
+ nearFraction?: number;
288
+ /** Fraction kept at `farDist` and beyond. Default 0.3. */
289
+ farFraction?: number;
290
+ }): void;
291
+ /** Stop thinning: every splat is processed again, on every level. */
292
+ disableSplatCountLod(): void;
293
+ /** Pick this frame's splat fraction from camera distance and apply it. */
294
+ private _applyCountLod;
295
+ /** Number of LOD levels loaded for this character (>=1). */
296
+ get renderLodLevels(): number;
297
+ /** Active render-LOD level (0 = highest fidelity). */
298
+ get activeRenderLevel(): number;
299
+ /**
300
+ * Tint every LOD splat. Fans out to `splats[*]` so the tint persists
301
+ * across render-LOD swaps - writing to a single splat (e.g. the legacy
302
+ * `c.splat.setTint(...)`) only affects that level, and the tint
303
+ * "vanishes" the next time `setRenderLod` swaps the visible level.
304
+ *
305
+ * Same signature as {@link Splat.setTint}.
306
+ */
307
+ setTint(intensity: number, color?: {
308
+ r: number;
309
+ g: number;
310
+ b: number;
311
+ }): void;
312
+ /**
313
+ * Re-sort every LOD splat. Cheap when called on inactive splats (no GPU
314
+ * sort runs there), and necessary for the active one to reflect a fresh
315
+ * transform on the next frame. Fans out so a swap to a previously-
316
+ * inactive LOD doesn't render with a stale sort order.
317
+ */
318
+ requestSort(): void;
319
+ /**
320
+ * World-space Y of the character's lowest point, using
321
+ * {@link Splat.getGroundWorldY}'s noise-robust low-quantile estimate across
322
+ * every enabled splat (the active render-LOD level, plus any variant splats
323
+ * that are currently visible). Returns `null` if no splat is enabled (e.g.
324
+ * mid-load). Ground-snap tooling only - the per-frame render path doesn't
325
+ * use it.
326
+ *
327
+ * This is a MEASUREMENT of where the character actually renders, so it
328
+ * already includes {@link bakedGroundShiftY}. A caller that snaps to ground
329
+ * from this number must not also apply that shift, or it corrects twice.
330
+ */
331
+ getLowestWorldY(quantile?: number): number | null;
332
+ /**
333
+ * Async primitive: load one more LOD level from `url` and link it to this
334
+ * character's armature, leaving it disabled (not rendered) until the caller
335
+ * explicitly picks it via {@link setRenderLevel}.
336
+ *
337
+ * Lets game code compose its own LOD-loading policy on top of an existing
338
+ * Character without the engine deciding when to swap. Typical pattern is
339
+ * progressive: load the smallest LOD via {@link Character.load} for instant
340
+ * first-frame, then background-load finer levels with `addLodLevel` and
341
+ * `setRenderLevel(splats.length - 1)` each time one arrives.
342
+ *
343
+ * Skeleton + animation are shared with the existing armature - any joints
344
+ * / animation data inside the additional `.bin` is intentionally ignored
345
+ * (it must match the original; only the splat + bone-weights chunks are
346
+ * consumed). The newly loaded `Splat` is pushed onto `this.splats` and
347
+ * starts in the disabled state.
348
+ *
349
+ * @param url - URL of another `.bin` (any LOD level of the same character).
350
+ * @returns Resolves once the splat is fully loaded, linked, and GPU-uploaded.
351
+ *
352
+ * @example
353
+ * ```ts
354
+ * // Progressive: start at L2 (smallest), upgrade as finer levels arrive.
355
+ * const c = await Character.load(engine, urls[2], opts);
356
+ * (async () => {
357
+ * await c.addLodLevel(urls[1]);
358
+ * c.setRenderLevel(c.splats.length - 1);
359
+ * await c.addLodLevel(urls[0]);
360
+ * c.setRenderLevel(c.splats.length - 1);
361
+ * })();
362
+ * ```
363
+ */
364
+ addLodLevel(url: string): Promise<void>;
365
+ /**
366
+ * Load an additional splat cloud, link it to this character's armature,
367
+ * and leave it ENABLED so it renders alongside the LOD pyramid. Returns
368
+ * the newly-created {@link Splat} so higher-level systems can drive it
369
+ * directly (typically via {@link Splat.setSplatGroups} +
370
+ * {@link Splat.setVariantMask}).
371
+ *
372
+ * Use case: damage / outfit / state variants where two skinned clouds
373
+ * share one skeleton and per-region visibility masks decide which
374
+ * gaussians each cloud contributes. See the `DamageVariant` preset for
375
+ * the body-part scheme built on top of this primitive.
376
+ *
377
+ * The variant splat's lifecycle is owned by this Character - it is
378
+ * destroyed in {@link destroy} and enabled/disabled by {@link setActive}.
379
+ * Call {@link removeVariantSplat} to retire one early.
380
+ *
381
+ * @param url - URL of a `.bin` rigged to the same skeleton as this
382
+ * character. The skeleton/animation chunks (if present) are ignored.
383
+ */
384
+ addVariantSplat(url: string): Promise<Splat>;
385
+ /** Remove and destroy a splat previously added via {@link addVariantSplat}.
386
+ * No-op if `splat` was not from this character. */
387
+ removeVariantSplat(splat: Splat): void;
388
+ /** Read-only view of currently-attached variant splats (in insertion order). */
389
+ get variantSplats(): readonly Splat[];
390
+ /**
391
+ * Set the character's position, with `y` measured at GROUND CONTACT: pass the
392
+ * floor height and the feet land on it, whichever container the character
393
+ * came from. Triggers a splat re-sort.
394
+ *
395
+ * `y` goes through {@link groundToEntityY} on the way to the pivot. That is
396
+ * the identity for every legacy `.bin` character, so this is byte-identical
397
+ * to the historical "y is the model origin" behaviour for everything already
398
+ * shipped; only `VIGGCHAR` v1 bundles move. Read the position back with
399
+ * {@link getPosition}, never with `armature.object3D.position`, or a
400
+ * round-trip will double-apply the shift.
401
+ */
402
+ setPosition(x: number, y: number, z: number): void;
403
+ /**
404
+ * The character's current position in the armature pivot's parent space, in
405
+ * the same convention {@link setPosition} takes (`y` at ground contact).
406
+ *
407
+ * The exact inverse of {@link setPosition}: reading a position and writing it
408
+ * straight back is a no-op. That is the property to preserve - it is what
409
+ * lets a caller integrate velocity, tween, or clamp against a position it
410
+ * read without the ground shim compounding once per frame.
411
+ *
412
+ * Returns a fresh {@link Vec3}; the pivot's own vector is not exposed.
413
+ */
414
+ getPosition(out?: Vector3): Vector3;
415
+ /** Set the character's rotation in Euler degrees. Triggers a splat re-sort. */
416
+ setRotation(xDeg: number, yDeg: number, zDeg: number): void;
417
+ /** Set non-uniform scale. Triggers a splat re-sort. */
418
+ setScale(x: number, y: number, z: number): void;
419
+ /**
420
+ * Immediately start a registered animation (no crossfade).
421
+ *
422
+ * The animation must have been registered on the engine via
423
+ * {@link Animation.register} or embedded in the `.bin` bundle.
424
+ *
425
+ * @param animName - Animation name in `engine.skeletalAnimationLibrary`.
426
+ * @param opts.loop - Loop playback. Default `true`.
427
+ * @param opts.rootMotion - Drive the armature position from the pelvis X/Z channel. Default `false`.
428
+ * @returns `true` if the animation was found and started; `false` otherwise.
429
+ */
430
+ playAnimation(animName: string, opts?: {
431
+ loop?: boolean;
432
+ rootMotion?: boolean;
433
+ }): boolean;
434
+ /**
435
+ * Smoothly blend from the current pose to a new animation.
436
+ *
437
+ * Snapshots the current bone poses and interpolates toward the new
438
+ * animation over `duration` seconds. Safe to call mid-crossfade - always
439
+ * starts from wherever bones actually are.
440
+ *
441
+ * @param animName - Animation name in `engine.skeletalAnimationLibrary`.
442
+ * @param opts.duration - Crossfade length in seconds. Default `0.08`.
443
+ * @param opts.loop - Loop the target animation. Default `true`.
444
+ * @param opts.rootMotion - Drive the armature position from the pelvis X/Z channel. Default `false`.
445
+ * @returns `true` if the animation was found and the blend started; `false` otherwise.
446
+ */
447
+ crossfadeTo(animName: string, opts?: {
448
+ duration?: number;
449
+ loop?: boolean;
450
+ rootMotion?: boolean;
451
+ preserveTime?: boolean;
452
+ }): boolean;
453
+ /**
454
+ * Play `animName` on the named overlay layer, restricted to a bone mask.
455
+ * Layers compose over the base animation each frame - masked bones blend
456
+ * toward the overlay by `opts.weight` (defaults to `1`).
457
+ *
458
+ * Pass `opts.region: 'upperBody' | 'lowerBody'` to use the built-in masks
459
+ * (canonical 86-bone rig), or `opts.mask` directly to supply your own.
460
+ *
461
+ * Typical use: `character.playAnimation('walk_loop'); character.playLayer('aim', 'aim_rifle', { region: 'upperBody' });`
462
+ */
463
+ playLayer(layerName: string, animName: string, opts?: {
464
+ region?: BodyRegion;
465
+ mask?: BoneMask;
466
+ loop?: boolean;
467
+ crossfade?: number;
468
+ weight?: number;
469
+ }): boolean;
470
+ /** Set the overlay weight (0..1) on a layer. */
471
+ setLayerWeight(layerName: string, weight: number): void;
472
+ /** Stop and tear down a layer; ramps weight to zero over `fadeDuration` if > 0. */
473
+ stopLayer(layerName: string, fadeDuration?: number): void;
474
+ /** Pause or resume every active overlay layer at once. */
475
+ setAllLayersPaused(paused: boolean): void;
476
+ /**
477
+ * Name of the currently-playing animation, or `null` if no animation is
478
+ * active. Mirrors {@link Armature.currentAnimationName}.
479
+ */
480
+ get currentAnimationName(): string | null;
481
+ /** `true` if an animation is currently active. */
482
+ get isPlaying(): boolean;
483
+ /**
484
+ * Playback-speed multiplier for the active clip (`1` = author speed, `2` =
485
+ * double speed). Scales only how fast animation time advances, not the
486
+ * authored frame rate or crossfade timing. Mirrors
487
+ * {@link Armature.playbackSpeed}. Use it to tie a locomotion clip's speed
488
+ * to the pivot's movement speed.
489
+ */
490
+ get playbackSpeed(): number;
491
+ set playbackSpeed(v: number);
492
+ /**
493
+ * `true` when a non-looping animation has reached its last frame and is no
494
+ * longer advancing.
495
+ */
496
+ get isFinished(): boolean;
497
+ /**
498
+ * Subscribe to frame-aligned animation events fired as `animTime` advances.
499
+ * The active animation's {@link Animation.events} array is the source; the
500
+ * engine doesn't interpret `name` or `payload` - games define their own
501
+ * vocabulary and route inside the callback.
502
+ *
503
+ * Listeners persist across animation switches. Returns an unsubscribe handle.
504
+ *
505
+ * @example
506
+ * ```ts
507
+ * character.onAnimEvent((e) => {
508
+ * if (e.name === 'footstep') playFootstepSfx(e.payload);
509
+ * });
510
+ * ```
511
+ */
512
+ onAnimEvent(cb: AnimEventCallback): () => void;
513
+ /**
514
+ * Stop playback.
515
+ *
516
+ * With `fadeDuration = 0` (the default), snaps immediately to the rest
517
+ * pose. A positive value crossfades back to rest over that many seconds.
518
+ */
519
+ stop(fadeDuration?: number): void;
520
+ /**
521
+ * Read and reset the pelvis X/Z delta the animation produced since the
522
+ * last call. Only meaningful when
523
+ * {@link Armature.rootMotionMode | armature.rootMotionMode} is `'extract'`;
524
+ * returns `{0, 0}` otherwise.
525
+ *
526
+ * Typical use: a motor / character controller reads this each tick and
527
+ * feeds it into its own velocity integration, so animation root motion
528
+ * composes cleanly with physics, networking, or input.
529
+ *
530
+ * ```ts
531
+ * // in your render loop, after engine.update(dt):
532
+ * const { dx, dz } = character.consumeRootMotionDelta();
533
+ * motor.setMoveVelocity(dx / dt, dz / dt);
534
+ * motor.update(dt);
535
+ * ```
536
+ */
537
+ consumeRootMotionDelta(): {
538
+ dx: number;
539
+ dz: number;
540
+ };
541
+ /** True while the character is ticking + rendering (see {@link setActive}). */
542
+ get active(): boolean;
543
+ /**
544
+ * Activate / deactivate this character WITHOUT releasing GPU resources -
545
+ * the basis for object pooling. Deactivating detaches the per-frame update
546
+ * handler and disables every splat object and the armature pivot, so the
547
+ * character stops animating and rendering but keeps all its splats,
548
+ * textures and compiled shaders alive. Reactivating re-attaches the update
549
+ * handler and re-enables the armature; render-LOD then re-picks which splat
550
+ * shows on the next frame (so we only force the current level visible to
551
+ * cover the LOD-disabled case).
552
+ *
553
+ * Reactivate is the cheap path that skips the entire `new Splat` setup cost
554
+ * (texture creation + bone upload + shader compile, ~tens of ms per
555
+ * character) that a fresh {@link load} pays. Callers reposition via
556
+ * `armature.move(...)` and restart animation via `playAnimation(...)` after
557
+ * reactivating.
558
+ *
559
+ * Idempotent.
560
+ */
561
+ setActive(on: boolean): void;
562
+ /**
563
+ * Detach the per-frame playback tick without touching rendering/GPU
564
+ * resources (unlike {@link setActive}). For hosts that drive the armature's
565
+ * clock themselves and apply their own per-frame pose edits, the auto-tick
566
+ * would re-evaluate and overwrite those edits. Idempotent.
567
+ */
568
+ detachAutoUpdate(): void;
569
+ /**
570
+ * Advance this character by `dt` seconds: pick the render LOD, evaluate the
571
+ * pose, push it to the splats. `engine.update()` calls this for every
572
+ * character still on auto-update; call it yourself after
573
+ * {@link detachAutoUpdate}.
574
+ */
575
+ update(dt: number): void;
576
+ /**
577
+ * Release all GPU/CPU resources: removes the engine update handler, unlinks
578
+ * the splat from the armature, destroys both, and removes the armature
579
+ * pivot from the scene graph.
580
+ */
581
+ destroy(): void;
582
+ }