我希望以计算速度快的方式创建一个“blob”。这里的blob被定义为像素的集合,可以是任何形状,但都是连接的。例子:
.ooo....
..oooo..
....oo..
.oooooo.
..o..o..
...ooooooooooooooooooo...
..........oooo.......oo..
.....ooooooo..........o..
.....oo..................
......ooooooo....
...ooooooooooo...
..oooooooooooooo.
..ooooooooooooooo
..oooooooooooo...
...ooooooo.......
....oooooooo.....
.....ooooo.......
.......oo........
哪里。是死空间,o是标记的像素。我只关心“二进制”生成 - 像素是ON还是OFF。因此,例如,这些看起来像一些想象中的番茄酱或虚构细菌或任何有机物质。
什么样的算法可以达到这个目的?我真的很茫然
答案 0 :(得分:20)
David Thonley的评论是正确的,但我会假设你想要一个具有“有机”形状和光滑边缘的斑点。为此你可以使用metaballs。 Metaballs是一个在标量场上工作的幂函数。使用行进立方体算法可以有效地渲染标量字段。通过改变球的数量,它们的位置和半径可以形成不同的形状。
请参阅此处了解2D元球的介绍:http://www.niksula.hut.fi/~hkankaan/Homepages/metaballs.html
这里是对行进立方体算法的介绍:http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/
请注意,3D中交叉点的256种组合在2D中仅为16种组合。它很容易实现。
编辑:
我用GLSL着色器一起破解了一个快速示例。这是使用50个blob的结果,具有来自hkankaan主页的能量函数。
这是实际的GLSL代码,尽管我对每个片段进行了评估。我没有使用行进立方体算法。你需要渲染一个全屏四边形才能工作(两个三角形)。 vec3均匀数组只是通过glUniform3fv
传递的各个blob的2D位置和半径。
/* Trivial bare-bone vertex shader */
#version 150
in vec2 vertex;
void main()
{
gl_Position = vec4(vertex.x, vertex.y, 0.0, 1.0);
}
/* Fragment shader */
#version 150
#define NUM_BALLS 50
out vec4 color_out;
uniform vec3 balls[NUM_BALLS]; //.xy is position .z is radius
bool energyField(in vec2 p, in float gooeyness, in float iso)
{
float en = 0.0;
bool result = false;
for(int i=0; i<NUM_BALLS; ++i)
{
float radius = balls[i].z;
float denom = max(0.0001, pow(length(vec2(balls[i].xy - p)), gooeyness));
en += (radius / denom);
}
if(en > iso)
result = true;
return result;
}
void main()
{
bool outside;
/* gl_FragCoord.xy is in screen space / fragment coordinates */
outside = energyField(gl_FragCoord.xy, 1.0, 40.0);
if(outside == true)
color_out = vec4(1.0, 0.0, 0.0, 1.0);
else
discard;
}
答案 1 :(得分:2)
这是我们首先生成分段仿射马铃薯,然后通过插值平滑它的方法。插值思想基于取DFT,然后保持原来的低频,用高频零填充,并采用逆DFT。
这里的代码只需要标准的Python库:
import cmath
from math import atan2
from random import random
def convexHull(pts): #Graham's scan.
xleftmost, yleftmost = min(pts)
by_theta = [(atan2(x-xleftmost, y-yleftmost), x, y) for x, y in pts]
by_theta.sort()
as_complex = [complex(x, y) for _, x, y in by_theta]
chull = as_complex[:2]
for pt in as_complex[2:]:
#Perp product.
while ((pt - chull[-1]).conjugate() * (chull[-1] - chull[-2])).imag < 0:
chull.pop()
chull.append(pt)
return [(pt.real, pt.imag) for pt in chull]
def dft(xs):
return [sum(x * cmath.exp(2j*pi*i*k/len(xs))
for i, x in enumerate(xs))
for k in range(len(xs))]
def interpolateSmoothly(xs, N):
"""For each point, add N points."""
fs = dft(xs)
half = (len(xs) + 1) // 2
fs2 = fs[:half] + [0]*(len(fs)*N) + fs[half:]
return [x.real / len(xs) for x in dft(fs2)[::-1]]
pts = convexHull([(random(), random()) for _ in range(10)])
xs, ys = [interpolateSmoothly(zs, 100) for zs in zip(*pts)] #Unzip.
这会产生类似这样的东西(初始点和插值):
这是另一次尝试:
pts = [(random() + 0.8) * cmath.exp(2j*pi*i/7) for i in range(7)]
pts = convexHull([(pt.real, pt.imag ) for pt in pts])
xs, ys = [interpolateSmoothly(zs, 30) for zs in zip(*pts)]
偶尔会出现扭结和凹陷。这就是这个斑点家族的本质。
请注意,SciPy具有凸包和FFT,因此上述函数可以用它们代替。
答案 2 :(得分:1)
您可能可以设计算法来做一些随机迷宫生成算法的微小变体。我会根据union-find方法建议一个。
union-find中的基本思想是,给定一组被分割成不相交(非重叠)子集的项,以快速识别特定项属于哪个分区。 “联合”将两个不相交的组合在一起形成一个更大的组,“查找”是确定特定成员属于哪个分区。这个想法是集合中的每个分区都可以由集合中的特定成员标识,因此您可以形成树结构,其中指针指向从成员到成员的根。您可以通过首先找到每个分区的根,然后修改一个根的(以前为空的)指针指向另一个分区来合并两个分区(每个分区都有一个任意成员)。
您可以将问题表述为不相交的联合问题。最初,每个单独的单元格都是它自己的分区。你想要的是合并分区,直到你获得少量的连接单元分区(不一定是两个)。然后,您只需选择一个(可能是最大的)分区并绘制它。
对于每个单元格,您将需要一个指针(最初为null)用于合并。您可能需要一个位向量来充当一组相邻单元格。最初,每个单元将具有一组其四个(或八个)相邻单元。
对于每次迭代,您随机选择一个单元格,然后按照指针链查找其根。在根中的详细信息中,您可以找到其邻居。从中选择一个随机成员,然后找到它的根,以识别相邻区域。执行联合(将一个根指向另一个,等等)以合并这两个区域。重复,直到你对其中一个地区感到满意为止。
合并分区时,新根的新邻居集将是前两个根的邻居集的设置对称差异(异或)。
您可能希望在扩展分区时维护其他数据 - 例如大小 - 在每个根元素中。您可以使用它来更好地选择特定联盟,并帮助决定何时停止。对分区中细胞散射的一些测量可能是相关的 - 例如,小偏差或标准偏差(相对于大细胞计数)可能表示密集的大致圆形斑点。
完成后,您只需扫描所有单元格以测试每个单元格是否是所选分区的一部分,以构建单独的位图。
在这种方法中,当您在迭代开始时随机选择一个单元格时,就会选择较大的分区。当您选择邻居时,也会偏向于选择较大的邻居分区。这意味着你很快会得到一个明显占优势的blob。