Skip to content

@aosengine/splat

Classes

AnimatedSplat

A dynamic splat: SplatSink plus lifecycle.

Implements

Constructors

Constructor
ts
new AnimatedSplat(renderer, options): AnimatedSplat;

Use createAnimatedSplat; the GPU buffers must be acquired before the object is usable, and that needs an initialised renderer.

Parameters
ParameterTypeDescription
rendererWebGPURendererAn initialised WebGPURenderer on the WebGPU backend.
optionsCreateAnimatedSplatOptionsCapacity, bounds and draw order.
Returns

AnimatedSplat

Properties

buffers
ts
readonly buffers: SplatGPUBuffers;

The four GPU buffers, in the layout documented in backendBuffers.ts.

Implementation of

SplatSink.buffers

capacity
ts
readonly capacity: number;

Total gaussians this sink can hold. Fixed for its lifetime.

Implementation of

SplatSink.capacity

splat
ts
readonly splat: AnimatedGaussianSplat;

The fork instance. Typed as the fork, not as Object3D, for object3D consumers.

Accessors

available
Get Signature
ts
get available(): number;

Slots not handed out. Named available, because free is the method above.

Returns

number

The count.

object3D
Get Signature
ts
get object3D(): Object3D;

The scene object.

Returns

Object3D

The fork instance, which is an ordinary Object3D.

The scene object. Add it to a scene, move it, parent it — it is an ordinary Object3D.

Implementation of

SplatSink.object3D

used
Get Signature
ts
get used(): number;

Slots currently handed out.

Returns

number

The count.

Methods

allocate()
ts
allocate(count): SlotRange;

Reserve a contiguous run of slots.

Parameters
ParameterTypeDescription
countnumberHow many gaussians.
Returns

SlotRange

The range.

Implementation of

SplatSink.allocate

clearSlots()
ts
clearSlots(range?): void;

Zero a slot range, which makes it invisible: a colour word of 0 is alpha 0.

This is a queue.writeBuffer of zeros, which is allowed here precisely because it is not a per-frame operation — it runs when a branch is allocated or retired. A producer must never upload gaussian data this way; that is what the compute pass is for.

Parameters
ParameterTypeDescription
range?SlotRangeThe range to clear. Defaults to the whole capacity.
Returns

void

Nothing.

dispose()
ts
dispose(): void;

Detach from the scene and release the geometry, material and storage buffers.

Returns

void

Nothing.

free()
ts
free(range): void;

Give a range back.

Parameters
ParameterTypeDescription
rangeSlotRangeA range from AnimatedSplat.allocate.
Returns

void

Nothing.

Implementation of

SplatSink.free

markGaussiansChanged()
ts
markGaussiansChanged(): void;

Force a re-sort on the next frame.

Returns

void

Nothing.

Implementation of

SplatSink.markGaussiansChanged

setBoundingSphere()
ts
setBoundingSphere(center, radius): void;

Declare where the gaussians are, in local space.

Parameters
ParameterTypeDescription
centerVec3TupleLocal-space centre.
radiusnumberLocal-space radius.
Returns

void

Nothing.

Implementation of

SplatSink.setBoundingSphere


SlotAllocationError

An allocation request that cannot be satisfied.

Extends

  • Error

Constructors

Constructor
ts
new SlotAllocationError(
   message, 
   requested, 
   largestFree
): SlotAllocationError;

Build a slot allocation error.

Parameters
ParameterTypeDescription
messagestringWhat went wrong.
requestednumberSlots requested.
largestFreenumberLargest free run at the time.
Returns

SlotAllocationError

Overrides
ts
Error.constructor

Properties

largestFree
ts
readonly largestFree: number;

The largest run available at the time.

requested
ts
readonly requested: number;

Slots that were asked for.


SplatLoadError

A splat file that could not be decoded.

Extends

  • Error

Constructors

Constructor
ts
new SplatLoadError(
   url, 
   message, 
   options?
): SplatLoadError;

Build a splat load error.

Parameters
ParameterTypeDescription
urlstringThe URL that failed.
messagestringWhat went wrong.
options?ErrorOptionsStandard Error options, used to keep the cause.
Returns

SplatLoadError

Overrides
ts
Error.constructor

Properties

url
ts
readonly url: string;

The URL that failed.

Interfaces

CreateAnimatedSplatOptions

Options accepted by createAnimatedSplat.

Properties

autoSort?
ts
readonly optional autoSort?: boolean;

Sort in onBeforeRender. Defaults to true, which is what a producer wants: paired with markGaussiansChanged it gives exactly one sort per frame.

boundingSphere?
ts
readonly optional boundingSphere?: object;

Where the gaussians will be, in local space. Defaults to a unit sphere at the origin, which is almost certainly wrong; set it as soon as the producer knows.

center
ts
readonly center: Vec3Tuple;
radius
ts
readonly radius: number;
capacity
ts
readonly capacity: number;

Gaussians to allocate. Fixed for the object's lifetime.

renderOrder?
ts
readonly optional renderOrder?: number;

Draw order. Defaults to SPLAT_RENDER_ORDER.


CreateSplatObjectOptions

Options accepted by createSplatObject.

Properties

autoSort?
ts
readonly optional autoSort?: boolean;

Re-sort in onBeforeRender when the camera turns far enough. Defaults to true. Turn it off only if you drive updateSort yourself.

renderOrder?
ts
readonly optional renderOrder?: number;

Draw order. Defaults to SPLAT_RENDER_ORDER.


GltfLoaderLike

The subset of GLTFLoader this package needs, so the loader itself is not imported.

Methods

register()
ts
register(callback): unknown;

three's plugin hook.

Parameters
ParameterTypeDescription
callback(parser) => unknownBuilds the plugin from the parser.
Returns

unknown

The loader, for chaining.


LoadSplatOptions

Options accepted by loadSplat.

Properties

fetch?
ts
readonly optional fetch?: {
  (input, init?): Promise<Response>;
  (input, init?): Promise<Response>;
};

Replaces globalThis.fetch, for tests and for an asset-manager transport.

Call Signature
ts
(input, init?): Promise<Response>;

MDN Reference

Parameters
ParameterType
inputURL | RequestInfo
init?RequestInit
Returns

Promise<Response>

Call Signature
ts
(input, init?): Promise<Response>;

MDN Reference

Parameters
ParameterType
inputstring | URL | Request
init?RequestInit
Returns

Promise<Response>

format?
ts
readonly optional format?: SplatFormatOption;

Force a decoder instead of sniffing. Defaults to 'auto'.

signal?
ts
readonly optional signal?: AbortSignal;

Aborts the fetch. The decode itself is synchronous and is not interruptible.


SlotAllocator

Hands out and reclaims slot ranges inside a fixed capacity.

Properties

available
ts
readonly available: number;

Slots not handed out. Equals capacity - used.

Named available rather than free because free is the method next to it, and a property cannot be both.

capacity
ts
readonly capacity: number;

Total slots.

freeRuns
ts
readonly freeRuns: readonly SlotRange[];

The free runs, low offset first. Exposed for tests and for the debug overlay.

largestFree
ts
readonly largestFree: number;

The largest range allocate could satisfy right now.

used
ts
readonly used: number;

Slots currently handed out.

Methods

allocate()
ts
allocate(count): SlotRange;

Take the first free run large enough.

Parameters
ParameterTypeDescription
countnumberHow many slots. Must be a positive integer.
Returns

SlotRange

The range.

Throws

When nothing is large enough, or RangeError when count is not a positive integer.

free()
ts
free(range): void;

Give a range back, coalescing with either neighbour.

Parameters
ParameterTypeDescription
rangeSlotRangeA range from allocate.
Returns

void

Nothing.

Throws

When the range was not allocated, or was already freed.

reset()
ts
reset(): void;

Drop every allocation and return to one free run.

Returns

void

Nothing.


SlotRange

A contiguous run of gaussian slots.

Properties

count
ts
readonly count: number;

How many slots. Always at least 1.

offset
ts
readonly offset: number;

Index of the first slot.


SplatAsset

A decoded splat file.

Properties

boundingSphere
ts
readonly boundingSphere: Sphere;

Bounds of the centres, in the file's own coordinate system.

count
ts
readonly count: number;

Gaussians in the file.

format
ts
readonly format: SplatFormat;

Which decoder ran.

geometry
ts
readonly geometry: BufferGeometry;

Geometry with position (f32x3), covariance (f32x6), color (u8x4) and optional SH.

shDegree
ts
readonly shDegree: number;

Spherical-harmonics degree, 0 to 3. Degree 0 is flat colour.

url
ts
readonly url: string;

Where it came from.


SplatGPUBuffers

The four storage buffers behind a GaussianSplat, as real GPUBuffers.

Properties

center
ts
readonly center: GPUBuffer;

array<vec4<f32>>: xyz = centre in local space, w unused.

color
ts
readonly color: GPUBuffer;

array<u32>: pack4x8unorm(vec4(r, g, b, a)).

covarianceA
ts
readonly covarianceA: GPUBuffer;

array<vec4<f32>>: (c00, c01, c02, c11).

covarianceB
ts
readonly covarianceB: GPUBuffer;

array<vec4<f32>>: (c12, c22, 0, 0).


SplatModuleOptions

Options accepted by splat.

Properties

format?
ts
readonly optional format?: SplatFormat;

Force a decoder for every splat asset instead of sniffing each one. Leave unset unless your assets are served from URLs with no useful extension.


SplatService

What engine.get('splat') returns.

Properties

loaded
ts
readonly loaded: ReadonlyMap<string, SplatAsset>;

Every splat asset loaded through the registry, by asset id.

Assets are cached by the registry, so this is a view of it, not a second cache.

Methods

add()
ts
add(id): object;

Add a loaded asset to the scene as a GaussianSplat, with the right draw order.

Parameters
ParameterTypeDescription
idstringAsset id, as declared in assets.json.
Returns

object

The object that was added.


SplatSink

What a producer writes into.

Deliberately small and three-free: a lift shader needs slots, buffers and a way to say the gaussians moved, and nothing else.

Properties

buffers
ts
readonly buffers: SplatGPUBuffers;

The four GPU buffers, in the layout documented in backendBuffers.ts.

capacity
ts
readonly capacity: number;

Total gaussians this sink can hold. Fixed for its lifetime.

object3D
ts
readonly object3D: Object3D;

The scene object. Add it to a scene, move it, parent it — it is an ordinary Object3D.

Methods

allocate()
ts
allocate(count): SlotRange;

Reserve a contiguous run of slots.

Parameters
ParameterTypeDescription
countnumberHow many gaussians.
Returns

SlotRange

The range, as { offset, count }.

free()
ts
free(range): void;

Give a range back. The slots are not cleared; call clearSlots first if the caller is not about to overwrite them.

Parameters
ParameterTypeDescription
rangeSlotRangeA range from allocate.
Returns

void

Nothing.

markGaussiansChanged()
ts
markGaussiansChanged(): void;

Tell the splat its gaussians moved, so the next frame re-sorts.

Call it once per frame after the compute pass. Without it the sort only runs when the camera turns more than about 1.81 degrees, and a moving avatar seen from a still camera renders in a stale depth order.

Returns

void

Nothing.

setBoundingSphere()
ts
setBoundingSphere(center, radius): void;

Declare where the gaussians are, in the object's local space.

The sort quantises depth into 4096 bins across this sphere, so a sphere that does not contain the splats costs precision, and frustum culling is off precisely because this value is a promise rather than a measurement.

Parameters
ParameterTypeDescription
centerVec3TupleLocal-space centre.
radiusnumberLocal-space radius.
Returns

void

Nothing.

Type Aliases

SplatFormat

ts
type SplatFormat = "spz" | "ply" | "splat" | "ksplat";

Container formats loadSplat understands.


SplatFormatOption

ts
type SplatFormatOption = SplatFormat | "auto";

SplatFormat, or 'auto' to sniff.


Vec3Tuple

ts
type Vec3Tuple = [number, number, number];

A centre as a plain triple, so producers need no three import.

Variables

BYTES_PER_GAUSSIAN

ts
const BYTES_PER_GAUSSIAN: number;

Bytes one gaussian occupies across the four storage buffers.


PACKAGE

ts
const PACKAGE: "@aosengine/splat";

Package identity marker.

Example

ts
import { PACKAGE } from '@aosengine/splat';

console.log(PACKAGE); // '@aosengine/splat'

SPLAT_RENDER_ORDER

ts
const SPLAT_RENDER_ORDER: 1000 = 1000;

Splats draw after opaque geometry.

The material is transparent with depthWrite: false, so three already puts it in the transparent pass; the render order pins splats after ordinary transparent meshes, because a splat cloud has no single depth to sort by and would otherwise be interleaved by its object centre.


SUPPORTED_THREE_VERSION

ts
const SUPPORTED_THREE_VERSION: "0.186.0" = '0.186.0';

The three version this file's private-surface knowledge was written against.

Functions

acquireSplatGPUBuffers()

ts
function acquireSplatGPUBuffers(renderer, splat): SplatGPUBuffers;

Materialise a splat's four storage attributes as real GPUBuffers and hand them back.

Idempotent in both directions: createStorageAttribute early-returns when the buffer already exists, and the same GPUBuffer objects come back on every call for the lifetime of the splat. The buffers are owned by three — do not destroy them; dispose the splat.

Parameters

ParameterTypeDescription
rendererWebGPURendererAn initialised WebGPURenderer.
splatobjectA GaussianSplat or AnimatedGaussianSplat.

Returns

SplatGPUBuffers

The four buffers, in the layout documented at the top of this file.

Throws

When the backend is WebGL, the renderer is not initialised, or three's internals moved.

Example

ts
const buffers = acquireSplatGPUBuffers(renderer, splat);
console.log(buffers.center.size / 16); // gaussian capacity

createAnimatedSplat()

ts
function createAnimatedSplat(renderer, options): Promise<AnimatedSplat>;

Build a dynamic splat on an initialised renderer.

Asynchronous by contract rather than by need: acquiring the buffers is synchronous today, but the call sits on the boundary where a backend could have to be asked for something, and every caller already awaits.

Parameters

ParameterTypeDescription
rendererWebGPURendererAn initialised WebGPURenderer on the WebGPU backend.
optionsCreateAnimatedSplatOptionsCapacity, bounds and draw order.

Returns

Promise<AnimatedSplat>

The sink, with its GPU buffers already acquired.

Throws

When the renderer is not initialised or fell back to WebGL.

Example

ts
import { createAnimatedSplat } from '@aosengine/splat';

const sink = await createAnimatedSplat(renderer, {
  capacity: 250_000,
  boundingSphere: { center: [0, 1, 0], radius: 1.4 },
});
scene.add(sink.object3D);

const head = sink.allocate(120_000);
// ... compute pass writes sink.buffers.* over [head.offset, head.offset + head.count)
sink.markGaussiansChanged();

createSlotAllocator()

ts
function createSlotAllocator(capacity): SlotAllocator;

Build a slot allocator over capacity slots.

Parameters

ParameterTypeDescription
capacitynumberTotal slots. Must be a positive integer.

Returns

SlotAllocator

An allocator with one free run covering everything.

Example

ts
import { createSlotAllocator } from '@aosengine/splat';

const slots = createSlotAllocator(1000);
const head = slots.allocate(400);
console.log(head.offset, head.count); // 0 400
slots.free(head);
console.log(slots.available); // 1000

createSplatObject()

ts
function createSplatObject(asset, options?): GaussianSplat;

Turn a decoded asset into a scene object.

Uses three's own GaussianSplat — the fork exists for the dynamic path and would be the wrong tool here, because a static splat wants exactly the one-time repack the fork skips.

Parameters

ParameterTypeDescription
assetSplatAssetA decoded asset.
optionsCreateSplatObjectOptionsSort behaviour and draw order.

Returns

GaussianSplat

The object, with its bounds computed and its render order set.

Example

ts
const splat = createSplatObject(asset, { autoSort: true });
splat.position.y = -1;
scene.add(splat);

getGPUDevice()

ts
function getGPUDevice(renderer): GPUDevice;

The one GPUDevice in the process — the renderer's.

AGENTS.md hard rule 7: nothing calls navigator.gpu.requestAdapter(). The device only exists after await renderer.init(), and the backend object is replaced when the renderer falls back to WebGL, so never cache either across init.

Parameters

ParameterTypeDescription
rendererWebGPURendererAn initialised WebGPURenderer.

Returns

GPUDevice

The device the renderer owns.

Throws

When the renderer is not initialised, or fell back to WebGL.

Example

ts
await renderer.init();
const device = getGPUDevice(renderer);
device.createShaderModule({ code });

isWebGPUBackend()

ts
function isWebGPUBackend(renderer): boolean;

Whether this renderer got a real WebGPU backend rather than the WebGL fallback.

Parameters

ParameterTypeDescription
rendererWebGPURendererA renderer, initialised or not.

Returns

boolean

True for the WebGPU backend.

Example

ts
await renderer.init();
if (!isWebGPUBackend(renderer)) console.warn('static splats only');

loadSplat()

ts
function loadSplat(url, options?): Promise<SplatAsset>;

Fetch and decode a splat file.

Parameters

ParameterTypeDescription
urlstringSPZ, PLY, SPLAT or KSPLAT.
optionsLoadSplatOptionsFormat override, abort signal, custom fetch.

Returns

Promise<SplatAsset>

The decoded asset.

Throws

On a non-2xx response, an unrecognised format or a decoder failure.

Example

ts
import { createSplatObject, loadSplat } from '@aosengine/splat';

const asset = await loadSplat('/models/arena.spz');
scene.add(createSplatObject(asset));

parseSplat()

ts
function parseSplat(
   buffer, 
   url, 
   format?
): Promise<SplatAsset>;

Decode a buffer that has already been fetched.

Separated from loadSplat so tests can drive every decoder from memory, and so an asset pipeline that already holds the bytes does not fetch twice.

Parameters

ParameterTypeDefault valueDescription
bufferArrayBufferundefinedThe file.
urlstringundefinedWhere it came from, for sniffing and error messages.
formatSplatFormatOption'auto'Decoder to use, or 'auto'.

Returns

Promise<SplatAsset>

The decoded asset.

Example

ts
import { parseSplat } from '@aosengine/splat';

const asset = await parseSplat(bytes, 'arena.spz');
console.log(asset.count, asset.shDegree);

registerGltfSplatExtension()

ts
function registerGltfSplatExtension(gltfLoader): void;

Teach a GLTFLoader to read KHR_gaussian_splatting.

Register it before the first load; meshes carrying the extension then come back as GaussianSplat nodes inside the glTF scene graph. Their render order is not set for you, because the glTF author owns that graph — walk the result and set SPLAT_RENDER_ORDER if splats are interleaving with your transparent meshes.

Parameters

ParameterTypeDescription
gltfLoaderGltfLoaderLikeThe loader to extend.

Returns

void

Nothing.

Example

ts
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { registerGltfSplatExtension } from '@aosengine/splat';

const loader = new GLTFLoader();
registerGltfSplatExtension(loader);
const gltf = await loader.loadAsync('/models/scene.glb');

sniffSplatFormat()

ts
function sniffSplatFormat(url, bytes): SplatFormat;

Guess the container format from the URL and the first bytes.

The extension wins when it is one we know, because .ksplat has no magic number and .splat has no header at all. Content sniffing is the fallback, and it can only recognise the two formats that are self-describing: SPZ (gzip, or NGSP for v4) and PLY (ply\n).

Parameters

ParameterTypeDescription
urlstringThe URL the bytes came from. Query and hash are ignored.
bytesUint8ArrayAt least the first 8 bytes of the file.

Returns

SplatFormat

The format.

Throws

When neither the extension nor the content identifies it.

Example

ts
import { sniffSplatFormat } from '@aosengine/splat';

sniffSplatFormat('/models/arena.spz', new Uint8Array(8)); // 'spz'

splat()

ts
function splat(options?): EngineModule;

The splat EngineModule.

Registers the splat asset type, so { "id": "arena", "type": "splat", "src": "arena.spz" } in assets.json resolves to a SplatAsset, and publishes a small service for putting one in the scene.

Parameters

ParameterTypeDescription
optionsSplatModuleOptionsOptional format override.

Returns

EngineModule

The module, to be passed in createEngine({ modules }).

Example

ts
import { createEngine } from '@aosengine/core';
import { splat } from '@aosengine/splat';

const engine = await createEngine({ canvas, manifest: '/assets.json', modules: [splat()] });
engine.get('splat').add('arena');
engine.start();