使用分离轴定理(Separating Axis Theorem, SAT)检测多边形形与圆的碰撞

前言

SAT 是一种用于检测两个凸形状之间碰撞的方法,核心思想是:如果两个凸形状在任何可能的分离轴上投影不重叠,则这两个形状不会发生碰撞;反之,如果所有可能的分离轴上投影都重叠,则发生碰撞。

一、确定分离轴

对于矩形,分离轴可以是矩形的四条边的法线方向(即边与法线垂直的方向)。

对于圆形,可以选择从矩形的每个顶点到圆心的向量作为分离轴。

// 向量的法线(垂直方向)
Vector2 perpendicular(const Vector2& v) {
    return {-v.y, v.x};
}

// 向量的单位化
Vector2 normalize(const Vector2& v) {
    float length = std::sqrt(v.x * v.x + v.y * v.y);
    return {v.x / length, v.y / length};
}

// 获取多边形的分离轴(法线方向)
std::vector<Vector2> getAxes(const Polygon& poly) {
    std::vector<Vector2> axes;
    size_t vertexCount = poly.vertices.size();

    for (size_t i = 0; i < vertexCount; ++i) {
        Vector2 edge = subtract(poly.vertices[(i + 1) % vertexCount], poly.vertices[i]);
        axes.push_back(normalize(perpendicular(edge)));  // 多边形边的法线
    }

    return axes;
}

二、投影到分离轴

矩形的投影: 将矩形的四个顶点沿分离轴投影,得到一个区间(即线段),这个区间表示矩形在该轴上的投影。

圆的投影: 圆的投影比较简单,它的投影区间就是圆心在分离轴上的投影点加减一个圆的半径。

 

// 在分离轴上对多边形进行投影
void projectPolygonOnAxis(const Polygon& poly, const Vector2& axis, float& min, float& max) {
    min = max = dot(poly.vertices[0], axis);

    for (const auto& vertex : poly.vertices) {
        float projection = dot(vertex, axis);
        if (projection < min) min = projection;
        if (projection > max) max = projection;
    }
}

// 在分离轴上对圆进行投影
void projectCircleOnAxis(const Circle& circle, const Vector2& axis, float& min, float& max) {
    float centerProjection = dot(circle.center, axis);
    min = centerProjection - circle.radius;
    max = centerProjection + circle.radius;
}

三、检查投影重叠 

检查矩形和圆在每个分离轴上的投影区间是否重叠。如果在某个轴上发现投影区间不重叠,则可以断定矩形和圆不发生碰撞;如果所有轴上的投影区间都重叠,则可以断定发生了碰撞。

// 检查两个投影区间是否重叠
bool checkOverlap(float minA, float maxA, float minB, float maxB) {
    return !(maxA < minB || maxB < minA);
}

// 分离轴定理的核心函数,检测任意凸多边形和圆是否碰撞
bool satCirclePolygonCollision(const Circle& circle, const Polygon& poly) {
    std::vector<Vector2> axes = getAxes(poly);

    // 添加多边形顶点到圆心的向量作为额外的分离轴
    for (const auto& vertex : poly.vertices) {
        Vector2 axis = normalize(subtract(vertex, circle.center));
        axes.push_back(axis);
    }

    // 检查每个轴上的投影
    for (const auto& axis : axes) {
        float minPoly, maxPoly, minCircle, maxCircle;

        projectPolygonOnAxis(poly, axis, minPoly, maxPoly);
        projectCircleOnAxis(circle, axis, minCircle, maxCircle);

        if (!checkOverlap(minPoly, maxPoly, minCircle, maxCircle)) {
            return false; // 找到一个轴没有重叠,说明没有碰撞
        }
    }

    return true; // 所有轴都有重叠,说明发生碰撞
}

四、测试函数 

#include <iostream>
#include <cmath>
#include <vector>

struct Vector2 {
    float x, y;
};

struct Polygon {
    std::vector<Vector2> vertices; // 多边形的顶点集合
};

struct Circle {
    Vector2 center;
    float radius;
};

// 向量加法
Vector2 add(const Vector2& a, const Vector2& b) {
    return {a.x + b.x, a.y + b.y};
}

// 向量减法
Vector2 subtract(const Vector2& a, const Vector2& b) {
    return {a.x - b.x, a.y - b.y};
}

// 向量点积
float dot(const Vector2& a, const Vector2& b) {
    return a.x * b.x + a.y * b.y;
}

// 向量的法线(垂直方向)
Vector2 perpendicular(const Vector2& v) {
    return {-v.y, v.x};
}

// 向量的单位化
Vector2 normalize(const Vector2& v) {
    float length = std::sqrt(v.x * v.x + v.y * v.y);
    return {v.x / length, v.y / length};
}

// 获取多边形的分离轴(法线方向)
std::vector<Vector2> getAxes(const Polygon& poly) {
    std::vector<Vector2> axes;
    size_t vertexCount = poly.vertices.size();

    for (size_t i = 0; i < vertexCount; ++i) {
        Vector2 edge = subtract(poly.vertices[(i + 1) % vertexCount], poly.vertices[i]);
        axes.push_back(normalize(perpendicular(edge)));
    }

    return axes;
}

// 在分离轴上对多边形进行投影
void projectPolygonOnAxis(const Polygon& poly, const Vector2& axis, float& min, float& max) {
    min = max = dot(poly.vertices[0], axis);

    for (const auto& vertex : poly.vertices) {
        float projection = dot(vertex, axis);
        if (projection < min) min = projection;
        if (projection > max) max = projection;
    }
}

// 在分离轴上对圆进行投影
void projectCircleOnAxis(const Circle& circle, const Vector2& axis, float& min, float& max) {
    float centerProjection = dot(circle.center, axis);
    min = centerProjection - circle.radius;
    max = centerProjection + circle.radius;
}

// 检查两个投影区间是否重叠
bool checkOverlap(float minA, float maxA, float minB, float maxB) {
    return !(maxA < minB || maxB < minA);
}

// 分离轴定理的核心函数,检测任意凸多边形和圆是否碰撞
bool satCirclePolygonCollision(const Circle& circle, const Polygon& poly) {
    std::vector<Vector2> axes = getAxes(poly);

    // 添加多边形顶点到圆心的向量作为额外的分离轴
    for (const auto& vertex : poly.vertices) {
        Vector2 axis = normalize(subtract(vertex, circle.center));
        axes.push_back(axis);
    }

    // 检查每个轴上的投影
    for (const auto& axis : axes) {
        float minPoly, maxPoly, minCircle, maxCircle;

        projectPolygonOnAxis(poly, axis, minPoly, maxPoly);
        projectCircleOnAxis(circle, axis, minCircle, maxCircle);

        if (!checkOverlap(minPoly, maxPoly, minCircle, maxCircle)) {
            return false; // 找到一个轴没有重叠,说明没有碰撞
        }
    }

    return true; // 所有轴都有重叠,说明发生碰撞
}

int main() {
    Polygon poly = {{{0, 0}, {4, 0}, {4, 3}, {0, 3}}}; // 一个四边形(矩形)
    Circle circle = {{5, 1}, 1}; // 圆心在(5, 1),半径为1

    if (satCirclePolygonCollision(circle, poly)) {
        std::cout << "多边形和圆发生了碰撞" << std::endl;
    } else {
        std::cout << "多边形和圆没有发生碰撞" << std::endl;
    }

    return 0;
}

 

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值