我有一组点。我想把它们分成两个不同的集合。为此,我选择两个点(a 和 b),并在它们之间画出一个虚拟的直线。现在我想把线左侧的所有点放入一个集合中,将右侧的点放入另一个集合中。
如何判断任何给定的点 z 是属于左边集合还是右边集合?我尝试计算 a-z-b 之间的角度 - 小于180度的角度位于右手边,大于180度的角度位于左手边 - 但由于ArcCos的定义,计算出的角度总是小于180度。是否有公式可以计算大于180度的角度(或任何其他选择左或右侧的公式)?
我有一组点。我想把它们分成两个不同的集合。为此,我选择两个点(a 和 b),并在它们之间画出一个虚拟的直线。现在我想把线左侧的所有点放入一个集合中,将右侧的点放入另一个集合中。
如何判断任何给定的点 z 是属于左边集合还是右边集合?我尝试计算 a-z-b 之间的角度 - 小于180度的角度位于右手边,大于180度的角度位于左手边 - 但由于ArcCos的定义,计算出的角度总是小于180度。是否有公式可以计算大于180度的角度(或任何其他选择左或右侧的公式)?
尝试使用叉积的以下代码。给定一条线段a--b和点c:
public bool isLeft(Point a, Point b, Point c) {
return (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x) > 0;
}
如果公式等于0,则点共线(c
与a-b对齐)。
如果直线是水平的,则如果点在直线上方,则返回true。
return (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);
,但编译器可能会自动优化它。 - Nicu Stiurca使用向量 (AB,AM)
的行列式符号,其中查询点为 M(X,Y)
:
position = sign((Bx - Ax) * (Y - Ay) - (By - Ay) * (X - Ax))
这一行上是0
,一侧是+1
,另一侧是-1
。
你需要查看行列式的符号:
| x2-x1 x3-x1 |
| y2-y1 y3-y1 |
对于位于一侧的点,它将是正数;对于另一侧的点则是负数(对于落在该直线上的点,则为零)。
向量(y1-y2, x2-x1)
垂直于直线,且始终指向右边(如果您的平面方向与我的方向不同,则始终指向左边)。
然后,您可以计算该向量和(x3-x1, y3-y1)
的点积,以确定该点是否与垂直向量在同一侧直线上(点积 > 0
)。
使用 直线方程 ab,获取与待排序点在相同y坐标处的直线上的x坐标。
nearlyEqual(double,double)
会在两个数字非常接近时返回true。/*
* @return integer code for which side of the line ab c is on. 1 means
* left turn, -1 means right turn. Returns
* 0 if all three are on a line
*/
public static int findSide(
double ax, double ay,
double bx, double by,
double cx, double cy) {
if (nearlyEqual(bx-ax,0)) { // vertical line
if (cx < bx) {
return by > ay ? 1 : -1;
}
if (cx > bx) {
return by > ay ? -1 : 1;
}
return 0;
}
if (nearlyEqual(by-ay,0)) { // horizontal line
if (cy < by) {
return bx > ax ? -1 : 1;
}
if (cy > by) {
return bx > ax ? 1 : -1;
}
return 0;
}
double slope = (by - ay) / (bx - ax);
double yIntercept = ay - ax * slope;
double cSolution = (slope*cx) + yIntercept;
if (slope != 0) {
if (cy > cSolution) {
return bx > ax ? 1 : -1;
}
if (cy < cSolution) {
return bx > ax ? -1 : 1;
}
return 0;
}
return 0;
}
这是单元测试:
@Test public void testFindSide() {
assertTrue("1", 1 == Utility.findSide(1, 0, 0, 0, -1, -1));
assertTrue("1.1", 1 == Utility.findSide(25, 0, 0, 0, -1, -14));
assertTrue("1.2", 1 == Utility.findSide(25, 20, 0, 20, -1, 6));
assertTrue("1.3", 1 == Utility.findSide(24, 20, -1, 20, -2, 6));
assertTrue("-1", -1 == Utility.findSide(1, 0, 0, 0, 1, 1));
assertTrue("-1.1", -1 == Utility.findSide(12, 0, 0, 0, 2, 1));
assertTrue("-1.2", -1 == Utility.findSide(-25, 0, 0, 0, -1, -14));
assertTrue("-1.3", -1 == Utility.findSide(1, 0.5, 0, 0, 1, 1));
assertTrue("2.1", -1 == Utility.findSide(0,5, 1,10, 10,20));
assertTrue("2.2", 1 == Utility.findSide(0,9.1, 1,10, 10,20));
assertTrue("2.3", -1 == Utility.findSide(0,5, 1,10, 20,10));
assertTrue("2.4", -1 == Utility.findSide(0,9.1, 1,10, 20,10));
assertTrue("vertical 1", 1 == Utility.findSide(1,1, 1,10, 0,0));
assertTrue("vertical 2", -1 == Utility.findSide(1,10, 1,1, 0,0));
assertTrue("vertical 3", -1 == Utility.findSide(1,1, 1,10, 5,0));
assertTrue("vertical 3", 1 == Utility.findSide(1,10, 1,1, 5,0));
assertTrue("horizontal 1", 1 == Utility.findSide(1,-1, 10,-1, 0,0));
assertTrue("horizontal 2", -1 == Utility.findSide(10,-1, 1,-1, 0,0));
assertTrue("horizontal 3", -1 == Utility.findSide(1,-1, 10,-1, 0,-9));
assertTrue("horizontal 4", 1 == Utility.findSide(10,-1, 1,-1, 0,-9));
assertTrue("positive slope 1", 1 == Utility.findSide(0,0, 10,10, 1,2));
assertTrue("positive slope 2", -1 == Utility.findSide(10,10, 0,0, 1,2));
assertTrue("positive slope 3", -1 == Utility.findSide(0,0, 10,10, 1,0));
assertTrue("positive slope 4", 1 == Utility.findSide(10,10, 0,0, 1,0));
assertTrue("negative slope 1", -1 == Utility.findSide(0,0, -10,10, 1,2));
assertTrue("negative slope 2", -1 == Utility.findSide(0,0, -10,10, 1,2));
assertTrue("negative slope 3", 1 == Utility.findSide(0,0, -10,10, -1,-2));
assertTrue("negative slope 4", -1 == Utility.findSide(-10,10, 0,0, -1,-2));
assertTrue("0", 0 == Utility.findSide(1, 0, 0, 0, -1, 0));
assertTrue("1", 0 == Utility.findSide(0,0, 0, 0, 0, 0));
assertTrue("2", 0 == Utility.findSide(0,0, 0,1, 0,2));
assertTrue("3", 0 == Utility.findSide(0,0, 2,0, 1,0));
assertTrue("4", 0 == Utility.findSide(1, -2, 0, 0, -1, 2));
}
fx = x_2 - x_1
fy = y_2 - y_1
根据以下测试的符号,您可以测试基于点(px,py)
的一侧。
var torque = fx*(py-y_1)-fy*(px-x_1)
if torque>0 then
"point on left side"
else if torque <0 then
"point on right side"
else
"point on line"
end if
首先检查您是否有一个垂直线:
if (x2-x1) == 0
if x3 < x2
it's on the left
if x3 > x2
it's on the right
else
it's on the line
m = (y2-y1)/(x2-x1)
。y - y1 = m*(x-x1) + y1
。为了解释方便,可以将其简化为斜截式(在算法中不是必需的):y = mx+b
。(x3, y3)
代入x
和y
。以下是一些伪代码详细说明应该发生的事情:if m > 0
if y3 > m*x3 + b
it's on the left
else if y3 < m*x3 + b
it's on the right
else
it's on the line
else if m < 0
if y3 < m*x3 + b
it's on the left
if y3 > m*x3+b
it's on the right
else
it's on the line
else
horizontal line; up to you what you do
(defn is-left? [line point]
(let [[[x1 y1] [x2 y2]] (sort line)
[x-pt y-pt] point]
(> (* (- x2 x1) (- y-pt y1)) (* (- y2 y1) (- x-pt x1)))))
使用示例:
(is-left? [[-3 -1] [3 1]] [0 10])
true
这就是说,点 (0, 10) 在由 (-3, -1) 和 (3, 1) 确定的直线左侧。
注意:此实现解决了其他实现(到目前为止)未能解决的问题!在确定构成直线的点时,顺序很重要。也就是说,它是一个“有向直线”,在某种意义上是有方向的。因此,使用上述代码,此调用还会产生true
的结果:
(is-left? [[3 1] [-3 -1]] [0 10])
true
(sort line)
(is-left? [[1 1] [3 1]] [10 1])
false