Алгоритм генерации случайного 2D многоугольника
Я не уверен, как подойти к этой проблеме. Я не уверен, насколько это сложная задача. Моя цель - иметь алгоритм, который генерирует любой многоугольник. Мое единственное требование заключается в том, что многоугольник не является сложным (т. е. стороны не пересекаются). Я использую Matlab для выполнения математики, но все абстрактное приветствуется.
любая помощь/направление?
EDIT:
Я больше думал о коде, который может генерировать любой полигон, даже такие вещи, как это:
5 ответов
есть аккуратный способ сделать то, что вы хотите, воспользовавшись классами MATLAB DelaunayTri
и TriRep
и различные методы, которые они используют для обработки треугольных сетках. Код ниже следует этим шагам для создания произвольного простые полигональные:
генерировать количество случайных точек, равных желаемому количеству сторон плюс коэффициент помадки. Фактор fudge обеспечивает что, независимо от результата триангуляция, у нас должно быть достаточно граней, чтобы обрезать треугольную сетку до многоугольника с желаемым количеством сторон.
создайте триангуляцию точек Делоне, в результате чего выпуклый многоугольник который построен из серии треугольных граней.
если граница триангуляции имеет больше ребер, чем требуется, выберите случайную треугольную грань на краю, которая имеет уникальную вершину (т. е. треугольник имеет только один край с остальной частью триангуляции). Удаление этой треугольной грани уменьшит количество граничных кромок.
если граница триангуляции имеет меньше ребер, чем требуется, или на предыдущем шаге не удалось найти треугольник для удаления, выберите случайную треугольную грань на краю, которая имеет только один из своих ребер на границе триангуляции. Снимая этот треугольная грань увеличит число граничных стыки.
если треугольные грани не могут быть найдены, соответствующие вышеуказанным критериям, отправьте предупреждение о том, что полигон с требуемым количеством сторон не может быть найден, и верните координаты x и y текущей границы триангуляции. В противном случае продолжайте удалять треугольные грани до тех пор, пока не будет выполнено требуемое число ребер, а затем верните координаты X и y границы триангуляции.
вот результат функция:
function [x, y, dt] = simple_polygon(numSides)
if numSides < 3
x = [];
y = [];
dt = DelaunayTri();
return
end
oldState = warning('off', 'MATLAB:TriRep:PtsNotInTriWarnId');
fudge = ceil(numSides/10);
x = rand(numSides+fudge, 1);
y = rand(numSides+fudge, 1);
dt = DelaunayTri(x, y);
boundaryEdges = freeBoundary(dt);
numEdges = size(boundaryEdges, 1);
while numEdges ~= numSides
if numEdges > numSides
triIndex = vertexAttachments(dt, boundaryEdges(:,1));
triIndex = triIndex(randperm(numel(triIndex)));
keep = (cellfun('size', triIndex, 2) ~= 1);
end
if (numEdges < numSides) || all(keep)
triIndex = edgeAttachments(dt, boundaryEdges);
triIndex = triIndex(randperm(numel(triIndex)));
triPoints = dt([triIndex{:}], :);
keep = all(ismember(triPoints, boundaryEdges(:,1)), 2);
end
if all(keep)
warning('Couldn''t achieve desired number of sides!');
break
end
triPoints = dt.Triangulation;
triPoints(triIndex{find(~keep, 1)}, :) = [];
dt = TriRep(triPoints, x, y);
boundaryEdges = freeBoundary(dt);
numEdges = size(boundaryEdges, 1);
end
boundaryEdges = [boundaryEdges(:,1); boundaryEdges(1,1)];
x = dt.X(boundaryEdges, 1);
y = dt.X(boundaryEdges, 2);
warning(oldState);
end
и вот некоторые примеры результатов:
сгенерированные полигоны могут быть выпуклыми или вогнутая, но для большего количества желаемых сторон они почти наверняка будут вогнутыми. Полигоны также генерируются из точек, случайно сгенерированных в пределах единичного квадрата, поэтому полигоны с большим количеством сторон обычно выглядят так, как будто они имеют" квадратную " границу (например, нижний правый пример выше с в 50-угольник). Чтобы изменить эту общую ограничивающую форму, вы можете изменить способ начального x
и y
точки выбираются случайным образом (т. е. из гауссова распределения и т. д.).
Я взял @MitchWheat и @ templatetypedef идею выборки точек на круге и взял ее немного дальше.
в моем приложении мне нужно иметь возможность контролировать, насколько странны полигоны, т. е. начинать с обычных полигонов, и по мере того, как я проверяю параметры, они становятся все более хаотичными. Основная идея, как указано в @templatetypedef; ходить по кругу, принимая случайный угловой шаг каждый раз, и на каждом шаге поставить точку в случайном радиусе. В уравнениях я генерирую угловые шаги
где theta_i и r_i дают угол и радиус каждой точки относительно центра, U(min, max) вытаскивает случайное число из равномерного распределения, а N(mu, sigma) вытаскивает случайное число из гауссова распределения, а clip (x, min, max) пороговое значение в диапазон. Это дает нам два действительно хороших параметра для управления тем, насколько дикими являются полигоны-epsilon, который я назову нарушение контролирует ли или не пункты равномерно пространство под углом вокруг круга и Сигма, которую я назову spikeyness который контролирует, насколько точки могут отличаться от окружности радиуса r_ave. Если вы установите оба из них в 0, то вы получите совершенно правильные многоугольники, если вы провернете их, то многоугольники станут безумнее.
я быстро взбитые это в python и получил вещи, как это:
вот полный код:
import math, random
def generatePolygon( ctrX, ctrY, aveRadius, irregularity, spikeyness, numVerts ) :
'''Start with the centre of the polygon at ctrX, ctrY,
then creates the polygon by sampling points on a circle around the centre.
Randon noise is added by varying the angular spacing between sequential points,
and by varying the radial distance of each point from the centre.
Params:
ctrX, ctrY - coordinates of the "centre" of the polygon
aveRadius - in px, the average radius of this polygon, this roughly controls how large the polygon is, really only useful for order of magnitude.
irregularity - [0,1] indicating how much variance there is in the angular spacing of vertices. [0,1] will map to [0, 2pi/numberOfVerts]
spikeyness - [0,1] indicating how much variance there is in each vertex from the circle of radius aveRadius. [0,1] will map to [0, aveRadius]
numVerts - self-explanatory
Returns a list of vertices, in CCW order.
'''
irregularity = clip( irregularity, 0,1 ) * 2*math.pi / numVerts
spikeyness = clip( spikeyness, 0,1 ) * aveRadius
# generate n angle steps
angleSteps = []
lower = (2*math.pi / numVerts) - irregularity
upper = (2*math.pi / numVerts) + irregularity
sum = 0
for i in range(numVerts) :
tmp = random.uniform(lower, upper)
angleSteps.append( tmp )
sum = sum + tmp
# normalize the steps so that point 0 and point n+1 are the same
k = sum / (2*math.pi)
for i in range(numVerts) :
angleSteps[i] = angleSteps[i] / k
# now generate the points
points = []
angle = random.uniform(0, 2*math.pi)
for i in range(numVerts) :
r_i = clip( random.gauss(aveRadius, spikeyness), 0, 2*aveRadius )
x = ctrX + r_i*math.cos(angle)
y = ctrY + r_i*math.sin(angle)
points.append( (int(x),int(y)) )
angle = angle + angleSteps[i]
return points
def clip(x, min, max) :
if( min > max ) : return x
elif( x < min ) : return min
elif( x > max ) : return max
else : return x
@MateuszKonieczny вот код для создайте изображение многоугольника из списка вершин.
verts = generatePolygon( ctrX=250, ctrY=250, aveRadius=100, irregularity=0.35, spikeyness=0.2, numVerts=16 )
black = (0,0,0)
white=(255,255,255)
im = Image.new('RGB', (500, 500), white)
imPxAccess = im.load()
draw = ImageDraw.Draw(im)
tupVerts = map(tuple,verts)
# either use .polygon(), if you want to fill the area with a solid colour
draw.polygon( tupVerts, outline=black,fill=white )
# or .line() if you want to control the line thickness, or use both methods together!
draw.line( tupVerts+[tupVerts[0]], width=2, fill=black )
im.show()
# now you can save the image (im), or do whatever else you want with it.
для выпуклого 2D-многоугольника (полностью с моей головы):
генерировать случайный радиус, R
генерировать N случайных точек на окружности окружности радиуса R
двигаться по кругу и рисовать прямые линии между соседними точками на окружности.
Как сказали @templatetypedef и @MitchWheat, это легко сделать, генерируя N
случайные углы и радиусы. Важно углов, иначе это будет не простой многоугольник. Обратите внимание, что я использую аккуратный трюк для рисования замкнутых кривых - я описал его в здесь. Кстати, полигоны могут быть вогнутая.
обратите внимание, что все эти полигоны будут в форме звезды. Создание более общего многоугольника-совсем не простая задача. Просто чтобы дать вам вкус проблемы-проверьте http://www.cosy.sbg.ac.at / ~held/projects/rpg/rpg.html и http://compgeom.cs.uiuc.edu/~jeffe/open/randompoly.html.
function CreateRandomPoly()
figure();
colors = {'r','g','b','k'};
for i=1:5
[x,y]=CreatePoly();
c = colors{ mod(i-1,numel(colors))+1};
plotc(x,y,c);
hold on;
end
end
function [x,y]=CreatePoly()
numOfPoints = randi(30);
theta = randi(360,[1 numOfPoints]);
theta = theta * pi / 180;
theta = sort(theta);
rho = randi(200,size(theta));
[x,y] = pol2cart(theta,rho);
xCenter = randi([-1000 1000]);
yCenter = randi([-1000 1000]);
x = x + xCenter;
y = y + yCenter;
end
function plotc(x,y,varargin)
x = [x(:) ; x(1)];
y = [y(:) ; y(1)];
plot(x,y,varargin{:})
end
вот рабочий порт для Matlab решения Mike Ounsworth. Я не оптимизировал его для matlab. Я мог бы обновить решение позже для этого.
function [points] = generatePolygon(ctrX, ctrY, aveRadius, irregularity, spikeyness, numVerts)
%{
Start with the centre of the polygon at ctrX, ctrY,
then creates the polygon by sampling points on a circle around the centre.
Randon noise is added by varying the angular spacing between sequential points,
and by varying the radial distance of each point from the centre.
Params:
ctrX, ctrY - coordinates of the "centre" of the polygon
aveRadius - in px, the average radius of this polygon, this roughly controls how large the polygon is, really only useful for order of magnitude.
irregularity - [0,1] indicating how much variance there is in the angular spacing of vertices. [0,1] will map to [0, 2pi/numberOfVerts]
spikeyness - [0,1] indicating how much variance there is in each vertex from the circle of radius aveRadius. [0,1] will map to [0, aveRadius]
numVerts - self-explanatory
Returns a list of vertices, in CCW order.
Website: https://stackoverflow.com/questions/8997099/algorithm-to-generate-random-2d-polygon
%}
irregularity = clip( irregularity, 0,1 ) * 2*pi/ numVerts;
spikeyness = clip( spikeyness, 0,1 ) * aveRadius;
% generate n angle steps
angleSteps = [];
lower = (2*pi / numVerts) - irregularity;
upper = (2*pi / numVerts) + irregularity;
sum = 0;
for i =1:numVerts
tmp = unifrnd(lower, upper);
angleSteps(i) = tmp;
sum = sum + tmp;
end
% normalize the steps so that point 0 and point n+1 are the same
k = sum / (2*pi);
for i =1:numVerts
angleSteps(i) = angleSteps(i) / k;
end
% now generate the points
points = [];
angle = unifrnd(0, 2*pi);
for i =1:numVerts
r_i = clip( normrnd(aveRadius, spikeyness), 0, 2*aveRadius);
x = ctrX + r_i* cos(angle);
y = ctrY + r_i* sin(angle);
points(i,:)= [(x),(y)];
angle = angle + angleSteps(i);
end
end
function value = clip(x, min, max)
if( min > max ); value = x; return; end
if( x < min ) ; value = min; return; end
if( x > max ) ; value = max; return; end
value = x;
end