空间填充不等大小的圆圈

时间:2012-03-16 02:20:03

标签: javascript ruby layout geometry

这是我的问题:

  • 我需要在画布中显示一堆圆圈。
  • 有任意数量的圆,每个圆都有预定义的半径。
  • 圆的总面积始终小于画布的面积。

我想定位圆圈,使它们占据画布内可用的最大空间,而不会相互接触。我的目标是实现视觉上令人愉悦的效果,其中圆圈在画布内分布均匀。我不知道这是否真的是“空间填充”,因为我的目标不是最小化元素之间的距离,而是最大化它。

这是我想要实现的一个例子:

Circles

我的第一个“蛮力”想法如下:

  1. 对于每个圆圈:计算其边界与每个圆圈边界之间的最短距离;总结所有这些距离,称之为X。
  2. 计算所有X的总和。
  3. 随机更改圆圈之间的距离。
  4. 重做1-3以获得预设的迭代次数,并获取步骤(2)中获得的最大值。
  5. 然而,这似乎并不优雅;我确信有更好的方法来做到这一点。有没有现成的算法来实现这样的布局?我可以使用任何现有的库(JavaScript或Ruby)来实现这个目标吗?

    修改

    以下是已接受答案的Javascript version,其中使用Raphael绘制圆圈。

3 个答案:

答案 0 :(得分:10)

我会尝试在球体之后插入球体(最大的第一个)。每个都添加在最大可用空间中,并带有一些随机抖动。

查找(或多或少)最大可用空间的一种相对简单的方法是想象视图上的点网格,并为每个网格点(在2D数组中)存储距离任何项目的最近距离:边缘或球体,以最接近的为准。添加每个新球体时,将更新此数组。

要添加新球体,只需获取最远距离的网格点并应用一些随机抖动(实际上您知道可以抖动多少,因为您知道距离最近项目的距离)。 (我会随机化不超过(d-r)/ 2,其中d是数组中的距离,r是要添加的球体的半径。

添加另一个圆后更新此数组并不是火箭科学:您为每个网格点计算新添加的球体的距离,如果更大,则替换存储的值。

您的网格可能太粗糙,并且您无法再添加圆圈(当2D阵列不包含大于要添加的圆的半径的距离时)。然后你必须在继续之前增加(例如加倍)网格分辨率。

以下是此实现的一些结果(它花了我大约100行代码)

  • 100个不同大小的圆圈

100 circles of varying size

  • 500个不同大小的圆圈

500 circles of varying size

  • 100个相同大小的圆圈

enter image description here

这里有一些粗略的C ++代码(只是算法,不要指望编译)

    // INITIALIZATION

    // Dimension of canvas
    float width = 768;
    float height = 1004;

    // The algorithm creates a grid on the canvas
    float gridSize=10;

    int gridColumns, gridRows;
    float *dist;

    void initDistances()
    {
      // Determine grid dimensions and allocate array
      gridColumns = width/gridSize;
      gridRows = height/gridSize;

      // We store a 2D array as a 1D array:
      dist = new float[ gridColumns * gridRows ];

      // Init dist array with shortest distances to the edges
      float y = gridSize/2.0;
      for (int row=0; row<gridRows; row++)
      {
        float distanceFromTop = y;
        float distanceFromBottom = height-y;
        for (int col=0; col<gridColumns; col++)
        {
          int i = row*gridColumns+col;
          dist[i]=(distanceFromTop<distanceFromBottom?distanceFromTop:distanceFromBottom);
        }
        y+=gridSize;
      }
      float x = gridSize/2.0;
      for (int col=0; col<gridColumns; col++)
      {
        float distanceFromLeft = x;
        float distanceFromRight = width-x;
        for (int row=0; row<gridRows; row++)
        {
          int i = row*gridColumns+col;
          if (dist[i]>distanceFromLeft) dist[i] = distanceFromLeft;
          if (dist[i]>distanceFromRight) dist[i] = distanceFromRight;
        }
        x+=gridSize;
      }
    }

    void drawCircles()
    {
      for (int circle = 0; circle<getNrOfCircles(); circle++)
      {
        // We assume circles are sorted large to small!
        float radius = getRadiusOfCircle( circle ); 

        // Find gridpoint with largest distance from anything
        int i=0;
        int maxR = 0;
        int maxC = 0;
        float maxDist = dist[0];

        for (int r=0; r<gridRows; r++) 
          for (int c=0; c<gridColumns; c++)
          {
            if (maxDist<dist[i]) {
              maxR= r; maxC= c; maxDist = dist[i];
            }
            i++;
          }

        // Calculate position of grid point
        float x = gridSize/2.0 + maxC*gridSize;
        float y = gridSize/2.0 + maxR*gridSize;

        // Apply some random Jitter
        float offset = (maxDist-radius)/2.0;
        x += (rand()/(float)RAND_MAX - 0.5) * 2 * offset;
        y += (rand()/(float)RAND_MAX - 0.5) * 2 * offset;


        drawCircle(x,y,radius);


        // Update Distance array with new circle;
        i=0;
        float yy = gridSize/2.0;
        for (int r=0; r<gridRows; r++)
        {
          float xx = gridSize/2.0;
          for (int c=0; c<gridColumns; c++)
          {
            float d2 = (xx-x)*(xx-x)+(yy-y)*(yy-y);

            // Naive implementation
            // float d = sqrt(d2) - radius;
            // if (dist[i]>d) dist[i] = d;

            // Optimized implementation (no unnecessary sqrt)
            float prev2 = dist[i]+radius;
            prev2 *= prev2;
            if (prev2 > d2)
            {
              float d = sqrt(d2) - radius;
              if (dist[i]>d) dist[i] = d;
            }



            xx += gridSize;
            i++;
          }
          yy += gridSize;
        }
      }
    }

答案 1 :(得分:2)

也许某些force-directed layout的应用会很有用。

答案 2 :(得分:1)

由于你的目标只是“达到令人愉悦的效果”,而不是解决数学问题,你应该尝试最简单的算法,它可以先工作,看看它是否好看。不应该使用非常复杂的数学。

我知道你希望球体“填充”可用空间,而不会留下大的空白区域,而其他区域则拥挤。您还希望布局看起来是随机的 - 不排列在网格上或类似的东西上。

实现这一目标的明显,简单的方法就是将球体逐个放置在随机位置。如果一个人落在已经放置的球体之上,则生成另一个随机位置,直到找到一个适合的位置。

图中显示的图像大约有40个球体。 40个球体全部落在图像的同一区域,将图像的其余部分留空的可能性非常非常小。随着球体数量的增加,获得非常不平衡布局的可能性将接近于零。

首先尝试,看看它是否符合您的需求。如果它不够“均匀”,你应该能够使用一些非常简单的数学来偏向随机选择的位置,而选择空白区域。不需要使用复杂的算法。