382 lines
14 KiB
C++
382 lines
14 KiB
C++
// debris_manager.cpp - Implementació del gestor de fragments
|
||
// © 2025 Port a C++20 amb SDL3
|
||
|
||
#include "debris_manager.hpp"
|
||
|
||
#include <cmath>
|
||
#include <cstdlib>
|
||
#include <iostream>
|
||
|
||
#include "core/audio/audio.hpp"
|
||
#include "core/defaults.hpp"
|
||
#include "core/rendering/line_renderer.hpp"
|
||
|
||
namespace Effects {
|
||
|
||
// Helper: transformar punt amb rotació, escala i trasllació
|
||
// (Copiat de shape_renderer.cpp:12-34)
|
||
static Punt transform_point(const Punt& point, const Punt& shape_centre, const Punt& posicio, float angle, float escala) {
|
||
// 1. Centrar el punt respecte al centre de la forma
|
||
float centered_x = point.x - shape_centre.x;
|
||
float centered_y = point.y - shape_centre.y;
|
||
|
||
// 2. Aplicar escala al punt centrat
|
||
float scaled_x = centered_x * escala;
|
||
float scaled_y = centered_y * escala;
|
||
|
||
// 3. Aplicar rotació
|
||
float cos_a = std::cos(angle);
|
||
float sin_a = std::sin(angle);
|
||
|
||
float rotated_x = scaled_x * cos_a - scaled_y * sin_a;
|
||
float rotated_y = scaled_x * sin_a + scaled_y * cos_a;
|
||
|
||
// 4. Aplicar trasllació a posició mundial
|
||
return {rotated_x + posicio.x, rotated_y + posicio.y};
|
||
}
|
||
|
||
DebrisManager::DebrisManager(SDL_Renderer* renderer)
|
||
: renderer_(renderer) {
|
||
// Inicialitzar tots els debris com inactius
|
||
for (auto& debris : debris_pool_) {
|
||
debris.actiu = false;
|
||
}
|
||
}
|
||
|
||
void DebrisManager::explotar(const std::shared_ptr<Graphics::Shape>& shape,
|
||
const Punt& centre,
|
||
float angle,
|
||
float escala,
|
||
float velocitat_base,
|
||
float brightness,
|
||
const Punt& velocitat_objecte,
|
||
float velocitat_angular,
|
||
float factor_herencia_visual,
|
||
const std::string& sound) {
|
||
if (!shape || !shape->es_valida()) {
|
||
return;
|
||
}
|
||
|
||
// Reproducir sonido de explosión
|
||
Audio::get()->playSound(sound, Audio::Group::GAME);
|
||
|
||
// Obtenir centre de la forma per a transformacions
|
||
const Punt& shape_centre = shape->get_centre();
|
||
|
||
// Iterar sobre totes les primitives de la forma
|
||
for (const auto& primitive : shape->get_primitives()) {
|
||
// Processar cada segment de línia
|
||
std::vector<std::pair<Punt, Punt>> segments;
|
||
|
||
if (primitive.type == Graphics::PrimitiveType::POLYLINE) {
|
||
// Polyline: extreure segments consecutius
|
||
for (size_t i = 0; i < primitive.points.size() - 1; i++) {
|
||
segments.push_back({primitive.points[i], primitive.points[i + 1]});
|
||
}
|
||
} else { // PrimitiveType::LINE
|
||
// Line: un únic segment
|
||
if (primitive.points.size() >= 2) {
|
||
segments.push_back({primitive.points[0], primitive.points[1]});
|
||
}
|
||
}
|
||
|
||
// Crear debris per a cada segment
|
||
for (const auto& [local_p1, local_p2] : segments) {
|
||
// 1. Transformar punts locals → coordenades mundials
|
||
Punt world_p1 =
|
||
transform_point(local_p1, shape_centre, centre, angle, escala);
|
||
Punt world_p2 =
|
||
transform_point(local_p2, shape_centre, centre, angle, escala);
|
||
|
||
// 2. Trobar slot lliure
|
||
Debris* debris = trobar_slot_lliure();
|
||
if (!debris) {
|
||
std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n";
|
||
return; // Pool ple
|
||
}
|
||
|
||
// 3. Inicialitzar geometria
|
||
debris->p1 = world_p1;
|
||
debris->p2 = world_p2;
|
||
|
||
// 4. Calcular direcció d'explosió (radial, des del centre cap a fora)
|
||
Punt direccio = calcular_direccio_explosio(world_p1, world_p2, centre);
|
||
|
||
// 5. Velocitat inicial (base ± variació aleatòria + velocitat heretada)
|
||
float speed =
|
||
velocitat_base +
|
||
((std::rand() / static_cast<float>(RAND_MAX)) * 2.0f - 1.0f) *
|
||
Defaults::Physics::Debris::VARIACIO_VELOCITAT;
|
||
|
||
// Heredar velocitat de l'objecte original (suma vectorial)
|
||
debris->velocitat.x = direccio.x * speed + velocitat_objecte.x;
|
||
debris->velocitat.y = direccio.y * speed + velocitat_objecte.y;
|
||
debris->acceleracio = Defaults::Physics::Debris::ACCELERACIO;
|
||
|
||
// 6. Herència de velocitat angular amb cap + conversió d'excés
|
||
|
||
// 6a. Rotació de TRAYECTORIA amb cap + conversió tangencial
|
||
if (std::abs(velocitat_angular) > 0.01f) {
|
||
// FASE 1: Aplicar herència i variació (igual que abans)
|
||
float factor_herencia =
|
||
Defaults::Physics::Debris::FACTOR_HERENCIA_MIN +
|
||
(std::rand() / static_cast<float>(RAND_MAX)) *
|
||
(Defaults::Physics::Debris::FACTOR_HERENCIA_MAX -
|
||
Defaults::Physics::Debris::FACTOR_HERENCIA_MIN);
|
||
|
||
float velocitat_ang_heretada = velocitat_angular * factor_herencia;
|
||
|
||
float variacio =
|
||
(std::rand() / static_cast<float>(RAND_MAX)) * 0.2f - 0.1f;
|
||
velocitat_ang_heretada *= (1.0f + variacio);
|
||
|
||
// FASE 2: Aplicar cap i calcular excés
|
||
constexpr float CAP = Defaults::Physics::Debris::VELOCITAT_ROT_MAX;
|
||
float abs_ang = std::abs(velocitat_ang_heretada);
|
||
float sign_ang = (velocitat_ang_heretada >= 0.0f) ? 1.0f : -1.0f;
|
||
|
||
if (abs_ang > CAP) {
|
||
// Excés: convertir a velocitat tangencial
|
||
float excess = abs_ang - CAP;
|
||
|
||
// Radi de la forma (enemics = 20 px)
|
||
float radius = 20.0f;
|
||
|
||
// Velocitat tangencial = ω_excés × radi
|
||
float v_tangential = excess * radius;
|
||
|
||
// Direcció tangencial: perpendicular a la radial (90° CCW)
|
||
// Si direccio = (dx, dy), tangent = (-dy, dx)
|
||
float tangent_x = -direccio.y;
|
||
float tangent_y = direccio.x;
|
||
|
||
// Afegir velocitat tangencial (suma vectorial)
|
||
debris->velocitat.x += tangent_x * v_tangential;
|
||
debris->velocitat.y += tangent_y * v_tangential;
|
||
|
||
// Aplicar cap a velocitat angular (preservar signe)
|
||
debris->velocitat_rot = sign_ang * CAP;
|
||
} else {
|
||
// Per sota del cap: comportament normal
|
||
debris->velocitat_rot = velocitat_ang_heretada;
|
||
}
|
||
} else {
|
||
debris->velocitat_rot = 0.0f; // Nave: sin curvas
|
||
}
|
||
|
||
// 6b. Rotació VISUAL (proporcional según factor_herencia_visual)
|
||
if (factor_herencia_visual > 0.01f && std::abs(velocitat_angular) > 0.01f) {
|
||
// Heredar rotación visual con factor proporcional
|
||
debris->velocitat_rot_visual = debris->velocitat_rot * factor_herencia_visual;
|
||
|
||
// Variació aleatòria petita (±5%) per naturalitat
|
||
float variacio_visual =
|
||
(std::rand() / static_cast<float>(RAND_MAX)) * 0.1f - 0.05f;
|
||
debris->velocitat_rot_visual *= (1.0f + variacio_visual);
|
||
} else {
|
||
// Rotació visual aleatòria (factor = 0.0 o sin velocidad angular)
|
||
debris->velocitat_rot_visual =
|
||
Defaults::Physics::Debris::ROTACIO_MIN +
|
||
(std::rand() / static_cast<float>(RAND_MAX)) *
|
||
(Defaults::Physics::Debris::ROTACIO_MAX -
|
||
Defaults::Physics::Debris::ROTACIO_MIN);
|
||
|
||
// 50% probabilitat de rotació en sentit contrari
|
||
if (std::rand() % 2 == 0) {
|
||
debris->velocitat_rot_visual = -debris->velocitat_rot_visual;
|
||
}
|
||
}
|
||
|
||
debris->angle_rotacio = 0.0f;
|
||
|
||
// 7. Configurar vida i shrinking
|
||
debris->temps_vida = 0.0f;
|
||
debris->temps_max = Defaults::Physics::Debris::TEMPS_VIDA;
|
||
debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE;
|
||
|
||
// 8. Heredar brightness
|
||
debris->brightness = brightness;
|
||
|
||
// 9. Activar
|
||
debris->actiu = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
void DebrisManager::actualitzar(float delta_time) {
|
||
for (auto& debris : debris_pool_) {
|
||
if (!debris.actiu)
|
||
continue;
|
||
|
||
// 1. Actualitzar temps de vida
|
||
debris.temps_vida += delta_time;
|
||
|
||
// Desactivar si ha superat temps màxim
|
||
if (debris.temps_vida >= debris.temps_max) {
|
||
debris.actiu = false;
|
||
continue;
|
||
}
|
||
|
||
// 2. Actualitzar velocitat (desacceleració)
|
||
// Aplicar fricció en la direcció del moviment
|
||
float speed = std::sqrt(debris.velocitat.x * debris.velocitat.x +
|
||
debris.velocitat.y * debris.velocitat.y);
|
||
|
||
if (speed > 1.0f) {
|
||
// Calcular direcció normalitzada
|
||
float dir_x = debris.velocitat.x / speed;
|
||
float dir_y = debris.velocitat.y / speed;
|
||
|
||
// Aplicar acceleració negativa (fricció)
|
||
float nova_speed = speed + debris.acceleracio * delta_time;
|
||
if (nova_speed < 0.0f)
|
||
nova_speed = 0.0f;
|
||
|
||
debris.velocitat.x = dir_x * nova_speed;
|
||
debris.velocitat.y = dir_y * nova_speed;
|
||
} else {
|
||
// Velocitat molt baixa, aturar
|
||
debris.velocitat.x = 0.0f;
|
||
debris.velocitat.y = 0.0f;
|
||
}
|
||
|
||
// 2b. Rotar vector de velocitat (trayectoria curva)
|
||
if (std::abs(debris.velocitat_rot) > 0.01f) {
|
||
// Calcular angle de rotació aquest frame
|
||
float dangle = debris.velocitat_rot * delta_time;
|
||
|
||
// Rotar vector de velocitat usant matriu de rotació 2D
|
||
float vel_x_old = debris.velocitat.x;
|
||
float vel_y_old = debris.velocitat.y;
|
||
|
||
float cos_a = std::cos(dangle);
|
||
float sin_a = std::sin(dangle);
|
||
|
||
debris.velocitat.x = vel_x_old * cos_a - vel_y_old * sin_a;
|
||
debris.velocitat.y = vel_x_old * sin_a + vel_y_old * cos_a;
|
||
}
|
||
|
||
// 2c. Aplicar fricció angular (desacceleració gradual)
|
||
if (std::abs(debris.velocitat_rot) > 0.01f) {
|
||
float sign = (debris.velocitat_rot > 0) ? 1.0f : -1.0f;
|
||
float reduccion =
|
||
Defaults::Physics::Debris::FRICCIO_ANGULAR * delta_time;
|
||
debris.velocitat_rot -= sign * reduccion;
|
||
|
||
// Evitar canvi de signe (no pot passar de CW a CCW)
|
||
if ((debris.velocitat_rot > 0) != (sign > 0)) {
|
||
debris.velocitat_rot = 0.0f;
|
||
}
|
||
}
|
||
|
||
// 3. Calcular centre del segment
|
||
Punt centre = {(debris.p1.x + debris.p2.x) / 2.0f,
|
||
(debris.p1.y + debris.p2.y) / 2.0f};
|
||
|
||
// 4. Actualitzar posició del centre
|
||
centre.x += debris.velocitat.x * delta_time;
|
||
centre.y += debris.velocitat.y * delta_time;
|
||
|
||
// 5. Actualitzar rotació VISUAL
|
||
debris.angle_rotacio += debris.velocitat_rot_visual * delta_time;
|
||
|
||
// 6. Aplicar shrinking (reducció de distància entre punts)
|
||
float shrink_factor =
|
||
1.0f - (debris.factor_shrink * debris.temps_vida / debris.temps_max);
|
||
shrink_factor = std::max(0.0f, shrink_factor); // No negatiu
|
||
|
||
// Calcular distància original entre punts
|
||
float dx = debris.p2.x - debris.p1.x;
|
||
float dy = debris.p2.y - debris.p1.y;
|
||
|
||
// 7. Reconstruir segment amb nova mida i rotació
|
||
float half_length = std::sqrt(dx * dx + dy * dy) * shrink_factor / 2.0f;
|
||
float original_angle = std::atan2(dy, dx);
|
||
float new_angle = original_angle + debris.angle_rotacio;
|
||
|
||
debris.p1.x = centre.x - half_length * std::cos(new_angle);
|
||
debris.p1.y = centre.y - half_length * std::sin(new_angle);
|
||
debris.p2.x = centre.x + half_length * std::cos(new_angle);
|
||
debris.p2.y = centre.y + half_length * std::sin(new_angle);
|
||
}
|
||
}
|
||
|
||
void DebrisManager::dibuixar() const {
|
||
for (const auto& debris : debris_pool_) {
|
||
if (!debris.actiu)
|
||
continue;
|
||
|
||
// Dibuixar segment de línia amb brightness heretat
|
||
Rendering::linea(renderer_,
|
||
static_cast<int>(debris.p1.x),
|
||
static_cast<int>(debris.p1.y),
|
||
static_cast<int>(debris.p2.x),
|
||
static_cast<int>(debris.p2.y),
|
||
true,
|
||
debris.brightness);
|
||
}
|
||
}
|
||
|
||
Debris* DebrisManager::trobar_slot_lliure() {
|
||
for (auto& debris : debris_pool_) {
|
||
if (!debris.actiu) {
|
||
return &debris;
|
||
}
|
||
}
|
||
return nullptr; // Pool ple
|
||
}
|
||
|
||
Punt DebrisManager::calcular_direccio_explosio(const Punt& p1,
|
||
const Punt& p2,
|
||
const Punt& centre_objecte) const {
|
||
// 1. Calcular centre del segment
|
||
float centro_seg_x = (p1.x + p2.x) / 2.0f;
|
||
float centro_seg_y = (p1.y + p2.y) / 2.0f;
|
||
|
||
// 2. Calcular vector des del centre de l'objecte cap al centre del segment
|
||
// Això garanteix que la direcció sempre apunte cap a fora (direcció radial)
|
||
float dx = centro_seg_x - centre_objecte.x;
|
||
float dy = centro_seg_y - centre_objecte.y;
|
||
|
||
// 3. Normalitzar (obtenir vector unitari)
|
||
float length = std::sqrt(dx * dx + dy * dy);
|
||
if (length < 0.001f) {
|
||
// Segment al centre (cas extrem molt improbable), retornar direcció aleatòria
|
||
float angle_rand =
|
||
(std::rand() / static_cast<float>(RAND_MAX)) * 2.0f * Defaults::Math::PI;
|
||
return {std::cos(angle_rand), std::sin(angle_rand)};
|
||
}
|
||
|
||
dx /= length;
|
||
dy /= length;
|
||
|
||
// 4. Afegir variació aleatòria petita (±15°) per varietat visual
|
||
float angle_variacio =
|
||
((std::rand() % 30) - 15) * Defaults::Math::PI / 180.0f;
|
||
|
||
float cos_v = std::cos(angle_variacio);
|
||
float sin_v = std::sin(angle_variacio);
|
||
|
||
float final_x = dx * cos_v - dy * sin_v;
|
||
float final_y = dx * sin_v + dy * cos_v;
|
||
|
||
return {final_x, final_y};
|
||
}
|
||
|
||
void DebrisManager::reiniciar() {
|
||
for (auto& debris : debris_pool_) {
|
||
debris.actiu = false;
|
||
}
|
||
}
|
||
|
||
int DebrisManager::get_num_actius() const {
|
||
int count = 0;
|
||
for (const auto& debris : debris_pool_) {
|
||
if (debris.actiu)
|
||
count++;
|
||
}
|
||
return count;
|
||
}
|
||
|
||
} // namespace Effects
|