在球体表面包装不规则圆圈

时间:2014-12-08 22:29:34

标签: javascript algorithm three.js geometry circle-pack

我正在使用THREE.js在球体上创建点,类似于periodic table of elements example

我的数据集是一个不规则大小的圆圈,我希望将它们均匀地分布在球体表面周围。经过几个小时的网络搜索后,我意识到很多比听起来更难。

以下是这个想法的实例:

vimeo

pic

circlePack java applet

任何人都可以帮我指点找到一个允许我这样做的算法吗?打包率不需要非常高,理想情况下,它可以快速轻松地在javascript中进行计算,以便在THREE.js(笛卡尔坐标系或坐标系)中进行渲染。效率是关键。

编辑:圆弧半径可以有很大差异。以下是使用周期表代码的示例: example

3 个答案:

答案 0 :(得分:7)

这是一种尝试的方法:使用模拟排斥力的迭代搜索。

<强>算法

首先通过在任何类型的算法中在表面上排列圆来初始化数据集。这仅用于初始化,因此它不一定非常好。周期表代码可以很好地完成。另外,使用半径作为质量值为每个圆圈指定一个“质量”。

现在开始迭代以收敛解决方案。对于每次通过主循环的操作,请执行以下操作:

  1. 计算每个圆圈的排斥力。在重力公式之后对你的排斥力进行建模,并进行两项调整:(a)物体彼此相互推开,彼此不相互吸引,(b)你需要调整“力常数”值以适合您的模型的比例。根据您的数学能力,您可以在计划期间计算出良好的常数值;其他明智的只是先做一点实验,你会发现一个很好的价值。

  2. 计算每个圆上的总力(如果你不确定如何做的话,请查看n体问题),沿着其总计算力的向量移动每个圆,使用长度矢量作为移动的距离。在这里您可能会发现必须调整力常数值。首先,你需要长度小于球体半径5%的运动。

  3. 步骤2中的移动会将圆圈推离球体表面(因为它们相互排斥)。现在将每个圆圈移回球体表面,朝向球体中心的方向。

  4. 对于每个圆圈,计算圆圈旧位置到新位置的距离。移动的最大距离是主循环中此迭代的移动长度。

  5. 继续迭代主循环一段时间。随着时间的推移,移动长度应该变得越来越小,因为圆圈的相对位置稳定成符合您标准的排列。当运动长度下降到一个非常小的值以下时退出循环。

    <强>扭捏

    您可能会发现必须调整力计算以使算法收敛于解决方案。你如何调整取决于你正在寻找的结果类型。首先调整力常数。如果这不起作用,您可能需要向上或向下更改质量值。或者可以在力计算中更改半径的指数。例如,而不是:

    f = ( k * m[i] * m[j] ) / ( r * r );
    

    你可以试试这个:

    f = ( k * m[i] * m[j] ) / pow( r, p );
    

    然后你可以尝试不同的p值。

    您还可以尝试使用不同的算法进行初始分发。

    反复试验的数量取决于您的设计目标。

答案 1 :(得分:4)

这是你可以建立的东西。它会沿着球体随机分布你的球体。稍后我们将迭代这个起点以获得均匀分布。

//random point on sphere of radius R
var sphereCenters = []
var numSpheres = 100;
for(var i = 0; i < numSpheres; i++) {
    var R = 1.0;
    var vec = new THREE.Vector3(Math.random(), Math.random(), Math.random()).normalize();
    var sphereCenter = new THREE.Vector3().copy(vec).multiplyScalar(R);
    sphereCenter.radius = Math.random() * 5; // RANDOM SPHERE SIZE, plug in your sizes here
    sphereCenters.push(sphereCenter);
    //Create a THREE.js sphere at sphereCenter
    ...

}

然后运行以下代码几次以有效地打包球体:

for(var i = 0; i < sphereCenters.length; i++) {
    for(var j = 0; j < sphereCenters.length; j++) {
        if(i === j) continue;
        //calculate the distance between sphereCenters[i] and sphereCenters[j]
        var dist = new THREE.Vector3().copy(sphereCenters[i]).sub(sphereCenters[j]);
        if(dist.length() < sphereSize) {
             //move the center of this sphere to to compensate
             //how far do we have to move?
             var mDist = sphereSize - dist.length();
             //perturb the sphere in direction of dist magnitude mDist
             var mVec = new THREE.Vector3().copy(dist).normalize();
             mVec.multiplyScalar(mDist);
             //offset the actual sphere
             sphereCenters[i].add(mVec).normalize().multiplyScalar(R);

        }
    }
}

多次运行第二部分将“收敛”您正在寻找的解决方案。您必须选择应运行多少次才能找到速度和准确度之间的最佳折衷。

更新了准确性

答案 2 :(得分:-1)

您可以使用与元素周期表中相同的代码。 那里的矩形没有触摸,所以你可以通过使用相同的代码实现与圆圈相同的效果。

以下是他们的代码:

            var vector = new THREE.Vector3();

            for ( var i = 0, l = objects.length; i < l; i ++ ) {

                var phi = Math.acos( -1 + ( 2 * i ) / l );
                var theta = Math.sqrt( l * Math.PI ) * phi;

                var object = new THREE.Object3D();

                object.position.x = 800 * Math.cos( theta ) * Math.sin( phi );
                object.position.y = 800 * Math.sin( theta ) * Math.sin( phi );
                object.position.z = 800 * Math.cos( phi );

                vector.copy( object.position ).multiplyScalar( 2 );

                object.lookAt( vector );

                targets.sphere.push( object );

            }