Intermediatesdl3
Intermediate

Implementing SAT Collision Detection in SDL3

Learn how to implement the Separating Axis Theorem (SAT) collision detection in SDL3. This tutorial covers the logic, code example, and integration in a 2D game environment.

Prerequisites / Tools :
  • SDL3
Nicolas Perdu
Written by Nicolas PerduSoftware engineer

SAT Collision Detection in SDL3

The Separating Axis Theorem (SAT)

The Separating Axis Theorem (SAT) states:

Two convex shapes do not collide if there exists at least one axis onto which their projections do not overlap.

To check this:

The Vector struct

We need a struct to represent a 2D vector along with helper methods to compute the dot product and the perpendicular vector:

struct Vector2 {
    float x, y;
    Vector2 operator-(const Vector2& o) const { return {x - o.x, y - o.y}; }
    float dot(const Vector2& o) const { return x * o.x + y * o.y; }
    Vector2 perpendicular() const { return {-y, x}; }
};

Handle OBB (oriented bounding boxes)

We need a function to rotate a point around a center point:

Vector2 rotate_point(Vector2 point, Vector2 center, float angleRad) {
    float s = sin(angleRad), c = cos(angleRad);
    point.x -= center.x;
    point.y -= center.y;
    float xnew = point.x * c - point.y * s;
    float ynew = point.x * s + point.y * c;
    return { xnew + center.x, ynew + center.y };
}

Get and rotate the corners

std::vector<Vector2> get_corners(SDL_FRect rect, float angleRad) {
    Vector2 center = {rect.x + rect.w / 2, rect.y + rect.h / 2};
    std::vector<Vector2> corners = {
        {rect.x, rect.y},
        {rect.x + rect.w, rect.y},
        {rect.x + rect.w, rect.y + rect.h},
        {rect.x, rect.y + rect.h}
    };
    for (auto& corner : corners) {
        corner = rotate_point(corner, center, angleRad);
    }
    return corners;
}

Projection onto an axis

Here we use the dot product to project the polygon vertices onto an axis and compute the scalar minimum and maximum range:

void project(const std::vector<Vector2>& corners, Vector2 axis, float& min, float& max) {
    min = max = corners[0].dot(axis);
    for (size_t i = 1; i < corners.size(); ++i) {
        float p = corners[i].dot(axis);
        if (p < min) min = p;
        if (p > max) max = p;
    }
}

Overlap on an axis

We need a function that returns false if there is a gap (a separating axis is found, meaning no collision):

bool overlap_on_axis(const std::vector<Vector2>& a, const std::vector<Vector2>& b, Vector2 axis) {
    float minA, maxA, minB, maxB;
    project(a, axis, minA, maxA);
    project(b, axis, minB, maxB);
    return !(maxA < minB || maxB < minA);
}

SAT Collision detection function

Here is a recap of the SAT collision algorithm:

Minimum Translation Vector (MTV)

When all projections on the SAT axes overlap, we know there’s a collision. But:

bool SAT_collision(SDL_FRect aRect, float aAngle, SDL_FRect bRect, float bAngle, Vector2& outMTV) {
    auto aCorners = get_corners(aRect, aAngle);
    auto bCorners = get_corners(bRect, bAngle);
    std::vector<Vector2> axes;
    axes.reserve(8);
        
    for (int i = 0; i < 4; ++i) {
        Vector2 edge = aCorners[(i + 1) % 4] - aCorners[i];
        axes.emplace_back(edge.perpendicular());
    }
    for (int i = 0; i < 4; ++i) {
        Vector2 edge = bCorners[(i + 1) % 4] - bCorners[i];
        axes.emplace_back(edge.perpendicular());
    }

    float minOverlap = INFINITY;
    Vector2 smallestAxis = {0, 0};

    for (auto axis : axes) {
        // Normalize axis to get accurate MTV magnitude
        float length = sqrt(axis.x * axis.x + axis.y * axis.y);
        if (length == 0.0f) continue; // skip invalid axis
        axis = {axis.x / length, axis.y / length};

        float minA, maxA, minB, maxB;
        project(aCorners, axis, minA, maxA);
        project(bCorners, axis, minB, maxB);

        if (maxA < minB || maxB < minA) {
            return false; // Separating axis found -> no collision
        }

        float overlap = std::min(maxA, maxB) - std::max(minA, minB);
        if (overlap < minOverlap) {
            minOverlap = overlap;
            smallestAxis = axis;
        }
    }

    outMTV = { smallestAxis.x * minOverlap, smallestAxis.y * minOverlap };
    return true;
}

Download the full project : Implementing SAT Collision Detection in SDL3

Need another OS ? => Windows, Mac, Linux