@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,166 @@
|
|
|
1
|
+
import { DataTexture, Frustum, Group, Vector3 } from 'three';
|
|
2
|
+
import type { Camera, IUniform, Object3D } from 'three';
|
|
3
|
+
import type { Animation } from './animation';
|
|
4
|
+
import type { AnimLodPreset } from './armature';
|
|
5
|
+
import type { PointLight } from './lighting/point-light';
|
|
6
|
+
import type { SkyLight } from './lighting/sky-light';
|
|
7
|
+
import type { SunLight } from './lighting/sun-light';
|
|
8
|
+
import type { SkeletonLibrary } from './loader/skeleton';
|
|
9
|
+
import type { SplatBackend } from './splat/backend';
|
|
10
|
+
/**
|
|
11
|
+
* Lights that splats respond to. Splats carry baked colour, so this is a
|
|
12
|
+
* multiplier on that colour rather than a shading model: ambient plus sun plus
|
|
13
|
+
* distance-attenuated point lights, evaluated at each splat's centre.
|
|
14
|
+
*
|
|
15
|
+
* With no sun and no sky the factor is exactly 1 and splats render as authored.
|
|
16
|
+
*
|
|
17
|
+
* @group Lighting
|
|
18
|
+
*/
|
|
19
|
+
export interface SceneLighting {
|
|
20
|
+
sun: SunLight | null;
|
|
21
|
+
sky: SkyLight | null;
|
|
22
|
+
points: PointLight[];
|
|
23
|
+
}
|
|
24
|
+
/** @group Engine */
|
|
25
|
+
export interface SplatEngineOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Parent for everything the engine creates. When given, the engine's
|
|
28
|
+
* {@link SplatEngine.contentRoot} is added to it, so loaded characters and
|
|
29
|
+
* environments appear without a further `scene.add`.
|
|
30
|
+
*/
|
|
31
|
+
scene?: Object3D;
|
|
32
|
+
/** Camera used for visibility gating and distance LOD. Can also be passed to {@link SplatEngine.update}. */
|
|
33
|
+
camera?: Camera;
|
|
34
|
+
/**
|
|
35
|
+
* Force a particular splat backend. Leave unset: each splat then picks the
|
|
36
|
+
* backend matching the renderer that first draws it.
|
|
37
|
+
*/
|
|
38
|
+
backend?: SplatBackend;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Shared state for a set of splat characters and environments: the skeleton
|
|
42
|
+
* library, the registry of named animation clips, the lights, and the
|
|
43
|
+
* per-frame update that advances every character.
|
|
44
|
+
*
|
|
45
|
+
* This is the three.js counterpart of the PlayCanvas build's `Scene`, minus
|
|
46
|
+
* everything three.js already owns. It creates no renderer, canvas or camera:
|
|
47
|
+
* you keep your own `WebGLRenderer`, `Scene` and render loop, and call
|
|
48
|
+
* {@link SplatEngine.update} once per frame before rendering.
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* const engine = new SplatEngine({ scene, camera });
|
|
52
|
+
* const character = await Character.load(engine, '/assets/hero.vsplat');
|
|
53
|
+
* await Animation.loadGlb(engine, '/assets/idle.glb', 'idle');
|
|
54
|
+
* character.playAnimation('idle', { loop: true });
|
|
55
|
+
*
|
|
56
|
+
* renderer.setAnimationLoop(() => {
|
|
57
|
+
* engine.update(clock.getDelta());
|
|
58
|
+
* renderer.render(scene, camera);
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @group Engine
|
|
63
|
+
*/
|
|
64
|
+
export declare class SplatEngine {
|
|
65
|
+
/** Parent of every character and environment loaded through this engine. */
|
|
66
|
+
readonly contentRoot: Group;
|
|
67
|
+
/** A forced splat backend, or `null` to pick per renderer (the default). */
|
|
68
|
+
readonly backend: SplatBackend | null;
|
|
69
|
+
readonly skeletonLibrary: SkeletonLibrary;
|
|
70
|
+
/** Clips by name. Populated by {@link Animation.register} and the `Animation.load*` helpers. */
|
|
71
|
+
readonly skeletalAnimationLibrary: Map<string, Animation>;
|
|
72
|
+
readonly lighting: SceneLighting;
|
|
73
|
+
/** Camera used for visibility gating and distance LOD. `null` disables both. */
|
|
74
|
+
camera: Camera | null;
|
|
75
|
+
/** Default animation-LOD bands for `Armature.setAnimationLod()` called with no arguments. */
|
|
76
|
+
animLodPreset?: AnimLodPreset;
|
|
77
|
+
/** Number of {@link SplatEngine.update} calls so far. */
|
|
78
|
+
frame: number;
|
|
79
|
+
/** {@link SplatEngine.frame} of the most recent character pose evaluation. */
|
|
80
|
+
characterPoseUpdatedFrame: number;
|
|
81
|
+
/**
|
|
82
|
+
* Lighting uniforms shared by every splat material this engine creates.
|
|
83
|
+
* Refreshed by {@link SplatEngine.syncLighting}.
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
readonly lightingUniforms: {
|
|
87
|
+
uSunColor: IUniform<Vector3>;
|
|
88
|
+
uSunIntensity: IUniform<number>;
|
|
89
|
+
uAmbientColor: IUniform<Vector3>;
|
|
90
|
+
uPointLightCount: IUniform<number>;
|
|
91
|
+
uPointLightTex: IUniform<DataTexture>;
|
|
92
|
+
};
|
|
93
|
+
private readonly _updaters;
|
|
94
|
+
private readonly _frustum;
|
|
95
|
+
private readonly _projScreen;
|
|
96
|
+
private readonly _cameraPosition;
|
|
97
|
+
private _frustumValid;
|
|
98
|
+
private _pointLightCapacity;
|
|
99
|
+
private _destroyed;
|
|
100
|
+
constructor(opts?: SplatEngineOptions);
|
|
101
|
+
get destroyed(): boolean;
|
|
102
|
+
private static _shared;
|
|
103
|
+
/**
|
|
104
|
+
* The engine the loaders use when none is given: one shared skeleton
|
|
105
|
+
* library, animation registry and set of lights for the whole page. Created
|
|
106
|
+
* on first use, attached to no scene.
|
|
107
|
+
*/
|
|
108
|
+
static get shared(): SplatEngine;
|
|
109
|
+
/**
|
|
110
|
+
* Advance every auto-updating character by `dt` seconds. Call once per
|
|
111
|
+
* frame, before rendering.
|
|
112
|
+
*
|
|
113
|
+
* @param camera - Optional; replaces {@link SplatEngine.camera} when given.
|
|
114
|
+
*/
|
|
115
|
+
update(dt: number, camera?: Camera): void;
|
|
116
|
+
/**
|
|
117
|
+
* Register a per-frame callback run by {@link SplatEngine.update}.
|
|
118
|
+
* @returns A function that removes it.
|
|
119
|
+
* @internal
|
|
120
|
+
*/
|
|
121
|
+
addUpdater(fn: (dt: number) => void): () => void;
|
|
122
|
+
/** @internal */
|
|
123
|
+
removeUpdater(fn: (dt: number) => void): void;
|
|
124
|
+
/**
|
|
125
|
+
* The camera frustum as of the last {@link SplatEngine.update}, or
|
|
126
|
+
* `undefined` when no camera is set - callers treat that as "visible".
|
|
127
|
+
* @internal
|
|
128
|
+
*/
|
|
129
|
+
get cameraFrustum(): Frustum | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* World-space camera position as of the last {@link SplatEngine.update}, or
|
|
132
|
+
* `null` when no camera is set.
|
|
133
|
+
* @internal
|
|
134
|
+
*/
|
|
135
|
+
get cameraWorldPosition(): Vector3 | null;
|
|
136
|
+
/**
|
|
137
|
+
* Incremented whenever {@link SplatEngine.syncLighting} finds the lights
|
|
138
|
+
* changed. Lets a backend skip work that depends on lighting.
|
|
139
|
+
* @internal
|
|
140
|
+
*/
|
|
141
|
+
lightingVersion: number;
|
|
142
|
+
/**
|
|
143
|
+
* Copy {@link SplatEngine.lighting} into the shared uniforms. Called per
|
|
144
|
+
* draw, so it only compares numbers and writes on change.
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
syncLighting(): void;
|
|
148
|
+
/**
|
|
149
|
+
* Detach the engine's content from the scene and stop updating. Characters
|
|
150
|
+
* and environments are not destroyed for you - destroy them first if you
|
|
151
|
+
* want their GPU resources released.
|
|
152
|
+
*/
|
|
153
|
+
dispose(): void;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Thrown when an asset finishes loading onto an engine that has already been
|
|
157
|
+
* disposed.
|
|
158
|
+
*
|
|
159
|
+
* This is a CANCELLATION, not a failure: the bytes were fine and the engine is
|
|
160
|
+
* simply gone. It exists as its own type so a caller can tell the two apart.
|
|
161
|
+
*
|
|
162
|
+
* @group Engine
|
|
163
|
+
*/
|
|
164
|
+
export declare class EngineDisposedError extends Error {
|
|
165
|
+
constructor(what: string);
|
|
166
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Object3D } from 'three';
|
|
2
|
+
import type { SplatEngine } from './engine';
|
|
3
|
+
import { Splat } from './splat/splat';
|
|
4
|
+
/** @group Environment */
|
|
5
|
+
export interface LoadEnvironmentOptions {
|
|
6
|
+
/** Object name. Default: basename of the URL. */
|
|
7
|
+
name?: string;
|
|
8
|
+
/** World position. Default: origin. */
|
|
9
|
+
position?: {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
z: number;
|
|
13
|
+
};
|
|
14
|
+
/** Euler angles in degrees. Default: no rotation. */
|
|
15
|
+
rotation?: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
z: number;
|
|
19
|
+
};
|
|
20
|
+
/** Uniform scale. Default: 1. */
|
|
21
|
+
scale?: number;
|
|
22
|
+
/** Explicit filename for extension detection on URLs without one. */
|
|
23
|
+
filename?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Gaussian-splat environment loaded into an engine.
|
|
27
|
+
*
|
|
28
|
+
* A static, unskinned splat that responds to the engine's lighting registry
|
|
29
|
+
* (sun + sky + point lights). Its depth sort runs in a worker and only when
|
|
30
|
+
* the view direction changes, so a large scene costs nothing while the camera
|
|
31
|
+
* is still.
|
|
32
|
+
*
|
|
33
|
+
* Construct via {@link Environment.load} - the constructor is internal.
|
|
34
|
+
*
|
|
35
|
+
* @group Environment
|
|
36
|
+
*/
|
|
37
|
+
export declare class Environment {
|
|
38
|
+
/** The splat that draws the environment. */
|
|
39
|
+
readonly splat: Splat;
|
|
40
|
+
private constructor();
|
|
41
|
+
/** The three.js object for the environment. Transform it like any other. */
|
|
42
|
+
get object3D(): Object3D;
|
|
43
|
+
/**
|
|
44
|
+
* Fetch, decode and add an environment splat.
|
|
45
|
+
*
|
|
46
|
+
* Reads uncompressed 3D Gaussian Splatting `.ply` files.
|
|
47
|
+
*
|
|
48
|
+
* @param url - Location of the splat file.
|
|
49
|
+
*/
|
|
50
|
+
static load(engine: SplatEngine, url: string, opts?: LoadEnvironmentOptions): Promise<Environment>;
|
|
51
|
+
/** Remove the environment and release its GPU resources. */
|
|
52
|
+
destroy(): void;
|
|
53
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export { SplatCharacter } from './three/splat-character';
|
|
2
|
+
export type { SplatCharacterEventMap, SplatCrossFadeOptions, SplatLayerOptions, SplatPlayOptions, SplatRootMotion, } from './three/splat-character';
|
|
3
|
+
export { SplatClipLoader, SplatPLYLoader, VSplatLoader } from './three/loaders';
|
|
4
|
+
export { Animation as SplatClip } from './animation';
|
|
5
|
+
export { SplatEngine, EngineDisposedError } from './engine';
|
|
6
|
+
export type { SceneLighting, SplatEngineOptions } from './engine';
|
|
7
|
+
export { Character } from './character';
|
|
8
|
+
export type { LoadCharacterOptions } from './character';
|
|
9
|
+
export { Animation } from './animation';
|
|
10
|
+
export type { AnimationEvent } from './animation';
|
|
11
|
+
export { Armature } from './armature';
|
|
12
|
+
export type { AnimLodPreset, PoseProvider, RootMotionMode } from './armature';
|
|
13
|
+
export { Environment } from './environment';
|
|
14
|
+
export type { LoadEnvironmentOptions } from './environment';
|
|
15
|
+
export { Splat } from './splat/splat';
|
|
16
|
+
export { SplatMesh } from './splat/splat-mesh';
|
|
17
|
+
export { WebGLSplatBackend } from './splat/webgl/backend';
|
|
18
|
+
export type { SplatBackend } from './splat/backend';
|
|
19
|
+
export type { SplatOptions, SplatSource } from './splat/splat';
|
|
20
|
+
export { packSplats } from './splat/packed-splats';
|
|
21
|
+
export type { PackedSplats } from './splat/packed-splats';
|
|
22
|
+
export { SunLight } from './lighting/sun-light';
|
|
23
|
+
export type { CreateSunLightOptions } from './lighting/sun-light';
|
|
24
|
+
export { SkyLight } from './lighting/sky-light';
|
|
25
|
+
export type { CreateSkyLightOptions } from './lighting/sky-light';
|
|
26
|
+
export { PointLight } from './lighting/point-light';
|
|
27
|
+
export type { CreatePointLightOptions } from './lighting/point-light';
|
|
28
|
+
export { makeRegionMask } from './skeletal/bone-mask';
|
|
29
|
+
export type { BodyRegion, BoneMask } from './skeletal/bone-mask';
|
|
30
|
+
export type { AnimEventCallback, CrossfadeOptions } from './skeletal/animation-player';
|
|
31
|
+
export type { BoneScaleInput } from './skeletal/bone-stretch';
|
|
32
|
+
export { decodeCharacterBundle, loadCharacterBundle, loadCharacterBundleFromBytes } from './loader/character/load';
|
|
33
|
+
export type { CharacterBundle } from './loader/character/load';
|
|
34
|
+
export { CharacterBundleError } from './loader/character/types';
|
|
35
|
+
export { parseGaussianPly } from './loader/environment/ply';
|
|
36
|
+
export type { SkeletonLibrary } from './loader/skeleton';
|
|
37
|
+
export type { ArmatureData, SplatWeights } from './types';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Color } from 'three';
|
|
2
|
+
/** Parse `#rrggbb` into raw 0..1 components. */
|
|
3
|
+
export declare function parseHex(css: string): [number, number, number];
|
|
4
|
+
/**
|
|
5
|
+
* Write a CSS hex colour into `color` as raw component values.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately not `Color.set(hex)`: light colours here multiply baked,
|
|
8
|
+
* display-referred splat colour, so they are used as written. three.js colour
|
|
9
|
+
* management would convert the hex to linear and darken every light.
|
|
10
|
+
*/
|
|
11
|
+
export declare function applyHexTo(color: Color, css: string): void;
|
|
12
|
+
/** A `Color` holding exactly the given component values, with no colour-space conversion. */
|
|
13
|
+
export declare function rawColor(color: Color, r: number, g: number, b: number): Color;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Color, Vector3 } from 'three';
|
|
2
|
+
import type { SplatEngine } from '../engine';
|
|
3
|
+
/** @group Lighting */
|
|
4
|
+
export interface CreatePointLightOptions {
|
|
5
|
+
position?: {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
z: number;
|
|
9
|
+
};
|
|
10
|
+
/** Light colour as CSS hex string. */
|
|
11
|
+
color?: string;
|
|
12
|
+
intensity?: number;
|
|
13
|
+
/** Hard cutoff distance in world units. Contribution is 0 at and beyond this. */
|
|
14
|
+
range?: number;
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** @group Lighting */
|
|
18
|
+
export declare class PointLight {
|
|
19
|
+
/** World-space position. */
|
|
20
|
+
position: Vector3;
|
|
21
|
+
/** Raw component values; not colour-managed. */
|
|
22
|
+
color: Color;
|
|
23
|
+
intensity: number;
|
|
24
|
+
range: number;
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
private _engine;
|
|
27
|
+
/** @internal */
|
|
28
|
+
_attach(engine: SplatEngine): void;
|
|
29
|
+
/** Remove this light from its engine. After calling, the light stops contributing. */
|
|
30
|
+
destroy(): void;
|
|
31
|
+
/** Create a point light and register it on the engine's lighting registry. */
|
|
32
|
+
static create(engine: SplatEngine, opts?: CreatePointLightOptions): PointLight;
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Color } from 'three';
|
|
2
|
+
import type { SplatEngine } from '../engine';
|
|
3
|
+
/** @group Lighting */
|
|
4
|
+
export interface CreateSkyLightOptions {
|
|
5
|
+
/** Sky colour as CSS hex string. */
|
|
6
|
+
color?: string;
|
|
7
|
+
intensity?: number;
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Ambient light for splats: a constant `color * intensity` added to every splat.
|
|
12
|
+
*
|
|
13
|
+
* @group Lighting
|
|
14
|
+
*/
|
|
15
|
+
export declare class SkyLight {
|
|
16
|
+
/** Raw component values; not colour-managed. */
|
|
17
|
+
color: Color;
|
|
18
|
+
intensity: number;
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
/** Create a sky light and make it the engine's sky. */
|
|
21
|
+
static create(engine: SplatEngine, opts?: CreateSkyLightOptions): SkyLight;
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Color, Vector3 } from 'three';
|
|
2
|
+
import type { SplatEngine } from '../engine';
|
|
3
|
+
/** @group Lighting */
|
|
4
|
+
export interface CreateSunLightOptions {
|
|
5
|
+
/** Azimuth in degrees. 0 = +Z, 90 = +X. */
|
|
6
|
+
azimuth?: number;
|
|
7
|
+
/** Elevation in degrees. 0 = horizon, 90 = directly overhead. */
|
|
8
|
+
elevation?: number;
|
|
9
|
+
/** Sun colour as CSS hex string, e.g. '#fff4e0'. */
|
|
10
|
+
color?: string;
|
|
11
|
+
/** Brightness multiplier applied to `color` in the shader. */
|
|
12
|
+
intensity?: number;
|
|
13
|
+
/** Start disabled. Lights default to enabled. */
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Directional light for splats. Splats have no normals, so the sun is a flat
|
|
18
|
+
* brightness term: every splat receives `color * intensity`, regardless of
|
|
19
|
+
* which way it faces.
|
|
20
|
+
*
|
|
21
|
+
* @group Lighting
|
|
22
|
+
*/
|
|
23
|
+
export declare class SunLight {
|
|
24
|
+
azimuth: number;
|
|
25
|
+
elevation: number;
|
|
26
|
+
/** Raw component values; not colour-managed. */
|
|
27
|
+
color: Color;
|
|
28
|
+
intensity: number;
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
/** Unit vector pointing from the scene toward the sun. */
|
|
31
|
+
lightDir(out?: Vector3): Vector3;
|
|
32
|
+
/** Create a sun and make it the engine's sun. */
|
|
33
|
+
static create(engine: SplatEngine, opts?: CreateSunLightOptions): SunLight;
|
|
34
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decoded contents of an *armature GLB* - a glTF 2.0 binary carrying a bone
|
|
3
|
+
* hierarchy, a skin, and one animation clip. Produced by the offline MetaHuman
|
|
4
|
+
* GLB exporter, and readable back after a Blender round-trip.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export interface ArmatureGlbAnimation {
|
|
9
|
+
/** Bone matrices in {@link Animation.data} layout: `numFrames * numBones * 16`. */
|
|
10
|
+
data: Float32Array;
|
|
11
|
+
numFrames: number;
|
|
12
|
+
numBones: number;
|
|
13
|
+
/** Bone names in bone-index order (canonical rig order when recognised). */
|
|
14
|
+
boneNames: string[];
|
|
15
|
+
/**
|
|
16
|
+
* Per bone: `1` if the clip drives that bone's translation, `0` if the bone
|
|
17
|
+
* must inherit the armature's rest translation. `undefined` when every bone
|
|
18
|
+
* is driven - which is what a `.anim` file means, since that format cannot
|
|
19
|
+
* say anything else.
|
|
20
|
+
*
|
|
21
|
+
* Derived from the DATA, not from which channels exist: a translation
|
|
22
|
+
* channel whose keys are all equal declares a rest pose, and a channel that
|
|
23
|
+
* varies is motion. See {@link parseArmatureGlbAnimation}.
|
|
24
|
+
*/
|
|
25
|
+
translationMask?: Uint8Array;
|
|
26
|
+
/** Source frame rate derived from the main timestamp accessor. */
|
|
27
|
+
frameRate: number;
|
|
28
|
+
/** Clip name as stored in the GLB. */
|
|
29
|
+
animationName: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* How far a translation channel may wander and still count as a declaration of
|
|
33
|
+
* rest rather than motion: 10 µm.
|
|
34
|
+
*
|
|
35
|
+
* The distinction cannot be drawn from channel *presence*, because a
|
|
36
|
+
* conformant writer emits a channel for every bone and Blender re-exports
|
|
37
|
+
* dense channels for everything it imports. It is drawn from the values, which
|
|
38
|
+
* survive that round trip: a constant track stays constant however densely it
|
|
39
|
+
* is written.
|
|
40
|
+
*
|
|
41
|
+
* The bound is empirical and has room on both sides. Below it: a Blender
|
|
42
|
+
* 4.2.18 import→export of our own files re-derives every bone through its own
|
|
43
|
+
* matrix pipeline and perturbs a constant translation by at most 7.96e-7 m,
|
|
44
|
+
* 12x under this. Above it: the smallest genuinely moving channel in the same
|
|
45
|
+
* files drifts 3.06e-4 m, 30x over. The writer's own collapse threshold
|
|
46
|
+
* (the exporter's own `TRANSLATION_EPS`, 1e-6 m) sits inside
|
|
47
|
+
* that gap too, so everything it calls constant still reads as constant here,
|
|
48
|
+
* before and after a round trip.
|
|
49
|
+
*
|
|
50
|
+
* Shared with the `.anim` reader ({@link Animation.parse}), which asks the same
|
|
51
|
+
* question of the same quantity, so one number keeps the two formats from ever
|
|
52
|
+
* disagreeing about what "constant" means. It has even more room there: a v3
|
|
53
|
+
* `.anim` stores float16, whose decode is deterministic, so a constant track
|
|
54
|
+
* comes back bit-identical (deviation exactly 0) while the smallest moving bone
|
|
55
|
+
* across `test_full` / `test_stripped` / `test_blend_*` drifts 2.69e-4 m. Motion
|
|
56
|
+
* finer than this bound is not representable at bone-offset magnitudes in
|
|
57
|
+
* float16 anyway (one ulp at 0.4 m is 2.4e-4 m), so it has already been
|
|
58
|
+
* quantized flat before the test sees it.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
export declare const CONSTANT_TRANSLATION_EPS = 0.00001;
|
|
63
|
+
/**
|
|
64
|
+
* Parse an armature GLB into the flat bone-matrix form that {@link Animation}
|
|
65
|
+
* holds after {@link Animation.parse}.
|
|
66
|
+
*
|
|
67
|
+
* **Bones are resolved by NAME, never by node index.** The joint set comes from
|
|
68
|
+
* `skins[0].joints` when a skin is present (files we write, and anything
|
|
69
|
+
* Blender exports), falling back to "every node is a bone" for older files
|
|
70
|
+
* that carried no skin. If the joint names match one of the engine's known rigs the bones are
|
|
71
|
+
* re-ordered into that rig's canonical order; otherwise the file's joint order
|
|
72
|
+
* stands. Nodes that are not joints - the armature parent, mesh nodes, cameras,
|
|
73
|
+
* anything a DCC tool adds - are ignored, and so are channels targeting them.
|
|
74
|
+
*
|
|
75
|
+
* What it accepts:
|
|
76
|
+
*
|
|
77
|
+
* - `translation`, `rotation` and `scale` channels; `weights` is ignored,
|
|
78
|
+
* there being no morph targets on an armature.
|
|
79
|
+
* - Rotations as FLOAT or `normalized: true` SHORT (dequantized + renormalized).
|
|
80
|
+
* - `LINEAR`, `STEP` and `CUBICSPLINE` samplers. Other modes throw by name.
|
|
81
|
+
* - Per-channel timelines: each sampler rides either the main timeline - the
|
|
82
|
+
* longest, so frame `f` is the same instant for every bone - or a one-key
|
|
83
|
+
* timeline of its own, meaning a constant channel held for the whole clip.
|
|
84
|
+
* - Accessors sharing one bufferView via `byteOffset`, and absent `min`/`max`.
|
|
85
|
+
*
|
|
86
|
+
* What it requires: exactly one rotation channel per bone, timestamps on a
|
|
87
|
+
* uniform grid (the engine indexes animations by frame, so key `i` must be
|
|
88
|
+
* frame `i`), and no duplicate channels.
|
|
89
|
+
*
|
|
90
|
+
* **Constant translation is a rest declaration, not motion.** A translation
|
|
91
|
+
* channel whose keys are all equal (within {@link CONSTANT_TRANSLATION_EPS})
|
|
92
|
+
* states the rig's own bone length; a channel that varies is real motion -
|
|
93
|
+
* root motion at the pelvis, and the face, which is translation-driven. The
|
|
94
|
+
* former is reported through {@link ArmatureGlbAnimation.translationMask} as a
|
|
95
|
+
* `0`, so playback keeps the TARGET character's rest translation and its limbs
|
|
96
|
+
* do not resize mid-clip. On our own clips that lands on 85 of 441 bones
|
|
97
|
+
* without a single bone name being mentioned anywhere.
|
|
98
|
+
*
|
|
99
|
+
* The test reads the data rather than the channel list on purpose. Absence
|
|
100
|
+
* could carry the same message, but only in a file nobody has touched: a
|
|
101
|
+
* conformant reader is entitled to use `nodes[b].translation` when a channel
|
|
102
|
+
* is missing, and Blender re-exports dense channels for every bone, which
|
|
103
|
+
* erases the distinction. Constancy survives both. Files that genuinely omit
|
|
104
|
+
* the channel - everything written before 2026-08 - are still read the old
|
|
105
|
+
* way, as `0`.
|
|
106
|
+
*
|
|
107
|
+
* Scale gets the same treatment, and resolves at parse time: a constant scale
|
|
108
|
+
* is a rest declaration, and every rig this engine builds rests at scale
|
|
109
|
+
* `1,1,1`, so a constant channel simply resolves to identity here and needs no
|
|
110
|
+
* mask of its own. A varying scale is honoured. Our writers emit no scale
|
|
111
|
+
* channels at all, so this only ever fires for a bone somebody scaled by hand
|
|
112
|
+
* in a DCC tool.
|
|
113
|
+
*
|
|
114
|
+
* @throws If the container, the layout, or an accessor is invalid.
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
export declare function parseArmatureGlbAnimation(buffer: ArrayBuffer, animationName?: string): ArmatureGlbAnimation;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character-bundle reader: the dispatch table and the public entry point.
|
|
3
|
+
*
|
|
4
|
+
* ONE top-level branch, and legacy behind one door:
|
|
5
|
+
*
|
|
6
|
+
* magic == VIGGCHAR -> look `version` up in VERSION_HANDLERS
|
|
7
|
+
* otherwise -> legacy/
|
|
8
|
+
*
|
|
9
|
+
* That is the whole structure, and it is the point of it: adding version N+1
|
|
10
|
+
* costs one module under `vN/` and one line in the table below, and removing a
|
|
11
|
+
* version costs deleting exactly those two things. If a change to support a new
|
|
12
|
+
* version has to touch an existing handler, the decomposition is not real.
|
|
13
|
+
* (Character-bundle format spec, "Reader architecture", R1-R5.)
|
|
14
|
+
*
|
|
15
|
+
* The only bytes read before dispatch are the 16 container bytes. Nothing here
|
|
16
|
+
* sniffs the payload, peeks at the header, tries a parse, or consults the
|
|
17
|
+
* filename, the extension, the URL or the MIME type - the `.vsplat` extension
|
|
18
|
+
* is advisory and says nothing this reader may act on.
|
|
19
|
+
*/
|
|
20
|
+
import { CharacterBundleError } from './types';
|
|
21
|
+
import type { DecodeResult } from './types';
|
|
22
|
+
/**
|
|
23
|
+
* Read a character bundle of any container this reader implements.
|
|
24
|
+
*
|
|
25
|
+
* @param file - the whole file as a read-only byte buffer.
|
|
26
|
+
* @returns the decoded bundle in the version-independent output type, plus the
|
|
27
|
+
* buffers a worker may transfer rather than copy. A caller cannot tell from the
|
|
28
|
+
* result which handler ran.
|
|
29
|
+
* @throws {CharacterBundleError} carrying the spec's error category.
|
|
30
|
+
*/
|
|
31
|
+
declare function parseCharacterBundle(file: ArrayBuffer): Promise<DecodeResult>;
|
|
32
|
+
export { parseCharacterBundle, CharacterBundleError };
|
|
33
|
+
export type { DecodeResult };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The legacy door.
|
|
3
|
+
*
|
|
4
|
+
* Everything whose first 8 bytes are not `VIGGCHAR` arrives here, and nothing
|
|
5
|
+
* else does. Behind it are the two container generations that shipped before
|
|
6
|
+
* the format was respecified:
|
|
7
|
+
*
|
|
8
|
+
* `VFCHARB1` -> interleaved per-splat records (v1 fp32, v2 fp16)
|
|
9
|
+
* `VFCHARB2` -> planar byte planes (v3; fp16 or q23 precision)
|
|
10
|
+
* `VSPLAT01` -> the pre-release prototype of the planar layout
|
|
11
|
+
*
|
|
12
|
+
* These readers are PERMANENT, not migration scaffolding. Files already in
|
|
13
|
+
* storage keep their old magic forever - there is no backfill - so this module
|
|
14
|
+
* is kept indefinitely and its decoders are moved, never improved. What it is
|
|
15
|
+
* NOT is entangled: it is one branch of one top-level `if`, it imports nothing
|
|
16
|
+
* from `../v1/`, and `../v1/` imports nothing from it.
|
|
17
|
+
*
|
|
18
|
+
* These containers spell `version` as one global lineage across two magics,
|
|
19
|
+
* which is precisely the accretion `VIGGCHAR` exists to end. Nothing about that
|
|
20
|
+
* lineage leaks past this file: the dispatcher upstream never reads it, and the
|
|
21
|
+
* bundle handed back is the same version-independent shape `v1/` returns.
|
|
22
|
+
*/
|
|
23
|
+
import type { DecodeResult } from '../types';
|
|
24
|
+
/**
|
|
25
|
+
* Decode a bundle in one of the pre-`VIGGCHAR` containers.
|
|
26
|
+
*
|
|
27
|
+
* The container is identified by SNIFFING THE 8-BYTE MAGIC and never by the
|
|
28
|
+
* filename or a header key - both generations arrive through the same URLs,
|
|
29
|
+
* with no out-of-band type information, so the bytes are the only honest
|
|
30
|
+
* answer. Anything this door does not recognise throws with the magic it
|
|
31
|
+
* actually found.
|
|
32
|
+
*/
|
|
33
|
+
declare function decodeLegacyBundle(file: ArrayBuffer): Promise<DecodeResult>;
|
|
34
|
+
export { decodeLegacyBundle };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interleaved character-splat decoder - the payload half of `VFCHARB1`.
|
|
3
|
+
*
|
|
4
|
+
* MOVED, NOT REWRITTEN. This is `binary-gsplat-worker.ts`'s `parseBinaryGsplat`
|
|
5
|
+
* verbatim, lifted out of the worker glue so the legacy readers sit behind one
|
|
6
|
+
* door (`../index.ts`). It decodes files that are already in storage and will
|
|
7
|
+
* stay there - there is no backfill - so any behaviour change here is a
|
|
8
|
+
* regression, and it keeps its own vocabulary: this container talks about
|
|
9
|
+
* CHUNKS, where the planar one talks about PLANES and `VIGGCHAR` talks about
|
|
10
|
+
* BLOCKS.
|
|
11
|
+
*
|
|
12
|
+
* `version` here numbers one global bundle lineage across both legacy magics
|
|
13
|
+
* (1 = fp32 interleaved, 2 = fp16 interleaved, 3 = planar), which is exactly the
|
|
14
|
+
* accretion `VIGGCHAR` was shaped to end.
|
|
15
|
+
*/
|
|
16
|
+
/** The interleaved container: per-splat records, v1 (fp32) and v2 (fp16). */
|
|
17
|
+
declare const CHARACTER_BUNDLE_MAGIC = "VFCHARB1";
|
|
18
|
+
interface BundleChunk {
|
|
19
|
+
offset: number;
|
|
20
|
+
length: number;
|
|
21
|
+
stride?: number;
|
|
22
|
+
}
|
|
23
|
+
interface AssetHeader {
|
|
24
|
+
format: string;
|
|
25
|
+
version: number;
|
|
26
|
+
numSplats: number;
|
|
27
|
+
numBones: number;
|
|
28
|
+
bounds: {
|
|
29
|
+
min: number[];
|
|
30
|
+
max: number[];
|
|
31
|
+
};
|
|
32
|
+
encoding?: {
|
|
33
|
+
/**
|
|
34
|
+
* Bundle-wide field precision. `'fp16'` means pos/scale/rot/opacity and
|
|
35
|
+
* the weights chunk's per-splat weights are stored as IEEE 754 half-floats
|
|
36
|
+
* (read 2 bytes / value, expanded to Float32 on parse). `'fp32'` or
|
|
37
|
+
* missing = legacy raw float32 (the v1 layout). RGB stays uint8 and bone
|
|
38
|
+
* indices stay uint16 either way. Bumps the file size by ~42%; expanding
|
|
39
|
+
* to Float32 here means GPU memory is unchanged.
|
|
40
|
+
*/
|
|
41
|
+
precision?: 'fp16' | 'fp32';
|
|
42
|
+
weights?: {
|
|
43
|
+
topK: number;
|
|
44
|
+
indexType: string;
|
|
45
|
+
weightType: string;
|
|
46
|
+
stride: number;
|
|
47
|
+
};
|
|
48
|
+
color?: {
|
|
49
|
+
format: string;
|
|
50
|
+
levels: number;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
chunks: {
|
|
54
|
+
splats: BundleChunk;
|
|
55
|
+
weights?: BundleChunk;
|
|
56
|
+
joints?: BundleChunk;
|
|
57
|
+
animation?: BundleChunk;
|
|
58
|
+
};
|
|
59
|
+
animation?: {
|
|
60
|
+
format: string;
|
|
61
|
+
numFrames: number;
|
|
62
|
+
numBones: number;
|
|
63
|
+
shape: number[];
|
|
64
|
+
stride: number;
|
|
65
|
+
};
|
|
66
|
+
joints?: {
|
|
67
|
+
count: number;
|
|
68
|
+
format: string;
|
|
69
|
+
stride: number;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
declare function parseBinaryGsplat(bundleBuffer: ArrayBuffer): {
|
|
73
|
+
result: {
|
|
74
|
+
header: AssetHeader;
|
|
75
|
+
numSplats: number;
|
|
76
|
+
splats: {
|
|
77
|
+
x: Float32Array<ArrayBuffer>;
|
|
78
|
+
y: Float32Array<ArrayBuffer>;
|
|
79
|
+
z: Float32Array<ArrayBuffer>;
|
|
80
|
+
scale_0: Float32Array<ArrayBuffer>;
|
|
81
|
+
scale_1: Float32Array<ArrayBuffer>;
|
|
82
|
+
scale_2: Float32Array<ArrayBuffer>;
|
|
83
|
+
rot_0: Float32Array<ArrayBuffer>;
|
|
84
|
+
rot_1: Float32Array<ArrayBuffer>;
|
|
85
|
+
rot_2: Float32Array<ArrayBuffer>;
|
|
86
|
+
rot_3: Float32Array<ArrayBuffer>;
|
|
87
|
+
f_dc_0: Float32Array<ArrayBuffer>;
|
|
88
|
+
f_dc_1: Float32Array<ArrayBuffer>;
|
|
89
|
+
f_dc_2: Float32Array<ArrayBuffer>;
|
|
90
|
+
opacity: Float32Array<ArrayBuffer>;
|
|
91
|
+
};
|
|
92
|
+
boneIndices: Uint16Array<ArrayBufferLike> | null;
|
|
93
|
+
boneWeights: Float32Array<ArrayBufferLike> | null;
|
|
94
|
+
animationArray: Float32Array<ArrayBufferLike> | null;
|
|
95
|
+
animNumFrames: number;
|
|
96
|
+
animNumBones: number;
|
|
97
|
+
jointsArray: Float32Array<ArrayBufferLike> | null;
|
|
98
|
+
};
|
|
99
|
+
transferables: ArrayBuffer[];
|
|
100
|
+
};
|
|
101
|
+
export { parseBinaryGsplat, CHARACTER_BUNDLE_MAGIC };
|