@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,524 @@
1
+ import { Group } from 'three';
2
+ import { Mat4, Quat, Vec3 } from './math';
3
+ import type { SplatEngine } from './engine';
4
+ import type { AnimEventCallback } from './skeletal/animation-player';
5
+ import type { BoneMask } from './skeletal/bone-mask';
6
+ import type { BoneScaleInput } from './skeletal/bone-stretch';
7
+ import { Pose } from './skeletal/pose';
8
+ import { Skeleton } from './skeletal/skeleton';
9
+ import type { Splat } from './splat/splat';
10
+ import type { ArmatureData } from './types';
11
+ /**
12
+ * Distance-based animation-LOD knobs. Used as the engine-wide preset
13
+ * (`SplatEngine.animLodPreset`) and as the per-armature override shape in
14
+ * {@link Armature.setAnimationLod}.
15
+ */
16
+ export interface AnimLodPreset {
17
+ /** World-space distance (m) below which the armature animates full rate. */
18
+ nearDist?: number;
19
+ /** World-space distance (m) beyond which `farStride` applies. */
20
+ farDist?: number;
21
+ /** Frames-per-update between `nearDist` and `farDist`. 1 = every frame. */
22
+ nearStride?: number;
23
+ /** Frames-per-update beyond `farDist`. */
24
+ farStride?: number;
25
+ }
26
+ /**
27
+ * Pluggable override that drives the per-frame pose in place of
28
+ * animation. The Armature has no knowledge of what's on the other end -
29
+ * ragdoll physics, an IK solver, an AI behavior tree's pose output, a
30
+ * network-replicated remote character - they all attach the same way.
31
+ *
32
+ * When `armature.poseProvider` is non-null, the Armature's animation
33
+ * player is **not advanced** that frame; the provider has total control
34
+ * of the pose. The Armature still owns the skinning pipeline (LOD,
35
+ * frustum cull, world-transform composition, GPU palette upload), so a
36
+ * provider only needs to fill `pose`.
37
+ *
38
+ * Implementations: {@link Ragdoll}.
39
+ *
40
+ * @group Characters & Animation
41
+ */
42
+ export interface PoseProvider {
43
+ /**
44
+ * Fill `pose` (per-bone TRS in armature-local bone space) for this
45
+ * frame. The Armature runs this between its LOD/frustum gate and the
46
+ * `Skeleton.applyLocal` composition step. To restore animation,
47
+ * detach by setting `armature.poseProvider = null`.
48
+ */
49
+ writePose(pose: Pose, armature: Armature, dt: number): void;
50
+ /**
51
+ * Optional teardown hook fired when the Armature is destroyed (or
52
+ * when a new provider replaces this one). Use to release external
53
+ * resources - e.g. ragdoll physics bodies.
54
+ */
55
+ onDetach?(): void;
56
+ }
57
+ /**
58
+ * How the armature handles the pelvis X/Z motion extracted from the active
59
+ * animation.
60
+ *
61
+ * - `'off'` - Pelvis X/Z is clamped to bind in bone space. The character
62
+ * looks "in-place" regardless of whether the underlying clip is
63
+ * locomotive. The pivot is not touched by animation. **Default.**
64
+ * - `'apply'` - The pivot tracks the pelvis as an absolute offset from a captured
65
+ * baseline: `object3D.position = baseline + (pelvis_pose − bind)` each
66
+ * frame. Switching `off → apply` captures the current pivot position as
67
+ * the baseline; switching `apply → off` snaps the pivot back to the
68
+ * baseline. Best for toggling-on/off demos and in-place looping animations
69
+ * (pelvis returns to bind at loop end → no teleport). Locomotive clips
70
+ * visibly snap at loop wrap; use `'extract'` for those.
71
+ * - `'extract'` - Engine computes the per-frame pelvis Δ
72
+ * (`pelvis(t) − pelvis(t − dt)`) and accumulates it into an internal
73
+ * buffer; the caller reads it via {@link Armature.consumeRootMotionDelta}
74
+ * and feeds it to a motor / physics body / network layer. Unity-style
75
+ * `OnAnimatorMove` pattern. The engine does **not** write to the pivot in
76
+ * this mode. Loop wraps and hard `play` calls suppress the resulting
77
+ * pelvis discontinuity so the next Δ is `0`.
78
+ *
79
+ * In every mode the pelvis bone itself is clamped to bind X/Z in bone space -
80
+ * the motion lives on the pivot (or in the consumed delta), never on the
81
+ * bone.
82
+ *
83
+ * @group Characters & Animation
84
+ */
85
+ export type RootMotionMode = 'off' | 'apply' | 'extract';
86
+ /**
87
+ * Composition root for one character: holds the three.js pivot object and
88
+ * wires together a {@link Skeleton}, an {@link AnimationPlayer}, a working
89
+ * {@link Pose}, and a {@link SplatSkinning} unit.
90
+ *
91
+ * Thin facade - `playAnimation` / `crossfadeTo` / `stop` / `update` delegate
92
+ * to the underlying modules. The rig-convention rules (pelvis X/Z clamp,
93
+ * pelvis-Y lock, root-bone clamp, root-motion extraction) live here, because
94
+ * they're a *character-setup* concern, not a pose-evaluation concern.
95
+ *
96
+ * Most users won't construct this directly - {@link Character.load} builds
97
+ * one automatically. It's exported for custom pipelines that need direct
98
+ * control over skeletal evaluation.
99
+ *
100
+ * @group Characters & Animation
101
+ */
102
+ export declare class Armature {
103
+ /** Pivot object; all linked splats become children of this. */
104
+ object3D: Group;
105
+ /** Rig data (bone count, rest translations/rotations, parents). */
106
+ armatureData: ArmatureData;
107
+ /** Number of bones in the rig. */
108
+ numBones: number;
109
+ private readonly _skeleton;
110
+ private readonly _currentPose;
111
+ private readonly _snapshotPose;
112
+ private readonly _worldTransforms;
113
+ private _engine;
114
+ private readonly _worldMatrix;
115
+ private _player;
116
+ private _skinning;
117
+ private _provider;
118
+ private readonly _layers;
119
+ private readonly _layerTmpQ;
120
+ /**
121
+ * Optional override that drives the pose in place of animation.
122
+ * Setting this to a non-null value skips animation evaluation; the
123
+ * provider has full control of `_currentPose`. Setting back to `null`
124
+ * resumes animation immediately. See {@link PoseProvider}.
125
+ *
126
+ * Replacing the provider (set non-null while another is attached)
127
+ * fires the outgoing provider's `onDetach()` first.
128
+ */
129
+ get poseProvider(): PoseProvider | null;
130
+ set poseProvider(p: PoseProvider | null);
131
+ private _poseProvider;
132
+ private _rootBoneIndex;
133
+ private _pelvisBoneIndex;
134
+ /**
135
+ * When true, {@link update} advances the animation player (so timers,
136
+ * crossfade, and event callbacks still fire) but skips pose evaluation
137
+ * and the skinning push. The previous frame's pose stays on the GPU
138
+ * palette and the splat keeps rendering at its last bone state.
139
+ *
140
+ * Use for dynamic animation LOD - e.g. when fps drops, set true on
141
+ * every other (or every Nth) frame to halve the per-frame skeletal
142
+ * cost. Cleared when fps recovers.
143
+ */
144
+ skipPose: boolean;
145
+ /**
146
+ * When true, {@link update} still pushes bone transforms to the GPU
147
+ * palette (so items, hitboxes, and outlines tracking bones still move
148
+ * correctly) but skips the CPU-side `splat.updatePositions(...)` call
149
+ * that recomputes sort centers from the deformed mesh. The depth-sort
150
+ * order lags one frame behind the visual pose.
151
+ *
152
+ * Use for alternate-frame throttling - `updatePositions` is the heavy
153
+ * call (full center buffer re-upload to the sort worker), and most
154
+ * frames don't visibly need a fresh sort.
155
+ */
156
+ skipPositions: boolean;
157
+ /**
158
+ * Update rate divider. `1` = pose evaluation runs on every {@link update}
159
+ * call (default - one pose update per `engine.update()`).
160
+ * Set to N>1 to advance the animation player every call but only do the
161
+ * heavy pose-eval + skinning push every Nth call.
162
+ *
163
+ * `dt` from skipped frames is accumulated and passed to the next
164
+ * evaluated frame, so animation time stays correct - only the GPU
165
+ * palette refresh rate drops.
166
+ *
167
+ * Typical use: set to `2` on background characters so the per-character
168
+ * armature cost halves while the engine still ticks at 30 Hz.
169
+ */
170
+ updateInterval: number;
171
+ private _updateCounter;
172
+ private _updateAccumDt;
173
+ private _lockPelvisY;
174
+ private _rootMotionMode;
175
+ private _baseLocalPosition;
176
+ private _lastPelvisPos;
177
+ private _hasLastPelvis;
178
+ private _rootMotionAccumDx;
179
+ private _rootMotionAccumDz;
180
+ private _lodNearDist;
181
+ private _lodFarDist;
182
+ private _lodNearStride;
183
+ private _lodFarStride;
184
+ private _lodFrameCounter;
185
+ private _sortLodNearDist;
186
+ private _sortLodFarDist;
187
+ private _sortLodNearStride;
188
+ private _sortLodFarStride;
189
+ private _sortLodFrameCounter;
190
+ constructor(name: string, armatureData: ArmatureData);
191
+ /** Splats currently being driven by this armature. */
192
+ get linkedSplats(): Set<Splat>;
193
+ /**
194
+ * Detach the armature from its engine and remove its pivot object. Splats
195
+ * still linked at the time of destroy are also destroyed so they stop
196
+ * holding GPU resources alive. The armature itself is not safe to
197
+ * reuse after this call.
198
+ *
199
+ * Use {@link Character.destroy} instead if you have the owning
200
+ * {@link Character} - it disposes both the armature and its primary splat.
201
+ */
202
+ destroy(): void;
203
+ /**
204
+ * Attach this armature to an engine. Required before playback. Adds the
205
+ * armature pivot as a child of `engine.contentRoot` and wires up the
206
+ * animation provider + splat skinning.
207
+ */
208
+ addToScene(engine: SplatEngine): void;
209
+ /**
210
+ * Register a {@link Splat} to be driven by this armature.
211
+ *
212
+ * Reparents the splat under the armature's pivot and pushes the current
213
+ * bone transforms so the splat appears in the correct pose immediately.
214
+ */
215
+ linkSplat(splat: Splat): void;
216
+ /**
217
+ * Stop driving the given splat. The splat is not destroyed; it just no
218
+ * longer receives bone transform updates.
219
+ */
220
+ unlinkSplat(splat: Splat): void;
221
+ /**
222
+ * Apply any combination of position, rotation, and scale to the armature
223
+ * pivot. All linked splats are requested to re-sort.
224
+ */
225
+ move(position?: Vec3, rotation?: Quat, scale?: Vec3): void;
226
+ /**
227
+ * How pelvis X/Z motion is routed each frame. See {@link RootMotionMode}.
228
+ * Default `'off'`.
229
+ *
230
+ * Transitions are coordinated so toggling is well-behaved:
231
+ *
232
+ * - `off → apply` captures the current pivot position as the baseline.
233
+ * The pivot then tracks `baseline + (pelvis − bind)` each frame.
234
+ * - `apply → off` (or `apply → extract`) snaps the pivot back to the
235
+ * baseline so the apply-mode offset doesn't leak into the next mode.
236
+ * This is what prevents the toggle-ratchet bug.
237
+ * - `* → extract` resets the per-frame Δ tracker so the next Δ is `0`.
238
+ */
239
+ get rootMotionMode(): RootMotionMode;
240
+ set rootMotionMode(m: RootMotionMode);
241
+ /**
242
+ * Convenience boolean alias for {@link rootMotionMode}.
243
+ * `true` ⇔ `'apply'`, `false` ⇔ `'off'`. To use `'extract'`, set
244
+ * {@link rootMotionMode} directly.
245
+ */
246
+ get rootMotion(): boolean;
247
+ set rootMotion(v: boolean);
248
+ /**
249
+ * Return the pelvis X/Z delta accumulated since the previous call (or since
250
+ * the armature was created / `rootMotionMode` was changed), and reset the
251
+ * accumulator. The delta is in the pivot's parent space - feed it directly
252
+ * to a motor's velocity input or to a physics body.
253
+ *
254
+ * Returns `{ dx: 0, dz: 0 }` in `'off'` or `'apply'` modes. In `'apply'`
255
+ * mode the delta has already been written to {@link object3D}, so consumers
256
+ * shouldn't read and re-apply it.
257
+ */
258
+ consumeRootMotionDelta(): {
259
+ dx: number;
260
+ dz: number;
261
+ };
262
+ /**
263
+ * When `true`, clamp the pelvis bone's Y translation to bind each frame.
264
+ * Use for in-place idle / gesture captures where vertical bob would push
265
+ * the character out of a tightly-framed thumbnail.
266
+ */
267
+ get lockPelvisY(): boolean;
268
+ set lockPelvisY(v: boolean);
269
+ get isPlaying(): boolean;
270
+ get isFinished(): boolean;
271
+ get paused(): boolean;
272
+ set paused(v: boolean);
273
+ get animationTime(): number;
274
+ set animationTime(v: number);
275
+ get animationDuration(): number;
276
+ /**
277
+ * Crossfade progress in `[0, 1]`. `0` when no crossfade is active; `1`
278
+ * once the active crossfade has fully blended into the target animation.
279
+ */
280
+ get crossfadeProgress(): number;
281
+ get frameRate(): number;
282
+ set frameRate(v: number);
283
+ /** Playback-speed multiplier (`1` = author speed). Scales how fast the
284
+ * active clip advances without touching its authored frame rate or
285
+ * crossfade timing. See {@link AnimationPlayer.playbackSpeed}. */
286
+ get playbackSpeed(): number;
287
+ set playbackSpeed(v: number);
288
+ hasAnimation(animName: string): boolean;
289
+ /**
290
+ * Bone names from the attached engine's skeleton library, or an empty array
291
+ * if the armature isn't bound yet. Consumers that need bone-name lookup
292
+ * (bone-tracked colliders, attachments, debug overlays) read from here
293
+ * instead of reaching into private armature state.
294
+ */
295
+ get boneNames(): readonly string[];
296
+ /**
297
+ * Bone names matching THIS armature's rig size. {@link boneNames} always
298
+ * returns the 86-bone std-male list; the 441-bone facial rig has its own
299
+ * name table (`boneNames441`). Name-keyed per-bone features (bone stretch,
300
+ * body-part classification) must use this accessor or they silently
301
+ * misclassify every bone on the large rig.
302
+ */
303
+ get rigBoneNames(): readonly string[];
304
+ /** The engine this armature is attached to, or `null` if not yet attached. */
305
+ get engine(): SplatEngine | null;
306
+ /** The skeleton owned by this armature (rest pose, parents, bind data). */
307
+ get skeleton(): Skeleton;
308
+ /**
309
+ * Live reference to the per-bone world-transform array filled in by
310
+ * the most recent {@link update}. Same Mat4 instances are reused
311
+ * across frames - callers may hold the array reference and re-read,
312
+ * or call {@link getBoneWorldMatrix} for a single bone. Empty until
313
+ * the first update.
314
+ */
315
+ get boneWorldMatrices(): readonly Mat4[];
316
+ /**
317
+ * Snap any active crossfade to its terminal state (target pose fully
318
+ * applied) on the next {@link Armature.update}. Useful when scrubbing
319
+ * through animation frames for precompute, where the per-frame pose
320
+ * needs to be the pure animation evaluation without crossfade blend.
321
+ */
322
+ endCrossfade(): void;
323
+ /**
324
+ * Return the world-space transform of bone `boneIdx` as computed by the
325
+ * most recent {@link Armature.update}. Returns a live reference into the
326
+ * armature's internal array - callers must not mutate it; copy first if
327
+ * you need to retain.
328
+ *
329
+ * Returns `null` if `boneIdx` is out of range, or if the bone transform
330
+ * array has not been populated yet (no `update()` has run, or the armature
331
+ * isn't attached to an engine).
332
+ *
333
+ * Used by per-bone attachments - bone-tracked colliders, weapon GLBs
334
+ * mounted to a bone, particle emitters following a bone, etc.
335
+ */
336
+ getBoneWorldMatrix(boneIdx: number): Mat4 | null;
337
+ /**
338
+ * Name of the currently-playing animation, or `null` if no animation is
339
+ * active. Mirrors {@link AnimationPlayer.currentAnimationName}.
340
+ */
341
+ get currentAnimationName(): string | null;
342
+ /**
343
+ * Subscribe to frame-aligned {@link AnimationEvent}s on the currently-
344
+ * playing animation. Events come from the active animation's
345
+ * {@link Animation.events} array; the engine doesn't interpret `name` or
346
+ * `payload` - games define their own vocabulary and route inside the
347
+ * callback.
348
+ *
349
+ * Listeners are kept across animation switches; one subscription is enough
350
+ * for the armature's lifetime. Returns an unsubscribe handle.
351
+ */
352
+ onAnimEvent(cb: AnimEventCallback): () => void;
353
+ /**
354
+ * Start playing an animation immediately (no crossfade). Restarts from
355
+ * frame 0. Returns `false` if not registered, or if the armature isn't
356
+ * attached to an engine yet.
357
+ *
358
+ * Passing `rootMotion: true` sets {@link rootMotionMode} to `'apply'`;
359
+ * passing `false` sets it to `'off'`. For `'extract'`, set the mode
360
+ * separately and pass `rootMotion: false` (or omit) here - the per-clip
361
+ * flag is just a convenience shortcut for the common case.
362
+ */
363
+ playAnimation(animName: string, rootMotion?: boolean, loop?: boolean): boolean;
364
+ /**
365
+ * Crossfade from the current pose to a new animation over `opts.duration`
366
+ * seconds. Snapshots current bone poses, then blends toward the new
367
+ * animation target. Pelvis motion stays continuous across the blend.
368
+ *
369
+ * `opts.rootMotion` is mapped to {@link rootMotionMode} the same way as
370
+ * {@link playAnimation}.
371
+ */
372
+ crossfadeTo(animName: string, opts?: {
373
+ duration?: number;
374
+ rootMotion?: boolean;
375
+ loop?: boolean;
376
+ preserveTime?: boolean;
377
+ }): boolean;
378
+ /**
379
+ * Play `animName` on the named overlay layer, gated to `mask`'s bone set.
380
+ * Layers compose on top of the base animation in {@link update} - for each
381
+ * bone in the mask, the current pose is interpolated toward the overlay's
382
+ * pose by the layer weight.
383
+ *
384
+ * Creates the layer lazily on first use (allocates a per-layer
385
+ * {@link AnimationPlayer} sharing the same skeleton and animation provider
386
+ * as the base). Subsequent calls reuse the same player.
387
+ */
388
+ playLayerAnimation(layerName: string, animName: string, mask: BoneMask, opts?: {
389
+ loop?: boolean;
390
+ crossfade?: number;
391
+ weight?: number;
392
+ }): boolean;
393
+ /** Set the overlay weight for a layer in `[0, 1]`. No-op if the layer doesn't exist. */
394
+ setLayerWeight(layerName: string, weight: number): void;
395
+ /**
396
+ * Pause or resume every active overlay layer. The base animation is
397
+ * controlled separately via {@link paused}. Useful so a single play/pause
398
+ * toggle in an editor or pause menu freezes the whole stack together.
399
+ */
400
+ setAllLayersPaused(paused: boolean): void;
401
+ /**
402
+ * Stop and remove the named layer. With `fadeDuration > 0`, ramps the
403
+ * layer weight to zero over `fadeDuration` seconds before tearing down.
404
+ */
405
+ stopLayer(layerName: string, fadeDuration?: number): void;
406
+ /** Stop with optional crossfade back to rest pose. */
407
+ stop(fadeDuration?: number): void;
408
+ /**
409
+ * Advance animation time by `dt` seconds, evaluate pose, and push bone
410
+ * transforms to linked splats. Called for you by `engine.update()` unless auto-update is detached.
411
+ */
412
+ update(dt: number): void;
413
+ /**
414
+ * Resolve named rig conventions once skeleton library is available.
415
+ * If `boneNames` is present, looks up "root" and any name containing
416
+ * "pelvis"; otherwise falls back to bone index 0 as root.
417
+ */
418
+ private _resolveRigIndices;
419
+ /**
420
+ * Compose overlay layers onto `_currentPose`. Each layer ticks its own
421
+ * player, evaluates a full pose into a scratch buffer, then per masked
422
+ * bone interpolates the current pose toward the overlay by the layer
423
+ * weight. Runs before `_applyRigPostClamps` so pelvis/root clamps stay
424
+ * authoritative.
425
+ */
426
+ private _evaluateLayers;
427
+ /**
428
+ * Apply rig-convention rules to the freshly-evaluated pose:
429
+ * - root-motion routing - `'apply'` writes `baseline + (pelvis − bind)`
430
+ * to the pivot; `'extract'` accumulates the per-frame pelvis Δ;
431
+ * `'off'` does nothing.
432
+ * - pelvis X/Z clamp to bind in bone space (always - the motion lives on
433
+ * the pivot / in the consumed delta, not on the bone).
434
+ * - pelvis-Y lock to bind (when {@link lockPelvisY} is set).
435
+ * - root-bone full clamp to bind.
436
+ */
437
+ private _applyRigPostClamps;
438
+ /**
439
+ * Apply runtime bone scaling. Each entry scales a bone's flesh + segment in
440
+ * its own frame - `{ along, width, thickness }` (each defaulting to `1`), or
441
+ * a bare number for uniform scale. e.g. `{ thigh_l: { along: 1.3 }, calf_l:
442
+ * { along: 1.3 } }` → a 1.3× longer left leg; `{ upperarm_l: { width: 1.4,
443
+ * thickness: 1.4 } }` → a beefier arm. Bones not listed stay at `1`. Pass
444
+ * `null` to clear. Works for any bone set (limbs, spine, …); see
445
+ * {@link computeBoneStretch} for the model. Recompute only when factors
446
+ * change - the result is applied every frame by the skeleton + skinning.
447
+ */
448
+ setBoneStretch(factors: Record<string, BoneScaleInput> | null): void;
449
+ /** Push the current pose through skeleton + skinning. Called at link time. */
450
+ private _pushPose;
451
+ private _cameraFrustum;
452
+ /**
453
+ * The pivot's world matrix in the skeletal core's own matrix type. Refreshes
454
+ * the three.js world matrix first, so a transform set this frame is seen
455
+ * this frame.
456
+ */
457
+ private _worldTransform;
458
+ /**
459
+ * Squared distance from the pivot's world origin to the engine camera, or
460
+ * `null` when the engine has no camera. Drives the distance-LOD bands.
461
+ * @internal
462
+ */
463
+ cameraDistanceSq(): number | null;
464
+ /**
465
+ * Enable / configure distance-based animation LOD for THIS armature.
466
+ * LOD is off by default (full rate); calling this is the opt-in.
467
+ *
468
+ * - **No args**: pull the engine's `animLodPreset` (set via
469
+ * `SplatEngine.animLodPreset`). If the engine has no preset, fall back to a
470
+ * built-in default (full rate ≤12 m, every-2nd ≤24 m, every-4th
471
+ * beyond). Typical gameplay use: set the preset once, then call
472
+ * `armature.setAnimationLod()` on each throttle-able character.
473
+ * - **Partial opts**: tweak individual knobs on top of current values.
474
+ *
475
+ * `nearDist` / `farDist` are world-space distances (m) from the camera;
476
+ * `nearStride` / `farStride` are frames-per-update in the mid / far
477
+ * bands. A stride of 1 means every frame.
478
+ */
479
+ setAnimationLod(opts?: {
480
+ nearDist?: number;
481
+ farDist?: number;
482
+ nearStride?: number;
483
+ farStride?: number;
484
+ }): void;
485
+ /** Disable LOD throttling - every character animates at full rate
486
+ * regardless of distance (frustum cull still applies). */
487
+ disableAnimationLod(): void;
488
+ /**
489
+ * Enable / configure distance-based SORT LOD for THIS armature.
490
+ *
491
+ * Throttles only the CPU-side `splat.updatePositions` call (the dominant
492
+ * cost - ~88% of per-character CPU at high counts) without freezing the
493
+ * rendered pose. On throttled frames the GPU palette still uploads, so
494
+ * animation looks correct; only the depth-sort order lags one frame.
495
+ *
496
+ * Composes with {@link setAnimationLod} - when anim LOD throttles a
497
+ * frame, sort LOD is moot for that frame; when anim LOD evaluates, sort
498
+ * LOD can independently decide to skip the positions push.
499
+ *
500
+ * Typical use: tighter near band than anim LOD (e.g. 5–10 m) so even
501
+ * mid-distance characters skip the sort re-upload most frames.
502
+ *
503
+ * - **No args**: enable with a built-in default
504
+ * (nearDist=5, farDist=15, nearStride=2, farStride=4).
505
+ * - **Partial opts**: tweak individual knobs on top of current values.
506
+ */
507
+ setSortLod(opts?: {
508
+ nearDist?: number;
509
+ farDist?: number;
510
+ nearStride?: number;
511
+ farStride?: number;
512
+ }): void;
513
+ /** Disable sort LOD - every character does the full updatePositions
514
+ * every frame (still gated by `skipPositions` and anim LOD). */
515
+ disableSortLod(): void;
516
+ /** Frames-per-update for this armature this frame, based on camera
517
+ * distance. 1 = full rate. Returns 1 when no camera is available so
518
+ * we never freeze a character due to missing engine state. */
519
+ private _lodStride;
520
+ /** True when sort LOD says "skip the CPU positions re-upload this frame".
521
+ * Uses the same camera-distance bands shape as anim LOD, but a separate
522
+ * counter so the two strides don't lock-step. */
523
+ private _sortLodSkipThisFrame;
524
+ }