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

两条折线算交点 Java 计算两条线的交点_线段

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



两条折线算交点 Java 计算两条线的交点_解方程_02


  • 求解方程系数:
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)
  • 交点坐标

两条折线算交点 Java 计算两条线的交点_线段相交_03

  • 两条直线的夹角(两条直线所形成的不大于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;
}
  • 线段比例计算:

两条折线算交点 Java 计算两条线的交点_两条折线算交点 Java_04

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的两侧两条折线算交点 Java 计算两条线的交点_两条折线算交点 Java_05
  • 判断p1、p4分别在p3-p4的两侧两条折线算交点 Java 计算两条线的交点_线段相交_06


两条折线算交点 Java 计算两条线的交点_几何关系_07



两条折线算交点 Java 计算两条线的交点_解方程_08


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