Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / src/systems/particle_system.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/particle_system.cpp 4.2 KB · 158 lines raw
#include "particle_system.hpp"

#include <SDL3/SDL.h>
#include <algorithm>

#include "entities/camera.hpp"
#include "entities/game_object.hpp"
#include "utils/config.hpp"

namespace
Nimbin
{

namespace {

// Random point on a unit sphere — used to spread burst velocities evenly.
// Uses the "two uniform angles" method; fine for fireworks.
Vec3 random_UnitSphere()
{
    double u    = Random::unit() * 2.0 - 1.0;
    double phi  = Random::unit() * SDL_PI_D * 2.0;
    double s    = SDL_sqrt(1.0 - u * u);
    return Vec3{ s * SDL_cos(phi), u, s * SDL_sin(phi) };
}

constexpr float  PARTICLE_HALF = 0.04f;

} // namespace


void
ParticleSystem::emit_FireworkBurst(const Vec3& center,
                                   SDL_FColor color,
                                   int count)
{
    _particles.reserve(_particles.size() + static_cast<size_t>(count));

    for (int i = 0; i < count; i++) {
        Particle p;
        p.pos = center;

        // Speed 5..10 m/s in a random direction.
        Vec3   dir   = random_UnitSphere();
        double speed = 5.0 + Random::unit() * 5.0;
        p.vel = dir * speed;

        // Slight upward bias so the burst reads as an explosion, not implosion.
        p.vel.y += 1.5;

        p.color    = color;
        p.age      = 0.0;
        p.lifetime = 2.5 + Random::unit() * 1.5;
        p.yaw      = static_cast<float>(Random::unit() * SDL_PI_D * 2.0);
        p.spin     = static_cast<float>((Random::unit() - 0.5) * 12.0);
        p.alive    = true;

        _particles.push_back(p);
    }
}


void
ParticleSystem::update(double dt,
                       const DynArray<GameObject*>& solid_colliders,
                       float world_scale)
{
    const double ws = static_cast<double>(world_scale);

    for (Particle& p : _particles) {
        if (!p.alive) continue;

        p.age += dt;
        if (p.age >= p.lifetime) { p.alive = false; continue; }

        p.vel.y -= Config::Physics::proj_gravity * dt;

        p.pos.x += p.vel.x * dt;
        p.pos.y += p.vel.y * dt;
        p.pos.z += p.vel.z * dt;

        p.yaw += p.spin * static_cast<float>(dt);

        if (p.pos.y < -ws + 0.05 || p.pos.y >  ws - 0.05 ||
            p.pos.x < -ws + 0.05 || p.pos.x >  ws - 0.05 ||
            p.pos.z < -ws + 0.05 || p.pos.z >  ws - 0.05)
        {
            p.alive = false;
            continue;
        }

        // Solid colliders — vanish on contact with pults, plates, platforms.
        // Cheap point-in-AABB check; particles are tiny so this is fine.
        for (const GameObject* obj : solid_colliders) {
            if (!obj || !obj->solid) continue;
            const Vec3 mn = obj->aabb_Min();
            const Vec3 mx = obj->aabb_Max();
            if (p.pos.x >= mn.x && p.pos.x <= mx.x &&
                p.pos.y >= mn.y && p.pos.y <= mx.y &&
                p.pos.z >= mn.z && p.pos.z <= mx.z)
            {
                p.alive = false;
                break;
            }
        }
    }

    // Compact: drop dead particles.
    _particles.erase(
        std::remove_if(_particles.begin(), _particles.end(),
            [](const Particle& p) { return !p.alive; }),
        _particles.end());
}


void
ParticleSystem::draw(GlRenderer& gl, const Camera& cam, const Light& light) const
{
    if (_particles.empty()) return;

    Light glow = light;

    glow.ambient = { 0.7, 0.7, 0.7 };
    glow.sun_intensity        = 0.3f;
    glow.flashlight_intensity = 0.0f;

    Mat4 view = buildViewMat4    (cam);
    Mat4 proj = gl.get_Projection();

    gl.set_Wireframe(false);
    gl.set_Light    (glow);

    for (const Particle& p : _particles) {
        // Fade out over the last 30% of lifetime.
        double tail   = p.lifetime * 0.3;
        double t_left = p.lifetime - p.age;
        float fade = (t_left < tail)
            ? static_cast<float>(t_left / tail)
            : 1.0f;
        if (fade < 0.0f) fade = 0.0f;

        Mat4 model = buildModelMat4(p.pos, p.yaw);
        Mat4 mvp   = Mat4::mul(proj, Mat4::mul(view, model));

        gl.set_MVP      (model, mvp);
        gl.set_NormalMat(model);
        gl.set_Color    (p.color.r * fade, p.color.g * fade, p.color.b * fade);
        gl.draw_Box     (PARTICLE_HALF, PARTICLE_HALF, PARTICLE_HALF);
    }
}


void
ParticleSystem::clear()
{
    _particles.clear();
}

} // namespace Nimbin