diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 03603f4..617a73b 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -25,9 +25,15 @@ namespace Defaults::Physics { constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²) constexpr float ROTACIO_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s) constexpr float ROTACIO_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s) - constexpr float TEMPS_VIDA = 2.0F; // Duració màxima (segons) - enemy/bullet debris - constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris lifetime (matches DEATH_DURATION) - constexpr float SHRINK_RATE = 0.5F; // Reducció de mida (factor/s) + constexpr float TEMPS_VIDA = 2.0F; // Vida mínima garantida (s) — després pot morir per velocitat baixa + constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris min lifetime (matches DEATH_DURATION) + constexpr float SHRINK_RATE = 0.5F; // Reducció de mida (factor sobre min_lifetime) + + // Política de mort: passat el min_lifetime, el fragment mor quan la + // seva velocity cau per sota d'aquest llindar. Així els fragments + // ràpids no "popen" en moviment. + constexpr float MIN_SPEED_TO_DIE = 5.0F; // px/s — al cuadrat per evitar sqrt en update + constexpr float MIN_SPEED_TO_DIE_SQ = MIN_SPEED_TO_DIE * MIN_SPEED_TO_DIE; // Herència de velocity angular (trayectorias curvas) constexpr float FACTOR_HERENCIA_MIN = 0.7F; // Mínimo 70% del drotacio heredat @@ -41,6 +47,12 @@ namespace Defaults::Physics { // 1.0 = inèrcia completa; >1.0 amplifica la deriva; <1.0 la atenua. constexpr float ENEMY_VELOCITY_INHERITANCE = 1.0F; + // Tuneig específic de l'explosió d'enemic (overrides als defaults + // que es passen com a paràmetres opcionals a explode()). + constexpr float ENEMY_LIFETIME = 3.0F; // Vida mínima del debris (s) — els que segueixen movent-se viuen més + constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més + constexpr int ENEMY_SEGMENT_MULTIPLIER = 3; // Còpies de cada segment (5 cares × 3 = 15 trossos) + // Angular velocity sin for trajectory inheritance // Excess above this threshold is converted to tangential linear velocity // Prevents "vortex trap" problem with high-rotation enemies diff --git a/source/game/effects/debris.hpp b/source/game/effects/debris.hpp index c13d18d..bc8f365 100644 --- a/source/game/effects/debris.hpp +++ b/source/game/effects/debris.hpp @@ -8,15 +8,15 @@ namespace Effects { -// Debris: un segment de línia que vola perpendicular a sí mismo -// Representa un fragment de una shape destruïda (ship, enemy, bullet) -struct Debris { + // Debris: un segment de línia que vola perpendicular a sí mismo + // Representa un fragment de una shape destruïda (ship, enemy, bullet) + struct Debris { // Geometria del segment (2 points en coordenades mundials) Vec2 p1; // Vec2 inicial del segment Vec2 p2; // Vec2 final del segment // Física - Vec2 velocity; // Velocidad en px/s (components x, y) + Vec2 velocity; // Velocidad en px/s (components x, y) float acceleration; // Aceleración negativa (fricció) en px/s² // Rotación @@ -25,9 +25,12 @@ struct Debris { float velocitat_rot_visual; // Velocidad de rotación VISUAL del segment (rad/s) // Estat de vida - float temps_vida; // Temps transcorregut (segons) - float temps_max; // Temps de vida màxim (segons) - bool active; // Está active? + // Política: viu sempre durant min_lifetime, després mor quan + // |velocity| < MIN_SPEED_TO_DIE (definit en Defaults). Així els + // fragments ràpids no "popen" en moviment. + float temps_vida; // Temps transcorregut (segons) + float min_lifetime; // Temps mínim garantit (segons) + bool active; // Està actiu? // Shrinking (reducció de distancia entre points) float factor_shrink; // Factor de reducció per segon (0.0-1.0) @@ -35,6 +38,6 @@ struct Debris { // Rendering float brightness; // Factor de brightness (0.0-1.0, heretat de l'objecte original) SDL_Color color{}; // Color heredado del padre. alpha==0 → usa global oscilador -}; + }; } // namespace Effects diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index de47ae8..0c263e0 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -14,375 +14,388 @@ namespace Effects { -// Helper: transformar point con rotación, scale i traslación -// (Copiat de shape_renderer.cpp:12-34) -static auto transformPoint(const Vec2& point, const Vec2& shape_centre, const Vec2& position, float angle, float scale) -> Vec2 { - // 1. Centrar el point respecte al centro de la shape - float centered_x = point.x - shape_centre.x; - float centered_y = point.y - shape_centre.y; + // Helper: transformar point con rotación, scale i traslación + // (Copiat de shape_renderer.cpp:12-34) + static auto transformPoint(const Vec2& point, const Vec2& shape_centre, const Vec2& position, float angle, float scale) -> Vec2 { + // 1. Centrar el point respecte al centro de la shape + float centered_x = point.x - shape_centre.x; + float centered_y = point.y - shape_centre.y; - // 2. Aplicar scale al point centrat - float scaled_x = centered_x * scale; - float scaled_y = centered_y * scale; + // 2. Aplicar scale al point centrat + float scaled_x = centered_x * scale; + float scaled_y = centered_y * scale; - // 3. Aplicar rotación - float cos_a = std::cos(angle); - float sin_a = std::sin(angle); + // 3. Aplicar rotación + 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); + float rotated_x = (scaled_x * cos_a) - (scaled_y * sin_a); + float rotated_y = (scaled_x * sin_a) + (scaled_y * cos_a); - // 4. Aplicar traslación a posición mundial - return {.x = rotated_x + position.x, .y = rotated_y + position.y}; -} - -DebrisManager::DebrisManager(Rendering::Renderer* renderer) - : renderer_(renderer) { - // Inicialitzar todos los debris como inactius - for (auto& debris : debris_pool_) { - debris.active = false; - } -} - -void DebrisManager::explode(const std::shared_ptr& shape, - const Vec2& centro, - float angle, - float scale, - float velocitat_base, - float brightness, - const Vec2& velocitat_objecte, - float velocitat_angular, - float factor_herencia_visual, - const std::string& sound, - SDL_Color color) { - if (!shape || !shape->isValid()) { - return; + // 4. Aplicar traslación a posición mundial + return {.x = rotated_x + position.x, .y = rotated_y + position.y}; } - // Reproducir sonido de explosión - Audio::get()->playSound(sound, Audio::Group::GAME); + DebrisManager::DebrisManager(Rendering::Renderer* renderer) + : renderer_(renderer) { + // Inicialitzar todos los debris como inactius + for (auto& debris : debris_pool_) { + debris.active = false; + } + } - const Vec2& shape_centre = shape->getCenter(); + void DebrisManager::explode(const std::shared_ptr& shape, + const Vec2& centro, + float angle, + float scale, + float velocitat_base, + float brightness, + const Vec2& velocitat_objecte, + float velocitat_angular, + float factor_herencia_visual, + const std::string& sound, + SDL_Color color, + float lifetime, + float friction, + int segment_multiplier) { + if (!shape || !shape->isValid()) { + return; + } - for (const auto& primitive : shape->getPrimitives()) { - for (const auto& [local_p1, local_p2] : extractSegments(primitive)) { - // Transformar points locals → coordenades mundials - Vec2 world_p1 = transformPoint(local_p1, shape_centre, centro, angle, scale); - Vec2 world_p2 = transformPoint(local_p2, shape_centre, centro, angle, scale); + // Reproducir sonido de explosión + Audio::get()->playSound(sound, Audio::Group::GAME); - // Si el pool es ple, no té sentit continuar amb la resta de segments - if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, - velocitat_objecte, velocitat_angular, - factor_herencia_visual, color)) { - return; + const Vec2& shape_centre = shape->getCenter(); + + // Multiplier: cada segment s'emet N vegades amb direccions aleatòries + // distintes (la variació ±15° de computeExplosionDirection ho garanteix). + const int COPIES = std::max(1, segment_multiplier); + + for (int copy = 0; copy < COPIES; copy++) { + for (const auto& primitive : shape->getPrimitives()) { + for (const auto& [local_p1, local_p2] : extractSegments(primitive)) { + // Transformar points locals → coordenades mundials + Vec2 world_p1 = transformPoint(local_p1, shape_centre, centro, angle, scale); + Vec2 world_p2 = transformPoint(local_p2, shape_centre, centro, angle, scale); + + // Si el pool es ple, no té sentit continuar amb la resta de segments + if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction)) { + return; + } + } } } } -} -auto DebrisManager::extractSegments(const Graphics::ShapePrimitive& primitive) - -> std::vector> { - std::vector> segments; + auto DebrisManager::extractSegments(const Graphics::ShapePrimitive& primitive) + -> std::vector> { + std::vector> segments; - if (primitive.type == Graphics::PrimitiveType::POLYLINE) { - // Polyline: extreure segments consecutius - for (size_t i = 0; i + 1 < primitive.points.size(); i++) { - segments.emplace_back(primitive.points[i], primitive.points[i + 1]); + if (primitive.type == Graphics::PrimitiveType::POLYLINE) { + // Polyline: extreure segments consecutius + for (size_t i = 0; i + 1 < primitive.points.size(); i++) { + segments.emplace_back(primitive.points[i], primitive.points[i + 1]); + } + return segments; + } + // PrimitiveType::LINE: un únic segment (si té els 2 punts) + if (primitive.points.size() >= 2) { + segments.emplace_back(primitive.points[0], primitive.points[1]); } return segments; } - // PrimitiveType::LINE: un únic segment (si té els 2 punts) - if (primitive.points.size() >= 2) { - segments.emplace_back(primitive.points[0], primitive.points[1]); - } - return segments; -} -auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, - const Vec2& centro, float velocitat_base, float brightness, - const Vec2& velocitat_objecte, float velocitat_angular, - float factor_herencia_visual, SDL_Color color) -> bool { - Debris* debris = findFreeSlot(); - if (debris == nullptr) { - std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n"; - return false; - } - - // Geometria - debris->p1 = world_p1; - debris->p2 = world_p2; - - // Direcció radial (desde el centro hacia el segment) - Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centro); - - // Velocidad inicial (base ± variació aleatòria + velocity heretada de l'objecte) - float speed = - velocitat_base + - (((std::rand() / static_cast(RAND_MAX)) * 2.0F - 1.0F) * - Defaults::Physics::Debris::VARIACIO_VELOCITAT); - debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x; - debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y; - debris->acceleration = Defaults::Physics::Debris::ACCELERACIO; - - // Rotación de trayectoria (con conversió a tangencial si excedeix cap) - applyAngularVelocity(*debris, direccio, velocitat_angular); - - // Rotación visual (proporcional o aleatòria) - applyVisualRotation(*debris, velocitat_angular, factor_herencia_visual); - - debris->angle_rotacio = 0.0F; - - // Vida i shrinking - debris->temps_vida = 0.0F; - debris->temps_max = Defaults::Physics::Debris::TEMPS_VIDA; - debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE; - - // Visuals heretades - debris->brightness = brightness; - debris->color = color; - - debris->active = true; - return true; -} - -void DebrisManager::applyAngularVelocity(Debris& debris, const Vec2& direccio, - float velocitat_angular) { - if (std::abs(velocitat_angular) <= 0.01F) { - debris.velocitat_rot = 0.0F; // Nave: sin curvas - return; - } - - // FASE 1: Aplicar herència i variació - float factor_herencia = - Defaults::Physics::Debris::FACTOR_HERENCIA_MIN + - ((std::rand() / static_cast(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(RAND_MAX)) * 0.2F) - 0.1F; - velocitat_ang_heretada *= (1.0F + variacio); - - // FASE 2: Cap a la velocity màxima; l'excés es converteix en tangencial - 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) { - debris.velocitat_rot = velocitat_ang_heretada; - return; - } - - // Excés: converteix l'excés de velocitat angular en velocitat tangencial lineal - float excess = abs_ang - CAP; - constexpr float RADIUS = 20.0F; // Radi típic de la shape (enemigos = 20 px) - float v_tangential = excess * RADIUS; - - // Direcció tangencial: perpendicular a la radial (90° CCW): tangent = (-dy, dx) - debris.velocity.x += -direccio.y * v_tangential; - debris.velocity.y += direccio.x * v_tangential; - - // Velocitat angular limitada al cap (preservant el signe) - debris.velocitat_rot = sign_ang * CAP; -} - -void DebrisManager::applyVisualRotation(Debris& debris, float velocitat_angular, - float factor_herencia_visual) { - if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) { - // Heredar rotación visual con factor proporcional + ±5% de variació - debris.velocitat_rot_visual = debris.velocitat_rot * factor_herencia_visual; - float variacio_visual = - ((std::rand() / static_cast(RAND_MAX)) * 0.1F) - 0.05F; - debris.velocitat_rot_visual *= (1.0F + variacio_visual); - return; - } - - // Rotación visual aleatòria (factor = 0.0 o sin velocidad angular) - debris.velocitat_rot_visual = - Defaults::Physics::Debris::ROTACIO_MIN + - ((std::rand() / static_cast(RAND_MAX)) * - (Defaults::Physics::Debris::ROTACIO_MAX - - Defaults::Physics::Debris::ROTACIO_MIN)); - - // 50% probabilitat de rotación en sentit contrari - if (std::rand() % 2 == 0) { - debris.velocitat_rot_visual = -debris.velocitat_rot_visual; - } -} - -void DebrisManager::update(float delta_time) { - for (auto& debris : debris_pool_) { - if (!debris.active) { - continue; + auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction) -> bool { + Debris* debris = findFreeSlot(); + if (debris == nullptr) { + std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n"; + return false; } - // 1. Actualitzar time de vida - debris.temps_vida += delta_time; + // Geometria + debris->p1 = world_p1; + debris->p2 = world_p2; - // Desactivar si ha superat time màxim - if (debris.temps_vida >= debris.temps_max) { - debris.active = false; - continue; + // Direcció radial (desde el centro hacia el segment) + Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centro); + + // Velocidad inicial (base ± variació aleatòria + velocity heretada de l'objecte) + float speed = + velocitat_base + + (((std::rand() / static_cast(RAND_MAX)) * 2.0F - 1.0F) * + Defaults::Physics::Debris::VARIACIO_VELOCITAT); + debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x; + debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y; + debris->acceleration = friction; + + // Rotación de trayectoria (con conversió a tangencial si excedeix cap) + applyAngularVelocity(*debris, direccio, velocitat_angular); + + // Rotación visual (proporcional o aleatòria) + applyVisualRotation(*debris, velocitat_angular, factor_herencia_visual); + + debris->angle_rotacio = 0.0F; + + // Vida i shrinking — min_lifetime és el temps mínim garantit; després + // el fragment mor quan |velocity| < MIN_SPEED_TO_DIE. + debris->temps_vida = 0.0F; + debris->min_lifetime = lifetime; + debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE; + + // Visuals heretades + debris->brightness = brightness; + debris->color = color; + + debris->active = true; + return true; + } + + void DebrisManager::applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular) { + if (std::abs(velocitat_angular) <= 0.01F) { + debris.velocitat_rot = 0.0F; // Nave: sin curvas + return; } - // 2. Actualitzar velocity (desacceleració) - // Aplicar fricció en la direcció del movement - float speed = std::sqrt((debris.velocity.x * debris.velocity.x) + - (debris.velocity.y * debris.velocity.y)); + // FASE 1: Aplicar herència i variació + float factor_herencia = + Defaults::Physics::Debris::FACTOR_HERENCIA_MIN + + ((std::rand() / static_cast(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(RAND_MAX)) * 0.2F) - 0.1F; + velocitat_ang_heretada *= (1.0F + variacio); - if (speed > 1.0F) { - // Calcular direcció normalitzada - float dir_x = debris.velocity.x / speed; - float dir_y = debris.velocity.y / speed; + // FASE 2: Cap a la velocity màxima; l'excés es converteix en tangencial + 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; - // Aplicar aceleración negativa (fricció) - float nova_speed = speed + (debris.acceleration * delta_time); - nova_speed = std::max(nova_speed, 0.0F); - - debris.velocity.x = dir_x * nova_speed; - debris.velocity.y = dir_y * nova_speed; - } else { - // Velocidad mucho baixa, aturar - debris.velocity.x = 0.0F; - debris.velocity.y = 0.0F; + if (abs_ang <= CAP) { + debris.velocitat_rot = velocitat_ang_heretada; + return; } - // 2b. Rotar vector de velocity (trayectoria curva) - if (std::abs(debris.velocitat_rot) > 0.01F) { - // Calcular angle de rotación este frame - float dangle = debris.velocitat_rot * delta_time; + // Excés: converteix l'excés de velocitat angular en velocitat tangencial lineal + float excess = abs_ang - CAP; + constexpr float RADIUS = 20.0F; // Radi típic de la shape (enemigos = 20 px) + float v_tangential = excess * RADIUS; - // Rotar vector de velocity usant matriu de rotación 2D - float vel_x_old = debris.velocity.x; - float vel_y_old = debris.velocity.y; + // Direcció tangencial: perpendicular a la radial (90° CCW): tangent = (-dy, dx) + debris.velocity.x += -direccio.y * v_tangential; + debris.velocity.y += direccio.x * v_tangential; - float cos_a = std::cos(dangle); - float sin_a = std::sin(dangle); + // Velocitat angular limitada al cap (preservant el signe) + debris.velocitat_rot = sign_ang * CAP; + } - debris.velocity.x = (vel_x_old * cos_a) - (vel_y_old * sin_a); - debris.velocity.y = (vel_x_old * sin_a) + (vel_y_old * cos_a); + void DebrisManager::applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual) { + if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) { + // Heredar rotación visual con factor proporcional + ±5% de variació + debris.velocitat_rot_visual = debris.velocitat_rot * factor_herencia_visual; + float variacio_visual = + ((std::rand() / static_cast(RAND_MAX)) * 0.1F) - 0.05F; + debris.velocitat_rot_visual *= (1.0F + variacio_visual); + return; } - // 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; + // Rotación visual aleatòria (factor = 0.0 o sin velocidad angular) + debris.velocitat_rot_visual = + Defaults::Physics::Debris::ROTACIO_MIN + + ((std::rand() / static_cast(RAND_MAX)) * + (Defaults::Physics::Debris::ROTACIO_MAX - + Defaults::Physics::Debris::ROTACIO_MIN)); - // Evitar canvi de signe (no pot passar de CW a CCW) - if ((debris.velocitat_rot > 0) != (sign > 0)) { - debris.velocitat_rot = 0.0F; + // 50% probabilitat de rotación en sentit contrari + if (std::rand() % 2 == 0) { + debris.velocitat_rot_visual = -debris.velocitat_rot_visual; + } + } + + void DebrisManager::update(float delta_time) { + for (auto& debris : debris_pool_) { + if (!debris.active) { + continue; + } + + // 1. Actualitzar time de vida + debris.temps_vida += delta_time; + + // Política de mort: viu sí o sí durant min_lifetime; després mor + // quan la velocity cau per sota d'un llindar. Així els fragments + // ràpids no desapareixen en moviment. + if (debris.temps_vida >= debris.min_lifetime) { + const float SPEED_SQ = (debris.velocity.x * debris.velocity.x) + + (debris.velocity.y * debris.velocity.y); + if (SPEED_SQ < Defaults::Physics::Debris::MIN_SPEED_TO_DIE_SQ) { + debris.active = false; + continue; + } + } + + // 2. Actualitzar velocity (desacceleració) + // Aplicar fricció en la direcció del movement + float speed = std::sqrt((debris.velocity.x * debris.velocity.x) + + (debris.velocity.y * debris.velocity.y)); + + if (speed > 1.0F) { + // Calcular direcció normalitzada + float dir_x = debris.velocity.x / speed; + float dir_y = debris.velocity.y / speed; + + // Aplicar aceleración negativa (fricció) + float nova_speed = speed + (debris.acceleration * delta_time); + nova_speed = std::max(nova_speed, 0.0F); + + debris.velocity.x = dir_x * nova_speed; + debris.velocity.y = dir_y * nova_speed; + } else { + // Velocidad mucho baixa, aturar + debris.velocity.x = 0.0F; + debris.velocity.y = 0.0F; + } + + // 2b. Rotar vector de velocity (trayectoria curva) + if (std::abs(debris.velocitat_rot) > 0.01F) { + // Calcular angle de rotación este frame + float dangle = debris.velocitat_rot * delta_time; + + // Rotar vector de velocity usant matriu de rotación 2D + float vel_x_old = debris.velocity.x; + float vel_y_old = debris.velocity.y; + + float cos_a = std::cos(dangle); + float sin_a = std::sin(dangle); + + debris.velocity.x = (vel_x_old * cos_a) - (vel_y_old * sin_a); + debris.velocity.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 centro del segment + Vec2 centro = {.x = (debris.p1.x + debris.p2.x) / 2.0F, + .y = (debris.p1.y + debris.p2.y) / 2.0F}; + + // 4. Actualitzar posición del centro + centro.x += debris.velocity.x * delta_time; + centro.y += debris.velocity.y * delta_time; + + // 5. Actualitzar rotación VISUAL + debris.angle_rotacio += debris.velocitat_rot_visual * delta_time; + + // 6. Aplicar shrinking (reducció de distancia entre points). + // El shrink es normalitza al min_lifetime (capat a 1.0) perquè els + // fragments que viuen més no es continuïn fent més petits per sempre. + const float SHRINK_T = std::min(debris.temps_vida / debris.min_lifetime, 1.0F); + float shrink_factor = 1.0F - (debris.factor_shrink * SHRINK_T); + shrink_factor = std::max(0.0F, shrink_factor); // No negatiu + + // Calcular distancia original entre points + float dx = debris.p2.x - debris.p1.x; + float dy = debris.p2.y - debris.p1.y; + + // 7. Reconstruir segment con nueva mida i rotación + 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 = centro.x - (half_length * std::cos(new_angle)); + debris.p1.y = centro.y - (half_length * std::sin(new_angle)); + debris.p2.x = centro.x + (half_length * std::cos(new_angle)); + debris.p2.y = centro.y + (half_length * std::sin(new_angle)); + } + } + + void DebrisManager::draw() const { + for (const auto& debris : debris_pool_) { + if (!debris.active) { + continue; + } + + // Dibujar segmento con brightness y color heredados del padre. + Rendering::linea(renderer_, + static_cast(debris.p1.x), + static_cast(debris.p1.y), + static_cast(debris.p2.x), + static_cast(debris.p2.y), + debris.brightness, + 0.0F, + debris.color); + } + } + + auto DebrisManager::findFreeSlot() -> Debris* { + for (auto& debris : debris_pool_) { + if (!debris.active) { + return &debris; } } - - // 3. Calcular centro del segment - Vec2 centro = {.x = (debris.p1.x + debris.p2.x) / 2.0F, - .y = (debris.p1.y + debris.p2.y) / 2.0F}; - - // 4. Actualitzar posición del centro - centro.x += debris.velocity.x * delta_time; - centro.y += debris.velocity.y * delta_time; - - // 5. Actualitzar rotación VISUAL - debris.angle_rotacio += debris.velocitat_rot_visual * delta_time; - - // 6. Aplicar shrinking (reducció de distancia entre points) - 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 distancia original entre points - float dx = debris.p2.x - debris.p1.x; - float dy = debris.p2.y - debris.p1.y; - - // 7. Reconstruir segment con nueva mida i rotación - 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 = centro.x - (half_length * std::cos(new_angle)); - debris.p1.y = centro.y - (half_length * std::sin(new_angle)); - debris.p2.x = centro.x + (half_length * std::cos(new_angle)); - debris.p2.y = centro.y + (half_length * std::sin(new_angle)); + return nullptr; // Pool ple } -} -void DebrisManager::draw() const { - for (const auto& debris : debris_pool_) { - if (!debris.active) { - continue; + auto DebrisManager::computeExplosionDirection(const Vec2& p1, + const Vec2& p2, + const Vec2& centre_objecte) -> Vec2 { + // 1. Calcular centro 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 centro de l'objecte hacia el centro del segment + // Això garanteix que la direcció siempre apunte hacia fuera (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 centro (cas extrem mucho improbable), retornar direcció aleatòria + float angle_rand = + (std::rand() / static_cast(RAND_MAX)) * 2.0F * Defaults::Math::PI; + return {.x = std::cos(angle_rand), .y = std::sin(angle_rand)}; } - // Dibujar segmento con brightness y color heredados del padre. - Rendering::linea(renderer_, - static_cast(debris.p1.x), - static_cast(debris.p1.y), - static_cast(debris.p2.x), - static_cast(debris.p2.y), - debris.brightness, 0.0F, debris.color); - } -} + dx /= length; + dy /= length; -auto DebrisManager::findFreeSlot() -> Debris* { - for (auto& debris : debris_pool_) { - if (!debris.active) { - return &debris; + // 4. Añadir variació aleatòria pequeña (±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 {.x = final_x, .y = final_y}; + } + + void DebrisManager::reset() { + for (auto& debris : debris_pool_) { + debris.active = false; } } - return nullptr; // Pool ple -} -auto DebrisManager::computeExplosionDirection(const Vec2& p1, - const Vec2& p2, - const Vec2& centre_objecte) -> Vec2 { - // 1. Calcular centro 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 centro de l'objecte hacia el centro del segment - // Això garanteix que la direcció siempre apunte hacia fuera (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 centro (cas extrem mucho improbable), retornar direcció aleatòria - float angle_rand = - (std::rand() / static_cast(RAND_MAX)) * 2.0F * Defaults::Math::PI; - return {.x = std::cos(angle_rand), .y = std::sin(angle_rand)}; - } - - dx /= length; - dy /= length; - - // 4. Añadir variació aleatòria pequeña (±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 {.x = final_x, .y = final_y}; -} - -void DebrisManager::reset() { - for (auto& debris : debris_pool_) { - debris.active = false; - } -} - -auto DebrisManager::getActiveCount() const -> int { - int count = 0; - for (const auto& debris : debris_pool_) { - if (debris.active) { - count++; + auto DebrisManager::getActiveCount() const -> int { + int count = 0; + for (const auto& debris : debris_pool_) { + if (debris.active) { + count++; + } } + return count; } - return count; -} } // namespace Effects diff --git a/source/game/effects/debris_manager.hpp b/source/game/effects/debris_manager.hpp index 48c0a43..6681deb 100644 --- a/source/game/effects/debris_manager.hpp +++ b/source/game/effects/debris_manager.hpp @@ -3,7 +3,6 @@ #pragma once -#include "core/rendering/render_context.hpp" #include #include @@ -13,15 +12,16 @@ #include "core/defaults.hpp" #include "core/graphics/shape.hpp" +#include "core/rendering/render_context.hpp" #include "core/types.hpp" #include "debris.hpp" namespace Effects { -// Gestor de fragments de explosions -// Manté un pool de objectes Debris i gestiona el seu cicle de vida -class DebrisManager { - public: + // Gestor de fragments de explosions + // Manté un pool de objectes Debris i gestiona el seu cicle de vida + class DebrisManager { + public: explicit DebrisManager(Rendering::Renderer* renderer); // Crear explosión a partir de una shape @@ -34,6 +34,9 @@ class DebrisManager { // - velocitat_objecte: velocity de l'objecte que explota (px/s, per defecte 0) // - velocitat_angular: velocity angular heretada (rad/s, per defecte 0) // - factor_herencia_visual: factor de herència rotación visual (0.0-1.0, per defecte 0.0) + // - lifetime: temps de vida del debris (s, per defecte TEMPS_VIDA = 2s) + // - friction: desacceleració del debris (px/s², per defecte ACCELERACIO = -60) + // - segment_multiplier: nombre de còpies per segment (per defecte 1 = sense duplicar) void explode(const std::shared_ptr& shape, const Vec2& centro, float angle, @@ -44,7 +47,10 @@ class DebrisManager { float velocitat_angular = 0.0F, float factor_herencia_visual = 0.0F, const std::string& sound = Defaults::Sound::EXPLOSION, - SDL_Color color = {0, 0, 0, 0}); // alpha==0 → fragmentos usan oscilador global + SDL_Color color = {0, 0, 0, 0}, // alpha==0 → fragmentos usan oscilador global + float lifetime = Defaults::Physics::Debris::TEMPS_VIDA, + float friction = Defaults::Physics::Debris::ACCELERACIO, + int segment_multiplier = 1); // Actualitzar todos los fragments active void update(float delta_time); @@ -58,14 +64,13 @@ class DebrisManager { // Obtenir número de fragments active [[nodiscard]] auto getActiveCount() const -> int; - private: + private: Rendering::Renderer* renderer_; // Pool de fragments (màxim concurrent) - // Un pentágono té 5 línies, 15 enemigos = 75 línies - // + ship (3 línies) + balas (5 línies * 3) = 93 línies màxim - // Arrodonit a 100 per seguretat - static constexpr int MAX_DEBRIS = 150; + // Pentàgon 5 línies × 15 enemics × multiplier 3 = 225 trossos només d'enemics. + // + ship (3 línies) + balas (5 línies × 3) = ~243. Arrodonit a 300. + static constexpr int MAX_DEBRIS = 300; std::array debris_pool_; // Trobar primer slot inactiu @@ -81,14 +86,9 @@ class DebrisManager { -> std::vector>; // Inicialitza un debris en un slot lliure i el deixa actiu. Retorna // false si el pool está ple (la cridadora ha d'aturar el bucle). - auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, - const Vec2& centro, float velocitat_base, float brightness, - const Vec2& velocitat_objecte, float velocitat_angular, - float factor_herencia_visual, SDL_Color color) -> bool; - static void applyAngularVelocity(Debris& debris, const Vec2& direccio, - float velocitat_angular); - static void applyVisualRotation(Debris& debris, float velocitat_angular, - float factor_herencia_visual); -}; + auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction) -> bool; + static void applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular); + static void applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual); + }; } // namespace Effects diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index a26b7fe..85c362a 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -73,7 +73,10 @@ namespace Systems::Collision { DROTACIO, 0.0F, // sin herencia visual Defaults::Sound::EXPLOSION, - COLOR); + COLOR, + Defaults::Physics::Debris::ENEMY_LIFETIME, + Defaults::Physics::Debris::ENEMY_FRICTION, + Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER); } } // anonymous namespace