From c9bcce6f9be1503a60ab65c585e60d74b4944a17 Mon Sep 17 00:00:00 2001 From: Sergio Date: Sat, 21 Mar 2026 10:52:07 +0100 Subject: [PATCH] style: aplicar fixes de clang-tidy (todo excepto uppercase-literal-suffix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corregidos ~2570 issues automáticamente con clang-tidy --fix-errors más ajustes manuales posteriores: - modernize: designated-initializers, trailing-return-type, use-auto, avoid-c-arrays (→ std::array<>), use-ranges, use-emplace, deprecated-headers, use-equals-default, pass-by-value, return-braced-init-list, use-default-member-init - readability: math-missing-parentheses, implicit-bool-conversion, braces-around-statements, isolate-declaration, use-std-min-max, identifier-naming, else-after-return, redundant-casting, convert-member-functions-to-static, make-member-function-const, static-accessed-through-instance - performance: avoid-endl, unnecessary-value-param, type-promotion, inefficient-vector-operation - dead code: XOR_KEY (orphan tras eliminar encryptData/decryptData), dead stores en engine.cpp y png_shape.cpp - NOLINT justificado en 10 funciones con alta complejidad cognitiva (initialize, render, main, processEvents, update×3, performDemoAction, randomizeOnDemoStart, renderDebugHUD, AppLogo::update) Compilación: gcc -Wall sin warnings. clang-tidy: 0 issues. Co-Authored-By: Claude Sonnet 4.6 --- .clang-tidy | 1 + source/ball.cpp | 75 +-- source/ball.hpp | 58 +- source/boids_mgr/boid_manager.cpp | 155 +++--- source/boids_mgr/boid_manager.hpp | 29 +- source/boids_mgr/spatial_grid.cpp | 20 +- source/boids_mgr/spatial_grid.hpp | 56 +- source/engine.cpp | 579 +++++++++++--------- source/engine.hpp | 99 ++-- source/gpu/gpu_ball_buffer.cpp | 42 +- source/gpu/gpu_ball_buffer.hpp | 34 +- source/gpu/gpu_context.cpp | 34 +- source/gpu/gpu_context.hpp | 37 +- source/gpu/gpu_pipeline.cpp | 289 +++++----- source/gpu/gpu_pipeline.hpp | 66 +-- source/gpu/gpu_sprite_batch.cpp | 176 +++--- source/gpu/gpu_sprite_batch.hpp | 97 ++-- source/gpu/gpu_texture.cpp | 136 +++-- source/gpu/gpu_texture.hpp | 55 +- source/input/input_handler.cpp | 75 ++- source/input/input_handler.hpp | 2 +- source/main.cpp | 26 +- source/resource_manager.cpp | 45 +- source/resource_manager.hpp | 98 ++-- source/resource_pack.cpp | 121 ++--- source/resource_pack.hpp | 78 +-- source/scene/scene_manager.cpp | 96 ++-- source/shapes/atom_shape.cpp | 31 +- source/shapes/atom_shape.hpp | 24 +- source/shapes/cube_shape.cpp | 51 +- source/shapes/cube_shape.hpp | 45 +- source/shapes/cylinder_shape.cpp | 27 +- source/shapes/cylinder_shape.hpp | 34 +- source/shapes/helix_shape.cpp | 10 +- source/shapes/helix_shape.hpp | 26 +- source/shapes/icosahedron_shape.cpp | 110 ++-- source/shapes/icosahedron_shape.hpp | 28 +- source/shapes/lissajous_shape.cpp | 18 +- source/shapes/lissajous_shape.hpp | 32 +- source/shapes/png_shape.cpp | 77 +-- source/shapes/png_shape.hpp | 156 +++--- source/shapes/shape.hpp | 48 +- source/shapes/sphere_shape.cpp | 22 +- source/shapes/sphere_shape.hpp | 22 +- source/shapes/torus_shape.cpp | 25 +- source/shapes/torus_shape.hpp | 26 +- source/shapes_mgr/shape_manager.cpp | 92 ++-- source/shapes_mgr/shape_manager.hpp | 11 +- source/state/state_manager.cpp | 191 ++++--- source/state/state_manager.hpp | 5 +- source/text/textrenderer.cpp | 76 +-- source/text/textrenderer.hpp | 81 +-- source/theme_manager.cpp | 812 ++++++++++++++++++---------- source/theme_manager.hpp | 25 +- source/themes/dynamic_theme.cpp | 39 +- source/themes/dynamic_theme.hpp | 24 +- source/themes/static_theme.cpp | 35 +- source/themes/static_theme.hpp | 18 +- source/themes/theme.hpp | 15 +- source/themes/theme_snapshot.hpp | 31 +- source/ui/app_logo.cpp | 455 ++++++++-------- source/ui/app_logo.hpp | 44 +- source/ui/help_overlay.cpp | 215 ++++---- source/ui/help_overlay.hpp | 138 ++--- source/ui/logo_scaler.cpp | 66 ++- source/ui/logo_scaler.hpp | 81 +-- source/ui/notifier.cpp | 52 +- source/ui/notifier.hpp | 155 +++--- source/ui/ui_manager.cpp | 192 +++---- source/ui/ui_manager.hpp | 61 +-- source/utils/easing_functions.hpp | 2 +- 71 files changed, 3469 insertions(+), 2838 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 82362cf..2a247ce 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -16,6 +16,7 @@ Checks: > -performance-inefficient-string-concatenation, -bugprone-integer-division, -bugprone-easily-swappable-parameters, + -readability-uppercase-literal-suffix, WarningsAsErrors: '*' # Solo incluir archivos de tu código fuente diff --git a/source/ball.cpp b/source/ball.cpp index e688a9a..902ff70 100644 --- a/source/ball.cpp +++ b/source/ball.cpp @@ -1,30 +1,31 @@ #include "ball.hpp" -#include // for rand - -#include // for fabs +#include +#include // for fabs +#include // for rand +#include #include "defines.hpp" // for Color, SCREEN_HEIGHT, GRAVITY_FORCE class Texture; // Función auxiliar para generar pérdida aleatoria en rebotes -float generateBounceVariation() { +auto generateBounceVariation() -> float { // Genera un valor entre 0 y BOUNCE_RANDOM_LOSS_PERCENT (solo pérdida adicional) float loss = (rand() % 1000) / 1000.0f * BOUNCE_RANDOM_LOSS_PERCENT; return 1.0f - loss; // Retorna multiplicador (ej: 0.90 - 1.00 para 10% max pérdida) } // Función auxiliar para generar pérdida lateral aleatoria -float generateLateralLoss() { +auto generateLateralLoss() -> float { // Genera un valor entre 0 y LATERAL_LOSS_PERCENT float loss = (rand() % 1000) / 1000.0f * LATERAL_LOSS_PERCENT; return 1.0f - loss; // Retorna multiplicador (ej: 0.98 - 1.0 para 0-2% pérdida) } // Constructor -Ball::Ball(float x, float y, float vx, float vy, Color color, std::shared_ptr texture, int screen_width, int screen_height, int ball_size, GravityDirection gravity_dir, float mass_factor) +Ball::Ball(float x, float y, float vx, float vy, Color color, const std::shared_ptr& texture, int screen_width, int screen_height, int ball_size, GravityDirection gravity_dir, float mass_factor) : sprite_(std::make_unique(texture)), - pos_({x, y, static_cast(ball_size), static_cast(ball_size)}) { + pos_({.x = x, .y = y, .w = static_cast(ball_size), .h = static_cast(ball_size)}) { // Convertir velocidades de píxeles/frame a píxeles/segundo (multiplicar por 60) vx_ = vx * 60.0f; vy_ = vy * 60.0f; @@ -36,7 +37,7 @@ Ball::Ball(float x, float y, float vx, float vy, Color color, std::shared_ptr float { // Siempre calcular distancia (útil para convergencia en LOGO mode) float dx = target_x_ - pos_.x; float dy = target_y_ - pos_.y; - return sqrtf(dx * dx + dy * dy); + return sqrtf((dx * dx) + (dy * dy)); } // Aplicar fuerza de resorte hacia punto objetivo en figuras 3D -void Ball::applyShapeForce(float target_x, float target_y, float sphere_radius, float deltaTime, - float spring_k_base, float damping_base_base, float damping_near_base, - float near_threshold_base, float max_force_base) { - if (!shape_attraction_active_) return; +void Ball::applyShapeForce(float target_x, float target_y, float sphere_radius, float delta_time, float spring_k_base, float damping_base_base, float damping_near_base, float near_threshold_base, float max_force_base) { + if (!shape_attraction_active_) { + return; + } // Calcular factor de escala basado en el radio (radio base = 80px) // Si radius=80 → scale=1.0, si radius=160 → scale=2.0, si radius=360 → scale=4.5 @@ -334,7 +335,7 @@ void Ball::applyShapeForce(float target_x, float target_y, float sphere_radius, float diff_y = target_y - pos_.y; // Calcular distancia al punto objetivo - float distance = sqrtf(diff_x * diff_x + diff_y * diff_y); + float distance = sqrtf((diff_x * diff_x) + (diff_y * diff_y)); // Fuerza de resorte (Ley de Hooke: F = -k * x) float spring_force_x = spring_k * diff_x; @@ -354,7 +355,7 @@ void Ball::applyShapeForce(float target_x, float target_y, float sphere_radius, float total_force_y = spring_force_y - damping_force_y; // Limitar magnitud de fuerza (evitar explosiones numéricas) - float force_magnitude = sqrtf(total_force_x * total_force_x + total_force_y * total_force_y); + float force_magnitude = sqrtf((total_force_x * total_force_x) + (total_force_y * total_force_y)); if (force_magnitude > max_force) { float scale_limit = max_force / force_magnitude; total_force_x *= scale_limit; @@ -363,18 +364,22 @@ void Ball::applyShapeForce(float target_x, float target_y, float sphere_radius, // Aplicar aceleración (F = ma, asumiendo m = 1 para simplificar) // a = F/m, pero m=1, así que a = F - vx_ += total_force_x * deltaTime; - vy_ += total_force_y * deltaTime; + vx_ += total_force_x * delta_time; + vy_ += total_force_y * delta_time; // Actualizar posición con física normal (velocidad integrada) - pos_.x += vx_ * deltaTime; - pos_.y += vy_ * deltaTime; + pos_.x += vx_ * delta_time; + pos_.y += vy_ * delta_time; // Mantener pelotas dentro de los límites de pantalla - if (pos_.x < 0) pos_.x = 0; - if (pos_.x + pos_.w > screen_width_) pos_.x = screen_width_ - pos_.w; - if (pos_.y < 0) pos_.y = 0; - if (pos_.y + pos_.h > screen_height_) pos_.y = screen_height_ - pos_.h; + pos_.x = std::max(pos_.x, 0); + if (pos_.x + pos_.w > screen_width_) { + pos_.x = screen_width_ - pos_.w; + } + pos_.y = std::max(pos_.y, 0); + if (pos_.y + pos_.h > screen_height_) { + pos_.y = screen_height_ - pos_.h; + } // Actualizar sprite para renderizado sprite_->setPos({pos_.x, pos_.y}); @@ -393,5 +398,5 @@ void Ball::updateSize(int new_size) { void Ball::setTexture(std::shared_ptr texture) { // Actualizar textura del sprite - sprite_->setTexture(texture); + sprite_->setTexture(std::move(texture)); } \ No newline at end of file diff --git a/source/ball.hpp b/source/ball.hpp index b0e43ff..294b687 100644 --- a/source/ball.hpp +++ b/source/ball.hpp @@ -10,34 +10,34 @@ class Texture; class Ball { private: - std::unique_ptr sprite_; // Sprite para pintar la clase - SDL_FRect pos_; // Posición y tamaño de la pelota - float vx_, vy_; // Velocidad - float gravity_force_; // Gravedad base - float gravity_mass_factor_; // Factor de masa individual (0.7-1.3, afecta gravedad) - GravityDirection gravity_direction_; // Direcci\u00f3n de la gravedad - int screen_width_; // Ancho del terreno de juego - int screen_height_; // Alto del terreno de juego - Color color_; // Color de la pelota - bool on_surface_; // Indica si la pelota est\u00e1 en la superficie (suelo/techo/pared) - float loss_; // Coeficiente de rebote. Pérdida de energía en cada rebote + std::unique_ptr sprite_; // Sprite para pintar la clase + SDL_FRect pos_; // Posición y tamaño de la pelota + float vx_, vy_; // Velocidad + float gravity_force_; // Gravedad base + float gravity_mass_factor_; // Factor de masa individual (0.7-1.3, afecta gravedad) + GravityDirection gravity_direction_; // Direcci\u00f3n de la gravedad + int screen_width_; // Ancho del terreno de juego + int screen_height_; // Alto del terreno de juego + Color color_; // Color de la pelota + bool on_surface_; // Indica si la pelota est\u00e1 en la superficie (suelo/techo/pared) + float loss_; // Coeficiente de rebote. Pérdida de energía en cada rebote // Datos para modo Shape (figuras 3D) float pos_3d_x_, pos_3d_y_, pos_3d_z_; // Posición 3D en la figura - float target_x_, target_y_; // Posición destino 2D (proyección) - float depth_brightness_; // Brillo según profundidad Z (0.0-1.0) - float depth_scale_; // Escala según profundidad Z (0.5-1.5) - bool shape_attraction_active_; // ¿Está siendo atraída hacia la figura? + float target_x_, target_y_; // Posición destino 2D (proyección) + float depth_brightness_; // Brillo según profundidad Z (0.0-1.0) + float depth_scale_; // Escala según profundidad Z (0.5-1.5) + bool shape_attraction_active_; // ¿Está siendo atraída hacia la figura? public: // Constructor - Ball(float x, float y, float vx, float vy, Color color, std::shared_ptr texture, int screen_width, int screen_height, int ball_size, GravityDirection gravity_dir = GravityDirection::DOWN, float mass_factor = 1.0f); + Ball(float x, float y, float vx, float vy, Color color, const std::shared_ptr& texture, int screen_width, int screen_height, int ball_size, GravityDirection gravity_dir = GravityDirection::DOWN, float mass_factor = 1.0f); // Destructor ~Ball() = default; // Actualiza la lógica de la clase - void update(float deltaTime); + void update(float delta_time); // Pinta la clase void render(); @@ -72,11 +72,20 @@ class Ball { bool isOnSurface() const { return on_surface_; } // Getters/Setters para velocidad (usado por BoidManager) - void getVelocity(float& vx, float& vy) const { vx = vx_; vy = vy_; } - void setVelocity(float vx, float vy) { vx_ = vx; vy_ = vy; } + void getVelocity(float& vx, float& vy) const { + vx = vx_; + vy = vy_; + } + void setVelocity(float vx, float vy) { + vx_ = vx; + vy_ = vy; + } // Setter para posición simple (usado por BoidManager) - void setPosition(float x, float y) { pos_.x = x; pos_.y = y; } + void setPosition(float x, float y) { + pos_.x = x; + pos_.y = y; + } // Getters/Setters para batch rendering SDL_FRect getPosition() const { return pos_; } @@ -84,7 +93,7 @@ class Ball { void setColor(const Color& color) { color_ = color; } // Sistema de cambio de sprite dinámico - void updateSize(int new_size); // Actualizar tamaño de hitbox + void updateSize(int new_size); // Actualizar tamaño de hitbox void setTexture(std::shared_ptr texture); // Cambiar textura del sprite // Funciones para modo Shape (figuras 3D) @@ -99,10 +108,5 @@ class Ball { // Sistema de atracción física hacia figuras 3D void enableShapeAttraction(bool enable); float getDistanceToTarget() const; // Distancia actual al punto objetivo - void applyShapeForce(float target_x, float target_y, float sphere_radius, float deltaTime, - float spring_k = SHAPE_SPRING_K, - float damping_base = SHAPE_DAMPING_BASE, - float damping_near = SHAPE_DAMPING_NEAR, - float near_threshold = SHAPE_NEAR_THRESHOLD, - float max_force = SHAPE_MAX_FORCE); + void applyShapeForce(float target_x, float target_y, float sphere_radius, float delta_time, float spring_k_base = SHAPE_SPRING_K, float damping_base_base = SHAPE_DAMPING_BASE, float damping_near_base = SHAPE_DAMPING_NEAR, float near_threshold_base = SHAPE_NEAR_THRESHOLD, float max_force_base = SHAPE_MAX_FORCE); }; \ No newline at end of file diff --git a/source/boids_mgr/boid_manager.cpp b/source/boids_mgr/boid_manager.cpp index 35450fc..70a325b 100644 --- a/source/boids_mgr/boid_manager.cpp +++ b/source/boids_mgr/boid_manager.cpp @@ -3,39 +3,38 @@ #include // for std::min, std::max #include // for sqrt, atan2 -#include "ball.hpp" // for Ball -#include "engine.hpp" // for Engine (si se necesita) -#include "scene/scene_manager.hpp" // for SceneManager -#include "state/state_manager.hpp" // for StateManager -#include "ui/ui_manager.hpp" // for UIManager +#include "ball.hpp" // for Ball +#include "engine.hpp" // for Engine (si se necesita) +#include "scene/scene_manager.hpp" // for SceneManager +#include "state/state_manager.hpp" // for StateManager +#include "ui/ui_manager.hpp" // for UIManager BoidManager::BoidManager() - : engine_(nullptr) - , scene_mgr_(nullptr) - , ui_mgr_(nullptr) - , state_mgr_(nullptr) - , screen_width_(0) - , screen_height_(0) - , boids_active_(false) - , spatial_grid_(800, 600, BOID_GRID_CELL_SIZE) // Tamaño por defecto, se actualiza en initialize() - , separation_radius_(BOID_SEPARATION_RADIUS) - , alignment_radius_(BOID_ALIGNMENT_RADIUS) - , cohesion_radius_(BOID_COHESION_RADIUS) - , separation_weight_(BOID_SEPARATION_WEIGHT) - , alignment_weight_(BOID_ALIGNMENT_WEIGHT) - , cohesion_weight_(BOID_COHESION_WEIGHT) - , max_speed_(BOID_MAX_SPEED) - , min_speed_(BOID_MIN_SPEED) - , max_force_(BOID_MAX_FORCE) - , boundary_margin_(BOID_BOUNDARY_MARGIN) - , boundary_weight_(BOID_BOUNDARY_WEIGHT) { + : engine_(nullptr), + scene_mgr_(nullptr), + ui_mgr_(nullptr), + state_mgr_(nullptr), + screen_width_(0), + screen_height_(0), + boids_active_(false), + spatial_grid_(800, 600, BOID_GRID_CELL_SIZE) // Tamaño por defecto, se actualiza en initialize() + , + separation_radius_(BOID_SEPARATION_RADIUS), + alignment_radius_(BOID_ALIGNMENT_RADIUS), + cohesion_radius_(BOID_COHESION_RADIUS), + separation_weight_(BOID_SEPARATION_WEIGHT), + alignment_weight_(BOID_ALIGNMENT_WEIGHT), + cohesion_weight_(BOID_COHESION_WEIGHT), + max_speed_(BOID_MAX_SPEED), + min_speed_(BOID_MIN_SPEED), + max_force_(BOID_MAX_FORCE), + boundary_margin_(BOID_BOUNDARY_MARGIN), + boundary_weight_(BOID_BOUNDARY_WEIGHT) { } -BoidManager::~BoidManager() { -} +BoidManager::~BoidManager() = default; -void BoidManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, - StateManager* state_mgr, int screen_width, int screen_height) { +void BoidManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height) { engine_ = engine; scene_mgr_ = scene_mgr; ui_mgr_ = ui_mgr; @@ -65,7 +64,8 @@ void BoidManager::activateBoids() { auto& balls = scene_mgr_->getBallsMutable(); for (auto& ball : balls) { // Dar velocidad inicial aleatoria si está quieto - float vx, vy; + float vx; + float vy; ball->getVelocity(vx, vy); if (vx == 0.0f && vy == 0.0f) { // Velocidad aleatoria entre -60 y +60 px/s (time-based) @@ -76,13 +76,15 @@ void BoidManager::activateBoids() { } // Mostrar notificación (solo si NO estamos en modo demo o logo) - if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { + if ((state_mgr_ != nullptr) && (ui_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { ui_mgr_->showNotification("Modo boids"); } } void BoidManager::deactivateBoids(bool force_gravity_on) { - if (!boids_active_) return; + if (!boids_active_) { + return; + } boids_active_ = false; @@ -92,7 +94,7 @@ void BoidManager::deactivateBoids(bool force_gravity_on) { } // Mostrar notificación (solo si NO estamos en modo demo o logo) - if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { + if ((state_mgr_ != nullptr) && (ui_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { ui_mgr_->showNotification("Modo física"); } } @@ -106,7 +108,9 @@ void BoidManager::toggleBoidsMode(bool force_gravity_on) { } void BoidManager::update(float delta_time) { - if (!boids_active_) return; + if (!boids_active_) { + return; + } auto& balls = scene_mgr_->getBallsMutable(); @@ -114,8 +118,8 @@ void BoidManager::update(float delta_time) { spatial_grid_.clear(); for (auto& ball : balls) { SDL_FRect pos = ball->getPosition(); - float center_x = pos.x + pos.w / 2.0f; - float center_y = pos.y + pos.h / 2.0f; + float center_x = pos.x + (pos.w / 2.0f); + float center_y = pos.y + (pos.h / 2.0f); spatial_grid_.insert(ball.get(), center_x, center_y); } @@ -131,7 +135,8 @@ void BoidManager::update(float delta_time) { // Actualizar posiciones con velocidades resultantes (time-based) for (auto& ball : balls) { - float vx, vy; + float vx; + float vy; ball->getVelocity(vx, vy); SDL_FRect pos = ball->getPosition(); @@ -153,22 +158,24 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) { int count = 0; SDL_FRect pos = boid->getPosition(); - float center_x = pos.x + pos.w / 2.0f; - float center_y = pos.y + pos.h / 2.0f; + float center_x = pos.x + (pos.w / 2.0f); + float center_y = pos.y + (pos.h / 2.0f); // FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n)) auto neighbors = spatial_grid_.queryRadius(center_x, center_y, separation_radius_); for (Ball* other : neighbors) { - if (other == boid) continue; // Ignorar a sí mismo + if (other == boid) { + continue; // Ignorar a sí mismo + } SDL_FRect other_pos = other->getPosition(); - float other_x = other_pos.x + other_pos.w / 2.0f; - float other_y = other_pos.y + other_pos.h / 2.0f; + float other_x = other_pos.x + (other_pos.w / 2.0f); + float other_y = other_pos.y + (other_pos.h / 2.0f); float dx = center_x - other_x; float dy = center_y - other_y; - float distance = std::sqrt(dx * dx + dy * dy); + float distance = std::sqrt((dx * dx) + (dy * dy)); if (distance > 0.0f && distance < separation_radius_) { // FASE 1.3: Separación más fuerte cuando más cerca (inversamente proporcional a distancia) @@ -186,7 +193,8 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) { steer_y /= count; // Aplicar fuerza de separación - float vx, vy; + float vx; + float vy; boid->getVelocity(vx, vy); vx += steer_x * separation_weight_ * delta_time; vy += steer_y * separation_weight_ * delta_time; @@ -201,25 +209,28 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) { int count = 0; SDL_FRect pos = boid->getPosition(); - float center_x = pos.x + pos.w / 2.0f; - float center_y = pos.y + pos.h / 2.0f; + float center_x = pos.x + (pos.w / 2.0f); + float center_y = pos.y + (pos.h / 2.0f); // FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n)) auto neighbors = spatial_grid_.queryRadius(center_x, center_y, alignment_radius_); for (Ball* other : neighbors) { - if (other == boid) continue; + if (other == boid) { + continue; + } SDL_FRect other_pos = other->getPosition(); - float other_x = other_pos.x + other_pos.w / 2.0f; - float other_y = other_pos.y + other_pos.h / 2.0f; + float other_x = other_pos.x + (other_pos.w / 2.0f); + float other_y = other_pos.y + (other_pos.h / 2.0f); float dx = center_x - other_x; float dy = center_y - other_y; - float distance = std::sqrt(dx * dx + dy * dy); + float distance = std::sqrt((dx * dx) + (dy * dy)); if (distance < alignment_radius_) { - float other_vx, other_vy; + float other_vx; + float other_vy; other->getVelocity(other_vx, other_vy); avg_vx += other_vx; avg_vy += other_vy; @@ -233,13 +244,14 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) { avg_vy /= count; // Steering hacia la velocidad promedio - float vx, vy; + float vx; + float vy; boid->getVelocity(vx, vy); float steer_x = (avg_vx - vx) * alignment_weight_ * delta_time; float steer_y = (avg_vy - vy) * alignment_weight_ * delta_time; // Limitar fuerza máxima de steering - float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y); + float steer_mag = std::sqrt((steer_x * steer_x) + (steer_y * steer_y)); if (steer_mag > max_force_) { steer_x = (steer_x / steer_mag) * max_force_; steer_y = (steer_y / steer_mag) * max_force_; @@ -258,22 +270,24 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) { int count = 0; SDL_FRect pos = boid->getPosition(); - float center_x = pos.x + pos.w / 2.0f; - float center_y = pos.y + pos.h / 2.0f; + float center_x = pos.x + (pos.w / 2.0f); + float center_y = pos.y + (pos.h / 2.0f); // FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n)) auto neighbors = spatial_grid_.queryRadius(center_x, center_y, cohesion_radius_); for (Ball* other : neighbors) { - if (other == boid) continue; + if (other == boid) { + continue; + } SDL_FRect other_pos = other->getPosition(); - float other_x = other_pos.x + other_pos.w / 2.0f; - float other_y = other_pos.y + other_pos.h / 2.0f; + float other_x = other_pos.x + (other_pos.w / 2.0f); + float other_y = other_pos.y + (other_pos.h / 2.0f); float dx = center_x - other_x; float dy = center_y - other_y; - float distance = std::sqrt(dx * dx + dy * dy); + float distance = std::sqrt((dx * dx) + (dy * dy)); if (distance < cohesion_radius_) { center_of_mass_x += other_x; @@ -290,7 +304,7 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) { // FASE 1.4: Normalizar dirección hacia el centro (CRÍTICO - antes no estaba normalizado!) float dx_to_center = center_of_mass_x - center_x; float dy_to_center = center_of_mass_y - center_y; - float distance_to_center = std::sqrt(dx_to_center * dx_to_center + dy_to_center * dy_to_center); + float distance_to_center = std::sqrt((dx_to_center * dx_to_center) + (dy_to_center * dy_to_center)); // Solo aplicar si hay distancia al centro (evitar división por cero) if (distance_to_center > 0.1f) { @@ -299,13 +313,14 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) { float steer_y = (dy_to_center / distance_to_center) * cohesion_weight_ * delta_time; // Limitar fuerza máxima de steering - float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y); + float steer_mag = std::sqrt((steer_x * steer_x) + (steer_y * steer_y)); if (steer_mag > max_force_) { steer_x = (steer_x / steer_mag) * max_force_; steer_y = (steer_y / steer_mag) * max_force_; } - float vx, vy; + float vx; + float vy; boid->getVelocity(vx, vy); vx += steer_x; vy += steer_y; @@ -314,12 +329,12 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) { } } -void BoidManager::applyBoundaries(Ball* boid) { +void BoidManager::applyBoundaries(Ball* boid) const { // NUEVA IMPLEMENTACIÓN: Bordes como obstáculos (repulsión en lugar de wrapping) // Cuando un boid se acerca a un borde, se aplica una fuerza alejándolo SDL_FRect pos = boid->getPosition(); - float center_x = pos.x + pos.w / 2.0f; - float center_y = pos.y + pos.h / 2.0f; + float center_x = pos.x + (pos.w / 2.0f); + float center_y = pos.y + (pos.h / 2.0f); float steer_x = 0.0f; float steer_y = 0.0f; @@ -363,11 +378,12 @@ void BoidManager::applyBoundaries(Ball* boid) { // Aplicar fuerza de repulsión si hay alguna if (steer_x != 0.0f || steer_y != 0.0f) { - float vx, vy; + float vx; + float vy; boid->getVelocity(vx, vy); // Normalizar fuerza de repulsión (para que todas las direcciones tengan la misma intensidad) - float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y); + float steer_mag = std::sqrt((steer_x * steer_x) + (steer_y * steer_y)); if (steer_mag > 0.0f) { steer_x /= steer_mag; steer_y /= steer_mag; @@ -381,12 +397,13 @@ void BoidManager::applyBoundaries(Ball* boid) { } } -void BoidManager::limitSpeed(Ball* boid) { +void BoidManager::limitSpeed(Ball* boid) const { // Limitar velocidad máxima del boid - float vx, vy; + float vx; + float vy; boid->getVelocity(vx, vy); - float speed = std::sqrt(vx * vx + vy * vy); + float speed = std::sqrt((vx * vx) + (vy * vy)); // Limitar velocidad máxima if (speed > max_speed_) { diff --git a/source/boids_mgr/boid_manager.hpp b/source/boids_mgr/boid_manager.hpp index 16d1c99..f738f36 100644 --- a/source/boids_mgr/boid_manager.hpp +++ b/source/boids_mgr/boid_manager.hpp @@ -46,8 +46,7 @@ class BoidManager { * @param screen_width Ancho de pantalla actual * @param screen_height Alto de pantalla actual */ - void initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, - StateManager* state_mgr, int screen_width, int screen_height); + void initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height); /** * @brief Actualiza el tamaño de pantalla (llamado en resize/fullscreen) @@ -105,22 +104,22 @@ class BoidManager { // === Parámetros ajustables en runtime (inicializados con valores de defines.h) === // Permite modificar comportamiento sin recompilar (para tweaking/debug visual) - float separation_radius_; // Radio de separación (evitar colisiones) - float alignment_radius_; // Radio de alineación (matching de velocidad) - float cohesion_radius_; // Radio de cohesión (centro de masa) - float separation_weight_; // Peso fuerza de separación (aceleración px/s²) - float alignment_weight_; // Peso fuerza de alineación (steering proporcional) - float cohesion_weight_; // Peso fuerza de cohesión (aceleración px/s²) - float max_speed_; // Velocidad máxima (px/s) - float min_speed_; // Velocidad mínima (px/s) - float max_force_; // Fuerza máxima de steering (px/s) - float boundary_margin_; // Margen para repulsión de bordes (px) - float boundary_weight_; // Peso fuerza de repulsión de bordes (aceleración px/s²) + float separation_radius_; // Radio de separación (evitar colisiones) + float alignment_radius_; // Radio de alineación (matching de velocidad) + float cohesion_radius_; // Radio de cohesión (centro de masa) + float separation_weight_; // Peso fuerza de separación (aceleración px/s²) + float alignment_weight_; // Peso fuerza de alineación (steering proporcional) + float cohesion_weight_; // Peso fuerza de cohesión (aceleración px/s²) + float max_speed_; // Velocidad máxima (px/s) + float min_speed_; // Velocidad mínima (px/s) + float max_force_; // Fuerza máxima de steering (px/s) + float boundary_margin_; // Margen para repulsión de bordes (px) + float boundary_weight_; // Peso fuerza de repulsión de bordes (aceleración px/s²) // Métodos privados para las reglas de Reynolds void applySeparation(Ball* boid, float delta_time); void applyAlignment(Ball* boid, float delta_time); void applyCohesion(Ball* boid, float delta_time); - void applyBoundaries(Ball* boid); // Repulsión de bordes (ya no wrapping) - void limitSpeed(Ball* boid); // Limitar velocidad máxima + void applyBoundaries(Ball* boid) const; // Repulsión de bordes (ya no wrapping) + void limitSpeed(Ball* boid) const; // Limitar velocidad máxima }; diff --git a/source/boids_mgr/spatial_grid.cpp b/source/boids_mgr/spatial_grid.cpp index 0cd3a8a..dffb354 100644 --- a/source/boids_mgr/spatial_grid.cpp +++ b/source/boids_mgr/spatial_grid.cpp @@ -6,9 +6,9 @@ #include "ball.hpp" // for Ball SpatialGrid::SpatialGrid(int world_width, int world_height, float cell_size) - : world_width_(world_width) - , world_height_(world_height) - , cell_size_(cell_size) { + : world_width_(world_width), + world_height_(world_height), + cell_size_(cell_size) { // Calcular número de celdas en cada dimensión grid_cols_ = static_cast(std::ceil(world_width / cell_size)); grid_rows_ = static_cast(std::ceil(world_height / cell_size)); @@ -21,7 +21,8 @@ void SpatialGrid::clear() { void SpatialGrid::insert(Ball* ball, float x, float y) { // Obtener coordenadas de celda - int cell_x, cell_y; + int cell_x; + int cell_y; getCellCoords(x, y, cell_x, cell_y); // Generar hash key y añadir a la celda @@ -29,11 +30,14 @@ void SpatialGrid::insert(Ball* ball, float x, float y) { cells_[key].push_back(ball); } -std::vector SpatialGrid::queryRadius(float x, float y, float radius) { +auto SpatialGrid::queryRadius(float x, float y, float radius) -> std::vector { std::vector results; // Calcular rango de celdas a revisar (AABB del círculo de búsqueda) - int min_cell_x, min_cell_y, max_cell_x, max_cell_y; + int min_cell_x; + int min_cell_y; + int max_cell_x; + int max_cell_y; getCellCoords(x - radius, y - radius, min_cell_x, min_cell_y); getCellCoords(x + radius, y + radius, max_cell_x, max_cell_y); @@ -82,8 +86,8 @@ void SpatialGrid::getCellCoords(float x, float y, int& cell_x, int& cell_y) cons cell_y = static_cast(std::floor(y / cell_size_)); } -int SpatialGrid::getCellKey(int cell_x, int cell_y) const { +auto SpatialGrid::getCellKey(int cell_x, int cell_y) const -> int { // Hash espacial 2D → 1D usando codificación por filas // Formula: key = y * ancho + x (similar a array 2D aplanado) - return cell_y * grid_cols_ + cell_x; + return (cell_y * grid_cols_) + cell_x; } diff --git a/source/boids_mgr/spatial_grid.hpp b/source/boids_mgr/spatial_grid.hpp index c38a418..754c3af 100644 --- a/source/boids_mgr/spatial_grid.hpp +++ b/source/boids_mgr/spatial_grid.hpp @@ -30,42 +30,42 @@ class Ball; // Forward declaration // ============================================================================ class SpatialGrid { -public: - // Constructor: especificar dimensiones del mundo y tamaño de celda - SpatialGrid(int world_width, int world_height, float cell_size); + public: + // Constructor: especificar dimensiones del mundo y tamaño de celda + SpatialGrid(int world_width, int world_height, float cell_size); - // Limpiar todas las celdas (llamar al inicio de cada frame) - void clear(); + // Limpiar todas las celdas (llamar al inicio de cada frame) + void clear(); - // Insertar objeto en el grid según su posición (x, y) - void insert(Ball* ball, float x, float y); + // Insertar objeto en el grid según su posición (x, y) + void insert(Ball* ball, float x, float y); - // Buscar todos los objetos dentro del radio especificado desde (x, y) - // Devuelve vector de punteros a Ball (puede contener duplicados si ball está en múltiples celdas) - std::vector queryRadius(float x, float y, float radius); + // Buscar todos los objetos dentro del radio especificado desde (x, y) + // Devuelve vector de punteros a Ball (puede contener duplicados si ball está en múltiples celdas) + std::vector queryRadius(float x, float y, float radius); - // Actualizar dimensiones del mundo (útil para cambios de resolución F4) - void updateWorldSize(int world_width, int world_height); + // Actualizar dimensiones del mundo (útil para cambios de resolución F4) + void updateWorldSize(int world_width, int world_height); -private: - // Convertir coordenadas (x, y) a índice de celda (cell_x, cell_y) - void getCellCoords(float x, float y, int& cell_x, int& cell_y) const; + private: + // Convertir coordenadas (x, y) a índice de celda (cell_x, cell_y) + void getCellCoords(float x, float y, int& cell_x, int& cell_y) const; - // Convertir (cell_x, cell_y) a hash key único para el mapa - int getCellKey(int cell_x, int cell_y) const; + // Convertir (cell_x, cell_y) a hash key único para el mapa + int getCellKey(int cell_x, int cell_y) const; - // Dimensiones del mundo (ancho/alto en píxeles) - int world_width_; - int world_height_; + // Dimensiones del mundo (ancho/alto en píxeles) + int world_width_; + int world_height_; - // Tamaño de cada celda (en píxeles) - float cell_size_; + // Tamaño de cada celda (en píxeles) + float cell_size_; - // Número de celdas en cada dimensión - int grid_cols_; - int grid_rows_; + // Número de celdas en cada dimensión + int grid_cols_; + int grid_rows_; - // Estructura de datos: hash map de cell_key → vector de Ball* - // Usamos unordered_map para O(1) lookup - std::unordered_map> cells_; + // Estructura de datos: hash map de cell_key → vector de Ball* + // Usamos unordered_map para O(1) lookup + std::unordered_map> cells_; }; diff --git a/source/engine.cpp b/source/engine.cpp index f927e11..af9aa75 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -8,6 +8,7 @@ #include // for SDL_GetTicks #include // for SDL_CreateWindow, SDL_DestroyWindow, SDL_GetDisplayBounds +#include // for std::array #include // for std::min, std::max, std::sort #include // for sqrtf, acosf, cosf, sinf (funciones matemáticas) #include // for rand, srand @@ -23,29 +24,31 @@ #include // for GetModuleFileName #endif -#include "ball.hpp" // for Ball -#include "external/mouse.hpp" // for Mouse namespace -#include "external/texture.hpp" // for Texture -#include "shapes/png_shape.hpp" // for PNGShape (dynamic_cast en callbacks LOGO) +#include "ball.hpp" // for Ball +#include "external/mouse.hpp" // for Mouse namespace +#include "external/texture.hpp" // for Texture +#include "shapes/png_shape.hpp" // for PNGShape (dynamic_cast en callbacks LOGO) // Implementación de métodos públicos -bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMode initial_mode) { +auto Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMode initial_mode) -> bool { // NOLINT(readability-function-cognitive-complexity) bool success = true; // Obtener resolución de pantalla para validación if (!SDL_Init(SDL_INIT_VIDEO)) { - std::cout << "¡SDL no se pudo inicializar! Error de SDL: " << SDL_GetError() << std::endl; + std::cout << "¡SDL no se pudo inicializar! Error de SDL: " << SDL_GetError() << '\n'; return false; } int num_displays = 0; SDL_DisplayID* displays = SDL_GetDisplays(&num_displays); - const auto* dm = (displays && num_displays > 0) ? SDL_GetCurrentDisplayMode(displays[0]) : nullptr; + const auto* dm = ((displays != nullptr) && num_displays > 0) ? SDL_GetCurrentDisplayMode(displays[0]) : nullptr; - int screen_w = dm ? dm->w : 1920; // Fallback si falla - int screen_h = dm ? dm->h - WINDOW_DECORATION_HEIGHT : 1080; + int screen_w = (dm != nullptr) ? dm->w : 1920; // Fallback si falla + int screen_h = (dm != nullptr) ? dm->h - WINDOW_DECORATION_HEIGHT : 1080; - if (displays) SDL_free(displays); + if (displays != nullptr) { + SDL_free(displays); + } // Usar parámetros o valores por defecto int logical_width = (width > 0) ? width : DEFAULT_SCREEN_WIDTH; @@ -67,10 +70,10 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod if (max_zoom < 1) { // Resolució lògica no cap en pantalla ni a zoom=1: escalar-la per fer-la càpida float scale = std::min(static_cast(screen_w) / logical_width, - static_cast(screen_h) / logical_height); - logical_width = std::max(320, static_cast(logical_width * scale)); + static_cast(screen_h) / logical_height); + logical_width = std::max(320, static_cast(logical_width * scale)); logical_height = std::max(240, static_cast(logical_height * scale)); - window_zoom = 1; + window_zoom = 1; std::cout << "Advertencia: Resolución no cabe en pantalla. Ajustando a " << logical_width << "x" << logical_height << "\n"; } else if (window_zoom > max_zoom) { @@ -109,7 +112,7 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod window_ = SDL_CreateWindow(WINDOW_CAPTION, window_width, window_height, window_flags); if (window_ == nullptr) { - std::cout << "¡No se pudo crear la ventana! Error de SDL: " << SDL_GetError() << std::endl; + std::cout << "¡No se pudo crear la ventana! Error de SDL: " << SDL_GetError() << '\n'; success = false; } else { // Centrar ventana en pantalla si no está en fullscreen @@ -120,7 +123,7 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod // Inicializar SDL_GPU (sustituye SDL_Renderer como backend principal) gpu_ctx_ = std::make_unique(); if (!gpu_ctx_->init(window_)) { - std::cout << "¡No se pudo inicializar SDL_GPU!" << std::endl; + std::cout << "¡No se pudo inicializar SDL_GPU!" << '\n'; success = false; } else { gpu_ctx_->setVSync(vsync_enabled_); @@ -128,11 +131,11 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod // Crear renderer de software para UI/texto (SDL3_ttf no es compatible con SDL_GPU) // Renderiza a ui_surface_, que luego se sube como textura GPU overlay ui_surface_ = SDL_CreateSurface(logical_width, logical_height, SDL_PIXELFORMAT_RGBA32); - if (ui_surface_) { + if (ui_surface_ != nullptr) { ui_renderer_ = SDL_CreateSoftwareRenderer(ui_surface_); } - if (!ui_renderer_) { - std::cout << "Advertencia: no se pudo crear el renderer de UI software" << std::endl; + if (ui_renderer_ == nullptr) { + std::cout << "Advertencia: no se pudo crear el renderer de UI software" << '\n'; // No es crítico — el juego funciona sin texto } } @@ -188,7 +191,7 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod } // Ordenar por tamaño (grande → pequeño): big(16) → normal(10) → small(6) → tiny(4) - std::sort(texture_files.begin(), texture_files.end(), [](const TextureInfo& a, const TextureInfo& b) { + std::ranges::sort(texture_files, [](const TextureInfo& a, const TextureInfo& b) { return a.width > b.width; }); @@ -200,14 +203,14 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod // Cargar textura GPU para renderizado de sprites auto gpu_tex = std::make_unique(); if (gpu_ctx_ && !gpu_tex->fromFile(gpu_ctx_->device(), info.path)) { - std::cerr << "Advertencia: no se pudo cargar textura GPU: " << info.name << std::endl; + std::cerr << "Advertencia: no se pudo cargar textura GPU: " << info.name << '\n'; } gpu_textures_.push_back(std::move(gpu_tex)); } // Verificar que se cargaron texturas if (textures_.empty()) { - std::cerr << "ERROR: No se pudieron cargar texturas" << std::endl; + std::cerr << "ERROR: No se pudieron cargar texturas" << '\n'; success = false; } @@ -227,7 +230,7 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod gpu_pipeline_ = std::make_unique(); if (!gpu_pipeline_->init(gpu_ctx_->device(), gpu_ctx_->swapchainFormat(), offscreen_fmt)) { - std::cerr << "ERROR: No se pudo crear el pipeline GPU" << std::endl; + std::cerr << "ERROR: No se pudo crear el pipeline GPU" << '\n'; success = false; } @@ -235,42 +238,45 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod // addBackground() no usa el guard de pushQuad(), así que no consume slots aquí. // init() reserva internamente +1 quad extra garantizado para el overlay. int sprite_capacity = BALL_COUNT_SCENARIOS[DEMO_AUTO_MAX_SCENARIO]; - if (custom_scenario_enabled_ && custom_scenario_balls_ > sprite_capacity) + if (custom_scenario_enabled_ && custom_scenario_balls_ > sprite_capacity) { sprite_capacity = custom_scenario_balls_; + } sprite_batch_ = std::make_unique(); if (!sprite_batch_->init(gpu_ctx_->device(), sprite_capacity)) { - std::cerr << "ERROR: No se pudo crear el sprite batch GPU" << std::endl; + std::cerr << "ERROR: No se pudo crear el sprite batch GPU" << '\n'; success = false; } gpu_ball_buffer_ = std::make_unique(); if (!gpu_ball_buffer_->init(gpu_ctx_->device())) { - std::cerr << "ERROR: No se pudo crear el ball buffer GPU" << std::endl; + std::cerr << "ERROR: No se pudo crear el ball buffer GPU" << '\n'; success = false; } ball_gpu_data_.reserve(GpuBallBuffer::MAX_BALLS); offscreen_tex_ = std::make_unique(); if (!offscreen_tex_->createRenderTarget(gpu_ctx_->device(), - current_screen_width_, current_screen_height_, - offscreen_fmt)) { - std::cerr << "ERROR: No se pudo crear render target offscreen" << std::endl; + current_screen_width_, + current_screen_height_, + offscreen_fmt)) { + std::cerr << "ERROR: No se pudo crear render target offscreen" << '\n'; success = false; } white_tex_ = std::make_unique(); if (!white_tex_->createWhite(gpu_ctx_->device())) { - std::cerr << "ERROR: No se pudo crear textura blanca" << std::endl; + std::cerr << "ERROR: No se pudo crear textura blanca" << '\n'; success = false; } // Create UI overlay texture (render target usage so GPU can sample it) ui_tex_ = std::make_unique(); if (!ui_tex_->createRenderTarget(gpu_ctx_->device(), - logical_width, logical_height, - SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM)) { - std::cerr << "Advertencia: no se pudo crear textura UI GPU" << std::endl; + logical_width, + logical_height, + SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM)) { + std::cerr << "Advertencia: no se pudo crear textura UI GPU" << '\n'; } } @@ -284,8 +290,9 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod theme_manager_->initialize(); { int max_balls = BALL_COUNT_SCENARIOS[DEMO_AUTO_MAX_SCENARIO]; - if (custom_scenario_enabled_ && custom_scenario_balls_ > max_balls) + if (custom_scenario_enabled_ && custom_scenario_balls_ > max_balls) { max_balls = custom_scenario_balls_; + } theme_manager_->setMaxBallCount(max_balls); } @@ -294,13 +301,15 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod scene_manager_->initialize(0, texture_, theme_manager_.get()); // Escenario 0 (10 bolas) por defecto // Propagar configuración custom si fue establecida antes de initialize() - if (custom_scenario_enabled_) + if (custom_scenario_enabled_) { scene_manager_->setCustomBallCount(custom_scenario_balls_); + } // Calcular tamaño físico de ventana ANTES de inicializar UIManager // NOTA: No llamar a updatePhysicalWindowSize() aquí porque ui_manager_ aún no existe // Calcular manualmente para poder pasar valores al constructor de UIManager - int window_w = 0, window_h = 0; + int window_w = 0; + int window_h = 0; SDL_GetWindowSizeInPixels(window_, &window_w, &window_h); physical_window_width_ = window_w; physical_window_height_ = window_h; @@ -308,14 +317,11 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod // Inicializar UIManager (HUD, FPS, notificaciones) // NOTA: Debe llamarse DESPUÉS de calcular physical_window_* y ThemeManager ui_manager_ = std::make_unique(); - ui_manager_->initialize(ui_renderer_, theme_manager_.get(), - physical_window_width_, physical_window_height_, - current_screen_width_, current_screen_height_); + ui_manager_->initialize(ui_renderer_, theme_manager_.get(), physical_window_width_, physical_window_height_, current_screen_width_, current_screen_height_); // Inicializar ShapeManager (gestión de figuras 3D) shape_manager_ = std::make_unique(); - shape_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), nullptr, - current_screen_width_, current_screen_height_); + shape_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), nullptr, current_screen_width_, current_screen_height_); // Inicializar StateManager (gestión de estados DEMO/LOGO) state_manager_ = std::make_unique(); @@ -326,50 +332,47 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod if (initial_mode == AppMode::DEMO) { state_manager_->toggleDemoMode(current_screen_width_, current_screen_height_); // Como estamos en SANDBOX (default), toggleDemoMode() cambiará a DEMO + randomizará - } - else if (initial_mode == AppMode::DEMO_LITE) { + } else if (initial_mode == AppMode::DEMO_LITE) { state_manager_->toggleDemoLiteMode(current_screen_width_, current_screen_height_); // Como estamos en SANDBOX (default), toggleDemoLiteMode() cambiará a DEMO_LITE + randomizará - } - else if (initial_mode == AppMode::LOGO) { + } else if (initial_mode == AppMode::LOGO) { size_t initial_ball_count = scene_manager_->getBallCount(); state_manager_->enterLogoMode(false, current_screen_width_, current_screen_height_, initial_ball_count); // enterLogoMode() hace: setState(LOGO) + configuración visual completa } // Actualizar ShapeManager con StateManager (dependencia circular - StateManager debe existir primero) - shape_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(), - current_screen_width_, current_screen_height_); + shape_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(), current_screen_width_, current_screen_height_); // Inicializar BoidManager (gestión de comportamiento de enjambre) boid_manager_ = std::make_unique(); - boid_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(), - current_screen_width_, current_screen_height_); + boid_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(), current_screen_width_, current_screen_height_); // Inicializar AppLogo (logo periódico en pantalla) app_logo_ = std::make_unique(); if (!app_logo_->initialize(ui_renderer_, current_screen_width_, current_screen_height_)) { - std::cerr << "Advertencia: No se pudo inicializar AppLogo (logo periódico)" << std::endl; + std::cerr << "Advertencia: No se pudo inicializar AppLogo (logo periódico)" << '\n'; // No es crítico, continuar sin logo app_logo_.reset(); } // Benchmark de rendimiento (determina max_auto_scenario_ para modos automáticos) - if (!skip_benchmark_) + if (!skip_benchmark_) { runPerformanceBenchmark(); - else if (custom_scenario_enabled_) + } else if (custom_scenario_enabled_) { custom_auto_available_ = true; // benchmark omitido: confiar en que el hardware lo soporta + } // Precalentar caché: shapes PNG (evitar I/O en primera activación de PNG_SHAPE) { - unsigned char* tmp = nullptr; size_t tmp_size = 0; + unsigned char* tmp = nullptr; + size_t tmp_size = 0; ResourceManager::loadResource("shapes/jailgames.png", tmp, tmp_size); delete[] tmp; } // Mostrar ventana ahora que el benchmark terminó SDL_ShowWindow(window_); - } return success; @@ -380,7 +383,7 @@ void Engine::run() { calculateDeltaTime(); // Procesar eventos de entrada (teclado, ratón, ventana) - if (input_handler_->processEvents(*this)) { + if (InputHandler::processEvents(*this)) { should_exit_ = true; } @@ -391,29 +394,58 @@ void Engine::run() { void Engine::shutdown() { // Wait for GPU idle before releasing GPU resources - if (gpu_ctx_) SDL_WaitForGPUIdle(gpu_ctx_->device()); + if (gpu_ctx_) { + SDL_WaitForGPUIdle(gpu_ctx_->device()); + } // Release GPU sprite textures gpu_textures_.clear(); // Release GPU render targets and utility textures if (gpu_ctx_) { - if (ui_tex_) { ui_tex_->destroy(gpu_ctx_->device()); ui_tex_.reset(); } - if (white_tex_) { white_tex_->destroy(gpu_ctx_->device()); white_tex_.reset(); } - if (offscreen_tex_) { offscreen_tex_->destroy(gpu_ctx_->device()); offscreen_tex_.reset(); } - if (sprite_batch_) { sprite_batch_->destroy(gpu_ctx_->device()); sprite_batch_.reset(); } - if (gpu_ball_buffer_) { gpu_ball_buffer_->destroy(gpu_ctx_->device()); gpu_ball_buffer_.reset(); } - if (gpu_pipeline_) { gpu_pipeline_->destroy(gpu_ctx_->device()); gpu_pipeline_.reset(); } + if (ui_tex_) { + ui_tex_->destroy(gpu_ctx_->device()); + ui_tex_.reset(); + } + if (white_tex_) { + white_tex_->destroy(gpu_ctx_->device()); + white_tex_.reset(); + } + if (offscreen_tex_) { + offscreen_tex_->destroy(gpu_ctx_->device()); + offscreen_tex_.reset(); + } + if (sprite_batch_) { + sprite_batch_->destroy(gpu_ctx_->device()); + sprite_batch_.reset(); + } + if (gpu_ball_buffer_) { + gpu_ball_buffer_->destroy(gpu_ctx_->device()); + gpu_ball_buffer_.reset(); + } + if (gpu_pipeline_) { + gpu_pipeline_->destroy(gpu_ctx_->device()); + gpu_pipeline_.reset(); + } } // Destroy software UI renderer and surface - if (ui_renderer_) { SDL_DestroyRenderer(ui_renderer_); ui_renderer_ = nullptr; } - if (ui_surface_) { SDL_DestroySurface(ui_surface_); ui_surface_ = nullptr; } + if (ui_renderer_ != nullptr) { + SDL_DestroyRenderer(ui_renderer_); + ui_renderer_ = nullptr; + } + if (ui_surface_ != nullptr) { + SDL_DestroySurface(ui_surface_); + ui_surface_ = nullptr; + } // Destroy GPU context (releases device and window claim) - if (gpu_ctx_) { gpu_ctx_->destroy(); gpu_ctx_.reset(); } + if (gpu_ctx_) { + gpu_ctx_->destroy(); + gpu_ctx_.reset(); + } - if (window_) { + if (window_ != nullptr) { SDL_DestroyWindow(window_); window_ = nullptr; } @@ -541,8 +573,8 @@ void Engine::toggleShapeMode() { } else { // Mostrar nombre de la figura actual (orden debe coincidir con enum ShapeType) // Índices: 0=NONE, 1=SPHERE, 2=CUBE, 3=HELIX, 4=TORUS, 5=LISSAJOUS, 6=CYLINDER, 7=ICOSAHEDRON, 8=ATOM, 9=PNG_SHAPE - const char* shape_names[] = {"Ninguna", "Esfera", "Cubo", "Hélice", "Toroide", "Lissajous", "Cilindro", "Icosaedro", "Átomo", "Forma PNG"}; - showNotificationForAction(shape_names[static_cast(shape_manager_->getCurrentShapeType())]); + constexpr std::array SHAPE_NAMES = {"Ninguna", "Esfera", "Cubo", "Hélice", "Toroide", "Lissajous", "Cilindro", "Icosaedro", "Átomo", "Forma PNG"}; + showNotificationForAction(SHAPE_NAMES[static_cast(shape_manager_->getCurrentShapeType())]); } } @@ -567,7 +599,7 @@ void Engine::toggleDepthZoom() { } // Boids (comportamiento de enjambre) -bool Engine::isScenarioAllowedForBoids(int scenario_id) const { +auto Engine::isScenarioAllowedForBoids(int scenario_id) const -> bool { int ball_count = (scenario_id == CUSTOM_SCENARIO_IDX) ? custom_scenario_balls_ : BALL_COUNT_SCENARIOS[scenario_id]; @@ -654,19 +686,23 @@ void Engine::setMaxBallsOverride(int n) { skip_benchmark_ = true; int best = DEMO_AUTO_MIN_SCENARIO; for (int i = DEMO_AUTO_MIN_SCENARIO; i <= DEMO_AUTO_MAX_SCENARIO; ++i) { - if (BALL_COUNT_SCENARIOS[i] <= n) best = i; - else break; + if (BALL_COUNT_SCENARIOS[i] <= n) { + best = i; + } else { + break; + } } max_auto_scenario_ = best; } // Escenario custom (--custom-balls) void Engine::setCustomScenario(int balls) { - custom_scenario_balls_ = balls; + custom_scenario_balls_ = balls; custom_scenario_enabled_ = true; // scene_manager_ puede no existir aún (llamada pre-init); propagación en initialize() - if (scene_manager_) + if (scene_manager_) { scene_manager_->setCustomBallCount(balls); + } } // Escenarios (número de pelotas) @@ -747,15 +783,19 @@ void Engine::toggleLogoMode() { } } -void Engine::render() { - if (!gpu_ctx_ || !sprite_batch_ || !gpu_pipeline_) return; +void Engine::render() { // NOLINT(readability-function-cognitive-complexity) + if (!gpu_ctx_ || !sprite_batch_ || !gpu_pipeline_) { + return; + } // === Render UI text to software surface === renderUIToSurface(); // === Acquire command buffer === SDL_GPUCommandBuffer* cmd = gpu_ctx_->acquireCommandBuffer(); - if (!cmd) return; + if (cmd == nullptr) { + return; + } // === Upload UI surface to GPU texture (inline copy pass) === uploadUISurface(cmd); @@ -764,16 +804,27 @@ void Engine::render() { sprite_batch_->beginFrame(); // Background gradient - float top_r = 0, top_g = 0, top_b = 0, bot_r = 0, bot_g = 0, bot_b = 0; + float top_r = 0; + float top_g = 0; + float top_b = 0; + float bot_r = 0; + float bot_g = 0; + float bot_b = 0; theme_manager_->getBackgroundColors(top_r, top_g, top_b, bot_r, bot_g, bot_b); sprite_batch_->addBackground( - static_cast(current_screen_width_), static_cast(current_screen_height_), - top_r, top_g, top_b, bot_r, bot_g, bot_b); + static_cast(current_screen_width_), + static_cast(current_screen_height_), + top_r, + top_g, + top_b, + bot_r, + bot_g, + bot_b); // Sprites (balls) const auto& balls = scene_manager_->getBalls(); - const float sw = static_cast(current_screen_width_); - const float sh = static_cast(current_screen_height_); + const auto SW = static_cast(current_screen_width_); + const auto SH = static_cast(current_screen_height_); if (current_mode_ == SimulationMode::SHAPE) { // SHAPE mode: bucket sort by depth Z (Painter's Algorithm), with depth scale. @@ -789,11 +840,7 @@ void Engine::render() { float brightness = balls[idx]->getDepthBrightness(); float depth_scale = balls[idx]->getDepthScale(); float bf = (ROTOBALL_MIN_BRIGHTNESS + brightness * (ROTOBALL_MAX_BRIGHTNESS - ROTOBALL_MIN_BRIGHTNESS)) / 255.0f; - sprite_batch_->addSprite(pos.x, pos.y, pos.w, pos.h, - color.r / 255.0f * bf, - color.g / 255.0f * bf, - color.b / 255.0f * bf, - 1.0f, depth_scale, sw, sh); + sprite_batch_->addSprite(pos.x, pos.y, pos.w, pos.h, color.r / 255.0f * bf, color.g / 255.0f * bf, color.b / 255.0f * bf, 1.0f, depth_scale, SW, SH); } depth_buckets_[b].clear(); } @@ -805,13 +852,11 @@ void Engine::render() { SDL_FRect pos = balls[idx]->getPosition(); Color color = theme_manager_->getInterpolatedColor(idx); // Convert to NDC center + NDC half-size (both positive) - float cx = ((pos.x + pos.w * 0.5f) / sw) * 2.0f - 1.0f; - float cy = 1.0f - ((pos.y + pos.h * 0.5f) / sh) * 2.0f; - float hw = pos.w / sw; - float hh = pos.h / sh; - ball_gpu_data_.push_back({cx, cy, hw, hh, - color.r / 255.0f, color.g / 255.0f, - color.b / 255.0f, 1.0f}); + float cx = (((pos.x + pos.w * 0.5f) / SW) * 2.0f) - 1.0f; + float cy = 1.0f - (((pos.y + pos.h * 0.5f) / SH) * 2.0f); + float hw = pos.w / SW; + float hh = pos.h / SH; + ball_gpu_data_.push_back({cx, cy, hw, hh, color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, 1.0f}); } } @@ -820,27 +865,27 @@ void Engine::render() { // Upload sprite batch (background + SHAPE balls + UI overlay quad) if (!sprite_batch_->uploadBatch(gpu_ctx_->device(), cmd)) { - gpu_ctx_->submit(cmd); + GpuContext::submit(cmd); return; } // Upload instanced ball buffer (PHYSICS / CPU-BOIDS modes) bool use_instanced_balls = (current_mode_ != SimulationMode::SHAPE) && !ball_gpu_data_.empty(); if (use_instanced_balls) { - gpu_ball_buffer_->upload(gpu_ctx_->device(), cmd, - ball_gpu_data_.data(), static_cast(ball_gpu_data_.size())); + gpu_ball_buffer_->upload(gpu_ctx_->device(), cmd, ball_gpu_data_.data(), static_cast(ball_gpu_data_.size())); } GpuTexture* sprite_tex = (!gpu_textures_.empty()) - ? gpu_textures_[current_texture_index_].get() : nullptr; + ? gpu_textures_[current_texture_index_].get() + : nullptr; // === Pass 1: Render background + balls to offscreen texture === - if (offscreen_tex_ && offscreen_tex_->isValid() && sprite_tex && sprite_tex->isValid()) { + if (offscreen_tex_ && offscreen_tex_->isValid() && (sprite_tex != nullptr) && sprite_tex->isValid()) { SDL_GPUColorTargetInfo ct = {}; - ct.texture = offscreen_tex_->texture(); - ct.load_op = SDL_GPU_LOADOP_CLEAR; - ct.clear_color = {0.0f, 0.0f, 0.0f, 1.0f}; - ct.store_op = SDL_GPU_STOREOP_STORE; + ct.texture = offscreen_tex_->texture(); + ct.load_op = SDL_GPU_LOADOP_CLEAR; + ct.clear_color = {.r = 0.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f}; + ct.store_op = SDL_GPU_STOREOP_STORE; SDL_GPURenderPass* pass1 = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr); @@ -875,85 +920,88 @@ void Engine::render() { SDL_BindGPUIndexBuffer(pass1, &ib, SDL_GPU_INDEXELEMENTSIZE_32BIT); SDL_GPUTextureSamplerBinding tsb = {sprite_tex->texture(), sprite_tex->sampler()}; SDL_BindGPUFragmentSamplers(pass1, 0, &tsb, 1); - SDL_DrawGPUIndexedPrimitives(pass1, sprite_batch_->spriteIndexCount(), 1, - sprite_batch_->spriteIndexOffset(), 0, 0); + SDL_DrawGPUIndexedPrimitives(pass1, sprite_batch_->spriteIndexCount(), 1, sprite_batch_->spriteIndexOffset(), 0, 0); } SDL_EndGPURenderPass(pass1); } // === Pass 2+: External multi-pass shader OR native PostFX → swapchain === - Uint32 sw_w = 0, sw_h = 0; + Uint32 sw_w = 0; + Uint32 sw_h = 0; SDL_GPUTexture* swapchain = gpu_ctx_->acquireSwapchainTexture(cmd, &sw_w, &sw_h); - if (swapchain && offscreen_tex_ && offscreen_tex_->isValid()) { - + if ((swapchain != nullptr) && offscreen_tex_ && offscreen_tex_->isValid()) { // Helper lambda for viewport/scissor (used in the final pass) - auto applyViewport = [&](SDL_GPURenderPass* rp) { - if (!fullscreen_enabled_) return; - float vp_x, vp_y, vp_w, vp_h; + auto apply_viewport = [&](SDL_GPURenderPass* rp) { + if (!fullscreen_enabled_) { + return; + } + float vp_x; + float vp_y; + float vp_w; + float vp_h; if (current_scaling_mode_ == ScalingMode::STRETCH) { - vp_x = 0.0f; vp_y = 0.0f; + vp_x = 0.0f; + vp_y = 0.0f; vp_w = static_cast(sw_w); vp_h = static_cast(sw_h); } else if (current_scaling_mode_ == ScalingMode::INTEGER) { int scale = static_cast(std::min(sw_w / static_cast(base_screen_width_), - sw_h / static_cast(base_screen_height_))); - if (scale < 1) scale = 1; - vp_w = static_cast(base_screen_width_ * scale); + sw_h / static_cast(base_screen_height_))); + scale = std::max(scale, 1); + vp_w = static_cast(base_screen_width_ * scale); vp_h = static_cast(base_screen_height_ * scale); vp_x = (static_cast(sw_w) - vp_w) * 0.5f; vp_y = (static_cast(sw_h) - vp_h) * 0.5f; - } else { // LETTERBOX + } else { // LETTERBOX float scale = std::min(static_cast(sw_w) / base_screen_width_, - static_cast(sw_h) / base_screen_height_); - vp_w = base_screen_width_ * scale; + static_cast(sw_h) / base_screen_height_); + vp_w = base_screen_width_ * scale; vp_h = base_screen_height_ * scale; vp_x = (static_cast(sw_w) - vp_w) * 0.5f; vp_y = (static_cast(sw_h) - vp_h) * 0.5f; } SDL_GPUViewport vp = {vp_x, vp_y, vp_w, vp_h, 0.0f, 1.0f}; SDL_SetGPUViewport(rp, &vp); - SDL_Rect scissor = {static_cast(vp_x), static_cast(vp_y), - static_cast(vp_w), static_cast(vp_h)}; + SDL_Rect scissor = {static_cast(vp_x), static_cast(vp_y), static_cast(vp_w), static_cast(vp_h)}; SDL_SetGPUScissor(rp, &scissor); }; { // --- Native PostFX path --- SDL_GPUColorTargetInfo ct = {}; - ct.texture = swapchain; - ct.load_op = SDL_GPU_LOADOP_CLEAR; - ct.clear_color = {0.0f, 0.0f, 0.0f, 1.0f}; - ct.store_op = SDL_GPU_STOREOP_STORE; + ct.texture = swapchain; + ct.load_op = SDL_GPU_LOADOP_CLEAR; + ct.clear_color = {.r = 0.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f}; + ct.store_op = SDL_GPU_STOREOP_STORE; SDL_GPURenderPass* pass2 = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr); - applyViewport(pass2); + apply_viewport(pass2); - // PostFX: full-screen triangle via vertex_id (no vertex buffer needed) - SDL_BindGPUGraphicsPipeline(pass2, gpu_pipeline_->postfxPipeline()); - SDL_GPUTextureSamplerBinding scene_tsb = {offscreen_tex_->texture(), offscreen_tex_->sampler()}; - SDL_BindGPUFragmentSamplers(pass2, 0, &scene_tsb, 1); - SDL_PushGPUFragmentUniformData(cmd, 0, &postfx_uniforms_, sizeof(PostFXUniforms)); - SDL_DrawGPUPrimitives(pass2, 3, 1, 0, 0); + // PostFX: full-screen triangle via vertex_id (no vertex buffer needed) + SDL_BindGPUGraphicsPipeline(pass2, gpu_pipeline_->postfxPipeline()); + SDL_GPUTextureSamplerBinding scene_tsb = {offscreen_tex_->texture(), offscreen_tex_->sampler()}; + SDL_BindGPUFragmentSamplers(pass2, 0, &scene_tsb, 1); + SDL_PushGPUFragmentUniformData(cmd, 0, &postfx_uniforms_, sizeof(PostFXUniforms)); + SDL_DrawGPUPrimitives(pass2, 3, 1, 0, 0); - // UI overlay (alpha-blended, uses sprite pipeline) - if (ui_tex_ && ui_tex_->isValid() && sprite_batch_->overlayIndexCount() > 0) { - SDL_BindGPUGraphicsPipeline(pass2, gpu_pipeline_->spritePipeline()); - SDL_GPUBufferBinding vb = {sprite_batch_->vertexBuffer(), 0}; - SDL_GPUBufferBinding ib = {sprite_batch_->indexBuffer(), 0}; - SDL_BindGPUVertexBuffers(pass2, 0, &vb, 1); - SDL_BindGPUIndexBuffer(pass2, &ib, SDL_GPU_INDEXELEMENTSIZE_32BIT); - SDL_GPUTextureSamplerBinding ui_tsb = {ui_tex_->texture(), ui_tex_->sampler()}; - SDL_BindGPUFragmentSamplers(pass2, 0, &ui_tsb, 1); - SDL_DrawGPUIndexedPrimitives(pass2, sprite_batch_->overlayIndexCount(), 1, - sprite_batch_->overlayIndexOffset(), 0, 0); - } + // UI overlay (alpha-blended, uses sprite pipeline) + if (ui_tex_ && ui_tex_->isValid() && sprite_batch_->overlayIndexCount() > 0) { + SDL_BindGPUGraphicsPipeline(pass2, gpu_pipeline_->spritePipeline()); + SDL_GPUBufferBinding vb = {sprite_batch_->vertexBuffer(), 0}; + SDL_GPUBufferBinding ib = {sprite_batch_->indexBuffer(), 0}; + SDL_BindGPUVertexBuffers(pass2, 0, &vb, 1); + SDL_BindGPUIndexBuffer(pass2, &ib, SDL_GPU_INDEXELEMENTSIZE_32BIT); + SDL_GPUTextureSamplerBinding ui_tsb = {ui_tex_->texture(), ui_tex_->sampler()}; + SDL_BindGPUFragmentSamplers(pass2, 0, &ui_tsb, 1); + SDL_DrawGPUIndexedPrimitives(pass2, sprite_batch_->overlayIndexCount(), 1, sprite_batch_->overlayIndexOffset(), 0, 0); + } - SDL_EndGPURenderPass(pass2); + SDL_EndGPURenderPass(pass2); } // end native PostFX } // end if (swapchain && ...) - gpu_ctx_->submit(cmd); + GpuContext::submit(cmd); } void Engine::showNotificationForAction(const std::string& text) { @@ -975,7 +1023,9 @@ void Engine::toggleVSync() { ui_manager_->updateVSyncText(vsync_enabled_); // Aplicar el cambio de V-Sync al contexto GPU - if (gpu_ctx_) gpu_ctx_->setVSync(vsync_enabled_); + if (gpu_ctx_) { + gpu_ctx_->setVSync(vsync_enabled_); + } } void Engine::toggleFullscreen() { @@ -989,7 +1039,7 @@ void Engine::toggleFullscreen() { // Si acabamos de salir de fullscreen, restaurar tamaño de ventana if (!fullscreen_enabled_) { - int restore_w = static_cast(std::round(base_screen_width_ * current_window_scale_)); + int restore_w = static_cast(std::round(base_screen_width_ * current_window_scale_)); int restore_h = static_cast(std::round(base_screen_height_ * current_window_scale_)); SDL_SetWindowSize(window_, restore_w, restore_h); SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); @@ -1046,7 +1096,7 @@ void Engine::toggleRealFullscreen() { // Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones if (current_mode_ == SimulationMode::SHAPE) { - generateShape(); // Regenerar figura con nuevas dimensiones de pantalla + generateShape(); // Regenerar figura con nuevas dimensiones de pantalla scene_manager_->enableShapeAttractionAll(true); // Crítico tras changeScenario } } @@ -1060,7 +1110,7 @@ void Engine::toggleRealFullscreen() { // Restaurar ventana normal con la escala actual SDL_SetWindowFullscreen(window_, false); - int restore_w = static_cast(std::round(base_screen_width_ * current_window_scale_)); + int restore_w = static_cast(std::round(base_screen_width_ * current_window_scale_)); int restore_h = static_cast(std::round(base_screen_height_ * current_window_scale_)); SDL_SetWindowSize(window_, restore_w, restore_h); SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); @@ -1085,25 +1135,29 @@ void Engine::toggleRealFullscreen() { // Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones if (current_mode_ == SimulationMode::SHAPE) { - generateShape(); // Regenerar figura con nuevas dimensiones de pantalla + generateShape(); // Regenerar figura con nuevas dimensiones de pantalla scene_manager_->enableShapeAttractionAll(true); // Crítico tras changeScenario } } } void Engine::applyPostFXPreset(int mode) { - static constexpr float presets[4][3] = { + static constexpr std::array, 4> PRESETS = {{ {0.8f, 0.0f, 0.0f}, // 0: Vinyeta {0.8f, 0.0f, 0.8f}, // 1: Scanlines {0.8f, 0.2f, 0.0f}, // 2: Cromàtica {0.8f, 0.2f, 0.8f}, // 3: Complet - }; - postfx_uniforms_.vignette_strength = presets[mode][0]; - postfx_uniforms_.chroma_strength = presets[mode][1]; - postfx_uniforms_.scanline_strength = presets[mode][2]; + }}; + postfx_uniforms_.vignette_strength = PRESETS[mode][0]; + postfx_uniforms_.chroma_strength = PRESETS[mode][1]; + postfx_uniforms_.scanline_strength = PRESETS[mode][2]; // Reaplicar overrides de CLI si están activos - if (postfx_override_vignette_ >= 0.f) postfx_uniforms_.vignette_strength = postfx_override_vignette_; - if (postfx_override_chroma_ >= 0.f) postfx_uniforms_.chroma_strength = postfx_override_chroma_; + if (postfx_override_vignette_ >= 0.f) { + postfx_uniforms_.vignette_strength = postfx_override_vignette_; + } + if (postfx_override_chroma_ >= 0.f) { + postfx_uniforms_.chroma_strength = postfx_override_chroma_; + } } void Engine::handlePostFXCycle() { @@ -1111,17 +1165,18 @@ void Engine::handlePostFXCycle() { } void Engine::handlePostFXToggle() { - static constexpr const char* names[4] = { - "PostFX viñeta", "PostFX scanlines", - "PostFX cromática", "PostFX completo" - }; + static constexpr std::array NAMES = { + "PostFX viñeta", + "PostFX scanlines", + "PostFX cromática", + "PostFX completo"}; postfx_enabled_ = !postfx_enabled_; if (postfx_enabled_) { applyPostFXPreset(postfx_effect_mode_); - showNotificationForAction(names[postfx_effect_mode_]); + showNotificationForAction(NAMES[postfx_effect_mode_]); } else { postfx_uniforms_.vignette_strength = 0.0f; - postfx_uniforms_.chroma_strength = 0.0f; + postfx_uniforms_.chroma_strength = 0.0f; postfx_uniforms_.scanline_strength = 0.0f; showNotificationForAction("PostFX desactivado"); } @@ -1135,26 +1190,33 @@ void Engine::setInitialPostFX(int mode) { void Engine::setPostFXParamOverrides(float vignette, float chroma) { postfx_override_vignette_ = vignette; - postfx_override_chroma_ = chroma; + postfx_override_chroma_ = chroma; postfx_enabled_ = true; // Aplicar inmediatamente sobre el preset activo - if (vignette >= 0.f) postfx_uniforms_.vignette_strength = vignette; - if (chroma >= 0.f) postfx_uniforms_.chroma_strength = chroma; + if (vignette >= 0.f) { + postfx_uniforms_.vignette_strength = vignette; + } + if (chroma >= 0.f) { + postfx_uniforms_.chroma_strength = chroma; + } } void Engine::cycleShader() { // X no hace nada si PostFX está desactivado - if (!postfx_enabled_) return; + if (!postfx_enabled_) { + return; + } // Cicla solo entre los 4 modos (sin OFF) postfx_effect_mode_ = (postfx_effect_mode_ + 1) % 4; applyPostFXPreset(postfx_effect_mode_); - static constexpr const char* names[4] = { - "PostFX viñeta", "PostFX scanlines", - "PostFX cromática", "PostFX completo" - }; - showNotificationForAction(names[postfx_effect_mode_]); + static constexpr std::array NAMES = { + "PostFX viñeta", + "PostFX scanlines", + "PostFX cromática", + "PostFX completo"}; + showNotificationForAction(NAMES[postfx_effect_mode_]); } void Engine::toggleIntegerScaling() { @@ -1171,51 +1233,58 @@ void Engine::toggleIntegerScaling() { break; } - const char* mode_name = "entero"; + const char* mode_name = nullptr; switch (current_scaling_mode_) { - case ScalingMode::INTEGER: mode_name = "entero"; break; - case ScalingMode::LETTERBOX: mode_name = "letterbox"; break; - case ScalingMode::STRETCH: mode_name = "stretch"; break; + case ScalingMode::INTEGER: + mode_name = "entero"; + break; + case ScalingMode::LETTERBOX: + mode_name = "letterbox"; + break; + case ScalingMode::STRETCH: + mode_name = "stretch"; + break; } showNotificationForAction(std::string("Escalado ") + mode_name); } void Engine::addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b, float scale) { - if (!sprite_batch_) return; - sprite_batch_->addSprite(x, y, w, h, - r / 255.0f, g / 255.0f, b / 255.0f, 1.0f, - scale, - static_cast(current_screen_width_), - static_cast(current_screen_height_)); + if (!sprite_batch_) { + return; + } + sprite_batch_->addSprite(x, y, w, h, r / 255.0f, g / 255.0f, b / 255.0f, 1.0f, scale, static_cast(current_screen_width_), static_cast(current_screen_height_)); } // Sistema de escala de ventana (pasos del 10%) -float Engine::calculateMaxWindowScale() const { +auto Engine::calculateMaxWindowScale() const -> float { SDL_Rect bounds; if (!SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &bounds)) { // bool: false = error - return WINDOW_SCALE_MIN; // Fallback solo si falla de verdad + return WINDOW_SCALE_MIN; // Fallback solo si falla de verdad } - float max_by_w = static_cast(bounds.w - 2 * WINDOW_DESKTOP_MARGIN) / base_screen_width_; - float max_by_h = static_cast(bounds.h - 2 * WINDOW_DESKTOP_MARGIN - WINDOW_DECORATION_HEIGHT) / base_screen_height_; + float max_by_w = static_cast(bounds.w - (2 * WINDOW_DESKTOP_MARGIN)) / base_screen_width_; + float max_by_h = static_cast(bounds.h - (2 * WINDOW_DESKTOP_MARGIN) - WINDOW_DECORATION_HEIGHT) / base_screen_height_; float result = std::max(WINDOW_SCALE_MIN, std::min(max_by_w, max_by_h)); return result; } // Redimensiona la ventana física manteniéndo su centro, con clamping a pantalla. static void resizeWindowCentered(SDL_Window* window, int new_w, int new_h) { - int cur_x, cur_y, cur_w, cur_h; + int cur_x; + int cur_y; + int cur_w; + int cur_h; SDL_GetWindowPosition(window, &cur_x, &cur_y); SDL_GetWindowSize(window, &cur_w, &cur_h); - int new_x = cur_x + (cur_w - new_w) / 2; - int new_y = cur_y + (cur_h - new_h) / 2; + int new_x = cur_x + ((cur_w - new_w) / 2); + int new_y = cur_y + ((cur_h - new_h) / 2); SDL_Rect bounds; if (SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &bounds)) { new_x = std::max(WINDOW_DESKTOP_MARGIN, - std::min(new_x, bounds.w - new_w - WINDOW_DESKTOP_MARGIN)); + std::min(new_x, bounds.w - new_w - WINDOW_DESKTOP_MARGIN)); new_y = std::max(WINDOW_DESKTOP_MARGIN, - std::min(new_y, bounds.h - new_h - WINDOW_DESKTOP_MARGIN - WINDOW_DECORATION_HEIGHT)); + std::min(new_y, bounds.h - new_h - WINDOW_DESKTOP_MARGIN - WINDOW_DECORATION_HEIGHT)); } SDL_SetWindowSize(window, new_w, new_h); @@ -1227,9 +1296,11 @@ void Engine::setWindowScale(float new_scale) { new_scale = std::max(WINDOW_SCALE_MIN, std::min(new_scale, max_scale)); new_scale = std::round(new_scale * 10.0f) / 10.0f; - if (new_scale == current_window_scale_) return; + if (new_scale == current_window_scale_) { + return; + } - int new_width = static_cast(std::round(current_screen_width_ * new_scale)); + int new_width = static_cast(std::round(current_screen_width_ * new_scale)); int new_height = static_cast(std::round(current_screen_height_ * new_scale)); resizeWindowCentered(window_, new_width, new_height); @@ -1242,9 +1313,9 @@ void Engine::zoomIn() { float prev = current_window_scale_; setWindowScale(current_window_scale_ + WINDOW_SCALE_STEP); if (current_window_scale_ != prev) { - char buf[32]; - std::snprintf(buf, sizeof(buf), "Zoom %.0f%%", current_window_scale_ * 100.0f); - showNotificationForAction(buf); + std::array buf{}; + std::snprintf(buf.data(), buf.size(), "Zoom %.0f%%", current_window_scale_ * 100.0f); + showNotificationForAction(buf.data()); } } @@ -1252,9 +1323,9 @@ void Engine::zoomOut() { float prev = current_window_scale_; setWindowScale(current_window_scale_ - WINDOW_SCALE_STEP); if (current_window_scale_ != prev) { - char buf[32]; - std::snprintf(buf, sizeof(buf), "Zoom %.0f%%", current_window_scale_ * 100.0f); - showNotificationForAction(buf); + std::array buf{}; + std::snprintf(buf.data(), buf.size(), "Zoom %.0f%%", current_window_scale_ * 100.0f); + showNotificationForAction(buf.data()); } } @@ -1262,14 +1333,16 @@ void Engine::setFieldScale(float new_scale) { float max_scale = calculateMaxWindowScale(); new_scale = std::max(WINDOW_SCALE_MIN, std::min(new_scale, max_scale)); new_scale = std::round(new_scale * 10.0f) / 10.0f; - if (new_scale == current_field_scale_) return; + if (new_scale == current_field_scale_) { + return; + } current_field_scale_ = new_scale; - current_screen_width_ = static_cast(std::round(base_screen_width_ * new_scale)); + current_screen_width_ = static_cast(std::round(base_screen_width_ * new_scale)); current_screen_height_ = static_cast(std::round(base_screen_height_ * new_scale)); // Ajustar ventana física: campo lógico × zoom actual, manteniendo centro - int phys_w = static_cast(std::round(current_screen_width_ * current_window_scale_)); + int phys_w = static_cast(std::round(current_screen_width_ * current_window_scale_)); int phys_h = static_cast(std::round(current_screen_height_ * current_window_scale_)); resizeWindowCentered(window_, phys_w, phys_h); @@ -1282,17 +1355,19 @@ void Engine::setFieldScale(float new_scale) { scene_manager_->changeScenario(scene_manager_->getCurrentScenario(), current_mode_); boid_manager_->updateScreenSize(current_screen_width_, current_screen_height_); shape_manager_->updateScreenSize(current_screen_width_, current_screen_height_); - if (app_logo_) app_logo_->updateScreenSize(current_screen_width_, current_screen_height_); + if (app_logo_) { + app_logo_->updateScreenSize(current_screen_width_, current_screen_height_); + } if (current_mode_ == SimulationMode::SHAPE) { generateShape(); scene_manager_->enableShapeAttractionAll(true); } showNotificationForAction("Campo " + std::to_string(current_screen_width_) + - " x " + std::to_string(current_screen_height_)); + " x " + std::to_string(current_screen_height_)); } -void Engine::fieldSizeUp() { setFieldScale(current_field_scale_ + WINDOW_SCALE_STEP); } +void Engine::fieldSizeUp() { setFieldScale(current_field_scale_ + WINDOW_SCALE_STEP); } void Engine::fieldSizeDown() { setFieldScale(current_field_scale_ - WINDOW_SCALE_STEP); } void Engine::updatePhysicalWindowSize() { @@ -1314,7 +1389,8 @@ void Engine::updatePhysicalWindowSize() { } } else { // En modo ventana, obtener tamaño FÍSICO real del framebuffer - int window_w = 0, window_h = 0; + int window_w = 0; + int window_h = 0; SDL_GetWindowSizeInPixels(window_, &window_w, &window_h); physical_window_width_ = window_w; physical_window_height_ = window_h; @@ -1323,9 +1399,7 @@ void Engine::updatePhysicalWindowSize() { // Notificar a UIManager del cambio de tamaño (delegado) // Pasar current_screen_height_ para que UIManager actualice la altura lógica // (necesario en F4 donde la resolución lógica cambia a la del display) - ui_manager_->updatePhysicalWindowSize(physical_window_width_, physical_window_height_, - current_screen_height_); - + ui_manager_->updatePhysicalWindowSize(physical_window_width_, physical_window_height_, current_screen_height_); } // ============================================================================ @@ -1345,7 +1419,9 @@ void Engine::switchTextureSilent() { } void Engine::setTextureByIndex(size_t index) { - if (index >= textures_.size()) return; + if (index >= textures_.size()) { + return; + } current_texture_index_ = index; texture_ = textures_[current_texture_index_]; int new_size = texture_->getWidth(); @@ -1356,7 +1432,9 @@ void Engine::setTextureByIndex(size_t index) { // Toggle manual del Modo Logo (tecla K) // Sistema de cambio de sprites dinámico void Engine::switchTextureInternal(bool show_notification) { - if (textures_.empty()) return; + if (textures_.empty()) { + return; + } // Cambiar a siguiente textura (ciclar) current_texture_index_ = (current_texture_index_ + 1) % textures_.size(); @@ -1372,7 +1450,7 @@ void Engine::switchTextureInternal(bool show_notification) { // Mostrar notificación con el nombre de la textura (solo si se solicita) if (show_notification) { std::string texture_name = texture_names_[current_texture_index_]; - std::transform(texture_name.begin(), texture_name.end(), texture_name.begin(), ::tolower); + std::ranges::transform(texture_name, texture_name.begin(), ::tolower); showNotificationForAction("Textura " + texture_name); } } @@ -1407,9 +1485,11 @@ void Engine::runPerformanceBenchmark() { int num_displays = 0; SDL_DisplayID* displays = SDL_GetDisplays(&num_displays); float monitor_hz = 60.0f; - if (displays && num_displays > 0) { + if ((displays != nullptr) && num_displays > 0) { const auto* dm = SDL_GetCurrentDisplayMode(displays[0]); - if (dm && dm->refresh_rate > 0) monitor_hz = dm->refresh_rate; + if ((dm != nullptr) && dm->refresh_rate > 0) { + monitor_hz = dm->refresh_rate; + } SDL_free(displays); } @@ -1493,59 +1573,70 @@ void Engine::runPerformanceBenchmark() { // GPU HELPERS // ============================================================================ -bool Engine::loadGpuSpriteTexture(size_t index) { - if (!gpu_ctx_ || index >= gpu_textures_.size()) return false; +auto Engine::loadGpuSpriteTexture(size_t index) -> bool { + if (!gpu_ctx_ || index >= gpu_textures_.size()) { + return false; + } return gpu_textures_[index] && gpu_textures_[index]->isValid(); } void Engine::recreateOffscreenTexture() { - if (!gpu_ctx_ || !offscreen_tex_) return; + if (!gpu_ctx_ || !offscreen_tex_) { + return; + } SDL_WaitForGPUIdle(gpu_ctx_->device()); offscreen_tex_->destroy(gpu_ctx_->device()); offscreen_tex_->createRenderTarget(gpu_ctx_->device(), - current_screen_width_, current_screen_height_, - SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM); + current_screen_width_, + current_screen_height_, + SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM); // Recreate UI texture to match new screen size if (ui_tex_) { ui_tex_->destroy(gpu_ctx_->device()); ui_tex_->createRenderTarget(gpu_ctx_->device(), - current_screen_width_, current_screen_height_, - SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM); + current_screen_width_, + current_screen_height_, + SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM); } // Recreate renderer de software (DESTRUIR renderer PRIMER, després surface) - if (ui_renderer_) { SDL_DestroyRenderer(ui_renderer_); ui_renderer_ = nullptr; } - if (ui_surface_) { SDL_DestroySurface(ui_surface_); ui_surface_ = nullptr; } + if (ui_renderer_ != nullptr) { + SDL_DestroyRenderer(ui_renderer_); + ui_renderer_ = nullptr; + } + if (ui_surface_ != nullptr) { + SDL_DestroySurface(ui_surface_); + ui_surface_ = nullptr; + } ui_surface_ = SDL_CreateSurface(current_screen_width_, current_screen_height_, SDL_PIXELFORMAT_RGBA32); - if (ui_surface_) { + if (ui_surface_ != nullptr) { ui_renderer_ = SDL_CreateSoftwareRenderer(ui_surface_); } // Re-inicialitzar components UI amb nou renderer - if (ui_renderer_ && ui_manager_) { - ui_manager_->initialize(ui_renderer_, theme_manager_.get(), - current_screen_width_, current_screen_height_, // physical - base_screen_width_, base_screen_height_); // logical (font size based on base) + if ((ui_renderer_ != nullptr) && ui_manager_) { + ui_manager_->initialize(ui_renderer_, theme_manager_.get(), current_screen_width_, current_screen_height_, // physical + base_screen_width_, + base_screen_height_); // logical (font size based on base) } - if (ui_renderer_ && app_logo_) { + if ((ui_renderer_ != nullptr) && app_logo_) { app_logo_->initialize(ui_renderer_, current_screen_width_, current_screen_height_); } } void Engine::renderUIToSurface() { - if (!ui_renderer_ || !ui_surface_) return; + if ((ui_renderer_ == nullptr) || (ui_surface_ == nullptr)) { + return; + } // Clear surface (fully transparent) SDL_SetRenderDrawColor(ui_renderer_, 0, 0, 0, 0); SDL_RenderClear(ui_renderer_); // Render UI (HUD, FPS counter, notifications) - ui_manager_->render(ui_renderer_, this, scene_manager_.get(), current_mode_, - state_manager_->getCurrentMode(), - shape_manager_->getActiveShape(), shape_manager_->getConvergence(), - physical_window_width_, physical_window_height_, current_screen_width_); + ui_manager_->render(ui_renderer_, this, scene_manager_.get(), current_mode_, state_manager_->getCurrentMode(), shape_manager_->getActiveShape(), shape_manager_->getConvergence(), physical_window_width_, physical_window_height_, current_screen_width_); // Render periodic logo overlay if (app_logo_) { @@ -1556,21 +1647,25 @@ void Engine::renderUIToSurface() { } void Engine::uploadUISurface(SDL_GPUCommandBuffer* cmd_buf) { - if (!ui_tex_ || !ui_tex_->isValid() || !ui_surface_ || !gpu_ctx_) return; + if (!ui_tex_ || !ui_tex_->isValid() || (ui_surface_ == nullptr) || !gpu_ctx_) { + return; + } int w = ui_surface_->w; int h = ui_surface_->h; - Uint32 data_size = static_cast(w * h * 4); // RGBA = 4 bytes/pixel + auto data_size = static_cast(w * h * 4); // RGBA = 4 bytes/pixel SDL_GPUTransferBufferCreateInfo tb_info = {}; tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; - tb_info.size = data_size; + tb_info.size = data_size; SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(gpu_ctx_->device(), &tb_info); - if (!transfer) return; + if (transfer == nullptr) { + return; + } void* mapped = SDL_MapGPUTransferBuffer(gpu_ctx_->device(), transfer, true); - if (!mapped) { + if (mapped == nullptr) { SDL_ReleaseGPUTransferBuffer(gpu_ctx_->device(), transfer); return; } @@ -1581,14 +1676,14 @@ void Engine::uploadUISurface(SDL_GPUCommandBuffer* cmd_buf) { SDL_GPUTextureTransferInfo src = {}; src.transfer_buffer = transfer; - src.offset = 0; - src.pixels_per_row = static_cast(w); - src.rows_per_layer = static_cast(h); + src.offset = 0; + src.pixels_per_row = static_cast(w); + src.rows_per_layer = static_cast(h); SDL_GPUTextureRegion dst = {}; - dst.texture = ui_tex_->texture(); + dst.texture = ui_tex_->texture(); dst.mip_level = 0; - dst.layer = 0; + dst.layer = 0; dst.x = dst.y = dst.z = 0; dst.w = static_cast(w); dst.h = static_cast(h); diff --git a/source/engine.hpp b/source/engine.hpp index 7a2d89c..34a27c9 100644 --- a/source/engine.hpp +++ b/source/engine.hpp @@ -1,32 +1,32 @@ #pragma once -#include // for SDL_Event -#include // for SDL_Renderer (ui_renderer_ software renderer) -#include // for Uint64 -#include // for SDL_Surface (ui_surface_) -#include // for SDL_Window +#include // for SDL_Event +#include // for SDL_Renderer (ui_renderer_ software renderer) +#include // for Uint64 +#include // for SDL_Surface (ui_surface_) +#include // for SDL_Window #include // for array #include // for unique_ptr, shared_ptr #include // for string #include // for vector -#include "ui/app_logo.hpp" // for AppLogo -#include "ball.hpp" // for Ball -#include "boids_mgr/boid_manager.hpp" // for BoidManager -#include "defines.hpp" // for GravityDirection, ColorTheme, ShapeType -#include "external/texture.hpp" // for Texture -#include "gpu/gpu_ball_buffer.hpp" // for GpuBallBuffer, BallGPUData -#include "gpu/gpu_context.hpp" // for GpuContext -#include "gpu/gpu_pipeline.hpp" // for GpuPipeline -#include "gpu/gpu_sprite_batch.hpp" // for GpuSpriteBatch -#include "gpu/gpu_texture.hpp" // for GpuTexture -#include "input/input_handler.hpp" // for InputHandler -#include "scene/scene_manager.hpp" // for SceneManager -#include "shapes_mgr/shape_manager.hpp" // for ShapeManager -#include "state/state_manager.hpp" // for StateManager -#include "theme_manager.hpp" // for ThemeManager -#include "ui/ui_manager.hpp" // for UIManager +#include "ball.hpp" // for Ball +#include "boids_mgr/boid_manager.hpp" // for BoidManager +#include "defines.hpp" // for GravityDirection, ColorTheme, ShapeType +#include "external/texture.hpp" // for Texture +#include "gpu/gpu_ball_buffer.hpp" // for GpuBallBuffer, BallGPUData +#include "gpu/gpu_context.hpp" // for GpuContext +#include "gpu/gpu_pipeline.hpp" // for GpuPipeline +#include "gpu/gpu_sprite_batch.hpp" // for GpuSpriteBatch +#include "gpu/gpu_texture.hpp" // for GpuTexture +#include "input/input_handler.hpp" // for InputHandler +#include "scene/scene_manager.hpp" // for SceneManager +#include "shapes_mgr/shape_manager.hpp" // for ShapeManager +#include "state/state_manager.hpp" // for StateManager +#include "theme_manager.hpp" // for ThemeManager +#include "ui/app_logo.hpp" // for AppLogo +#include "ui/ui_manager.hpp" // for UIManager class Engine { public: @@ -97,8 +97,8 @@ class Engine { // Escenario custom (tecla 9, --custom-balls) void setCustomScenario(int balls); bool isCustomScenarioEnabled() const { return custom_scenario_enabled_; } - bool isCustomAutoAvailable() const { return custom_auto_available_; } - int getCustomScenarioBalls() const { return custom_scenario_balls_; } + bool isCustomAutoAvailable() const { return custom_auto_available_; } + int getCustomScenarioBalls() const { return custom_scenario_balls_; } // Control manual del benchmark (--skip-benchmark, --max-balls) void setSkipBenchmark(); @@ -113,10 +113,10 @@ class Engine { void toggleLogoMode(); // === Métodos públicos para StateManager (automatización DEMO/LOGO sin notificación) === - void enterShapeMode(ShapeType type); // Activar figura (sin notificación) - void exitShapeMode(bool force_gravity = true); // Volver a física (sin notificación) - void switchTextureSilent(); // Cambiar textura (sin notificación) - void setTextureByIndex(size_t index); // Restaurar textura específica + void enterShapeMode(ShapeType type); // Activar figura (sin notificación) + void exitShapeMode(bool force_gravity = true); // Volver a física (sin notificación) + void switchTextureSilent(); // Cambiar textura (sin notificación) + void setTextureByIndex(size_t index); // Restaurar textura específica // === Getters públicos para UIManager (Debug HUD) === bool getVSyncEnabled() const { return vsync_enabled_; } @@ -133,11 +133,11 @@ class Engine { int getBaseScreenHeight() const { return base_screen_height_; } int getMaxAutoScenario() const { return max_auto_scenario_; } size_t getCurrentTextureIndex() const { return current_texture_index_; } - bool isPostFXEnabled() const { return postfx_enabled_; } - int getPostFXMode() const { return postfx_effect_mode_; } - float getPostFXVignette() const { return postfx_uniforms_.vignette_strength; } - float getPostFXChroma() const { return postfx_uniforms_.chroma_strength; } - float getPostFXScanline() const { return postfx_uniforms_.scanline_strength; } + bool isPostFXEnabled() const { return postfx_enabled_; } + int getPostFXMode() const { return postfx_effect_mode_; } + float getPostFXVignette() const { return postfx_uniforms_.vignette_strength; } + float getPostFXChroma() const { return postfx_uniforms_.chroma_strength; } + float getPostFXScanline() const { return postfx_uniforms_.scanline_strength; } private: // === Componentes del sistema (Composición) === @@ -153,23 +153,23 @@ class Engine { SDL_Window* window_ = nullptr; // === SDL_GPU rendering pipeline === - std::unique_ptr gpu_ctx_; // Device + swapchain - std::unique_ptr gpu_pipeline_; // Sprite + ball + postfx pipelines - std::unique_ptr sprite_batch_; // Per-frame vertex/index batch (bg + shape + UI) - std::unique_ptr gpu_ball_buffer_; // Instanced ball instance data (PHYSICS/BOIDS) - std::vector ball_gpu_data_; // CPU-side staging vector (reused each frame) - std::unique_ptr offscreen_tex_; // Offscreen render target (Pass 1) - std::unique_ptr white_tex_; // 1×1 white (background gradient) - std::unique_ptr ui_tex_; // UI text overlay texture + std::unique_ptr gpu_ctx_; // Device + swapchain + std::unique_ptr gpu_pipeline_; // Sprite + ball + postfx pipelines + std::unique_ptr sprite_batch_; // Per-frame vertex/index batch (bg + shape + UI) + std::unique_ptr gpu_ball_buffer_; // Instanced ball instance data (PHYSICS/BOIDS) + std::vector ball_gpu_data_; // CPU-side staging vector (reused each frame) + std::unique_ptr offscreen_tex_; // Offscreen render target (Pass 1) + std::unique_ptr white_tex_; // 1×1 white (background gradient) + std::unique_ptr ui_tex_; // UI text overlay texture // GPU sprite textures (one per ball skin, parallel to textures_/texture_names_) - std::unique_ptr gpu_texture_; // Active GPU sprite texture + std::unique_ptr gpu_texture_; // Active GPU sprite texture std::vector> gpu_textures_; // All GPU sprite textures // === SDL_Renderer (software, for UI text via SDL3_ttf) === // Renders to ui_surface_, then uploaded as gpu texture overlay. SDL_Renderer* ui_renderer_ = nullptr; - SDL_Surface* ui_surface_ = nullptr; + SDL_Surface* ui_surface_ = nullptr; // Legacy Texture objects — kept for ball physics sizing and AppLogo std::shared_ptr texture_ = nullptr; // Textura activa actual @@ -190,7 +190,7 @@ class Engine { int postfx_effect_mode_ = 3; bool postfx_enabled_ = false; float postfx_override_vignette_ = -1.f; // -1 = sin override - float postfx_override_chroma_ = -1.f; + float postfx_override_chroma_ = -1.f; // Sistema de escala de ventana float current_window_scale_ = 1.0f; @@ -228,10 +228,10 @@ class Engine { int max_auto_scenario_ = 5; // Escenario custom (--custom-balls) - int custom_scenario_balls_ = 0; + int custom_scenario_balls_ = 0; bool custom_scenario_enabled_ = false; - bool custom_auto_available_ = false; - bool skip_benchmark_ = false; + bool custom_auto_available_ = false; + bool skip_benchmark_ = false; // Bucket sort per z-ordering (SHAPE mode) static constexpr int DEPTH_SORT_BUCKETS = 256; @@ -273,9 +273,8 @@ class Engine { bool isScenarioAllowedForBoids(int scenario_id) const; // GPU helpers - bool loadGpuSpriteTexture(size_t index); // Upload one sprite texture to GPU - void recreateOffscreenTexture(); // Recreate when resolution changes - void renderUIToSurface(); // Render text/UI to ui_surface_ + bool loadGpuSpriteTexture(size_t index); // Upload one sprite texture to GPU + void recreateOffscreenTexture(); // Recreate when resolution changes + void renderUIToSurface(); // Render text/UI to ui_surface_ void uploadUISurface(SDL_GPUCommandBuffer* cmd_buf); // Upload ui_surface_ → ui_tex_ - }; diff --git a/source/gpu/gpu_ball_buffer.cpp b/source/gpu/gpu_ball_buffer.cpp index 4949b92..a6294b9 100644 --- a/source/gpu/gpu_ball_buffer.cpp +++ b/source/gpu/gpu_ball_buffer.cpp @@ -1,18 +1,19 @@ #include "gpu_ball_buffer.hpp" #include + #include // std::min #include // memcpy -bool GpuBallBuffer::init(SDL_GPUDevice* device) { +auto GpuBallBuffer::init(SDL_GPUDevice* device) -> bool { Uint32 buf_size = static_cast(MAX_BALLS) * sizeof(BallGPUData); // GPU vertex buffer (instance-rate data read by the ball instanced shader) SDL_GPUBufferCreateInfo buf_info = {}; buf_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX; - buf_info.size = buf_size; + buf_info.size = buf_size; gpu_buf_ = SDL_CreateGPUBuffer(device, &buf_info); - if (!gpu_buf_) { + if (gpu_buf_ == nullptr) { SDL_Log("GpuBallBuffer: GPU buffer creation failed: %s", SDL_GetError()); return false; } @@ -20,34 +21,45 @@ bool GpuBallBuffer::init(SDL_GPUDevice* device) { // Transfer buffer (upload staging, cycled every frame) SDL_GPUTransferBufferCreateInfo tb_info = {}; tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; - tb_info.size = buf_size; + tb_info.size = buf_size; transfer_buf_ = SDL_CreateGPUTransferBuffer(device, &tb_info); - if (!transfer_buf_) { + if (transfer_buf_ == nullptr) { SDL_Log("GpuBallBuffer: transfer buffer creation failed: %s", SDL_GetError()); return false; } SDL_Log("GpuBallBuffer: initialized (capacity %d balls, %.1f MB VRAM)", - MAX_BALLS, buf_size / (1024.0f * 1024.0f)); + MAX_BALLS, + buf_size / (1024.0f * 1024.0f)); return true; } void GpuBallBuffer::destroy(SDL_GPUDevice* device) { - if (!device) return; - if (transfer_buf_) { SDL_ReleaseGPUTransferBuffer(device, transfer_buf_); transfer_buf_ = nullptr; } - if (gpu_buf_) { SDL_ReleaseGPUBuffer(device, gpu_buf_); gpu_buf_ = nullptr; } + if (device == nullptr) { + return; + } + if (transfer_buf_ != nullptr) { + SDL_ReleaseGPUTransferBuffer(device, transfer_buf_); + transfer_buf_ = nullptr; + } + if (gpu_buf_ != nullptr) { + SDL_ReleaseGPUBuffer(device, gpu_buf_); + gpu_buf_ = nullptr; + } count_ = 0; } -bool GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd, - const BallGPUData* data, int count) { - if (!data || count <= 0) { count_ = 0; return false; } +auto GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd, const BallGPUData* data, int count) -> bool { + if ((data == nullptr) || count <= 0) { + count_ = 0; + return false; + } count = std::min(count, MAX_BALLS); Uint32 upload_size = static_cast(count) * sizeof(BallGPUData); void* ptr = SDL_MapGPUTransferBuffer(device, transfer_buf_, true /* cycle */); - if (!ptr) { + if (ptr == nullptr) { SDL_Log("GpuBallBuffer: transfer buffer map failed: %s", SDL_GetError()); return false; } @@ -55,8 +67,8 @@ bool GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd, SDL_UnmapGPUTransferBuffer(device, transfer_buf_); SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd); - SDL_GPUTransferBufferLocation src = { transfer_buf_, 0 }; - SDL_GPUBufferRegion dst = { gpu_buf_, 0, upload_size }; + SDL_GPUTransferBufferLocation src = {transfer_buf_, 0}; + SDL_GPUBufferRegion dst = {gpu_buf_, 0, upload_size}; SDL_UploadToGPUBuffer(copy, &src, &dst, true /* cycle */); SDL_EndGPUCopyPass(copy); diff --git a/source/gpu/gpu_ball_buffer.hpp b/source/gpu/gpu_ball_buffer.hpp index 07ed78f..b3c3758 100644 --- a/source/gpu/gpu_ball_buffer.hpp +++ b/source/gpu/gpu_ball_buffer.hpp @@ -1,6 +1,7 @@ #pragma once #include + #include // --------------------------------------------------------------------------- @@ -12,9 +13,9 @@ // r,g,b,a: RGBA in [0,1] // --------------------------------------------------------------------------- struct BallGPUData { - float cx, cy; // NDC center - float hw, hh; // NDC half-size (positive) - float r, g, b, a; // RGBA color [0,1] + float cx, cy; // NDC center + float hw, hh; // NDC half-size (positive) + float r, g, b, a; // RGBA color [0,1] }; static_assert(sizeof(BallGPUData) == 32, "BallGPUData must be 32 bytes"); @@ -26,22 +27,21 @@ static_assert(sizeof(BallGPUData) == 32, "BallGPUData must be 32 bytes"); // // Then in render pass: bind buffer, SDL_DrawGPUPrimitives(pass, 6, count, 0, 0) // ============================================================================ class GpuBallBuffer { -public: - static constexpr int MAX_BALLS = 500000; + public: + static constexpr int MAX_BALLS = 500000; - bool init(SDL_GPUDevice* device); - void destroy(SDL_GPUDevice* device); + bool init(SDL_GPUDevice* device); + void destroy(SDL_GPUDevice* device); - // Upload ball array to GPU via an internal copy pass. - // count is clamped to MAX_BALLS. Returns false on error or empty input. - bool upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd, - const BallGPUData* data, int count); + // Upload ball array to GPU via an internal copy pass. + // count is clamped to MAX_BALLS. Returns false on error or empty input. + bool upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd, const BallGPUData* data, int count); - SDL_GPUBuffer* buffer() const { return gpu_buf_; } - int count() const { return count_; } + SDL_GPUBuffer* buffer() const { return gpu_buf_; } + int count() const { return count_; } -private: - SDL_GPUBuffer* gpu_buf_ = nullptr; - SDL_GPUTransferBuffer* transfer_buf_ = nullptr; - int count_ = 0; + private: + SDL_GPUBuffer* gpu_buf_ = nullptr; + SDL_GPUTransferBuffer* transfer_buf_ = nullptr; + int count_ = 0; }; diff --git a/source/gpu/gpu_context.cpp b/source/gpu/gpu_context.cpp index 1cab7f7..e9a2818 100644 --- a/source/gpu/gpu_context.cpp +++ b/source/gpu/gpu_context.cpp @@ -1,9 +1,10 @@ #include "gpu_context.hpp" #include + #include -bool GpuContext::init(SDL_Window* window) { +auto GpuContext::init(SDL_Window* window) -> bool { window_ = window; // Create GPU device: Metal on Apple, Vulkan elsewhere @@ -13,15 +14,15 @@ bool GpuContext::init(SDL_Window* window) { SDL_GPUShaderFormat preferred = SDL_GPU_SHADERFORMAT_SPIRV; #endif device_ = SDL_CreateGPUDevice(preferred, false, nullptr); - if (!device_) { - std::cerr << "GpuContext: SDL_CreateGPUDevice failed: " << SDL_GetError() << std::endl; + if (device_ == nullptr) { + std::cerr << "GpuContext: SDL_CreateGPUDevice failed: " << SDL_GetError() << '\n'; return false; } - std::cout << "GpuContext: driver = " << SDL_GetGPUDeviceDriver(device_) << std::endl; + std::cout << "GpuContext: driver = " << SDL_GetGPUDeviceDriver(device_) << '\n'; // Claim the window so the GPU device owns its swapchain if (!SDL_ClaimWindowForGPUDevice(device_, window_)) { - std::cerr << "GpuContext: SDL_ClaimWindowForGPUDevice failed: " << SDL_GetError() << std::endl; + std::cerr << "GpuContext: SDL_ClaimWindowForGPUDevice failed: " << SDL_GetError() << '\n'; SDL_DestroyGPUDevice(device_); device_ = nullptr; return false; @@ -29,17 +30,15 @@ bool GpuContext::init(SDL_Window* window) { // Query swapchain format (Metal: typically B8G8R8A8_UNORM or R8G8B8A8_UNORM) swapchain_format_ = SDL_GetGPUSwapchainTextureFormat(device_, window_); - std::cout << "GpuContext: swapchain format = " << static_cast(swapchain_format_) << std::endl; + std::cout << "GpuContext: swapchain format = " << static_cast(swapchain_format_) << '\n'; // Default: VSync ON - SDL_SetGPUSwapchainParameters(device_, window_, - SDL_GPU_SWAPCHAINCOMPOSITION_SDR, - SDL_GPU_PRESENTMODE_VSYNC); + SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_VSYNC); return true; } void GpuContext::destroy() { - if (device_) { + if (device_ != nullptr) { SDL_WaitForGPUIdle(device_); SDL_ReleaseWindowFromGPUDevice(device_, window_); SDL_DestroyGPUDevice(device_); @@ -48,16 +47,17 @@ void GpuContext::destroy() { window_ = nullptr; } -SDL_GPUCommandBuffer* GpuContext::acquireCommandBuffer() { +auto GpuContext::acquireCommandBuffer() -> SDL_GPUCommandBuffer* { SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_); - if (!cmd) { + if (cmd == nullptr) { SDL_Log("GpuContext: SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError()); } return cmd; } -SDL_GPUTexture* GpuContext::acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf, - Uint32* out_w, Uint32* out_h) { +auto GpuContext::acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf, + Uint32* out_w, + Uint32* out_h) -> SDL_GPUTexture* { SDL_GPUTexture* tex = nullptr; if (!SDL_AcquireGPUSwapchainTexture(cmd_buf, window_, &tex, out_w, out_h)) { SDL_Log("GpuContext: SDL_AcquireGPUSwapchainTexture failed: %s", SDL_GetError()); @@ -71,10 +71,8 @@ void GpuContext::submit(SDL_GPUCommandBuffer* cmd_buf) { SDL_SubmitGPUCommandBuffer(cmd_buf); } -bool GpuContext::setVSync(bool enabled) { +auto GpuContext::setVSync(bool enabled) -> bool { SDL_GPUPresentMode mode = enabled ? SDL_GPU_PRESENTMODE_VSYNC : SDL_GPU_PRESENTMODE_IMMEDIATE; - return SDL_SetGPUSwapchainParameters(device_, window_, - SDL_GPU_SWAPCHAINCOMPOSITION_SDR, - mode); + return SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, mode); } diff --git a/source/gpu/gpu_context.hpp b/source/gpu/gpu_context.hpp index d26007e..52e6eea 100644 --- a/source/gpu/gpu_context.hpp +++ b/source/gpu/gpu_context.hpp @@ -8,26 +8,27 @@ // Replaces SDL_Renderer as the main rendering backend. // ============================================================================ class GpuContext { -public: - bool init(SDL_Window* window); - void destroy(); + public: + bool init(SDL_Window* window); + void destroy(); - SDL_GPUDevice* device() const { return device_; } - SDL_Window* window() const { return window_; } - SDL_GPUTextureFormat swapchainFormat() const { return swapchain_format_; } + SDL_GPUDevice* device() const { return device_; } + SDL_Window* window() const { return window_; } + SDL_GPUTextureFormat swapchainFormat() const { return swapchain_format_; } - // Per-frame helpers - SDL_GPUCommandBuffer* acquireCommandBuffer(); - // Returns nullptr if window is minimized (swapchain not available). - SDL_GPUTexture* acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf, - Uint32* out_w, Uint32* out_h); - void submit(SDL_GPUCommandBuffer* cmd_buf); + // Per-frame helpers + SDL_GPUCommandBuffer* acquireCommandBuffer(); + // Returns nullptr if window is minimized (swapchain not available). + SDL_GPUTexture* acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf, + Uint32* out_w, + Uint32* out_h); + static void submit(SDL_GPUCommandBuffer* cmd_buf); - // VSync control (call after init) - bool setVSync(bool enabled); + // VSync control (call after init) + bool setVSync(bool enabled); -private: - SDL_GPUDevice* device_ = nullptr; - SDL_Window* window_ = nullptr; - SDL_GPUTextureFormat swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID; + private: + SDL_GPUDevice* device_ = nullptr; + SDL_Window* window_ = nullptr; + SDL_GPUTextureFormat swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID; }; diff --git a/source/gpu/gpu_pipeline.cpp b/source/gpu/gpu_pipeline.cpp index 753be43..19afff7 100644 --- a/source/gpu/gpu_pipeline.cpp +++ b/source/gpu/gpu_pipeline.cpp @@ -1,18 +1,21 @@ #include "gpu_pipeline.hpp" -#include "gpu_sprite_batch.hpp" // for GpuVertex layout -#include "gpu_ball_buffer.hpp" // for BallGPUData layout #include + +#include // for std::array #include // offsetof #include // strlen +#include "gpu_ball_buffer.hpp" // for BallGPUData layout +#include "gpu_sprite_batch.hpp" // for GpuVertex layout + #ifndef __APPLE__ // Generated at build time by CMake + glslc (see cmake/spv_to_header.cmake) -#include "sprite_vert_spv.h" -#include "sprite_frag_spv.h" -#include "postfx_vert_spv.h" -#include "postfx_frag_spv.h" #include "ball_vert_spv.h" +#include "postfx_frag_spv.h" +#include "postfx_vert_spv.h" +#include "sprite_frag_spv.h" +#include "sprite_vert_spv.h" #endif #ifdef __APPLE__ @@ -198,15 +201,15 @@ vertex BallVOut ball_instanced_vs(BallInstance inst [[stage_in]], return out; } )"; -#endif // __APPLE__ +#endif // __APPLE__ // ============================================================================ // GpuPipeline implementation // ============================================================================ -bool GpuPipeline::init(SDL_GPUDevice* device, - SDL_GPUTextureFormat target_format, - SDL_GPUTextureFormat offscreen_format) { +auto GpuPipeline::init(SDL_GPUDevice* device, + SDL_GPUTextureFormat target_format, + SDL_GPUTextureFormat offscreen_format) -> bool { SDL_GPUShaderFormat supported = SDL_GetGPUShaderFormats(device); #ifdef __APPLE__ if (!(supported & SDL_GPU_SHADERFORMAT_MSL)) { @@ -214,7 +217,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device, return false; } #else - if (!(supported & SDL_GPU_SHADERFORMAT_SPIRV)) { + if ((supported & SDL_GPU_SHADERFORMAT_SPIRV) == 0u) { SDL_Log("GpuPipeline: SPIRV not supported (format mask=%u)", supported); return false; } @@ -224,81 +227,81 @@ bool GpuPipeline::init(SDL_GPUDevice* device, // Sprite pipeline // ---------------------------------------------------------------- #ifdef __APPLE__ - SDL_GPUShader* sprite_vert = createShader(device, kSpriteVertMSL, "sprite_vs", - SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* sprite_frag = createShader(device, kSpriteFragMSL, "sprite_fs", - SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); + SDL_GPUShader* sprite_vert = createShader(device, kSpriteVertMSL, "sprite_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* sprite_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); #else - SDL_GPUShader* sprite_vert = createShaderSPIRV(device, ksprite_vert_spv, ksprite_vert_spv_size, - "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* sprite_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size, - "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); + SDL_GPUShader* sprite_vert = createShaderSPIRV(device, ksprite_vert_spv, ksprite_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* sprite_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); #endif - if (!sprite_vert || !sprite_frag) { + if ((sprite_vert == nullptr) || (sprite_frag == nullptr)) { SDL_Log("GpuPipeline: failed to create sprite shaders"); - if (sprite_vert) SDL_ReleaseGPUShader(device, sprite_vert); - if (sprite_frag) SDL_ReleaseGPUShader(device, sprite_frag); + if (sprite_vert != nullptr) { + SDL_ReleaseGPUShader(device, sprite_vert); + } + if (sprite_frag != nullptr) { + SDL_ReleaseGPUShader(device, sprite_frag); + } return false; } // Vertex input: GpuVertex layout SDL_GPUVertexBufferDescription vb_desc = {}; - vb_desc.slot = 0; - vb_desc.pitch = sizeof(GpuVertex); - vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX; + vb_desc.slot = 0; + vb_desc.pitch = sizeof(GpuVertex); + vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX; vb_desc.instance_step_rate = 0; - SDL_GPUVertexAttribute attrs[3] = {}; - attrs[0].location = 0; + std::array attrs = {}; + attrs[0].location = 0; attrs[0].buffer_slot = 0; - attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; - attrs[0].offset = static_cast(offsetof(GpuVertex, x)); + attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; + attrs[0].offset = static_cast(offsetof(GpuVertex, x)); - attrs[1].location = 1; + attrs[1].location = 1; attrs[1].buffer_slot = 0; - attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; - attrs[1].offset = static_cast(offsetof(GpuVertex, u)); + attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; + attrs[1].offset = static_cast(offsetof(GpuVertex, u)); - attrs[2].location = 2; + attrs[2].location = 2; attrs[2].buffer_slot = 0; - attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4; - attrs[2].offset = static_cast(offsetof(GpuVertex, r)); + attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4; + attrs[2].offset = static_cast(offsetof(GpuVertex, r)); SDL_GPUVertexInputState vertex_input = {}; vertex_input.vertex_buffer_descriptions = &vb_desc; - vertex_input.num_vertex_buffers = 1; - vertex_input.vertex_attributes = attrs; - vertex_input.num_vertex_attributes = 3; + vertex_input.num_vertex_buffers = 1; + vertex_input.vertex_attributes = attrs.data(); + vertex_input.num_vertex_attributes = 3; // Alpha blend state (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) SDL_GPUColorTargetBlendState blend = {}; - blend.enable_blend = true; - blend.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA; - blend.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; - blend.color_blend_op = SDL_GPU_BLENDOP_ADD; - blend.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE; - blend.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; - blend.alpha_blend_op = SDL_GPU_BLENDOP_ADD; - blend.enable_color_write_mask = false; // write all channels + blend.enable_blend = true; + blend.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA; + blend.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.color_blend_op = SDL_GPU_BLENDOP_ADD; + blend.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE; + blend.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.alpha_blend_op = SDL_GPU_BLENDOP_ADD; + blend.enable_color_write_mask = false; // write all channels SDL_GPUColorTargetDescription color_target_desc = {}; - color_target_desc.format = offscreen_format; + color_target_desc.format = offscreen_format; color_target_desc.blend_state = blend; SDL_GPUGraphicsPipelineCreateInfo sprite_pipe_info = {}; - sprite_pipe_info.vertex_shader = sprite_vert; - sprite_pipe_info.fragment_shader = sprite_frag; + sprite_pipe_info.vertex_shader = sprite_vert; + sprite_pipe_info.fragment_shader = sprite_frag; sprite_pipe_info.vertex_input_state = vertex_input; - sprite_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; - sprite_pipe_info.target_info.num_color_targets = 1; - sprite_pipe_info.target_info.color_target_descriptions = &color_target_desc; + sprite_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; + sprite_pipe_info.target_info.num_color_targets = 1; + sprite_pipe_info.target_info.color_target_descriptions = &color_target_desc; sprite_pipeline_ = SDL_CreateGPUGraphicsPipeline(device, &sprite_pipe_info); SDL_ReleaseGPUShader(device, sprite_vert); SDL_ReleaseGPUShader(device, sprite_frag); - if (!sprite_pipeline_) { + if (sprite_pipeline_ == nullptr) { SDL_Log("GpuPipeline: sprite pipeline creation failed: %s", SDL_GetError()); return false; } @@ -310,59 +313,59 @@ bool GpuPipeline::init(SDL_GPUDevice* device, // Targets: offscreen (same as sprite pipeline) // ---------------------------------------------------------------- #ifdef __APPLE__ - SDL_GPUShader* ball_vert = createShader(device, kBallInstancedVertMSL, "ball_instanced_vs", - SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* ball_frag = createShader(device, kSpriteFragMSL, "sprite_fs", - SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); + SDL_GPUShader* ball_vert = createShader(device, kBallInstancedVertMSL, "ball_instanced_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* ball_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); #else - SDL_GPUShader* ball_vert = createShaderSPIRV(device, kball_vert_spv, kball_vert_spv_size, - "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* ball_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size, - "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); + SDL_GPUShader* ball_vert = createShaderSPIRV(device, kball_vert_spv, kball_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* ball_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); #endif - if (!ball_vert || !ball_frag) { + if ((ball_vert == nullptr) || (ball_frag == nullptr)) { SDL_Log("GpuPipeline: failed to create ball instanced shaders"); - if (ball_vert) SDL_ReleaseGPUShader(device, ball_vert); - if (ball_frag) SDL_ReleaseGPUShader(device, ball_frag); + if (ball_vert != nullptr) { + SDL_ReleaseGPUShader(device, ball_vert); + } + if (ball_frag != nullptr) { + SDL_ReleaseGPUShader(device, ball_frag); + } return false; } // Vertex input: BallGPUData as per-instance data (step rate = 1 instance) SDL_GPUVertexBufferDescription ball_vb_desc = {}; - ball_vb_desc.slot = 0; - ball_vb_desc.pitch = sizeof(BallGPUData); - ball_vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE; + ball_vb_desc.slot = 0; + ball_vb_desc.pitch = sizeof(BallGPUData); + ball_vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE; ball_vb_desc.instance_step_rate = 1; - SDL_GPUVertexAttribute ball_attrs[3] = {}; + std::array ball_attrs = {}; // attr 0: center (float2) at offset 0 - ball_attrs[0].location = 0; + ball_attrs[0].location = 0; ball_attrs[0].buffer_slot = 0; - ball_attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; - ball_attrs[0].offset = static_cast(offsetof(BallGPUData, cx)); + ball_attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; + ball_attrs[0].offset = static_cast(offsetof(BallGPUData, cx)); // attr 1: half-size (float2) at offset 8 - ball_attrs[1].location = 1; + ball_attrs[1].location = 1; ball_attrs[1].buffer_slot = 0; - ball_attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; - ball_attrs[1].offset = static_cast(offsetof(BallGPUData, hw)); + ball_attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; + ball_attrs[1].offset = static_cast(offsetof(BallGPUData, hw)); // attr 2: color (float4) at offset 16 - ball_attrs[2].location = 2; + ball_attrs[2].location = 2; ball_attrs[2].buffer_slot = 0; - ball_attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4; - ball_attrs[2].offset = static_cast(offsetof(BallGPUData, r)); + ball_attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4; + ball_attrs[2].offset = static_cast(offsetof(BallGPUData, r)); SDL_GPUVertexInputState ball_vertex_input = {}; ball_vertex_input.vertex_buffer_descriptions = &ball_vb_desc; - ball_vertex_input.num_vertex_buffers = 1; - ball_vertex_input.vertex_attributes = ball_attrs; - ball_vertex_input.num_vertex_attributes = 3; + ball_vertex_input.num_vertex_buffers = 1; + ball_vertex_input.vertex_attributes = ball_attrs.data(); + ball_vertex_input.num_vertex_attributes = 3; SDL_GPUGraphicsPipelineCreateInfo ball_pipe_info = {}; - ball_pipe_info.vertex_shader = ball_vert; - ball_pipe_info.fragment_shader = ball_frag; + ball_pipe_info.vertex_shader = ball_vert; + ball_pipe_info.fragment_shader = ball_frag; ball_pipe_info.vertex_input_state = ball_vertex_input; - ball_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; - ball_pipe_info.target_info.num_color_targets = 1; + ball_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; + ball_pipe_info.target_info.num_color_targets = 1; ball_pipe_info.target_info.color_target_descriptions = &color_target_desc; ball_pipeline_ = SDL_CreateGPUGraphicsPipeline(device, &ball_pipe_info); @@ -370,7 +373,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device, SDL_ReleaseGPUShader(device, ball_vert); SDL_ReleaseGPUShader(device, ball_frag); - if (!ball_pipeline_) { + if (ball_pipeline_ == nullptr) { SDL_Log("GpuPipeline: ball instanced pipeline creation failed: %s", SDL_GetError()); return false; } @@ -389,20 +392,20 @@ bool GpuPipeline::init(SDL_GPUDevice* device, // PostFX pipeline // ---------------------------------------------------------------- #ifdef __APPLE__ - SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs", - SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs", - SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); + SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); #else - SDL_GPUShader* postfx_vert = createShaderSPIRV(device, kpostfx_vert_spv, kpostfx_vert_spv_size, - "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* postfx_frag = createShaderSPIRV(device, kpostfx_frag_spv, kpostfx_frag_spv_size, - "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); + SDL_GPUShader* postfx_vert = createShaderSPIRV(device, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* postfx_frag = createShaderSPIRV(device, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); #endif - if (!postfx_vert || !postfx_frag) { + if ((postfx_vert == nullptr) || (postfx_frag == nullptr)) { SDL_Log("GpuPipeline: failed to create postfx shaders"); - if (postfx_vert) SDL_ReleaseGPUShader(device, postfx_vert); - if (postfx_frag) SDL_ReleaseGPUShader(device, postfx_frag); + if (postfx_vert != nullptr) { + SDL_ReleaseGPUShader(device, postfx_vert); + } + if (postfx_frag != nullptr) { + SDL_ReleaseGPUShader(device, postfx_frag); + } return false; } @@ -412,17 +415,17 @@ bool GpuPipeline::init(SDL_GPUDevice* device, no_blend.enable_color_write_mask = false; SDL_GPUColorTargetDescription postfx_target_desc = {}; - postfx_target_desc.format = target_format; + postfx_target_desc.format = target_format; postfx_target_desc.blend_state = no_blend; SDL_GPUVertexInputState no_input = {}; SDL_GPUGraphicsPipelineCreateInfo postfx_pipe_info = {}; - postfx_pipe_info.vertex_shader = postfx_vert; - postfx_pipe_info.fragment_shader = postfx_frag; + postfx_pipe_info.vertex_shader = postfx_vert; + postfx_pipe_info.fragment_shader = postfx_frag; postfx_pipe_info.vertex_input_state = no_input; - postfx_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; - postfx_pipe_info.target_info.num_color_targets = 1; + postfx_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; + postfx_pipe_info.target_info.num_color_targets = 1; postfx_pipe_info.target_info.color_target_descriptions = &postfx_target_desc; postfx_pipeline_ = SDL_CreateGPUGraphicsPipeline(device, &postfx_pipe_info); @@ -430,7 +433,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device, SDL_ReleaseGPUShader(device, postfx_vert); SDL_ReleaseGPUShader(device, postfx_frag); - if (!postfx_pipeline_) { + if (postfx_pipeline_ == nullptr) { SDL_Log("GpuPipeline: postfx pipeline creation failed: %s", SDL_GetError()); return false; } @@ -440,55 +443,65 @@ bool GpuPipeline::init(SDL_GPUDevice* device, } void GpuPipeline::destroy(SDL_GPUDevice* device) { - if (sprite_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, sprite_pipeline_); sprite_pipeline_ = nullptr; } - if (ball_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, ball_pipeline_); ball_pipeline_ = nullptr; } - if (postfx_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, postfx_pipeline_); postfx_pipeline_ = nullptr; } + if (sprite_pipeline_ != nullptr) { + SDL_ReleaseGPUGraphicsPipeline(device, sprite_pipeline_); + sprite_pipeline_ = nullptr; + } + if (ball_pipeline_ != nullptr) { + SDL_ReleaseGPUGraphicsPipeline(device, ball_pipeline_); + ball_pipeline_ = nullptr; + } + if (postfx_pipeline_ != nullptr) { + SDL_ReleaseGPUGraphicsPipeline(device, postfx_pipeline_); + postfx_pipeline_ = nullptr; + } } -SDL_GPUShader* GpuPipeline::createShaderSPIRV(SDL_GPUDevice* device, - const uint8_t* spv_code, - size_t spv_size, - const char* entrypoint, - SDL_GPUShaderStage stage, - Uint32 num_samplers, - Uint32 num_uniform_buffers, - Uint32 num_storage_buffers) { +auto GpuPipeline::createShaderSPIRV(SDL_GPUDevice* device, + const uint8_t* spv_code, + size_t spv_size, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers, + Uint32 num_storage_buffers) -> SDL_GPUShader* { SDL_GPUShaderCreateInfo info = {}; - info.code = spv_code; - info.code_size = spv_size; - info.entrypoint = entrypoint; - info.format = SDL_GPU_SHADERFORMAT_SPIRV; - info.stage = stage; - info.num_samplers = num_samplers; + info.code = spv_code; + info.code_size = spv_size; + info.entrypoint = entrypoint; + info.format = SDL_GPU_SHADERFORMAT_SPIRV; + info.stage = stage; + info.num_samplers = num_samplers; info.num_storage_textures = 0; - info.num_storage_buffers = num_storage_buffers; - info.num_uniform_buffers = num_uniform_buffers; + info.num_storage_buffers = num_storage_buffers; + info.num_uniform_buffers = num_uniform_buffers; SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info); - if (!shader) + if (shader == nullptr) { SDL_Log("GpuPipeline: SPIRV shader '%s' failed: %s", entrypoint, SDL_GetError()); + } return shader; } -SDL_GPUShader* GpuPipeline::createShader(SDL_GPUDevice* device, - const char* msl_source, - const char* entrypoint, - SDL_GPUShaderStage stage, - Uint32 num_samplers, - Uint32 num_uniform_buffers, - Uint32 num_storage_buffers) { +auto GpuPipeline::createShader(SDL_GPUDevice* device, + const char* msl_source, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers, + Uint32 num_storage_buffers) -> SDL_GPUShader* { SDL_GPUShaderCreateInfo info = {}; - info.code = reinterpret_cast(msl_source); - info.code_size = static_cast(strlen(msl_source) + 1); - info.entrypoint = entrypoint; - info.format = SDL_GPU_SHADERFORMAT_MSL; - info.stage = stage; - info.num_samplers = num_samplers; + info.code = reinterpret_cast(msl_source); + info.code_size = static_cast(strlen(msl_source) + 1); + info.entrypoint = entrypoint; + info.format = SDL_GPU_SHADERFORMAT_MSL; + info.stage = stage; + info.num_samplers = num_samplers; info.num_storage_textures = 0; - info.num_storage_buffers = num_storage_buffers; - info.num_uniform_buffers = num_uniform_buffers; + info.num_storage_buffers = num_storage_buffers; + info.num_uniform_buffers = num_uniform_buffers; SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info); - if (!shader) { + if (shader == nullptr) { SDL_Log("GpuPipeline: shader '%s' failed: %s", entrypoint, SDL_GetError()); } return shader; diff --git a/source/gpu/gpu_pipeline.hpp b/source/gpu/gpu_pipeline.hpp index 8be09ab..3c27327 100644 --- a/source/gpu/gpu_pipeline.hpp +++ b/source/gpu/gpu_pipeline.hpp @@ -8,10 +8,10 @@ // MSL binding: constant PostFXUniforms& u [[buffer(0)]] // ============================================================================ struct PostFXUniforms { - float vignette_strength; // 0 = none, 0.8 = default subtle - float chroma_strength; // 0 = off, 0.2 = default chromatic aberration - float scanline_strength; // 0 = off, 1 = full scanlines - float screen_height; // logical render target height (px), for resolution-independent scanlines + float vignette_strength; // 0 = none, 0.8 = default subtle + float chroma_strength; // 0 = off, 0.2 = default chromatic aberration + float scanline_strength; // 0 = off, 1 = full scanlines + float screen_height; // logical render target height (px), for resolution-independent scanlines }; // ============================================================================ @@ -27,37 +27,37 @@ struct PostFXUniforms { // Accepts PostFXUniforms via fragment uniform buffer slot 0. // ============================================================================ class GpuPipeline { -public: - // target_format: pass SDL_GetGPUSwapchainTextureFormat() result. - // offscreen_format: format of the offscreen render target. - bool init(SDL_GPUDevice* device, - SDL_GPUTextureFormat target_format, - SDL_GPUTextureFormat offscreen_format); - void destroy(SDL_GPUDevice* device); + public: + // target_format: pass SDL_GetGPUSwapchainTextureFormat() result. + // offscreen_format: format of the offscreen render target. + bool init(SDL_GPUDevice* device, + SDL_GPUTextureFormat target_format, + SDL_GPUTextureFormat offscreen_format); + void destroy(SDL_GPUDevice* device); - SDL_GPUGraphicsPipeline* spritePipeline() const { return sprite_pipeline_; } - SDL_GPUGraphicsPipeline* ballPipeline() const { return ball_pipeline_; } - SDL_GPUGraphicsPipeline* postfxPipeline() const { return postfx_pipeline_; } + SDL_GPUGraphicsPipeline* spritePipeline() const { return sprite_pipeline_; } + SDL_GPUGraphicsPipeline* ballPipeline() const { return ball_pipeline_; } + SDL_GPUGraphicsPipeline* postfxPipeline() const { return postfx_pipeline_; } -private: - SDL_GPUShader* createShader(SDL_GPUDevice* device, - const char* msl_source, - const char* entrypoint, - SDL_GPUShaderStage stage, - Uint32 num_samplers, - Uint32 num_uniform_buffers, - Uint32 num_storage_buffers = 0); + private: + static SDL_GPUShader* createShader(SDL_GPUDevice* device, + const char* msl_source, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers, + Uint32 num_storage_buffers = 0); - SDL_GPUShader* createShaderSPIRV(SDL_GPUDevice* device, - const uint8_t* spv_code, - size_t spv_size, - const char* entrypoint, - SDL_GPUShaderStage stage, - Uint32 num_samplers, - Uint32 num_uniform_buffers, - Uint32 num_storage_buffers = 0); + static SDL_GPUShader* createShaderSPIRV(SDL_GPUDevice* device, + const uint8_t* spv_code, + size_t spv_size, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers, + Uint32 num_storage_buffers = 0); - SDL_GPUGraphicsPipeline* sprite_pipeline_ = nullptr; - SDL_GPUGraphicsPipeline* ball_pipeline_ = nullptr; - SDL_GPUGraphicsPipeline* postfx_pipeline_ = nullptr; + SDL_GPUGraphicsPipeline* sprite_pipeline_ = nullptr; + SDL_GPUGraphicsPipeline* ball_pipeline_ = nullptr; + SDL_GPUGraphicsPipeline* postfx_pipeline_ = nullptr; }; diff --git a/source/gpu/gpu_sprite_batch.cpp b/source/gpu/gpu_sprite_batch.cpp index 17ebe2a..a3f564c 100644 --- a/source/gpu/gpu_sprite_batch.cpp +++ b/source/gpu/gpu_sprite_batch.cpp @@ -1,28 +1,29 @@ #include "gpu_sprite_batch.hpp" #include + #include // memcpy // --------------------------------------------------------------------------- // Public interface // --------------------------------------------------------------------------- -bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) { +auto GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) -> bool { max_sprites_ = max_sprites; // Pre-allocate GPU buffers large enough for (max_sprites_ + 2) quads. // The +2 reserves one slot for the background quad and one for the fullscreen overlay. - Uint32 max_verts = static_cast(max_sprites_ + 2) * 4; + Uint32 max_verts = static_cast(max_sprites_ + 2) * 4; Uint32 max_indices = static_cast(max_sprites_ + 2) * 6; - Uint32 vb_size = max_verts * sizeof(GpuVertex); + Uint32 vb_size = max_verts * sizeof(GpuVertex); Uint32 ib_size = max_indices * sizeof(uint32_t); // Vertex buffer SDL_GPUBufferCreateInfo vb_info = {}; vb_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX; - vb_info.size = vb_size; + vb_info.size = vb_size; vertex_buf_ = SDL_CreateGPUBuffer(device, &vb_info); - if (!vertex_buf_) { + if (vertex_buf_ == nullptr) { SDL_Log("GpuSpriteBatch: vertex buffer creation failed: %s", SDL_GetError()); return false; } @@ -30,9 +31,9 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) { // Index buffer SDL_GPUBufferCreateInfo ib_info = {}; ib_info.usage = SDL_GPU_BUFFERUSAGE_INDEX; - ib_info.size = ib_size; + ib_info.size = ib_size; index_buf_ = SDL_CreateGPUBuffer(device, &ib_info); - if (!index_buf_) { + if (index_buf_ == nullptr) { SDL_Log("GpuSpriteBatch: index buffer creation failed: %s", SDL_GetError()); return false; } @@ -41,16 +42,16 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) { SDL_GPUTransferBufferCreateInfo tb_info = {}; tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; - tb_info.size = vb_size; + tb_info.size = vb_size; vertex_transfer_ = SDL_CreateGPUTransferBuffer(device, &tb_info); - if (!vertex_transfer_) { + if (vertex_transfer_ == nullptr) { SDL_Log("GpuSpriteBatch: vertex transfer buffer failed: %s", SDL_GetError()); return false; } - tb_info.size = ib_size; + tb_info.size = ib_size; index_transfer_ = SDL_CreateGPUTransferBuffer(device, &tb_info); - if (!index_transfer_) { + if (index_transfer_ == nullptr) { SDL_Log("GpuSpriteBatch: index transfer buffer failed: %s", SDL_GetError()); return false; } @@ -61,67 +62,84 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) { } void GpuSpriteBatch::destroy(SDL_GPUDevice* device) { - if (!device) return; - if (vertex_transfer_) { SDL_ReleaseGPUTransferBuffer(device, vertex_transfer_); vertex_transfer_ = nullptr; } - if (index_transfer_) { SDL_ReleaseGPUTransferBuffer(device, index_transfer_); index_transfer_ = nullptr; } - if (vertex_buf_) { SDL_ReleaseGPUBuffer(device, vertex_buf_); vertex_buf_ = nullptr; } - if (index_buf_) { SDL_ReleaseGPUBuffer(device, index_buf_); index_buf_ = nullptr; } + if (device == nullptr) { + return; + } + if (vertex_transfer_ != nullptr) { + SDL_ReleaseGPUTransferBuffer(device, vertex_transfer_); + vertex_transfer_ = nullptr; + } + if (index_transfer_ != nullptr) { + SDL_ReleaseGPUTransferBuffer(device, index_transfer_); + index_transfer_ = nullptr; + } + if (vertex_buf_ != nullptr) { + SDL_ReleaseGPUBuffer(device, vertex_buf_); + vertex_buf_ = nullptr; + } + if (index_buf_ != nullptr) { + SDL_ReleaseGPUBuffer(device, index_buf_); + index_buf_ = nullptr; + } } void GpuSpriteBatch::beginFrame() { vertices_.clear(); indices_.clear(); - bg_index_count_ = 0; - sprite_index_offset_ = 0; - sprite_index_count_ = 0; + bg_index_count_ = 0; + sprite_index_offset_ = 0; + sprite_index_count_ = 0; overlay_index_offset_ = 0; - overlay_index_count_ = 0; + overlay_index_count_ = 0; } -void GpuSpriteBatch::addBackground(float screen_w, float screen_h, - float top_r, float top_g, float top_b, - float bot_r, float bot_g, float bot_b) { +void GpuSpriteBatch::addBackground(float screen_w, float screen_h, float top_r, float top_g, float top_b, float bot_r, float bot_g, float bot_b) { // Background is the full screen quad, corners: // TL(-1, 1) TR(1, 1) → top color // BL(-1,-1) BR(1,-1) → bottom color // We push it as 4 separate vertices (different colors per row). - uint32_t vi = static_cast(vertices_.size()); + auto vi = static_cast(vertices_.size()); // Top-left - vertices_.push_back({ -1.0f, 1.0f, 0.0f, 0.0f, top_r, top_g, top_b, 1.0f }); + vertices_.push_back({-1.0f, 1.0f, 0.0f, 0.0f, top_r, top_g, top_b, 1.0f}); // Top-right - vertices_.push_back({ 1.0f, 1.0f, 1.0f, 0.0f, top_r, top_g, top_b, 1.0f }); + vertices_.push_back({1.0f, 1.0f, 1.0f, 0.0f, top_r, top_g, top_b, 1.0f}); // Bottom-right - vertices_.push_back({ 1.0f, -1.0f, 1.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f }); + vertices_.push_back({1.0f, -1.0f, 1.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f}); // Bottom-left - vertices_.push_back({ -1.0f, -1.0f, 0.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f }); + vertices_.push_back({-1.0f, -1.0f, 0.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f}); // Two triangles: TL-TR-BR, BR-BL-TL - indices_.push_back(vi + 0); indices_.push_back(vi + 1); indices_.push_back(vi + 2); - indices_.push_back(vi + 2); indices_.push_back(vi + 3); indices_.push_back(vi + 0); + indices_.push_back(vi + 0); + indices_.push_back(vi + 1); + indices_.push_back(vi + 2); + indices_.push_back(vi + 2); + indices_.push_back(vi + 3); + indices_.push_back(vi + 0); - bg_index_count_ = 6; + bg_index_count_ = 6; sprite_index_offset_ = 6; - (void)screen_w; (void)screen_h; // unused — bg always covers full NDC + (void)screen_w; + (void)screen_h; // unused — bg always covers full NDC } -void GpuSpriteBatch::addSprite(float x, float y, float w, float h, - float r, float g, float b, float a, - float scale, - float screen_w, float screen_h) { +void GpuSpriteBatch::addSprite(float x, float y, float w, float h, float r, float g, float b, float a, float scale, float screen_w, float screen_h) { // Apply scale around the sprite centre - float scaled_w = w * scale; - float scaled_h = h * scale; - float offset_x = (w - scaled_w) * 0.5f; - float offset_y = (h - scaled_h) * 0.5f; + float scaled_w = w * scale; + float scaled_h = h * scale; + float offset_x = (w - scaled_w) * 0.5f; + float offset_y = (h - scaled_h) * 0.5f; float px0 = x + offset_x; float py0 = y + offset_y; float px1 = px0 + scaled_w; float py1 = py0 + scaled_h; - float ndx0, ndy0, ndx1, ndy1; + float ndx0; + float ndy0; + float ndx1; + float ndy1; toNDC(px0, py0, screen_w, screen_h, ndx0, ndy0); toNDC(px1, py1, screen_w, screen_h, ndx1, ndy1); @@ -133,42 +151,54 @@ void GpuSpriteBatch::addFullscreenOverlay() { // El overlay es un slot reservado fuera del espacio de max_sprites_, igual que el background. // Escribe directamente sin pasar por el guard de pushQuad(). overlay_index_offset_ = static_cast(indices_.size()); - uint32_t vi = static_cast(vertices_.size()); - vertices_.push_back({ -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f }); - vertices_.push_back({ 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f }); - vertices_.push_back({ 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }); - vertices_.push_back({ -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }); - indices_.push_back(vi + 0); indices_.push_back(vi + 1); indices_.push_back(vi + 2); - indices_.push_back(vi + 2); indices_.push_back(vi + 3); indices_.push_back(vi + 0); + auto vi = static_cast(vertices_.size()); + vertices_.push_back({-1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f}); + vertices_.push_back({1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f}); + vertices_.push_back({1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}); + vertices_.push_back({-1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}); + indices_.push_back(vi + 0); + indices_.push_back(vi + 1); + indices_.push_back(vi + 2); + indices_.push_back(vi + 2); + indices_.push_back(vi + 3); + indices_.push_back(vi + 0); overlay_index_count_ = 6; } -bool GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf) { - if (vertices_.empty()) return false; +auto GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf) -> bool { + if (vertices_.empty()) { + return false; + } - Uint32 vb_size = static_cast(vertices_.size() * sizeof(GpuVertex)); - Uint32 ib_size = static_cast(indices_.size() * sizeof(uint32_t)); + auto vb_size = static_cast(vertices_.size() * sizeof(GpuVertex)); + auto ib_size = static_cast(indices_.size() * sizeof(uint32_t)); // Map → write → unmap transfer buffers void* vp = SDL_MapGPUTransferBuffer(device, vertex_transfer_, true /* cycle */); - if (!vp) { SDL_Log("GpuSpriteBatch: vertex map failed"); return false; } + if (vp == nullptr) { + SDL_Log("GpuSpriteBatch: vertex map failed"); + return false; + } memcpy(vp, vertices_.data(), vb_size); SDL_UnmapGPUTransferBuffer(device, vertex_transfer_); void* ip = SDL_MapGPUTransferBuffer(device, index_transfer_, true /* cycle */); - if (!ip) { SDL_Log("GpuSpriteBatch: index map failed"); return false; } + if (ip == nullptr) { + SDL_Log("GpuSpriteBatch: index map failed"); + return false; + } memcpy(ip, indices_.data(), ib_size); SDL_UnmapGPUTransferBuffer(device, index_transfer_); // Upload via copy pass SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd_buf); - SDL_GPUTransferBufferLocation v_src = { vertex_transfer_, 0 }; - SDL_GPUBufferRegion v_dst = { vertex_buf_, 0, vb_size }; + SDL_GPUTransferBufferLocation v_src = {vertex_transfer_, 0}; + SDL_GPUBufferRegion v_dst = {vertex_buf_, 0, vb_size}; SDL_UploadToGPUBuffer(copy, &v_src, &v_dst, true /* cycle */); - SDL_GPUTransferBufferLocation i_src = { index_transfer_, 0 }; - SDL_GPUBufferRegion i_dst = { index_buf_, 0, ib_size }; + SDL_GPUTransferBufferLocation i_src = {index_transfer_, 0}; + SDL_GPUBufferRegion i_dst = {index_buf_, 0, ib_size}; SDL_UploadToGPUBuffer(copy, &i_src, &i_dst, true /* cycle */); SDL_EndGPUCopyPass(copy); @@ -179,26 +209,28 @@ bool GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cm // Private helpers // --------------------------------------------------------------------------- -void GpuSpriteBatch::toNDC(float px, float py, - float screen_w, float screen_h, - float& ndx, float& ndy) const { +void GpuSpriteBatch::toNDC(float px, float py, float screen_w, float screen_h, float& ndx, float& ndy) { ndx = (px / screen_w) * 2.0f - 1.0f; ndy = 1.0f - (py / screen_h) * 2.0f; } -void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1, - float u0, float v0, float u1, float v1, - float r, float g, float b, float a) { +void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1, float u0, float v0, float u1, float v1, float r, float g, float b, float a) { // +1 reserva el slot del background que ya entró sin pasar por este guard. - if (vertices_.size() + 4 > static_cast(max_sprites_ + 1) * 4) return; - uint32_t vi = static_cast(vertices_.size()); + if (vertices_.size() + 4 > static_cast(max_sprites_ + 1) * 4) { + return; + } + auto vi = static_cast(vertices_.size()); // TL, TR, BR, BL - vertices_.push_back({ ndx0, ndy0, u0, v0, r, g, b, a }); - vertices_.push_back({ ndx1, ndy0, u1, v0, r, g, b, a }); - vertices_.push_back({ ndx1, ndy1, u1, v1, r, g, b, a }); - vertices_.push_back({ ndx0, ndy1, u0, v1, r, g, b, a }); + vertices_.push_back({ndx0, ndy0, u0, v0, r, g, b, a}); + vertices_.push_back({ndx1, ndy0, u1, v0, r, g, b, a}); + vertices_.push_back({ndx1, ndy1, u1, v1, r, g, b, a}); + vertices_.push_back({ndx0, ndy1, u0, v1, r, g, b, a}); - indices_.push_back(vi + 0); indices_.push_back(vi + 1); indices_.push_back(vi + 2); - indices_.push_back(vi + 2); indices_.push_back(vi + 3); indices_.push_back(vi + 0); + indices_.push_back(vi + 0); + indices_.push_back(vi + 1); + indices_.push_back(vi + 2); + indices_.push_back(vi + 2); + indices_.push_back(vi + 3); + indices_.push_back(vi + 0); } diff --git a/source/gpu/gpu_sprite_batch.hpp b/source/gpu/gpu_sprite_batch.hpp index b92c5e3..952f4d0 100644 --- a/source/gpu/gpu_sprite_batch.hpp +++ b/source/gpu/gpu_sprite_batch.hpp @@ -1,17 +1,18 @@ #pragma once #include -#include + #include +#include // --------------------------------------------------------------------------- // GpuVertex — 8-float vertex layout sent to the GPU. // Position is in NDC (pre-transformed on CPU), UV in [0,1], color in [0,1]. // --------------------------------------------------------------------------- struct GpuVertex { - float x, y; // NDC position (−1..1) - float u, v; // Texture coords (0..1) - float r, g, b, a; // RGBA color (0..1) + float x, y; // NDC position (−1..1) + float u, v; // Texture coords (0..1) + float r, g, b, a; // RGBA color (0..1) }; // ============================================================================ @@ -25,64 +26,56 @@ struct GpuVertex { // // Then in render pass: bind buffers, draw bg with white tex, draw sprites. // ============================================================================ class GpuSpriteBatch { -public: - // Default maximum sprites (background + UI overlay each count as one sprite) - static constexpr int DEFAULT_MAX_SPRITES = 200000; + public: + // Default maximum sprites (background + UI overlay each count as one sprite) + static constexpr int DEFAULT_MAX_SPRITES = 200000; - bool init(SDL_GPUDevice* device, int max_sprites = DEFAULT_MAX_SPRITES); - void destroy(SDL_GPUDevice* device); + bool init(SDL_GPUDevice* device, int max_sprites = DEFAULT_MAX_SPRITES); + void destroy(SDL_GPUDevice* device); - void beginFrame(); + void beginFrame(); - // Add the full-screen background gradient quad. - // top_* and bot_* are RGB in [0,1]. - void addBackground(float screen_w, float screen_h, - float top_r, float top_g, float top_b, - float bot_r, float bot_g, float bot_b); + // Add the full-screen background gradient quad. + // top_* and bot_* are RGB in [0,1]. + void addBackground(float screen_w, float screen_h, float top_r, float top_g, float top_b, float bot_r, float bot_g, float bot_b); - // Add a sprite quad (pixel coordinates). - // scale: uniform scale around the quad centre. - void addSprite(float x, float y, float w, float h, - float r, float g, float b, float a, - float scale, - float screen_w, float screen_h); + // Add a sprite quad (pixel coordinates). + // scale: uniform scale around the quad centre. + void addSprite(float x, float y, float w, float h, float r, float g, float b, float a, float scale, float screen_w, float screen_h); - // Add a full-screen overlay quad (e.g. UI surface, NDC −1..1). - void addFullscreenOverlay(); + // Add a full-screen overlay quad (e.g. UI surface, NDC −1..1). + void addFullscreenOverlay(); - // Upload CPU vectors to GPU buffers via a copy pass. - // Returns false if the batch is empty. - bool uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf); + // Upload CPU vectors to GPU buffers via a copy pass. + // Returns false if the batch is empty. + bool uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf); - SDL_GPUBuffer* vertexBuffer() const { return vertex_buf_; } - SDL_GPUBuffer* indexBuffer() const { return index_buf_; } - int bgIndexCount() const { return bg_index_count_; } - int overlayIndexOffset() const { return overlay_index_offset_; } - int overlayIndexCount() const { return overlay_index_count_; } - int spriteIndexOffset() const { return sprite_index_offset_; } - int spriteIndexCount() const { return sprite_index_count_; } - bool isEmpty() const { return vertices_.empty(); } + SDL_GPUBuffer* vertexBuffer() const { return vertex_buf_; } + SDL_GPUBuffer* indexBuffer() const { return index_buf_; } + int bgIndexCount() const { return bg_index_count_; } + int overlayIndexOffset() const { return overlay_index_offset_; } + int overlayIndexCount() const { return overlay_index_count_; } + int spriteIndexOffset() const { return sprite_index_offset_; } + int spriteIndexCount() const { return sprite_index_count_; } + bool isEmpty() const { return vertices_.empty(); } -private: - void toNDC(float px, float py, float screen_w, float screen_h, - float& ndx, float& ndy) const; - void pushQuad(float ndx0, float ndy0, float ndx1, float ndy1, - float u0, float v0, float u1, float v1, - float r, float g, float b, float a); + private: + static void toNDC(float px, float py, float screen_w, float screen_h, float& ndx, float& ndy); + void pushQuad(float ndx0, float ndy0, float ndx1, float ndy1, float u0, float v0, float u1, float v1, float r, float g, float b, float a); - std::vector vertices_; - std::vector indices_; + std::vector vertices_; + std::vector indices_; - SDL_GPUBuffer* vertex_buf_ = nullptr; - SDL_GPUBuffer* index_buf_ = nullptr; - SDL_GPUTransferBuffer* vertex_transfer_ = nullptr; - SDL_GPUTransferBuffer* index_transfer_ = nullptr; + SDL_GPUBuffer* vertex_buf_ = nullptr; + SDL_GPUBuffer* index_buf_ = nullptr; + SDL_GPUTransferBuffer* vertex_transfer_ = nullptr; + SDL_GPUTransferBuffer* index_transfer_ = nullptr; - int bg_index_count_ = 0; - int sprite_index_offset_ = 0; - int sprite_index_count_ = 0; - int overlay_index_offset_ = 0; - int overlay_index_count_ = 0; + int bg_index_count_ = 0; + int sprite_index_offset_ = 0; + int sprite_index_count_ = 0; + int overlay_index_offset_ = 0; + int overlay_index_count_ = 0; - int max_sprites_ = DEFAULT_MAX_SPRITES; + int max_sprites_ = DEFAULT_MAX_SPRITES; }; diff --git a/source/gpu/gpu_texture.cpp b/source/gpu/gpu_texture.cpp index d84be95..bcfb345 100644 --- a/source/gpu/gpu_texture.cpp +++ b/source/gpu/gpu_texture.cpp @@ -2,6 +2,8 @@ #include #include + +#include // for std::array #include // memcpy #include @@ -13,7 +15,7 @@ // Public interface // --------------------------------------------------------------------------- -bool GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) { +auto GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) -> bool { unsigned char* resource_data = nullptr; size_t resource_size = 0; @@ -22,15 +24,22 @@ bool GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) { return false; } - int w = 0, h = 0, orig = 0; + int w = 0; + int h = 0; + int orig = 0; unsigned char* pixels = stbi_load_from_memory( - resource_data, static_cast(resource_size), - &w, &h, &orig, STBI_rgb_alpha); + resource_data, + static_cast(resource_size), + &w, + &h, + &orig, + STBI_rgb_alpha); delete[] resource_data; - if (!pixels) { + if (pixels == nullptr) { SDL_Log("GpuTexture: stbi decode failed for '%s': %s", - file_path.c_str(), stbi_failure_reason()); + file_path.c_str(), + stbi_failure_reason()); return false; } @@ -44,15 +53,17 @@ bool GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) { return ok; } -bool GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest) { - if (!surface) return false; +auto GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest) -> bool { + if (surface == nullptr) { + return false; + } // Ensure RGBA32 format SDL_Surface* rgba = surface; bool need_free = false; if (surface->format != SDL_PIXELFORMAT_RGBA32) { rgba = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32); - if (!rgba) { + if (rgba == nullptr) { SDL_Log("GpuTexture: SDL_ConvertSurface failed: %s", SDL_GetError()); return false; } @@ -60,55 +71,65 @@ bool GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool n } destroy(device); - bool ok = uploadPixels(device, rgba->pixels, rgba->w, rgba->h, - SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM); - if (ok) ok = createSampler(device, nearest); + bool ok = uploadPixels(device, rgba->pixels, rgba->w, rgba->h, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM); + if (ok) { + ok = createSampler(device, nearest); + } - if (need_free) SDL_DestroySurface(rgba); + if (need_free) { + SDL_DestroySurface(rgba); + } return ok; } -bool GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h, - SDL_GPUTextureFormat format) { +auto GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h, SDL_GPUTextureFormat format) -> bool { destroy(device); SDL_GPUTextureCreateInfo info = {}; - info.type = SDL_GPU_TEXTURETYPE_2D; - info.format = format; - info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET - | SDL_GPU_TEXTUREUSAGE_SAMPLER; - info.width = static_cast(w); - info.height = static_cast(h); + info.type = SDL_GPU_TEXTURETYPE_2D; + info.format = format; + info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; + info.width = static_cast(w); + info.height = static_cast(h); info.layer_count_or_depth = 1; - info.num_levels = 1; - info.sample_count = SDL_GPU_SAMPLECOUNT_1; + info.num_levels = 1; + info.sample_count = SDL_GPU_SAMPLECOUNT_1; texture_ = SDL_CreateGPUTexture(device, &info); - if (!texture_) { + if (texture_ == nullptr) { SDL_Log("GpuTexture: createRenderTarget failed: %s", SDL_GetError()); return false; } - width_ = w; + width_ = w; height_ = h; // Render targets are sampled with linear filter (postfx reads them) return createSampler(device, false); } -bool GpuTexture::createWhite(SDL_GPUDevice* device) { +auto GpuTexture::createWhite(SDL_GPUDevice* device) -> bool { destroy(device); // 1×1 white RGBA pixel - const Uint8 white[4] = {255, 255, 255, 255}; - bool ok = uploadPixels(device, white, 1, 1, - SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM); - if (ok) ok = createSampler(device, true); + constexpr std::array WHITE = {255, 255, 255, 255}; + bool ok = uploadPixels(device, WHITE.data(), 1, 1, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM); + if (ok) { + ok = createSampler(device, true); + } return ok; } void GpuTexture::destroy(SDL_GPUDevice* device) { - if (!device) return; - if (sampler_) { SDL_ReleaseGPUSampler(device, sampler_); sampler_ = nullptr; } - if (texture_) { SDL_ReleaseGPUTexture(device, texture_); texture_ = nullptr; } + if (device == nullptr) { + return; + } + if (sampler_ != nullptr) { + SDL_ReleaseGPUSampler(device, sampler_); + sampler_ = nullptr; + } + if (texture_ != nullptr) { + SDL_ReleaseGPUTexture(device, texture_); + texture_ = nullptr; + } width_ = height_ = 0; } @@ -116,34 +137,33 @@ void GpuTexture::destroy(SDL_GPUDevice* device) { // Private helpers // --------------------------------------------------------------------------- -bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels, - int w, int h, SDL_GPUTextureFormat format) { +auto GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels, int w, int h, SDL_GPUTextureFormat format) -> bool { // Create GPU texture SDL_GPUTextureCreateInfo tex_info = {}; - tex_info.type = SDL_GPU_TEXTURETYPE_2D; - tex_info.format = format; - tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; - tex_info.width = static_cast(w); - tex_info.height = static_cast(h); + tex_info.type = SDL_GPU_TEXTURETYPE_2D; + tex_info.format = format; + tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; + tex_info.width = static_cast(w); + tex_info.height = static_cast(h); tex_info.layer_count_or_depth = 1; - tex_info.num_levels = 1; - tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1; + tex_info.num_levels = 1; + tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1; texture_ = SDL_CreateGPUTexture(device, &tex_info); - if (!texture_) { + if (texture_ == nullptr) { SDL_Log("GpuTexture: SDL_CreateGPUTexture failed: %s", SDL_GetError()); return false; } // Create transfer buffer and upload pixels - Uint32 data_size = static_cast(w * h * 4); // RGBA = 4 bytes/pixel + auto data_size = static_cast(w * h * 4); // RGBA = 4 bytes/pixel SDL_GPUTransferBufferCreateInfo tb_info = {}; tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; - tb_info.size = data_size; + tb_info.size = data_size; SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(device, &tb_info); - if (!transfer) { + if (transfer == nullptr) { SDL_Log("GpuTexture: transfer buffer creation failed: %s", SDL_GetError()); SDL_ReleaseGPUTexture(device, texture_); texture_ = nullptr; @@ -151,7 +171,7 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels, } void* mapped = SDL_MapGPUTransferBuffer(device, transfer, false); - if (!mapped) { + if (mapped == nullptr) { SDL_Log("GpuTexture: map failed: %s", SDL_GetError()); SDL_ReleaseGPUTransferBuffer(device, transfer); SDL_ReleaseGPUTexture(device, texture_); @@ -167,14 +187,14 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels, SDL_GPUTextureTransferInfo src = {}; src.transfer_buffer = transfer; - src.offset = 0; - src.pixels_per_row = static_cast(w); - src.rows_per_layer = static_cast(h); + src.offset = 0; + src.pixels_per_row = static_cast(w); + src.rows_per_layer = static_cast(h); SDL_GPUTextureRegion dst = {}; - dst.texture = texture_; + dst.texture = texture_; dst.mip_level = 0; - dst.layer = 0; + dst.layer = 0; dst.x = dst.y = dst.z = 0; dst.w = static_cast(w); dst.h = static_cast(h); @@ -185,22 +205,22 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels, SDL_SubmitGPUCommandBuffer(cmd); SDL_ReleaseGPUTransferBuffer(device, transfer); - width_ = w; + width_ = w; height_ = h; return true; } -bool GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) { +auto GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) -> bool { SDL_GPUSamplerCreateInfo info = {}; - info.min_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR; - info.mag_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR; - info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST; + info.min_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR; + info.mag_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR; + info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST; info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; sampler_ = SDL_CreateGPUSampler(device, &info); - if (!sampler_) { + if (sampler_ == nullptr) { SDL_Log("GpuTexture: SDL_CreateGPUSampler failed: %s", SDL_GetError()); return false; } diff --git a/source/gpu/gpu_texture.hpp b/source/gpu/gpu_texture.hpp index 64145ab..edf5880 100644 --- a/source/gpu/gpu_texture.hpp +++ b/source/gpu/gpu_texture.hpp @@ -2,6 +2,7 @@ #include #include + #include // ============================================================================ @@ -9,40 +10,38 @@ // Handles sprite textures, render targets, and the 1×1 white utility texture. // ============================================================================ class GpuTexture { -public: - GpuTexture() = default; - ~GpuTexture() = default; + public: + GpuTexture() = default; + ~GpuTexture() = default; - // Load from resource path (pack or disk) using stb_image. - bool fromFile(SDL_GPUDevice* device, const std::string& file_path); + // Load from resource path (pack or disk) using stb_image. + bool fromFile(SDL_GPUDevice* device, const std::string& file_path); - // Upload pixel data from an SDL_Surface to a new GPU texture + sampler. - // Uses nearest-neighbor filter for sprite pixel-perfect look. - bool fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest = true); + // Upload pixel data from an SDL_Surface to a new GPU texture + sampler. + // Uses nearest-neighbor filter for sprite pixel-perfect look. + bool fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest = true); - // Create an offscreen render target (COLOR_TARGET | SAMPLER usage). - bool createRenderTarget(SDL_GPUDevice* device, int w, int h, - SDL_GPUTextureFormat format); + // Create an offscreen render target (COLOR_TARGET | SAMPLER usage). + bool createRenderTarget(SDL_GPUDevice* device, int w, int h, SDL_GPUTextureFormat format); - // Create a 1×1 opaque white texture (used for untextured geometry). - bool createWhite(SDL_GPUDevice* device); + // Create a 1×1 opaque white texture (used for untextured geometry). + bool createWhite(SDL_GPUDevice* device); - // Release GPU resources. - void destroy(SDL_GPUDevice* device); + // Release GPU resources. + void destroy(SDL_GPUDevice* device); - SDL_GPUTexture* texture() const { return texture_; } - SDL_GPUSampler* sampler() const { return sampler_; } - int width() const { return width_; } - int height() const { return height_; } - bool isValid() const { return texture_ != nullptr; } + SDL_GPUTexture* texture() const { return texture_; } + SDL_GPUSampler* sampler() const { return sampler_; } + int width() const { return width_; } + int height() const { return height_; } + bool isValid() const { return texture_ != nullptr; } -private: - bool uploadPixels(SDL_GPUDevice* device, const void* pixels, - int w, int h, SDL_GPUTextureFormat format); - bool createSampler(SDL_GPUDevice* device, bool nearest); + private: + bool uploadPixels(SDL_GPUDevice* device, const void* pixels, int w, int h, SDL_GPUTextureFormat format); + bool createSampler(SDL_GPUDevice* device, bool nearest); - SDL_GPUTexture* texture_ = nullptr; - SDL_GPUSampler* sampler_ = nullptr; - int width_ = 0; - int height_ = 0; + SDL_GPUTexture* texture_ = nullptr; + SDL_GPUSampler* sampler_ = nullptr; + int width_ = 0; + int height_ = 0; }; diff --git a/source/input/input_handler.cpp b/source/input/input_handler.cpp index b090897..4a15936 100644 --- a/source/input/input_handler.cpp +++ b/source/input/input_handler.cpp @@ -1,13 +1,14 @@ #include "input_handler.hpp" #include // for SDL_Keycode -#include // for std::string, std::to_string + +#include // for std::string, std::to_string #include "defines.hpp" // for KIOSK_NOTIFICATION_TEXT #include "engine.hpp" // for Engine #include "external/mouse.hpp" // for Mouse namespace -bool InputHandler::processEvents(Engine& engine) { +auto InputHandler::processEvents(Engine& engine) -> bool { // NOLINT(readability-function-cognitive-complexity) SDL_Event event; while (SDL_PollEvent(&event)) { // Procesar eventos de ratón (auto-ocultar cursor) @@ -19,7 +20,7 @@ bool InputHandler::processEvents(Engine& engine) { } // Procesar eventos de teclado - if (event.type == SDL_EVENT_KEY_DOWN && event.key.repeat == 0) { + if (event.type == SDL_EVENT_KEY_DOWN && static_cast(event.key.repeat) == 0) { switch (event.key.key) { case SDLK_ESCAPE: if (engine.isKioskMode()) { @@ -105,23 +106,21 @@ bool InputHandler::processEvents(Engine& engine) { // Toggle Modo Boids (comportamiento de enjambre) case SDLK_B: - engine.toggleBoidsMode(); + engine.toggleBoidsMode(); break; // Ciclar temas de color (movido de B a C) - case SDLK_C: - { - // Detectar si Shift está presionado - SDL_Keymod modstate = SDL_GetModState(); - if (modstate & SDL_KMOD_SHIFT) { - // Shift+C: Ciclar hacia atrás (tema anterior) - engine.cycleTheme(false); - } else { - // C solo: Ciclar hacia adelante (tema siguiente) - engine.cycleTheme(true); - } + case SDLK_C: { + // Detectar si Shift está presionado + SDL_Keymod modstate = SDL_GetModState(); + if ((modstate & SDL_KMOD_SHIFT) != 0u) { + // Shift+C: Ciclar hacia atrás (tema anterior) + engine.cycleTheme(false); + } else { + // C solo: Ciclar hacia adelante (tema siguiente) + engine.cycleTheme(true); } - break; + } break; // Temas de colores con teclado numérico (con transición suave) case SDLK_KP_1: @@ -233,25 +232,37 @@ bool InputHandler::processEvents(Engine& engine) { // Controles de zoom dinámico (solo si no estamos en fullscreen) case SDLK_F1: - if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); - else engine.handleZoomOut(); + if (engine.isKioskMode()) { + engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); + } else { + engine.handleZoomOut(); + } break; case SDLK_F2: - if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); - else engine.handleZoomIn(); + if (engine.isKioskMode()) { + engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); + } else { + engine.handleZoomIn(); + } break; // Control de pantalla completa case SDLK_F3: - if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); - else engine.toggleFullscreen(); + if (engine.isKioskMode()) { + engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); + } else { + engine.toggleFullscreen(); + } break; // Modo real fullscreen (cambia resolución interna) case SDLK_F4: - if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); - else engine.toggleRealFullscreen(); + if (engine.isKioskMode()) { + engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); + } else { + engine.toggleRealFullscreen(); + } break; // Toggle PostFX activo/inactivo @@ -266,19 +277,25 @@ bool InputHandler::processEvents(Engine& engine) { // Redimensionar campo de juego (tamaño lógico + físico) case SDLK_F7: - if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); - else engine.fieldSizeDown(); + if (engine.isKioskMode()) { + engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); + } else { + engine.fieldSizeDown(); + } break; case SDLK_F8: - if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); - else engine.fieldSizeUp(); + if (engine.isKioskMode()) { + engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT); + } else { + engine.fieldSizeUp(); + } break; // Toggle Modo DEMO COMPLETO (auto-play) o Pausar tema dinámico (Shift+D) case SDLK_D: // Shift+D = Pausar tema dinámico - if (event.key.mod & SDL_KMOD_SHIFT) { + if ((event.key.mod & SDL_KMOD_SHIFT) != 0u) { engine.pauseDynamicTheme(); } else { // D sin Shift = Toggle DEMO ↔ SANDBOX diff --git a/source/input/input_handler.hpp b/source/input/input_handler.hpp index 58623a8..b78f0a3 100644 --- a/source/input/input_handler.hpp +++ b/source/input/input_handler.hpp @@ -24,7 +24,7 @@ class InputHandler { * @param engine Referencia al engine para ejecutar acciones * @return true si se debe salir de la aplicación (ESC o cerrar ventana), false en caso contrario */ - bool processEvents(Engine& engine); + static bool processEvents(Engine& engine); private: // Sin estado interno por ahora - el InputHandler es stateless diff --git a/source/main.cpp b/source/main.cpp index 8acdf70..bcb8c52 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,8 +1,9 @@ -#include #include +#include #include -#include "engine.hpp" + #include "defines.hpp" +#include "engine.hpp" #include "resource_manager.hpp" // getExecutableDirectory() ya está definido en defines.h como inline @@ -38,7 +39,7 @@ void printHelp() { std::cout << "Nota: Si resolución > pantalla, se usa default. Zoom se ajusta automáticamente.\n"; } -int main(int argc, char* argv[]) { +auto main(int argc, char* argv[]) -> int { // NOLINT(readability-function-cognitive-complexity) int width = 0; int height = 0; int zoom = 0; @@ -58,7 +59,8 @@ int main(int argc, char* argv[]) { if (strcmp(argv[i], "--help") == 0) { printHelp(); return 0; - } else if (strcmp(argv[i], "-w") == 0 || strcmp(argv[i], "--width") == 0) { + } + if (strcmp(argv[i], "-w") == 0 || strcmp(argv[i], "--width") == 0) { if (i + 1 < argc) { width = atoi(argv[++i]); if (width < 320) { @@ -189,25 +191,29 @@ int main(int argc, char* argv[]) { Engine engine; - if (custom_balls > 0) + if (custom_balls > 0) { engine.setCustomScenario(custom_balls); // pre-init: asigna campos antes del benchmark + } - if (max_balls_override > 0) + if (max_balls_override > 0) { engine.setMaxBallsOverride(max_balls_override); - else if (skip_benchmark) + } else if (skip_benchmark) { engine.setSkipBenchmark(); + } - if (initial_postfx >= 0) + if (initial_postfx >= 0) { engine.setInitialPostFX(initial_postfx); + } if (override_vignette >= 0.f || override_chroma >= 0.f) { - if (initial_postfx < 0) + if (initial_postfx < 0) { engine.setInitialPostFX(0); + } engine.setPostFXParamOverrides(override_vignette, override_chroma); } if (!engine.initialize(width, height, zoom, fullscreen, initial_mode)) { - std::cout << "¡Error al inicializar el engine!" << std::endl; + std::cout << "¡Error al inicializar el engine!" << '\n'; return -1; } diff --git a/source/resource_manager.cpp b/source/resource_manager.cpp index 3eef8be..5457ab5 100644 --- a/source/resource_manager.cpp +++ b/source/resource_manager.cpp @@ -1,15 +1,16 @@ #include "resource_manager.hpp" -#include "resource_pack.hpp" -#include -#include #include +#include +#include + +#include "resource_pack.hpp" // Inicializar estáticos ResourcePack* ResourceManager::resourcePack_ = nullptr; std::map> ResourceManager::cache_; -bool ResourceManager::init(const std::string& packFilePath) { +auto ResourceManager::init(const std::string& pack_file_path) -> bool { // Si ya estaba inicializado, liberar primero if (resourcePack_ != nullptr) { delete resourcePack_; @@ -18,15 +19,15 @@ bool ResourceManager::init(const std::string& packFilePath) { // Intentar cargar el pack resourcePack_ = new ResourcePack(); - if (!resourcePack_->loadPack(packFilePath)) { + if (!resourcePack_->loadPack(pack_file_path)) { // Si falla, borrar instancia (usará fallback a disco) delete resourcePack_; resourcePack_ = nullptr; - std::cout << "resources.pack no encontrado - usando carpeta data/" << std::endl; + std::cout << "resources.pack no encontrado - usando carpeta data/" << '\n'; return false; } - std::cout << "resources.pack cargado (" << resourcePack_->getResourceCount() << " recursos)" << std::endl; + std::cout << "resources.pack cargado (" << resourcePack_->getResourceCount() << " recursos)" << '\n'; return true; } @@ -38,12 +39,12 @@ void ResourceManager::shutdown() { } } -bool ResourceManager::loadResource(const std::string& resourcePath, unsigned char*& data, size_t& size) { +auto ResourceManager::loadResource(const std::string& resource_path, unsigned char*& data, size_t& size) -> bool { data = nullptr; size = 0; // 1. Consultar caché en RAM (sin I/O) - auto it = cache_.find(resourcePath); + auto it = cache_.find(resource_path); if (it != cache_.end()) { size = it->second.size(); data = new unsigned char[size]; @@ -53,20 +54,20 @@ bool ResourceManager::loadResource(const std::string& resourcePath, unsigned cha // 2. Intentar cargar desde pack (si está disponible) if (resourcePack_ != nullptr) { - ResourcePack::ResourceData packData = resourcePack_->loadResource(resourcePath); - if (packData.data != nullptr) { - cache_[resourcePath] = std::vector(packData.data, packData.data + packData.size); - data = packData.data; - size = packData.size; + ResourcePack::ResourceData pack_data = resourcePack_->loadResource(resource_path); + if (pack_data.data != nullptr) { + cache_[resource_path] = std::vector(pack_data.data, pack_data.data + pack_data.size); + data = pack_data.data; + size = pack_data.size; return true; } } // 3. Fallback: cargar desde disco - std::ifstream file(resourcePath, std::ios::binary | std::ios::ate); + std::ifstream file(resource_path, std::ios::binary | std::ios::ate); if (!file) { - std::string dataPath = "data/" + resourcePath; - file.open(dataPath, std::ios::binary | std::ios::ate); + std::string data_path = "data/" + resource_path; + file.open(data_path, std::ios::binary | std::ios::ate); if (!file) { return false; } } size = static_cast(file.tellg()); @@ -76,22 +77,22 @@ bool ResourceManager::loadResource(const std::string& resourcePath, unsigned cha file.close(); // Guardar en caché - cache_[resourcePath] = std::vector(data, data + size); + cache_[resource_path] = std::vector(data, data + size); return true; } -bool ResourceManager::isPackLoaded() { +auto ResourceManager::isPackLoaded() -> bool { return resourcePack_ != nullptr; } -std::vector ResourceManager::getResourceList() { +auto ResourceManager::getResourceList() -> std::vector { if (resourcePack_ != nullptr) { return resourcePack_->getResourceList(); } - return std::vector(); // Vacío si no hay pack + return {}; // Vacío si no hay pack } -size_t ResourceManager::getResourceCount() { +auto ResourceManager::getResourceCount() -> size_t { if (resourcePack_ != nullptr) { return resourcePack_->getResourceCount(); } diff --git a/source/resource_manager.hpp b/source/resource_manager.hpp index 6403be9..fa56500 100644 --- a/source/resource_manager.hpp +++ b/source/resource_manager.hpp @@ -25,62 +25,62 @@ class ResourcePack; * } */ class ResourceManager { -public: - /** - * Inicializa el sistema de recursos empaquetados - * Debe llamarse una única vez al inicio del programa - * - * @param packFilePath Ruta al archivo .pack (ej: "resources.pack") - * @return true si el pack se cargó correctamente, false si no existe (fallback a disco) - */ - static bool init(const std::string& packFilePath); + public: + /** + * Inicializa el sistema de recursos empaquetados + * Debe llamarse una única vez al inicio del programa + * + * @param packFilePath Ruta al archivo .pack (ej: "resources.pack") + * @return true si el pack se cargó correctamente, false si no existe (fallback a disco) + */ + static bool init(const std::string& pack_file_path); - /** - * Libera el sistema de recursos - * Opcional - se llama automáticamente al cerrar el programa - */ - static void shutdown(); + /** + * Libera el sistema de recursos + * Opcional - se llama automáticamente al cerrar el programa + */ + static void shutdown(); - /** - * Carga un recurso desde el pack (o disco si no existe pack) - * - * @param resourcePath Ruta relativa del recurso (ej: "textures/ball.png") - * @param data [out] Puntero donde se almacenará el buffer (debe liberar con delete[]) - * @param size [out] Tamaño del buffer en bytes - * @return true si se cargó correctamente, false si falla - */ - static bool loadResource(const std::string& resourcePath, unsigned char*& data, size_t& size); + /** + * Carga un recurso desde el pack (o disco si no existe pack) + * + * @param resourcePath Ruta relativa del recurso (ej: "textures/ball.png") + * @param data [out] Puntero donde se almacenará el buffer (debe liberar con delete[]) + * @param size [out] Tamaño del buffer en bytes + * @return true si se cargó correctamente, false si falla + */ + static bool loadResource(const std::string& resource_path, unsigned char*& data, size_t& size); - /** - * Verifica si el pack está cargado - * @return true si hay un pack cargado, false si se usa disco - */ - static bool isPackLoaded(); + /** + * Verifica si el pack está cargado + * @return true si hay un pack cargado, false si se usa disco + */ + static bool isPackLoaded(); - /** - * Obtiene la lista de recursos disponibles en el pack - * @return Vector con las rutas de todos los recursos, vacío si no hay pack - */ - static std::vector getResourceList(); + /** + * Obtiene la lista de recursos disponibles en el pack + * @return Vector con las rutas de todos los recursos, vacío si no hay pack + */ + static std::vector getResourceList(); - /** - * Obtiene el número de recursos en el pack - * @return Número de recursos, 0 si no hay pack - */ - static size_t getResourceCount(); + /** + * Obtiene el número de recursos en el pack + * @return Número de recursos, 0 si no hay pack + */ + static size_t getResourceCount(); -private: - // Constructor privado (singleton) - ResourceManager() = default; - ~ResourceManager() = default; + private: + // Constructor privado (singleton) + ResourceManager() = default; + ~ResourceManager() = default; - // Deshabilitar copia y asignación - ResourceManager(const ResourceManager&) = delete; - ResourceManager& operator=(const ResourceManager&) = delete; + // Deshabilitar copia y asignación + ResourceManager(const ResourceManager&) = delete; + ResourceManager& operator=(const ResourceManager&) = delete; - // Instancia del pack (nullptr si no está cargado) - static ResourcePack* resourcePack_; + // Instancia del pack (nullptr si no está cargado) + static ResourcePack* resourcePack_; - // Caché en RAM para evitar I/O repetido en el bucle principal - static std::map> cache_; + // Caché en RAM para evitar I/O repetido en el bucle principal + static std::map> cache_; }; diff --git a/source/resource_pack.cpp b/source/resource_pack.cpp index 71f5ccf..3442c28 100644 --- a/source/resource_pack.cpp +++ b/source/resource_pack.cpp @@ -7,10 +7,8 @@ namespace fs = std::filesystem; -// Clave XOR para ofuscación simple (puede cambiarse) -constexpr uint8_t XOR_KEY = 0x5A; - -ResourcePack::ResourcePack() : isLoaded_(false) {} +ResourcePack::ResourcePack() + : isLoaded_(false) {} ResourcePack::~ResourcePack() { clear(); @@ -20,54 +18,54 @@ ResourcePack::~ResourcePack() { // EMPAQUETADO (herramienta pack_resources) // ============================================================================ -bool ResourcePack::addDirectory(const std::string& dirPath, const std::string& prefix) { - if (!fs::exists(dirPath) || !fs::is_directory(dirPath)) { - std::cerr << "Error: Directorio no existe: " << dirPath << std::endl; +auto ResourcePack::addDirectory(const std::string& dir_path, const std::string& prefix) -> bool { + if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) { + std::cerr << "Error: Directorio no existe: " << dir_path << '\n'; return false; } - for (const auto& entry : fs::recursive_directory_iterator(dirPath)) { + for (const auto& entry : fs::recursive_directory_iterator(dir_path)) { if (entry.is_regular_file()) { // Construir ruta relativa desde data/ (ej: "data/ball.png" → "ball.png") - std::string relativePath = fs::relative(entry.path(), dirPath).string(); - std::string fullPath = prefix.empty() ? relativePath : prefix + "/" + relativePath; - fullPath = normalizePath(fullPath); + std::string relative_path = fs::relative(entry.path(), dir_path).string(); + std::string full_path = prefix.empty() ? relative_path : prefix + "/" + relative_path; + full_path = normalizePath(full_path); // Leer archivo completo std::ifstream file(entry.path(), std::ios::binary); if (!file) { - std::cerr << "Error: No se pudo abrir: " << entry.path() << std::endl; + std::cerr << "Error: No se pudo abrir: " << entry.path() << '\n'; continue; } file.seekg(0, std::ios::end); - size_t fileSize = file.tellg(); + size_t file_size = file.tellg(); file.seekg(0, std::ios::beg); - std::vector buffer(fileSize); - file.read(reinterpret_cast(buffer.data()), fileSize); + std::vector buffer(file_size); + file.read(reinterpret_cast(buffer.data()), file_size); file.close(); // Crear entrada de recurso ResourceEntry resource; - resource.path = fullPath; + resource.path = full_path; resource.offset = 0; // Se calculará al guardar - resource.size = static_cast(fileSize); - resource.checksum = calculateChecksum(buffer.data(), fileSize); + resource.size = static_cast(file_size); + resource.checksum = calculateChecksum(buffer.data(), file_size); - resources_[fullPath] = resource; + resources_[full_path] = resource; - std::cout << " Añadido: " << fullPath << " (" << fileSize << " bytes)" << std::endl; + std::cout << " Añadido: " << full_path << " (" << file_size << " bytes)" << '\n'; } } return !resources_.empty(); } -bool ResourcePack::savePack(const std::string& packFilePath) { - std::ofstream packFile(packFilePath, std::ios::binary); - if (!packFile) { - std::cerr << "Error: No se pudo crear pack: " << packFilePath << std::endl; +auto ResourcePack::savePack(const std::string& pack_file_path) -> bool { + std::ofstream pack_file(pack_file_path, std::ios::binary); + if (!pack_file) { + std::cerr << "Error: No se pudo crear pack: " << pack_file_path << '\n'; return false; } @@ -76,39 +74,39 @@ bool ResourcePack::savePack(const std::string& packFilePath) { std::memcpy(header.magic, "VBE3", 4); header.version = 1; header.fileCount = static_cast(resources_.size()); - packFile.write(reinterpret_cast(&header), sizeof(PackHeader)); + pack_file.write(reinterpret_cast(&header), sizeof(PackHeader)); // 2. Calcular offsets (después del header + índice) - uint32_t currentOffset = sizeof(PackHeader); + uint32_t current_offset = sizeof(PackHeader); // Calcular tamaño del índice (cada entrada: uint32_t pathLen + path + 3*uint32_t) for (const auto& [path, entry] : resources_) { - currentOffset += sizeof(uint32_t); // pathLen - currentOffset += static_cast(path.size()); // path - currentOffset += sizeof(uint32_t) * 3; // offset, size, checksum + current_offset += sizeof(uint32_t); // pathLen + current_offset += static_cast(path.size()); // path + current_offset += sizeof(uint32_t) * 3; // offset, size, checksum } // 3. Escribir índice for (auto& [path, entry] : resources_) { - entry.offset = currentOffset; + entry.offset = current_offset; - uint32_t pathLen = static_cast(path.size()); - packFile.write(reinterpret_cast(&pathLen), sizeof(uint32_t)); - packFile.write(path.c_str(), pathLen); - packFile.write(reinterpret_cast(&entry.offset), sizeof(uint32_t)); - packFile.write(reinterpret_cast(&entry.size), sizeof(uint32_t)); - packFile.write(reinterpret_cast(&entry.checksum), sizeof(uint32_t)); + auto path_len = static_cast(path.size()); + pack_file.write(reinterpret_cast(&path_len), sizeof(uint32_t)); + pack_file.write(path.c_str(), path_len); + pack_file.write(reinterpret_cast(&entry.offset), sizeof(uint32_t)); + pack_file.write(reinterpret_cast(&entry.size), sizeof(uint32_t)); + pack_file.write(reinterpret_cast(&entry.checksum), sizeof(uint32_t)); - currentOffset += entry.size; + current_offset += entry.size; } // 4. Escribir datos de archivos (sin encriptar en pack, se encripta al cargar) for (const auto& [path, entry] : resources_) { // Encontrar archivo original en disco - fs::path originalPath = fs::current_path() / "data" / path; - std::ifstream file(originalPath, std::ios::binary); + fs::path original_path = fs::current_path() / "data" / path; + std::ifstream file(original_path, std::ios::binary); if (!file) { - std::cerr << "Error: No se pudo re-leer: " << originalPath << std::endl; + std::cerr << "Error: No se pudo re-leer: " << original_path << '\n'; continue; } @@ -116,10 +114,10 @@ bool ResourcePack::savePack(const std::string& packFilePath) { file.read(reinterpret_cast(buffer.data()), entry.size); file.close(); - packFile.write(reinterpret_cast(buffer.data()), entry.size); + pack_file.write(reinterpret_cast(buffer.data()), entry.size); } - packFile.close(); + pack_file.close(); return true; } @@ -127,10 +125,10 @@ bool ResourcePack::savePack(const std::string& packFilePath) { // DESEMPAQUETADO (juego) // ============================================================================ -bool ResourcePack::loadPack(const std::string& packFilePath) { +auto ResourcePack::loadPack(const std::string& pack_file_path) -> bool { clear(); - packFile_.open(packFilePath, std::ios::binary); + packFile_.open(pack_file_path, std::ios::binary); if (!packFile_) { return false; } @@ -140,13 +138,13 @@ bool ResourcePack::loadPack(const std::string& packFilePath) { packFile_.read(reinterpret_cast(&header), sizeof(PackHeader)); if (std::memcmp(header.magic, "VBE3", 4) != 0) { - std::cerr << "Error: Pack inválido (magic incorrecto)" << std::endl; + std::cerr << "Error: Pack inválido (magic incorrecto)" << '\n'; packFile_.close(); return false; } if (header.version != 1) { - std::cerr << "Error: Versión de pack no soportada: " << header.version << std::endl; + std::cerr << "Error: Versión de pack no soportada: " << header.version << '\n'; packFile_.close(); return false; } @@ -155,12 +153,12 @@ bool ResourcePack::loadPack(const std::string& packFilePath) { for (uint32_t i = 0; i < header.fileCount; i++) { ResourceEntry entry; - uint32_t pathLen; - packFile_.read(reinterpret_cast(&pathLen), sizeof(uint32_t)); + uint32_t path_len; + packFile_.read(reinterpret_cast(&path_len), sizeof(uint32_t)); - std::vector pathBuffer(pathLen + 1, '\0'); - packFile_.read(pathBuffer.data(), pathLen); - entry.path = std::string(pathBuffer.data()); + std::vector path_buffer(path_len + 1, '\0'); + packFile_.read(path_buffer.data(), path_len); + entry.path = std::string(path_buffer.data()); packFile_.read(reinterpret_cast(&entry.offset), sizeof(uint32_t)); packFile_.read(reinterpret_cast(&entry.size), sizeof(uint32_t)); @@ -173,15 +171,15 @@ bool ResourcePack::loadPack(const std::string& packFilePath) { return true; } -ResourcePack::ResourceData ResourcePack::loadResource(const std::string& resourcePath) { - ResourceData result = {nullptr, 0}; +auto ResourcePack::loadResource(const std::string& resource_path) -> ResourcePack::ResourceData { + ResourceData result = {.data = nullptr, .size = 0}; if (!isLoaded_) { return result; } - std::string normalizedPath = normalizePath(resourcePath); - auto it = resources_.find(normalizedPath); + std::string normalized_path = normalizePath(resource_path); + auto it = resources_.find(normalized_path); if (it == resources_.end()) { return result; } @@ -197,7 +195,7 @@ ResourcePack::ResourceData ResourcePack::loadResource(const std::string& resourc // Verificar checksum uint32_t checksum = calculateChecksum(result.data, entry.size); if (checksum != entry.checksum) { - std::cerr << "Warning: Checksum incorrecto para: " << resourcePath << std::endl; + std::cerr << "Warning: Checksum incorrecto para: " << resource_path << '\n'; } return result; @@ -207,15 +205,16 @@ ResourcePack::ResourceData ResourcePack::loadResource(const std::string& resourc // UTILIDADES // ============================================================================ -std::vector ResourcePack::getResourceList() const { +auto ResourcePack::getResourceList() const -> std::vector { std::vector list; + list.reserve(resources_.size()); for (const auto& [path, entry] : resources_) { list.push_back(path); } return list; } -size_t ResourcePack::getResourceCount() const { +auto ResourcePack::getResourceCount() const -> size_t { return resources_.size(); } @@ -231,7 +230,7 @@ void ResourcePack::clear() { // FUNCIONES AUXILIARES // ============================================================================ -uint32_t ResourcePack::calculateChecksum(const unsigned char* data, size_t size) { +auto ResourcePack::calculateChecksum(const unsigned char* data, size_t size) -> uint32_t { uint32_t checksum = 0; for (size_t i = 0; i < size; i++) { checksum ^= static_cast(data[i]); @@ -240,11 +239,11 @@ uint32_t ResourcePack::calculateChecksum(const unsigned char* data, size_t size) return checksum; } -std::string ResourcePack::normalizePath(const std::string& path) { +auto ResourcePack::normalizePath(const std::string& path) -> std::string { std::string normalized = path; // Reemplazar \ por / - std::replace(normalized.begin(), normalized.end(), '\\', '/'); + std::ranges::replace(normalized, '\\', '/'); // Buscar "data/" en cualquier parte del path y extraer lo que viene después size_t data_pos = normalized.find("data/"); diff --git a/source/resource_pack.hpp b/source/resource_pack.hpp index da7a039..f80fc1b 100644 --- a/source/resource_pack.hpp +++ b/source/resource_pack.hpp @@ -13,51 +13,51 @@ * único y ofuscado. Proporciona fallback automático a carpeta data/ si no existe pack. */ class ResourcePack { -public: - ResourcePack(); - ~ResourcePack(); + public: + ResourcePack(); + ~ResourcePack(); - // Empaquetado (usado por herramienta pack_resources) - bool addDirectory(const std::string& dirPath, const std::string& prefix = ""); - bool savePack(const std::string& packFilePath); + // Empaquetado (usado por herramienta pack_resources) + bool addDirectory(const std::string& dir_path, const std::string& prefix = ""); + bool savePack(const std::string& pack_file_path); - // Desempaquetado (usado por el juego) - bool loadPack(const std::string& packFilePath); + // Desempaquetado (usado por el juego) + bool loadPack(const std::string& pack_file_path); - // Carga de recursos individuales - struct ResourceData { - unsigned char* data; - size_t size; - }; - ResourceData loadResource(const std::string& resourcePath); + // Carga de recursos individuales + struct ResourceData { + unsigned char* data; + size_t size; + }; + ResourceData loadResource(const std::string& resource_path); - // Utilidades - std::vector getResourceList() const; - size_t getResourceCount() const; - void clear(); + // Utilidades + std::vector getResourceList() const; + size_t getResourceCount() const; + void clear(); -private: - // Header del pack (12 bytes) - struct PackHeader { - char magic[4]; // "VBE3" - uint32_t version; // Versión del formato (1) - uint32_t fileCount; // Número de archivos empaquetados - }; + private: + // Header del pack (12 bytes) + struct PackHeader { + char magic[4]; // "VBE3" + uint32_t version; // Versión del formato (1) + uint32_t fileCount; // Número de archivos empaquetados + }; - // Índice de un recurso (variable length) - struct ResourceEntry { - std::string path; // Ruta relativa del recurso - uint32_t offset; // Offset en el archivo pack - uint32_t size; // Tamaño en bytes - uint32_t checksum; // Checksum simple (XOR de bytes) - }; + // Índice de un recurso (variable length) + struct ResourceEntry { + std::string path; // Ruta relativa del recurso + uint32_t offset; // Offset en el archivo pack + uint32_t size; // Tamaño en bytes + uint32_t checksum; // Checksum simple (XOR de bytes) + }; - // Datos internos - std::map resources_; - std::ifstream packFile_; - bool isLoaded_; + // Datos internos + std::map resources_; + std::ifstream packFile_; + bool isLoaded_; - // Funciones auxiliares - uint32_t calculateChecksum(const unsigned char* data, size_t size); - std::string normalizePath(const std::string& path); + // Funciones auxiliares + static uint32_t calculateChecksum(const unsigned char* data, size_t size); + static std::string normalizePath(const std::string& path); }; diff --git a/source/scene/scene_manager.cpp b/source/scene/scene_manager.cpp index 52a048c..0acdaa1 100644 --- a/source/scene/scene_manager.cpp +++ b/source/scene/scene_manager.cpp @@ -1,24 +1,25 @@ #include "scene_manager.hpp" #include // for rand +#include -#include "defines.hpp" // for BALL_COUNT_SCENARIOS, GRAVITY_MASS_MIN, etc -#include "external/texture.hpp" // for Texture -#include "theme_manager.hpp" // for ThemeManager +#include "defines.hpp" // for BALL_COUNT_SCENARIOS, GRAVITY_MASS_MIN, etc +#include "external/texture.hpp" // for Texture +#include "theme_manager.hpp" // for ThemeManager SceneManager::SceneManager(int screen_width, int screen_height) - : current_gravity_(GravityDirection::DOWN) - , scenario_(0) - , screen_width_(screen_width) - , screen_height_(screen_height) - , current_ball_size_(10) - , texture_(nullptr) - , theme_manager_(nullptr) { + : current_gravity_(GravityDirection::DOWN), + scenario_(0), + screen_width_(screen_width), + screen_height_(screen_height), + current_ball_size_(10), + texture_(nullptr), + theme_manager_(nullptr) { } void SceneManager::initialize(int scenario, std::shared_ptr texture, ThemeManager* theme_manager) { scenario_ = scenario; - texture_ = texture; + texture_ = std::move(texture); theme_manager_ = theme_manager; current_ball_size_ = texture_->getWidth(); @@ -48,28 +49,31 @@ void SceneManager::changeScenario(int scenario_id, SimulationMode mode) { ? custom_ball_count_ : BALL_COUNT_SCENARIOS[scenario_id]; for (int i = 0; i < ball_count; ++i) { - float X, Y, VX, VY; + float x; + float y; + float vx; + float vy; // Inicialización según SimulationMode (RULES.md líneas 23-26) switch (mode) { case SimulationMode::PHYSICS: { // PHYSICS: Parte superior, 75% distribución central en X const int SIGN = ((rand() % 2) * 2) - 1; - const int margin = static_cast(screen_width_ * BALL_SPAWN_MARGIN); - const int spawn_zone_width = screen_width_ - (2 * margin); - X = (rand() % spawn_zone_width) + margin; - Y = 0.0f; // Parte superior - VX = (((rand() % 20) + 10) * 0.1f) * SIGN; - VY = ((rand() % 60) - 30) * 0.1f; + const int MARGIN = static_cast(screen_width_ * BALL_SPAWN_MARGIN); + const int SPAWN_ZONE_WIDTH = screen_width_ - (2 * MARGIN); + x = (rand() % SPAWN_ZONE_WIDTH) + MARGIN; + y = 0.0f; // Parte superior + vx = (((rand() % 20) + 10) * 0.1f) * SIGN; + vy = ((rand() % 60) - 30) * 0.1f; break; } case SimulationMode::SHAPE: { // SHAPE: Centro de pantalla, sin velocidad inicial - X = screen_width_ / 2.0f; - Y = screen_height_ / 2.0f; // Centro vertical - VX = 0.0f; - VY = 0.0f; + x = screen_width_ / 2.0f; + y = screen_height_ / 2.0f; // Centro vertical + vx = 0.0f; + vy = 0.0f; break; } @@ -77,48 +81,57 @@ void SceneManager::changeScenario(int scenario_id, SimulationMode mode) { // BOIDS: Posiciones aleatorias, velocidades aleatorias const int SIGN_X = ((rand() % 2) * 2) - 1; const int SIGN_Y = ((rand() % 2) * 2) - 1; - X = static_cast(rand() % screen_width_); - Y = static_cast(rand() % screen_height_); // Posición Y aleatoria - VX = (((rand() % 40) + 10) * 0.1f) * SIGN_X; // 1.0 - 5.0 px/frame - VY = (((rand() % 40) + 10) * 0.1f) * SIGN_Y; + x = static_cast(rand() % screen_width_); + y = static_cast(rand() % screen_height_); // Posición Y aleatoria + vx = (((rand() % 40) + 10) * 0.1f) * SIGN_X; // 1.0 - 5.0 px/frame + vy = (((rand() % 40) + 10) * 0.1f) * SIGN_Y; break; } default: // Fallback a PHYSICS por seguridad const int SIGN = ((rand() % 2) * 2) - 1; - const int margin = static_cast(screen_width_ * BALL_SPAWN_MARGIN); - const int spawn_zone_width = screen_width_ - (2 * margin); - X = (rand() % spawn_zone_width) + margin; - Y = 0.0f; // Parte superior - VX = (((rand() % 20) + 10) * 0.1f) * SIGN; - VY = ((rand() % 60) - 30) * 0.1f; + const int MARGIN = static_cast(screen_width_ * BALL_SPAWN_MARGIN); + const int SPAWN_ZONE_WIDTH = screen_width_ - (2 * MARGIN); + x = (rand() % SPAWN_ZONE_WIDTH) + MARGIN; + y = 0.0f; // Parte superior + vx = (((rand() % 20) + 10) * 0.1f) * SIGN; + vy = ((rand() % 60) - 30) * 0.1f; break; } // Seleccionar color de la paleta del tema actual (delegado a ThemeManager) int random_index = rand(); - Color COLOR = theme_manager_->getInitialBallColor(random_index); + Color color = theme_manager_->getInitialBallColor(random_index); // Generar factor de masa aleatorio (0.7 = ligera, 1.3 = pesada) - float mass_factor = GRAVITY_MASS_MIN + (rand() % 1000) / 1000.0f * (GRAVITY_MASS_MAX - GRAVITY_MASS_MIN); + float mass_factor = GRAVITY_MASS_MIN + ((rand() % 1000) / 1000.0f * (GRAVITY_MASS_MAX - GRAVITY_MASS_MIN)); balls_.emplace_back(std::make_unique( - X, Y, VX, VY, COLOR, texture_, - screen_width_, screen_height_, current_ball_size_, - current_gravity_, mass_factor - )); + x, + y, + vx, + vy, + color, + texture_, + screen_width_, + screen_height_, + current_ball_size_, + current_gravity_, + mass_factor)); } } void SceneManager::updateBallTexture(std::shared_ptr new_texture, int new_ball_size) { - if (balls_.empty()) return; + if (balls_.empty()) { + return; + } // Guardar tamaño antiguo int old_size = current_ball_size_; // Actualizar textura y tamaño - texture_ = new_texture; + texture_ = std::move(new_texture); current_ball_size_ = new_ball_size; // Actualizar texturas de todas las pelotas @@ -136,7 +149,8 @@ void SceneManager::pushBallsAwayFromGravity() { const float LATERAL = (((rand() % 20) + 10) * 0.1f) * SIGNO; const float MAIN = ((rand() % 40) * 0.1f) + 5; - float vx = 0, vy = 0; + float vx = 0; + float vy = 0; switch (current_gravity_) { case GravityDirection::DOWN: // Impulsar ARRIBA vx = LATERAL; diff --git a/source/shapes/atom_shape.cpp b/source/shapes/atom_shape.cpp index ff41616..94c2070 100644 --- a/source/shapes/atom_shape.cpp +++ b/source/shapes/atom_shape.cpp @@ -1,7 +1,10 @@ #include "atom_shape.hpp" -#include "defines.hpp" + +#include #include +#include "defines.hpp" + void AtomShape::generatePoints(int num_points, float screen_width, float screen_height) { num_points_ = num_points; nucleus_radius_ = screen_height * ATOM_NUCLEUS_RADIUS_FACTOR; @@ -25,15 +28,15 @@ void AtomShape::getPoint3D(int index, float& x, float& y, float& z) const { int num_orbits = static_cast(ATOM_NUM_ORBITS); // Calcular cuántos puntos para núcleo vs órbitas - int nucleus_points = (num_points_ < 10) ? 1 : (num_points_ / 10); // 10% para núcleo - if (nucleus_points < 1) nucleus_points = 1; + int nucleus_points = (num_points_ < 10) ? 1 : (num_points_ / 10); // 10% para núcleo + nucleus_points = std::max(nucleus_points, 1); // Si estamos en el núcleo if (index < nucleus_points) { // Distribuir puntos en esfera pequeña (núcleo) float t = static_cast(index) / static_cast(nucleus_points); - float phi = acosf(1.0f - 2.0f * t); - float theta = PI * 2.0f * t * 3.61803398875f; // Golden ratio + float phi = acosf(1.0f - (2.0f * t)); + float theta = PI * 2.0f * t * 3.61803398875f; // Golden ratio float x_nuc = nucleus_radius_ * cosf(theta) * sinf(phi); float y_nuc = nucleus_radius_ * sinf(theta) * sinf(phi); @@ -51,16 +54,18 @@ void AtomShape::getPoint3D(int index, float& x, float& y, float& z) const { // Puntos restantes: distribuir en órbitas int orbit_points = num_points_ - nucleus_points; int points_per_orbit = orbit_points / num_orbits; - if (points_per_orbit < 1) points_per_orbit = 1; + points_per_orbit = std::max(points_per_orbit, 1); int orbit_index = (index - nucleus_points) / points_per_orbit; - if (orbit_index >= num_orbits) orbit_index = num_orbits - 1; + if (orbit_index >= num_orbits) { + orbit_index = num_orbits - 1; + } int point_in_orbit = (index - nucleus_points) % points_per_orbit; // Ángulo del electrón en su órbita float electron_angle = (static_cast(point_in_orbit) / static_cast(points_per_orbit)) * 2.0f * PI; - electron_angle += orbit_phase_; // Añadir rotación animada + electron_angle += orbit_phase_; // Añadir rotación animada // Inclinación del plano orbital (cada órbita en ángulo diferente) float orbit_tilt = (static_cast(orbit_index) / static_cast(num_orbits)) * PI; @@ -73,21 +78,21 @@ void AtomShape::getPoint3D(int index, float& x, float& y, float& z) const { // Inclinar el plano orbital (rotación en eje X local) float cos_tilt = cosf(orbit_tilt); float sin_tilt = sinf(orbit_tilt); - float y_tilted = y_local * cos_tilt - z_local * sin_tilt; - float z_tilted = y_local * sin_tilt + z_local * cos_tilt; + float y_tilted = (y_local * cos_tilt) - (z_local * sin_tilt); + float z_tilted = (y_local * sin_tilt) + (z_local * cos_tilt); // Aplicar rotación global del átomo (eje Y) float cos_y = cosf(angle_y_); float sin_y = sinf(angle_y_); - float x_rot = x_local * cos_y - z_tilted * sin_y; - float z_rot = x_local * sin_y + z_tilted * cos_y; + float x_rot = (x_local * cos_y) - (z_tilted * sin_y); + float z_rot = (x_local * sin_y) + (z_tilted * cos_y); x = x_rot; y = y_tilted; z = z_rot; } -float AtomShape::getScaleFactor(float screen_height) const { +auto AtomShape::getScaleFactor(float screen_height) const -> float { // Factor de escala para física: proporcional al radio de órbita // Radio órbita base = 72px (0.30 * 240px en resolución 320x240) const float BASE_RADIUS = 72.0f; diff --git a/source/shapes/atom_shape.hpp b/source/shapes/atom_shape.hpp index cdcc644..6fd3416 100644 --- a/source/shapes/atom_shape.hpp +++ b/source/shapes/atom_shape.hpp @@ -6,17 +6,17 @@ // Comportamiento: Núcleo estático + electrones orbitando en planos inclinados // Efecto: Modelo atómico clásico Bohr class AtomShape : public Shape { -private: - float angle_y_ = 0.0f; // Ángulo de rotación global en eje Y (rad) - float orbit_phase_ = 0.0f; // Fase de rotación de electrones (rad) - float nucleus_radius_ = 0.0f; // Radio del núcleo central (píxeles) - float orbit_radius_ = 0.0f; // Radio de las órbitas (píxeles) - int num_points_ = 0; // Cantidad total de puntos + private: + float angle_y_ = 0.0f; // Ángulo de rotación global en eje Y (rad) + float orbit_phase_ = 0.0f; // Fase de rotación de electrones (rad) + float nucleus_radius_ = 0.0f; // Radio del núcleo central (píxeles) + float orbit_radius_ = 0.0f; // Radio de las órbitas (píxeles) + int num_points_ = 0; // Cantidad total de puntos -public: - void generatePoints(int num_points, float screen_width, float screen_height) override; - void update(float delta_time, float screen_width, float screen_height) override; - void getPoint3D(int index, float& x, float& y, float& z) const override; - const char* getName() const override { return "ATOM"; } - float getScaleFactor(float screen_height) const override; + public: + void generatePoints(int num_points, float screen_width, float screen_height) override; + void update(float delta_time, float screen_width, float screen_height) override; + void getPoint3D(int index, float& x, float& y, float& z) const override; + const char* getName() const override { return "ATOM"; } + float getScaleFactor(float screen_height) const override; }; diff --git a/source/shapes/cube_shape.cpp b/source/shapes/cube_shape.cpp index e25c08b..9ba7217 100644 --- a/source/shapes/cube_shape.cpp +++ b/source/shapes/cube_shape.cpp @@ -1,7 +1,10 @@ #include "cube_shape.hpp" -#include "defines.hpp" + +#include #include +#include "defines.hpp" + void CubeShape::generatePoints(int num_points, float screen_width, float screen_height) { num_points_ = num_points; size_ = screen_height * CUBE_SIZE_FACTOR; @@ -52,23 +55,23 @@ void CubeShape::getPoint3D(int index, float& x, float& y, float& z) const { // Aplicar rotación en eje Z float cos_z = cosf(angle_z_); float sin_z = sinf(angle_z_); - float x_rot_z = x_base * cos_z - y_base * sin_z; - float y_rot_z = x_base * sin_z + y_base * cos_z; + float x_rot_z = (x_base * cos_z) - (y_base * sin_z); + float y_rot_z = (x_base * sin_z) + (y_base * cos_z); float z_rot_z = z_base; // Aplicar rotación en eje Y float cos_y = cosf(angle_y_); float sin_y = sinf(angle_y_); - float x_rot_y = x_rot_z * cos_y + z_rot_z * sin_y; + float x_rot_y = (x_rot_z * cos_y) + (z_rot_z * sin_y); float y_rot_y = y_rot_z; - float z_rot_y = -x_rot_z * sin_y + z_rot_z * cos_y; + float z_rot_y = (-x_rot_z * sin_y) + (z_rot_z * cos_y); // Aplicar rotación en eje X float cos_x = cosf(angle_x_); float sin_x = sinf(angle_x_); float x_final = x_rot_y; - float y_final = y_rot_y * cos_x - z_rot_y * sin_x; - float z_final = y_rot_y * sin_x + z_rot_y * cos_x; + float y_final = (y_rot_y * cos_x) - (z_rot_y * sin_x); + float z_final = (y_rot_y * sin_x) + (z_rot_y * cos_x); // Retornar coordenadas finales rotadas x = x_final; @@ -76,7 +79,7 @@ void CubeShape::getPoint3D(int index, float& x, float& y, float& z) const { z = z_final; } -float CubeShape::getScaleFactor(float screen_height) const { +auto CubeShape::getScaleFactor(float screen_height) const -> float { // Factor de escala para física: proporcional al tamaño del cubo // Tamaño base = 60px (resolución 320x240, factor 0.25) const float BASE_SIZE = 60.0f; @@ -105,12 +108,24 @@ void CubeShape::generateVerticesAndCenters() { // 2. Añadir 6 centros de caras // Caras: X=±size (Y,Z varían), Y=±size (X,Z varían), Z=±size (X,Y varían) - base_x_.push_back(size_); base_y_.push_back(0); base_z_.push_back(0); // +X - base_x_.push_back(-size_); base_y_.push_back(0); base_z_.push_back(0); // -X - base_x_.push_back(0); base_y_.push_back(size_); base_z_.push_back(0); // +Y - base_x_.push_back(0); base_y_.push_back(-size_);base_z_.push_back(0); // -Y - base_x_.push_back(0); base_y_.push_back(0); base_z_.push_back(size_); // +Z - base_x_.push_back(0); base_y_.push_back(0); base_z_.push_back(-size_); // -Z + base_x_.push_back(size_); + base_y_.push_back(0); + base_z_.push_back(0); // +X + base_x_.push_back(-size_); + base_y_.push_back(0); + base_z_.push_back(0); // -X + base_x_.push_back(0); + base_y_.push_back(size_); + base_z_.push_back(0); // +Y + base_x_.push_back(0); + base_y_.push_back(-size_); + base_z_.push_back(0); // -Y + base_x_.push_back(0); + base_y_.push_back(0); + base_z_.push_back(size_); // +Z + base_x_.push_back(0); + base_y_.push_back(0); + base_z_.push_back(-size_); // -Z // 3. Añadir 12 centros de aristas // Aristas paralelas a X (4), Y (4), Z (4) @@ -143,16 +158,16 @@ void CubeShape::generateVerticesAndCenters() { void CubeShape::generateVolumetricGrid() { // Calcular dimensión del grid cúbico: N³ ≈ num_points int grid_dim = static_cast(ceilf(cbrtf(static_cast(num_points_)))); - if (grid_dim < 3) grid_dim = 3; // Mínimo grid 3x3x3 + grid_dim = std::max(grid_dim, 3); // Mínimo grid 3x3x3 float step = (2.0f * size_) / (grid_dim - 1); // Espacio entre puntos for (int ix = 0; ix < grid_dim; ix++) { for (int iy = 0; iy < grid_dim; iy++) { for (int iz = 0; iz < grid_dim; iz++) { - float x = -size_ + ix * step; - float y = -size_ + iy * step; - float z = -size_ + iz * step; + float x = -size_ + (ix * step); + float y = -size_ + (iy * step); + float z = -size_ + (iz * step); base_x_.push_back(x); base_y_.push_back(y); diff --git a/source/shapes/cube_shape.hpp b/source/shapes/cube_shape.hpp index 5613b40..f8a9c01 100644 --- a/source/shapes/cube_shape.hpp +++ b/source/shapes/cube_shape.hpp @@ -1,8 +1,9 @@ #pragma once -#include "shape.hpp" #include +#include "shape.hpp" + // Figura: Cubo 3D rotante // Distribución: // - 1-8 pelotas: Solo vértices (8 puntos) @@ -10,28 +11,28 @@ // - 27+ pelotas: Grid volumétrico 3D uniforme // Comportamiento: Rotación simultánea en ejes X, Y, Z (efecto Rubik) class CubeShape : public Shape { -private: - float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad) - float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) - float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad) - float size_ = 0.0f; // Mitad del lado del cubo (píxeles) - int num_points_ = 0; // Cantidad de puntos generados + private: + float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad) + float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) + float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad) + float size_ = 0.0f; // Mitad del lado del cubo (píxeles) + int num_points_ = 0; // Cantidad de puntos generados - // Posiciones base 3D (sin rotar) - se calculan en generatePoints() - std::vector base_x_; - std::vector base_y_; - std::vector base_z_; + // Posiciones base 3D (sin rotar) - se calculan en generatePoints() + std::vector base_x_; + std::vector base_y_; + std::vector base_z_; -public: - void generatePoints(int num_points, float screen_width, float screen_height) override; - void update(float delta_time, float screen_width, float screen_height) override; - void getPoint3D(int index, float& x, float& y, float& z) const override; - const char* getName() const override { return "CUBE"; } - float getScaleFactor(float screen_height) const override; + public: + void generatePoints(int num_points, float screen_width, float screen_height) override; + void update(float delta_time, float screen_width, float screen_height) override; + void getPoint3D(int index, float& x, float& y, float& z) const override; + const char* getName() const override { return "CUBE"; } + float getScaleFactor(float screen_height) const override; -private: - // Métodos auxiliares para distribución de puntos - void generateVertices(); // 8 vértices - void generateVerticesAndCenters(); // 26 puntos (vértices + caras + aristas) - void generateVolumetricGrid(); // Grid 3D para muchas pelotas + private: + // Métodos auxiliares para distribución de puntos + void generateVertices(); // 8 vértices + void generateVerticesAndCenters(); // 26 puntos (vértices + caras + aristas) + void generateVolumetricGrid(); // Grid 3D para muchas pelotas }; diff --git a/source/shapes/cylinder_shape.cpp b/source/shapes/cylinder_shape.cpp index 18b526a..b622f3b 100644 --- a/source/shapes/cylinder_shape.cpp +++ b/source/shapes/cylinder_shape.cpp @@ -1,8 +1,11 @@ #include "cylinder_shape.hpp" -#include "defines.hpp" + +#include #include #include // Para rand() +#include "defines.hpp" + void CylinderShape::generatePoints(int num_points, float screen_width, float screen_height) { num_points_ = num_points; radius_ = screen_height * CYLINDER_RADIUS_FACTOR; @@ -37,7 +40,7 @@ void CylinderShape::update(float delta_time, float screen_width, float screen_he float t = tumble_progress; float ease = t < 0.5f ? 2.0f * t * t - : 1.0f - (-2.0f * t + 2.0f) * (-2.0f * t + 2.0f) / 2.0f; + : 1.0f - ((-2.0f * t + 2.0f) * (-2.0f * t + 2.0f) / 2.0f); angle_x_ = ease * tumble_target_; } } else { @@ -58,10 +61,10 @@ void CylinderShape::getPoint3D(int index, float& x, float& y, float& z) const { // Calcular número de anillos (altura) y puntos por anillo (circunferencia) int num_rings = static_cast(sqrtf(static_cast(num_points_) * 0.5f)); - if (num_rings < 2) num_rings = 2; + num_rings = std::max(num_rings, 2); int points_per_ring = num_points_ / num_rings; - if (points_per_ring < 3) points_per_ring = 3; + points_per_ring = std::max(points_per_ring, 3); // Obtener parámetros u (ángulo) y v (altura) del índice int ring = index / points_per_ring; @@ -80,8 +83,10 @@ void CylinderShape::getPoint3D(int index, float& x, float& y, float& z) const { float u = (static_cast(point_in_ring) / static_cast(points_per_ring)) * 2.0f * PI; // Parámetro v (altura normalizada): [-1, 1] - float v = (static_cast(ring) / static_cast(num_rings - 1)) * 2.0f - 1.0f; - if (num_rings == 1) v = 0.0f; + float v = ((static_cast(ring) / static_cast(num_rings - 1)) * 2.0f) - 1.0f; + if (num_rings == 1) { + v = 0.0f; + } // Ecuaciones paramétricas del cilindro // x = radius * cos(u) @@ -94,14 +99,14 @@ void CylinderShape::getPoint3D(int index, float& x, float& y, float& z) const { // Aplicar rotación en eje Y (principal, siempre activa) float cos_y = cosf(angle_y_); float sin_y = sinf(angle_y_); - float x_rot_y = x_base * cos_y - z_base * sin_y; - float z_rot_y = x_base * sin_y + z_base * cos_y; + float x_rot_y = (x_base * cos_y) - (z_base * sin_y); + float z_rot_y = (x_base * sin_y) + (z_base * cos_y); // Aplicar rotación en eje X (tumbling ocasional) float cos_x = cosf(angle_x_); float sin_x = sinf(angle_x_); - float y_rot = y_base * cos_x - z_rot_y * sin_x; - float z_rot = y_base * sin_x + z_rot_y * cos_x; + float y_rot = (y_base * cos_x) - (z_rot_y * sin_x); + float z_rot = (y_base * sin_x) + (z_rot_y * cos_x); // Retornar coordenadas finales con ambas rotaciones x = x_rot_y; @@ -109,7 +114,7 @@ void CylinderShape::getPoint3D(int index, float& x, float& y, float& z) const { z = z_rot; } -float CylinderShape::getScaleFactor(float screen_height) const { +auto CylinderShape::getScaleFactor(float screen_height) const -> float { // Factor de escala para física: proporcional a la dimensión mayor (altura) // Altura base = 120px (0.5 * 240px en resolución 320x240) const float BASE_HEIGHT = 120.0f; diff --git a/source/shapes/cylinder_shape.hpp b/source/shapes/cylinder_shape.hpp index d758574..9f543fe 100644 --- a/source/shapes/cylinder_shape.hpp +++ b/source/shapes/cylinder_shape.hpp @@ -6,23 +6,23 @@ // Comportamiento: Superficie cilíndrica con rotación en eje Y + tumbling ocasional en X/Z // Ecuaciones: x = r*cos(u), y = v, z = r*sin(u) class CylinderShape : public Shape { -private: - float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) - float angle_x_ = 0.0f; // Ángulo de rotación en eje X (tumbling ocasional) - float radius_ = 0.0f; // Radio del cilindro (píxeles) - float height_ = 0.0f; // Altura del cilindro (píxeles) - int num_points_ = 0; // Cantidad de puntos generados + private: + float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) + float angle_x_ = 0.0f; // Ángulo de rotación en eje X (tumbling ocasional) + float radius_ = 0.0f; // Radio del cilindro (píxeles) + float height_ = 0.0f; // Altura del cilindro (píxeles) + int num_points_ = 0; // Cantidad de puntos generados - // Sistema de tumbling ocasional - float tumble_timer_ = 0.0f; // Temporizador para próximo tumble - float tumble_duration_ = 0.0f; // Duración del tumble actual - bool is_tumbling_ = false; // ¿Estamos en modo tumble? - float tumble_target_ = 0.0f; // Ángulo objetivo del tumble + // Sistema de tumbling ocasional + float tumble_timer_ = 0.0f; // Temporizador para próximo tumble + float tumble_duration_ = 0.0f; // Duración del tumble actual + bool is_tumbling_ = false; // ¿Estamos en modo tumble? + float tumble_target_ = 0.0f; // Ángulo objetivo del tumble -public: - void generatePoints(int num_points, float screen_width, float screen_height) override; - void update(float delta_time, float screen_width, float screen_height) override; - void getPoint3D(int index, float& x, float& y, float& z) const override; - const char* getName() const override { return "CYLINDER"; } - float getScaleFactor(float screen_height) const override; + public: + void generatePoints(int num_points, float screen_width, float screen_height) override; + void update(float delta_time, float screen_width, float screen_height) override; + void getPoint3D(int index, float& x, float& y, float& z) const override; + const char* getName() const override { return "CYLINDER"; } + float getScaleFactor(float screen_height) const override; }; diff --git a/source/shapes/helix_shape.cpp b/source/shapes/helix_shape.cpp index d84f32e..5fc38f3 100644 --- a/source/shapes/helix_shape.cpp +++ b/source/shapes/helix_shape.cpp @@ -1,7 +1,9 @@ #include "helix_shape.hpp" -#include "defines.hpp" + #include +#include "defines.hpp" + void HelixShape::generatePoints(int num_points, float screen_width, float screen_height) { num_points_ = num_points; radius_ = screen_height * HELIX_RADIUS_FACTOR; @@ -41,8 +43,8 @@ void HelixShape::getPoint3D(int index, float& x, float& y, float& z) const { // Aplicar rotación en eje Y (horizontal) float cos_y = cosf(angle_y_); float sin_y = sinf(angle_y_); - float x_rot = x_base * cos_y - z_base * sin_y; - float z_rot = x_base * sin_y + z_base * cos_y; + float x_rot = (x_base * cos_y) - (z_base * sin_y); + float z_rot = (x_base * sin_y) + (z_base * cos_y); // Retornar coordenadas finales x = x_rot; @@ -50,7 +52,7 @@ void HelixShape::getPoint3D(int index, float& x, float& y, float& z) const { z = z_rot; } -float HelixShape::getScaleFactor(float screen_height) const { +auto HelixShape::getScaleFactor(float screen_height) const -> float { // Factor de escala para física: proporcional a la dimensión mayor (altura total) // Altura base = 180px para 3 vueltas con pitch=0.25 en 240px de altura (180 = 240 * 0.25 * 3) const float BASE_HEIGHT = 180.0f; diff --git a/source/shapes/helix_shape.hpp b/source/shapes/helix_shape.hpp index 6ffabcb..2bf22f0 100644 --- a/source/shapes/helix_shape.hpp +++ b/source/shapes/helix_shape.hpp @@ -6,18 +6,18 @@ // Comportamiento: Rotación en eje Y + animación de fase vertical // Ecuaciones: x = r*cos(t), y = pitch*t + phase, z = r*sin(t) class HelixShape : public Shape { -private: - float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) - float phase_offset_ = 0.0f; // Offset de fase para animación vertical (rad) - float radius_ = 0.0f; // Radio de la espiral (píxeles) - float pitch_ = 0.0f; // Separación vertical entre vueltas (píxeles) - float total_height_ = 0.0f; // Altura total de la espiral (píxeles) - int num_points_ = 0; // Cantidad de puntos generados + private: + float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) + float phase_offset_ = 0.0f; // Offset de fase para animación vertical (rad) + float radius_ = 0.0f; // Radio de la espiral (píxeles) + float pitch_ = 0.0f; // Separación vertical entre vueltas (píxeles) + float total_height_ = 0.0f; // Altura total de la espiral (píxeles) + int num_points_ = 0; // Cantidad de puntos generados -public: - void generatePoints(int num_points, float screen_width, float screen_height) override; - void update(float delta_time, float screen_width, float screen_height) override; - void getPoint3D(int index, float& x, float& y, float& z) const override; - const char* getName() const override { return "HELIX"; } - float getScaleFactor(float screen_height) const override; + public: + void generatePoints(int num_points, float screen_width, float screen_height) override; + void update(float delta_time, float screen_width, float screen_height) override; + void getPoint3D(int index, float& x, float& y, float& z) const override; + const char* getName() const override { return "HELIX"; } + float getScaleFactor(float screen_height) const override; }; diff --git a/source/shapes/icosahedron_shape.cpp b/source/shapes/icosahedron_shape.cpp index a94f82c..2091411 100644 --- a/source/shapes/icosahedron_shape.cpp +++ b/source/shapes/icosahedron_shape.cpp @@ -1,8 +1,12 @@ #include "icosahedron_shape.hpp" -#include "defines.hpp" + +#include +#include #include #include +#include "defines.hpp" + void IcosahedronShape::generatePoints(int num_points, float screen_width, float screen_height) { num_points_ = num_points; radius_ = screen_height * ICOSAHEDRON_RADIUS_FACTOR; @@ -21,37 +25,36 @@ void IcosahedronShape::update(float delta_time, float screen_width, float screen void IcosahedronShape::getPoint3D(int index, float& x, float& y, float& z) const { // Proporción áurea (golden ratio) - const float phi = (1.0f + sqrtf(5.0f)) / 2.0f; + const float PHI = (1.0f + sqrtf(5.0f)) / 2.0f; // 12 vértices del icosaedro regular normalizado // Basados en 3 rectángulos áureos ortogonales - static const float vertices[12][3] = { + const std::array, 12> VERTICES = {{ // Rectángulo XY - {-1.0f, phi, 0.0f}, - { 1.0f, phi, 0.0f}, - {-1.0f, -phi, 0.0f}, - { 1.0f, -phi, 0.0f}, + {-1.0f, PHI, 0.0f}, + {1.0f, PHI, 0.0f}, + {-1.0f, -PHI, 0.0f}, + {1.0f, -PHI, 0.0f}, // Rectángulo YZ - { 0.0f, -1.0f, phi}, - { 0.0f, 1.0f, phi}, - { 0.0f, -1.0f, -phi}, - { 0.0f, 1.0f, -phi}, + {0.0f, -1.0f, PHI}, + {0.0f, 1.0f, PHI}, + {0.0f, -1.0f, -PHI}, + {0.0f, 1.0f, -PHI}, // Rectángulo ZX - { phi, 0.0f, -1.0f}, - { phi, 0.0f, 1.0f}, - {-phi, 0.0f, -1.0f}, - {-phi, 0.0f, 1.0f} - }; + {PHI, 0.0f, -1.0f}, + {PHI, 0.0f, 1.0f}, + {-PHI, 0.0f, -1.0f}, + {-PHI, 0.0f, 1.0f}}}; // Normalizar para esfera circunscrita - const float normalization = sqrtf(1.0f + phi * phi); + const float NORMALIZATION = sqrtf(1.0f + (PHI * PHI)); // Si tenemos 12 o menos puntos, usar solo vértices if (num_points_ <= 12) { int vertex_index = index % 12; - float x_base = vertices[vertex_index][0] / normalization * radius_; - float y_base = vertices[vertex_index][1] / normalization * radius_; - float z_base = vertices[vertex_index][2] / normalization * radius_; + float x_base = VERTICES[vertex_index][0] / NORMALIZATION * radius_; + float y_base = VERTICES[vertex_index][1] / NORMALIZATION * radius_; + float z_base = VERTICES[vertex_index][2] / NORMALIZATION * radius_; // Aplicar rotaciones applyRotations(x_base, y_base, z_base, x, y, z); @@ -62,9 +65,9 @@ void IcosahedronShape::getPoint3D(int index, float& x, float& y, float& z) const // Distribuir puntos entre vértices (primero) y caras (después) if (index < 12) { // Primeros 12 puntos: vértices del icosaedro - float x_base = vertices[index][0] / normalization * radius_; - float y_base = vertices[index][1] / normalization * radius_; - float z_base = vertices[index][2] / normalization * radius_; + float x_base = VERTICES[index][0] / NORMALIZATION * radius_; + float y_base = VERTICES[index][1] / NORMALIZATION * radius_; + float z_base = VERTICES[index][2] / NORMALIZATION * radius_; applyRotations(x_base, y_base, z_base, x, y, z); return; } @@ -73,38 +76,55 @@ void IcosahedronShape::getPoint3D(int index, float& x, float& y, float& z) const // El icosaedro tiene 20 caras triangulares int remaining_points = index - 12; int points_per_face = (num_points_ - 12) / 20; - if (points_per_face < 1) points_per_face = 1; + points_per_face = std::max(points_per_face, 1); int face_index = remaining_points / points_per_face; - if (face_index >= 20) face_index = 19; + if (face_index >= 20) { + face_index = 19; + } int point_in_face = remaining_points % points_per_face; // Definir algunas caras del icosaedro (usando índices de vértices) // Solo necesitamos generar puntos, no renderizar caras completas - static const int faces[20][3] = { - {0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11}, - {1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8}, - {3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9}, - {4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1} - }; + static constexpr std::array, 20> FACES = {{ + {0, 11, 5}, + {0, 5, 1}, + {0, 1, 7}, + {0, 7, 10}, + {0, 10, 11}, + {1, 5, 9}, + {5, 11, 4}, + {11, 10, 2}, + {10, 7, 6}, + {7, 1, 8}, + {3, 9, 4}, + {3, 4, 2}, + {3, 2, 6}, + {3, 6, 8}, + {3, 8, 9}, + {4, 9, 5}, + {2, 4, 11}, + {6, 2, 10}, + {8, 6, 7}, + {9, 8, 1}}}; // Obtener vértices de la cara - int v0 = faces[face_index][0]; - int v1 = faces[face_index][1]; - int v2 = faces[face_index][2]; + int v0 = FACES[face_index][0]; + int v1 = FACES[face_index][1]; + int v2 = FACES[face_index][2]; // Interpolar dentro del triángulo usando coordenadas baricéntricas simples float t = static_cast(point_in_face) / static_cast(points_per_face + 1); float u = sqrtf(t); float v = t - u; - float x_interp = vertices[v0][0] * (1.0f - u - v) + vertices[v1][0] * u + vertices[v2][0] * v; - float y_interp = vertices[v0][1] * (1.0f - u - v) + vertices[v1][1] * u + vertices[v2][1] * v; - float z_interp = vertices[v0][2] * (1.0f - u - v) + vertices[v1][2] * u + vertices[v2][2] * v; + float x_interp = (VERTICES[v0][0] * (1.0f - u - v)) + (VERTICES[v1][0] * u) + (VERTICES[v2][0] * v); + float y_interp = (VERTICES[v0][1] * (1.0f - u - v)) + (VERTICES[v1][1] * u) + (VERTICES[v2][1] * v); + float z_interp = (VERTICES[v0][2] * (1.0f - u - v)) + (VERTICES[v1][2] * u) + (VERTICES[v2][2] * v); // Proyectar a la esfera - float len = sqrtf(x_interp * x_interp + y_interp * y_interp + z_interp * z_interp); + float len = sqrtf((x_interp * x_interp) + (y_interp * y_interp) + (z_interp * z_interp)); if (len > 0.0001f) { x_interp /= len; y_interp /= len; @@ -122,27 +142,27 @@ void IcosahedronShape::applyRotations(float x_in, float y_in, float z_in, float& // Aplicar rotación en eje X float cos_x = cosf(angle_x_); float sin_x = sinf(angle_x_); - float y_rot_x = y_in * cos_x - z_in * sin_x; - float z_rot_x = y_in * sin_x + z_in * cos_x; + float y_rot_x = (y_in * cos_x) - (z_in * sin_x); + float z_rot_x = (y_in * sin_x) + (z_in * cos_x); // Aplicar rotación en eje Y float cos_y = cosf(angle_y_); float sin_y = sinf(angle_y_); - float x_rot_y = x_in * cos_y - z_rot_x * sin_y; - float z_rot_y = x_in * sin_y + z_rot_x * cos_y; + float x_rot_y = (x_in * cos_y) - (z_rot_x * sin_y); + float z_rot_y = (x_in * sin_y) + (z_rot_x * cos_y); // Aplicar rotación en eje Z float cos_z = cosf(angle_z_); float sin_z = sinf(angle_z_); - float x_final = x_rot_y * cos_z - y_rot_x * sin_z; - float y_final = x_rot_y * sin_z + y_rot_x * cos_z; + float x_final = (x_rot_y * cos_z) - (y_rot_x * sin_z); + float y_final = (x_rot_y * sin_z) + (y_rot_x * cos_z); x_out = x_final; y_out = y_final; z_out = z_rot_y; } -float IcosahedronShape::getScaleFactor(float screen_height) const { +auto IcosahedronShape::getScaleFactor(float screen_height) const -> float { // Factor de escala para física: proporcional al radio // Radio base = 72px (0.30 * 240px en resolución 320x240) const float BASE_RADIUS = 72.0f; diff --git a/source/shapes/icosahedron_shape.hpp b/source/shapes/icosahedron_shape.hpp index 1a4a3f8..4e742fd 100644 --- a/source/shapes/icosahedron_shape.hpp +++ b/source/shapes/icosahedron_shape.hpp @@ -6,20 +6,20 @@ // Comportamiento: 12 vértices distribuidos uniformemente con rotación triple // Geometría: Basado en proporción áurea (golden ratio) class IcosahedronShape : public Shape { -private: - float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad) - float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) - float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad) - float radius_ = 0.0f; // Radio de la esfera circunscrita (píxeles) - int num_points_ = 0; // Cantidad de puntos generados + private: + float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad) + float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) + float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad) + float radius_ = 0.0f; // Radio de la esfera circunscrita (píxeles) + int num_points_ = 0; // Cantidad de puntos generados - // Helper para aplicar rotaciones triple XYZ - void applyRotations(float x_in, float y_in, float z_in, float& x_out, float& y_out, float& z_out) const; + // Helper para aplicar rotaciones triple XYZ + void applyRotations(float x_in, float y_in, float z_in, float& x_out, float& y_out, float& z_out) const; -public: - void generatePoints(int num_points, float screen_width, float screen_height) override; - void update(float delta_time, float screen_width, float screen_height) override; - void getPoint3D(int index, float& x, float& y, float& z) const override; - const char* getName() const override { return "ICOSAHEDRON"; } - float getScaleFactor(float screen_height) const override; + public: + void generatePoints(int num_points, float screen_width, float screen_height) override; + void update(float delta_time, float screen_width, float screen_height) override; + void getPoint3D(int index, float& x, float& y, float& z) const override; + const char* getName() const override { return "ICOSAHEDRON"; } + float getScaleFactor(float screen_height) const override; }; diff --git a/source/shapes/lissajous_shape.cpp b/source/shapes/lissajous_shape.cpp index 04d8763..89496b6 100644 --- a/source/shapes/lissajous_shape.cpp +++ b/source/shapes/lissajous_shape.cpp @@ -1,7 +1,9 @@ #include "lissajous_shape.hpp" -#include "defines.hpp" + #include +#include "defines.hpp" + void LissajousShape::generatePoints(int num_points, float screen_width, float screen_height) { num_points_ = num_points; amplitude_ = screen_height * LISSAJOUS_SIZE_FACTOR; @@ -33,21 +35,21 @@ void LissajousShape::getPoint3D(int index, float& x, float& y, float& z) const { // x(t) = A * sin(freq_x * t + phase_x) // y(t) = A * sin(freq_y * t) // z(t) = A * sin(freq_z * t + phase_z) - float x_local = amplitude_ * sinf(freq_x_ * t + phase_x_); + float x_local = amplitude_ * sinf((freq_x_ * t) + phase_x_); float y_local = amplitude_ * sinf(freq_y_ * t); - float z_local = amplitude_ * sinf(freq_z_ * t + phase_z_); + float z_local = amplitude_ * sinf((freq_z_ * t) + phase_z_); // Aplicar rotación global en eje X float cos_x = cosf(rotation_x_); float sin_x = sinf(rotation_x_); - float y_rot = y_local * cos_x - z_local * sin_x; - float z_rot = y_local * sin_x + z_local * cos_x; + float y_rot = (y_local * cos_x) - (z_local * sin_x); + float z_rot = (y_local * sin_x) + (z_local * cos_x); // Aplicar rotación global en eje Y float cos_y = cosf(rotation_y_); float sin_y = sinf(rotation_y_); - float x_final = x_local * cos_y - z_rot * sin_y; - float z_final = x_local * sin_y + z_rot * cos_y; + float x_final = (x_local * cos_y) - (z_rot * sin_y); + float z_final = (x_local * sin_y) + (z_rot * cos_y); // Retornar coordenadas rotadas x = x_final; @@ -55,7 +57,7 @@ void LissajousShape::getPoint3D(int index, float& x, float& y, float& z) const { z = z_final; } -float LissajousShape::getScaleFactor(float screen_height) const { +auto LissajousShape::getScaleFactor(float screen_height) const -> float { // Factor de escala para física: proporcional a la amplitud de la curva // Amplitud base = 84px (0.35 * 240px en resolución 320x240) const float BASE_SIZE = 84.0f; diff --git a/source/shapes/lissajous_shape.hpp b/source/shapes/lissajous_shape.hpp index 53378de..ed6143f 100644 --- a/source/shapes/lissajous_shape.hpp +++ b/source/shapes/lissajous_shape.hpp @@ -6,21 +6,21 @@ // Comportamiento: Curva paramétrica 3D con rotación global y animación de fase // Ecuaciones: x(t) = A*sin(freq_x*t + phase_x), y(t) = A*sin(freq_y*t), z(t) = A*sin(freq_z*t + phase_z) class LissajousShape : public Shape { -private: - float freq_x_ = 0.0f; // Frecuencia en eje X - float freq_y_ = 0.0f; // Frecuencia en eje Y - float freq_z_ = 0.0f; // Frecuencia en eje Z - float phase_x_ = 0.0f; // Desfase X (animado) - float phase_z_ = 0.0f; // Desfase Z (animado) - float rotation_x_ = 0.0f; // Rotación global en eje X (rad) - float rotation_y_ = 0.0f; // Rotación global en eje Y (rad) - float amplitude_ = 0.0f; // Amplitud de la curva (píxeles) - int num_points_ = 0; // Cantidad total de puntos + private: + float freq_x_ = 0.0f; // Frecuencia en eje X + float freq_y_ = 0.0f; // Frecuencia en eje Y + float freq_z_ = 0.0f; // Frecuencia en eje Z + float phase_x_ = 0.0f; // Desfase X (animado) + float phase_z_ = 0.0f; // Desfase Z (animado) + float rotation_x_ = 0.0f; // Rotación global en eje X (rad) + float rotation_y_ = 0.0f; // Rotación global en eje Y (rad) + float amplitude_ = 0.0f; // Amplitud de la curva (píxeles) + int num_points_ = 0; // Cantidad total de puntos -public: - void generatePoints(int num_points, float screen_width, float screen_height) override; - void update(float delta_time, float screen_width, float screen_height) override; - void getPoint3D(int index, float& x, float& y, float& z) const override; - const char* getName() const override { return "LISSAJOUS"; } - float getScaleFactor(float screen_height) const override; + public: + void generatePoints(int num_points, float screen_width, float screen_height) override; + void update(float delta_time, float screen_width, float screen_height) override; + void getPoint3D(int index, float& x, float& y, float& z) const override; + const char* getName() const override { return "LISSAJOUS"; } + float getScaleFactor(float screen_height) const override; }; diff --git a/source/shapes/png_shape.cpp b/source/shapes/png_shape.cpp index a3eb156..e3371c3 100644 --- a/source/shapes/png_shape.cpp +++ b/source/shapes/png_shape.cpp @@ -1,16 +1,18 @@ #include "png_shape.hpp" + +#include +#include +#include +#include + #include "defines.hpp" #include "external/stb_image.h" #include "resource_manager.hpp" -#include -#include -#include -#include PNGShape::PNGShape(const char* png_path) { // Cargar PNG desde path if (!loadPNG(png_path)) { - std::cerr << "[PNGShape] Usando fallback 10x10" << std::endl; + std::cerr << "[PNGShape] Usando fallback 10x10" << '\n'; // Fallback: generar un cuadrado simple si falla la carga image_width_ = 10; image_height_ = 10; @@ -21,7 +23,7 @@ PNGShape::PNGShape(const char* png_path) { next_idle_time_ = PNG_IDLE_TIME_MIN + (rand() % 1000) / 1000.0f * (PNG_IDLE_TIME_MAX - PNG_IDLE_TIME_MIN); } -bool PNGShape::loadPNG(const char* resource_key) { +auto PNGShape::loadPNG(const char* resource_key) -> bool { { std::string fn = std::string(resource_key); fn = fn.substr(fn.find_last_of("\\/") + 1); @@ -30,15 +32,16 @@ bool PNGShape::loadPNG(const char* resource_key) { unsigned char* file_data = nullptr; size_t file_size = 0; if (!ResourceManager::loadResource(resource_key, file_data, file_size)) { - std::cerr << "[PNGShape] ERROR: recurso no encontrado: " << resource_key << std::endl; + std::cerr << "[PNGShape] ERROR: recurso no encontrado: " << resource_key << '\n'; return false; } - int width, height, channels; - unsigned char* pixels = stbi_load_from_memory(file_data, static_cast(file_size), - &width, &height, &channels, 1); + int width; + int height; + int channels; + unsigned char* pixels = stbi_load_from_memory(file_data, static_cast(file_size), &width, &height, &channels, 1); delete[] file_data; - if (!pixels) { - std::cerr << "[PNGShape] ERROR al decodificar PNG: " << stbi_failure_reason() << std::endl; + if (pixels == nullptr) { + std::cerr << "[PNGShape] ERROR al decodificar PNG: " << stbi_failure_reason() << '\n'; return false; } image_width_ = width; @@ -57,9 +60,11 @@ void PNGShape::detectEdges() { // Detectar píxeles del contorno (píxeles blancos con al menos un vecino negro) for (int y = 0; y < image_height_; y++) { for (int x = 0; x < image_width_; x++) { - int idx = y * image_width_ + x; + int idx = (y * image_width_) + x; - if (!pixel_data_[idx]) continue; // Solo píxeles blancos + if (!pixel_data_[idx]) { + continue; // Solo píxeles blancos + } // Verificar vecinos (arriba, abajo, izq, der) bool is_edge = false; @@ -68,10 +73,10 @@ void PNGShape::detectEdges() { is_edge = true; // Bordes de la imagen } else { // Verificar 4 vecinos - if (!pixel_data_[idx - 1] || // Izquierda - !pixel_data_[idx + 1] || // Derecha - !pixel_data_[idx - image_width_] || // Arriba - !pixel_data_[idx + image_width_]) { // Abajo + if (!pixel_data_[idx - 1] || // Izquierda + !pixel_data_[idx + 1] || // Derecha + !pixel_data_[idx - image_width_] || // Arriba + !pixel_data_[idx + image_width_]) { // Abajo is_edge = true; } } @@ -90,7 +95,7 @@ void PNGShape::floodFill() { for (int y = 0; y < image_height_; y++) { for (int x = 0; x < image_width_; x++) { - int idx = y * image_width_ + x; + int idx = (y * image_width_) + x; if (pixel_data_[idx]) { filled_points_.push_back({static_cast(x), static_cast(y)}); } @@ -114,8 +119,8 @@ void PNGShape::generatePoints(int num_points, float screen_width, float screen_h num_layers_ = PNG_NUM_EXTRUSION_LAYERS; // Generar AMBOS conjuntos de puntos (relleno Y bordes) - floodFill(); // Generar filled_points_ - detectEdges(); // Generar edge_points_ + floodFill(); // Generar filled_points_ + detectEdges(); // Generar edge_points_ // Guardar copias originales (las funciones de filtrado modifican los vectores) std::vector filled_points_original = filled_points_; @@ -123,7 +128,7 @@ void PNGShape::generatePoints(int num_points, float screen_width, float screen_h // Conjunto de puntos ACTIVO (será modificado por filtros) std::vector active_points_data; - std::string mode_name = ""; + std::string mode_name; // === SISTEMA DE DISTRIBUCIÓN ADAPTATIVA === // Estrategia: Optimizar según número de pelotas disponibles @@ -196,8 +201,6 @@ void PNGShape::generatePoints(int num_points, float screen_width, float screen_h std::vector vertices = extractCornerVertices(source_for_vertices); if (!vertices.empty() && vertices.size() < active_points_data.size()) { active_points_data = vertices; - num_2d_points = active_points_data.size(); - total_3d_points = num_2d_points * num_layers_; mode_name = "VÉRTICES"; } } @@ -216,7 +219,7 @@ void PNGShape::generatePoints(int num_points, float screen_width, float screen_h // Extraer filas alternas de puntos (FUNCIÓN PURA: no modifica parámetros) // Recibe vector original y devuelve nuevo vector filtrado -std::vector PNGShape::extractAlternateRows(const std::vector& source, int row_skip) { +auto PNGShape::extractAlternateRows(const std::vector& source, int row_skip) -> std::vector { std::vector result; if (row_skip <= 1 || source.empty()) { @@ -243,7 +246,7 @@ std::vector PNGShape::extractAlternateRows(const std::vector< } // Extraer vértices y esquinas (FUNCIÓN PURA: devuelve nuevo vector) -std::vector PNGShape::extractCornerVertices(const std::vector& source) { +auto PNGShape::extractCornerVertices(const std::vector& source) -> std::vector { std::vector result; if (source.empty()) { @@ -267,9 +270,9 @@ std::vector PNGShape::extractCornerVertices(const std::vector // Generar puntos en extremos de cada fila for (const auto& [row_y, extremes] : row_extremes) { - result.push_back({extremes.first, static_cast(row_y)}); // Extremo izquierdo - if (extremes.second != extremes.first) { // Solo añadir derecho si es diferente - result.push_back({extremes.second, static_cast(row_y)}); // Extremo derecho + result.push_back({extremes.first, static_cast(row_y)}); // Extremo izquierdo + if (extremes.second != extremes.first) { // Solo añadir derecho si es diferente + result.push_back({extremes.second, static_cast(row_y)}); // Extremo derecho } } @@ -376,8 +379,8 @@ void PNGShape::getPoint3D(int index, float& x, float& y, float& z) const { float v = y_base / (logo_size * 0.5f); // Calcular pivoteo (amplitudes más grandes) - float tilt_amount_x = sinf(tilt_x_) * 0.15f; // 15% - float tilt_amount_y = sinf(tilt_y_) * 0.1f; // 10% + float tilt_amount_x = sinf(tilt_x_) * 0.15f; // 15% + float tilt_amount_y = sinf(tilt_y_) * 0.1f; // 10% // Aplicar pivoteo proporcional al tamaño del logo float z_tilt = (u * tilt_amount_y + v * tilt_amount_x) * logo_size; @@ -386,14 +389,14 @@ void PNGShape::getPoint3D(int index, float& x, float& y, float& z) const { // Aplicar rotación en eje Y (horizontal) float cos_y = cosf(angle_y_); float sin_y = sinf(angle_y_); - float x_rot_y = x_base * cos_y - z_base * sin_y; - float z_rot_y = x_base * sin_y + z_base * cos_y; + float x_rot_y = (x_base * cos_y) - (z_base * sin_y); + float z_rot_y = (x_base * sin_y) + (z_base * cos_y); // Aplicar rotación en eje X (vertical) float cos_x = cosf(angle_x_); float sin_x = sinf(angle_x_); - float y_rot = y_base * cos_x - z_rot_y * sin_x; - float z_rot = y_base * sin_x + z_rot_y * cos_x; + float y_rot = (y_base * cos_x) - (z_rot_y * sin_x); + float z_rot = (y_base * sin_x) + (z_rot_y * cos_x); // Retornar coordenadas finales x = x_rot_y; @@ -408,7 +411,7 @@ void PNGShape::getPoint3D(int index, float& x, float& y, float& z) const { } } -float PNGShape::getScaleFactor(float screen_height) const { +auto PNGShape::getScaleFactor(float screen_height) const -> float { // Escala dinámica según resolución return PNG_SIZE_FACTOR; } @@ -432,7 +435,7 @@ void PNGShape::setConvergence(float convergence) { } // Obtener progreso del flip actual (0.0 = inicio del flip, 1.0 = fin del flip) -float PNGShape::getFlipProgress() const { +auto PNGShape::getFlipProgress() const -> float { if (!is_flipping_) { return 0.0f; // No está flipping, progreso = 0 } diff --git a/source/shapes/png_shape.hpp b/source/shapes/png_shape.hpp index 7accb92..ad775e1 100644 --- a/source/shapes/png_shape.hpp +++ b/source/shapes/png_shape.hpp @@ -1,104 +1,108 @@ #pragma once -#include "shape.hpp" -#include "defines.hpp" // Para PNG_IDLE_TIME_MIN/MAX constantes +#include // Para rand() #include -#include // Para rand() + +#include "defines.hpp" // Para PNG_IDLE_TIME_MIN/MAX constantes +#include "shape.hpp" // Figura: Shape generada desde PNG 1-bit (blanco sobre negro) // Enfoque A: Extrusión 2D (implementado) // Enfoque B: Voxelización 3D (preparado para futuro) class PNGShape : public Shape { -private: - // Datos de la imagen cargada - int image_width_ = 0; - int image_height_ = 0; - std::vector pixel_data_; // Mapa de píxeles blancos (true = blanco) + private: + // Datos de la imagen cargada + int image_width_ = 0; + int image_height_ = 0; + std::vector pixel_data_; // Mapa de píxeles blancos (true = blanco) - // Puntos generados (Enfoque A: Extrusión 2D) - struct Point2D { - float x, y; - }; - std::vector edge_points_; // Contorno (solo bordes) - ORIGINAL sin optimizar - std::vector filled_points_; // Relleno completo - ORIGINAL sin optimizar - std::vector optimized_points_; // Puntos finales optimizados (usado por getPoint3D) + // Puntos generados (Enfoque A: Extrusión 2D) + struct Point2D { + float x, y; + }; + std::vector edge_points_; // Contorno (solo bordes) - ORIGINAL sin optimizar + std::vector filled_points_; // Relleno completo - ORIGINAL sin optimizar + std::vector optimized_points_; // Puntos finales optimizados (usado por getPoint3D) - // Parámetros de extrusión - float extrusion_depth_ = 0.0f; // Profundidad de extrusión en Z - int num_layers_ = 0; // Capas de extrusión (más capas = más denso) + // Parámetros de extrusión + float extrusion_depth_ = 0.0f; // Profundidad de extrusión en Z + int num_layers_ = 0; // Capas de extrusión (más capas = más denso) - // Rotación "legible" (de frente con volteretas ocasionales) - float angle_x_ = 0.0f; - float angle_y_ = 0.0f; - float idle_timer_ = 0.0f; // Timer para tiempo de frente - float flip_timer_ = 0.0f; // Timer para voltereta - float next_idle_time_ = 5.0f; // Próximo tiempo de espera (aleatorio) - bool is_flipping_ = false; // Estado: quieto o voltereta - int flip_axis_ = 0; // Eje de voltereta (0=X, 1=Y, 2=ambos) + // Rotación "legible" (de frente con volteretas ocasionales) + float angle_x_ = 0.0f; + float angle_y_ = 0.0f; + float idle_timer_ = 0.0f; // Timer para tiempo de frente + float flip_timer_ = 0.0f; // Timer para voltereta + float next_idle_time_ = 5.0f; // Próximo tiempo de espera (aleatorio) + bool is_flipping_ = false; // Estado: quieto o voltereta + int flip_axis_ = 0; // Eje de voltereta (0=X, 1=Y, 2=ambos) - // Pivoteo sutil en estado IDLE - float tilt_x_ = 0.0f; // Oscilación sutil en eje X - float tilt_y_ = 0.0f; // Oscilación sutil en eje Y + // Pivoteo sutil en estado IDLE + float tilt_x_ = 0.0f; // Oscilación sutil en eje X + float tilt_y_ = 0.0f; // Oscilación sutil en eje Y - // Modo LOGO (intervalos de flip más largos) - bool is_logo_mode_ = false; // true = usar intervalos LOGO (más lentos) + // Modo LOGO (intervalos de flip más largos) + bool is_logo_mode_ = false; // true = usar intervalos LOGO (más lentos) - // Sistema de convergencia (solo relevante en modo LOGO) - float current_convergence_ = 0.0f; // Porcentaje actual de convergencia (0.0-1.0) - bool convergence_threshold_reached_ = false; // true si ha alcanzado umbral mínimo (80%) + // Sistema de convergencia (solo relevante en modo LOGO) + float current_convergence_ = 0.0f; // Porcentaje actual de convergencia (0.0-1.0) + bool convergence_threshold_reached_ = false; // true si ha alcanzado umbral mínimo (80%) - // Sistema de tracking de flips (para modo LOGO - espera de flips) - int flip_count_ = 0; // Contador de flips completados (reset al entrar a LOGO) - bool was_flipping_last_frame_ = false; // Estado previo para detectar transiciones + // Sistema de tracking de flips (para modo LOGO - espera de flips) + int flip_count_ = 0; // Contador de flips completados (reset al entrar a LOGO) + bool was_flipping_last_frame_ = false; // Estado previo para detectar transiciones - // Dimensiones normalizadas - float scale_factor_ = 1.0f; - float center_offset_x_ = 0.0f; - float center_offset_y_ = 0.0f; + // Dimensiones normalizadas + float scale_factor_ = 1.0f; + float center_offset_x_ = 0.0f; + float center_offset_y_ = 0.0f; - int num_points_ = 0; // Total de puntos generados (para indexación) + int num_points_ = 0; // Total de puntos generados (para indexación) - // Métodos internos - bool loadPNG(const char* path); // Cargar PNG con stb_image - void detectEdges(); // Detectar contorno (Enfoque A) - void floodFill(); // Rellenar interior (Enfoque B - futuro) - void generateExtrudedPoints(); // Generar puntos con extrusión 2D + // Métodos internos + bool loadPNG(const char* resource_key); // Cargar PNG con stb_image + void detectEdges(); // Detectar contorno (Enfoque A) + void floodFill(); // Rellenar interior (Enfoque B - futuro) + void generateExtrudedPoints(); // Generar puntos con extrusión 2D - // Métodos de distribución adaptativa (funciones puras, no modifican parámetros) - std::vector extractAlternateRows(const std::vector& source, int row_skip); // Extraer filas alternas - std::vector extractCornerVertices(const std::vector& source); // Extraer vértices/esquinas + // Métodos de distribución adaptativa (funciones puras, no modifican parámetros) + static std::vector extractAlternateRows(const std::vector& source, int row_skip); // Extraer filas alternas + static std::vector extractCornerVertices(const std::vector& source); // Extraer vértices/esquinas -public: - // Constructor: recibe path relativo al PNG - PNGShape(const char* png_path = "data/shapes/jailgames.png"); + public: + // Constructor: recibe path relativo al PNG + PNGShape(const char* png_path = "data/shapes/jailgames.png"); - void generatePoints(int num_points, float screen_width, float screen_height) override; - void update(float delta_time, float screen_width, float screen_height) override; - void getPoint3D(int index, float& x, float& y, float& z) const override; - const char* getName() const override { return "PNG SHAPE"; } - float getScaleFactor(float screen_height) const override; + void generatePoints(int num_points, float screen_width, float screen_height) override; + void update(float delta_time, float screen_width, float screen_height) override; + void getPoint3D(int index, float& x, float& y, float& z) const override; + const char* getName() const override { return "PNG SHAPE"; } + float getScaleFactor(float screen_height) const override; - // Consultar estado de flip - bool isFlipping() const { return is_flipping_; } + // Consultar estado de flip + bool isFlipping() const { return is_flipping_; } - // Obtener progreso del flip actual (0.0 = inicio, 1.0 = fin) - float getFlipProgress() const; + // Obtener progreso del flip actual (0.0 = inicio, 1.0 = fin) + float getFlipProgress() const; - // Obtener número de flips completados (para modo LOGO) - int getFlipCount() const { return flip_count_; } + // Obtener número de flips completados (para modo LOGO) + int getFlipCount() const { return flip_count_; } - // Resetear contador de flips (llamar al entrar a LOGO MODE) - void resetFlipCount() { flip_count_ = 0; was_flipping_last_frame_ = false; } + // Resetear contador de flips (llamar al entrar a LOGO MODE) + void resetFlipCount() { + flip_count_ = 0; + was_flipping_last_frame_ = false; + } - // Control de modo LOGO (flip intervals más largos) - void setLogoMode(bool enable) { - is_logo_mode_ = enable; - // Recalcular next_idle_time_ con el rango apropiado - float idle_min = enable ? PNG_IDLE_TIME_MIN_LOGO : PNG_IDLE_TIME_MIN; - float idle_max = enable ? PNG_IDLE_TIME_MAX_LOGO : PNG_IDLE_TIME_MAX; - next_idle_time_ = idle_min + (rand() % 1000) / 1000.0f * (idle_max - idle_min); - } + // Control de modo LOGO (flip intervals más largos) + void setLogoMode(bool enable) { + is_logo_mode_ = enable; + // Recalcular next_idle_time_ con el rango apropiado + float idle_min = enable ? PNG_IDLE_TIME_MIN_LOGO : PNG_IDLE_TIME_MIN; + float idle_max = enable ? PNG_IDLE_TIME_MAX_LOGO : PNG_IDLE_TIME_MAX; + next_idle_time_ = idle_min + (rand() % 1000) / 1000.0f * (idle_max - idle_min); + } - // Sistema de convergencia (override de Shape::setConvergence) - void setConvergence(float convergence) override; + // Sistema de convergencia (override de Shape::setConvergence) + void setConvergence(float convergence) override; }; diff --git a/source/shapes/shape.hpp b/source/shapes/shape.hpp index 7ce132a..82166c8 100644 --- a/source/shapes/shape.hpp +++ b/source/shapes/shape.hpp @@ -2,34 +2,34 @@ // Interfaz abstracta para todas las figuras 3D class Shape { -public: - virtual ~Shape() = default; + public: + virtual ~Shape() = default; - // Generar distribución inicial de puntos en la figura - // num_points: cantidad de pelotas a distribuir - // screen_width/height: dimensiones del área de juego (para escalar) - virtual void generatePoints(int num_points, float screen_width, float screen_height) = 0; + // Generar distribución inicial de puntos en la figura + // num_points: cantidad de pelotas a distribuir + // screen_width/height: dimensiones del área de juego (para escalar) + virtual void generatePoints(int num_points, float screen_width, float screen_height) = 0; - // Actualizar animación de la figura (rotación, deformación, etc.) - // delta_time: tiempo transcurrido desde último frame - // screen_width/height: dimensiones actuales (puede cambiar con F4) - virtual void update(float delta_time, float screen_width, float screen_height) = 0; + // Actualizar animación de la figura (rotación, deformación, etc.) + // delta_time: tiempo transcurrido desde último frame + // screen_width/height: dimensiones actuales (puede cambiar con F4) + virtual void update(float delta_time, float screen_width, float screen_height) = 0; - // Obtener posición 3D del punto i después de transformaciones (rotación, etc.) - // index: índice del punto (0 a num_points-1) - // x, y, z: coordenadas 3D en espacio mundo (centradas en 0,0,0) - virtual void getPoint3D(int index, float& x, float& y, float& z) const = 0; + // Obtener posición 3D del punto i después de transformaciones (rotación, etc.) + // index: índice del punto (0 a num_points-1) + // x, y, z: coordenadas 3D en espacio mundo (centradas en 0,0,0) + virtual void getPoint3D(int index, float& x, float& y, float& z) const = 0; - // Obtener nombre de la figura para debug display - virtual const char* getName() const = 0; + // Obtener nombre de la figura para debug display + virtual const char* getName() const = 0; - // Obtener factor de escala para ajustar física según tamaño de figura - // screen_height: altura actual de pantalla - // Retorna: factor multiplicador para constantes de física (spring_k, damping, etc.) - virtual float getScaleFactor(float screen_height) const = 0; + // Obtener factor de escala para ajustar física según tamaño de figura + // screen_height: altura actual de pantalla + // Retorna: factor multiplicador para constantes de física (spring_k, damping, etc.) + virtual float getScaleFactor(float screen_height) const = 0; - // Notificar a la figura sobre el porcentaje de convergencia (pelotas cerca del objetivo) - // convergence: valor de 0.0 (0%) a 1.0 (100%) indicando cuántas pelotas están en posición - // Default: no-op (la mayoría de figuras no necesitan esta información) - virtual void setConvergence(float convergence) {} + // Notificar a la figura sobre el porcentaje de convergencia (pelotas cerca del objetivo) + // convergence: valor de 0.0 (0%) a 1.0 (100%) indicando cuántas pelotas están en posición + // Default: no-op (la mayoría de figuras no necesitan esta información) + virtual void setConvergence(float convergence) {} }; diff --git a/source/shapes/sphere_shape.cpp b/source/shapes/sphere_shape.cpp index e2dc6ed..7891c4c 100644 --- a/source/shapes/sphere_shape.cpp +++ b/source/shapes/sphere_shape.cpp @@ -1,7 +1,9 @@ #include "sphere_shape.hpp" -#include "defines.hpp" + #include +#include "defines.hpp" + void SphereShape::generatePoints(int num_points, float screen_width, float screen_height) { num_points_ = num_points; radius_ = screen_height * ROTOBALL_RADIUS_FACTOR; @@ -19,12 +21,12 @@ void SphereShape::update(float delta_time, float screen_width, float screen_heig void SphereShape::getPoint3D(int index, float& x, float& y, float& z) const { // Algoritmo Fibonacci Sphere para distribución uniforme - const float golden_ratio = (1.0f + sqrtf(5.0f)) / 2.0f; - const float angle_increment = PI * 2.0f * golden_ratio; + const float GOLDEN_RATIO = (1.0f + sqrtf(5.0f)) / 2.0f; + const float ANGLE_INCREMENT = PI * 2.0f * GOLDEN_RATIO; float t = static_cast(index) / static_cast(num_points_); - float phi = acosf(1.0f - 2.0f * t); // Latitud - float theta = angle_increment * static_cast(index); // Longitud + float phi = acosf(1.0f - (2.0f * t)); // Latitud + float theta = ANGLE_INCREMENT * static_cast(index); // Longitud // Convertir coordenadas esféricas a cartesianas float x_base = cosf(theta) * sinf(phi) * radius_; @@ -34,14 +36,14 @@ void SphereShape::getPoint3D(int index, float& x, float& y, float& z) const { // Aplicar rotación en eje Y float cos_y = cosf(angle_y_); float sin_y = sinf(angle_y_); - float x_rot = x_base * cos_y - z_base * sin_y; - float z_rot = x_base * sin_y + z_base * cos_y; + float x_rot = (x_base * cos_y) - (z_base * sin_y); + float z_rot = (x_base * sin_y) + (z_base * cos_y); // Aplicar rotación en eje X float cos_x = cosf(angle_x_); float sin_x = sinf(angle_x_); - float y_rot = y_base * cos_x - z_rot * sin_x; - float z_final = y_base * sin_x + z_rot * cos_x; + float y_rot = (y_base * cos_x) - (z_rot * sin_x); + float z_final = (y_base * sin_x) + (z_rot * cos_x); // Retornar coordenadas finales rotadas x = x_rot; @@ -49,7 +51,7 @@ void SphereShape::getPoint3D(int index, float& x, float& y, float& z) const { z = z_final; } -float SphereShape::getScaleFactor(float screen_height) const { +auto SphereShape::getScaleFactor(float screen_height) const -> float { // Factor de escala para física: proporcional al radio // Radio base = 80px (resolución 320x240) const float BASE_RADIUS = 80.0f; diff --git a/source/shapes/sphere_shape.hpp b/source/shapes/sphere_shape.hpp index 64914c5..3c6c75a 100644 --- a/source/shapes/sphere_shape.hpp +++ b/source/shapes/sphere_shape.hpp @@ -6,16 +6,16 @@ // Comportamiento: Rotación dual en ejes X e Y // Uso anterior: RotoBall class SphereShape : public Shape { -private: - float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad) - float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) - float radius_ = 0.0f; // Radio de la esfera (píxeles) - int num_points_ = 0; // Cantidad de puntos generados + private: + float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad) + float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) + float radius_ = 0.0f; // Radio de la esfera (píxeles) + int num_points_ = 0; // Cantidad de puntos generados -public: - void generatePoints(int num_points, float screen_width, float screen_height) override; - void update(float delta_time, float screen_width, float screen_height) override; - void getPoint3D(int index, float& x, float& y, float& z) const override; - const char* getName() const override { return "SPHERE"; } - float getScaleFactor(float screen_height) const override; + public: + void generatePoints(int num_points, float screen_width, float screen_height) override; + void update(float delta_time, float screen_width, float screen_height) override; + void getPoint3D(int index, float& x, float& y, float& z) const override; + const char* getName() const override { return "SPHERE"; } + float getScaleFactor(float screen_height) const override; }; diff --git a/source/shapes/torus_shape.cpp b/source/shapes/torus_shape.cpp index fd91825..2e9bee0 100644 --- a/source/shapes/torus_shape.cpp +++ b/source/shapes/torus_shape.cpp @@ -1,7 +1,10 @@ #include "torus_shape.hpp" -#include "defines.hpp" + +#include #include +#include "defines.hpp" + void TorusShape::generatePoints(int num_points, float screen_width, float screen_height) { num_points_ = num_points; major_radius_ = screen_height * TORUS_MAJOR_RADIUS_FACTOR; @@ -26,10 +29,10 @@ void TorusShape::getPoint3D(int index, float& x, float& y, float& z) const { // Calcular número aproximado de anillos y puntos por anillo int num_rings = static_cast(sqrtf(static_cast(num_points_) * 0.5f)); - if (num_rings < 2) num_rings = 2; + num_rings = std::max(num_rings, 2); int points_per_ring = num_points_ / num_rings; - if (points_per_ring < 3) points_per_ring = 3; + points_per_ring = std::max(points_per_ring, 3); // Obtener parámetros u y v del índice int ring = index / points_per_ring; @@ -57,7 +60,7 @@ void TorusShape::getPoint3D(int index, float& x, float& y, float& z) const { float cos_u = cosf(u); float sin_u = sinf(u); - float radius_at_v = major_radius_ + minor_radius_ * cos_v; + float radius_at_v = major_radius_ + (minor_radius_ * cos_v); float x_base = radius_at_v * cos_u; float y_base = radius_at_v * sin_u; @@ -66,20 +69,20 @@ void TorusShape::getPoint3D(int index, float& x, float& y, float& z) const { // Aplicar rotación en eje X float cos_x = cosf(angle_x_); float sin_x = sinf(angle_x_); - float y_rot_x = y_base * cos_x - z_base * sin_x; - float z_rot_x = y_base * sin_x + z_base * cos_x; + float y_rot_x = (y_base * cos_x) - (z_base * sin_x); + float z_rot_x = (y_base * sin_x) + (z_base * cos_x); // Aplicar rotación en eje Y float cos_y = cosf(angle_y_); float sin_y = sinf(angle_y_); - float x_rot_y = x_base * cos_y - z_rot_x * sin_y; - float z_rot_y = x_base * sin_y + z_rot_x * cos_y; + float x_rot_y = (x_base * cos_y) - (z_rot_x * sin_y); + float z_rot_y = (x_base * sin_y) + (z_rot_x * cos_y); // Aplicar rotación en eje Z float cos_z = cosf(angle_z_); float sin_z = sinf(angle_z_); - float x_final = x_rot_y * cos_z - y_rot_x * sin_z; - float y_final = x_rot_y * sin_z + y_rot_x * cos_z; + float x_final = (x_rot_y * cos_z) - (y_rot_x * sin_z); + float y_final = (x_rot_y * sin_z) + (y_rot_x * cos_z); // Retornar coordenadas finales rotadas x = x_final; @@ -87,7 +90,7 @@ void TorusShape::getPoint3D(int index, float& x, float& y, float& z) const { z = z_rot_y; } -float TorusShape::getScaleFactor(float screen_height) const { +auto TorusShape::getScaleFactor(float screen_height) const -> float { // Factor de escala para física: proporcional al radio mayor // Radio mayor base = 60px (0.25 * 240px en resolución 320x240) const float BASE_RADIUS = 60.0f; diff --git a/source/shapes/torus_shape.hpp b/source/shapes/torus_shape.hpp index 3119675..27032f2 100644 --- a/source/shapes/torus_shape.hpp +++ b/source/shapes/torus_shape.hpp @@ -6,18 +6,18 @@ // Comportamiento: Superficie toroidal con rotación triple (X, Y, Z) // Ecuaciones: x = (R + r*cos(v))*cos(u), y = (R + r*cos(v))*sin(u), z = r*sin(v) class TorusShape : public Shape { -private: - float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad) - float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) - float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad) - float major_radius_ = 0.0f; // Radio mayor R (del centro al tubo) - float minor_radius_ = 0.0f; // Radio menor r (grosor del tubo) - int num_points_ = 0; // Cantidad de puntos generados + private: + float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad) + float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad) + float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad) + float major_radius_ = 0.0f; // Radio mayor R (del centro al tubo) + float minor_radius_ = 0.0f; // Radio menor r (grosor del tubo) + int num_points_ = 0; // Cantidad de puntos generados -public: - void generatePoints(int num_points, float screen_width, float screen_height) override; - void update(float delta_time, float screen_width, float screen_height) override; - void getPoint3D(int index, float& x, float& y, float& z) const override; - const char* getName() const override { return "TORUS"; } - float getScaleFactor(float screen_height) const override; + public: + void generatePoints(int num_points, float screen_width, float screen_height) override; + void update(float delta_time, float screen_width, float screen_height) override; + void getPoint3D(int index, float& x, float& y, float& z) const override; + const char* getName() const override { return "TORUS"; } + float getScaleFactor(float screen_height) const override; }; diff --git a/source/shapes_mgr/shape_manager.cpp b/source/shapes_mgr/shape_manager.cpp index 04b1acb..b6cc33a 100644 --- a/source/shapes_mgr/shape_manager.cpp +++ b/source/shapes_mgr/shape_manager.cpp @@ -1,15 +1,15 @@ #include "shape_manager.hpp" -#include // for std::min, std::max, std::transform -#include // for ::tolower -#include // for rand -#include // for std::string +#include // for std::min, std::max, std::transform +#include // for ::tolower +#include // for rand +#include // for std::string -#include "ball.hpp" // for Ball -#include "defines.hpp" // for constantes -#include "scene/scene_manager.hpp" // for SceneManager -#include "state/state_manager.hpp" // for StateManager -#include "ui/ui_manager.hpp" // for UIManager +#include "ball.hpp" // for Ball +#include "defines.hpp" // for constantes +#include "scene/scene_manager.hpp" // for SceneManager +#include "state/state_manager.hpp" // for StateManager +#include "ui/ui_manager.hpp" // for UIManager // Includes de todas las shapes (necesario para creación polimórfica) #include "shapes/atom_shape.hpp" @@ -23,26 +23,24 @@ #include "shapes/torus_shape.hpp" ShapeManager::ShapeManager() - : engine_(nullptr) - , scene_mgr_(nullptr) - , ui_mgr_(nullptr) - , state_mgr_(nullptr) - , current_mode_(SimulationMode::PHYSICS) - , current_shape_type_(ShapeType::SPHERE) - , last_shape_type_(ShapeType::SPHERE) - , active_shape_(nullptr) - , shape_scale_factor_(1.0f) - , depth_zoom_enabled_(true) - , screen_width_(0) - , screen_height_(0) - , shape_convergence_(0.0f) { + : engine_(nullptr), + scene_mgr_(nullptr), + ui_mgr_(nullptr), + state_mgr_(nullptr), + current_mode_(SimulationMode::PHYSICS), + current_shape_type_(ShapeType::SPHERE), + last_shape_type_(ShapeType::SPHERE), + active_shape_(nullptr), + shape_scale_factor_(1.0f), + depth_zoom_enabled_(true), + screen_width_(0), + screen_height_(0), + shape_convergence_(0.0f) { } -ShapeManager::~ShapeManager() { -} +ShapeManager::~ShapeManager() = default; -void ShapeManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, - StateManager* state_mgr, int screen_width, int screen_height) { +void ShapeManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height) { engine_ = engine; scene_mgr_ = scene_mgr; ui_mgr_ = ui_mgr; @@ -66,17 +64,17 @@ void ShapeManager::toggleShapeMode(bool force_gravity_on_exit) { activateShapeInternal(last_shape_type_); // Si estamos en modo LOGO y la figura es PNG_SHAPE, restaurar configuración LOGO - if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO && last_shape_type_ == ShapeType::PNG_SHAPE) { + if ((state_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::LOGO && last_shape_type_ == ShapeType::PNG_SHAPE) { if (active_shape_) { - PNGShape* png_shape = dynamic_cast(active_shape_.get()); - if (png_shape) { + auto* png_shape = dynamic_cast(active_shape_.get()); + if (png_shape != nullptr) { png_shape->setLogoMode(true); } } } // Si estamos en LOGO MODE, resetear convergencia al entrar - if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO) { + if ((state_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::LOGO) { shape_convergence_ = 0.0f; } } else { @@ -93,7 +91,7 @@ void ShapeManager::toggleShapeMode(bool force_gravity_on_exit) { } // Mostrar notificación (solo si NO estamos en modo demo o logo) - if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { + if ((state_mgr_ != nullptr) && (ui_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { ui_mgr_->showNotification("Modo física"); } } @@ -113,8 +111,8 @@ void ShapeManager::handleShapeScaleChange(bool increase) { clampShapeScale(); // Mostrar notificación si está en modo SANDBOX - if (ui_mgr_ && state_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { - std::string notification = "Escala " + std::to_string(static_cast(shape_scale_factor_ * 100.0f + 0.5f)) + "%"; + if ((ui_mgr_ != nullptr) && (state_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { + std::string notification = "Escala " + std::to_string(static_cast((shape_scale_factor_ * 100.0f) + 0.5f)) + "%"; ui_mgr_->showNotification(notification); } } @@ -125,7 +123,7 @@ void ShapeManager::resetShapeScale() { shape_scale_factor_ = SHAPE_SCALE_DEFAULT; // Mostrar notificación si está en modo SANDBOX - if (ui_mgr_ && state_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { + if ((ui_mgr_ != nullptr) && (state_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { ui_mgr_->showNotification("Escala 100%"); } } @@ -136,14 +134,16 @@ void ShapeManager::toggleDepthZoom() { depth_zoom_enabled_ = !depth_zoom_enabled_; // Mostrar notificación si está en modo SANDBOX - if (ui_mgr_ && state_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { + if ((ui_mgr_ != nullptr) && (state_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { ui_mgr_->showNotification(depth_zoom_enabled_ ? "Profundidad on" : "Profundidad off"); } } } void ShapeManager::update(float delta_time) { - if (!active_shape_ || current_mode_ != SimulationMode::SHAPE) return; + if (!active_shape_ || current_mode_ != SimulationMode::SHAPE) { + return; + } // Actualizar animación de la figura active_shape_->update(delta_time, static_cast(screen_width_), static_cast(screen_height_)); @@ -161,7 +161,9 @@ void ShapeManager::update(float delta_time) { // Actualizar cada pelota con física de atracción for (size_t i = 0; i < balls.size(); i++) { // Obtener posición 3D rotada del punto i - float x_3d, y_3d, z_3d; + float x_3d; + float y_3d; + float z_3d; active_shape_->getPoint3D(static_cast(i), x_3d, y_3d, z_3d); // Aplicar escala manual a las coordenadas 3D @@ -179,9 +181,7 @@ void ShapeManager::update(float delta_time) { // Aplicar fuerza de atracción física hacia el punto rotado // Usar constantes SHAPE (mayor pegajosidad que ROTOBALL) float shape_size = scale_factor * 80.0f; // 80px = radio base - balls[i]->applyShapeForce(target_x, target_y, shape_size, delta_time, - SHAPE_SPRING_K, SHAPE_DAMPING_BASE, SHAPE_DAMPING_NEAR, - SHAPE_NEAR_THRESHOLD, SHAPE_MAX_FORCE); + balls[i]->applyShapeForce(target_x, target_y, shape_size, delta_time, SHAPE_SPRING_K, SHAPE_DAMPING_BASE, SHAPE_DAMPING_NEAR, SHAPE_NEAR_THRESHOLD, SHAPE_MAX_FORCE); // Calcular brillo según profundidad Z para renderizado // Normalizar Z al rango de la figura (asumiendo simetría ±shape_size) @@ -191,12 +191,12 @@ void ShapeManager::update(float delta_time) { // Calcular escala según profundidad Z (perspectiva) - solo si está activado // 0.0 (fondo) → 0.5x, 0.5 (medio) → 1.0x, 1.0 (frente) → 1.5x - float depth_scale = depth_zoom_enabled_ ? (0.5f + z_normalized * 1.0f) : 1.0f; + float depth_scale = depth_zoom_enabled_ ? (0.5f + (z_normalized * 1.0f)) : 1.0f; balls[i]->setDepthScale(depth_scale); } // Calcular convergencia en LOGO MODE (% de pelotas cerca de su objetivo) - if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) { + if ((state_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) { int balls_near = 0; float distance_threshold = LOGO_CONVERGENCE_DISTANCE; // 20px fijo (más permisivo) @@ -215,7 +215,9 @@ void ShapeManager::update(float delta_time) { } void ShapeManager::generateShape() { - if (!active_shape_) return; + if (!active_shape_) { + return; + } int num_points = static_cast(scene_mgr_->getBallCount()); active_shape_->generatePoints(num_points, static_cast(screen_width_), static_cast(screen_height_)); @@ -277,9 +279,9 @@ void ShapeManager::activateShapeInternal(ShapeType type) { scene_mgr_->enableShapeAttractionAll(true); // Mostrar notificación con nombre de figura (solo si NO estamos en modo demo o logo) - if (active_shape_ && state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { + if (active_shape_ && (state_mgr_ != nullptr) && (ui_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { std::string shape_name = active_shape_->getName(); - std::transform(shape_name.begin(), shape_name.end(), shape_name.begin(), ::tolower); + std::ranges::transform(shape_name, shape_name.begin(), ::tolower); std::string notification = std::string("Modo ") + shape_name; ui_mgr_->showNotification(notification); } diff --git a/source/shapes_mgr/shape_manager.hpp b/source/shapes_mgr/shape_manager.hpp index 0953b38..1fcdffe 100644 --- a/source/shapes_mgr/shape_manager.hpp +++ b/source/shapes_mgr/shape_manager.hpp @@ -46,8 +46,7 @@ class ShapeManager { * @param screen_width Ancho lógico de pantalla * @param screen_height Alto lógico de pantalla */ - void initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, - StateManager* state_mgr, int screen_width, int screen_height); + void initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height); /** * @brief Toggle entre modo PHYSICS y SHAPE @@ -147,10 +146,10 @@ class ShapeManager { private: // === Referencias a otros componentes === - Engine* engine_; // Callback al Engine (legacy - temporal) - SceneManager* scene_mgr_; // Acceso a bolas y física - UIManager* ui_mgr_; // Notificaciones - StateManager* state_mgr_; // Verificación de modo actual + Engine* engine_; // Callback al Engine (legacy - temporal) + SceneManager* scene_mgr_; // Acceso a bolas y física + UIManager* ui_mgr_; // Notificaciones + StateManager* state_mgr_; // Verificación de modo actual // === Estado de figuras 3D === SimulationMode current_mode_; diff --git a/source/state/state_manager.cpp b/source/state/state_manager.cpp index b6a8bdb..63f8a87 100644 --- a/source/state/state_manager.cpp +++ b/source/state/state_manager.cpp @@ -1,39 +1,37 @@ #include "state_manager.hpp" #include // for std::min +#include // for std::array #include // for rand #include // for std::vector -#include "defines.hpp" // for constantes DEMO/LOGO -#include "engine.hpp" // for Engine (enter/exitShapeMode, texture) -#include "scene/scene_manager.hpp" // for SceneManager -#include "shapes_mgr/shape_manager.hpp" // for ShapeManager -#include "shapes/png_shape.hpp" // for PNGShape flip detection -#include "theme_manager.hpp" // for ThemeManager +#include "defines.hpp" // for constantes DEMO/LOGO +#include "engine.hpp" // for Engine (enter/exitShapeMode, texture) +#include "scene/scene_manager.hpp" // for SceneManager +#include "shapes/png_shape.hpp" // for PNGShape flip detection +#include "shapes_mgr/shape_manager.hpp" // for ShapeManager +#include "theme_manager.hpp" // for ThemeManager StateManager::StateManager() - : engine_(nullptr) - , scene_mgr_(nullptr) - , theme_mgr_(nullptr) - , shape_mgr_(nullptr) - , current_app_mode_(AppMode::SANDBOX) - , previous_app_mode_(AppMode::SANDBOX) - , demo_timer_(0.0f) - , demo_next_action_time_(0.0f) - , logo_convergence_threshold_(0.90f) - , logo_min_time_(3.0f) - , logo_max_time_(5.0f) - , logo_waiting_for_flip_(false) - , logo_target_flip_number_(0) - , logo_target_flip_percentage_(0.0f) - , logo_current_flip_count_(0) - , logo_entered_manually_(false) - , logo_previous_theme_(0) - , logo_previous_texture_index_(0) - , logo_previous_shape_scale_(1.0f) { -} - -StateManager::~StateManager() { + : engine_(nullptr), + scene_mgr_(nullptr), + theme_mgr_(nullptr), + shape_mgr_(nullptr), + current_app_mode_(AppMode::SANDBOX), + previous_app_mode_(AppMode::SANDBOX), + demo_timer_(0.0f), + demo_next_action_time_(0.0f), + logo_convergence_threshold_(0.90f), + logo_min_time_(3.0f), + logo_max_time_(5.0f), + logo_waiting_for_flip_(false), + logo_target_flip_number_(0), + logo_target_flip_percentage_(0.0f), + logo_current_flip_count_(0), + logo_entered_manually_(false), + logo_previous_theme_(0), + logo_previous_texture_index_(0), + logo_previous_shape_scale_(1.0f) { } void StateManager::initialize(Engine* engine, SceneManager* scene_mgr, ThemeManager* theme_mgr, ShapeManager* shape_mgr) { @@ -53,8 +51,10 @@ void StateManager::setLogoPreviousState(int theme, size_t texture_index, float s // ACTUALIZACIÓN DE ESTADOS // =========================================================================== -void StateManager::update(float delta_time, float shape_convergence, Shape* active_shape) { - if (current_app_mode_ == AppMode::SANDBOX) return; +void StateManager::update(float delta_time, float shape_convergence, Shape* active_shape) { // NOLINT(readability-function-cognitive-complexity) + if (current_app_mode_ == AppMode::SANDBOX) { + return; + } demo_timer_ += delta_time; @@ -63,14 +63,12 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti if (current_app_mode_ == AppMode::LOGO) { if (logo_waiting_for_flip_) { // CAMINO B: Esperando a que ocurran flips - PNGShape* png_shape = dynamic_cast(active_shape); + auto* png_shape = dynamic_cast(active_shape); - if (png_shape) { + if (png_shape != nullptr) { int current_flip_count = png_shape->getFlipCount(); - if (current_flip_count > logo_current_flip_count_) { - logo_current_flip_count_ = current_flip_count; - } + logo_current_flip_count_ = std::max(current_flip_count, logo_current_flip_count_); if (logo_current_flip_count_ + 1 >= logo_target_flip_number_) { if (png_shape->isFlipping()) { @@ -93,7 +91,9 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti should_trigger = demo_timer_ >= demo_next_action_time_; } - if (!should_trigger) return; + if (!should_trigger) { + return; + } if (current_app_mode_ == AppMode::LOGO) { // LOGO MODE: Sistema de acciones variadas con gravedad dinámica @@ -104,7 +104,7 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti if (logo_waiting_for_flip_) { // Ya estábamos esperando flips → hacer el cambio SHAPE → PHYSICS if (action < 50) { - engine_->exitShapeMode(true); // Con gravedad ON + engine_->exitShapeMode(true); // Con gravedad ON } else { engine_->exitShapeMode(false); // Con gravedad OFF } @@ -122,15 +122,15 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti logo_target_flip_percentage_ = LOGO_FLIP_TRIGGER_MIN + (rand() % 1000) / 1000.0f * (LOGO_FLIP_TRIGGER_MAX - LOGO_FLIP_TRIGGER_MIN); logo_current_flip_count_ = 0; - PNGShape* png_shape = dynamic_cast(shape_mgr_->getActiveShape()); - if (png_shape) { + auto* png_shape = dynamic_cast(shape_mgr_->getActiveShape()); + if (png_shape != nullptr) { png_shape->resetFlipCount(); } // No hacer nada más — esperar a que ocurran los flips } else { // CAMINO A (50%): Cambio inmediato if (action < 50) { - engine_->exitShapeMode(true); // SHAPE → PHYSICS con gravedad ON + engine_->exitShapeMode(true); // SHAPE → PHYSICS con gravedad ON } else { engine_->exitShapeMode(false); // SHAPE → PHYSICS con gravedad OFF } @@ -158,7 +158,7 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti scene_mgr_->forceBallsGravityOff(); } else { // 16%: Cambiar dirección de gravedad - GravityDirection new_direction = static_cast(rand() % 4); + auto new_direction = static_cast(rand() % 4); scene_mgr_->changeGravityDirection(new_direction); scene_mgr_->forceBallsGravityOn(); } @@ -186,7 +186,9 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti } void StateManager::setState(AppMode new_mode, int current_screen_width, int current_screen_height) { - if (current_app_mode_ == new_mode) return; + if (current_app_mode_ == new_mode) { + return; + } if (current_app_mode_ == AppMode::LOGO && new_mode != AppMode::LOGO) { previous_app_mode_ = new_mode; @@ -201,7 +203,8 @@ void StateManager::setState(AppMode new_mode, int current_screen_width, int curr demo_timer_ = 0.0f; if (new_mode == AppMode::DEMO || new_mode == AppMode::DEMO_LITE || new_mode == AppMode::LOGO) { - float min_interval, max_interval; + float min_interval; + float max_interval; if (new_mode == AppMode::LOGO) { float resolution_scale = current_screen_height / 720.0f; @@ -250,8 +253,10 @@ void StateManager::toggleLogoMode(int current_screen_width, int current_screen_h // ACCIONES DE DEMO // =========================================================================== -void StateManager::performDemoAction(bool is_lite) { - if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return; +void StateManager::performDemoAction(bool is_lite) { // NOLINT(readability-function-cognitive-complexity) + if ((engine_ == nullptr) || (scene_mgr_ == nullptr) || (theme_mgr_ == nullptr) || (shape_mgr_ == nullptr)) { + return; + } // ============================================ // SALTO AUTOMÁTICO A LOGO MODE (Easter Egg) @@ -278,18 +283,18 @@ void StateManager::performDemoAction(bool is_lite) { // ACCIONES NORMALES DE DEMO/DEMO_LITE // ============================================ - int TOTAL_WEIGHT; + int total_weight; int random_value; int accumulated_weight = 0; if (is_lite) { - TOTAL_WEIGHT = DEMO_LITE_WEIGHT_GRAVITY_DIR + DEMO_LITE_WEIGHT_GRAVITY_TOGGLE + DEMO_LITE_WEIGHT_SHAPE + DEMO_LITE_WEIGHT_TOGGLE_PHYSICS + DEMO_LITE_WEIGHT_IMPULSE; - random_value = rand() % TOTAL_WEIGHT; + total_weight = DEMO_LITE_WEIGHT_GRAVITY_DIR + DEMO_LITE_WEIGHT_GRAVITY_TOGGLE + DEMO_LITE_WEIGHT_SHAPE + DEMO_LITE_WEIGHT_TOGGLE_PHYSICS + DEMO_LITE_WEIGHT_IMPULSE; + random_value = rand() % total_weight; // Cambiar dirección gravedad (25%) accumulated_weight += DEMO_LITE_WEIGHT_GRAVITY_DIR; if (random_value < accumulated_weight) { - GravityDirection new_direction = static_cast(rand() % 4); + auto new_direction = static_cast(rand() % 4); scene_mgr_->changeGravityDirection(new_direction); return; } @@ -304,8 +309,8 @@ void StateManager::performDemoAction(bool is_lite) { // Activar figura 3D (25%) - PNG_SHAPE excluido accumulated_weight += DEMO_LITE_WEIGHT_SHAPE; if (random_value < accumulated_weight) { - ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM}; - engine_->enterShapeMode(shapes[rand() % 8]); + constexpr std::array SHAPES = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM}; + engine_->enterShapeMode(SHAPES[rand() % 8]); return; } @@ -324,13 +329,13 @@ void StateManager::performDemoAction(bool is_lite) { } } else { - TOTAL_WEIGHT = DEMO_WEIGHT_GRAVITY_DIR + DEMO_WEIGHT_GRAVITY_TOGGLE + DEMO_WEIGHT_SHAPE + DEMO_WEIGHT_TOGGLE_PHYSICS + DEMO_WEIGHT_REGENERATE_SHAPE + DEMO_WEIGHT_THEME + DEMO_WEIGHT_SCENARIO + DEMO_WEIGHT_IMPULSE + DEMO_WEIGHT_DEPTH_ZOOM + DEMO_WEIGHT_SHAPE_SCALE + DEMO_WEIGHT_SPRITE; - random_value = rand() % TOTAL_WEIGHT; + total_weight = DEMO_WEIGHT_GRAVITY_DIR + DEMO_WEIGHT_GRAVITY_TOGGLE + DEMO_WEIGHT_SHAPE + DEMO_WEIGHT_TOGGLE_PHYSICS + DEMO_WEIGHT_REGENERATE_SHAPE + DEMO_WEIGHT_THEME + DEMO_WEIGHT_SCENARIO + DEMO_WEIGHT_IMPULSE + DEMO_WEIGHT_DEPTH_ZOOM + DEMO_WEIGHT_SHAPE_SCALE + DEMO_WEIGHT_SPRITE; + random_value = rand() % total_weight; // Cambiar dirección gravedad (10%) accumulated_weight += DEMO_WEIGHT_GRAVITY_DIR; if (random_value < accumulated_weight) { - GravityDirection new_direction = static_cast(rand() % 4); + auto new_direction = static_cast(rand() % 4); scene_mgr_->changeGravityDirection(new_direction); return; } @@ -345,8 +350,8 @@ void StateManager::performDemoAction(bool is_lite) { // Activar figura 3D (20%) - PNG_SHAPE excluido accumulated_weight += DEMO_WEIGHT_SHAPE; if (random_value < accumulated_weight) { - ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM}; - engine_->enterShapeMode(shapes[rand() % 8]); + constexpr std::array SHAPES = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM}; + engine_->enterShapeMode(SHAPES[rand() % 8]); return; } @@ -378,10 +383,12 @@ void StateManager::performDemoAction(bool is_lite) { if (random_value < accumulated_weight) { int auto_max = std::min(engine_->getMaxAutoScenario(), DEMO_AUTO_MAX_SCENARIO); std::vector candidates; - for (int i = DEMO_AUTO_MIN_SCENARIO; i <= auto_max; ++i) + for (int i = DEMO_AUTO_MIN_SCENARIO; i <= auto_max; ++i) { candidates.push_back(i); - if (engine_->isCustomScenarioEnabled() && engine_->isCustomAutoAvailable()) + } + if (engine_->isCustomScenarioEnabled() && engine_->isCustomAutoAvailable()) { candidates.push_back(CUSTOM_SCENARIO_IDX); + } int new_scenario = candidates[rand() % candidates.size()]; SimulationMode current_sim_mode = shape_mgr_->getCurrentMode(); scene_mgr_->changeScenario(new_scenario, current_sim_mode); @@ -439,15 +446,15 @@ void StateManager::performDemoAction(bool is_lite) { // RANDOMIZACIÓN AL INICIAR DEMO // =========================================================================== -void StateManager::randomizeOnDemoStart(bool is_lite) { - if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return; +void StateManager::randomizeOnDemoStart(bool is_lite) { // NOLINT(readability-function-cognitive-complexity) + if ((engine_ == nullptr) || (scene_mgr_ == nullptr) || (theme_mgr_ == nullptr) || (shape_mgr_ == nullptr)) { + return; + } // Si venimos de LOGO con PNG_SHAPE, cambiar figura obligatoriamente if (shape_mgr_->getCurrentShapeType() == ShapeType::PNG_SHAPE) { - ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, - ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, - ShapeType::ICOSAHEDRON, ShapeType::ATOM}; - engine_->enterShapeMode(shapes[rand() % 8]); + constexpr std::array SHAPES = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM}; + engine_->enterShapeMode(SHAPES[rand() % 8]); } if (is_lite) { @@ -457,11 +464,11 @@ void StateManager::randomizeOnDemoStart(bool is_lite) { engine_->exitShapeMode(false); } } else { - ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM}; - engine_->enterShapeMode(shapes[rand() % 8]); + constexpr std::array SHAPES = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM}; + engine_->enterShapeMode(SHAPES[rand() % 8]); } - GravityDirection new_direction = static_cast(rand() % 4); + auto new_direction = static_cast(rand() % 4); scene_mgr_->changeGravityDirection(new_direction); if (rand() % 2 == 0) { toggleGravityOnOff(); @@ -476,14 +483,14 @@ void StateManager::randomizeOnDemoStart(bool is_lite) { engine_->exitShapeMode(false); } } else { - ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM}; - ShapeType selected_shape = shapes[rand() % 8]; + constexpr std::array SHAPES = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM}; + ShapeType selected_shape = SHAPES[rand() % 8]; // Randomizar profundidad y escala ANTES de activar la figura if (rand() % 2 == 0) { shape_mgr_->setDepthZoomEnabled(!shape_mgr_->isDepthZoomEnabled()); } - shape_mgr_->setShapeScaleFactor(0.5f + (rand() % 1500) / 1000.0f); + shape_mgr_->setShapeScaleFactor(0.5f + ((rand() % 1500) / 1000.0f)); engine_->enterShapeMode(selected_shape); } @@ -491,10 +498,12 @@ void StateManager::randomizeOnDemoStart(bool is_lite) { // 2. Escenario int auto_max = std::min(engine_->getMaxAutoScenario(), DEMO_AUTO_MAX_SCENARIO); std::vector candidates; - for (int i = DEMO_AUTO_MIN_SCENARIO; i <= auto_max; ++i) + for (int i = DEMO_AUTO_MIN_SCENARIO; i <= auto_max; ++i) { candidates.push_back(i); - if (engine_->isCustomScenarioEnabled() && engine_->isCustomAutoAvailable()) + } + if (engine_->isCustomScenarioEnabled() && engine_->isCustomAutoAvailable()) { candidates.push_back(CUSTOM_SCENARIO_IDX); + } int new_scenario = candidates[rand() % candidates.size()]; SimulationMode current_sim_mode = shape_mgr_->getCurrentMode(); scene_mgr_->changeScenario(new_scenario, current_sim_mode); @@ -513,7 +522,7 @@ void StateManager::randomizeOnDemoStart(bool is_lite) { } // 5. Gravedad - GravityDirection new_direction = static_cast(rand() % 4); + auto new_direction = static_cast(rand() % 4); scene_mgr_->changeGravityDirection(new_direction); if (rand() % 3 == 0) { toggleGravityOnOff(); @@ -526,10 +535,12 @@ void StateManager::randomizeOnDemoStart(bool is_lite) { // =========================================================================== void StateManager::toggleGravityOnOff() { - if (!scene_mgr_) return; + if (scene_mgr_ == nullptr) { + return; + } bool gravity_enabled = scene_mgr_->hasBalls() && - (scene_mgr_->getFirstBall()->getGravityForce() > 0.0f); + (scene_mgr_->getFirstBall()->getGravityForce() > 0.0f); if (gravity_enabled) { scene_mgr_->forceBallsGravityOff(); @@ -543,7 +554,9 @@ void StateManager::toggleGravityOnOff() { // =========================================================================== void StateManager::enterLogoMode(bool from_demo, int current_screen_width, int current_screen_height, size_t ball_count) { - if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return; + if ((engine_ == nullptr) || (scene_mgr_ == nullptr) || (theme_mgr_ == nullptr) || (shape_mgr_ == nullptr)) { + return; + } logo_entered_manually_ = !from_demo; @@ -585,8 +598,8 @@ void StateManager::enterLogoMode(bool from_demo, int current_screen_width, int c } // Cambiar a tema aleatorio entre: MONOCHROME, LAVENDER, CRIMSON, ESMERALDA - int logo_themes[] = {5, 6, 7, 8}; - theme_mgr_->switchToTheme(logo_themes[rand() % 4]); + constexpr std::array LOGO_THEMES = {5, 6, 7, 8}; + theme_mgr_->switchToTheme(LOGO_THEMES[rand() % 4]); // Establecer escala a 120% shape_mgr_->setShapeScaleFactor(LOGO_MODE_SHAPE_SCALE); @@ -595,8 +608,8 @@ void StateManager::enterLogoMode(bool from_demo, int current_screen_width, int c engine_->enterShapeMode(ShapeType::PNG_SHAPE); // Configurar PNG_SHAPE en modo LOGO - PNGShape* png_shape = dynamic_cast(shape_mgr_->getActiveShape()); - if (png_shape) { + auto* png_shape = dynamic_cast(shape_mgr_->getActiveShape()); + if (png_shape != nullptr) { png_shape->setLogoMode(true); png_shape->resetFlipCount(); } @@ -607,8 +620,12 @@ void StateManager::enterLogoMode(bool from_demo, int current_screen_width, int c // =========================================================================== void StateManager::exitLogoMode(bool return_to_demo) { - if (current_app_mode_ != AppMode::LOGO) return; - if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return; + if (current_app_mode_ != AppMode::LOGO) { + return; + } + if ((engine_ == nullptr) || (scene_mgr_ == nullptr) || (theme_mgr_ == nullptr) || (shape_mgr_ == nullptr)) { + return; + } logo_entered_manually_ = false; @@ -624,17 +641,15 @@ void StateManager::exitLogoMode(bool return_to_demo) { } // Desactivar modo LOGO en PNG_SHAPE - PNGShape* png_shape = dynamic_cast(shape_mgr_->getActiveShape()); - if (png_shape) { + auto* png_shape = dynamic_cast(shape_mgr_->getActiveShape()); + if (png_shape != nullptr) { png_shape->setLogoMode(false); } // Si la figura activa es PNG_SHAPE, cambiar a otra figura aleatoria if (shape_mgr_->getCurrentShapeType() == ShapeType::PNG_SHAPE) { - ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, - ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, - ShapeType::ICOSAHEDRON, ShapeType::ATOM}; - engine_->enterShapeMode(shapes[rand() % 8]); + constexpr std::array SHAPES = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM}; + engine_->enterShapeMode(SHAPES[rand() % 8]); } if (!return_to_demo) { diff --git a/source/state/state_manager.hpp b/source/state/state_manager.hpp index 5e4f53f..6bcac29 100644 --- a/source/state/state_manager.hpp +++ b/source/state/state_manager.hpp @@ -1,7 +1,8 @@ #pragma once #include // for Uint64 -#include // for size_t + +#include // for size_t #include "defines.hpp" // for AppMode, ShapeType, GravityDirection @@ -37,7 +38,7 @@ class StateManager { /** * @brief Destructor */ - ~StateManager(); + ~StateManager() = default; /** * @brief Inicializa el StateManager con referencias a los subsistemas necesarios diff --git a/source/text/textrenderer.cpp b/source/text/textrenderer.cpp index ce35613..03f79d2 100644 --- a/source/text/textrenderer.cpp +++ b/source/text/textrenderer.cpp @@ -1,24 +1,32 @@ #include "textrenderer.hpp" + #include #include + #include + #include "resource_manager.hpp" -TextRenderer::TextRenderer() : renderer_(nullptr), font_(nullptr), font_size_(0), use_antialiasing_(true), font_data_buffer_(nullptr) { +TextRenderer::TextRenderer() + : renderer_(nullptr), + font_(nullptr), + font_size_(0), + use_antialiasing_(true), + font_data_buffer_(nullptr) { } TextRenderer::~TextRenderer() { cleanup(); } -bool TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing) { +auto TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing) -> bool { renderer_ = renderer; font_size_ = font_size; use_antialiasing_ = use_antialiasing; font_path_ = font_path; // Guardar ruta para reinitialize() // Inicializar SDL_ttf si no está inicializado - if (!TTF_WasInit()) { + if (TTF_WasInit() == 0) { if (!TTF_Init()) { SDL_Log("Error al inicializar SDL_ttf: %s", SDL_GetError()); return false; @@ -26,34 +34,33 @@ bool TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_ } // Intentar cargar la fuente desde ResourceManager (pack o disco) - unsigned char* fontData = nullptr; - size_t fontDataSize = 0; + unsigned char* font_data = nullptr; + size_t font_data_size = 0; - if (ResourceManager::loadResource(font_path, fontData, fontDataSize)) { + if (ResourceManager::loadResource(font_path, font_data, font_data_size)) { // Crear SDL_IOStream desde memoria - SDL_IOStream* fontIO = SDL_IOFromConstMem(fontData, static_cast(fontDataSize)); - if (fontIO != nullptr) { + SDL_IOStream* font_io = SDL_IOFromConstMem(font_data, font_data_size); + if (font_io != nullptr) { // Cargar fuente desde IOStream - font_ = TTF_OpenFontIO(fontIO, true, font_size); // true = cerrar stream automáticamente + font_ = TTF_OpenFontIO(font_io, true, font_size); // true = cerrar stream automáticamente if (font_ == nullptr) { SDL_Log("Error al cargar fuente desde memoria '%s': %s", font_path, SDL_GetError()); - delete[] fontData; // Liberar solo si falla la carga + delete[] font_data; // Liberar solo si falla la carga return false; } // CRÍTICO: NO eliminar fontData aquí - SDL_ttf necesita estos datos en memoria // mientras la fuente esté abierta. Se liberará en cleanup() - font_data_buffer_ = fontData; + font_data_buffer_ = font_data; { std::string fn = std::string(font_path); fn = fn.substr(fn.find_last_of("\\/") + 1); std::cout << "[Fuente] " << fn << " (" << (ResourceManager::isPackLoaded() ? "pack" : "disco") << ")\n"; } return true; - } else { - delete[] fontData; } + delete[] font_data; } // Fallback final: intentar cargar directamente desde disco (por si falla ResourceManager) @@ -66,7 +73,7 @@ bool TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_ return true; } -bool TextRenderer::reinitialize(int new_font_size) { +auto TextRenderer::reinitialize(int new_font_size) -> bool { // Verificar que tenemos todo lo necesario if (renderer_ == nullptr || font_path_.empty()) { SDL_Log("Error: TextRenderer no inicializado correctamente para reinitialize()"); @@ -89,39 +96,42 @@ bool TextRenderer::reinitialize(int new_font_size) { } // Intentar cargar la fuente desde ResourceManager con el nuevo tamaño - unsigned char* fontData = nullptr; - size_t fontDataSize = 0; + unsigned char* font_data = nullptr; + size_t font_data_size = 0; - if (ResourceManager::loadResource(font_path_, fontData, fontDataSize)) { - SDL_IOStream* fontIO = SDL_IOFromConstMem(fontData, static_cast(fontDataSize)); - if (fontIO != nullptr) { - font_ = TTF_OpenFontIO(fontIO, true, new_font_size); + if (ResourceManager::loadResource(font_path_, font_data, font_data_size)) { + SDL_IOStream* font_io = SDL_IOFromConstMem(font_data, font_data_size); + if (font_io != nullptr) { + font_ = TTF_OpenFontIO(font_io, true, new_font_size); if (font_ == nullptr) { SDL_Log("Error al recargar fuente '%s' con tamaño %d: %s", - font_path_.c_str(), new_font_size, SDL_GetError()); - delete[] fontData; // Liberar solo si falla + font_path_.c_str(), + new_font_size, + SDL_GetError()); + delete[] font_data; // Liberar solo si falla return false; } // Mantener buffer en memoria (NO eliminar) - font_data_buffer_ = fontData; + font_data_buffer_ = font_data; font_size_ = new_font_size; { std::string fn = font_path_.substr(font_path_.find_last_of("\\/") + 1); std::cout << "[Fuente] " << fn << " (" << (ResourceManager::isPackLoaded() ? "pack" : "disco") << ")\n"; } return true; - } else { - delete[] fontData; } + delete[] font_data; } // Fallback: cargar directamente desde disco font_ = TTF_OpenFont(font_path_.c_str(), new_font_size); if (font_ == nullptr) { SDL_Log("Error al recargar fuente '%s' con tamaño %d: %s", - font_path_.c_str(), new_font_size, SDL_GetError()); + font_path_.c_str(), + new_font_size, + SDL_GetError()); return false; } @@ -233,7 +243,8 @@ void TextRenderer::printPhysical(int logical_x, int logical_y, const char* text, dest_rect.h = static_cast(text_surface->h); // Deshabilitar temporalmente presentación lógica para renderizar en píxeles físicos - int logical_w = 0, logical_h = 0; + int logical_w = 0; + int logical_h = 0; SDL_RendererLogicalPresentation presentation_mode; SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &presentation_mode); @@ -301,7 +312,8 @@ void TextRenderer::printAbsolute(int physical_x, int physical_y, const char* tex dest_rect.h = static_cast(text_surface->h); // Deshabilitar temporalmente presentación lógica para renderizar en píxeles físicos - int logical_w = 0, logical_h = 0; + int logical_w = 0; + int logical_h = 0; SDL_RendererLogicalPresentation presentation_mode; SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &presentation_mode); @@ -332,7 +344,7 @@ void TextRenderer::printAbsoluteShadowed(int physical_x, int physical_y, const s printAbsoluteShadowed(physical_x, physical_y, text.c_str()); } -int TextRenderer::getTextWidth(const char* text) { +auto TextRenderer::getTextWidth(const char* text) -> int { if (!isInitialized() || text == nullptr) { return 0; } @@ -345,7 +357,7 @@ int TextRenderer::getTextWidth(const char* text) { return width; } -int TextRenderer::getTextWidthPhysical(const char* text) { +auto TextRenderer::getTextWidthPhysical(const char* text) -> int { // Retorna el ancho REAL en píxeles físicos (sin escalado lógico) // Idéntico a getTextWidth() pero semánticamente diferente: // - Este método se usa cuando se necesita el ancho REAL de la fuente @@ -362,7 +374,7 @@ int TextRenderer::getTextWidthPhysical(const char* text) { return width; // Ancho real de la textura generada por TTF } -int TextRenderer::getTextHeight() { +auto TextRenderer::getTextHeight() -> int { if (!isInitialized()) { return 0; } @@ -370,7 +382,7 @@ int TextRenderer::getTextHeight() { return TTF_GetFontHeight(font_); } -int TextRenderer::getGlyphHeight() { +auto TextRenderer::getGlyphHeight() -> int { if (!isInitialized()) { return 0; } diff --git a/source/text/textrenderer.hpp b/source/text/textrenderer.hpp index e6bdf25..b5a90ba 100644 --- a/source/text/textrenderer.hpp +++ b/source/text/textrenderer.hpp @@ -2,60 +2,61 @@ #include #include + #include class TextRenderer { -public: - TextRenderer(); - ~TextRenderer(); + public: + TextRenderer(); + ~TextRenderer(); - // Inicializa el renderizador de texto con una fuente - bool init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing = true); + // Inicializa el renderizador de texto con una fuente + bool init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing = true); - // Reinicializa el renderizador con un nuevo tamaño de fuente - bool reinitialize(int new_font_size); + // Reinicializa el renderizador con un nuevo tamaño de fuente + bool reinitialize(int new_font_size); - // Libera recursos - void cleanup(); + // Libera recursos + void cleanup(); - // Renderiza texto en la posición especificada con color RGB - void print(int x, int y, const char* text, uint8_t r, uint8_t g, uint8_t b); - void print(int x, int y, const std::string& text, uint8_t r, uint8_t g, uint8_t b); + // Renderiza texto en la posición especificada con color RGB + void print(int x, int y, const char* text, uint8_t r, uint8_t g, uint8_t b); + void print(int x, int y, const std::string& text, uint8_t r, uint8_t g, uint8_t b); - // Renderiza texto en coordenadas lógicas, pero convierte a físicas para tamaño absoluto - void printPhysical(int logical_x, int logical_y, const char* text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y); - void printPhysical(int logical_x, int logical_y, const std::string& text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y); + // Renderiza texto en coordenadas lógicas, pero convierte a físicas para tamaño absoluto + void printPhysical(int logical_x, int logical_y, const char* text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y); + void printPhysical(int logical_x, int logical_y, const std::string& text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y); - // Renderiza texto en coordenadas físicas absolutas (tamaño fijo independiente de resolución) - // NOTA: Este método usa el tamaño de fuente tal cual fue cargado, sin escalado - void printAbsolute(int physical_x, int physical_y, const char* text, SDL_Color color); - void printAbsolute(int physical_x, int physical_y, const std::string& text, SDL_Color color); + // Renderiza texto en coordenadas físicas absolutas (tamaño fijo independiente de resolución) + // NOTA: Este método usa el tamaño de fuente tal cual fue cargado, sin escalado + void printAbsolute(int physical_x, int physical_y, const char* text, SDL_Color color); + void printAbsolute(int physical_x, int physical_y, const std::string& text, SDL_Color color); - // Renderiza texto con sombra negra (+1px offset) para máxima legibilidad sobre cualquier fondo - void printAbsoluteShadowed(int physical_x, int physical_y, const char* text); - void printAbsoluteShadowed(int physical_x, int physical_y, const std::string& text); + // Renderiza texto con sombra negra (+1px offset) para máxima legibilidad sobre cualquier fondo + void printAbsoluteShadowed(int physical_x, int physical_y, const char* text); + void printAbsoluteShadowed(int physical_x, int physical_y, const std::string& text); - // Obtiene el ancho de un texto renderizado (en píxeles lógicos para compatibilidad) - int getTextWidth(const char* text); + // Obtiene el ancho de un texto renderizado (en píxeles lógicos para compatibilidad) + int getTextWidth(const char* text); - // Obtiene el ancho de un texto en píxeles FÍSICOS reales (sin escalado) - // Útil para notificaciones y elementos UI de tamaño fijo - int getTextWidthPhysical(const char* text); + // Obtiene el ancho de un texto en píxeles FÍSICOS reales (sin escalado) + // Útil para notificaciones y elementos UI de tamaño fijo + int getTextWidthPhysical(const char* text); - // Obtiene la altura de la fuente (incluye line_gap) - int getTextHeight(); + // Obtiene la altura de la fuente (incluye line_gap) + int getTextHeight(); - // Obtiene la altura real del glifo (ascender + |descendente|, sin line_gap) - int getGlyphHeight(); + // Obtiene la altura real del glifo (ascender + |descendente|, sin line_gap) + int getGlyphHeight(); - // Verifica si está inicializado correctamente - bool isInitialized() const { return font_ != nullptr && renderer_ != nullptr; } + // Verifica si está inicializado correctamente + bool isInitialized() const { return font_ != nullptr && renderer_ != nullptr; } -private: - SDL_Renderer* renderer_; - TTF_Font* font_; - int font_size_; - bool use_antialiasing_; - std::string font_path_; // Almacenar ruta para reinitialize() - unsigned char* font_data_buffer_; // Buffer de datos de fuente (mantener en memoria mientras esté abierta) + private: + SDL_Renderer* renderer_; + TTF_Font* font_; + int font_size_; + bool use_antialiasing_; + std::string font_path_; // Almacenar ruta para reinitialize() + unsigned char* font_data_buffer_; // Buffer de datos de fuente (mantener en memoria mientras esté abierta) }; diff --git a/source/theme_manager.cpp b/source/theme_manager.cpp index 0010547..35dadf2 100644 --- a/source/theme_manager.cpp +++ b/source/theme_manager.cpp @@ -1,7 +1,7 @@ #include "theme_manager.hpp" -#include "themes/static_theme.hpp" #include "themes/dynamic_theme.hpp" +#include "themes/static_theme.hpp" // ============================================================================ // INICIALIZACIÓN @@ -19,149 +19,252 @@ void ThemeManager::initialize() { themes_.push_back(std::make_unique( "Sunset", "Atardecer", - 255, 140, 60, // Color texto: naranja cálido - 120, 40, 80, // Color fondo notificación: púrpura oscuro (contrasta con naranja) - 180.0f / 255.0f, 140.0f / 255.0f, 100.0f / 255.0f, // Fondo superior: naranja suave - 40.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: púrpura oscuro + 255, + 140, + 60, // Color texto: naranja cálido + 120, + 40, + 80, // Color fondo notificación: púrpura oscuro (contrasta con naranja) + 180.0f / 255.0f, + 140.0f / 255.0f, + 100.0f / 255.0f, // Fondo superior: naranja suave + 40.0f / 255.0f, + 20.0f / 255.0f, + 60.0f / 255.0f, // Fondo inferior: púrpura oscuro std::vector{ - {255, 140, 0}, {255, 69, 0}, {255, 215, 0}, {255, 20, 147}, - {255, 99, 71}, {255, 165, 0}, {255, 192, 203}, {220, 20, 60} - } - )); + {.r = 255, .g = 140, .b = 0}, + {.r = 255, .g = 69, .b = 0}, + {.r = 255, .g = 215, .b = 0}, + {.r = 255, .g = 20, .b = 147}, + {.r = 255, .g = 99, .b = 71}, + {.r = 255, .g = 165, .b = 0}, + {.r = 255, .g = 192, .b = 203}, + {.r = 220, .g = 20, .b = 60}})); // 1: OCEAN (Océano) - Azules, turquesas, blancos themes_.push_back(std::make_unique( "Ocean", "Océano", - 80, 200, 255, // Color texto: azul océano - 20, 50, 90, // Color fondo notificación: azul marino oscuro (contrasta con cian) - 100.0f / 255.0f, 150.0f / 255.0f, 200.0f / 255.0f, // Fondo superior: azul cielo - 20.0f / 255.0f, 40.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: azul marino + 80, + 200, + 255, // Color texto: azul océano + 20, + 50, + 90, // Color fondo notificación: azul marino oscuro (contrasta con cian) + 100.0f / 255.0f, + 150.0f / 255.0f, + 200.0f / 255.0f, // Fondo superior: azul cielo + 20.0f / 255.0f, + 40.0f / 255.0f, + 80.0f / 255.0f, // Fondo inferior: azul marino std::vector{ - {0, 191, 255}, {0, 255, 255}, {32, 178, 170}, {176, 224, 230}, - {70, 130, 180}, {0, 206, 209}, {240, 248, 255}, {64, 224, 208} - } - )); + {.r = 0, .g = 191, .b = 255}, + {.r = 0, .g = 255, .b = 255}, + {.r = 32, .g = 178, .b = 170}, + {.r = 176, .g = 224, .b = 230}, + {.r = 70, .g = 130, .b = 180}, + {.r = 0, .g = 206, .b = 209}, + {.r = 240, .g = 248, .b = 255}, + {.r = 64, .g = 224, .b = 208}})); // 2: NEON - Cian, magenta, verde lima, amarillo vibrante themes_.push_back(std::make_unique( "Neon", "Neón", - 255, 60, 255, // Color texto: magenta brillante - 60, 0, 80, // Color fondo notificación: púrpura muy oscuro (contrasta con neón) - 20.0f / 255.0f, 20.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: negro azulado - 0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro + 255, + 60, + 255, // Color texto: magenta brillante + 60, + 0, + 80, // Color fondo notificación: púrpura muy oscuro (contrasta con neón) + 20.0f / 255.0f, + 20.0f / 255.0f, + 40.0f / 255.0f, // Fondo superior: negro azulado + 0.0f / 255.0f, + 0.0f / 255.0f, + 0.0f / 255.0f, // Fondo inferior: negro std::vector{ - {0, 255, 255}, {255, 0, 255}, {50, 205, 50}, {255, 255, 0}, - {255, 20, 147}, {0, 255, 127}, {138, 43, 226}, {255, 69, 0} - } - )); + {.r = 0, .g = 255, .b = 255}, + {.r = 255, .g = 0, .b = 255}, + {.r = 50, .g = 205, .b = 50}, + {.r = 255, .g = 255, .b = 0}, + {.r = 255, .g = 20, .b = 147}, + {.r = 0, .g = 255, .b = 127}, + {.r = 138, .g = 43, .b = 226}, + {.r = 255, .g = 69, .b = 0}})); // 3: FOREST (Bosque) - Verdes, marrones, amarillos otoño themes_.push_back(std::make_unique( "Forest", "Bosque", - 100, 255, 100, // Color texto: verde natural - 70, 50, 30, // Color fondo notificación: marrón oscuro tierra (contrasta con verde) - 144.0f / 255.0f, 238.0f / 255.0f, 144.0f / 255.0f, // Fondo superior: verde claro - 101.0f / 255.0f, 67.0f / 255.0f, 33.0f / 255.0f, // Fondo inferior: marrón tierra + 100, + 255, + 100, // Color texto: verde natural + 70, + 50, + 30, // Color fondo notificación: marrón oscuro tierra (contrasta con verde) + 144.0f / 255.0f, + 238.0f / 255.0f, + 144.0f / 255.0f, // Fondo superior: verde claro + 101.0f / 255.0f, + 67.0f / 255.0f, + 33.0f / 255.0f, // Fondo inferior: marrón tierra std::vector{ - {34, 139, 34}, {107, 142, 35}, {154, 205, 50}, {255, 215, 0}, - {210, 180, 140}, {160, 82, 45}, {218, 165, 32}, {50, 205, 50} - } - )); + {.r = 34, .g = 139, .b = 34}, + {.r = 107, .g = 142, .b = 35}, + {.r = 154, .g = 205, .b = 50}, + {.r = 255, .g = 215, .b = 0}, + {.r = 210, .g = 180, .b = 140}, + {.r = 160, .g = 82, .b = 45}, + {.r = 218, .g = 165, .b = 32}, + {.r = 50, .g = 205, .b = 50}})); // 4: RGB - Círculo cromático con 24 puntos (cada 15°) themes_.push_back(std::make_unique( "RGB", "RGB", - 100, 100, 100, // Color texto: gris oscuro - 220, 220, 220, // Color fondo notificación: gris muy claro (contrasta sobre blanco) - 1.0f, 1.0f, 1.0f, // Fondo superior: blanco puro - 1.0f, 1.0f, 1.0f, // Fondo inferior: blanco puro (sin degradado) + 100, + 100, + 100, // Color texto: gris oscuro + 220, + 220, + 220, // Color fondo notificación: gris muy claro (contrasta sobre blanco) + 1.0f, + 1.0f, + 1.0f, // Fondo superior: blanco puro + 1.0f, + 1.0f, + 1.0f, // Fondo inferior: blanco puro (sin degradado) std::vector{ - {255, 0, 0}, // 0° - Rojo puro - {255, 64, 0}, // 15° - Rojo-Naranja - {255, 128, 0}, // 30° - Naranja - {255, 191, 0}, // 45° - Naranja-Amarillo - {255, 255, 0}, // 60° - Amarillo puro - {191, 255, 0}, // 75° - Amarillo-Verde claro - {128, 255, 0}, // 90° - Verde-Amarillo - {64, 255, 0}, // 105° - Verde claro-Amarillo - {0, 255, 0}, // 120° - Verde puro - {0, 255, 64}, // 135° - Verde-Cian claro - {0, 255, 128}, // 150° - Verde-Cian - {0, 255, 191}, // 165° - Verde claro-Cian - {0, 255, 255}, // 180° - Cian puro - {0, 191, 255}, // 195° - Cian-Azul claro - {0, 128, 255}, // 210° - Azul-Cian - {0, 64, 255}, // 225° - Azul claro-Cian - {0, 0, 255}, // 240° - Azul puro - {64, 0, 255}, // 255° - Azul-Magenta claro - {128, 0, 255}, // 270° - Azul-Magenta - {191, 0, 255}, // 285° - Azul claro-Magenta - {255, 0, 255}, // 300° - Magenta puro - {255, 0, 191}, // 315° - Magenta-Rojo claro - {255, 0, 128}, // 330° - Magenta-Rojo - {255, 0, 64} // 345° - Magenta claro-Rojo - } - )); + {.r = 255, .g = 0, .b = 0}, // 0° - Rojo puro + {.r = 255, .g = 64, .b = 0}, // 15° - Rojo-Naranja + {.r = 255, .g = 128, .b = 0}, // 30° - Naranja + {.r = 255, .g = 191, .b = 0}, // 45° - Naranja-Amarillo + {.r = 255, .g = 255, .b = 0}, // 60° - Amarillo puro + {.r = 191, .g = 255, .b = 0}, // 75° - Amarillo-Verde claro + {.r = 128, .g = 255, .b = 0}, // 90° - Verde-Amarillo + {.r = 64, .g = 255, .b = 0}, // 105° - Verde claro-Amarillo + {.r = 0, .g = 255, .b = 0}, // 120° - Verde puro + {.r = 0, .g = 255, .b = 64}, // 135° - Verde-Cian claro + {.r = 0, .g = 255, .b = 128}, // 150° - Verde-Cian + {.r = 0, .g = 255, .b = 191}, // 165° - Verde claro-Cian + {.r = 0, .g = 255, .b = 255}, // 180° - Cian puro + {.r = 0, .g = 191, .b = 255}, // 195° - Cian-Azul claro + {.r = 0, .g = 128, .b = 255}, // 210° - Azul-Cian + {.r = 0, .g = 64, .b = 255}, // 225° - Azul claro-Cian + {.r = 0, .g = 0, .b = 255}, // 240° - Azul puro + {.r = 64, .g = 0, .b = 255}, // 255° - Azul-Magenta claro + {.r = 128, .g = 0, .b = 255}, // 270° - Azul-Magenta + {.r = 191, .g = 0, .b = 255}, // 285° - Azul claro-Magenta + {.r = 255, .g = 0, .b = 255}, // 300° - Magenta puro + {.r = 255, .g = 0, .b = 191}, // 315° - Magenta-Rojo claro + {.r = 255, .g = 0, .b = 128}, // 330° - Magenta-Rojo + {.r = 255, .g = 0, .b = 64} // 345° - Magenta claro-Rojo + })); // 5: MONOCHROME (Monocromo) - Fondo negro degradado, sprites blancos themes_.push_back(std::make_unique( "Monochrome", "Monocromo", - 200, 200, 200, // Color texto: gris claro - 50, 50, 50, // Color fondo notificación: gris medio oscuro (contrasta con texto claro) - 20.0f / 255.0f, 20.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: gris muy oscuro - 0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro + 200, + 200, + 200, // Color texto: gris claro + 50, + 50, + 50, // Color fondo notificación: gris medio oscuro (contrasta con texto claro) + 20.0f / 255.0f, + 20.0f / 255.0f, + 20.0f / 255.0f, // Fondo superior: gris muy oscuro + 0.0f / 255.0f, + 0.0f / 255.0f, + 0.0f / 255.0f, // Fondo inferior: negro std::vector{ - {255, 255, 255}, {255, 255, 255}, {255, 255, 255}, {255, 255, 255}, - {255, 255, 255}, {255, 255, 255}, {255, 255, 255}, {255, 255, 255} - } - )); + {.r = 255, .g = 255, .b = 255}, + {.r = 255, .g = 255, .b = 255}, + {.r = 255, .g = 255, .b = 255}, + {.r = 255, .g = 255, .b = 255}, + {.r = 255, .g = 255, .b = 255}, + {.r = 255, .g = 255, .b = 255}, + {.r = 255, .g = 255, .b = 255}, + {.r = 255, .g = 255, .b = 255}})); // 6: LAVENDER (Lavanda) - Degradado violeta oscuro → azul medianoche, pelotas amarillo dorado themes_.push_back(std::make_unique( "Lavender", "Lavanda", - 255, 200, 100, // Color texto: amarillo cálido - 80, 50, 100, // Color fondo notificación: violeta muy oscuro (contrasta con amarillo) - 120.0f / 255.0f, 80.0f / 255.0f, 140.0f / 255.0f, // Fondo superior: violeta oscuro - 25.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul medianoche + 255, + 200, + 100, // Color texto: amarillo cálido + 80, + 50, + 100, // Color fondo notificación: violeta muy oscuro (contrasta con amarillo) + 120.0f / 255.0f, + 80.0f / 255.0f, + 140.0f / 255.0f, // Fondo superior: violeta oscuro + 25.0f / 255.0f, + 30.0f / 255.0f, + 60.0f / 255.0f, // Fondo inferior: azul medianoche std::vector{ - {255, 215, 0}, {255, 215, 0}, {255, 215, 0}, {255, 215, 0}, - {255, 215, 0}, {255, 215, 0}, {255, 215, 0}, {255, 215, 0} - } - )); + {.r = 255, .g = 215, .b = 0}, + {.r = 255, .g = 215, .b = 0}, + {.r = 255, .g = 215, .b = 0}, + {.r = 255, .g = 215, .b = 0}, + {.r = 255, .g = 215, .b = 0}, + {.r = 255, .g = 215, .b = 0}, + {.r = 255, .g = 215, .b = 0}, + {.r = 255, .g = 215, .b = 0}})); // 7: CRIMSON (Carmesí) - Fondo negro-rojo oscuro, pelotas rojas uniformes themes_.push_back(std::make_unique( "Crimson", "Carmesí", - 255, 100, 100, // Color texto: rojo claro - 80, 10, 10, // Color fondo notificación: rojo muy oscuro (contrasta con texto claro) - 40.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: rojo muy oscuro - 0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro + 255, + 100, + 100, // Color texto: rojo claro + 80, + 10, + 10, // Color fondo notificación: rojo muy oscuro (contrasta con texto claro) + 40.0f / 255.0f, + 0.0f / 255.0f, + 0.0f / 255.0f, // Fondo superior: rojo muy oscuro + 0.0f / 255.0f, + 0.0f / 255.0f, + 0.0f / 255.0f, // Fondo inferior: negro puro std::vector{ - {220, 20, 60}, {220, 20, 60}, {220, 20, 60}, {220, 20, 60}, - {220, 20, 60}, {220, 20, 60}, {220, 20, 60}, {220, 20, 60} - } - )); + {.r = 220, .g = 20, .b = 60}, + {.r = 220, .g = 20, .b = 60}, + {.r = 220, .g = 20, .b = 60}, + {.r = 220, .g = 20, .b = 60}, + {.r = 220, .g = 20, .b = 60}, + {.r = 220, .g = 20, .b = 60}, + {.r = 220, .g = 20, .b = 60}, + {.r = 220, .g = 20, .b = 60}})); // 8: EMERALD (Esmeralda) - Fondo negro-verde oscuro, pelotas verdes uniformes themes_.push_back(std::make_unique( "Emerald", "Esmeralda", - 100, 255, 100, // Color texto: verde claro - 10, 80, 10, // Color fondo notificación: verde muy oscuro (contrasta con texto claro) - 0.0f / 255.0f, 40.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: verde muy oscuro - 0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro + 100, + 255, + 100, // Color texto: verde claro + 10, + 80, + 10, // Color fondo notificación: verde muy oscuro (contrasta con texto claro) + 0.0f / 255.0f, + 40.0f / 255.0f, + 0.0f / 255.0f, // Fondo superior: verde muy oscuro + 0.0f / 255.0f, + 0.0f / 255.0f, + 0.0f / 255.0f, // Fondo inferior: negro puro std::vector{ - {50, 205, 50}, {50, 205, 50}, {50, 205, 50}, {50, 205, 50}, - {50, 205, 50}, {50, 205, 50}, {50, 205, 50}, {50, 205, 50} - } - )); + {.r = 50, .g = 205, .b = 50}, + {.r = 50, .g = 205, .b = 50}, + {.r = 50, .g = 205, .b = 50}, + {.r = 50, .g = 205, .b = 50}, + {.r = 50, .g = 205, .b = 50}, + {.r = 50, .g = 205, .b = 50}, + {.r = 50, .g = 205, .b = 50}, + {.r = 50, .g = 205, .b = 50}})); // ======================================== // TEMAS DINÁMICOS (índices 9-14) @@ -171,276 +274,387 @@ void ThemeManager::initialize() { themes_.push_back(std::make_unique( "Sunrise", "Amanecer", - 255, 200, 100, // Color texto: amarillo cálido + 255, + 200, + 100, // Color texto: amarillo cálido std::vector{ // Keyframe 0: Noche oscura (estado inicial) { - 20.0f / 255.0f, 25.0f / 255.0f, 60.0f / 255.0f, // Fondo superior: azul medianoche - 10.0f / 255.0f, 10.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: azul muy oscuro - 20, 30, 80, // Color fondo notificación: azul oscuro (noche) - std::vector{ - {100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160}, - {95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165} - }, - 0.0f // Sin transición (estado inicial) + .bg_top_r = 20.0f / 255.0f, + .bg_top_g = 25.0f / 255.0f, + .bg_top_b = 60.0f / 255.0f, // Fondo superior: azul medianoche + .bg_bottom_r = 10.0f / 255.0f, + .bg_bottom_g = 10.0f / 255.0f, + .bg_bottom_b = 30.0f / 255.0f, // Fondo inferior: azul muy oscuro + .notif_bg_r = 20, + .notif_bg_g = 30, + .notif_bg_b = 80, // Color fondo notificación: azul oscuro (noche) + .ball_colors = std::vector{ + {.r = 100, .g = 100, .b = 150}, + {.r = 120, .g = 120, .b = 170}, + {.r = 90, .g = 90, .b = 140}, + {.r = 110, .g = 110, .b = 160}, + {.r = 95, .g = 95, .b = 145}, + {.r = 105, .g = 105, .b = 155}, + {.r = 100, .g = 100, .b = 150}, + {.r = 115, .g = 115, .b = 165}}, + .duration = 0.0f // Sin transición (estado inicial) }, // Keyframe 1: Alba naranja-rosa { - 180.0f / 255.0f, 100.0f / 255.0f, 120.0f / 255.0f, // Fondo superior: naranja-rosa - 255.0f / 255.0f, 140.0f / 255.0f, 100.0f / 255.0f, // Fondo inferior: naranja cálido - 140, 60, 80, // Color fondo notificación: naranja-rojo oscuro (alba) - std::vector{ - {255, 180, 100}, {255, 160, 80}, {255, 200, 120}, {255, 150, 90}, - {255, 190, 110}, {255, 170, 95}, {255, 185, 105}, {255, 165, 88} - }, - 4.0f // 4 segundos para llegar aquí + .bg_top_r = 180.0f / 255.0f, + .bg_top_g = 100.0f / 255.0f, + .bg_top_b = 120.0f / 255.0f, // Fondo superior: naranja-rosa + .bg_bottom_r = 255.0f / 255.0f, + .bg_bottom_g = 140.0f / 255.0f, + .bg_bottom_b = 100.0f / 255.0f, // Fondo inferior: naranja cálido + .notif_bg_r = 140, + .notif_bg_g = 60, + .notif_bg_b = 80, // Color fondo notificación: naranja-rojo oscuro (alba) + .ball_colors = std::vector{{.r = 255, .g = 180, .b = 100}, {.r = 255, .g = 160, .b = 80}, {.r = 255, .g = 200, .b = 120}, {.r = 255, .g = 150, .b = 90}, {.r = 255, .g = 190, .b = 110}, {.r = 255, .g = 170, .b = 95}, {.r = 255, .g = 185, .b = 105}, {.r = 255, .g = 165, .b = 88}}, + .duration = 4.0f // 4 segundos para llegar aquí }, // Keyframe 2: Día brillante amarillo { - 255.0f / 255.0f, 240.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: amarillo claro - 255.0f / 255.0f, 255.0f / 255.0f, 220.0f / 255.0f, // Fondo inferior: amarillo muy claro - 200, 180, 140, // Color fondo notificación: amarillo oscuro (día) - std::vector{ - {255, 255, 200}, {255, 255, 180}, {255, 255, 220}, {255, 255, 190}, - {255, 255, 210}, {255, 255, 185}, {255, 255, 205}, {255, 255, 195} - }, - 3.0f // 3 segundos para llegar aquí - } - // NOTA: Keyframe 3 (vuelta a noche) eliminado - loop=true lo maneja automáticamente + .bg_top_r = 255.0f / 255.0f, + .bg_top_g = 240.0f / 255.0f, + .bg_top_b = 180.0f / 255.0f, // Fondo superior: amarillo claro + .bg_bottom_r = 255.0f / 255.0f, + .bg_bottom_g = 255.0f / 255.0f, + .bg_bottom_b = 220.0f / 255.0f, // Fondo inferior: amarillo muy claro + .notif_bg_r = 200, + .notif_bg_g = 180, + .notif_bg_b = 140, // Color fondo notificación: amarillo oscuro (día) + .ball_colors = std::vector{{.r = 255, .g = 255, .b = 200}, {.r = 255, .g = 255, .b = 180}, {.r = 255, .g = 255, .b = 220}, {.r = 255, .g = 255, .b = 190}, {.r = 255, .g = 255, .b = 210}, {.r = 255, .g = 255, .b = 185}, {.r = 255, .g = 255, .b = 205}, {.r = 255, .g = 255, .b = 195}}, + .duration = 3.0f // 3 segundos para llegar aquí + } // NOTA: Keyframe 3 (vuelta a noche) eliminado - loop=true lo maneja automáticamente }, true // Loop = true - )); + )); // 10: OCEAN WAVES (Olas Oceánicas) - Azul oscuro ↔ Turquesa (loop) themes_.push_back(std::make_unique( "Ocean Waves", "Olas Oceánicas", - 100, 220, 255, // Color texto: cian claro + 100, + 220, + 255, // Color texto: cian claro std::vector{ // Keyframe 0: Profundidad oceánica (azul oscuro) { - 20.0f / 255.0f, 50.0f / 255.0f, 100.0f / 255.0f, // Fondo superior: azul marino - 10.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul muy oscuro - 10, 30, 70, // Color fondo notificación: azul muy oscuro (profundidad) - std::vector{ - {60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175}, - {65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168} - }, - 0.0f // Estado inicial + .bg_top_r = 20.0f / 255.0f, + .bg_top_g = 50.0f / 255.0f, + .bg_top_b = 100.0f / 255.0f, // Fondo superior: azul marino + .bg_bottom_r = 10.0f / 255.0f, + .bg_bottom_g = 30.0f / 255.0f, + .bg_bottom_b = 60.0f / 255.0f, // Fondo inferior: azul muy oscuro + .notif_bg_r = 10, + .notif_bg_g = 30, + .notif_bg_b = 70, // Color fondo notificación: azul muy oscuro (profundidad) + .ball_colors = std::vector{ + {.r = 60, .g = 100, .b = 180}, + {.r = 50, .g = 90, .b = 170}, + {.r = 70, .g = 110, .b = 190}, + {.r = 55, .g = 95, .b = 175}, + {.r = 65, .g = 105, .b = 185}, + {.r = 58, .g = 98, .b = 172}, + {.r = 62, .g = 102, .b = 182}, + {.r = 52, .g = 92, .b = 168}}, + .duration = 0.0f // Estado inicial }, // Keyframe 1: Aguas poco profundas (turquesa brillante) { - 100.0f / 255.0f, 200.0f / 255.0f, 230.0f / 255.0f, // Fondo superior: turquesa claro - 50.0f / 255.0f, 150.0f / 255.0f, 200.0f / 255.0f, // Fondo inferior: turquesa medio - 30, 100, 140, // Color fondo notificación: turquesa oscuro (aguas poco profundas) - std::vector{ - {100, 220, 255}, {90, 210, 245}, {110, 230, 255}, {95, 215, 250}, - {105, 225, 255}, {98, 218, 248}, {102, 222, 252}, {92, 212, 242} - }, - 4.0f // 4 segundos para llegar - } - // NOTA: Keyframe 2 (vuelta a profundidad) eliminado - loop=true lo maneja automáticamente + .bg_top_r = 100.0f / 255.0f, + .bg_top_g = 200.0f / 255.0f, + .bg_top_b = 230.0f / 255.0f, // Fondo superior: turquesa claro + .bg_bottom_r = 50.0f / 255.0f, + .bg_bottom_g = 150.0f / 255.0f, + .bg_bottom_b = 200.0f / 255.0f, // Fondo inferior: turquesa medio + .notif_bg_r = 30, + .notif_bg_g = 100, + .notif_bg_b = 140, // Color fondo notificación: turquesa oscuro (aguas poco profundas) + .ball_colors = std::vector{{.r = 100, .g = 220, .b = 255}, {.r = 90, .g = 210, .b = 245}, {.r = 110, .g = 230, .b = 255}, {.r = 95, .g = 215, .b = 250}, {.r = 105, .g = 225, .b = 255}, {.r = 98, .g = 218, .b = 248}, {.r = 102, .g = 222, .b = 252}, {.r = 92, .g = 212, .b = 242}}, + .duration = 4.0f // 4 segundos para llegar + } // NOTA: Keyframe 2 (vuelta a profundidad) eliminado - loop=true lo maneja automáticamente }, true // Loop = true - )); + )); // 11: NEON PULSE (Pulso Neón) - Negro → Neón brillante (rápido ping-pong) themes_.push_back(std::make_unique( "Neon Pulse", "Pulso Neón", - 255, 60, 255, // Color texto: magenta brillante + 255, + 60, + 255, // Color texto: magenta brillante std::vector{ // Keyframe 0: Apagado (negro) { - 0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: negro - 0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro - 30, 30, 30, // Color fondo notificación: gris muy oscuro (apagado) - std::vector{ - {40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48}, - {42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46} - }, - 0.0f // Estado inicial + .bg_top_r = 0.0f / 255.0f, + .bg_top_g = 0.0f / 255.0f, + .bg_top_b = 0.0f / 255.0f, // Fondo superior: negro + .bg_bottom_r = 0.0f / 255.0f, + .bg_bottom_g = 0.0f / 255.0f, + .bg_bottom_b = 0.0f / 255.0f, // Fondo inferior: negro + .notif_bg_r = 30, + .notif_bg_g = 30, + .notif_bg_b = 30, // Color fondo notificación: gris muy oscuro (apagado) + .ball_colors = std::vector{ + {.r = 40, .g = 40, .b = 40}, + {.r = 50, .g = 50, .b = 50}, + {.r = 45, .g = 45, .b = 45}, + {.r = 48, .g = 48, .b = 48}, + {.r = 42, .g = 42, .b = 42}, + {.r = 47, .g = 47, .b = 47}, + {.r = 44, .g = 44, .b = 44}, + {.r = 46, .g = 46, .b = 46}}, + .duration = 0.0f // Estado inicial }, // Keyframe 1: Encendido (neón cian-magenta) { - 20.0f / 255.0f, 20.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: azul oscuro - 0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro - 60, 0, 80, // Color fondo notificación: púrpura oscuro (neón encendido) - std::vector{ - {0, 255, 255}, {255, 0, 255}, {0, 255, 200}, {255, 50, 255}, - {50, 255, 255}, {255, 0, 200}, {0, 255, 230}, {255, 80, 255} - }, - 1.5f // 1.5 segundos para encender (rápido) - } - // NOTA: Keyframe 2 (vuelta a apagado) eliminado - loop=true crea ping-pong automáticamente + .bg_top_r = 20.0f / 255.0f, + .bg_top_g = 20.0f / 255.0f, + .bg_top_b = 40.0f / 255.0f, // Fondo superior: azul oscuro + .bg_bottom_r = 0.0f / 255.0f, + .bg_bottom_g = 0.0f / 255.0f, + .bg_bottom_b = 0.0f / 255.0f, // Fondo inferior: negro + .notif_bg_r = 60, + .notif_bg_g = 0, + .notif_bg_b = 80, // Color fondo notificación: púrpura oscuro (neón encendido) + .ball_colors = std::vector{{.r = 0, .g = 255, .b = 255}, {.r = 255, .g = 0, .b = 255}, {.r = 0, .g = 255, .b = 200}, {.r = 255, .g = 50, .b = 255}, {.r = 50, .g = 255, .b = 255}, {.r = 255, .g = 0, .b = 200}, {.r = 0, .g = 255, .b = 230}, {.r = 255, .g = 80, .b = 255}}, + .duration = 1.5f // 1.5 segundos para encender (rápido) + } // NOTA: Keyframe 2 (vuelta a apagado) eliminado - loop=true crea ping-pong automáticamente }, true // Loop = true - )); + )); // 12: FIRE (Fuego Vivo) - Brasas → Llamas → Inferno (loop) themes_.push_back(std::make_unique( "Fire", "Fuego", - 255, 150, 80, // Color texto: naranja cálido + 255, + 150, + 80, // Color texto: naranja cálido std::vector{ // Keyframe 0: Brasas oscuras (estado inicial) { - 60.0f / 255.0f, 20.0f / 255.0f, 10.0f / 255.0f, // Fondo superior: rojo muy oscuro - 20.0f / 255.0f, 10.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: casi negro - 70, 20, 10, // Color fondo notificación: rojo muy oscuro (brasas) - std::vector{ - {120, 40, 20}, {140, 35, 15}, {130, 38, 18}, {125, 42, 22}, - {135, 37, 16}, {128, 40, 20}, {132, 39, 19}, {138, 36, 17} - }, - 0.0f // Estado inicial + .bg_top_r = 60.0f / 255.0f, + .bg_top_g = 20.0f / 255.0f, + .bg_top_b = 10.0f / 255.0f, // Fondo superior: rojo muy oscuro + .bg_bottom_r = 20.0f / 255.0f, + .bg_bottom_g = 10.0f / 255.0f, + .bg_bottom_b = 0.0f / 255.0f, // Fondo inferior: casi negro + .notif_bg_r = 70, + .notif_bg_g = 20, + .notif_bg_b = 10, // Color fondo notificación: rojo muy oscuro (brasas) + .ball_colors = std::vector{ + {.r = 120, .g = 40, .b = 20}, + {.r = 140, .g = 35, .b = 15}, + {.r = 130, .g = 38, .b = 18}, + {.r = 125, .g = 42, .b = 22}, + {.r = 135, .g = 37, .b = 16}, + {.r = 128, .g = 40, .b = 20}, + {.r = 132, .g = 39, .b = 19}, + {.r = 138, .g = 36, .b = 17}}, + .duration = 0.0f // Estado inicial }, // Keyframe 1: Llamas naranjas (transición) { - 180.0f / 255.0f, 80.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: naranja fuerte - 100.0f / 255.0f, 30.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro - 110, 40, 10, // Color fondo notificación: naranja-rojo oscuro (llamas) - std::vector{ - {255, 140, 0}, {255, 120, 10}, {255, 160, 20}, {255, 130, 5}, - {255, 150, 15}, {255, 125, 8}, {255, 145, 12}, {255, 135, 18} - }, - 3.5f // 3.5 segundos para llegar + .bg_top_r = 180.0f / 255.0f, + .bg_top_g = 80.0f / 255.0f, + .bg_top_b = 20.0f / 255.0f, // Fondo superior: naranja fuerte + .bg_bottom_r = 100.0f / 255.0f, + .bg_bottom_g = 30.0f / 255.0f, + .bg_bottom_b = 10.0f / 255.0f, // Fondo inferior: rojo oscuro + .notif_bg_r = 110, + .notif_bg_g = 40, + .notif_bg_b = 10, // Color fondo notificación: naranja-rojo oscuro (llamas) + .ball_colors = std::vector{{.r = 255, .g = 140, .b = 0}, {.r = 255, .g = 120, .b = 10}, {.r = 255, .g = 160, .b = 20}, {.r = 255, .g = 130, .b = 5}, {.r = 255, .g = 150, .b = 15}, {.r = 255, .g = 125, .b = 8}, {.r = 255, .g = 145, .b = 12}, {.r = 255, .g = 135, .b = 18}}, + .duration = 3.5f // 3.5 segundos para llegar }, // Keyframe 2: Inferno brillante (clímax) { - 255.0f / 255.0f, 180.0f / 255.0f, 80.0f / 255.0f, // Fondo superior: amarillo-naranja brillante - 220.0f / 255.0f, 100.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: naranja intenso - 160, 80, 30, // Color fondo notificación: naranja oscuro (inferno) - std::vector{ - {255, 220, 100}, {255, 200, 80}, {255, 240, 120}, {255, 210, 90}, - {255, 230, 110}, {255, 205, 85}, {255, 225, 105}, {255, 215, 95} - }, - 3.0f // 3 segundos para llegar + .bg_top_r = 255.0f / 255.0f, + .bg_top_g = 180.0f / 255.0f, + .bg_top_b = 80.0f / 255.0f, // Fondo superior: amarillo-naranja brillante + .bg_bottom_r = 220.0f / 255.0f, + .bg_bottom_g = 100.0f / 255.0f, + .bg_bottom_b = 30.0f / 255.0f, // Fondo inferior: naranja intenso + .notif_bg_r = 160, + .notif_bg_g = 80, + .notif_bg_b = 30, // Color fondo notificación: naranja oscuro (inferno) + .ball_colors = std::vector{{.r = 255, .g = 220, .b = 100}, {.r = 255, .g = 200, .b = 80}, {.r = 255, .g = 240, .b = 120}, {.r = 255, .g = 210, .b = 90}, {.r = 255, .g = 230, .b = 110}, {.r = 255, .g = 205, .b = 85}, {.r = 255, .g = 225, .b = 105}, {.r = 255, .g = 215, .b = 95}}, + .duration = 3.0f // 3 segundos para llegar }, // Keyframe 3: Vuelta a llamas (antes de reiniciar loop) { - 180.0f / 255.0f, 80.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: naranja fuerte - 100.0f / 255.0f, 30.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro - 110, 40, 10, // Color fondo notificación: naranja-rojo oscuro (llamas) - std::vector{ - {255, 140, 0}, {255, 120, 10}, {255, 160, 20}, {255, 130, 5}, - {255, 150, 15}, {255, 125, 8}, {255, 145, 12}, {255, 135, 18} - }, - 3.5f // 3.5 segundos para volver - } - // Loop = true hará transición automática de keyframe 3 → keyframe 0 + .bg_top_r = 180.0f / 255.0f, + .bg_top_g = 80.0f / 255.0f, + .bg_top_b = 20.0f / 255.0f, // Fondo superior: naranja fuerte + .bg_bottom_r = 100.0f / 255.0f, + .bg_bottom_g = 30.0f / 255.0f, + .bg_bottom_b = 10.0f / 255.0f, // Fondo inferior: rojo oscuro + .notif_bg_r = 110, + .notif_bg_g = 40, + .notif_bg_b = 10, // Color fondo notificación: naranja-rojo oscuro (llamas) + .ball_colors = std::vector{{.r = 255, .g = 140, .b = 0}, {.r = 255, .g = 120, .b = 10}, {.r = 255, .g = 160, .b = 20}, {.r = 255, .g = 130, .b = 5}, {.r = 255, .g = 150, .b = 15}, {.r = 255, .g = 125, .b = 8}, {.r = 255, .g = 145, .b = 12}, {.r = 255, .g = 135, .b = 18}}, + .duration = 3.5f // 3.5 segundos para volver + } // Loop = true hará transición automática de keyframe 3 → keyframe 0 }, true // Loop = true (ciclo completo: brasas→llamas→inferno→llamas→brasas...) - )); + )); // 13: AURORA (Aurora Boreal) - Verde → Violeta → Cian (loop) themes_.push_back(std::make_unique( "Aurora", "Aurora", - 150, 255, 200, // Color texto: verde claro + 150, + 255, + 200, // Color texto: verde claro std::vector{ // Keyframe 0: Verde aurora (estado inicial) { - 30.0f / 255.0f, 80.0f / 255.0f, 60.0f / 255.0f, // Fondo superior: verde oscuro - 10.0f / 255.0f, 20.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: azul muy oscuro - 15, 50, 40, // Color fondo notificación: verde muy oscuro (aurora verde) - std::vector{ - {100, 255, 180}, {80, 240, 160}, {120, 255, 200}, {90, 245, 170}, - {110, 255, 190}, {85, 242, 165}, {105, 252, 185}, {95, 248, 175} - }, - 0.0f // Estado inicial + .bg_top_r = 30.0f / 255.0f, + .bg_top_g = 80.0f / 255.0f, + .bg_top_b = 60.0f / 255.0f, // Fondo superior: verde oscuro + .bg_bottom_r = 10.0f / 255.0f, + .bg_bottom_g = 20.0f / 255.0f, + .bg_bottom_b = 30.0f / 255.0f, // Fondo inferior: azul muy oscuro + .notif_bg_r = 15, + .notif_bg_g = 50, + .notif_bg_b = 40, // Color fondo notificación: verde muy oscuro (aurora verde) + .ball_colors = std::vector{ + {.r = 100, .g = 255, .b = 180}, + {.r = 80, .g = 240, .b = 160}, + {.r = 120, .g = 255, .b = 200}, + {.r = 90, .g = 245, .b = 170}, + {.r = 110, .g = 255, .b = 190}, + {.r = 85, .g = 242, .b = 165}, + {.r = 105, .g = 252, .b = 185}, + {.r = 95, .g = 248, .b = 175}}, + .duration = 0.0f // Estado inicial }, // Keyframe 1: Violeta aurora (transición) { - 120.0f / 255.0f, 60.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: violeta - 40.0f / 255.0f, 20.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: violeta oscuro - 70, 30, 100, // Color fondo notificación: violeta oscuro (aurora violeta) - std::vector{ - {200, 100, 255}, {180, 80, 240}, {220, 120, 255}, {190, 90, 245}, - {210, 110, 255}, {185, 85, 242}, {205, 105, 252}, {195, 95, 248} - }, - 5.0f // 5 segundos para llegar (transición lenta) + .bg_top_r = 120.0f / 255.0f, + .bg_top_g = 60.0f / 255.0f, + .bg_top_b = 180.0f / 255.0f, // Fondo superior: violeta + .bg_bottom_r = 40.0f / 255.0f, + .bg_bottom_g = 20.0f / 255.0f, + .bg_bottom_b = 80.0f / 255.0f, // Fondo inferior: violeta oscuro + .notif_bg_r = 70, + .notif_bg_g = 30, + .notif_bg_b = 100, // Color fondo notificación: violeta oscuro (aurora violeta) + .ball_colors = std::vector{{.r = 200, .g = 100, .b = 255}, {.r = 180, .g = 80, .b = 240}, {.r = 220, .g = 120, .b = 255}, {.r = 190, .g = 90, .b = 245}, {.r = 210, .g = 110, .b = 255}, {.r = 185, .g = 85, .b = 242}, {.r = 205, .g = 105, .b = 252}, {.r = 195, .g = 95, .b = 248}}, + .duration = 5.0f // 5 segundos para llegar (transición lenta) }, // Keyframe 2: Cian aurora (clímax) { - 60.0f / 255.0f, 180.0f / 255.0f, 220.0f / 255.0f, // Fondo superior: cian brillante - 20.0f / 255.0f, 80.0f / 255.0f, 120.0f / 255.0f, // Fondo inferior: azul oscuro - 20, 90, 120, // Color fondo notificación: cian oscuro (aurora cian) - std::vector{ - {100, 220, 255}, {80, 200, 240}, {120, 240, 255}, {90, 210, 245}, - {110, 230, 255}, {85, 205, 242}, {105, 225, 252}, {95, 215, 248} - }, - 4.5f // 4.5 segundos para llegar + .bg_top_r = 60.0f / 255.0f, + .bg_top_g = 180.0f / 255.0f, + .bg_top_b = 220.0f / 255.0f, // Fondo superior: cian brillante + .bg_bottom_r = 20.0f / 255.0f, + .bg_bottom_g = 80.0f / 255.0f, + .bg_bottom_b = 120.0f / 255.0f, // Fondo inferior: azul oscuro + .notif_bg_r = 20, + .notif_bg_g = 90, + .notif_bg_b = 120, // Color fondo notificación: cian oscuro (aurora cian) + .ball_colors = std::vector{{.r = 100, .g = 220, .b = 255}, {.r = 80, .g = 200, .b = 240}, {.r = 120, .g = 240, .b = 255}, {.r = 90, .g = 210, .b = 245}, {.r = 110, .g = 230, .b = 255}, {.r = 85, .g = 205, .b = 242}, {.r = 105, .g = 225, .b = 252}, {.r = 95, .g = 215, .b = 248}}, + .duration = 4.5f // 4.5 segundos para llegar }, // Keyframe 3: Vuelta a violeta (antes de reiniciar) { - 120.0f / 255.0f, 60.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: violeta - 40.0f / 255.0f, 20.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: violeta oscuro - 70, 30, 100, // Color fondo notificación: violeta oscuro (aurora violeta) - std::vector{ - {200, 100, 255}, {180, 80, 240}, {220, 120, 255}, {190, 90, 245}, - {210, 110, 255}, {185, 85, 242}, {205, 105, 252}, {195, 95, 248} - }, - 4.5f // 4.5 segundos para volver - } - // Loop = true hará transición automática de keyframe 3 → keyframe 0 + .bg_top_r = 120.0f / 255.0f, + .bg_top_g = 60.0f / 255.0f, + .bg_top_b = 180.0f / 255.0f, // Fondo superior: violeta + .bg_bottom_r = 40.0f / 255.0f, + .bg_bottom_g = 20.0f / 255.0f, + .bg_bottom_b = 80.0f / 255.0f, // Fondo inferior: violeta oscuro + .notif_bg_r = 70, + .notif_bg_g = 30, + .notif_bg_b = 100, // Color fondo notificación: violeta oscuro (aurora violeta) + .ball_colors = std::vector{{.r = 200, .g = 100, .b = 255}, {.r = 180, .g = 80, .b = 240}, {.r = 220, .g = 120, .b = 255}, {.r = 190, .g = 90, .b = 245}, {.r = 210, .g = 110, .b = 255}, {.r = 185, .g = 85, .b = 242}, {.r = 205, .g = 105, .b = 252}, {.r = 195, .g = 95, .b = 248}}, + .duration = 4.5f // 4.5 segundos para volver + } // Loop = true hará transición automática de keyframe 3 → keyframe 0 }, true // Loop = true (ciclo: verde→violeta→cian→violeta→verde...) - )); + )); // 14: VOLCANIC (Erupción Volcánica) - Ceniza → Erupción → Lava (loop) themes_.push_back(std::make_unique( "Volcanic", "Volcán", - 200, 120, 80, // Color texto: naranja apagado + 200, + 120, + 80, // Color texto: naranja apagado std::vector{ // Keyframe 0: Ceniza oscura (pre-erupción) { - 40.0f / 255.0f, 40.0f / 255.0f, 45.0f / 255.0f, // Fondo superior: gris oscuro - 20.0f / 255.0f, 15.0f / 255.0f, 15.0f / 255.0f, // Fondo inferior: casi negro - 50, 50, 55, // Color fondo notificación: gris oscuro (ceniza) - std::vector{ - {80, 80, 90}, {75, 75, 85}, {85, 85, 95}, {78, 78, 88}, - {82, 82, 92}, {76, 76, 86}, {84, 84, 94}, {79, 79, 89} - }, - 0.0f // Estado inicial + .bg_top_r = 40.0f / 255.0f, + .bg_top_g = 40.0f / 255.0f, + .bg_top_b = 45.0f / 255.0f, // Fondo superior: gris oscuro + .bg_bottom_r = 20.0f / 255.0f, + .bg_bottom_g = 15.0f / 255.0f, + .bg_bottom_b = 15.0f / 255.0f, // Fondo inferior: casi negro + .notif_bg_r = 50, + .notif_bg_g = 50, + .notif_bg_b = 55, // Color fondo notificación: gris oscuro (ceniza) + .ball_colors = std::vector{ + {.r = 80, .g = 80, .b = 90}, + {.r = 75, .g = 75, .b = 85}, + {.r = 85, .g = 85, .b = 95}, + {.r = 78, .g = 78, .b = 88}, + {.r = 82, .g = 82, .b = 92}, + {.r = 76, .g = 76, .b = 86}, + {.r = 84, .g = 84, .b = 94}, + {.r = 79, .g = 79, .b = 89}}, + .duration = 0.0f // Estado inicial }, // Keyframe 1: Erupción naranja-roja (explosión) { - 180.0f / 255.0f, 60.0f / 255.0f, 30.0f / 255.0f, // Fondo superior: naranja-rojo - 80.0f / 255.0f, 20.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro - 120, 30, 15, // Color fondo notificación: naranja-rojo oscuro (erupción) - std::vector{ - {255, 80, 40}, {255, 100, 50}, {255, 70, 35}, {255, 90, 45}, - {255, 75, 38}, {255, 95, 48}, {255, 85, 42}, {255, 78, 40} - }, - 3.0f // 3 segundos para erupción (rápido) + .bg_top_r = 180.0f / 255.0f, + .bg_top_g = 60.0f / 255.0f, + .bg_top_b = 30.0f / 255.0f, // Fondo superior: naranja-rojo + .bg_bottom_r = 80.0f / 255.0f, + .bg_bottom_g = 20.0f / 255.0f, + .bg_bottom_b = 10.0f / 255.0f, // Fondo inferior: rojo oscuro + .notif_bg_r = 120, + .notif_bg_g = 30, + .notif_bg_b = 15, // Color fondo notificación: naranja-rojo oscuro (erupción) + .ball_colors = std::vector{{.r = 255, .g = 80, .b = 40}, {.r = 255, .g = 100, .b = 50}, {.r = 255, .g = 70, .b = 35}, {.r = 255, .g = 90, .b = 45}, {.r = 255, .g = 75, .b = 38}, {.r = 255, .g = 95, .b = 48}, {.r = 255, .g = 85, .b = 42}, {.r = 255, .g = 78, .b = 40}}, + .duration = 3.0f // 3 segundos para erupción (rápido) }, // Keyframe 2: Lava brillante (clímax) { - 220.0f / 255.0f, 120.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: naranja brillante - 180.0f / 255.0f, 60.0f / 255.0f, 20.0f / 255.0f, // Fondo inferior: naranja-rojo - 150, 70, 25, // Color fondo notificación: naranja oscuro (lava) - std::vector{ - {255, 180, 80}, {255, 200, 100}, {255, 170, 70}, {255, 190, 90}, - {255, 175, 75}, {255, 195, 95}, {255, 185, 85}, {255, 178, 78} - }, - 3.5f // 3.5 segundos para lava máxima + .bg_top_r = 220.0f / 255.0f, + .bg_top_g = 120.0f / 255.0f, + .bg_top_b = 40.0f / 255.0f, // Fondo superior: naranja brillante + .bg_bottom_r = 180.0f / 255.0f, + .bg_bottom_g = 60.0f / 255.0f, + .bg_bottom_b = 20.0f / 255.0f, // Fondo inferior: naranja-rojo + .notif_bg_r = 150, + .notif_bg_g = 70, + .notif_bg_b = 25, // Color fondo notificación: naranja oscuro (lava) + .ball_colors = std::vector{{.r = 255, .g = 180, .b = 80}, {.r = 255, .g = 200, .b = 100}, {.r = 255, .g = 170, .b = 70}, {.r = 255, .g = 190, .b = 90}, {.r = 255, .g = 175, .b = 75}, {.r = 255, .g = 195, .b = 95}, {.r = 255, .g = 185, .b = 85}, {.r = 255, .g = 178, .b = 78}}, + .duration = 3.5f // 3.5 segundos para lava máxima }, // Keyframe 3: Enfriamiento (vuelta a ceniza gradual) { - 100.0f / 255.0f, 80.0f / 255.0f, 70.0f / 255.0f, // Fondo superior: gris-naranja - 50.0f / 255.0f, 40.0f / 255.0f, 35.0f / 255.0f, // Fondo inferior: gris oscuro - 80, 60, 50, // Color fondo notificación: gris-naranja oscuro (enfriamiento) - std::vector{ - {150, 120, 100}, {140, 110, 90}, {160, 130, 110}, {145, 115, 95}, - {155, 125, 105}, {142, 112, 92}, {158, 128, 108}, {148, 118, 98} - }, - 5.5f // 5.5 segundos para enfriamiento (lento) - } - // Loop = true hará transición automática de keyframe 3 → keyframe 0 + .bg_top_r = 100.0f / 255.0f, + .bg_top_g = 80.0f / 255.0f, + .bg_top_b = 70.0f / 255.0f, // Fondo superior: gris-naranja + .bg_bottom_r = 50.0f / 255.0f, + .bg_bottom_g = 40.0f / 255.0f, + .bg_bottom_b = 35.0f / 255.0f, // Fondo inferior: gris oscuro + .notif_bg_r = 80, + .notif_bg_g = 60, + .notif_bg_b = 50, // Color fondo notificación: gris-naranja oscuro (enfriamiento) + .ball_colors = std::vector{{.r = 150, .g = 120, .b = 100}, {.r = 140, .g = 110, .b = 90}, {.r = 160, .g = 130, .b = 110}, {.r = 145, .g = 115, .b = 95}, {.r = 155, .g = 125, .b = 105}, {.r = 142, .g = 112, .b = 92}, {.r = 158, .g = 128, .b = 108}, {.r = 148, .g = 118, .b = 98}}, + .duration = 5.5f // 5.5 segundos para enfriamiento (lento) + } // Loop = true hará transición automática de keyframe 3 → keyframe 0 }, true // Loop = true (ciclo: ceniza→erupción→lava→enfriamiento→ceniza...) - )); + )); } // ============================================================================ @@ -525,7 +739,7 @@ void ThemeManager::pauseDynamic() { // QUERIES DE COLORES // ============================================================================ -Color ThemeManager::getInterpolatedColor(size_t ball_index) const { +auto ThemeManager::getInterpolatedColor(size_t ball_index) const -> Color { if (!transitioning_ || !source_snapshot_) { // Sin transición: color directo del tema activo return themes_[current_theme_index_]->getBallColor(ball_index, 0.0f); @@ -536,14 +750,12 @@ Color ThemeManager::getInterpolatedColor(size_t ball_index) const { Color target_color = themes_[current_theme_index_]->getBallColor(ball_index, 0.0f); return { - static_cast(lerp(static_cast(source_color.r), static_cast(target_color.r), transition_progress_)), - static_cast(lerp(static_cast(source_color.g), static_cast(target_color.g), transition_progress_)), - static_cast(lerp(static_cast(source_color.b), static_cast(target_color.b), transition_progress_)) - }; + .r = static_cast(lerp(static_cast(source_color.r), static_cast(target_color.r), transition_progress_)), + .g = static_cast(lerp(static_cast(source_color.g), static_cast(target_color.g), transition_progress_)), + .b = static_cast(lerp(static_cast(source_color.b), static_cast(target_color.b), transition_progress_))}; } -void ThemeManager::getBackgroundColors(float& top_r, float& top_g, float& top_b, - float& bottom_r, float& bottom_g, float& bottom_b) const { +void ThemeManager::getBackgroundColors(float& top_r, float& top_g, float& top_b, float& bottom_r, float& bottom_g, float& bottom_b) const { if (!transitioning_ || !source_snapshot_) { // Sin transición: colores directos del tema activo themes_[current_theme_index_]->getBackgroundColors(0.0f, top_r, top_g, top_b, bottom_r, bottom_g, bottom_b); @@ -551,7 +763,12 @@ void ThemeManager::getBackgroundColors(float& top_r, float& top_g, float& top_b, } // PHASE 3: Con transición: LERP entre snapshot origen y tema destino - float target_tr, target_tg, target_tb, target_br, target_bg, target_bb; + float target_tr; + float target_tg; + float target_tb; + float target_br; + float target_bg; + float target_bb; themes_[current_theme_index_]->getBackgroundColors(0.0f, target_tr, target_tg, target_tb, target_br, target_bg, target_bb); top_r = lerp(source_snapshot_->bg_top_r, target_tr, transition_progress_); @@ -567,15 +784,15 @@ void ThemeManager::getBackgroundColors(float& top_r, float& top_g, float& top_b, // QUERIES DE ESTADO // ============================================================================ -bool ThemeManager::isCurrentThemeDynamic() const { +auto ThemeManager::isCurrentThemeDynamic() const -> bool { return themes_[current_theme_index_]->needsUpdate(); } -const char* ThemeManager::getCurrentThemeNameEN() const { +auto ThemeManager::getCurrentThemeNameEN() const -> const char* { return themes_[current_theme_index_]->getNameEN(); } -const char* ThemeManager::getCurrentThemeNameES() const { +auto ThemeManager::getCurrentThemeNameES() const -> const char* { return themes_[current_theme_index_]->getNameES(); } @@ -587,7 +804,9 @@ void ThemeManager::getCurrentThemeTextColor(int& r, int& g, int& b) const { } // PHASE 3: Con transición: LERP entre snapshot origen y tema destino - int target_r, target_g, target_b; + int target_r; + int target_g; + int target_b; themes_[current_theme_index_]->getTextColor(target_r, target_g, target_b); r = static_cast(lerp(static_cast(source_snapshot_->text_color_r), static_cast(target_r), transition_progress_)); @@ -603,7 +822,9 @@ void ThemeManager::getCurrentNotificationBackgroundColor(int& r, int& g, int& b) } // PHASE 3: Con transición: LERP entre snapshot origen y tema destino - int target_r, target_g, target_b; + int target_r; + int target_g; + int target_b; themes_[current_theme_index_]->getNotificationBackgroundColor(target_r, target_g, target_b); r = static_cast(lerp(static_cast(source_snapshot_->notif_bg_r), static_cast(target_r), transition_progress_)); @@ -611,7 +832,7 @@ void ThemeManager::getCurrentNotificationBackgroundColor(int& r, int& g, int& b) b = static_cast(lerp(static_cast(source_snapshot_->notif_bg_b), static_cast(target_b), transition_progress_)); } -Color ThemeManager::getInitialBallColor(int random_index) const { +auto ThemeManager::getInitialBallColor(int random_index) const -> Color { // Obtener color inicial del tema activo (progress = 0.0f) return themes_[current_theme_index_]->getBallColor(random_index, 0.0f); } @@ -620,21 +841,29 @@ Color ThemeManager::getInitialBallColor(int random_index) const { // SISTEMA DE TRANSICIÓN LERP (PHASE 3) // ============================================================================ -std::unique_ptr ThemeManager::captureCurrentSnapshot() const { +auto ThemeManager::captureCurrentSnapshot() const -> std::unique_ptr { auto snapshot = std::make_unique(); // Capturar colores de fondo themes_[current_theme_index_]->getBackgroundColors(0.0f, - snapshot->bg_top_r, snapshot->bg_top_g, snapshot->bg_top_b, - snapshot->bg_bottom_r, snapshot->bg_bottom_g, snapshot->bg_bottom_b); + snapshot->bg_top_r, + snapshot->bg_top_g, + snapshot->bg_top_b, + snapshot->bg_bottom_r, + snapshot->bg_bottom_g, + snapshot->bg_bottom_b); // Capturar color de texto themes_[current_theme_index_]->getTextColor( - snapshot->text_color_r, snapshot->text_color_g, snapshot->text_color_b); + snapshot->text_color_r, + snapshot->text_color_g, + snapshot->text_color_b); // Capturar color de fondo de notificaciones themes_[current_theme_index_]->getNotificationBackgroundColor( - snapshot->notif_bg_r, snapshot->notif_bg_g, snapshot->notif_bg_b); + snapshot->notif_bg_r, + snapshot->notif_bg_g, + snapshot->notif_bg_b); // Capturar nombres snapshot->name_en = themes_[current_theme_index_]->getNameEN(); @@ -645,8 +874,7 @@ std::unique_ptr ThemeManager::captureCurrentSnapshot() const { snapshot->ball_colors.reserve(max_ball_count_); for (int i = 0; i < max_ball_count_; i++) { snapshot->ball_colors.push_back( - themes_[current_theme_index_]->getBallColor(i, 0.0f) - ); + themes_[current_theme_index_]->getBallColor(i, 0.0f)); } return snapshot; diff --git a/source/theme_manager.hpp b/source/theme_manager.hpp index f942b4c..5e22ecf 100644 --- a/source/theme_manager.hpp +++ b/source/theme_manager.hpp @@ -3,10 +3,10 @@ #include // for unique_ptr #include // for vector -#include "ball.hpp" // for Ball class -#include "defines.hpp" // for Color, ColorTheme -#include "themes/theme.hpp" // for Theme interface -#include "themes/theme_snapshot.hpp" // for ThemeSnapshot +#include "ball.hpp" // for Ball class +#include "defines.hpp" // for Color, ColorTheme +#include "themes/theme.hpp" // for Theme interface +#include "themes/theme_snapshot.hpp" // for ThemeSnapshot /** * ThemeManager: Gestiona el sistema de temas visuales (unificado, estáticos y dinámicos) @@ -42,7 +42,7 @@ class ThemeManager { ~ThemeManager() = default; // Inicialización - void initialize(); // Inicializa 15 temas unificados (9 estáticos + 6 dinámicos) + void initialize(); // Inicializa 15 temas unificados (9 estáticos + 6 dinámicos) void setMaxBallCount(int n) { max_ball_count_ = n; } // Máximo real (escenario 8 o custom si mayor) // Interfaz unificada (PHASE 2 + PHASE 3) @@ -53,9 +53,8 @@ class ThemeManager { void pauseDynamic(); // Toggle pausa de animación (Shift+D, solo dinámicos) // Queries de colores (usado en rendering) - Color getInterpolatedColor(size_t ball_index) const; // Obtiene color interpolado para pelota - void getBackgroundColors(float& top_r, float& top_g, float& top_b, - float& bottom_r, float& bottom_g, float& bottom_b) const; // Obtiene colores de fondo degradado + Color getInterpolatedColor(size_t ball_index) const; // Obtiene color interpolado para pelota + void getBackgroundColors(float& top_r, float& top_g, float& top_b, float& bottom_r, float& bottom_g, float& bottom_b) const; // Obtiene colores de fondo degradado // Queries de estado (para debug display y lógica) int getCurrentThemeIndex() const { return current_theme_index_; } @@ -89,13 +88,13 @@ class ThemeManager { // ======================================== // Estado de transición - bool transitioning_ = false; // ¿Hay transición LERP activa? - float transition_progress_ = 0.0f; // Progreso 0.0-1.0 (0.0 = origen, 1.0 = destino) + bool transitioning_ = false; // ¿Hay transición LERP activa? + float transition_progress_ = 0.0f; // Progreso 0.0-1.0 (0.0 = origen, 1.0 = destino) float transition_duration_ = THEME_TRANSITION_DURATION; // Duración en segundos (configurable en defines.h) // Índices de temas involucrados en transición - int source_theme_index_ = 0; // Tema origen (del que venimos) - int target_theme_index_ = 0; // Tema destino (al que vamos) + int source_theme_index_ = 0; // Tema origen (del que venimos) + int target_theme_index_ = 0; // Tema destino (al que vamos) // Snapshot del tema origen (capturado al iniciar transición) std::unique_ptr source_snapshot_; // nullptr si no hay transición @@ -112,6 +111,6 @@ class ThemeManager { void initializeDynamicThemes(); // Crea 6 temas dinámicos (índices 9-14) // Sistema de transición LERP (PHASE 3) - std::unique_ptr captureCurrentSnapshot() const; // Captura snapshot del tema actual + std::unique_ptr captureCurrentSnapshot() const; // Captura snapshot del tema actual float lerp(float a, float b, float t) const { return a + (b - a) * t; } // Interpolación lineal }; diff --git a/source/themes/dynamic_theme.cpp b/source/themes/dynamic_theme.cpp index 4869b6b..4425e98 100644 --- a/source/themes/dynamic_theme.cpp +++ b/source/themes/dynamic_theme.cpp @@ -1,19 +1,15 @@ #include "dynamic_theme.hpp" + #include // for std::min -DynamicTheme::DynamicTheme(const char* name_en, const char* name_es, - int text_r, int text_g, int text_b, - std::vector keyframes, - bool loop) +DynamicTheme::DynamicTheme(const char* name_en, const char* name_es, int text_r, int text_g, int text_b, std::vector keyframes, bool loop) : name_en_(name_en), name_es_(name_es), - text_r_(text_r), text_g_(text_g), text_b_(text_b), + text_r_(text_r), + text_g_(text_g), + text_b_(text_b), keyframes_(std::move(keyframes)), - loop_(loop), - current_keyframe_index_(0), - target_keyframe_index_(1), - transition_progress_(0.0f), - paused_(false) { + loop_(loop) { // Validación: mínimo 2 keyframes if (keyframes_.size() < 2) { // Fallback: duplicar primer keyframe si solo hay 1 @@ -29,7 +25,9 @@ DynamicTheme::DynamicTheme(const char* name_en, const char* name_es, } void DynamicTheme::update(float delta_time) { - if (paused_) return; // No actualizar si está pausado + if (paused_) { + return; // No actualizar si está pausado + } // Obtener duración del keyframe objetivo float duration = keyframes_[target_keyframe_index_].duration; @@ -74,14 +72,14 @@ void DynamicTheme::advanceToNextKeyframe() { transition_progress_ = 0.0f; } -Color DynamicTheme::getBallColor(size_t ball_index, float progress) const { +auto DynamicTheme::getBallColor(size_t ball_index, float progress) const -> Color { // Obtener keyframes actual y objetivo const auto& current_kf = keyframes_[current_keyframe_index_]; const auto& target_kf = keyframes_[target_keyframe_index_]; // Si paletas vacías, retornar blanco if (current_kf.ball_colors.empty() || target_kf.ball_colors.empty()) { - return {255, 255, 255}; + return {.r = 255, .g = 255, .b = 255}; } // Obtener colores de ambos keyframes (con wrap) @@ -95,15 +93,18 @@ Color DynamicTheme::getBallColor(size_t ball_index, float progress) const { // (progress parámetro será usado en PHASE 3 para LERP externo) float t = transition_progress_; return { - static_cast(lerp(c1.r, c2.r, t)), - static_cast(lerp(c1.g, c2.g, t)), - static_cast(lerp(c1.b, c2.b, t)) - }; + .r = static_cast(lerp(c1.r, c2.r, t)), + .g = static_cast(lerp(c1.g, c2.g, t)), + .b = static_cast(lerp(c1.b, c2.b, t))}; } void DynamicTheme::getBackgroundColors(float progress, - float& tr, float& tg, float& tb, - float& br, float& bg, float& bb) const { + float& tr, + float& tg, + float& tb, + float& br, + float& bg, + float& bb) const { // Obtener keyframes actual y objetivo const auto& current_kf = keyframes_[current_keyframe_index_]; const auto& target_kf = keyframes_[target_keyframe_index_]; diff --git a/source/themes/dynamic_theme.hpp b/source/themes/dynamic_theme.hpp index 8b46280..13e890d 100644 --- a/source/themes/dynamic_theme.hpp +++ b/source/themes/dynamic_theme.hpp @@ -1,8 +1,9 @@ #pragma once -#include "theme.hpp" #include +#include "theme.hpp" + // Forward declaration (estructura definida en defines.h) struct DynamicThemeKeyframe; @@ -30,10 +31,7 @@ class DynamicTheme : public Theme { * @param keyframes: Vector de keyframes (mínimo 2) * @param loop: ¿Volver al inicio al terminar? (siempre true en esta app) */ - DynamicTheme(const char* name_en, const char* name_es, - int text_r, int text_g, int text_b, - std::vector keyframes, - bool loop = true); + DynamicTheme(const char* name_en, const char* name_es, int text_r, int text_g, int text_b, std::vector keyframes, bool loop = true); ~DynamicTheme() override = default; @@ -56,8 +54,12 @@ class DynamicTheme : public Theme { Color getBallColor(size_t ball_index, float progress) const override; void getBackgroundColors(float progress, - float& tr, float& tg, float& tb, - float& br, float& bg, float& bb) const override; + float& tr, + float& tg, + float& tb, + float& br, + float& bg, + float& bb) const override; // ======================================== // ANIMACIÓN (soporte completo) @@ -90,10 +92,10 @@ class DynamicTheme : public Theme { // ESTADO DE ANIMACIÓN // ======================================== - size_t current_keyframe_index_ = 0; // Keyframe actual - size_t target_keyframe_index_ = 1; // Próximo keyframe - float transition_progress_ = 0.0f; // Progreso 0.0-1.0 hacia target - bool paused_ = false; // Pausa manual con Shift+D + size_t current_keyframe_index_ = 0; // Keyframe actual + size_t target_keyframe_index_ = 1; // Próximo keyframe + float transition_progress_ = 0.0f; // Progreso 0.0-1.0 hacia target + bool paused_ = false; // Pausa manual con Shift+D // ======================================== // UTILIDADES PRIVADAS diff --git a/source/themes/static_theme.cpp b/source/themes/static_theme.cpp index f0b4663..0759f05 100644 --- a/source/themes/static_theme.cpp +++ b/source/themes/static_theme.cpp @@ -1,32 +1,39 @@ #include "static_theme.hpp" -StaticTheme::StaticTheme(const char* name_en, const char* name_es, - int text_r, int text_g, int text_b, - int notif_bg_r, int notif_bg_g, int notif_bg_b, - float bg_top_r, float bg_top_g, float bg_top_b, - float bg_bottom_r, float bg_bottom_g, float bg_bottom_b, - std::vector ball_colors) +StaticTheme::StaticTheme(const char* name_en, const char* name_es, int text_r, int text_g, int text_b, int notif_bg_r, int notif_bg_g, int notif_bg_b, float bg_top_r, float bg_top_g, float bg_top_b, float bg_bottom_r, float bg_bottom_g, float bg_bottom_b, std::vector ball_colors) : name_en_(name_en), name_es_(name_es), - text_r_(text_r), text_g_(text_g), text_b_(text_b), - notif_bg_r_(notif_bg_r), notif_bg_g_(notif_bg_g), notif_bg_b_(notif_bg_b), - bg_top_r_(bg_top_r), bg_top_g_(bg_top_g), bg_top_b_(bg_top_b), - bg_bottom_r_(bg_bottom_r), bg_bottom_g_(bg_bottom_g), bg_bottom_b_(bg_bottom_b), + text_r_(text_r), + text_g_(text_g), + text_b_(text_b), + notif_bg_r_(notif_bg_r), + notif_bg_g_(notif_bg_g), + notif_bg_b_(notif_bg_b), + bg_top_r_(bg_top_r), + bg_top_g_(bg_top_g), + bg_top_b_(bg_top_b), + bg_bottom_r_(bg_bottom_r), + bg_bottom_g_(bg_bottom_g), + bg_bottom_b_(bg_bottom_b), ball_colors_(std::move(ball_colors)) { } -Color StaticTheme::getBallColor(size_t ball_index, float progress) const { +auto StaticTheme::getBallColor(size_t ball_index, float progress) const -> Color { // Tema estático: siempre retorna color de paleta según índice // (progress se ignora aquí, pero será usado en PHASE 3 para LERP externo) if (ball_colors_.empty()) { - return {255, 255, 255}; // Blanco por defecto si paleta vacía + return {.r = 255, .g = 255, .b = 255}; // Blanco por defecto si paleta vacía } return ball_colors_[ball_index % ball_colors_.size()]; } void StaticTheme::getBackgroundColors(float progress, - float& tr, float& tg, float& tb, - float& br, float& bg, float& bb) const { + float& tr, + float& tg, + float& tb, + float& br, + float& bg, + float& bb) const { // Tema estático: siempre retorna colores de fondo fijos // (progress se ignora aquí, pero será usado en PHASE 3 para LERP externo) tr = bg_top_r_; diff --git a/source/themes/static_theme.hpp b/source/themes/static_theme.hpp index 653bde6..f1c2342 100644 --- a/source/themes/static_theme.hpp +++ b/source/themes/static_theme.hpp @@ -1,8 +1,9 @@ #pragma once -#include "theme.hpp" #include +#include "theme.hpp" + /** * StaticTheme: Tema estático con 1 keyframe (sin animación) * @@ -28,12 +29,7 @@ class StaticTheme : public Theme { * @param bg_bottom_r, bg_bottom_g, bg_bottom_b: Color inferior de fondo * @param ball_colors: Paleta de colores para pelotas */ - StaticTheme(const char* name_en, const char* name_es, - int text_r, int text_g, int text_b, - int notif_bg_r, int notif_bg_g, int notif_bg_b, - float bg_top_r, float bg_top_g, float bg_top_b, - float bg_bottom_r, float bg_bottom_g, float bg_bottom_b, - std::vector ball_colors); + StaticTheme(const char* name_en, const char* name_es, int text_r, int text_g, int text_b, int notif_bg_r, int notif_bg_g, int notif_bg_b, float bg_top_r, float bg_top_g, float bg_top_b, float bg_bottom_r, float bg_bottom_g, float bg_bottom_b, std::vector ball_colors); ~StaticTheme() override = default; @@ -60,8 +56,12 @@ class StaticTheme : public Theme { Color getBallColor(size_t ball_index, float progress) const override; void getBackgroundColors(float progress, - float& tr, float& tg, float& tb, - float& br, float& bg, float& bb) const override; + float& tr, + float& tg, + float& tb, + float& br, + float& bg, + float& bb) const override; // ======================================== // ANIMACIÓN (sin soporte - tema estático) diff --git a/source/themes/theme.hpp b/source/themes/theme.hpp index 0c27c7f..cb47570 100644 --- a/source/themes/theme.hpp +++ b/source/themes/theme.hpp @@ -1,6 +1,7 @@ #pragma once #include + #include "defines.hpp" // for Color, ThemeKeyframe /** @@ -47,8 +48,12 @@ class Theme { * @param br, bg, bb: Color inferior (out) */ virtual void getBackgroundColors(float progress, - float& tr, float& tg, float& tb, - float& br, float& bg, float& bb) const = 0; + float& tr, + float& tg, + float& tb, + float& br, + float& bg, + float& bb) const = 0; // ======================================== // ANIMACIÓN (solo temas dinámicos) @@ -58,7 +63,7 @@ class Theme { * Actualiza progreso de animación interna (solo dinámicos) * @param delta_time: Tiempo transcurrido desde último frame */ - virtual void update(float delta_time) { } + virtual void update(float delta_time) {} /** * ¿Este tema necesita update() cada frame? @@ -75,7 +80,7 @@ class Theme { /** * Reinicia progreso de animación a 0.0 (usado al activar tema) */ - virtual void resetProgress() { } + virtual void resetProgress() {} // ======================================== // PAUSA (solo temas dinámicos) @@ -90,5 +95,5 @@ class Theme { /** * Toggle pausa de animación (solo dinámicos, tecla Shift+D) */ - virtual void togglePause() { } + virtual void togglePause() {} }; diff --git a/source/themes/theme_snapshot.hpp b/source/themes/theme_snapshot.hpp index 436bb02..58de87d 100644 --- a/source/themes/theme_snapshot.hpp +++ b/source/themes/theme_snapshot.hpp @@ -2,6 +2,7 @@ #include #include + #include "defines.hpp" // for Color /** @@ -24,23 +25,23 @@ * - Nombres del tema (para debug display durante transición) */ struct ThemeSnapshot { - // Colores de fondo degradado - float bg_top_r, bg_top_g, bg_top_b; - float bg_bottom_r, bg_bottom_g, bg_bottom_b; + // Colores de fondo degradado + float bg_top_r, bg_top_g, bg_top_b; + float bg_bottom_r, bg_bottom_g, bg_bottom_b; - // Color de texto UI - int text_color_r, text_color_g, text_color_b; + // Color de texto UI + int text_color_r, text_color_g, text_color_b; - // Color de fondo de notificaciones - int notif_bg_r, notif_bg_g, notif_bg_b; + // Color de fondo de notificaciones + int notif_bg_r, notif_bg_g, notif_bg_b; - // Nombres del tema (para mostrar "SOURCE → TARGET" durante transición) - std::string name_en; - std::string name_es; + // Nombres del tema (para mostrar "SOURCE → TARGET" durante transición) + std::string name_en; + std::string name_es; - // Colores de pelotas capturados (índice = ball_index % ball_colors.size()) - // Se capturan suficientes colores para cubrir escenario máximo (50,000 pelotas) - // Nota: Si el tema tiene 8 colores y capturamos 50,000, habrá repetición - // pero permite LERP correcto incluso con muchas pelotas - std::vector ball_colors; + // Colores de pelotas capturados (índice = ball_index % ball_colors.size()) + // Se capturan suficientes colores para cubrir escenario máximo (50,000 pelotas) + // Nota: Si el tema tiene 8 colores y capturamos 50,000, habrá repetición + // pero permite LERP correcto incluso con muchas pelotas + std::vector ball_colors; }; diff --git a/source/ui/app_logo.cpp b/source/ui/app_logo.cpp index a56f4bc..e464dce 100644 --- a/source/ui/app_logo.cpp +++ b/source/ui/app_logo.cpp @@ -1,31 +1,34 @@ #include "app_logo.hpp" #include // for SDL_DestroyTexture, SDL_RenderGeometry, SDL_SetTextureAlphaMod -#include // for powf, sinf, cosf -#include // for free() -#include // for std::cout -#include "logo_scaler.hpp" // for LogoScaler -#include "defines.hpp" // for APPLOGO_HEIGHT_PERCENT, getResourcesDirectory +#include // for std::array +#include // for powf, sinf, cosf +#include // for free() +#include // for std::cout +#include + +#include "defines.hpp" // for APPLOGO_HEIGHT_PERCENT, getResourcesDirectory +#include "logo_scaler.hpp" // for LogoScaler // ============================================================================ // Destructor - Liberar las 4 texturas SDL // ============================================================================ AppLogo::~AppLogo() { - if (logo1_base_texture_) { + if (logo1_base_texture_ != nullptr) { SDL_DestroyTexture(logo1_base_texture_); logo1_base_texture_ = nullptr; } - if (logo1_native_texture_) { + if (logo1_native_texture_ != nullptr) { SDL_DestroyTexture(logo1_native_texture_); logo1_native_texture_ = nullptr; } - if (logo2_base_texture_) { + if (logo2_base_texture_ != nullptr) { SDL_DestroyTexture(logo2_base_texture_); logo2_base_texture_ = nullptr; } - if (logo2_native_texture_) { + if (logo2_native_texture_ != nullptr) { SDL_DestroyTexture(logo2_native_texture_); logo2_native_texture_ = nullptr; } @@ -35,11 +38,23 @@ AppLogo::~AppLogo() { // Inicialización - Pre-escalar logos a 2 resoluciones (base y nativa) // ============================================================================ -bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) { - if (logo1_base_texture_) { SDL_DestroyTexture(logo1_base_texture_); logo1_base_texture_ = nullptr; } - if (logo1_native_texture_) { SDL_DestroyTexture(logo1_native_texture_); logo1_native_texture_ = nullptr; } - if (logo2_base_texture_) { SDL_DestroyTexture(logo2_base_texture_); logo2_base_texture_ = nullptr; } - if (logo2_native_texture_) { SDL_DestroyTexture(logo2_native_texture_); logo2_native_texture_ = nullptr; } +auto AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) -> bool { + if (logo1_base_texture_ != nullptr) { + SDL_DestroyTexture(logo1_base_texture_); + logo1_base_texture_ = nullptr; + } + if (logo1_native_texture_ != nullptr) { + SDL_DestroyTexture(logo1_native_texture_); + logo1_native_texture_ = nullptr; + } + if (logo2_base_texture_ != nullptr) { + SDL_DestroyTexture(logo2_base_texture_); + logo2_base_texture_ = nullptr; + } + if (logo2_native_texture_ != nullptr) { + SDL_DestroyTexture(logo2_native_texture_); + logo2_native_texture_ = nullptr; + } renderer_ = renderer; base_screen_width_ = screen_width; @@ -53,7 +68,7 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he // 1. Detectar resolución nativa del monitor // ======================================================================== if (!LogoScaler::detectNativeResolution(native_screen_width_, native_screen_height_)) { - std::cout << "No se pudo detectar resolución nativa, usando solo base" << std::endl; + std::cout << "No se pudo detectar resolución nativa, usando solo base" << '\n'; // Fallback: usar resolución base como nativa native_screen_width_ = screen_width; native_screen_height_ = screen_height; @@ -76,20 +91,21 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he 0, // width calculado automáticamente por aspect ratio logo_base_target_height, logo1_base_width_, - logo1_base_height_ - ); + logo1_base_height_); if (logo1_base_data == nullptr) { - std::cout << "Error: No se pudo escalar logo1 (base)" << std::endl; + std::cout << "Error: No se pudo escalar logo1 (base)" << '\n'; return false; } logo1_base_texture_ = LogoScaler::createTextureFromBuffer( - renderer, logo1_base_data, logo1_base_width_, logo1_base_height_ - ); + renderer, + logo1_base_data, + logo1_base_width_, + logo1_base_height_); free(logo1_base_data); // Liberar buffer temporal if (logo1_base_texture_ == nullptr) { - std::cout << "Error: No se pudo crear textura logo1 (base)" << std::endl; + std::cout << "Error: No se pudo crear textura logo1 (base)" << '\n'; return false; } @@ -102,20 +118,21 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he 0, // width calculado automáticamente logo_native_target_height, logo1_native_width_, - logo1_native_height_ - ); + logo1_native_height_); if (logo1_native_data == nullptr) { - std::cout << "Error: No se pudo escalar logo1 (nativa)" << std::endl; + std::cout << "Error: No se pudo escalar logo1 (nativa)" << '\n'; return false; } logo1_native_texture_ = LogoScaler::createTextureFromBuffer( - renderer, logo1_native_data, logo1_native_width_, logo1_native_height_ - ); + renderer, + logo1_native_data, + logo1_native_width_, + logo1_native_height_); free(logo1_native_data); if (logo1_native_texture_ == nullptr) { - std::cout << "Error: No se pudo crear textura logo1 (nativa)" << std::endl; + std::cout << "Error: No se pudo crear textura logo1 (nativa)" << '\n'; return false; } @@ -132,20 +149,21 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he 0, logo_base_target_height, logo2_base_width_, - logo2_base_height_ - ); + logo2_base_height_); if (logo2_base_data == nullptr) { - std::cout << "Error: No se pudo escalar logo2 (base)" << std::endl; + std::cout << "Error: No se pudo escalar logo2 (base)" << '\n'; return false; } logo2_base_texture_ = LogoScaler::createTextureFromBuffer( - renderer, logo2_base_data, logo2_base_width_, logo2_base_height_ - ); + renderer, + logo2_base_data, + logo2_base_width_, + logo2_base_height_); free(logo2_base_data); if (logo2_base_texture_ == nullptr) { - std::cout << "Error: No se pudo crear textura logo2 (base)" << std::endl; + std::cout << "Error: No se pudo crear textura logo2 (base)" << '\n'; return false; } @@ -157,20 +175,21 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he 0, logo_native_target_height, logo2_native_width_, - logo2_native_height_ - ); + logo2_native_height_); if (logo2_native_data == nullptr) { - std::cout << "Error: No se pudo escalar logo2 (nativa)" << std::endl; + std::cout << "Error: No se pudo escalar logo2 (nativa)" << '\n'; return false; } logo2_native_texture_ = LogoScaler::createTextureFromBuffer( - renderer, logo2_native_data, logo2_native_width_, logo2_native_height_ - ); + renderer, + logo2_native_data, + logo2_native_width_, + logo2_native_height_); free(logo2_native_data); if (logo2_native_texture_ == nullptr) { - std::cout << "Error: No se pudo crear textura logo2 (nativa)" << std::endl; + std::cout << "Error: No se pudo crear textura logo2 (nativa)" << '\n'; return false; } @@ -191,7 +210,7 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he return true; } -void AppLogo::update(float delta_time, AppMode current_mode) { +void AppLogo::update(float delta_time, AppMode current_mode) { // NOLINT(readability-function-cognitive-complexity) // Si estamos en SANDBOX, resetear y no hacer nada (logo desactivado) if (current_mode == AppMode::SANDBOX) { state_ = AppLogoState::HIDDEN; @@ -262,63 +281,57 @@ void AppLogo::update(float delta_time, AppMode current_mode) { logo2_rotation_ = 0.0f; break; - case AppLogoAnimationType::ELASTIC_STICK: - { - float prog1 = std::min(1.0f, fade_progress_logo1); - float elastic_t1 = easeOutElastic(prog1); - logo1_scale_ = 1.2f - (elastic_t1 * 0.2f); - float squash_t1 = easeOutBack(prog1); - logo1_squash_y_ = 0.6f + (squash_t1 * 0.4f); - logo1_stretch_x_ = 1.0f + (1.0f - logo1_squash_y_) * 0.5f; - logo1_rotation_ = 0.0f; + case AppLogoAnimationType::ELASTIC_STICK: { + float prog1 = std::min(1.0f, fade_progress_logo1); + float elastic_t1 = easeOutElastic(prog1); + logo1_scale_ = 1.2f - (elastic_t1 * 0.2f); + float squash_t1 = easeOutBack(prog1); + logo1_squash_y_ = 0.6f + (squash_t1 * 0.4f); + logo1_stretch_x_ = 1.0f + (1.0f - logo1_squash_y_) * 0.5f; + logo1_rotation_ = 0.0f; - float prog2 = std::min(1.0f, fade_progress_logo2); - float elastic_t2 = easeOutElastic(prog2); - logo2_scale_ = 1.2f - (elastic_t2 * 0.2f); - float squash_t2 = easeOutBack(prog2); - logo2_squash_y_ = 0.6f + (squash_t2 * 0.4f); - logo2_stretch_x_ = 1.0f + (1.0f - logo2_squash_y_) * 0.5f; - logo2_rotation_ = 0.0f; - } - break; + float prog2 = std::min(1.0f, fade_progress_logo2); + float elastic_t2 = easeOutElastic(prog2); + logo2_scale_ = 1.2f - (elastic_t2 * 0.2f); + float squash_t2 = easeOutBack(prog2); + logo2_squash_y_ = 0.6f + (squash_t2 * 0.4f); + logo2_stretch_x_ = 1.0f + (1.0f - logo2_squash_y_) * 0.5f; + logo2_rotation_ = 0.0f; + } break; - case AppLogoAnimationType::ROTATE_SPIRAL: - { - float prog1 = std::min(1.0f, fade_progress_logo1); - float ease_t1 = easeInOutQuad(prog1); - logo1_scale_ = 0.3f + (ease_t1 * 0.7f); - logo1_rotation_ = (1.0f - prog1) * 6.28f; - logo1_squash_y_ = 1.0f; - logo1_stretch_x_ = 1.0f; + case AppLogoAnimationType::ROTATE_SPIRAL: { + float prog1 = std::min(1.0f, fade_progress_logo1); + float ease_t1 = easeInOutQuad(prog1); + logo1_scale_ = 0.3f + (ease_t1 * 0.7f); + logo1_rotation_ = (1.0f - prog1) * 6.28f; + logo1_squash_y_ = 1.0f; + logo1_stretch_x_ = 1.0f; - float prog2 = std::min(1.0f, fade_progress_logo2); - float ease_t2 = easeInOutQuad(prog2); - logo2_scale_ = 0.3f + (ease_t2 * 0.7f); - logo2_rotation_ = (1.0f - prog2) * 6.28f; - logo2_squash_y_ = 1.0f; - logo2_stretch_x_ = 1.0f; - } - break; + float prog2 = std::min(1.0f, fade_progress_logo2); + float ease_t2 = easeInOutQuad(prog2); + logo2_scale_ = 0.3f + (ease_t2 * 0.7f); + logo2_rotation_ = (1.0f - prog2) * 6.28f; + logo2_squash_y_ = 1.0f; + logo2_stretch_x_ = 1.0f; + } break; - case AppLogoAnimationType::BOUNCE_SQUASH: - { - float prog1 = std::min(1.0f, fade_progress_logo1); - float bounce_t1 = easeOutBounce(prog1); - logo1_scale_ = 1.0f; - float squash_amount1 = (1.0f - bounce_t1) * 0.3f; - logo1_squash_y_ = 1.0f - squash_amount1; - logo1_stretch_x_ = 1.0f + squash_amount1 * 0.5f; - logo1_rotation_ = 0.0f; + case AppLogoAnimationType::BOUNCE_SQUASH: { + float prog1 = std::min(1.0f, fade_progress_logo1); + float bounce_t1 = easeOutBounce(prog1); + logo1_scale_ = 1.0f; + float squash_amount1 = (1.0f - bounce_t1) * 0.3f; + logo1_squash_y_ = 1.0f - squash_amount1; + logo1_stretch_x_ = 1.0f + squash_amount1 * 0.5f; + logo1_rotation_ = 0.0f; - float prog2 = std::min(1.0f, fade_progress_logo2); - float bounce_t2 = easeOutBounce(prog2); - logo2_scale_ = 1.0f; - float squash_amount2 = (1.0f - bounce_t2) * 0.3f; - logo2_squash_y_ = 1.0f - squash_amount2; - logo2_stretch_x_ = 1.0f + squash_amount2 * 0.5f; - logo2_rotation_ = 0.0f; - } - break; + float prog2 = std::min(1.0f, fade_progress_logo2); + float bounce_t2 = easeOutBounce(prog2); + logo2_scale_ = 1.0f; + float squash_amount2 = (1.0f - bounce_t2) * 0.3f; + logo2_squash_y_ = 1.0f - squash_amount2; + logo2_stretch_x_ = 1.0f + squash_amount2 * 0.5f; + logo2_rotation_ = 0.0f; + } break; } } } @@ -379,69 +392,63 @@ void AppLogo::update(float delta_time, AppMode current_mode) { logo2_rotation_ = 0.0f; break; - case AppLogoAnimationType::ELASTIC_STICK: - { - float prog1 = std::min(1.0f, fade_progress_logo1); - logo1_scale_ = 1.0f + (prog1 * prog1 * 0.2f); - logo1_squash_y_ = 1.0f + (prog1 * 0.3f); - logo1_stretch_x_ = 1.0f - (prog1 * 0.2f); - logo1_rotation_ = prog1 * 0.1f; + case AppLogoAnimationType::ELASTIC_STICK: { + float prog1 = std::min(1.0f, fade_progress_logo1); + logo1_scale_ = 1.0f + (prog1 * prog1 * 0.2f); + logo1_squash_y_ = 1.0f + (prog1 * 0.3f); + logo1_stretch_x_ = 1.0f - (prog1 * 0.2f); + logo1_rotation_ = prog1 * 0.1f; - float prog2 = std::min(1.0f, fade_progress_logo2); - logo2_scale_ = 1.0f + (prog2 * prog2 * 0.2f); - logo2_squash_y_ = 1.0f + (prog2 * 0.3f); - logo2_stretch_x_ = 1.0f - (prog2 * 0.2f); - logo2_rotation_ = prog2 * 0.1f; + float prog2 = std::min(1.0f, fade_progress_logo2); + logo2_scale_ = 1.0f + (prog2 * prog2 * 0.2f); + logo2_squash_y_ = 1.0f + (prog2 * 0.3f); + logo2_stretch_x_ = 1.0f - (prog2 * 0.2f); + logo2_rotation_ = prog2 * 0.1f; + } break; + + case AppLogoAnimationType::ROTATE_SPIRAL: { + float prog1 = std::min(1.0f, fade_progress_logo1); + float ease_t1 = easeInOutQuad(prog1); + logo1_scale_ = 1.0f - (ease_t1 * 0.7f); + logo1_rotation_ = prog1 * 6.28f; + logo1_squash_y_ = 1.0f; + logo1_stretch_x_ = 1.0f; + + float prog2 = std::min(1.0f, fade_progress_logo2); + float ease_t2 = easeInOutQuad(prog2); + logo2_scale_ = 1.0f - (ease_t2 * 0.7f); + logo2_rotation_ = prog2 * 6.28f; + logo2_squash_y_ = 1.0f; + logo2_stretch_x_ = 1.0f; + } break; + + case AppLogoAnimationType::BOUNCE_SQUASH: { + float prog1 = std::min(1.0f, fade_progress_logo1); + if (prog1 < 0.2f) { + float squash_t = prog1 / 0.2f; + logo1_squash_y_ = 1.0f - (squash_t * 0.3f); + logo1_stretch_x_ = 1.0f + (squash_t * 0.2f); + } else { + float jump_t = (prog1 - 0.2f) / 0.8f; + logo1_squash_y_ = 0.7f + (jump_t * 0.5f); + logo1_stretch_x_ = 1.2f - (jump_t * 0.2f); } - break; + logo1_scale_ = 1.0f + (prog1 * 0.3f); + logo1_rotation_ = 0.0f; - case AppLogoAnimationType::ROTATE_SPIRAL: - { - float prog1 = std::min(1.0f, fade_progress_logo1); - float ease_t1 = easeInOutQuad(prog1); - logo1_scale_ = 1.0f - (ease_t1 * 0.7f); - logo1_rotation_ = prog1 * 6.28f; - logo1_squash_y_ = 1.0f; - logo1_stretch_x_ = 1.0f; - - float prog2 = std::min(1.0f, fade_progress_logo2); - float ease_t2 = easeInOutQuad(prog2); - logo2_scale_ = 1.0f - (ease_t2 * 0.7f); - logo2_rotation_ = prog2 * 6.28f; - logo2_squash_y_ = 1.0f; - logo2_stretch_x_ = 1.0f; + float prog2 = std::min(1.0f, fade_progress_logo2); + if (prog2 < 0.2f) { + float squash_t = prog2 / 0.2f; + logo2_squash_y_ = 1.0f - (squash_t * 0.3f); + logo2_stretch_x_ = 1.0f + (squash_t * 0.2f); + } else { + float jump_t = (prog2 - 0.2f) / 0.8f; + logo2_squash_y_ = 0.7f + (jump_t * 0.5f); + logo2_stretch_x_ = 1.2f - (jump_t * 0.2f); } - break; - - case AppLogoAnimationType::BOUNCE_SQUASH: - { - float prog1 = std::min(1.0f, fade_progress_logo1); - if (prog1 < 0.2f) { - float squash_t = prog1 / 0.2f; - logo1_squash_y_ = 1.0f - (squash_t * 0.3f); - logo1_stretch_x_ = 1.0f + (squash_t * 0.2f); - } else { - float jump_t = (prog1 - 0.2f) / 0.8f; - logo1_squash_y_ = 0.7f + (jump_t * 0.5f); - logo1_stretch_x_ = 1.2f - (jump_t * 0.2f); - } - logo1_scale_ = 1.0f + (prog1 * 0.3f); - logo1_rotation_ = 0.0f; - - float prog2 = std::min(1.0f, fade_progress_logo2); - if (prog2 < 0.2f) { - float squash_t = prog2 / 0.2f; - logo2_squash_y_ = 1.0f - (squash_t * 0.3f); - logo2_stretch_x_ = 1.0f + (squash_t * 0.2f); - } else { - float jump_t = (prog2 - 0.2f) / 0.8f; - logo2_squash_y_ = 0.7f + (jump_t * 0.5f); - logo2_stretch_x_ = 1.2f - (jump_t * 0.2f); - } - logo2_scale_ = 1.0f + (prog2 * 0.3f); - logo2_rotation_ = 0.0f; - } - break; + logo2_scale_ = 1.0f + (prog2 * 0.3f); + logo2_rotation_ = 0.0f; + } break; } } } @@ -477,7 +484,7 @@ void AppLogo::updateScreenSize(int screen_width, int screen_height) { logo2_current_width_ = logo2_native_width_; logo2_current_height_ = logo2_native_height_; - std::cout << "AppLogo: Cambiado a texturas NATIVAS" << std::endl; + std::cout << "AppLogo: Cambiado a texturas NATIVAS" << '\n'; } else { // Cambiar a texturas base (ventana redimensionable) logo1_current_texture_ = logo1_base_texture_; @@ -488,7 +495,7 @@ void AppLogo::updateScreenSize(int screen_width, int screen_height) { logo2_current_width_ = logo2_base_width_; logo2_current_height_ = logo2_base_height_; - std::cout << "AppLogo: Cambiado a texturas BASE" << std::endl; + std::cout << "AppLogo: Cambiado a texturas BASE" << '\n'; } // Nota: No es necesario recalcular escalas porque las texturas están pre-escaladas @@ -499,57 +506,61 @@ void AppLogo::updateScreenSize(int screen_width, int screen_height) { // Funciones de easing para animaciones // ============================================================================ -float AppLogo::easeOutElastic(float t) { +auto AppLogo::easeOutElastic(float t) -> float { // Elastic easing out: bounce elástico al final - const float c4 = (2.0f * 3.14159f) / 3.0f; + const float C4 = (2.0f * std::numbers::pi_v) / 3.0f; - if (t == 0.0f) return 0.0f; - if (t == 1.0f) return 1.0f; - - return powf(2.0f, -10.0f * t) * sinf((t * 10.0f - 0.75f) * c4) + 1.0f; -} - -float AppLogo::easeOutBack(float t) { - // Back easing out: overshoot suave al final - const float c1 = 1.70158f; - const float c3 = c1 + 1.0f; - - return 1.0f + c3 * powf(t - 1.0f, 3.0f) + c1 * powf(t - 1.0f, 2.0f); -} - -float AppLogo::easeOutBounce(float t) { - // Bounce easing out: rebotes decrecientes (para BOUNCE_SQUASH) - const float n1 = 7.5625f; - const float d1 = 2.75f; - - if (t < 1.0f / d1) { - return n1 * t * t; - } else if (t < 2.0f / d1) { - t -= 1.5f / d1; - return n1 * t * t + 0.75f; - } else if (t < 2.5f / d1) { - t -= 2.25f / d1; - return n1 * t * t + 0.9375f; - } else { - t -= 2.625f / d1; - return n1 * t * t + 0.984375f; + if (t == 0.0f) { + return 0.0f; } + if (t == 1.0f) { + return 1.0f; + } + + return (powf(2.0f, -10.0f * t) * sinf((t * 10.0f - 0.75f) * C4)) + 1.0f; } -float AppLogo::easeInOutQuad(float t) { +auto AppLogo::easeOutBack(float t) -> float { + // Back easing out: overshoot suave al final + const float C1 = 1.70158f; + const float C3 = C1 + 1.0f; + + return 1.0f + (C3 * powf(t - 1.0f, 3.0f)) + (C1 * powf(t - 1.0f, 2.0f)); +} + +auto AppLogo::easeOutBounce(float t) -> float { + // Bounce easing out: rebotes decrecientes (para BOUNCE_SQUASH) + const float N1 = 7.5625f; + const float D1 = 2.75f; + + if (t < 1.0f / D1) { + return N1 * t * t; + } + if (t < 2.0f / D1) { + t -= 1.5f / D1; + return (N1 * t * t) + 0.75f; + } + if (t < 2.5f / D1) { + t -= 2.25f / D1; + return (N1 * t * t) + 0.9375f; + } + t -= 2.625f / D1; + return (N1 * t * t) + 0.984375f; +} + +auto AppLogo::easeInOutQuad(float t) -> float { // Quadratic easing in/out: aceleración suave (para ROTATE_SPIRAL) if (t < 0.5f) { return 2.0f * t * t; - } else { - return 1.0f - powf(-2.0f * t + 2.0f, 2.0f) / 2.0f; } + return 1.0f - (powf((-2.0f * t) + 2.0f, 2.0f) / 2.0f); } // ============================================================================ // Función auxiliar para aleatorización // ============================================================================ -AppLogoAnimationType AppLogo::getRandomAnimation() { +auto AppLogo::getRandomAnimation() -> AppLogoAnimationType { // Generar número aleatorio entre 0 y 3 (4 tipos de animación) int random_value = rand() % 4; @@ -571,15 +582,23 @@ AppLogoAnimationType AppLogo::getRandomAnimation() { // ============================================================================ void AppLogo::renderWithGeometry(int logo_index) { - if (!renderer_) return; + if (renderer_ == nullptr) { + return; + } // Seleccionar variables según el logo_index (1 = logo1, 2 = logo2) SDL_Texture* texture; - int base_width, base_height; - float scale, squash_y, stretch_x, rotation; + int base_width; + int base_height; + float scale; + float squash_y; + float stretch_x; + float rotation; if (logo_index == 1) { - if (!logo1_current_texture_) return; + if (logo1_current_texture_ == nullptr) { + return; + } texture = logo1_current_texture_; base_width = logo1_current_width_; base_height = logo1_current_height_; @@ -588,7 +607,9 @@ void AppLogo::renderWithGeometry(int logo_index) { stretch_x = logo1_stretch_x_; rotation = logo1_rotation_; } else if (logo_index == 2) { - if (!logo2_current_texture_) return; + if (logo2_current_texture_ == nullptr) { + return; + } texture = logo2_current_texture_; base_width = logo2_current_width_; base_height = logo2_current_height_; @@ -628,7 +649,7 @@ void AppLogo::renderWithGeometry(int logo_index) { float sin_rot = sinf(rotation); // Crear 4 vértices del quad (centrado en center_x, center_y) - SDL_Vertex vertices[4]; + std::array vertices{}; // Offset desde el centro float half_w = width / 2.0f; @@ -638,49 +659,49 @@ void AppLogo::renderWithGeometry(int logo_index) { { float local_x = -half_w; float local_y = -half_h; - float rotated_x = local_x * cos_rot - local_y * sin_rot; - float rotated_y = local_x * sin_rot + local_y * cos_rot; - vertices[0].position = {center_x + rotated_x, center_y + rotated_y}; - vertices[0].tex_coord = {0.0f, 0.0f}; - vertices[0].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice + float rotated_x = (local_x * cos_rot) - (local_y * sin_rot); + float rotated_y = (local_x * sin_rot) + (local_y * cos_rot); + vertices[0].position = {.x = center_x + rotated_x, .y = center_y + rotated_y}; + vertices[0].tex_coord = {.x = 0.0f, .y = 0.0f}; + vertices[0].color = {.r = 1.0f, .g = 1.0f, .b = 1.0f, .a = alpha_normalized}; // Alpha aplicado al vértice } // Vértice superior derecho (rotado) { float local_x = half_w; float local_y = -half_h; - float rotated_x = local_x * cos_rot - local_y * sin_rot; - float rotated_y = local_x * sin_rot + local_y * cos_rot; - vertices[1].position = {center_x + rotated_x, center_y + rotated_y}; - vertices[1].tex_coord = {1.0f, 0.0f}; - vertices[1].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice + float rotated_x = (local_x * cos_rot) - (local_y * sin_rot); + float rotated_y = (local_x * sin_rot) + (local_y * cos_rot); + vertices[1].position = {.x = center_x + rotated_x, .y = center_y + rotated_y}; + vertices[1].tex_coord = {.x = 1.0f, .y = 0.0f}; + vertices[1].color = {.r = 1.0f, .g = 1.0f, .b = 1.0f, .a = alpha_normalized}; // Alpha aplicado al vértice } // Vértice inferior derecho (rotado) { float local_x = half_w; float local_y = half_h; - float rotated_x = local_x * cos_rot - local_y * sin_rot; - float rotated_y = local_x * sin_rot + local_y * cos_rot; - vertices[2].position = {center_x + rotated_x, center_y + rotated_y}; - vertices[2].tex_coord = {1.0f, 1.0f}; - vertices[2].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice + float rotated_x = (local_x * cos_rot) - (local_y * sin_rot); + float rotated_y = (local_x * sin_rot) + (local_y * cos_rot); + vertices[2].position = {.x = center_x + rotated_x, .y = center_y + rotated_y}; + vertices[2].tex_coord = {.x = 1.0f, .y = 1.0f}; + vertices[2].color = {.r = 1.0f, .g = 1.0f, .b = 1.0f, .a = alpha_normalized}; // Alpha aplicado al vértice } // Vértice inferior izquierdo (rotado) { float local_x = -half_w; float local_y = half_h; - float rotated_x = local_x * cos_rot - local_y * sin_rot; - float rotated_y = local_x * sin_rot + local_y * cos_rot; - vertices[3].position = {center_x + rotated_x, center_y + rotated_y}; - vertices[3].tex_coord = {0.0f, 1.0f}; - vertices[3].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice + float rotated_x = (local_x * cos_rot) - (local_y * sin_rot); + float rotated_y = (local_x * sin_rot) + (local_y * cos_rot); + vertices[3].position = {.x = center_x + rotated_x, .y = center_y + rotated_y}; + vertices[3].tex_coord = {.x = 0.0f, .y = 1.0f}; + vertices[3].color = {.r = 1.0f, .g = 1.0f, .b = 1.0f, .a = alpha_normalized}; // Alpha aplicado al vértice } // Índices para 2 triángulos - int indices[6] = {0, 1, 2, 2, 3, 0}; + std::array indices = {0, 1, 2, 2, 3, 0}; // Renderizar con la textura del logo correspondiente - SDL_RenderGeometry(renderer_, texture, vertices, 4, indices, 6); + SDL_RenderGeometry(renderer_, texture, vertices.data(), 4, indices.data(), 6); } diff --git a/source/ui/app_logo.hpp b/source/ui/app_logo.hpp index 81617ef..51e293b 100644 --- a/source/ui/app_logo.hpp +++ b/source/ui/app_logo.hpp @@ -11,18 +11,18 @@ class Sprite; // Estados de la máquina de estados del logo enum class AppLogoState { - HIDDEN, // Logo oculto, esperando APPLOGO_DISPLAY_INTERVAL - FADE_IN, // Apareciendo (alpha 0 → 255) - VISIBLE, // Completamente visible, esperando APPLOGO_DISPLAY_DURATION - FADE_OUT // Desapareciendo (alpha 255 → 0) + HIDDEN, // Logo oculto, esperando APPLOGO_DISPLAY_INTERVAL + FADE_IN, // Apareciendo (alpha 0 → 255) + VISIBLE, // Completamente visible, esperando APPLOGO_DISPLAY_DURATION + FADE_OUT // Desapareciendo (alpha 255 → 0) }; // Tipo de animación de entrada/salida enum class AppLogoAnimationType { - ZOOM_ONLY, // A: Solo zoom simple (120% → 100% → 120%) - ELASTIC_STICK, // B: Zoom + deformación elástica tipo "pegatina" - ROTATE_SPIRAL, // C: Rotación en espiral (entra girando, sale girando) - BOUNCE_SQUASH // D: Rebote con aplastamiento (cae rebotando, salta) + ZOOM_ONLY, // A: Solo zoom simple (120% → 100% → 120%) + ELASTIC_STICK, // B: Zoom + deformación elástica tipo "pegatina" + ROTATE_SPIRAL, // C: Rotación en espiral (entra girando, sale girando) + BOUNCE_SQUASH // D: Rebote con aplastamiento (cae rebotando, salta) }; class AppLogo { @@ -46,10 +46,10 @@ class AppLogo { // ==================================================================== // Texturas pre-escaladas (4 texturas: 2 logos × 2 resoluciones) // ==================================================================== - SDL_Texture* logo1_base_texture_ = nullptr; // Logo1 para resolución base - SDL_Texture* logo1_native_texture_ = nullptr; // Logo1 para resolución nativa (F4) - SDL_Texture* logo2_base_texture_ = nullptr; // Logo2 para resolución base - SDL_Texture* logo2_native_texture_ = nullptr; // Logo2 para resolución nativa (F4) + SDL_Texture* logo1_base_texture_ = nullptr; // Logo1 para resolución base + SDL_Texture* logo1_native_texture_ = nullptr; // Logo1 para resolución nativa (F4) + SDL_Texture* logo2_base_texture_ = nullptr; // Logo2 para resolución base + SDL_Texture* logo2_native_texture_ = nullptr; // Logo2 para resolución nativa (F4) // Dimensiones pre-calculadas para cada textura int logo1_base_width_ = 0, logo1_base_height_ = 0; @@ -64,8 +64,8 @@ class AppLogo { int logo2_current_width_ = 0, logo2_current_height_ = 0; // Resoluciones conocidas - int base_screen_width_ = 0, base_screen_height_ = 0; // Resolución inicial - int native_screen_width_ = 0, native_screen_height_ = 0; // Resolución nativa (F4) + int base_screen_width_ = 0, base_screen_height_ = 0; // Resolución inicial + int native_screen_width_ = 0, native_screen_height_ = 0; // Resolución nativa (F4) // ==================================================================== // Variables COMPARTIDAS (sincronización de ambos logos) @@ -74,8 +74,8 @@ class AppLogo { float timer_ = 0.0f; // Contador de tiempo para estado actual // Alpha INDEPENDIENTE para cada logo (Logo 2 con retraso) - int logo1_alpha_ = 0; // Alpha de Logo 1 (0-255) - int logo2_alpha_ = 0; // Alpha de Logo 2 (0-255, con retraso) + int logo1_alpha_ = 0; // Alpha de Logo 1 (0-255) + int logo2_alpha_ = 0; // Alpha de Logo 2 (0-255, con retraso) // Animación COMPARTIDA (misma para ambos logos, misma entrada y salida) AppLogoAnimationType current_animation_ = AppLogoAnimationType::ZOOM_ONLY; @@ -103,15 +103,15 @@ class AppLogo { SDL_Renderer* renderer_ = nullptr; // Métodos privados auxiliares - void updateLogoPosition(); // Centrar ambos logos en pantalla (superpuestos) + void updateLogoPosition(); // Centrar ambos logos en pantalla (superpuestos) void renderWithGeometry(int logo_index); // Renderizar logo con vértices deformados (1 o 2) // Funciones de easing - float easeOutElastic(float t); // Elastic bounce out - float easeOutBack(float t); // Overshoot out - float easeOutBounce(float t); // Bounce easing (para BOUNCE_SQUASH) - float easeInOutQuad(float t); // Quadratic easing (para ROTATE_SPIRAL) + static float easeOutElastic(float t); // Elastic bounce out + static float easeOutBack(float t); // Overshoot out + static float easeOutBounce(float t); // Bounce easing (para BOUNCE_SQUASH) + static float easeInOutQuad(float t); // Quadratic easing (para ROTATE_SPIRAL) // Función auxiliar para elegir animación aleatoria - AppLogoAnimationType getRandomAnimation(); + static AppLogoAnimationType getRandomAnimation(); }; diff --git a/source/ui/help_overlay.cpp b/source/ui/help_overlay.cpp index e9bb58e..b212ee8 100644 --- a/source/ui/help_overlay.cpp +++ b/source/ui/help_overlay.cpp @@ -1,6 +1,7 @@ #include "help_overlay.hpp" #include // for std::min +#include // for std::array #include "defines.hpp" #include "text/textrenderer.hpp" @@ -21,69 +22,69 @@ HelpOverlay::HelpOverlay() column2_width_(0), column3_width_(0), cached_texture_(nullptr), - last_category_color_({0, 0, 0, 255}), - last_content_color_({0, 0, 0, 255}), - last_bg_color_({0, 0, 0, 255}), + last_category_color_({.r = 0, .g = 0, .b = 0, .a = 255}), + last_content_color_({.r = 0, .g = 0, .b = 0, .a = 255}), + last_bg_color_({.r = 0, .g = 0, .b = 0, .a = 255}), texture_needs_rebuild_(true) { // Llenar lista de controles (organizados por categoría, equilibrado en 3 columnas) key_bindings_ = { // COLUMNA 1: SIMULACIÓN - {"SIMULACIÓN", ""}, - {"1-8", "Escenarios (10 a 50.000 pelotas)"}, - {"F", "Cambia entre figura y física"}, - {"B", "Cambia entre boids y física"}, - {"ESPACIO", "Impulso contra la gravedad"}, - {"G", "Activar / Desactivar gravedad"}, - {"CURSORES", "Dirección de la gravedad"}, - {"", ""}, // Separador + {.key = "SIMULACIÓN", .description = ""}, + {.key = "1-8", .description = "Escenarios (10 a 50.000 pelotas)"}, + {.key = "F", .description = "Cambia entre figura y física"}, + {.key = "B", .description = "Cambia entre boids y física"}, + {.key = "ESPACIO", .description = "Impulso contra la gravedad"}, + {.key = "G", .description = "Activar / Desactivar gravedad"}, + {.key = "CURSORES", .description = "Dirección de la gravedad"}, + {.key = "", .description = ""}, // Separador // COLUMNA 1: FIGURAS 3D - {"FIGURAS 3D", ""}, - {"Q/W/E/R", "Esfera / Lissajous / Hélice / Toroide"}, - {"T/Y/U/I", "Cubo / Cilindro / Icosaedro / Átomo"}, - {"Num+/-", "Escalar figura"}, - {"Num*", "Reset escala"}, - {"Num/", "Activar / Desactivar profundidad"}, - {"[new_col]", ""}, // CAMBIO DE COLUMNA -> COLUMNA 2 + {.key = "FIGURAS 3D", .description = ""}, + {.key = "Q/W/E/R", .description = "Esfera / Lissajous / Hélice / Toroide"}, + {.key = "T/Y/U/I", .description = "Cubo / Cilindro / Icosaedro / Átomo"}, + {.key = "Num+/-", .description = "Escalar figura"}, + {.key = "Num*", .description = "Reset escala"}, + {.key = "Num/", .description = "Activar / Desactivar profundidad"}, + {.key = "[new_col]", .description = ""}, // CAMBIO DE COLUMNA -> COLUMNA 2 // COLUMNA 2: MODOS - {"MODOS", ""}, - {"D", "Activar / Desactivar modo demo"}, - {"L", "Activar / Desactivar modo demo lite"}, - {"K", "Activar / Desactivar modo logo"}, - {"", ""}, // Separador + {.key = "MODOS", .description = ""}, + {.key = "D", .description = "Activar / Desactivar modo demo"}, + {.key = "L", .description = "Activar / Desactivar modo demo lite"}, + {.key = "K", .description = "Activar / Desactivar modo logo"}, + {.key = "", .description = ""}, // Separador // COLUMNA 2: VISUAL - {"VISUAL", ""}, - {"C", "Tema siguiente"}, - {"Shift+C", "Tema anterior"}, - {"NumEnter", "Página de temas"}, - {"Shift+D", "Pausar tema dinámico"}, - {"N", "Cambiar tamaño de pelota"}, - {"X", "Ciclar presets PostFX"}, - {"[new_col]", ""}, // CAMBIO DE COLUMNA -> COLUMNA 3 - + {.key = "VISUAL", .description = ""}, + {.key = "C", .description = "Tema siguiente"}, + {.key = "Shift+C", .description = "Tema anterior"}, + {.key = "NumEnter", .description = "Página de temas"}, + {.key = "Shift+D", .description = "Pausar tema dinámico"}, + {.key = "N", .description = "Cambiar tamaño de pelota"}, + {.key = "X", .description = "Ciclar presets PostFX"}, + {.key = "[new_col]", .description = ""}, // CAMBIO DE COLUMNA -> COLUMNA 3 + // COLUMNA 3: PANTALLA - {"PANTALLA", ""}, - {"F1", "Disminuye ventana"}, - {"F2", "Aumenta ventana"}, - {"F3", "Pantalla completa"}, - {"F4", "Pantalla completa real"}, - {"F5", "Activar / Desactivar PostFX"}, - {"F6", "Cambia el escalado de pantalla"}, - {"V", "Activar / Desactivar V-Sync"}, - {"", ""}, // Separador + {.key = "PANTALLA", .description = ""}, + {.key = "F1", .description = "Disminuye ventana"}, + {.key = "F2", .description = "Aumenta ventana"}, + {.key = "F3", .description = "Pantalla completa"}, + {.key = "F4", .description = "Pantalla completa real"}, + {.key = "F5", .description = "Activar / Desactivar PostFX"}, + {.key = "F6", .description = "Cambia el escalado de pantalla"}, + {.key = "V", .description = "Activar / Desactivar V-Sync"}, + {.key = "", .description = ""}, // Separador // COLUMNA 3: DEBUG/AYUDA - {"DEBUG / AYUDA", ""}, - {"F12", "Activar / Desactivar info debug"}, - {"H", "Esta ayuda"}, - {"ESC", "Salir"}}; + {.key = "DEBUG / AYUDA", .description = ""}, + {.key = "F12", .description = "Activar / Desactivar info debug"}, + {.key = "H", .description = "Esta ayuda"}, + {.key = "ESC", .description = "Salir"}}; } HelpOverlay::~HelpOverlay() { // Destruir textura cacheada si existe - if (cached_texture_) { + if (cached_texture_ != nullptr) { SDL_DestroyTexture(cached_texture_); cached_texture_ = nullptr; } @@ -117,7 +118,9 @@ void HelpOverlay::updatePhysicalWindowSize(int physical_width, int physical_heig } void HelpOverlay::reinitializeFontSize(int new_font_size) { - if (!text_renderer_) return; + if (text_renderer_ == nullptr) { + return; + } // Reinicializar text renderer con nuevo tamaño text_renderer_->reinitialize(new_font_size); @@ -136,7 +139,7 @@ void HelpOverlay::updateAll(int font_size, int physical_width, int physical_heig physical_height_ = physical_height; // Reinicializar text renderer con nuevo tamaño (si cambió) - if (text_renderer_) { + if (text_renderer_ != nullptr) { text_renderer_->reinitialize(font_size); } @@ -148,7 +151,7 @@ void HelpOverlay::updateAll(int font_size, int physical_width, int physical_heig } void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) { - if (!text_renderer_) { + if (text_renderer_ == nullptr) { max_width = 0; total_height = 0; return; @@ -210,7 +213,7 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) { max_width = max_col1_width + max_col2_width + max_col3_width + padding * 2 + col_gap * 2; // Calcular altura real simulando exactamente lo que hace el render - int col_heights[3] = {0, 0, 0}; + std::array col_heights = {0, 0, 0}; current_column = 0; for (const auto& binding : key_bindings_) { @@ -220,11 +223,11 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) { } if (binding.key[0] == '\0') { - col_heights[current_column] += line_height; // separador vacío + col_heights[current_column] += line_height; // separador vacío } else if (binding.description[0] == '\0') { - col_heights[current_column] += line_height; // encabezado + col_heights[current_column] += line_height; // encabezado } else { - col_heights[current_column] += line_height; // línea normal + col_heights[current_column] += line_height; // línea normal } } @@ -240,7 +243,8 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) { void HelpOverlay::calculateBoxDimensions() { // Calcular dimensiones necesarias según el texto - int text_width, text_height; + int text_width; + int text_height; calculateTextDimensions(text_width, text_height); // Aplicar límites máximos: 95% ancho, 90% altura @@ -253,26 +257,27 @@ void HelpOverlay::calculateBoxDimensions() { // Centrar en pantalla box_x_ = (physical_width_ - box_width_) / 2; box_y_ = (physical_height_ - box_height_) / 2; - } void HelpOverlay::rebuildCachedTexture() { - if (!renderer_ || !theme_mgr_ || !text_renderer_) return; + if ((renderer_ == nullptr) || (theme_mgr_ == nullptr) || (text_renderer_ == nullptr)) { + return; + } // Destruir textura anterior si existe - if (cached_texture_) { + if (cached_texture_ != nullptr) { SDL_DestroyTexture(cached_texture_); cached_texture_ = nullptr; } // Crear nueva textura del tamaño del overlay cached_texture_ = SDL_CreateTexture(renderer_, - SDL_PIXELFORMAT_RGBA8888, - SDL_TEXTUREACCESS_TARGET, - box_width_, - box_height_); + SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_TARGET, + box_width_, + box_height_); - if (!cached_texture_) { + if (cached_texture_ == nullptr) { SDL_Log("Error al crear textura cacheada: %s", SDL_GetError()); return; } @@ -294,42 +299,46 @@ void HelpOverlay::rebuildCachedTexture() { SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND); // Obtener colores actuales del tema - int notif_bg_r, notif_bg_g, notif_bg_b; + int notif_bg_r; + int notif_bg_g; + int notif_bg_b; theme_mgr_->getCurrentNotificationBackgroundColor(notif_bg_r, notif_bg_g, notif_bg_b); // Renderizar fondo del overlay a la textura float alpha = 0.85f; - SDL_Vertex bg_vertices[4]; + std::array bg_vertices{}; float r = notif_bg_r / 255.0f; float g = notif_bg_g / 255.0f; float b = notif_bg_b / 255.0f; // Vértices del fondo (posición relativa 0,0 porque estamos renderizando a textura) - bg_vertices[0].position = {0, 0}; - bg_vertices[0].tex_coord = {0.0f, 0.0f}; - bg_vertices[0].color = {r, g, b, alpha}; + bg_vertices[0].position = {.x = 0, .y = 0}; + bg_vertices[0].tex_coord = {.x = 0.0f, .y = 0.0f}; + bg_vertices[0].color = {.r = r, .g = g, .b = b, .a = alpha}; - bg_vertices[1].position = {static_cast(box_width_), 0}; - bg_vertices[1].tex_coord = {1.0f, 0.0f}; - bg_vertices[1].color = {r, g, b, alpha}; + bg_vertices[1].position = {.x = static_cast(box_width_), .y = 0}; + bg_vertices[1].tex_coord = {.x = 1.0f, .y = 0.0f}; + bg_vertices[1].color = {.r = r, .g = g, .b = b, .a = alpha}; - bg_vertices[2].position = {static_cast(box_width_), static_cast(box_height_)}; - bg_vertices[2].tex_coord = {1.0f, 1.0f}; - bg_vertices[2].color = {r, g, b, alpha}; + bg_vertices[2].position = {.x = static_cast(box_width_), .y = static_cast(box_height_)}; + bg_vertices[2].tex_coord = {.x = 1.0f, .y = 1.0f}; + bg_vertices[2].color = {.r = r, .g = g, .b = b, .a = alpha}; - bg_vertices[3].position = {0, static_cast(box_height_)}; - bg_vertices[3].tex_coord = {0.0f, 1.0f}; - bg_vertices[3].color = {r, g, b, alpha}; + bg_vertices[3].position = {.x = 0, .y = static_cast(box_height_)}; + bg_vertices[3].tex_coord = {.x = 0.0f, .y = 1.0f}; + bg_vertices[3].color = {.r = r, .g = g, .b = b, .a = alpha}; - int bg_indices[6] = {0, 1, 2, 2, 3, 0}; - SDL_RenderGeometry(renderer_, nullptr, bg_vertices, 4, bg_indices, 6); + std::array bg_indices = {0, 1, 2, 2, 3, 0}; + SDL_RenderGeometry(renderer_, nullptr, bg_vertices.data(), 4, bg_indices.data(), 6); // Renderizar texto del overlay (ajustando coordenadas para que sean relativas a 0,0) // Necesito renderizar el texto igual que en renderHelpText() pero con coordenadas ajustadas // Obtener colores para el texto - int text_r, text_g, text_b; + int text_r; + int text_g; + int text_b; theme_mgr_->getCurrentThemeTextColor(text_r, text_g, text_b); SDL_Color category_color = {static_cast(text_r), static_cast(text_g), static_cast(text_b), 255}; @@ -339,7 +348,7 @@ void HelpOverlay::rebuildCachedTexture() { // Guardar colores actuales para comparación futura last_category_color_ = category_color; last_content_color_ = content_color; - last_bg_color_ = {static_cast(notif_bg_r), static_cast(notif_bg_g), static_cast(notif_bg_b), 255}; + last_bg_color_ = {.r = static_cast(notif_bg_r), .g = static_cast(notif_bg_g), .b = static_cast(notif_bg_b), .a = 255}; // Configuración de espaciado int line_height = text_renderer_->getTextHeight(); @@ -347,13 +356,13 @@ void HelpOverlay::rebuildCachedTexture() { int col_gap = padding * 2; // Posición X de inicio de cada columna - int col_start[3]; + std::array col_start{}; col_start[0] = padding; col_start[1] = padding + column1_width_ + col_gap; col_start[2] = padding + column1_width_ + col_gap + column2_width_ + col_gap; // Ancho de cada columna (para centrado interno) - int col_width[3] = {column1_width_, column2_width_, column3_width_}; + std::array col_width = {column1_width_, column2_width_, column3_width_}; int glyph_height = text_renderer_->getGlyphHeight(); int current_y = padding; @@ -387,7 +396,7 @@ void HelpOverlay::rebuildCachedTexture() { } else { // Encabezado de sección — centrado en la columna int w = text_renderer_->getTextWidthPhysical(binding.key); - text_renderer_->printAbsolute(cx + (cw - w) / 2, current_y, binding.key, category_color); + text_renderer_->printAbsolute(cx + ((cw - w) / 2), current_y, binding.key, category_color); current_y += line_height; } continue; @@ -397,7 +406,7 @@ void HelpOverlay::rebuildCachedTexture() { int key_width = text_renderer_->getTextWidthPhysical(binding.key); int desc_width = text_renderer_->getTextWidthPhysical(binding.description); int total_width = key_width + 10 + desc_width; - int line_x = cx + (cw - total_width) / 2; + int line_x = cx + ((cw - total_width) / 2); text_renderer_->printAbsolute(line_x, current_y, binding.key, category_color); text_renderer_->printAbsolute(line_x + key_width + 10, current_y, binding.description, content_color); @@ -413,13 +422,19 @@ void HelpOverlay::rebuildCachedTexture() { } void HelpOverlay::render(SDL_Renderer* renderer) { - if (!visible_) return; + if (!visible_) { + return; + } // Obtener colores actuales del tema - int notif_bg_r, notif_bg_g, notif_bg_b; + int notif_bg_r; + int notif_bg_g; + int notif_bg_b; theme_mgr_->getCurrentNotificationBackgroundColor(notif_bg_r, notif_bg_g, notif_bg_b); - int text_r, text_g, text_b; + int text_r; + int text_g; + int text_b; theme_mgr_->getCurrentThemeTextColor(text_r, text_g, text_b); Color ball_color = theme_mgr_->getInterpolatedColor(0); @@ -433,22 +448,24 @@ void HelpOverlay::render(SDL_Renderer* renderer) { constexpr int COLOR_CHANGE_THRESHOLD = 5; bool colors_changed = (abs(current_bg.r - last_bg_color_.r) > COLOR_CHANGE_THRESHOLD || - abs(current_bg.g - last_bg_color_.g) > COLOR_CHANGE_THRESHOLD || - abs(current_bg.b - last_bg_color_.b) > COLOR_CHANGE_THRESHOLD || - abs(current_category.r - last_category_color_.r) > COLOR_CHANGE_THRESHOLD || - abs(current_category.g - last_category_color_.g) > COLOR_CHANGE_THRESHOLD || - abs(current_category.b - last_category_color_.b) > COLOR_CHANGE_THRESHOLD || - abs(current_content.r - last_content_color_.r) > COLOR_CHANGE_THRESHOLD || - abs(current_content.g - last_content_color_.g) > COLOR_CHANGE_THRESHOLD || - abs(current_content.b - last_content_color_.b) > COLOR_CHANGE_THRESHOLD); + abs(current_bg.g - last_bg_color_.g) > COLOR_CHANGE_THRESHOLD || + abs(current_bg.b - last_bg_color_.b) > COLOR_CHANGE_THRESHOLD || + abs(current_category.r - last_category_color_.r) > COLOR_CHANGE_THRESHOLD || + abs(current_category.g - last_category_color_.g) > COLOR_CHANGE_THRESHOLD || + abs(current_category.b - last_category_color_.b) > COLOR_CHANGE_THRESHOLD || + abs(current_content.r - last_content_color_.r) > COLOR_CHANGE_THRESHOLD || + abs(current_content.g - last_content_color_.g) > COLOR_CHANGE_THRESHOLD || + abs(current_content.b - last_content_color_.b) > COLOR_CHANGE_THRESHOLD); // Regenerar textura si es necesario (colores cambiaron O flag de rebuild activo) - if (texture_needs_rebuild_ || colors_changed || !cached_texture_) { + if (texture_needs_rebuild_ || colors_changed || (cached_texture_ == nullptr)) { rebuildCachedTexture(); } // Si no hay textura cacheada (error), salir - if (!cached_texture_) return; + if (cached_texture_ == nullptr) { + return; + } // CRÍTICO: Habilitar alpha blending para que la transparencia funcione SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); @@ -460,8 +477,8 @@ void HelpOverlay::render(SDL_Renderer* renderer) { // Calcular posición centrada dentro del VIEWPORT, no de la pantalla física // viewport.w y viewport.h son las dimensiones del área visible // viewport.x y viewport.y son el offset de las barras negras - int centered_x = viewport.x + (viewport.w - box_width_) / 2; - int centered_y = viewport.y + (viewport.h - box_height_) / 2; + int centered_x = viewport.x + ((viewport.w - box_width_) / 2); + int centered_y = viewport.y + ((viewport.h - box_height_) / 2); // Renderizar la textura cacheada centrada en el viewport SDL_FRect dest_rect; diff --git a/source/ui/help_overlay.hpp b/source/ui/help_overlay.hpp index 8e9cd1c..ee2b0e3 100644 --- a/source/ui/help_overlay.hpp +++ b/source/ui/help_overlay.hpp @@ -17,89 +17,89 @@ class TextRenderer; * Toggle on/off con tecla H. La simulación continúa en el fondo. */ class HelpOverlay { - public: - HelpOverlay(); - ~HelpOverlay(); + public: + HelpOverlay(); + ~HelpOverlay(); - /** - * @brief Inicializa el overlay con renderer y theme manager - */ - void initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height, int font_size); + /** + * @brief Inicializa el overlay con renderer y theme manager + */ + void initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height, int font_size); - /** - * @brief Renderiza el overlay si está visible - */ - void render(SDL_Renderer* renderer); + /** + * @brief Renderiza el overlay si está visible + */ + void render(SDL_Renderer* renderer); - /** - * @brief Actualiza dimensiones físicas de ventana (zoom, fullscreen, etc.) - */ - void updatePhysicalWindowSize(int physical_width, int physical_height); + /** + * @brief Actualiza dimensiones físicas de ventana (zoom, fullscreen, etc.) + */ + void updatePhysicalWindowSize(int physical_width, int physical_height); - /** - * @brief Reinitializa el tamaño de fuente (cuando cambia el tamaño de ventana) - */ - void reinitializeFontSize(int new_font_size); + /** + * @brief Reinitializa el tamaño de fuente (cuando cambia el tamaño de ventana) + */ + void reinitializeFontSize(int new_font_size); - /** - * @brief Actualiza font size Y dimensiones físicas de forma atómica - * @param font_size Tamaño de fuente actual - * @param physical_width Nueva anchura física - * @param physical_height Nueva altura física - */ - void updateAll(int font_size, int physical_width, int physical_height); + /** + * @brief Actualiza font size Y dimensiones físicas de forma atómica + * @param font_size Tamaño de fuente actual + * @param physical_width Nueva anchura física + * @param physical_height Nueva altura física + */ + void updateAll(int font_size, int physical_width, int physical_height); - /** - * @brief Toggle visibilidad del overlay - */ - void toggle(); + /** + * @brief Toggle visibilidad del overlay + */ + void toggle(); - /** - * @brief Consulta si el overlay está visible - */ - bool isVisible() const { return visible_; } + /** + * @brief Consulta si el overlay está visible + */ + bool isVisible() const { return visible_; } - private: - SDL_Renderer* renderer_; - ThemeManager* theme_mgr_; - TextRenderer* text_renderer_; // Renderer de texto para la ayuda - int physical_width_; - int physical_height_; - bool visible_; + private: + SDL_Renderer* renderer_; + ThemeManager* theme_mgr_; + TextRenderer* text_renderer_; // Renderer de texto para la ayuda + int physical_width_; + int physical_height_; + bool visible_; - // Dimensiones calculadas del recuadro (anchura dinámica según texto, centrado) - int box_width_; - int box_height_; - int box_x_; - int box_y_; + // Dimensiones calculadas del recuadro (anchura dinámica según texto, centrado) + int box_width_; + int box_height_; + int box_x_; + int box_y_; - // Anchos individuales de cada columna (para evitar solapamiento) - int column1_width_; - int column2_width_; - int column3_width_; + // Anchos individuales de cada columna (para evitar solapamiento) + int column1_width_; + int column2_width_; + int column3_width_; - // Sistema de caché para optimización de rendimiento - SDL_Texture* cached_texture_; // Textura cacheada del overlay completo - SDL_Color last_category_color_; // Último color de categorías renderizado - SDL_Color last_content_color_; // Último color de contenido renderizado - SDL_Color last_bg_color_; // Último color de fondo renderizado - bool texture_needs_rebuild_; // Flag para forzar regeneración de textura + // Sistema de caché para optimización de rendimiento + SDL_Texture* cached_texture_; // Textura cacheada del overlay completo + SDL_Color last_category_color_; // Último color de categorías renderizado + SDL_Color last_content_color_; // Último color de contenido renderizado + SDL_Color last_bg_color_; // Último color de fondo renderizado + bool texture_needs_rebuild_; // Flag para forzar regeneración de textura - // Calcular dimensiones del texto más largo - void calculateTextDimensions(int& max_width, int& total_height); + // Calcular dimensiones del texto más largo + void calculateTextDimensions(int& max_width, int& total_height); - // Calcular dimensiones del recuadro según tamaño de ventana y texto - void calculateBoxDimensions(); + // Calcular dimensiones del recuadro según tamaño de ventana y texto + void calculateBoxDimensions(); - // Regenerar textura cacheada del overlay - void rebuildCachedTexture(); + // Regenerar textura cacheada del overlay + void rebuildCachedTexture(); - // Estructura para par tecla-descripción - struct KeyBinding { - const char* key; - const char* description; - }; + // Estructura para par tecla-descripción + struct KeyBinding { + const char* key; + const char* description; + }; - // Lista de todos los controles (se llena en constructor) - std::vector key_bindings_; + // Lista de todos los controles (se llena en constructor) + std::vector key_bindings_; }; diff --git a/source/ui/logo_scaler.cpp b/source/ui/logo_scaler.cpp index c4d54f6..a7596a4 100644 --- a/source/ui/logo_scaler.cpp +++ b/source/ui/logo_scaler.cpp @@ -1,25 +1,25 @@ #define STB_IMAGE_RESIZE_IMPLEMENTATION #include "logo_scaler.hpp" -#include // Para SDL_GetError -#include // Para SDL_Log -#include // Para SDL_PixelFormat -#include // Para SDL_CreateTexture -#include // Para SDL_CreateSurfaceFrom -#include // Para SDL_GetDisplays +#include // Para SDL_GetError +#include // Para SDL_Log +#include // Para SDL_PixelFormat +#include // Para SDL_CreateTexture +#include // Para SDL_CreateSurfaceFrom +#include // Para SDL_GetDisplays #include // Para free() #include // Para std::cout #include "external/stb_image.h" // Para stbi_load, stbi_image_free #include "external/stb_image_resize2.h" // Para stbir_resize_uint8_srgb -#include "resource_manager.hpp" // Para cargar desde pack +#include "resource_manager.hpp" // Para cargar desde pack // ============================================================================ // Detectar resolución nativa del monitor principal // ============================================================================ -bool LogoScaler::detectNativeResolution(int& native_width, int& native_height) { +auto LogoScaler::detectNativeResolution(int& native_width, int& native_height) -> bool { int num_displays = 0; SDL_DisplayID* displays = SDL_GetDisplays(&num_displays); @@ -48,22 +48,25 @@ bool LogoScaler::detectNativeResolution(int& native_width, int& native_height) { // Cargar PNG y escalar al tamaño especificado // ============================================================================ -unsigned char* LogoScaler::loadAndScale(const std::string& path, - int target_width, int target_height, - int& out_width, int& out_height) { +auto LogoScaler::loadAndScale(const std::string& path, + int target_width, + int target_height, + int& out_width, + int& out_height) -> unsigned char* { // 1. Intentar cargar imagen desde ResourceManager (pack o disco) - int orig_width, orig_height, orig_channels; + int orig_width; + int orig_height; + int orig_channels; unsigned char* orig_data = nullptr; // 1a. Cargar desde ResourceManager - unsigned char* resourceData = nullptr; - size_t resourceSize = 0; + unsigned char* resource_data = nullptr; + size_t resource_size = 0; - if (ResourceManager::loadResource(path, resourceData, resourceSize)) { + if (ResourceManager::loadResource(path, resource_data, resource_size)) { // Descodificar imagen desde memoria usando stb_image - orig_data = stbi_load_from_memory(resourceData, static_cast(resourceSize), - &orig_width, &orig_height, &orig_channels, STBI_rgb_alpha); - delete[] resourceData; // Liberar buffer temporal + orig_data = stbi_load_from_memory(resource_data, static_cast(resource_size), &orig_width, &orig_height, &orig_channels, STBI_rgb_alpha); + delete[] resource_data; // Liberar buffer temporal } // 1b. Si falla todo, error @@ -80,7 +83,7 @@ unsigned char* LogoScaler::loadAndScale(const std::string& path, out_height = target_height; // 3. Alocar buffer para imagen escalada (RGBA = 4 bytes por píxel) - unsigned char* scaled_data = static_cast(malloc(out_width * out_height * 4)); + auto* scaled_data = static_cast(malloc(out_width * out_height * 4)); if (scaled_data == nullptr) { SDL_Log("Error al alocar memoria para imagen escalada"); stbi_image_free(orig_data); @@ -90,9 +93,15 @@ unsigned char* LogoScaler::loadAndScale(const std::string& path, // 4. Escalar con stb_image_resize2 (algoritmo Mitchell, espacio sRGB) // La función devuelve el puntero de salida, o nullptr si falla unsigned char* result = stbir_resize_uint8_srgb( - orig_data, orig_width, orig_height, 0, // Input - scaled_data, out_width, out_height, 0, // Output - STBIR_RGBA // Formato píxel + orig_data, + orig_width, + orig_height, + 0, // Input + scaled_data, + out_width, + out_height, + 0, // Output + STBIR_RGBA // Formato píxel ); // Liberar imagen original (ya no la necesitamos) @@ -111,9 +120,10 @@ unsigned char* LogoScaler::loadAndScale(const std::string& path, // Crear textura SDL desde buffer RGBA // ============================================================================ -SDL_Texture* LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer, - unsigned char* data, - int width, int height) { +auto LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer, + unsigned char* data, + int width, + int height) -> SDL_Texture* { if (renderer == nullptr || data == nullptr || width <= 0 || height <= 0) { SDL_Log("Parámetros inválidos para createTextureFromBuffer"); return nullptr; @@ -124,11 +134,11 @@ SDL_Texture* LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer, SDL_PixelFormat pixel_format = SDL_PIXELFORMAT_RGBA32; SDL_Surface* surface = SDL_CreateSurfaceFrom( - width, height, + width, + height, pixel_format, data, - pitch - ); + pitch); if (surface == nullptr) { SDL_Log("Error al crear surface: %s", SDL_GetError()); diff --git a/source/ui/logo_scaler.hpp b/source/ui/logo_scaler.hpp index a1561ec..7949484 100644 --- a/source/ui/logo_scaler.hpp +++ b/source/ui/logo_scaler.hpp @@ -17,45 +17,48 @@ * de pantalla, eliminando el escalado dinámico de SDL y mejorando calidad visual. */ class LogoScaler { -public: - /** - * @brief Detecta la resolución nativa del monitor principal - * - * @param native_width [out] Ancho nativo del display en píxeles - * @param native_height [out] Alto nativo del display en píxeles - * @return true si se pudo detectar, false si hubo error - */ - static bool detectNativeResolution(int& native_width, int& native_height); + public: + /** + * @brief Detecta la resolución nativa del monitor principal + * + * @param native_width [out] Ancho nativo del display en píxeles + * @param native_height [out] Alto nativo del display en píxeles + * @return true si se pudo detectar, false si hubo error + */ + static bool detectNativeResolution(int& native_width, int& native_height); - /** - * @brief Carga un PNG y lo escala al tamaño especificado - * - * Usa stb_image para cargar y stb_image_resize2 para escalar con - * algoritmo Mitchell (balance calidad/velocidad) en espacio sRGB. - * - * @param path Ruta al archivo PNG (ej: "data/logo/logo.png") - * @param target_width Ancho destino en píxeles - * @param target_height Alto destino en píxeles - * @param out_width [out] Ancho real de la imagen escalada - * @param out_height [out] Alto real de la imagen escalada - * @return Buffer RGBA (4 bytes por píxel) o nullptr si falla - * IMPORTANTE: El caller debe liberar con free() cuando termine - */ - static unsigned char* loadAndScale(const std::string& path, - int target_width, int target_height, - int& out_width, int& out_height); + /** + * @brief Carga un PNG y lo escala al tamaño especificado + * + * Usa stb_image para cargar y stb_image_resize2 para escalar con + * algoritmo Mitchell (balance calidad/velocidad) en espacio sRGB. + * + * @param path Ruta al archivo PNG (ej: "data/logo/logo.png") + * @param target_width Ancho destino en píxeles + * @param target_height Alto destino en píxeles + * @param out_width [out] Ancho real de la imagen escalada + * @param out_height [out] Alto real de la imagen escalada + * @return Buffer RGBA (4 bytes por píxel) o nullptr si falla + * IMPORTANTE: El caller debe liberar con free() cuando termine + */ + static unsigned char* loadAndScale(const std::string& path, + int target_width, + int target_height, + int& out_width, + int& out_height); - /** - * @brief Crea una textura SDL desde un buffer RGBA - * - * @param renderer Renderizador SDL activo - * @param data Buffer RGBA (4 bytes por píxel) - * @param width Ancho del buffer en píxeles - * @param height Alto del buffer en píxeles - * @return Textura SDL creada o nullptr si falla - * IMPORTANTE: El caller debe destruir con SDL_DestroyTexture() - */ - static SDL_Texture* createTextureFromBuffer(SDL_Renderer* renderer, - unsigned char* data, - int width, int height); + /** + * @brief Crea una textura SDL desde un buffer RGBA + * + * @param renderer Renderizador SDL activo + * @param data Buffer RGBA (4 bytes por píxel) + * @param width Ancho del buffer en píxeles + * @param height Alto del buffer en píxeles + * @return Textura SDL creada o nullptr si falla + * IMPORTANTE: El caller debe destruir con SDL_DestroyTexture() + */ + static SDL_Texture* createTextureFromBuffer(SDL_Renderer* renderer, + unsigned char* data, + int width, + int height); }; diff --git a/source/ui/notifier.cpp b/source/ui/notifier.cpp index 6bac32c..3502ace 100644 --- a/source/ui/notifier.cpp +++ b/source/ui/notifier.cpp @@ -1,9 +1,11 @@ #include "notifier.hpp" + +#include + +#include "defines.hpp" #include "text/textrenderer.hpp" #include "theme_manager.hpp" -#include "defines.hpp" #include "utils/easing_functions.hpp" -#include // ============================================================================ // HELPER: Obtener viewport en coordenadas físicas (no lógicas) @@ -11,9 +13,10 @@ // SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación // lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar // temporalmente la presentación lógica. -static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) { +static auto getPhysicalViewport(SDL_Renderer* renderer) -> SDL_Rect { // Guardar estado actual de presentación lógica - int logical_w = 0, logical_h = 0; + int logical_w = 0; + int logical_h = 0; SDL_RendererLogicalPresentation presentation_mode; SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode); @@ -31,19 +34,19 @@ static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) { } Notifier::Notifier() - : renderer_(nullptr) - , text_renderer_(nullptr) - , theme_manager_(nullptr) - , window_width_(0) - , window_height_(0) - , current_notification_(nullptr) { + : renderer_(nullptr), + text_renderer_(nullptr), + theme_manager_(nullptr), + window_width_(0), + window_height_(0), + current_notification_(nullptr) { } Notifier::~Notifier() { clear(); } -bool Notifier::init(SDL_Renderer* renderer, TextRenderer* text_renderer, ThemeManager* theme_manager, int window_width, int window_height) { +auto Notifier::init(SDL_Renderer* renderer, TextRenderer* text_renderer, ThemeManager* theme_manager, int window_width, int window_height) -> bool { renderer_ = renderer; text_renderer_ = text_renderer; theme_manager_ = theme_manager; @@ -105,7 +108,7 @@ void Notifier::update(Uint64 current_time) { // Animación de entrada (NOTIFICATION_SLIDE_TIME ms) if (elapsed < NOTIFICATION_SLIDE_TIME) { float progress = static_cast(elapsed) / static_cast(NOTIFICATION_SLIDE_TIME); - float eased = Easing::easeOutBack(progress); // Efecto con ligero overshoot + float eased = Easing::easeOutBack(progress); // Efecto con ligero overshoot current_notification_->y_offset = -50.0f + (50.0f * eased); // De -50 a 0 } else { // Transición a VISIBLE @@ -151,28 +154,30 @@ void Notifier::update(Uint64 current_time) { } void Notifier::render() { - if (!current_notification_ || !text_renderer_ || !renderer_ || !theme_manager_) { + if (!current_notification_ || (text_renderer_ == nullptr) || (renderer_ == nullptr) || (theme_manager_ == nullptr)) { return; } // Obtener colores DINÁMICOS desde ThemeManager (incluye LERP automático) - int text_r, text_g, text_b; + int text_r; + int text_g; + int text_b; theme_manager_->getCurrentThemeTextColor(text_r, text_g, text_b); SDL_Color text_color = { static_cast(text_r), static_cast(text_g), static_cast(text_b), - static_cast(current_notification_->alpha * 255.0f) - }; + static_cast(current_notification_->alpha * 255.0f)}; - int bg_r, bg_g, bg_b; + int bg_r; + int bg_g; + int bg_b; theme_manager_->getCurrentNotificationBackgroundColor(bg_r, bg_g, bg_b); SDL_Color bg_color = { static_cast(bg_r), static_cast(bg_g), static_cast(bg_b), - 255 - }; + 255}; // Calcular dimensiones del texto en píxeles FÍSICOS // IMPORTANTE: Usar getTextWidthPhysical() en lugar de getTextWidth() @@ -207,7 +212,7 @@ void Notifier::render() { } void Notifier::renderBackground(int x, int y, int width, int height, float alpha, SDL_Color bg_color) { - if (!renderer_) { + if (renderer_ == nullptr) { return; } @@ -225,7 +230,7 @@ void Notifier::renderBackground(int x, int y, int width, int height, float alpha bg_rect.h = static_cast(height); // Color del tema con alpha - Uint8 bg_alpha = static_cast(alpha * 255.0f); + auto bg_alpha = static_cast(alpha * 255.0f); SDL_SetRenderDrawColor(renderer_, bg_color.r, bg_color.g, bg_color.b, bg_alpha); // Habilitar blending para transparencia @@ -233,7 +238,8 @@ void Notifier::renderBackground(int x, int y, int width, int height, float alpha // CRÍTICO: Deshabilitar presentación lógica para renderizar en píxeles físicos absolutos // (igual que printAbsolute() en TextRenderer) - int logical_w = 0, logical_h = 0; + int logical_w = 0; + int logical_h = 0; SDL_RendererLogicalPresentation presentation_mode; SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &presentation_mode); @@ -248,7 +254,7 @@ void Notifier::renderBackground(int x, int y, int width, int height, float alpha SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_NONE); } -bool Notifier::isActive() const { +auto Notifier::isActive() const -> bool { return (current_notification_ != nullptr); } diff --git a/source/ui/notifier.hpp b/source/ui/notifier.hpp index a3ebca7..778a194 100644 --- a/source/ui/notifier.hpp +++ b/source/ui/notifier.hpp @@ -1,9 +1,10 @@ #pragma once #include -#include -#include + #include +#include +#include // Forward declarations class TextRenderer; @@ -20,92 +21,92 @@ class ThemeManager; * - Texto de tamaño fijo independiente de resolución */ class Notifier { -public: - enum class NotificationState { - SLIDING_IN, // Animación de entrada desde arriba - VISIBLE, // Visible estático - FADING_OUT, // Animación de salida (fade) - DONE // Completado, listo para eliminar - }; + public: + enum class NotificationState { + SLIDING_IN, // Animación de entrada desde arriba + VISIBLE, // Visible estático + FADING_OUT, // Animación de salida (fade) + DONE // Completado, listo para eliminar + }; - struct Notification { - std::string text; - Uint64 created_time; - Uint64 duration; - NotificationState state; - float alpha; // Opacidad 0.0-1.0 - float y_offset; // Offset Y para animación slide (píxeles) - // NOTA: Los colores se obtienen dinámicamente desde ThemeManager en render() - }; + struct Notification { + std::string text; + Uint64 created_time; + Uint64 duration; + NotificationState state; + float alpha; // Opacidad 0.0-1.0 + float y_offset; // Offset Y para animación slide (píxeles) + // NOTA: Los colores se obtienen dinámicamente desde ThemeManager en render() + }; - Notifier(); - ~Notifier(); + Notifier(); + ~Notifier(); - /** - * @brief Inicializa el notifier con un TextRenderer y ThemeManager - * @param renderer SDL renderer para dibujar - * @param text_renderer TextRenderer configurado con tamaño absoluto - * @param theme_manager ThemeManager para obtener colores dinámicos con LERP - * @param window_width Ancho de ventana física - * @param window_height Alto de ventana física - * @return true si inicialización exitosa - */ - bool init(SDL_Renderer* renderer, TextRenderer* text_renderer, ThemeManager* theme_manager, int window_width, int window_height); + /** + * @brief Inicializa el notifier con un TextRenderer y ThemeManager + * @param renderer SDL renderer para dibujar + * @param text_renderer TextRenderer configurado con tamaño absoluto + * @param theme_manager ThemeManager para obtener colores dinámicos con LERP + * @param window_width Ancho de ventana física + * @param window_height Alto de ventana física + * @return true si inicialización exitosa + */ + bool init(SDL_Renderer* renderer, TextRenderer* text_renderer, ThemeManager* theme_manager, int window_width, int window_height); - /** - * @brief Actualiza las dimensiones de la ventana (llamar en resize) - * @param window_width Nuevo ancho de ventana física - * @param window_height Nuevo alto de ventana física - */ - void updateWindowSize(int window_width, int window_height); + /** + * @brief Actualiza las dimensiones de la ventana (llamar en resize) + * @param window_width Nuevo ancho de ventana física + * @param window_height Nuevo alto de ventana física + */ + void updateWindowSize(int window_width, int window_height); - /** - * @brief Muestra una nueva notificación - * @param text Texto a mostrar - * @param duration Duración en milisegundos (0 = usar default) - * @note Los colores se obtienen dinámicamente desde ThemeManager cada frame - */ - void show(const std::string& text, Uint64 duration = 0); + /** + * @brief Muestra una nueva notificación + * @param text Texto a mostrar + * @param duration Duración en milisegundos (0 = usar default) + * @note Los colores se obtienen dinámicamente desde ThemeManager cada frame + */ + void show(const std::string& text, Uint64 duration = 0); - /** - * @brief Actualiza las animaciones de notificaciones - * @param current_time Tiempo actual en ms (SDL_GetTicks()) - */ - void update(Uint64 current_time); + /** + * @brief Actualiza las animaciones de notificaciones + * @param current_time Tiempo actual en ms (SDL_GetTicks()) + */ + void update(Uint64 current_time); - /** - * @brief Renderiza la notificación activa - */ - void render(); + /** + * @brief Renderiza la notificación activa + */ + void render(); - /** - * @brief Verifica si hay una notificación activa (visible) - * @return true si hay notificación mostrándose - */ - bool isActive() const; + /** + * @brief Verifica si hay una notificación activa (visible) + * @return true si hay notificación mostrándose + */ + bool isActive() const; - /** - * @brief Limpia todas las notificaciones pendientes - */ - void clear(); + /** + * @brief Limpia todas las notificaciones pendientes + */ + void clear(); -private: - SDL_Renderer* renderer_; - TextRenderer* text_renderer_; - ThemeManager* theme_manager_; // Gestor de temas para obtener colores dinámicos con LERP - int window_width_; - int window_height_; + private: + SDL_Renderer* renderer_; + TextRenderer* text_renderer_; + ThemeManager* theme_manager_; // Gestor de temas para obtener colores dinámicos con LERP + int window_width_; + int window_height_; - std::queue notification_queue_; - std::unique_ptr current_notification_; + std::queue notification_queue_; + std::unique_ptr current_notification_; - /** - * @brief Procesa la cola y activa la siguiente notificación si es posible - */ - void processQueue(); + /** + * @brief Procesa la cola y activa la siguiente notificación si es posible + */ + void processQueue(); - /** - * @brief Dibuja el fondo semitransparente de la notificación - */ - void renderBackground(int x, int y, int width, int height, float alpha, SDL_Color bg_color); + /** + * @brief Dibuja el fondo semitransparente de la notificación + */ + void renderBackground(int x, int y, int width, int height, float alpha, SDL_Color bg_color); }; diff --git a/source/ui/ui_manager.cpp b/source/ui/ui_manager.cpp index 6631e74..e06ab11 100644 --- a/source/ui/ui_manager.cpp +++ b/source/ui/ui_manager.cpp @@ -1,18 +1,20 @@ #include "ui_manager.hpp" #include + #include +#include #include -#include "ball.hpp" // for Ball -#include "defines.hpp" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode -#include "engine.hpp" // for Engine (info de sistema) -#include "scene/scene_manager.hpp" // for SceneManager -#include "shapes/shape.hpp" // for Shape -#include "text/textrenderer.hpp" // for TextRenderer -#include "theme_manager.hpp" // for ThemeManager -#include "notifier.hpp" // for Notifier -#include "help_overlay.hpp" // for HelpOverlay +#include "ball.hpp" // for Ball +#include "defines.hpp" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode +#include "engine.hpp" // for Engine (info de sistema) +#include "help_overlay.hpp" // for HelpOverlay +#include "notifier.hpp" // for Notifier +#include "scene/scene_manager.hpp" // for SceneManager +#include "shapes/shape.hpp" // for Shape +#include "text/textrenderer.hpp" // for TextRenderer +#include "theme_manager.hpp" // for ThemeManager // ============================================================================ // HELPER: Obtener viewport en coordenadas físicas (no lógicas) @@ -20,9 +22,10 @@ // SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación // lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar // temporalmente la presentación lógica. -static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) { +static auto getPhysicalViewport(SDL_Renderer* renderer) -> SDL_Rect { // Guardar estado actual de presentación lógica - int logical_w = 0, logical_h = 0; + int logical_w = 0; + int logical_h = 0; SDL_RendererLogicalPresentation presentation_mode; SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode); @@ -40,23 +43,23 @@ static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) { } UIManager::UIManager() - : text_renderer_debug_(nullptr) - , text_renderer_notifier_(nullptr) - , notifier_(nullptr) - , help_overlay_(nullptr) - , show_debug_(false) - , fps_last_time_(0) - , fps_frame_count_(0) - , fps_current_(0) - , fps_text_("FPS: 0") - , vsync_text_("VSYNC ON") - , renderer_(nullptr) - , theme_manager_(nullptr) - , physical_window_width_(0) - , physical_window_height_(0) - , logical_window_width_(0) - , logical_window_height_(0) - , current_font_size_(18) { // Tamaño por defecto (medium) + : text_renderer_debug_(nullptr), + text_renderer_notifier_(nullptr), + notifier_(nullptr), + help_overlay_(nullptr), + show_debug_(false), + fps_last_time_(0), + fps_frame_count_(0), + fps_current_(0), + fps_text_("FPS: 0"), + vsync_text_("VSYNC ON"), + renderer_(nullptr), + theme_manager_(nullptr), + physical_window_width_(0), + physical_window_height_(0), + logical_window_width_(0), + logical_window_height_(0), + current_font_size_(18) { // Tamaño por defecto (medium) } UIManager::~UIManager() { @@ -67,13 +70,15 @@ UIManager::~UIManager() { delete help_overlay_; } -void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, - int physical_width, int physical_height, - int logical_width, int logical_height) { - delete text_renderer_debug_; text_renderer_debug_ = nullptr; - delete text_renderer_notifier_; text_renderer_notifier_ = nullptr; - delete notifier_; notifier_ = nullptr; - delete help_overlay_; help_overlay_ = nullptr; +void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, int physical_width, int physical_height, int logical_width, int logical_height) { + delete text_renderer_debug_; + text_renderer_debug_ = nullptr; + delete text_renderer_notifier_; + text_renderer_notifier_ = nullptr; + delete notifier_; + notifier_ = nullptr; + delete help_overlay_; + help_overlay_ = nullptr; renderer_ = renderer; theme_manager_ = theme_manager; @@ -95,8 +100,7 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, // Crear y configurar sistema de notificaciones notifier_ = new Notifier(); - notifier_->init(renderer, text_renderer_notifier_, theme_manager_, - physical_width, physical_height); + notifier_->init(renderer, text_renderer_notifier_, theme_manager_, physical_width, physical_height); // Crear y configurar sistema de ayuda (overlay) help_overlay_ = new HelpOverlay(); @@ -123,30 +127,29 @@ void UIManager::update(Uint64 current_time, float delta_time) { } void UIManager::render(SDL_Renderer* renderer, - const Engine* engine, - const SceneManager* scene_manager, - SimulationMode current_mode, - AppMode current_app_mode, - const Shape* active_shape, - float shape_convergence, - int physical_width, - int physical_height, - int current_screen_width) { + const Engine* engine, + const SceneManager* scene_manager, + SimulationMode current_mode, + AppMode current_app_mode, + const Shape* active_shape, + float shape_convergence, + int physical_width, + int physical_height, + int current_screen_width) { // Actualizar dimensiones físicas (puede cambiar en fullscreen) physical_window_width_ = physical_width; physical_window_height_ = physical_height; // Renderizar debug HUD si está activo if (show_debug_) { - renderDebugHUD(engine, scene_manager, current_mode, current_app_mode, - active_shape, shape_convergence); + renderDebugHUD(engine, scene_manager, current_mode, current_app_mode, active_shape, shape_convergence); } // Renderizar notificaciones (siempre al final, sobre todo lo demás) notifier_->render(); // Renderizar ayuda (siempre última, sobre todo incluso notificaciones) - if (help_overlay_) { + if (help_overlay_ != nullptr) { help_overlay_->render(renderer); } } @@ -156,7 +159,7 @@ void UIManager::toggleDebug() { } void UIManager::toggleHelp() { - if (help_overlay_) { + if (help_overlay_ != nullptr) { help_overlay_->toggle(); } } @@ -190,16 +193,16 @@ void UIManager::updatePhysicalWindowSize(int width, int height, int logical_heig current_font_size_ = new_font_size; // Reinicializar text renderers con nuevo tamaño - if (text_renderer_debug_) { + if (text_renderer_debug_ != nullptr) { text_renderer_debug_->reinitialize(std::max(9, current_font_size_ - 2)); } - if (text_renderer_notifier_) { + if (text_renderer_notifier_ != nullptr) { text_renderer_notifier_->reinitialize(current_font_size_); } } // Actualizar help overlay con font size actual Y nuevas dimensiones (atómicamente) - if (help_overlay_) { + if (help_overlay_ != nullptr) { help_overlay_->updateAll(std::max(9, current_font_size_ - 1), width, height); } @@ -209,12 +212,12 @@ void UIManager::updatePhysicalWindowSize(int width, int height, int logical_heig // === Métodos privados === -void UIManager::renderDebugHUD(const Engine* engine, - const SceneManager* scene_manager, - SimulationMode current_mode, - AppMode current_app_mode, - const Shape* active_shape, - float shape_convergence) { +void UIManager::renderDebugHUD(const Engine* engine, // NOLINT(readability-function-cognitive-complexity) + const SceneManager* scene_manager, + SimulationMode current_mode, + AppMode current_app_mode, + const Shape* active_shape, + float shape_convergence) { int line_height = text_renderer_debug_->getTextHeight(); int margin = 8; SDL_Rect physical_viewport = getPhysicalViewport(renderer_); @@ -236,7 +239,7 @@ void UIManager::renderDebugHUD(const Engine* engine, if (current_mode == SimulationMode::PHYSICS) { simmode_text = "SimMode: PHYSICS"; } else if (current_mode == SimulationMode::SHAPE) { - if (active_shape) { + if (active_shape != nullptr) { simmode_text = std::string("SimMode: SHAPE (") + active_shape->getName() + ")"; } else { simmode_text = "SimMode: SHAPE"; @@ -246,7 +249,7 @@ void UIManager::renderDebugHUD(const Engine* engine, } std::string sprite_name = engine->getCurrentTextureName(); - std::transform(sprite_name.begin(), sprite_name.end(), sprite_name.begin(), ::toupper); + std::ranges::transform(sprite_name, sprite_name.begin(), ::toupper); std::string sprite_text = "Sprite: " + sprite_name; size_t ball_count = scene_manager->getBallCount(); @@ -256,7 +259,9 @@ void UIManager::renderDebugHUD(const Engine* engine, std::string formatted; int digits = static_cast(count_str.length()); for (int i = 0; i < digits; i++) { - if (i > 0 && (digits - i) % 3 == 0) formatted += ','; + if (i > 0 && (digits - i) % 3 == 0) { + formatted += ','; + } formatted += count_str[i]; } balls_text = "Balls: " + formatted; @@ -275,7 +280,9 @@ void UIManager::renderDebugHUD(const Engine* engine, std::string formatted; int digits = static_cast(count_str.length()); for (int i = 0; i < digits; i++) { - if (i > 0 && (digits - i) % 3 == 0) formatted += ','; + if (i > 0 && (digits - i) % 3 == 0) { + formatted += ','; + } formatted += count_str[i]; } max_auto_text = "Auto max: " + formatted; @@ -303,9 +310,9 @@ void UIManager::renderDebugHUD(const Engine* engine, std::string refresh_text; int num_displays = 0; SDL_DisplayID* displays = SDL_GetDisplays(&num_displays); - if (displays && num_displays > 0) { + if ((displays != nullptr) && num_displays > 0) { const auto* dm = SDL_GetCurrentDisplayMode(displays[0]); - if (dm) { + if (dm != nullptr) { refresh_text = "Refresh: " + std::to_string(static_cast(dm->refresh_rate)) + " Hz"; } else { refresh_text = "Refresh: N/A"; @@ -322,9 +329,9 @@ void UIManager::renderDebugHUD(const Engine* engine, int hh = static_cast(total_secs / 3600); int mm = static_cast((total_secs % 3600) / 60); int ss = static_cast(total_secs % 60); - char elapsed_buf[32]; - SDL_snprintf(elapsed_buf, sizeof(elapsed_buf), "Elapsed: %02d:%02d:%02d", hh, mm, ss); - std::string elapsed_text(elapsed_buf); + std::array elapsed_buf{}; + SDL_snprintf(elapsed_buf.data(), elapsed_buf.size(), "Elapsed: %02d:%02d:%02d", hh, mm, ss); + std::string elapsed_text(elapsed_buf.data()); // --- Construir vector de líneas en orden --- std::vector lines; @@ -344,17 +351,15 @@ void UIManager::renderDebugHUD(const Engine* engine, if (!engine->isPostFXEnabled()) { postfx_text = "PostFX: OFF"; } else { - static constexpr const char* preset_names[4] = { - "Vinyeta", "Scanlines", "Cromatica", "Complet" - }; + static constexpr std::array PRESET_NAMES = { + "Vinyeta", + "Scanlines", + "Cromatica", + "Complet"}; int mode = engine->getPostFXMode(); - char buf[64]; - SDL_snprintf(buf, sizeof(buf), "PostFX: %s [V:%.2f C:%.2f S:%.2f]", - preset_names[mode], - engine->getPostFXVignette(), - engine->getPostFXChroma(), - engine->getPostFXScanline()); - postfx_text = buf; + std::array buf{}; + SDL_snprintf(buf.data(), buf.size(), "PostFX: %s [V:%.2f C:%.2f S:%.2f]", PRESET_NAMES[mode], engine->getPostFXVignette(), engine->getPostFXChroma(), engine->getPostFXScanline()); + postfx_text = buf.data(); } lines.push_back(postfx_text); lines.push_back(elapsed_text); @@ -366,7 +371,7 @@ void UIManager::renderDebugHUD(const Engine* engine, SDL_FRect pos = first_ball->getPosition(); lines.push_back("Pos: (" + std::to_string(static_cast(pos.x)) + ", " + std::to_string(static_cast(pos.y)) + ")"); lines.push_back("Gravity: " + std::to_string(static_cast(first_ball->getGravityForce()))); - lines.push_back(first_ball->isOnSurface() ? "Surface: YES" : "Surface: NO"); + lines.emplace_back(first_ball->isOnSurface() ? "Surface: YES" : "Surface: NO"); lines.push_back("Loss: " + std::to_string(first_ball->getLossCoefficient()).substr(0, 4)); lines.push_back("Dir: " + gravityDirectionToString(static_cast(scene_manager->getCurrentGravity()))); } @@ -378,29 +383,34 @@ void UIManager::renderDebugHUD(const Engine* engine, // --- Render con desbordamiento a segunda columna --- int max_lines = (physical_viewport.h - 2 * margin) / line_height; - if (max_lines < 1) max_lines = 1; + max_lines = std::max(max_lines, 1); int col_width = physical_viewport.w / 2; for (int i = 0; i < static_cast(lines.size()); i++) { int col = i / max_lines; int row = i % max_lines; - int x = margin + col * col_width; - int y = margin + row * line_height; + int x = margin + (col * col_width); + int y = margin + (row * line_height); text_renderer_debug_->printAbsoluteShadowed(x, y, lines[i].c_str()); } } -std::string UIManager::gravityDirectionToString(int direction) const { +auto UIManager::gravityDirectionToString(int direction) -> std::string { switch (direction) { - case 0: return "Abajo"; // DOWN - case 1: return "Arriba"; // UP - case 2: return "Izquierda"; // LEFT - case 3: return "Derecha"; // RIGHT - default: return "Desconocida"; + case 0: + return "Abajo"; // DOWN + case 1: + return "Arriba"; // UP + case 2: + return "Izquierda"; // LEFT + case 3: + return "Derecha"; // RIGHT + default: + return "Desconocida"; } } -int UIManager::calculateFontSize(int logical_height) const { +auto UIManager::calculateFontSize(int logical_height) -> int { // Escalado híbrido basado en ALTURA LÓGICA (resolución interna, sin zoom) // Esto asegura que el tamaño de fuente sea consistente independientemente del zoom de ventana // - Proporcional en extremos (muy bajo/alto) @@ -435,8 +445,8 @@ int UIManager::calculateFontSize(int logical_height) const { } // Aplicar límites: mínimo 9px, máximo 72px - if (font_size < 9) font_size = 9; - if (font_size > 72) font_size = 72; + font_size = std::max(font_size, 9); + font_size = std::min(font_size, 72); return font_size; } diff --git a/source/ui/ui_manager.hpp b/source/ui/ui_manager.hpp index 82fa4cc..1c25da1 100644 --- a/source/ui/ui_manager.hpp +++ b/source/ui/ui_manager.hpp @@ -1,7 +1,8 @@ #pragma once #include // for Uint64 -#include // for std::string + +#include // for std::string // Forward declarations struct SDL_Renderer; @@ -49,9 +50,7 @@ class UIManager { * @param logical_width Ancho lógico (resolución interna) * @param logical_height Alto lógico (resolución interna) */ - void initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, - int physical_width, int physical_height, - int logical_width, int logical_height); + void initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, int physical_width, int physical_height, int logical_width, int logical_height); /** * @brief Actualiza UI (FPS counter, notificaciones, texto obsoleto) @@ -74,15 +73,15 @@ class UIManager { * @param current_screen_width Ancho lógico de pantalla (para texto centrado) */ void render(SDL_Renderer* renderer, - const Engine* engine, - const SceneManager* scene_manager, - SimulationMode current_mode, - AppMode current_app_mode, - const Shape* active_shape, - float shape_convergence, - int physical_width, - int physical_height, - int current_screen_width); + const Engine* engine, + const SceneManager* scene_manager, + SimulationMode current_mode, + AppMode current_app_mode, + const Shape* active_shape, + float shape_convergence, + int physical_width, + int physical_height, + int current_screen_width); /** * @brief Toggle del debug HUD (tecla F12) @@ -138,31 +137,31 @@ class UIManager { * @param shape_convergence % de convergencia en LOGO mode */ void renderDebugHUD(const Engine* engine, - const SceneManager* scene_manager, - SimulationMode current_mode, - AppMode current_app_mode, - const Shape* active_shape, - float shape_convergence); + const SceneManager* scene_manager, + SimulationMode current_mode, + AppMode current_app_mode, + const Shape* active_shape, + float shape_convergence); /** * @brief Convierte dirección de gravedad a string * @param direction Dirección como int (cast de GravityDirection) * @return String en español ("Abajo", "Arriba", etc.) */ - std::string gravityDirectionToString(int direction) const; + static std::string gravityDirectionToString(int direction); /** * @brief Calcula tamaño de fuente apropiado según dimensiones lógicas * @param logical_height Alto lógico (resolución interna, sin zoom) * @return Tamaño de fuente (9-72px) */ - int calculateFontSize(int logical_height) const; + static int calculateFontSize(int logical_height); // === Recursos de renderizado === - TextRenderer* text_renderer_debug_; // HUD de debug - TextRenderer* text_renderer_notifier_; // Notificaciones - Notifier* notifier_; // Sistema de notificaciones - HelpOverlay* help_overlay_; // Overlay de ayuda (tecla H) + TextRenderer* text_renderer_debug_; // HUD de debug + TextRenderer* text_renderer_notifier_; // Notificaciones + Notifier* notifier_; // Sistema de notificaciones + HelpOverlay* help_overlay_; // Overlay de ayuda (tecla H) // === Estado de UI === bool show_debug_; // HUD de debug activo (tecla F12) @@ -175,13 +174,13 @@ class UIManager { std::string vsync_text_; // Texto "V-Sync: On/Off" // === Referencias externas === - SDL_Renderer* renderer_; // Renderizador SDL3 (referencia) - ThemeManager* theme_manager_; // Gestor de temas (para colores) - int physical_window_width_; // Ancho físico de ventana (píxeles reales) - int physical_window_height_; // Alto físico de ventana (píxeles reales) - int logical_window_width_; // Ancho lógico (resolución interna) - int logical_window_height_; // Alto lógico (resolución interna) + SDL_Renderer* renderer_; // Renderizador SDL3 (referencia) + ThemeManager* theme_manager_; // Gestor de temas (para colores) + int physical_window_width_; // Ancho físico de ventana (píxeles reales) + int physical_window_height_; // Alto físico de ventana (píxeles reales) + int logical_window_width_; // Ancho lógico (resolución interna) + int logical_window_height_; // Alto lógico (resolución interna) // === Sistema de escalado dinámico de texto === - int current_font_size_; // Tamaño de fuente actual (9-72px) + int current_font_size_; // Tamaño de fuente actual (9-72px) }; diff --git a/source/utils/easing_functions.hpp b/source/utils/easing_functions.hpp index a727f36..81fe1b2 100644 --- a/source/utils/easing_functions.hpp +++ b/source/utils/easing_functions.hpp @@ -210,4 +210,4 @@ inline float easeOutCirc(float t) { return sqrtf(1.0f - powf(t - 1.0f, 2.0f)); } -} // namespace Easing +} // namespace Easing