Хороший способ процедурно генерировать графику "blob" в 2D
Я хочу создать "blob" в вычислительной быстроте. Здесь blob определяется как совокупность пикселей, которые могут быть любой формы, но все связаны. Примеры:
.ooo....
..oooo..
....oo..
.oooooo.
..o..o..
...ooooooooooooooooooo...
..........oooo.......oo..
.....ooooooo..........o..
.....oo..................
......ooooooo....
...ooooooooooo...
..oooooooooooooo.
..ooooooooooooooo
..oooooooooooo...
...ooooooo.......
....oooooooo.....
.....ooooo.......
.......oo........
Где. это мертвое пространство, а o - обозначенный пиксель. Меня волнует только "двоичное" поколение - пиксель включен или выключен. Так, например, это выглядело бы как мнимая капля кетчупа или вымышленная бактерия или какое бы то ни было органическое вещество.
Какой алгоритм может это достичь? Я действительно в недоумении
Ответы
Ответ 1
Комментарий Дэвида Тонли, но я собираюсь предположить, что вам нужен капля с "органической" формой и гладкими краями. Для этого вы можете использовать метабаллы. Metaballs - это функция мощности, которая работает на скалярном поле. Скалярные поля могут быть эффективно обработаны алгоритмом маршевых кубов. Различные формы могут быть сделаны путем изменения количества шаров, их положений и их радиуса.
См. здесь введение в 2D метабаллы: http://www.niksula.hut.fi/~hkankaan/Homepages/metaballs.html
И вот для введения в алгоритм маршевых кубов: http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/
Обратите внимание, что 256 комбинаций для пересечений в 3D - всего 16 комбинаций в 2D. Это очень легко реализовать.
EDIT:
Я взломал быстрый пример с помощью шейдера GLSL. Вот результат, используя 50 blobs, с функцией энергии с hkankaan homepage.
![alt text]()
Вот фактический код GLSL, хотя я оцениваю этот фрагмент. Я не использую алгоритм маршевых кубов. Для этого вам нужно отобразить полноэкранный четырехъядерный процессор (два треугольника). Единый массив vec3 - это просто двумерные позиции и радиусы отдельных капель, прошедших с помощью 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;
}
Ответ 2
Здесь подход, в котором мы сначала генерируем кусочно-аффинный картофель, а затем сглаживаем его путем интерполяции. Идея интерполяции основана на принятии ДПФ, затем оставлении низких частот по мере их заполнения, заполнении нулями на высоких частотах и обратном ДПФ.
Здесь код, требующий только стандартных библиотек Python:
import cmath
from math import atan2
from random import random
def convexHull(pts): #Graham 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)]
![Примеры]()
У них иногда есть перегибы и вогнутости. Такова природа этого семейства blobs.
Обратите внимание, что SciPy имеет выпуклую оболочку и FFT, поэтому вышеупомянутые функции могут быть заменены ими.
Ответ 3
Возможно, вы могли бы разработать алгоритмы для этого, которые являются второстепенными вариантами ряда алгоритмов генерации случайных лабиринтов. Я предлагаю метод, основанный на методе union-find.
Основная идея в union-find - это набор элементов, которые разбиты на непересекающиеся (неперекрывающиеся) подмножества, чтобы быстро определить, к какому разделу принадлежит определенный элемент. "Объединение" объединяет два непересекающихся множества вместе, чтобы сформировать больший набор, "find" определяет, к какому разделу принадлежит определенный член. Идея состоит в том, что каждый раздел набора может быть идентифицирован конкретным членом множества, поэтому вы можете сформировать древовидные структуры, где указатели указывают от члена к члену к корню. Вы можете объединить два раздела (для каждого из них произвольный член), сначала найдя корень для каждого раздела, а затем изменив указатель (ранее нулевой) для одного корня, чтобы указать на другой.
Вы можете сформулировать свою проблему как проблему развязанного объединения. Первоначально каждая отдельная ячейка является отдельным разделом. Вы хотите объединить разделы, пока не получите небольшое количество разделов (не обязательно два) связанных ячеек. Затем вы просто выбираете один (возможно, самый большой) разделов и рисуете его.
Для каждой ячейки вам понадобится указатель (изначально нулевой) для объединения. Вам, вероятно, понадобится бит-вектор, чтобы действовать как набор соседних ячеек. Первоначально каждая ячейка будет иметь набор из четырех (или восьми) соседних ячеек.
Для каждой итерации вы выбираете ячейку наугад, а затем следуйте цепочке указателей, чтобы найти ее корень. В деталях из корня вы обнаружите, что его соседи установлены. Выберите случайный член из этого, затем найдите корень для этого, чтобы определить соседнюю область. Выполните объединение (укажите один корень на другой и т.д.), Чтобы объединить два региона. Повторяйте, пока вы не удовлетворены одним из регионов.
При объединении разделов новым соседом, установленным для нового корня, будет установленная симметричная разница (исключительная или) соседних наборов для двух предыдущих корней.
Вероятно, вы захотите сохранить другие данные по мере роста своих разделов - например, размер - в каждом корневом элементе. Вы можете использовать это, чтобы быть немного более избирательным, чтобы идти вперед с конкретным союзом и помогать решать, когда остановиться. Некоторая мера рассеяния ячеек в перегородке может быть актуальной. небольшое отклонение или стандартное отклонение (относительно большого количества клеток), вероятно, указывает на плотный грубо-круговой блок.
Когда вы закончите, вы просто сканируете все ячейки, чтобы проверить, является ли каждая часть вашего выбранного раздела для создания отдельного растрового изображения.
В этом подходе, когда вы произвольно выбираете ячейку в начале итерации, существует сильное смещение в сторону выбора больших разделов. Когда вы выбираете соседа, есть также смещение к выбору более крупного соседнего раздела. Это означает, что вы, как правило, получаете достаточно четко доминирующий blob.