style: aplicar fixes de clang-tidy (todo excepto uppercase-literal-suffix)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
#include "ball.hpp"
|
||||
|
||||
#include <stdlib.h> // for rand
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath> // for fabs
|
||||
#include <cstdlib> // for rand
|
||||
#include <utility>
|
||||
|
||||
#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> 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>& texture, int screen_width, int screen_height, int ball_size, GravityDirection gravity_dir, float mass_factor)
|
||||
: sprite_(std::make_unique<Sprite>(texture)),
|
||||
pos_({x, y, static_cast<float>(ball_size), static_cast<float>(ball_size)}) {
|
||||
pos_({.x = x, .y = y, .w = static_cast<float>(ball_size), .h = static_cast<float>(ball_size)}) {
|
||||
// Convertir velocidades de píxeles/frame a píxeles/segundo (multiplicar por 60)
|
||||
vx_ = vx * 60.0f;
|
||||
vy_ = vy * 60.0f;
|
||||
@@ -54,11 +55,11 @@ Ball::Ball(float x, float y, float vx, float vy, Color color, std::shared_ptr<Te
|
||||
}
|
||||
|
||||
// Actualiza la lógica de la clase
|
||||
void Ball::update(float deltaTime) {
|
||||
void Ball::update(float delta_time) { // NOLINT(readability-function-cognitive-complexity)
|
||||
// Aplica la gravedad según la dirección (píxeles/segundo²)
|
||||
if (!on_surface_) {
|
||||
// Aplicar gravedad multiplicada por factor de masa individual
|
||||
float effective_gravity = gravity_force_ * gravity_mass_factor_ * deltaTime;
|
||||
float effective_gravity = gravity_force_ * gravity_mass_factor_ * delta_time;
|
||||
switch (gravity_direction_) {
|
||||
case GravityDirection::DOWN:
|
||||
vy_ += effective_gravity;
|
||||
@@ -77,26 +78,26 @@ void Ball::update(float deltaTime) {
|
||||
|
||||
// Actualiza la posición en función de la velocidad (píxeles/segundo)
|
||||
if (!on_surface_) {
|
||||
pos_.x += vx_ * deltaTime;
|
||||
pos_.y += vy_ * deltaTime;
|
||||
pos_.x += vx_ * delta_time;
|
||||
pos_.y += vy_ * delta_time;
|
||||
} else {
|
||||
// Si está en superficie, mantener posición según dirección de gravedad
|
||||
switch (gravity_direction_) {
|
||||
case GravityDirection::DOWN:
|
||||
pos_.y = screen_height_ - pos_.h;
|
||||
pos_.x += vx_ * deltaTime; // Seguir moviéndose en X
|
||||
pos_.x += vx_ * delta_time; // Seguir moviéndose en X
|
||||
break;
|
||||
case GravityDirection::UP:
|
||||
pos_.y = 0;
|
||||
pos_.x += vx_ * deltaTime; // Seguir moviéndose en X
|
||||
pos_.x += vx_ * delta_time; // Seguir moviéndose en X
|
||||
break;
|
||||
case GravityDirection::LEFT:
|
||||
pos_.x = 0;
|
||||
pos_.y += vy_ * deltaTime; // Seguir moviéndose en Y
|
||||
pos_.y += vy_ * delta_time; // Seguir moviéndose en Y
|
||||
break;
|
||||
case GravityDirection::RIGHT:
|
||||
pos_.x = screen_width_ - pos_.w;
|
||||
pos_.y += vy_ * deltaTime; // Seguir moviéndose en Y
|
||||
pos_.y += vy_ * delta_time; // Seguir moviéndose en Y
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -176,7 +177,7 @@ void Ball::update(float deltaTime) {
|
||||
// Aplica rozamiento al estar en superficie
|
||||
if (on_surface_) {
|
||||
// Convertir rozamiento de frame-based a time-based
|
||||
float friction_factor = pow(0.97f, 60.0f * deltaTime);
|
||||
float friction_factor = std::pow(0.97f, 60.0f * delta_time);
|
||||
|
||||
switch (gravity_direction_) {
|
||||
case GravityDirection::DOWN:
|
||||
@@ -246,7 +247,7 @@ void Ball::setGravityDirection(GravityDirection direction) {
|
||||
// Aplica un pequeño empuje lateral aleatorio
|
||||
void Ball::applyRandomLateralPush() {
|
||||
// Generar velocidad lateral aleatoria (nunca 0)
|
||||
float lateral_speed = GRAVITY_CHANGE_LATERAL_MIN + (rand() % 1000) / 1000.0f * (GRAVITY_CHANGE_LATERAL_MAX - GRAVITY_CHANGE_LATERAL_MIN);
|
||||
float lateral_speed = GRAVITY_CHANGE_LATERAL_MIN + ((rand() % 1000) / 1000.0f * (GRAVITY_CHANGE_LATERAL_MAX - GRAVITY_CHANGE_LATERAL_MIN));
|
||||
|
||||
// Signo aleatorio (+ o -)
|
||||
int sign = ((rand() % 2) * 2) - 1;
|
||||
@@ -304,18 +305,18 @@ void Ball::enableShapeAttraction(bool enable) {
|
||||
}
|
||||
|
||||
// Obtener distancia actual al punto objetivo (para calcular convergencia)
|
||||
float Ball::getDistanceToTarget() const {
|
||||
auto Ball::getDistanceToTarget() const -> 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<float>(pos_.x, 0);
|
||||
if (pos_.x + pos_.w > screen_width_) {
|
||||
pos_.x = screen_width_ - pos_.w;
|
||||
}
|
||||
pos_.y = std::max<float>(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> texture) {
|
||||
// Actualizar textura del sprite
|
||||
sprite_->setTexture(texture);
|
||||
sprite_->setTexture(std::move(texture));
|
||||
}
|
||||
@@ -31,13 +31,13 @@ class Ball {
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Ball(float x, float y, float vx, float vy, Color color, std::shared_ptr<Texture> 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>& 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_; }
|
||||
@@ -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);
|
||||
};
|
||||
@@ -10,32 +10,31 @@
|
||||
#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_) {
|
||||
|
||||
@@ -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)
|
||||
@@ -121,6 +120,6 @@ class BoidManager {
|
||||
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
|
||||
};
|
||||
|
||||
@@ -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<int>(std::ceil(world_width / cell_size));
|
||||
grid_rows_ = static_cast<int>(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<Ball*> SpatialGrid::queryRadius(float x, float y, float radius) {
|
||||
auto SpatialGrid::queryRadius(float x, float y, float radius) -> std::vector<Ball*> {
|
||||
std::vector<Ball*> 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<int>(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;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <SDL3/SDL_timer.h> // for SDL_GetTicks
|
||||
#include <SDL3/SDL_video.h> // for SDL_CreateWindow, SDL_DestroyWindow, SDL_GetDisplayBounds
|
||||
|
||||
#include <array> // for std::array
|
||||
#include <algorithm> // for std::min, std::max, std::sort
|
||||
#include <cmath> // for sqrtf, acosf, cosf, sinf (funciones matemáticas)
|
||||
#include <cstdlib> // for rand, srand
|
||||
@@ -29,23 +30,25 @@
|
||||
#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;
|
||||
@@ -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<GpuContext>();
|
||||
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<GpuTexture>();
|
||||
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<GpuPipeline>();
|
||||
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<GpuSpriteBatch>();
|
||||
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<GpuBallBuffer>();
|
||||
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<GpuTexture>();
|
||||
if (!offscreen_tex_->createRenderTarget(gpu_ctx_->device(),
|
||||
current_screen_width_, current_screen_height_,
|
||||
current_screen_width_,
|
||||
current_screen_height_,
|
||||
offscreen_fmt)) {
|
||||
std::cerr << "ERROR: No se pudo crear render target offscreen" << std::endl;
|
||||
std::cerr << "ERROR: No se pudo crear render target offscreen" << '\n';
|
||||
success = false;
|
||||
}
|
||||
|
||||
white_tex_ = std::make_unique<GpuTexture>();
|
||||
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<GpuTexture>();
|
||||
if (!ui_tex_->createRenderTarget(gpu_ctx_->device(),
|
||||
logical_width, logical_height,
|
||||
logical_width,
|
||||
logical_height,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM)) {
|
||||
std::cerr << "Advertencia: no se pudo crear textura UI GPU" << std::endl;
|
||||
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<UIManager>();
|
||||
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<ShapeManager>();
|
||||
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<StateManager>();
|
||||
@@ -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<BoidManager>();
|
||||
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<AppLogo>();
|
||||
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<int>(shape_manager_->getCurrentShapeType())]);
|
||||
constexpr std::array<const char*, 10> SHAPE_NAMES = {"Ninguna", "Esfera", "Cubo", "Hélice", "Toroide", "Lissajous", "Cilindro", "Icosaedro", "Átomo", "Forma PNG"};
|
||||
showNotificationForAction(SHAPE_NAMES[static_cast<int>(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,8 +686,11 @@ 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;
|
||||
}
|
||||
@@ -665,9 +700,10 @@ void Engine::setCustomScenario(int 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)
|
||||
void Engine::changeScenario(int scenario_id, const char* notification_text) {
|
||||
@@ -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<float>(current_screen_width_), static_cast<float>(current_screen_height_),
|
||||
top_r, top_g, top_b, bot_r, bot_g, bot_b);
|
||||
static_cast<float>(current_screen_width_),
|
||||
static_cast<float>(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<float>(current_screen_width_);
|
||||
const float sh = static_cast<float>(current_screen_height_);
|
||||
const auto SW = static_cast<float>(current_screen_width_);
|
||||
const auto SH = static_cast<float>(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,26 +865,26 @@ 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<int>(ball_gpu_data_.size()));
|
||||
gpu_ball_buffer_->upload(gpu_ctx_->device(), cmd, ball_gpu_data_.data(), static_cast<int>(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.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,30 +920,35 @@ 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<float>(sw_w);
|
||||
vp_h = static_cast<float>(sw_h);
|
||||
} else if (current_scaling_mode_ == ScalingMode::INTEGER) {
|
||||
int scale = static_cast<int>(std::min(sw_w / static_cast<Uint32>(base_screen_width_),
|
||||
sw_h / static_cast<Uint32>(base_screen_height_)));
|
||||
if (scale < 1) scale = 1;
|
||||
scale = std::max(scale, 1);
|
||||
vp_w = static_cast<float>(base_screen_width_ * scale);
|
||||
vp_h = static_cast<float>(base_screen_height_ * scale);
|
||||
vp_x = (static_cast<float>(sw_w) - vp_w) * 0.5f;
|
||||
@@ -913,8 +963,7 @@ void Engine::render() {
|
||||
}
|
||||
SDL_GPUViewport vp = {vp_x, vp_y, vp_w, vp_h, 0.0f, 1.0f};
|
||||
SDL_SetGPUViewport(rp, &vp);
|
||||
SDL_Rect scissor = {static_cast<int>(vp_x), static_cast<int>(vp_y),
|
||||
static_cast<int>(vp_w), static_cast<int>(vp_h)};
|
||||
SDL_Rect scissor = {static_cast<int>(vp_x), static_cast<int>(vp_y), static_cast<int>(vp_w), static_cast<int>(vp_h)};
|
||||
SDL_SetGPUScissor(rp, &scissor);
|
||||
};
|
||||
|
||||
@@ -923,11 +972,11 @@ void Engine::render() {
|
||||
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.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());
|
||||
@@ -945,15 +994,14 @@ void Engine::render() {
|
||||
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_DrawGPUIndexedPrimitives(pass2, sprite_batch_->overlayIndexCount(), 1, sprite_batch_->overlayIndexOffset(), 0, 0);
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -1092,18 +1142,22 @@ void Engine::toggleRealFullscreen() {
|
||||
}
|
||||
|
||||
void Engine::applyPostFXPreset(int mode) {
|
||||
static constexpr float presets[4][3] = {
|
||||
static constexpr std::array<std::array<float, 3>, 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,14 +1165,15 @@ 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<const char*, 4> 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;
|
||||
@@ -1138,23 +1193,30 @@ void Engine::setPostFXParamOverrides(float vignette, float 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<const char*, 4> NAMES = {
|
||||
"PostFX viñeta",
|
||||
"PostFX scanlines",
|
||||
"PostFX cromática",
|
||||
"PostFX completo"};
|
||||
showNotificationForAction(NAMES[postfx_effect_mode_]);
|
||||
}
|
||||
|
||||
void Engine::toggleIntegerScaling() {
|
||||
@@ -1171,44 +1233,51 @@ 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<float>(current_screen_width_),
|
||||
static_cast<float>(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<float>(current_screen_width_), static_cast<float>(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
|
||||
}
|
||||
float max_by_w = static_cast<float>(bounds.w - 2 * WINDOW_DESKTOP_MARGIN) / base_screen_width_;
|
||||
float max_by_h = static_cast<float>(bounds.h - 2 * WINDOW_DESKTOP_MARGIN - WINDOW_DECORATION_HEIGHT) / base_screen_height_;
|
||||
float max_by_w = static_cast<float>(bounds.w - (2 * WINDOW_DESKTOP_MARGIN)) / base_screen_width_;
|
||||
float max_by_h = static_cast<float>(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)) {
|
||||
@@ -1227,7 +1296,9 @@ 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<int>(std::round(current_screen_width_ * new_scale));
|
||||
int new_height = static_cast<int>(std::round(current_screen_height_ * new_scale));
|
||||
@@ -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<char, 32> 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<char, 32> buf{};
|
||||
std::snprintf(buf.data(), buf.size(), "Zoom %.0f%%", current_window_scale_ * 100.0f);
|
||||
showNotificationForAction(buf.data());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1262,7 +1333,9 @@ 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<int>(std::round(base_screen_width_ * new_scale));
|
||||
@@ -1282,7 +1355,9 @@ 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);
|
||||
@@ -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_,
|
||||
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_,
|
||||
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<Uint32>(w * h * 4); // RGBA = 4 bytes/pixel
|
||||
auto data_size = static_cast<Uint32>(w * h * 4); // RGBA = 4 bytes/pixel
|
||||
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <string> // for string
|
||||
#include <vector> // 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
|
||||
@@ -26,6 +25,7 @@
|
||||
#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 {
|
||||
@@ -277,5 +277,4 @@ class Engine {
|
||||
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_
|
||||
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#include "gpu_ball_buffer.hpp"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include <algorithm> // std::min
|
||||
#include <cstring> // memcpy
|
||||
|
||||
bool GpuBallBuffer::init(SDL_GPUDevice* device) {
|
||||
auto GpuBallBuffer::init(SDL_GPUDevice* device) -> bool {
|
||||
Uint32 buf_size = static_cast<Uint32>(MAX_BALLS) * sizeof(BallGPUData);
|
||||
|
||||
// GPU vertex buffer (instance-rate data read by the ball instanced shader)
|
||||
@@ -12,7 +13,7 @@ bool GpuBallBuffer::init(SDL_GPUDevice* device) {
|
||||
buf_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||
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;
|
||||
}
|
||||
@@ -22,32 +23,43 @@ bool GpuBallBuffer::init(SDL_GPUDevice* device) {
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
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<Uint32>(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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -34,8 +35,7 @@ public:
|
||||
|
||||
// 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);
|
||||
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_; }
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#include "gpu_context.hpp"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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<int>(swapchain_format_) << std::endl;
|
||||
std::cout << "GpuContext: swapchain format = " << static_cast<int>(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);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,9 @@ public:
|
||||
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);
|
||||
Uint32* out_w,
|
||||
Uint32* out_h);
|
||||
static void submit(SDL_GPUCommandBuffer* cmd_buf);
|
||||
|
||||
// VSync control (call after init)
|
||||
bool setVSync(bool enabled);
|
||||
|
||||
@@ -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 <SDL3/SDL_log.h>
|
||||
|
||||
#include <array> // for std::array
|
||||
#include <cstddef> // offsetof
|
||||
#include <cstring> // 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__
|
||||
@@ -204,9 +207,9 @@ vertex BallVOut ball_instanced_vs(BallInstance inst [[stage_in]],
|
||||
// GpuPipeline implementation
|
||||
// ============================================================================
|
||||
|
||||
bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
auto GpuPipeline::init(SDL_GPUDevice* device,
|
||||
SDL_GPUTextureFormat target_format,
|
||||
SDL_GPUTextureFormat offscreen_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,20 +227,20 @@ 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;
|
||||
}
|
||||
|
||||
@@ -248,7 +251,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||
vb_desc.instance_step_rate = 0;
|
||||
|
||||
SDL_GPUVertexAttribute attrs[3] = {};
|
||||
std::array<SDL_GPUVertexAttribute, 3> attrs = {};
|
||||
attrs[0].location = 0;
|
||||
attrs[0].buffer_slot = 0;
|
||||
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
@@ -267,7 +270,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
SDL_GPUVertexInputState vertex_input = {};
|
||||
vertex_input.vertex_buffer_descriptions = &vb_desc;
|
||||
vertex_input.num_vertex_buffers = 1;
|
||||
vertex_input.vertex_attributes = attrs;
|
||||
vertex_input.vertex_attributes = attrs.data();
|
||||
vertex_input.num_vertex_attributes = 3;
|
||||
|
||||
// Alpha blend state (SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
|
||||
@@ -298,7 +301,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
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,20 +313,20 @@ 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;
|
||||
}
|
||||
|
||||
@@ -334,7 +337,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
ball_vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE;
|
||||
ball_vb_desc.instance_step_rate = 1;
|
||||
|
||||
SDL_GPUVertexAttribute ball_attrs[3] = {};
|
||||
std::array<SDL_GPUVertexAttribute, 3> ball_attrs = {};
|
||||
// attr 0: center (float2) at offset 0
|
||||
ball_attrs[0].location = 0;
|
||||
ball_attrs[0].buffer_slot = 0;
|
||||
@@ -354,7 +357,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
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.vertex_attributes = ball_attrs.data();
|
||||
ball_vertex_input.num_vertex_attributes = 3;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo 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;
|
||||
}
|
||||
|
||||
@@ -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,19 +443,28 @@ 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,
|
||||
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) {
|
||||
Uint32 num_storage_buffers) -> SDL_GPUShader* {
|
||||
SDL_GPUShaderCreateInfo info = {};
|
||||
info.code = spv_code;
|
||||
info.code_size = spv_size;
|
||||
@@ -464,18 +476,19 @@ SDL_GPUShader* GpuPipeline::createShaderSPIRV(SDL_GPUDevice* device,
|
||||
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,
|
||||
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) {
|
||||
Uint32 num_storage_buffers) -> SDL_GPUShader* {
|
||||
SDL_GPUShaderCreateInfo info = {};
|
||||
info.code = reinterpret_cast<const Uint8*>(msl_source);
|
||||
info.code_size = static_cast<size_t>(strlen(msl_source) + 1);
|
||||
@@ -488,7 +501,7 @@ SDL_GPUShader* GpuPipeline::createShader(SDL_GPUDevice* device,
|
||||
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;
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
SDL_GPUGraphicsPipeline* postfxPipeline() const { return postfx_pipeline_; }
|
||||
|
||||
private:
|
||||
SDL_GPUShader* createShader(SDL_GPUDevice* device,
|
||||
static SDL_GPUShader* createShader(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
@@ -48,7 +48,7 @@ private:
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers = 0);
|
||||
|
||||
SDL_GPUShader* createShaderSPIRV(SDL_GPUDevice* device,
|
||||
static SDL_GPUShader* createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "gpu_sprite_batch.hpp"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include <cstring> // 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.
|
||||
@@ -22,7 +23,7 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
||||
vb_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||
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;
|
||||
}
|
||||
@@ -32,7 +33,7 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
||||
ib_info.usage = SDL_GPU_BUFFERUSAGE_INDEX;
|
||||
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;
|
||||
}
|
||||
@@ -43,14 +44,14 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
||||
|
||||
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;
|
||||
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,11 +62,25 @@ 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() {
|
||||
@@ -78,14 +93,12 @@ void GpuSpriteBatch::beginFrame() {
|
||||
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<uint32_t>(vertices_.size());
|
||||
auto vi = static_cast<uint32_t>(vertices_.size());
|
||||
|
||||
// Top-left
|
||||
vertices_.push_back({-1.0f, 1.0f, 0.0f, 0.0f, top_r, top_g, top_b, 1.0f});
|
||||
@@ -97,19 +110,21 @@ void GpuSpriteBatch::addBackground(float screen_w, float screen_h,
|
||||
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;
|
||||
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;
|
||||
@@ -121,7 +136,10 @@ void GpuSpriteBatch::addSprite(float x, float y, float w, float h,
|
||||
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,30 +151,42 @@ 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<int>(indices_.size());
|
||||
uint32_t vi = static_cast<uint32_t>(vertices_.size());
|
||||
auto vi = static_cast<uint32_t>(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);
|
||||
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<Uint32>(vertices_.size() * sizeof(GpuVertex));
|
||||
Uint32 ib_size = static_cast<Uint32>(indices_.size() * sizeof(uint32_t));
|
||||
auto vb_size = static_cast<Uint32>(vertices_.size() * sizeof(GpuVertex));
|
||||
auto ib_size = static_cast<Uint32>(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_);
|
||||
|
||||
@@ -179,19 +209,17 @@ 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<size_t>(max_sprites_ + 1) * 4) return;
|
||||
uint32_t vi = static_cast<uint32_t>(vertices_.size());
|
||||
if (vertices_.size() + 4 > static_cast<size_t>(max_sprites_ + 1) * 4) {
|
||||
return;
|
||||
}
|
||||
auto vi = static_cast<uint32_t>(vertices_.size());
|
||||
|
||||
// TL, TR, BR, BL
|
||||
vertices_.push_back({ndx0, ndy0, u0, v0, r, g, b, a});
|
||||
@@ -199,6 +227,10 @@ void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1,
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GpuVertex — 8-float vertex layout sent to the GPU.
|
||||
@@ -36,16 +37,11 @@ public:
|
||||
|
||||
// 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);
|
||||
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);
|
||||
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();
|
||||
@@ -64,11 +60,8 @@ public:
|
||||
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);
|
||||
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<GpuVertex> vertices_;
|
||||
std::vector<uint32_t> indices_;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
#include <SDL3/SDL_pixels.h>
|
||||
|
||||
#include <array> // for std::array
|
||||
#include <cstring> // memcpy
|
||||
#include <string>
|
||||
|
||||
@@ -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<int>(resource_size),
|
||||
&w, &h, &orig, STBI_rgb_alpha);
|
||||
resource_data,
|
||||
static_cast<int>(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,23 +71,24 @@ 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.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
info.width = static_cast<Uint32>(w);
|
||||
info.height = static_cast<Uint32>(h);
|
||||
info.layer_count_or_depth = 1;
|
||||
@@ -84,7 +96,7 @@ bool GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h,
|
||||
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;
|
||||
}
|
||||
@@ -95,20 +107,29 @@ bool GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h,
|
||||
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<Uint8, 4> 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,8 +137,7 @@ 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;
|
||||
@@ -130,20 +150,20 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
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<Uint32>(w * h * 4); // RGBA = 4 bytes/pixel
|
||||
auto data_size = static_cast<Uint32>(w * h * 4); // RGBA = 4 bytes/pixel
|
||||
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
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_);
|
||||
@@ -190,7 +210,7 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
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;
|
||||
@@ -200,7 +220,7 @@ bool GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
#include <SDL3/SDL_surface.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
// ============================================================================
|
||||
@@ -21,8 +22,7 @@ public:
|
||||
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);
|
||||
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);
|
||||
@@ -37,8 +37,7 @@ public:
|
||||
bool isValid() const { return texture_ != nullptr; }
|
||||
|
||||
private:
|
||||
bool uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
int w, int h, SDL_GPUTextureFormat format);
|
||||
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;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "input_handler.hpp"
|
||||
|
||||
#include <SDL3/SDL_keycode.h> // for SDL_Keycode
|
||||
|
||||
#include <string> // 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<int>(event.key.repeat) == 0) {
|
||||
switch (event.key.key) {
|
||||
case SDLK_ESCAPE:
|
||||
if (engine.isKioskMode()) {
|
||||
@@ -109,19 +110,17 @@ bool InputHandler::processEvents(Engine& engine) {
|
||||
break;
|
||||
|
||||
// Ciclar temas de color (movido de B a C)
|
||||
case SDLK_C:
|
||||
{
|
||||
case SDLK_C: {
|
||||
// Detectar si Shift está presionado
|
||||
SDL_Keymod modstate = SDL_GetModState();
|
||||
if (modstate & SDL_KMOD_SHIFT) {
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
#include "resource_manager.hpp"
|
||||
#include "resource_pack.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "resource_pack.hpp"
|
||||
|
||||
// Inicializar estáticos
|
||||
ResourcePack* ResourceManager::resourcePack_ = nullptr;
|
||||
std::map<std::string, std::vector<unsigned char>> 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<unsigned char>(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<unsigned char>(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<size_t>(file.tellg());
|
||||
@@ -76,22 +77,22 @@ bool ResourceManager::loadResource(const std::string& resourcePath, unsigned cha
|
||||
file.close();
|
||||
|
||||
// Guardar en caché
|
||||
cache_[resourcePath] = std::vector<unsigned char>(data, data + size);
|
||||
cache_[resource_path] = std::vector<unsigned char>(data, data + size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceManager::isPackLoaded() {
|
||||
auto ResourceManager::isPackLoaded() -> bool {
|
||||
return resourcePack_ != nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::string> ResourceManager::getResourceList() {
|
||||
auto ResourceManager::getResourceList() -> std::vector<std::string> {
|
||||
if (resourcePack_ != nullptr) {
|
||||
return resourcePack_->getResourceList();
|
||||
}
|
||||
return std::vector<std::string>(); // 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();
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
* @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);
|
||||
static bool init(const std::string& pack_file_path);
|
||||
|
||||
/**
|
||||
* Libera el sistema de recursos
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
* @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);
|
||||
static bool loadResource(const std::string& resource_path, unsigned char*& data, size_t& size);
|
||||
|
||||
/**
|
||||
* Verifica si el pack está cargado
|
||||
|
||||
@@ -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<unsigned char> buffer(fileSize);
|
||||
file.read(reinterpret_cast<char*>(buffer.data()), fileSize);
|
||||
std::vector<unsigned char> buffer(file_size);
|
||||
file.read(reinterpret_cast<char*>(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<uint32_t>(fileSize);
|
||||
resource.checksum = calculateChecksum(buffer.data(), fileSize);
|
||||
resource.size = static_cast<uint32_t>(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<uint32_t>(resources_.size());
|
||||
packFile.write(reinterpret_cast<const char*>(&header), sizeof(PackHeader));
|
||||
pack_file.write(reinterpret_cast<const char*>(&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<uint32_t>(path.size()); // path
|
||||
currentOffset += sizeof(uint32_t) * 3; // offset, size, checksum
|
||||
current_offset += sizeof(uint32_t); // pathLen
|
||||
current_offset += static_cast<uint32_t>(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<uint32_t>(path.size());
|
||||
packFile.write(reinterpret_cast<const char*>(&pathLen), sizeof(uint32_t));
|
||||
packFile.write(path.c_str(), pathLen);
|
||||
packFile.write(reinterpret_cast<const char*>(&entry.offset), sizeof(uint32_t));
|
||||
packFile.write(reinterpret_cast<const char*>(&entry.size), sizeof(uint32_t));
|
||||
packFile.write(reinterpret_cast<const char*>(&entry.checksum), sizeof(uint32_t));
|
||||
auto path_len = static_cast<uint32_t>(path.size());
|
||||
pack_file.write(reinterpret_cast<const char*>(&path_len), sizeof(uint32_t));
|
||||
pack_file.write(path.c_str(), path_len);
|
||||
pack_file.write(reinterpret_cast<const char*>(&entry.offset), sizeof(uint32_t));
|
||||
pack_file.write(reinterpret_cast<const char*>(&entry.size), sizeof(uint32_t));
|
||||
pack_file.write(reinterpret_cast<const char*>(&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<char*>(buffer.data()), entry.size);
|
||||
file.close();
|
||||
|
||||
packFile.write(reinterpret_cast<const char*>(buffer.data()), entry.size);
|
||||
pack_file.write(reinterpret_cast<const char*>(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<char*>(&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<char*>(&pathLen), sizeof(uint32_t));
|
||||
uint32_t path_len;
|
||||
packFile_.read(reinterpret_cast<char*>(&path_len), sizeof(uint32_t));
|
||||
|
||||
std::vector<char> pathBuffer(pathLen + 1, '\0');
|
||||
packFile_.read(pathBuffer.data(), pathLen);
|
||||
entry.path = std::string(pathBuffer.data());
|
||||
std::vector<char> path_buffer(path_len + 1, '\0');
|
||||
packFile_.read(path_buffer.data(), path_len);
|
||||
entry.path = std::string(path_buffer.data());
|
||||
|
||||
packFile_.read(reinterpret_cast<char*>(&entry.offset), sizeof(uint32_t));
|
||||
packFile_.read(reinterpret_cast<char*>(&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<std::string> ResourcePack::getResourceList() const {
|
||||
auto ResourcePack::getResourceList() const -> std::vector<std::string> {
|
||||
std::vector<std::string> 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<uint32_t>(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/");
|
||||
|
||||
@@ -18,18 +18,18 @@ public:
|
||||
~ResourcePack();
|
||||
|
||||
// Empaquetado (usado por herramienta pack_resources)
|
||||
bool addDirectory(const std::string& dirPath, const std::string& prefix = "");
|
||||
bool savePack(const std::string& packFilePath);
|
||||
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);
|
||||
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);
|
||||
ResourceData loadResource(const std::string& resource_path);
|
||||
|
||||
// Utilidades
|
||||
std::vector<std::string> getResourceList() const;
|
||||
@@ -58,6 +58,6 @@ private:
|
||||
bool isLoaded_;
|
||||
|
||||
// Funciones auxiliares
|
||||
uint32_t calculateChecksum(const unsigned char* data, size_t size);
|
||||
std::string normalizePath(const std::string& path);
|
||||
static uint32_t calculateChecksum(const unsigned char* data, size_t size);
|
||||
static std::string normalizePath(const std::string& path);
|
||||
};
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
#include "scene_manager.hpp"
|
||||
|
||||
#include <cstdlib> // for rand
|
||||
#include <utility>
|
||||
|
||||
#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> 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<int>(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<int>(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<float>(rand() % screen_width_);
|
||||
Y = static_cast<float>(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<float>(rand() % screen_width_);
|
||||
y = static_cast<float>(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<int>(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<int>(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<Ball>(
|
||||
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<Texture> 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;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "atom_shape.hpp"
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#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;
|
||||
@@ -26,13 +29,13 @@ void AtomShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
||||
|
||||
// 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;
|
||||
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<float>(index) / static_cast<float>(nucleus_points);
|
||||
float phi = acosf(1.0f - 2.0f * t);
|
||||
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);
|
||||
@@ -51,10 +54,12 @@ 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "cube_shape.hpp"
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#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<int>(ceilf(cbrtf(static_cast<float>(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);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "shape.hpp"
|
||||
#include <vector>
|
||||
|
||||
#include "shape.hpp"
|
||||
|
||||
// Figura: Cubo 3D rotante
|
||||
// Distribución:
|
||||
// - 1-8 pelotas: Solo vértices (8 puntos)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#include "cylinder_shape.hpp"
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib> // 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<int>(sqrtf(static_cast<float>(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<float>(point_in_ring) / static_cast<float>(points_per_ring)) * 2.0f * PI;
|
||||
|
||||
// Parámetro v (altura normalizada): [-1, 1]
|
||||
float v = (static_cast<float>(ring) / static_cast<float>(num_rings - 1)) * 2.0f - 1.0f;
|
||||
if (num_rings == 1) v = 0.0f;
|
||||
float v = ((static_cast<float>(ring) / static_cast<float>(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;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "helix_shape.hpp"
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#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;
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#include "icosahedron_shape.hpp"
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#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<std::array<float, 3>, 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<std::array<int, 3>, 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<float>(point_in_face) / static_cast<float>(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;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "lissajous_shape.hpp"
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#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;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
#include "png_shape.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
#include "defines.hpp"
|
||||
#include "external/stb_image.h"
|
||||
#include "resource_manager.hpp"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
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<int>(file_size),
|
||||
&width, &height, &channels, 1);
|
||||
int width;
|
||||
int height;
|
||||
int channels;
|
||||
unsigned char* pixels = stbi_load_from_memory(file_data, static_cast<int>(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;
|
||||
@@ -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<float>(x), static_cast<float>(y)});
|
||||
}
|
||||
@@ -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<Point2D> 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<Point2D> 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::Point2D> PNGShape::extractAlternateRows(const std::vector<Point2D>& source, int row_skip) {
|
||||
auto PNGShape::extractAlternateRows(const std::vector<Point2D>& source, int row_skip) -> std::vector<PNGShape::Point2D> {
|
||||
std::vector<Point2D> result;
|
||||
|
||||
if (row_skip <= 1 || source.empty()) {
|
||||
@@ -243,7 +246,7 @@ std::vector<PNGShape::Point2D> PNGShape::extractAlternateRows(const std::vector<
|
||||
}
|
||||
|
||||
// Extraer vértices y esquinas (FUNCIÓN PURA: devuelve nuevo vector)
|
||||
std::vector<PNGShape::Point2D> PNGShape::extractCornerVertices(const std::vector<Point2D>& source) {
|
||||
auto PNGShape::extractCornerVertices(const std::vector<Point2D>& source) -> std::vector<PNGShape::Point2D> {
|
||||
std::vector<Point2D> result;
|
||||
|
||||
if (source.empty()) {
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "shape.hpp"
|
||||
#include "defines.hpp" // Para PNG_IDLE_TIME_MIN/MAX constantes
|
||||
#include <vector>
|
||||
#include <cstdlib> // Para rand()
|
||||
#include <vector>
|
||||
|
||||
#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)
|
||||
@@ -59,14 +60,14 @@ private:
|
||||
int num_points_ = 0; // Total de puntos generados (para indexación)
|
||||
|
||||
// Métodos internos
|
||||
bool loadPNG(const char* path); // Cargar PNG con stb_image
|
||||
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<Point2D> extractAlternateRows(const std::vector<Point2D>& source, int row_skip); // Extraer filas alternas
|
||||
std::vector<Point2D> extractCornerVertices(const std::vector<Point2D>& source); // Extraer vértices/esquinas
|
||||
static std::vector<Point2D> extractAlternateRows(const std::vector<Point2D>& source, int row_skip); // Extraer filas alternas
|
||||
static std::vector<Point2D> extractCornerVertices(const std::vector<Point2D>& source); // Extraer vértices/esquinas
|
||||
|
||||
public:
|
||||
// Constructor: recibe path relativo al PNG
|
||||
@@ -88,7 +89,10 @@ public:
|
||||
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; }
|
||||
void resetFlipCount() {
|
||||
flip_count_ = 0;
|
||||
was_flipping_last_frame_ = false;
|
||||
}
|
||||
|
||||
// Control de modo LOGO (flip intervals más largos)
|
||||
void setLogoMode(bool enable) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "sphere_shape.hpp"
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#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<float>(index) / static_cast<float>(num_points_);
|
||||
float phi = acosf(1.0f - 2.0f * t); // Latitud
|
||||
float theta = angle_increment * static_cast<float>(index); // Longitud
|
||||
float phi = acosf(1.0f - (2.0f * t)); // Latitud
|
||||
float theta = ANGLE_INCREMENT * static_cast<float>(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;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "torus_shape.hpp"
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#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<int>(sqrtf(static_cast<float>(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;
|
||||
|
||||
@@ -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<PNGShape*>(active_shape_.get());
|
||||
if (png_shape) {
|
||||
auto* png_shape = dynamic_cast<PNGShape*>(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<int>(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<int>((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<float>(screen_width_), static_cast<float>(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<int>(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<int>(scene_mgr_->getBallCount());
|
||||
active_shape_->generatePoints(num_points, static_cast<float>(screen_width_), static_cast<float>(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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,39 +1,37 @@
|
||||
#include "state_manager.hpp"
|
||||
|
||||
#include <algorithm> // for std::min
|
||||
#include <array> // for std::array
|
||||
#include <cstdlib> // for rand
|
||||
#include <vector> // 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 "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<PNGShape*>(active_shape);
|
||||
auto* png_shape = dynamic_cast<PNGShape*>(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
|
||||
@@ -122,8 +122,8 @@ 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<PNGShape*>(shape_mgr_->getActiveShape());
|
||||
if (png_shape) {
|
||||
auto* png_shape = dynamic_cast<PNGShape*>(shape_mgr_->getActiveShape());
|
||||
if (png_shape != nullptr) {
|
||||
png_shape->resetFlipCount();
|
||||
}
|
||||
// No hacer nada más — esperar a que ocurran los flips
|
||||
@@ -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<GravityDirection>(rand() % 4);
|
||||
auto new_direction = static_cast<GravityDirection>(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<GravityDirection>(rand() % 4);
|
||||
auto new_direction = static_cast<GravityDirection>(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<ShapeType, 8> 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<GravityDirection>(rand() % 4);
|
||||
auto new_direction = static_cast<GravityDirection>(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<ShapeType, 8> 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<int> 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<ShapeType, 8> 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<ShapeType, 8> 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<GravityDirection>(rand() % 4);
|
||||
auto new_direction = static_cast<GravityDirection>(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<ShapeType, 8> 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<int> 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<GravityDirection>(rand() % 4);
|
||||
auto new_direction = static_cast<GravityDirection>(rand() % 4);
|
||||
scene_mgr_->changeGravityDirection(new_direction);
|
||||
if (rand() % 3 == 0) {
|
||||
toggleGravityOnOff();
|
||||
@@ -526,7 +535,9 @@ 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);
|
||||
@@ -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<int, 4> 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<PNGShape*>(shape_mgr_->getActiveShape());
|
||||
if (png_shape) {
|
||||
auto* png_shape = dynamic_cast<PNGShape*>(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<PNGShape*>(shape_mgr_->getActiveShape());
|
||||
if (png_shape) {
|
||||
auto* png_shape = dynamic_cast<PNGShape*>(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<ShapeType, 8> 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) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_stdinc.h> // for Uint64
|
||||
|
||||
#include <cstddef> // 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
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
#include "textrenderer.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#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<size_t>(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<size_t>(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<float>(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<float>(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;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
class TextRenderer {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,8 +54,7 @@ class ThemeManager {
|
||||
|
||||
// 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
|
||||
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_; }
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
#include "dynamic_theme.hpp"
|
||||
|
||||
#include <algorithm> // for std::min
|
||||
|
||||
DynamicTheme::DynamicTheme(const char* name_en, const char* name_es,
|
||||
int text_r, int text_g, int text_b,
|
||||
std::vector<DynamicThemeKeyframe> keyframes,
|
||||
bool loop)
|
||||
DynamicTheme::DynamicTheme(const char* name_en, const char* name_es, int text_r, int text_g, int text_b, std::vector<DynamicThemeKeyframe> 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<int>(lerp(c1.r, c2.r, t)),
|
||||
static_cast<int>(lerp(c1.g, c2.g, t)),
|
||||
static_cast<int>(lerp(c1.b, c2.b, t))
|
||||
};
|
||||
.r = static_cast<int>(lerp(c1.r, c2.r, t)),
|
||||
.g = static_cast<int>(lerp(c1.g, c2.g, t)),
|
||||
.b = static_cast<int>(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_];
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "theme.hpp"
|
||||
#include <string>
|
||||
|
||||
#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<DynamicThemeKeyframe> keyframes,
|
||||
bool loop = true);
|
||||
DynamicTheme(const char* name_en, const char* name_es, int text_r, int text_g, int text_b, std::vector<DynamicThemeKeyframe> 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)
|
||||
|
||||
@@ -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<Color> 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<Color> 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_;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "theme.hpp"
|
||||
#include <string>
|
||||
|
||||
#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<Color> 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<Color> 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)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#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)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "defines.hpp" // for Color
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
#include "app_logo.hpp"
|
||||
|
||||
#include <SDL3/SDL_render.h> // for SDL_DestroyTexture, SDL_RenderGeometry, SDL_SetTextureAlphaMod
|
||||
|
||||
#include <array> // for std::array
|
||||
#include <cmath> // for powf, sinf, cosf
|
||||
#include <cstdlib> // for free()
|
||||
#include <iostream> // for std::cout
|
||||
#include <numbers>
|
||||
|
||||
#include "logo_scaler.hpp" // for LogoScaler
|
||||
#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,8 +281,7 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
logo2_rotation_ = 0.0f;
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ELASTIC_STICK:
|
||||
{
|
||||
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);
|
||||
@@ -279,11 +297,9 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
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;
|
||||
} break;
|
||||
|
||||
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||
{
|
||||
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);
|
||||
@@ -297,11 +313,9 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
logo2_rotation_ = (1.0f - prog2) * 6.28f;
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||
{
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH: {
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
float bounce_t1 = easeOutBounce(prog1);
|
||||
logo1_scale_ = 1.0f;
|
||||
@@ -317,8 +331,7 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
logo2_squash_y_ = 1.0f - squash_amount2;
|
||||
logo2_stretch_x_ = 1.0f + squash_amount2 * 0.5f;
|
||||
logo2_rotation_ = 0.0f;
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -379,8 +392,7 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
logo2_rotation_ = 0.0f;
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ELASTIC_STICK:
|
||||
{
|
||||
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);
|
||||
@@ -392,11 +404,9 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
logo2_squash_y_ = 1.0f + (prog2 * 0.3f);
|
||||
logo2_stretch_x_ = 1.0f - (prog2 * 0.2f);
|
||||
logo2_rotation_ = prog2 * 0.1f;
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
|
||||
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||
{
|
||||
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);
|
||||
@@ -410,11 +420,9 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
logo2_rotation_ = prog2 * 6.28f;
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||
{
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH: {
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
if (prog1 < 0.2f) {
|
||||
float squash_t = prog1 / 0.2f;
|
||||
@@ -440,8 +448,7 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
}
|
||||
logo2_scale_ = 1.0f + (prog2 * 0.3f);
|
||||
logo2_rotation_ = 0.0f;
|
||||
}
|
||||
break;
|
||||
} 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<float>) / 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;
|
||||
if (t == 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
if (t == 1.0f) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float AppLogo::easeOutBack(float t) {
|
||||
return (powf(2.0f, -10.0f * t) * sinf((t * 10.0f - 0.75f) * C4)) + 1.0f;
|
||||
}
|
||||
|
||||
auto AppLogo::easeOutBack(float t) -> float {
|
||||
// Back easing out: overshoot suave al final
|
||||
const float c1 = 1.70158f;
|
||||
const float c3 = c1 + 1.0f;
|
||||
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);
|
||||
return 1.0f + (C3 * powf(t - 1.0f, 3.0f)) + (C1 * powf(t - 1.0f, 2.0f));
|
||||
}
|
||||
|
||||
float AppLogo::easeOutBounce(float t) {
|
||||
auto AppLogo::easeOutBounce(float t) -> float {
|
||||
// Bounce easing out: rebotes decrecientes (para BOUNCE_SQUASH)
|
||||
const float n1 = 7.5625f;
|
||||
const float d1 = 2.75f;
|
||||
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 < 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;
|
||||
}
|
||||
|
||||
float AppLogo::easeInOutQuad(float t) {
|
||||
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<SDL_Vertex, 4> 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<int, 6> 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);
|
||||
}
|
||||
|
||||
@@ -107,11 +107,11 @@ class AppLogo {
|
||||
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();
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "help_overlay.hpp"
|
||||
|
||||
#include <algorithm> // for std::min
|
||||
#include <array> // 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<int, 3> col_heights = {0, 0, 0};
|
||||
current_column = 0;
|
||||
|
||||
for (const auto& binding : key_bindings_) {
|
||||
@@ -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,14 +257,15 @@ 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;
|
||||
}
|
||||
@@ -272,7 +277,7 @@ void HelpOverlay::rebuildCachedTexture() {
|
||||
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<SDL_Vertex, 4> 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<float>(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<float>(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<float>(box_width_), static_cast<float>(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<float>(box_width_), .y = static_cast<float>(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<float>(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<float>(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<int, 6> 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<Uint8>(text_r), static_cast<Uint8>(text_g), static_cast<Uint8>(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<Uint8>(notif_bg_r), static_cast<Uint8>(notif_bg_g), static_cast<Uint8>(notif_bg_b), 255};
|
||||
last_bg_color_ = {.r = static_cast<Uint8>(notif_bg_r), .g = static_cast<Uint8>(notif_bg_g), .b = static_cast<Uint8>(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<int, 3> 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<int, 3> 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);
|
||||
@@ -443,12 +458,14 @@ void HelpOverlay::render(SDL_Renderer* renderer) {
|
||||
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;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// 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<int>(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<int>(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<unsigned char*>(malloc(out_width * out_height * 4));
|
||||
auto* scaled_data = static_cast<unsigned char*>(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,8 +93,14 @@ 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
|
||||
orig_data,
|
||||
orig_width,
|
||||
orig_height,
|
||||
0, // Input
|
||||
scaled_data,
|
||||
out_width,
|
||||
out_height,
|
||||
0, // Output
|
||||
STBIR_RGBA // Formato píxel
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
auto LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer,
|
||||
unsigned char* data,
|
||||
int width, int height) {
|
||||
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());
|
||||
|
||||
@@ -42,8 +42,10 @@ public:
|
||||
* 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);
|
||||
int target_width,
|
||||
int target_height,
|
||||
int& out_width,
|
||||
int& out_height);
|
||||
|
||||
/**
|
||||
* @brief Crea una textura SDL desde un buffer RGBA
|
||||
@@ -57,5 +59,6 @@ public:
|
||||
*/
|
||||
static SDL_Texture* createTextureFromBuffer(SDL_Renderer* renderer,
|
||||
unsigned char* data,
|
||||
int width, int height);
|
||||
int width,
|
||||
int height);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include "notifier.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "defines.hpp"
|
||||
#include "text/textrenderer.hpp"
|
||||
#include "theme_manager.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "utils/easing_functions.hpp"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
// ============================================================================
|
||||
// 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;
|
||||
@@ -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<Uint8>(text_r),
|
||||
static_cast<Uint8>(text_g),
|
||||
static_cast<Uint8>(text_b),
|
||||
static_cast<Uint8>(current_notification_->alpha * 255.0f)
|
||||
};
|
||||
static_cast<Uint8>(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<Uint8>(bg_r),
|
||||
static_cast<Uint8>(bg_g),
|
||||
static_cast<Uint8>(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<float>(height);
|
||||
|
||||
// Color del tema con alpha
|
||||
Uint8 bg_alpha = static_cast<Uint8>(alpha * 255.0f);
|
||||
auto bg_alpha = static_cast<Uint8>(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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <string>
|
||||
#include <queue>
|
||||
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
// Forward declarations
|
||||
class TextRenderer;
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
#include "ui_manager.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#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
|
||||
#include "notifier.hpp" // for Notifier
|
||||
#include "help_overlay.hpp" // for HelpOverlay
|
||||
|
||||
// ============================================================================
|
||||
// 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();
|
||||
@@ -138,15 +142,14 @@ void UIManager::render(SDL_Renderer* renderer,
|
||||
|
||||
// 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,7 +212,7 @@ void UIManager::updatePhysicalWindowSize(int width, int height, int logical_heig
|
||||
|
||||
// === Métodos privados ===
|
||||
|
||||
void UIManager::renderDebugHUD(const Engine* engine,
|
||||
void UIManager::renderDebugHUD(const Engine* engine, // NOLINT(readability-function-cognitive-complexity)
|
||||
const SceneManager* scene_manager,
|
||||
SimulationMode current_mode,
|
||||
AppMode current_app_mode,
|
||||
@@ -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<int>(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<int>(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<int>(dm->refresh_rate)) + " Hz";
|
||||
} else {
|
||||
refresh_text = "Refresh: N/A";
|
||||
@@ -322,9 +329,9 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
||||
int hh = static_cast<int>(total_secs / 3600);
|
||||
int mm = static_cast<int>((total_secs % 3600) / 60);
|
||||
int ss = static_cast<int>(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<char, 32> 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<std::string> 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<const char*, 4> 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<char, 64> 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<int>(pos.x)) + ", " + std::to_string(static_cast<int>(pos.y)) + ")");
|
||||
lines.push_back("Gravity: " + std::to_string(static_cast<int>(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<int>(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<int>(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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_stdinc.h> // for Uint64
|
||||
|
||||
#include <string> // for std::string
|
||||
|
||||
// Forward declarations
|
||||
@@ -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)
|
||||
@@ -149,14 +148,14 @@ class UIManager {
|
||||
* @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
|
||||
|
||||
Reference in New Issue
Block a user