SplatCollection.ts
src/render/SplatCollection.ts · raw · all examples · site home
Exports: SplatObjectAnySplatObjectMappedObjectSplatCollection
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
import * as THREE from "three";
import { Splats, type BundleLike } from "../core/splats";
import type { GpuArrays } from "../gpu/GpuArrays";
import { SplatGenerator, type AnySplatGenerator, type StoredSplats } from "../gpu/splatGenerator";
import type { SplatObjectParams } from "./splatParams";
// One object of a collection: `count` splats of a generator, placed in the
// world by `matrixWorld`. A collection locks generator, data and count on
// the object itself; matrixWorld, params and release can still be reassigned.
export interface SplatObject<Data = unknown> {
readonly generator: SplatGenerator<Data>;
// What generator.bind gets before the object's dispatch ({ splats,
// indices? } for fromSplats); nothing is bound when undefined
readonly data?: Data;
readonly count: number;
// The object's world transform (object to world), read every render: a
// matrix (an Object3D's matrixWorld, say) or a function returning one;
// default the identity. Rotations times a uniform scale take the cheap
// path; any other matrix goes through covariances.
matrixWorld?: THREE.Matrix4 | (() => THREE.Matrix4);
// Over the view's and renderer's objectParams, read every render
params?: Partial<SplatObjectParams>;
// Runs once this object is in no live collection of any SplatRenderer
// (none draws it, sorts it or may still apply an order for it): free or
// pool what the object owns. Use one object wherever it is shared.
release?: () => void;
}
// An object of any data
export type AnySplatObject = Omit<SplatObject, "generator" | "data"> & { readonly generator: AnySplatGenerator; readonly data?: unknown };
// An object of a collection, at its offset in the projected splats
export interface MappedObject {
readonly object: AnySplatObject;
readonly offset: number;
}
const IDENTITY = new THREE.Matrix4();
const LOCKED = ["generator", "data", "count"] as const;
// Which splats to draw: an immutable, ordered collection of objects
// (generator, data and count locked; matrixWorld and params stay live). Its
// identity is its version: change which objects it holds or how many
// splats, and make a new collection. The order of objects matters:
// projected splat i of the collection is splat i - offset of the object
// holding it, which is what a CPU sort's order indexes.
export class SplatCollection {
static readonly EMPTY = new SplatCollection([]);
readonly objects: readonly MappedObject[];
// Splats in all
readonly count: number;
constructor(objects: readonly AnySplatObject[]) {
let offset = 0;
this.objects = Object.freeze(objects.map((object) => {
for (const key of LOCKED) {
Object.defineProperty(object, key, { value: object[key], writable: false, configurable: false, enumerable: true });
}
const mapped = Object.freeze({ object, offset });
offset += object.count;
return mapped;
}));
this.count = offset;
}
// An object whose data is checked against its generator's
static object<Data>(object: SplatObject<Data>): SplatObject<Data> {
return object;
}
// An object for splat arrays (all of them unless `count` says
// otherwise), drawn with the spherical harmonics they hold (per splat, or
// through `shPalette`; `sh` caps the degree, 0 or false: none): one shared
// generator per kind (SplatGenerator.forArrays), so arrays of the same
// kind run one program
static arrays(arrays: GpuArrays, { sh, shPalette, ...object }: Partial<Omit<SplatObject, "generator" | "data">> & {
sh?: number | false;
shPalette?: GpuArrays;
} = {}): SplatObject<StoredSplats> {
if (!Splats.findBundle(arrays.bundles as Record<string, BundleLike>)) {
throw new Error("SplatCollection.arrays: no splat bundle in these arrays");
}
const generator = SplatGenerator.forArrays(arrays, { sh, shPalette });
return { count: arrays.count, ...object, generator, data: { splats: arrays, shPalette } };
}
// An object's world transform this render, or the identity
static matrixWorldOf(object: AnySplatObject): THREE.Matrix4 {
return (typeof object.matrixWorld === "function" ? object.matrixWorld() : object.matrixWorld) ?? IDENTITY;
}
}