@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,139 @@
1
+ /**
2
+ * Planar character-splat decoder - the payload half of `VFCHARB2`.
3
+ *
4
+ * A planar container stores one contiguous, component-major byte plane per
5
+ * attribute (all x, then all y, then all z) instead of the per-splat records
6
+ * `VFCHARB1` uses. Two precision modes share the one layout:
7
+ *
8
+ * `fp16` - the same half-float values the shipping v2 `VFCHARB1` bundle
9
+ * carries, only re-laid-out. It decodes bit-identically to the v2
10
+ * path, which is what makes it the control when judging q23.
11
+ * `q23` - ~23 B/gaussian: u16 position against per-file bounds, u8 rotation
12
+ * (w derived), u8 log-scale, u8 logit-opacity, u8 weights (the last
13
+ * one derived), bone indices through a per-file palette.
14
+ *
15
+ * This module is deliberately free of Worker glue so it can be driven directly
16
+ * (from `binary-gsplat-worker.ts`, and from a plain Node process when the
17
+ * decode is being compared against the Python reference implementation).
18
+ */
19
+ /**
20
+ * Magic -> the `format` string a file under that magic must declare.
21
+ *
22
+ * The MAGIC is the container generation and the one field this decoder refuses
23
+ * to guess at: a planar payload published under `VFCHARB1` would sail past
24
+ * every version check in the interleaved reader and decode to silent garbage.
25
+ * `format` is then checked as well - dispatching on `version` alone is exactly
26
+ * how a file can be misparsed without anything throwing.
27
+ *
28
+ * `VSPLAT01` is the pre-release prototype of the same layout. It is kept
29
+ * readable so the `.vsplat` LOD/quantisation A/B rig keeps working, and because
30
+ * routing it through this same decoder means the prototype exercises the
31
+ * production code path rather than a copy of it.
32
+ */
33
+ declare const PLANAR_CONTAINERS: Record<string, string>;
34
+ /** First planar `version`. `1` = fp32 interleaved, `2` = fp16 interleaved. */
35
+ declare const FIRST_PLANAR_VERSION = 3;
36
+ type PlaneDtype = 'f32' | 'f16' | 'u16' | 'u8';
37
+ /**
38
+ * Per-plane compression codec, as written in the header.
39
+ *
40
+ * WHY DEFLATE AND NOT BROTLI OR ZSTD, which both compress ~10% better: these
41
+ * three are the only formats `DecompressionStream` accepts, and the equivalents
42
+ * (`GZipStream` / `DeflateStream`) are in the .NET base library, so a file with
43
+ * compressed planes decodes on both targets with ZERO shipped decoder. Browsers
44
+ * expose brotli and zstd only to the HTTP layer, never to script, so choosing
45
+ * either would put a WASM decoder on the critical path of every character load.
46
+ *
47
+ * In-file compression is for platforms with no HTTP layer (Unity
48
+ * StreamingAssets, AssetBundles, local files). On the web the file is served
49
+ * flat with `Content-Encoding: br` instead, which is free and compresses
50
+ * better. This decoder honours whichever the file declares.
51
+ */
52
+ type PlaneCodec = 'gzip' | 'deflate' | 'deflate-raw';
53
+ interface PlaneInfo {
54
+ offset: number;
55
+ /** Bytes IN THE FILE - the compressed size when `compression` is set. */
56
+ length: number;
57
+ dtype: PlaneDtype;
58
+ /** Values per splat. Stored COMPONENT-major: all c0, then all c1, ... */
59
+ components: number;
60
+ count: number;
61
+ compression?: PlaneCodec;
62
+ /** Bytes after inflation. Normative when `compression` is set. */
63
+ uncompressedLength?: number;
64
+ }
65
+ interface PlanarHeader {
66
+ format: string;
67
+ version: number;
68
+ precision: 'fp16' | 'q23';
69
+ numSplats: number;
70
+ numBones: number;
71
+ topK: number;
72
+ bounds: {
73
+ min: number[];
74
+ max: number[];
75
+ };
76
+ /** q23 only - normative, positions cannot be reconstructed without them. */
77
+ logScale?: {
78
+ min: number[];
79
+ max: number[];
80
+ };
81
+ logitOpacity?: {
82
+ min: number;
83
+ max: number;
84
+ };
85
+ bonePalette?: number[];
86
+ joints?: {
87
+ count: number;
88
+ };
89
+ /** Free-form; informational. The plane table is what the decoder obeys. */
90
+ encoding?: {
91
+ planeCompression?: PlaneCodec | null;
92
+ };
93
+ planes: Record<string, PlaneInfo>;
94
+ }
95
+ /** The 8-byte ASCII magic, or `''` when the buffer is too short to have one. */
96
+ declare function readMagic(buffer: ArrayBuffer): string;
97
+ /** True when `magic` names a planar container this module can decode. */
98
+ declare function isPlanarMagic(magic: string): boolean;
99
+ /**
100
+ * Decode a planar character bundle into the Float32 arrays the GPU pipeline
101
+ * expects, plus the skinning and joint data.
102
+ *
103
+ * Returns the same shape `parseBinaryGsplat` returns for the interleaved
104
+ * container (minus the animation chunk, which planar files do not carry - clips
105
+ * are registered separately), so both feed one `loadBinaryGsplat`.
106
+ */
107
+ declare function parsePlanarSplats(buffer: ArrayBuffer): Promise<{
108
+ result: {
109
+ header: PlanarHeader;
110
+ numSplats: number;
111
+ splats: {
112
+ x: Float32Array<ArrayBuffer>;
113
+ y: Float32Array<ArrayBuffer>;
114
+ z: Float32Array<ArrayBuffer>;
115
+ scale_0: Float32Array<ArrayBuffer>;
116
+ scale_1: Float32Array<ArrayBuffer>;
117
+ scale_2: Float32Array<ArrayBuffer>;
118
+ rot_0: Float32Array<ArrayBuffer>;
119
+ rot_1: Float32Array<ArrayBuffer>;
120
+ rot_2: Float32Array<ArrayBuffer>;
121
+ rot_3: Float32Array<ArrayBuffer>;
122
+ f_dc_0: Float32Array<ArrayBuffer>;
123
+ f_dc_1: Float32Array<ArrayBuffer>;
124
+ f_dc_2: Float32Array<ArrayBuffer>;
125
+ opacity: Float32Array<ArrayBuffer>;
126
+ };
127
+ boneIndices: Uint16Array<ArrayBufferLike> | null;
128
+ boneWeights: Float32Array<ArrayBufferLike> | null;
129
+ animationArray: Float32Array | null;
130
+ animNumFrames: number;
131
+ animNumBones: number;
132
+ jointsArray: Float32Array<ArrayBufferLike> | null;
133
+ nativeFloat16: boolean;
134
+ planeCompression: PlaneCodec | null;
135
+ };
136
+ transferables: ArrayBuffer[];
137
+ }>;
138
+ export { parsePlanarSplats, readMagic, isPlanarMagic, PLANAR_CONTAINERS, FIRST_PLANAR_VERSION };
139
+ export type { PlanarHeader, PlaneInfo, PlaneCodec, PlaneDtype };
@@ -0,0 +1,33 @@
1
+ import type { SplatSource } from '../../splat/splat';
2
+ import type { ArmatureData } from '../../types';
3
+ /** A clip embedded in a legacy character bundle: per-frame, per-bone 4x4 matrices. */
4
+ export interface RawAnimationData {
5
+ data: Float32Array;
6
+ numFrames: number;
7
+ numBones: number;
8
+ keyframeFrames?: number[];
9
+ }
10
+ /** A decoded character bundle, ready to become a {@link Splat} and an {@link Armature}. */
11
+ export interface CharacterBundle {
12
+ source: SplatSource;
13
+ armatureData?: ArmatureData;
14
+ animationData?: RawAnimationData;
15
+ }
16
+ /**
17
+ * Decode a character bundle held in memory.
18
+ *
19
+ * Accepts every container the reader implements - `VIGGCHAR` (`.vsplat`) and
20
+ * the legacy `.bin` layouts - and dispatches on the file's own magic, never on
21
+ * a filename.
22
+ *
23
+ * Decoding runs on the calling thread. A bundle is a few megabytes and decodes
24
+ * in single-digit milliseconds, which does not earn the packaging cost of a
25
+ * separate worker file.
26
+ *
27
+ * @throws {CharacterBundleError} carrying the format spec's error category.
28
+ */
29
+ export declare function decodeCharacterBundle(file: ArrayBuffer): Promise<CharacterBundle>;
30
+ /** Fetch and decode a character bundle. */
31
+ export declare function loadCharacterBundle(url: string): Promise<CharacterBundle>;
32
+ /** Decode a character bundle from bytes already in memory. */
33
+ export declare function loadCharacterBundleFromBytes(bytes: Uint8Array): Promise<CharacterBundle>;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * The one interface every character-bundle version handler satisfies, and the
3
+ * one error type they all fail with.
4
+ *
5
+ * This module is version-agnostic on purpose and holds no knowledge of any
6
+ * particular version: it is the boundary that lets `index.ts` treat `v1/` and
7
+ * `legacy/` as interchangeable, and the reason adding version N+1 costs exactly
8
+ * one module and one registry entry
9
+ * (character-bundle format spec, R2/R3).
10
+ */
11
+ /**
12
+ * Error categories, from the spec's `Errors` table.
13
+ *
14
+ * A caller distinguishes "not our file" (`WrongMagic`) from "our file, broken"
15
+ * (everything else) without parsing a message string.
16
+ */
17
+ type BundleErrorCategory = 'WrongMagic' | 'UnknownVersion' | 'Truncated' | 'MalformedHeader' | 'UnalignedPayload' | 'MissingField' | 'UnexpectedField' | 'BadFieldValue' | 'BlockSizeMismatch' | 'InconsistentHeader';
18
+ /**
19
+ * A rejection carrying its category.
20
+ *
21
+ * The category travels over the worker boundary alongside the message, so the
22
+ * main thread can tell a foreign file from a corrupt one without re-reading the
23
+ * bytes. The message names the offending block or field and reports the value
24
+ * found next to the value expected.
25
+ */
26
+ declare class CharacterBundleError extends Error {
27
+ readonly category: BundleErrorCategory;
28
+ constructor(category: BundleErrorCategory, message: string);
29
+ }
30
+ /** The 14 Float32 attribute arrays the GPU pipeline consumes, one per column. */
31
+ interface SplatAttributes {
32
+ x: Float32Array;
33
+ y: Float32Array;
34
+ z: Float32Array;
35
+ scale_0: Float32Array;
36
+ scale_1: Float32Array;
37
+ scale_2: Float32Array;
38
+ /** Quaternion `w`. The engine wants `wxyz`, whatever order the file stores. */
39
+ rot_0: Float32Array;
40
+ rot_1: Float32Array;
41
+ rot_2: Float32Array;
42
+ rot_3: Float32Array;
43
+ f_dc_0: Float32Array;
44
+ f_dc_1: Float32Array;
45
+ f_dc_2: Float32Array;
46
+ /** The LOGIT, not the alpha. */
47
+ opacity: Float32Array;
48
+ }
49
+ /**
50
+ * The version-independent decode output. Every handler returns this shape and a
51
+ * caller cannot tell from it which handler ran.
52
+ *
53
+ * Three of these fields exist because the answer differs per version and the
54
+ * QUESTION does not, so asking it downstream would put a per-version branch
55
+ * outside the handlers - the one thing R2 forbids:
56
+ *
57
+ * - `rigBoneCount` - the legacy containers spell the rig's size three
58
+ * different ways across two magics; version 1 has no header key for it at all
59
+ * and derives it from `blocks.joints.count`.
60
+ * - `corrected` - the legacy containers carry a correction receipt, in either
61
+ * of two spellings, and files exist both with and without it. Version 1 needs
62
+ * no receipt: the version itself means corrected, and the spec forbids a
63
+ * reader from looking for a signal.
64
+ * - `bundledRestIsPerCharacter` - whether `jointsArray` is this character's own
65
+ * vetted rest pose, i.e. whether a consumer may pose the splats against it
66
+ * instead of against the shared canonical tables.
67
+ */
68
+ interface DecodedBundle {
69
+ /** The version's own header, verbatim. Diagnostics; nothing dispatches on it. */
70
+ header: unknown;
71
+ numSplats: number;
72
+ splats: SplatAttributes;
73
+ /** `numSplats * K` global rig indices, or `null` when unskinned. */
74
+ boneIndices: Uint16Array | null;
75
+ /** `numSplats * K` weights, matching `boneIndices`, or `null`. */
76
+ boneWeights: Float32Array | null;
77
+ animationArray: Float32Array | null;
78
+ animNumFrames: number;
79
+ animNumBones: number;
80
+ /** Flat `[x,y,z] * rigBoneCount` rest translations, or `null` when no rig. */
81
+ jointsArray: Float32Array | null;
82
+ /** Bones in the rig this bundle poses. `0` when it carries no rig. */
83
+ rigBoneCount: number;
84
+ /** Whether the geometric corrections are baked into the splats and joints. */
85
+ corrected: boolean;
86
+ /**
87
+ * Whether `jointsArray` is this character's OWN rest pose, vetted against the
88
+ * gaussians it ships with - i.e. whether posing the splats from it (rather
89
+ * than from the shared canonical rest tables) is correct.
90
+ *
91
+ * A separate question from `corrected`, and deliberately not derived from it.
92
+ * `corrected` answers "did the correction pipeline run", which some legacy
93
+ * files answer yes to and others no, from the same generation of writer;
94
+ * this one answers "is the joints block authoritative", which is a property
95
+ * of the CONTAINER:
96
+ *
97
+ * - version 1 - yes, by definition. The joint canonicalisation, the ankle
98
+ * pitch bake and the ground shift are all baked into the gaussians AND
99
+ * into this block, so the two agree and nothing else does.
100
+ * - legacy - no. Those joints were written for diagnostics, were never
101
+ * vetted as a posing rest, and the shipped characters render today against
102
+ * the canonical tables.
103
+ *
104
+ * Read by `Character`'s ground-convention shim, which must be able to tell
105
+ * a v1 bundle from a legacy one WITHOUT keying on `corrected` - some legacy
106
+ * bundles carry a receipt, and moving those would move already-shipped
107
+ * characters. See `Character.bakedGroundShiftY`.
108
+ */
109
+ bundledRestIsPerCharacter: boolean;
110
+ }
111
+ /**
112
+ * What a handler hands back: the bundle, plus the buffers the worker may
113
+ * transfer instead of copying.
114
+ */
115
+ interface DecodeResult {
116
+ result: DecodedBundle;
117
+ transferables: ArrayBuffer[];
118
+ }
119
+ /**
120
+ * One payload version's reader.
121
+ *
122
+ * Given the whole file, the offset its payload begins at, and how long that
123
+ * payload is. A handler is total over its own version, never learns that
124
+ * another version exists, and never re-checks the magic or the version - the
125
+ * dispatcher owns both.
126
+ */
127
+ type VersionHandler = (file: ArrayBuffer, payloadOffset: number, payloadLength: number) => Promise<DecodeResult>;
128
+ export { CharacterBundleError };
129
+ export type { BundleErrorCategory, DecodeResult, DecodedBundle, SplatAttributes, VersionHandler, };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `VIGGCHAR` version 1 - the quantised block payload.
3
+ *
4
+ * Written from the character-bundle format spec and from nothing else.
5
+ * A JSON header with exactly two keys, `numSplats` and `blocks`, followed by
6
+ * the blocks it lists. Each block holds one attribute for every gaussian,
7
+ * stored component-major, and each block's entry carries everything needed to
8
+ * decode THAT block - its dequantisation bounds, its palette. There is no
9
+ * top-level decoding parameter and no file-wide precision mode.
10
+ *
11
+ * This handler is total over version 1 and knows that no other version exists.
12
+ * It never re-checks the magic or the version: the dispatcher owns both.
13
+ *
14
+ * Vocabulary note: this format says BLOCKS. The legacy containers next door say
15
+ * planes and chunks. The difference is deliberate - a block is self-describing
16
+ * in a way a plane was not, and keeping the words apart keeps the two readers
17
+ * from being read as one.
18
+ */
19
+ import type { DecodeResult } from '../types';
20
+ /**
21
+ * Decode a `VIGGCHAR` version 1 payload.
22
+ *
23
+ * Version 1 means CORRECTED: the ankle pitch bake and then the ground shift are
24
+ * already in the gaussians and in the `joints` block, and the character stands
25
+ * on `y = 0` by construction. There is no in-file signal about that and nothing
26
+ * below looks for one - the version answered it.
27
+ *
28
+ * The decode is straight-line and never yields; it is declared `async` only
29
+ * because `VersionHandler` is promise-shaped for the handlers that must be.
30
+ */
31
+ declare function decodeV1(file: ArrayBuffer, payloadOffset: number, payloadLength: number): Promise<DecodeResult>;
32
+ export { decodeV1 };
@@ -0,0 +1,16 @@
1
+ import type { SplatAttributes } from '../character/types';
2
+ /**
3
+ * Parse an uncompressed 3D Gaussian Splatting `.ply` (the layout written by
4
+ * the reference trainer and most tools since): one binary little-endian
5
+ * `vertex` element carrying position, log-scale, a `wxyz` rotation, the
6
+ * spherical-harmonic DC colour and a logit opacity.
7
+ *
8
+ * Higher-order SH (`f_rest_*`) and normals are skipped: splats here are lit
9
+ * flat, by their DC colour.
10
+ *
11
+ * @throws If the file is not a binary little-endian PLY, or lacks a required property.
12
+ */
13
+ export declare function parseGaussianPly(file: ArrayBuffer): {
14
+ numSplats: number;
15
+ splats: SplatAttributes;
16
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Fetch through a persistent Cache API layer with a 1-hour TTL.
3
+ * - Assets with cacheable extensions (.bin, .vsplat, .anim, .ply, …) →
4
+ * cached in Cache API
5
+ * - Signed URLs (with ?verify= or ?X-Amz-) → query stripped for cache key
6
+ * - No-store assets → never persisted. `NO_STORE_EXTENSIONS` is empty
7
+ * today, so nothing takes this branch; see its declaration.
8
+ * Server `Cache-Control: max-age` and `Expires` headers are honored as a cap
9
+ * when they provide a positive TTL.
10
+ * On network error, a stale cached copy is returned in preference to throwing.
11
+ */
12
+ declare function cachedFetch(url: string, init?: RequestInit): Promise<Response>;
13
+ /**
14
+ * Sweep the persistent cache and delete every entry whose stamp is in the
15
+ * past. Intended to run once per app boot, non-blocking, to keep the Cache
16
+ * API store from accumulating dead entries across sessions.
17
+ */
18
+ declare function evictExpired(): Promise<number>;
19
+ export { cachedFetch, evictExpired };
@@ -0,0 +1,19 @@
1
+ /** @group Characters & Animation */
2
+ export interface SkeletonLibrary {
3
+ boneNames?: string[];
4
+ skeleton1185Names?: string[];
5
+ skeleton1185StrippedNames?: string[];
6
+ stdMaleRestTranslations?: Float32Array;
7
+ stdMaleRestRotations?: Float32Array;
8
+ stdMaleParents?: Int32Array;
9
+ boneNames441?: string[];
10
+ restTranslations441?: Float32Array;
11
+ restRotations441?: Float32Array;
12
+ parents441?: Int32Array;
13
+ }
14
+ export declare function getStripped1185Names(library: SkeletonLibrary): string[] | undefined;
15
+ /**
16
+ * Build the skeleton library from inlined data.
17
+ * No network requests - all data is bundled in the engine.
18
+ */
19
+ export declare function loadSkeletonLibrary(): SkeletonLibrary;
@@ -0,0 +1,5 @@
1
+ export declare const NUM_BONES_441 = 441;
2
+ export declare const BONE_NAMES_441: string[];
3
+ export declare const PARENTS_441: number[];
4
+ export declare const REST_TRANSLATIONS_441: number[];
5
+ export declare const REST_ROTATIONS_441: number[];
@@ -0,0 +1,6 @@
1
+ export declare const NUM_BONES = 86;
2
+ export declare const BONE_NAMES: string[];
3
+ export declare const SKELETON_1185_NAMES: string[];
4
+ export declare const REST_TRANSLATIONS: number[];
5
+ export declare const REST_ROTATIONS: number[];
6
+ export declare const PARENTS: number[];
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Internal math kernel for the skeletal core.
3
+ *
4
+ * The rig, animation and skinning code is shared with the PlayCanvas build of
5
+ * this engine and was written against that engine's `Vec3` / `Quat` / `Mat4`.
6
+ * These classes keep the same method names, argument orders and formulas, so
7
+ * the shared files compile unchanged and the bone matrices they produce match
8
+ * the PlayCanvas build to float precision - which is what the A/B suite checks.
9
+ *
10
+ * Deliberately NOT `THREE.Vector3` and friends: the conventions differ in ways
11
+ * that silently change results (`lerp(a, b, t)` vs `lerp(v, t)`, a two-argument
12
+ * `cross`, a `Float32Array`-backed matrix), and a port that swapped them by
13
+ * hand would be a port nobody could diff against the original.
14
+ *
15
+ * Nothing here is part of the public API. Values cross into three.js types at
16
+ * the `Character` / `Armature` boundary.
17
+ */
18
+ export declare class Vec3 {
19
+ x: number;
20
+ y: number;
21
+ z: number;
22
+ constructor(x?: number, y?: number, z?: number);
23
+ set(x: number, y: number, z: number): this;
24
+ copy(rhs: {
25
+ x: number;
26
+ y: number;
27
+ z: number;
28
+ }): this;
29
+ clone(): Vec3;
30
+ add(rhs: Vec3): this;
31
+ add2(lhs: Vec3, rhs: Vec3): this;
32
+ sub2(lhs: Vec3, rhs: Vec3): this;
33
+ mulScalar(scalar: number): this;
34
+ dot(rhs: Vec3): number;
35
+ cross(lhs: Vec3, rhs: Vec3): this;
36
+ length(): number;
37
+ normalize(): this;
38
+ lerp(lhs: Vec3, rhs: Vec3, alpha: number): this;
39
+ }
40
+ export declare class Quat {
41
+ x: number;
42
+ y: number;
43
+ z: number;
44
+ w: number;
45
+ constructor(x?: number, y?: number, z?: number, w?: number);
46
+ set(x: number, y: number, z: number, w: number): this;
47
+ copy(rhs: {
48
+ x: number;
49
+ y: number;
50
+ z: number;
51
+ w: number;
52
+ }): this;
53
+ clone(): Quat;
54
+ conjugate(): this;
55
+ dot(other: Quat): number;
56
+ length(): number;
57
+ mulScalar(scalar: number): this;
58
+ normalize(): this;
59
+ /** `this = lhs * rhs`. Safe when `this` aliases either operand. */
60
+ mul2(lhs: Quat, rhs: Quat): this;
61
+ mul(rhs: Quat): this;
62
+ slerp(lhs: Quat, rhs: Quat, alpha: number): this;
63
+ /** Rotation of the upper 3x3 of `m`, with any scale (and a mirror) divided out. */
64
+ setFromMat4(m: Mat4): this;
65
+ }
66
+ /** Column-major 4x4, `Float32Array`-backed. Same element layout as `THREE.Matrix4.elements`. */
67
+ export declare class Mat4 {
68
+ data: Float32Array;
69
+ constructor();
70
+ copy(rhs: Mat4): this;
71
+ clone(): Mat4;
72
+ setIdentity(): this;
73
+ /** Copy from any 16-element column-major source (e.g. `THREE.Matrix4.elements`). */
74
+ setFromArray(src: ArrayLike<number>): this;
75
+ setTRS(t: Vec3, r: Quat, s: Vec3): this;
76
+ /** `this = lhs * rhs`. Safe when `this` aliases either operand. */
77
+ mul2(lhs: Mat4, rhs: Mat4): this;
78
+ mul(rhs: Mat4): this;
79
+ invert(): this;
80
+ getTranslation(t?: Vec3): Vec3;
81
+ getScale(scale?: Vec3): Vec3;
82
+ }
@@ -0,0 +1,185 @@
1
+ import type { Animation, AnimationEvent } from '../animation';
2
+ import type { Pose } from './pose';
3
+ import type { Skeleton } from './skeleton';
4
+ /**
5
+ * Resolves an animation by name and returns an already-filtered/converted
6
+ * {@link Animation}, or `null` if not registered. Lets {@link AnimationPlayer}
7
+ * stay free of `SplatEngine` and `SkeletonLibrary`.
8
+ *
9
+ * @group Characters & Animation
10
+ */
11
+ export type AnimationProvider = (name: string) => Animation | null;
12
+ /**
13
+ * Options for {@link AnimationPlayer.crossfadeTo}.
14
+ *
15
+ * @group Characters & Animation
16
+ */
17
+ export interface CrossfadeOptions {
18
+ /** Blend duration in seconds. Defaults to `defaultCrossfadeDuration`. */
19
+ duration?: number;
20
+ /** Loop the target animation. Default `true`. */
21
+ loop?: boolean;
22
+ /**
23
+ * Preserve the current animation's normalized phase
24
+ * (`animTime / numFrames`) when crossfading. Useful for "style change"
25
+ * crossfades where the new looping animation should start at the same
26
+ * cycle position as the outgoing one (e.g. switching idle variants
27
+ * mid-cycle without a snap). Default `false` - new anim starts at
28
+ * frame 0.
29
+ */
30
+ preserveTime?: boolean;
31
+ }
32
+ /**
33
+ * Callback fired when {@link AnimationPlayer.tick} crosses a frame that has
34
+ * one or more {@link AnimationEvent}s on the active animation.
35
+ *
36
+ * @group Characters & Animation
37
+ */
38
+ export type AnimEventCallback = (event: AnimationEvent) => void;
39
+ /**
40
+ * Skeletal animation state machine. Owns playback state (current animation,
41
+ * time, loop flag, crossfade window) and writes the per-bone target pose into
42
+ * a caller-owned {@link Pose} each `update`.
43
+ *
44
+ * Knows nothing about splats, entities, or bone names. Output is a raw
45
+ * crossfaded pose; rig-convention clamps (pelvis X/Z lock, root-bone clamp,
46
+ * root-motion extraction onto the armature pivot) are the
47
+ * {@link Armature} facade's job.
48
+ *
49
+ * @group Characters & Animation
50
+ */
51
+ export declare class AnimationPlayer {
52
+ /** Default crossfade duration when {@link CrossfadeOptions.duration} is omitted. */
53
+ defaultCrossfadeDuration: number;
54
+ /** Frame rate used to convert `dt` (seconds) to frame time. Default `30`. */
55
+ frameRate: number;
56
+ /**
57
+ * Playback-speed multiplier applied to `dt` in {@link tick}. `1` = author
58
+ * speed, `2` = double speed, `0.5` = half. Unlike {@link frameRate} this
59
+ * scales only how fast time advances - the clip's authored frame rate (and
60
+ * thus event-frame mapping) is unchanged. Lets a game tie a locomotion
61
+ * clip's speed to the pivot's actual movement speed. Crossfade blending is
62
+ * deliberately NOT sped up (it runs on real `dt`), so style swaps stay
63
+ * smooth regardless of playback speed.
64
+ */
65
+ playbackSpeed: number;
66
+ private readonly _skeleton;
67
+ private readonly _provider;
68
+ private _active;
69
+ private _animName;
70
+ private _animData;
71
+ private _keyframes;
72
+ private _keyframesReady;
73
+ private _animTime;
74
+ private _loop;
75
+ private _paused;
76
+ private _blendToRest;
77
+ private _crossfadeTime;
78
+ private _crossfadeDuration;
79
+ private readonly _snapshot;
80
+ private _lastTickFrame;
81
+ private readonly _eventListeners;
82
+ private readonly _tmpPos;
83
+ private readonly _tmpRot;
84
+ private readonly _tmpScale;
85
+ private readonly _tmpSlerpRot;
86
+ private readonly _tmpToRot;
87
+ private readonly _restIn;
88
+ private readonly _restOut;
89
+ private readonly _splineValues;
90
+ constructor(opts: {
91
+ skeleton: Skeleton;
92
+ animationProvider: AnimationProvider;
93
+ snapshotPose: Pose;
94
+ });
95
+ get isPlaying(): boolean;
96
+ get isFinished(): boolean;
97
+ get paused(): boolean;
98
+ set paused(v: boolean);
99
+ get animationTime(): number;
100
+ set animationTime(v: number);
101
+ get animationDuration(): number;
102
+ /**
103
+ * Crossfade alpha in `[0, 1]`. `0` when no crossfade is active or one just
104
+ * started; `1` when the in-progress crossfade has reached its terminal
105
+ * blend. Useful for games that want to gate logic on "the rig has fully
106
+ * committed to the new pose" without reaching into player internals.
107
+ */
108
+ get crossfadeProgress(): number;
109
+ /**
110
+ * Name of the currently-playing animation, or `null` if no animation is
111
+ * active (after {@link AnimationPlayer.stop} with `fadeDuration: 0`, or
112
+ * before the first {@link AnimationPlayer.play}).
113
+ */
114
+ get currentAnimationName(): string | null;
115
+ /**
116
+ * Subscribe to frame-aligned {@link AnimationEvent}s on the currently-
117
+ * playing animation. The callback fires once per crossed frame, after the
118
+ * `tick` advances `animTime`.
119
+ *
120
+ * @returns an unsubscribe handle.
121
+ */
122
+ onAnimEvent(cb: AnimEventCallback): () => void;
123
+ private _dispatchEventsForFrameTransition;
124
+ private _fireEvent;
125
+ /**
126
+ * Start an animation immediately (no crossfade). Restarts from frame 0.
127
+ * Returns `false` if `name` isn't resolvable through the provider.
128
+ */
129
+ play(name: string, opts?: {
130
+ loop?: boolean;
131
+ }): boolean;
132
+ /**
133
+ * Crossfade from `currentPose` to `name` over `opts.duration` seconds. The
134
+ * current pose is captured into the internal snapshot - caller can keep
135
+ * mutating `currentPose` after the call returns.
136
+ */
137
+ crossfadeTo(name: string, currentPose: Pose, opts?: CrossfadeOptions): boolean;
138
+ /**
139
+ * Snap any active crossfade to its terminal state (alpha = 1, target pose
140
+ * fully applied) on the next {@link AnimationPlayer.evaluate}. No-op if no
141
+ * crossfade is active. Useful when a game scrubs through animation frames
142
+ * for precompute and wants per-frame poses without the blend artefacts of
143
+ * a still-in-progress crossfade.
144
+ */
145
+ endCrossfade(): void;
146
+ /**
147
+ * Stop playback. With `fadeDuration > 0`, snapshots `currentPose` and
148
+ * crossfades back to bind over `fadeDuration` seconds. With `0`, deactivates
149
+ * immediately (the caller is expected to reset the pose to bind).
150
+ */
151
+ stop(currentPose: Pose, fadeDuration?: number): void;
152
+ /**
153
+ * Advance animation time by `dt` seconds. Always cheap - call every frame
154
+ * even for off-screen characters so they resume at the correct frame when
155
+ * the camera pans back. Does **not** write to any `Pose`.
156
+ *
157
+ * Returns `wrapped: true` if `animTime` rolled past the end of a looping
158
+ * animation on this tick. The facade uses that signal to suppress the
159
+ * end-to-start pelvis discontinuity when extracting root motion.
160
+ */
161
+ tick(dt: number): {
162
+ wrapped: boolean;
163
+ };
164
+ /**
165
+ * Evaluate the current pose at the current `animTime` into `outPose`,
166
+ * applying the crossfade blend with the internal snapshot if one is active.
167
+ *
168
+ * Returns `blendToRestComplete: true` once on the tick a previously-
169
+ * scheduled `stop(_, fade > 0)` reaches alpha 1 - the facade uses that to
170
+ * snap pose to bind and reset rootMotion baseline.
171
+ *
172
+ * Skip this call when the character is off-screen; `tick` keeps time
173
+ * advancing in the meantime, so the next on-screen frame evaluates straight
174
+ * to the correct pose with no visible pop.
175
+ */
176
+ evaluate(outPose: Pose): {
177
+ blendToRestComplete: boolean;
178
+ };
179
+ private _activate;
180
+ private _deactivate;
181
+ private _prepareKeyframes;
182
+ private _evalTargetPose;
183
+ private _evaluateSpline;
184
+ private _applyFromRest;
185
+ }