diff --git a/src/SplatMesh.ts b/src/SplatMesh.ts index 5a033fab..1e288e6b 100644 --- a/src/SplatMesh.ts +++ b/src/SplatMesh.ts @@ -182,6 +182,17 @@ export interface SplatSource { }: { index: DynoVal<"int">; viewOrigin?: DynoVal<"vec3"> }): DynoVal< typeof Gsplat >; + + forEachSplat( + callback: ( + index: number, + center: THREE.Vector3, + scales: THREE.Vector3, + quaternion: THREE.Quaternion, + opacity: number, + color: THREE.Color, + ) => void, + ): void; } export class EmptySplatSource implements SplatSource { @@ -213,6 +224,8 @@ export class EmptySplatSource implements SplatSource { fetchSplat({ index }: { index: DynoVal<"int"> }): DynoVal { return this.fetchDyno; } + + forEachSplat() {} } export class SplatMesh extends SplatGenerator { @@ -546,11 +559,7 @@ export class SplatMesh extends SplatGenerator { color: THREE.Color, ) => void, ) { - if (this.packedSplats) { - this.packedSplats.forEachSplat(callback); - } else if (this.extSplats) { - this.extSplats.forEachSplat(callback); - } + this.splats?.forEachSplat(callback); } // Call this when you are finished with the SplatMesh and want to free @@ -583,9 +592,6 @@ export class SplatMesh extends SplatGenerator { "Cannot get bounding box before SplatMesh is initialized", ); } - if (!this.packedSplats && !this.extSplats) { - throw new Error("Bounding box requires PackedSplats or ExtSplats"); - } const minVec = new THREE.Vector3( Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, @@ -627,11 +633,7 @@ export class SplatMesh extends SplatGenerator { } } - if (this.packedSplats) { - this.packedSplats.forEachSplat(callback); - } else if (this.extSplats) { - this.extSplats.forEachSplat(callback); - } + this.splats?.forEachSplat(callback); const box = new THREE.Box3(minVec, maxVec); return box; } diff --git a/src/SplatPager.ts b/src/SplatPager.ts index 58af2f6d..4b619932 100644 --- a/src/SplatPager.ts +++ b/src/SplatPager.ts @@ -16,7 +16,7 @@ import { SplatFileType, } from "./defines"; import { pagedSplatTexCoord } from "./dyno"; -import { getTextureSize } from "./utils"; +import { decodeExtSplat, getTextureSize, unpackSplat } from "./utils"; export interface PagedSplatsOptions { pager?: SplatPager; @@ -430,6 +430,48 @@ export class PagedSplats implements SplatSource { } return this.pager.readSplatExt.apply({ index: splatIndex }).gsplat; } + + // Iterate over Gsplats index 0..=(this.numSplats-1), unpack each Gsplat + // and invoke the callback function with the Gsplat attributes. + forEachSplat( + callback: ( + index: number, + center: THREE.Vector3, + scales: THREE.Vector3, + quaternion: THREE.Quaternion, + opacity: number, + color: THREE.Color, + ) => void, + ) { + if (!this.pager || !this.numSplats) { + return; + } + const extSplats = this.pager.extSplats; + const indices = this.dynoIndices.value.image.data as Uint32Array; + const packedSplatArray = this.pager.packedTexture.value.image + .data as Uint32Array; + const extPackedSplatArray = this.pager.extTexture.value.image + .data as Uint32Array; + const extArrays: [Uint32Array, Uint32Array] = [ + packedSplatArray, + extPackedSplatArray, + ]; + + for (let i = 0; i < this.numSplats; ++i) { + const splatIndex = indices[i]; + const unpacked = extSplats + ? decodeExtSplat(extArrays, splatIndex) + : unpackSplat(packedSplatArray, splatIndex, this.splatEncoding); + callback( + i, + unpacked.center, + unpacked.scales, + unpacked.quaternion, + unpacked.opacity, + unpacked.color, + ); + } + } } export interface SplatPagerOptions {