点到线段的距离

之前编程上有个小错误,应该算点到线段的距离,结果算成了点到直线的距离,导致bug。今天就来记载一下,这两个的数学上的区别和代码上的差异。


点到线段最短距离的运算与点到直线的最短距离的运算二者之间存在一定的差别,即求点到线段最短距离时需要考虑参考点在沿线段方向的投影点是否在线段上,若在线段上才可采用点到直线距离公式,如图1所示。

具体算法主要有以下三种:

1、方法——经典算法

该算法直接用高中时所学习到的解析几何知识对点到线段的距离进行求解。其基本思想是先判断点在线段端点、点在线上等等的特殊情况,逐步的由特殊到一般,当忽略点在线段上的特殊情况时,判断点到线段方向的垂线是否落在线段上的方法是通过比较横纵坐标的方式来判断,最后把不同的判断情况用不同的几何方式来进行处理计算得出结果。

由上面叙述的基本思路可以知道这种算法虽然很容易理解和接受,但从算法的实用性的角度分析还是有很大的缺点的,首先是算法复杂,计算量巨大,大量的比较判断、距离计算、角度计算等等,实际应用中往往是需要求由大量线段组成的折线到某点的最短距离,如此用这样的算法计算量是不能想象的。其次经典算法中使用的一些简化运算的函数不利于语言的重新包装,如果想换编程语言的话,就比较麻烦了。

2、方法二——面积算法

该方法主要是先判断投影点是否在线段上,投影点在线段延长线上时,最短距离长度为点到端点的线段长度;当投影点在线段上时,先使用海伦公式计算三角形面积,再计算出三角形的高,即为最短距离。

运用面积算法求解点到线段最短距离思路很清晰,也很容易理解。从效率方面考虑,比如需要多次计算平方、根号,这对于大量数据进行运算是负担很重的。求面积就必须把三条边长全部求出,并且用到的海伦公式也需要进行开方运算,计算过程显得繁琐。

3、方法三——矢量算法

矢量算法过程清晰,如果具有一定的空间几何基础,则是解决此类问题时应优先考虑的方法。当需要计算的数据量很大时,这种方式优势明显。由于矢量具有方向性,故一些方向的判断直接根据其正负号就可以得知,使得其中的一些问题得以很简单的解决。用此方法考虑,我们只需要找到向量 方向上的投影,具体如下:

上面的 方向上的单位向量,其意义是给所求向量确定方向。是的两个向量的内积,且   ,其中θ为向量AP与AB之间的夹角。是向量长度。那么即为上图中线段AC的长度值,不带有方向性。此数值与上述表征方向的 整体构成有大小、有方向的新向量,即为 方向上的投影向量,C为投影点。根据得到的,由向量的方向性可知:

如果情况是上图(A)所示,那么0<r<1;

(B)所示的情况,那么r ≥1;

(C)所示的情况,那么得到r ≤0;

特殊情况如点在线段上、点在端点、点在线段延长线上等等的情况全部适用于此公式,只是作为特殊情况出现,无需另作讨论。这也是矢量算法思想的优势所在。

故根据r值的不同,最短距离                       

double PointToSegDist(double x, double y, double x1, double y1, double x2, double y2)
{
double cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1); //|AB*AP|:矢量乘
if (cross <= 0) return Math.Sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1)); //是|AP|:矢量的大小
  
double d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); //|AB|^2:矢量AB的大小的平方
if (cross >= d2) return Math.Sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2)); //是|BP|:矢量的大小
  
double r = cross / d2;  //相似三角形原理求出c点的坐标
double px = x1 + (x2 - x1) * r;
double py = y1 + (y2 - y1) * r;
return Math.Sqrt((x - px) * (x - px) + (py - y) * (py - y));
}

换一下变量名这样写:

double pointToSegDistance2D(glm::vec2 p, glm::vec2 l1, glm::vec2 l2)
{
	glm::vec2 line_vec = l2 - l1; //AB
	glm::vec2 point_vec = p - l1; //AP
	double c = glm::dot(line_vec, point_vec); //|AB*AP|
	if (c <= 0) return (glm::length(point_vec)); //|AP|

	double d = pow(glm::length(line_vec), 2); //|AB|^2
	if (c >= d) return (glm::length(p - l2)); //|BP|

	double r = c / d; //相似三角形求出c点的坐标
	double px = l1.x + (l2.x - l1.x)*r;
	double py = l1.y + (l2.y - l1.y)*r;
	glm::vec2 p_shadow = glm::vec2(px, py); //投影点c

	return (glm::length(p - p_shadow)); //|CP|


}

如果是点到直线的距离,那就不用考虑那么多了:

double pointToLineDistance2D(glm::vec2 p, glm::vec2 l1, glm::vec2 l2)
{
	assert(p != l1&&p != l2);
	glm::vec2 line_vec = l2 - l1; //AB
	glm::vec2 point_vec = p - l1; //AP
	double d = glm::dot(line_vec, point_vec) / glm::length(line_vec); //投影的长度 |AC|
	return (sqrt(pow(glm::length(point_vec), 2) - pow(d, 2))); //勾股定理:|CP| = sqrt(AP^2-AC^2)
}

试了一下程序,果然效果不错。我真是个小机灵鬼~


本文参考博客:

https://www.cnblogs.com/flyinggod/p/9359534.html

https://blog.csdn.net/qq_41890797/article/details/82822519

https://blog.csdn.net/love_phoebe/article/details/81112531

  • 11
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

keneyr

老爷~给小的赏点盘缠吧555~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值