@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.
- package/LICENSE +21 -0
- package/README.md +290 -2
- package/dist/chunks/chunk-MM6DJU6S.js +7801 -0
- package/dist/chunks/chunk-MM6DJU6S.js.map +7 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +7 -0
- package/dist/types/anim/animation-conversion.d.ts +25 -0
- package/dist/types/anim/bind-pose.d.ts +15 -0
- package/dist/types/anim/keyframe-splines.d.ts +59 -0
- package/dist/types/anim/rest-relative.d.ts +32 -0
- package/dist/types/anim/spline.d.ts +14 -0
- package/dist/types/animation.d.ts +216 -0
- package/dist/types/armature.d.ts +524 -0
- package/dist/types/character.d.ts +540 -0
- package/dist/types/engine.d.ts +166 -0
- package/dist/types/environment.d.ts +53 -0
- package/dist/types/index.d.ts +37 -0
- package/dist/types/lighting/color.d.ts +13 -0
- package/dist/types/lighting/point-light.d.ts +33 -0
- package/dist/types/lighting/sky-light.d.ts +22 -0
- package/dist/types/lighting/sun-light.d.ts +34 -0
- package/dist/types/loader/animation/animation-glb.d.ts +117 -0
- package/dist/types/loader/character/index.d.ts +33 -0
- package/dist/types/loader/character/legacy/index.d.ts +34 -0
- package/dist/types/loader/character/legacy/interleaved-splat.d.ts +101 -0
- package/dist/types/loader/character/legacy/planar-splat.d.ts +139 -0
- package/dist/types/loader/character/load.d.ts +33 -0
- package/dist/types/loader/character/types.d.ts +129 -0
- package/dist/types/loader/character/v1/decode.d.ts +32 -0
- package/dist/types/loader/environment/ply.d.ts +16 -0
- package/dist/types/loader/shared/bin-cache.d.ts +19 -0
- package/dist/types/loader/skeleton/index.d.ts +19 -0
- package/dist/types/loader/skeleton/skeleton-data-441.d.ts +5 -0
- package/dist/types/loader/skeleton/skeleton-data.d.ts +6 -0
- package/dist/types/math.d.ts +82 -0
- package/dist/types/skeletal/animation-player.d.ts +185 -0
- package/dist/types/skeletal/bone-mask.d.ts +27 -0
- package/dist/types/skeletal/bone-stretch.d.ts +65 -0
- package/dist/types/skeletal/pose.d.ts +27 -0
- package/dist/types/skeletal/skeleton.d.ts +57 -0
- package/dist/types/skeletal/splat-skinning.d.ts +69 -0
- package/dist/types/splat/backend.d.ts +57 -0
- package/dist/types/splat/bone-binding.d.ts +38 -0
- package/dist/types/splat/depth-sort.d.ts +58 -0
- package/dist/types/splat/packed-splats.d.ts +36 -0
- package/dist/types/splat/splat-mesh.d.ts +71 -0
- package/dist/types/splat/splat.d.ts +186 -0
- package/dist/types/splat/transform-palette.d.ts +42 -0
- package/dist/types/splat/webgl/backend.d.ts +17 -0
- package/dist/types/splat/webgl/shaders.d.ts +2 -0
- package/dist/types/splat/webgl/splat-mesh.d.ts +52 -0
- package/dist/types/splat/webgpu/backend.d.ts +25 -0
- package/dist/types/splat/webgpu/lighting.d.ts +34 -0
- package/dist/types/splat/webgpu/radix-sort.d.ts +46 -0
- package/dist/types/splat/webgpu/splat-mesh.d.ts +71 -0
- package/dist/types/three/loaders.d.ts +77 -0
- package/dist/types/three/splat-character.d.ts +192 -0
- package/dist/types/types.d.ts +30 -0
- package/dist/types/webgpu.d.ts +5 -0
- package/dist/webgpu.js +813 -0
- package/dist/webgpu.js.map +7 -0
- 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,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
|
+
}
|