@viggle/splat-engine-three 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +450 -2
- package/dist/chunks/chunk-WN5WBKM4.js +8606 -0
- package/dist/chunks/chunk-WN5WBKM4.js.map +7 -0
- package/dist/index.js +56 -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 +582 -0
- package/dist/types/engine.d.ts +166 -0
- package/dist/types/environment.d.ts +53 -0
- package/dist/types/index.d.ts +40 -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 +76 -0
- package/dist/types/splat/importance.d.ts +60 -0
- package/dist/types/splat/packed-splats.d.ts +36 -0
- package/dist/types/splat/prune.d.ts +60 -0
- package/dist/types/splat/quality.d.ts +175 -0
- package/dist/types/splat/splat-mesh.d.ts +71 -0
- package/dist/types/splat/splat.d.ts +252 -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 +54 -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 +73 -0
- package/dist/types/three/loaders.d.ts +77 -0
- package/dist/types/three/splat-character.d.ts +220 -0
- package/dist/types/types.d.ts +30 -0
- package/dist/types/webgpu.d.ts +5 -0
- package/dist/webgpu.js +843 -0
- package/dist/webgpu.js.map +7 -0
- package/package.json +46 -4
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bone-mask utilities for layered animation. A {@link BoneMask} selects a
|
|
3
|
+
* subset of an armature's bones by index; the {@link Armature} layer system
|
|
4
|
+
* uses it to gate an overlay animation to (e.g.) just the upper body while a
|
|
5
|
+
* base full-body clip drives the rest.
|
|
6
|
+
*
|
|
7
|
+
* Built-in regions ({@link UPPER_BODY_BONE_NAMES} / {@link LOWER_BODY_BONE_NAMES})
|
|
8
|
+
* follow the canonical 86-bone rig naming. The split bone is the pelvis:
|
|
9
|
+
* - UPPER covers everything from `spine_01` up (spine 01-05, neck, head,
|
|
10
|
+
* clavicles, arms, hands, all fingers) so the overlay fully replaces
|
|
11
|
+
* the torso without leak from the base's spine rotation.
|
|
12
|
+
* - LOWER covers `pelvis` plus the leg chain (thighs, calves, calf
|
|
13
|
+
* twists, feet, ball, all toes). Including pelvis means an overlay's
|
|
14
|
+
* pelvis Y motion drives the rig and routes through `rootMotionMode`,
|
|
15
|
+
* so the lower-body overlay owns root motion when active.
|
|
16
|
+
*
|
|
17
|
+
* @group Characters & Animation
|
|
18
|
+
*/
|
|
19
|
+
export declare const UPPER_BODY_BONE_NAMES: readonly ["spine_01", "spine_02", "spine_03", "spine_04", "spine_05", "neck_01", "neck_02", "head", "clavicle_l", "clavicle_r", "upperarm_l", "upperarm_r", "lowerarm_l", "lowerarm_r", "hand_l", "hand_r", "thumb_01_l", "thumb_02_l", "thumb_03_l", "index_metacarpal_l", "index_01_l", "index_02_l", "index_03_l", "middle_metacarpal_l", "middle_01_l", "middle_02_l", "middle_03_l", "ring_metacarpal_l", "ring_01_l", "ring_02_l", "ring_03_l", "pinky_metacarpal_l", "pinky_01_l", "pinky_02_l", "pinky_03_l", "thumb_01_r", "thumb_02_r", "thumb_03_r", "index_metacarpal_r", "index_01_r", "index_02_r", "index_03_r", "middle_metacarpal_r", "middle_01_r", "middle_02_r", "middle_03_r", "ring_metacarpal_r", "ring_01_r", "ring_02_r", "ring_03_r", "pinky_metacarpal_r", "pinky_01_r", "pinky_02_r", "pinky_03_r"];
|
|
20
|
+
export declare const LOWER_BODY_BONE_NAMES: readonly ["pelvis", "thigh_l", "thigh_r", "calf_l", "calf_r", "calf_twist_01_l", "calf_twist_01_r", "foot_l", "foot_r", "ball_l", "ball_r", "bigtoe_01_l", "bigtoe_02_l", "indextoe_01_l", "indextoe_02_l", "middletoe_01_l", "middletoe_02_l", "ringtoe_01_l", "ringtoe_02_l", "littletoe_01_l", "littletoe_02_l", "bigtoe_01_r", "bigtoe_02_r", "indextoe_01_r", "indextoe_02_r", "middletoe_01_r", "middletoe_02_r", "ringtoe_01_r", "ringtoe_02_r", "littletoe_01_r", "littletoe_02_r"];
|
|
21
|
+
export interface BoneMask {
|
|
22
|
+
name: string;
|
|
23
|
+
boneIndices: Int32Array;
|
|
24
|
+
}
|
|
25
|
+
export declare function resolveBoneIndices(allBoneNames: readonly string[], maskNames: readonly string[]): Int32Array;
|
|
26
|
+
export type BodyRegion = 'upperBody' | 'lowerBody';
|
|
27
|
+
export declare function makeRegionMask(region: BodyRegion, allBoneNames: readonly string[]): BoneMask;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Mat4 } from '../math';
|
|
2
|
+
import type { Skeleton } from './skeleton';
|
|
3
|
+
/**
|
|
4
|
+
* Per-axis scale for a single bone, in a frame aligned to the bone:
|
|
5
|
+
* - `along` - scale down the bone's own axis (lengthen / shorten),
|
|
6
|
+
* - `width` / `thickness` - scale across the bone (the two cross-section axes;
|
|
7
|
+
* their world orientation is bone-dependent, so treat them as "the two
|
|
8
|
+
* perpendicular directions" rather than a fixed front/side).
|
|
9
|
+
*
|
|
10
|
+
* Each defaults to `1`. A bare `number` is shorthand for a uniform scale on all
|
|
11
|
+
* three axes.
|
|
12
|
+
*
|
|
13
|
+
* @group Characters & Animation
|
|
14
|
+
*/
|
|
15
|
+
export interface BoneScale {
|
|
16
|
+
along: number;
|
|
17
|
+
width: number;
|
|
18
|
+
thickness: number;
|
|
19
|
+
}
|
|
20
|
+
/** A bone scale, or a number meaning uniform scale on all three axes. */
|
|
21
|
+
export type BoneScaleInput = Partial<BoneScale> | number;
|
|
22
|
+
/**
|
|
23
|
+
* Per-bone data that realizes a set of bone scales. Produced by
|
|
24
|
+
* {@link computeBoneStretch}, consumed by {@link Skeleton} (lengthening) and
|
|
25
|
+
* {@link SplatSkinning} (flesh/covariance stretch).
|
|
26
|
+
*
|
|
27
|
+
* @group Characters & Animation
|
|
28
|
+
*/
|
|
29
|
+
export interface BoneStretch {
|
|
30
|
+
/**
|
|
31
|
+
* Per-bone multiplier on the local rest translation. A bone's local
|
|
32
|
+
* translation is its offset from its parent (the parent segment's vector),
|
|
33
|
+
* so scaling it lengthens that segment by the parent's `along` factor.
|
|
34
|
+
* Translation-only → never inflates covariance, never compounds down a
|
|
35
|
+
* chain. Indexed by bone; `1` = unchanged.
|
|
36
|
+
*/
|
|
37
|
+
lengthScale: Float32Array;
|
|
38
|
+
/**
|
|
39
|
+
* Per-bone local stretch matrix (`F·diag(along,width,thickness)·Fᵀ` in the
|
|
40
|
+
* bone-aligned frame `F`), folded into the skinning palette so the bone's
|
|
41
|
+
* Gaussians scale on all three axes and their covariances follow. Identity
|
|
42
|
+
* for unscaled bones. Indexed by bone.
|
|
43
|
+
*/
|
|
44
|
+
covStretch: Mat4[];
|
|
45
|
+
/** True when at least one bone is actually scaled. */
|
|
46
|
+
any: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Translate a map of per-bone scales into the {@link BoneStretch} arrays.
|
|
50
|
+
* `factors[boneName]` scales that bone's flesh + segment (e.g.
|
|
51
|
+
* `{ thigh_l: { along: 1.3 }, calf_l: { along: 1.3 } }` → a 1.3× longer left
|
|
52
|
+
* leg; `{ upperarm_l: { width: 1.4, thickness: 1.4 } }` → a beefier arm).
|
|
53
|
+
* Bones not listed stay at `1`.
|
|
54
|
+
*
|
|
55
|
+
* Correct for arbitrary bone sets (limbs, spine, …) with no boundary
|
|
56
|
+
* compensation: lengthening lives in per-segment translations (no cumulative
|
|
57
|
+
* scale), and the flesh scale is per-bone in the palette (non-propagating).
|
|
58
|
+
* Each bone scales in ITS OWN frame, so bones at any orientation scale
|
|
59
|
+
* correctly.
|
|
60
|
+
*
|
|
61
|
+
* @param skeleton Rig to read the rest pose / hierarchy from.
|
|
62
|
+
* @param boneNames Bone names in index order (from the armature).
|
|
63
|
+
* @param factors Per-bone scales keyed by bone name.
|
|
64
|
+
*/
|
|
65
|
+
export declare function computeBoneStretch(skeleton: Skeleton, boneNames: readonly string[], factors: Record<string, BoneScaleInput>): BoneStretch;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Quat, Vec3 } from '../math';
|
|
2
|
+
/**
|
|
3
|
+
* Per-bone local TRS for a whole rig. The seam between
|
|
4
|
+
* {@link AnimationPlayer} (which writes pose values) and
|
|
5
|
+
* {@link Skeleton} (which composes them into world transforms).
|
|
6
|
+
*
|
|
7
|
+
* Three parallel arrays of length `numBones`. Values live in armature-local
|
|
8
|
+
* bone space - absolute, not rest-relative. Allocation happens once;
|
|
9
|
+
* AnimationPlayer mutates entries in place each frame.
|
|
10
|
+
*
|
|
11
|
+
* @group Characters & Animation
|
|
12
|
+
*/
|
|
13
|
+
export declare class Pose {
|
|
14
|
+
readonly positions: Vec3[];
|
|
15
|
+
readonly rotations: Quat[];
|
|
16
|
+
readonly scales: Vec3[];
|
|
17
|
+
constructor(numBones: number);
|
|
18
|
+
get numBones(): number;
|
|
19
|
+
/** Copy every bone from `other` into this pose. */
|
|
20
|
+
copyFrom(other: Pose): void;
|
|
21
|
+
/**
|
|
22
|
+
* Reset every bone to the bind-pose values held in `bindT` (flat
|
|
23
|
+
* `[x,y,z]*numBones`) and `bindR` (flat `[x,y,z,w]*numBones`). Scales reset
|
|
24
|
+
* to unit.
|
|
25
|
+
*/
|
|
26
|
+
setToBind(bindT: Float32Array, bindR: Float32Array): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Mat4 } from '../math';
|
|
2
|
+
import type { RestPose } from '../anim/rest-relative';
|
|
3
|
+
import type { ArmatureData } from '../types';
|
|
4
|
+
import type { Pose } from './pose';
|
|
5
|
+
/**
|
|
6
|
+
* Rig data and skeletal math for one character. Owns the bone hierarchy, the
|
|
7
|
+
* rest pose, and the (lazy) inverse-bind pose. Composes per-bone local
|
|
8
|
+
* transforms into world-space matrices.
|
|
9
|
+
*
|
|
10
|
+
* Pure data + math - no three.js object, no `SplatEngine`, no `Splat`. Safe to
|
|
11
|
+
* construct in tests with a fixture rig.
|
|
12
|
+
*
|
|
13
|
+
* @group Characters & Animation
|
|
14
|
+
*/
|
|
15
|
+
export declare class Skeleton {
|
|
16
|
+
readonly numBones: number;
|
|
17
|
+
/** Parent bone index per bone. `-1` for root bones. */
|
|
18
|
+
readonly parents: Int32Array;
|
|
19
|
+
/** Flat bind-pose translations: `[x,y,z]*numBones`. */
|
|
20
|
+
readonly bindT: Float32Array;
|
|
21
|
+
/** Flat bind-pose rotations (quaternions): `[x,y,z,w]*numBones`. */
|
|
22
|
+
readonly bindR: Float32Array;
|
|
23
|
+
/** Rest pose as parallel `Vec3`/`Quat` arrays. */
|
|
24
|
+
readonly restPose: RestPose;
|
|
25
|
+
/**
|
|
26
|
+
* Optional per-bone multiplier on the local rest translation, applied during
|
|
27
|
+
* FK. A bone's local translation is its offset from its parent, so scaling
|
|
28
|
+
* it lengthens that segment - this is the lengthening half of bone-length
|
|
29
|
+
* scaling (see {@link computeBoneStretch}). It deliberately affects only the
|
|
30
|
+
* translation, not the bone's orientation/scale, so it never inflates
|
|
31
|
+
* Gaussian covariance and never compounds down a chain. `null` = unscaled
|
|
32
|
+
* (the normal path). The bind/inverse-bind are intentionally NOT rescaled,
|
|
33
|
+
* so the palette carries the standard→stretched delta.
|
|
34
|
+
*/
|
|
35
|
+
lengthScale: Float32Array | null;
|
|
36
|
+
private readonly _children;
|
|
37
|
+
private readonly _rootBones;
|
|
38
|
+
private _inverseBindPose;
|
|
39
|
+
private readonly _tmpLocalMat;
|
|
40
|
+
private readonly _tmpPos;
|
|
41
|
+
private readonly _kfStack;
|
|
42
|
+
constructor(armatureData: ArmatureData);
|
|
43
|
+
/**
|
|
44
|
+
* World-space inverse-bind pose, one matrix per bone. Lazy - bind pose is
|
|
45
|
+
* only needed once splat skinning starts pushing transforms.
|
|
46
|
+
*/
|
|
47
|
+
get inverseBindPose(): Mat4[];
|
|
48
|
+
/**
|
|
49
|
+
* Compose local-space `pose` into world-space matrices, prefixed by
|
|
50
|
+
* `entityWorldTransform`. Walks the hierarchy parent-first so child world
|
|
51
|
+
* transforms read the freshly-written parent.
|
|
52
|
+
*
|
|
53
|
+
* `out` is reused - entries are created lazily if missing, but the caller
|
|
54
|
+
* owns the buffer (no per-frame allocation).
|
|
55
|
+
*/
|
|
56
|
+
applyLocal(pose: Pose, entityWorldTransform: Mat4, out: Mat4[]): void;
|
|
57
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Frustum } from 'three';
|
|
2
|
+
import { Mat4 } from '../math';
|
|
3
|
+
import type { Splat } from '../splat/splat';
|
|
4
|
+
import type { SkeletonLibrary } from '../types';
|
|
5
|
+
import type { Skeleton } from './skeleton';
|
|
6
|
+
/**
|
|
7
|
+
* Per-character splat skinning coordinator. Owns the set of {@link Splat}s
|
|
8
|
+
* driven by an armature, their palette-slot maps, and the GPU push that
|
|
9
|
+
* uploads bone matrices each frame.
|
|
10
|
+
*
|
|
11
|
+
* Also owns the frustum-visibility gate: the armature can skip the expensive
|
|
12
|
+
* pose-eval + skinning path when no linked splat is on-screen.
|
|
13
|
+
*
|
|
14
|
+
* @group Characters & Animation
|
|
15
|
+
*/
|
|
16
|
+
export declare class SplatSkinning {
|
|
17
|
+
/** Splats currently driven by this skinning unit. */
|
|
18
|
+
readonly linkedSplats: Set<Splat>;
|
|
19
|
+
/**
|
|
20
|
+
* Optional per-bone local stretch matrices (the covariance half of
|
|
21
|
+
* bone-length scaling - see {@link computeBoneStretch}). When set, each
|
|
22
|
+
* bone's palette entry gets this stretch folded in, the per-bone scale is
|
|
23
|
+
* NOT normalized out of the palette, and linked splats render with the
|
|
24
|
+
* `USE_NONRIGID_COV` shader path so Gaussian covariances follow the stretch.
|
|
25
|
+
* `null` = rigid skinning (the normal path). Set via {@link setCovStretch}.
|
|
26
|
+
*/
|
|
27
|
+
covStretch: Mat4[] | null;
|
|
28
|
+
private readonly _skeleton;
|
|
29
|
+
private readonly _skeletonLibrary;
|
|
30
|
+
private readonly _bonePaletteFirstIdx;
|
|
31
|
+
private readonly _boneIndexLists;
|
|
32
|
+
private readonly _finalBoneTransforms;
|
|
33
|
+
private readonly _armatureWorldToLocal;
|
|
34
|
+
constructor(skeleton: Skeleton, skeletonLibrary: SkeletonLibrary | undefined);
|
|
35
|
+
link(splat: Splat): void;
|
|
36
|
+
unlink(splat: Splat): void;
|
|
37
|
+
/**
|
|
38
|
+
* Set (or clear with `null`) the per-bone covariance-stretch matrices for
|
|
39
|
+
* bone-length scaling. Also flips the `USE_NONRIGID_COV` shader path on every
|
|
40
|
+
* linked splat so covariances follow the stretch. Pass `null` to return to
|
|
41
|
+
* rigid skinning.
|
|
42
|
+
*/
|
|
43
|
+
setCovStretch(mats: Mat4[] | null): void;
|
|
44
|
+
/**
|
|
45
|
+
* Returns `true` when at least one linked splat's bounding sphere intersects
|
|
46
|
+
* the camera frustum. Used by the armature to short-circuit the per-frame
|
|
47
|
+
* skinning math for off-screen characters.
|
|
48
|
+
*
|
|
49
|
+
* When `frustum` is missing (e.g. before the first `engine.update()`) returns `true` -
|
|
50
|
+
* the conservative choice is to do the extra work rather than freeze a
|
|
51
|
+
* character's pose.
|
|
52
|
+
*/
|
|
53
|
+
anyVisible(frustum: Frustum | undefined): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Compose `worldTransforms` (one per bone) with the pivot's inverse world
|
|
56
|
+
* transform and the inverse-bind pose, then upload the resulting palette to
|
|
57
|
+
* every linked splat. `worldTransforms[i]` is the i-th bone's matrix in
|
|
58
|
+
* world space, as produced by {@link Skeleton.applyLocal}.
|
|
59
|
+
*
|
|
60
|
+
* Strips scale from the per-bone linear part before composing with the
|
|
61
|
+
* inverse bind. Without this, any net scale (from `splat.object3D` world scale
|
|
62
|
+
* or animated bone scale) inflates Gaussian sigmas on the GPU via the
|
|
63
|
+
* palette's column lengths - visible as radial streaks on hands/fists, and
|
|
64
|
+
* desyncs CPU-computed centers from GPU-rendered positions.
|
|
65
|
+
*/
|
|
66
|
+
pushTransforms(worldTransforms: Mat4[], entityWorldTransform: Mat4, skipPositions?: boolean): void;
|
|
67
|
+
/** Request a re-sort on every linked splat (e.g. after a transform change). */
|
|
68
|
+
requestSortAll(): void;
|
|
69
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { BufferGeometry, Camera, Material, Mesh } from 'three';
|
|
2
|
+
import type { SplatEngine } from '../engine';
|
|
3
|
+
import type { Splat } from './splat';
|
|
4
|
+
/**
|
|
5
|
+
* The renderer-specific half of a {@link SplatMesh}: the geometry and material
|
|
6
|
+
* one kind of three.js renderer needs, plus the per-frame work to keep them
|
|
7
|
+
* current.
|
|
8
|
+
*
|
|
9
|
+
* Everything a splat is - its packed data, weights, palette, tint - lives on
|
|
10
|
+
* the `Splat`. An implementation only turns that into something one renderer
|
|
11
|
+
* can draw, and owns whatever that takes (sort state, GPU buffers).
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export interface SplatRenderImpl {
|
|
16
|
+
readonly geometry: BufferGeometry;
|
|
17
|
+
readonly material: Material;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the splat's centres must be re-skinned on the CPU every frame.
|
|
20
|
+
* True when the depth sort runs on the CPU; false when the GPU skins for its
|
|
21
|
+
* own sort, in which case that pass - the dominant per-character CPU cost -
|
|
22
|
+
* is skipped entirely.
|
|
23
|
+
*/
|
|
24
|
+
readonly needsCpuCenters: boolean;
|
|
25
|
+
/** True while the drawn order is stale or absent because a sort is outstanding. */
|
|
26
|
+
readonly sortPending: boolean;
|
|
27
|
+
/** Force a re-sort on the next render even if nothing appears to have changed. */
|
|
28
|
+
requestSort(): void;
|
|
29
|
+
/** Rebuild the shader variant after a feature toggle on the splat. */
|
|
30
|
+
syncDefines(): void;
|
|
31
|
+
/** Refresh culling bounds from the splat's current local bounds. */
|
|
32
|
+
syncBounds(): void;
|
|
33
|
+
/** Re-upload bone indices/weights after the splat's arrays were edited. */
|
|
34
|
+
syncBoneData(): void;
|
|
35
|
+
/** Re-upload per-splat group ids after {@link Splat.setSplatGroups}. */
|
|
36
|
+
syncVariantGroups(): void;
|
|
37
|
+
/** Called from the host mesh's `onBeforeRender`, once per draw. */
|
|
38
|
+
beforeRender(renderer: unknown, camera: Camera): void;
|
|
39
|
+
/** Release GPU resources. The splat's own textures are not this object's to free. */
|
|
40
|
+
dispose(): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates {@link SplatRenderImpl}s for one kind of three.js renderer.
|
|
44
|
+
*
|
|
45
|
+
* You rarely need to name one: a {@link SplatMesh} picks the backend that
|
|
46
|
+
* matches whichever renderer first draws it.
|
|
47
|
+
*
|
|
48
|
+
* @group Engine
|
|
49
|
+
*/
|
|
50
|
+
export interface SplatBackend {
|
|
51
|
+
/**
|
|
52
|
+
* @param host - The mesh in the scene graph the implementation draws through.
|
|
53
|
+
* @param asyncSort - The splat is static, so a sort that lags the camera by
|
|
54
|
+
* a frame or two is acceptable. Backends that sort on the GPU ignore this.
|
|
55
|
+
*/
|
|
56
|
+
createImpl(host: Mesh, splat: Splat, engine: SplatEngine, asyncSort: boolean): SplatRenderImpl;
|
|
57
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SkeletonLibrary } from '../types';
|
|
2
|
+
import type { Splat } from './splat';
|
|
3
|
+
/**
|
|
4
|
+
* Build the source→target bone remap when the splat's `.bin` bundle was
|
|
5
|
+
* authored against a wider skeleton than the armature uses (e.g. a 1185-bone
|
|
6
|
+
* source on the 86-bone std-male rig, or a 441-bone full body+facial source
|
|
7
|
+
* on the 86-bone body rig).
|
|
8
|
+
*
|
|
9
|
+
* Returns `null` when no remap is needed (source and target bone counts
|
|
10
|
+
* match) or when the skeleton library doesn't carry enough naming metadata to
|
|
11
|
+
* resolve the mapping. Unmapped bones in the result are `-1`, except facial
|
|
12
|
+
* bones (`FACIAL_*`), which fall back to the `head` bone so the splats track
|
|
13
|
+
* head motion rather than snapping to root.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildSourceBoneRemap(splat: Splat, numBones: number, skeletonLibrary: SkeletonLibrary | undefined): Int32Array | null;
|
|
16
|
+
/**
|
|
17
|
+
* Allocate the splat's slice of the shared transform palette and rewrite its
|
|
18
|
+
* bone-index texture so each splat's K influences point at the palette slots
|
|
19
|
+
* the armature will write into.
|
|
20
|
+
*
|
|
21
|
+
* Splats with no resolvable target bone (typically facial influences on a
|
|
22
|
+
* body-only rig that can't fall back to `head`) get weight 0 - they stay at
|
|
23
|
+
* rest instead of snapping to bone 0.
|
|
24
|
+
*
|
|
25
|
+
* Returns the first palette index allocated for this splat's bone block.
|
|
26
|
+
* Palette slot for bone `i` is always `firstIdx + i`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function initializeSplatBoneMapping(splat: Splat, numBones: number, skeletonLibrary: SkeletonLibrary | undefined): number;
|
|
29
|
+
/**
|
|
30
|
+
* Walk the splat's per-influence bone-index buffer and return the sorted list
|
|
31
|
+
* of distinct bone indices it actually references. Used to skip palette writes
|
|
32
|
+
* for bones no splat influences (e.g. a 441-bone rig where ~half the bones are
|
|
33
|
+
* facial and the splat has no facial weights).
|
|
34
|
+
*
|
|
35
|
+
* Returns an empty `Int32Array` when the splat has no weights data - callers
|
|
36
|
+
* treat that as "fall back to walking every bone."
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildSplatBoneIndexList(splat: Splat, numBones: number, firstPaletteIdx: number): Int32Array;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Back-to-front depth ordering for splats: a counting sort over quantised
|
|
3
|
+
* distance along the view direction.
|
|
4
|
+
*
|
|
5
|
+
* The same routine runs in two places. Skinned characters are small and move
|
|
6
|
+
* every frame, so they sort synchronously on the main thread and never show a
|
|
7
|
+
* stale order. Environments are large and only need a new order when the camera
|
|
8
|
+
* moves, so they sort in a worker ({@link AsyncDepthSorter}) and keep drawing
|
|
9
|
+
* the previous order until the new one lands.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Range of `dot(p, dir)` over an axis-aligned box - the projection of the box
|
|
13
|
+
* onto the view direction.
|
|
14
|
+
*/
|
|
15
|
+
export declare function projectBoundsOnDirection(min: ArrayLike<number>, max: ArrayLike<number>, dx: number, dy: number, dz: number): {
|
|
16
|
+
depthMin: number;
|
|
17
|
+
depthMax: number;
|
|
18
|
+
};
|
|
19
|
+
/** Synchronous sorter with its own scratch buffers. One per splat. */
|
|
20
|
+
export declare class DepthSorter {
|
|
21
|
+
private readonly _scratch;
|
|
22
|
+
/**
|
|
23
|
+
* @param count - How many splats take part. With `subset`, the length of
|
|
24
|
+
* the prefix of it to use; without, the splats are `[0, count)`.
|
|
25
|
+
* @param subset - Splat indices to sort, for a count LOD. See
|
|
26
|
+
* {@link Splat.setCountLod}.
|
|
27
|
+
*/
|
|
28
|
+
sort(centers: Float32Array, count: number, dx: number, dy: number, dz: number, depthMin: number, depthMax: number, order: Uint32Array, subset?: Uint32Array | null): void;
|
|
29
|
+
}
|
|
30
|
+
export interface SortRequest {
|
|
31
|
+
dx: number;
|
|
32
|
+
dy: number;
|
|
33
|
+
dz: number;
|
|
34
|
+
depthMin: number;
|
|
35
|
+
depthMax: number;
|
|
36
|
+
/**
|
|
37
|
+
* How many splats to order, for a count LOD. Defaults to all of them; with
|
|
38
|
+
* a subset set, the length of the prefix of it to use.
|
|
39
|
+
*/
|
|
40
|
+
count?: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Worker-backed sorter for static splats.
|
|
44
|
+
*
|
|
45
|
+
* At most one sort is in flight. A request made while the worker is busy
|
|
46
|
+
* replaces any earlier pending one, so a fast-moving camera never queues work
|
|
47
|
+
* it no longer wants. A single order buffer is transferred to the worker and
|
|
48
|
+
* back, never copied.
|
|
49
|
+
*
|
|
50
|
+
* The worker is built from a Blob of this module's own sort routine, so there
|
|
51
|
+
* is no second file for a bundler to lose.
|
|
52
|
+
*/
|
|
53
|
+
export declare class AsyncDepthSorter {
|
|
54
|
+
private _worker;
|
|
55
|
+
private _pending;
|
|
56
|
+
/** The order buffer, when it is on this side of the worker boundary. */
|
|
57
|
+
private _order;
|
|
58
|
+
/**
|
|
59
|
+
* @param centers - Flat `[x,y,z] * count`. Copied into the worker once.
|
|
60
|
+
* @param onSorted - Called with each finished order and how many of its
|
|
61
|
+
* entries are current. The array is only valid for the duration of the call
|
|
62
|
+
* - it goes back to the worker on the next request - so copy out of it
|
|
63
|
+
* before returning.
|
|
64
|
+
*/
|
|
65
|
+
constructor(centers: Float32Array, count: number, onSorted: (order: Uint32Array, count: number) => void);
|
|
66
|
+
/**
|
|
67
|
+
* Give the worker the importance ordering a count LOD selects from. Sent
|
|
68
|
+
* once and kept there, like the centres; after this a request carries only
|
|
69
|
+
* how many of its entries to use.
|
|
70
|
+
*/
|
|
71
|
+
setSubset(subset: Uint32Array): void;
|
|
72
|
+
/** True while a sort is running in the worker. */
|
|
73
|
+
get busy(): boolean;
|
|
74
|
+
request(req: SortRequest): void;
|
|
75
|
+
dispose(): void;
|
|
76
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ranking splats by how much they contribute, and the spatial bucketing both
|
|
3
|
+
* users of that ranking share.
|
|
4
|
+
*
|
|
5
|
+
* Two features are built on it. {@link pruneSplats} throws the weakest splats
|
|
6
|
+
* away at load; the count LOD ({@link Splat.setCountLod}) keeps them all and
|
|
7
|
+
* processes a prefix of the ranking that shrinks with distance. The ranking
|
|
8
|
+
* has to be the same one in both cases, which is why it lives here rather than
|
|
9
|
+
* in either of them.
|
|
10
|
+
*/
|
|
11
|
+
import type { PackedSplats } from './packed-splats';
|
|
12
|
+
/**
|
|
13
|
+
* Group indices by spatial cell without a comparison sort: hash the cells to
|
|
14
|
+
* dense ids, count, prefix-sum, scatter.
|
|
15
|
+
*
|
|
16
|
+
* O(n) with one map probe per splat, against the O(n log n) a sort over a
|
|
17
|
+
* composite key would cost - which measured 11-16 ms of a ~40 ms character
|
|
18
|
+
* load in the Unity build this is ported from.
|
|
19
|
+
*/
|
|
20
|
+
export declare function bucketByCell(cellKey: Float64Array, n: number): {
|
|
21
|
+
order: Uint32Array;
|
|
22
|
+
start: Uint32Array;
|
|
23
|
+
cells: number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* One splat's cell key on a `1 / voxelSize` grid whose origin is `min`.
|
|
27
|
+
*
|
|
28
|
+
* The one definition of the grid. Both users of it have to agree - a splat
|
|
29
|
+
* pruned by one rule and ranked by another would be thinned twice in the same
|
|
30
|
+
* place - and they walk different layouts, so what they share is this rather
|
|
31
|
+
* than a loop.
|
|
32
|
+
*/
|
|
33
|
+
export declare function cellKeyOf(x: number, y: number, z: number, minX: number, minY: number, minZ: number, invVoxel: number): number;
|
|
34
|
+
/**
|
|
35
|
+
* Cell keys for `centers`, with the grid origin at the actual minimum of the
|
|
36
|
+
* positions rather than at any declared bounds, which can carry a margin the
|
|
37
|
+
* grid should not inherit.
|
|
38
|
+
*/
|
|
39
|
+
export declare function cellKeysFor(centers: Float32Array, count: number, voxelSize: number): Float64Array;
|
|
40
|
+
/**
|
|
41
|
+
* Splat indices ordered so that any prefix of the result is a good-looking
|
|
42
|
+
* subset of the whole: the most important splat of every cell first, then the
|
|
43
|
+
* second of every cell, and so on.
|
|
44
|
+
*
|
|
45
|
+
* Importance within a cell is opacity times the two largest gaussian axes -
|
|
46
|
+
* the area the splat actually paints, which the Unity build measured as
|
|
47
|
+
* ranking 0.80 with the ink each splat deposits, at no cost to compute.
|
|
48
|
+
*
|
|
49
|
+
* The round-robin across cells is what makes this usable as a prefix, and it
|
|
50
|
+
* is the same decision the load-time prune makes for the same reason: ranked
|
|
51
|
+
* globally, a prefix would be every strong splat on the jacket and none of the
|
|
52
|
+
* many individually-faint ones that make up a face, so the face would be the
|
|
53
|
+
* first thing to go. Taken a rank at a time across cells, every region thins
|
|
54
|
+
* by the same proportion at every prefix length, so a character at 30% looks
|
|
55
|
+
* like a sparser version of itself rather than a damaged one.
|
|
56
|
+
*
|
|
57
|
+
* Costs one pass over the splats plus a small sort inside each cell, and is
|
|
58
|
+
* built once, the first time a count LOD asks for it.
|
|
59
|
+
*/
|
|
60
|
+
export declare function buildImportanceOrder(packed: PackedSplats, voxelSize: number): Uint32Array;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SplatAttributes } from '../loader/character/types';
|
|
2
|
+
/**
|
|
3
|
+
* Splat attributes packed into the three texel streams the shaders read.
|
|
4
|
+
*
|
|
5
|
+
* Renderer-agnostic on purpose: these are plain typed arrays laid out as
|
|
6
|
+
* `width x height` RGBA texels, so the WebGL and WebGPU backends wrap the same
|
|
7
|
+
* bytes in their own texture objects.
|
|
8
|
+
*
|
|
9
|
+
* The layout is 32 bytes per splat:
|
|
10
|
+
*
|
|
11
|
+
* - `color` RGBA16F - `rgb` display-referred colour, `a` opacity (post-sigmoid).
|
|
12
|
+
* - `transformA` RGBA32UI - `xyz` the centre as raw float32 bits, `w` two packed
|
|
13
|
+
* half floats: rotation `x` (low 16) and `y` (high 16).
|
|
14
|
+
* - `transformB` RGBA16F - `xyz` linear scale, `w` rotation `z`.
|
|
15
|
+
*
|
|
16
|
+
* Rotation `w` is not stored: quaternions are normalised and flipped to
|
|
17
|
+
* `w >= 0` here, so the shader recovers it as `sqrt(1 - |xyz|^2)`.
|
|
18
|
+
*/
|
|
19
|
+
export interface PackedSplats {
|
|
20
|
+
numSplats: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
color: Uint16Array;
|
|
24
|
+
transformA: Uint32Array;
|
|
25
|
+
transformB: Uint16Array;
|
|
26
|
+
/** Rest-pose centres, flat `[x,y,z] * numSplats`. The depth sort reads these. */
|
|
27
|
+
centers: Float32Array;
|
|
28
|
+
boundsMin: [number, number, number];
|
|
29
|
+
boundsMax: [number, number, number];
|
|
30
|
+
}
|
|
31
|
+
/** Texture dimensions for `count` texels: near-square, wide enough to hold every row. */
|
|
32
|
+
export declare function textureSizeFor(count: number): {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
};
|
|
36
|
+
export declare function packSplats(numSplats: number, s: SplatAttributes): PackedSplats;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load-time splat removal: the two passes that make a character cheaper by
|
|
3
|
+
* containing fewer splats, rather than by drawing each one faster.
|
|
4
|
+
*
|
|
5
|
+
* Both run on the decoded attribute columns, before {@link packSplats} turns
|
|
6
|
+
* them into texels, so a removed splat costs nothing anywhere afterwards -
|
|
7
|
+
* no texel, no CPU skinning slot, no sort slot, no quad. That is what
|
|
8
|
+
* separates these from {@link SplatQuality.quadAlphaCutoff}, which shrinks
|
|
9
|
+
* quads and saves rasterisation alone.
|
|
10
|
+
*
|
|
11
|
+
* Ported from the Unity build's `SplatCharacterBinData.CullByOpacity` and
|
|
12
|
+
* `PruneByImportance`, including the two decisions in them that are easy to
|
|
13
|
+
* get wrong: ranking inside spatial cells rather than globally, and selecting
|
|
14
|
+
* rather than sorting within a cell.
|
|
15
|
+
*/
|
|
16
|
+
import type { SplatAttributes } from '../loader/character/types';
|
|
17
|
+
import type { SplatWeights } from '../types';
|
|
18
|
+
/** What {@link pruneSplats} did, for a caller that wants to log or report it. */
|
|
19
|
+
export interface PruneReport {
|
|
20
|
+
/** Splats the decode handed in. */
|
|
21
|
+
numSplatsIn: number;
|
|
22
|
+
/** Splats left after both passes. This is what every consumer should read. */
|
|
23
|
+
numSplatsOut: number;
|
|
24
|
+
/** Removed by the opacity floor. */
|
|
25
|
+
droppedByOpacity: number;
|
|
26
|
+
/** Removed by the per-voxel importance quota, on top of the opacity floor. */
|
|
27
|
+
droppedByImportance: number;
|
|
28
|
+
/**
|
|
29
|
+
* The skinning influences, re-viewed onto the compacted prefix when anything
|
|
30
|
+
* was removed, or the caller's own object when nothing was. `undefined` for
|
|
31
|
+
* a splat with no skinning.
|
|
32
|
+
*
|
|
33
|
+
* Handed back rather than mutated in place because the arrays' *length* is
|
|
34
|
+
* what says how many influences each splat has: `Splat` divides it by the
|
|
35
|
+
* splat count to recover `K`, and leaving a pruned set at its original
|
|
36
|
+
* length would make that division report a `K` several times too large.
|
|
37
|
+
*/
|
|
38
|
+
weights?: SplatWeights;
|
|
39
|
+
}
|
|
40
|
+
export interface PruneOptions {
|
|
41
|
+
/** Defaults to {@link SplatQuality.minOpacity}. */
|
|
42
|
+
minOpacity?: number;
|
|
43
|
+
/** Defaults to {@link SplatQuality.pruneRatio}. */
|
|
44
|
+
pruneRatio?: number;
|
|
45
|
+
/** Defaults to {@link SplatQuality.pruneVoxelSize}. */
|
|
46
|
+
voxelSize?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Remove splats the current quality settings say are not worth carrying,
|
|
50
|
+
* compacting every column in place.
|
|
51
|
+
*
|
|
52
|
+
* `attributes` and `weights` are mutated: their arrays keep their allocated
|
|
53
|
+
* length and the returned count is what every consumer should read, which is
|
|
54
|
+
* exactly how {@link packSplats} already uses `numSplats`. Order is preserved,
|
|
55
|
+
* so whatever spatial coherence the file had survives.
|
|
56
|
+
*
|
|
57
|
+
* Returns the input count unchanged, having touched nothing, when both knobs
|
|
58
|
+
* are off - the default - so this sits on the load path for free.
|
|
59
|
+
*/
|
|
60
|
+
export declare function pruneSplats(numSplats: number, attributes: SplatAttributes, weights?: SplatWeights, opts?: PruneOptions): PruneReport;
|