在球面上放置不规则圆形

11
我正在使用Three.js在球体上创建点,类似于元素周期表示例
我的数据集是大小不规则的圆形,我希望将它们均匀分布在球体表面上。经过数小时的网上搜索,我意识到这比听起来要困难得多。
以下是此想法实际运用的示例: Vimeo Picture circlePack Java applet 是否有一种算法可以让我做到这一点?打包比率不需要非常高,最好是能够快速轻松地在JavaScript中计算并在Three.js(笛卡尔或坐标系)中呈现。效率至关重要。
圆的半径可以有很大的变化。以下是使用周期表代码的示例:

Example


2
你试图优化什么?例如:最小的球以适应所有圆,或者最大化特定大小的球上适合的圆的数量,还是其他什么?与这个问题相关的是,“均匀分布”是什么意思? - Nuclearman
看一下这个页面的源代码——可能会给你一个或两个想法;http://www.engineeringtoolbox.com/smaller-circles-in-larger-circle-d_1849.html - mike510a
3个回答

8
以下是一种可尝试的方法:使用模拟斥力进行迭代搜索。 算法 首先,通过任何类型的算法将圆排列在表面上初始化数据集。这只是为了初始化,所以不必很好。周期表代码会很好用。另外,使用其半径作为其质量值,为每个圆分配一个“质量”。
现在开始迭代以收敛于解决方案。对于每次通过主循环,执行以下操作:
1.计算每个圆的斥力。将您的斥力建模为引力公式,进行两个调整:(a)对象应该被推开,而不是互相吸引,(b)您需要调整“力常数”值以适应您的模型规模。根据您的数学能力,您可以在计划期间计算出一个良好的常数值;否则,请先进行一些实验,您会找到一个良好的值。
2.计算每个圆上的总力(如果您不确定如何执行此操作,请查找n体问题),使用长度作为移动距离,沿其总计算力的向量移动每个圆。这是您可能会发现必须调整力常数值的地方。首先,您需要移动长度小于球体半径的5%。
3.步骤2中的移动将使圆从球体表面上推出(因为它们互相排斥)。现在将每个圆移回到球体表面,在指向球体中心的方向上。
4.对于每个圆,计算圆的旧位置到新位置的距离。移动的最大距离是主循环的迭代步长。
持续执行主循环的迭代。随着时间的推移,随着圆的相对位置稳定到符合您的条件的排列,移动长度应该变得越来越小。当移动长度降至某个非常小的值以下时退出循环。 调整 您可能会发现需要调整力量计算才能使算法收敛于解决方案。要调整取决于您要寻找的结果类型。首先尝试调整力量常数。如果这样不行,则可能必须上下更改质量值。或者可能更改力计算中半径的指数。例如,可以更改如下:
f = ( k * m[i] * m[j] ) / ( r * r );

你可以尝试这个方法:
f = ( k * m[i] * m[j] ) / pow( r, p );

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

您还可以尝试不同的算法来进行初始分配。

试错的量取决于您的设计目标。


感谢您解释这种方法。该方法不仅适用于three.js / javascript,而且对于任何尝试圆形排列的人来说都是一个很好的例子。 - Jared

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

多次运行第二部分将会“收敛”到您寻找的解决方案。您必须选择要运行多少次才能在速度和准确性之间取得最佳平衡。


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

            }

我使用这种方法进行了几次测试,发现在值的差异较大时会出现问题,例如圆形的值范围从10到100不等。 - Jared
我添加了一张截图,以防你想看到不规则的圆形。在我看来,它看起来不好,我正在寻找更好的分布方式。 - Jared

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接