Алгоритм генерации случайного 2D многоугольника

Я не уверен, как подойти к этой проблеме. Я не уверен, насколько это сложная задача. Моя цель - иметь алгоритм, который генерирует любой многоугольник. Мое единственное требование заключается в том, что многоугольник не является сложным (т. е. стороны не пересекаются). Я использую Matlab для выполнения математики, но все абстрактное приветствуется.

любая помощь/направление?

EDIT:

Я больше думал о коде, который может генерировать любой полигон, даже такие вещи, как это:

enter image description here

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

и вот некоторые примеры результатов:

enter image description here

сгенерированные полигоны могут быть выпуклыми или вогнутая, но для большего количества желаемых сторон они почти наверняка будут вогнутыми. Полигоны также генерируются из точек, случайно сгенерированных в пределах единичного квадрата, поэтому полигоны с большим количеством сторон обычно выглядят так, как будто они имеют" квадратную " границу (например, нижний правый пример выше с в 50-угольник). Чтобы изменить эту общую ограничивающую форму, вы можете изменить способ начального x и y точки выбираются случайным образом (т. е. из гауссова распределения и т. д.).


Я взял @MitchWheat и @ templatetypedef идею выборки точек на круге и взял ее немного дальше.

в моем приложении мне нужно иметь возможность контролировать, насколько странны полигоны, т. е. начинать с обычных полигонов, и по мере того, как я проверяю параметры, они становятся все более хаотичными. Основная идея, как указано в @templatetypedef; ходить по кругу, принимая случайный угловой шаг каждый раз, и на каждом шаге поставить точку в случайном радиусе. В уравнениях я генерирую угловые шаги equations for the angles and radii of the vertices

где theta_i и r_i дают угол и радиус каждой точки относительно центра, U(min, max) вытаскивает случайное число из равномерного распределения, а N(mu, sigma) вытаскивает случайное число из гауссова распределения, а clip (x, min, max) пороговое значение в диапазон. Это дает нам два действительно хороших параметра для управления тем, насколько дикими являются полигоны-epsilon, который я назову нарушение контролирует ли или не пункты равномерно пространство под углом вокруг круга и Сигма, которую я назову spikeyness который контролирует, насколько точки могут отличаться от окружности радиуса r_ave. Если вы установите оба из них в 0, то вы получите совершенно правильные многоугольники, если вы провернете их, то многоугольники станут безумнее.

я быстро взбитые это в python и получил вещи, как это: some polygons I generated

вот полный код:

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-многоугольника (полностью с моей головы):

  1. генерировать случайный радиус, R

  2. генерировать N случайных точек на окружности окружности радиуса R

  3. двигаться по кругу и рисовать прямые линии между соседними точками на окружности.


Как сказали @templatetypedef и @MitchWheat, это легко сделать, генерируя N случайные углы и радиусы. Важно углов, иначе это будет не простой многоугольник. Обратите внимание, что я использую аккуратный трюк для рисования замкнутых кривых - я описал его в здесь. Кстати, полигоны могут быть вогнутая.

обратите внимание, что все эти полигоны будут в форме звезды. Создание более общего многоугольника-совсем не простая задача. Просто чтобы дать вам вкус проблемы-проверьте http://www.cosy.sbg.ac.at / ~held/projects/rpg/rpg.html и http://compgeom.cs.uiuc.edu/~jeffe/open/randompoly.html.

enter image description here

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