【数学计算】判断两条线段是否相交+计算两条线段的交点和夹角

序言
  • 还是那句话,学习是为了应用。书到用时方恨"用得少"
1. 计算两条直线的交点
  • 直线一般式方程

A x + B y + C = 0 Ax+By+C = 0 Ax+By+C=0

  • 设线段p1-p2对应的直线方程:a1 * x + b1 * y + c1 = 0
  • 设线段p3-p4对应的直线方程:a2 * x + b2 * y + c2 = 0
  • 求解方程系数:

    A = y2 - y1 = p2.y() - p1.y()
    B = x1 - x2 = p1.x() - p2.x()
    C = x2 * y1 - x1 * y2 = p2.x() * p1.y() - p1.x() * p2.y()
    
  • 联立方程式求交点坐标

    a1 * x + b1 * y + c1 = 0	-- (1)
    a2 * x + b2 * y + c2 = 0	-- (2)
    
    (1) * b2 - (2) * b1得到:
    a1 * b2 * x + b1 * b2 * y + c1 * b2 = 0
    a2 * b1 * x + b1 * b2 * y + c2 * b1 = 0
    
    两式相减:
    x = (c2 * b1 - c1 * b2) / denominator
    
    (1) * a2 - (2) * a1得到:
    a1 * a2 * x + b1 * a2 * y + c1 * a2 = 0
    a1 * a2 * x + b2 * a1 * y + c2 * a1 = 0
    
    两式相减:
    y = (c1 * a2 - c2 * a1) / denominator
    
    (注:denominator = a1 * b2 - a2 * b1)
    
  • 交点坐标

( x , y ) = ( c 2 ∗ b 1 − c 1 ∗ b 2 a 1 ∗ b 2 − a 2 ∗ b 1 , c 1 ∗ a 2 − c 2 ∗ a 1 a 1 ∗ b 2 − a 2 ∗ b 1 ) (x, y) = (\frac{c2 * b1 - c1 * b2}{a1 * b2 - a2 * b1} , \frac{c1 * a2 - c2 * a1}{a1 * b2 - a2 * b1} ) (x,y)=(a1b2a2b1c2b1c1b2,a1b2a2b1c1a2c2a1)

  • 两条直线的夹角(两条直线所形成的不大于90°的角):

    double denominator = a1 * a2 + b1 * b2;
    double numerator = a1 * b2 - a2 * b1;
    if (fabs(denominator) < 0.0000001) {
    	return PI / 2.0;	// 两直线垂直
    }
    return fabs(std::atan(numerator / denominator));	// 返回的是弧度值
    
2. 判断两条线段是否相交:方法1
  • 直线一般式常规方法求解。

  • 证明两条线段相交:

    • (1) 证明denominator != 0,denominator == 0表示两条直线平行
    • (1) 根据1中公式求解交点坐标
    • (2) 证明交点既在线段p1-p2上,也在线段p3-p4上。即如下关系成立
    r_x0 = (p.x() - p1.x()) / (p2.x() - p1.x());
    r_y0 = (p.y() - p1.y()) / (p2.y() - p1.y());
    r_x1 = (p.x() - p3.x()) / (p4.x() - p3.x());
    r_y1 = (p.y() - p3.y()) / (p4.y() - p3.y());
    bool condition_1 = (r_x0 >= 0.0 && r_x0 <= 1.0) || (r_y0 >= 0.0 && r_y0 <= 1);
    bool condition_2 = (r_x1 >= 0.0 && r_x1 <= 1.0) || (r_y1 >= 0.0 && r_y1 <= 1.0);
    if (condition_1 && condition_2) {
    	return true;
    }
    
  • 线段比例计算:

r a t i o x 0 = p . x − p 1 . x p 2 . x − p 1 . x r a t i o y 0 = p . y − p 1 . y p 2 . y − p 1 . y r a t i o x 1 = p . x − p 3 . x p 4 . x − p 3 . x r a t i o y 1 = p . y − p 3 . y p 4 . y − p 3 . y \begin{aligned} ratio_{x0} &= \frac{p.x - p_1.x}{p_2.x - p_1.x} \\ ratio_{y0} &= \frac{p.y - p_1.y}{p_2.y - p_1.y} \\ ratio_{x1} &= \frac{p.x - p_3.x}{p_4.x - p_3.x} \\ ratio_{y1} &= \frac{p.y - p_3.y}{p_4.y - p_3.y} \\ \end{aligned} ratiox0ratioy0ratiox1ratioy1=p2.xp1.xp.xp1.x=p2.yp1.yp.yp1.y=p4.xp3.xp.xp3.x=p4.yp3.yp.yp3.y

3. 判断两条线段是否相交:方法2
  • 快速排斥实验和跨立实验计算。

  • 证明两条线段相交:

    • (1) 通过快速排斥实验
    • (2) 且通过跨立实验
    /* 1. 快速排斥实验 */
    bool cond_1 = max(p1.x, p2.x) < min(p3.x, p4.x);
    bool cond_2 = max(p1.y, p2.y) < min(p3.y, p4.y);
    bool cond_3 = max(p3.x, p4.x) < min(p1.x, p2.x);
    bool cond_4 = max(p3.y, p4.y) < min(p2.y, p2.y);
    if (cond_1 || cond_2 || cond_3 || cond_4) {
    	return false;	// 有一个条件为真,表示两线段必不相交
    }
    /* 2. 跨立实验 */
    if(cross(p3 - p1,p2 - p1) * cross(p4 - p1, p2 - p1) > 0
       || cross(p2 - p3, p4 - p3) * cross(p1 - p3, p4 - p3) > 0) {
    	return false;	// 叉积 == 0可能共线,但已通过快速排斥实验过滤
    }
    return true;
    
  • 快速排斥实验:即验证线段的横坐标或纵坐标是否总小于或大于另一线段的横坐标或纵坐标

  • 跨立实验:通过叉积证明点p1和p2在p3-p4的两侧,以及p3和p4在p1-p2的两侧

    • 两向量叉积表示向量所在平面的法向量

    • 叉积有一个非常重要的性质,可通过叉积的符号来判断两向量的顺逆时针关系:

      P x Q > 0, 则向量P在向量Q的顺时针方向;
      P x Q < 0, 则向量P在向量Q的逆时针方向;
      P x Q = 0,表示P与Q共线,可能同向也可能反向
      
    • 若向量P = (x1, y1), Q = (x2, y2),则向量叉积P x Q = x1 * y2 - x2 * y1

    • 如果两线段相交,则线段端点相互跨立,即p1, p2分别在p3-p4两侧,p3, p4分别在p1-p2两侧

    • 判断p3、p4分别在p1-p2的两侧 ( p 1 p 3 × p 1 p 2 ) ∗ ( p 1 p 4 × p 1 p 2 ) ≤ 0 (p_1p_3\times p_1p_2) * (p_1p_4\times p_1p_2)\le 0 (p1p3×p1p2)(p1p4×p1p2)0

    • 判断p1、p4分别在p3-p4的两侧 ( p 3 p 2 × p 3 p 4 ) ∗ ( p 3 p 1 × p 3 p 4 ) ≤ 0 (p_3p_2\times p_3p_4) * (p_3p_1\times p_3p_4)\le 0 (p3p2×p3p4)(p3p1×p3p4)0

4. 总结
  • 方法2比较直观,方法1判断两条线段是否相交比方法2实现上复杂一点;
  • 方法1能直接计算线段夹角;
  • 方法2叉积可调用Eigen库函数实现(待补充)

《参考文章》
快速排斥实验和跨立实验-1
快速排斥实验和跨立实验-2
直线一般式判断线段相交-1
直线一般式判断线段相交-2
直线一般式计算两直线夹角-1
直线一般式计算两直线夹角-2

created by shuaixio, 2022.10.7

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值