web-dev-qa-db-fra.com

Trier les points dans le sens des aiguilles d'une montre?

É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

139
Philipp Lenssen

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.

173
ciamej

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.

17
Iterator

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.

17
static_rtti

Une autre version (retourne vrai si a vient avant b dans le sens antihoraire):

    bool lessCcw(const Vector2D &center, 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.

2
AGPX
  • vecteur3 a = nouveau vecteur3 (1, 0, 0) .............. w.r.t X_axis
  • vecteur3 b = any_point - Center;
- 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

0
Pavan