"求线段交点"是一种非常基础的几何计算, 在很多游戏中都会被使用到.

下面我就现学现卖的把最近才学会的一些"求线段交点"的算法说一说, 希望对大家有所帮助. 
本文讲的内容都很初级, 主要是面向和我一样的初学者, 所以请各位算法帝们轻拍啊 嘎嘎

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

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

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

实现代码如下 :

  1. function segmentsIntr(a, b, c, d){
  2. /** 1 解线性方程组, 求线段交点. **/
  3. // 如果分母为0 则平行或共线, 不相交
  4. var denominator = (b.y - a.y)*(d.x - c.x) - (a.x - b.x)*(c.y - d.y);
  5. if (denominator==0) {
  6. return false;
  7. }
  8. // 线段所在直线的交点坐标 (x , y)
  9. var x = ( (b.x - a.x) * (d.x - c.x) * (c.y - a.y)
  10. + (b.y - a.y) * (d.x - c.x) * a.x
  11. - (d.y - c.y) * (b.x - a.x) * c.x ) / denominator ;
  12. var y = -( (b.y - a.y) * (d.y - c.y) * (c.x - a.x)
  13. + (b.x - a.x) * (d.y - c.y) * a.y
  14. - (d.x - c.x) * (b.y - a.y) * c.y ) / denominator;
  15. /** 2 判断交点是否在两条线段上 **/
  16. if (
  17. // 交点在线段1上
  18. (x - a.x) * (x - b.x) <= 0 && (y - a.y) * (y - b.y) <= 0
  19. // 且交点也在线段2上
  20. && (x - c.x) * (x - d.x) <= 0 && (y - c.y) * (y - d.y) <= 0
  21. ){
  22. // 返回交点p
  23. return {
  24. x :  x,
  25. y :  y
  26. }
  27. }
  28. //否则不相交
  29. return false
  30. }

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

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

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

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

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

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

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

求线段ab的法线:

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

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

求点c在法线上的投影位置:

  1. 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时, 我们还需要进一步求交点. 
求交点的过程后面再说, 先看一下该算法的完整实现 :

  1. function segmentsIntr(a, b, c, d){
  2. //线段ab的法线N1
  3. var nx1 = (b.y - a.y), ny1 = (a.x - b.x);
  4. //线段cd的法线N2
  5. var nx2 = (d.y - c.y), ny2 = (c.x - d.x);
  6. //两条法线做叉乘, 如果结果为0, 说明线段ab和线段cd平行或共线,不相交
  7. var denominator = nx1*ny2 - ny1*nx2;
  8. if (denominator==0) {
  9. return false;
  10. }
  11. //在法线N2上的投影
  12. var distC_N2=nx2 * c.x + ny2 * c.y;
  13. var distA_N2=nx2 * a.x + ny2 * a.y-distC_N2;
  14. var distB_N2=nx2 * b.x + ny2 * b.y-distC_N2;
  15. // 点a投影和点b投影在点c投影同侧 (对点在线段上的情况,本例当作不相交处理);
  16. if ( distA_N2*distB_N2>=0  ) {
  17. return false;
  18. }
  19. //
  20. //判断点c点d 和线段ab的关系, 原理同上
  21. //
  22. //在法线N1上的投影
  23. var distA_N1=nx1 * a.x + ny1 * a.y;
  24. var distC_N1=nx1 * c.x + ny1 * c.y-distA_N1;
  25. var distD_N1=nx1 * d.x + ny1 * d.y-distA_N1;
  26. if ( distC_N1*distD_N1>=0  ) {
  27. return false;
  28. }
  29. //计算交点坐标
  30. var fraction= distA_N2 / denominator;
  31. var dx= fraction * ny1,
  32. dy= -fraction * nx1;
  33. return { x: a.x + dx , y: a.y + dy };
  34. }

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

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

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

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

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

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

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

  1. 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 了.

  1. function segmentsIntr(a, b, c, d){
  2. // 三角形abc 面积的2倍
  3. var area_abc = (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x);
  4. // 三角形abd 面积的2倍
  5. var area_abd = (a.x - d.x) * (b.y - d.y) - (a.y - d.y) * (b.x - d.x);
  6. // 面积符号相同则两点在线段同侧,不相交 (对点在线段上的情况,本例当作不相交处理);
  7. if ( area_abc*area_abd>=0 ) {
  8. return false;
  9. }
  10. // 三角形cda 面积的2倍
  11. var area_cda = (c.x - a.x) * (d.y - a.y) - (c.y - a.y) * (d.x - a.x);
  12. // 三角形cdb 面积的2倍
  13. // 注意: 这里有一个小优化.不需要再用公式计算面积,而是通过已知的三个面积加减得出.
  14. var area_cdb = area_cda + area_abc - area_abd ;
  15. if (  area_cda * area_cdb >= 0 ) {
  16. return false;
  17. }
  18. //计算交点坐标
  19. var t = area_cda / ( area_abd- area_abc );
  20. var dx= t*(b.x - a.x),
  21. dy= t*(b.y - a.y);
  22. return { x: a.x + dx , y: a.y + dy };
  23. }

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

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

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

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

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

文章来源:http://fins.iteye.com/blog/1522259

求两条线段交点zz的更多相关文章

  1. 两条线段求交点+叉积求面积 poj 1408

    题目链接:https://vjudge.net/problem/POJ-1408 题目是叫我们求出所有四边形里最大的那个的面积. 思路:因为这里只给了我们正方形四条边上的点,所以我们要先计算横竖线段两 ...

  2. 平面内,线与线 两条线找交点 两条线段的位置关系(相交)判定与交点求解 C#

    个人亲自编写.测试,可以正常使用   道理看原文,这里不多说   网上找到的几篇基本都不能用的   C#代码 bool Equal(float f1, float f2) { return (Math ...

  3. 计算几何--判断两条线段相交--poj 2653

    Pick-up sticks Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 8862   Accepted: 3262 De ...

  4. 团体程序设计天梯赛-练习集L1-008. 求整数段和

    L1-008. 求整数段和 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 杨起帆 给定两个整数A和B,输出从A到B的所有整数以及这些 ...

  5. Pick-up sticks(判断两条线段是否相交)

    Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 8351 Accepted: 3068 Description Stan has ...

  6. *循环-01. 求整数段和【help】

    /* * Main.c * 循环-01. 求整数段和 * Created on: 2014年6月18日 * Author: Boomkeeper ***测试木有通过**** */ #include & ...

  7. MySql 求一段时间范围内的每一天,每一小时,每一分钟

    平常经常会求一段时间内的每一天统计数据,或者每一时点的统计数据.但是mysql本身是没有直接获取时点列表的函数或表.下面是自己用到的一些方法,利用临时变量和一个已存在的比较多数据(这个需要根据实际情况 ...

  8. hdu 2857:Mirror and Light(计算几何,点关于直线的对称点,求两线段交点坐标)

    Mirror and Light Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  9. Help Hanzo lightof 1197 求一段区间内素数个数,[l,r] 在 [1,1e9] 范围内。r-l<=1e5; 采用和平常筛素数的方法。平移区间即可。

    /** 题目:Help Hanzo lightof 1197 链接:https://vjudge.net/contest/154246#problem/M 题意:求一段区间内素数个数,[l,r] 在 ...

随机推荐

  1. 【转载】Arcengine效率探究之二——属性的更新

    文转载自hymyjl2010<Arcengine效率探究之二——属性的更新>   修改一批要素的属性有多种方法,当数据量较大时,若选择不当可能会大大影响速度. 一.IRowBuffer 方 ...

  2. Linux下php安装Redis扩展

    说明: 操作系统:CentOS php安装目录:/usr/local/php php.ini配置文件路径:/usr/local/php7/etc/php.ini Nginx安装目录:/usr/loca ...

  3. [译]ASP.NET 5 Configuration

    原文:https://docs.asp.net/en/latest/fundamentals/configuration.html ASP.NET 5支持多种配置选项. 应用的配置文件可以是JSON, ...

  4. jaxb

    一.简介 JAXB(Java Architecture for XML Binding) 是一个业界的标准,是一项可以根据XML Schema产生Java类的技术.该过程中,JAXB也提供了将XML实 ...

  5. 2.1WebApi的路由

    这篇文章描述 ASP.NET Web API如何将 HTTP 请求通过路由去访问控制器. 如果你熟悉 ASP.NET MVC,Web API 路由是非常类似于 MVC 路由.主要的区别是 Web AP ...

  6. zend studio 做前端推荐安装的插件

    zend studio 做前端推荐安装的插件 1.Aptana插件代码提示 Zend Studio的aptana插件,解决了Zend Studio对前台代码支持不足的问题,而且在某些方面还比诸如dw优 ...

  7. JVM内存管理------垃圾搜集器精解(让你在垃圾搜集器的世界里耍的游刃有余)

    引言 在上一章我们已经探讨过hotspot上垃圾搜集器的实现,一共有六种实现六种组合.本次LZ与各位一起探讨下这六种搜集器各自的威力以及组合的威力如何. 为了方便各位的观看与对比,LZ决定采用当初写设 ...

  8. linux下nat配置

    iptables要启用nat表,必须启动nat表的支持.默认情况下,linux下是没有开启nat表的支持的. #启动内核的路由功能 echo > /proc/sys/net/ipv4/ip_fo ...

  9. php实验四

    实验四 1.创建一个Person类,Person中包含三个属性name,age,wealth,分别设置为public,private,protected,再定义Person类的子类Student. 2 ...

  10. vim技巧

    (三)多窗口操作 改变高度:res +n(增加n行的高度)n ctrlw  +/-改变宽度:vertical res +n(增加n列的宽度)n ctrl w >/< (一)缩进 vim提供 ...