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 <cmath> // for fabs
|
||||
#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;
|
||||
@@ -36,7 +37,7 @@ Ball::Ball(float x, float y, float vx, float vy, Color color, std::shared_ptr<Te
|
||||
gravity_force_ = GRAVITY_FORCE * 60.0f * 60.0f;
|
||||
gravity_mass_factor_ = mass_factor; // Factor de masa individual para esta pelota
|
||||
gravity_direction_ = gravity_dir;
|
||||
screen_width_ = screen_width; // Dimensiones del terreno de juego
|
||||
screen_width_ = screen_width; // Dimensiones del terreno de juego
|
||||
screen_height_ = screen_height;
|
||||
on_surface_ = false;
|
||||
// Coeficiente base IGUAL para todas las pelotas (solo variación por rebote individual)
|
||||
@@ -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));
|
||||
}
|
||||
@@ -10,34 +10,34 @@ class Texture;
|
||||
|
||||
class Ball {
|
||||
private:
|
||||
std::unique_ptr<Sprite> sprite_; // Sprite para pintar la clase
|
||||
SDL_FRect pos_; // Posición y tamaño de la pelota
|
||||
float vx_, vy_; // Velocidad
|
||||
float gravity_force_; // Gravedad base
|
||||
float gravity_mass_factor_; // Factor de masa individual (0.7-1.3, afecta gravedad)
|
||||
GravityDirection gravity_direction_; // Direcci\u00f3n de la gravedad
|
||||
int screen_width_; // Ancho del terreno de juego
|
||||
int screen_height_; // Alto del terreno de juego
|
||||
Color color_; // Color de la pelota
|
||||
bool on_surface_; // Indica si la pelota est\u00e1 en la superficie (suelo/techo/pared)
|
||||
float loss_; // Coeficiente de rebote. Pérdida de energía en cada rebote
|
||||
std::unique_ptr<Sprite> sprite_; // Sprite para pintar la clase
|
||||
SDL_FRect pos_; // Posición y tamaño de la pelota
|
||||
float vx_, vy_; // Velocidad
|
||||
float gravity_force_; // Gravedad base
|
||||
float gravity_mass_factor_; // Factor de masa individual (0.7-1.3, afecta gravedad)
|
||||
GravityDirection gravity_direction_; // Direcci\u00f3n de la gravedad
|
||||
int screen_width_; // Ancho del terreno de juego
|
||||
int screen_height_; // Alto del terreno de juego
|
||||
Color color_; // Color de la pelota
|
||||
bool on_surface_; // Indica si la pelota est\u00e1 en la superficie (suelo/techo/pared)
|
||||
float loss_; // Coeficiente de rebote. Pérdida de energía en cada rebote
|
||||
|
||||
// Datos para modo Shape (figuras 3D)
|
||||
float pos_3d_x_, pos_3d_y_, pos_3d_z_; // Posición 3D en la figura
|
||||
float target_x_, target_y_; // Posición destino 2D (proyección)
|
||||
float depth_brightness_; // Brillo según profundidad Z (0.0-1.0)
|
||||
float depth_scale_; // Escala según profundidad Z (0.5-1.5)
|
||||
bool shape_attraction_active_; // ¿Está siendo atraída hacia la figura?
|
||||
float target_x_, target_y_; // Posición destino 2D (proyección)
|
||||
float depth_brightness_; // Brillo según profundidad Z (0.0-1.0)
|
||||
float depth_scale_; // Escala según profundidad Z (0.5-1.5)
|
||||
bool shape_attraction_active_; // ¿Está siendo atraída hacia la figura?
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Ball(float x, float y, float vx, float vy, Color color, std::shared_ptr<Texture> 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_; }
|
||||
@@ -84,7 +93,7 @@ class Ball {
|
||||
void setColor(const Color& color) { color_ = color; }
|
||||
|
||||
// Sistema de cambio de sprite dinámico
|
||||
void updateSize(int new_size); // Actualizar tamaño de hitbox
|
||||
void updateSize(int new_size); // Actualizar tamaño de hitbox
|
||||
void setTexture(std::shared_ptr<Texture> texture); // Cambiar textura del sprite
|
||||
|
||||
// Funciones para modo Shape (figuras 3D)
|
||||
@@ -99,10 +108,5 @@ class Ball {
|
||||
// Sistema de atracción física hacia figuras 3D
|
||||
void enableShapeAttraction(bool enable);
|
||||
float getDistanceToTarget() const; // Distancia actual al punto objetivo
|
||||
void applyShapeForce(float target_x, float target_y, float sphere_radius, float deltaTime,
|
||||
float spring_k = SHAPE_SPRING_K,
|
||||
float damping_base = SHAPE_DAMPING_BASE,
|
||||
float damping_near = SHAPE_DAMPING_NEAR,
|
||||
float near_threshold = SHAPE_NEAR_THRESHOLD,
|
||||
float max_force = SHAPE_MAX_FORCE);
|
||||
void applyShapeForce(float target_x, float target_y, float sphere_radius, float delta_time, float spring_k_base = SHAPE_SPRING_K, float damping_base_base = SHAPE_DAMPING_BASE, float damping_near_base = SHAPE_DAMPING_NEAR, float near_threshold_base = SHAPE_NEAR_THRESHOLD, float max_force_base = SHAPE_MAX_FORCE);
|
||||
};
|
||||
@@ -3,39 +3,38 @@
|
||||
#include <algorithm> // for std::min, std::max
|
||||
#include <cmath> // for sqrt, atan2
|
||||
|
||||
#include "ball.hpp" // for Ball
|
||||
#include "engine.hpp" // for Engine (si se necesita)
|
||||
#include "scene/scene_manager.hpp" // for SceneManager
|
||||
#include "state/state_manager.hpp" // for StateManager
|
||||
#include "ui/ui_manager.hpp" // for UIManager
|
||||
#include "ball.hpp" // for Ball
|
||||
#include "engine.hpp" // for Engine (si se necesita)
|
||||
#include "scene/scene_manager.hpp" // for SceneManager
|
||||
#include "state/state_manager.hpp" // for StateManager
|
||||
#include "ui/ui_manager.hpp" // for UIManager
|
||||
|
||||
BoidManager::BoidManager()
|
||||
: engine_(nullptr)
|
||||
, scene_mgr_(nullptr)
|
||||
, ui_mgr_(nullptr)
|
||||
, state_mgr_(nullptr)
|
||||
, screen_width_(0)
|
||||
, screen_height_(0)
|
||||
, boids_active_(false)
|
||||
, spatial_grid_(800, 600, BOID_GRID_CELL_SIZE) // Tamaño por defecto, se actualiza en initialize()
|
||||
, separation_radius_(BOID_SEPARATION_RADIUS)
|
||||
, alignment_radius_(BOID_ALIGNMENT_RADIUS)
|
||||
, cohesion_radius_(BOID_COHESION_RADIUS)
|
||||
, separation_weight_(BOID_SEPARATION_WEIGHT)
|
||||
, alignment_weight_(BOID_ALIGNMENT_WEIGHT)
|
||||
, cohesion_weight_(BOID_COHESION_WEIGHT)
|
||||
, max_speed_(BOID_MAX_SPEED)
|
||||
, min_speed_(BOID_MIN_SPEED)
|
||||
, max_force_(BOID_MAX_FORCE)
|
||||
, boundary_margin_(BOID_BOUNDARY_MARGIN)
|
||||
, boundary_weight_(BOID_BOUNDARY_WEIGHT) {
|
||||
: engine_(nullptr),
|
||||
scene_mgr_(nullptr),
|
||||
ui_mgr_(nullptr),
|
||||
state_mgr_(nullptr),
|
||||
screen_width_(0),
|
||||
screen_height_(0),
|
||||
boids_active_(false),
|
||||
spatial_grid_(800, 600, BOID_GRID_CELL_SIZE) // Tamaño por defecto, se actualiza en initialize()
|
||||
,
|
||||
separation_radius_(BOID_SEPARATION_RADIUS),
|
||||
alignment_radius_(BOID_ALIGNMENT_RADIUS),
|
||||
cohesion_radius_(BOID_COHESION_RADIUS),
|
||||
separation_weight_(BOID_SEPARATION_WEIGHT),
|
||||
alignment_weight_(BOID_ALIGNMENT_WEIGHT),
|
||||
cohesion_weight_(BOID_COHESION_WEIGHT),
|
||||
max_speed_(BOID_MAX_SPEED),
|
||||
min_speed_(BOID_MIN_SPEED),
|
||||
max_force_(BOID_MAX_FORCE),
|
||||
boundary_margin_(BOID_BOUNDARY_MARGIN),
|
||||
boundary_weight_(BOID_BOUNDARY_WEIGHT) {
|
||||
}
|
||||
|
||||
BoidManager::~BoidManager() {
|
||||
}
|
||||
BoidManager::~BoidManager() = default;
|
||||
|
||||
void BoidManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr,
|
||||
StateManager* state_mgr, int screen_width, int screen_height) {
|
||||
void BoidManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height) {
|
||||
engine_ = engine;
|
||||
scene_mgr_ = scene_mgr;
|
||||
ui_mgr_ = ui_mgr;
|
||||
@@ -65,7 +64,8 @@ void BoidManager::activateBoids() {
|
||||
auto& balls = scene_mgr_->getBallsMutable();
|
||||
for (auto& ball : balls) {
|
||||
// Dar velocidad inicial aleatoria si está quieto
|
||||
float vx, vy;
|
||||
float vx;
|
||||
float vy;
|
||||
ball->getVelocity(vx, vy);
|
||||
if (vx == 0.0f && vy == 0.0f) {
|
||||
// Velocidad aleatoria entre -60 y +60 px/s (time-based)
|
||||
@@ -76,13 +76,15 @@ void BoidManager::activateBoids() {
|
||||
}
|
||||
|
||||
// Mostrar notificación (solo si NO estamos en modo demo o logo)
|
||||
if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
|
||||
if ((state_mgr_ != nullptr) && (ui_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
|
||||
ui_mgr_->showNotification("Modo boids");
|
||||
}
|
||||
}
|
||||
|
||||
void BoidManager::deactivateBoids(bool force_gravity_on) {
|
||||
if (!boids_active_) return;
|
||||
if (!boids_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
boids_active_ = false;
|
||||
|
||||
@@ -92,7 +94,7 @@ void BoidManager::deactivateBoids(bool force_gravity_on) {
|
||||
}
|
||||
|
||||
// Mostrar notificación (solo si NO estamos en modo demo o logo)
|
||||
if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
|
||||
if ((state_mgr_ != nullptr) && (ui_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
|
||||
ui_mgr_->showNotification("Modo física");
|
||||
}
|
||||
}
|
||||
@@ -106,7 +108,9 @@ void BoidManager::toggleBoidsMode(bool force_gravity_on) {
|
||||
}
|
||||
|
||||
void BoidManager::update(float delta_time) {
|
||||
if (!boids_active_) return;
|
||||
if (!boids_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& balls = scene_mgr_->getBallsMutable();
|
||||
|
||||
@@ -114,8 +118,8 @@ void BoidManager::update(float delta_time) {
|
||||
spatial_grid_.clear();
|
||||
for (auto& ball : balls) {
|
||||
SDL_FRect pos = ball->getPosition();
|
||||
float center_x = pos.x + pos.w / 2.0f;
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
float center_x = pos.x + (pos.w / 2.0f);
|
||||
float center_y = pos.y + (pos.h / 2.0f);
|
||||
spatial_grid_.insert(ball.get(), center_x, center_y);
|
||||
}
|
||||
|
||||
@@ -131,7 +135,8 @@ void BoidManager::update(float delta_time) {
|
||||
|
||||
// Actualizar posiciones con velocidades resultantes (time-based)
|
||||
for (auto& ball : balls) {
|
||||
float vx, vy;
|
||||
float vx;
|
||||
float vy;
|
||||
ball->getVelocity(vx, vy);
|
||||
|
||||
SDL_FRect pos = ball->getPosition();
|
||||
@@ -153,22 +158,24 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
|
||||
int count = 0;
|
||||
|
||||
SDL_FRect pos = boid->getPosition();
|
||||
float center_x = pos.x + pos.w / 2.0f;
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
float center_x = pos.x + (pos.w / 2.0f);
|
||||
float center_y = pos.y + (pos.h / 2.0f);
|
||||
|
||||
// FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n))
|
||||
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, separation_radius_);
|
||||
|
||||
for (Ball* other : neighbors) {
|
||||
if (other == boid) continue; // Ignorar a sí mismo
|
||||
if (other == boid) {
|
||||
continue; // Ignorar a sí mismo
|
||||
}
|
||||
|
||||
SDL_FRect other_pos = other->getPosition();
|
||||
float other_x = other_pos.x + other_pos.w / 2.0f;
|
||||
float other_y = other_pos.y + other_pos.h / 2.0f;
|
||||
float other_x = other_pos.x + (other_pos.w / 2.0f);
|
||||
float other_y = other_pos.y + (other_pos.h / 2.0f);
|
||||
|
||||
float dx = center_x - other_x;
|
||||
float dy = center_y - other_y;
|
||||
float distance = std::sqrt(dx * dx + dy * dy);
|
||||
float distance = std::sqrt((dx * dx) + (dy * dy));
|
||||
|
||||
if (distance > 0.0f && distance < separation_radius_) {
|
||||
// FASE 1.3: Separación más fuerte cuando más cerca (inversamente proporcional a distancia)
|
||||
@@ -186,7 +193,8 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
|
||||
steer_y /= count;
|
||||
|
||||
// Aplicar fuerza de separación
|
||||
float vx, vy;
|
||||
float vx;
|
||||
float vy;
|
||||
boid->getVelocity(vx, vy);
|
||||
vx += steer_x * separation_weight_ * delta_time;
|
||||
vy += steer_y * separation_weight_ * delta_time;
|
||||
@@ -201,25 +209,28 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
|
||||
int count = 0;
|
||||
|
||||
SDL_FRect pos = boid->getPosition();
|
||||
float center_x = pos.x + pos.w / 2.0f;
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
float center_x = pos.x + (pos.w / 2.0f);
|
||||
float center_y = pos.y + (pos.h / 2.0f);
|
||||
|
||||
// FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n))
|
||||
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, alignment_radius_);
|
||||
|
||||
for (Ball* other : neighbors) {
|
||||
if (other == boid) continue;
|
||||
if (other == boid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SDL_FRect other_pos = other->getPosition();
|
||||
float other_x = other_pos.x + other_pos.w / 2.0f;
|
||||
float other_y = other_pos.y + other_pos.h / 2.0f;
|
||||
float other_x = other_pos.x + (other_pos.w / 2.0f);
|
||||
float other_y = other_pos.y + (other_pos.h / 2.0f);
|
||||
|
||||
float dx = center_x - other_x;
|
||||
float dy = center_y - other_y;
|
||||
float distance = std::sqrt(dx * dx + dy * dy);
|
||||
float distance = std::sqrt((dx * dx) + (dy * dy));
|
||||
|
||||
if (distance < alignment_radius_) {
|
||||
float other_vx, other_vy;
|
||||
float other_vx;
|
||||
float other_vy;
|
||||
other->getVelocity(other_vx, other_vy);
|
||||
avg_vx += other_vx;
|
||||
avg_vy += other_vy;
|
||||
@@ -233,13 +244,14 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
|
||||
avg_vy /= count;
|
||||
|
||||
// Steering hacia la velocidad promedio
|
||||
float vx, vy;
|
||||
float vx;
|
||||
float vy;
|
||||
boid->getVelocity(vx, vy);
|
||||
float steer_x = (avg_vx - vx) * alignment_weight_ * delta_time;
|
||||
float steer_y = (avg_vy - vy) * alignment_weight_ * delta_time;
|
||||
|
||||
// Limitar fuerza máxima de steering
|
||||
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
|
||||
float steer_mag = std::sqrt((steer_x * steer_x) + (steer_y * steer_y));
|
||||
if (steer_mag > max_force_) {
|
||||
steer_x = (steer_x / steer_mag) * max_force_;
|
||||
steer_y = (steer_y / steer_mag) * max_force_;
|
||||
@@ -258,22 +270,24 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
||||
int count = 0;
|
||||
|
||||
SDL_FRect pos = boid->getPosition();
|
||||
float center_x = pos.x + pos.w / 2.0f;
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
float center_x = pos.x + (pos.w / 2.0f);
|
||||
float center_y = pos.y + (pos.h / 2.0f);
|
||||
|
||||
// FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n))
|
||||
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, cohesion_radius_);
|
||||
|
||||
for (Ball* other : neighbors) {
|
||||
if (other == boid) continue;
|
||||
if (other == boid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SDL_FRect other_pos = other->getPosition();
|
||||
float other_x = other_pos.x + other_pos.w / 2.0f;
|
||||
float other_y = other_pos.y + other_pos.h / 2.0f;
|
||||
float other_x = other_pos.x + (other_pos.w / 2.0f);
|
||||
float other_y = other_pos.y + (other_pos.h / 2.0f);
|
||||
|
||||
float dx = center_x - other_x;
|
||||
float dy = center_y - other_y;
|
||||
float distance = std::sqrt(dx * dx + dy * dy);
|
||||
float distance = std::sqrt((dx * dx) + (dy * dy));
|
||||
|
||||
if (distance < cohesion_radius_) {
|
||||
center_of_mass_x += other_x;
|
||||
@@ -290,7 +304,7 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
||||
// FASE 1.4: Normalizar dirección hacia el centro (CRÍTICO - antes no estaba normalizado!)
|
||||
float dx_to_center = center_of_mass_x - center_x;
|
||||
float dy_to_center = center_of_mass_y - center_y;
|
||||
float distance_to_center = std::sqrt(dx_to_center * dx_to_center + dy_to_center * dy_to_center);
|
||||
float distance_to_center = std::sqrt((dx_to_center * dx_to_center) + (dy_to_center * dy_to_center));
|
||||
|
||||
// Solo aplicar si hay distancia al centro (evitar división por cero)
|
||||
if (distance_to_center > 0.1f) {
|
||||
@@ -299,13 +313,14 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
||||
float steer_y = (dy_to_center / distance_to_center) * cohesion_weight_ * delta_time;
|
||||
|
||||
// Limitar fuerza máxima de steering
|
||||
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
|
||||
float steer_mag = std::sqrt((steer_x * steer_x) + (steer_y * steer_y));
|
||||
if (steer_mag > max_force_) {
|
||||
steer_x = (steer_x / steer_mag) * max_force_;
|
||||
steer_y = (steer_y / steer_mag) * max_force_;
|
||||
}
|
||||
|
||||
float vx, vy;
|
||||
float vx;
|
||||
float vy;
|
||||
boid->getVelocity(vx, vy);
|
||||
vx += steer_x;
|
||||
vy += steer_y;
|
||||
@@ -314,12 +329,12 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
||||
}
|
||||
}
|
||||
|
||||
void BoidManager::applyBoundaries(Ball* boid) {
|
||||
void BoidManager::applyBoundaries(Ball* boid) const {
|
||||
// NUEVA IMPLEMENTACIÓN: Bordes como obstáculos (repulsión en lugar de wrapping)
|
||||
// Cuando un boid se acerca a un borde, se aplica una fuerza alejándolo
|
||||
SDL_FRect pos = boid->getPosition();
|
||||
float center_x = pos.x + pos.w / 2.0f;
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
float center_x = pos.x + (pos.w / 2.0f);
|
||||
float center_y = pos.y + (pos.h / 2.0f);
|
||||
|
||||
float steer_x = 0.0f;
|
||||
float steer_y = 0.0f;
|
||||
@@ -363,11 +378,12 @@ void BoidManager::applyBoundaries(Ball* boid) {
|
||||
|
||||
// Aplicar fuerza de repulsión si hay alguna
|
||||
if (steer_x != 0.0f || steer_y != 0.0f) {
|
||||
float vx, vy;
|
||||
float vx;
|
||||
float vy;
|
||||
boid->getVelocity(vx, vy);
|
||||
|
||||
// Normalizar fuerza de repulsión (para que todas las direcciones tengan la misma intensidad)
|
||||
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
|
||||
float steer_mag = std::sqrt((steer_x * steer_x) + (steer_y * steer_y));
|
||||
if (steer_mag > 0.0f) {
|
||||
steer_x /= steer_mag;
|
||||
steer_y /= steer_mag;
|
||||
@@ -381,12 +397,13 @@ void BoidManager::applyBoundaries(Ball* boid) {
|
||||
}
|
||||
}
|
||||
|
||||
void BoidManager::limitSpeed(Ball* boid) {
|
||||
void BoidManager::limitSpeed(Ball* boid) const {
|
||||
// Limitar velocidad máxima del boid
|
||||
float vx, vy;
|
||||
float vx;
|
||||
float vy;
|
||||
boid->getVelocity(vx, vy);
|
||||
|
||||
float speed = std::sqrt(vx * vx + vy * vy);
|
||||
float speed = std::sqrt((vx * vx) + (vy * vy));
|
||||
|
||||
// Limitar velocidad máxima
|
||||
if (speed > max_speed_) {
|
||||
|
||||
@@ -46,8 +46,7 @@ class BoidManager {
|
||||
* @param screen_width Ancho de pantalla actual
|
||||
* @param screen_height Alto de pantalla actual
|
||||
*/
|
||||
void initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr,
|
||||
StateManager* state_mgr, int screen_width, int screen_height);
|
||||
void initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height);
|
||||
|
||||
/**
|
||||
* @brief Actualiza el tamaño de pantalla (llamado en resize/fullscreen)
|
||||
@@ -105,22 +104,22 @@ class BoidManager {
|
||||
|
||||
// === Parámetros ajustables en runtime (inicializados con valores de defines.h) ===
|
||||
// Permite modificar comportamiento sin recompilar (para tweaking/debug visual)
|
||||
float separation_radius_; // Radio de separación (evitar colisiones)
|
||||
float alignment_radius_; // Radio de alineación (matching de velocidad)
|
||||
float cohesion_radius_; // Radio de cohesión (centro de masa)
|
||||
float separation_weight_; // Peso fuerza de separación (aceleración px/s²)
|
||||
float alignment_weight_; // Peso fuerza de alineación (steering proporcional)
|
||||
float cohesion_weight_; // Peso fuerza de cohesión (aceleración px/s²)
|
||||
float max_speed_; // Velocidad máxima (px/s)
|
||||
float min_speed_; // Velocidad mínima (px/s)
|
||||
float max_force_; // Fuerza máxima de steering (px/s)
|
||||
float boundary_margin_; // Margen para repulsión de bordes (px)
|
||||
float boundary_weight_; // Peso fuerza de repulsión de bordes (aceleración px/s²)
|
||||
float separation_radius_; // Radio de separación (evitar colisiones)
|
||||
float alignment_radius_; // Radio de alineación (matching de velocidad)
|
||||
float cohesion_radius_; // Radio de cohesión (centro de masa)
|
||||
float separation_weight_; // Peso fuerza de separación (aceleración px/s²)
|
||||
float alignment_weight_; // Peso fuerza de alineación (steering proporcional)
|
||||
float cohesion_weight_; // Peso fuerza de cohesión (aceleración px/s²)
|
||||
float max_speed_; // Velocidad máxima (px/s)
|
||||
float min_speed_; // Velocidad mínima (px/s)
|
||||
float max_force_; // Fuerza máxima de steering (px/s)
|
||||
float boundary_margin_; // Margen para repulsión de bordes (px)
|
||||
float boundary_weight_; // Peso fuerza de repulsión de bordes (aceleración px/s²)
|
||||
|
||||
// Métodos privados para las reglas de Reynolds
|
||||
void applySeparation(Ball* boid, float delta_time);
|
||||
void applyAlignment(Ball* boid, float delta_time);
|
||||
void applyCohesion(Ball* boid, float delta_time);
|
||||
void applyBoundaries(Ball* boid); // Repulsión de bordes (ya no wrapping)
|
||||
void limitSpeed(Ball* boid); // Limitar velocidad máxima
|
||||
void applyBoundaries(Ball* boid) const; // Repulsión de bordes (ya no wrapping)
|
||||
void limitSpeed(Ball* boid) const; // Limitar velocidad máxima
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -30,42 +30,42 @@ class Ball; // Forward declaration
|
||||
// ============================================================================
|
||||
|
||||
class SpatialGrid {
|
||||
public:
|
||||
// Constructor: especificar dimensiones del mundo y tamaño de celda
|
||||
SpatialGrid(int world_width, int world_height, float cell_size);
|
||||
public:
|
||||
// Constructor: especificar dimensiones del mundo y tamaño de celda
|
||||
SpatialGrid(int world_width, int world_height, float cell_size);
|
||||
|
||||
// Limpiar todas las celdas (llamar al inicio de cada frame)
|
||||
void clear();
|
||||
// Limpiar todas las celdas (llamar al inicio de cada frame)
|
||||
void clear();
|
||||
|
||||
// Insertar objeto en el grid según su posición (x, y)
|
||||
void insert(Ball* ball, float x, float y);
|
||||
// Insertar objeto en el grid según su posición (x, y)
|
||||
void insert(Ball* ball, float x, float y);
|
||||
|
||||
// Buscar todos los objetos dentro del radio especificado desde (x, y)
|
||||
// Devuelve vector de punteros a Ball (puede contener duplicados si ball está en múltiples celdas)
|
||||
std::vector<Ball*> queryRadius(float x, float y, float radius);
|
||||
// Buscar todos los objetos dentro del radio especificado desde (x, y)
|
||||
// Devuelve vector de punteros a Ball (puede contener duplicados si ball está en múltiples celdas)
|
||||
std::vector<Ball*> queryRadius(float x, float y, float radius);
|
||||
|
||||
// Actualizar dimensiones del mundo (útil para cambios de resolución F4)
|
||||
void updateWorldSize(int world_width, int world_height);
|
||||
// Actualizar dimensiones del mundo (útil para cambios de resolución F4)
|
||||
void updateWorldSize(int world_width, int world_height);
|
||||
|
||||
private:
|
||||
// Convertir coordenadas (x, y) a índice de celda (cell_x, cell_y)
|
||||
void getCellCoords(float x, float y, int& cell_x, int& cell_y) const;
|
||||
private:
|
||||
// Convertir coordenadas (x, y) a índice de celda (cell_x, cell_y)
|
||||
void getCellCoords(float x, float y, int& cell_x, int& cell_y) const;
|
||||
|
||||
// Convertir (cell_x, cell_y) a hash key único para el mapa
|
||||
int getCellKey(int cell_x, int cell_y) const;
|
||||
// Convertir (cell_x, cell_y) a hash key único para el mapa
|
||||
int getCellKey(int cell_x, int cell_y) const;
|
||||
|
||||
// Dimensiones del mundo (ancho/alto en píxeles)
|
||||
int world_width_;
|
||||
int world_height_;
|
||||
// Dimensiones del mundo (ancho/alto en píxeles)
|
||||
int world_width_;
|
||||
int world_height_;
|
||||
|
||||
// Tamaño de cada celda (en píxeles)
|
||||
float cell_size_;
|
||||
// Tamaño de cada celda (en píxeles)
|
||||
float cell_size_;
|
||||
|
||||
// Número de celdas en cada dimensión
|
||||
int grid_cols_;
|
||||
int grid_rows_;
|
||||
// Número de celdas en cada dimensión
|
||||
int grid_cols_;
|
||||
int grid_rows_;
|
||||
|
||||
// Estructura de datos: hash map de cell_key → vector de Ball*
|
||||
// Usamos unordered_map para O(1) lookup
|
||||
std::unordered_map<int, std::vector<Ball*>> cells_;
|
||||
// Estructura de datos: hash map de cell_key → vector de Ball*
|
||||
// Usamos unordered_map para O(1) lookup
|
||||
std::unordered_map<int, std::vector<Ball*>> cells_;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,32 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_events.h> // for SDL_Event
|
||||
#include <SDL3/SDL_render.h> // for SDL_Renderer (ui_renderer_ software renderer)
|
||||
#include <SDL3/SDL_stdinc.h> // for Uint64
|
||||
#include <SDL3/SDL_surface.h> // for SDL_Surface (ui_surface_)
|
||||
#include <SDL3/SDL_video.h> // for SDL_Window
|
||||
#include <SDL3/SDL_events.h> // for SDL_Event
|
||||
#include <SDL3/SDL_render.h> // for SDL_Renderer (ui_renderer_ software renderer)
|
||||
#include <SDL3/SDL_stdinc.h> // for Uint64
|
||||
#include <SDL3/SDL_surface.h> // for SDL_Surface (ui_surface_)
|
||||
#include <SDL3/SDL_video.h> // for SDL_Window
|
||||
|
||||
#include <array> // for array
|
||||
#include <memory> // for unique_ptr, shared_ptr
|
||||
#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
|
||||
#include "external/texture.hpp" // for Texture
|
||||
#include "gpu/gpu_ball_buffer.hpp" // for GpuBallBuffer, BallGPUData
|
||||
#include "gpu/gpu_context.hpp" // for GpuContext
|
||||
#include "gpu/gpu_pipeline.hpp" // for GpuPipeline
|
||||
#include "gpu/gpu_sprite_batch.hpp" // for GpuSpriteBatch
|
||||
#include "gpu/gpu_texture.hpp" // for GpuTexture
|
||||
#include "input/input_handler.hpp" // for InputHandler
|
||||
#include "scene/scene_manager.hpp" // for SceneManager
|
||||
#include "shapes_mgr/shape_manager.hpp" // for ShapeManager
|
||||
#include "state/state_manager.hpp" // for StateManager
|
||||
#include "theme_manager.hpp" // for ThemeManager
|
||||
#include "ui/ui_manager.hpp" // for UIManager
|
||||
#include "ball.hpp" // for Ball
|
||||
#include "boids_mgr/boid_manager.hpp" // for BoidManager
|
||||
#include "defines.hpp" // for GravityDirection, ColorTheme, ShapeType
|
||||
#include "external/texture.hpp" // for Texture
|
||||
#include "gpu/gpu_ball_buffer.hpp" // for GpuBallBuffer, BallGPUData
|
||||
#include "gpu/gpu_context.hpp" // for GpuContext
|
||||
#include "gpu/gpu_pipeline.hpp" // for GpuPipeline
|
||||
#include "gpu/gpu_sprite_batch.hpp" // for GpuSpriteBatch
|
||||
#include "gpu/gpu_texture.hpp" // for GpuTexture
|
||||
#include "input/input_handler.hpp" // for InputHandler
|
||||
#include "scene/scene_manager.hpp" // for SceneManager
|
||||
#include "shapes_mgr/shape_manager.hpp" // for ShapeManager
|
||||
#include "state/state_manager.hpp" // for StateManager
|
||||
#include "theme_manager.hpp" // for ThemeManager
|
||||
#include "ui/app_logo.hpp" // for AppLogo
|
||||
#include "ui/ui_manager.hpp" // for UIManager
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
@@ -97,8 +97,8 @@ class Engine {
|
||||
// Escenario custom (tecla 9, --custom-balls)
|
||||
void setCustomScenario(int balls);
|
||||
bool isCustomScenarioEnabled() const { return custom_scenario_enabled_; }
|
||||
bool isCustomAutoAvailable() const { return custom_auto_available_; }
|
||||
int getCustomScenarioBalls() const { return custom_scenario_balls_; }
|
||||
bool isCustomAutoAvailable() const { return custom_auto_available_; }
|
||||
int getCustomScenarioBalls() const { return custom_scenario_balls_; }
|
||||
|
||||
// Control manual del benchmark (--skip-benchmark, --max-balls)
|
||||
void setSkipBenchmark();
|
||||
@@ -113,10 +113,10 @@ class Engine {
|
||||
void toggleLogoMode();
|
||||
|
||||
// === Métodos públicos para StateManager (automatización DEMO/LOGO sin notificación) ===
|
||||
void enterShapeMode(ShapeType type); // Activar figura (sin notificación)
|
||||
void exitShapeMode(bool force_gravity = true); // Volver a física (sin notificación)
|
||||
void switchTextureSilent(); // Cambiar textura (sin notificación)
|
||||
void setTextureByIndex(size_t index); // Restaurar textura específica
|
||||
void enterShapeMode(ShapeType type); // Activar figura (sin notificación)
|
||||
void exitShapeMode(bool force_gravity = true); // Volver a física (sin notificación)
|
||||
void switchTextureSilent(); // Cambiar textura (sin notificación)
|
||||
void setTextureByIndex(size_t index); // Restaurar textura específica
|
||||
|
||||
// === Getters públicos para UIManager (Debug HUD) ===
|
||||
bool getVSyncEnabled() const { return vsync_enabled_; }
|
||||
@@ -133,11 +133,11 @@ class Engine {
|
||||
int getBaseScreenHeight() const { return base_screen_height_; }
|
||||
int getMaxAutoScenario() const { return max_auto_scenario_; }
|
||||
size_t getCurrentTextureIndex() const { return current_texture_index_; }
|
||||
bool isPostFXEnabled() const { return postfx_enabled_; }
|
||||
int getPostFXMode() const { return postfx_effect_mode_; }
|
||||
float getPostFXVignette() const { return postfx_uniforms_.vignette_strength; }
|
||||
float getPostFXChroma() const { return postfx_uniforms_.chroma_strength; }
|
||||
float getPostFXScanline() const { return postfx_uniforms_.scanline_strength; }
|
||||
bool isPostFXEnabled() const { return postfx_enabled_; }
|
||||
int getPostFXMode() const { return postfx_effect_mode_; }
|
||||
float getPostFXVignette() const { return postfx_uniforms_.vignette_strength; }
|
||||
float getPostFXChroma() const { return postfx_uniforms_.chroma_strength; }
|
||||
float getPostFXScanline() const { return postfx_uniforms_.scanline_strength; }
|
||||
|
||||
private:
|
||||
// === Componentes del sistema (Composición) ===
|
||||
@@ -153,23 +153,23 @@ class Engine {
|
||||
SDL_Window* window_ = nullptr;
|
||||
|
||||
// === SDL_GPU rendering pipeline ===
|
||||
std::unique_ptr<GpuContext> gpu_ctx_; // Device + swapchain
|
||||
std::unique_ptr<GpuPipeline> gpu_pipeline_; // Sprite + ball + postfx pipelines
|
||||
std::unique_ptr<GpuSpriteBatch> sprite_batch_; // Per-frame vertex/index batch (bg + shape + UI)
|
||||
std::unique_ptr<GpuBallBuffer> gpu_ball_buffer_; // Instanced ball instance data (PHYSICS/BOIDS)
|
||||
std::vector<BallGPUData> ball_gpu_data_; // CPU-side staging vector (reused each frame)
|
||||
std::unique_ptr<GpuTexture> offscreen_tex_; // Offscreen render target (Pass 1)
|
||||
std::unique_ptr<GpuTexture> white_tex_; // 1×1 white (background gradient)
|
||||
std::unique_ptr<GpuTexture> ui_tex_; // UI text overlay texture
|
||||
std::unique_ptr<GpuContext> gpu_ctx_; // Device + swapchain
|
||||
std::unique_ptr<GpuPipeline> gpu_pipeline_; // Sprite + ball + postfx pipelines
|
||||
std::unique_ptr<GpuSpriteBatch> sprite_batch_; // Per-frame vertex/index batch (bg + shape + UI)
|
||||
std::unique_ptr<GpuBallBuffer> gpu_ball_buffer_; // Instanced ball instance data (PHYSICS/BOIDS)
|
||||
std::vector<BallGPUData> ball_gpu_data_; // CPU-side staging vector (reused each frame)
|
||||
std::unique_ptr<GpuTexture> offscreen_tex_; // Offscreen render target (Pass 1)
|
||||
std::unique_ptr<GpuTexture> white_tex_; // 1×1 white (background gradient)
|
||||
std::unique_ptr<GpuTexture> ui_tex_; // UI text overlay texture
|
||||
|
||||
// GPU sprite textures (one per ball skin, parallel to textures_/texture_names_)
|
||||
std::unique_ptr<GpuTexture> gpu_texture_; // Active GPU sprite texture
|
||||
std::unique_ptr<GpuTexture> gpu_texture_; // Active GPU sprite texture
|
||||
std::vector<std::unique_ptr<GpuTexture>> gpu_textures_; // All GPU sprite textures
|
||||
|
||||
// === SDL_Renderer (software, for UI text via SDL3_ttf) ===
|
||||
// Renders to ui_surface_, then uploaded as gpu texture overlay.
|
||||
SDL_Renderer* ui_renderer_ = nullptr;
|
||||
SDL_Surface* ui_surface_ = nullptr;
|
||||
SDL_Surface* ui_surface_ = nullptr;
|
||||
|
||||
// Legacy Texture objects — kept for ball physics sizing and AppLogo
|
||||
std::shared_ptr<Texture> texture_ = nullptr; // Textura activa actual
|
||||
@@ -190,7 +190,7 @@ class Engine {
|
||||
int postfx_effect_mode_ = 3;
|
||||
bool postfx_enabled_ = false;
|
||||
float postfx_override_vignette_ = -1.f; // -1 = sin override
|
||||
float postfx_override_chroma_ = -1.f;
|
||||
float postfx_override_chroma_ = -1.f;
|
||||
|
||||
// Sistema de escala de ventana
|
||||
float current_window_scale_ = 1.0f;
|
||||
@@ -228,10 +228,10 @@ class Engine {
|
||||
int max_auto_scenario_ = 5;
|
||||
|
||||
// Escenario custom (--custom-balls)
|
||||
int custom_scenario_balls_ = 0;
|
||||
int custom_scenario_balls_ = 0;
|
||||
bool custom_scenario_enabled_ = false;
|
||||
bool custom_auto_available_ = false;
|
||||
bool skip_benchmark_ = false;
|
||||
bool custom_auto_available_ = false;
|
||||
bool skip_benchmark_ = false;
|
||||
|
||||
// Bucket sort per z-ordering (SHAPE mode)
|
||||
static constexpr int DEPTH_SORT_BUCKETS = 256;
|
||||
@@ -273,9 +273,8 @@ class Engine {
|
||||
bool isScenarioAllowedForBoids(int scenario_id) const;
|
||||
|
||||
// GPU helpers
|
||||
bool loadGpuSpriteTexture(size_t index); // Upload one sprite texture to GPU
|
||||
void recreateOffscreenTexture(); // Recreate when resolution changes
|
||||
void renderUIToSurface(); // Render text/UI to ui_surface_
|
||||
bool loadGpuSpriteTexture(size_t index); // Upload one sprite texture to GPU
|
||||
void recreateOffscreenTexture(); // Recreate when resolution changes
|
||||
void renderUIToSurface(); // Render text/UI to ui_surface_
|
||||
void uploadUISurface(SDL_GPUCommandBuffer* cmd_buf); // Upload ui_surface_ → ui_tex_
|
||||
|
||||
};
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
#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)
|
||||
SDL_GPUBufferCreateInfo buf_info = {};
|
||||
buf_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||
buf_info.size = buf_size;
|
||||
buf_info.size = buf_size;
|
||||
gpu_buf_ = SDL_CreateGPUBuffer(device, &buf_info);
|
||||
if (!gpu_buf_) {
|
||||
if (gpu_buf_ == nullptr) {
|
||||
SDL_Log("GpuBallBuffer: GPU buffer creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -20,34 +21,45 @@ bool GpuBallBuffer::init(SDL_GPUDevice* device) {
|
||||
// Transfer buffer (upload staging, cycled every frame)
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
tb_info.size = buf_size;
|
||||
tb_info.size = buf_size;
|
||||
transfer_buf_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||
if (!transfer_buf_) {
|
||||
if (transfer_buf_ == nullptr) {
|
||||
SDL_Log("GpuBallBuffer: transfer buffer creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Log("GpuBallBuffer: initialized (capacity %d balls, %.1f MB VRAM)",
|
||||
MAX_BALLS, buf_size / (1024.0f * 1024.0f));
|
||||
MAX_BALLS,
|
||||
buf_size / (1024.0f * 1024.0f));
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpuBallBuffer::destroy(SDL_GPUDevice* device) {
|
||||
if (!device) return;
|
||||
if (transfer_buf_) { SDL_ReleaseGPUTransferBuffer(device, transfer_buf_); transfer_buf_ = nullptr; }
|
||||
if (gpu_buf_) { SDL_ReleaseGPUBuffer(device, gpu_buf_); gpu_buf_ = nullptr; }
|
||||
if (device == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (transfer_buf_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device, transfer_buf_);
|
||||
transfer_buf_ = nullptr;
|
||||
}
|
||||
if (gpu_buf_ != nullptr) {
|
||||
SDL_ReleaseGPUBuffer(device, gpu_buf_);
|
||||
gpu_buf_ = nullptr;
|
||||
}
|
||||
count_ = 0;
|
||||
}
|
||||
|
||||
bool GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd,
|
||||
const BallGPUData* data, int count) {
|
||||
if (!data || count <= 0) { count_ = 0; return false; }
|
||||
auto GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd, const BallGPUData* data, int count) -> bool {
|
||||
if ((data == nullptr) || count <= 0) {
|
||||
count_ = 0;
|
||||
return false;
|
||||
}
|
||||
count = std::min(count, MAX_BALLS);
|
||||
|
||||
Uint32 upload_size = static_cast<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;
|
||||
}
|
||||
@@ -55,8 +67,8 @@ bool GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd,
|
||||
SDL_UnmapGPUTransferBuffer(device, transfer_buf_);
|
||||
|
||||
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);
|
||||
SDL_GPUTransferBufferLocation src = { transfer_buf_, 0 };
|
||||
SDL_GPUBufferRegion dst = { gpu_buf_, 0, upload_size };
|
||||
SDL_GPUTransferBufferLocation src = {transfer_buf_, 0};
|
||||
SDL_GPUBufferRegion dst = {gpu_buf_, 0, upload_size};
|
||||
SDL_UploadToGPUBuffer(copy, &src, &dst, true /* cycle */);
|
||||
SDL_EndGPUCopyPass(copy);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -12,9 +13,9 @@
|
||||
// r,g,b,a: RGBA in [0,1]
|
||||
// ---------------------------------------------------------------------------
|
||||
struct BallGPUData {
|
||||
float cx, cy; // NDC center
|
||||
float hw, hh; // NDC half-size (positive)
|
||||
float r, g, b, a; // RGBA color [0,1]
|
||||
float cx, cy; // NDC center
|
||||
float hw, hh; // NDC half-size (positive)
|
||||
float r, g, b, a; // RGBA color [0,1]
|
||||
};
|
||||
static_assert(sizeof(BallGPUData) == 32, "BallGPUData must be 32 bytes");
|
||||
|
||||
@@ -26,22 +27,21 @@ static_assert(sizeof(BallGPUData) == 32, "BallGPUData must be 32 bytes");
|
||||
// // Then in render pass: bind buffer, SDL_DrawGPUPrimitives(pass, 6, count, 0, 0)
|
||||
// ============================================================================
|
||||
class GpuBallBuffer {
|
||||
public:
|
||||
static constexpr int MAX_BALLS = 500000;
|
||||
public:
|
||||
static constexpr int MAX_BALLS = 500000;
|
||||
|
||||
bool init(SDL_GPUDevice* device);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
bool init(SDL_GPUDevice* device);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
|
||||
// Upload ball array to GPU via an internal copy pass.
|
||||
// count is clamped to MAX_BALLS. Returns false on error or empty input.
|
||||
bool upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd,
|
||||
const BallGPUData* data, int count);
|
||||
// Upload ball array to GPU via an internal copy pass.
|
||||
// count is clamped to MAX_BALLS. Returns false on error or empty input.
|
||||
bool upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd, const BallGPUData* data, int count);
|
||||
|
||||
SDL_GPUBuffer* buffer() const { return gpu_buf_; }
|
||||
int count() const { return count_; }
|
||||
SDL_GPUBuffer* buffer() const { return gpu_buf_; }
|
||||
int count() const { return count_; }
|
||||
|
||||
private:
|
||||
SDL_GPUBuffer* gpu_buf_ = nullptr;
|
||||
SDL_GPUTransferBuffer* transfer_buf_ = nullptr;
|
||||
int count_ = 0;
|
||||
private:
|
||||
SDL_GPUBuffer* gpu_buf_ = nullptr;
|
||||
SDL_GPUTransferBuffer* transfer_buf_ = nullptr;
|
||||
int count_ = 0;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -8,26 +8,27 @@
|
||||
// Replaces SDL_Renderer as the main rendering backend.
|
||||
// ============================================================================
|
||||
class GpuContext {
|
||||
public:
|
||||
bool init(SDL_Window* window);
|
||||
void destroy();
|
||||
public:
|
||||
bool init(SDL_Window* window);
|
||||
void destroy();
|
||||
|
||||
SDL_GPUDevice* device() const { return device_; }
|
||||
SDL_Window* window() const { return window_; }
|
||||
SDL_GPUTextureFormat swapchainFormat() const { return swapchain_format_; }
|
||||
SDL_GPUDevice* device() const { return device_; }
|
||||
SDL_Window* window() const { return window_; }
|
||||
SDL_GPUTextureFormat swapchainFormat() const { return swapchain_format_; }
|
||||
|
||||
// Per-frame helpers
|
||||
SDL_GPUCommandBuffer* acquireCommandBuffer();
|
||||
// Returns nullptr if window is minimized (swapchain not available).
|
||||
SDL_GPUTexture* acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf,
|
||||
Uint32* out_w, Uint32* out_h);
|
||||
void submit(SDL_GPUCommandBuffer* cmd_buf);
|
||||
// Per-frame helpers
|
||||
SDL_GPUCommandBuffer* acquireCommandBuffer();
|
||||
// Returns nullptr if window is minimized (swapchain not available).
|
||||
SDL_GPUTexture* acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf,
|
||||
Uint32* out_w,
|
||||
Uint32* out_h);
|
||||
static void submit(SDL_GPUCommandBuffer* cmd_buf);
|
||||
|
||||
// VSync control (call after init)
|
||||
bool setVSync(bool enabled);
|
||||
// VSync control (call after init)
|
||||
bool setVSync(bool enabled);
|
||||
|
||||
private:
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUTextureFormat swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||
private:
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUTextureFormat swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||
};
|
||||
|
||||
@@ -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__
|
||||
@@ -198,15 +201,15 @@ vertex BallVOut ball_instanced_vs(BallInstance inst [[stage_in]],
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
#endif // __APPLE__
|
||||
#endif // __APPLE__
|
||||
|
||||
// ============================================================================
|
||||
// GpuPipeline implementation
|
||||
// ============================================================================
|
||||
|
||||
bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
SDL_GPUTextureFormat target_format,
|
||||
SDL_GPUTextureFormat offscreen_format) {
|
||||
auto GpuPipeline::init(SDL_GPUDevice* device,
|
||||
SDL_GPUTextureFormat target_format,
|
||||
SDL_GPUTextureFormat offscreen_format) -> bool {
|
||||
SDL_GPUShaderFormat supported = SDL_GetGPUShaderFormats(device);
|
||||
#ifdef __APPLE__
|
||||
if (!(supported & SDL_GPU_SHADERFORMAT_MSL)) {
|
||||
@@ -214,7 +217,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!(supported & SDL_GPU_SHADERFORMAT_SPIRV)) {
|
||||
if ((supported & SDL_GPU_SHADERFORMAT_SPIRV) == 0u) {
|
||||
SDL_Log("GpuPipeline: SPIRV not supported (format mask=%u)", supported);
|
||||
return false;
|
||||
}
|
||||
@@ -224,81 +227,81 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
// Sprite pipeline
|
||||
// ----------------------------------------------------------------
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* sprite_vert = createShader(device, kSpriteVertMSL, "sprite_vs",
|
||||
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* sprite_frag = createShader(device, kSpriteFragMSL, "sprite_fs",
|
||||
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
SDL_GPUShader* sprite_vert = createShader(device, kSpriteVertMSL, "sprite_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* sprite_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#else
|
||||
SDL_GPUShader* sprite_vert = createShaderSPIRV(device, ksprite_vert_spv, ksprite_vert_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* sprite_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
SDL_GPUShader* sprite_vert = createShaderSPIRV(device, ksprite_vert_spv, ksprite_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* sprite_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#endif
|
||||
if (!sprite_vert || !sprite_frag) {
|
||||
if ((sprite_vert == nullptr) || (sprite_frag == nullptr)) {
|
||||
SDL_Log("GpuPipeline: failed to create sprite shaders");
|
||||
if (sprite_vert) SDL_ReleaseGPUShader(device, sprite_vert);
|
||||
if (sprite_frag) SDL_ReleaseGPUShader(device, sprite_frag);
|
||||
if (sprite_vert != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, sprite_vert);
|
||||
}
|
||||
if (sprite_frag != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, sprite_frag);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vertex input: GpuVertex layout
|
||||
SDL_GPUVertexBufferDescription vb_desc = {};
|
||||
vb_desc.slot = 0;
|
||||
vb_desc.pitch = sizeof(GpuVertex);
|
||||
vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||
vb_desc.slot = 0;
|
||||
vb_desc.pitch = sizeof(GpuVertex);
|
||||
vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||
vb_desc.instance_step_rate = 0;
|
||||
|
||||
SDL_GPUVertexAttribute attrs[3] = {};
|
||||
attrs[0].location = 0;
|
||||
std::array<SDL_GPUVertexAttribute, 3> attrs = {};
|
||||
attrs[0].location = 0;
|
||||
attrs[0].buffer_slot = 0;
|
||||
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
attrs[0].offset = static_cast<Uint32>(offsetof(GpuVertex, x));
|
||||
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
attrs[0].offset = static_cast<Uint32>(offsetof(GpuVertex, x));
|
||||
|
||||
attrs[1].location = 1;
|
||||
attrs[1].location = 1;
|
||||
attrs[1].buffer_slot = 0;
|
||||
attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
attrs[1].offset = static_cast<Uint32>(offsetof(GpuVertex, u));
|
||||
attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
attrs[1].offset = static_cast<Uint32>(offsetof(GpuVertex, u));
|
||||
|
||||
attrs[2].location = 2;
|
||||
attrs[2].location = 2;
|
||||
attrs[2].buffer_slot = 0;
|
||||
attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||
attrs[2].offset = static_cast<Uint32>(offsetof(GpuVertex, r));
|
||||
attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||
attrs[2].offset = static_cast<Uint32>(offsetof(GpuVertex, r));
|
||||
|
||||
SDL_GPUVertexInputState vertex_input = {};
|
||||
vertex_input.vertex_buffer_descriptions = &vb_desc;
|
||||
vertex_input.num_vertex_buffers = 1;
|
||||
vertex_input.vertex_attributes = attrs;
|
||||
vertex_input.num_vertex_attributes = 3;
|
||||
vertex_input.num_vertex_buffers = 1;
|
||||
vertex_input.vertex_attributes = attrs.data();
|
||||
vertex_input.num_vertex_attributes = 3;
|
||||
|
||||
// Alpha blend state (SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
|
||||
SDL_GPUColorTargetBlendState blend = {};
|
||||
blend.enable_blend = true;
|
||||
blend.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
||||
blend.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
blend.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
||||
blend.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
blend.enable_color_write_mask = false; // write all channels
|
||||
blend.enable_blend = true;
|
||||
blend.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
||||
blend.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
blend.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
||||
blend.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
blend.enable_color_write_mask = false; // write all channels
|
||||
|
||||
SDL_GPUColorTargetDescription color_target_desc = {};
|
||||
color_target_desc.format = offscreen_format;
|
||||
color_target_desc.format = offscreen_format;
|
||||
color_target_desc.blend_state = blend;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo sprite_pipe_info = {};
|
||||
sprite_pipe_info.vertex_shader = sprite_vert;
|
||||
sprite_pipe_info.fragment_shader = sprite_frag;
|
||||
sprite_pipe_info.vertex_shader = sprite_vert;
|
||||
sprite_pipe_info.fragment_shader = sprite_frag;
|
||||
sprite_pipe_info.vertex_input_state = vertex_input;
|
||||
sprite_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
sprite_pipe_info.target_info.num_color_targets = 1;
|
||||
sprite_pipe_info.target_info.color_target_descriptions = &color_target_desc;
|
||||
sprite_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
sprite_pipe_info.target_info.num_color_targets = 1;
|
||||
sprite_pipe_info.target_info.color_target_descriptions = &color_target_desc;
|
||||
|
||||
sprite_pipeline_ = SDL_CreateGPUGraphicsPipeline(device, &sprite_pipe_info);
|
||||
|
||||
SDL_ReleaseGPUShader(device, sprite_vert);
|
||||
SDL_ReleaseGPUShader(device, sprite_frag);
|
||||
|
||||
if (!sprite_pipeline_) {
|
||||
if (sprite_pipeline_ == nullptr) {
|
||||
SDL_Log("GpuPipeline: sprite pipeline creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -310,59 +313,59 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
// Targets: offscreen (same as sprite pipeline)
|
||||
// ----------------------------------------------------------------
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* ball_vert = createShader(device, kBallInstancedVertMSL, "ball_instanced_vs",
|
||||
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ball_frag = createShader(device, kSpriteFragMSL, "sprite_fs",
|
||||
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
SDL_GPUShader* ball_vert = createShader(device, kBallInstancedVertMSL, "ball_instanced_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ball_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#else
|
||||
SDL_GPUShader* ball_vert = createShaderSPIRV(device, kball_vert_spv, kball_vert_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ball_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
SDL_GPUShader* ball_vert = createShaderSPIRV(device, kball_vert_spv, kball_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ball_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#endif
|
||||
if (!ball_vert || !ball_frag) {
|
||||
if ((ball_vert == nullptr) || (ball_frag == nullptr)) {
|
||||
SDL_Log("GpuPipeline: failed to create ball instanced shaders");
|
||||
if (ball_vert) SDL_ReleaseGPUShader(device, ball_vert);
|
||||
if (ball_frag) SDL_ReleaseGPUShader(device, ball_frag);
|
||||
if (ball_vert != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, ball_vert);
|
||||
}
|
||||
if (ball_frag != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, ball_frag);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vertex input: BallGPUData as per-instance data (step rate = 1 instance)
|
||||
SDL_GPUVertexBufferDescription ball_vb_desc = {};
|
||||
ball_vb_desc.slot = 0;
|
||||
ball_vb_desc.pitch = sizeof(BallGPUData);
|
||||
ball_vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE;
|
||||
ball_vb_desc.slot = 0;
|
||||
ball_vb_desc.pitch = sizeof(BallGPUData);
|
||||
ball_vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE;
|
||||
ball_vb_desc.instance_step_rate = 1;
|
||||
|
||||
SDL_GPUVertexAttribute ball_attrs[3] = {};
|
||||
std::array<SDL_GPUVertexAttribute, 3> ball_attrs = {};
|
||||
// attr 0: center (float2) at offset 0
|
||||
ball_attrs[0].location = 0;
|
||||
ball_attrs[0].location = 0;
|
||||
ball_attrs[0].buffer_slot = 0;
|
||||
ball_attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
ball_attrs[0].offset = static_cast<Uint32>(offsetof(BallGPUData, cx));
|
||||
ball_attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
ball_attrs[0].offset = static_cast<Uint32>(offsetof(BallGPUData, cx));
|
||||
// attr 1: half-size (float2) at offset 8
|
||||
ball_attrs[1].location = 1;
|
||||
ball_attrs[1].location = 1;
|
||||
ball_attrs[1].buffer_slot = 0;
|
||||
ball_attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
ball_attrs[1].offset = static_cast<Uint32>(offsetof(BallGPUData, hw));
|
||||
ball_attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
ball_attrs[1].offset = static_cast<Uint32>(offsetof(BallGPUData, hw));
|
||||
// attr 2: color (float4) at offset 16
|
||||
ball_attrs[2].location = 2;
|
||||
ball_attrs[2].location = 2;
|
||||
ball_attrs[2].buffer_slot = 0;
|
||||
ball_attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||
ball_attrs[2].offset = static_cast<Uint32>(offsetof(BallGPUData, r));
|
||||
ball_attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||
ball_attrs[2].offset = static_cast<Uint32>(offsetof(BallGPUData, r));
|
||||
|
||||
SDL_GPUVertexInputState ball_vertex_input = {};
|
||||
ball_vertex_input.vertex_buffer_descriptions = &ball_vb_desc;
|
||||
ball_vertex_input.num_vertex_buffers = 1;
|
||||
ball_vertex_input.vertex_attributes = ball_attrs;
|
||||
ball_vertex_input.num_vertex_attributes = 3;
|
||||
ball_vertex_input.num_vertex_buffers = 1;
|
||||
ball_vertex_input.vertex_attributes = ball_attrs.data();
|
||||
ball_vertex_input.num_vertex_attributes = 3;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo ball_pipe_info = {};
|
||||
ball_pipe_info.vertex_shader = ball_vert;
|
||||
ball_pipe_info.fragment_shader = ball_frag;
|
||||
ball_pipe_info.vertex_shader = ball_vert;
|
||||
ball_pipe_info.fragment_shader = ball_frag;
|
||||
ball_pipe_info.vertex_input_state = ball_vertex_input;
|
||||
ball_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
ball_pipe_info.target_info.num_color_targets = 1;
|
||||
ball_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
ball_pipe_info.target_info.num_color_targets = 1;
|
||||
ball_pipe_info.target_info.color_target_descriptions = &color_target_desc;
|
||||
|
||||
ball_pipeline_ = SDL_CreateGPUGraphicsPipeline(device, &ball_pipe_info);
|
||||
@@ -370,7 +373,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
SDL_ReleaseGPUShader(device, ball_vert);
|
||||
SDL_ReleaseGPUShader(device, ball_frag);
|
||||
|
||||
if (!ball_pipeline_) {
|
||||
if (ball_pipeline_ == nullptr) {
|
||||
SDL_Log("GpuPipeline: ball instanced pipeline creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -389,20 +392,20 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
// PostFX pipeline
|
||||
// ----------------------------------------------------------------
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs",
|
||||
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs",
|
||||
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* postfx_vert = createShaderSPIRV(device, kpostfx_vert_spv, kpostfx_vert_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* postfx_frag = createShaderSPIRV(device, kpostfx_frag_spv, kpostfx_frag_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* postfx_vert = createShaderSPIRV(device, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* postfx_frag = createShaderSPIRV(device, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#endif
|
||||
if (!postfx_vert || !postfx_frag) {
|
||||
if ((postfx_vert == nullptr) || (postfx_frag == nullptr)) {
|
||||
SDL_Log("GpuPipeline: failed to create postfx shaders");
|
||||
if (postfx_vert) SDL_ReleaseGPUShader(device, postfx_vert);
|
||||
if (postfx_frag) SDL_ReleaseGPUShader(device, postfx_frag);
|
||||
if (postfx_vert != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, postfx_vert);
|
||||
}
|
||||
if (postfx_frag != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, postfx_frag);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -412,17 +415,17 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
no_blend.enable_color_write_mask = false;
|
||||
|
||||
SDL_GPUColorTargetDescription postfx_target_desc = {};
|
||||
postfx_target_desc.format = target_format;
|
||||
postfx_target_desc.format = target_format;
|
||||
postfx_target_desc.blend_state = no_blend;
|
||||
|
||||
SDL_GPUVertexInputState no_input = {};
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo postfx_pipe_info = {};
|
||||
postfx_pipe_info.vertex_shader = postfx_vert;
|
||||
postfx_pipe_info.fragment_shader = postfx_frag;
|
||||
postfx_pipe_info.vertex_shader = postfx_vert;
|
||||
postfx_pipe_info.fragment_shader = postfx_frag;
|
||||
postfx_pipe_info.vertex_input_state = no_input;
|
||||
postfx_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
postfx_pipe_info.target_info.num_color_targets = 1;
|
||||
postfx_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
postfx_pipe_info.target_info.num_color_targets = 1;
|
||||
postfx_pipe_info.target_info.color_target_descriptions = &postfx_target_desc;
|
||||
|
||||
postfx_pipeline_ = SDL_CreateGPUGraphicsPipeline(device, &postfx_pipe_info);
|
||||
@@ -430,7 +433,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
SDL_ReleaseGPUShader(device, postfx_vert);
|
||||
SDL_ReleaseGPUShader(device, postfx_frag);
|
||||
|
||||
if (!postfx_pipeline_) {
|
||||
if (postfx_pipeline_ == nullptr) {
|
||||
SDL_Log("GpuPipeline: postfx pipeline creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -440,55 +443,65 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
}
|
||||
|
||||
void GpuPipeline::destroy(SDL_GPUDevice* device) {
|
||||
if (sprite_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, sprite_pipeline_); sprite_pipeline_ = nullptr; }
|
||||
if (ball_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, ball_pipeline_); ball_pipeline_ = nullptr; }
|
||||
if (postfx_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, postfx_pipeline_); postfx_pipeline_ = nullptr; }
|
||||
if (sprite_pipeline_ != nullptr) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device, sprite_pipeline_);
|
||||
sprite_pipeline_ = nullptr;
|
||||
}
|
||||
if (ball_pipeline_ != nullptr) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device, ball_pipeline_);
|
||||
ball_pipeline_ = nullptr;
|
||||
}
|
||||
if (postfx_pipeline_ != nullptr) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device, postfx_pipeline_);
|
||||
postfx_pipeline_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_GPUShader* GpuPipeline::createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers) {
|
||||
auto GpuPipeline::createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers) -> SDL_GPUShader* {
|
||||
SDL_GPUShaderCreateInfo info = {};
|
||||
info.code = spv_code;
|
||||
info.code_size = spv_size;
|
||||
info.entrypoint = entrypoint;
|
||||
info.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||
info.stage = stage;
|
||||
info.num_samplers = num_samplers;
|
||||
info.code = spv_code;
|
||||
info.code_size = spv_size;
|
||||
info.entrypoint = entrypoint;
|
||||
info.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||
info.stage = stage;
|
||||
info.num_samplers = num_samplers;
|
||||
info.num_storage_textures = 0;
|
||||
info.num_storage_buffers = num_storage_buffers;
|
||||
info.num_uniform_buffers = num_uniform_buffers;
|
||||
info.num_storage_buffers = num_storage_buffers;
|
||||
info.num_uniform_buffers = num_uniform_buffers;
|
||||
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
|
||||
if (!shader)
|
||||
if (shader == nullptr) {
|
||||
SDL_Log("GpuPipeline: SPIRV shader '%s' failed: %s", entrypoint, SDL_GetError());
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
SDL_GPUShader* GpuPipeline::createShader(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers) {
|
||||
auto GpuPipeline::createShader(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers) -> SDL_GPUShader* {
|
||||
SDL_GPUShaderCreateInfo info = {};
|
||||
info.code = reinterpret_cast<const Uint8*>(msl_source);
|
||||
info.code_size = static_cast<size_t>(strlen(msl_source) + 1);
|
||||
info.entrypoint = entrypoint;
|
||||
info.format = SDL_GPU_SHADERFORMAT_MSL;
|
||||
info.stage = stage;
|
||||
info.num_samplers = num_samplers;
|
||||
info.code = reinterpret_cast<const Uint8*>(msl_source);
|
||||
info.code_size = static_cast<size_t>(strlen(msl_source) + 1);
|
||||
info.entrypoint = entrypoint;
|
||||
info.format = SDL_GPU_SHADERFORMAT_MSL;
|
||||
info.stage = stage;
|
||||
info.num_samplers = num_samplers;
|
||||
info.num_storage_textures = 0;
|
||||
info.num_storage_buffers = num_storage_buffers;
|
||||
info.num_uniform_buffers = num_uniform_buffers;
|
||||
info.num_storage_buffers = num_storage_buffers;
|
||||
info.num_uniform_buffers = num_uniform_buffers;
|
||||
|
||||
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
|
||||
if (!shader) {
|
||||
if (shader == nullptr) {
|
||||
SDL_Log("GpuPipeline: shader '%s' failed: %s", entrypoint, SDL_GetError());
|
||||
}
|
||||
return shader;
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
// MSL binding: constant PostFXUniforms& u [[buffer(0)]]
|
||||
// ============================================================================
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength; // 0 = none, 0.8 = default subtle
|
||||
float chroma_strength; // 0 = off, 0.2 = default chromatic aberration
|
||||
float scanline_strength; // 0 = off, 1 = full scanlines
|
||||
float screen_height; // logical render target height (px), for resolution-independent scanlines
|
||||
float vignette_strength; // 0 = none, 0.8 = default subtle
|
||||
float chroma_strength; // 0 = off, 0.2 = default chromatic aberration
|
||||
float scanline_strength; // 0 = off, 1 = full scanlines
|
||||
float screen_height; // logical render target height (px), for resolution-independent scanlines
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
@@ -27,37 +27,37 @@ struct PostFXUniforms {
|
||||
// Accepts PostFXUniforms via fragment uniform buffer slot 0.
|
||||
// ============================================================================
|
||||
class GpuPipeline {
|
||||
public:
|
||||
// target_format: pass SDL_GetGPUSwapchainTextureFormat() result.
|
||||
// offscreen_format: format of the offscreen render target.
|
||||
bool init(SDL_GPUDevice* device,
|
||||
SDL_GPUTextureFormat target_format,
|
||||
SDL_GPUTextureFormat offscreen_format);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
public:
|
||||
// target_format: pass SDL_GetGPUSwapchainTextureFormat() result.
|
||||
// offscreen_format: format of the offscreen render target.
|
||||
bool init(SDL_GPUDevice* device,
|
||||
SDL_GPUTextureFormat target_format,
|
||||
SDL_GPUTextureFormat offscreen_format);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
|
||||
SDL_GPUGraphicsPipeline* spritePipeline() const { return sprite_pipeline_; }
|
||||
SDL_GPUGraphicsPipeline* ballPipeline() const { return ball_pipeline_; }
|
||||
SDL_GPUGraphicsPipeline* postfxPipeline() const { return postfx_pipeline_; }
|
||||
SDL_GPUGraphicsPipeline* spritePipeline() const { return sprite_pipeline_; }
|
||||
SDL_GPUGraphicsPipeline* ballPipeline() const { return ball_pipeline_; }
|
||||
SDL_GPUGraphicsPipeline* postfxPipeline() const { return postfx_pipeline_; }
|
||||
|
||||
private:
|
||||
SDL_GPUShader* createShader(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers = 0);
|
||||
private:
|
||||
static SDL_GPUShader* createShader(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers = 0);
|
||||
|
||||
SDL_GPUShader* createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers = 0);
|
||||
static SDL_GPUShader* createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers = 0);
|
||||
|
||||
SDL_GPUGraphicsPipeline* sprite_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* ball_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* postfx_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* sprite_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* ball_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* postfx_pipeline_ = nullptr;
|
||||
};
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
#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.
|
||||
Uint32 max_verts = static_cast<Uint32>(max_sprites_ + 2) * 4;
|
||||
Uint32 max_verts = static_cast<Uint32>(max_sprites_ + 2) * 4;
|
||||
Uint32 max_indices = static_cast<Uint32>(max_sprites_ + 2) * 6;
|
||||
|
||||
Uint32 vb_size = max_verts * sizeof(GpuVertex);
|
||||
Uint32 vb_size = max_verts * sizeof(GpuVertex);
|
||||
Uint32 ib_size = max_indices * sizeof(uint32_t);
|
||||
|
||||
// Vertex buffer
|
||||
SDL_GPUBufferCreateInfo vb_info = {};
|
||||
vb_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||
vb_info.size = vb_size;
|
||||
vb_info.size = vb_size;
|
||||
vertex_buf_ = SDL_CreateGPUBuffer(device, &vb_info);
|
||||
if (!vertex_buf_) {
|
||||
if (vertex_buf_ == nullptr) {
|
||||
SDL_Log("GpuSpriteBatch: vertex buffer creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -30,9 +31,9 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
||||
// Index buffer
|
||||
SDL_GPUBufferCreateInfo ib_info = {};
|
||||
ib_info.usage = SDL_GPU_BUFFERUSAGE_INDEX;
|
||||
ib_info.size = ib_size;
|
||||
ib_info.size = ib_size;
|
||||
index_buf_ = SDL_CreateGPUBuffer(device, &ib_info);
|
||||
if (!index_buf_) {
|
||||
if (index_buf_ == nullptr) {
|
||||
SDL_Log("GpuSpriteBatch: index buffer creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -41,16 +42,16 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
|
||||
tb_info.size = vb_size;
|
||||
tb_info.size = vb_size;
|
||||
vertex_transfer_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||
if (!vertex_transfer_) {
|
||||
if (vertex_transfer_ == nullptr) {
|
||||
SDL_Log("GpuSpriteBatch: vertex transfer buffer failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
tb_info.size = ib_size;
|
||||
tb_info.size = ib_size;
|
||||
index_transfer_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||
if (!index_transfer_) {
|
||||
if (index_transfer_ == nullptr) {
|
||||
SDL_Log("GpuSpriteBatch: index transfer buffer failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -61,67 +62,84 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
||||
}
|
||||
|
||||
void GpuSpriteBatch::destroy(SDL_GPUDevice* device) {
|
||||
if (!device) return;
|
||||
if (vertex_transfer_) { SDL_ReleaseGPUTransferBuffer(device, vertex_transfer_); vertex_transfer_ = nullptr; }
|
||||
if (index_transfer_) { SDL_ReleaseGPUTransferBuffer(device, index_transfer_); index_transfer_ = nullptr; }
|
||||
if (vertex_buf_) { SDL_ReleaseGPUBuffer(device, vertex_buf_); vertex_buf_ = nullptr; }
|
||||
if (index_buf_) { SDL_ReleaseGPUBuffer(device, index_buf_); index_buf_ = nullptr; }
|
||||
if (device == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (vertex_transfer_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device, vertex_transfer_);
|
||||
vertex_transfer_ = nullptr;
|
||||
}
|
||||
if (index_transfer_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device, index_transfer_);
|
||||
index_transfer_ = nullptr;
|
||||
}
|
||||
if (vertex_buf_ != nullptr) {
|
||||
SDL_ReleaseGPUBuffer(device, vertex_buf_);
|
||||
vertex_buf_ = nullptr;
|
||||
}
|
||||
if (index_buf_ != nullptr) {
|
||||
SDL_ReleaseGPUBuffer(device, index_buf_);
|
||||
index_buf_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void GpuSpriteBatch::beginFrame() {
|
||||
vertices_.clear();
|
||||
indices_.clear();
|
||||
bg_index_count_ = 0;
|
||||
sprite_index_offset_ = 0;
|
||||
sprite_index_count_ = 0;
|
||||
bg_index_count_ = 0;
|
||||
sprite_index_offset_ = 0;
|
||||
sprite_index_count_ = 0;
|
||||
overlay_index_offset_ = 0;
|
||||
overlay_index_count_ = 0;
|
||||
overlay_index_count_ = 0;
|
||||
}
|
||||
|
||||
void GpuSpriteBatch::addBackground(float screen_w, float screen_h,
|
||||
float top_r, float top_g, float top_b,
|
||||
float bot_r, float bot_g, float bot_b) {
|
||||
void GpuSpriteBatch::addBackground(float screen_w, float screen_h, float top_r, float top_g, float top_b, float bot_r, float bot_g, float bot_b) {
|
||||
// Background is the full screen quad, corners:
|
||||
// TL(-1, 1) TR(1, 1) → top color
|
||||
// BL(-1,-1) BR(1,-1) → bottom color
|
||||
// We push it as 4 separate vertices (different colors per row).
|
||||
uint32_t vi = static_cast<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 });
|
||||
vertices_.push_back({-1.0f, 1.0f, 0.0f, 0.0f, top_r, top_g, top_b, 1.0f});
|
||||
// Top-right
|
||||
vertices_.push_back({ 1.0f, 1.0f, 1.0f, 0.0f, top_r, top_g, top_b, 1.0f });
|
||||
vertices_.push_back({1.0f, 1.0f, 1.0f, 0.0f, top_r, top_g, top_b, 1.0f});
|
||||
// Bottom-right
|
||||
vertices_.push_back({ 1.0f, -1.0f, 1.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f });
|
||||
vertices_.push_back({1.0f, -1.0f, 1.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f});
|
||||
// Bottom-left
|
||||
vertices_.push_back({ -1.0f, -1.0f, 0.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f });
|
||||
vertices_.push_back({-1.0f, -1.0f, 0.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f});
|
||||
|
||||
// Two triangles: TL-TR-BR, BR-BL-TL
|
||||
indices_.push_back(vi + 0); indices_.push_back(vi + 1); indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 2); indices_.push_back(vi + 3); indices_.push_back(vi + 0);
|
||||
indices_.push_back(vi + 0);
|
||||
indices_.push_back(vi + 1);
|
||||
indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 3);
|
||||
indices_.push_back(vi + 0);
|
||||
|
||||
bg_index_count_ = 6;
|
||||
bg_index_count_ = 6;
|
||||
sprite_index_offset_ = 6;
|
||||
|
||||
(void)screen_w; (void)screen_h; // unused — bg always covers full NDC
|
||||
(void)screen_w;
|
||||
(void)screen_h; // unused — bg always covers full NDC
|
||||
}
|
||||
|
||||
void GpuSpriteBatch::addSprite(float x, float y, float w, float h,
|
||||
float r, float g, float b, float a,
|
||||
float scale,
|
||||
float screen_w, float screen_h) {
|
||||
void GpuSpriteBatch::addSprite(float x, float y, float w, float h, float r, float g, float b, float a, float scale, float screen_w, float screen_h) {
|
||||
// Apply scale around the sprite centre
|
||||
float scaled_w = w * scale;
|
||||
float scaled_h = h * scale;
|
||||
float offset_x = (w - scaled_w) * 0.5f;
|
||||
float offset_y = (h - scaled_h) * 0.5f;
|
||||
float scaled_w = w * scale;
|
||||
float scaled_h = h * scale;
|
||||
float offset_x = (w - scaled_w) * 0.5f;
|
||||
float offset_y = (h - scaled_h) * 0.5f;
|
||||
|
||||
float px0 = x + offset_x;
|
||||
float py0 = y + offset_y;
|
||||
float px1 = px0 + scaled_w;
|
||||
float py1 = py0 + scaled_h;
|
||||
|
||||
float ndx0, ndy0, ndx1, ndy1;
|
||||
float ndx0;
|
||||
float ndy0;
|
||||
float ndx1;
|
||||
float ndy1;
|
||||
toNDC(px0, py0, screen_w, screen_h, ndx0, ndy0);
|
||||
toNDC(px1, py1, screen_w, screen_h, ndx1, ndy1);
|
||||
|
||||
@@ -133,42 +151,54 @@ void GpuSpriteBatch::addFullscreenOverlay() {
|
||||
// El overlay es un slot reservado fuera del espacio de max_sprites_, igual que el background.
|
||||
// Escribe directamente sin pasar por el guard de pushQuad().
|
||||
overlay_index_offset_ = static_cast<int>(indices_.size());
|
||||
uint32_t 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);
|
||||
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);
|
||||
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_);
|
||||
|
||||
// Upload via copy pass
|
||||
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd_buf);
|
||||
|
||||
SDL_GPUTransferBufferLocation v_src = { vertex_transfer_, 0 };
|
||||
SDL_GPUBufferRegion v_dst = { vertex_buf_, 0, vb_size };
|
||||
SDL_GPUTransferBufferLocation v_src = {vertex_transfer_, 0};
|
||||
SDL_GPUBufferRegion v_dst = {vertex_buf_, 0, vb_size};
|
||||
SDL_UploadToGPUBuffer(copy, &v_src, &v_dst, true /* cycle */);
|
||||
|
||||
SDL_GPUTransferBufferLocation i_src = { index_transfer_, 0 };
|
||||
SDL_GPUBufferRegion i_dst = { index_buf_, 0, ib_size };
|
||||
SDL_GPUTransferBufferLocation i_src = {index_transfer_, 0};
|
||||
SDL_GPUBufferRegion i_dst = {index_buf_, 0, ib_size};
|
||||
SDL_UploadToGPUBuffer(copy, &i_src, &i_dst, true /* cycle */);
|
||||
|
||||
SDL_EndGPUCopyPass(copy);
|
||||
@@ -179,26 +209,28 @@ bool GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cm
|
||||
// Private helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void GpuSpriteBatch::toNDC(float px, float py,
|
||||
float screen_w, float screen_h,
|
||||
float& ndx, float& ndy) const {
|
||||
void GpuSpriteBatch::toNDC(float px, float py, float screen_w, float screen_h, float& ndx, float& ndy) {
|
||||
ndx = (px / screen_w) * 2.0f - 1.0f;
|
||||
ndy = 1.0f - (py / screen_h) * 2.0f;
|
||||
}
|
||||
|
||||
void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1,
|
||||
float u0, float v0, float u1, float v1,
|
||||
float r, float g, float b, float a) {
|
||||
void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1, float u0, float v0, float u1, float v1, float r, float g, float b, float a) {
|
||||
// +1 reserva el slot del background que ya entró sin pasar por este guard.
|
||||
if (vertices_.size() + 4 > static_cast<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 });
|
||||
vertices_.push_back({ ndx1, ndy0, u1, v0, r, g, b, a });
|
||||
vertices_.push_back({ ndx1, ndy1, u1, v1, r, g, b, a });
|
||||
vertices_.push_back({ ndx0, ndy1, u0, v1, r, g, b, a });
|
||||
vertices_.push_back({ndx0, ndy0, u0, v0, r, g, b, a});
|
||||
vertices_.push_back({ndx1, ndy0, u1, v0, r, g, b, a});
|
||||
vertices_.push_back({ndx1, ndy1, u1, v1, r, g, b, a});
|
||||
vertices_.push_back({ndx0, ndy1, u0, v1, r, g, b, a});
|
||||
|
||||
indices_.push_back(vi + 0); indices_.push_back(vi + 1); indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 2); indices_.push_back(vi + 3); indices_.push_back(vi + 0);
|
||||
indices_.push_back(vi + 0);
|
||||
indices_.push_back(vi + 1);
|
||||
indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 3);
|
||||
indices_.push_back(vi + 0);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GpuVertex — 8-float vertex layout sent to the GPU.
|
||||
// Position is in NDC (pre-transformed on CPU), UV in [0,1], color in [0,1].
|
||||
// ---------------------------------------------------------------------------
|
||||
struct GpuVertex {
|
||||
float x, y; // NDC position (−1..1)
|
||||
float u, v; // Texture coords (0..1)
|
||||
float r, g, b, a; // RGBA color (0..1)
|
||||
float x, y; // NDC position (−1..1)
|
||||
float u, v; // Texture coords (0..1)
|
||||
float r, g, b, a; // RGBA color (0..1)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
@@ -25,64 +26,56 @@ struct GpuVertex {
|
||||
// // Then in render pass: bind buffers, draw bg with white tex, draw sprites.
|
||||
// ============================================================================
|
||||
class GpuSpriteBatch {
|
||||
public:
|
||||
// Default maximum sprites (background + UI overlay each count as one sprite)
|
||||
static constexpr int DEFAULT_MAX_SPRITES = 200000;
|
||||
public:
|
||||
// Default maximum sprites (background + UI overlay each count as one sprite)
|
||||
static constexpr int DEFAULT_MAX_SPRITES = 200000;
|
||||
|
||||
bool init(SDL_GPUDevice* device, int max_sprites = DEFAULT_MAX_SPRITES);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
bool init(SDL_GPUDevice* device, int max_sprites = DEFAULT_MAX_SPRITES);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
|
||||
void beginFrame();
|
||||
void beginFrame();
|
||||
|
||||
// Add the full-screen background gradient quad.
|
||||
// top_* and bot_* are RGB in [0,1].
|
||||
void addBackground(float screen_w, float screen_h,
|
||||
float top_r, float top_g, float top_b,
|
||||
float bot_r, float bot_g, float bot_b);
|
||||
// Add the full-screen background gradient quad.
|
||||
// top_* and bot_* are RGB in [0,1].
|
||||
void addBackground(float screen_w, float screen_h, float top_r, float top_g, float top_b, float bot_r, float bot_g, float bot_b);
|
||||
|
||||
// Add a sprite quad (pixel coordinates).
|
||||
// scale: uniform scale around the quad centre.
|
||||
void addSprite(float x, float y, float w, float h,
|
||||
float r, float g, float b, float a,
|
||||
float scale,
|
||||
float screen_w, float screen_h);
|
||||
// Add a sprite quad (pixel coordinates).
|
||||
// scale: uniform scale around the quad centre.
|
||||
void addSprite(float x, float y, float w, float h, float r, float g, float b, float a, float scale, float screen_w, float screen_h);
|
||||
|
||||
// Add a full-screen overlay quad (e.g. UI surface, NDC −1..1).
|
||||
void addFullscreenOverlay();
|
||||
// Add a full-screen overlay quad (e.g. UI surface, NDC −1..1).
|
||||
void addFullscreenOverlay();
|
||||
|
||||
// Upload CPU vectors to GPU buffers via a copy pass.
|
||||
// Returns false if the batch is empty.
|
||||
bool uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf);
|
||||
// Upload CPU vectors to GPU buffers via a copy pass.
|
||||
// Returns false if the batch is empty.
|
||||
bool uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf);
|
||||
|
||||
SDL_GPUBuffer* vertexBuffer() const { return vertex_buf_; }
|
||||
SDL_GPUBuffer* indexBuffer() const { return index_buf_; }
|
||||
int bgIndexCount() const { return bg_index_count_; }
|
||||
int overlayIndexOffset() const { return overlay_index_offset_; }
|
||||
int overlayIndexCount() const { return overlay_index_count_; }
|
||||
int spriteIndexOffset() const { return sprite_index_offset_; }
|
||||
int spriteIndexCount() const { return sprite_index_count_; }
|
||||
bool isEmpty() const { return vertices_.empty(); }
|
||||
SDL_GPUBuffer* vertexBuffer() const { return vertex_buf_; }
|
||||
SDL_GPUBuffer* indexBuffer() const { return index_buf_; }
|
||||
int bgIndexCount() const { return bg_index_count_; }
|
||||
int overlayIndexOffset() const { return overlay_index_offset_; }
|
||||
int overlayIndexCount() const { return overlay_index_count_; }
|
||||
int spriteIndexOffset() const { return sprite_index_offset_; }
|
||||
int spriteIndexCount() const { return sprite_index_count_; }
|
||||
bool isEmpty() const { return vertices_.empty(); }
|
||||
|
||||
private:
|
||||
void toNDC(float px, float py, float screen_w, float screen_h,
|
||||
float& ndx, float& ndy) const;
|
||||
void pushQuad(float ndx0, float ndy0, float ndx1, float ndy1,
|
||||
float u0, float v0, float u1, float v1,
|
||||
float r, float g, float b, float a);
|
||||
private:
|
||||
static void toNDC(float px, float py, float screen_w, float screen_h, float& ndx, float& ndy);
|
||||
void pushQuad(float ndx0, float ndy0, float ndx1, float ndy1, float u0, float v0, float u1, float v1, float r, float g, float b, float a);
|
||||
|
||||
std::vector<GpuVertex> vertices_;
|
||||
std::vector<uint32_t> indices_;
|
||||
std::vector<GpuVertex> vertices_;
|
||||
std::vector<uint32_t> indices_;
|
||||
|
||||
SDL_GPUBuffer* vertex_buf_ = nullptr;
|
||||
SDL_GPUBuffer* index_buf_ = nullptr;
|
||||
SDL_GPUTransferBuffer* vertex_transfer_ = nullptr;
|
||||
SDL_GPUTransferBuffer* index_transfer_ = nullptr;
|
||||
SDL_GPUBuffer* vertex_buf_ = nullptr;
|
||||
SDL_GPUBuffer* index_buf_ = nullptr;
|
||||
SDL_GPUTransferBuffer* vertex_transfer_ = nullptr;
|
||||
SDL_GPUTransferBuffer* index_transfer_ = nullptr;
|
||||
|
||||
int bg_index_count_ = 0;
|
||||
int sprite_index_offset_ = 0;
|
||||
int sprite_index_count_ = 0;
|
||||
int overlay_index_offset_ = 0;
|
||||
int overlay_index_count_ = 0;
|
||||
int bg_index_count_ = 0;
|
||||
int sprite_index_offset_ = 0;
|
||||
int sprite_index_count_ = 0;
|
||||
int overlay_index_offset_ = 0;
|
||||
int overlay_index_count_ = 0;
|
||||
|
||||
int max_sprites_ = DEFAULT_MAX_SPRITES;
|
||||
int max_sprites_ = DEFAULT_MAX_SPRITES;
|
||||
};
|
||||
|
||||
@@ -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,55 +71,65 @@ bool GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool n
|
||||
}
|
||||
|
||||
destroy(device);
|
||||
bool ok = uploadPixels(device, rgba->pixels, rgba->w, rgba->h,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||
if (ok) ok = createSampler(device, nearest);
|
||||
bool ok = uploadPixels(device, rgba->pixels, rgba->w, rgba->h, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||
if (ok) {
|
||||
ok = createSampler(device, nearest);
|
||||
}
|
||||
|
||||
if (need_free) SDL_DestroySurface(rgba);
|
||||
if (need_free) {
|
||||
SDL_DestroySurface(rgba);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h,
|
||||
SDL_GPUTextureFormat format) {
|
||||
auto GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h, SDL_GPUTextureFormat format) -> bool {
|
||||
destroy(device);
|
||||
|
||||
SDL_GPUTextureCreateInfo info = {};
|
||||
info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
info.format = format;
|
||||
info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET
|
||||
| SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
info.width = static_cast<Uint32>(w);
|
||||
info.height = static_cast<Uint32>(h);
|
||||
info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
info.format = format;
|
||||
info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
info.width = static_cast<Uint32>(w);
|
||||
info.height = static_cast<Uint32>(h);
|
||||
info.layer_count_or_depth = 1;
|
||||
info.num_levels = 1;
|
||||
info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
info.num_levels = 1;
|
||||
info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
|
||||
texture_ = SDL_CreateGPUTexture(device, &info);
|
||||
if (!texture_) {
|
||||
if (texture_ == nullptr) {
|
||||
SDL_Log("GpuTexture: createRenderTarget failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
width_ = w;
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
// Render targets are sampled with linear filter (postfx reads them)
|
||||
return createSampler(device, false);
|
||||
}
|
||||
|
||||
bool GpuTexture::createWhite(SDL_GPUDevice* device) {
|
||||
auto GpuTexture::createWhite(SDL_GPUDevice* device) -> bool {
|
||||
destroy(device);
|
||||
// 1×1 white RGBA pixel
|
||||
const Uint8 white[4] = {255, 255, 255, 255};
|
||||
bool ok = uploadPixels(device, white, 1, 1,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||
if (ok) ok = createSampler(device, true);
|
||||
constexpr std::array<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,34 +137,33 @@ void GpuTexture::destroy(SDL_GPUDevice* device) {
|
||||
// Private helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
int w, int h, SDL_GPUTextureFormat format) {
|
||||
auto GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels, int w, int h, SDL_GPUTextureFormat format) -> bool {
|
||||
// Create GPU texture
|
||||
SDL_GPUTextureCreateInfo tex_info = {};
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tex_info.format = format;
|
||||
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
tex_info.width = static_cast<Uint32>(w);
|
||||
tex_info.height = static_cast<Uint32>(h);
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tex_info.format = format;
|
||||
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
tex_info.width = static_cast<Uint32>(w);
|
||||
tex_info.height = static_cast<Uint32>(h);
|
||||
tex_info.layer_count_or_depth = 1;
|
||||
tex_info.num_levels = 1;
|
||||
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
tex_info.num_levels = 1;
|
||||
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
|
||||
texture_ = SDL_CreateGPUTexture(device, &tex_info);
|
||||
if (!texture_) {
|
||||
if (texture_ == nullptr) {
|
||||
SDL_Log("GpuTexture: SDL_CreateGPUTexture failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create transfer buffer and upload pixels
|
||||
Uint32 data_size = static_cast<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;
|
||||
tb_info.size = data_size;
|
||||
|
||||
SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||
if (!transfer) {
|
||||
if (transfer == nullptr) {
|
||||
SDL_Log("GpuTexture: transfer buffer creation failed: %s", SDL_GetError());
|
||||
SDL_ReleaseGPUTexture(device, texture_);
|
||||
texture_ = nullptr;
|
||||
@@ -151,7 +171,7 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
}
|
||||
|
||||
void* mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
|
||||
if (!mapped) {
|
||||
if (mapped == nullptr) {
|
||||
SDL_Log("GpuTexture: map failed: %s", SDL_GetError());
|
||||
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
||||
SDL_ReleaseGPUTexture(device, texture_);
|
||||
@@ -167,14 +187,14 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
|
||||
SDL_GPUTextureTransferInfo src = {};
|
||||
src.transfer_buffer = transfer;
|
||||
src.offset = 0;
|
||||
src.pixels_per_row = static_cast<Uint32>(w);
|
||||
src.rows_per_layer = static_cast<Uint32>(h);
|
||||
src.offset = 0;
|
||||
src.pixels_per_row = static_cast<Uint32>(w);
|
||||
src.rows_per_layer = static_cast<Uint32>(h);
|
||||
|
||||
SDL_GPUTextureRegion dst = {};
|
||||
dst.texture = texture_;
|
||||
dst.texture = texture_;
|
||||
dst.mip_level = 0;
|
||||
dst.layer = 0;
|
||||
dst.layer = 0;
|
||||
dst.x = dst.y = dst.z = 0;
|
||||
dst.w = static_cast<Uint32>(w);
|
||||
dst.h = static_cast<Uint32>(h);
|
||||
@@ -185,22 +205,22 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
|
||||
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
||||
width_ = w;
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) {
|
||||
auto GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) -> bool {
|
||||
SDL_GPUSamplerCreateInfo info = {};
|
||||
info.min_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
|
||||
info.mag_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
|
||||
info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
||||
info.min_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
|
||||
info.mag_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
|
||||
info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
||||
info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
|
||||
sampler_ = SDL_CreateGPUSampler(device, &info);
|
||||
if (!sampler_) {
|
||||
if (sampler_ == nullptr) {
|
||||
SDL_Log("GpuTexture: SDL_CreateGPUSampler failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
#include <SDL3/SDL_surface.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
// ============================================================================
|
||||
@@ -9,40 +10,38 @@
|
||||
// Handles sprite textures, render targets, and the 1×1 white utility texture.
|
||||
// ============================================================================
|
||||
class GpuTexture {
|
||||
public:
|
||||
GpuTexture() = default;
|
||||
~GpuTexture() = default;
|
||||
public:
|
||||
GpuTexture() = default;
|
||||
~GpuTexture() = default;
|
||||
|
||||
// Load from resource path (pack or disk) using stb_image.
|
||||
bool fromFile(SDL_GPUDevice* device, const std::string& file_path);
|
||||
// Load from resource path (pack or disk) using stb_image.
|
||||
bool fromFile(SDL_GPUDevice* device, const std::string& file_path);
|
||||
|
||||
// Upload pixel data from an SDL_Surface to a new GPU texture + sampler.
|
||||
// Uses nearest-neighbor filter for sprite pixel-perfect look.
|
||||
bool fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest = true);
|
||||
// Upload pixel data from an SDL_Surface to a new GPU texture + sampler.
|
||||
// Uses nearest-neighbor filter for sprite pixel-perfect look.
|
||||
bool fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest = true);
|
||||
|
||||
// Create an offscreen render target (COLOR_TARGET | SAMPLER usage).
|
||||
bool createRenderTarget(SDL_GPUDevice* device, int w, int h,
|
||||
SDL_GPUTextureFormat format);
|
||||
// Create an offscreen render target (COLOR_TARGET | SAMPLER usage).
|
||||
bool createRenderTarget(SDL_GPUDevice* device, int w, int h, SDL_GPUTextureFormat format);
|
||||
|
||||
// Create a 1×1 opaque white texture (used for untextured geometry).
|
||||
bool createWhite(SDL_GPUDevice* device);
|
||||
// Create a 1×1 opaque white texture (used for untextured geometry).
|
||||
bool createWhite(SDL_GPUDevice* device);
|
||||
|
||||
// Release GPU resources.
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
// Release GPU resources.
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
|
||||
SDL_GPUTexture* texture() const { return texture_; }
|
||||
SDL_GPUSampler* sampler() const { return sampler_; }
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
bool isValid() const { return texture_ != nullptr; }
|
||||
SDL_GPUTexture* texture() const { return texture_; }
|
||||
SDL_GPUSampler* sampler() const { return sampler_; }
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
bool isValid() const { return texture_ != nullptr; }
|
||||
|
||||
private:
|
||||
bool uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
int w, int h, SDL_GPUTextureFormat format);
|
||||
bool createSampler(SDL_GPUDevice* device, bool nearest);
|
||||
private:
|
||||
bool uploadPixels(SDL_GPUDevice* device, const void* pixels, int w, int h, SDL_GPUTextureFormat format);
|
||||
bool createSampler(SDL_GPUDevice* device, bool nearest);
|
||||
|
||||
SDL_GPUTexture* texture_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
SDL_GPUTexture* texture_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
};
|
||||
|
||||
@@ -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 <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()) {
|
||||
@@ -105,23 +106,21 @@ bool InputHandler::processEvents(Engine& engine) {
|
||||
|
||||
// Toggle Modo Boids (comportamiento de enjambre)
|
||||
case SDLK_B:
|
||||
engine.toggleBoidsMode();
|
||||
engine.toggleBoidsMode();
|
||||
break;
|
||||
|
||||
// Ciclar temas de color (movido de B a C)
|
||||
case SDLK_C:
|
||||
{
|
||||
// Detectar si Shift está presionado
|
||||
SDL_Keymod modstate = SDL_GetModState();
|
||||
if (modstate & SDL_KMOD_SHIFT) {
|
||||
// Shift+C: Ciclar hacia atrás (tema anterior)
|
||||
engine.cycleTheme(false);
|
||||
} else {
|
||||
// C solo: Ciclar hacia adelante (tema siguiente)
|
||||
engine.cycleTheme(true);
|
||||
}
|
||||
case SDLK_C: {
|
||||
// Detectar si Shift está presionado
|
||||
SDL_Keymod modstate = SDL_GetModState();
|
||||
if ((modstate & SDL_KMOD_SHIFT) != 0u) {
|
||||
// Shift+C: Ciclar hacia atrás (tema anterior)
|
||||
engine.cycleTheme(false);
|
||||
} else {
|
||||
// C solo: Ciclar hacia adelante (tema siguiente)
|
||||
engine.cycleTheme(true);
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
|
||||
// Temas de colores con teclado numérico (con transición suave)
|
||||
case SDLK_KP_1:
|
||||
@@ -233,25 +232,37 @@ bool InputHandler::processEvents(Engine& engine) {
|
||||
|
||||
// Controles de zoom dinámico (solo si no estamos en fullscreen)
|
||||
case SDLK_F1:
|
||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
else engine.handleZoomOut();
|
||||
if (engine.isKioskMode()) {
|
||||
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
} else {
|
||||
engine.handleZoomOut();
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_F2:
|
||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
else engine.handleZoomIn();
|
||||
if (engine.isKioskMode()) {
|
||||
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
} else {
|
||||
engine.handleZoomIn();
|
||||
}
|
||||
break;
|
||||
|
||||
// Control de pantalla completa
|
||||
case SDLK_F3:
|
||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
else engine.toggleFullscreen();
|
||||
if (engine.isKioskMode()) {
|
||||
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
} else {
|
||||
engine.toggleFullscreen();
|
||||
}
|
||||
break;
|
||||
|
||||
// Modo real fullscreen (cambia resolución interna)
|
||||
case SDLK_F4:
|
||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
else engine.toggleRealFullscreen();
|
||||
if (engine.isKioskMode()) {
|
||||
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
} else {
|
||||
engine.toggleRealFullscreen();
|
||||
}
|
||||
break;
|
||||
|
||||
// Toggle PostFX activo/inactivo
|
||||
@@ -266,19 +277,25 @@ bool InputHandler::processEvents(Engine& engine) {
|
||||
|
||||
// Redimensionar campo de juego (tamaño lógico + físico)
|
||||
case SDLK_F7:
|
||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
else engine.fieldSizeDown();
|
||||
if (engine.isKioskMode()) {
|
||||
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
} else {
|
||||
engine.fieldSizeDown();
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_F8:
|
||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
else engine.fieldSizeUp();
|
||||
if (engine.isKioskMode()) {
|
||||
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||
} else {
|
||||
engine.fieldSizeUp();
|
||||
}
|
||||
break;
|
||||
|
||||
// Toggle Modo DEMO COMPLETO (auto-play) o Pausar tema dinámico (Shift+D)
|
||||
case SDLK_D:
|
||||
// Shift+D = Pausar tema dinámico
|
||||
if (event.key.mod & SDL_KMOD_SHIFT) {
|
||||
if ((event.key.mod & SDL_KMOD_SHIFT) != 0u) {
|
||||
engine.pauseDynamicTheme();
|
||||
} else {
|
||||
// D sin Shift = Toggle DEMO ↔ SANDBOX
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -25,62 +25,62 @@ class ResourcePack;
|
||||
* }
|
||||
*/
|
||||
class ResourceManager {
|
||||
public:
|
||||
/**
|
||||
* Inicializa el sistema de recursos empaquetados
|
||||
* Debe llamarse una única vez al inicio del programa
|
||||
*
|
||||
* @param packFilePath Ruta al archivo .pack (ej: "resources.pack")
|
||||
* @return true si el pack se cargó correctamente, false si no existe (fallback a disco)
|
||||
*/
|
||||
static bool init(const std::string& packFilePath);
|
||||
public:
|
||||
/**
|
||||
* Inicializa el sistema de recursos empaquetados
|
||||
* Debe llamarse una única vez al inicio del programa
|
||||
*
|
||||
* @param packFilePath Ruta al archivo .pack (ej: "resources.pack")
|
||||
* @return true si el pack se cargó correctamente, false si no existe (fallback a disco)
|
||||
*/
|
||||
static bool init(const std::string& pack_file_path);
|
||||
|
||||
/**
|
||||
* Libera el sistema de recursos
|
||||
* Opcional - se llama automáticamente al cerrar el programa
|
||||
*/
|
||||
static void shutdown();
|
||||
/**
|
||||
* Libera el sistema de recursos
|
||||
* Opcional - se llama automáticamente al cerrar el programa
|
||||
*/
|
||||
static void shutdown();
|
||||
|
||||
/**
|
||||
* Carga un recurso desde el pack (o disco si no existe pack)
|
||||
*
|
||||
* @param resourcePath Ruta relativa del recurso (ej: "textures/ball.png")
|
||||
* @param data [out] Puntero donde se almacenará el buffer (debe liberar con delete[])
|
||||
* @param size [out] Tamaño del buffer en bytes
|
||||
* @return true si se cargó correctamente, false si falla
|
||||
*/
|
||||
static bool loadResource(const std::string& resourcePath, unsigned char*& data, size_t& size);
|
||||
/**
|
||||
* Carga un recurso desde el pack (o disco si no existe pack)
|
||||
*
|
||||
* @param resourcePath Ruta relativa del recurso (ej: "textures/ball.png")
|
||||
* @param data [out] Puntero donde se almacenará el buffer (debe liberar con delete[])
|
||||
* @param size [out] Tamaño del buffer en bytes
|
||||
* @return true si se cargó correctamente, false si falla
|
||||
*/
|
||||
static bool loadResource(const std::string& resource_path, unsigned char*& data, size_t& size);
|
||||
|
||||
/**
|
||||
* Verifica si el pack está cargado
|
||||
* @return true si hay un pack cargado, false si se usa disco
|
||||
*/
|
||||
static bool isPackLoaded();
|
||||
/**
|
||||
* Verifica si el pack está cargado
|
||||
* @return true si hay un pack cargado, false si se usa disco
|
||||
*/
|
||||
static bool isPackLoaded();
|
||||
|
||||
/**
|
||||
* Obtiene la lista de recursos disponibles en el pack
|
||||
* @return Vector con las rutas de todos los recursos, vacío si no hay pack
|
||||
*/
|
||||
static std::vector<std::string> getResourceList();
|
||||
/**
|
||||
* Obtiene la lista de recursos disponibles en el pack
|
||||
* @return Vector con las rutas de todos los recursos, vacío si no hay pack
|
||||
*/
|
||||
static std::vector<std::string> getResourceList();
|
||||
|
||||
/**
|
||||
* Obtiene el número de recursos en el pack
|
||||
* @return Número de recursos, 0 si no hay pack
|
||||
*/
|
||||
static size_t getResourceCount();
|
||||
/**
|
||||
* Obtiene el número de recursos en el pack
|
||||
* @return Número de recursos, 0 si no hay pack
|
||||
*/
|
||||
static size_t getResourceCount();
|
||||
|
||||
private:
|
||||
// Constructor privado (singleton)
|
||||
ResourceManager() = default;
|
||||
~ResourceManager() = default;
|
||||
private:
|
||||
// Constructor privado (singleton)
|
||||
ResourceManager() = default;
|
||||
~ResourceManager() = default;
|
||||
|
||||
// Deshabilitar copia y asignación
|
||||
ResourceManager(const ResourceManager&) = delete;
|
||||
ResourceManager& operator=(const ResourceManager&) = delete;
|
||||
// Deshabilitar copia y asignación
|
||||
ResourceManager(const ResourceManager&) = delete;
|
||||
ResourceManager& operator=(const ResourceManager&) = delete;
|
||||
|
||||
// Instancia del pack (nullptr si no está cargado)
|
||||
static ResourcePack* resourcePack_;
|
||||
// Instancia del pack (nullptr si no está cargado)
|
||||
static ResourcePack* resourcePack_;
|
||||
|
||||
// Caché en RAM para evitar I/O repetido en el bucle principal
|
||||
static std::map<std::string, std::vector<unsigned char>> cache_;
|
||||
// Caché en RAM para evitar I/O repetido en el bucle principal
|
||||
static std::map<std::string, std::vector<unsigned char>> cache_;
|
||||
};
|
||||
|
||||
@@ -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/");
|
||||
|
||||
@@ -13,51 +13,51 @@
|
||||
* único y ofuscado. Proporciona fallback automático a carpeta data/ si no existe pack.
|
||||
*/
|
||||
class ResourcePack {
|
||||
public:
|
||||
ResourcePack();
|
||||
~ResourcePack();
|
||||
public:
|
||||
ResourcePack();
|
||||
~ResourcePack();
|
||||
|
||||
// Empaquetado (usado por herramienta pack_resources)
|
||||
bool addDirectory(const std::string& dirPath, const std::string& prefix = "");
|
||||
bool savePack(const std::string& packFilePath);
|
||||
// Empaquetado (usado por herramienta pack_resources)
|
||||
bool addDirectory(const std::string& dir_path, const std::string& prefix = "");
|
||||
bool savePack(const std::string& pack_file_path);
|
||||
|
||||
// Desempaquetado (usado por el juego)
|
||||
bool loadPack(const std::string& packFilePath);
|
||||
// Desempaquetado (usado por el juego)
|
||||
bool loadPack(const std::string& pack_file_path);
|
||||
|
||||
// Carga de recursos individuales
|
||||
struct ResourceData {
|
||||
unsigned char* data;
|
||||
size_t size;
|
||||
};
|
||||
ResourceData loadResource(const std::string& resourcePath);
|
||||
// Carga de recursos individuales
|
||||
struct ResourceData {
|
||||
unsigned char* data;
|
||||
size_t size;
|
||||
};
|
||||
ResourceData loadResource(const std::string& resource_path);
|
||||
|
||||
// Utilidades
|
||||
std::vector<std::string> getResourceList() const;
|
||||
size_t getResourceCount() const;
|
||||
void clear();
|
||||
// Utilidades
|
||||
std::vector<std::string> getResourceList() const;
|
||||
size_t getResourceCount() const;
|
||||
void clear();
|
||||
|
||||
private:
|
||||
// Header del pack (12 bytes)
|
||||
struct PackHeader {
|
||||
char magic[4]; // "VBE3"
|
||||
uint32_t version; // Versión del formato (1)
|
||||
uint32_t fileCount; // Número de archivos empaquetados
|
||||
};
|
||||
private:
|
||||
// Header del pack (12 bytes)
|
||||
struct PackHeader {
|
||||
char magic[4]; // "VBE3"
|
||||
uint32_t version; // Versión del formato (1)
|
||||
uint32_t fileCount; // Número de archivos empaquetados
|
||||
};
|
||||
|
||||
// Índice de un recurso (variable length)
|
||||
struct ResourceEntry {
|
||||
std::string path; // Ruta relativa del recurso
|
||||
uint32_t offset; // Offset en el archivo pack
|
||||
uint32_t size; // Tamaño en bytes
|
||||
uint32_t checksum; // Checksum simple (XOR de bytes)
|
||||
};
|
||||
// Índice de un recurso (variable length)
|
||||
struct ResourceEntry {
|
||||
std::string path; // Ruta relativa del recurso
|
||||
uint32_t offset; // Offset en el archivo pack
|
||||
uint32_t size; // Tamaño en bytes
|
||||
uint32_t checksum; // Checksum simple (XOR de bytes)
|
||||
};
|
||||
|
||||
// Datos internos
|
||||
std::map<std::string, ResourceEntry> resources_;
|
||||
std::ifstream packFile_;
|
||||
bool isLoaded_;
|
||||
// Datos internos
|
||||
std::map<std::string, ResourceEntry> resources_;
|
||||
std::ifstream packFile_;
|
||||
bool isLoaded_;
|
||||
|
||||
// Funciones auxiliares
|
||||
uint32_t calculateChecksum(const unsigned char* data, size_t size);
|
||||
std::string normalizePath(const std::string& path);
|
||||
// Funciones auxiliares
|
||||
static uint32_t calculateChecksum(const unsigned char* data, size_t size);
|
||||
static std::string normalizePath(const std::string& path);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
#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;
|
||||
@@ -25,15 +28,15 @@ void AtomShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
||||
int num_orbits = static_cast<int>(ATOM_NUM_ORBITS);
|
||||
|
||||
// Calcular cuántos puntos para núcleo vs órbitas
|
||||
int nucleus_points = (num_points_ < 10) ? 1 : (num_points_ / 10); // 10% para núcleo
|
||||
if (nucleus_points < 1) nucleus_points = 1;
|
||||
int nucleus_points = (num_points_ < 10) ? 1 : (num_points_ / 10); // 10% para núcleo
|
||||
nucleus_points = std::max(nucleus_points, 1);
|
||||
|
||||
// Si estamos en el núcleo
|
||||
if (index < nucleus_points) {
|
||||
// Distribuir puntos en esfera pequeña (núcleo)
|
||||
float t = static_cast<float>(index) / static_cast<float>(nucleus_points);
|
||||
float phi = acosf(1.0f - 2.0f * t);
|
||||
float theta = PI * 2.0f * t * 3.61803398875f; // Golden ratio
|
||||
float phi = acosf(1.0f - (2.0f * t));
|
||||
float theta = PI * 2.0f * t * 3.61803398875f; // Golden ratio
|
||||
|
||||
float x_nuc = nucleus_radius_ * cosf(theta) * sinf(phi);
|
||||
float y_nuc = nucleus_radius_ * sinf(theta) * sinf(phi);
|
||||
@@ -51,16 +54,18 @@ void AtomShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
||||
// Puntos restantes: distribuir en órbitas
|
||||
int orbit_points = num_points_ - nucleus_points;
|
||||
int points_per_orbit = orbit_points / num_orbits;
|
||||
if (points_per_orbit < 1) points_per_orbit = 1;
|
||||
points_per_orbit = std::max(points_per_orbit, 1);
|
||||
|
||||
int orbit_index = (index - nucleus_points) / points_per_orbit;
|
||||
if (orbit_index >= num_orbits) orbit_index = num_orbits - 1;
|
||||
if (orbit_index >= num_orbits) {
|
||||
orbit_index = num_orbits - 1;
|
||||
}
|
||||
|
||||
int point_in_orbit = (index - nucleus_points) % points_per_orbit;
|
||||
|
||||
// Ángulo del electrón en su órbita
|
||||
float electron_angle = (static_cast<float>(point_in_orbit) / static_cast<float>(points_per_orbit)) * 2.0f * PI;
|
||||
electron_angle += orbit_phase_; // Añadir rotación animada
|
||||
electron_angle += orbit_phase_; // Añadir rotación animada
|
||||
|
||||
// Inclinación del plano orbital (cada órbita en ángulo diferente)
|
||||
float orbit_tilt = (static_cast<float>(orbit_index) / static_cast<float>(num_orbits)) * PI;
|
||||
@@ -73,21 +78,21 @@ void AtomShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
||||
// Inclinar el plano orbital (rotación en eje X local)
|
||||
float cos_tilt = cosf(orbit_tilt);
|
||||
float sin_tilt = sinf(orbit_tilt);
|
||||
float y_tilted = y_local * cos_tilt - z_local * sin_tilt;
|
||||
float z_tilted = y_local * sin_tilt + z_local * cos_tilt;
|
||||
float y_tilted = (y_local * cos_tilt) - (z_local * sin_tilt);
|
||||
float z_tilted = (y_local * sin_tilt) + (z_local * cos_tilt);
|
||||
|
||||
// Aplicar rotación global del átomo (eje Y)
|
||||
float cos_y = cosf(angle_y_);
|
||||
float sin_y = sinf(angle_y_);
|
||||
float x_rot = x_local * cos_y - z_tilted * sin_y;
|
||||
float z_rot = x_local * sin_y + z_tilted * cos_y;
|
||||
float x_rot = (x_local * cos_y) - (z_tilted * sin_y);
|
||||
float z_rot = (x_local * sin_y) + (z_tilted * cos_y);
|
||||
|
||||
x = x_rot;
|
||||
y = y_tilted;
|
||||
z = z_rot;
|
||||
}
|
||||
|
||||
float AtomShape::getScaleFactor(float screen_height) const {
|
||||
auto AtomShape::getScaleFactor(float screen_height) const -> float {
|
||||
// Factor de escala para física: proporcional al radio de órbita
|
||||
// Radio órbita base = 72px (0.30 * 240px en resolución 320x240)
|
||||
const float BASE_RADIUS = 72.0f;
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
// Comportamiento: Núcleo estático + electrones orbitando en planos inclinados
|
||||
// Efecto: Modelo atómico clásico Bohr
|
||||
class AtomShape : public Shape {
|
||||
private:
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación global en eje Y (rad)
|
||||
float orbit_phase_ = 0.0f; // Fase de rotación de electrones (rad)
|
||||
float nucleus_radius_ = 0.0f; // Radio del núcleo central (píxeles)
|
||||
float orbit_radius_ = 0.0f; // Radio de las órbitas (píxeles)
|
||||
int num_points_ = 0; // Cantidad total de puntos
|
||||
private:
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación global en eje Y (rad)
|
||||
float orbit_phase_ = 0.0f; // Fase de rotación de electrones (rad)
|
||||
float nucleus_radius_ = 0.0f; // Radio del núcleo central (píxeles)
|
||||
float orbit_radius_ = 0.0f; // Radio de las órbitas (píxeles)
|
||||
int num_points_ = 0; // Cantidad total de puntos
|
||||
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "ATOM"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "ATOM"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
@@ -10,28 +11,28 @@
|
||||
// - 27+ pelotas: Grid volumétrico 3D uniforme
|
||||
// Comportamiento: Rotación simultánea en ejes X, Y, Z (efecto Rubik)
|
||||
class CubeShape : public Shape {
|
||||
private:
|
||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad)
|
||||
float size_ = 0.0f; // Mitad del lado del cubo (píxeles)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
private:
|
||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad)
|
||||
float size_ = 0.0f; // Mitad del lado del cubo (píxeles)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
|
||||
// Posiciones base 3D (sin rotar) - se calculan en generatePoints()
|
||||
std::vector<float> base_x_;
|
||||
std::vector<float> base_y_;
|
||||
std::vector<float> base_z_;
|
||||
// Posiciones base 3D (sin rotar) - se calculan en generatePoints()
|
||||
std::vector<float> base_x_;
|
||||
std::vector<float> base_y_;
|
||||
std::vector<float> base_z_;
|
||||
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "CUBE"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "CUBE"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
|
||||
private:
|
||||
// Métodos auxiliares para distribución de puntos
|
||||
void generateVertices(); // 8 vértices
|
||||
void generateVerticesAndCenters(); // 26 puntos (vértices + caras + aristas)
|
||||
void generateVolumetricGrid(); // Grid 3D para muchas pelotas
|
||||
private:
|
||||
// Métodos auxiliares para distribución de puntos
|
||||
void generateVertices(); // 8 vértices
|
||||
void generateVerticesAndCenters(); // 26 puntos (vértices + caras + aristas)
|
||||
void generateVolumetricGrid(); // Grid 3D para muchas pelotas
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,23 +6,23 @@
|
||||
// Comportamiento: Superficie cilíndrica con rotación en eje Y + tumbling ocasional en X/Z
|
||||
// Ecuaciones: x = r*cos(u), y = v, z = r*sin(u)
|
||||
class CylinderShape : public Shape {
|
||||
private:
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (tumbling ocasional)
|
||||
float radius_ = 0.0f; // Radio del cilindro (píxeles)
|
||||
float height_ = 0.0f; // Altura del cilindro (píxeles)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
private:
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (tumbling ocasional)
|
||||
float radius_ = 0.0f; // Radio del cilindro (píxeles)
|
||||
float height_ = 0.0f; // Altura del cilindro (píxeles)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
|
||||
// Sistema de tumbling ocasional
|
||||
float tumble_timer_ = 0.0f; // Temporizador para próximo tumble
|
||||
float tumble_duration_ = 0.0f; // Duración del tumble actual
|
||||
bool is_tumbling_ = false; // ¿Estamos en modo tumble?
|
||||
float tumble_target_ = 0.0f; // Ángulo objetivo del tumble
|
||||
// Sistema de tumbling ocasional
|
||||
float tumble_timer_ = 0.0f; // Temporizador para próximo tumble
|
||||
float tumble_duration_ = 0.0f; // Duración del tumble actual
|
||||
bool is_tumbling_ = false; // ¿Estamos en modo tumble?
|
||||
float tumble_target_ = 0.0f; // Ángulo objetivo del tumble
|
||||
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "CYLINDER"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "CYLINDER"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
// Comportamiento: Rotación en eje Y + animación de fase vertical
|
||||
// Ecuaciones: x = r*cos(t), y = pitch*t + phase, z = r*sin(t)
|
||||
class HelixShape : public Shape {
|
||||
private:
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float phase_offset_ = 0.0f; // Offset de fase para animación vertical (rad)
|
||||
float radius_ = 0.0f; // Radio de la espiral (píxeles)
|
||||
float pitch_ = 0.0f; // Separación vertical entre vueltas (píxeles)
|
||||
float total_height_ = 0.0f; // Altura total de la espiral (píxeles)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
private:
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float phase_offset_ = 0.0f; // Offset de fase para animación vertical (rad)
|
||||
float radius_ = 0.0f; // Radio de la espiral (píxeles)
|
||||
float pitch_ = 0.0f; // Separación vertical entre vueltas (píxeles)
|
||||
float total_height_ = 0.0f; // Altura total de la espiral (píxeles)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "HELIX"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "HELIX"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
// Comportamiento: 12 vértices distribuidos uniformemente con rotación triple
|
||||
// Geometría: Basado en proporción áurea (golden ratio)
|
||||
class IcosahedronShape : public Shape {
|
||||
private:
|
||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad)
|
||||
float radius_ = 0.0f; // Radio de la esfera circunscrita (píxeles)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
private:
|
||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad)
|
||||
float radius_ = 0.0f; // Radio de la esfera circunscrita (píxeles)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
|
||||
// Helper para aplicar rotaciones triple XYZ
|
||||
void applyRotations(float x_in, float y_in, float z_in, float& x_out, float& y_out, float& z_out) const;
|
||||
// Helper para aplicar rotaciones triple XYZ
|
||||
void applyRotations(float x_in, float y_in, float z_in, float& x_out, float& y_out, float& z_out) const;
|
||||
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "ICOSAHEDRON"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "ICOSAHEDRON"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,21 +6,21 @@
|
||||
// Comportamiento: Curva paramétrica 3D con rotación global y animación de fase
|
||||
// Ecuaciones: x(t) = A*sin(freq_x*t + phase_x), y(t) = A*sin(freq_y*t), z(t) = A*sin(freq_z*t + phase_z)
|
||||
class LissajousShape : public Shape {
|
||||
private:
|
||||
float freq_x_ = 0.0f; // Frecuencia en eje X
|
||||
float freq_y_ = 0.0f; // Frecuencia en eje Y
|
||||
float freq_z_ = 0.0f; // Frecuencia en eje Z
|
||||
float phase_x_ = 0.0f; // Desfase X (animado)
|
||||
float phase_z_ = 0.0f; // Desfase Z (animado)
|
||||
float rotation_x_ = 0.0f; // Rotación global en eje X (rad)
|
||||
float rotation_y_ = 0.0f; // Rotación global en eje Y (rad)
|
||||
float amplitude_ = 0.0f; // Amplitud de la curva (píxeles)
|
||||
int num_points_ = 0; // Cantidad total de puntos
|
||||
private:
|
||||
float freq_x_ = 0.0f; // Frecuencia en eje X
|
||||
float freq_y_ = 0.0f; // Frecuencia en eje Y
|
||||
float freq_z_ = 0.0f; // Frecuencia en eje Z
|
||||
float phase_x_ = 0.0f; // Desfase X (animado)
|
||||
float phase_z_ = 0.0f; // Desfase Z (animado)
|
||||
float rotation_x_ = 0.0f; // Rotación global en eje X (rad)
|
||||
float rotation_y_ = 0.0f; // Rotación global en eje Y (rad)
|
||||
float amplitude_ = 0.0f; // Amplitud de la curva (píxeles)
|
||||
int num_points_ = 0; // Cantidad total de puntos
|
||||
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "LISSAJOUS"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "LISSAJOUS"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -68,10 +73,10 @@ void PNGShape::detectEdges() {
|
||||
is_edge = true; // Bordes de la imagen
|
||||
} else {
|
||||
// Verificar 4 vecinos
|
||||
if (!pixel_data_[idx - 1] || // Izquierda
|
||||
!pixel_data_[idx + 1] || // Derecha
|
||||
!pixel_data_[idx - image_width_] || // Arriba
|
||||
!pixel_data_[idx + image_width_]) { // Abajo
|
||||
if (!pixel_data_[idx - 1] || // Izquierda
|
||||
!pixel_data_[idx + 1] || // Derecha
|
||||
!pixel_data_[idx - image_width_] || // Arriba
|
||||
!pixel_data_[idx + image_width_]) { // Abajo
|
||||
is_edge = true;
|
||||
}
|
||||
}
|
||||
@@ -90,7 +95,7 @@ void PNGShape::floodFill() {
|
||||
|
||||
for (int y = 0; y < image_height_; y++) {
|
||||
for (int x = 0; x < image_width_; x++) {
|
||||
int idx = y * image_width_ + x;
|
||||
int idx = (y * image_width_) + x;
|
||||
if (pixel_data_[idx]) {
|
||||
filled_points_.push_back({static_cast<float>(x), static_cast<float>(y)});
|
||||
}
|
||||
@@ -114,8 +119,8 @@ void PNGShape::generatePoints(int num_points, float screen_width, float screen_h
|
||||
num_layers_ = PNG_NUM_EXTRUSION_LAYERS;
|
||||
|
||||
// Generar AMBOS conjuntos de puntos (relleno Y bordes)
|
||||
floodFill(); // Generar filled_points_
|
||||
detectEdges(); // Generar edge_points_
|
||||
floodFill(); // Generar filled_points_
|
||||
detectEdges(); // Generar edge_points_
|
||||
|
||||
// Guardar copias originales (las funciones de filtrado modifican los vectores)
|
||||
std::vector<Point2D> filled_points_original = filled_points_;
|
||||
@@ -123,7 +128,7 @@ void PNGShape::generatePoints(int num_points, float screen_width, float screen_h
|
||||
|
||||
// Conjunto de puntos ACTIVO (será modificado por filtros)
|
||||
std::vector<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()) {
|
||||
@@ -267,9 +270,9 @@ std::vector<PNGShape::Point2D> PNGShape::extractCornerVertices(const std::vector
|
||||
|
||||
// Generar puntos en extremos de cada fila
|
||||
for (const auto& [row_y, extremes] : row_extremes) {
|
||||
result.push_back({extremes.first, static_cast<float>(row_y)}); // Extremo izquierdo
|
||||
if (extremes.second != extremes.first) { // Solo añadir derecho si es diferente
|
||||
result.push_back({extremes.second, static_cast<float>(row_y)}); // Extremo derecho
|
||||
result.push_back({extremes.first, static_cast<float>(row_y)}); // Extremo izquierdo
|
||||
if (extremes.second != extremes.first) { // Solo añadir derecho si es diferente
|
||||
result.push_back({extremes.second, static_cast<float>(row_y)}); // Extremo derecho
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,8 +379,8 @@ void PNGShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
||||
float v = y_base / (logo_size * 0.5f);
|
||||
|
||||
// Calcular pivoteo (amplitudes más grandes)
|
||||
float tilt_amount_x = sinf(tilt_x_) * 0.15f; // 15%
|
||||
float tilt_amount_y = sinf(tilt_y_) * 0.1f; // 10%
|
||||
float tilt_amount_x = sinf(tilt_x_) * 0.15f; // 15%
|
||||
float tilt_amount_y = sinf(tilt_y_) * 0.1f; // 10%
|
||||
|
||||
// Aplicar pivoteo proporcional al tamaño del logo
|
||||
float z_tilt = (u * tilt_amount_y + v * tilt_amount_x) * logo_size;
|
||||
@@ -386,14 +389,14 @@ void PNGShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
||||
// Aplicar rotación en eje Y (horizontal)
|
||||
float cos_y = cosf(angle_y_);
|
||||
float sin_y = sinf(angle_y_);
|
||||
float x_rot_y = x_base * cos_y - z_base * sin_y;
|
||||
float z_rot_y = x_base * sin_y + z_base * cos_y;
|
||||
float x_rot_y = (x_base * cos_y) - (z_base * sin_y);
|
||||
float z_rot_y = (x_base * sin_y) + (z_base * cos_y);
|
||||
|
||||
// Aplicar rotación en eje X (vertical)
|
||||
float cos_x = cosf(angle_x_);
|
||||
float sin_x = sinf(angle_x_);
|
||||
float y_rot = y_base * cos_x - z_rot_y * sin_x;
|
||||
float z_rot = y_base * sin_x + z_rot_y * cos_x;
|
||||
float y_rot = (y_base * cos_x) - (z_rot_y * sin_x);
|
||||
float z_rot = (y_base * sin_x) + (z_rot_y * cos_x);
|
||||
|
||||
// Retornar coordenadas finales
|
||||
x = x_rot_y;
|
||||
@@ -408,7 +411,7 @@ void PNGShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
||||
}
|
||||
}
|
||||
|
||||
float PNGShape::getScaleFactor(float screen_height) const {
|
||||
auto PNGShape::getScaleFactor(float screen_height) const -> float {
|
||||
// Escala dinámica según resolución
|
||||
return PNG_SIZE_FACTOR;
|
||||
}
|
||||
@@ -432,7 +435,7 @@ void PNGShape::setConvergence(float convergence) {
|
||||
}
|
||||
|
||||
// Obtener progreso del flip actual (0.0 = inicio del flip, 1.0 = fin del flip)
|
||||
float PNGShape::getFlipProgress() const {
|
||||
auto PNGShape::getFlipProgress() const -> float {
|
||||
if (!is_flipping_) {
|
||||
return 0.0f; // No está flipping, progreso = 0
|
||||
}
|
||||
|
||||
@@ -1,104 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "shape.hpp"
|
||||
#include "defines.hpp" // Para PNG_IDLE_TIME_MIN/MAX constantes
|
||||
#include <cstdlib> // Para rand()
|
||||
#include <vector>
|
||||
#include <cstdlib> // Para rand()
|
||||
|
||||
#include "defines.hpp" // Para PNG_IDLE_TIME_MIN/MAX constantes
|
||||
#include "shape.hpp"
|
||||
|
||||
// Figura: Shape generada desde PNG 1-bit (blanco sobre negro)
|
||||
// Enfoque A: Extrusión 2D (implementado)
|
||||
// Enfoque B: Voxelización 3D (preparado para futuro)
|
||||
class PNGShape : public Shape {
|
||||
private:
|
||||
// Datos de la imagen cargada
|
||||
int image_width_ = 0;
|
||||
int image_height_ = 0;
|
||||
std::vector<bool> pixel_data_; // Mapa de píxeles blancos (true = blanco)
|
||||
private:
|
||||
// Datos de la imagen cargada
|
||||
int image_width_ = 0;
|
||||
int image_height_ = 0;
|
||||
std::vector<bool> pixel_data_; // Mapa de píxeles blancos (true = blanco)
|
||||
|
||||
// Puntos generados (Enfoque A: Extrusión 2D)
|
||||
struct Point2D {
|
||||
float x, y;
|
||||
};
|
||||
std::vector<Point2D> edge_points_; // Contorno (solo bordes) - ORIGINAL sin optimizar
|
||||
std::vector<Point2D> filled_points_; // Relleno completo - ORIGINAL sin optimizar
|
||||
std::vector<Point2D> optimized_points_; // Puntos finales optimizados (usado por getPoint3D)
|
||||
// Puntos generados (Enfoque A: Extrusión 2D)
|
||||
struct Point2D {
|
||||
float x, y;
|
||||
};
|
||||
std::vector<Point2D> edge_points_; // Contorno (solo bordes) - ORIGINAL sin optimizar
|
||||
std::vector<Point2D> filled_points_; // Relleno completo - ORIGINAL sin optimizar
|
||||
std::vector<Point2D> optimized_points_; // Puntos finales optimizados (usado por getPoint3D)
|
||||
|
||||
// Parámetros de extrusión
|
||||
float extrusion_depth_ = 0.0f; // Profundidad de extrusión en Z
|
||||
int num_layers_ = 0; // Capas de extrusión (más capas = más denso)
|
||||
// Parámetros de extrusión
|
||||
float extrusion_depth_ = 0.0f; // Profundidad de extrusión en Z
|
||||
int num_layers_ = 0; // Capas de extrusión (más capas = más denso)
|
||||
|
||||
// Rotación "legible" (de frente con volteretas ocasionales)
|
||||
float angle_x_ = 0.0f;
|
||||
float angle_y_ = 0.0f;
|
||||
float idle_timer_ = 0.0f; // Timer para tiempo de frente
|
||||
float flip_timer_ = 0.0f; // Timer para voltereta
|
||||
float next_idle_time_ = 5.0f; // Próximo tiempo de espera (aleatorio)
|
||||
bool is_flipping_ = false; // Estado: quieto o voltereta
|
||||
int flip_axis_ = 0; // Eje de voltereta (0=X, 1=Y, 2=ambos)
|
||||
// Rotación "legible" (de frente con volteretas ocasionales)
|
||||
float angle_x_ = 0.0f;
|
||||
float angle_y_ = 0.0f;
|
||||
float idle_timer_ = 0.0f; // Timer para tiempo de frente
|
||||
float flip_timer_ = 0.0f; // Timer para voltereta
|
||||
float next_idle_time_ = 5.0f; // Próximo tiempo de espera (aleatorio)
|
||||
bool is_flipping_ = false; // Estado: quieto o voltereta
|
||||
int flip_axis_ = 0; // Eje de voltereta (0=X, 1=Y, 2=ambos)
|
||||
|
||||
// Pivoteo sutil en estado IDLE
|
||||
float tilt_x_ = 0.0f; // Oscilación sutil en eje X
|
||||
float tilt_y_ = 0.0f; // Oscilación sutil en eje Y
|
||||
// Pivoteo sutil en estado IDLE
|
||||
float tilt_x_ = 0.0f; // Oscilación sutil en eje X
|
||||
float tilt_y_ = 0.0f; // Oscilación sutil en eje Y
|
||||
|
||||
// Modo LOGO (intervalos de flip más largos)
|
||||
bool is_logo_mode_ = false; // true = usar intervalos LOGO (más lentos)
|
||||
// Modo LOGO (intervalos de flip más largos)
|
||||
bool is_logo_mode_ = false; // true = usar intervalos LOGO (más lentos)
|
||||
|
||||
// Sistema de convergencia (solo relevante en modo LOGO)
|
||||
float current_convergence_ = 0.0f; // Porcentaje actual de convergencia (0.0-1.0)
|
||||
bool convergence_threshold_reached_ = false; // true si ha alcanzado umbral mínimo (80%)
|
||||
// Sistema de convergencia (solo relevante en modo LOGO)
|
||||
float current_convergence_ = 0.0f; // Porcentaje actual de convergencia (0.0-1.0)
|
||||
bool convergence_threshold_reached_ = false; // true si ha alcanzado umbral mínimo (80%)
|
||||
|
||||
// Sistema de tracking de flips (para modo LOGO - espera de flips)
|
||||
int flip_count_ = 0; // Contador de flips completados (reset al entrar a LOGO)
|
||||
bool was_flipping_last_frame_ = false; // Estado previo para detectar transiciones
|
||||
// Sistema de tracking de flips (para modo LOGO - espera de flips)
|
||||
int flip_count_ = 0; // Contador de flips completados (reset al entrar a LOGO)
|
||||
bool was_flipping_last_frame_ = false; // Estado previo para detectar transiciones
|
||||
|
||||
// Dimensiones normalizadas
|
||||
float scale_factor_ = 1.0f;
|
||||
float center_offset_x_ = 0.0f;
|
||||
float center_offset_y_ = 0.0f;
|
||||
// Dimensiones normalizadas
|
||||
float scale_factor_ = 1.0f;
|
||||
float center_offset_x_ = 0.0f;
|
||||
float center_offset_y_ = 0.0f;
|
||||
|
||||
int num_points_ = 0; // Total de puntos generados (para indexación)
|
||||
int num_points_ = 0; // Total de puntos generados (para indexación)
|
||||
|
||||
// Métodos internos
|
||||
bool loadPNG(const char* path); // Cargar PNG con stb_image
|
||||
void detectEdges(); // Detectar contorno (Enfoque A)
|
||||
void floodFill(); // Rellenar interior (Enfoque B - futuro)
|
||||
void generateExtrudedPoints(); // Generar puntos con extrusión 2D
|
||||
// Métodos internos
|
||||
bool loadPNG(const char* resource_key); // Cargar PNG con stb_image
|
||||
void detectEdges(); // Detectar contorno (Enfoque A)
|
||||
void floodFill(); // Rellenar interior (Enfoque B - futuro)
|
||||
void generateExtrudedPoints(); // Generar puntos con extrusión 2D
|
||||
|
||||
// Métodos de distribución adaptativa (funciones puras, no modifican parámetros)
|
||||
std::vector<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
|
||||
// Métodos de distribución adaptativa (funciones puras, no modifican parámetros)
|
||||
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
|
||||
PNGShape(const char* png_path = "data/shapes/jailgames.png");
|
||||
public:
|
||||
// Constructor: recibe path relativo al PNG
|
||||
PNGShape(const char* png_path = "data/shapes/jailgames.png");
|
||||
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "PNG SHAPE"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "PNG SHAPE"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
|
||||
// Consultar estado de flip
|
||||
bool isFlipping() const { return is_flipping_; }
|
||||
// Consultar estado de flip
|
||||
bool isFlipping() const { return is_flipping_; }
|
||||
|
||||
// Obtener progreso del flip actual (0.0 = inicio, 1.0 = fin)
|
||||
float getFlipProgress() const;
|
||||
// Obtener progreso del flip actual (0.0 = inicio, 1.0 = fin)
|
||||
float getFlipProgress() const;
|
||||
|
||||
// Obtener número de flips completados (para modo LOGO)
|
||||
int getFlipCount() const { return flip_count_; }
|
||||
// Obtener número de flips completados (para modo LOGO)
|
||||
int getFlipCount() const { return flip_count_; }
|
||||
|
||||
// Resetear contador de flips (llamar al entrar a LOGO MODE)
|
||||
void resetFlipCount() { flip_count_ = 0; was_flipping_last_frame_ = false; }
|
||||
// Resetear contador de flips (llamar al entrar a LOGO MODE)
|
||||
void resetFlipCount() {
|
||||
flip_count_ = 0;
|
||||
was_flipping_last_frame_ = false;
|
||||
}
|
||||
|
||||
// Control de modo LOGO (flip intervals más largos)
|
||||
void setLogoMode(bool enable) {
|
||||
is_logo_mode_ = enable;
|
||||
// Recalcular next_idle_time_ con el rango apropiado
|
||||
float idle_min = enable ? PNG_IDLE_TIME_MIN_LOGO : PNG_IDLE_TIME_MIN;
|
||||
float idle_max = enable ? PNG_IDLE_TIME_MAX_LOGO : PNG_IDLE_TIME_MAX;
|
||||
next_idle_time_ = idle_min + (rand() % 1000) / 1000.0f * (idle_max - idle_min);
|
||||
}
|
||||
// Control de modo LOGO (flip intervals más largos)
|
||||
void setLogoMode(bool enable) {
|
||||
is_logo_mode_ = enable;
|
||||
// Recalcular next_idle_time_ con el rango apropiado
|
||||
float idle_min = enable ? PNG_IDLE_TIME_MIN_LOGO : PNG_IDLE_TIME_MIN;
|
||||
float idle_max = enable ? PNG_IDLE_TIME_MAX_LOGO : PNG_IDLE_TIME_MAX;
|
||||
next_idle_time_ = idle_min + (rand() % 1000) / 1000.0f * (idle_max - idle_min);
|
||||
}
|
||||
|
||||
// Sistema de convergencia (override de Shape::setConvergence)
|
||||
void setConvergence(float convergence) override;
|
||||
// Sistema de convergencia (override de Shape::setConvergence)
|
||||
void setConvergence(float convergence) override;
|
||||
};
|
||||
|
||||
@@ -2,34 +2,34 @@
|
||||
|
||||
// Interfaz abstracta para todas las figuras 3D
|
||||
class Shape {
|
||||
public:
|
||||
virtual ~Shape() = default;
|
||||
public:
|
||||
virtual ~Shape() = default;
|
||||
|
||||
// Generar distribución inicial de puntos en la figura
|
||||
// num_points: cantidad de pelotas a distribuir
|
||||
// screen_width/height: dimensiones del área de juego (para escalar)
|
||||
virtual void generatePoints(int num_points, float screen_width, float screen_height) = 0;
|
||||
// Generar distribución inicial de puntos en la figura
|
||||
// num_points: cantidad de pelotas a distribuir
|
||||
// screen_width/height: dimensiones del área de juego (para escalar)
|
||||
virtual void generatePoints(int num_points, float screen_width, float screen_height) = 0;
|
||||
|
||||
// Actualizar animación de la figura (rotación, deformación, etc.)
|
||||
// delta_time: tiempo transcurrido desde último frame
|
||||
// screen_width/height: dimensiones actuales (puede cambiar con F4)
|
||||
virtual void update(float delta_time, float screen_width, float screen_height) = 0;
|
||||
// Actualizar animación de la figura (rotación, deformación, etc.)
|
||||
// delta_time: tiempo transcurrido desde último frame
|
||||
// screen_width/height: dimensiones actuales (puede cambiar con F4)
|
||||
virtual void update(float delta_time, float screen_width, float screen_height) = 0;
|
||||
|
||||
// Obtener posición 3D del punto i después de transformaciones (rotación, etc.)
|
||||
// index: índice del punto (0 a num_points-1)
|
||||
// x, y, z: coordenadas 3D en espacio mundo (centradas en 0,0,0)
|
||||
virtual void getPoint3D(int index, float& x, float& y, float& z) const = 0;
|
||||
// Obtener posición 3D del punto i después de transformaciones (rotación, etc.)
|
||||
// index: índice del punto (0 a num_points-1)
|
||||
// x, y, z: coordenadas 3D en espacio mundo (centradas en 0,0,0)
|
||||
virtual void getPoint3D(int index, float& x, float& y, float& z) const = 0;
|
||||
|
||||
// Obtener nombre de la figura para debug display
|
||||
virtual const char* getName() const = 0;
|
||||
// Obtener nombre de la figura para debug display
|
||||
virtual const char* getName() const = 0;
|
||||
|
||||
// Obtener factor de escala para ajustar física según tamaño de figura
|
||||
// screen_height: altura actual de pantalla
|
||||
// Retorna: factor multiplicador para constantes de física (spring_k, damping, etc.)
|
||||
virtual float getScaleFactor(float screen_height) const = 0;
|
||||
// Obtener factor de escala para ajustar física según tamaño de figura
|
||||
// screen_height: altura actual de pantalla
|
||||
// Retorna: factor multiplicador para constantes de física (spring_k, damping, etc.)
|
||||
virtual float getScaleFactor(float screen_height) const = 0;
|
||||
|
||||
// Notificar a la figura sobre el porcentaje de convergencia (pelotas cerca del objetivo)
|
||||
// convergence: valor de 0.0 (0%) a 1.0 (100%) indicando cuántas pelotas están en posición
|
||||
// Default: no-op (la mayoría de figuras no necesitan esta información)
|
||||
virtual void setConvergence(float convergence) {}
|
||||
// Notificar a la figura sobre el porcentaje de convergencia (pelotas cerca del objetivo)
|
||||
// convergence: valor de 0.0 (0%) a 1.0 (100%) indicando cuántas pelotas están en posición
|
||||
// Default: no-op (la mayoría de figuras no necesitan esta información)
|
||||
virtual void setConvergence(float convergence) {}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
// Comportamiento: Rotación dual en ejes X e Y
|
||||
// Uso anterior: RotoBall
|
||||
class SphereShape : public Shape {
|
||||
private:
|
||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float radius_ = 0.0f; // Radio de la esfera (píxeles)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
private:
|
||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float radius_ = 0.0f; // Radio de la esfera (píxeles)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "SPHERE"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "SPHERE"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
// Comportamiento: Superficie toroidal con rotación triple (X, Y, Z)
|
||||
// Ecuaciones: x = (R + r*cos(v))*cos(u), y = (R + r*cos(v))*sin(u), z = r*sin(v)
|
||||
class TorusShape : public Shape {
|
||||
private:
|
||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad)
|
||||
float major_radius_ = 0.0f; // Radio mayor R (del centro al tubo)
|
||||
float minor_radius_ = 0.0f; // Radio menor r (grosor del tubo)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
private:
|
||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||
float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad)
|
||||
float major_radius_ = 0.0f; // Radio mayor R (del centro al tubo)
|
||||
float minor_radius_ = 0.0f; // Radio menor r (grosor del tubo)
|
||||
int num_points_ = 0; // Cantidad de puntos generados
|
||||
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "TORUS"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
public:
|
||||
void generatePoints(int num_points, float screen_width, float screen_height) override;
|
||||
void update(float delta_time, float screen_width, float screen_height) override;
|
||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||
const char* getName() const override { return "TORUS"; }
|
||||
float getScaleFactor(float screen_height) const override;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#include "shape_manager.hpp"
|
||||
|
||||
#include <algorithm> // for std::min, std::max, std::transform
|
||||
#include <cctype> // for ::tolower
|
||||
#include <cstdlib> // for rand
|
||||
#include <string> // for std::string
|
||||
#include <algorithm> // for std::min, std::max, std::transform
|
||||
#include <cctype> // for ::tolower
|
||||
#include <cstdlib> // for rand
|
||||
#include <string> // for std::string
|
||||
|
||||
#include "ball.hpp" // for Ball
|
||||
#include "defines.hpp" // for constantes
|
||||
#include "scene/scene_manager.hpp" // for SceneManager
|
||||
#include "state/state_manager.hpp" // for StateManager
|
||||
#include "ui/ui_manager.hpp" // for UIManager
|
||||
#include "ball.hpp" // for Ball
|
||||
#include "defines.hpp" // for constantes
|
||||
#include "scene/scene_manager.hpp" // for SceneManager
|
||||
#include "state/state_manager.hpp" // for StateManager
|
||||
#include "ui/ui_manager.hpp" // for UIManager
|
||||
|
||||
// Includes de todas las shapes (necesario para creación polimórfica)
|
||||
#include "shapes/atom_shape.hpp"
|
||||
@@ -23,26 +23,24 @@
|
||||
#include "shapes/torus_shape.hpp"
|
||||
|
||||
ShapeManager::ShapeManager()
|
||||
: engine_(nullptr)
|
||||
, scene_mgr_(nullptr)
|
||||
, ui_mgr_(nullptr)
|
||||
, state_mgr_(nullptr)
|
||||
, current_mode_(SimulationMode::PHYSICS)
|
||||
, current_shape_type_(ShapeType::SPHERE)
|
||||
, last_shape_type_(ShapeType::SPHERE)
|
||||
, active_shape_(nullptr)
|
||||
, shape_scale_factor_(1.0f)
|
||||
, depth_zoom_enabled_(true)
|
||||
, screen_width_(0)
|
||||
, screen_height_(0)
|
||||
, shape_convergence_(0.0f) {
|
||||
: engine_(nullptr),
|
||||
scene_mgr_(nullptr),
|
||||
ui_mgr_(nullptr),
|
||||
state_mgr_(nullptr),
|
||||
current_mode_(SimulationMode::PHYSICS),
|
||||
current_shape_type_(ShapeType::SPHERE),
|
||||
last_shape_type_(ShapeType::SPHERE),
|
||||
active_shape_(nullptr),
|
||||
shape_scale_factor_(1.0f),
|
||||
depth_zoom_enabled_(true),
|
||||
screen_width_(0),
|
||||
screen_height_(0),
|
||||
shape_convergence_(0.0f) {
|
||||
}
|
||||
|
||||
ShapeManager::~ShapeManager() {
|
||||
}
|
||||
ShapeManager::~ShapeManager() = default;
|
||||
|
||||
void ShapeManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr,
|
||||
StateManager* state_mgr, int screen_width, int screen_height) {
|
||||
void ShapeManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height) {
|
||||
engine_ = engine;
|
||||
scene_mgr_ = scene_mgr;
|
||||
ui_mgr_ = ui_mgr;
|
||||
@@ -66,17 +64,17 @@ void ShapeManager::toggleShapeMode(bool force_gravity_on_exit) {
|
||||
activateShapeInternal(last_shape_type_);
|
||||
|
||||
// Si estamos en modo LOGO y la figura es PNG_SHAPE, restaurar configuración LOGO
|
||||
if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO && last_shape_type_ == ShapeType::PNG_SHAPE) {
|
||||
if ((state_mgr_ != nullptr) && state_mgr_->getCurrentMode() == AppMode::LOGO && last_shape_type_ == ShapeType::PNG_SHAPE) {
|
||||
if (active_shape_) {
|
||||
PNGShape* png_shape = dynamic_cast<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
|
||||
@@ -147,10 +146,10 @@ class ShapeManager {
|
||||
|
||||
private:
|
||||
// === Referencias a otros componentes ===
|
||||
Engine* engine_; // Callback al Engine (legacy - temporal)
|
||||
SceneManager* scene_mgr_; // Acceso a bolas y física
|
||||
UIManager* ui_mgr_; // Notificaciones
|
||||
StateManager* state_mgr_; // Verificación de modo actual
|
||||
Engine* engine_; // Callback al Engine (legacy - temporal)
|
||||
SceneManager* scene_mgr_; // Acceso a bolas y física
|
||||
UIManager* ui_mgr_; // Notificaciones
|
||||
StateManager* state_mgr_; // Verificación de modo actual
|
||||
|
||||
// === Estado de figuras 3D ===
|
||||
SimulationMode current_mode_;
|
||||
|
||||
@@ -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 "theme_manager.hpp" // for ThemeManager
|
||||
#include "defines.hpp" // for constantes DEMO/LOGO
|
||||
#include "engine.hpp" // for Engine (enter/exitShapeMode, texture)
|
||||
#include "scene/scene_manager.hpp" // for SceneManager
|
||||
#include "shapes/png_shape.hpp" // for PNGShape flip detection
|
||||
#include "shapes_mgr/shape_manager.hpp" // for ShapeManager
|
||||
#include "theme_manager.hpp" // for ThemeManager
|
||||
|
||||
StateManager::StateManager()
|
||||
: engine_(nullptr)
|
||||
, scene_mgr_(nullptr)
|
||||
, theme_mgr_(nullptr)
|
||||
, shape_mgr_(nullptr)
|
||||
, current_app_mode_(AppMode::SANDBOX)
|
||||
, previous_app_mode_(AppMode::SANDBOX)
|
||||
, demo_timer_(0.0f)
|
||||
, demo_next_action_time_(0.0f)
|
||||
, logo_convergence_threshold_(0.90f)
|
||||
, logo_min_time_(3.0f)
|
||||
, logo_max_time_(5.0f)
|
||||
, logo_waiting_for_flip_(false)
|
||||
, logo_target_flip_number_(0)
|
||||
, logo_target_flip_percentage_(0.0f)
|
||||
, logo_current_flip_count_(0)
|
||||
, logo_entered_manually_(false)
|
||||
, logo_previous_theme_(0)
|
||||
, logo_previous_texture_index_(0)
|
||||
, logo_previous_shape_scale_(1.0f) {
|
||||
}
|
||||
|
||||
StateManager::~StateManager() {
|
||||
: engine_(nullptr),
|
||||
scene_mgr_(nullptr),
|
||||
theme_mgr_(nullptr),
|
||||
shape_mgr_(nullptr),
|
||||
current_app_mode_(AppMode::SANDBOX),
|
||||
previous_app_mode_(AppMode::SANDBOX),
|
||||
demo_timer_(0.0f),
|
||||
demo_next_action_time_(0.0f),
|
||||
logo_convergence_threshold_(0.90f),
|
||||
logo_min_time_(3.0f),
|
||||
logo_max_time_(5.0f),
|
||||
logo_waiting_for_flip_(false),
|
||||
logo_target_flip_number_(0),
|
||||
logo_target_flip_percentage_(0.0f),
|
||||
logo_current_flip_count_(0),
|
||||
logo_entered_manually_(false),
|
||||
logo_previous_theme_(0),
|
||||
logo_previous_texture_index_(0),
|
||||
logo_previous_shape_scale_(1.0f) {
|
||||
}
|
||||
|
||||
void StateManager::initialize(Engine* engine, SceneManager* scene_mgr, ThemeManager* theme_mgr, ShapeManager* shape_mgr) {
|
||||
@@ -53,8 +51,10 @@ void StateManager::setLogoPreviousState(int theme, size_t texture_index, float s
|
||||
// ACTUALIZACIÓN DE ESTADOS
|
||||
// ===========================================================================
|
||||
|
||||
void StateManager::update(float delta_time, float shape_convergence, Shape* active_shape) {
|
||||
if (current_app_mode_ == AppMode::SANDBOX) return;
|
||||
void StateManager::update(float delta_time, float shape_convergence, Shape* active_shape) { // NOLINT(readability-function-cognitive-complexity)
|
||||
if (current_app_mode_ == AppMode::SANDBOX) {
|
||||
return;
|
||||
}
|
||||
|
||||
demo_timer_ += delta_time;
|
||||
|
||||
@@ -63,14 +63,12 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti
|
||||
if (current_app_mode_ == AppMode::LOGO) {
|
||||
if (logo_waiting_for_flip_) {
|
||||
// CAMINO B: Esperando a que ocurran flips
|
||||
PNGShape* png_shape = dynamic_cast<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
|
||||
@@ -104,7 +104,7 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti
|
||||
if (logo_waiting_for_flip_) {
|
||||
// Ya estábamos esperando flips → hacer el cambio SHAPE → PHYSICS
|
||||
if (action < 50) {
|
||||
engine_->exitShapeMode(true); // Con gravedad ON
|
||||
engine_->exitShapeMode(true); // Con gravedad ON
|
||||
} else {
|
||||
engine_->exitShapeMode(false); // Con gravedad OFF
|
||||
}
|
||||
@@ -122,15 +122,15 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti
|
||||
logo_target_flip_percentage_ = LOGO_FLIP_TRIGGER_MIN + (rand() % 1000) / 1000.0f * (LOGO_FLIP_TRIGGER_MAX - LOGO_FLIP_TRIGGER_MIN);
|
||||
logo_current_flip_count_ = 0;
|
||||
|
||||
PNGShape* png_shape = dynamic_cast<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
|
||||
} else {
|
||||
// CAMINO A (50%): Cambio inmediato
|
||||
if (action < 50) {
|
||||
engine_->exitShapeMode(true); // SHAPE → PHYSICS con gravedad ON
|
||||
engine_->exitShapeMode(true); // SHAPE → PHYSICS con gravedad ON
|
||||
} else {
|
||||
engine_->exitShapeMode(false); // SHAPE → PHYSICS con gravedad OFF
|
||||
}
|
||||
@@ -158,7 +158,7 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti
|
||||
scene_mgr_->forceBallsGravityOff();
|
||||
} else {
|
||||
// 16%: Cambiar dirección de gravedad
|
||||
GravityDirection new_direction = static_cast<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,10 +535,12 @@ void StateManager::randomizeOnDemoStart(bool is_lite) {
|
||||
// ===========================================================================
|
||||
|
||||
void StateManager::toggleGravityOnOff() {
|
||||
if (!scene_mgr_) return;
|
||||
if (scene_mgr_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool gravity_enabled = scene_mgr_->hasBalls() &&
|
||||
(scene_mgr_->getFirstBall()->getGravityForce() > 0.0f);
|
||||
(scene_mgr_->getFirstBall()->getGravityForce() > 0.0f);
|
||||
|
||||
if (gravity_enabled) {
|
||||
scene_mgr_->forceBallsGravityOff();
|
||||
@@ -543,7 +554,9 @@ void StateManager::toggleGravityOnOff() {
|
||||
// ===========================================================================
|
||||
|
||||
void StateManager::enterLogoMode(bool from_demo, int current_screen_width, int current_screen_height, size_t ball_count) {
|
||||
if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return;
|
||||
if ((engine_ == nullptr) || (scene_mgr_ == nullptr) || (theme_mgr_ == nullptr) || (shape_mgr_ == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
logo_entered_manually_ = !from_demo;
|
||||
|
||||
@@ -585,8 +598,8 @@ void StateManager::enterLogoMode(bool from_demo, int current_screen_width, int c
|
||||
}
|
||||
|
||||
// Cambiar a tema aleatorio entre: MONOCHROME, LAVENDER, CRIMSON, ESMERALDA
|
||||
int logo_themes[] = {5, 6, 7, 8};
|
||||
theme_mgr_->switchToTheme(logo_themes[rand() % 4]);
|
||||
constexpr std::array<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,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_stdinc.h> // for Uint64
|
||||
#include <cstddef> // for size_t
|
||||
|
||||
#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,60 +2,61 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
class TextRenderer {
|
||||
public:
|
||||
TextRenderer();
|
||||
~TextRenderer();
|
||||
public:
|
||||
TextRenderer();
|
||||
~TextRenderer();
|
||||
|
||||
// Inicializa el renderizador de texto con una fuente
|
||||
bool init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing = true);
|
||||
// Inicializa el renderizador de texto con una fuente
|
||||
bool init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing = true);
|
||||
|
||||
// Reinicializa el renderizador con un nuevo tamaño de fuente
|
||||
bool reinitialize(int new_font_size);
|
||||
// Reinicializa el renderizador con un nuevo tamaño de fuente
|
||||
bool reinitialize(int new_font_size);
|
||||
|
||||
// Libera recursos
|
||||
void cleanup();
|
||||
// Libera recursos
|
||||
void cleanup();
|
||||
|
||||
// Renderiza texto en la posición especificada con color RGB
|
||||
void print(int x, int y, const char* text, uint8_t r, uint8_t g, uint8_t b);
|
||||
void print(int x, int y, const std::string& text, uint8_t r, uint8_t g, uint8_t b);
|
||||
// Renderiza texto en la posición especificada con color RGB
|
||||
void print(int x, int y, const char* text, uint8_t r, uint8_t g, uint8_t b);
|
||||
void print(int x, int y, const std::string& text, uint8_t r, uint8_t g, uint8_t b);
|
||||
|
||||
// Renderiza texto en coordenadas lógicas, pero convierte a físicas para tamaño absoluto
|
||||
void printPhysical(int logical_x, int logical_y, const char* text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y);
|
||||
void printPhysical(int logical_x, int logical_y, const std::string& text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y);
|
||||
// Renderiza texto en coordenadas lógicas, pero convierte a físicas para tamaño absoluto
|
||||
void printPhysical(int logical_x, int logical_y, const char* text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y);
|
||||
void printPhysical(int logical_x, int logical_y, const std::string& text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y);
|
||||
|
||||
// Renderiza texto en coordenadas físicas absolutas (tamaño fijo independiente de resolución)
|
||||
// NOTA: Este método usa el tamaño de fuente tal cual fue cargado, sin escalado
|
||||
void printAbsolute(int physical_x, int physical_y, const char* text, SDL_Color color);
|
||||
void printAbsolute(int physical_x, int physical_y, const std::string& text, SDL_Color color);
|
||||
// Renderiza texto en coordenadas físicas absolutas (tamaño fijo independiente de resolución)
|
||||
// NOTA: Este método usa el tamaño de fuente tal cual fue cargado, sin escalado
|
||||
void printAbsolute(int physical_x, int physical_y, const char* text, SDL_Color color);
|
||||
void printAbsolute(int physical_x, int physical_y, const std::string& text, SDL_Color color);
|
||||
|
||||
// Renderiza texto con sombra negra (+1px offset) para máxima legibilidad sobre cualquier fondo
|
||||
void printAbsoluteShadowed(int physical_x, int physical_y, const char* text);
|
||||
void printAbsoluteShadowed(int physical_x, int physical_y, const std::string& text);
|
||||
// Renderiza texto con sombra negra (+1px offset) para máxima legibilidad sobre cualquier fondo
|
||||
void printAbsoluteShadowed(int physical_x, int physical_y, const char* text);
|
||||
void printAbsoluteShadowed(int physical_x, int physical_y, const std::string& text);
|
||||
|
||||
// Obtiene el ancho de un texto renderizado (en píxeles lógicos para compatibilidad)
|
||||
int getTextWidth(const char* text);
|
||||
// Obtiene el ancho de un texto renderizado (en píxeles lógicos para compatibilidad)
|
||||
int getTextWidth(const char* text);
|
||||
|
||||
// Obtiene el ancho de un texto en píxeles FÍSICOS reales (sin escalado)
|
||||
// Útil para notificaciones y elementos UI de tamaño fijo
|
||||
int getTextWidthPhysical(const char* text);
|
||||
// Obtiene el ancho de un texto en píxeles FÍSICOS reales (sin escalado)
|
||||
// Útil para notificaciones y elementos UI de tamaño fijo
|
||||
int getTextWidthPhysical(const char* text);
|
||||
|
||||
// Obtiene la altura de la fuente (incluye line_gap)
|
||||
int getTextHeight();
|
||||
// Obtiene la altura de la fuente (incluye line_gap)
|
||||
int getTextHeight();
|
||||
|
||||
// Obtiene la altura real del glifo (ascender + |descendente|, sin line_gap)
|
||||
int getGlyphHeight();
|
||||
// Obtiene la altura real del glifo (ascender + |descendente|, sin line_gap)
|
||||
int getGlyphHeight();
|
||||
|
||||
// Verifica si está inicializado correctamente
|
||||
bool isInitialized() const { return font_ != nullptr && renderer_ != nullptr; }
|
||||
// Verifica si está inicializado correctamente
|
||||
bool isInitialized() const { return font_ != nullptr && renderer_ != nullptr; }
|
||||
|
||||
private:
|
||||
SDL_Renderer* renderer_;
|
||||
TTF_Font* font_;
|
||||
int font_size_;
|
||||
bool use_antialiasing_;
|
||||
std::string font_path_; // Almacenar ruta para reinitialize()
|
||||
unsigned char* font_data_buffer_; // Buffer de datos de fuente (mantener en memoria mientras esté abierta)
|
||||
private:
|
||||
SDL_Renderer* renderer_;
|
||||
TTF_Font* font_;
|
||||
int font_size_;
|
||||
bool use_antialiasing_;
|
||||
std::string font_path_; // Almacenar ruta para reinitialize()
|
||||
unsigned char* font_data_buffer_; // Buffer de datos de fuente (mantener en memoria mientras esté abierta)
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
#include <memory> // for unique_ptr
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ball.hpp" // for Ball class
|
||||
#include "defines.hpp" // for Color, ColorTheme
|
||||
#include "themes/theme.hpp" // for Theme interface
|
||||
#include "themes/theme_snapshot.hpp" // for ThemeSnapshot
|
||||
#include "ball.hpp" // for Ball class
|
||||
#include "defines.hpp" // for Color, ColorTheme
|
||||
#include "themes/theme.hpp" // for Theme interface
|
||||
#include "themes/theme_snapshot.hpp" // for ThemeSnapshot
|
||||
|
||||
/**
|
||||
* ThemeManager: Gestiona el sistema de temas visuales (unificado, estáticos y dinámicos)
|
||||
@@ -42,7 +42,7 @@ class ThemeManager {
|
||||
~ThemeManager() = default;
|
||||
|
||||
// Inicialización
|
||||
void initialize(); // Inicializa 15 temas unificados (9 estáticos + 6 dinámicos)
|
||||
void initialize(); // Inicializa 15 temas unificados (9 estáticos + 6 dinámicos)
|
||||
void setMaxBallCount(int n) { max_ball_count_ = n; } // Máximo real (escenario 8 o custom si mayor)
|
||||
|
||||
// Interfaz unificada (PHASE 2 + PHASE 3)
|
||||
@@ -53,9 +53,8 @@ class ThemeManager {
|
||||
void pauseDynamic(); // Toggle pausa de animación (Shift+D, solo dinámicos)
|
||||
|
||||
// Queries de colores (usado en rendering)
|
||||
Color getInterpolatedColor(size_t ball_index) const; // Obtiene color interpolado para pelota
|
||||
void getBackgroundColors(float& top_r, float& top_g, float& top_b,
|
||||
float& bottom_r, float& bottom_g, float& bottom_b) const; // Obtiene colores de fondo degradado
|
||||
Color getInterpolatedColor(size_t ball_index) const; // Obtiene color interpolado para pelota
|
||||
void getBackgroundColors(float& top_r, float& top_g, float& top_b, float& bottom_r, float& bottom_g, float& bottom_b) const; // Obtiene colores de fondo degradado
|
||||
|
||||
// Queries de estado (para debug display y lógica)
|
||||
int getCurrentThemeIndex() const { return current_theme_index_; }
|
||||
@@ -89,13 +88,13 @@ class ThemeManager {
|
||||
// ========================================
|
||||
|
||||
// Estado de transición
|
||||
bool transitioning_ = false; // ¿Hay transición LERP activa?
|
||||
float transition_progress_ = 0.0f; // Progreso 0.0-1.0 (0.0 = origen, 1.0 = destino)
|
||||
bool transitioning_ = false; // ¿Hay transición LERP activa?
|
||||
float transition_progress_ = 0.0f; // Progreso 0.0-1.0 (0.0 = origen, 1.0 = destino)
|
||||
float transition_duration_ = THEME_TRANSITION_DURATION; // Duración en segundos (configurable en defines.h)
|
||||
|
||||
// Índices de temas involucrados en transición
|
||||
int source_theme_index_ = 0; // Tema origen (del que venimos)
|
||||
int target_theme_index_ = 0; // Tema destino (al que vamos)
|
||||
int source_theme_index_ = 0; // Tema origen (del que venimos)
|
||||
int target_theme_index_ = 0; // Tema destino (al que vamos)
|
||||
|
||||
// Snapshot del tema origen (capturado al iniciar transición)
|
||||
std::unique_ptr<ThemeSnapshot> source_snapshot_; // nullptr si no hay transición
|
||||
@@ -112,6 +111,6 @@ class ThemeManager {
|
||||
void initializeDynamicThemes(); // Crea 6 temas dinámicos (índices 9-14)
|
||||
|
||||
// Sistema de transición LERP (PHASE 3)
|
||||
std::unique_ptr<ThemeSnapshot> captureCurrentSnapshot() const; // Captura snapshot del tema actual
|
||||
std::unique_ptr<ThemeSnapshot> captureCurrentSnapshot() const; // Captura snapshot del tema actual
|
||||
float lerp(float a, float b, float t) const { return a + (b - a) * t; } // Interpolación lineal
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
@@ -90,10 +92,10 @@ class DynamicTheme : public Theme {
|
||||
// ESTADO DE ANIMACIÓN
|
||||
// ========================================
|
||||
|
||||
size_t current_keyframe_index_ = 0; // Keyframe actual
|
||||
size_t target_keyframe_index_ = 1; // Próximo keyframe
|
||||
float transition_progress_ = 0.0f; // Progreso 0.0-1.0 hacia target
|
||||
bool paused_ = false; // Pausa manual con Shift+D
|
||||
size_t current_keyframe_index_ = 0; // Keyframe actual
|
||||
size_t target_keyframe_index_ = 1; // Próximo keyframe
|
||||
float transition_progress_ = 0.0f; // Progreso 0.0-1.0 hacia target
|
||||
bool paused_ = false; // Pausa manual con Shift+D
|
||||
|
||||
// ========================================
|
||||
// UTILIDADES PRIVADAS
|
||||
|
||||
@@ -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)
|
||||
@@ -58,7 +63,7 @@ class Theme {
|
||||
* Actualiza progreso de animación interna (solo dinámicos)
|
||||
* @param delta_time: Tiempo transcurrido desde último frame
|
||||
*/
|
||||
virtual void update(float delta_time) { }
|
||||
virtual void update(float delta_time) {}
|
||||
|
||||
/**
|
||||
* ¿Este tema necesita update() cada frame?
|
||||
@@ -75,7 +80,7 @@ class Theme {
|
||||
/**
|
||||
* Reinicia progreso de animación a 0.0 (usado al activar tema)
|
||||
*/
|
||||
virtual void resetProgress() { }
|
||||
virtual void resetProgress() {}
|
||||
|
||||
// ========================================
|
||||
// PAUSA (solo temas dinámicos)
|
||||
@@ -90,5 +95,5 @@ class Theme {
|
||||
/**
|
||||
* Toggle pausa de animación (solo dinámicos, tecla Shift+D)
|
||||
*/
|
||||
virtual void togglePause() { }
|
||||
virtual void togglePause() {}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "defines.hpp" // for Color
|
||||
|
||||
/**
|
||||
@@ -24,23 +25,23 @@
|
||||
* - Nombres del tema (para debug display durante transición)
|
||||
*/
|
||||
struct ThemeSnapshot {
|
||||
// Colores de fondo degradado
|
||||
float bg_top_r, bg_top_g, bg_top_b;
|
||||
float bg_bottom_r, bg_bottom_g, bg_bottom_b;
|
||||
// Colores de fondo degradado
|
||||
float bg_top_r, bg_top_g, bg_top_b;
|
||||
float bg_bottom_r, bg_bottom_g, bg_bottom_b;
|
||||
|
||||
// Color de texto UI
|
||||
int text_color_r, text_color_g, text_color_b;
|
||||
// Color de texto UI
|
||||
int text_color_r, text_color_g, text_color_b;
|
||||
|
||||
// Color de fondo de notificaciones
|
||||
int notif_bg_r, notif_bg_g, notif_bg_b;
|
||||
// Color de fondo de notificaciones
|
||||
int notif_bg_r, notif_bg_g, notif_bg_b;
|
||||
|
||||
// Nombres del tema (para mostrar "SOURCE → TARGET" durante transición)
|
||||
std::string name_en;
|
||||
std::string name_es;
|
||||
// Nombres del tema (para mostrar "SOURCE → TARGET" durante transición)
|
||||
std::string name_en;
|
||||
std::string name_es;
|
||||
|
||||
// Colores de pelotas capturados (índice = ball_index % ball_colors.size())
|
||||
// Se capturan suficientes colores para cubrir escenario máximo (50,000 pelotas)
|
||||
// Nota: Si el tema tiene 8 colores y capturamos 50,000, habrá repetición
|
||||
// pero permite LERP correcto incluso con muchas pelotas
|
||||
std::vector<Color> ball_colors;
|
||||
// Colores de pelotas capturados (índice = ball_index % ball_colors.size())
|
||||
// Se capturan suficientes colores para cubrir escenario máximo (50,000 pelotas)
|
||||
// Nota: Si el tema tiene 8 colores y capturamos 50,000, habrá repetición
|
||||
// pero permite LERP correcto incluso con muchas pelotas
|
||||
std::vector<Color> ball_colors;
|
||||
};
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
#include "app_logo.hpp"
|
||||
|
||||
#include <SDL3/SDL_render.h> // for SDL_DestroyTexture, SDL_RenderGeometry, SDL_SetTextureAlphaMod
|
||||
#include <cmath> // for powf, sinf, cosf
|
||||
#include <cstdlib> // for free()
|
||||
#include <iostream> // for std::cout
|
||||
|
||||
#include "logo_scaler.hpp" // for LogoScaler
|
||||
#include "defines.hpp" // for APPLOGO_HEIGHT_PERCENT, getResourcesDirectory
|
||||
#include <array> // for std::array
|
||||
#include <cmath> // for powf, sinf, cosf
|
||||
#include <cstdlib> // for free()
|
||||
#include <iostream> // for std::cout
|
||||
#include <numbers>
|
||||
|
||||
#include "defines.hpp" // for APPLOGO_HEIGHT_PERCENT, getResourcesDirectory
|
||||
#include "logo_scaler.hpp" // for LogoScaler
|
||||
|
||||
// ============================================================================
|
||||
// Destructor - Liberar las 4 texturas SDL
|
||||
// ============================================================================
|
||||
|
||||
AppLogo::~AppLogo() {
|
||||
if (logo1_base_texture_) {
|
||||
if (logo1_base_texture_ != nullptr) {
|
||||
SDL_DestroyTexture(logo1_base_texture_);
|
||||
logo1_base_texture_ = nullptr;
|
||||
}
|
||||
if (logo1_native_texture_) {
|
||||
if (logo1_native_texture_ != nullptr) {
|
||||
SDL_DestroyTexture(logo1_native_texture_);
|
||||
logo1_native_texture_ = nullptr;
|
||||
}
|
||||
if (logo2_base_texture_) {
|
||||
if (logo2_base_texture_ != nullptr) {
|
||||
SDL_DestroyTexture(logo2_base_texture_);
|
||||
logo2_base_texture_ = nullptr;
|
||||
}
|
||||
if (logo2_native_texture_) {
|
||||
if (logo2_native_texture_ != nullptr) {
|
||||
SDL_DestroyTexture(logo2_native_texture_);
|
||||
logo2_native_texture_ = nullptr;
|
||||
}
|
||||
@@ -35,11 +38,23 @@ AppLogo::~AppLogo() {
|
||||
// Inicialización - Pre-escalar logos a 2 resoluciones (base y nativa)
|
||||
// ============================================================================
|
||||
|
||||
bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) {
|
||||
if (logo1_base_texture_) { SDL_DestroyTexture(logo1_base_texture_); logo1_base_texture_ = nullptr; }
|
||||
if (logo1_native_texture_) { SDL_DestroyTexture(logo1_native_texture_); logo1_native_texture_ = nullptr; }
|
||||
if (logo2_base_texture_) { SDL_DestroyTexture(logo2_base_texture_); logo2_base_texture_ = nullptr; }
|
||||
if (logo2_native_texture_) { SDL_DestroyTexture(logo2_native_texture_); logo2_native_texture_ = nullptr; }
|
||||
auto AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) -> bool {
|
||||
if (logo1_base_texture_ != nullptr) {
|
||||
SDL_DestroyTexture(logo1_base_texture_);
|
||||
logo1_base_texture_ = nullptr;
|
||||
}
|
||||
if (logo1_native_texture_ != nullptr) {
|
||||
SDL_DestroyTexture(logo1_native_texture_);
|
||||
logo1_native_texture_ = nullptr;
|
||||
}
|
||||
if (logo2_base_texture_ != nullptr) {
|
||||
SDL_DestroyTexture(logo2_base_texture_);
|
||||
logo2_base_texture_ = nullptr;
|
||||
}
|
||||
if (logo2_native_texture_ != nullptr) {
|
||||
SDL_DestroyTexture(logo2_native_texture_);
|
||||
logo2_native_texture_ = nullptr;
|
||||
}
|
||||
|
||||
renderer_ = renderer;
|
||||
base_screen_width_ = screen_width;
|
||||
@@ -53,7 +68,7 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
||||
// 1. Detectar resolución nativa del monitor
|
||||
// ========================================================================
|
||||
if (!LogoScaler::detectNativeResolution(native_screen_width_, native_screen_height_)) {
|
||||
std::cout << "No se pudo detectar resolución nativa, usando solo base" << std::endl;
|
||||
std::cout << "No se pudo detectar resolución nativa, usando solo base" << '\n';
|
||||
// Fallback: usar resolución base como nativa
|
||||
native_screen_width_ = screen_width;
|
||||
native_screen_height_ = screen_height;
|
||||
@@ -76,20 +91,21 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
||||
0, // width calculado automáticamente por aspect ratio
|
||||
logo_base_target_height,
|
||||
logo1_base_width_,
|
||||
logo1_base_height_
|
||||
);
|
||||
logo1_base_height_);
|
||||
if (logo1_base_data == nullptr) {
|
||||
std::cout << "Error: No se pudo escalar logo1 (base)" << std::endl;
|
||||
std::cout << "Error: No se pudo escalar logo1 (base)" << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
logo1_base_texture_ = LogoScaler::createTextureFromBuffer(
|
||||
renderer, logo1_base_data, logo1_base_width_, logo1_base_height_
|
||||
);
|
||||
renderer,
|
||||
logo1_base_data,
|
||||
logo1_base_width_,
|
||||
logo1_base_height_);
|
||||
free(logo1_base_data); // Liberar buffer temporal
|
||||
|
||||
if (logo1_base_texture_ == nullptr) {
|
||||
std::cout << "Error: No se pudo crear textura logo1 (base)" << std::endl;
|
||||
std::cout << "Error: No se pudo crear textura logo1 (base)" << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -102,20 +118,21 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
||||
0, // width calculado automáticamente
|
||||
logo_native_target_height,
|
||||
logo1_native_width_,
|
||||
logo1_native_height_
|
||||
);
|
||||
logo1_native_height_);
|
||||
if (logo1_native_data == nullptr) {
|
||||
std::cout << "Error: No se pudo escalar logo1 (nativa)" << std::endl;
|
||||
std::cout << "Error: No se pudo escalar logo1 (nativa)" << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
logo1_native_texture_ = LogoScaler::createTextureFromBuffer(
|
||||
renderer, logo1_native_data, logo1_native_width_, logo1_native_height_
|
||||
);
|
||||
renderer,
|
||||
logo1_native_data,
|
||||
logo1_native_width_,
|
||||
logo1_native_height_);
|
||||
free(logo1_native_data);
|
||||
|
||||
if (logo1_native_texture_ == nullptr) {
|
||||
std::cout << "Error: No se pudo crear textura logo1 (nativa)" << std::endl;
|
||||
std::cout << "Error: No se pudo crear textura logo1 (nativa)" << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -132,20 +149,21 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
||||
0,
|
||||
logo_base_target_height,
|
||||
logo2_base_width_,
|
||||
logo2_base_height_
|
||||
);
|
||||
logo2_base_height_);
|
||||
if (logo2_base_data == nullptr) {
|
||||
std::cout << "Error: No se pudo escalar logo2 (base)" << std::endl;
|
||||
std::cout << "Error: No se pudo escalar logo2 (base)" << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
logo2_base_texture_ = LogoScaler::createTextureFromBuffer(
|
||||
renderer, logo2_base_data, logo2_base_width_, logo2_base_height_
|
||||
);
|
||||
renderer,
|
||||
logo2_base_data,
|
||||
logo2_base_width_,
|
||||
logo2_base_height_);
|
||||
free(logo2_base_data);
|
||||
|
||||
if (logo2_base_texture_ == nullptr) {
|
||||
std::cout << "Error: No se pudo crear textura logo2 (base)" << std::endl;
|
||||
std::cout << "Error: No se pudo crear textura logo2 (base)" << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -157,20 +175,21 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
||||
0,
|
||||
logo_native_target_height,
|
||||
logo2_native_width_,
|
||||
logo2_native_height_
|
||||
);
|
||||
logo2_native_height_);
|
||||
if (logo2_native_data == nullptr) {
|
||||
std::cout << "Error: No se pudo escalar logo2 (nativa)" << std::endl;
|
||||
std::cout << "Error: No se pudo escalar logo2 (nativa)" << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
logo2_native_texture_ = LogoScaler::createTextureFromBuffer(
|
||||
renderer, logo2_native_data, logo2_native_width_, logo2_native_height_
|
||||
);
|
||||
renderer,
|
||||
logo2_native_data,
|
||||
logo2_native_width_,
|
||||
logo2_native_height_);
|
||||
free(logo2_native_data);
|
||||
|
||||
if (logo2_native_texture_ == nullptr) {
|
||||
std::cout << "Error: No se pudo crear textura logo2 (nativa)" << std::endl;
|
||||
std::cout << "Error: No se pudo crear textura logo2 (nativa)" << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -191,7 +210,7 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
void AppLogo::update(float delta_time, AppMode current_mode) { // NOLINT(readability-function-cognitive-complexity)
|
||||
// Si estamos en SANDBOX, resetear y no hacer nada (logo desactivado)
|
||||
if (current_mode == AppMode::SANDBOX) {
|
||||
state_ = AppLogoState::HIDDEN;
|
||||
@@ -262,63 +281,57 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
logo2_rotation_ = 0.0f;
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ELASTIC_STICK:
|
||||
{
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
float elastic_t1 = easeOutElastic(prog1);
|
||||
logo1_scale_ = 1.2f - (elastic_t1 * 0.2f);
|
||||
float squash_t1 = easeOutBack(prog1);
|
||||
logo1_squash_y_ = 0.6f + (squash_t1 * 0.4f);
|
||||
logo1_stretch_x_ = 1.0f + (1.0f - logo1_squash_y_) * 0.5f;
|
||||
logo1_rotation_ = 0.0f;
|
||||
case AppLogoAnimationType::ELASTIC_STICK: {
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
float elastic_t1 = easeOutElastic(prog1);
|
||||
logo1_scale_ = 1.2f - (elastic_t1 * 0.2f);
|
||||
float squash_t1 = easeOutBack(prog1);
|
||||
logo1_squash_y_ = 0.6f + (squash_t1 * 0.4f);
|
||||
logo1_stretch_x_ = 1.0f + (1.0f - logo1_squash_y_) * 0.5f;
|
||||
logo1_rotation_ = 0.0f;
|
||||
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
float elastic_t2 = easeOutElastic(prog2);
|
||||
logo2_scale_ = 1.2f - (elastic_t2 * 0.2f);
|
||||
float squash_t2 = easeOutBack(prog2);
|
||||
logo2_squash_y_ = 0.6f + (squash_t2 * 0.4f);
|
||||
logo2_stretch_x_ = 1.0f + (1.0f - logo2_squash_y_) * 0.5f;
|
||||
logo2_rotation_ = 0.0f;
|
||||
}
|
||||
break;
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
float elastic_t2 = easeOutElastic(prog2);
|
||||
logo2_scale_ = 1.2f - (elastic_t2 * 0.2f);
|
||||
float squash_t2 = easeOutBack(prog2);
|
||||
logo2_squash_y_ = 0.6f + (squash_t2 * 0.4f);
|
||||
logo2_stretch_x_ = 1.0f + (1.0f - logo2_squash_y_) * 0.5f;
|
||||
logo2_rotation_ = 0.0f;
|
||||
} break;
|
||||
|
||||
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||
{
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
float ease_t1 = easeInOutQuad(prog1);
|
||||
logo1_scale_ = 0.3f + (ease_t1 * 0.7f);
|
||||
logo1_rotation_ = (1.0f - prog1) * 6.28f;
|
||||
logo1_squash_y_ = 1.0f;
|
||||
logo1_stretch_x_ = 1.0f;
|
||||
case AppLogoAnimationType::ROTATE_SPIRAL: {
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
float ease_t1 = easeInOutQuad(prog1);
|
||||
logo1_scale_ = 0.3f + (ease_t1 * 0.7f);
|
||||
logo1_rotation_ = (1.0f - prog1) * 6.28f;
|
||||
logo1_squash_y_ = 1.0f;
|
||||
logo1_stretch_x_ = 1.0f;
|
||||
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
float ease_t2 = easeInOutQuad(prog2);
|
||||
logo2_scale_ = 0.3f + (ease_t2 * 0.7f);
|
||||
logo2_rotation_ = (1.0f - prog2) * 6.28f;
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
}
|
||||
break;
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
float ease_t2 = easeInOutQuad(prog2);
|
||||
logo2_scale_ = 0.3f + (ease_t2 * 0.7f);
|
||||
logo2_rotation_ = (1.0f - prog2) * 6.28f;
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
} break;
|
||||
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||
{
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
float bounce_t1 = easeOutBounce(prog1);
|
||||
logo1_scale_ = 1.0f;
|
||||
float squash_amount1 = (1.0f - bounce_t1) * 0.3f;
|
||||
logo1_squash_y_ = 1.0f - squash_amount1;
|
||||
logo1_stretch_x_ = 1.0f + squash_amount1 * 0.5f;
|
||||
logo1_rotation_ = 0.0f;
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH: {
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
float bounce_t1 = easeOutBounce(prog1);
|
||||
logo1_scale_ = 1.0f;
|
||||
float squash_amount1 = (1.0f - bounce_t1) * 0.3f;
|
||||
logo1_squash_y_ = 1.0f - squash_amount1;
|
||||
logo1_stretch_x_ = 1.0f + squash_amount1 * 0.5f;
|
||||
logo1_rotation_ = 0.0f;
|
||||
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
float bounce_t2 = easeOutBounce(prog2);
|
||||
logo2_scale_ = 1.0f;
|
||||
float squash_amount2 = (1.0f - bounce_t2) * 0.3f;
|
||||
logo2_squash_y_ = 1.0f - squash_amount2;
|
||||
logo2_stretch_x_ = 1.0f + squash_amount2 * 0.5f;
|
||||
logo2_rotation_ = 0.0f;
|
||||
}
|
||||
break;
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
float bounce_t2 = easeOutBounce(prog2);
|
||||
logo2_scale_ = 1.0f;
|
||||
float squash_amount2 = (1.0f - bounce_t2) * 0.3f;
|
||||
logo2_squash_y_ = 1.0f - squash_amount2;
|
||||
logo2_stretch_x_ = 1.0f + squash_amount2 * 0.5f;
|
||||
logo2_rotation_ = 0.0f;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -379,69 +392,63 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
logo2_rotation_ = 0.0f;
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ELASTIC_STICK:
|
||||
{
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
logo1_scale_ = 1.0f + (prog1 * prog1 * 0.2f);
|
||||
logo1_squash_y_ = 1.0f + (prog1 * 0.3f);
|
||||
logo1_stretch_x_ = 1.0f - (prog1 * 0.2f);
|
||||
logo1_rotation_ = prog1 * 0.1f;
|
||||
case AppLogoAnimationType::ELASTIC_STICK: {
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
logo1_scale_ = 1.0f + (prog1 * prog1 * 0.2f);
|
||||
logo1_squash_y_ = 1.0f + (prog1 * 0.3f);
|
||||
logo1_stretch_x_ = 1.0f - (prog1 * 0.2f);
|
||||
logo1_rotation_ = prog1 * 0.1f;
|
||||
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
logo2_scale_ = 1.0f + (prog2 * prog2 * 0.2f);
|
||||
logo2_squash_y_ = 1.0f + (prog2 * 0.3f);
|
||||
logo2_stretch_x_ = 1.0f - (prog2 * 0.2f);
|
||||
logo2_rotation_ = prog2 * 0.1f;
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
logo2_scale_ = 1.0f + (prog2 * prog2 * 0.2f);
|
||||
logo2_squash_y_ = 1.0f + (prog2 * 0.3f);
|
||||
logo2_stretch_x_ = 1.0f - (prog2 * 0.2f);
|
||||
logo2_rotation_ = prog2 * 0.1f;
|
||||
} break;
|
||||
|
||||
case AppLogoAnimationType::ROTATE_SPIRAL: {
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
float ease_t1 = easeInOutQuad(prog1);
|
||||
logo1_scale_ = 1.0f - (ease_t1 * 0.7f);
|
||||
logo1_rotation_ = prog1 * 6.28f;
|
||||
logo1_squash_y_ = 1.0f;
|
||||
logo1_stretch_x_ = 1.0f;
|
||||
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
float ease_t2 = easeInOutQuad(prog2);
|
||||
logo2_scale_ = 1.0f - (ease_t2 * 0.7f);
|
||||
logo2_rotation_ = prog2 * 6.28f;
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
} break;
|
||||
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH: {
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
if (prog1 < 0.2f) {
|
||||
float squash_t = prog1 / 0.2f;
|
||||
logo1_squash_y_ = 1.0f - (squash_t * 0.3f);
|
||||
logo1_stretch_x_ = 1.0f + (squash_t * 0.2f);
|
||||
} else {
|
||||
float jump_t = (prog1 - 0.2f) / 0.8f;
|
||||
logo1_squash_y_ = 0.7f + (jump_t * 0.5f);
|
||||
logo1_stretch_x_ = 1.2f - (jump_t * 0.2f);
|
||||
}
|
||||
break;
|
||||
logo1_scale_ = 1.0f + (prog1 * 0.3f);
|
||||
logo1_rotation_ = 0.0f;
|
||||
|
||||
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||
{
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
float ease_t1 = easeInOutQuad(prog1);
|
||||
logo1_scale_ = 1.0f - (ease_t1 * 0.7f);
|
||||
logo1_rotation_ = prog1 * 6.28f;
|
||||
logo1_squash_y_ = 1.0f;
|
||||
logo1_stretch_x_ = 1.0f;
|
||||
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
float ease_t2 = easeInOutQuad(prog2);
|
||||
logo2_scale_ = 1.0f - (ease_t2 * 0.7f);
|
||||
logo2_rotation_ = prog2 * 6.28f;
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
if (prog2 < 0.2f) {
|
||||
float squash_t = prog2 / 0.2f;
|
||||
logo2_squash_y_ = 1.0f - (squash_t * 0.3f);
|
||||
logo2_stretch_x_ = 1.0f + (squash_t * 0.2f);
|
||||
} else {
|
||||
float jump_t = (prog2 - 0.2f) / 0.8f;
|
||||
logo2_squash_y_ = 0.7f + (jump_t * 0.5f);
|
||||
logo2_stretch_x_ = 1.2f - (jump_t * 0.2f);
|
||||
}
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||
{
|
||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||
if (prog1 < 0.2f) {
|
||||
float squash_t = prog1 / 0.2f;
|
||||
logo1_squash_y_ = 1.0f - (squash_t * 0.3f);
|
||||
logo1_stretch_x_ = 1.0f + (squash_t * 0.2f);
|
||||
} else {
|
||||
float jump_t = (prog1 - 0.2f) / 0.8f;
|
||||
logo1_squash_y_ = 0.7f + (jump_t * 0.5f);
|
||||
logo1_stretch_x_ = 1.2f - (jump_t * 0.2f);
|
||||
}
|
||||
logo1_scale_ = 1.0f + (prog1 * 0.3f);
|
||||
logo1_rotation_ = 0.0f;
|
||||
|
||||
float prog2 = std::min(1.0f, fade_progress_logo2);
|
||||
if (prog2 < 0.2f) {
|
||||
float squash_t = prog2 / 0.2f;
|
||||
logo2_squash_y_ = 1.0f - (squash_t * 0.3f);
|
||||
logo2_stretch_x_ = 1.0f + (squash_t * 0.2f);
|
||||
} else {
|
||||
float jump_t = (prog2 - 0.2f) / 0.8f;
|
||||
logo2_squash_y_ = 0.7f + (jump_t * 0.5f);
|
||||
logo2_stretch_x_ = 1.2f - (jump_t * 0.2f);
|
||||
}
|
||||
logo2_scale_ = 1.0f + (prog2 * 0.3f);
|
||||
logo2_rotation_ = 0.0f;
|
||||
}
|
||||
break;
|
||||
logo2_scale_ = 1.0f + (prog2 * 0.3f);
|
||||
logo2_rotation_ = 0.0f;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -477,7 +484,7 @@ void AppLogo::updateScreenSize(int screen_width, int screen_height) {
|
||||
logo2_current_width_ = logo2_native_width_;
|
||||
logo2_current_height_ = logo2_native_height_;
|
||||
|
||||
std::cout << "AppLogo: Cambiado a texturas NATIVAS" << std::endl;
|
||||
std::cout << "AppLogo: Cambiado a texturas NATIVAS" << '\n';
|
||||
} else {
|
||||
// Cambiar a texturas base (ventana redimensionable)
|
||||
logo1_current_texture_ = logo1_base_texture_;
|
||||
@@ -488,7 +495,7 @@ void AppLogo::updateScreenSize(int screen_width, int screen_height) {
|
||||
logo2_current_width_ = logo2_base_width_;
|
||||
logo2_current_height_ = logo2_base_height_;
|
||||
|
||||
std::cout << "AppLogo: Cambiado a texturas BASE" << std::endl;
|
||||
std::cout << "AppLogo: Cambiado a texturas BASE" << '\n';
|
||||
}
|
||||
|
||||
// Nota: No es necesario recalcular escalas porque las texturas están pre-escaladas
|
||||
@@ -499,57 +506,61 @@ void AppLogo::updateScreenSize(int screen_width, int screen_height) {
|
||||
// Funciones de easing para animaciones
|
||||
// ============================================================================
|
||||
|
||||
float AppLogo::easeOutElastic(float t) {
|
||||
auto AppLogo::easeOutElastic(float t) -> float {
|
||||
// Elastic easing out: bounce elástico al final
|
||||
const float c4 = (2.0f * 3.14159f) / 3.0f;
|
||||
const float C4 = (2.0f * std::numbers::pi_v<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;
|
||||
}
|
||||
|
||||
float AppLogo::easeOutBack(float t) {
|
||||
// Back easing out: overshoot suave al final
|
||||
const float c1 = 1.70158f;
|
||||
const float c3 = c1 + 1.0f;
|
||||
|
||||
return 1.0f + c3 * powf(t - 1.0f, 3.0f) + c1 * powf(t - 1.0f, 2.0f);
|
||||
}
|
||||
|
||||
float AppLogo::easeOutBounce(float t) {
|
||||
// Bounce easing out: rebotes decrecientes (para BOUNCE_SQUASH)
|
||||
const float n1 = 7.5625f;
|
||||
const float d1 = 2.75f;
|
||||
|
||||
if (t < 1.0f / d1) {
|
||||
return n1 * t * t;
|
||||
} else if (t < 2.0f / d1) {
|
||||
t -= 1.5f / d1;
|
||||
return n1 * t * t + 0.75f;
|
||||
} else if (t < 2.5f / d1) {
|
||||
t -= 2.25f / d1;
|
||||
return n1 * t * t + 0.9375f;
|
||||
} else {
|
||||
t -= 2.625f / d1;
|
||||
return n1 * t * t + 0.984375f;
|
||||
if (t == 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
if (t == 1.0f) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return (powf(2.0f, -10.0f * t) * sinf((t * 10.0f - 0.75f) * C4)) + 1.0f;
|
||||
}
|
||||
|
||||
float AppLogo::easeInOutQuad(float t) {
|
||||
auto AppLogo::easeOutBack(float t) -> float {
|
||||
// Back easing out: overshoot suave al final
|
||||
const float C1 = 1.70158f;
|
||||
const float C3 = C1 + 1.0f;
|
||||
|
||||
return 1.0f + (C3 * powf(t - 1.0f, 3.0f)) + (C1 * powf(t - 1.0f, 2.0f));
|
||||
}
|
||||
|
||||
auto AppLogo::easeOutBounce(float t) -> float {
|
||||
// Bounce easing out: rebotes decrecientes (para BOUNCE_SQUASH)
|
||||
const float N1 = 7.5625f;
|
||||
const float D1 = 2.75f;
|
||||
|
||||
if (t < 1.0f / D1) {
|
||||
return N1 * t * t;
|
||||
}
|
||||
if (t < 2.0f / D1) {
|
||||
t -= 1.5f / D1;
|
||||
return (N1 * t * t) + 0.75f;
|
||||
}
|
||||
if (t < 2.5f / D1) {
|
||||
t -= 2.25f / D1;
|
||||
return (N1 * t * t) + 0.9375f;
|
||||
}
|
||||
t -= 2.625f / D1;
|
||||
return (N1 * t * t) + 0.984375f;
|
||||
}
|
||||
|
||||
auto AppLogo::easeInOutQuad(float t) -> float {
|
||||
// Quadratic easing in/out: aceleración suave (para ROTATE_SPIRAL)
|
||||
if (t < 0.5f) {
|
||||
return 2.0f * t * t;
|
||||
} else {
|
||||
return 1.0f - powf(-2.0f * t + 2.0f, 2.0f) / 2.0f;
|
||||
}
|
||||
return 1.0f - (powf((-2.0f * t) + 2.0f, 2.0f) / 2.0f);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Función auxiliar para aleatorización
|
||||
// ============================================================================
|
||||
|
||||
AppLogoAnimationType AppLogo::getRandomAnimation() {
|
||||
auto AppLogo::getRandomAnimation() -> AppLogoAnimationType {
|
||||
// Generar número aleatorio entre 0 y 3 (4 tipos de animación)
|
||||
int random_value = rand() % 4;
|
||||
|
||||
@@ -571,15 +582,23 @@ AppLogoAnimationType AppLogo::getRandomAnimation() {
|
||||
// ============================================================================
|
||||
|
||||
void AppLogo::renderWithGeometry(int logo_index) {
|
||||
if (!renderer_) return;
|
||||
if (renderer_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Seleccionar variables según el logo_index (1 = logo1, 2 = logo2)
|
||||
SDL_Texture* texture;
|
||||
int base_width, base_height;
|
||||
float scale, squash_y, stretch_x, rotation;
|
||||
int base_width;
|
||||
int base_height;
|
||||
float scale;
|
||||
float squash_y;
|
||||
float stretch_x;
|
||||
float rotation;
|
||||
|
||||
if (logo_index == 1) {
|
||||
if (!logo1_current_texture_) return;
|
||||
if (logo1_current_texture_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
texture = logo1_current_texture_;
|
||||
base_width = logo1_current_width_;
|
||||
base_height = logo1_current_height_;
|
||||
@@ -588,7 +607,9 @@ void AppLogo::renderWithGeometry(int logo_index) {
|
||||
stretch_x = logo1_stretch_x_;
|
||||
rotation = logo1_rotation_;
|
||||
} else if (logo_index == 2) {
|
||||
if (!logo2_current_texture_) return;
|
||||
if (logo2_current_texture_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
texture = logo2_current_texture_;
|
||||
base_width = logo2_current_width_;
|
||||
base_height = logo2_current_height_;
|
||||
@@ -628,7 +649,7 @@ void AppLogo::renderWithGeometry(int logo_index) {
|
||||
float sin_rot = sinf(rotation);
|
||||
|
||||
// Crear 4 vértices del quad (centrado en center_x, center_y)
|
||||
SDL_Vertex vertices[4];
|
||||
std::array<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);
|
||||
}
|
||||
|
||||
@@ -11,18 +11,18 @@ class Sprite;
|
||||
|
||||
// Estados de la máquina de estados del logo
|
||||
enum class AppLogoState {
|
||||
HIDDEN, // Logo oculto, esperando APPLOGO_DISPLAY_INTERVAL
|
||||
FADE_IN, // Apareciendo (alpha 0 → 255)
|
||||
VISIBLE, // Completamente visible, esperando APPLOGO_DISPLAY_DURATION
|
||||
FADE_OUT // Desapareciendo (alpha 255 → 0)
|
||||
HIDDEN, // Logo oculto, esperando APPLOGO_DISPLAY_INTERVAL
|
||||
FADE_IN, // Apareciendo (alpha 0 → 255)
|
||||
VISIBLE, // Completamente visible, esperando APPLOGO_DISPLAY_DURATION
|
||||
FADE_OUT // Desapareciendo (alpha 255 → 0)
|
||||
};
|
||||
|
||||
// Tipo de animación de entrada/salida
|
||||
enum class AppLogoAnimationType {
|
||||
ZOOM_ONLY, // A: Solo zoom simple (120% → 100% → 120%)
|
||||
ELASTIC_STICK, // B: Zoom + deformación elástica tipo "pegatina"
|
||||
ROTATE_SPIRAL, // C: Rotación en espiral (entra girando, sale girando)
|
||||
BOUNCE_SQUASH // D: Rebote con aplastamiento (cae rebotando, salta)
|
||||
ZOOM_ONLY, // A: Solo zoom simple (120% → 100% → 120%)
|
||||
ELASTIC_STICK, // B: Zoom + deformación elástica tipo "pegatina"
|
||||
ROTATE_SPIRAL, // C: Rotación en espiral (entra girando, sale girando)
|
||||
BOUNCE_SQUASH // D: Rebote con aplastamiento (cae rebotando, salta)
|
||||
};
|
||||
|
||||
class AppLogo {
|
||||
@@ -46,10 +46,10 @@ class AppLogo {
|
||||
// ====================================================================
|
||||
// Texturas pre-escaladas (4 texturas: 2 logos × 2 resoluciones)
|
||||
// ====================================================================
|
||||
SDL_Texture* logo1_base_texture_ = nullptr; // Logo1 para resolución base
|
||||
SDL_Texture* logo1_native_texture_ = nullptr; // Logo1 para resolución nativa (F4)
|
||||
SDL_Texture* logo2_base_texture_ = nullptr; // Logo2 para resolución base
|
||||
SDL_Texture* logo2_native_texture_ = nullptr; // Logo2 para resolución nativa (F4)
|
||||
SDL_Texture* logo1_base_texture_ = nullptr; // Logo1 para resolución base
|
||||
SDL_Texture* logo1_native_texture_ = nullptr; // Logo1 para resolución nativa (F4)
|
||||
SDL_Texture* logo2_base_texture_ = nullptr; // Logo2 para resolución base
|
||||
SDL_Texture* logo2_native_texture_ = nullptr; // Logo2 para resolución nativa (F4)
|
||||
|
||||
// Dimensiones pre-calculadas para cada textura
|
||||
int logo1_base_width_ = 0, logo1_base_height_ = 0;
|
||||
@@ -64,8 +64,8 @@ class AppLogo {
|
||||
int logo2_current_width_ = 0, logo2_current_height_ = 0;
|
||||
|
||||
// Resoluciones conocidas
|
||||
int base_screen_width_ = 0, base_screen_height_ = 0; // Resolución inicial
|
||||
int native_screen_width_ = 0, native_screen_height_ = 0; // Resolución nativa (F4)
|
||||
int base_screen_width_ = 0, base_screen_height_ = 0; // Resolución inicial
|
||||
int native_screen_width_ = 0, native_screen_height_ = 0; // Resolución nativa (F4)
|
||||
|
||||
// ====================================================================
|
||||
// Variables COMPARTIDAS (sincronización de ambos logos)
|
||||
@@ -74,8 +74,8 @@ class AppLogo {
|
||||
float timer_ = 0.0f; // Contador de tiempo para estado actual
|
||||
|
||||
// Alpha INDEPENDIENTE para cada logo (Logo 2 con retraso)
|
||||
int logo1_alpha_ = 0; // Alpha de Logo 1 (0-255)
|
||||
int logo2_alpha_ = 0; // Alpha de Logo 2 (0-255, con retraso)
|
||||
int logo1_alpha_ = 0; // Alpha de Logo 1 (0-255)
|
||||
int logo2_alpha_ = 0; // Alpha de Logo 2 (0-255, con retraso)
|
||||
|
||||
// Animación COMPARTIDA (misma para ambos logos, misma entrada y salida)
|
||||
AppLogoAnimationType current_animation_ = AppLogoAnimationType::ZOOM_ONLY;
|
||||
@@ -103,15 +103,15 @@ class AppLogo {
|
||||
SDL_Renderer* renderer_ = nullptr;
|
||||
|
||||
// Métodos privados auxiliares
|
||||
void updateLogoPosition(); // Centrar ambos logos en pantalla (superpuestos)
|
||||
void updateLogoPosition(); // Centrar ambos logos en pantalla (superpuestos)
|
||||
void renderWithGeometry(int logo_index); // Renderizar logo con vértices deformados (1 o 2)
|
||||
|
||||
// Funciones de easing
|
||||
float easeOutElastic(float t); // Elastic bounce out
|
||||
float easeOutBack(float t); // Overshoot out
|
||||
float easeOutBounce(float t); // Bounce easing (para BOUNCE_SQUASH)
|
||||
float easeInOutQuad(float t); // Quadratic easing (para ROTATE_SPIRAL)
|
||||
static float easeOutElastic(float t); // Elastic bounce out
|
||||
static float easeOutBack(float t); // Overshoot out
|
||||
static float easeOutBounce(float t); // Bounce easing (para BOUNCE_SQUASH)
|
||||
static float easeInOutQuad(float t); // Quadratic easing (para ROTATE_SPIRAL)
|
||||
|
||||
// Función auxiliar para elegir animación aleatoria
|
||||
AppLogoAnimationType getRandomAnimation();
|
||||
static AppLogoAnimationType getRandomAnimation();
|
||||
};
|
||||
|
||||
@@ -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_) {
|
||||
@@ -220,11 +223,11 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
|
||||
}
|
||||
|
||||
if (binding.key[0] == '\0') {
|
||||
col_heights[current_column] += line_height; // separador vacío
|
||||
col_heights[current_column] += line_height; // separador vacío
|
||||
} else if (binding.description[0] == '\0') {
|
||||
col_heights[current_column] += line_height; // encabezado
|
||||
col_heights[current_column] += line_height; // encabezado
|
||||
} else {
|
||||
col_heights[current_column] += line_height; // línea normal
|
||||
col_heights[current_column] += line_height; // línea normal
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +243,8 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
|
||||
|
||||
void HelpOverlay::calculateBoxDimensions() {
|
||||
// Calcular dimensiones necesarias según el texto
|
||||
int text_width, text_height;
|
||||
int text_width;
|
||||
int text_height;
|
||||
calculateTextDimensions(text_width, text_height);
|
||||
|
||||
// Aplicar límites máximos: 95% ancho, 90% altura
|
||||
@@ -253,26 +257,27 @@ void HelpOverlay::calculateBoxDimensions() {
|
||||
// Centrar en pantalla
|
||||
box_x_ = (physical_width_ - box_width_) / 2;
|
||||
box_y_ = (physical_height_ - box_height_) / 2;
|
||||
|
||||
}
|
||||
|
||||
void HelpOverlay::rebuildCachedTexture() {
|
||||
if (!renderer_ || !theme_mgr_ || !text_renderer_) return;
|
||||
if ((renderer_ == nullptr) || (theme_mgr_ == nullptr) || (text_renderer_ == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Destruir textura anterior si existe
|
||||
if (cached_texture_) {
|
||||
if (cached_texture_ != nullptr) {
|
||||
SDL_DestroyTexture(cached_texture_);
|
||||
cached_texture_ = nullptr;
|
||||
}
|
||||
|
||||
// Crear nueva textura del tamaño del overlay
|
||||
cached_texture_ = SDL_CreateTexture(renderer_,
|
||||
SDL_PIXELFORMAT_RGBA8888,
|
||||
SDL_TEXTUREACCESS_TARGET,
|
||||
box_width_,
|
||||
box_height_);
|
||||
SDL_PIXELFORMAT_RGBA8888,
|
||||
SDL_TEXTUREACCESS_TARGET,
|
||||
box_width_,
|
||||
box_height_);
|
||||
|
||||
if (!cached_texture_) {
|
||||
if (cached_texture_ == nullptr) {
|
||||
SDL_Log("Error al crear textura cacheada: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
@@ -294,42 +299,46 @@ void HelpOverlay::rebuildCachedTexture() {
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Obtener colores actuales del tema
|
||||
int notif_bg_r, notif_bg_g, notif_bg_b;
|
||||
int notif_bg_r;
|
||||
int notif_bg_g;
|
||||
int notif_bg_b;
|
||||
theme_mgr_->getCurrentNotificationBackgroundColor(notif_bg_r, notif_bg_g, notif_bg_b);
|
||||
|
||||
// Renderizar fondo del overlay a la textura
|
||||
float alpha = 0.85f;
|
||||
SDL_Vertex bg_vertices[4];
|
||||
std::array<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);
|
||||
@@ -433,22 +448,24 @@ void HelpOverlay::render(SDL_Renderer* renderer) {
|
||||
constexpr int COLOR_CHANGE_THRESHOLD = 5;
|
||||
bool colors_changed =
|
||||
(abs(current_bg.r - last_bg_color_.r) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_bg.g - last_bg_color_.g) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_bg.b - last_bg_color_.b) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_category.r - last_category_color_.r) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_category.g - last_category_color_.g) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_category.b - last_category_color_.b) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_content.r - last_content_color_.r) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_content.g - last_content_color_.g) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_content.b - last_content_color_.b) > COLOR_CHANGE_THRESHOLD);
|
||||
abs(current_bg.g - last_bg_color_.g) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_bg.b - last_bg_color_.b) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_category.r - last_category_color_.r) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_category.g - last_category_color_.g) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_category.b - last_category_color_.b) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_content.r - last_content_color_.r) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_content.g - last_content_color_.g) > COLOR_CHANGE_THRESHOLD ||
|
||||
abs(current_content.b - last_content_color_.b) > COLOR_CHANGE_THRESHOLD);
|
||||
|
||||
// Regenerar textura si es necesario (colores cambiaron O flag de rebuild activo)
|
||||
if (texture_needs_rebuild_ || colors_changed || !cached_texture_) {
|
||||
if (texture_needs_rebuild_ || colors_changed || (cached_texture_ == nullptr)) {
|
||||
rebuildCachedTexture();
|
||||
}
|
||||
|
||||
// Si no hay textura cacheada (error), salir
|
||||
if (!cached_texture_) return;
|
||||
if (cached_texture_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// CRÍTICO: Habilitar alpha blending para que la transparencia funcione
|
||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||
@@ -460,8 +477,8 @@ void HelpOverlay::render(SDL_Renderer* renderer) {
|
||||
// Calcular posición centrada dentro del VIEWPORT, no de la pantalla física
|
||||
// viewport.w y viewport.h son las dimensiones del área visible
|
||||
// viewport.x y viewport.y son el offset de las barras negras
|
||||
int centered_x = viewport.x + (viewport.w - box_width_) / 2;
|
||||
int centered_y = viewport.y + (viewport.h - box_height_) / 2;
|
||||
int centered_x = viewport.x + ((viewport.w - box_width_) / 2);
|
||||
int centered_y = viewport.y + ((viewport.h - box_height_) / 2);
|
||||
|
||||
// Renderizar la textura cacheada centrada en el viewport
|
||||
SDL_FRect dest_rect;
|
||||
|
||||
@@ -17,89 +17,89 @@ class TextRenderer;
|
||||
* Toggle on/off con tecla H. La simulación continúa en el fondo.
|
||||
*/
|
||||
class HelpOverlay {
|
||||
public:
|
||||
HelpOverlay();
|
||||
~HelpOverlay();
|
||||
public:
|
||||
HelpOverlay();
|
||||
~HelpOverlay();
|
||||
|
||||
/**
|
||||
* @brief Inicializa el overlay con renderer y theme manager
|
||||
*/
|
||||
void initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height, int font_size);
|
||||
/**
|
||||
* @brief Inicializa el overlay con renderer y theme manager
|
||||
*/
|
||||
void initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height, int font_size);
|
||||
|
||||
/**
|
||||
* @brief Renderiza el overlay si está visible
|
||||
*/
|
||||
void render(SDL_Renderer* renderer);
|
||||
/**
|
||||
* @brief Renderiza el overlay si está visible
|
||||
*/
|
||||
void render(SDL_Renderer* renderer);
|
||||
|
||||
/**
|
||||
* @brief Actualiza dimensiones físicas de ventana (zoom, fullscreen, etc.)
|
||||
*/
|
||||
void updatePhysicalWindowSize(int physical_width, int physical_height);
|
||||
/**
|
||||
* @brief Actualiza dimensiones físicas de ventana (zoom, fullscreen, etc.)
|
||||
*/
|
||||
void updatePhysicalWindowSize(int physical_width, int physical_height);
|
||||
|
||||
/**
|
||||
* @brief Reinitializa el tamaño de fuente (cuando cambia el tamaño de ventana)
|
||||
*/
|
||||
void reinitializeFontSize(int new_font_size);
|
||||
/**
|
||||
* @brief Reinitializa el tamaño de fuente (cuando cambia el tamaño de ventana)
|
||||
*/
|
||||
void reinitializeFontSize(int new_font_size);
|
||||
|
||||
/**
|
||||
* @brief Actualiza font size Y dimensiones físicas de forma atómica
|
||||
* @param font_size Tamaño de fuente actual
|
||||
* @param physical_width Nueva anchura física
|
||||
* @param physical_height Nueva altura física
|
||||
*/
|
||||
void updateAll(int font_size, int physical_width, int physical_height);
|
||||
/**
|
||||
* @brief Actualiza font size Y dimensiones físicas de forma atómica
|
||||
* @param font_size Tamaño de fuente actual
|
||||
* @param physical_width Nueva anchura física
|
||||
* @param physical_height Nueva altura física
|
||||
*/
|
||||
void updateAll(int font_size, int physical_width, int physical_height);
|
||||
|
||||
/**
|
||||
* @brief Toggle visibilidad del overlay
|
||||
*/
|
||||
void toggle();
|
||||
/**
|
||||
* @brief Toggle visibilidad del overlay
|
||||
*/
|
||||
void toggle();
|
||||
|
||||
/**
|
||||
* @brief Consulta si el overlay está visible
|
||||
*/
|
||||
bool isVisible() const { return visible_; }
|
||||
/**
|
||||
* @brief Consulta si el overlay está visible
|
||||
*/
|
||||
bool isVisible() const { return visible_; }
|
||||
|
||||
private:
|
||||
SDL_Renderer* renderer_;
|
||||
ThemeManager* theme_mgr_;
|
||||
TextRenderer* text_renderer_; // Renderer de texto para la ayuda
|
||||
int physical_width_;
|
||||
int physical_height_;
|
||||
bool visible_;
|
||||
private:
|
||||
SDL_Renderer* renderer_;
|
||||
ThemeManager* theme_mgr_;
|
||||
TextRenderer* text_renderer_; // Renderer de texto para la ayuda
|
||||
int physical_width_;
|
||||
int physical_height_;
|
||||
bool visible_;
|
||||
|
||||
// Dimensiones calculadas del recuadro (anchura dinámica según texto, centrado)
|
||||
int box_width_;
|
||||
int box_height_;
|
||||
int box_x_;
|
||||
int box_y_;
|
||||
// Dimensiones calculadas del recuadro (anchura dinámica según texto, centrado)
|
||||
int box_width_;
|
||||
int box_height_;
|
||||
int box_x_;
|
||||
int box_y_;
|
||||
|
||||
// Anchos individuales de cada columna (para evitar solapamiento)
|
||||
int column1_width_;
|
||||
int column2_width_;
|
||||
int column3_width_;
|
||||
// Anchos individuales de cada columna (para evitar solapamiento)
|
||||
int column1_width_;
|
||||
int column2_width_;
|
||||
int column3_width_;
|
||||
|
||||
// Sistema de caché para optimización de rendimiento
|
||||
SDL_Texture* cached_texture_; // Textura cacheada del overlay completo
|
||||
SDL_Color last_category_color_; // Último color de categorías renderizado
|
||||
SDL_Color last_content_color_; // Último color de contenido renderizado
|
||||
SDL_Color last_bg_color_; // Último color de fondo renderizado
|
||||
bool texture_needs_rebuild_; // Flag para forzar regeneración de textura
|
||||
// Sistema de caché para optimización de rendimiento
|
||||
SDL_Texture* cached_texture_; // Textura cacheada del overlay completo
|
||||
SDL_Color last_category_color_; // Último color de categorías renderizado
|
||||
SDL_Color last_content_color_; // Último color de contenido renderizado
|
||||
SDL_Color last_bg_color_; // Último color de fondo renderizado
|
||||
bool texture_needs_rebuild_; // Flag para forzar regeneración de textura
|
||||
|
||||
// Calcular dimensiones del texto más largo
|
||||
void calculateTextDimensions(int& max_width, int& total_height);
|
||||
// Calcular dimensiones del texto más largo
|
||||
void calculateTextDimensions(int& max_width, int& total_height);
|
||||
|
||||
// Calcular dimensiones del recuadro según tamaño de ventana y texto
|
||||
void calculateBoxDimensions();
|
||||
// Calcular dimensiones del recuadro según tamaño de ventana y texto
|
||||
void calculateBoxDimensions();
|
||||
|
||||
// Regenerar textura cacheada del overlay
|
||||
void rebuildCachedTexture();
|
||||
// Regenerar textura cacheada del overlay
|
||||
void rebuildCachedTexture();
|
||||
|
||||
// Estructura para par tecla-descripción
|
||||
struct KeyBinding {
|
||||
const char* key;
|
||||
const char* description;
|
||||
};
|
||||
// Estructura para par tecla-descripción
|
||||
struct KeyBinding {
|
||||
const char* key;
|
||||
const char* description;
|
||||
};
|
||||
|
||||
// Lista de todos los controles (se llena en constructor)
|
||||
std::vector<KeyBinding> key_bindings_;
|
||||
// Lista de todos los controles (se llena en constructor)
|
||||
std::vector<KeyBinding> key_bindings_;
|
||||
};
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include "logo_scaler.hpp"
|
||||
|
||||
#include <SDL3/SDL_error.h> // Para SDL_GetError
|
||||
#include <SDL3/SDL_log.h> // Para SDL_Log
|
||||
#include <SDL3/SDL_pixels.h> // Para SDL_PixelFormat
|
||||
#include <SDL3/SDL_render.h> // Para SDL_CreateTexture
|
||||
#include <SDL3/SDL_surface.h> // Para SDL_CreateSurfaceFrom
|
||||
#include <SDL3/SDL_video.h> // Para SDL_GetDisplays
|
||||
#include <SDL3/SDL_error.h> // Para SDL_GetError
|
||||
#include <SDL3/SDL_log.h> // Para SDL_Log
|
||||
#include <SDL3/SDL_pixels.h> // Para SDL_PixelFormat
|
||||
#include <SDL3/SDL_render.h> // Para SDL_CreateTexture
|
||||
#include <SDL3/SDL_surface.h> // Para SDL_CreateSurfaceFrom
|
||||
#include <SDL3/SDL_video.h> // Para SDL_GetDisplays
|
||||
|
||||
#include <cstdlib> // Para free()
|
||||
#include <iostream> // Para std::cout
|
||||
|
||||
#include "external/stb_image.h" // Para stbi_load, stbi_image_free
|
||||
#include "external/stb_image_resize2.h" // Para stbir_resize_uint8_srgb
|
||||
#include "resource_manager.hpp" // Para cargar desde pack
|
||||
#include "resource_manager.hpp" // Para cargar desde pack
|
||||
|
||||
// ============================================================================
|
||||
// Detectar resolución nativa del monitor principal
|
||||
// ============================================================================
|
||||
|
||||
bool LogoScaler::detectNativeResolution(int& native_width, int& native_height) {
|
||||
auto LogoScaler::detectNativeResolution(int& native_width, int& native_height) -> bool {
|
||||
int num_displays = 0;
|
||||
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
||||
|
||||
@@ -48,22 +48,25 @@ bool LogoScaler::detectNativeResolution(int& native_width, int& native_height) {
|
||||
// Cargar PNG y escalar al tamaño especificado
|
||||
// ============================================================================
|
||||
|
||||
unsigned char* LogoScaler::loadAndScale(const std::string& path,
|
||||
int target_width, int target_height,
|
||||
int& out_width, int& out_height) {
|
||||
auto LogoScaler::loadAndScale(const std::string& path,
|
||||
int target_width,
|
||||
int target_height,
|
||||
int& out_width,
|
||||
int& out_height) -> unsigned char* {
|
||||
// 1. Intentar cargar imagen desde ResourceManager (pack o disco)
|
||||
int orig_width, orig_height, orig_channels;
|
||||
int orig_width;
|
||||
int orig_height;
|
||||
int orig_channels;
|
||||
unsigned char* orig_data = nullptr;
|
||||
|
||||
// 1a. Cargar desde ResourceManager
|
||||
unsigned char* resourceData = nullptr;
|
||||
size_t resourceSize = 0;
|
||||
unsigned char* resource_data = nullptr;
|
||||
size_t resource_size = 0;
|
||||
|
||||
if (ResourceManager::loadResource(path, resourceData, resourceSize)) {
|
||||
if (ResourceManager::loadResource(path, resource_data, resource_size)) {
|
||||
// Descodificar imagen desde memoria usando stb_image
|
||||
orig_data = stbi_load_from_memory(resourceData, static_cast<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,9 +93,15 @@ unsigned char* LogoScaler::loadAndScale(const std::string& path,
|
||||
// 4. Escalar con stb_image_resize2 (algoritmo Mitchell, espacio sRGB)
|
||||
// La función devuelve el puntero de salida, o nullptr si falla
|
||||
unsigned char* result = stbir_resize_uint8_srgb(
|
||||
orig_data, orig_width, orig_height, 0, // Input
|
||||
scaled_data, out_width, out_height, 0, // Output
|
||||
STBIR_RGBA // Formato píxel
|
||||
orig_data,
|
||||
orig_width,
|
||||
orig_height,
|
||||
0, // Input
|
||||
scaled_data,
|
||||
out_width,
|
||||
out_height,
|
||||
0, // Output
|
||||
STBIR_RGBA // Formato píxel
|
||||
);
|
||||
|
||||
// Liberar imagen original (ya no la necesitamos)
|
||||
@@ -111,9 +120,10 @@ unsigned char* LogoScaler::loadAndScale(const std::string& path,
|
||||
// Crear textura SDL desde buffer RGBA
|
||||
// ============================================================================
|
||||
|
||||
SDL_Texture* LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer,
|
||||
unsigned char* data,
|
||||
int width, int height) {
|
||||
auto LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer,
|
||||
unsigned char* data,
|
||||
int width,
|
||||
int height) -> SDL_Texture* {
|
||||
if (renderer == nullptr || data == nullptr || width <= 0 || height <= 0) {
|
||||
SDL_Log("Parámetros inválidos para createTextureFromBuffer");
|
||||
return nullptr;
|
||||
@@ -124,11 +134,11 @@ SDL_Texture* LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer,
|
||||
SDL_PixelFormat pixel_format = SDL_PIXELFORMAT_RGBA32;
|
||||
|
||||
SDL_Surface* surface = SDL_CreateSurfaceFrom(
|
||||
width, height,
|
||||
width,
|
||||
height,
|
||||
pixel_format,
|
||||
data,
|
||||
pitch
|
||||
);
|
||||
pitch);
|
||||
|
||||
if (surface == nullptr) {
|
||||
SDL_Log("Error al crear surface: %s", SDL_GetError());
|
||||
|
||||
@@ -17,45 +17,48 @@
|
||||
* de pantalla, eliminando el escalado dinámico de SDL y mejorando calidad visual.
|
||||
*/
|
||||
class LogoScaler {
|
||||
public:
|
||||
/**
|
||||
* @brief Detecta la resolución nativa del monitor principal
|
||||
*
|
||||
* @param native_width [out] Ancho nativo del display en píxeles
|
||||
* @param native_height [out] Alto nativo del display en píxeles
|
||||
* @return true si se pudo detectar, false si hubo error
|
||||
*/
|
||||
static bool detectNativeResolution(int& native_width, int& native_height);
|
||||
public:
|
||||
/**
|
||||
* @brief Detecta la resolución nativa del monitor principal
|
||||
*
|
||||
* @param native_width [out] Ancho nativo del display en píxeles
|
||||
* @param native_height [out] Alto nativo del display en píxeles
|
||||
* @return true si se pudo detectar, false si hubo error
|
||||
*/
|
||||
static bool detectNativeResolution(int& native_width, int& native_height);
|
||||
|
||||
/**
|
||||
* @brief Carga un PNG y lo escala al tamaño especificado
|
||||
*
|
||||
* Usa stb_image para cargar y stb_image_resize2 para escalar con
|
||||
* algoritmo Mitchell (balance calidad/velocidad) en espacio sRGB.
|
||||
*
|
||||
* @param path Ruta al archivo PNG (ej: "data/logo/logo.png")
|
||||
* @param target_width Ancho destino en píxeles
|
||||
* @param target_height Alto destino en píxeles
|
||||
* @param out_width [out] Ancho real de la imagen escalada
|
||||
* @param out_height [out] Alto real de la imagen escalada
|
||||
* @return Buffer RGBA (4 bytes por píxel) o nullptr si falla
|
||||
* IMPORTANTE: El caller debe liberar con free() cuando termine
|
||||
*/
|
||||
static unsigned char* loadAndScale(const std::string& path,
|
||||
int target_width, int target_height,
|
||||
int& out_width, int& out_height);
|
||||
/**
|
||||
* @brief Carga un PNG y lo escala al tamaño especificado
|
||||
*
|
||||
* Usa stb_image para cargar y stb_image_resize2 para escalar con
|
||||
* algoritmo Mitchell (balance calidad/velocidad) en espacio sRGB.
|
||||
*
|
||||
* @param path Ruta al archivo PNG (ej: "data/logo/logo.png")
|
||||
* @param target_width Ancho destino en píxeles
|
||||
* @param target_height Alto destino en píxeles
|
||||
* @param out_width [out] Ancho real de la imagen escalada
|
||||
* @param out_height [out] Alto real de la imagen escalada
|
||||
* @return Buffer RGBA (4 bytes por píxel) o nullptr si falla
|
||||
* IMPORTANTE: El caller debe liberar con free() cuando termine
|
||||
*/
|
||||
static unsigned char* loadAndScale(const std::string& path,
|
||||
int target_width,
|
||||
int target_height,
|
||||
int& out_width,
|
||||
int& out_height);
|
||||
|
||||
/**
|
||||
* @brief Crea una textura SDL desde un buffer RGBA
|
||||
*
|
||||
* @param renderer Renderizador SDL activo
|
||||
* @param data Buffer RGBA (4 bytes por píxel)
|
||||
* @param width Ancho del buffer en píxeles
|
||||
* @param height Alto del buffer en píxeles
|
||||
* @return Textura SDL creada o nullptr si falla
|
||||
* IMPORTANTE: El caller debe destruir con SDL_DestroyTexture()
|
||||
*/
|
||||
static SDL_Texture* createTextureFromBuffer(SDL_Renderer* renderer,
|
||||
unsigned char* data,
|
||||
int width, int height);
|
||||
/**
|
||||
* @brief Crea una textura SDL desde un buffer RGBA
|
||||
*
|
||||
* @param renderer Renderizador SDL activo
|
||||
* @param data Buffer RGBA (4 bytes por píxel)
|
||||
* @param width Ancho del buffer en píxeles
|
||||
* @param height Alto del buffer en píxeles
|
||||
* @return Textura SDL creada o nullptr si falla
|
||||
* IMPORTANTE: El caller debe destruir con SDL_DestroyTexture()
|
||||
*/
|
||||
static SDL_Texture* createTextureFromBuffer(SDL_Renderer* renderer,
|
||||
unsigned char* data,
|
||||
int width,
|
||||
int height);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -105,7 +108,7 @@ void Notifier::update(Uint64 current_time) {
|
||||
// Animación de entrada (NOTIFICATION_SLIDE_TIME ms)
|
||||
if (elapsed < NOTIFICATION_SLIDE_TIME) {
|
||||
float progress = static_cast<float>(elapsed) / static_cast<float>(NOTIFICATION_SLIDE_TIME);
|
||||
float eased = Easing::easeOutBack(progress); // Efecto con ligero overshoot
|
||||
float eased = Easing::easeOutBack(progress); // Efecto con ligero overshoot
|
||||
current_notification_->y_offset = -50.0f + (50.0f * eased); // De -50 a 0
|
||||
} else {
|
||||
// Transición a VISIBLE
|
||||
@@ -151,28 +154,30 @@ void Notifier::update(Uint64 current_time) {
|
||||
}
|
||||
|
||||
void Notifier::render() {
|
||||
if (!current_notification_ || !text_renderer_ || !renderer_ || !theme_manager_) {
|
||||
if (!current_notification_ || (text_renderer_ == nullptr) || (renderer_ == nullptr) || (theme_manager_ == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtener colores DINÁMICOS desde ThemeManager (incluye LERP automático)
|
||||
int text_r, text_g, text_b;
|
||||
int text_r;
|
||||
int text_g;
|
||||
int text_b;
|
||||
theme_manager_->getCurrentThemeTextColor(text_r, text_g, text_b);
|
||||
SDL_Color text_color = {
|
||||
static_cast<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;
|
||||
@@ -20,92 +21,92 @@ class ThemeManager;
|
||||
* - Texto de tamaño fijo independiente de resolución
|
||||
*/
|
||||
class Notifier {
|
||||
public:
|
||||
enum class NotificationState {
|
||||
SLIDING_IN, // Animación de entrada desde arriba
|
||||
VISIBLE, // Visible estático
|
||||
FADING_OUT, // Animación de salida (fade)
|
||||
DONE // Completado, listo para eliminar
|
||||
};
|
||||
public:
|
||||
enum class NotificationState {
|
||||
SLIDING_IN, // Animación de entrada desde arriba
|
||||
VISIBLE, // Visible estático
|
||||
FADING_OUT, // Animación de salida (fade)
|
||||
DONE // Completado, listo para eliminar
|
||||
};
|
||||
|
||||
struct Notification {
|
||||
std::string text;
|
||||
Uint64 created_time;
|
||||
Uint64 duration;
|
||||
NotificationState state;
|
||||
float alpha; // Opacidad 0.0-1.0
|
||||
float y_offset; // Offset Y para animación slide (píxeles)
|
||||
// NOTA: Los colores se obtienen dinámicamente desde ThemeManager en render()
|
||||
};
|
||||
struct Notification {
|
||||
std::string text;
|
||||
Uint64 created_time;
|
||||
Uint64 duration;
|
||||
NotificationState state;
|
||||
float alpha; // Opacidad 0.0-1.0
|
||||
float y_offset; // Offset Y para animación slide (píxeles)
|
||||
// NOTA: Los colores se obtienen dinámicamente desde ThemeManager en render()
|
||||
};
|
||||
|
||||
Notifier();
|
||||
~Notifier();
|
||||
Notifier();
|
||||
~Notifier();
|
||||
|
||||
/**
|
||||
* @brief Inicializa el notifier con un TextRenderer y ThemeManager
|
||||
* @param renderer SDL renderer para dibujar
|
||||
* @param text_renderer TextRenderer configurado con tamaño absoluto
|
||||
* @param theme_manager ThemeManager para obtener colores dinámicos con LERP
|
||||
* @param window_width Ancho de ventana física
|
||||
* @param window_height Alto de ventana física
|
||||
* @return true si inicialización exitosa
|
||||
*/
|
||||
bool init(SDL_Renderer* renderer, TextRenderer* text_renderer, ThemeManager* theme_manager, int window_width, int window_height);
|
||||
/**
|
||||
* @brief Inicializa el notifier con un TextRenderer y ThemeManager
|
||||
* @param renderer SDL renderer para dibujar
|
||||
* @param text_renderer TextRenderer configurado con tamaño absoluto
|
||||
* @param theme_manager ThemeManager para obtener colores dinámicos con LERP
|
||||
* @param window_width Ancho de ventana física
|
||||
* @param window_height Alto de ventana física
|
||||
* @return true si inicialización exitosa
|
||||
*/
|
||||
bool init(SDL_Renderer* renderer, TextRenderer* text_renderer, ThemeManager* theme_manager, int window_width, int window_height);
|
||||
|
||||
/**
|
||||
* @brief Actualiza las dimensiones de la ventana (llamar en resize)
|
||||
* @param window_width Nuevo ancho de ventana física
|
||||
* @param window_height Nuevo alto de ventana física
|
||||
*/
|
||||
void updateWindowSize(int window_width, int window_height);
|
||||
/**
|
||||
* @brief Actualiza las dimensiones de la ventana (llamar en resize)
|
||||
* @param window_width Nuevo ancho de ventana física
|
||||
* @param window_height Nuevo alto de ventana física
|
||||
*/
|
||||
void updateWindowSize(int window_width, int window_height);
|
||||
|
||||
/**
|
||||
* @brief Muestra una nueva notificación
|
||||
* @param text Texto a mostrar
|
||||
* @param duration Duración en milisegundos (0 = usar default)
|
||||
* @note Los colores se obtienen dinámicamente desde ThemeManager cada frame
|
||||
*/
|
||||
void show(const std::string& text, Uint64 duration = 0);
|
||||
/**
|
||||
* @brief Muestra una nueva notificación
|
||||
* @param text Texto a mostrar
|
||||
* @param duration Duración en milisegundos (0 = usar default)
|
||||
* @note Los colores se obtienen dinámicamente desde ThemeManager cada frame
|
||||
*/
|
||||
void show(const std::string& text, Uint64 duration = 0);
|
||||
|
||||
/**
|
||||
* @brief Actualiza las animaciones de notificaciones
|
||||
* @param current_time Tiempo actual en ms (SDL_GetTicks())
|
||||
*/
|
||||
void update(Uint64 current_time);
|
||||
/**
|
||||
* @brief Actualiza las animaciones de notificaciones
|
||||
* @param current_time Tiempo actual en ms (SDL_GetTicks())
|
||||
*/
|
||||
void update(Uint64 current_time);
|
||||
|
||||
/**
|
||||
* @brief Renderiza la notificación activa
|
||||
*/
|
||||
void render();
|
||||
/**
|
||||
* @brief Renderiza la notificación activa
|
||||
*/
|
||||
void render();
|
||||
|
||||
/**
|
||||
* @brief Verifica si hay una notificación activa (visible)
|
||||
* @return true si hay notificación mostrándose
|
||||
*/
|
||||
bool isActive() const;
|
||||
/**
|
||||
* @brief Verifica si hay una notificación activa (visible)
|
||||
* @return true si hay notificación mostrándose
|
||||
*/
|
||||
bool isActive() const;
|
||||
|
||||
/**
|
||||
* @brief Limpia todas las notificaciones pendientes
|
||||
*/
|
||||
void clear();
|
||||
/**
|
||||
* @brief Limpia todas las notificaciones pendientes
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
SDL_Renderer* renderer_;
|
||||
TextRenderer* text_renderer_;
|
||||
ThemeManager* theme_manager_; // Gestor de temas para obtener colores dinámicos con LERP
|
||||
int window_width_;
|
||||
int window_height_;
|
||||
private:
|
||||
SDL_Renderer* renderer_;
|
||||
TextRenderer* text_renderer_;
|
||||
ThemeManager* theme_manager_; // Gestor de temas para obtener colores dinámicos con LERP
|
||||
int window_width_;
|
||||
int window_height_;
|
||||
|
||||
std::queue<Notification> notification_queue_;
|
||||
std::unique_ptr<Notification> current_notification_;
|
||||
std::queue<Notification> notification_queue_;
|
||||
std::unique_ptr<Notification> current_notification_;
|
||||
|
||||
/**
|
||||
* @brief Procesa la cola y activa la siguiente notificación si es posible
|
||||
*/
|
||||
void processQueue();
|
||||
/**
|
||||
* @brief Procesa la cola y activa la siguiente notificación si es posible
|
||||
*/
|
||||
void processQueue();
|
||||
|
||||
/**
|
||||
* @brief Dibuja el fondo semitransparente de la notificación
|
||||
*/
|
||||
void renderBackground(int x, int y, int width, int height, float alpha, SDL_Color bg_color);
|
||||
/**
|
||||
* @brief Dibuja el fondo semitransparente de la notificación
|
||||
*/
|
||||
void renderBackground(int x, int y, int width, int height, float alpha, SDL_Color bg_color);
|
||||
};
|
||||
|
||||
@@ -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 "scene/scene_manager.hpp" // for SceneManager
|
||||
#include "shapes/shape.hpp" // for Shape
|
||||
#include "text/textrenderer.hpp" // for TextRenderer
|
||||
#include "theme_manager.hpp" // for ThemeManager
|
||||
#include "notifier.hpp" // for Notifier
|
||||
#include "help_overlay.hpp" // for HelpOverlay
|
||||
#include "ball.hpp" // for Ball
|
||||
#include "defines.hpp" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode
|
||||
#include "engine.hpp" // for Engine (info de sistema)
|
||||
#include "help_overlay.hpp" // for HelpOverlay
|
||||
#include "notifier.hpp" // for Notifier
|
||||
#include "scene/scene_manager.hpp" // for SceneManager
|
||||
#include "shapes/shape.hpp" // for Shape
|
||||
#include "text/textrenderer.hpp" // for TextRenderer
|
||||
#include "theme_manager.hpp" // for ThemeManager
|
||||
|
||||
// ============================================================================
|
||||
// HELPER: Obtener viewport en coordenadas físicas (no lógicas)
|
||||
@@ -20,9 +22,10 @@
|
||||
// SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación
|
||||
// lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar
|
||||
// temporalmente la presentación lógica.
|
||||
static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) {
|
||||
static auto getPhysicalViewport(SDL_Renderer* renderer) -> SDL_Rect {
|
||||
// Guardar estado actual de presentación lógica
|
||||
int logical_w = 0, logical_h = 0;
|
||||
int logical_w = 0;
|
||||
int logical_h = 0;
|
||||
SDL_RendererLogicalPresentation presentation_mode;
|
||||
SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode);
|
||||
|
||||
@@ -40,23 +43,23 @@ static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) {
|
||||
}
|
||||
|
||||
UIManager::UIManager()
|
||||
: text_renderer_debug_(nullptr)
|
||||
, text_renderer_notifier_(nullptr)
|
||||
, notifier_(nullptr)
|
||||
, help_overlay_(nullptr)
|
||||
, show_debug_(false)
|
||||
, fps_last_time_(0)
|
||||
, fps_frame_count_(0)
|
||||
, fps_current_(0)
|
||||
, fps_text_("FPS: 0")
|
||||
, vsync_text_("VSYNC ON")
|
||||
, renderer_(nullptr)
|
||||
, theme_manager_(nullptr)
|
||||
, physical_window_width_(0)
|
||||
, physical_window_height_(0)
|
||||
, logical_window_width_(0)
|
||||
, logical_window_height_(0)
|
||||
, current_font_size_(18) { // Tamaño por defecto (medium)
|
||||
: text_renderer_debug_(nullptr),
|
||||
text_renderer_notifier_(nullptr),
|
||||
notifier_(nullptr),
|
||||
help_overlay_(nullptr),
|
||||
show_debug_(false),
|
||||
fps_last_time_(0),
|
||||
fps_frame_count_(0),
|
||||
fps_current_(0),
|
||||
fps_text_("FPS: 0"),
|
||||
vsync_text_("VSYNC ON"),
|
||||
renderer_(nullptr),
|
||||
theme_manager_(nullptr),
|
||||
physical_window_width_(0),
|
||||
physical_window_height_(0),
|
||||
logical_window_width_(0),
|
||||
logical_window_height_(0),
|
||||
current_font_size_(18) { // Tamaño por defecto (medium)
|
||||
}
|
||||
|
||||
UIManager::~UIManager() {
|
||||
@@ -67,13 +70,15 @@ UIManager::~UIManager() {
|
||||
delete help_overlay_;
|
||||
}
|
||||
|
||||
void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
||||
int physical_width, int physical_height,
|
||||
int logical_width, int logical_height) {
|
||||
delete text_renderer_debug_; text_renderer_debug_ = nullptr;
|
||||
delete text_renderer_notifier_; text_renderer_notifier_ = nullptr;
|
||||
delete notifier_; notifier_ = nullptr;
|
||||
delete help_overlay_; help_overlay_ = nullptr;
|
||||
void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, int physical_width, int physical_height, int logical_width, int logical_height) {
|
||||
delete text_renderer_debug_;
|
||||
text_renderer_debug_ = nullptr;
|
||||
delete text_renderer_notifier_;
|
||||
text_renderer_notifier_ = nullptr;
|
||||
delete notifier_;
|
||||
notifier_ = nullptr;
|
||||
delete help_overlay_;
|
||||
help_overlay_ = nullptr;
|
||||
|
||||
renderer_ = renderer;
|
||||
theme_manager_ = theme_manager;
|
||||
@@ -95,8 +100,7 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
||||
|
||||
// Crear y configurar sistema de notificaciones
|
||||
notifier_ = new Notifier();
|
||||
notifier_->init(renderer, text_renderer_notifier_, theme_manager_,
|
||||
physical_width, physical_height);
|
||||
notifier_->init(renderer, text_renderer_notifier_, theme_manager_, physical_width, physical_height);
|
||||
|
||||
// Crear y configurar sistema de ayuda (overlay)
|
||||
help_overlay_ = new HelpOverlay();
|
||||
@@ -123,30 +127,29 @@ void UIManager::update(Uint64 current_time, float delta_time) {
|
||||
}
|
||||
|
||||
void UIManager::render(SDL_Renderer* renderer,
|
||||
const Engine* engine,
|
||||
const SceneManager* scene_manager,
|
||||
SimulationMode current_mode,
|
||||
AppMode current_app_mode,
|
||||
const Shape* active_shape,
|
||||
float shape_convergence,
|
||||
int physical_width,
|
||||
int physical_height,
|
||||
int current_screen_width) {
|
||||
const Engine* engine,
|
||||
const SceneManager* scene_manager,
|
||||
SimulationMode current_mode,
|
||||
AppMode current_app_mode,
|
||||
const Shape* active_shape,
|
||||
float shape_convergence,
|
||||
int physical_width,
|
||||
int physical_height,
|
||||
int current_screen_width) {
|
||||
// Actualizar dimensiones físicas (puede cambiar en fullscreen)
|
||||
physical_window_width_ = physical_width;
|
||||
physical_window_height_ = physical_height;
|
||||
|
||||
// Renderizar debug HUD si está activo
|
||||
if (show_debug_) {
|
||||
renderDebugHUD(engine, scene_manager, current_mode, current_app_mode,
|
||||
active_shape, shape_convergence);
|
||||
renderDebugHUD(engine, scene_manager, current_mode, current_app_mode, active_shape, shape_convergence);
|
||||
}
|
||||
|
||||
// Renderizar notificaciones (siempre al final, sobre todo lo demás)
|
||||
notifier_->render();
|
||||
|
||||
// Renderizar ayuda (siempre última, sobre todo incluso notificaciones)
|
||||
if (help_overlay_) {
|
||||
if (help_overlay_ != nullptr) {
|
||||
help_overlay_->render(renderer);
|
||||
}
|
||||
}
|
||||
@@ -156,7 +159,7 @@ void UIManager::toggleDebug() {
|
||||
}
|
||||
|
||||
void UIManager::toggleHelp() {
|
||||
if (help_overlay_) {
|
||||
if (help_overlay_ != nullptr) {
|
||||
help_overlay_->toggle();
|
||||
}
|
||||
}
|
||||
@@ -190,16 +193,16 @@ void UIManager::updatePhysicalWindowSize(int width, int height, int logical_heig
|
||||
current_font_size_ = new_font_size;
|
||||
|
||||
// Reinicializar text renderers con nuevo tamaño
|
||||
if (text_renderer_debug_) {
|
||||
if (text_renderer_debug_ != nullptr) {
|
||||
text_renderer_debug_->reinitialize(std::max(9, current_font_size_ - 2));
|
||||
}
|
||||
if (text_renderer_notifier_) {
|
||||
if (text_renderer_notifier_ != nullptr) {
|
||||
text_renderer_notifier_->reinitialize(current_font_size_);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar help overlay con font size actual Y nuevas dimensiones (atómicamente)
|
||||
if (help_overlay_) {
|
||||
if (help_overlay_ != nullptr) {
|
||||
help_overlay_->updateAll(std::max(9, current_font_size_ - 1), width, height);
|
||||
}
|
||||
|
||||
@@ -209,12 +212,12 @@ void UIManager::updatePhysicalWindowSize(int width, int height, int logical_heig
|
||||
|
||||
// === Métodos privados ===
|
||||
|
||||
void UIManager::renderDebugHUD(const Engine* engine,
|
||||
const SceneManager* scene_manager,
|
||||
SimulationMode current_mode,
|
||||
AppMode current_app_mode,
|
||||
const Shape* active_shape,
|
||||
float shape_convergence) {
|
||||
void UIManager::renderDebugHUD(const Engine* engine, // NOLINT(readability-function-cognitive-complexity)
|
||||
const SceneManager* scene_manager,
|
||||
SimulationMode current_mode,
|
||||
AppMode current_app_mode,
|
||||
const Shape* active_shape,
|
||||
float shape_convergence) {
|
||||
int line_height = text_renderer_debug_->getTextHeight();
|
||||
int margin = 8;
|
||||
SDL_Rect physical_viewport = getPhysicalViewport(renderer_);
|
||||
@@ -236,7 +239,7 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
||||
if (current_mode == SimulationMode::PHYSICS) {
|
||||
simmode_text = "SimMode: PHYSICS";
|
||||
} else if (current_mode == SimulationMode::SHAPE) {
|
||||
if (active_shape) {
|
||||
if (active_shape != nullptr) {
|
||||
simmode_text = std::string("SimMode: SHAPE (") + active_shape->getName() + ")";
|
||||
} else {
|
||||
simmode_text = "SimMode: SHAPE";
|
||||
@@ -246,7 +249,7 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
||||
}
|
||||
|
||||
std::string sprite_name = engine->getCurrentTextureName();
|
||||
std::transform(sprite_name.begin(), sprite_name.end(), sprite_name.begin(), ::toupper);
|
||||
std::ranges::transform(sprite_name, sprite_name.begin(), ::toupper);
|
||||
std::string sprite_text = "Sprite: " + sprite_name;
|
||||
|
||||
size_t ball_count = scene_manager->getBallCount();
|
||||
@@ -256,7 +259,9 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
||||
std::string formatted;
|
||||
int digits = static_cast<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,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_stdinc.h> // for Uint64
|
||||
#include <string> // for std::string
|
||||
|
||||
#include <string> // for std::string
|
||||
|
||||
// Forward declarations
|
||||
struct SDL_Renderer;
|
||||
@@ -49,9 +50,7 @@ class UIManager {
|
||||
* @param logical_width Ancho lógico (resolución interna)
|
||||
* @param logical_height Alto lógico (resolución interna)
|
||||
*/
|
||||
void initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
||||
int physical_width, int physical_height,
|
||||
int logical_width, int logical_height);
|
||||
void initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, int physical_width, int physical_height, int logical_width, int logical_height);
|
||||
|
||||
/**
|
||||
* @brief Actualiza UI (FPS counter, notificaciones, texto obsoleto)
|
||||
@@ -74,15 +73,15 @@ class UIManager {
|
||||
* @param current_screen_width Ancho lógico de pantalla (para texto centrado)
|
||||
*/
|
||||
void render(SDL_Renderer* renderer,
|
||||
const Engine* engine,
|
||||
const SceneManager* scene_manager,
|
||||
SimulationMode current_mode,
|
||||
AppMode current_app_mode,
|
||||
const Shape* active_shape,
|
||||
float shape_convergence,
|
||||
int physical_width,
|
||||
int physical_height,
|
||||
int current_screen_width);
|
||||
const Engine* engine,
|
||||
const SceneManager* scene_manager,
|
||||
SimulationMode current_mode,
|
||||
AppMode current_app_mode,
|
||||
const Shape* active_shape,
|
||||
float shape_convergence,
|
||||
int physical_width,
|
||||
int physical_height,
|
||||
int current_screen_width);
|
||||
|
||||
/**
|
||||
* @brief Toggle del debug HUD (tecla F12)
|
||||
@@ -138,31 +137,31 @@ class UIManager {
|
||||
* @param shape_convergence % de convergencia en LOGO mode
|
||||
*/
|
||||
void renderDebugHUD(const Engine* engine,
|
||||
const SceneManager* scene_manager,
|
||||
SimulationMode current_mode,
|
||||
AppMode current_app_mode,
|
||||
const Shape* active_shape,
|
||||
float shape_convergence);
|
||||
const SceneManager* scene_manager,
|
||||
SimulationMode current_mode,
|
||||
AppMode current_app_mode,
|
||||
const Shape* active_shape,
|
||||
float shape_convergence);
|
||||
|
||||
/**
|
||||
* @brief Convierte dirección de gravedad a string
|
||||
* @param direction Dirección como int (cast de GravityDirection)
|
||||
* @return String en español ("Abajo", "Arriba", etc.)
|
||||
*/
|
||||
std::string gravityDirectionToString(int direction) const;
|
||||
static std::string gravityDirectionToString(int direction);
|
||||
|
||||
/**
|
||||
* @brief Calcula tamaño de fuente apropiado según dimensiones lógicas
|
||||
* @param logical_height Alto lógico (resolución interna, sin zoom)
|
||||
* @return Tamaño de fuente (9-72px)
|
||||
*/
|
||||
int calculateFontSize(int logical_height) const;
|
||||
static int calculateFontSize(int logical_height);
|
||||
|
||||
// === Recursos de renderizado ===
|
||||
TextRenderer* text_renderer_debug_; // HUD de debug
|
||||
TextRenderer* text_renderer_notifier_; // Notificaciones
|
||||
Notifier* notifier_; // Sistema de notificaciones
|
||||
HelpOverlay* help_overlay_; // Overlay de ayuda (tecla H)
|
||||
TextRenderer* text_renderer_debug_; // HUD de debug
|
||||
TextRenderer* text_renderer_notifier_; // Notificaciones
|
||||
Notifier* notifier_; // Sistema de notificaciones
|
||||
HelpOverlay* help_overlay_; // Overlay de ayuda (tecla H)
|
||||
|
||||
// === Estado de UI ===
|
||||
bool show_debug_; // HUD de debug activo (tecla F12)
|
||||
@@ -175,13 +174,13 @@ class UIManager {
|
||||
std::string vsync_text_; // Texto "V-Sync: On/Off"
|
||||
|
||||
// === Referencias externas ===
|
||||
SDL_Renderer* renderer_; // Renderizador SDL3 (referencia)
|
||||
ThemeManager* theme_manager_; // Gestor de temas (para colores)
|
||||
int physical_window_width_; // Ancho físico de ventana (píxeles reales)
|
||||
int physical_window_height_; // Alto físico de ventana (píxeles reales)
|
||||
int logical_window_width_; // Ancho lógico (resolución interna)
|
||||
int logical_window_height_; // Alto lógico (resolución interna)
|
||||
SDL_Renderer* renderer_; // Renderizador SDL3 (referencia)
|
||||
ThemeManager* theme_manager_; // Gestor de temas (para colores)
|
||||
int physical_window_width_; // Ancho físico de ventana (píxeles reales)
|
||||
int physical_window_height_; // Alto físico de ventana (píxeles reales)
|
||||
int logical_window_width_; // Ancho lógico (resolución interna)
|
||||
int logical_window_height_; // Alto lógico (resolución interna)
|
||||
|
||||
// === Sistema de escalado dinámico de texto ===
|
||||
int current_font_size_; // Tamaño de fuente actual (9-72px)
|
||||
int current_font_size_; // Tamaño de fuente actual (9-72px)
|
||||
};
|
||||
|
||||
@@ -210,4 +210,4 @@ inline float easeOutCirc(float t) {
|
||||
return sqrtf(1.0f - powf(t - 1.0f, 2.0f));
|
||||
}
|
||||
|
||||
} // namespace Easing
|
||||
} // namespace Easing
|
||||
|
||||
Reference in New Issue
Block a user