6

I am using Three.js (r86) to render a group of spheres. I'm doing this by calling a createSphereGroup function from an external library:

// External library code.
function createSphereGroup(sphereCount) [
  var group = new THREE.Group();
  for (var i = 0; i < sphereCount; i++) {
    var geometry = new THREE.SphereGeometry(50);
    var material = new THREE.MeshPhongMaterial({ color: 0xFF0000 });
    var sphere = new THREE.Mesh(geometry, material);
    sphere.position.x = i * 150;
    group.add(sphere);
  }
  return group;
}

(Assume that the external library is opaque and I can't modify any code in it.)


// My code.
var group = createSphereGroup(5);
scene.add(group);

...which looks like this:

Screenshot

As you can see, the group of spheres is off-center. I would like the group to be centered, with the 3rd sphere to be at the point where the two lines intersect (x=0).

So is there some way that I can center the group? Maybe by computing the width of the group and then positioning it at x=-width/2? I know you can call computeBoundingBox on a geometry to determine its width, but a THREE.Group doesn't have a geometry, so I'm not sure how to do this...

XåpplI'-I0llwlg'I -
  • 21,649
  • 28
  • 102
  • 151

2 Answers2

24

If you want to center an object (or group) and its children, you can do so by setting the object's position like so:

new THREE.Box3().setFromObject( object ).getCenter( object.position ).multiplyScalar( - 1 );

scene.add( object );

three.js r.92

WestLangley
  • 102,557
  • 10
  • 276
  • 276
4

If all the objects in the group are rather the same size, as in your example, you can just take the average of the children's position :

function computeGroupCenter(count) {
    var center = new THREE.Vector3();
    var children = group.children;
    var count = children.length;
    for (var i = 0; i < count; i++) {
        center.add(children[i].position);
    }
    center.divideScalar(count);
    return center;
}

Another (more robust) way to do this is to create a bounding box containing the bounding boxes of every child of the group.

There are some important things to consider :

  • A THREE.Group can contain others THREE.Group, therefore, the algorithm should be recursive
  • Bounding boxes are computed in local space, however, the object may be translated with respect to its parent so we need to work in world space.
  • The group itself might be translated to! So we have to jump back from world space to the group's local space to have the center defined in the group's space.

You can achieve this easily with THREE.Object3D.traverse, THREE.BufferGeometry.computeBoundingBox and THREE.Box3.ApplyMatrix4 :

/**
 * Compute the center of a THREE.Group by creating a bounding box
 * containing every children's bounding box.
 * @param {THREE.Group} group - the input group
 * @param {THREE.Vector3=} optionalTarget - an optional output point
 * @return {THREE.Vector3} the center of the group
 */
var computeGroupCenter = (function () {
    var childBox = new THREE.Box3();
    var groupBox = new THREE.Box3();
    var invMatrixWorld = new THREE.Matrix4();

    return function (group, optionalTarget) {
        if (!optionalTarget) optionalTarget = new THREE.Vector3();

        group.traverse(function (child) {
            if (child instanceof THREE.Mesh) {
                if (!child.geometry.boundingBox) {
                    child.geometry.computeBoundingBox();
                    childBox.copy(child.geometry.boundingBox);
                    child.updateMatrixWorld(true);
                    childBox.applyMatrix4(child.matrixWorld);
                    groupBox.min.min(childBox.min);
                    groupBox.max.max(childBox.max);
                }
            }
        });

        // All computations are in world space
        // But the group might not be in world space
        group.matrixWorld.getInverse(invMatrixWorld);
        groupBox.applyMatrix4(invMatrixWorld);

        groupBox.getCenter(optionalTarget);
        return optionalTarget;
    }
})();

Here's a fiddle.

neeh
  • 2,777
  • 3
  • 24
  • 32
  • This does not work for all groups. For example with the model used in this example: https://github.com/mrdoob/three.js/blob/dev/examples/webgl_loader_collada_skinning.html. This is related to https://stackoverflow.com/questions/50173778/three-js-draw-bounding-box-around-skinnedmesh – blockwork May 04 '18 at 12:58