如何根据子女的宽度来集中一个THREE.Group?

时间:2017-09-11 21:00:17

标签: three.js

我使用Three.js(r86)渲染一组球体。我这样做是通过从外部库调用createSphereGroup函数来实现的:

// 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;
}

(假设外部库是不透明的,我无法修改其中的任何代码。)

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

......看起来像这样:

Screenshot

如您所见,这组球体偏离中心。我希望该组居中,第三个球体位于两条线相交的点(x=0)。

那么我可以通过某种方式使这个群体居中吗?也许通过计算组的宽度然后将其定位在x=-width/2?我知道你可以在几何体上调用computeBoundingBox来确定它的宽度,但是THREE.Group没有几何体,所以我不知道如何做到这一点...

2 个答案:

答案 0 :(得分:8)

如果要将对象(或组)及其子对象居中,可以通过设置对象的位置来实现:

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

scene.add( object );

three.js r.92

答案 1 :(得分:1)

如果组中的所有对象的大小相同,如示例所示,您可以只取儿童位置的平均值:

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;
}

另一种(更强大)的方法是创建一个包含该组中每个孩子的边界框的边界框。

有一些重要的事情需要考虑:

  • THREE.Group可以包含其他THREE.Group,因此,算法应该是递归的
  • 边界框是在局部空间中计算的,但是,对象可以相对于其父对象进行翻译,因此我们需要在世界空间中工作。
  • 该组织本身可能会被翻译成!因此,我们必须从世界空间跳回到集团的本地空间,以便在集团的空间中定义中心。

您可以使用THREE.Object3D.traverseTHREE.BufferGeometry.computeBoundingBoxTHREE.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