已知线段AB,求某点到该线段的距离。有三种情况,如图:
图中只画出两种,其实垂足在另一侧也是一样的。还有就是垂足与A或B共点,这两种情形可以按照前述两种情况任意之一处理。
当垂足在线段上时(包括垂足与A或B共点)
以A为起点B为终点构建向量u,以A为起点C为终点构建向量v
由向量叉积的定义容易得出:以向量u为底、向量v为侧边的平行四边形的面积。则
翻译成程序员的大白话就是三角形abc的面积=abs((Bx-Ax)*(Cy-Ay)-(By-Ay)*(Cx-Ax))/2
那么点到线段的距离就是
翻译下就是线段CD的长度=abs((Bx-Ax)*(Cy-Ay)-(By-Ay)*(Cx-Ax))/sqrt((Ax-Bx)^2+(Ay-By)^2)
程序中如果线段是固定位置的则应预先计算长度避免重复计算开方,进一步的,可以预先计算(1/sqrt((Ax-Bx)^2+(Ay-By)^2))用一式与之相乘避免除法运算减少性能损失。
当垂足不在线段上时
这种情形比较简单,只要确定在哪个端点一侧,计算该点到最近一侧端点的距离即可。
最重要的是如何区分属于哪种情形呢?
由向量点积的性质可知,将两个向量的起点对齐,如果构成的角度小于90度,点积大于0、角度大于90度时点积小于0。那么以B为起点C为终点,作向量w。注意是计算C到线段AB的距离,A为向量u的起点,B为终点。
上图u·v>0,u·w<0。
此时u·v<0,u·w<0
此时u·v>0,u·w>0
为了照顾程序员朋友给下公式吧
u·v=(Bx-Ax)*(Cx-Ax)+(By-Ay)*(Cy-Ay)
u·w=(Bx-Ax)*(Cx-Bx)+(By-Ay)*(Cy-By)
结论:
当u·v与u·w不同号时垂足在线段上,使用u与v的叉积除以u的长度得出距离。(点积为0时也可使用这个方法计算距离,使用端点计算亦可)
当u·v与u·w同时小于0时垂足在起点一侧,计算起点与该点的距离即可。
当u·v与u·w同时大于0时垂足在终点一侧,计算终点与该点的距离即可。
代码如下:(哪种语言也不能直接运行,姑且认为是java或者C++吧,都需要自己定义Point类和abs、sqrt方法,C++注意改成引用参数)
float pointToLine(Point s, Point e, Point p) {
float ux = e.x - s.x;
float uy = e.y - s.y;
float vx = p.x - s.x;
float vy = p.y - s.y;
float wx = p.x - e.x;
float wy = p.y - e.y;
float umv = ux * vx + uy * vy;
float umw = ux * wx + uy * wy;
if (umv * umw > 0) {
// 点的垂足不在线段上
if (umv > 0) {
// 垂足在终点一侧
return sqrt(wx * wx + wy * wy);
} else {
// 垂足在起点一侧
return sqrt(vx * vx + vy * vy);
}
} else {
// 点的垂足在线段上
return abs(ux * vy - uy * vx) / sqrt(ux * ux + uy * uy);
}
}
注意,如果线段长度是固定的,就应该将sqrt(ux*ux+uy*uy)缓存起来,它只表示长度,与位置无关。如果要计算距离平方可以将结果平方一下(好像有什么不对劲,不过已经少算一次开方了,乘一下还是可以接受的)。
另外由于浮点数精度的问题,算术运算的次数越多偏差越大,尤其是除法,所以想减少偏差就尽量少做除法。