@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
package/dist/index.js ADDED
@@ -0,0 +1,56 @@
1
+ import {
2
+ Animation,
3
+ Armature,
4
+ Character,
5
+ CharacterBundleError,
6
+ EngineDisposedError,
7
+ Environment,
8
+ PointLight,
9
+ SkyLight,
10
+ Splat,
11
+ SplatCharacter,
12
+ SplatClipLoader,
13
+ SplatEngine,
14
+ SplatMesh,
15
+ SplatPLYLoader,
16
+ SplatQuality,
17
+ SunLight,
18
+ VSplatLoader,
19
+ WebGLSplatBackend,
20
+ decodeCharacterBundle,
21
+ loadCharacterBundle,
22
+ loadCharacterBundleFromBytes,
23
+ makeRegionMask,
24
+ packSplats,
25
+ parseGaussianPly,
26
+ pruneSplats
27
+ } from "./chunks/chunk-WN5WBKM4.js";
28
+ export {
29
+ Animation,
30
+ Armature,
31
+ Character,
32
+ CharacterBundleError,
33
+ EngineDisposedError,
34
+ Environment,
35
+ PointLight,
36
+ SkyLight,
37
+ Splat,
38
+ SplatCharacter,
39
+ Animation as SplatClip,
40
+ SplatClipLoader,
41
+ SplatEngine,
42
+ SplatMesh,
43
+ SplatPLYLoader,
44
+ SplatQuality,
45
+ SunLight,
46
+ VSplatLoader,
47
+ WebGLSplatBackend,
48
+ decodeCharacterBundle,
49
+ loadCharacterBundle,
50
+ loadCharacterBundleFromBytes,
51
+ makeRegionMask,
52
+ packSplats,
53
+ parseGaussianPly,
54
+ pruneSplats
55
+ };
56
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,25 @@
1
+ import { Animation } from '../animation';
2
+ import type { SkeletonLibrary } from '../types';
3
+ /**
4
+ * Walk the raw 4×4 bone matrices stored in `animationData.data` and produce
5
+ * the per-bone keyframe + spline cache used at playback time.
6
+ *
7
+ * Idempotent - subsequent calls on the same `Animation` are no-ops (guarded
8
+ * by `Animation.skeletalConverted`). {@link Animation.register} calls this
9
+ * for you.
10
+ *
11
+ * Side effect: clears `animationData.data` once converted, since the raw
12
+ * matrices are no longer needed and would otherwise hold ~16 floats per bone
13
+ * per frame in memory for the lifetime of the animation.
14
+ */
15
+ export declare function convertSkeletalAnimation(animationData: Animation, _skeletonLibrary: SkeletonLibrary): void;
16
+ /**
17
+ * Return a view of `animationData` remapped to the target armature's bone
18
+ * count, handling rig mismatches (e.g. a 1185-bone source animation played on
19
+ * an 86-bone body rig). Target bones with no matching source bone stay at
20
+ * rest pose during playback.
21
+ *
22
+ * Result is cached on the input via `Animation.filteredForBones`, so repeated
23
+ * calls for the same target bone count reuse the filtered copy.
24
+ */
25
+ export declare function filterAnimationForArmature(animationData: Animation, targetBoneCount: number, skeletonLibrary: SkeletonLibrary): Animation;
@@ -0,0 +1,15 @@
1
+ import { Mat4 } from '../math';
2
+ /**
3
+ * Walk the rig's rest pose to produce world-space bind-pose matrices, one per
4
+ * bone. Parents are expected to come before their children in index order (the
5
+ * skeleton library's bone arrays satisfy this).
6
+ *
7
+ * Returns `null` if any of the three arrays is missing - the armature's
8
+ * `armatureData` carries them as optional fields.
9
+ */
10
+ export declare function computeBindPoseTransforms(numBones: number, restTranslations: Float32Array, restRotations: Float32Array, parents: Int32Array): Mat4[];
11
+ /**
12
+ * Compute the inverse of each matrix in `bindPose`. Returns a new array; the
13
+ * input is left untouched.
14
+ */
15
+ export declare function invertBindPose(bindPose: Mat4[]): Mat4[];
@@ -0,0 +1,59 @@
1
+ import { Quat, Vec3 } from '../math';
2
+ import type { RestPose } from './rest-relative';
3
+ import { CubicSpline } from './spline';
4
+ /** One sample on a per-bone animation curve. */
5
+ export interface Pose {
6
+ frame: number;
7
+ position: Vec3;
8
+ rotation: Quat;
9
+ scale: Vec3;
10
+ }
11
+ /**
12
+ * Per-bone keyframe entry: a list of poses sorted by frame plus the cached
13
+ * cubic spline that interpolates them. `spline` is null when there are fewer
14
+ * than two poses (a constant-pose bone - the consumer samples `poses[0]`).
15
+ */
16
+ export interface KeyframeEntryData {
17
+ poses: Pose[];
18
+ spline: CubicSpline | null;
19
+ }
20
+ interface RebuildOptions {
21
+ /** Include poses where `frame === duration`. Default `false`. */
22
+ includeEnd?: boolean;
23
+ /** Build a looping spline that wraps at `duration`. Default `false`. */
24
+ looping?: boolean;
25
+ }
26
+ /**
27
+ * Filter `poses` against `[0, duration)` (or `[0, duration]` when
28
+ * `options.includeEnd`), sort by frame, and build the cubic spline that
29
+ * interpolates them.
30
+ *
31
+ * Returns `{ poses, spline }`: a fresh sorted poses array (callers should
32
+ * replace their original) and the spline, or `null` if fewer than two poses
33
+ * survive the filter.
34
+ *
35
+ * Quaternion sign is normalized along the timeline so neighboring keys take
36
+ * the short arc - without this the spline would interpolate the long way
37
+ * around whenever a sample flips hemisphere.
38
+ */
39
+ export declare function rebuildKeyframeSpline(poses: Pose[], duration: number, options?: RebuildOptions): {
40
+ poses: Pose[];
41
+ spline: CubicSpline | null;
42
+ };
43
+ /**
44
+ * Build the per-bone keyframe array an armature samples from at playback time.
45
+ *
46
+ * For each bone in `libraryKeyframes` whose index is below `numBones`:
47
+ * 1. convert every pose into the bone's rest-relative space (so playback can
48
+ * blend a delta on top of the rest pose), and
49
+ * 2. rebuild the cubic spline, filtered+sorted by frame and wrapped for
50
+ * looping if requested.
51
+ *
52
+ * Result is a sparse array of length `numBones`; bones not referenced by the
53
+ * source map stay `null` and fall back to rest pose at evaluation time.
54
+ *
55
+ * If `restPose.positions` is empty (rig has no rest data), poses are emitted
56
+ * unchanged - the rest-relative conversion no-ops.
57
+ */
58
+ export declare function buildArmatureKeyframes(libraryKeyframes: Map<number, KeyframeEntryData>, numFrames: number, numBones: number, restPose: RestPose, looping: boolean, translationMask?: Uint8Array): Array<KeyframeEntryData | null>;
59
+ export {};
@@ -0,0 +1,32 @@
1
+ import { Quat } from '../math';
2
+ import type { Vec3 } from '../math';
3
+ /**
4
+ * Rest pose for a rig, stored as parallel arrays indexed by bone. An empty
5
+ * `positions` array marks an uninitialized rest pose - the rest-relative
6
+ * conversion functions pass poses through unchanged in that case.
7
+ */
8
+ export interface RestPose {
9
+ positions: Vec3[];
10
+ rotations: Quat[];
11
+ scales: Vec3[];
12
+ }
13
+ /** A bone-space transform: position, rotation (as quaternion), and scale. */
14
+ export interface BonePose {
15
+ position: Vec3;
16
+ rotation: Quat;
17
+ scale: Vec3;
18
+ }
19
+ /**
20
+ * Express `pose` relative to the bone's rest transform. Writes the result
21
+ * into `out`. Safe to call with `out === pose` (in-place).
22
+ *
23
+ * If `boneIndex` is out of range or `rest` is uninitialized, `pose` is copied
24
+ * to `out` unchanged.
25
+ */
26
+ export declare function toRestRelative(boneIndex: number, pose: BonePose, rest: RestPose, out: BonePose): void;
27
+ /**
28
+ * Inverse of {@link toRestRelative}: compose a rest-relative `delta` back into
29
+ * an absolute bone pose. Writes the result into `out`. Safe to call with
30
+ * `out === delta` (in-place).
31
+ */
32
+ export declare function fromRestRelative(boneIndex: number, delta: BonePose, rest: RestPose, out: BonePose): void;
@@ -0,0 +1,14 @@
1
+ declare class CubicSpline {
2
+ times: number[];
3
+ knots: number[];
4
+ dim: number;
5
+ readonly keyframeOffset: number;
6
+ constructor(times: number[], knots: number[], keyframeOffset?: number);
7
+ evaluate(time: number, result: number[]): void;
8
+ getKnot(index: number, result: number[]): void;
9
+ evaluateSegment(segment: number, t: number, result: number[]): void;
10
+ static calcKnots(times: number[], points: number[], smoothness: number): number[];
11
+ static fromPoints(times: number[], points: number[], smoothness?: number): CubicSpline;
12
+ static fromPointsLooping(length: number, times: number[], points: number[], smoothness?: number): CubicSpline;
13
+ }
14
+ export { CubicSpline };
@@ -0,0 +1,216 @@
1
+ import type { Quat, Vec3 } from './math';
2
+ import type { CubicSpline } from './anim/spline';
3
+ import type { SplatEngine } from './engine';
4
+ /**
5
+ * A frame-aligned event attached to an {@link Animation}. Events are fired by
6
+ * {@link AnimationPlayer} as `animTime` crosses each `frame`, dispatched
7
+ * through {@link Armature.onAnimEvent} / {@link Character.onAnimEvent}.
8
+ *
9
+ * The engine treats `name` and `payload` as opaque - games define their own
10
+ * vocabulary (e.g. `'footstep'`, `'hit-impact'`, `'screen-shake'`) and route
11
+ * via the listener.
12
+ *
13
+ * @group Characters & Animation
14
+ */
15
+ export interface AnimationEvent {
16
+ /** Frame index (integer, `0 .. numFrames-1`). */
17
+ frame: number;
18
+ /** Game-defined event name. */
19
+ name: string;
20
+ /** Optional game-defined payload, opaque to the engine. */
21
+ payload?: unknown;
22
+ }
23
+ /**
24
+ * Per-bone keyframe entry inside a {@link SkeletalStore}.
25
+ * @internal
26
+ */
27
+ export interface SkeletalKeyframeEntry {
28
+ poses: Array<{
29
+ frame: number;
30
+ position: Vec3;
31
+ rotation: Quat;
32
+ scale: Vec3;
33
+ }>;
34
+ spline: CubicSpline | null;
35
+ }
36
+ /**
37
+ * Engine-private cache attached to an {@link Animation} after
38
+ * {@link convertSkeletalAnimation} processes the raw matrices into
39
+ * per-bone keyframes + splines. Subsequent registrations and playback reads
40
+ * go through this store rather than re-walking the raw `Float32Array`.
41
+ *
42
+ * @internal
43
+ */
44
+ export interface SkeletalStore {
45
+ keyframes: Map<number, SkeletalKeyframeEntry>;
46
+ manualBones: Set<number>;
47
+ source: string;
48
+ }
49
+ /**
50
+ * Parsed skeletal animation data.
51
+ *
52
+ * An `Animation` is the deserialized form of one clip, whatever it was
53
+ * authored as. It holds a flat `Float32Array` of per-bone 4×4 matrices indexed
54
+ * by frame and bone. Once registered on an engine under a name, characters can play
55
+ * it via {@link Character.playAnimation} / {@link Character.crossfadeTo}.
56
+ *
57
+ * The primary entry point is {@link Animation.loadGlb}, which fetches a
58
+ * MetaHuman GLB, parses and registers it in one call. For custom fetch
59
+ * pipelines, use {@link Animation.fromGlb} on an already-fetched
60
+ * `ArrayBuffer`, then {@link Animation#register} to attach it to the engine.
61
+ * The `.anim` container is still read, by {@link Animation.load} and
62
+ * {@link Animation.parse}.
63
+ *
64
+ * @group Characters & Animation
65
+ */
66
+ export declare class Animation {
67
+ /** Column-major bone matrices (translation at `[12..14]`, the layout
68
+ * `THREE.Matrix4.elements` uses), length `numFrames * numBones * 16`. */
69
+ data: Float32Array;
70
+ /** Number of animation frames. */
71
+ numFrames: number;
72
+ /** Number of bones per frame. */
73
+ numBones: number;
74
+ /** Optional sparse keyframe indices. If present, frames between keyframes are interpolated. */
75
+ keyframeFrames?: number[];
76
+ /**
77
+ * Per bone: `1` if this clip drives the bone's translation, `0` if the bone
78
+ * must keep the armature's own rest translation.
79
+ *
80
+ * A constant translation track is not motion - it is the source rig's bone
81
+ * length. Applied to a character whose skeleton differs, it silently replaces
82
+ * that character's bone lengths mid-clip while the bind pose keeps the
83
+ * originals, which compresses the skin. Both readers are built with that in
84
+ * mind and set a `0` for every bone whose translation holds still, judged from
85
+ * the decoded data: {@link Animation.fromGlb} per channel, {@link Animation.parse}
86
+ * across the per-frame matrices. `undefined` means every bone genuinely
87
+ * translates, and every bone drives its own translation.
88
+ *
89
+ * Set once, at parse time, and never mutated afterwards. Playback caches
90
+ * keyframes per `Animation` object, so a mask that could change would go stale
91
+ * inside them. `filterAnimationForArmature` remaps rather than edits, onto a
92
+ * new `Animation` that is its own cache key.
93
+ *
94
+ * Consumed by `buildArmatureKeyframes`, which zeroes the position delta for
95
+ * masked bones so `fromRestRelative` yields `pose.position = restPosition`.
96
+ */
97
+ translationMask?: Uint8Array;
98
+ /**
99
+ * Frame-aligned events fired as `animTime` advances. Set by
100
+ * {@link Animation.parse} from the `.anim` header's optional `events` array,
101
+ * or assigned directly by game code before {@link Animation.register}.
102
+ */
103
+ events?: AnimationEvent[];
104
+ /** @internal Set by convertSkeletalAnimation on first registration. */
105
+ /** The clip's own name, where its file gave one. Not the name it is registered under. */
106
+ name: string;
107
+ /** Frames per second the clip was authored at. `30` unless the file says otherwise. */
108
+ frameRate: number;
109
+ /** The name of the most recent {@link Animation.register}, if any. */
110
+ registeredName?: string;
111
+ private _registeredIn;
112
+ /** Length in seconds at the authored frame rate. */
113
+ get duration(): number;
114
+ skeletalConverted: boolean;
115
+ /** @internal Per-bone keyframe cache produced by convertSkeletalAnimation. */
116
+ skeletalStore?: SkeletalStore;
117
+ /** @internal Per-target-bone-count cache of `filterAnimationForArmature` outputs. */
118
+ filteredForBones?: Map<number, Animation>;
119
+ constructor(data: Float32Array, numFrames: number, numBones: number, keyframeFrames?: number[]);
120
+ /**
121
+ * Parse an in-memory `.anim` buffer.
122
+ *
123
+ * Accepts both v2 (float32) and v3 (float16) data encodings. Use this when
124
+ * fetching through a custom pipeline; pair with {@link Animation#register}
125
+ * to make the result playable. Otherwise prefer {@link Animation.load}, the
126
+ * fetching wrapper around this one. For a MetaHuman GLB, the current
127
+ * container, the pair is {@link Animation.fromGlb} / {@link Animation.loadGlb}.
128
+ *
129
+ * Also derives {@link Animation#translationMask} from the decoded matrices, so
130
+ * bones whose translation never moves keep the *target* character's bone
131
+ * lengths instead of the source rig's.
132
+ *
133
+ * @throws If the header is missing, malformed, or not of type `skeletal_animation`.
134
+ */
135
+ static parse(buffer: ArrayBuffer): Animation;
136
+ /**
137
+ * Parse an in-memory *MetaHuman GLB* - a glTF 2.0 binary holding a bone
138
+ * hierarchy and one animation clip, carrying no real geometry (as produced
139
+ * by the offline MetaHuman GLB exporter).
140
+ *
141
+ * The result is indistinguishable from {@link Animation.parse} output: the
142
+ * clip's absolute parent-local TRS is packed into the same flat 4x4 bone
143
+ * matrices, so every downstream stage - `convertSkeletalAnimation`,
144
+ * `filterAnimationForArmature`, `AnimationPlayer` - behaves exactly as it
145
+ * does for a `.anim`.
146
+ *
147
+ * `animationName` selects a clip by name when the GLB holds more than one;
148
+ * omitted, the first clip is used. Pair with {@link Animation#register} to
149
+ * make the result playable, or use {@link Animation.loadGlb}.
150
+ *
151
+ * @throws If the container is not GLB 2.0, or the clip does not supply a
152
+ * translation and a rotation channel for every bone on one shared timeline.
153
+ */
154
+ static fromGlb(buffer: ArrayBuffer, animationName?: string): Animation;
155
+ /**
156
+ * {@link Animation.fromGlb} without the frame-rate warning: the clip's own
157
+ * rate is recorded on {@link Animation.frameRate} for a caller that applies it.
158
+ * @internal
159
+ */
160
+ static parseGlb(buffer: ArrayBuffer, animationName?: string): Animation;
161
+ /**
162
+ * Fetch, parse, and register a `.anim` file on the engine under `name`.
163
+ *
164
+ * After this resolves, characters can play it via
165
+ * `character.playAnimation(name)` / `character.crossfadeTo(name)`.
166
+ *
167
+ * @throws If the network request fails or the file is not a valid animation.
168
+ * @example
169
+ * ```ts
170
+ * await Animation.load(engine, '/assets/idle.anim', 'idle');
171
+ * character.playAnimation('idle', { loop: true });
172
+ * ```
173
+ */
174
+ static load(engine: SplatEngine, url: string, name: string): Promise<Animation>;
175
+ /**
176
+ * Fetch, parse, and register a MetaHuman GLB on the engine under `name`.
177
+ * The GLB counterpart of {@link Animation.load}.
178
+ *
179
+ * `animationName` selects a clip inside the GLB by name; omitted, the first
180
+ * clip is used. After this resolves, characters can play it via
181
+ * `character.playAnimation(name)` / `character.crossfadeTo(name)` - the
182
+ * source format is invisible from that point on.
183
+ *
184
+ * @throws If the network request fails or the GLB is not a valid armature clip.
185
+ * @example
186
+ * ```ts
187
+ * await Animation.loadGlb(engine, '/assets/metahuman_full.glb', 'walk');
188
+ * character.playAnimation('walk', { loop: true });
189
+ * ```
190
+ */
191
+ static loadGlb(engine: SplatEngine, url: string, name: string, animationName?: string): Promise<Animation>;
192
+ /**
193
+ * Process-wide ArrayBuffer cache for .anim URLs. Each `SplatEngine` still
194
+ * gets its own parsed `Animation` (storage arrays are mutated by the
195
+ * skeletal converter so they're not safely shareable across scenes),
196
+ * but the network bytes are shared. Avoids re-downloading the same
197
+ * idle/walk clips every time the app swaps one engine for another.
198
+ */
199
+ private static _bufferCache;
200
+ private static _bufferLoading;
201
+ private static _fetchBuffer;
202
+ /**
203
+ * Convert the animation data against the engine's skeleton library and
204
+ * register it under `name` so characters can play it.
205
+ *
206
+ * Must be called before {@link Character.playAnimation} / {@link Character.crossfadeTo}
207
+ * reference this animation.
208
+ */
209
+ register(engine: SplatEngine, name: string): void;
210
+ /**
211
+ * Remove the clip from the engine it is registered with, so it can be
212
+ * garbage-collected once nothing is playing it. A character already playing
213
+ * the clip keeps playing; starting it again registers it afresh.
214
+ */
215
+ dispose(): void;
216
+ }