碰撞检测|分离轴算法(SAT)

碰撞检测|分离轴算法(SAT)

​ 在碰撞检测中如何检测两个物体是否相交?这里就介绍一种算法分离轴算法(SAT)。分离轴算法很好理解:如果两个物体彼此分离,那么一定能找到一条(只需要一条)轴线将这两个物体分隔在轴线的两侧,如图。

相反如果两个物体相交,那么一定找不到一条(只需要一条)轴线能将两个物体分隔在轴线的两侧。

​ 算法的原理就是这样,那么我们应该怎么将其翻译成代码呢?先仔细分析这个问题。注意到两个分离的物体在垂直于分离轴的直线上的投影是不相交的如图。

那么我们就将问题转为为如何找到一条轴使得两个物体在轴上的投影区间没有交集,事实上如果两个物体分离那么分离轴有无数条,我们只需找到一条就行,那么找轴的方式就有很多种。不妨我们将多边形的边作为轴,我们遍历多边形的每一条边,如果存在一条轴使得两个物体在轴上的投影区间没有交集那么我们就知道这两个物体没有发生碰撞。如图

可以看到在三角形ABC以AB边为轴做两个物体的投影,[A,B],[I,J]观察可知区间[A,B],[I,J]无交集。事实上,判断两个区间是否有交集可以用以下算法

#include <iostream>
using namespace std;

//闭区间
struct Interval{
    float min_value;
    float max_value;
};

bool IntersectionIntervals(Interval& a,Interval &b){
    // 检查是否有交集
    if (a.min_value <= b.max_value && a.max_value >= b.min_value) {
        return true;  // 存在交集
    }
    return false;  // 不存在交集

}
int main(){
    Interval AB = {1,4};
    Interval IJ = {4,8};
    if(IntersectionIntervals(AB,IJ)){
        cout<<"存在交集";
    }
    else{
        cout<<"不纯在交集";
    }
    
    return 0;
}

​ 实际上,SAT算法只能用于判断凸多边形之间的碰撞,与凸多边形与圆形的碰撞,圆有一条特殊的轴,它由圆心与 离圆最近的多边形上的点连接而成。分析办法与多边形之间的碰撞类似。而其他非多边形不能使用SAT算法处理。

static IntersectData IntersectPolygons(const FlatVector& mass_center_a, const std::vector<FlatVector>& vertices_a,
    const FlatVector& mass_center_b, const std::vector<FlatVector>& vertices_b) {
    IntersectData data;
    data.depth = std::numeric_limits<float>::max();

    auto CheckAxis = [&](const FlatVector& axis, const std::vector<FlatVector>& vertices_a, const std::vector<FlatVector>& vertices_b) {
        // 计算多边形在该轴上的投影
        FlatVector project_polygon_a = ProjectVertices(axis, vertices_a);
        FlatVector project_polygon_b = ProjectVertices(axis, vertices_b);

        // 判断投影是否重叠
        if (project_polygon_a.y < project_polygon_b.x || project_polygon_b.y < project_polygon_a.x) {
            
            return false;
        }

        // 计算投影的重叠深度
        float axis_depth = std::min(project_polygon_b.y - project_polygon_a.x, project_polygon_a.y - project_polygon_b.x);     

        // 如果深度小于当前最小深度,则更新法向量和深度
        if (axis_depth < data.depth) {
            data.depth = axis_depth;
            data.normal = axis;
        }
        return true;
        };

    // 遍历多边形A的每条边
    for (int i = 0; i < vertices_a.size(); i++) {
        FlatVector v_a = vertices_a[i];
        FlatVector v_b = vertices_a[(i + 1) % vertices_a.size()];

        // 计算边的法向量
        FlatVector edge_vector = v_b - v_a;
        FlatVector axis;

        //保持法向量都指向图形外面
        if (edge_vector.x * edge_vector.y > 0) {
            axis = FlatVector(edge_vector.y, -edge_vector.x);
        }
        else if (edge_vector.x * edge_vector.y < 0) {
            axis = FlatVector(-edge_vector.y, edge_vector.x);
        }
        else {
            axis = FlatVector(edge_vector.y, -edge_vector.x);
        }
        axis.normalize();

        if (!CheckAxis(axis, vertices_a, vertices_b)) {
            
            return data; // 没有重叠,返回
        }
    }

    // 遍历多边形B的每条边
    for (int i = 0; i < vertices_b.size(); i++) {
        FlatVector v_a = vertices_b[i];
        FlatVector v_b = vertices_b[(i + 1) % vertices_b.size()];

        // 计算边的法向量
        FlatVector edge_vector = v_b - v_a;
        FlatVector axis;

        //保持法向量都指向图形外面
        if (edge_vector.x * edge_vector.y >0) {
             axis = FlatVector(edge_vector.y, -edge_vector.x);
        }
        else if (edge_vector.x * edge_vector.y < 0) {
             axis = FlatVector(-edge_vector.y, edge_vector.x);
        }
        else {
            axis = FlatVector(edge_vector.y, -edge_vector.x);
        }
        
        axis.normalize();

        if (!CheckAxis(axis, vertices_b, vertices_a)) {
           
            return data; // 没有重叠,返回
        }
    }
SAT算法(Separating Axis Theorem,分离轴定理)是一种常用于二维和三维碰撞检测算法。它的基本思路是,如果两个物体之间不存在分离轴(即二者的投影重叠),则它们一定发生了碰撞。下面我们以二维碰撞检测为例,给出一个基于SAT算法的示例。 假设我们有两个矩形,分别用四个顶点表示为: 矩形A:A1(x1, y1), A2(x2, y1), A3(x2, y2), A4(x1, y2) 矩形B:B1(x3, y3), B2(x4, y3), B3(x4, y4), B4(x3, y4) 首先,我们需要找到所有可能成为分离轴的轴线。对于一个矩形而言,其边界上的法向量即可作为该矩形的分离轴。因此,我们需要计算出矩形A和矩形B的所有边界法向量,共计8条。具体计算方法如下: 矩形A的边界法向量: - AB1: (y2 - y1, -(x2 - x1)) - A1A4: (-(y2 - y1), x2 - x1) - A4A3: (y2 - y1, -(x2 - x1)) - A3A2: (-(y2 - y1), x2 - x1) 矩形B的边界法向量: - B1B2: (y4 - y3, -(x4 - x3)) - B2B3: (-(y4 - y3), x4 - x3) - B3B4: (y4 - y3, -(x4 - x3)) - B4B1: (-(y4 - y3), x4 - x3) 然后,我们需要将矩形A和矩形B分别投影到每个分离轴上,并计算它们在该轴上的投影重叠程度。如果存在任意一条分离轴上二者的投影不重叠,则二者一定不相交。具体计算方法如下: 对于一条分离轴(axis_x, axis_y),矩形A在该轴上的投影长度为: projA = (axis_x * x1 + axis_y * y1, axis_x * x2 + axis_y * y2, axis_x * x3 + axis_y * y3, axis_x * x4 + axis_y * y4) 即将矩形A的四个顶点坐标(x, y)带入分离轴的方程(ax + by = c)中,得到该点在该轴上的投影长度。 同理,矩形B在该轴上的投影长度为: projB = (axis_x * x3 + axis_y * y3, axis_x * x4 + axis_y * y4, axis_x * x1 + axis_y * y1, axis_x * x2 + axis_y * y2) 接下来,我们需要判断投影重叠程度。如果二者在该轴上的投影重叠,则它们在该轴上没有分离。否则,它们在该轴上分离。判断投影重叠程度的方法如下: maxA = max(projA) minA = min(projA) maxB = max(projB) minB = min(projB) 如果maxA < minB 或者 maxB < minA,则二者在该轴上分离。 最后,我们需要判断所有可能的分离轴上二者是否都有重叠。如果所有分离轴上二者都有重叠,则它们相交;否则,它们不相交。具体实现如下: def check_collision(A, B): for axis_x, axis_y in A.edges + B.edges: projA = [axis_x * x + axis_y * y for x, y in A.vertices] projB = [axis_x * x + axis_y * y for x, y in B.vertices] maxA, minA = max(projA), min(projA) maxB, minB = max(projB), min(projB) if maxA < minB or maxB < minA: return False return True 其中,A.edges和B.edges分别为矩形A和矩形B的所有边界法向量,A.vertices和B.vertices分别为矩形A和矩形B的所有顶点坐标。 以上就是一个基于SAT算法碰撞检测示例。在实际开发中,我们可以将其应用于各种物体的碰撞检测,从而实现更加生动、丰富的游戏体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值