Javascript 用大小不等的圆填充空间

Javascript 用大小不等的圆填充空间,javascript,ruby,layout,geometry,Javascript,Ruby,Layout,Geometry,我的问题是: 我需要在画布中显示一组圆圈 有任意数量的圆,每个圆具有预定义的半径 圆的总面积始终小于画布的面积 我想定位这些圆,以便它们在画布内占据最大的可用空间,而不相互接触。我的目标是实现一个视觉上令人愉悦的效果,圆圈在画布中均匀分布。我不知道这是否真的是“空间填充”,因为我的目标不是最小化元素之间的距离,而是最大化它 以下是我努力实现的一个例子: 我的第一个“暴力”想法如下: 对于每个圆:计算其边界与其他圆边界之间的最短距离;将所有这些距离相加,称之为X 计算所有X的总和 随机更改圆

我的问题是:

  • 我需要在画布中显示一组圆圈
  • 有任意数量的圆,每个圆具有预定义的半径
  • 圆的总面积始终小于画布的面积
我想定位这些圆,以便它们在画布内占据最大的可用空间,而不相互接触。我的目标是实现一个视觉上令人愉悦的效果,圆圈在画布中均匀分布。我不知道这是否真的是“空间填充”,因为我的目标不是最小化元素之间的距离,而是最大化它

以下是我努力实现的一个例子:

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

  • 对于每个圆:计算其边界与其他圆边界之间的最短距离;将所有这些距离相加,称之为X
  • 计算所有X的总和
  • 随机更改圆之间的距离
  • 按预设的迭代次数重做1-3,并取步骤(2)中获得的最大值
  • 然而,这似乎并不优雅;我相信有更好的办法。是否有任何现有的算法来实现这种布局?是否有任何现有的库可以使用(JavaScript或Ruby)来实现这一点

    编辑


    这是一个公认的答案,它使用拉斐尔来画圆圈。

    也许一些应用会有用。

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

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

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

    在添加另一个圆后更新此阵列不是火箭科学:您可以为每个网格点计算到新添加球体的距离,如果该距离更大,则替换存储的值

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

    下面是这个实现的一些结果(我花了大约100行代码)

    • 100个大小不等的圆圈

    • 500个大小不等的圆圈

    • 100个同样大小的圆圈

    这里是一些粗糙C++代码(只是算法,不要指望它编译)< /P>

    //初始化
    //画布尺寸
    浮动宽度=768;
    浮子高度=1004;
    //该算法在画布上创建网格
    浮动网格大小=10;
    int gridColumns,gridRows;
    浮动*距离;
    void init距离()
    {
    //确定网格维度并分配数组
    gridColumns=宽度/网格大小;
    网格行=高度/网格大小;
    //我们将二维数组存储为一维数组:
    dist=新浮点[gridColumns*gridRows];
    //到边缘距离最短的初始距离数组
    浮动y=网格大小/2.0;
    
    对于(int row=0;row,因为你的目标只是“达到令人愉快的效果”,而不是解决数学问题,你应该先尝试最简单的算法,看看它是否合适。不需要使用非常复杂的数学

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

    要达到这一目的,最简单的方法就是将球体一个接一个地放置在随机位置。如果一个球体落在已放置球体的顶部,则生成另一个随机位置,直到找到合适的位置为止

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


    首先尝试一下,看看它是否满足你的需要。如果它不是“甚至”足够了,你应该能够使用一些非常简单的数学来偏向随机选择的位置,以选择空白区域。应该不需要使用复杂的算法。

    不是圆,而是路易丝,看看克里斯的答案!这些图像看起来很棒——我想你应该接受它。@KrisVanBael,在GitHub上发布代码怎么样?+1开始时很简单。但随机确实不好看。你确实激励我编辑我的答案。是的,这是一个很好的开始建议。我只是尝试了随机的非重叠位置,它已经比手动定位好,但仍然远离我想要达到的视觉效果。我会在尝试其他解决方案时发布一些更新。我喜欢它,+1。我认为这可能是最好的解决方案。谢谢你,亚历克斯。但是自从Loisty没有接受我的回答,我就一直在那里实现它。非常好的克里斯,太棒了!你介意把代码发布到Git上吗?我发布了我的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;
            }
          }
        }