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
まず、中心点を計算します。次に、好きな並べ替えアルゴリズムを使用してポイントを並べ替えますが、特別な比較ルーチンを使用して、あるポイントが他のポイントよりも小さいかどうかを判断します。
次の簡単な計算により、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でなければなりません。ただし、単純にするために、コードはそのままにしておくことにしました。コンパイラーがコードを最適化し、とにかく同じ結果を生成する可能性は十分にあります。
問題に対する興味深い代替アプローチは、巡回セールスマン問題(TSP)の近似最小値を見つけることです。すべてのポイントを結ぶ最短ルート。ポイントが凸形状を形成している場合、それは正しいソリューションである必要があります。そうでなければ、それでも見栄えがよくなります(「ソリッド」形状は、ここで最適化する境界/面積比が低いものとして定義できます) 。
TSPのオプティマイザーの実装はどれでも使用できますが、選択した言語で1トンを見つけることができると確信しています。
別のバージョン(aがbの前に反時計回りに来る場合、trueを返します):
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;
}
}
これは、コンパイラ(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
楽しい。
- 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