Skip to content

Commit 0705207

Browse files
authored
Bounding box helper function (#126)
* bounding box implementation * set default bbox to center-based computation * [bbox] clean up bbox and editor logic * [bbox] add error handling and documentation
1 parent 05ddd83 commit 0705207

3 files changed

Lines changed: 73 additions & 1 deletion

File tree

docs/docs/splat-mesh.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ Creates a new splat with the provided parameters (all values in "float" space, i
9797

9898
This method iterates over all splats in this instance's `packedSplats`, invoking the provided callback with `index: number` in `0..=(this.numSplats-1)`, `center: THREE.Vector3`, `scales: THREE.Vector3`, `quaternion: THREE.Quaternion`, `opacity: number` (0..1), and `color: THREE.Color` (rgb values in 0..1). Note that the objects passed in as `center` etc. are the same for every callback invocation: they are reused for efficiency. *Changing these values has no effect* as they are decoded/unpacked copies of the underlying data. To update the `packedSplats`, call `.packedSplats.setSplat(index, center, scales, quaternion, opacity, color)`.
9999

100+
101+
## `getBoundingBox(centers_only=true)`
102+
103+
This method returns a `THREE.Box3` representing the axis-aligned bounding box of all splats in the mesh.
104+
The parameter `centers_only` (boolean, default: `true`) controls whether we calculate the bounding box using only splat center positions, or include the full extent of each splat by considering their scales and orientations. The latter gives a slightly more accurate but more computationally expensive bounding box.
105+
Note that this function will raise an error if called before splats are initialized.
106+
100107
## `updateGenerator()`
101108

102109
Call this whenever something changes in the splat processing pipeline, for example changing `maxSh` or updating `objectModifier` or `worldModifier`. Compiled generators are cached for efficiency and re-used when the same graph structure emerges after successive changes.

examples/editor/index.html

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
reversePointerDir: false,
146146
reversePointerSlide: false,
147147
backgroundColor: "#000000",
148+
viewBoundingBox: false,
148149
openFiles: () => {
149150
fileInput.click();
150151
},
@@ -340,7 +341,7 @@
340341

341342
async function loadFiles(splatFiles) {
342343
if (guiOptions.resetOnLoad) {
343-
const toRemove = frame.children.filter((child) => child instanceof SplatMesh);
344+
const toRemove = frame.children.filter((child) => child instanceof SplatMesh || child instanceof THREE.Box3Helper);
344345
for (const child of toRemove) {
345346
frame.remove(child);
346347
}
@@ -403,6 +404,7 @@
403404
}
404405
frame.add(splatMesh);
405406
console.log(`Loaded ${fileName} with ${splatMesh.numSplats} splats`);
407+
addBoundingBoxHelper(splatMesh);
406408

407409
const splatFolder = splatsFolder.addFolder(fileName).close();
408410
splatFolder.add(splatMesh, "opacity", 0, 1, 0.01).name("Opacity").listen();
@@ -433,6 +435,14 @@
433435
canvas.focus();
434436
}
435437

438+
async function addBoundingBoxHelper(splatMesh) {
439+
await splatMesh.initialized;
440+
const box = splatMesh.getBoundingBox();
441+
const boxHelper = new THREE.Box3Helper(box, 0x00ff00);
442+
boxHelper.visible = guiOptions.viewBoundingBox;
443+
frame.add(boxHelper);
444+
}
445+
436446
secondGui.add(guiOptions, "resetOnLoad").name("Reset on load");
437447
secondGui.add(guiOptions, "loadOffset", -2, 2, 0.01).name("Loading offset");
438448
secondGui.add(guiOptions, "openFiles").name("Select Files");
@@ -502,6 +512,13 @@
502512
const debugFolder = gui.addFolder("Debug").close();
503513
const normalColor = dyno.dynoBool(false);
504514
debugFolder.add(normalColor, "value").name("Normal color").onChange(() => updateFrameSplats());
515+
debugFolder.add(guiOptions, "viewBoundingBox").name("View bounding boxes").onChange((viewBoundingBox) => {
516+
frame.children.forEach((child) => {
517+
if (child instanceof THREE.Box3Helper) {
518+
child.visible = viewBoundingBox;
519+
}
520+
});
521+
});
505522

506523
debugFolder.add(spark, "maxStdDev", 0.1, 3.0, 0.01).name("Max Gsplat stddev").listen();
507524
debugFolder.add(spark, "falloff", 0, 1, 0.01).name("Gaussian falloff").listen();
@@ -688,6 +705,7 @@
688705
}
689706

690707
const instructions = makeInstructions();
708+
addBoundingBoxHelper(instructions);
691709
frame.add(instructions);
692710

693711
// Load URLs from query parameters if any

src/SplatMesh.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,53 @@ export class SplatMesh extends SplatGenerator {
319319
this.packedSplats.dispose();
320320
}
321321

322+
// Returns axis-aligned bounding box of the SplatMesh. If centers_only is true,
323+
// only the centers of the splats are used to compute the bounding box.
324+
// IMPORTANT: This should only be called after the SplatMesh is initialized.
325+
getBoundingBox(centers_only = true) {
326+
if (!this.initialized) {
327+
throw new Error(
328+
"Cannot get bounding box before SplatMesh is initialized",
329+
);
330+
}
331+
const minVec = new THREE.Vector3(
332+
Number.POSITIVE_INFINITY,
333+
Number.POSITIVE_INFINITY,
334+
Number.POSITIVE_INFINITY,
335+
);
336+
const maxVec = new THREE.Vector3(
337+
Number.NEGATIVE_INFINITY,
338+
Number.NEGATIVE_INFINITY,
339+
Number.NEGATIVE_INFINITY,
340+
);
341+
const corners = new THREE.Vector3();
342+
const signs = [-1, 1];
343+
this.packedSplats.forEachSplat(
344+
(_index, center, scales, quaternion, _opacity, _color) => {
345+
if (centers_only) {
346+
minVec.min(center);
347+
maxVec.max(center);
348+
} else {
349+
// Get the 8 corners of the AABB in local space
350+
for (const x of signs) {
351+
for (const y of signs) {
352+
for (const z of signs) {
353+
corners.set(x * scales.x, y * scales.y, z * scales.z);
354+
// Transform corner by rotation and position
355+
corners.applyQuaternion(quaternion);
356+
corners.add(center);
357+
minVec.min(corners);
358+
maxVec.max(corners);
359+
}
360+
}
361+
}
362+
}
363+
},
364+
);
365+
const box = new THREE.Box3(minVec, maxVec);
366+
return box;
367+
}
368+
322369
constructGenerator(context: SplatMeshContext) {
323370
const { transform, viewToObject, recolor } = context;
324371
const generator = dynoBlock(

0 commit comments

Comments
 (0)