@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,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): Int32Array;
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
sort(centers: Float32Array, count: number, dx: number, dy: number, dz: number, depthMin: number, depthMax: number, order: Uint32Array): void;
|
|
23
|
+
}
|
|
24
|
+
export interface SortRequest {
|
|
25
|
+
dx: number;
|
|
26
|
+
dy: number;
|
|
27
|
+
dz: number;
|
|
28
|
+
depthMin: number;
|
|
29
|
+
depthMax: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Worker-backed sorter for static splats.
|
|
33
|
+
*
|
|
34
|
+
* At most one sort is in flight. A request made while the worker is busy
|
|
35
|
+
* replaces any earlier pending one, so a fast-moving camera never queues work
|
|
36
|
+
* it no longer wants. A single order buffer is transferred to the worker and
|
|
37
|
+
* back, never copied.
|
|
38
|
+
*
|
|
39
|
+
* The worker is built from a Blob of this module's own sort routine, so there
|
|
40
|
+
* is no second file for a bundler to lose.
|
|
41
|
+
*/
|
|
42
|
+
export declare class AsyncDepthSorter {
|
|
43
|
+
private _worker;
|
|
44
|
+
private _pending;
|
|
45
|
+
/** The order buffer, when it is on this side of the worker boundary. */
|
|
46
|
+
private _order;
|
|
47
|
+
/**
|
|
48
|
+
* @param centers - Flat `[x,y,z] * count`. Copied into the worker once.
|
|
49
|
+
* @param onSorted - Called with each finished order. The array is only valid
|
|
50
|
+
* for the duration of the call - it goes back to the worker on the next
|
|
51
|
+
* request - so copy out of it before returning.
|
|
52
|
+
*/
|
|
53
|
+
constructor(centers: Float32Array, count: number, onSorted: (order: Uint32Array) => void);
|
|
54
|
+
/** True while a sort is running in the worker. */
|
|
55
|
+
get busy(): boolean;
|
|
56
|
+
request(req: SortRequest): void;
|
|
57
|
+
dispose(): void;
|
|
58
|
+
}
|
|
@@ -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,71 @@
|
|
|
1
|
+
import { Mesh } from 'three';
|
|
2
|
+
import type { Camera } from 'three';
|
|
3
|
+
import type { SplatEngine } from '../engine';
|
|
4
|
+
import type { SplatBackend } from './backend';
|
|
5
|
+
import type { Splat } from './splat';
|
|
6
|
+
/**
|
|
7
|
+
* Make the WebGPU backend available to every {@link SplatMesh}. Called by the
|
|
8
|
+
* `@viggle/splat-engine-three/webgpu` entry point when it is imported; the
|
|
9
|
+
* default entry point leaves it out so WebGL applications never load
|
|
10
|
+
* `three/webgpu`.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export declare function registerWebGPUBackend(backend: SplatBackend): void;
|
|
15
|
+
/**
|
|
16
|
+
* The object that draws one {@link Splat}: an ordinary `THREE.Mesh` in your
|
|
17
|
+
* scene graph, so `visible`, `renderOrder`, `layers` and parent transforms all
|
|
18
|
+
* work as they do for any mesh.
|
|
19
|
+
*
|
|
20
|
+
* It works with both `THREE.WebGLRenderer` and `WebGPURenderer`. Which one is
|
|
21
|
+
* not known until something renders it, so the renderer-specific geometry and
|
|
22
|
+
* material are attached the first time it is drawn; the mesh shows from the
|
|
23
|
+
* following frame.
|
|
24
|
+
*
|
|
25
|
+
* @group Splats
|
|
26
|
+
*/
|
|
27
|
+
export declare class SplatMesh extends Mesh {
|
|
28
|
+
/** Type flag, in the three.js manner. */
|
|
29
|
+
readonly isSplatMesh = true;
|
|
30
|
+
/** The splat this mesh draws: its data, tint, grade and render options. */
|
|
31
|
+
readonly splat: Splat;
|
|
32
|
+
private readonly _engine;
|
|
33
|
+
private readonly _asyncSort;
|
|
34
|
+
private _impl;
|
|
35
|
+
/**
|
|
36
|
+
* @param asyncSort - See {@link SplatBackend.createImpl}.
|
|
37
|
+
* @internal Construct a {@link Splat} instead; it creates its own mesh.
|
|
38
|
+
*/
|
|
39
|
+
constructor(splat: Splat, engine: SplatEngine, asyncSort: boolean);
|
|
40
|
+
private _attach;
|
|
41
|
+
/** See {@link SplatRenderImpl.needsCpuCenters}. False until a renderer has drawn this mesh. */
|
|
42
|
+
get needsCpuCenters(): boolean;
|
|
43
|
+
/** True while the drawn order is stale or absent, including before the first draw. */
|
|
44
|
+
get sortPending(): boolean;
|
|
45
|
+
requestSort(): void;
|
|
46
|
+
syncDefines(): void;
|
|
47
|
+
syncBounds(): void;
|
|
48
|
+
syncBoneData(): void;
|
|
49
|
+
syncVariantGroups(): void;
|
|
50
|
+
onBeforeRender(renderer: unknown, _scene: unknown, camera: Camera): void;
|
|
51
|
+
/**
|
|
52
|
+
* A second mesh drawing the same splat data, at the same transform. The
|
|
53
|
+
* decoded data is shared; the GPU copy of it is not.
|
|
54
|
+
*
|
|
55
|
+
* Only for static splats. A mesh that belongs to a character is driven by
|
|
56
|
+
* that character's rig and cannot stand alone - clone the `SplatCharacter`.
|
|
57
|
+
*/
|
|
58
|
+
clone(): this;
|
|
59
|
+
/**
|
|
60
|
+
* Splats are not pickable. The inherited implementation would test the ray
|
|
61
|
+
* against this mesh's own geometry, which is a single proxy quad at the
|
|
62
|
+
* local origin - every instance is positioned in the shader - and report
|
|
63
|
+
* hits that have nothing to do with where the splats are drawn.
|
|
64
|
+
*/
|
|
65
|
+
raycast(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Release every GPU resource behind this mesh - the splat's data textures as
|
|
68
|
+
* well as the renderer-specific buffers - and remove it from its parent.
|
|
69
|
+
*/
|
|
70
|
+
dispose(): void;
|
|
71
|
+
}
|