<code class="hljs avrasm">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){ // 返回交点preturn { x : x, y : y }}//否则不相交return false} </code>算法一思路比较清晰易懂, 但是性能并不高. 因为它在不确定交点是否有效(在线段上)之前, 就先去计算了交点, 耗费了较多的时间.
点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
的几何意义表示法线的方向, 而不是坐标.var dist= normalLine.x*c.x + normalLine.y*c.y;注意: 这里的"投影位置"是一个标量, 表示的是到法线原点的距离, 而不是投影点的坐标.
function segmentsIntr(a, b, c, d){ //线段ab的法线N1var nx1 = (b.y - a.y), ny1 = (a.x - b.x); //线段cd的法线N2var 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 }; }最后 求交点坐标的部分 所用的方法看起来有点奇怪, 有种摸不着头脑的感觉.
<code class="hljs avrasm">var triArea=( (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x) ) /2 ; </code>因为 两向量叉乘==两向量构成的平行四边形(以两向量为邻边)的面积 , 所以上面的公式也不难理解.
图中虚线所示的三角形, 缠绕方向(三边的定义顺序)不同, 所以面积的正负符号不同.
下面还是先看代码:
由于我们只要判断符号即可, 所以前面的三角形面积公式我们就不需要后面的 除以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 };}最后 计算交点坐标的部分 和算法二同理.