按顺时针顺序排序?

给定一个由 x,y 点组成的数组,我如何按顺时针方向(围绕它们的总平均中心点)对这个数组的点排序?我的目标是将这些点传递给一个直线创建函数,使其最终看起来相当“实心”,尽可能凸,没有直线相交。

不管怎样,我正在使用 Lua,但是任何伪代码都是值得的。

更新: 作为参考,这是基于 Ciamej 出色回答的 Lua 代码(忽略我的“ 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

104373 次浏览

首先,计算中心点。 然后使用你喜欢的任何排序算法对这些点进行排序,但要使用特殊的比较程序来确定一个点是否比另一个点少。

你可以通过这个简单的计算来检查一个点(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变量的类型能够保存执行的计算结果。

如果你想实现一些看起来坚实的东西,尽可能凸,那么我猜你正在寻找一个 凸壳。您可以使用 格雷厄姆扫描计算它。 在这个算法中,您还必须从一个特殊的枢轴点开始,顺时针(或逆时针)对这些点进行排序。然后你重复简单的循环步骤每次检查,如果你左转或右转向凸壳添加新的点,这个检查是基于一个十字乘积,就像在上面的比较函数。

编辑:

增加了一个 if 语句 if (a.y - center.y >= 0 || b.y - center.y >=0),以确保 x = 0和负 y 的点从距离中心较远的点开始排序。如果你不关心在同一个“小时”的点的顺序,你可以省略这个 If 语句,并且总是返回 a.y > b.y

通过添加 -center.x-center.y更正了第一个 if 语句。

添加了第二个 if 语句 (a.x - center.x < 0 && b.x - center.x >= 0)。很明显,它不见了。现在可以重新组织 if 语句,因为有些检查是多余的。例如,如果第一个 if 语句中的第一个条件为 false,那么第二个 if 的第一个条件必须为 true。但是,为了简单起见,我决定让代码保持原样。编译器很有可能优化代码并产生相同的结果。

你要的是一个叫 极坐标的系统。从笛卡尔坐标系到极坐标系的转换在任何语言中都很容易完成。这些公式可以在 这部分中找到。

在转换到极坐标后,只需按角度 θ 排序。

一个有趣的替代方法,您的问题将找到近似最小的旅行推销员问题(TSP) ,即。连接你所有要点的最短路线。如果你的点形成一个凸形,它应该是正确的解决方案,否则,它应该看起来仍然不错(一个“实心”形状可以定义为一个有一个低周长/面积比,这是我们在这里优化)。

您可以为 TSP 使用任何优化器的实现,我非常确信您可以在您所选择的语言中找到大量的优化器。

另一个版本(如果 a 出现在 b 前面,返回真逆时针方向) :

    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

好好享受吧。

  • Vector3a = new vector3(1,0,0) ... ... ... ... ... wr.t X _ ax. ..
  • Vector3b = 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


最后得到的是 Anticlockwize 排序的 Verts

顺时针顺序

下面是一种按时钟顺序对矩形顶点排序的方法。我修改了由 简易图片搜索提供的原始解决方案,并消除了 scipy 依赖关系。

import numpy as np


def pointwise_distance(pts1, pts2):
"""Calculates the distance between pairs of points


Args:
pts1 (np.ndarray): array of form [[x1, y1], [x2, y2], ...]
pts2 (np.ndarray): array of form [[x1, y1], [x2, y2], ...]


Returns:
np.array: distances between corresponding points
"""
dist = np.sqrt(np.sum((pts1 - pts2)**2, axis=1))
return dist


def order_points(pts):
"""Orders points in form [top left, top right, bottom right, bottom left].
Source: https://www.pyimagesearch.com/2016/03/21/ordering-coordinates-clockwise-with-python-and-opencv/


Args:
pts (np.ndarray): list of points of form [[x1, y1], [x2, y2], [x3, y3], [x4, y4]]


Returns:
[type]: [description]
"""
# sort the points based on their x-coordinates
x_sorted = pts[np.argsort(pts[:, 0]), :]


# grab the left-most and right-most points from the sorted
# x-roodinate points
left_most = x_sorted[:2, :]
right_most = x_sorted[2:, :]


# now, sort the left-most coordinates according to their
# y-coordinates so we can grab the top-left and bottom-left
# points, respectively
left_most = left_most[np.argsort(left_most[:, 1]), :]
tl, bl = left_most


# now that we have the top-left coordinate, use it as an
# anchor to calculate the Euclidean distance between the
# top-left and right-most points; by the Pythagorean
# theorem, the point with the largest distance will be
# our bottom-right point. Note: this is a valid assumption because
# we are dealing with rectangles only.
# We need to use this instead of just using min/max to handle the case where
# there are points that have the same x or y value.
D = pointwise_distance(np.vstack([tl, tl]), right_most)
    

br, tr = right_most[np.argsort(D)[::-1], :]


# return the coordinates in top-left, top-right,
# bottom-right, and bottom-left order
return np.array([tl, tr, br, bl], dtype="float32")


我知道这有点像一篇老文章,有一个很好的被接受的答案,但是我觉得我仍然可以贡献一些有用的东西。到目前为止,所有的答案基本上都使用 比较函数来比较两个点并确定它们的顺序,但是如果一次只使用一个点和 关键功能呢?

这不仅是可能的,而且产生的代码也非常紧凑。下面是使用 Python 内置排序函数的完整解决方案:

# Create some random points
num = 7
points = np.random.random((num, 2))
# Compute their center
center = np.mean(points, axis=0)


# Make arctan2 function that returns a value from [0, 2 pi) instead of [-pi, pi)
arctan2 = lambda s, c: angle if (angle := np.arctan2(s, c)) >= 0 else 2 * np.pi + angle


# Define the key function
def clockwise_around_center(point):
diff = point - center
rcos = np.dot(diff, center)
rsin = np.cross(diff, center)
return arctan2(rsin, rcos)


# Sort our points using the key function
sorted_points = sorted(points, key=clockwise_around_center)


如果这些点位于嵌入在3D 中的2D 平面上,那么这个答案也适用于3D。我们只需要在 rsin的计算中加上平面的法向量就可以了。例如。

rsin = np.dot([0,0,1], np.cross(diff, center))

如果这个平面的法向量是 e_z

这段代码的优点在于,它使用键函数一次只在一个点上工作。数量 rsin,如果你在一个系数水平上计算出来,它与接受者答案中的 det完全相同,除了我计算它在 point - centercenter之间,而不是在 point1 - centerpoint2 - center之间。但是这个量的几何意义是,半径乘以角度的正弦,因此我称这个变量为 rsin。点乘也是类似的,它是半径乘以角的余弦,因此称为 rcos

有人可能会说,这种解决方案使用 arctan2,因此不太干净。然而,我个人认为,使用键函数的清晰度超过了调用一个 trig 函数的需要。请注意,我更喜欢让 arctan2[0, 2 pi)返回一个值,因为当 point碰巧与 center相同时,我们得到的角度 0,因此它将是我们排序列表中的第一个点。这是一个可选择的选择。

为了理解为什么这个代码工作,关键的见解是,我们所有的点都被定义为箭头相对于原点,包括 center点本身。因此,如果我们计算 point - center,这相当于把箭头从 center的顶端,到 point的顶端,在原点。因此我们可以根据箭头指向 center的角度对箭头 point - center进行排序。