web-dev-qa-db-fra.com

Algorithme pour générer un polygone 2D aléatoire

Je ne sais pas comment aborder ce problème. Je ne suis pas sûr de la complexité d'une tâche. Mon objectif est d'avoir un algorithme qui génère n'importe quel polygone. Ma seule exigence est que le polygone ne soit pas complexe (c'est-à-dire que les côtés ne se coupent pas). J'utilise Matlab pour faire les calculs mais tout ce qui est abstrait est le bienvenu.

Une aide/direction?

ÉDITER:

Je pensais davantage au code qui pourrait générer n'importe quel polygone, même des choses comme ça:

enter image description here

36
s5s

Il y a une bonne façon de faire ce que vous voulez en profitant des classes MATLAB DelaunayTri et TriRep et des différentes méthodes qu'elles utilisent pour manipulation de maillages triangulaires. Le code ci-dessous suit ces étapes pour créer un arbitraire simple polygone :

  • Générez un nombre de points aléatoires égal au nombre de côtés souhaité plus un facteur de fudge. Le facteur de fudge garantit que, quel que soit le résultat de la triangulation, nous devrions avoir suffisamment de facettes pour pouvoir couper le maillage triangulaire jusqu'à un polygone avec le nombre de côtés souhaité.

  • Créez une triangulation de Delaunay des points, résultant en un polygone convexe qui est construit à partir d'une série de facettes triangulaires.

  • Si la limite de la triangulation a plus d'arêtes que souhaité, choisissez une facette triangulaire aléatoire sur l'arête qui a un sommet unique (c'est-à-dire que le triangle ne partage qu'une arête avec le reste de la triangulation). La suppression de cette facette triangulaire réduira le nombre d'arêtes limites.

  • Si la limite de la triangulation a moins d'arêtes que souhaité ou si l'étape précédente n'a pas pu trouver de triangle à supprimer, choisissez une facette triangulaire aléatoire sur l'arête qui n'a qu'une seule de ses arêtes sur la frontière de triangulation. La suppression de cette facette triangulaire augmentera le nombre d'arêtes limites.

  • Si aucune facette triangulaire ne correspond aux critères ci-dessus, affichez un avertissement qu'un polygone avec le nombre de côtés souhaité n'a pas pu être trouvé et renvoyez les coordonnées x et y de la limite de triangulation actuelle. Sinon, continuez à supprimer les facettes triangulaires jusqu'à ce que le nombre d'arêtes souhaité soit atteint, puis retournez les coordonnées x et y de la limite de triangulation.

Voici la fonction résultante:

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

Et voici quelques exemples de résultats:

enter image description here

Les polygones générés peuvent être soit convexes soit concaves , mais pour un plus grand nombre de côtés souhaités, ils seront presque certainement concaves. Les polygones sont également générés à partir de points générés de manière aléatoire dans un carré unitaire, de sorte que les polygones avec un plus grand nombre de côtés ressemblent généralement à une limite "carrée" (comme l'exemple en bas à droite ci-dessus avec le polygone à 50 côtés). Pour modifier cette forme de délimitation générale, vous pouvez changer la façon dont les points initiaux x et y sont choisis au hasard (c'est-à-dire à partir d'une distribution gaussienne, etc.).

24
gnovice

J'ai pris l'idée de @MitchWheat et @ templatetypedef d'échantillonner des points sur un cercle et je suis allé un peu plus loin.

Dans mon application, je dois pouvoir contrôler à quel point les polygones sont étranges, c'est-à-dire commencer par des polygones réguliers et au fur et à mesure que je monte les paramètres, ils deviennent de plus en plus chaotiques. L'idée de base est celle énoncée par @templatetypedef; faire le tour du cercle en faisant un pas aléatoire angular à chaque fois, et à chaque pas mettre un point dans un rayon aléatoire. Dans les équations je génère le angular étapes comme equations for the angles and radii of the vertices

où theta_i et r_i donnent l'angle et le rayon de chaque point par rapport au centre, U (min, max) tire un nombre aléatoire d'une distribution uniforme, et N (mu, sigma) tire un nombre aléatoire d'une distribution gaussienne et clip (x, min, max) définit une valeur dans une plage. Cela nous donne deux paramètres vraiment sympas pour contrôler à quel point les polygones sont sauvages - epsilon que j'appellerai irrégularité contrôle si les points sont uniformément espacés angulairement autour du cercle, et sigma que j'appellerai spikeyness qui contrôle la variation des points par rapport au cercle de rayon r_ave. Si vous définissez ces deux valeurs sur 0, vous obtenez des polygones parfaitement réguliers, si vous les augmentez, les polygones deviennent plus fous.

J'ai fouetté cela rapidement en python et j'ai obtenu des trucs comme ceci: some polygons I generated

Voici le code complet 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 voici du code pour créer une image d'un polygone à partir d'une liste de sommets.

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.
29
Mike Ounsworth

Pour un polygone 2D convexe (totalement hors du haut de ma tête):

  1. Génère un rayon aléatoire, R

  2. Générer N points aléatoires sur la circonférence d'un cercle de rayon R

  3. Déplacez-vous autour du cercle et tracez des lignes droites entre les points adjacents du cercle.

12
Mitch Wheat

Comme l'ont dit @templatetypedef et @MitchWheat, il est facile de le faire en générant N angles et rayons aléatoires. Il est important de trier les angles, sinon ce ne sera pas un simple polygone. Notez que j'utilise une astuce pour dessiner des courbes fermées - je l'ai décrit dans ici . Par ailleurs, les polygones peuvent être concaves .

Notez que tous ces polygones seront en forme d'étoile. La génération d'un polygone plus général n'est pas du tout un problème simple. Juste pour vous donner un aperçu du problème - consultez http://www.cosy.sbg.ac.at/~held/projects/rpg/rpg.html et 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
6
Andrey Rubshtein

Voici un port de travail pour Matlab de la solution Mike Ounsworth. Je ne l'ai pas optimisé pour matlab. Je pourrais mettre à jour la solution plus tard pour cela.

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
1
MosGeo