feat(fireworks): infraestructura (manager + pool + render, sin spawn aún)

This commit is contained in:
JailDesigner committed 2026-05-21 17:22:46 +02:00
1 parent 5aab26f2ca
commit 56ce1a3236
7 files changed
+364

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
// firework.hpp - Partícula d'efecte starburst (una línia per partícula)
// © 2026 JailDesigner
#pragma once
#include <SDL3/SDL.h>
#include "core/types.hpp"
namespace Effects {
// Una partícula de firework: una línia que neix d'un punt origen, viatja
// radialment cap a fora i s'encongeix conforme perd velocitat.
//
// Geometria (es deriva al draw):
// head: extrem davanter (es mou amb velocity).
// tail = head − velocity_normalitzada × current_length.
//
// Cicle de vida:
// Fase 1 (temps_vida < grow_duration): current_length creix linealment
// de 0 a max_length. Brillor al màxim.
// Fase 2: current_length = max_length × (speed/initial_speed) i brillor
// amb la mateixa proporció. Mor quan length o brightness cauen sota
// llindar.
struct Firework {
Vec2 head; // Punta davantera (posició actual)
Vec2 velocity; // Velocidad en px/s
float acceleration; // Fricció lineal (px/s², negativa)
float current_length; // Longitud actual del segment (px)
float max_length; // Longitud màxima (final de la fase de creixement)
float grow_duration; // Temps de creixement de 0 a max_length (s)
float temps_vida; // Acumulador (s)
float initial_speed; // Speed inicial per a la proporció de fase 2
float brightness; // 0..1
SDL_Color color{}; // alpha==0 → oscilador global
bool active;
};
} // namespace Effects
+219
View File
@@ -0,0 +1,219 @@
// firework_manager.cpp - Implementació del gestor de fireworks
// © 2026 JailDesigner
#include "firework_manager.hpp"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include "core/defaults.hpp"
#include "core/rendering/line_renderer.hpp"
namespace Effects {
namespace {
// Random float in [-1, 1].
auto randSigned() -> float {
return ((std::rand() / static_cast<float>(RAND_MAX)) * 2.0F) - 1.0F;
}
// Rebot del head contra els límits del PLAYAREA (mateix patró que
// DebrisManager::bounceOffPlayArea).
void bounceOffPlayArea(Vec2& head, Vec2& velocity) {
const auto& bounds = Defaults::Zones::PLAYAREA;
constexpr float REST = Defaults::FX::Firework::RESTITUTION_BOUNDS;
const float MIN_X = bounds.x;
const float MAX_X = bounds.x + bounds.w;
const float MIN_Y = bounds.y;
const float MAX_Y = bounds.y + bounds.h;
if (head.x < MIN_X) {
head.x = MIN_X;
if (velocity.x < 0.0F) {
velocity.x = -velocity.x * REST;
}
}
if (head.x > MAX_X) {
head.x = MAX_X;
if (velocity.x > 0.0F) {
velocity.x = -velocity.x * REST;
}
}
if (head.y < MIN_Y) {
head.y = MIN_Y;
if (velocity.y < 0.0F) {
velocity.y = -velocity.y * REST;
}
}
if (head.y > MAX_Y) {
head.y = MAX_Y;
if (velocity.y > 0.0F) {
velocity.y = -velocity.y * REST;
}
}
}
} // namespace
FireworkManager::FireworkManager(Rendering::Renderer* renderer)
: renderer_(renderer) {
for (auto& fw : pool_) {
fw.active = false;
}
}
void FireworkManager::spawn(const Vec2& origen,
SDL_Color color,
float initial_speed,
int n_points,
float initial_brightness) {
if (n_points <= 0) {
return;
}
const float ANGLE_STEP = 2.0F * Defaults::Math::PI / static_cast<float>(n_points);
const float JITTER_RAD =
Defaults::FX::Firework::ANGULAR_JITTER_DEG * Defaults::Math::PI / 180.0F;
for (int i = 0; i < n_points; i++) {
Firework* fw = findFreeSlot();
if (fw == nullptr) {
return; // Pool ple
}
const float BASE_ANGLE = ANGLE_STEP * static_cast<float>(i);
const float JITTER = randSigned() * JITTER_RAD;
const float ANGLE = BASE_ANGLE + JITTER;
const float SPEED =
initial_speed + (randSigned() * Defaults::FX::Firework::SPEED_VARIATION);
fw->head = origen;
fw->velocity = {.x = std::cos(ANGLE) * SPEED, .y = std::sin(ANGLE) * SPEED};
fw->acceleration = Defaults::FX::Firework::FRICTION;
fw->current_length = 0.0F;
fw->max_length = Defaults::FX::Firework::MAX_LENGTH;
fw->grow_duration = Defaults::FX::Firework::GROW_DURATION;
fw->temps_vida = 0.0F;
fw->initial_speed = SPEED;
fw->brightness = initial_brightness;
fw->color = color;
fw->active = true;
}
}
void FireworkManager::update(float delta_time) {
for (auto& fw : pool_) {
if (!fw.active) {
continue;
}
fw.temps_vida += delta_time;
// 1. Fricció lineal (aplicar en la direcció del movement).
const float SPEED = std::sqrt(
(fw.velocity.x * fw.velocity.x) + (fw.velocity.y * fw.velocity.y));
if (SPEED > 1.0F) {
const float DIR_X = fw.velocity.x / SPEED;
const float DIR_Y = fw.velocity.y / SPEED;
float new_speed = SPEED + (fw.acceleration * delta_time);
new_speed = std::max(new_speed, 0.0F);
fw.velocity.x = DIR_X * new_speed;
fw.velocity.y = DIR_Y * new_speed;
} else {
fw.velocity = {.x = 0.0F, .y = 0.0F};
}
// 2. Avançar head.
fw.head.x += fw.velocity.x * delta_time;
fw.head.y += fw.velocity.y * delta_time;
// 3. Rebot contra PLAYAREA.
bounceOffPlayArea(fw.head, fw.velocity);
// 4. Calcular longitud i brillor segons fase.
if (fw.temps_vida < fw.grow_duration) {
// Fase 1: creixement lineal de 0 a max_length.
const float T = fw.temps_vida / fw.grow_duration;
fw.current_length = fw.max_length * T;
fw.brightness = Defaults::FX::Firework::INITIAL_BRIGHTNESS;
} else {
// Fase 2: longitud i brillor proporcionals a la velocity actual.
const float CURRENT_SPEED = std::sqrt(
(fw.velocity.x * fw.velocity.x) + (fw.velocity.y * fw.velocity.y));
const float RATIO = (fw.initial_speed > 0.01F)
? std::min(CURRENT_SPEED / fw.initial_speed, 1.0F)
: 0.0F;
fw.current_length = fw.max_length * RATIO;
fw.brightness = Defaults::FX::Firework::INITIAL_BRIGHTNESS * RATIO;
}
// 5. Morir si la longitud o el brillor cauen sota llindar.
if (fw.current_length < Defaults::FX::Firework::MIN_LENGTH ||
fw.brightness < Defaults::FX::Firework::MIN_BRIGHTNESS) {
fw.active = false;
}
}
}
void FireworkManager::draw() const {
for (const auto& fw : pool_) {
if (!fw.active) {
continue;
}
// tail = head − velocity_normalitzada × current_length.
const float SPEED = std::sqrt(
(fw.velocity.x * fw.velocity.x) + (fw.velocity.y * fw.velocity.y));
if (SPEED < 0.01F) {
continue; // Sense direcció no podem orientar la línia.
}
const float DIR_X = fw.velocity.x / SPEED;
const float DIR_Y = fw.velocity.y / SPEED;
const Vec2 TAIL = {
.x = fw.head.x - (DIR_X * fw.current_length),
.y = fw.head.y - (DIR_Y * fw.current_length),
};
Rendering::linea(renderer_,
static_cast<int>(fw.head.x),
static_cast<int>(fw.head.y),
static_cast<int>(TAIL.x),
static_cast<int>(TAIL.y),
fw.brightness,
0.0F,
fw.color);
}
}
void FireworkManager::reset() {
for (auto& fw : pool_) {
fw.active = false;
}
}
auto FireworkManager::getActiveCount() const -> int {
int count = 0;
for (const auto& fw : pool_) {
if (fw.active) {
count++;
}
}
return count;
}
auto FireworkManager::findFreeSlot() -> Firework* {
for (auto& fw : pool_) {
if (!fw.active) {
return &fw;
}
}
return nullptr;
}
} // namespace Effects
+50
View File
@@ -0,0 +1,50 @@
// firework_manager.hpp - Gestor de bursts radials (fireworks)
// © 2026 JailDesigner
#pragma once
#include <SDL3/SDL.h>
#include <array>
#include "core/defaults.hpp"
#include "core/rendering/render_context.hpp"
#include "core/types.hpp"
#include "firework.hpp"
namespace Effects {
// Pool de partícules. spawn() emet un burst d'N línies radials des
// d'`origen`. Cada partícula viu independent (update/draw/rebot).
class FireworkManager {
public:
explicit FireworkManager(Rendering::Renderer* renderer);
// Emet un burst radial:
// origen: punt central del burst.
// color: color de les línies (heretat del pare).
// initial_speed: velocitat radial inicial (px/s).
// n_points: nombre de línies. Default Defaults::FX::Firework::N_POINTS.
// initial_brightness: 0..1.
void spawn(const Vec2& origen,
SDL_Color color,
float initial_speed = Defaults::FX::Firework::SPEED,
int n_points = Defaults::FX::Firework::N_POINTS,
float initial_brightness = Defaults::FX::Firework::INITIAL_BRIGHTNESS);
void update(float delta_time);
void draw() const;
void reset();
[[nodiscard]] auto getActiveCount() const -> int;
private:
Rendering::Renderer* renderer_;
static constexpr int POOL_SIZE = Defaults::FX::Firework::POOL_SIZE;
std::array<Firework, POOL_SIZE> pool_;
auto findFreeSlot() -> Firework*;
};
} // namespace Effects