web-dev-qa-db-ja.com

時計回りにポイントを並べ替えますか?

X、yポイントの配列が与えられた場合、この配列のポイントを時計回りの順序で(全体の平均中心点の周りに)ソートするにはどうすればよいですか?私の目標は、ポイントを線作成関数に渡して、線が交差せずに可能な限り凸面に近い「立体」に見えるものにすることです。

それが価値があるもののために、私はLuaを使用していますが、どんな擬似コードでも大歓迎です。助けてくれてありがとう!

更新:参考のために、これはCiamejの優れた答えに基づいたLuaコードです(私の「アプリ」プレフィックスを無視します):

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
143
Philipp Lenssen

まず、中心点を計算します。次に、好きな並べ替えアルゴリズムを使用してポイントを並べ替えますが、特別な比較ルーチンを使用して、あるポイントが他のポイントよりも小さいかどうかを判断します。

次の簡単な計算により、1つの点(a)が中心を基準にして他の点(b)の左側にあるか右側にあるかを確認できます。

det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y)

結果がゼロの場合、それらは中心から同じ線上にあり、正または負の場合、一方または他方にあるため、一方の点が他方の前になります。これを使用して、より小さい関係を構築してポイントを比較し、ソートされた配列に表示される順序を決定できます。しかし、あなたはその順序の始まりがどこであるかを定義する必要があります、私はどの角度が最初のものになるかを意味します(例えば、x軸の正の半分)。

比較関数のコードは次のようになります。

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;
}

これにより、12時から時計回りにポイントが並べられます。同じ「時間」のポイントは、中心から遠いポイントから順番に並べられます。

整数型(Luaには実際に存在しない)を使用する場合、det、d1、d2変数は、実行された計算の結果を保持できる型であることを保証する必要があります。

可能な限り凸状に見えるものを実現したい場合は、 Convex Hull を探していると思います。 Graham Scan を使用して計算できます。このアルゴリズムでは、特別なピボットポイントから開始して、ポイントを時計回り(または反時計回り)に並べ替える必要もあります。次に、左または右に曲がって凸包に新しいポイントを追加するかどうかをチェックするたびに、単純なループステップを繰り返します。このチェックは、上記の比較関数と同様に外積に基づいています。

編集:

Ifステートメントif (a.y - center.y >= 0 || b.y - center.y >=0)をもう1つ追加して、x = 0および負のyを持つポイントが、中心から遠いポイントから順にソートされるようにします。同じ「時間」のポイントの順序を気にしない場合は、このifステートメントを省略して、常にa.y > b.yを返すことができます。

-center.xおよび-center.yを追加した最初のifステートメントを修正しました。

2番目のifステートメント(a.x - center.x < 0 && b.x - center.x >= 0)を追加しました。欠落していることは明らかな見落としでした。一部のチェックが冗長であるため、ifステートメントを再編成できるようになりました。たとえば、最初のifステートメントの最初の条件がfalseの場合、2番目のifの最初の条件はtrueでなければなりません。ただし、単純にするために、コードはそのままにしておくことにしました。コンパイラーがコードを最適化し、とにかく同じ結果を生成する可能性は十分にあります。

177
ciamej

あなたが求めているのは、 極座標 として知られるシステムです。デカルト座標から極座標への変換は、どの言語でも簡単に実行できます。数式は このセクション にあります。

私はLuaを知りませんが、 このページ はこの変換のコードスニペットを提供しているようです。

極座標に変換した後、角度θでソートするだけです。

18
Iterator

問題に対する興味深い代替アプローチは、巡回セールスマン問題(TSP)の近似最小値を見つけることです。すべてのポイントを結ぶ最短ルート。ポイントが凸形状を形成している場合、それは正しいソリューションである必要があります。そうでなければ、それでも見栄えがよくなります(「ソリッド」形状は、ここで最適化する境界/面積比が低いものとして定義できます) 。

TSPのオプティマイザーの実装はどれでも使用できますが、選択した言語で1トンを見つけることができると確信しています。

17
static_rtti

別のバージョン(aがbの前に反時計回りに来る場合、trueを返します):

    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;
       } 
    }

これは、コンパイラ(Visual C++ 2015でテスト済み)がdax、day、dbx、dbyを計算するためのジャンプを生成しないためです。ここで、コンパイラからの出力アセンブリ:

; 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

楽しい。

3
AGPX
  • vector3 a =新しいvector3(1、0、0).............. w.r.t X_axis
  • vector3 b = any_point-中心;
- 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

最後に、Anticlockwizeソートされた頂点を取得します

list.Reverse().................. Clockwise_order

0
Pavan