mirror of
https://github.com/storytold/spark.git
synced 2026-10-18 07:40:38 +00:00
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
This commit is contained in:
@@ -97,6 +97,13 @@ Creates a new splat with the provided parameters (all values in "float" space, i
|
|||||||
|
|
||||||
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)`.
|
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)`.
|
||||||
|
|
||||||
|
|
||||||
|
## `getBoundingBox(centers_only=true)`
|
||||||
|
|
||||||
|
This method returns a `THREE.Box3` representing the axis-aligned bounding box of all splats in the mesh.
|
||||||
|
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.
|
||||||
|
Note that this function will raise an error if called before splats are initialized.
|
||||||
|
|
||||||
## `updateGenerator()`
|
## `updateGenerator()`
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|||||||
@@ -145,6 +145,7 @@
|
|||||||
reversePointerDir: false,
|
reversePointerDir: false,
|
||||||
reversePointerSlide: false,
|
reversePointerSlide: false,
|
||||||
backgroundColor: "#000000",
|
backgroundColor: "#000000",
|
||||||
|
viewBoundingBox: false,
|
||||||
openFiles: () => {
|
openFiles: () => {
|
||||||
fileInput.click();
|
fileInput.click();
|
||||||
},
|
},
|
||||||
@@ -340,7 +341,7 @@
|
|||||||
|
|
||||||
async function loadFiles(splatFiles) {
|
async function loadFiles(splatFiles) {
|
||||||
if (guiOptions.resetOnLoad) {
|
if (guiOptions.resetOnLoad) {
|
||||||
const toRemove = frame.children.filter((child) => child instanceof SplatMesh);
|
const toRemove = frame.children.filter((child) => child instanceof SplatMesh || child instanceof THREE.Box3Helper);
|
||||||
for (const child of toRemove) {
|
for (const child of toRemove) {
|
||||||
frame.remove(child);
|
frame.remove(child);
|
||||||
}
|
}
|
||||||
@@ -403,6 +404,7 @@
|
|||||||
}
|
}
|
||||||
frame.add(splatMesh);
|
frame.add(splatMesh);
|
||||||
console.log(`Loaded ${fileName} with ${splatMesh.numSplats} splats`);
|
console.log(`Loaded ${fileName} with ${splatMesh.numSplats} splats`);
|
||||||
|
addBoundingBoxHelper(splatMesh);
|
||||||
|
|
||||||
const splatFolder = splatsFolder.addFolder(fileName).close();
|
const splatFolder = splatsFolder.addFolder(fileName).close();
|
||||||
splatFolder.add(splatMesh, "opacity", 0, 1, 0.01).name("Opacity").listen();
|
splatFolder.add(splatMesh, "opacity", 0, 1, 0.01).name("Opacity").listen();
|
||||||
@@ -433,6 +435,14 @@
|
|||||||
canvas.focus();
|
canvas.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function addBoundingBoxHelper(splatMesh) {
|
||||||
|
await splatMesh.initialized;
|
||||||
|
const box = splatMesh.getBoundingBox();
|
||||||
|
const boxHelper = new THREE.Box3Helper(box, 0x00ff00);
|
||||||
|
boxHelper.visible = guiOptions.viewBoundingBox;
|
||||||
|
frame.add(boxHelper);
|
||||||
|
}
|
||||||
|
|
||||||
secondGui.add(guiOptions, "resetOnLoad").name("Reset on load");
|
secondGui.add(guiOptions, "resetOnLoad").name("Reset on load");
|
||||||
secondGui.add(guiOptions, "loadOffset", -2, 2, 0.01).name("Loading offset");
|
secondGui.add(guiOptions, "loadOffset", -2, 2, 0.01).name("Loading offset");
|
||||||
secondGui.add(guiOptions, "openFiles").name("Select Files");
|
secondGui.add(guiOptions, "openFiles").name("Select Files");
|
||||||
@@ -502,6 +512,13 @@
|
|||||||
const debugFolder = gui.addFolder("Debug").close();
|
const debugFolder = gui.addFolder("Debug").close();
|
||||||
const normalColor = dyno.dynoBool(false);
|
const normalColor = dyno.dynoBool(false);
|
||||||
debugFolder.add(normalColor, "value").name("Normal color").onChange(() => updateFrameSplats());
|
debugFolder.add(normalColor, "value").name("Normal color").onChange(() => updateFrameSplats());
|
||||||
|
debugFolder.add(guiOptions, "viewBoundingBox").name("View bounding boxes").onChange((viewBoundingBox) => {
|
||||||
|
frame.children.forEach((child) => {
|
||||||
|
if (child instanceof THREE.Box3Helper) {
|
||||||
|
child.visible = viewBoundingBox;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
debugFolder.add(spark, "maxStdDev", 0.1, 3.0, 0.01).name("Max Gsplat stddev").listen();
|
debugFolder.add(spark, "maxStdDev", 0.1, 3.0, 0.01).name("Max Gsplat stddev").listen();
|
||||||
debugFolder.add(spark, "falloff", 0, 1, 0.01).name("Gaussian falloff").listen();
|
debugFolder.add(spark, "falloff", 0, 1, 0.01).name("Gaussian falloff").listen();
|
||||||
@@ -688,6 +705,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const instructions = makeInstructions();
|
const instructions = makeInstructions();
|
||||||
|
addBoundingBoxHelper(instructions);
|
||||||
frame.add(instructions);
|
frame.add(instructions);
|
||||||
|
|
||||||
// Load URLs from query parameters if any
|
// Load URLs from query parameters if any
|
||||||
|
|||||||
@@ -319,6 +319,53 @@ export class SplatMesh extends SplatGenerator {
|
|||||||
this.packedSplats.dispose();
|
this.packedSplats.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns axis-aligned bounding box of the SplatMesh. If centers_only is true,
|
||||||
|
// only the centers of the splats are used to compute the bounding box.
|
||||||
|
// IMPORTANT: This should only be called after the SplatMesh is initialized.
|
||||||
|
getBoundingBox(centers_only = true) {
|
||||||
|
if (!this.initialized) {
|
||||||
|
throw new Error(
|
||||||
|
"Cannot get bounding box before SplatMesh is initialized",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const minVec = new THREE.Vector3(
|
||||||
|
Number.POSITIVE_INFINITY,
|
||||||
|
Number.POSITIVE_INFINITY,
|
||||||
|
Number.POSITIVE_INFINITY,
|
||||||
|
);
|
||||||
|
const maxVec = new THREE.Vector3(
|
||||||
|
Number.NEGATIVE_INFINITY,
|
||||||
|
Number.NEGATIVE_INFINITY,
|
||||||
|
Number.NEGATIVE_INFINITY,
|
||||||
|
);
|
||||||
|
const corners = new THREE.Vector3();
|
||||||
|
const signs = [-1, 1];
|
||||||
|
this.packedSplats.forEachSplat(
|
||||||
|
(_index, center, scales, quaternion, _opacity, _color) => {
|
||||||
|
if (centers_only) {
|
||||||
|
minVec.min(center);
|
||||||
|
maxVec.max(center);
|
||||||
|
} else {
|
||||||
|
// Get the 8 corners of the AABB in local space
|
||||||
|
for (const x of signs) {
|
||||||
|
for (const y of signs) {
|
||||||
|
for (const z of signs) {
|
||||||
|
corners.set(x * scales.x, y * scales.y, z * scales.z);
|
||||||
|
// Transform corner by rotation and position
|
||||||
|
corners.applyQuaternion(quaternion);
|
||||||
|
corners.add(center);
|
||||||
|
minVec.min(corners);
|
||||||
|
maxVec.max(corners);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const box = new THREE.Box3(minVec, maxVec);
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
constructGenerator(context: SplatMeshContext) {
|
constructGenerator(context: SplatMeshContext) {
|
||||||
const { transform, viewToObject, recolor } = context;
|
const { transform, viewToObject, recolor } = context;
|
||||||
const generator = dynoBlock(
|
const generator = dynoBlock(
|
||||||
|
|||||||
Reference in New Issue
Block a user