sdl_runtime_compiler git · main
SDL3 game for running and compiling code at runtime
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.gzsrc/systems/particle_system.cpp 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