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:
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:
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.).
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
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:
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.
Pour un polygone 2D convexe (totalement hors du haut de ma tête):
Génère un rayon aléatoire, R
Générer N points aléatoires sur la circonférence d'un cercle de rayon R
Déplacez-vous autour du cercle et tracez des lignes droites entre les points adjacents du cercle.
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 .
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
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