Files
orni-attack/source/game/effects/firework_manager.cpp
T

220 lines
7.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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