这是我的问题:
我想定位圆圈,使它们占据画布内可用的最大空间,而不会相互接触。我的目标是实现视觉上令人愉悦的效果,其中圆圈在画布内分布均匀。我不知道这是否真的是“空间填充”,因为我的目标不是最小化元素之间的距离,而是最大化它。
这是我想要实现的一个例子:
我的第一个“蛮力”想法如下:
然而,这似乎并不优雅;我确信有更好的方法来做到这一点。有没有现成的算法来实现这样的布局?我可以使用任何现有的库(JavaScript或Ruby)来实现这个目标吗?
修改
以下是已接受答案的Javascript version,其中使用Raphael绘制圆圈。
答案 0 :(得分:10)
我会尝试在球体之后插入球体(最大的第一个)。每个都添加在最大可用空间中,并带有一些随机抖动。
查找(或多或少)最大可用空间的一种相对简单的方法是想象视图上的点网格,并为每个网格点(在2D数组中)存储距离任何项目的最近距离:边缘或球体,以最接近的为准。添加每个新球体时,将更新此数组。
要添加新球体,只需获取最远距离的网格点并应用一些随机抖动(实际上您知道可以抖动多少,因为您知道距离最近项目的距离)。 (我会随机化不超过(d-r)/ 2,其中d是数组中的距离,r是要添加的球体的半径。
添加另一个圆后更新此数组并不是火箭科学:您为每个网格点计算新添加的球体的距离,如果更大,则替换存储的值。
您的网格可能太粗糙,并且您无法再添加圆圈(当2D阵列不包含大于要添加的圆的半径的距离时)。然后你必须在继续之前增加(例如加倍)网格分辨率。
以下是此实现的一些结果(它花了我大约100行代码)
这里有一些粗略的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个球体全部落在图像的同一区域,将图像的其余部分留空的可能性非常非常小。随着球体数量的增加,获得非常不平衡布局的可能性将接近于零。
首先尝试,看看它是否符合您的需求。如果它不够“均匀”,你应该能够使用一些非常简单的数学来偏向随机选择的位置,而选择空白区域。不需要使用复杂的算法。