Bounding box for all child meshes in gltf model are the same as the model group

I’d like to get the bounding box and ultimately the center of each child mesh in a gltf model file. I’ve used various code samples to find the computed bounding box of each child mesh in the gltf model but they all return the bounding box of the whole model group. I’ve attached a simple .gltf file and an image of the resulting model view in the browser.

One code block came from
[Bounding box is calculated wrong?]

Here are the variations I used but they all produce the same bounding box dimensions.

scene.add(model);

const helper = new THREE.BoxHelper(model);
helper.geometry.computeBoundingBox();
console.log(helper.geometry.boundingBox);

const helper2 = new THREE.BoxHelper(model.children[0].children[0]);
helper2.geometry.computeBoundingBox();
console.log(helper2.geometry.boundingBox);

const helper3 = new THREE.BoxHelper(model.children[0].children[1]);
helper3.geometry.computeBoundingBox();
console.log(helper3.geometry.boundingBox);

model.children[0].children[0].geometry.computeBoundingBox();
console.log(model.children[0].children[0].geometry.boundingBox);

model.children[0].children[1].geometry.computeBoundingBox();
console.log(model.children[0].children[1].geometry.boundingBox);

console.log(new THREE.Box3().setFromObject(model.children[0].children[0]));

// discourse.threejs.org/t/bounding-box-is-calculated-wrong/1763/5
var boundingBox = new THREE.Box3();
var mesh = model.children[0].children[1];
boundingBox.copy(mesh.geometry.boundingBox);
mesh.updateMatrixWorld(true); // Ensure world matrix is up to date
boundingBox.applyMatrix4(mesh.matrixWorld);
console.log(boundingBox);

Mugen87 made the comment “Be aware that geometries can be shared across meshes. So the behavior of the library is actually correct.” I don’t know if the comment applies here.

How can I retrieve the unique bounding box – and center – of each child BufferGeometry in a gltf model?

Blocks.gltf (5.7 KB)

The problem is that your geometry data are organized in a somewhat unusual way. Both meshes have a unique geometry object but share the same position and normal buffer attribute. They just have different indices in order to refer to different vertex data in these buffers.

The bounding volume computation of three.js does not support this kind of setup (meaning combined geometry data in a single buffer attribute but with different indices).

It seems you have created Blocks.gltf in Sketchup, right? It’s a bit unfortunate that the respective glTF exporter creates assets like this. Is there maybe an export option that avoids the merge of geometry data or something similar?

Thanks for the helpful information.

Both meshes have a unique geometry object but share the same position and normal buffer attribute.

While debugging I saw that the position and normal buffer attributes had the same data but I didn’t understand why. Thanks for explaining that the ‘indices’ property points to different vertex data in the position and normal buffers.

It seems you have created Blocks.gltf in Sketchup, right?

Yes, you’re correct. The ‘blocks.gltf’ was generated by the ‘Khronos glTF’ extension in the SketchUp Warehouse.

Is there maybe an export option that avoids the merge of geometry data or something similar?

Currently, there are two glTF export extensions in the SketchUp Warehouse. The .gltf file generated by the other export extension (‘SimLab GLTF Exporter’) is more verbose but separates geometry data. The bounding box of each mesh is generated correctly.

The problem is that your geometry data are organized in an unusual way.

The format of the glTF file generated by the ‘Khronos glTF’ extension looks similar to section samples listed in the ‘glTF Specification 2.0’ document. See the ‘Meshes’ section. Under the ‘Meshes’ section it says “…indices are defined as references to accessors containing corresponding data.”

Should the bounding volume computation of three.js support this setup given that the ‘Khronos glTF’ generated format is supported by the glTF 2.0 specification?

Rather than the bounding volume computation supporting the combined geometry data shouldn’t the position and normal buffer attributes support the combined geometry data in a single buffer attribute but with different indices?

That is, if three.js loads a glTF file and sees that a mesh has an indices property then three.js should generate the position and normal buffer attributes with the vertex data referred to by the indices value.

Once the position buffer attribute contains the proper geometry data the current bounding volume computation function will give the correct bounding box.

The conversion to and non-indexed BufferGeometry is not always wanted since the usage of indices can lead to smaller buffer sizes. Hence, an automatic rollout before rendering will not be implemented.

You can try to call BufferGeometry.toNonIndexed() by yourself and replace the existing geometries. This should be a valid workaround for now.

I think you are right and it’s probably best to create an issue at github for this.

@donmccurdy What is your opinion about this topic?

I would expect – at least intuitively – that having fewer WebGL buffers is more efficient to render (independent of memory), and that this export practice gives somewhat better runtime performance. That’s an untested assumption, though. I’m not familiar yet with how threejs translates BufferAttributes into underlying WebGL buffers and manages state changes.

That said, it is very limited optimization, compared to more ambitious interleaved buffer setups, which glTF also supports but threejs does not.

Supporting this in computeBoundingBox() looks like an easy change, but would add a little overhead. I think I would support that change. Box3.setFromBufferAttribute() might change from:

// before
box.setFromBufferAttribute( attribute )

// after
box.setFromBufferAttribute( position, index /* optional */ )

Another option would be to create a function that can merge geometries that differ only in their indices and materials, creating a utility function that accepts a subtree as input, then flattens it by creating grouped BufferGeometries were possible:

// Before:
// 
// - Object3D
//   - Mesh
//   - Mesh
//   - Mesh
THREE.BufferGeometryUtils.flatten( object );

// After
//
// - Object3D
//   - Mesh (w/ mesh.geometry.groups.length === 3)

That is not really an optimization, aside from simplifying the scene graph, but can improve developer ergonomics by making the threejs scene graph look more like the scene graph in the modeling tool (which might have had multiple materials per mesh, for example).

This also assumes that you want those child meshes to be consolidated, which may not be true.

IMO, both of these changes are probably worthwhile.

2 Likes

WebGLRenderer manages the respective state changes and buffer bindings in setupVertexAttributes(). The function is called in .renderBufferDirect() when certain conditions are true. three.js does not perform any optimizations on buffer level. If setupVertexAttributes() is called, the engine binds all used buffers of the current geometry regardless of the current binding state.

We wanted to improve this process with VAO. However, I believe even this change would not account for shared buffers across multiple attributes.

Thanks for this suggestion. As you state it’s a valid workaround for now. I added this line of code before the calculation of the center vector.

// Workaround: Convert to non-indexed buffer geometry for glTF models with shared geometry buffer attributes        
object.geometry = object.geometry.toNonIndexed();
// Get the object's center
center = new THREE.Box3().setFromObject(object).getCenter(new THREE.Vector3());

I still believe that THREE.GLTFLoader() (GLTFLoader.js) should set the geometry’s position attribute to the vertex data referred to by the indices value set in the glTF file. To me it’s a bug/oversight that the glTF loader doesn’t follow the glTF specification 2.0 and use the indices value in the file to extract a mesh’s position geometry from the buffer.

The computeBoundingBox function in the three.js build file accesses the position data through var position = this.attributes.position. If the position property was set just the vertex data of the object then no changes are necessary to computeBoundingBox or any other method in the three.js build file that relies on this.attributes.position (computeBoundingSphere, applyMatrix, updateFromObject).

Intuitively when I see object.geometry.attributes.position I would expect it to return just the position data of the mesh and nothing more.

Since the shared buffer in a glTF file allows for smaller file sizes and therefore, faster transmission, I would expect to see this format used more in future glTF model exporters (or at least provide an option to export in the shared buffer format).

Ideally, a developer would expect three.js to abstract differences in how glTF files store vertex data.