[Java教程]谈谈求线段交点的几种算法(js实现,完整版)

0 2014-08-27 10:05:22

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

下面我就现学现卖的把最近才学会的一些"求线段交点"的算法总结一下, 希望对大家有所帮助.

本文讲的内容都很初级, 主要是面向和我一样的初学者, 所以请各位算法帝们轻拍啊 嘎嘎引用

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

===============================

算法一: 求两条线段所在直线的交点, 再判断交点是否在两条线段上. 求直线交点时 我们可通过直线的一般方程 ax+by+c=0 求得(方程中的abc为系数,不是前面提到的端点,另外也可用点斜式方程和斜截式方程,此处暂且不论).

然后根据交点的与线段端点的位置关系来判断交点是否在线段上. 公式如下图:

实现代码如下 :Javascript代码

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的法线:Javascript代码

var nx=b.y - a.y,

ny=a.x - b.x;

var normalLine = {  x: nx, y: ny };

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

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

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时, 我们还需要进一步求交点.

求交点的过程后面再说, 先看一下该算法的完整实现 :Javascript代码

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), 三角形面积为:Javascript代码

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 了.Javascript代码

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引擎的人哦 )还推荐了另外一种更好的算法, 不过我还没有理解透彻.

等我学会了 再来和大家分享

本文网址:http://www.shaoqun.com/a/99259.html

*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们:admin@shaoqun.com。

JS

0

java 直线交点_[Java教程]谈谈求线段交点的几种算法(js实现,完整版)相关推荐

  1. js求两圆交点_谈谈求线段交点的几种算法(js实现,完整版)

    "求线段交点"是一种非常基础的几何计算, 在很多游戏中都会被使用到. 下面我就现学现卖的把最近才学会的一些"求线段交点"的算法总结一下, 希望对大家有所帮助. ...

  2. 求最大公约数的4种算法(C++)

    求最大公约数的4种算法(C++) 一.实验目的 1.计算两个正整数的最大公约数和最小公倍数,并进行程序的调式与测试. 2.理解四种不同的求最大公约数的方法,学习其思维模式. 3.了解算法的概念.对问题 ...

  3. java小朋友猜拳_[Java教程]Java猜拳小游戏(剪刀、石头、布)

    [Java教程]Java猜拳小游戏(剪刀.石头.布) 0 2015-09-29 08:00:04 import java.util.Random;import java.util.Scanner;pu ...

  4. mc经常闪退是java有问题_【疑问】求大神回答,mc闪退,提供崩溃报告

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 ---- Minecraft Crash Report ---- // Who set us up the TNT? Time: 15-5-22 上午2: ...

  5. java星空屏幕_[Java教程]窗口设置_星空网

    窗口设置 2016-04-13 0 /** * 这个是GUI的事例程序: * */ package w160412.wang.main;import java.awt.Color; import ja ...

  6. java控制语句练习题_[Java初探实例篇02]__流程控制语句知识相关的实例练习

    本例就流程控制语句的应用方面,通过三个练习题来深入学习和巩固下学习的流程控制语句方面的知识,设计到,if条件判断语句,switch多分支语句,for循环语句及其嵌套多层使用,while循环语句. 练习 ...

  7. java取负数_[Java] 告别“CV 工程师”码出高效!(基础篇)

    作为一名资深的 CV 工程师,某天,当我再一次日常看见满屏的报错信息与键盘上已经磨的泛白的 Ctrl.C.V 这三个按键时,我顿悟了. 百度谷歌复制粘贴虽然很香,但是总是依靠前人种树,终会有一天失去乘 ...

  8. java 文本压缩_[Java基础]Java使用GZIP进行文本压缩

    import java.io.IOException; import java.util.zip.GZIPOutputStream; import org.apache.commons.io.outp ...

  9. java jpa性能_[Java Performance] 数据库性能最佳实践 - JPA和读写优化

    数据库性能最佳实践 当应用须要连接数据库时.那么应用的性能就可能收到数据库性能的影响. 比方当数据库的I/O能力存在限制,或者因缺失了索引而导致运行的SQL语句须要对整张表进行遍历.对于这些问题.只相 ...

最新文章

  1. 牛客网_剑指Offer_Python实现_更新中
  2. 快手团队长文解读:基于FPGA加速的自动语音识别在大规模直播和短视频场景的应用...
  3. #100天计划# 2013年10月1日
  4. HDU 6155 Subsequence Count (DP、线性代数、线段树)
  5. 前端学习(1391):多人管理项目11邮箱地址查询信息
  6. Android 闹钟
  7. Linux常用命令 一
  8. 全局变量定义的时候左侧加了static_c语言中static 用法
  9. 面经整理:大华C++服务器开发(2021-07-19)
  10. 三星android系统应用,三星Android系统文件夹全解
  11. 【读书笔记】增长黑客
  12. 常见测试场景用例设计(1):登录页面测试用例
  13. protractor测试框架6----报告、公共方法、公共变量
  14. H5的学习(二)编辑器的选择
  15. STL中Vector的内存分配机制
  16. zabbix 表结构: triggers中expression 对应的信息
  17. Java习题练习:和尚挑水
  18. Ubuntu 18.04安装搜狗输入法笔记
  19. 美国科学院院士张首晟:摩尔定律即将失效,人工智能怎么化解这个尴尬? | 2017 IT领袖峰会...
  20. 3C裁员潮背后:巨头的“大公司病”

热门文章

  1. PWM转4~20mA电路
  2. system pause
  3. 火狐浏览器书签工具栏图标_在Firefox书签工具栏中浓缩书签
  4. 漫步者和南卡蓝牙耳机哪个好?高性价比蓝牙耳机测评
  5. mysql数据库有关教程
  6. Wireshark软件使用
  7. Keras中predict()方法和predict_classes()方法的区别
  8. nbd 相关概念及操作
  9. 第六次网页前端培训笔记(JavaScript)
  10. busybox的实现原理分析(C语言实现简易版的busybox)