Files
orni-attack/source/game/effects/debris_manager.cpp
T
JailDesigner c45e524109 Lint: clang-tidy --fix mecánico (trailing return, default member init, auto, enum size)
Pase automático de clang-tidy --fix sobre el conjunto de checks que son
puro transform de sintaxis y no rompen API. Invocado con
--format-style=none para que clang-tidy NO arrastre clang-format sobre
las líneas tocadas (evita la regla NamespaceIndentation: All del
.clang-format reformateando solo trozos del archivo).

Checks aplicados:

- modernize-use-trailing-return-type (193 hits): 'int foo()' →
  'auto foo() -> int'. Estilo coherente con la convención del proyecto.
- modernize-use-default-member-init (36 hits): inicialización de
  miembros pasa de la lista del constructor a la declaración. Reduce
  duplicación cuando hay varios constructores con los mismos defaults.
- modernize-use-auto (6 hits): tipos largos sustituidos por auto donde
  el tipo es evidente del contexto (new T, dynamic_cast, etc).
- modernize-use-starts-ends-with (2 hits): s.rfind(x) == 0 →
  s.starts_with(x), aprovechando C++20.
- performance-enum-size (10 hits): enums pequeños declaran tipo
  subyacente (uint8_t / similar) para reducir tamaño y precisar layout.

NO aplicado en este pase (riesgo de cambios semánticos o de API):
- readability-identifier-naming (renames pueden romper callsites parciales)
- readability-convert-member-functions-to-static (cambia firma)
- readability-use-anyofallof (reescribe loops, side effects)
- readability-function-cognitive-complexity (requiere refactor manual)
- bugs reales (bugprone-*, clang-diagnostic-*) → uno a uno

Cambios manuales asociados:
- SDLManager::clear() ahora devuelve bool: propaga el resultado de
  beginFrame al caller para que Director::runFrameLoop salte
  draw+present cuando la swapchain no esté disponible (ventana
  minimizada). Antes la función ignoraba el [[nodiscard]] del
  beginFrame y los vértices se acumulaban en el batch sin nadie que
  los consumiera.
- vector_text.cpp: borrada la línea suelta "// Test pre-commit hook"
  que quedó como cruft.

clang-tidy crashea en LLVM 19.1 con performance-noexcept-move-constructor
(recursión infinita en ExceptionSpecAnalyzer al procesar std::set);
check deshabilitado en .clang-tidy con comentario explicativo.

Build limpio, smoke test OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:59:56 +02:00

386 lines
14 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.
// debris_manager.cpp - Implementació del gestor de fragments
// © 2026 JailDesigner
#include "debris_manager.hpp"
#include <algorithm>
#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 point con rotación, scale i traslación
// (Copiat de shape_renderer.cpp:12-34)
static auto transform_point(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;
// 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);
// 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<Graphics::Shape>& 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;
}
// Reproducir sonido de explosión
Audio::get()->playSound(sound, Audio::Group::GAME);
// Obtenir centro de la shape para transformacions
const Vec2& shape_centre = shape->getCenter();
// Iterar sobre todas las primitives de la shape
for (const auto& primitive : shape->get_primitives()) {
// Processar cada segment de línia
std::vector<std::pair<Vec2, Vec2>> segments;
if (primitive.type == Graphics::PrimitiveType::POLYLINE) {
// Polyline: extreure segments consecutius
for (size_t i = 0; i < primitive.points.size() - 1; i++) {
segments.emplace_back(primitive.points[i], primitive.points[i + 1]);
}
} else { // PrimitiveType::LINE
// Line: un únic segment
if (primitive.points.size() >= 2) {
segments.emplace_back(primitive.points[0], primitive.points[1]);
}
}
// Crear debris para cada segment
for (const auto& [local_p1, local_p2] : segments) {
// 1. Transformar points locals → coordenades mundials
Vec2 world_p1 =
transform_point(local_p1, shape_centre, centro, angle, scale);
Vec2 world_p2 =
transform_point(local_p2, shape_centre, centro, angle, scale);
// 2. Trobar slot lliure
Debris* debris = findFreeSlot();
if (debris == nullptr) {
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ó de explosión (radial, des del centro hacia fuera)
Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centro);
// 5. Velocidad inicial (base ± variació aleatòria + velocity heretada)
float speed =
velocitat_base +
(((std::rand() / static_cast<float>(RAND_MAX)) * 2.0F - 1.0F) *
Defaults::Physics::Debris::VARIACIO_VELOCITAT);
// Heredar velocity de l'objecte original (suma vectorial)
debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x;
debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y;
debris->acceleration = Defaults::Physics::Debris::ACCELERACIO;
// 6. Herència de velocity angular con sin + conversió de excés
// 6a. Rotación de TRAYECTORIA con sin + conversió tangencial
if (std::abs(velocitat_angular) > 0.01F) {
// FASE 1: Aplicar herència i variació (igual que antes)
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 sin 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 velocity tangencial
float excess = abs_ang - CAP;
// Radi de la shape (enemigos = 20 px)
float radius = 20.0F;
// Velocidad 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;
// Añadir velocity tangencial (suma vectorial)
debris->velocity.x += tangent_x * v_tangential;
debris->velocity.y += tangent_y * v_tangential;
// Aplicar hacia velocity angular (preservar signe)
debris->velocitat_rot = sign_ang * CAP;
} else {
// Per sota del sin: comportament normal
debris->velocitat_rot = velocitat_ang_heretada;
}
} else {
debris->velocitat_rot = 0.0F; // Nave: sin curvas
}
// 6b. Rotación 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 pequeña (±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ón 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ón 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 y color del padre
debris->brightness = brightness;
debris->color = color;
// 9. Activar
debris->active = true;
}
}
}
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;
// Desactivar si ha superat time màxim
if (debris.temps_vida >= debris.temps_max) {
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)
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));
}
}
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<int>(debris.p1.x),
static_cast<int>(debris.p1.y),
static_cast<int>(debris.p2.x),
static_cast<int>(debris.p2.y),
debris.brightness, 0.0F, debris.color);
}
}
auto DebrisManager::findFreeSlot() -> Debris* {
for (auto& debris : debris_pool_) {
if (!debris.active) {
return &debris;
}
}
return nullptr; // Pool ple
}
auto DebrisManager::computeExplosionDirection(const Vec2& p1,
const Vec2& p2,
const Vec2& centre_objecte) const -> 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<float>(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++;
}
}
return count;
}
} // namespace Effects