@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,540 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import type { Object3D } from 'three';
|
|
3
|
+
import { Armature } from './armature';
|
|
4
|
+
import type { SplatEngine } from './engine';
|
|
5
|
+
import type { CharacterBundle } from './loader/character/load';
|
|
6
|
+
import type { AnimEventCallback } from './skeletal/animation-player';
|
|
7
|
+
import type { BodyRegion, BoneMask } from './skeletal/bone-mask';
|
|
8
|
+
import { Splat } from './splat/splat';
|
|
9
|
+
/**
|
|
10
|
+
* Options for {@link Character.load}.
|
|
11
|
+
*
|
|
12
|
+
* @group Characters & Animation
|
|
13
|
+
*/
|
|
14
|
+
export interface LoadCharacterOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Name used for the armature and any embedded animation registered on the
|
|
17
|
+
* engine. Defaults to the file basename (stripped of extension).
|
|
18
|
+
*/
|
|
19
|
+
name?: string;
|
|
20
|
+
/** Initial position, `y` at GROUND CONTACT - same convention as
|
|
21
|
+
* {@link Character.setPosition}. Defaults to the origin. */
|
|
22
|
+
position?: {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
z: number;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Which rest translations pose the rig.
|
|
29
|
+
*
|
|
30
|
+
* - `'auto'` (default) - `'bundled'` for a bundle carrying a correction
|
|
31
|
+
* receipt, `'builtin'` for one without. Apps should pass nothing: the
|
|
32
|
+
* discriminator lives in the file, so the decision belongs here rather than
|
|
33
|
+
* in each consuming app, where the copies would otherwise drift apart.
|
|
34
|
+
* - `'builtin'` - the canonical std-male tables from the skeleton library.
|
|
35
|
+
* Every character is posed on one shared skeleton.
|
|
36
|
+
* - `'bundled'` - the per-character `joints` array for BODY bones, canonical
|
|
37
|
+
* for `FACIAL_*` ones and for toes. See `keepsCanonicalRest` for the
|
|
38
|
+
* measurements behind both exclusions.
|
|
39
|
+
*
|
|
40
|
+
* The explicit values exist for the comparison scenes, which show one
|
|
41
|
+
* character both ways on purpose.
|
|
42
|
+
*
|
|
43
|
+
* The body tables disagree, and not subtly: on a sample character 81 of 86
|
|
44
|
+
* body bones differ by more than 1 mm, worst 36 mm at `foot_r`, with the
|
|
45
|
+
* canonical limbs uniformly SHORTER (thigh -14.7 mm, calf -36.4 mm). That
|
|
46
|
+
* leaves the canonical knee ~36 mm high and the canonical foot floating
|
|
47
|
+
* ~72 mm off the ground on a character whose own skeleton reaches it.
|
|
48
|
+
*
|
|
49
|
+
* At rest the choice is invisible - skinning cancels `worldPose · inverseBind`
|
|
50
|
+
* to identity either way. It only shows once a joint ROTATES, because the
|
|
51
|
+
* rotation happens about the chosen pivot: a splat at the true knee swung
|
|
52
|
+
* about a centre 36 mm away lands ~51 mm off through a 90° bend.
|
|
53
|
+
*
|
|
54
|
+
* Clips are authored on the canonical rig - their body translations match
|
|
55
|
+
* `REST_TRANSLATIONS_441` to 0.0007 mm - which USED to make a canonical bind
|
|
56
|
+
* the only one that agreed with the pose, and `'bundled'` a diagnostic.
|
|
57
|
+
* That is no longer so: a bone whose translation never changes across a clip
|
|
58
|
+
* is now read as a rest declaration and left to the character
|
|
59
|
+
* (`Animation`'s `translationMask`, derived for `.anim`, GLB and the
|
|
60
|
+
* `.bin`-embedded clip alike). Playback is effectively rotation-only for
|
|
61
|
+
* every bone the clip does not actually translate - 341 of 342 on the body
|
|
62
|
+
* rig, 829 of 1185 on the full one - so `'bundled'` now inherits the
|
|
63
|
+
* character's own lengths instead of having them overwritten.
|
|
64
|
+
*
|
|
65
|
+
* Bundles carry translations only, never rotations, so `'bundled'` yields
|
|
66
|
+
* per-character bone LENGTHS with canonical joint ORIENTATIONS - a hybrid.
|
|
67
|
+
* Falls back to `'builtin'` when the bundle has no joints (older files) or
|
|
68
|
+
* the array length disagrees with the bone count.
|
|
69
|
+
*/
|
|
70
|
+
restSource?: 'auto' | 'builtin' | 'bundled';
|
|
71
|
+
}
|
|
72
|
+
export declare class Character {
|
|
73
|
+
/** Render-LOD level pyramid - when more than one entry, only one is
|
|
74
|
+
* enabled at a time (the active level). `splats[0]` is always the
|
|
75
|
+
* highest-fidelity (L0). Single-LOD characters have `splats.length === 1`. */
|
|
76
|
+
splats: Splat[];
|
|
77
|
+
/**
|
|
78
|
+
* The currently-rendered splat. For single-bin characters this is
|
|
79
|
+
* always `splats[0]`; for multi-LOD characters it tracks the active
|
|
80
|
+
* render-LOD level and flips with `setRenderLod` / `setRenderLevel`.
|
|
81
|
+
*
|
|
82
|
+
* Safe for read-only queries (`.numSplats`, `.object3D`, `.isInFrustum`,
|
|
83
|
+
* etc.). For **state mutations** that should persist across LOD swaps
|
|
84
|
+
* (tint, sort, visibility) prefer the Character-level helpers
|
|
85
|
+
* ({@link setTint}, {@link requestSort}) - writing to a single splat
|
|
86
|
+
* here only affects whichever level is active right now and the change
|
|
87
|
+
* vanishes when the active level swaps.
|
|
88
|
+
*/
|
|
89
|
+
get splat(): Splat;
|
|
90
|
+
private _renderLodEnabled;
|
|
91
|
+
private _renderLodNearDist;
|
|
92
|
+
private _renderLodFarDist;
|
|
93
|
+
private _activeLevel;
|
|
94
|
+
private _variantSplats;
|
|
95
|
+
/** Whether this character is live (ticking + rendering). Toggled by
|
|
96
|
+
* {@link setActive} for object pooling - a deactivated character keeps all
|
|
97
|
+
* its GPU resources (splats, textures, compiled shaders) but stops
|
|
98
|
+
* updating and rendering, so it can be reactivated at a new position with
|
|
99
|
+
* none of the ~tens-of-ms `new Splat` setup cost. */
|
|
100
|
+
private _active;
|
|
101
|
+
/** Set by {@link destroy}. Async loaders that resolve afterwards (LOD
|
|
102
|
+
* upgrades, variant splats) check this so they don't re-add splats that
|
|
103
|
+
* nothing owns or ticks anymore - without it, a character destroyed while
|
|
104
|
+
* a finer LOD is still downloading leaks that splat into the scene as a
|
|
105
|
+
* frozen, orphaned figure. */
|
|
106
|
+
private _destroyed;
|
|
107
|
+
/** True once {@link destroy} has run. Progressive-load choreography
|
|
108
|
+
* (coarse-first upgrade loops) should stop streaming when this flips. */
|
|
109
|
+
get destroyed(): boolean;
|
|
110
|
+
private _bakedGroundShiftY;
|
|
111
|
+
/**
|
|
112
|
+
* The ground shift a `VIGGCHAR` v1 bake folded into this character, in
|
|
113
|
+
* metres. `0` for legacy `.bin` bundles and for anything without one.
|
|
114
|
+
*
|
|
115
|
+
* Diagnostics and ground-snap tooling only. {@link setPosition},
|
|
116
|
+
* {@link getPosition} and {@link Character.load}'s `position` option already
|
|
117
|
+
* apply it, so game code should not need to read this - and code that
|
|
118
|
+
* measures ground contact empirically (a {@link getLowestWorldY} probe) must
|
|
119
|
+
* NOT apply it, because such a probe observes the shift's result already and
|
|
120
|
+
* would double-correct.
|
|
121
|
+
*/
|
|
122
|
+
get bakedGroundShiftY(): number;
|
|
123
|
+
/**
|
|
124
|
+
* Convert a ground-contact Y - the convention {@link setPosition} takes -
|
|
125
|
+
* into the raw Y to write on `armature.object3D`.
|
|
126
|
+
*
|
|
127
|
+
* For hot paths that must bypass {@link setPosition} and write the pivot
|
|
128
|
+
* directly (a per-frame sync with a pooled `Vec3`, say). Everything else
|
|
129
|
+
* should use {@link setPosition} and never see this.
|
|
130
|
+
*/
|
|
131
|
+
groundToEntityY(groundY: number): number;
|
|
132
|
+
/**
|
|
133
|
+
* Exact inverse of {@link groundToEntityY}: a raw `armature.object3D` Y back
|
|
134
|
+
* into the ground-contact convention.
|
|
135
|
+
*
|
|
136
|
+
* Read positions back through this (or through {@link getPosition}) so that a
|
|
137
|
+
* read/modify/write round-trip - a motor integrating velocity, a tween
|
|
138
|
+
* sampling its own start value - can never apply the shift twice.
|
|
139
|
+
*/
|
|
140
|
+
entityToGroundY(entityY: number): number;
|
|
141
|
+
private _updateHandler;
|
|
142
|
+
/** Skeleton + animation controller. */
|
|
143
|
+
armature: Armature;
|
|
144
|
+
private _engine;
|
|
145
|
+
/**
|
|
146
|
+
* The three.js object for the whole character - the armature's pivot, with
|
|
147
|
+
* the character's splats parented under it. Already added to the engine's
|
|
148
|
+
* content root; reparent it if you need the character under something else.
|
|
149
|
+
*/
|
|
150
|
+
get object3D(): Object3D;
|
|
151
|
+
constructor(armature: Armature, splats: Splat[], engine: SplatEngine);
|
|
152
|
+
/**
|
|
153
|
+
* Load a character bundle and add it to the engine's content root.
|
|
154
|
+
*
|
|
155
|
+
* The bundle's bone count selects the rig: 441 bones = full body + facial
|
|
156
|
+
* rig, otherwise the 86-bone standard-male body rig. If the bundle contains
|
|
157
|
+
* an embedded animation, it is registered on the engine under
|
|
158
|
+
* {@link LoadCharacterOptions.name} (or the file basename).
|
|
159
|
+
*
|
|
160
|
+
* @param engine - Target engine.
|
|
161
|
+
* @param urlOrUrls - One bundle URL, or an array of LOD levels for the same
|
|
162
|
+
* character, finest first. Every level shares the armature and animation
|
|
163
|
+
* from the FIRST entry; see {@link setRenderLod} for how a level is picked
|
|
164
|
+
* at runtime and {@link addLodLevel} for adding one later.
|
|
165
|
+
* @param opts - See {@link LoadCharacterOptions}.
|
|
166
|
+
* @returns A fully-wired character whose armature drives the splat every frame.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```ts
|
|
170
|
+
* const character = await Character.load(engine, '/assets/hero.vsplat');
|
|
171
|
+
* character.playAnimation('idle', { loop: true });
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
static load(engine: SplatEngine, urlOrUrls: string | string[], opts?: LoadCharacterOptions): Promise<Character>;
|
|
175
|
+
/**
|
|
176
|
+
* Load a `.bin` character bundle from in-memory bytes.
|
|
177
|
+
*
|
|
178
|
+
* Same shape as {@link Character.load} but consumes bytes directly. Use for
|
|
179
|
+
* runtime-generated content (an offline generation pipeline's output, hot
|
|
180
|
+
* swaps, tests) where there is no fetchable URL.
|
|
181
|
+
*
|
|
182
|
+
* @param engine - Target engine.
|
|
183
|
+
* @param bytes - Raw `.bin` bundle bytes.
|
|
184
|
+
* @param opts - See {@link LoadCharacterOptions}. `opts.name` is required for
|
|
185
|
+
* anim registration since there is no URL basename to fall back on.
|
|
186
|
+
* @returns A fully-wired character whose armature drives the splat every frame.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```ts
|
|
190
|
+
* const character = await Character.loadFromBytes(engine, generatedBundle, { name: 'hero' });
|
|
191
|
+
* character.playAnimation('idle', { loop: true });
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
static loadFromBytes(engine: SplatEngine, bytes: Uint8Array, opts?: LoadCharacterOptions): Promise<Character>;
|
|
195
|
+
/**
|
|
196
|
+
* Load a `.vsplat` bundle.
|
|
197
|
+
*
|
|
198
|
+
* The extension is advisory: the bytes go to the same magic-dispatching
|
|
199
|
+
* reader {@link Character.load} uses, so a `.vsplat` may be a `VIGGCHAR`
|
|
200
|
+
* version 1 bundle or one of the legacy planar containers. None of those
|
|
201
|
+
* carries an animation chunk, so the character comes up in its rest pose;
|
|
202
|
+
* register clips separately with {@link Animation.loadGlb} if you need motion.
|
|
203
|
+
*
|
|
204
|
+
* Differs from {@link Character.load} only in skipping the in-memory
|
|
205
|
+
* `ArrayBuffer` cache - see `loader/shared/asset-loader.ts`.
|
|
206
|
+
*
|
|
207
|
+
* @param engine - Target engine.
|
|
208
|
+
* @param source - URL, or raw `.vsplat` bytes.
|
|
209
|
+
* @param opts - See {@link LoadCharacterOptions}.
|
|
210
|
+
*/
|
|
211
|
+
static loadVsplat(engine: SplatEngine, source: string | Uint8Array, opts?: LoadCharacterOptions): Promise<Character>;
|
|
212
|
+
/**
|
|
213
|
+
* Build a character from bundles that are already decoded. The bundles are
|
|
214
|
+
* read, never modified, so the same ones can back any number of characters.
|
|
215
|
+
* @internal
|
|
216
|
+
*/
|
|
217
|
+
static fromBundles(engine: SplatEngine, bundles: CharacterBundle[], defaultName: string, opts?: LoadCharacterOptions): Character;
|
|
218
|
+
private static _buildFromLoadResult;
|
|
219
|
+
/** Build + register the per-frame update handler. Called once at
|
|
220
|
+
* construction; kept separate so the handler can close over `this`. */
|
|
221
|
+
private _wireUpdate;
|
|
222
|
+
/** Pick the render-LOD level for this frame based on camera distance.
|
|
223
|
+
* near band → L0; far band → last; in between → intermediate level. */
|
|
224
|
+
private _pickRenderLevel;
|
|
225
|
+
/**
|
|
226
|
+
* Enable distance-based render LOD. Per frame, the character switches
|
|
227
|
+
* between the loaded LOD splats (`splats[0..N-1]`, from finest to
|
|
228
|
+
* coarsest). Near the camera → L0 (full); beyond `farDist` → last level;
|
|
229
|
+
* in between → intermediate levels split evenly across the band.
|
|
230
|
+
*
|
|
231
|
+
* Composes with {@link Armature.setAnimationLod} / {@link Armature.setSortLod}:
|
|
232
|
+
* render LOD throws away splats at distance, the other two throttle
|
|
233
|
+
* animation / sort work - stack all three for maximum CPU savings.
|
|
234
|
+
*
|
|
235
|
+
* No-op if only one LOD level was loaded.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```ts
|
|
239
|
+
* const c = await Character.load(engine, ['hero.l0.vsplat', 'hero.l1.vsplat', 'hero.l2.vsplat']);
|
|
240
|
+
* c.setRenderLod({ nearDist: 8, farDist: 20 });
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
setRenderLod(opts: {
|
|
244
|
+
nearDist: number;
|
|
245
|
+
farDist: number;
|
|
246
|
+
}): void;
|
|
247
|
+
/** Disable render LOD - pins the character to the highest-fidelity level
|
|
248
|
+
* (L0). Other loaded LOD splats remain in memory but are not rendered. */
|
|
249
|
+
disableRenderLod(): void;
|
|
250
|
+
/** Force a specific render-LOD level, bypassing distance bands. Useful
|
|
251
|
+
* for debugging / A-B comparisons in the profiling scene. */
|
|
252
|
+
setRenderLevel(level: number): void;
|
|
253
|
+
/** Number of LOD levels loaded for this character (>=1). */
|
|
254
|
+
get renderLodLevels(): number;
|
|
255
|
+
/** Active render-LOD level (0 = highest fidelity). */
|
|
256
|
+
get activeRenderLevel(): number;
|
|
257
|
+
/**
|
|
258
|
+
* Tint every LOD splat. Fans out to `splats[*]` so the tint persists
|
|
259
|
+
* across render-LOD swaps - writing to a single splat (e.g. the legacy
|
|
260
|
+
* `c.splat.setTint(...)`) only affects that level, and the tint
|
|
261
|
+
* "vanishes" the next time `setRenderLod` swaps the visible level.
|
|
262
|
+
*
|
|
263
|
+
* Same signature as {@link Splat.setTint}.
|
|
264
|
+
*/
|
|
265
|
+
setTint(intensity: number, color?: {
|
|
266
|
+
r: number;
|
|
267
|
+
g: number;
|
|
268
|
+
b: number;
|
|
269
|
+
}): void;
|
|
270
|
+
/**
|
|
271
|
+
* Re-sort every LOD splat. Cheap when called on inactive splats (no GPU
|
|
272
|
+
* sort runs there), and necessary for the active one to reflect a fresh
|
|
273
|
+
* transform on the next frame. Fans out so a swap to a previously-
|
|
274
|
+
* inactive LOD doesn't render with a stale sort order.
|
|
275
|
+
*/
|
|
276
|
+
requestSort(): void;
|
|
277
|
+
/**
|
|
278
|
+
* World-space Y of the character's lowest point, using
|
|
279
|
+
* {@link Splat.getGroundWorldY}'s noise-robust low-quantile estimate across
|
|
280
|
+
* every enabled splat (the active render-LOD level, plus any variant splats
|
|
281
|
+
* that are currently visible). Returns `null` if no splat is enabled (e.g.
|
|
282
|
+
* mid-load). Ground-snap tooling only - the per-frame render path doesn't
|
|
283
|
+
* use it.
|
|
284
|
+
*
|
|
285
|
+
* This is a MEASUREMENT of where the character actually renders, so it
|
|
286
|
+
* already includes {@link bakedGroundShiftY}. A caller that snaps to ground
|
|
287
|
+
* from this number must not also apply that shift, or it corrects twice.
|
|
288
|
+
*/
|
|
289
|
+
getLowestWorldY(quantile?: number): number | null;
|
|
290
|
+
/**
|
|
291
|
+
* Async primitive: load one more LOD level from `url` and link it to this
|
|
292
|
+
* character's armature, leaving it disabled (not rendered) until the caller
|
|
293
|
+
* explicitly picks it via {@link setRenderLevel}.
|
|
294
|
+
*
|
|
295
|
+
* Lets game code compose its own LOD-loading policy on top of an existing
|
|
296
|
+
* Character without the engine deciding when to swap. Typical pattern is
|
|
297
|
+
* progressive: load the smallest LOD via {@link Character.load} for instant
|
|
298
|
+
* first-frame, then background-load finer levels with `addLodLevel` and
|
|
299
|
+
* `setRenderLevel(splats.length - 1)` each time one arrives.
|
|
300
|
+
*
|
|
301
|
+
* Skeleton + animation are shared with the existing armature - any joints
|
|
302
|
+
* / animation data inside the additional `.bin` is intentionally ignored
|
|
303
|
+
* (it must match the original; only the splat + bone-weights chunks are
|
|
304
|
+
* consumed). The newly loaded `Splat` is pushed onto `this.splats` and
|
|
305
|
+
* starts in the disabled state.
|
|
306
|
+
*
|
|
307
|
+
* @param url - URL of another `.bin` (any LOD level of the same character).
|
|
308
|
+
* @returns Resolves once the splat is fully loaded, linked, and GPU-uploaded.
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* ```ts
|
|
312
|
+
* // Progressive: start at L2 (smallest), upgrade as finer levels arrive.
|
|
313
|
+
* const c = await Character.load(engine, urls[2], opts);
|
|
314
|
+
* (async () => {
|
|
315
|
+
* await c.addLodLevel(urls[1]);
|
|
316
|
+
* c.setRenderLevel(c.splats.length - 1);
|
|
317
|
+
* await c.addLodLevel(urls[0]);
|
|
318
|
+
* c.setRenderLevel(c.splats.length - 1);
|
|
319
|
+
* })();
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
addLodLevel(url: string): Promise<void>;
|
|
323
|
+
/**
|
|
324
|
+
* Load an additional splat cloud, link it to this character's armature,
|
|
325
|
+
* and leave it ENABLED so it renders alongside the LOD pyramid. Returns
|
|
326
|
+
* the newly-created {@link Splat} so higher-level systems can drive it
|
|
327
|
+
* directly (typically via {@link Splat.setSplatGroups} +
|
|
328
|
+
* {@link Splat.setVariantMask}).
|
|
329
|
+
*
|
|
330
|
+
* Use case: damage / outfit / state variants where two skinned clouds
|
|
331
|
+
* share one skeleton and per-region visibility masks decide which
|
|
332
|
+
* gaussians each cloud contributes. See the `DamageVariant` preset for
|
|
333
|
+
* the body-part scheme built on top of this primitive.
|
|
334
|
+
*
|
|
335
|
+
* The variant splat's lifecycle is owned by this Character - it is
|
|
336
|
+
* destroyed in {@link destroy} and enabled/disabled by {@link setActive}.
|
|
337
|
+
* Call {@link removeVariantSplat} to retire one early.
|
|
338
|
+
*
|
|
339
|
+
* @param url - URL of a `.bin` rigged to the same skeleton as this
|
|
340
|
+
* character. The skeleton/animation chunks (if present) are ignored.
|
|
341
|
+
*/
|
|
342
|
+
addVariantSplat(url: string): Promise<Splat>;
|
|
343
|
+
/** Remove and destroy a splat previously added via {@link addVariantSplat}.
|
|
344
|
+
* No-op if `splat` was not from this character. */
|
|
345
|
+
removeVariantSplat(splat: Splat): void;
|
|
346
|
+
/** Read-only view of currently-attached variant splats (in insertion order). */
|
|
347
|
+
get variantSplats(): readonly Splat[];
|
|
348
|
+
/**
|
|
349
|
+
* Set the character's position, with `y` measured at GROUND CONTACT: pass the
|
|
350
|
+
* floor height and the feet land on it, whichever container the character
|
|
351
|
+
* came from. Triggers a splat re-sort.
|
|
352
|
+
*
|
|
353
|
+
* `y` goes through {@link groundToEntityY} on the way to the pivot. That is
|
|
354
|
+
* the identity for every legacy `.bin` character, so this is byte-identical
|
|
355
|
+
* to the historical "y is the model origin" behaviour for everything already
|
|
356
|
+
* shipped; only `VIGGCHAR` v1 bundles move. Read the position back with
|
|
357
|
+
* {@link getPosition}, never with `armature.object3D.position`, or a
|
|
358
|
+
* round-trip will double-apply the shift.
|
|
359
|
+
*/
|
|
360
|
+
setPosition(x: number, y: number, z: number): void;
|
|
361
|
+
/**
|
|
362
|
+
* The character's current position in the armature pivot's parent space, in
|
|
363
|
+
* the same convention {@link setPosition} takes (`y` at ground contact).
|
|
364
|
+
*
|
|
365
|
+
* The exact inverse of {@link setPosition}: reading a position and writing it
|
|
366
|
+
* straight back is a no-op. That is the property to preserve - it is what
|
|
367
|
+
* lets a caller integrate velocity, tween, or clamp against a position it
|
|
368
|
+
* read without the ground shim compounding once per frame.
|
|
369
|
+
*
|
|
370
|
+
* Returns a fresh {@link Vec3}; the pivot's own vector is not exposed.
|
|
371
|
+
*/
|
|
372
|
+
getPosition(out?: Vector3): Vector3;
|
|
373
|
+
/** Set the character's rotation in Euler degrees. Triggers a splat re-sort. */
|
|
374
|
+
setRotation(xDeg: number, yDeg: number, zDeg: number): void;
|
|
375
|
+
/** Set non-uniform scale. Triggers a splat re-sort. */
|
|
376
|
+
setScale(x: number, y: number, z: number): void;
|
|
377
|
+
/**
|
|
378
|
+
* Immediately start a registered animation (no crossfade).
|
|
379
|
+
*
|
|
380
|
+
* The animation must have been registered on the engine via
|
|
381
|
+
* {@link Animation.register} or embedded in the `.bin` bundle.
|
|
382
|
+
*
|
|
383
|
+
* @param animName - Animation name in `engine.skeletalAnimationLibrary`.
|
|
384
|
+
* @param opts.loop - Loop playback. Default `true`.
|
|
385
|
+
* @param opts.rootMotion - Drive the armature position from the pelvis X/Z channel. Default `false`.
|
|
386
|
+
* @returns `true` if the animation was found and started; `false` otherwise.
|
|
387
|
+
*/
|
|
388
|
+
playAnimation(animName: string, opts?: {
|
|
389
|
+
loop?: boolean;
|
|
390
|
+
rootMotion?: boolean;
|
|
391
|
+
}): boolean;
|
|
392
|
+
/**
|
|
393
|
+
* Smoothly blend from the current pose to a new animation.
|
|
394
|
+
*
|
|
395
|
+
* Snapshots the current bone poses and interpolates toward the new
|
|
396
|
+
* animation over `duration` seconds. Safe to call mid-crossfade - always
|
|
397
|
+
* starts from wherever bones actually are.
|
|
398
|
+
*
|
|
399
|
+
* @param animName - Animation name in `engine.skeletalAnimationLibrary`.
|
|
400
|
+
* @param opts.duration - Crossfade length in seconds. Default `0.08`.
|
|
401
|
+
* @param opts.loop - Loop the target animation. Default `true`.
|
|
402
|
+
* @param opts.rootMotion - Drive the armature position from the pelvis X/Z channel. Default `false`.
|
|
403
|
+
* @returns `true` if the animation was found and the blend started; `false` otherwise.
|
|
404
|
+
*/
|
|
405
|
+
crossfadeTo(animName: string, opts?: {
|
|
406
|
+
duration?: number;
|
|
407
|
+
loop?: boolean;
|
|
408
|
+
rootMotion?: boolean;
|
|
409
|
+
preserveTime?: boolean;
|
|
410
|
+
}): boolean;
|
|
411
|
+
/**
|
|
412
|
+
* Play `animName` on the named overlay layer, restricted to a bone mask.
|
|
413
|
+
* Layers compose over the base animation each frame - masked bones blend
|
|
414
|
+
* toward the overlay by `opts.weight` (defaults to `1`).
|
|
415
|
+
*
|
|
416
|
+
* Pass `opts.region: 'upperBody' | 'lowerBody'` to use the built-in masks
|
|
417
|
+
* (canonical 86-bone rig), or `opts.mask` directly to supply your own.
|
|
418
|
+
*
|
|
419
|
+
* Typical use: `character.playAnimation('walk_loop'); character.playLayer('aim', 'aim_rifle', { region: 'upperBody' });`
|
|
420
|
+
*/
|
|
421
|
+
playLayer(layerName: string, animName: string, opts?: {
|
|
422
|
+
region?: BodyRegion;
|
|
423
|
+
mask?: BoneMask;
|
|
424
|
+
loop?: boolean;
|
|
425
|
+
crossfade?: number;
|
|
426
|
+
weight?: number;
|
|
427
|
+
}): boolean;
|
|
428
|
+
/** Set the overlay weight (0..1) on a layer. */
|
|
429
|
+
setLayerWeight(layerName: string, weight: number): void;
|
|
430
|
+
/** Stop and tear down a layer; ramps weight to zero over `fadeDuration` if > 0. */
|
|
431
|
+
stopLayer(layerName: string, fadeDuration?: number): void;
|
|
432
|
+
/** Pause or resume every active overlay layer at once. */
|
|
433
|
+
setAllLayersPaused(paused: boolean): void;
|
|
434
|
+
/**
|
|
435
|
+
* Name of the currently-playing animation, or `null` if no animation is
|
|
436
|
+
* active. Mirrors {@link Armature.currentAnimationName}.
|
|
437
|
+
*/
|
|
438
|
+
get currentAnimationName(): string | null;
|
|
439
|
+
/** `true` if an animation is currently active. */
|
|
440
|
+
get isPlaying(): boolean;
|
|
441
|
+
/**
|
|
442
|
+
* Playback-speed multiplier for the active clip (`1` = author speed, `2` =
|
|
443
|
+
* double speed). Scales only how fast animation time advances, not the
|
|
444
|
+
* authored frame rate or crossfade timing. Mirrors
|
|
445
|
+
* {@link Armature.playbackSpeed}. Use it to tie a locomotion clip's speed
|
|
446
|
+
* to the pivot's movement speed.
|
|
447
|
+
*/
|
|
448
|
+
get playbackSpeed(): number;
|
|
449
|
+
set playbackSpeed(v: number);
|
|
450
|
+
/**
|
|
451
|
+
* `true` when a non-looping animation has reached its last frame and is no
|
|
452
|
+
* longer advancing.
|
|
453
|
+
*/
|
|
454
|
+
get isFinished(): boolean;
|
|
455
|
+
/**
|
|
456
|
+
* Subscribe to frame-aligned animation events fired as `animTime` advances.
|
|
457
|
+
* The active animation's {@link Animation.events} array is the source; the
|
|
458
|
+
* engine doesn't interpret `name` or `payload` - games define their own
|
|
459
|
+
* vocabulary and route inside the callback.
|
|
460
|
+
*
|
|
461
|
+
* Listeners persist across animation switches. Returns an unsubscribe handle.
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```ts
|
|
465
|
+
* character.onAnimEvent((e) => {
|
|
466
|
+
* if (e.name === 'footstep') playFootstepSfx(e.payload);
|
|
467
|
+
* });
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
onAnimEvent(cb: AnimEventCallback): () => void;
|
|
471
|
+
/**
|
|
472
|
+
* Stop playback.
|
|
473
|
+
*
|
|
474
|
+
* With `fadeDuration = 0` (the default), snaps immediately to the rest
|
|
475
|
+
* pose. A positive value crossfades back to rest over that many seconds.
|
|
476
|
+
*/
|
|
477
|
+
stop(fadeDuration?: number): void;
|
|
478
|
+
/**
|
|
479
|
+
* Read and reset the pelvis X/Z delta the animation produced since the
|
|
480
|
+
* last call. Only meaningful when
|
|
481
|
+
* {@link Armature.rootMotionMode | armature.rootMotionMode} is `'extract'`;
|
|
482
|
+
* returns `{0, 0}` otherwise.
|
|
483
|
+
*
|
|
484
|
+
* Typical use: a motor / character controller reads this each tick and
|
|
485
|
+
* feeds it into its own velocity integration, so animation root motion
|
|
486
|
+
* composes cleanly with physics, networking, or input.
|
|
487
|
+
*
|
|
488
|
+
* ```ts
|
|
489
|
+
* // in your render loop, after engine.update(dt):
|
|
490
|
+
* const { dx, dz } = character.consumeRootMotionDelta();
|
|
491
|
+
* motor.setMoveVelocity(dx / dt, dz / dt);
|
|
492
|
+
* motor.update(dt);
|
|
493
|
+
* ```
|
|
494
|
+
*/
|
|
495
|
+
consumeRootMotionDelta(): {
|
|
496
|
+
dx: number;
|
|
497
|
+
dz: number;
|
|
498
|
+
};
|
|
499
|
+
/** True while the character is ticking + rendering (see {@link setActive}). */
|
|
500
|
+
get active(): boolean;
|
|
501
|
+
/**
|
|
502
|
+
* Activate / deactivate this character WITHOUT releasing GPU resources -
|
|
503
|
+
* the basis for object pooling. Deactivating detaches the per-frame update
|
|
504
|
+
* handler and disables every splat object and the armature pivot, so the
|
|
505
|
+
* character stops animating and rendering but keeps all its splats,
|
|
506
|
+
* textures and compiled shaders alive. Reactivating re-attaches the update
|
|
507
|
+
* handler and re-enables the armature; render-LOD then re-picks which splat
|
|
508
|
+
* shows on the next frame (so we only force the current level visible to
|
|
509
|
+
* cover the LOD-disabled case).
|
|
510
|
+
*
|
|
511
|
+
* Reactivate is the cheap path that skips the entire `new Splat` setup cost
|
|
512
|
+
* (texture creation + bone upload + shader compile, ~tens of ms per
|
|
513
|
+
* character) that a fresh {@link load} pays. Callers reposition via
|
|
514
|
+
* `armature.move(...)` and restart animation via `playAnimation(...)` after
|
|
515
|
+
* reactivating.
|
|
516
|
+
*
|
|
517
|
+
* Idempotent.
|
|
518
|
+
*/
|
|
519
|
+
setActive(on: boolean): void;
|
|
520
|
+
/**
|
|
521
|
+
* Detach the per-frame playback tick without touching rendering/GPU
|
|
522
|
+
* resources (unlike {@link setActive}). For hosts that drive the armature's
|
|
523
|
+
* clock themselves and apply their own per-frame pose edits, the auto-tick
|
|
524
|
+
* would re-evaluate and overwrite those edits. Idempotent.
|
|
525
|
+
*/
|
|
526
|
+
detachAutoUpdate(): void;
|
|
527
|
+
/**
|
|
528
|
+
* Advance this character by `dt` seconds: pick the render LOD, evaluate the
|
|
529
|
+
* pose, push it to the splats. `engine.update()` calls this for every
|
|
530
|
+
* character still on auto-update; call it yourself after
|
|
531
|
+
* {@link detachAutoUpdate}.
|
|
532
|
+
*/
|
|
533
|
+
update(dt: number): void;
|
|
534
|
+
/**
|
|
535
|
+
* Release all GPU/CPU resources: removes the engine update handler, unlinks
|
|
536
|
+
* the splat from the armature, destroys both, and removes the armature
|
|
537
|
+
* pivot from the scene graph.
|
|
538
|
+
*/
|
|
539
|
+
destroy(): void;
|
|
540
|
+
}
|