3. 二维碰撞检测

碰撞检测,说白了就是判断两个物体有没有「碰在一起」。做机器人路径规划也好,做游戏物理引擎也罢,这步都是地基。我刚开始写代码那会儿,觉得碰撞检测不就是比大小嘛,结果一跑起来各种漏检、误检,调试到怀疑人生。

二维碰撞检测的核心就三件事:几何关系、分离轴、边界条件。今天咱们把最常用的三种情况讲透——点与矩形、圆与圆、矩形与矩形。你想想看,这几种搞明白了,后面三维的、多边形的,思路都是类似的。

核心原则:碰撞检测不是「等碰上了再判断」,而是「在每一帧/每一步,提前检查是否相交」。做避障时,我们通常用预测位置做检测,而不是当前位置。
二维碰撞检测知识体系 二维碰撞检测 点与矩形 圆与圆 矩形与矩形 边界比较 包含关系 距离平方 半径和比较 AABB重叠 分离轴定理 💡 核心思路:将连续空间离散化,用几何不等式判断是否相交 所有算法都基于「如果两个物体不相交,则存在一条分离线/分离轴」

3.1 点与矩形的碰撞检测

这是最简单的碰撞检测,没有之一。判断一个点是否在矩形内部,只需要比较坐标范围。

假设矩形用 左上角 (rx, ry) 和 宽高 (rw, rh) 表示,点坐标为 (px, py)。那么碰撞条件就是:

// 点与轴对齐矩形(AABB)碰撞检测
bool pointRectCollision(float px, float py, 
                        float rx, float ry, 
                        float rw, float rh) {
    // 点在矩形左右边界之间
    bool inX = (px >= rx) && (px <= rx + rw);
    // 点在矩形上下边界之间
    bool inY = (py >= ry) && (py <= ry + rh);
    
    return inX && inY;
}
我的习惯:边界条件用 >= 和 <= 还是 > 和 <?这取决于你的坐标系和「碰撞」的定义。我个人习惯用闭区间,因为当点刚好在边界上时,通常也算碰撞。但如果你做的是物理模拟,开区间可以避免「粘边」的抖动问题。

我在项目中遇到过一个问题:用鼠标点击选择屏幕上的物体时,如果矩形有旋转,上面的代码就不灵了。这时候需要把点转换到矩形的局部坐标系再做判断。不过那是进阶话题,咱们先把基础打牢。

3.2 圆与圆的碰撞检测

圆与圆的碰撞检测,核心就一句话:两圆心距离小于半径之和。

但这里有个坑——你千万别直接算距离再开平方。开平方是昂贵的操作,在实时系统中能省则省。正确的做法是:比较距离的平方与半径和的平方。

// 圆与圆碰撞检测(优化版)
bool circleCircleCollision(float x1, float y1, float r1,
                           float x2, float y2, float r2) {
    // 计算圆心距离的平方
    float dx = x2 - x1;
    float dy = y2 - y1;
    float distSq = dx * dx + dy * dy;
    
    // 计算半径和的平方
    float radSum = r1 + r2;
    float radSumSq = radSum * radSum;
    
    // 比较(避免开平方)
    return distSq <= radSumSq;
}
我曾经踩过的坑:用 sqrt() 做碰撞检测,在嵌入式机器人上帧率直接掉了一半。后来改成平方比较,性能瞬间恢复。记住:能平方就不开方,这是碰撞检测的黄金法则。

你想想看,如果两个圆完全重合,距离为0,肯定小于半径和,所以也算碰撞。这在某些场景下需要特殊处理——比如两个机器人完全重叠时,你可能需要把它们「弹开」而不是继续检测。

3.3 矩形与矩形的碰撞检测

矩形与矩形的碰撞检测,分两种情况:轴对齐矩形(AABB) 和 有旋转的矩形(OBB)。咱们先讲最常用的 AABB。

3.3.1 AABB 碰撞检测

AABB 的碰撞检测思路很直观:两个矩形在 X 轴和 Y 轴上的投影都重叠,才算碰撞。说白了就是「两个方向都重叠」。

// AABB 碰撞检测
bool aabbCollision(float ax, float ay, float aw, float ah,
                   float bx, float by, float bw, float bh) {
    // X 轴投影是否重叠
    bool overlapX = (ax < bx + bw) && (bx < ax + aw);
    // Y 轴投影是否重叠
    bool overlapY = (ay < by + bh) && (by < ay + ah);
    
    return overlapX && overlapY;
}

这个算法的本质是分离轴定理(SAT)的简化版。对于 AABB,两个矩形的分离轴就是 X 轴和 Y 轴。只要在任意一个轴上投影不重叠,两个矩形就不相交。

性能对比:AABB 碰撞检测只需要 4 次比较和 4 次加减法,是最高效的碰撞检测算法之一。在需要检测大量物体时(比如 1000 个机器人),优先用 AABB 做粗筛。

3.3.2 OBB 碰撞检测(带旋转的矩形)

如果矩形有旋转角度,事情就复杂了。这时候需要用分离轴定理(SAT)。对于两个 OBB,最多需要检测 4 条分离轴(每个矩形的两条边法线)。

嗯,这里要注意:SAT 的核心是「如果存在一条轴,两个多边形在该轴上的投影不重叠,则它们不相交」。对于矩形,我们只需要检测每条边的法线方向。

// OBB 碰撞检测(简化版,仅展示核心逻辑)
bool obbCollision(OBB& a, OBB& b) {
    // 获取两个矩形的四个轴(每个矩形两条边的法线)
    Vector2 axes[4] = {
        a.getAxisX(), a.getAxisY(),
        b.getAxisX(), b.getAxisY()
    };
    
    for (int i = 0; i < 4; i++) {
        // 计算两个矩形在当前轴上的投影
        float projA = a.project(axes[i]);
        float projB = b.project(axes[i]);
        
        // 如果投影不重叠,则不相交
        if (!overlap(projA, projB)) {
            return false;  // 找到分离轴,不相交
        }
    }
    
    return true;  // 所有轴都重叠,相交
}
我的经验:在实际项目中,我通常先用 AABB 做粗筛(把旋转矩形包在一个轴对齐包围盒里),只有粗筛通过后才用 SAT 做精检测。这样能过滤掉 80% 以上的不相交情况,性能提升很明显。

3.4 三种算法的对比与选型

算法 计算量 适用场景 精度 旋转支持
点与矩形 4 次比较 鼠标拾取、传感器点云 精确 否(需转换)
圆与圆 3 次乘法 + 1 次比较 圆形机器人、球状障碍物 精确 天然支持
AABB 4 次比较 矩形障碍物、粗筛阶段 精确(无旋转) 否
OBB (SAT) 8-16 次投影计算 旋转矩形、精确碰撞 精确 是
选型建议:别一上来就用 SAT。如果你的物体不旋转,AABB 完全够用。如果物体是圆的,用圆碰撞。只有当你需要精确检测旋转矩形时,才上 SAT。我见过太多人把简单问题复杂化了。

3.5 实战中的注意事项

  • 浮点数精度:比较时不要用 ==,用 <= epsilon。我习惯设 epsilon = 1e-6。
  • 坐标系统一:确保所有物体在同一个坐标系下做检测。我踩过坑——把世界坐标和局部坐标混着用,结果碰撞检测全乱套。
  • 性能优化:如果检测对象超过 100 对,考虑空间分区(四叉树、网格)。别裸奔做 O(n²) 检测。
  • 边界情况:两个物体刚好相切算不算碰撞?这取决于你的业务逻辑。在路径规划中,相切通常算「危险」,需要避让。