按程序生成“blob”的好方法二维图形

发布于 2024-09-16 00:31:34 字数 546 浏览 13 评论 0原文

我希望以计算快速的方式创建一个“blob”。这里的斑点被定义为可以是任何形状但全部相连的像素的集合。示例:

.ooo....  
..oooo..  
....oo..  
.oooooo.
..o..o..  

...ooooooooooooooooooo...  
..........oooo.......oo..  
.....ooooooo..........o..  
.....oo..................  


......ooooooo....  
...ooooooooooo...  
..oooooooooooooo.  
..ooooooooooooooo  
..oooooooooooo...  
...ooooooo.......  
....oooooooo.....  
.....ooooo.......  
.......oo........  

哪里 .是死区,o 是标记像素。我只关心“二进制”生成 - 像素要么打开,要么关闭。例如,这些看起来像一些想象中的番茄酱或虚构的细菌或任何有机物质。

什么样的算法可以实现这一点?我真的很茫然

I'm looking to create a "blob" in a computationally fast manner. A blob here is defined as a collection of pixels that could be any shape, but all connected. Examples:

.ooo....  
..oooo..  
....oo..  
.oooooo.
..o..o..  

...ooooooooooooooooooo...  
..........oooo.......oo..  
.....ooooooo..........o..  
.....oo..................  


......ooooooo....  
...ooooooooooo...  
..oooooooooooooo.  
..ooooooooooooooo  
..oooooooooooo...  
...ooooooo.......  
....oooooooo.....  
.....ooooo.......  
.......oo........  

Where . is dead space and o is a marked pixel. I only care about "binary" generation - a pixel is either ON or OFF. So for instance these would look like some imaginary blob of ketchup or fictional bacterium or whatever organic substance.

What kind of algorithm could achieve this? I'm really at a loss

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

那一片橙海, 2024-09-23 00:31:34

David Thonley 的评论是正确的,但我假设你想要一个具有“有机”形状和光滑边缘的斑点。为此,您可以使用元球。元球是一种作用于标量场的幂函数。标量场可以通过行进立方体算法有效地渲染。通过改变球的数量、位置和半径可以形成不同的形状。

有关 2D 元球的介绍,请参阅此处:https://web.archive.org/web/20161018194403/https://www.niksula.hut.fi/~hkankaan/Homepages/metaballs.html

这里有一个介绍行进立方体算法: https://web.archive.org/web/20120329000652/http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/

请注意,交叉点的 256 种组合3D 中的组合只有 2D 中的 16 种组合。它很容易实现。

编辑:

我用 GLSL 着色器编写了一个快速示例。这是使用 50 个斑点以及 hkankaan 主页上的能量函数得到的结果。
alt text

这是实际的 GLSL 代码,尽管我评估了每个片段。我没有使用行进立方体算法。您需要渲染一个全屏四边形才能工作(两个三角形)。 vec3 统一数组只是通过 glUniform3fv 传递的各个斑点的 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;
}

David Thonley's comment is right on, but I'm going to assume you want a blob with an 'organic' shape and smooth edges. For that you can use metaballs. Metaballs is a power function that works on a scalar field. Scalar fields can be rendered efficiently with the marching cubes algorithm. Different shapes can be made by changing the number of balls, their positions and their radius.

See here for an introduction to 2D metaballs: https://web.archive.org/web/20161018194403/https://www.niksula.hut.fi/~hkankaan/Homepages/metaballs.html

And here for an introduction to the marching cubes algorithm: https://web.archive.org/web/20120329000652/http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/

Note that the 256 combinations for the intersections in 3D is only 16 combinations in 2D. It's very easy to implement.

EDIT:

I hacked together a quick example with a GLSL shader. Here is the result by using 50 blobs, with the energy function from hkankaan's homepage.
alt text

Here is the actual GLSL code, though I evaluate this per-fragment. I'm not using the marching cubes algorithm. You need to render a full-screen quad for it to work (two triangles). The vec3 uniform array is simply the 2D positions and radiuses of the individual blobs passed with glUniform3fv.

/* 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;
}
梦断已成空 2024-09-23 00:31:34

这是一种方法,我们首先生成分段仿射马铃薯,然后通过插值对其进行平滑。插值思想基于采用 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):
    pi = 3.14
    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)]

Examples

这些偶尔会有扭结和凹陷。这就是这个斑点家族的本质。

请注意,SciPy 具有凸包和 FFT,因此上述函数可以用它们代替。

Here's an approach where we first generate a piecewise-affine potato, and then smooth it by interpolating. The interpolation idea is based on taking the DFT, then leaving the low frequencies as they are, padding with zeros at high frequencies, and taking an inverse DFT.

Here's code requiring only standard Python libraries:

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):
    pi = 3.14
    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.

This generates something like this (the initial points, and the interpolation):

Piecewise-affine potato and the smoothed version

Here's another attempt:

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

Examples

These have kinks and concavities occasionally. Such is the nature of this family of blobs.

Note that SciPy has convex hull and FFT, so the above functions could be substituted by them.

橙味迷妹 2024-09-23 00:31:34

您可以设计算法来执行此操作,这些算法是一系列随机迷宫生成算法的较小变体。我会建议一种基于 union-find 方法。

联合查找的基本思想是,给定一组被划分为不相交(不重叠)子集的项目,快速识别特定项目属于哪个分区。 “并集”是将两个不相交的集合组合在一起形成一个更大的集合,“查找”是确定特定成员属于哪个分区。这个想法是,集合的每个分区都可以由集合的特定成员来标识,因此您可以形成树结构,其中指针从成员指向根。您可以通过首先查找每个分区的根,然后修改一个根的(以前为空)指针以指向另一个根,来合并两个分区(为每个分区指定一个任意成员)。

您可以将您的问题表述为不相交的并集问题。最初,每个单独的细胞都是它自己的一个分区。您想要的是合并分区,直到获得少量(不一定是两个)连接单元格的分区。然后,您只需选择一个分区(可能是最大的)并绘制它。

对于每个单元格,您将需要一个指针(最初为空)来进行合并。您可能需要一个位向量来充当一组相邻单元格。最初,每个单元都有一组四个(或八个)相邻单元。

对于每次迭代,您随机选择一个单元格,然后沿着指针链找到其根。在根的详细信息中,您可以找到其邻居集。从中选择一个随机成员,然后找到它的根,以识别相邻区域。执行并集(将一个根指向另一个根等)以合并两个区域。重复此操作,直到您对其中一个区域感到满意为止。

合并分区时,新根的新邻居集将是前两个根的邻居集的集合对称差(异或)。

当您增加每个根元素中的分区时,您可能需要维护其他数据(例如大小)。您可以使用它来更有选择性地继续某个特定的联盟,并帮助决定何时停止。分区中细胞分散的某些测量可能是相关的 - 例如,小的偏差或标准偏差(相对于大的细胞计数)可能表明密集的大致圆形斑点。

完成后,您只需扫描所有单元格来测试每个单元格是否是您选择的分区的一部分,以构建单独的位图。

在这种方法中,当您在迭代开始时随机选择一个单元格时,会强烈倾向于选择较大的分区。当您选择邻居时,也会倾向于选择较大的相邻分区。这意味着您往往会很快获得一个明显占主导地位的斑点。

You could probably design algorithms to do this that are minor variants of a range of random maze generating algorithms. I'll suggest one based on the union-find method.

The basic idea in union-find is, given a set of items that is partitioned into disjoint (non-overlapping) subsets, to identify quickly which partition a particular item belongs to. The "union" is combining two disjoint sets together to form a larger set, the "find" is determining which partition a particular member belongs to. The idea is that each partition of the set can be identified by a particular member of the set, so you can form tree structures where pointers point from member to member towards the root. You can union two partitions (given an arbitrary member for each) by first finding the root for each partition, then modifying the (previously null) pointer for one root to point to the other.

You can formulate your problem as a disjoint union problem. Initially, every individual cell is a partition of its own. What you want is to merge partitions until you get a small number of partitions (not necessarily two) of connected cells. Then, you simply choose one (possibly the largest) of the partitions and draw it.

For each cell, you will need a pointer (initially null) for the unioning. You will probably need a bit vector to act as a set of neighbouring cells. Initially, each cell will have a set of its four (or eight) adjacent cells.

For each iteration, you choose a cell at random, then follow a pointer chain to find its root. In the details from the root, you find its neighbours set. Choose a random member from that, then find the root for that, to identify a neighbouring region. Perform the union (point one root to the other, etc) to merge the two regions. Repeat until you're happy with one of the regions.

When merging partitions, the new neighbour set for the new root will be the set symmetric difference (exclusive or) of the neighbour sets for the two previous roots.

You'll probably want to maintain other data as you grow your partitions - e.g. the size - in each root element. You can use this to be a bit more selective about going ahead with a particular union, and to help decide when to stop. Some measure of the scattering of the cells in a partition may be relevant - e.g. a small deviance or standard deviation (relative to a large cell count) probably indicates a dense roughly-circular blob.

When you finish, you just scan all cells to test whether each is a part of your chosen partition to build a separate bitmap.

In this approach, when you randomly choose a cell at the start of an iteration, there's a strong bias towards choosing the larger partitions. When you choose a neighbour, there's also a bias towards choosing a larger neighbouring partition. This means you tend to get one clearly dominant blob quite quickly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文