Étant donné un tableau de points x, y, comment puis-je trier les points de ce tableau dans le sens des aiguilles d'une montre (autour de leur point central moyen global)? Mon objectif est de transmettre les points à une fonction de création de ligne pour obtenir quelque chose d'assez "solide", aussi convexe que possible, sans intersection de lignes.
Pour ce que ça vaut, j'utilise Lua, mais tout pseudocode serait apprécié. Merci beaucoup pour toute aide!
Mise à jour: Pour référence, il s'agit du code Lua basé sur l'excellente réponse de Ciamej (ignore mon préfixe "app"):
function appSortPointsClockwise(points)
local centerPoint = appGetCenterPointOfPoints(points)
app.pointsCenterPoint = centerPoint
table.sort(points, appGetIsLess)
return points
end
function appGetIsLess(a, b)
local center = app.pointsCenterPoint
if a.x >= 0 and b.x < 0 then return true
elseif a.x == 0 and b.x == 0 then return a.y > b.y
end
local det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y)
if det < 0 then return true
elseif det > 0 then return false
end
local d1 = (a.x - center.x) * (a.x - center.x) + (a.y - center.y) * (a.y - center.y)
local d2 = (b.x - center.x) * (b.x - center.x) + (b.y - center.y) * (b.y - center.y)
return d1 > d2
end
function appGetCenterPointOfPoints(points)
local pointsSum = {x = 0, y = 0}
for i = 1, #points do pointsSum.x = pointsSum.x + points[i].x; pointsSum.y = pointsSum.y + points[i].y end
return {x = pointsSum.x / #points, y = pointsSum.y / #points}
end
Commencez par calculer le point central . Triez ensuite les points à l’aide de l’algorithme de tri que vous préférez, mais utilisez une routine de comparaison spéciale pour déterminer si un point est inférieur à l’autre.
Vous pouvez vérifier si un point (a) est à gauche ou à droite de l'autre (b) par rapport au centre par ce simple calcul:
det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y)
si le résultat est zéro, alors ils sont sur la même ligne depuis le centre, si c'est positif ou négatif, alors c'est d'un côté ou de l'autre, donc un point précédera l'autre . En l'utilisant, vous pouvez construire un relation inférieure à pour comparer les points et déterminer l'ordre dans lequel ils doivent apparaître dans le tableau trié. Mais vous devez définir où se trouve le début de cet ordre, je veux dire quel angle sera le premier (par exemple, la moitié positive de l’axe des x).
Le code de la fonction de comparaison peut ressembler à ceci:
bool less(point a, point b)
{
if (a.x - center.x >= 0 && b.x - center.x < 0)
return true;
if (a.x - center.x < 0 && b.x - center.x >= 0)
return false;
if (a.x - center.x == 0 && b.x - center.x == 0) {
if (a.y - center.y >= 0 || b.y - center.y >= 0)
return a.y > b.y;
return b.y > a.y;
}
// compute the cross product of vectors (center -> a) x (center -> b)
int det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y);
if (det < 0)
return true;
if (det > 0)
return false;
// points a and b are on the same line from the center
// check which point is closer to the center
int d1 = (a.x - center.x) * (a.x - center.x) + (a.y - center.y) * (a.y - center.y);
int d2 = (b.x - center.x) * (b.x - center.x) + (b.y - center.y) * (b.y - center.y);
return d1 > d2;
}
Cela ordonnera les points dans le sens des aiguilles d'une montre à partir de midi. Les points sur la même "heure" seront ordonnés à partir de ceux qui sont plus éloignés du centre.
Si vous utilisez des types entiers (qui ne sont pas vraiment présents dans Lua), vous devez vous assurer que les variables det, d1 et d2 sont d'un type pouvant contenir le résultat des calculs effectués.
Si vous voulez obtenir quelque chose qui semble solide, aussi convexe que possible, alors je suppose que vous recherchez un Convex Hull . Vous pouvez le calculer à l'aide de Graham Scan . Dans cet algorithme, vous devez également trier les points dans le sens des aiguilles d'une montre (ou dans le sens contraire) en partant d'un point pivot particulier. Ensuite, vous répétez des étapes simples à chaque fois en vérifiant si vous tournez à gauche ou à droite en ajoutant de nouveaux points à la coque convexe. Cette vérification est basée sur un produit croisé, comme dans la fonction de comparaison ci-dessus.
Modifier:
Ajout d’une instruction if supplémentaire if (a.y - center.y >= 0 || b.y - center.y >=0)
pour s’assurer que les points avec x = 0 et y négatif sont triés à partir de ceux qui sont plus éloignés du centre. Si vous ne vous souciez pas de l'ordre des points sur la même "heure", vous pouvez omettre cette instruction if et toujours renvoyer a.y > b.y
.
Correction de la première instruction if avec l'ajout de -center.x
et -center.y
.
Ajout de la deuxième instruction if (a.x - center.x < 0 && b.x - center.x >= 0)
. C'était un oubli évident qu'il manquait. Les instructions if pourraient être réorganisées maintenant car certaines vérifications sont redondantes. Par exemple, si la première condition de la première instruction if est fausse, la première condition de la seconde if doit être vraie. J'ai toutefois décidé de laisser le code tel quel pour des raisons de simplicité. Il est tout à fait possible que le compilateur optimise le code et produise le même résultat.
Ce que vous demandez, c'est un système appelé coordonnées polaires . La conversion de coordonnées cartésiennes en coordonnées polaires s’effectue facilement dans n’importe quelle langue. Les formules se trouvent dans cette section .
Je ne connais pas Lua, mais cette page semble proposer des extraits de code pour cette conversion.
Après conversion en coordonnées polaires, il suffit de trier par l’angle thêta.
Une approche alternative intéressante à votre problème serait de trouver le minimum approximatif au problème du voyageur de commerce (TSP), c'est-à-dire. la route la plus courte reliant tous vos points. Si vos points forment une forme convexe, ce devrait être la bonne solution, sinon, elle devrait quand même être belle (une forme "solide" peut être définie comme une forme ayant un faible ratio périmètre/surface, ce que nous optimisons ici) .
Vous pouvez utiliser n’importe quelle implémentation d’un optimiseur pour le TSP, et je suis sûr que vous pouvez en trouver une tonne dans la langue de votre choix.
Une autre version (retourne vrai si a vient avant b dans le sens antihoraire):
bool lessCcw(const Vector2D ¢er, const Vector2D &a, const Vector2D &b) const
{
// Computes the quadrant for a and b (0-3):
// ^
// 1 | 0
// ---+-->
// 2 | 3
const int dax = ((a.x() - center.x()) > 0) ? 1 : 0;
const int day = ((a.y() - center.y()) > 0) ? 1 : 0;
const int qa = (1 - dax) + (1 - day) + ((dax & (1 - day)) << 1);
/* The previous computes the following:
const int qa =
( (a.x() > center.x())
? ((a.y() > center.y())
? 0 : 3)
: ((a.y() > center.y())
? 1 : 2)); */
const int dbx = ((b.x() - center.x()) > 0) ? 1 : 0;
const int dby = ((b.y() - center.y()) > 0) ? 1 : 0;
const int qb = (1 - dbx) + (1 - dby) + ((dbx & (1 - dby)) << 1);
if (qa == qb) {
return (b.x() - center.x()) * (a.y() - center.y()) < (b.y() - center.y()) * (a.x() - center.x());
} else {
return qa < qb;
}
}
Ceci est plus rapide, car le compilateur (testé sur Visual C++ 2015) ne génère pas de saut pour calculer dax, jour, dbx, dby. Voici la sortie Assembly du compilateur:
; 28 : const int dax = ((a.x() - center.x()) > 0) ? 1 : 0;
vmovss xmm2, DWORD PTR [ecx]
vmovss xmm0, DWORD PTR [edx]
; 29 : const int day = ((a.y() - center.y()) > 0) ? 1 : 0;
vmovss xmm1, DWORD PTR [ecx+4]
vsubss xmm4, xmm0, xmm2
vmovss xmm0, DWORD PTR [edx+4]
Push ebx
xor ebx, ebx
vxorps xmm3, xmm3, xmm3
vcomiss xmm4, xmm3
vsubss xmm5, xmm0, xmm1
seta bl
xor ecx, ecx
vcomiss xmm5, xmm3
Push esi
seta cl
; 30 : const int qa = (1 - dax) + (1 - day) + ((dax & (1 - day)) << 1);
mov esi, 2
Push edi
mov edi, esi
; 31 :
; 32 : /* The previous computes the following:
; 33 :
; 34 : const int qa =
; 35 : ( (a.x() > center.x())
; 36 : ? ((a.y() > center.y()) ? 0 : 3)
; 37 : : ((a.y() > center.y()) ? 1 : 2));
; 38 : */
; 39 :
; 40 : const int dbx = ((b.x() - center.x()) > 0) ? 1 : 0;
xor edx, edx
lea eax, DWORD PTR [ecx+ecx]
sub edi, eax
lea eax, DWORD PTR [ebx+ebx]
and edi, eax
mov eax, DWORD PTR _b$[esp+8]
sub edi, ecx
sub edi, ebx
add edi, esi
vmovss xmm0, DWORD PTR [eax]
vsubss xmm2, xmm0, xmm2
; 41 : const int dby = ((b.y() - center.y()) > 0) ? 1 : 0;
vmovss xmm0, DWORD PTR [eax+4]
vcomiss xmm2, xmm3
vsubss xmm0, xmm0, xmm1
seta dl
xor ecx, ecx
vcomiss xmm0, xmm3
seta cl
; 42 : const int qb = (1 - dbx) + (1 - dby) + ((dbx & (1 - dby)) << 1);
lea eax, DWORD PTR [ecx+ecx]
sub esi, eax
lea eax, DWORD PTR [edx+edx]
and esi, eax
sub esi, ecx
sub esi, edx
add esi, 2
; 43 :
; 44 : if (qa == qb) {
cmp edi, esi
jne SHORT $LN37@lessCcw
; 45 : return (b.x() - center.x()) * (a.y() - center.y()) < (b.y() - center.y()) * (a.x() - center.x());
vmulss xmm1, xmm2, xmm5
vmulss xmm0, xmm0, xmm4
xor eax, eax
pop edi
vcomiss xmm0, xmm1
pop esi
seta al
pop ebx
; 46 : } else {
; 47 : return qa < qb;
; 48 : }
; 49 : }
ret 0
$LN37@lessCcw:
pop edi
pop esi
setl al
pop ebx
ret 0
?lessCcw@@YA_NABVVector2D@@00@Z ENDP ; lessCcw
Prendre plaisir.
- y = |a * b| , x = a . b
- Atan2(y , x)...............................gives angle between -PI to + PI in radians
- (Input % 360 + 360) % 360................to convert it from 0 to 2PI in radians
- sort by adding_points to list_of_polygon_verts by angle we got 0 to 360
Enfin, vous obtenez des verts triés Anticlockwize
list.Reverse () .................. Clockwise_order