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