`

谈谈"求线段交点"的几种算法(js实现,完整版)

阅读更多
谈谈"求线段交点"的几种算法(js实现,完整版)

"求线段交点"是一种非常基础的几何计算, 在很多游戏中都会被使用到.
下面我就现学现卖的把最近才学会的一些"求线段交点"的算法说一说, 希望对大家有所帮助.
本文讲的内容都很初级, 主要是面向和我一样的初学者, 所以请各位算法帝们轻拍啊 嘎嘎

引用
已知线段1(a,b) 和线段2(c,d) ,其中a b c d为端点, 求线段交点p .(平行或共线视作不相交)


===============================
算法一: 求两条线段所在直线的交点, 再判断交点是否在两条线段上.

求直线交点时 我们可通过直线的一般方程 ax+by+c=0 求得(方程中的abc为系数,不是前面提到的端点,另外也可用点斜式方程和斜截式方程,此处暂且不论).
然后根据交点的与线段端点的位置关系来判断交点是否在线段上. 公式如下图:



实现代码如下 :

function segmentsIntr(a, b, c, d){

/** 1 解线性方程组, 求线段交点. **/
// 如果分母为0 则平行或共线, 不相交
    var denominator = (b.y - a.y)*(d.x - c.x) - (a.x - b.x)*(c.y - d.y);
    if (denominator==0) {
        return false;
    }
 
// 线段所在直线的交点坐标 (x , y)    
    var x = ( (b.x - a.x) * (d.x - c.x) * (c.y - a.y) 
                + (b.y - a.y) * (d.x - c.x) * a.x 
                - (d.y - c.y) * (b.x - a.x) * c.x ) / denominator ;
    var y = -( (b.y - a.y) * (d.y - c.y) * (c.x - a.x) 
                + (b.x - a.x) * (d.y - c.y) * a.y 
                - (d.x - c.x) * (b.y - a.y) * c.y ) / denominator;

/** 2 判断交点是否在两条线段上 **/
    if (
        // 交点在线段1上
        (x - a.x) * (x - b.x) <= 0 && (y - a.y) * (y - b.y) <= 0
        // 且交点也在线段2上
         && (x - c.x) * (x - d.x) <= 0 && (y - c.y) * (y - d.y) <= 0
        ){

        // 返回交点p
        return {
                x :  x,
                y :  y
            }
    }
    //否则不相交
    return false

}
                 

算法一思路比较清晰易懂, 但是性能并不高. 因为它在不确定交点是否有效(在线段上)之前, 就先去计算了交点, 耗费了较多的时间.
如果最后发现交点无效, 那么之前的计算就白折腾了. 而且整个计算的过程也很复杂.
那么有没有一种思路,可以让我们先判断是否存在有效交点,然后再去计算它呢?
显然答案是肯定的. 于是就有了后面的一些算法.


===============================
算法二: 判断每一条线段的两个端点是否都在另一条线段的两侧, 是则求出两条线段所在直线的交点, 否则不相交.

第一步判断两个点是否在某条线段的两侧, 通常可采用投影法:

求出线段的法线向量, 然后把点投影到法线上, 最后根据投影的位置来判断点和线段的关系. 见下图



点a和点b在线段cd法线上的投影如图所示, 这时候我们还要做一次线段cd在自己法线上的投影(选择点c或点d中的一个即可).
主要用来做参考.
图中点a投影和点b投影在点c投影的两侧, 说明线段ab的端点在线段cd的两侧.

同理, 再判断一次cd是否在线段ab两侧即可.

求法线 , 求投影 什么的听起来很复杂的样子, 实际上对于我来说也确实挺复杂,在几个月前我也不会(念书那会儿的几何知识都忘光了 :'( )'
不过好在学习和实现起来还不算复杂, 皆有公式可循:


求线段ab的法线:

var nx=b.y - a.y, 
    ny=a.x - b.x;
var normalLine = {  x: nx, y: ny };


注意: 其中 normalLine.x和normalLine.y的几何意义表示法线的方向, 而不是坐标.


求点c在法线上的投影位置:
var dist= normalLine.x*c.x + normalLine.y*c.y;


注意: 这里的"投影位置"是一个标量, 表示的是到法线原点的距离, 而不是投影点的坐标.
通常知道这个距离就足够了.

当我们把图中 点a投影(distA),点b投影(distB),点c投影(distC) 都求出来之后, 就可以很容易的根据各自的大小判断出相对位置.

distA==distB==distC 时, 两条线段共线
distA==distB!=distC 时, 两条线段平行
distA 和 distB 在distC 同侧时, 两条线段不相交.
distA 和 distB 在distC 异侧时, 两条线段是否相交需要再判断点c点d与线段ab的关系.

前面的那些步骤, 只是实现了"判断线段是否相交", 当结果为true时, 我们还需要进一步求交点.
求交点的过程后面再说, 先看一下该算法的完整实现 :

function segmentsIntr(a, b, c, d){

    //线段ab的法线N1
    var nx1 = (b.y - a.y), ny1 = (a.x - b.x);

    //线段cd的法线N2
    var nx2 = (d.y - c.y), ny2 = (c.x - d.x);
    
    //两条法线做叉乘, 如果结果为0, 说明线段ab和线段cd平行或共线,不相交
    var denominator = nx1*ny2 - ny1*nx2;
    if (denominator==0) {
        return false;
    }
    
    //在法线N2上的投影
    var distC_N2=nx2 * c.x + ny2 * c.y;
    var distA_N2=nx2 * a.x + ny2 * a.y-distC_N2;
    var distB_N2=nx2 * b.x + ny2 * b.y-distC_N2;

    // 点a投影和点b投影在点c投影同侧 (对点在线段上的情况,本例当作不相交处理);
    if ( distA_N2*distB_N2>=0  ) {
        return false;
    }
    
    //
    //判断点c点d 和线段ab的关系, 原理同上
    //
    //在法线N1上的投影
    var distA_N1=nx1 * a.x + ny1 * a.y;
    var distC_N1=nx1 * c.x + ny1 * c.y-distA_N1;
    var distD_N1=nx1 * d.x + ny1 * d.y-distA_N1;
    if ( distC_N1*distD_N1>=0  ) {
        return false;
    }

    //计算交点坐标
    var fraction= distA_N2 / denominator;
    var dx= fraction * ny1,
        dy= -fraction * nx1;
    return { x: a.x + dx , y: a.y + dy };
}


最后 求交点坐标的部分 所用的方法看起来有点奇怪, 有种摸不着头脑的感觉.
其实它和算法一 里面的算法是类似的,只是里面的很多计算项已经被提前计算好了.
换句话说, 算法二里求交点坐标的部分 其实也是用的直线的线性方程组来做的.

现在来简单粗略 很不科学的对比一下算法一和算法二:
1 最好情况下, 两种算法的复杂度相同
2 最坏情况, 算法一和算法二的计算量差不多
3 但是算法二提供了 更多的"提前结束条件",所以平均情况下,应该算法二更优.

实际测试下来, 实际情况也确实如此.

前面的两种算法基本上是比较常见的可以应付绝大多数情况. 但是事实上还有一种更好的算法.
这也是我最近才新学会的(我现学现卖了,大家不要介意啊...)

===============================
算法三: 判断每一条线段的两个端点是否都在另一条线段的两侧, 是则求出两条线段所在直线的交点, 否则不相交.

(咦? 怎么感觉和算法二一样啊? 不要怀疑 确实一样 ... 囧)
所谓算法三, 其实只是对算法二的一个改良, 改良的地方主要就是 :
不通过法线投影来判断点和线段的位置关系, 而是通过点和线段构成的三角形面积来判断.

先来复习下三角形面积公式: 已知三角形三点a(x,y) b(x,y) c(x,y), 三角形面积为:

var triArea=( (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x) ) /2 ;


因为 两向量叉乘==两向量构成的平行四边形(以两向量为邻边)的面积 , 所以上面的公式也不难理解.
而且由于向量是有方向的, 所以面积也是有方向的, 通常我们以逆时针为正, 顺时针为负数.

改良算法关键点就是:
如果"线段ab和点c构成的三角形面积"与"线段ab和点d构成的三角形面积" 构成的三角形面积的正负符号相异,
那么点c和点d位于线段ab两侧. 如下图所示:



图中虚线所示的三角形, 缠绕方向(三边的定义顺序)不同, 所以面积的正负符号不同.


下面还是先看代码:
由于我们只要判断符号即可, 所以前面的三角形面积公式我们就不需要后面的 除以2 了.

function segmentsIntr(a, b, c, d){

    // 三角形abc 面积的2倍
    var area_abc = (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x);

    // 三角形abd 面积的2倍
    var area_abd = (a.x - d.x) * (b.y - d.y) - (a.y - d.y) * (b.x - d.x); 

    // 面积符号相同则两点在线段同侧,不相交 (对点在线段上的情况,本例当作不相交处理);
    if ( area_abc*area_abd>=0 ) {
        return false;
    }

    // 三角形cda 面积的2倍
    var area_cda = (c.x - a.x) * (d.y - a.y) - (c.y - a.y) * (d.x - a.x);
    // 三角形cdb 面积的2倍
    // 注意: 这里有一个小优化.不需要再用公式计算面积,而是通过已知的三个面积加减得出.
    var area_cdb = area_cda + area_abc - area_abd ;
    if (  area_cda * area_cdb >= 0 ) {
        return false;
    }

    //计算交点坐标
    var t = area_cda / ( area_abd- area_abc );
    var dx= t*(b.x - a.x),
        dy= t*(b.y - a.y);
    return { x: a.x + dx , y: a.y + dy };

}



最后 计算交点坐标的部分 和算法二同理.


算法三在算法二的基础上, 大大简化了计算步骤, 代码也更精简. 可以说,是三种算法里, 最好的.实际测试结果也是如此.

当然必须坦诚的来说, 在Javascript里, 对于普通的计算, 三种算法的时间复杂度其实是差不多的(尤其是V8引擎下).
我的测试用例里也是进行变态的百万次级别的线段相交测试 才能拉开三种算法之间的差距.

不过本着精益求精 以及学习的态度而言, 追求一个更好的算法, 总是有其积极意义的.


好了 不啰嗦了, 就到这里吧.
现学现卖的东西, 难免有错误, 还请大家不吝斧正. 先谢谢啦



补充:
后来微博上@miloyip (这个是真正的大牛, 是会自己写3D引擎的人哦 )还推荐了另外一种更好的算法, 不过我还没有理解透彻.
等我学会了 再来和大家分享



(完)




6
0
分享到:
评论
5 楼 ni4wangba0 2015-10-13  
ni4wangba0 写道
亲测,算法有问题。
对不起,其实是我自己搞错了,方法可用的。谢谢博主。
4 楼 ni4wangba0 2015-10-13  
亲测,算法有问题。
3 楼 polluxll 2013-03-15  
先前使用斜率判断,求两条线段的交点,发现求出的交点有问题,最后采用你的第三种方法,发现出来的结果还是有问题,交点有偏差
2 楼 yuruei2000 2012-05-11  
拒绝0回复,求粉
1 楼 kxys422834 2012-05-10  
看到a,b,c,d还以为是二元一次方程的参数呢,糊涂了半天.....

相关推荐

    计算机图形学求线段交点

    在这个领域中,求线段交点是一项基础且重要的任务,它广泛应用于2D和3D图形渲染、碰撞检测、路径规划等场景。理解并实现线段交点的计算方法对于计算机图形学的学习和实践至关重要。 线段交点的计算涉及到几何和代数...

    线扫描算法求所有线段交点演示

    线扫描算法,也称为线扫过算法或扫描转换算法,是一种在计算机图形学中广泛使用的算法,主要用于处理二维几何形状,尤其是求解线段的交点。这种算法的基本思想是沿着一条虚拟的扫描线(通常是水平线)从左到右移动,...

    详解js实现线段交点的三种算法

    下面将详细介绍三种算法来求解线段交点的问题。 首先需要明确的是,当线段平行或共线时,我们视作没有交点。在三维空间中,还需要注意线段是否处于同一平面内。 ### 算法一:求直线交点判断是否为线段交点 这种...

    扫描线算法求线段交点.zip_dangerousgux_判断线段相交_扫描线算法_线段集_计算几何

    这个名为"扫描线算法求线段交点.zip_dangerousgux_判断线段相交_扫描线算法_线段集_计算几何"的压缩包文件,显然是一个关于计算几何的教学或项目资源,包含了实现该算法的源代码。 在计算几何中,线段相交是一个...

    求线段与圆弧的交点实现源码

    求线段与圆弧的交点,我们需要解这两个方程组。线段和圆的交点满足上述两个方程。对于线段,我们需要确保交点在线段上,即交点的x坐标位于x1和x2之间,且对应的y坐标也在线段的范围内。对于圆,任何距离中心点距离...

    c++求两条线段的交点坐标

    c++求一个平面内,两条线段的交点坐标。传入四个坐标点,返回一个坐标点。

    求平面两条线段交点效果演示

    6. **程序实现**:"求线段与线段交点效果演示.exe"程序可能采用了上述方法,通过编程语言(如C++、Python或Java)实现,将随机生成的线段在屏幕上绘制出来,并实时显示判断结果和交点坐标,提供了一个直观的交互式...

    delphi 求线段交点实例

    本实例将详细介绍如何在 Delphi 中编写求线段交点的算法,并结合实际的窗体程序来展示这一过程。 首先,我们要理解线段交点的基本概念。在二维平面内,两条线段可能相交也可能不相交。如果它们相交,那么交点就是...

    两种算法线段求交

    线段求交是计算机图形学、算法和几何计算中的一个重要问题。在二维平面上,给定一组线段,我们需要找出其中相交的线段对。..."LineSegmentIntersect"中的代码示例可以作为学习和理解这两种算法的良好资源。

    求两直线交点的算法

    在这个场景中,我们关注的是如何在MFC环境下实现画直线和求交点的算法。 首先,我们需要了解直线的数学表示。在二维空间中,一条直线可以用点斜式或者一般式来表示。点斜式是y = mx + b,其中m是斜率,b是y轴截距;...

    关于求线段和线段,线段和圆弧,圆弧与圆弧的交点算法和测试程序

    关于求线段和线段,线段和圆弧,圆弧与圆弧的交点算法和测试程序,里面有算法代码,和测试程序,主要时理解算法,算法分析:http://blog.csdn.net/u012727080/article/details/78908614

    算法,求两直线交点

    ### 知识点详解:“算法,求两直线交点” #### 核心概念与方法 在平面几何中,计算两直线的交点是一项基础而重要的技能,尤其在计算机图形学、算法设计等领域有着广泛的应用。当面对两条由各自两端点确定的直线时...

    求2线段交点坐标.txt

    实现在二维坐标中,2条线段交点坐标算法。 形参为2条直线的起始与结束点坐标,返回一个坐标点。

    两线段求交点,线段判断求交

    在计算机图形学、几何计算和算法设计中,求解线段的交点是一个常见的问题。在VB(Visual Basic)编程环境中,实现这个功能涉及到一系列的几何和代数知识。线段是由两个点定义的,每个点都有自己的坐标,通常表示为...

    代码判断两条线段是否相交(两种实现算法)

    在计算机图形学和几何算法中,判断两条线段是否相交是一个常见的问题。这个任务涉及到二维空间中的几何对象,特别是点、直线和线段的概念。本文将深入探讨两种不同的算法来解决这个问题,一种是“暴力”方法,另一种...

    计算几何常用算法源码——线段与直线基本算法

    2. 求点到线段所在直线垂线的垂足 4 3. 点到线段的最近点 4 4. 点到线段所在直线的距离 4 5. 点到折线集的最近距离 4 6. 判断圆是否在多边形内 5 7. 求矢量夹角余弦 5 8. 求线段之间的夹角 5 9. 判断线段是否...

    判断两线段是否相交,相交求交点

    本文将围绕“判断两线段是否相交,相交求交点”这一主题,深入探讨其算法原理及实现细节。 ### 一、问题背景与数学模型 线段相交问题涉及几何学中的基本概念——线段,通常由两个端点在二维或三维空间中定义。对于...

Global site tag (gtag.js) - Google Analytics