Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / src/systems/collision.cpp

sdl_runtime_compiler git · main

SDL3 game for running and compiling code at runtime

sdl3 c++ compiler dlopen cmake · first commit 2026-04-19 · last commit 2026-07-03 (3 months ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-runtime-compiler

C++ 72.3% C 26.2%
git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gz
src/systems/collision.cpp 5.3 KB · 161 lines raw
#include <SDL3/SDL.h>
#include <algorithm>

#include "entities/game_object.hpp"
#include "systems/collision.hpp"

namespace
Nimbin
{

namespace Collision
{

static constexpr double FRICTION_IMPULSE_CUTOFF = 2.0;

bool
aabb_Overlap(const GameObject& a, const GameObject& b)
{
    return a.aabb_Min().x <= b.aabb_Max().x &&
           a.aabb_Max().x >= b.aabb_Min().x &&
           a.aabb_Min().y <= b.aabb_Max().y &&
           a.aabb_Max().y >= b.aabb_Min().y &&
           a.aabb_Min().z <= b.aabb_Max().z &&
           a.aabb_Max().z >= b.aabb_Min().z;
}


Manifold
aabb_Manifold(const GameObject& a, const GameObject& b)
{
    Manifold m;

    double overlap_x = SDL_min(a.aabb_Max().x, b.aabb_Max().x) -
                SDL_max(a.aabb_Min().x, b.aabb_Min().x);
    double overlap_y = SDL_min(a.aabb_Max().y, b.aabb_Max().y) -
                SDL_max(a.aabb_Min().y, b.aabb_Min().y);
    double overlap_z = SDL_min(a.aabb_Max().z, b.aabb_Max().z) -
                SDL_max(a.aabb_Min().z, b.aabb_Min().z);

    if (overlap_x <= 0.0 || overlap_y <= 0.0 || overlap_z <= 0.0)
        return m;

    m.hit = true;

    if (overlap_x < overlap_y && overlap_x < overlap_z) {
        m.overlap  = overlap_x;
        m.normal.x = (a.pos.x < b.pos.x) ? -1.0 : 1.0;
    }
    else if (overlap_y < overlap_z) {
        m.overlap  = overlap_y;
        m.normal.y = (a.pos.y < b.pos.y) ? -1.0 : 1.0;
    }
    else {
        m.overlap  = overlap_z;
        m.normal.z = (a.pos.z < b.pos.z) ? -1.0 : 1.0;
    }
    return m;
}


void
resolve_Objects(GameObject& a, GameObject& b)
{
    Manifold manifold = aabb_Manifold(a, b);
    if (!manifold.hit) return;

    const bool a_static = (a.body.mode == PhysicsMode::Static);
    const bool b_static = (b.body.mode == PhysicsMode::Static);

    const double share_a = b_static ? 1.0 : 0.5;
    const double share_b = a_static ? 1.0 : 0.5;
    if (!a_static) a.pos = a.pos + manifold.normal * (manifold.overlap * share_a);
    if (!b_static) b.pos = b.pos - manifold.normal * (manifold.overlap * share_b);

    // impulse resolution
    const double rel_vel = Math::dot(b.body.vel - a.body.vel, manifold.normal);
    if (rel_vel >= 0.0) return;

    const double inv_mass_a = a_static ? 0.0 : 1.0 / a.body.mass;
    const double inv_mass_b = b_static ? 0.0 : 1.0 / b.body.mass;
    const double e          = SDL_min(a.body.restitution, b.body.restitution);
    const double impulse    = -(1.0 + e) * rel_vel / (inv_mass_a + inv_mass_b);

    auto apply_impulse = [&](GameObject& o, double inv_mass, double sign) {
        if (inv_mass == 0.0) return;
        o.body.vel = o.body.vel + manifold.normal * (sign * impulse * inv_mass);

        if (SDL_fabs(impulse) < FRICTION_IMPULSE_CUTOFF) {
            const double vn  = Math::dot(o.body.vel, manifold.normal);
            Vec3         v_t = o.body.vel - manifold.normal * vn;
            v_t = v_t * o.body.friction;
            if (Math::length(v_t) < 0.02) v_t = Vec3{};
            o.body.vel = manifold.normal * vn + v_t;
        }
    };
    apply_impulse(a, inv_mass_a, +1.0);
    apply_impulse(b, inv_mass_b, -1.0);
}


Vec3
reflect(const Vec3& vel, const Vec3& normal, double restitution)
{
    double d = Math::dot(vel, normal);
    return {
        vel.x - (1.0 + restitution) * d * normal.x,
        vel.y - (1.0 + restitution) * d * normal.y,
        vel.z - (1.0 + restitution) * d * normal.z,
    };
}


SweptResult
swept_RayAabb(const Vec3& origin, const Vec3& dir,
              const Vec3& box_min, const Vec3& box_max)
{
    double t_enter   = -1e18;   // latest entry time across slabs (real, unclamped)
    double t_exit    =  1e18;   // earliest exit time across slabs
    int    axis_in   = -1;      // which axis produced t_enter
    double sign_in   =  0.0;    // sign overlap_f the entry-face normal overlap_n that axis

    auto slab = [&](double o, double d, double lo, double hi, int ax) -> bool
    {
        if (SDL_fabs(d) < 1e-9) {
            // Movement parallel to slab. Miss only if origin is outside slab.
            return (o >= lo && o <= hi);
        }
        double inv  = 1.0 / d;
        double t1   = (lo - o) * inv;    // enters slab from `-` face if d>0
        double t2   = (hi - o) * inv;    // exits slab through `+` face if d>0
        double sign = -1.0;              // assume entry through `-` face
        if (t1 > t2) {                   // moving in -axis direction
            double tmp = t1; t1 = t2; t2 = tmp;
            sign = 1.0;                  // entry through `+` face instead
        }
        if (t1 > t_enter) { t_enter = t1; axis_in = ax; sign_in = sign; }
        if (t2 < t_exit)  { t_exit  = t2; }
        return true;
    };

    if (!slab(origin.x, dir.x, box_min.x, box_max.x, 0)) return {};
    if (!slab(origin.y, dir.y, box_min.y, box_max.y, 1)) return {};
    if (!slab(origin.z, dir.z, box_min.z, box_max.z, 2)) return {};

    // Miss if the slabs don't overlap, overlap_r the hit window is outside [0, 1]
    if (t_enter > t_exit) return {};
    if (t_exit  < 0.0)    return {};   // already past the box
    if (t_enter > 1.0)    return {};   // hit is in a future step

    SweptResult r;
    r.hit = true;
    r.t   = SDL_max(t_enter, 0.0);     // clamp to this step
    if (axis_in == 0) r.normal.x = sign_in;
    else if (axis_in == 1) r.normal.y = sign_in;
    else if (axis_in == 2) r.normal.z = sign_in;
    return r;
}

}

} // namespace Nimbin