假设线段的两个端点分别为:A、B,另外一点为 P。
问题:判断 P 点是否位于线段 AB 上。
方法1:通过线段确定的直线方程判断。
(1) 在二维空间中,三点坐标表示为:A(xa,ya), B(xb,yb), P(xp,yp)。
AB确定的直线方程(点斜式)为:
k = (yb - ya) * 1 / (xb - xa)
y = k * (x - xa) + ya
P点若位于直线上,首先应该满足直线的方程:
yp = k * (xp - xa) + ya。
若上式成立,说明A、B、P共线,接下来只需判断 P 位于 A 和 B 中间,即
(xa < xb && xa <= xp <= xb) || (xa > xb && xa >= xp >= xb)
(ya < yb && ya <= yp <= yb) || (ya > yb && ya >= yp >= yb)
同时成立。否则,P 不在线段AB上。
注意到直线方程中有除法运算,需做一步AB是否重合的判断,否则除数为零。上式稍作变形,便可去掉除法运算,即:
(y - ya) / (yb - ya) = (x - xa) / (xb - xa)
进而
(y - ya) * (xb - xa) - (yb - ya) * (x - xa) = 0。
上式成立的条件,(x,y)与A或B点重合,或者(x,y)、A、B三点共线。接下来再做 P 是否位于AB中间的判断即可。
y = k * (x - xa) + ya
P点若位于直线上,首先应该满足直线的方程:
yp = k * (xp - xa) + ya。
若上式成立,说明A、B、P共线,接下来只需判断 P 位于 A 和 B 中间,即
(xa < xb && xa <= xp <= xb) || (xa > xb && xa >= xp >= xb)
(ya < yb && ya <= yp <= yb) || (ya > yb && ya >= yp >= yb)
同时成立。否则,P 不在线段AB上。
(y - ya) / (yb - ya) = (x - xa) / (xb - xa)
进而
(y - ya) * (xb - xa) - (yb - ya) * (x - xa) = 0。
上式成立的条件,(x,y)与A或B点重合,或者(x,y)、A、B三点共线。接下来再做 P 是否位于AB中间的判断即可。
上述变形后的等式,其实表示的是二维平面中向量的一种运算:叉积(cross product),
A x B = xa*yb - ya*xb
由直线方程的启示,不难理解叉积为零,则可判定两向量共线或者一个向量为零。
AB确定的直线方程相应变为:
(y - ya) / (yb - ya) = (x - xa) / (xb - xa) = (z - za) / (zb - za)。
按(1)中所述,将P点代入直线方程成立后,只需再多做一步 z 轴坐标的判断:
(za < zb && za <= zp <= zb) || (za > zb && za >= zp >= zb)。
所有条件同时成立,则 P 点位于线段 AB 上。
二维空间中的叉积定义已经介绍,三维空间的描述可以参考「4」,叉积可以作为判定两个向量是否共线的一个工具,三维空间中的叉积结果会得到一个新的向量。
先求得两个向量:v1 = B - A, v2 = P - A。然后 v1 和 v2 做叉积:v = v1 x v2,如果 v 是零向量,即 v 的x、y、z分量都为 0,则v1 和 v2 至少有一个为零或者两个向量共线。
if v2 == 0,then P == A;
else if v1 == 0 then A == B AND P doesn't lie in AB ; // v2 != 0
else P,A,B are colinear;
end
如果判定共线后,接下来判断 P 是否位于 AB 内的方式同方法1.
方法3:面积法。
如果A、B、P三点组成的三角形的面积为零,那么可以得出三点共线的结论。
三角形面积可以使用海伦公式直接得出,另外上述v1和v2叉积得到的向量的长度的一半,也是三点组成的三角形的面积(参考叉积的定义)。
不过这两种方式都涉及到开根号的运算,代价较高。
另外在判定了三点共线后,v1 和 v2 的夹角要么0度,要么180度,点积的符号可以用来确定P点的位置,如:v1 ・ v2 < 0。那么,v1 和 v2 夹角为180,反向,P点位于AB之外。
接下来再以 B 点为基准做两个向量v1’ = A - B, v2’ = P - B,若v1’ ・ v2’ > 0,即可断定 P 位于线段AB上。
如果不以 B 为基准做两个向量,可以通过长度来判断,由内积的定义可以知道:
v1 ・ v2 = |v1| |v2| cos<v1, v2> = |v1| |v2| cos(0) = |v1| |v2|。
如果v1 ・ v2 > |v1|*|v1| = v1 ・ v1 => |v2| > |v1|,即PA的长度大于BA,又PA和BA同向,那么P点一定位于AB之外。
不过上述的点积判定符号的方法,包含了许多乘法的运算,进行坐标比较的方法反而更加直接简单。综上所述,方法2更值得推荐。
[1] http://stackoverflow.com/questions/328107/how-can-you-determine-a-point-is-between-two-other-points-on-a-line-segment
[2] http://stackoverflow.com/questions/7050186/find-if-point-lay-on-line-segment
- 上一篇:有关struct的构造函数
-
这是一个三维空间中的平面问题(三角形确定一个平面),假设三角形的三个顶点为A(xa, ya, za)、B(xb, yb, zb)、C(xc, yc, zc),另外一个顶点为P(xp, yp, zp)。问题:判断顶点 P 是否位于ABC组成的三角形上(内部和边界)。
方法1:效率较低的方法。
(1) 利用面积判断。如果顶点落在三角形上,那么顶点P分别和ABC三点连接后组成的三个小三角形的面积之和一定等于三角形ABC的面积,否则P位于三角形ABC之外。三角形面积的求法有海伦公式,叉积法等。
(2) 利用角度判断。连接顶点P和三角形的三个顶点ABC,每两条边的夹角之和如果等于 2*PI,则 P 位于三角形上,否则位于三角形外。两个向量之间的夹角可以利用向量的点积来求,不过要求反三角函数。
方法2:同侧检测:判定顶点是否位于三角形三条边的同侧。
如果顶点P位于三角形的内部,那么按顺时针或者逆时针将三角形的三条边形成的向量首尾相接(AB, BC, CA),顶点P一定是位于三个向量的同侧。如果顶点相对于三向量的位置发生了改变,比如逆时针时由左侧变为了右侧,则顶点一定位于三角形的外部。
如何判定顶点P位于向量的哪侧呢?我们知道向量的叉积是一个新的向量,具有方向。所以,通过向量叉积即可确定一个顶点位于一个向量的哪一侧。
那么又如何确定顺时针还是逆时针呢?其实,不需考虑顺时针还是逆时针。只要顶点 P 与三角形除却判断的向量(比如AB)外余下的那个顶点(比如C)位于同一侧,就可以判定P的相对位置。
接下来就是如何确定两点位于一个向量的同侧?因为向量的点积可以确定两个向量夹角的大小,当两个向量的点积大于0时,夹角小于 PI/2,可认为同向,否则两向量反向。于是,确定两个顶点P、C是否位于线段AB同侧的方法为:构造三个向量:v1 = B - A,v2 = P - A,v3 = C - A;判断P、C相对于AB的方向,做叉积:vp = v1 x v2, vc = v3 x v1;判断vp、vc是否同向,做点积:d = vp ・ vc;如果 d >= 0,说明叉积向量同向,进而推出P、C位于AB的同侧。
最后,如果针对三角形的三条边,P都和另外的一个顶点同向的话,那么P一定位于三角形上。
所以整个算法的流程如下:function SameSide(p1,p2, a,b)cp1 = CrossProduct(b-a, p1-a)cp2 = CrossProduct(b-a, p2-a)if DotProduct(cp1, cp2) >= 0 then return trueelse return false
function PointInTriangle(p, a,b,c)if SameSide(p,a, b,c) and SameSide(p,b, a,c) and SameSide(p,c, a,b) thenreturn trueelsereturn false算法没有开根号和反三角函数的求解,所以效率相对较高。
方法3:重心法(barycentric technique)
在三角形确定的平面上的任意一点P,都可以使用一个重心坐标的形式来表示P的坐标:P = uA + vB + wC,u + v + w = 1;消去w:P - C = u(A - C) + v(B - C)上式可以理解为,平面上的任意一个向量都能表示成两个不共线的向量的线性形式。如果P位于三角形上,那么u、v需要满足:0 <= u, v <= 1 && u + v <= 1为简化书写,我们做以下标记:v2 = P - C, v0 = A - C, v1 = B - C进而v2 = u*v0 + v*v1接下来就是求得u、v。因为v0、v1和v2是已知向量,可以直接利用坐标来求解。但是三维空间中,可以得到三个(xyz)关于u、v的方程,而我们只需要两个方程即可,如何选择需要进一步的做些判断,如果选择方程组不合理可能无法解出u、v。
这里有个避免上述选择的做法,就是利用向量的点积,将向量转化为实数,方程两边同时点积一个向量:(v2) ・ v0 = (u * v0 + v * v1) ・ v0(v2) ・ v1 = (u * v0 + v * v1) ・ v1进而v2 ・ v0 = u * (v0 ・ v0) + v * (v1 ・ v0)v2 ・ v1 = u * (v0 ・ v1) + v * (v1 ・ v1)利用线性方程组的求解方法可以得到u、v:u = ((v1・v1)(v2・v0)-(v1・v0)(v2・v1)) / ((v0・v0)(v1・v1) - (v0・v1)(v1・v0))v = ((v0・v0)(v2・v1)-(v0・v1)(v2・v0)) / ((v0・v0)(v1・v1) - (v0・v1)(v1・v0))
此处,看到出现了除法,不能保证除数不等于0。可以分析什么情况下除数为0,预先判断即可。(v0・v0)(v1・v1) - (v0・v1)(v1・v0) = 0上式中如果 v0 或 v1 等于 0,则三角形便会退化成线段或者点的情况,这变成了点位于线段上的问题。当v0、v1都不为0时,由内积公式,上式转变为:(v0・v1)(v0・v1) / ((v0・v0)(v1・v1))
= |v0|^2 * |v1|^2 * (cos<v0,v1> )^2 / (|v0|^2 * |v1|^2)
= (cos<v0,v1>)^2
= 1进而可以知道:<v0, v1> = 0 或者 <v0, v1> = PIv0和v1的夹角无论是哪一种情况,都表示v0和v1是共线的。剩下的又变成了顶点是否位于线段上的问题。
当 (v0・v0)(v1・v1) - (v0・v1)(v1・v0) != 0 时,便可求解u、v,进而确定P相对于三角形的位置。
算法的描述如下:function PointInTriangle(p, a,b,c)v0 = a - c; v1 = b -c; v2 = p - c;dot00 = dot(v0, v0); dot01 = dot(v0, v1); dot11 = dot(v1, v1);invDemon = dot00 * dot11 - dot01 * dot01;if (invDemon < epsilon && invDemon > -epsilon)return PointInLineSegment(p, a, b) || PointInLineSegment(p, a, c) || PointInLineSegment(p, b, c)invDemon = 1 / invDemon;dot02 = dot(v0, v2); dot12 = dot(v1, v2);u = (dot11*dot02 - dot01*dot12) * invDemon;v = (dot00*dot12 - dot01*dot02) * invDemon;return 0<= u、v <= 1 && u + v <= 1
顶点是否位于线段上的判定算法PointInLineSegment,参看前篇博文。
重心法相较方法2中的同侧比较运算要少,所以更高效些。详细讨论见[1].
参考: - 上一篇:判断顶点是否位于线段上