1. 概述
在本教程中,我们将讨论如何检测一个圆与一条直线或线段之间的碰撞。
我们将从问题定义开始,通过示例帮助理解。接着会介绍两种解决思路:
- 一种是判断直线与圆的碰撞
- 另一种是判断线段与圆的碰撞
两种方法的数学原理相似,但实现细节略有不同。
2. 问题定义
我们有一个圆 C,圆心为 O,半径为 R。同时,有一条由两点 P 和 Q 确定的直线 L。
目标是判断圆 C 和直线 L 是否存在交点。
举个例子更直观:
- 图 A:直线 L 与圆 C 没有交点,结果为 ❌ false
- 图 B:直线 L 与圆 C 有两个交点,结果为 ✅ true
3. 直线与圆的碰撞检测
3.1 数学原理
判断直线与圆是否相交的核心思想是:
计算圆心 O 到直线 L 的最短距离,如果这个距离小于等于圆的半径 R,说明直线与圆相交或相切。
这个最短距离其实就是圆心 O 到直线 L 的投影长度。
我们可以通过三角形面积法来求这个距离:
- 用三点 O、P、Q 构造一个三角形
- 利用叉积计算三角形面积
- 再根据面积公式反推高(即最短距离)
公式如下: $$ \text{distance} = \frac{2 \times \text{TriangleArea}(O, P, Q)}{\text{distance}(P, Q)} $$
3.2 三角形面积计算
我们用叉积来计算三角形面积:
double triangleArea(Point A, Point B, Point C) {
double ax = B.x - A.x;
double ay = B.y - A.y;
double bx = C.x - A.x;
double by = C.y - A.y;
double cross = ax * by - ay * bx;
return Math.abs(cross) / 2.0;
}
3.3 算法实现
boolean lineCircleCollision(double radius, Point O, Point P, Point Q) {
double minDist = 2 * triangleArea(O, P, Q) / distance(P, Q);
return minDist <= radius;
}
3.4 时间复杂度
✅ 时间复杂度为 O(1),因为只涉及常数次运算。
4. 线段与圆的碰撞检测
4.1 数学原理
线段与圆的碰撞检测比直线复杂,因为线段是有限的。我们需要考虑以下情况:
- 圆心 O 到线段 PQ 的最短距离是否小于等于 R
- 同时,线段是否完全在圆内或部分在圆内
关键点:
- 如果 O 的投影在 PQ 上,则使用三角形面积法求最短距离
- 如果不在 PQ 上,则取 O 到端点 P 或 Q 的最小距离
同时,我们还要判断线段的最远端点是否在圆外,否则线段可能完全在圆内,不算碰撞。
4.2 算法实现
boolean lineSegmentCircleCollision(double radius, Point O, Point P, Point Q) {
double maxDist = Math.max(distance(O, P), distance(O, Q));
double minDist;
// 判断投影是否在线段上
if (dot(P.subtract(O), P.subtract(Q)) > 0 && dot(Q.subtract(O), Q.subtract(P)) > 0) {
minDist = 2 * triangleArea(O, P, Q) / distance(P, Q);
} else {
minDist = Math.min(distance(O, P), distance(O, Q));
}
return minDist <= radius && maxDist >= radius;
}
⚠️ 注意:
dot()
是向量点乘函数,subtract()
是向量减法函数,需要自行实现。
4.3 时间复杂度
✅ 时间复杂度仍为 O(1),所有运算都是固定次数。
5. 总结
我们介绍了两种碰撞检测算法:
类型 | 原理简述 | 时间复杂度 |
---|---|---|
直线 & 圆 | 计算圆心到直线最短距离 | O(1) |
线段 & 圆 | 先判断投影是否在线段上,再分别计算最短/最远距离 | O(1) |
两种方法都基于几何原理,适用于游戏开发、图形学、物理引擎等场景。
✅ 优点:高效、准确、实现简单
❌ 限制:仅适用于 2D 平面场景,不适用于三维空间
如果你在实现过程中遇到投影判断逻辑不清晰、点乘叉积理解困难等问题,欢迎留言交流,我们一起踩坑填坑 😄