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,
|
-performance-inefficient-string-concatenation,
|
||||||
-bugprone-integer-division,
|
-bugprone-integer-division,
|
||||||
-bugprone-easily-swappable-parameters,
|
-bugprone-easily-swappable-parameters,
|
||||||
|
-readability-uppercase-literal-suffix,
|
||||||
|
|
||||||
WarningsAsErrors: '*'
|
WarningsAsErrors: '*'
|
||||||
# Solo incluir archivos de tu código fuente
|
# Solo incluir archivos de tu código fuente
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
#include "ball.hpp"
|
#include "ball.hpp"
|
||||||
|
|
||||||
#include <stdlib.h> // for rand
|
#include <algorithm>
|
||||||
|
|
||||||
#include <cmath> // for fabs
|
#include <cmath> // for fabs
|
||||||
|
#include <cstdlib> // for rand
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "defines.hpp" // for Color, SCREEN_HEIGHT, GRAVITY_FORCE
|
#include "defines.hpp" // for Color, SCREEN_HEIGHT, GRAVITY_FORCE
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
// Función auxiliar para generar pérdida aleatoria en rebotes
|
// 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)
|
// Genera un valor entre 0 y BOUNCE_RANDOM_LOSS_PERCENT (solo pérdida adicional)
|
||||||
float loss = (rand() % 1000) / 1000.0f * BOUNCE_RANDOM_LOSS_PERCENT;
|
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)
|
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
|
// Función auxiliar para generar pérdida lateral aleatoria
|
||||||
float generateLateralLoss() {
|
auto generateLateralLoss() -> float {
|
||||||
// Genera un valor entre 0 y LATERAL_LOSS_PERCENT
|
// Genera un valor entre 0 y LATERAL_LOSS_PERCENT
|
||||||
float loss = (rand() % 1000) / 1000.0f * 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)
|
return 1.0f - loss; // Retorna multiplicador (ej: 0.98 - 1.0 para 0-2% pérdida)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
// 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)),
|
: 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)
|
// Convertir velocidades de píxeles/frame a píxeles/segundo (multiplicar por 60)
|
||||||
vx_ = vx * 60.0f;
|
vx_ = vx * 60.0f;
|
||||||
vy_ = vy * 60.0f;
|
vy_ = vy * 60.0f;
|
||||||
@@ -54,11 +55,11 @@ Ball::Ball(float x, float y, float vx, float vy, Color color, std::shared_ptr<Te
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza la lógica de la clase
|
// 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²)
|
// Aplica la gravedad según la dirección (píxeles/segundo²)
|
||||||
if (!on_surface_) {
|
if (!on_surface_) {
|
||||||
// Aplicar gravedad multiplicada por factor de masa individual
|
// 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_) {
|
switch (gravity_direction_) {
|
||||||
case GravityDirection::DOWN:
|
case GravityDirection::DOWN:
|
||||||
vy_ += effective_gravity;
|
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)
|
// Actualiza la posición en función de la velocidad (píxeles/segundo)
|
||||||
if (!on_surface_) {
|
if (!on_surface_) {
|
||||||
pos_.x += vx_ * deltaTime;
|
pos_.x += vx_ * delta_time;
|
||||||
pos_.y += vy_ * deltaTime;
|
pos_.y += vy_ * delta_time;
|
||||||
} else {
|
} else {
|
||||||
// Si está en superficie, mantener posición según dirección de gravedad
|
// Si está en superficie, mantener posición según dirección de gravedad
|
||||||
switch (gravity_direction_) {
|
switch (gravity_direction_) {
|
||||||
case GravityDirection::DOWN:
|
case GravityDirection::DOWN:
|
||||||
pos_.y = screen_height_ - pos_.h;
|
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;
|
break;
|
||||||
case GravityDirection::UP:
|
case GravityDirection::UP:
|
||||||
pos_.y = 0;
|
pos_.y = 0;
|
||||||
pos_.x += vx_ * deltaTime; // Seguir moviéndose en X
|
pos_.x += vx_ * delta_time; // Seguir moviéndose en X
|
||||||
break;
|
break;
|
||||||
case GravityDirection::LEFT:
|
case GravityDirection::LEFT:
|
||||||
pos_.x = 0;
|
pos_.x = 0;
|
||||||
pos_.y += vy_ * deltaTime; // Seguir moviéndose en Y
|
pos_.y += vy_ * delta_time; // Seguir moviéndose en Y
|
||||||
break;
|
break;
|
||||||
case GravityDirection::RIGHT:
|
case GravityDirection::RIGHT:
|
||||||
pos_.x = screen_width_ - pos_.w;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,7 +177,7 @@ void Ball::update(float deltaTime) {
|
|||||||
// Aplica rozamiento al estar en superficie
|
// Aplica rozamiento al estar en superficie
|
||||||
if (on_surface_) {
|
if (on_surface_) {
|
||||||
// Convertir rozamiento de frame-based a time-based
|
// 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_) {
|
switch (gravity_direction_) {
|
||||||
case GravityDirection::DOWN:
|
case GravityDirection::DOWN:
|
||||||
@@ -246,7 +247,7 @@ void Ball::setGravityDirection(GravityDirection direction) {
|
|||||||
// Aplica un pequeño empuje lateral aleatorio
|
// Aplica un pequeño empuje lateral aleatorio
|
||||||
void Ball::applyRandomLateralPush() {
|
void Ball::applyRandomLateralPush() {
|
||||||
// Generar velocidad lateral aleatoria (nunca 0)
|
// 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 -)
|
// Signo aleatorio (+ o -)
|
||||||
int sign = ((rand() % 2) * 2) - 1;
|
int sign = ((rand() % 2) * 2) - 1;
|
||||||
@@ -304,18 +305,18 @@ void Ball::enableShapeAttraction(bool enable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Obtener distancia actual al punto objetivo (para calcular convergencia)
|
// 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)
|
// Siempre calcular distancia (útil para convergencia en LOGO mode)
|
||||||
float dx = target_x_ - pos_.x;
|
float dx = target_x_ - pos_.x;
|
||||||
float dy = target_y_ - pos_.y;
|
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
|
// Aplicar fuerza de resorte hacia punto objetivo en figuras 3D
|
||||||
void Ball::applyShapeForce(float target_x, float target_y, float sphere_radius, float deltaTime,
|
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) {
|
||||||
float spring_k_base, float damping_base_base, float damping_near_base,
|
if (!shape_attraction_active_) {
|
||||||
float near_threshold_base, float max_force_base) {
|
return;
|
||||||
if (!shape_attraction_active_) return;
|
}
|
||||||
|
|
||||||
// Calcular factor de escala basado en el radio (radio base = 80px)
|
// 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
|
// 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;
|
float diff_y = target_y - pos_.y;
|
||||||
|
|
||||||
// Calcular distancia al punto objetivo
|
// 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)
|
// Fuerza de resorte (Ley de Hooke: F = -k * x)
|
||||||
float spring_force_x = spring_k * diff_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;
|
float total_force_y = spring_force_y - damping_force_y;
|
||||||
|
|
||||||
// Limitar magnitud de fuerza (evitar explosiones numéricas)
|
// 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) {
|
if (force_magnitude > max_force) {
|
||||||
float scale_limit = max_force / force_magnitude;
|
float scale_limit = max_force / force_magnitude;
|
||||||
total_force_x *= scale_limit;
|
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)
|
// Aplicar aceleración (F = ma, asumiendo m = 1 para simplificar)
|
||||||
// a = F/m, pero m=1, así que a = F
|
// a = F/m, pero m=1, así que a = F
|
||||||
vx_ += total_force_x * deltaTime;
|
vx_ += total_force_x * delta_time;
|
||||||
vy_ += total_force_y * deltaTime;
|
vy_ += total_force_y * delta_time;
|
||||||
|
|
||||||
// Actualizar posición con física normal (velocidad integrada)
|
// Actualizar posición con física normal (velocidad integrada)
|
||||||
pos_.x += vx_ * deltaTime;
|
pos_.x += vx_ * delta_time;
|
||||||
pos_.y += vy_ * deltaTime;
|
pos_.y += vy_ * delta_time;
|
||||||
|
|
||||||
// Mantener pelotas dentro de los límites de pantalla
|
// Mantener pelotas dentro de los límites de pantalla
|
||||||
if (pos_.x < 0) pos_.x = 0;
|
pos_.x = std::max<float>(pos_.x, 0);
|
||||||
if (pos_.x + pos_.w > screen_width_) pos_.x = screen_width_ - pos_.w;
|
if (pos_.x + pos_.w > screen_width_) {
|
||||||
if (pos_.y < 0) pos_.y = 0;
|
pos_.x = screen_width_ - pos_.w;
|
||||||
if (pos_.y + pos_.h > screen_height_) pos_.y = screen_height_ - pos_.h;
|
}
|
||||||
|
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
|
// Actualizar sprite para renderizado
|
||||||
sprite_->setPos({pos_.x, pos_.y});
|
sprite_->setPos({pos_.x, pos_.y});
|
||||||
@@ -393,5 +398,5 @@ void Ball::updateSize(int new_size) {
|
|||||||
|
|
||||||
void Ball::setTexture(std::shared_ptr<Texture> texture) {
|
void Ball::setTexture(std::shared_ptr<Texture> texture) {
|
||||||
// Actualizar textura del sprite
|
// Actualizar textura del sprite
|
||||||
sprite_->setTexture(texture);
|
sprite_->setTexture(std::move(texture));
|
||||||
}
|
}
|
||||||
@@ -31,13 +31,13 @@ class Ball {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// 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
|
// Destructor
|
||||||
~Ball() = default;
|
~Ball() = default;
|
||||||
|
|
||||||
// Actualiza la lógica de la clase
|
// Actualiza la lógica de la clase
|
||||||
void update(float deltaTime);
|
void update(float delta_time);
|
||||||
|
|
||||||
// Pinta la clase
|
// Pinta la clase
|
||||||
void render();
|
void render();
|
||||||
@@ -72,11 +72,20 @@ class Ball {
|
|||||||
bool isOnSurface() const { return on_surface_; }
|
bool isOnSurface() const { return on_surface_; }
|
||||||
|
|
||||||
// Getters/Setters para velocidad (usado por BoidManager)
|
// Getters/Setters para velocidad (usado por BoidManager)
|
||||||
void getVelocity(float& vx, float& vy) const { vx = vx_; vy = vy_; }
|
void getVelocity(float& vx, float& vy) const {
|
||||||
void setVelocity(float vx, float vy) { vx_ = vx; vy_ = vy; }
|
vx = vx_;
|
||||||
|
vy = vy_;
|
||||||
|
}
|
||||||
|
void setVelocity(float vx, float vy) {
|
||||||
|
vx_ = vx;
|
||||||
|
vy_ = vy;
|
||||||
|
}
|
||||||
|
|
||||||
// Setter para posición simple (usado por BoidManager)
|
// 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
|
// Getters/Setters para batch rendering
|
||||||
SDL_FRect getPosition() const { return pos_; }
|
SDL_FRect getPosition() const { return pos_; }
|
||||||
@@ -99,10 +108,5 @@ class Ball {
|
|||||||
// Sistema de atracción física hacia figuras 3D
|
// Sistema de atracción física hacia figuras 3D
|
||||||
void enableShapeAttraction(bool enable);
|
void enableShapeAttraction(bool enable);
|
||||||
float getDistanceToTarget() const; // Distancia actual al punto objetivo
|
float getDistanceToTarget() const; // Distancia actual al punto objetivo
|
||||||
void applyShapeForce(float target_x, float target_y, float sphere_radius, float deltaTime,
|
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);
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
@@ -10,32 +10,31 @@
|
|||||||
#include "ui/ui_manager.hpp" // for UIManager
|
#include "ui/ui_manager.hpp" // for UIManager
|
||||||
|
|
||||||
BoidManager::BoidManager()
|
BoidManager::BoidManager()
|
||||||
: engine_(nullptr)
|
: engine_(nullptr),
|
||||||
, scene_mgr_(nullptr)
|
scene_mgr_(nullptr),
|
||||||
, ui_mgr_(nullptr)
|
ui_mgr_(nullptr),
|
||||||
, state_mgr_(nullptr)
|
state_mgr_(nullptr),
|
||||||
, screen_width_(0)
|
screen_width_(0),
|
||||||
, screen_height_(0)
|
screen_height_(0),
|
||||||
, boids_active_(false)
|
boids_active_(false),
|
||||||
, spatial_grid_(800, 600, BOID_GRID_CELL_SIZE) // Tamaño por defecto, se actualiza en initialize()
|
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)
|
separation_radius_(BOID_SEPARATION_RADIUS),
|
||||||
, cohesion_radius_(BOID_COHESION_RADIUS)
|
alignment_radius_(BOID_ALIGNMENT_RADIUS),
|
||||||
, separation_weight_(BOID_SEPARATION_WEIGHT)
|
cohesion_radius_(BOID_COHESION_RADIUS),
|
||||||
, alignment_weight_(BOID_ALIGNMENT_WEIGHT)
|
separation_weight_(BOID_SEPARATION_WEIGHT),
|
||||||
, cohesion_weight_(BOID_COHESION_WEIGHT)
|
alignment_weight_(BOID_ALIGNMENT_WEIGHT),
|
||||||
, max_speed_(BOID_MAX_SPEED)
|
cohesion_weight_(BOID_COHESION_WEIGHT),
|
||||||
, min_speed_(BOID_MIN_SPEED)
|
max_speed_(BOID_MAX_SPEED),
|
||||||
, max_force_(BOID_MAX_FORCE)
|
min_speed_(BOID_MIN_SPEED),
|
||||||
, boundary_margin_(BOID_BOUNDARY_MARGIN)
|
max_force_(BOID_MAX_FORCE),
|
||||||
, boundary_weight_(BOID_BOUNDARY_WEIGHT) {
|
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,
|
void BoidManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height) {
|
||||||
StateManager* state_mgr, int screen_width, int screen_height) {
|
|
||||||
engine_ = engine;
|
engine_ = engine;
|
||||||
scene_mgr_ = scene_mgr;
|
scene_mgr_ = scene_mgr;
|
||||||
ui_mgr_ = ui_mgr;
|
ui_mgr_ = ui_mgr;
|
||||||
@@ -65,7 +64,8 @@ void BoidManager::activateBoids() {
|
|||||||
auto& balls = scene_mgr_->getBallsMutable();
|
auto& balls = scene_mgr_->getBallsMutable();
|
||||||
for (auto& ball : balls) {
|
for (auto& ball : balls) {
|
||||||
// Dar velocidad inicial aleatoria si está quieto
|
// Dar velocidad inicial aleatoria si está quieto
|
||||||
float vx, vy;
|
float vx;
|
||||||
|
float vy;
|
||||||
ball->getVelocity(vx, vy);
|
ball->getVelocity(vx, vy);
|
||||||
if (vx == 0.0f && vy == 0.0f) {
|
if (vx == 0.0f && vy == 0.0f) {
|
||||||
// Velocidad aleatoria entre -60 y +60 px/s (time-based)
|
// 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)
|
// 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");
|
ui_mgr_->showNotification("Modo boids");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BoidManager::deactivateBoids(bool force_gravity_on) {
|
void BoidManager::deactivateBoids(bool force_gravity_on) {
|
||||||
if (!boids_active_) return;
|
if (!boids_active_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
boids_active_ = false;
|
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)
|
// 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");
|
ui_mgr_->showNotification("Modo física");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +108,9 @@ void BoidManager::toggleBoidsMode(bool force_gravity_on) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BoidManager::update(float delta_time) {
|
void BoidManager::update(float delta_time) {
|
||||||
if (!boids_active_) return;
|
if (!boids_active_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& balls = scene_mgr_->getBallsMutable();
|
auto& balls = scene_mgr_->getBallsMutable();
|
||||||
|
|
||||||
@@ -114,8 +118,8 @@ void BoidManager::update(float delta_time) {
|
|||||||
spatial_grid_.clear();
|
spatial_grid_.clear();
|
||||||
for (auto& ball : balls) {
|
for (auto& ball : balls) {
|
||||||
SDL_FRect pos = ball->getPosition();
|
SDL_FRect pos = ball->getPosition();
|
||||||
float center_x = pos.x + pos.w / 2.0f;
|
float center_x = pos.x + (pos.w / 2.0f);
|
||||||
float center_y = pos.y + pos.h / 2.0f;
|
float center_y = pos.y + (pos.h / 2.0f);
|
||||||
spatial_grid_.insert(ball.get(), center_x, center_y);
|
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)
|
// Actualizar posiciones con velocidades resultantes (time-based)
|
||||||
for (auto& ball : balls) {
|
for (auto& ball : balls) {
|
||||||
float vx, vy;
|
float vx;
|
||||||
|
float vy;
|
||||||
ball->getVelocity(vx, vy);
|
ball->getVelocity(vx, vy);
|
||||||
|
|
||||||
SDL_FRect pos = ball->getPosition();
|
SDL_FRect pos = ball->getPosition();
|
||||||
@@ -153,22 +158,24 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
SDL_FRect pos = boid->getPosition();
|
SDL_FRect pos = boid->getPosition();
|
||||||
float center_x = pos.x + pos.w / 2.0f;
|
float center_x = pos.x + (pos.w / 2.0f);
|
||||||
float center_y = pos.y + pos.h / 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))
|
// 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_);
|
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, separation_radius_);
|
||||||
|
|
||||||
for (Ball* other : neighbors) {
|
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();
|
SDL_FRect other_pos = other->getPosition();
|
||||||
float other_x = other_pos.x + other_pos.w / 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 other_y = other_pos.y + (other_pos.h / 2.0f);
|
||||||
|
|
||||||
float dx = center_x - other_x;
|
float dx = center_x - other_x;
|
||||||
float dy = center_y - other_y;
|
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_) {
|
if (distance > 0.0f && distance < separation_radius_) {
|
||||||
// FASE 1.3: Separación más fuerte cuando más cerca (inversamente proporcional a distancia)
|
// 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;
|
steer_y /= count;
|
||||||
|
|
||||||
// Aplicar fuerza de separación
|
// Aplicar fuerza de separación
|
||||||
float vx, vy;
|
float vx;
|
||||||
|
float vy;
|
||||||
boid->getVelocity(vx, vy);
|
boid->getVelocity(vx, vy);
|
||||||
vx += steer_x * separation_weight_ * delta_time;
|
vx += steer_x * separation_weight_ * delta_time;
|
||||||
vy += steer_y * 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;
|
int count = 0;
|
||||||
|
|
||||||
SDL_FRect pos = boid->getPosition();
|
SDL_FRect pos = boid->getPosition();
|
||||||
float center_x = pos.x + pos.w / 2.0f;
|
float center_x = pos.x + (pos.w / 2.0f);
|
||||||
float center_y = pos.y + pos.h / 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))
|
// 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_);
|
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, alignment_radius_);
|
||||||
|
|
||||||
for (Ball* other : neighbors) {
|
for (Ball* other : neighbors) {
|
||||||
if (other == boid) continue;
|
if (other == boid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
SDL_FRect other_pos = other->getPosition();
|
SDL_FRect other_pos = other->getPosition();
|
||||||
float other_x = other_pos.x + other_pos.w / 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 other_y = other_pos.y + (other_pos.h / 2.0f);
|
||||||
|
|
||||||
float dx = center_x - other_x;
|
float dx = center_x - other_x;
|
||||||
float dy = center_y - other_y;
|
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_) {
|
if (distance < alignment_radius_) {
|
||||||
float other_vx, other_vy;
|
float other_vx;
|
||||||
|
float other_vy;
|
||||||
other->getVelocity(other_vx, other_vy);
|
other->getVelocity(other_vx, other_vy);
|
||||||
avg_vx += other_vx;
|
avg_vx += other_vx;
|
||||||
avg_vy += other_vy;
|
avg_vy += other_vy;
|
||||||
@@ -233,13 +244,14 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
|
|||||||
avg_vy /= count;
|
avg_vy /= count;
|
||||||
|
|
||||||
// Steering hacia la velocidad promedio
|
// Steering hacia la velocidad promedio
|
||||||
float vx, vy;
|
float vx;
|
||||||
|
float vy;
|
||||||
boid->getVelocity(vx, vy);
|
boid->getVelocity(vx, vy);
|
||||||
float steer_x = (avg_vx - vx) * alignment_weight_ * delta_time;
|
float steer_x = (avg_vx - vx) * alignment_weight_ * delta_time;
|
||||||
float steer_y = (avg_vy - vy) * alignment_weight_ * delta_time;
|
float steer_y = (avg_vy - vy) * alignment_weight_ * delta_time;
|
||||||
|
|
||||||
// Limitar fuerza máxima de steering
|
// 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_) {
|
if (steer_mag > max_force_) {
|
||||||
steer_x = (steer_x / steer_mag) * max_force_;
|
steer_x = (steer_x / steer_mag) * max_force_;
|
||||||
steer_y = (steer_y / 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;
|
int count = 0;
|
||||||
|
|
||||||
SDL_FRect pos = boid->getPosition();
|
SDL_FRect pos = boid->getPosition();
|
||||||
float center_x = pos.x + pos.w / 2.0f;
|
float center_x = pos.x + (pos.w / 2.0f);
|
||||||
float center_y = pos.y + pos.h / 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))
|
// 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_);
|
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, cohesion_radius_);
|
||||||
|
|
||||||
for (Ball* other : neighbors) {
|
for (Ball* other : neighbors) {
|
||||||
if (other == boid) continue;
|
if (other == boid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
SDL_FRect other_pos = other->getPosition();
|
SDL_FRect other_pos = other->getPosition();
|
||||||
float other_x = other_pos.x + other_pos.w / 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 other_y = other_pos.y + (other_pos.h / 2.0f);
|
||||||
|
|
||||||
float dx = center_x - other_x;
|
float dx = center_x - other_x;
|
||||||
float dy = center_y - other_y;
|
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_) {
|
if (distance < cohesion_radius_) {
|
||||||
center_of_mass_x += other_x;
|
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!)
|
// 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 dx_to_center = center_of_mass_x - center_x;
|
||||||
float dy_to_center = center_of_mass_y - center_y;
|
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)
|
// Solo aplicar si hay distancia al centro (evitar división por cero)
|
||||||
if (distance_to_center > 0.1f) {
|
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;
|
float steer_y = (dy_to_center / distance_to_center) * cohesion_weight_ * delta_time;
|
||||||
|
|
||||||
// Limitar fuerza máxima de steering
|
// 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_) {
|
if (steer_mag > max_force_) {
|
||||||
steer_x = (steer_x / steer_mag) * max_force_;
|
steer_x = (steer_x / steer_mag) * max_force_;
|
||||||
steer_y = (steer_y / steer_mag) * max_force_;
|
steer_y = (steer_y / steer_mag) * max_force_;
|
||||||
}
|
}
|
||||||
|
|
||||||
float vx, vy;
|
float vx;
|
||||||
|
float vy;
|
||||||
boid->getVelocity(vx, vy);
|
boid->getVelocity(vx, vy);
|
||||||
vx += steer_x;
|
vx += steer_x;
|
||||||
vy += steer_y;
|
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)
|
// 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
|
// Cuando un boid se acerca a un borde, se aplica una fuerza alejándolo
|
||||||
SDL_FRect pos = boid->getPosition();
|
SDL_FRect pos = boid->getPosition();
|
||||||
float center_x = pos.x + pos.w / 2.0f;
|
float center_x = pos.x + (pos.w / 2.0f);
|
||||||
float center_y = pos.y + pos.h / 2.0f;
|
float center_y = pos.y + (pos.h / 2.0f);
|
||||||
|
|
||||||
float steer_x = 0.0f;
|
float steer_x = 0.0f;
|
||||||
float steer_y = 0.0f;
|
float steer_y = 0.0f;
|
||||||
@@ -363,11 +378,12 @@ void BoidManager::applyBoundaries(Ball* boid) {
|
|||||||
|
|
||||||
// Aplicar fuerza de repulsión si hay alguna
|
// Aplicar fuerza de repulsión si hay alguna
|
||||||
if (steer_x != 0.0f || steer_y != 0.0f) {
|
if (steer_x != 0.0f || steer_y != 0.0f) {
|
||||||
float vx, vy;
|
float vx;
|
||||||
|
float vy;
|
||||||
boid->getVelocity(vx, vy);
|
boid->getVelocity(vx, vy);
|
||||||
|
|
||||||
// Normalizar fuerza de repulsión (para que todas las direcciones tengan la misma intensidad)
|
// 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) {
|
if (steer_mag > 0.0f) {
|
||||||
steer_x /= steer_mag;
|
steer_x /= steer_mag;
|
||||||
steer_y /= 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
|
// Limitar velocidad máxima del boid
|
||||||
float vx, vy;
|
float vx;
|
||||||
|
float vy;
|
||||||
boid->getVelocity(vx, 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
|
// Limitar velocidad máxima
|
||||||
if (speed > max_speed_) {
|
if (speed > max_speed_) {
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ class BoidManager {
|
|||||||
* @param screen_width Ancho de pantalla actual
|
* @param screen_width Ancho de pantalla actual
|
||||||
* @param screen_height Alto de pantalla actual
|
* @param screen_height Alto de pantalla actual
|
||||||
*/
|
*/
|
||||||
void initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr,
|
void initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height);
|
||||||
StateManager* state_mgr, int screen_width, int screen_height);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Actualiza el tamaño de pantalla (llamado en resize/fullscreen)
|
* @brief Actualiza el tamaño de pantalla (llamado en resize/fullscreen)
|
||||||
@@ -121,6 +120,6 @@ class BoidManager {
|
|||||||
void applySeparation(Ball* boid, float delta_time);
|
void applySeparation(Ball* boid, float delta_time);
|
||||||
void applyAlignment(Ball* boid, float delta_time);
|
void applyAlignment(Ball* boid, float delta_time);
|
||||||
void applyCohesion(Ball* boid, float delta_time);
|
void applyCohesion(Ball* boid, float delta_time);
|
||||||
void applyBoundaries(Ball* boid); // Repulsión de bordes (ya no wrapping)
|
void applyBoundaries(Ball* boid) const; // Repulsión de bordes (ya no wrapping)
|
||||||
void limitSpeed(Ball* boid); // Limitar velocidad máxima
|
void limitSpeed(Ball* boid) const; // Limitar velocidad máxima
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
#include "ball.hpp" // for Ball
|
#include "ball.hpp" // for Ball
|
||||||
|
|
||||||
SpatialGrid::SpatialGrid(int world_width, int world_height, float cell_size)
|
SpatialGrid::SpatialGrid(int world_width, int world_height, float cell_size)
|
||||||
: world_width_(world_width)
|
: world_width_(world_width),
|
||||||
, world_height_(world_height)
|
world_height_(world_height),
|
||||||
, cell_size_(cell_size) {
|
cell_size_(cell_size) {
|
||||||
// Calcular número de celdas en cada dimensión
|
// Calcular número de celdas en cada dimensión
|
||||||
grid_cols_ = static_cast<int>(std::ceil(world_width / cell_size));
|
grid_cols_ = static_cast<int>(std::ceil(world_width / cell_size));
|
||||||
grid_rows_ = static_cast<int>(std::ceil(world_height / 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) {
|
void SpatialGrid::insert(Ball* ball, float x, float y) {
|
||||||
// Obtener coordenadas de celda
|
// Obtener coordenadas de celda
|
||||||
int cell_x, cell_y;
|
int cell_x;
|
||||||
|
int cell_y;
|
||||||
getCellCoords(x, y, cell_x, cell_y);
|
getCellCoords(x, y, cell_x, cell_y);
|
||||||
|
|
||||||
// Generar hash key y añadir a la celda
|
// 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);
|
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;
|
std::vector<Ball*> results;
|
||||||
|
|
||||||
// Calcular rango de celdas a revisar (AABB del círculo de búsqueda)
|
// 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, min_cell_x, min_cell_y);
|
||||||
getCellCoords(x + radius, y + radius, max_cell_x, max_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_));
|
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
|
// Hash espacial 2D → 1D usando codificación por filas
|
||||||
// Formula: key = y * ancho + x (similar a array 2D aplanado)
|
// 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,7 +30,7 @@ class Ball; // Forward declaration
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
class SpatialGrid {
|
class SpatialGrid {
|
||||||
public:
|
public:
|
||||||
// Constructor: especificar dimensiones del mundo y tamaño de celda
|
// Constructor: especificar dimensiones del mundo y tamaño de celda
|
||||||
SpatialGrid(int world_width, int world_height, float cell_size);
|
SpatialGrid(int world_width, int world_height, float cell_size);
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ public:
|
|||||||
// Actualizar dimensiones del mundo (útil para cambios de resolución F4)
|
// Actualizar dimensiones del mundo (útil para cambios de resolución F4)
|
||||||
void updateWorldSize(int world_width, int world_height);
|
void updateWorldSize(int world_width, int world_height);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Convertir coordenadas (x, y) a índice de celda (cell_x, cell_y)
|
// 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;
|
void getCellCoords(float x, float y, int& cell_x, int& cell_y) const;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <SDL3/SDL_timer.h> // for SDL_GetTicks
|
#include <SDL3/SDL_timer.h> // for SDL_GetTicks
|
||||||
#include <SDL3/SDL_video.h> // for SDL_CreateWindow, SDL_DestroyWindow, SDL_GetDisplayBounds
|
#include <SDL3/SDL_video.h> // for SDL_CreateWindow, SDL_DestroyWindow, SDL_GetDisplayBounds
|
||||||
|
|
||||||
|
#include <array> // for std::array
|
||||||
#include <algorithm> // for std::min, std::max, std::sort
|
#include <algorithm> // for std::min, std::max, std::sort
|
||||||
#include <cmath> // for sqrtf, acosf, cosf, sinf (funciones matemáticas)
|
#include <cmath> // for sqrtf, acosf, cosf, sinf (funciones matemáticas)
|
||||||
#include <cstdlib> // for rand, srand
|
#include <cstdlib> // for rand, srand
|
||||||
@@ -29,23 +30,25 @@
|
|||||||
#include "shapes/png_shape.hpp" // for PNGShape (dynamic_cast en callbacks LOGO)
|
#include "shapes/png_shape.hpp" // for PNGShape (dynamic_cast en callbacks LOGO)
|
||||||
|
|
||||||
// Implementación de métodos públicos
|
// Implementación de métodos públicos
|
||||||
bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMode initial_mode) {
|
auto Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMode initial_mode) -> bool { // NOLINT(readability-function-cognitive-complexity)
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
// Obtener resolución de pantalla para validación
|
// Obtener resolución de pantalla para validación
|
||||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||||
std::cout << "¡SDL no se pudo inicializar! Error de SDL: " << SDL_GetError() << std::endl;
|
std::cout << "¡SDL no se pudo inicializar! Error de SDL: " << SDL_GetError() << '\n';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int num_displays = 0;
|
int num_displays = 0;
|
||||||
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
||||||
const auto* dm = (displays && num_displays > 0) ? SDL_GetCurrentDisplayMode(displays[0]) : nullptr;
|
const auto* dm = ((displays != nullptr) && num_displays > 0) ? SDL_GetCurrentDisplayMode(displays[0]) : nullptr;
|
||||||
|
|
||||||
int screen_w = dm ? dm->w : 1920; // Fallback si falla
|
int screen_w = (dm != nullptr) ? dm->w : 1920; // Fallback si falla
|
||||||
int screen_h = dm ? dm->h - WINDOW_DECORATION_HEIGHT : 1080;
|
int screen_h = (dm != nullptr) ? dm->h - WINDOW_DECORATION_HEIGHT : 1080;
|
||||||
|
|
||||||
if (displays) SDL_free(displays);
|
if (displays != nullptr) {
|
||||||
|
SDL_free(displays);
|
||||||
|
}
|
||||||
|
|
||||||
// Usar parámetros o valores por defecto
|
// Usar parámetros o valores por defecto
|
||||||
int logical_width = (width > 0) ? width : DEFAULT_SCREEN_WIDTH;
|
int logical_width = (width > 0) ? width : DEFAULT_SCREEN_WIDTH;
|
||||||
@@ -109,7 +112,7 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
|||||||
|
|
||||||
window_ = SDL_CreateWindow(WINDOW_CAPTION, window_width, window_height, window_flags);
|
window_ = SDL_CreateWindow(WINDOW_CAPTION, window_width, window_height, window_flags);
|
||||||
if (window_ == nullptr) {
|
if (window_ == nullptr) {
|
||||||
std::cout << "¡No se pudo crear la ventana! Error de SDL: " << SDL_GetError() << std::endl;
|
std::cout << "¡No se pudo crear la ventana! Error de SDL: " << SDL_GetError() << '\n';
|
||||||
success = false;
|
success = false;
|
||||||
} else {
|
} else {
|
||||||
// Centrar ventana en pantalla si no está en fullscreen
|
// Centrar ventana en pantalla si no está en fullscreen
|
||||||
@@ -120,7 +123,7 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
|||||||
// Inicializar SDL_GPU (sustituye SDL_Renderer como backend principal)
|
// Inicializar SDL_GPU (sustituye SDL_Renderer como backend principal)
|
||||||
gpu_ctx_ = std::make_unique<GpuContext>();
|
gpu_ctx_ = std::make_unique<GpuContext>();
|
||||||
if (!gpu_ctx_->init(window_)) {
|
if (!gpu_ctx_->init(window_)) {
|
||||||
std::cout << "¡No se pudo inicializar SDL_GPU!" << std::endl;
|
std::cout << "¡No se pudo inicializar SDL_GPU!" << '\n';
|
||||||
success = false;
|
success = false;
|
||||||
} else {
|
} else {
|
||||||
gpu_ctx_->setVSync(vsync_enabled_);
|
gpu_ctx_->setVSync(vsync_enabled_);
|
||||||
@@ -128,11 +131,11 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
|||||||
// Crear renderer de software para UI/texto (SDL3_ttf no es compatible con SDL_GPU)
|
// Crear renderer de software para UI/texto (SDL3_ttf no es compatible con SDL_GPU)
|
||||||
// Renderiza a ui_surface_, que luego se sube como textura GPU overlay
|
// Renderiza a ui_surface_, que luego se sube como textura GPU overlay
|
||||||
ui_surface_ = SDL_CreateSurface(logical_width, logical_height, SDL_PIXELFORMAT_RGBA32);
|
ui_surface_ = SDL_CreateSurface(logical_width, logical_height, SDL_PIXELFORMAT_RGBA32);
|
||||||
if (ui_surface_) {
|
if (ui_surface_ != nullptr) {
|
||||||
ui_renderer_ = SDL_CreateSoftwareRenderer(ui_surface_);
|
ui_renderer_ = SDL_CreateSoftwareRenderer(ui_surface_);
|
||||||
}
|
}
|
||||||
if (!ui_renderer_) {
|
if (ui_renderer_ == nullptr) {
|
||||||
std::cout << "Advertencia: no se pudo crear el renderer de UI software" << std::endl;
|
std::cout << "Advertencia: no se pudo crear el renderer de UI software" << '\n';
|
||||||
// No es crítico — el juego funciona sin texto
|
// No es crítico — el juego funciona sin texto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,7 +191,7 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ordenar por tamaño (grande → pequeño): big(16) → normal(10) → small(6) → tiny(4)
|
// Ordenar por tamaño (grande → pequeño): big(16) → normal(10) → small(6) → tiny(4)
|
||||||
std::sort(texture_files.begin(), texture_files.end(), [](const TextureInfo& a, const TextureInfo& b) {
|
std::ranges::sort(texture_files, [](const TextureInfo& a, const TextureInfo& b) {
|
||||||
return a.width > b.width;
|
return a.width > b.width;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -200,14 +203,14 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
|||||||
// Cargar textura GPU para renderizado de sprites
|
// Cargar textura GPU para renderizado de sprites
|
||||||
auto gpu_tex = std::make_unique<GpuTexture>();
|
auto gpu_tex = std::make_unique<GpuTexture>();
|
||||||
if (gpu_ctx_ && !gpu_tex->fromFile(gpu_ctx_->device(), info.path)) {
|
if (gpu_ctx_ && !gpu_tex->fromFile(gpu_ctx_->device(), info.path)) {
|
||||||
std::cerr << "Advertencia: no se pudo cargar textura GPU: " << info.name << std::endl;
|
std::cerr << "Advertencia: no se pudo cargar textura GPU: " << info.name << '\n';
|
||||||
}
|
}
|
||||||
gpu_textures_.push_back(std::move(gpu_tex));
|
gpu_textures_.push_back(std::move(gpu_tex));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar que se cargaron texturas
|
// Verificar que se cargaron texturas
|
||||||
if (textures_.empty()) {
|
if (textures_.empty()) {
|
||||||
std::cerr << "ERROR: No se pudieron cargar texturas" << std::endl;
|
std::cerr << "ERROR: No se pudieron cargar texturas" << '\n';
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +230,7 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
|||||||
|
|
||||||
gpu_pipeline_ = std::make_unique<GpuPipeline>();
|
gpu_pipeline_ = std::make_unique<GpuPipeline>();
|
||||||
if (!gpu_pipeline_->init(gpu_ctx_->device(), gpu_ctx_->swapchainFormat(), offscreen_fmt)) {
|
if (!gpu_pipeline_->init(gpu_ctx_->device(), gpu_ctx_->swapchainFormat(), offscreen_fmt)) {
|
||||||
std::cerr << "ERROR: No se pudo crear el pipeline GPU" << std::endl;
|
std::cerr << "ERROR: No se pudo crear el pipeline GPU" << '\n';
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,42 +238,45 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
|||||||
// addBackground() no usa el guard de pushQuad(), así que no consume slots aquí.
|
// addBackground() no usa el guard de pushQuad(), así que no consume slots aquí.
|
||||||
// init() reserva internamente +1 quad extra garantizado para el overlay.
|
// init() reserva internamente +1 quad extra garantizado para el overlay.
|
||||||
int sprite_capacity = BALL_COUNT_SCENARIOS[DEMO_AUTO_MAX_SCENARIO];
|
int sprite_capacity = BALL_COUNT_SCENARIOS[DEMO_AUTO_MAX_SCENARIO];
|
||||||
if (custom_scenario_enabled_ && custom_scenario_balls_ > sprite_capacity)
|
if (custom_scenario_enabled_ && custom_scenario_balls_ > sprite_capacity) {
|
||||||
sprite_capacity = custom_scenario_balls_;
|
sprite_capacity = custom_scenario_balls_;
|
||||||
|
}
|
||||||
|
|
||||||
sprite_batch_ = std::make_unique<GpuSpriteBatch>();
|
sprite_batch_ = std::make_unique<GpuSpriteBatch>();
|
||||||
if (!sprite_batch_->init(gpu_ctx_->device(), sprite_capacity)) {
|
if (!sprite_batch_->init(gpu_ctx_->device(), sprite_capacity)) {
|
||||||
std::cerr << "ERROR: No se pudo crear el sprite batch GPU" << std::endl;
|
std::cerr << "ERROR: No se pudo crear el sprite batch GPU" << '\n';
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
gpu_ball_buffer_ = std::make_unique<GpuBallBuffer>();
|
gpu_ball_buffer_ = std::make_unique<GpuBallBuffer>();
|
||||||
if (!gpu_ball_buffer_->init(gpu_ctx_->device())) {
|
if (!gpu_ball_buffer_->init(gpu_ctx_->device())) {
|
||||||
std::cerr << "ERROR: No se pudo crear el ball buffer GPU" << std::endl;
|
std::cerr << "ERROR: No se pudo crear el ball buffer GPU" << '\n';
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
ball_gpu_data_.reserve(GpuBallBuffer::MAX_BALLS);
|
ball_gpu_data_.reserve(GpuBallBuffer::MAX_BALLS);
|
||||||
|
|
||||||
offscreen_tex_ = std::make_unique<GpuTexture>();
|
offscreen_tex_ = std::make_unique<GpuTexture>();
|
||||||
if (!offscreen_tex_->createRenderTarget(gpu_ctx_->device(),
|
if (!offscreen_tex_->createRenderTarget(gpu_ctx_->device(),
|
||||||
current_screen_width_, current_screen_height_,
|
current_screen_width_,
|
||||||
|
current_screen_height_,
|
||||||
offscreen_fmt)) {
|
offscreen_fmt)) {
|
||||||
std::cerr << "ERROR: No se pudo crear render target offscreen" << std::endl;
|
std::cerr << "ERROR: No se pudo crear render target offscreen" << '\n';
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
white_tex_ = std::make_unique<GpuTexture>();
|
white_tex_ = std::make_unique<GpuTexture>();
|
||||||
if (!white_tex_->createWhite(gpu_ctx_->device())) {
|
if (!white_tex_->createWhite(gpu_ctx_->device())) {
|
||||||
std::cerr << "ERROR: No se pudo crear textura blanca" << std::endl;
|
std::cerr << "ERROR: No se pudo crear textura blanca" << '\n';
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create UI overlay texture (render target usage so GPU can sample it)
|
// Create UI overlay texture (render target usage so GPU can sample it)
|
||||||
ui_tex_ = std::make_unique<GpuTexture>();
|
ui_tex_ = std::make_unique<GpuTexture>();
|
||||||
if (!ui_tex_->createRenderTarget(gpu_ctx_->device(),
|
if (!ui_tex_->createRenderTarget(gpu_ctx_->device(),
|
||||||
logical_width, logical_height,
|
logical_width,
|
||||||
|
logical_height,
|
||||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM)) {
|
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM)) {
|
||||||
std::cerr << "Advertencia: no se pudo crear textura UI GPU" << std::endl;
|
std::cerr << "Advertencia: no se pudo crear textura UI GPU" << '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,8 +290,9 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
|||||||
theme_manager_->initialize();
|
theme_manager_->initialize();
|
||||||
{
|
{
|
||||||
int max_balls = BALL_COUNT_SCENARIOS[DEMO_AUTO_MAX_SCENARIO];
|
int max_balls = BALL_COUNT_SCENARIOS[DEMO_AUTO_MAX_SCENARIO];
|
||||||
if (custom_scenario_enabled_ && custom_scenario_balls_ > max_balls)
|
if (custom_scenario_enabled_ && custom_scenario_balls_ > max_balls) {
|
||||||
max_balls = custom_scenario_balls_;
|
max_balls = custom_scenario_balls_;
|
||||||
|
}
|
||||||
theme_manager_->setMaxBallCount(max_balls);
|
theme_manager_->setMaxBallCount(max_balls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,13 +301,15 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
|||||||
scene_manager_->initialize(0, texture_, theme_manager_.get()); // Escenario 0 (10 bolas) por defecto
|
scene_manager_->initialize(0, texture_, theme_manager_.get()); // Escenario 0 (10 bolas) por defecto
|
||||||
|
|
||||||
// Propagar configuración custom si fue establecida antes de initialize()
|
// Propagar configuración custom si fue establecida antes de initialize()
|
||||||
if (custom_scenario_enabled_)
|
if (custom_scenario_enabled_) {
|
||||||
scene_manager_->setCustomBallCount(custom_scenario_balls_);
|
scene_manager_->setCustomBallCount(custom_scenario_balls_);
|
||||||
|
}
|
||||||
|
|
||||||
// Calcular tamaño físico de ventana ANTES de inicializar UIManager
|
// Calcular tamaño físico de ventana ANTES de inicializar UIManager
|
||||||
// NOTA: No llamar a updatePhysicalWindowSize() aquí porque ui_manager_ aún no existe
|
// NOTA: No llamar a updatePhysicalWindowSize() aquí porque ui_manager_ aún no existe
|
||||||
// Calcular manualmente para poder pasar valores al constructor de UIManager
|
// Calcular manualmente para poder pasar valores al constructor de UIManager
|
||||||
int window_w = 0, window_h = 0;
|
int window_w = 0;
|
||||||
|
int window_h = 0;
|
||||||
SDL_GetWindowSizeInPixels(window_, &window_w, &window_h);
|
SDL_GetWindowSizeInPixels(window_, &window_w, &window_h);
|
||||||
physical_window_width_ = window_w;
|
physical_window_width_ = window_w;
|
||||||
physical_window_height_ = window_h;
|
physical_window_height_ = window_h;
|
||||||
@@ -308,14 +317,11 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
|||||||
// Inicializar UIManager (HUD, FPS, notificaciones)
|
// Inicializar UIManager (HUD, FPS, notificaciones)
|
||||||
// NOTA: Debe llamarse DESPUÉS de calcular physical_window_* y ThemeManager
|
// NOTA: Debe llamarse DESPUÉS de calcular physical_window_* y ThemeManager
|
||||||
ui_manager_ = std::make_unique<UIManager>();
|
ui_manager_ = std::make_unique<UIManager>();
|
||||||
ui_manager_->initialize(ui_renderer_, theme_manager_.get(),
|
ui_manager_->initialize(ui_renderer_, theme_manager_.get(), physical_window_width_, physical_window_height_, current_screen_width_, current_screen_height_);
|
||||||
physical_window_width_, physical_window_height_,
|
|
||||||
current_screen_width_, current_screen_height_);
|
|
||||||
|
|
||||||
// Inicializar ShapeManager (gestión de figuras 3D)
|
// Inicializar ShapeManager (gestión de figuras 3D)
|
||||||
shape_manager_ = std::make_unique<ShapeManager>();
|
shape_manager_ = std::make_unique<ShapeManager>();
|
||||||
shape_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), nullptr,
|
shape_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), nullptr, current_screen_width_, current_screen_height_);
|
||||||
current_screen_width_, current_screen_height_);
|
|
||||||
|
|
||||||
// Inicializar StateManager (gestión de estados DEMO/LOGO)
|
// Inicializar StateManager (gestión de estados DEMO/LOGO)
|
||||||
state_manager_ = std::make_unique<StateManager>();
|
state_manager_ = std::make_unique<StateManager>();
|
||||||
@@ -326,50 +332,47 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
|||||||
if (initial_mode == AppMode::DEMO) {
|
if (initial_mode == AppMode::DEMO) {
|
||||||
state_manager_->toggleDemoMode(current_screen_width_, current_screen_height_);
|
state_manager_->toggleDemoMode(current_screen_width_, current_screen_height_);
|
||||||
// Como estamos en SANDBOX (default), toggleDemoMode() cambiará a DEMO + randomizará
|
// Como estamos en SANDBOX (default), toggleDemoMode() cambiará a DEMO + randomizará
|
||||||
}
|
} else if (initial_mode == AppMode::DEMO_LITE) {
|
||||||
else if (initial_mode == AppMode::DEMO_LITE) {
|
|
||||||
state_manager_->toggleDemoLiteMode(current_screen_width_, current_screen_height_);
|
state_manager_->toggleDemoLiteMode(current_screen_width_, current_screen_height_);
|
||||||
// Como estamos en SANDBOX (default), toggleDemoLiteMode() cambiará a DEMO_LITE + randomizará
|
// Como estamos en SANDBOX (default), toggleDemoLiteMode() cambiará a DEMO_LITE + randomizará
|
||||||
}
|
} else if (initial_mode == AppMode::LOGO) {
|
||||||
else if (initial_mode == AppMode::LOGO) {
|
|
||||||
size_t initial_ball_count = scene_manager_->getBallCount();
|
size_t initial_ball_count = scene_manager_->getBallCount();
|
||||||
state_manager_->enterLogoMode(false, current_screen_width_, current_screen_height_, initial_ball_count);
|
state_manager_->enterLogoMode(false, current_screen_width_, current_screen_height_, initial_ball_count);
|
||||||
// enterLogoMode() hace: setState(LOGO) + configuración visual completa
|
// enterLogoMode() hace: setState(LOGO) + configuración visual completa
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar ShapeManager con StateManager (dependencia circular - StateManager debe existir primero)
|
// Actualizar ShapeManager con StateManager (dependencia circular - StateManager debe existir primero)
|
||||||
shape_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(),
|
shape_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(), current_screen_width_, current_screen_height_);
|
||||||
current_screen_width_, current_screen_height_);
|
|
||||||
|
|
||||||
// Inicializar BoidManager (gestión de comportamiento de enjambre)
|
// Inicializar BoidManager (gestión de comportamiento de enjambre)
|
||||||
boid_manager_ = std::make_unique<BoidManager>();
|
boid_manager_ = std::make_unique<BoidManager>();
|
||||||
boid_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(),
|
boid_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(), current_screen_width_, current_screen_height_);
|
||||||
current_screen_width_, current_screen_height_);
|
|
||||||
|
|
||||||
// Inicializar AppLogo (logo periódico en pantalla)
|
// Inicializar AppLogo (logo periódico en pantalla)
|
||||||
app_logo_ = std::make_unique<AppLogo>();
|
app_logo_ = std::make_unique<AppLogo>();
|
||||||
if (!app_logo_->initialize(ui_renderer_, current_screen_width_, current_screen_height_)) {
|
if (!app_logo_->initialize(ui_renderer_, current_screen_width_, current_screen_height_)) {
|
||||||
std::cerr << "Advertencia: No se pudo inicializar AppLogo (logo periódico)" << std::endl;
|
std::cerr << "Advertencia: No se pudo inicializar AppLogo (logo periódico)" << '\n';
|
||||||
// No es crítico, continuar sin logo
|
// No es crítico, continuar sin logo
|
||||||
app_logo_.reset();
|
app_logo_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmark de rendimiento (determina max_auto_scenario_ para modos automáticos)
|
// Benchmark de rendimiento (determina max_auto_scenario_ para modos automáticos)
|
||||||
if (!skip_benchmark_)
|
if (!skip_benchmark_) {
|
||||||
runPerformanceBenchmark();
|
runPerformanceBenchmark();
|
||||||
else if (custom_scenario_enabled_)
|
} else if (custom_scenario_enabled_) {
|
||||||
custom_auto_available_ = true; // benchmark omitido: confiar en que el hardware lo soporta
|
custom_auto_available_ = true; // benchmark omitido: confiar en que el hardware lo soporta
|
||||||
|
}
|
||||||
|
|
||||||
// Precalentar caché: shapes PNG (evitar I/O en primera activación de PNG_SHAPE)
|
// Precalentar caché: shapes PNG (evitar I/O en primera activación de PNG_SHAPE)
|
||||||
{
|
{
|
||||||
unsigned char* tmp = nullptr; size_t tmp_size = 0;
|
unsigned char* tmp = nullptr;
|
||||||
|
size_t tmp_size = 0;
|
||||||
ResourceManager::loadResource("shapes/jailgames.png", tmp, tmp_size);
|
ResourceManager::loadResource("shapes/jailgames.png", tmp, tmp_size);
|
||||||
delete[] tmp;
|
delete[] tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mostrar ventana ahora que el benchmark terminó
|
// Mostrar ventana ahora que el benchmark terminó
|
||||||
SDL_ShowWindow(window_);
|
SDL_ShowWindow(window_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
@@ -380,7 +383,7 @@ void Engine::run() {
|
|||||||
calculateDeltaTime();
|
calculateDeltaTime();
|
||||||
|
|
||||||
// Procesar eventos de entrada (teclado, ratón, ventana)
|
// Procesar eventos de entrada (teclado, ratón, ventana)
|
||||||
if (input_handler_->processEvents(*this)) {
|
if (InputHandler::processEvents(*this)) {
|
||||||
should_exit_ = true;
|
should_exit_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,29 +394,58 @@ void Engine::run() {
|
|||||||
|
|
||||||
void Engine::shutdown() {
|
void Engine::shutdown() {
|
||||||
// Wait for GPU idle before releasing GPU resources
|
// Wait for GPU idle before releasing GPU resources
|
||||||
if (gpu_ctx_) SDL_WaitForGPUIdle(gpu_ctx_->device());
|
if (gpu_ctx_) {
|
||||||
|
SDL_WaitForGPUIdle(gpu_ctx_->device());
|
||||||
|
}
|
||||||
|
|
||||||
// Release GPU sprite textures
|
// Release GPU sprite textures
|
||||||
gpu_textures_.clear();
|
gpu_textures_.clear();
|
||||||
|
|
||||||
// Release GPU render targets and utility textures
|
// Release GPU render targets and utility textures
|
||||||
if (gpu_ctx_) {
|
if (gpu_ctx_) {
|
||||||
if (ui_tex_) { ui_tex_->destroy(gpu_ctx_->device()); ui_tex_.reset(); }
|
if (ui_tex_) {
|
||||||
if (white_tex_) { white_tex_->destroy(gpu_ctx_->device()); white_tex_.reset(); }
|
ui_tex_->destroy(gpu_ctx_->device());
|
||||||
if (offscreen_tex_) { offscreen_tex_->destroy(gpu_ctx_->device()); offscreen_tex_.reset(); }
|
ui_tex_.reset();
|
||||||
if (sprite_batch_) { sprite_batch_->destroy(gpu_ctx_->device()); sprite_batch_.reset(); }
|
}
|
||||||
if (gpu_ball_buffer_) { gpu_ball_buffer_->destroy(gpu_ctx_->device()); gpu_ball_buffer_.reset(); }
|
if (white_tex_) {
|
||||||
if (gpu_pipeline_) { gpu_pipeline_->destroy(gpu_ctx_->device()); gpu_pipeline_.reset(); }
|
white_tex_->destroy(gpu_ctx_->device());
|
||||||
|
white_tex_.reset();
|
||||||
|
}
|
||||||
|
if (offscreen_tex_) {
|
||||||
|
offscreen_tex_->destroy(gpu_ctx_->device());
|
||||||
|
offscreen_tex_.reset();
|
||||||
|
}
|
||||||
|
if (sprite_batch_) {
|
||||||
|
sprite_batch_->destroy(gpu_ctx_->device());
|
||||||
|
sprite_batch_.reset();
|
||||||
|
}
|
||||||
|
if (gpu_ball_buffer_) {
|
||||||
|
gpu_ball_buffer_->destroy(gpu_ctx_->device());
|
||||||
|
gpu_ball_buffer_.reset();
|
||||||
|
}
|
||||||
|
if (gpu_pipeline_) {
|
||||||
|
gpu_pipeline_->destroy(gpu_ctx_->device());
|
||||||
|
gpu_pipeline_.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy software UI renderer and surface
|
// Destroy software UI renderer and surface
|
||||||
if (ui_renderer_) { SDL_DestroyRenderer(ui_renderer_); ui_renderer_ = nullptr; }
|
if (ui_renderer_ != nullptr) {
|
||||||
if (ui_surface_) { SDL_DestroySurface(ui_surface_); ui_surface_ = nullptr; }
|
SDL_DestroyRenderer(ui_renderer_);
|
||||||
|
ui_renderer_ = nullptr;
|
||||||
|
}
|
||||||
|
if (ui_surface_ != nullptr) {
|
||||||
|
SDL_DestroySurface(ui_surface_);
|
||||||
|
ui_surface_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Destroy GPU context (releases device and window claim)
|
// Destroy GPU context (releases device and window claim)
|
||||||
if (gpu_ctx_) { gpu_ctx_->destroy(); gpu_ctx_.reset(); }
|
if (gpu_ctx_) {
|
||||||
|
gpu_ctx_->destroy();
|
||||||
|
gpu_ctx_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
if (window_) {
|
if (window_ != nullptr) {
|
||||||
SDL_DestroyWindow(window_);
|
SDL_DestroyWindow(window_);
|
||||||
window_ = nullptr;
|
window_ = nullptr;
|
||||||
}
|
}
|
||||||
@@ -541,8 +573,8 @@ void Engine::toggleShapeMode() {
|
|||||||
} else {
|
} else {
|
||||||
// Mostrar nombre de la figura actual (orden debe coincidir con enum ShapeType)
|
// Mostrar nombre de la figura actual (orden debe coincidir con enum ShapeType)
|
||||||
// Índices: 0=NONE, 1=SPHERE, 2=CUBE, 3=HELIX, 4=TORUS, 5=LISSAJOUS, 6=CYLINDER, 7=ICOSAHEDRON, 8=ATOM, 9=PNG_SHAPE
|
// Índices: 0=NONE, 1=SPHERE, 2=CUBE, 3=HELIX, 4=TORUS, 5=LISSAJOUS, 6=CYLINDER, 7=ICOSAHEDRON, 8=ATOM, 9=PNG_SHAPE
|
||||||
const char* shape_names[] = {"Ninguna", "Esfera", "Cubo", "Hélice", "Toroide", "Lissajous", "Cilindro", "Icosaedro", "Átomo", "Forma PNG"};
|
constexpr std::array<const char*, 10> SHAPE_NAMES = {"Ninguna", "Esfera", "Cubo", "Hélice", "Toroide", "Lissajous", "Cilindro", "Icosaedro", "Átomo", "Forma PNG"};
|
||||||
showNotificationForAction(shape_names[static_cast<int>(shape_manager_->getCurrentShapeType())]);
|
showNotificationForAction(SHAPE_NAMES[static_cast<int>(shape_manager_->getCurrentShapeType())]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,7 +599,7 @@ void Engine::toggleDepthZoom() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Boids (comportamiento de enjambre)
|
// Boids (comportamiento de enjambre)
|
||||||
bool Engine::isScenarioAllowedForBoids(int scenario_id) const {
|
auto Engine::isScenarioAllowedForBoids(int scenario_id) const -> bool {
|
||||||
int ball_count = (scenario_id == CUSTOM_SCENARIO_IDX)
|
int ball_count = (scenario_id == CUSTOM_SCENARIO_IDX)
|
||||||
? custom_scenario_balls_
|
? custom_scenario_balls_
|
||||||
: BALL_COUNT_SCENARIOS[scenario_id];
|
: BALL_COUNT_SCENARIOS[scenario_id];
|
||||||
@@ -654,8 +686,11 @@ void Engine::setMaxBallsOverride(int n) {
|
|||||||
skip_benchmark_ = true;
|
skip_benchmark_ = true;
|
||||||
int best = DEMO_AUTO_MIN_SCENARIO;
|
int best = DEMO_AUTO_MIN_SCENARIO;
|
||||||
for (int i = DEMO_AUTO_MIN_SCENARIO; i <= DEMO_AUTO_MAX_SCENARIO; ++i) {
|
for (int i = DEMO_AUTO_MIN_SCENARIO; i <= DEMO_AUTO_MAX_SCENARIO; ++i) {
|
||||||
if (BALL_COUNT_SCENARIOS[i] <= n) best = i;
|
if (BALL_COUNT_SCENARIOS[i] <= n) {
|
||||||
else break;
|
best = i;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
max_auto_scenario_ = best;
|
max_auto_scenario_ = best;
|
||||||
}
|
}
|
||||||
@@ -665,8 +700,9 @@ void Engine::setCustomScenario(int balls) {
|
|||||||
custom_scenario_balls_ = balls;
|
custom_scenario_balls_ = balls;
|
||||||
custom_scenario_enabled_ = true;
|
custom_scenario_enabled_ = true;
|
||||||
// scene_manager_ puede no existir aún (llamada pre-init); propagación en initialize()
|
// scene_manager_ puede no existir aún (llamada pre-init); propagación en initialize()
|
||||||
if (scene_manager_)
|
if (scene_manager_) {
|
||||||
scene_manager_->setCustomBallCount(balls);
|
scene_manager_->setCustomBallCount(balls);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escenarios (número de pelotas)
|
// Escenarios (número de pelotas)
|
||||||
@@ -747,15 +783,19 @@ void Engine::toggleLogoMode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::render() {
|
void Engine::render() { // NOLINT(readability-function-cognitive-complexity)
|
||||||
if (!gpu_ctx_ || !sprite_batch_ || !gpu_pipeline_) return;
|
if (!gpu_ctx_ || !sprite_batch_ || !gpu_pipeline_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// === Render UI text to software surface ===
|
// === Render UI text to software surface ===
|
||||||
renderUIToSurface();
|
renderUIToSurface();
|
||||||
|
|
||||||
// === Acquire command buffer ===
|
// === Acquire command buffer ===
|
||||||
SDL_GPUCommandBuffer* cmd = gpu_ctx_->acquireCommandBuffer();
|
SDL_GPUCommandBuffer* cmd = gpu_ctx_->acquireCommandBuffer();
|
||||||
if (!cmd) return;
|
if (cmd == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// === Upload UI surface to GPU texture (inline copy pass) ===
|
// === Upload UI surface to GPU texture (inline copy pass) ===
|
||||||
uploadUISurface(cmd);
|
uploadUISurface(cmd);
|
||||||
@@ -764,16 +804,27 @@ void Engine::render() {
|
|||||||
sprite_batch_->beginFrame();
|
sprite_batch_->beginFrame();
|
||||||
|
|
||||||
// Background gradient
|
// Background gradient
|
||||||
float top_r = 0, top_g = 0, top_b = 0, bot_r = 0, bot_g = 0, bot_b = 0;
|
float top_r = 0;
|
||||||
|
float top_g = 0;
|
||||||
|
float top_b = 0;
|
||||||
|
float bot_r = 0;
|
||||||
|
float bot_g = 0;
|
||||||
|
float bot_b = 0;
|
||||||
theme_manager_->getBackgroundColors(top_r, top_g, top_b, bot_r, bot_g, bot_b);
|
theme_manager_->getBackgroundColors(top_r, top_g, top_b, bot_r, bot_g, bot_b);
|
||||||
sprite_batch_->addBackground(
|
sprite_batch_->addBackground(
|
||||||
static_cast<float>(current_screen_width_), static_cast<float>(current_screen_height_),
|
static_cast<float>(current_screen_width_),
|
||||||
top_r, top_g, top_b, bot_r, bot_g, bot_b);
|
static_cast<float>(current_screen_height_),
|
||||||
|
top_r,
|
||||||
|
top_g,
|
||||||
|
top_b,
|
||||||
|
bot_r,
|
||||||
|
bot_g,
|
||||||
|
bot_b);
|
||||||
|
|
||||||
// Sprites (balls)
|
// Sprites (balls)
|
||||||
const auto& balls = scene_manager_->getBalls();
|
const auto& balls = scene_manager_->getBalls();
|
||||||
const float sw = static_cast<float>(current_screen_width_);
|
const auto SW = static_cast<float>(current_screen_width_);
|
||||||
const float sh = static_cast<float>(current_screen_height_);
|
const auto SH = static_cast<float>(current_screen_height_);
|
||||||
|
|
||||||
if (current_mode_ == SimulationMode::SHAPE) {
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
// SHAPE mode: bucket sort by depth Z (Painter's Algorithm), with depth scale.
|
// SHAPE mode: bucket sort by depth Z (Painter's Algorithm), with depth scale.
|
||||||
@@ -789,11 +840,7 @@ void Engine::render() {
|
|||||||
float brightness = balls[idx]->getDepthBrightness();
|
float brightness = balls[idx]->getDepthBrightness();
|
||||||
float depth_scale = balls[idx]->getDepthScale();
|
float depth_scale = balls[idx]->getDepthScale();
|
||||||
float bf = (ROTOBALL_MIN_BRIGHTNESS + brightness * (ROTOBALL_MAX_BRIGHTNESS - ROTOBALL_MIN_BRIGHTNESS)) / 255.0f;
|
float bf = (ROTOBALL_MIN_BRIGHTNESS + brightness * (ROTOBALL_MAX_BRIGHTNESS - ROTOBALL_MIN_BRIGHTNESS)) / 255.0f;
|
||||||
sprite_batch_->addSprite(pos.x, pos.y, pos.w, pos.h,
|
sprite_batch_->addSprite(pos.x, pos.y, pos.w, pos.h, color.r / 255.0f * bf, color.g / 255.0f * bf, color.b / 255.0f * bf, 1.0f, depth_scale, SW, SH);
|
||||||
color.r / 255.0f * bf,
|
|
||||||
color.g / 255.0f * bf,
|
|
||||||
color.b / 255.0f * bf,
|
|
||||||
1.0f, depth_scale, sw, sh);
|
|
||||||
}
|
}
|
||||||
depth_buckets_[b].clear();
|
depth_buckets_[b].clear();
|
||||||
}
|
}
|
||||||
@@ -805,13 +852,11 @@ void Engine::render() {
|
|||||||
SDL_FRect pos = balls[idx]->getPosition();
|
SDL_FRect pos = balls[idx]->getPosition();
|
||||||
Color color = theme_manager_->getInterpolatedColor(idx);
|
Color color = theme_manager_->getInterpolatedColor(idx);
|
||||||
// Convert to NDC center + NDC half-size (both positive)
|
// Convert to NDC center + NDC half-size (both positive)
|
||||||
float cx = ((pos.x + pos.w * 0.5f) / sw) * 2.0f - 1.0f;
|
float cx = (((pos.x + pos.w * 0.5f) / SW) * 2.0f) - 1.0f;
|
||||||
float cy = 1.0f - ((pos.y + pos.h * 0.5f) / sh) * 2.0f;
|
float cy = 1.0f - (((pos.y + pos.h * 0.5f) / SH) * 2.0f);
|
||||||
float hw = pos.w / sw;
|
float hw = pos.w / SW;
|
||||||
float hh = pos.h / sh;
|
float hh = pos.h / SH;
|
||||||
ball_gpu_data_.push_back({cx, cy, hw, hh,
|
ball_gpu_data_.push_back({cx, cy, hw, hh, color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, 1.0f});
|
||||||
color.r / 255.0f, color.g / 255.0f,
|
|
||||||
color.b / 255.0f, 1.0f});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -820,26 +865,26 @@ void Engine::render() {
|
|||||||
|
|
||||||
// Upload sprite batch (background + SHAPE balls + UI overlay quad)
|
// Upload sprite batch (background + SHAPE balls + UI overlay quad)
|
||||||
if (!sprite_batch_->uploadBatch(gpu_ctx_->device(), cmd)) {
|
if (!sprite_batch_->uploadBatch(gpu_ctx_->device(), cmd)) {
|
||||||
gpu_ctx_->submit(cmd);
|
GpuContext::submit(cmd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload instanced ball buffer (PHYSICS / CPU-BOIDS modes)
|
// Upload instanced ball buffer (PHYSICS / CPU-BOIDS modes)
|
||||||
bool use_instanced_balls = (current_mode_ != SimulationMode::SHAPE) && !ball_gpu_data_.empty();
|
bool use_instanced_balls = (current_mode_ != SimulationMode::SHAPE) && !ball_gpu_data_.empty();
|
||||||
if (use_instanced_balls) {
|
if (use_instanced_balls) {
|
||||||
gpu_ball_buffer_->upload(gpu_ctx_->device(), cmd,
|
gpu_ball_buffer_->upload(gpu_ctx_->device(), cmd, ball_gpu_data_.data(), static_cast<int>(ball_gpu_data_.size()));
|
||||||
ball_gpu_data_.data(), static_cast<int>(ball_gpu_data_.size()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GpuTexture* sprite_tex = (!gpu_textures_.empty())
|
GpuTexture* sprite_tex = (!gpu_textures_.empty())
|
||||||
? gpu_textures_[current_texture_index_].get() : nullptr;
|
? gpu_textures_[current_texture_index_].get()
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
// === Pass 1: Render background + balls to offscreen texture ===
|
// === Pass 1: Render background + balls to offscreen texture ===
|
||||||
if (offscreen_tex_ && offscreen_tex_->isValid() && sprite_tex && sprite_tex->isValid()) {
|
if (offscreen_tex_ && offscreen_tex_->isValid() && (sprite_tex != nullptr) && sprite_tex->isValid()) {
|
||||||
SDL_GPUColorTargetInfo ct = {};
|
SDL_GPUColorTargetInfo ct = {};
|
||||||
ct.texture = offscreen_tex_->texture();
|
ct.texture = offscreen_tex_->texture();
|
||||||
ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||||
ct.clear_color = {0.0f, 0.0f, 0.0f, 1.0f};
|
ct.clear_color = {.r = 0.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f};
|
||||||
ct.store_op = SDL_GPU_STOREOP_STORE;
|
ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||||
|
|
||||||
SDL_GPURenderPass* pass1 = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
|
SDL_GPURenderPass* pass1 = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
|
||||||
@@ -875,30 +920,35 @@ void Engine::render() {
|
|||||||
SDL_BindGPUIndexBuffer(pass1, &ib, SDL_GPU_INDEXELEMENTSIZE_32BIT);
|
SDL_BindGPUIndexBuffer(pass1, &ib, SDL_GPU_INDEXELEMENTSIZE_32BIT);
|
||||||
SDL_GPUTextureSamplerBinding tsb = {sprite_tex->texture(), sprite_tex->sampler()};
|
SDL_GPUTextureSamplerBinding tsb = {sprite_tex->texture(), sprite_tex->sampler()};
|
||||||
SDL_BindGPUFragmentSamplers(pass1, 0, &tsb, 1);
|
SDL_BindGPUFragmentSamplers(pass1, 0, &tsb, 1);
|
||||||
SDL_DrawGPUIndexedPrimitives(pass1, sprite_batch_->spriteIndexCount(), 1,
|
SDL_DrawGPUIndexedPrimitives(pass1, sprite_batch_->spriteIndexCount(), 1, sprite_batch_->spriteIndexOffset(), 0, 0);
|
||||||
sprite_batch_->spriteIndexOffset(), 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_EndGPURenderPass(pass1);
|
SDL_EndGPURenderPass(pass1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Pass 2+: External multi-pass shader OR native PostFX → swapchain ===
|
// === Pass 2+: External multi-pass shader OR native PostFX → swapchain ===
|
||||||
Uint32 sw_w = 0, sw_h = 0;
|
Uint32 sw_w = 0;
|
||||||
|
Uint32 sw_h = 0;
|
||||||
SDL_GPUTexture* swapchain = gpu_ctx_->acquireSwapchainTexture(cmd, &sw_w, &sw_h);
|
SDL_GPUTexture* swapchain = gpu_ctx_->acquireSwapchainTexture(cmd, &sw_w, &sw_h);
|
||||||
if (swapchain && offscreen_tex_ && offscreen_tex_->isValid()) {
|
if ((swapchain != nullptr) && offscreen_tex_ && offscreen_tex_->isValid()) {
|
||||||
|
|
||||||
// Helper lambda for viewport/scissor (used in the final pass)
|
// Helper lambda for viewport/scissor (used in the final pass)
|
||||||
auto applyViewport = [&](SDL_GPURenderPass* rp) {
|
auto apply_viewport = [&](SDL_GPURenderPass* rp) {
|
||||||
if (!fullscreen_enabled_) return;
|
if (!fullscreen_enabled_) {
|
||||||
float vp_x, vp_y, vp_w, vp_h;
|
return;
|
||||||
|
}
|
||||||
|
float vp_x;
|
||||||
|
float vp_y;
|
||||||
|
float vp_w;
|
||||||
|
float vp_h;
|
||||||
if (current_scaling_mode_ == ScalingMode::STRETCH) {
|
if (current_scaling_mode_ == ScalingMode::STRETCH) {
|
||||||
vp_x = 0.0f; vp_y = 0.0f;
|
vp_x = 0.0f;
|
||||||
|
vp_y = 0.0f;
|
||||||
vp_w = static_cast<float>(sw_w);
|
vp_w = static_cast<float>(sw_w);
|
||||||
vp_h = static_cast<float>(sw_h);
|
vp_h = static_cast<float>(sw_h);
|
||||||
} else if (current_scaling_mode_ == ScalingMode::INTEGER) {
|
} else if (current_scaling_mode_ == ScalingMode::INTEGER) {
|
||||||
int scale = static_cast<int>(std::min(sw_w / static_cast<Uint32>(base_screen_width_),
|
int scale = static_cast<int>(std::min(sw_w / static_cast<Uint32>(base_screen_width_),
|
||||||
sw_h / static_cast<Uint32>(base_screen_height_)));
|
sw_h / static_cast<Uint32>(base_screen_height_)));
|
||||||
if (scale < 1) scale = 1;
|
scale = std::max(scale, 1);
|
||||||
vp_w = static_cast<float>(base_screen_width_ * scale);
|
vp_w = static_cast<float>(base_screen_width_ * scale);
|
||||||
vp_h = static_cast<float>(base_screen_height_ * scale);
|
vp_h = static_cast<float>(base_screen_height_ * scale);
|
||||||
vp_x = (static_cast<float>(sw_w) - vp_w) * 0.5f;
|
vp_x = (static_cast<float>(sw_w) - vp_w) * 0.5f;
|
||||||
@@ -913,8 +963,7 @@ void Engine::render() {
|
|||||||
}
|
}
|
||||||
SDL_GPUViewport vp = {vp_x, vp_y, vp_w, vp_h, 0.0f, 1.0f};
|
SDL_GPUViewport vp = {vp_x, vp_y, vp_w, vp_h, 0.0f, 1.0f};
|
||||||
SDL_SetGPUViewport(rp, &vp);
|
SDL_SetGPUViewport(rp, &vp);
|
||||||
SDL_Rect scissor = {static_cast<int>(vp_x), static_cast<int>(vp_y),
|
SDL_Rect scissor = {static_cast<int>(vp_x), static_cast<int>(vp_y), static_cast<int>(vp_w), static_cast<int>(vp_h)};
|
||||||
static_cast<int>(vp_w), static_cast<int>(vp_h)};
|
|
||||||
SDL_SetGPUScissor(rp, &scissor);
|
SDL_SetGPUScissor(rp, &scissor);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -923,11 +972,11 @@ void Engine::render() {
|
|||||||
SDL_GPUColorTargetInfo ct = {};
|
SDL_GPUColorTargetInfo ct = {};
|
||||||
ct.texture = swapchain;
|
ct.texture = swapchain;
|
||||||
ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||||
ct.clear_color = {0.0f, 0.0f, 0.0f, 1.0f};
|
ct.clear_color = {.r = 0.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f};
|
||||||
ct.store_op = SDL_GPU_STOREOP_STORE;
|
ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||||
|
|
||||||
SDL_GPURenderPass* pass2 = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
|
SDL_GPURenderPass* pass2 = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
|
||||||
applyViewport(pass2);
|
apply_viewport(pass2);
|
||||||
|
|
||||||
// PostFX: full-screen triangle via vertex_id (no vertex buffer needed)
|
// PostFX: full-screen triangle via vertex_id (no vertex buffer needed)
|
||||||
SDL_BindGPUGraphicsPipeline(pass2, gpu_pipeline_->postfxPipeline());
|
SDL_BindGPUGraphicsPipeline(pass2, gpu_pipeline_->postfxPipeline());
|
||||||
@@ -945,15 +994,14 @@ void Engine::render() {
|
|||||||
SDL_BindGPUIndexBuffer(pass2, &ib, SDL_GPU_INDEXELEMENTSIZE_32BIT);
|
SDL_BindGPUIndexBuffer(pass2, &ib, SDL_GPU_INDEXELEMENTSIZE_32BIT);
|
||||||
SDL_GPUTextureSamplerBinding ui_tsb = {ui_tex_->texture(), ui_tex_->sampler()};
|
SDL_GPUTextureSamplerBinding ui_tsb = {ui_tex_->texture(), ui_tex_->sampler()};
|
||||||
SDL_BindGPUFragmentSamplers(pass2, 0, &ui_tsb, 1);
|
SDL_BindGPUFragmentSamplers(pass2, 0, &ui_tsb, 1);
|
||||||
SDL_DrawGPUIndexedPrimitives(pass2, sprite_batch_->overlayIndexCount(), 1,
|
SDL_DrawGPUIndexedPrimitives(pass2, sprite_batch_->overlayIndexCount(), 1, sprite_batch_->overlayIndexOffset(), 0, 0);
|
||||||
sprite_batch_->overlayIndexOffset(), 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_EndGPURenderPass(pass2);
|
SDL_EndGPURenderPass(pass2);
|
||||||
} // end native PostFX
|
} // end native PostFX
|
||||||
} // end if (swapchain && ...)
|
} // end if (swapchain && ...)
|
||||||
|
|
||||||
gpu_ctx_->submit(cmd);
|
GpuContext::submit(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::showNotificationForAction(const std::string& text) {
|
void Engine::showNotificationForAction(const std::string& text) {
|
||||||
@@ -975,7 +1023,9 @@ void Engine::toggleVSync() {
|
|||||||
ui_manager_->updateVSyncText(vsync_enabled_);
|
ui_manager_->updateVSyncText(vsync_enabled_);
|
||||||
|
|
||||||
// Aplicar el cambio de V-Sync al contexto GPU
|
// Aplicar el cambio de V-Sync al contexto GPU
|
||||||
if (gpu_ctx_) gpu_ctx_->setVSync(vsync_enabled_);
|
if (gpu_ctx_) {
|
||||||
|
gpu_ctx_->setVSync(vsync_enabled_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::toggleFullscreen() {
|
void Engine::toggleFullscreen() {
|
||||||
@@ -1092,18 +1142,22 @@ void Engine::toggleRealFullscreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Engine::applyPostFXPreset(int mode) {
|
void Engine::applyPostFXPreset(int mode) {
|
||||||
static constexpr float presets[4][3] = {
|
static constexpr std::array<std::array<float, 3>, 4> PRESETS = {{
|
||||||
{0.8f, 0.0f, 0.0f}, // 0: Vinyeta
|
{0.8f, 0.0f, 0.0f}, // 0: Vinyeta
|
||||||
{0.8f, 0.0f, 0.8f}, // 1: Scanlines
|
{0.8f, 0.0f, 0.8f}, // 1: Scanlines
|
||||||
{0.8f, 0.2f, 0.0f}, // 2: Cromàtica
|
{0.8f, 0.2f, 0.0f}, // 2: Cromàtica
|
||||||
{0.8f, 0.2f, 0.8f}, // 3: Complet
|
{0.8f, 0.2f, 0.8f}, // 3: Complet
|
||||||
};
|
}};
|
||||||
postfx_uniforms_.vignette_strength = presets[mode][0];
|
postfx_uniforms_.vignette_strength = PRESETS[mode][0];
|
||||||
postfx_uniforms_.chroma_strength = presets[mode][1];
|
postfx_uniforms_.chroma_strength = PRESETS[mode][1];
|
||||||
postfx_uniforms_.scanline_strength = presets[mode][2];
|
postfx_uniforms_.scanline_strength = PRESETS[mode][2];
|
||||||
// Reaplicar overrides de CLI si están activos
|
// Reaplicar overrides de CLI si están activos
|
||||||
if (postfx_override_vignette_ >= 0.f) postfx_uniforms_.vignette_strength = postfx_override_vignette_;
|
if (postfx_override_vignette_ >= 0.f) {
|
||||||
if (postfx_override_chroma_ >= 0.f) postfx_uniforms_.chroma_strength = postfx_override_chroma_;
|
postfx_uniforms_.vignette_strength = postfx_override_vignette_;
|
||||||
|
}
|
||||||
|
if (postfx_override_chroma_ >= 0.f) {
|
||||||
|
postfx_uniforms_.chroma_strength = postfx_override_chroma_;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::handlePostFXCycle() {
|
void Engine::handlePostFXCycle() {
|
||||||
@@ -1111,14 +1165,15 @@ void Engine::handlePostFXCycle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Engine::handlePostFXToggle() {
|
void Engine::handlePostFXToggle() {
|
||||||
static constexpr const char* names[4] = {
|
static constexpr std::array<const char*, 4> NAMES = {
|
||||||
"PostFX viñeta", "PostFX scanlines",
|
"PostFX viñeta",
|
||||||
"PostFX cromática", "PostFX completo"
|
"PostFX scanlines",
|
||||||
};
|
"PostFX cromática",
|
||||||
|
"PostFX completo"};
|
||||||
postfx_enabled_ = !postfx_enabled_;
|
postfx_enabled_ = !postfx_enabled_;
|
||||||
if (postfx_enabled_) {
|
if (postfx_enabled_) {
|
||||||
applyPostFXPreset(postfx_effect_mode_);
|
applyPostFXPreset(postfx_effect_mode_);
|
||||||
showNotificationForAction(names[postfx_effect_mode_]);
|
showNotificationForAction(NAMES[postfx_effect_mode_]);
|
||||||
} else {
|
} else {
|
||||||
postfx_uniforms_.vignette_strength = 0.0f;
|
postfx_uniforms_.vignette_strength = 0.0f;
|
||||||
postfx_uniforms_.chroma_strength = 0.0f;
|
postfx_uniforms_.chroma_strength = 0.0f;
|
||||||
@@ -1138,23 +1193,30 @@ void Engine::setPostFXParamOverrides(float vignette, float chroma) {
|
|||||||
postfx_override_chroma_ = chroma;
|
postfx_override_chroma_ = chroma;
|
||||||
postfx_enabled_ = true;
|
postfx_enabled_ = true;
|
||||||
// Aplicar inmediatamente sobre el preset activo
|
// Aplicar inmediatamente sobre el preset activo
|
||||||
if (vignette >= 0.f) postfx_uniforms_.vignette_strength = vignette;
|
if (vignette >= 0.f) {
|
||||||
if (chroma >= 0.f) postfx_uniforms_.chroma_strength = chroma;
|
postfx_uniforms_.vignette_strength = vignette;
|
||||||
|
}
|
||||||
|
if (chroma >= 0.f) {
|
||||||
|
postfx_uniforms_.chroma_strength = chroma;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::cycleShader() {
|
void Engine::cycleShader() {
|
||||||
// X no hace nada si PostFX está desactivado
|
// X no hace nada si PostFX está desactivado
|
||||||
if (!postfx_enabled_) return;
|
if (!postfx_enabled_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Cicla solo entre los 4 modos (sin OFF)
|
// Cicla solo entre los 4 modos (sin OFF)
|
||||||
postfx_effect_mode_ = (postfx_effect_mode_ + 1) % 4;
|
postfx_effect_mode_ = (postfx_effect_mode_ + 1) % 4;
|
||||||
applyPostFXPreset(postfx_effect_mode_);
|
applyPostFXPreset(postfx_effect_mode_);
|
||||||
|
|
||||||
static constexpr const char* names[4] = {
|
static constexpr std::array<const char*, 4> NAMES = {
|
||||||
"PostFX viñeta", "PostFX scanlines",
|
"PostFX viñeta",
|
||||||
"PostFX cromática", "PostFX completo"
|
"PostFX scanlines",
|
||||||
};
|
"PostFX cromática",
|
||||||
showNotificationForAction(names[postfx_effect_mode_]);
|
"PostFX completo"};
|
||||||
|
showNotificationForAction(NAMES[postfx_effect_mode_]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::toggleIntegerScaling() {
|
void Engine::toggleIntegerScaling() {
|
||||||
@@ -1171,44 +1233,51 @@ void Engine::toggleIntegerScaling() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* mode_name = "entero";
|
const char* mode_name = nullptr;
|
||||||
switch (current_scaling_mode_) {
|
switch (current_scaling_mode_) {
|
||||||
case ScalingMode::INTEGER: mode_name = "entero"; break;
|
case ScalingMode::INTEGER:
|
||||||
case ScalingMode::LETTERBOX: mode_name = "letterbox"; break;
|
mode_name = "entero";
|
||||||
case ScalingMode::STRETCH: mode_name = "stretch"; break;
|
break;
|
||||||
|
case ScalingMode::LETTERBOX:
|
||||||
|
mode_name = "letterbox";
|
||||||
|
break;
|
||||||
|
case ScalingMode::STRETCH:
|
||||||
|
mode_name = "stretch";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
showNotificationForAction(std::string("Escalado ") + mode_name);
|
showNotificationForAction(std::string("Escalado ") + mode_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b, float scale) {
|
void Engine::addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b, float scale) {
|
||||||
if (!sprite_batch_) return;
|
if (!sprite_batch_) {
|
||||||
sprite_batch_->addSprite(x, y, w, h,
|
return;
|
||||||
r / 255.0f, g / 255.0f, b / 255.0f, 1.0f,
|
}
|
||||||
scale,
|
sprite_batch_->addSprite(x, y, w, h, r / 255.0f, g / 255.0f, b / 255.0f, 1.0f, scale, static_cast<float>(current_screen_width_), static_cast<float>(current_screen_height_));
|
||||||
static_cast<float>(current_screen_width_),
|
|
||||||
static_cast<float>(current_screen_height_));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sistema de escala de ventana (pasos del 10%)
|
// Sistema de escala de ventana (pasos del 10%)
|
||||||
float Engine::calculateMaxWindowScale() const {
|
auto Engine::calculateMaxWindowScale() const -> float {
|
||||||
SDL_Rect bounds;
|
SDL_Rect bounds;
|
||||||
if (!SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &bounds)) { // bool: false = error
|
if (!SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &bounds)) { // bool: false = error
|
||||||
return WINDOW_SCALE_MIN; // Fallback solo si falla de verdad
|
return WINDOW_SCALE_MIN; // Fallback solo si falla de verdad
|
||||||
}
|
}
|
||||||
float max_by_w = static_cast<float>(bounds.w - 2 * WINDOW_DESKTOP_MARGIN) / base_screen_width_;
|
float max_by_w = static_cast<float>(bounds.w - (2 * WINDOW_DESKTOP_MARGIN)) / base_screen_width_;
|
||||||
float max_by_h = static_cast<float>(bounds.h - 2 * WINDOW_DESKTOP_MARGIN - WINDOW_DECORATION_HEIGHT) / base_screen_height_;
|
float max_by_h = static_cast<float>(bounds.h - (2 * WINDOW_DESKTOP_MARGIN) - WINDOW_DECORATION_HEIGHT) / base_screen_height_;
|
||||||
float result = std::max(WINDOW_SCALE_MIN, std::min(max_by_w, max_by_h));
|
float result = std::max(WINDOW_SCALE_MIN, std::min(max_by_w, max_by_h));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redimensiona la ventana física manteniéndo su centro, con clamping a pantalla.
|
// Redimensiona la ventana física manteniéndo su centro, con clamping a pantalla.
|
||||||
static void resizeWindowCentered(SDL_Window* window, int new_w, int new_h) {
|
static void resizeWindowCentered(SDL_Window* window, int new_w, int new_h) {
|
||||||
int cur_x, cur_y, cur_w, cur_h;
|
int cur_x;
|
||||||
|
int cur_y;
|
||||||
|
int cur_w;
|
||||||
|
int cur_h;
|
||||||
SDL_GetWindowPosition(window, &cur_x, &cur_y);
|
SDL_GetWindowPosition(window, &cur_x, &cur_y);
|
||||||
SDL_GetWindowSize(window, &cur_w, &cur_h);
|
SDL_GetWindowSize(window, &cur_w, &cur_h);
|
||||||
|
|
||||||
int new_x = cur_x + (cur_w - new_w) / 2;
|
int new_x = cur_x + ((cur_w - new_w) / 2);
|
||||||
int new_y = cur_y + (cur_h - new_h) / 2;
|
int new_y = cur_y + ((cur_h - new_h) / 2);
|
||||||
|
|
||||||
SDL_Rect bounds;
|
SDL_Rect bounds;
|
||||||
if (SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &bounds)) {
|
if (SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &bounds)) {
|
||||||
@@ -1227,7 +1296,9 @@ void Engine::setWindowScale(float new_scale) {
|
|||||||
new_scale = std::max(WINDOW_SCALE_MIN, std::min(new_scale, max_scale));
|
new_scale = std::max(WINDOW_SCALE_MIN, std::min(new_scale, max_scale));
|
||||||
new_scale = std::round(new_scale * 10.0f) / 10.0f;
|
new_scale = std::round(new_scale * 10.0f) / 10.0f;
|
||||||
|
|
||||||
if (new_scale == current_window_scale_) return;
|
if (new_scale == current_window_scale_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int new_width = static_cast<int>(std::round(current_screen_width_ * new_scale));
|
int new_width = static_cast<int>(std::round(current_screen_width_ * new_scale));
|
||||||
int new_height = static_cast<int>(std::round(current_screen_height_ * new_scale));
|
int new_height = static_cast<int>(std::round(current_screen_height_ * new_scale));
|
||||||
@@ -1242,9 +1313,9 @@ void Engine::zoomIn() {
|
|||||||
float prev = current_window_scale_;
|
float prev = current_window_scale_;
|
||||||
setWindowScale(current_window_scale_ + WINDOW_SCALE_STEP);
|
setWindowScale(current_window_scale_ + WINDOW_SCALE_STEP);
|
||||||
if (current_window_scale_ != prev) {
|
if (current_window_scale_ != prev) {
|
||||||
char buf[32];
|
std::array<char, 32> buf{};
|
||||||
std::snprintf(buf, sizeof(buf), "Zoom %.0f%%", current_window_scale_ * 100.0f);
|
std::snprintf(buf.data(), buf.size(), "Zoom %.0f%%", current_window_scale_ * 100.0f);
|
||||||
showNotificationForAction(buf);
|
showNotificationForAction(buf.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1252,9 +1323,9 @@ void Engine::zoomOut() {
|
|||||||
float prev = current_window_scale_;
|
float prev = current_window_scale_;
|
||||||
setWindowScale(current_window_scale_ - WINDOW_SCALE_STEP);
|
setWindowScale(current_window_scale_ - WINDOW_SCALE_STEP);
|
||||||
if (current_window_scale_ != prev) {
|
if (current_window_scale_ != prev) {
|
||||||
char buf[32];
|
std::array<char, 32> buf{};
|
||||||
std::snprintf(buf, sizeof(buf), "Zoom %.0f%%", current_window_scale_ * 100.0f);
|
std::snprintf(buf.data(), buf.size(), "Zoom %.0f%%", current_window_scale_ * 100.0f);
|
||||||
showNotificationForAction(buf);
|
showNotificationForAction(buf.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1262,7 +1333,9 @@ void Engine::setFieldScale(float new_scale) {
|
|||||||
float max_scale = calculateMaxWindowScale();
|
float max_scale = calculateMaxWindowScale();
|
||||||
new_scale = std::max(WINDOW_SCALE_MIN, std::min(new_scale, max_scale));
|
new_scale = std::max(WINDOW_SCALE_MIN, std::min(new_scale, max_scale));
|
||||||
new_scale = std::round(new_scale * 10.0f) / 10.0f;
|
new_scale = std::round(new_scale * 10.0f) / 10.0f;
|
||||||
if (new_scale == current_field_scale_) return;
|
if (new_scale == current_field_scale_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
current_field_scale_ = new_scale;
|
current_field_scale_ = new_scale;
|
||||||
current_screen_width_ = static_cast<int>(std::round(base_screen_width_ * new_scale));
|
current_screen_width_ = static_cast<int>(std::round(base_screen_width_ * new_scale));
|
||||||
@@ -1282,7 +1355,9 @@ void Engine::setFieldScale(float new_scale) {
|
|||||||
scene_manager_->changeScenario(scene_manager_->getCurrentScenario(), current_mode_);
|
scene_manager_->changeScenario(scene_manager_->getCurrentScenario(), current_mode_);
|
||||||
boid_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
boid_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||||
shape_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
shape_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||||
if (app_logo_) app_logo_->updateScreenSize(current_screen_width_, current_screen_height_);
|
if (app_logo_) {
|
||||||
|
app_logo_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||||
|
}
|
||||||
if (current_mode_ == SimulationMode::SHAPE) {
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
generateShape();
|
generateShape();
|
||||||
scene_manager_->enableShapeAttractionAll(true);
|
scene_manager_->enableShapeAttractionAll(true);
|
||||||
@@ -1314,7 +1389,8 @@ void Engine::updatePhysicalWindowSize() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// En modo ventana, obtener tamaño FÍSICO real del framebuffer
|
// En modo ventana, obtener tamaño FÍSICO real del framebuffer
|
||||||
int window_w = 0, window_h = 0;
|
int window_w = 0;
|
||||||
|
int window_h = 0;
|
||||||
SDL_GetWindowSizeInPixels(window_, &window_w, &window_h);
|
SDL_GetWindowSizeInPixels(window_, &window_w, &window_h);
|
||||||
physical_window_width_ = window_w;
|
physical_window_width_ = window_w;
|
||||||
physical_window_height_ = window_h;
|
physical_window_height_ = window_h;
|
||||||
@@ -1323,9 +1399,7 @@ void Engine::updatePhysicalWindowSize() {
|
|||||||
// Notificar a UIManager del cambio de tamaño (delegado)
|
// Notificar a UIManager del cambio de tamaño (delegado)
|
||||||
// Pasar current_screen_height_ para que UIManager actualice la altura lógica
|
// Pasar current_screen_height_ para que UIManager actualice la altura lógica
|
||||||
// (necesario en F4 donde la resolución lógica cambia a la del display)
|
// (necesario en F4 donde la resolución lógica cambia a la del display)
|
||||||
ui_manager_->updatePhysicalWindowSize(physical_window_width_, physical_window_height_,
|
ui_manager_->updatePhysicalWindowSize(physical_window_width_, physical_window_height_, current_screen_height_);
|
||||||
current_screen_height_);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1345,7 +1419,9 @@ void Engine::switchTextureSilent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Engine::setTextureByIndex(size_t index) {
|
void Engine::setTextureByIndex(size_t index) {
|
||||||
if (index >= textures_.size()) return;
|
if (index >= textures_.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
current_texture_index_ = index;
|
current_texture_index_ = index;
|
||||||
texture_ = textures_[current_texture_index_];
|
texture_ = textures_[current_texture_index_];
|
||||||
int new_size = texture_->getWidth();
|
int new_size = texture_->getWidth();
|
||||||
@@ -1356,7 +1432,9 @@ void Engine::setTextureByIndex(size_t index) {
|
|||||||
// Toggle manual del Modo Logo (tecla K)
|
// Toggle manual del Modo Logo (tecla K)
|
||||||
// Sistema de cambio de sprites dinámico
|
// Sistema de cambio de sprites dinámico
|
||||||
void Engine::switchTextureInternal(bool show_notification) {
|
void Engine::switchTextureInternal(bool show_notification) {
|
||||||
if (textures_.empty()) return;
|
if (textures_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Cambiar a siguiente textura (ciclar)
|
// Cambiar a siguiente textura (ciclar)
|
||||||
current_texture_index_ = (current_texture_index_ + 1) % textures_.size();
|
current_texture_index_ = (current_texture_index_ + 1) % textures_.size();
|
||||||
@@ -1372,7 +1450,7 @@ void Engine::switchTextureInternal(bool show_notification) {
|
|||||||
// Mostrar notificación con el nombre de la textura (solo si se solicita)
|
// Mostrar notificación con el nombre de la textura (solo si se solicita)
|
||||||
if (show_notification) {
|
if (show_notification) {
|
||||||
std::string texture_name = texture_names_[current_texture_index_];
|
std::string texture_name = texture_names_[current_texture_index_];
|
||||||
std::transform(texture_name.begin(), texture_name.end(), texture_name.begin(), ::tolower);
|
std::ranges::transform(texture_name, texture_name.begin(), ::tolower);
|
||||||
showNotificationForAction("Textura " + texture_name);
|
showNotificationForAction("Textura " + texture_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1407,9 +1485,11 @@ void Engine::runPerformanceBenchmark() {
|
|||||||
int num_displays = 0;
|
int num_displays = 0;
|
||||||
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
||||||
float monitor_hz = 60.0f;
|
float monitor_hz = 60.0f;
|
||||||
if (displays && num_displays > 0) {
|
if ((displays != nullptr) && num_displays > 0) {
|
||||||
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
|
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
|
||||||
if (dm && dm->refresh_rate > 0) monitor_hz = dm->refresh_rate;
|
if ((dm != nullptr) && dm->refresh_rate > 0) {
|
||||||
|
monitor_hz = dm->refresh_rate;
|
||||||
|
}
|
||||||
SDL_free(displays);
|
SDL_free(displays);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1493,59 +1573,70 @@ void Engine::runPerformanceBenchmark() {
|
|||||||
// GPU HELPERS
|
// GPU HELPERS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
bool Engine::loadGpuSpriteTexture(size_t index) {
|
auto Engine::loadGpuSpriteTexture(size_t index) -> bool {
|
||||||
if (!gpu_ctx_ || index >= gpu_textures_.size()) return false;
|
if (!gpu_ctx_ || index >= gpu_textures_.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return gpu_textures_[index] && gpu_textures_[index]->isValid();
|
return gpu_textures_[index] && gpu_textures_[index]->isValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::recreateOffscreenTexture() {
|
void Engine::recreateOffscreenTexture() {
|
||||||
if (!gpu_ctx_ || !offscreen_tex_) return;
|
if (!gpu_ctx_ || !offscreen_tex_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
SDL_WaitForGPUIdle(gpu_ctx_->device());
|
SDL_WaitForGPUIdle(gpu_ctx_->device());
|
||||||
|
|
||||||
offscreen_tex_->destroy(gpu_ctx_->device());
|
offscreen_tex_->destroy(gpu_ctx_->device());
|
||||||
offscreen_tex_->createRenderTarget(gpu_ctx_->device(),
|
offscreen_tex_->createRenderTarget(gpu_ctx_->device(),
|
||||||
current_screen_width_, current_screen_height_,
|
current_screen_width_,
|
||||||
|
current_screen_height_,
|
||||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||||
|
|
||||||
// Recreate UI texture to match new screen size
|
// Recreate UI texture to match new screen size
|
||||||
if (ui_tex_) {
|
if (ui_tex_) {
|
||||||
ui_tex_->destroy(gpu_ctx_->device());
|
ui_tex_->destroy(gpu_ctx_->device());
|
||||||
ui_tex_->createRenderTarget(gpu_ctx_->device(),
|
ui_tex_->createRenderTarget(gpu_ctx_->device(),
|
||||||
current_screen_width_, current_screen_height_,
|
current_screen_width_,
|
||||||
|
current_screen_height_,
|
||||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recreate renderer de software (DESTRUIR renderer PRIMER, després surface)
|
// Recreate renderer de software (DESTRUIR renderer PRIMER, després surface)
|
||||||
if (ui_renderer_) { SDL_DestroyRenderer(ui_renderer_); ui_renderer_ = nullptr; }
|
if (ui_renderer_ != nullptr) {
|
||||||
if (ui_surface_) { SDL_DestroySurface(ui_surface_); ui_surface_ = nullptr; }
|
SDL_DestroyRenderer(ui_renderer_);
|
||||||
|
ui_renderer_ = nullptr;
|
||||||
|
}
|
||||||
|
if (ui_surface_ != nullptr) {
|
||||||
|
SDL_DestroySurface(ui_surface_);
|
||||||
|
ui_surface_ = nullptr;
|
||||||
|
}
|
||||||
ui_surface_ = SDL_CreateSurface(current_screen_width_, current_screen_height_, SDL_PIXELFORMAT_RGBA32);
|
ui_surface_ = SDL_CreateSurface(current_screen_width_, current_screen_height_, SDL_PIXELFORMAT_RGBA32);
|
||||||
if (ui_surface_) {
|
if (ui_surface_ != nullptr) {
|
||||||
ui_renderer_ = SDL_CreateSoftwareRenderer(ui_surface_);
|
ui_renderer_ = SDL_CreateSoftwareRenderer(ui_surface_);
|
||||||
}
|
}
|
||||||
// Re-inicialitzar components UI amb nou renderer
|
// Re-inicialitzar components UI amb nou renderer
|
||||||
if (ui_renderer_ && ui_manager_) {
|
if ((ui_renderer_ != nullptr) && ui_manager_) {
|
||||||
ui_manager_->initialize(ui_renderer_, theme_manager_.get(),
|
ui_manager_->initialize(ui_renderer_, theme_manager_.get(), current_screen_width_, current_screen_height_, // physical
|
||||||
current_screen_width_, current_screen_height_, // physical
|
base_screen_width_,
|
||||||
base_screen_width_, base_screen_height_); // logical (font size based on base)
|
base_screen_height_); // logical (font size based on base)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ui_renderer_ && app_logo_) {
|
if ((ui_renderer_ != nullptr) && app_logo_) {
|
||||||
app_logo_->initialize(ui_renderer_, current_screen_width_, current_screen_height_);
|
app_logo_->initialize(ui_renderer_, current_screen_width_, current_screen_height_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::renderUIToSurface() {
|
void Engine::renderUIToSurface() {
|
||||||
if (!ui_renderer_ || !ui_surface_) return;
|
if ((ui_renderer_ == nullptr) || (ui_surface_ == nullptr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Clear surface (fully transparent)
|
// Clear surface (fully transparent)
|
||||||
SDL_SetRenderDrawColor(ui_renderer_, 0, 0, 0, 0);
|
SDL_SetRenderDrawColor(ui_renderer_, 0, 0, 0, 0);
|
||||||
SDL_RenderClear(ui_renderer_);
|
SDL_RenderClear(ui_renderer_);
|
||||||
|
|
||||||
// Render UI (HUD, FPS counter, notifications)
|
// Render UI (HUD, FPS counter, notifications)
|
||||||
ui_manager_->render(ui_renderer_, this, scene_manager_.get(), current_mode_,
|
ui_manager_->render(ui_renderer_, this, scene_manager_.get(), current_mode_, state_manager_->getCurrentMode(), shape_manager_->getActiveShape(), shape_manager_->getConvergence(), physical_window_width_, physical_window_height_, current_screen_width_);
|
||||||
state_manager_->getCurrentMode(),
|
|
||||||
shape_manager_->getActiveShape(), shape_manager_->getConvergence(),
|
|
||||||
physical_window_width_, physical_window_height_, current_screen_width_);
|
|
||||||
|
|
||||||
// Render periodic logo overlay
|
// Render periodic logo overlay
|
||||||
if (app_logo_) {
|
if (app_logo_) {
|
||||||
@@ -1556,21 +1647,25 @@ void Engine::renderUIToSurface() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Engine::uploadUISurface(SDL_GPUCommandBuffer* cmd_buf) {
|
void Engine::uploadUISurface(SDL_GPUCommandBuffer* cmd_buf) {
|
||||||
if (!ui_tex_ || !ui_tex_->isValid() || !ui_surface_ || !gpu_ctx_) return;
|
if (!ui_tex_ || !ui_tex_->isValid() || (ui_surface_ == nullptr) || !gpu_ctx_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int w = ui_surface_->w;
|
int w = ui_surface_->w;
|
||||||
int h = ui_surface_->h;
|
int h = ui_surface_->h;
|
||||||
Uint32 data_size = static_cast<Uint32>(w * h * 4); // RGBA = 4 bytes/pixel
|
auto data_size = static_cast<Uint32>(w * h * 4); // RGBA = 4 bytes/pixel
|
||||||
|
|
||||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||||
tb_info.size = data_size;
|
tb_info.size = data_size;
|
||||||
|
|
||||||
SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(gpu_ctx_->device(), &tb_info);
|
SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(gpu_ctx_->device(), &tb_info);
|
||||||
if (!transfer) return;
|
if (transfer == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void* mapped = SDL_MapGPUTransferBuffer(gpu_ctx_->device(), transfer, true);
|
void* mapped = SDL_MapGPUTransferBuffer(gpu_ctx_->device(), transfer, true);
|
||||||
if (!mapped) {
|
if (mapped == nullptr) {
|
||||||
SDL_ReleaseGPUTransferBuffer(gpu_ctx_->device(), transfer);
|
SDL_ReleaseGPUTransferBuffer(gpu_ctx_->device(), transfer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
#include "ui/app_logo.hpp" // for AppLogo
|
|
||||||
#include "ball.hpp" // for Ball
|
#include "ball.hpp" // for Ball
|
||||||
#include "boids_mgr/boid_manager.hpp" // for BoidManager
|
#include "boids_mgr/boid_manager.hpp" // for BoidManager
|
||||||
#include "defines.hpp" // for GravityDirection, ColorTheme, ShapeType
|
#include "defines.hpp" // for GravityDirection, ColorTheme, ShapeType
|
||||||
@@ -26,6 +25,7 @@
|
|||||||
#include "shapes_mgr/shape_manager.hpp" // for ShapeManager
|
#include "shapes_mgr/shape_manager.hpp" // for ShapeManager
|
||||||
#include "state/state_manager.hpp" // for StateManager
|
#include "state/state_manager.hpp" // for StateManager
|
||||||
#include "theme_manager.hpp" // for ThemeManager
|
#include "theme_manager.hpp" // for ThemeManager
|
||||||
|
#include "ui/app_logo.hpp" // for AppLogo
|
||||||
#include "ui/ui_manager.hpp" // for UIManager
|
#include "ui/ui_manager.hpp" // for UIManager
|
||||||
|
|
||||||
class Engine {
|
class Engine {
|
||||||
@@ -277,5 +277,4 @@ class Engine {
|
|||||||
void recreateOffscreenTexture(); // Recreate when resolution changes
|
void recreateOffscreenTexture(); // Recreate when resolution changes
|
||||||
void renderUIToSurface(); // Render text/UI to ui_surface_
|
void renderUIToSurface(); // Render text/UI to ui_surface_
|
||||||
void uploadUISurface(SDL_GPUCommandBuffer* cmd_buf); // Upload ui_surface_ → ui_tex_
|
void uploadUISurface(SDL_GPUCommandBuffer* cmd_buf); // Upload ui_surface_ → ui_tex_
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
#include "gpu_ball_buffer.hpp"
|
#include "gpu_ball_buffer.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL_log.h>
|
#include <SDL3/SDL_log.h>
|
||||||
|
|
||||||
#include <algorithm> // std::min
|
#include <algorithm> // std::min
|
||||||
#include <cstring> // memcpy
|
#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);
|
Uint32 buf_size = static_cast<Uint32>(MAX_BALLS) * sizeof(BallGPUData);
|
||||||
|
|
||||||
// GPU vertex buffer (instance-rate data read by the ball instanced shader)
|
// GPU vertex buffer (instance-rate data read by the ball instanced shader)
|
||||||
@@ -12,7 +13,7 @@ bool GpuBallBuffer::init(SDL_GPUDevice* device) {
|
|||||||
buf_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
buf_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||||
buf_info.size = buf_size;
|
buf_info.size = buf_size;
|
||||||
gpu_buf_ = SDL_CreateGPUBuffer(device, &buf_info);
|
gpu_buf_ = SDL_CreateGPUBuffer(device, &buf_info);
|
||||||
if (!gpu_buf_) {
|
if (gpu_buf_ == nullptr) {
|
||||||
SDL_Log("GpuBallBuffer: GPU buffer creation failed: %s", SDL_GetError());
|
SDL_Log("GpuBallBuffer: GPU buffer creation failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -22,32 +23,43 @@ bool GpuBallBuffer::init(SDL_GPUDevice* device) {
|
|||||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||||
tb_info.size = buf_size;
|
tb_info.size = buf_size;
|
||||||
transfer_buf_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
transfer_buf_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||||
if (!transfer_buf_) {
|
if (transfer_buf_ == nullptr) {
|
||||||
SDL_Log("GpuBallBuffer: transfer buffer creation failed: %s", SDL_GetError());
|
SDL_Log("GpuBallBuffer: transfer buffer creation failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Log("GpuBallBuffer: initialized (capacity %d balls, %.1f MB VRAM)",
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GpuBallBuffer::destroy(SDL_GPUDevice* device) {
|
void GpuBallBuffer::destroy(SDL_GPUDevice* device) {
|
||||||
if (!device) return;
|
if (device == nullptr) {
|
||||||
if (transfer_buf_) { SDL_ReleaseGPUTransferBuffer(device, transfer_buf_); transfer_buf_ = nullptr; }
|
return;
|
||||||
if (gpu_buf_) { SDL_ReleaseGPUBuffer(device, gpu_buf_); gpu_buf_ = nullptr; }
|
}
|
||||||
|
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;
|
count_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd,
|
auto GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd, const BallGPUData* data, int count) -> bool {
|
||||||
const BallGPUData* data, int count) {
|
if ((data == nullptr) || count <= 0) {
|
||||||
if (!data || count <= 0) { count_ = 0; return false; }
|
count_ = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
count = std::min(count, MAX_BALLS);
|
count = std::min(count, MAX_BALLS);
|
||||||
|
|
||||||
Uint32 upload_size = static_cast<Uint32>(count) * sizeof(BallGPUData);
|
Uint32 upload_size = static_cast<Uint32>(count) * sizeof(BallGPUData);
|
||||||
|
|
||||||
void* ptr = SDL_MapGPUTransferBuffer(device, transfer_buf_, true /* cycle */);
|
void* ptr = SDL_MapGPUTransferBuffer(device, transfer_buf_, true /* cycle */);
|
||||||
if (!ptr) {
|
if (ptr == nullptr) {
|
||||||
SDL_Log("GpuBallBuffer: transfer buffer map failed: %s", SDL_GetError());
|
SDL_Log("GpuBallBuffer: transfer buffer map failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -55,8 +67,8 @@ bool GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd,
|
|||||||
SDL_UnmapGPUTransferBuffer(device, transfer_buf_);
|
SDL_UnmapGPUTransferBuffer(device, transfer_buf_);
|
||||||
|
|
||||||
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);
|
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);
|
||||||
SDL_GPUTransferBufferLocation src = { transfer_buf_, 0 };
|
SDL_GPUTransferBufferLocation src = {transfer_buf_, 0};
|
||||||
SDL_GPUBufferRegion dst = { gpu_buf_, 0, upload_size };
|
SDL_GPUBufferRegion dst = {gpu_buf_, 0, upload_size};
|
||||||
SDL_UploadToGPUBuffer(copy, &src, &dst, true /* cycle */);
|
SDL_UploadToGPUBuffer(copy, &src, &dst, true /* cycle */);
|
||||||
SDL_EndGPUCopyPass(copy);
|
SDL_EndGPUCopyPass(copy);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SDL3/SDL_gpu.h>
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -26,7 +27,7 @@ static_assert(sizeof(BallGPUData) == 32, "BallGPUData must be 32 bytes");
|
|||||||
// // Then in render pass: bind buffer, SDL_DrawGPUPrimitives(pass, 6, count, 0, 0)
|
// // Then in render pass: bind buffer, SDL_DrawGPUPrimitives(pass, 6, count, 0, 0)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class GpuBallBuffer {
|
class GpuBallBuffer {
|
||||||
public:
|
public:
|
||||||
static constexpr int MAX_BALLS = 500000;
|
static constexpr int MAX_BALLS = 500000;
|
||||||
|
|
||||||
bool init(SDL_GPUDevice* device);
|
bool init(SDL_GPUDevice* device);
|
||||||
@@ -34,13 +35,12 @@ public:
|
|||||||
|
|
||||||
// Upload ball array to GPU via an internal copy pass.
|
// Upload ball array to GPU via an internal copy pass.
|
||||||
// count is clamped to MAX_BALLS. Returns false on error or empty input.
|
// count is clamped to MAX_BALLS. Returns false on error or empty input.
|
||||||
bool upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd,
|
bool upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd, const BallGPUData* data, int count);
|
||||||
const BallGPUData* data, int count);
|
|
||||||
|
|
||||||
SDL_GPUBuffer* buffer() const { return gpu_buf_; }
|
SDL_GPUBuffer* buffer() const { return gpu_buf_; }
|
||||||
int count() const { return count_; }
|
int count() const { return count_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_GPUBuffer* gpu_buf_ = nullptr;
|
SDL_GPUBuffer* gpu_buf_ = nullptr;
|
||||||
SDL_GPUTransferBuffer* transfer_buf_ = nullptr;
|
SDL_GPUTransferBuffer* transfer_buf_ = nullptr;
|
||||||
int count_ = 0;
|
int count_ = 0;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#include "gpu_context.hpp"
|
#include "gpu_context.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL_log.h>
|
#include <SDL3/SDL_log.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
bool GpuContext::init(SDL_Window* window) {
|
auto GpuContext::init(SDL_Window* window) -> bool {
|
||||||
window_ = window;
|
window_ = window;
|
||||||
|
|
||||||
// Create GPU device: Metal on Apple, Vulkan elsewhere
|
// 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;
|
SDL_GPUShaderFormat preferred = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||||
#endif
|
#endif
|
||||||
device_ = SDL_CreateGPUDevice(preferred, false, nullptr);
|
device_ = SDL_CreateGPUDevice(preferred, false, nullptr);
|
||||||
if (!device_) {
|
if (device_ == nullptr) {
|
||||||
std::cerr << "GpuContext: SDL_CreateGPUDevice failed: " << SDL_GetError() << std::endl;
|
std::cerr << "GpuContext: SDL_CreateGPUDevice failed: " << SDL_GetError() << '\n';
|
||||||
return false;
|
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
|
// Claim the window so the GPU device owns its swapchain
|
||||||
if (!SDL_ClaimWindowForGPUDevice(device_, window_)) {
|
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_);
|
SDL_DestroyGPUDevice(device_);
|
||||||
device_ = nullptr;
|
device_ = nullptr;
|
||||||
return false;
|
return false;
|
||||||
@@ -29,17 +30,15 @@ bool GpuContext::init(SDL_Window* window) {
|
|||||||
|
|
||||||
// Query swapchain format (Metal: typically B8G8R8A8_UNORM or R8G8B8A8_UNORM)
|
// Query swapchain format (Metal: typically B8G8R8A8_UNORM or R8G8B8A8_UNORM)
|
||||||
swapchain_format_ = SDL_GetGPUSwapchainTextureFormat(device_, window_);
|
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
|
// Default: VSync ON
|
||||||
SDL_SetGPUSwapchainParameters(device_, window_,
|
SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_VSYNC);
|
||||||
SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
|
|
||||||
SDL_GPU_PRESENTMODE_VSYNC);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GpuContext::destroy() {
|
void GpuContext::destroy() {
|
||||||
if (device_) {
|
if (device_ != nullptr) {
|
||||||
SDL_WaitForGPUIdle(device_);
|
SDL_WaitForGPUIdle(device_);
|
||||||
SDL_ReleaseWindowFromGPUDevice(device_, window_);
|
SDL_ReleaseWindowFromGPUDevice(device_, window_);
|
||||||
SDL_DestroyGPUDevice(device_);
|
SDL_DestroyGPUDevice(device_);
|
||||||
@@ -48,16 +47,17 @@ void GpuContext::destroy() {
|
|||||||
window_ = nullptr;
|
window_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_GPUCommandBuffer* GpuContext::acquireCommandBuffer() {
|
auto GpuContext::acquireCommandBuffer() -> SDL_GPUCommandBuffer* {
|
||||||
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_);
|
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_);
|
||||||
if (!cmd) {
|
if (cmd == nullptr) {
|
||||||
SDL_Log("GpuContext: SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError());
|
SDL_Log("GpuContext: SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError());
|
||||||
}
|
}
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_GPUTexture* GpuContext::acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf,
|
auto GpuContext::acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf,
|
||||||
Uint32* out_w, Uint32* out_h) {
|
Uint32* out_w,
|
||||||
|
Uint32* out_h) -> SDL_GPUTexture* {
|
||||||
SDL_GPUTexture* tex = nullptr;
|
SDL_GPUTexture* tex = nullptr;
|
||||||
if (!SDL_AcquireGPUSwapchainTexture(cmd_buf, window_, &tex, out_w, out_h)) {
|
if (!SDL_AcquireGPUSwapchainTexture(cmd_buf, window_, &tex, out_w, out_h)) {
|
||||||
SDL_Log("GpuContext: SDL_AcquireGPUSwapchainTexture failed: %s", SDL_GetError());
|
SDL_Log("GpuContext: SDL_AcquireGPUSwapchainTexture failed: %s", SDL_GetError());
|
||||||
@@ -71,10 +71,8 @@ void GpuContext::submit(SDL_GPUCommandBuffer* cmd_buf) {
|
|||||||
SDL_SubmitGPUCommandBuffer(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_GPUPresentMode mode = enabled ? SDL_GPU_PRESENTMODE_VSYNC
|
||||||
: SDL_GPU_PRESENTMODE_IMMEDIATE;
|
: SDL_GPU_PRESENTMODE_IMMEDIATE;
|
||||||
return SDL_SetGPUSwapchainParameters(device_, window_,
|
return SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, mode);
|
||||||
SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
|
|
||||||
mode);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
// Replaces SDL_Renderer as the main rendering backend.
|
// Replaces SDL_Renderer as the main rendering backend.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class GpuContext {
|
class GpuContext {
|
||||||
public:
|
public:
|
||||||
bool init(SDL_Window* window);
|
bool init(SDL_Window* window);
|
||||||
void destroy();
|
void destroy();
|
||||||
|
|
||||||
@@ -20,13 +20,14 @@ public:
|
|||||||
SDL_GPUCommandBuffer* acquireCommandBuffer();
|
SDL_GPUCommandBuffer* acquireCommandBuffer();
|
||||||
// Returns nullptr if window is minimized (swapchain not available).
|
// Returns nullptr if window is minimized (swapchain not available).
|
||||||
SDL_GPUTexture* acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf,
|
SDL_GPUTexture* acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf,
|
||||||
Uint32* out_w, Uint32* out_h);
|
Uint32* out_w,
|
||||||
void submit(SDL_GPUCommandBuffer* cmd_buf);
|
Uint32* out_h);
|
||||||
|
static void submit(SDL_GPUCommandBuffer* cmd_buf);
|
||||||
|
|
||||||
// VSync control (call after init)
|
// VSync control (call after init)
|
||||||
bool setVSync(bool enabled);
|
bool setVSync(bool enabled);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_GPUDevice* device_ = nullptr;
|
SDL_GPUDevice* device_ = nullptr;
|
||||||
SDL_Window* window_ = nullptr;
|
SDL_Window* window_ = nullptr;
|
||||||
SDL_GPUTextureFormat swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID;
|
SDL_GPUTextureFormat swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
#include "gpu_pipeline.hpp"
|
#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 <SDL3/SDL_log.h>
|
||||||
|
|
||||||
|
#include <array> // for std::array
|
||||||
#include <cstddef> // offsetof
|
#include <cstddef> // offsetof
|
||||||
#include <cstring> // strlen
|
#include <cstring> // strlen
|
||||||
|
|
||||||
|
#include "gpu_ball_buffer.hpp" // for BallGPUData layout
|
||||||
|
#include "gpu_sprite_batch.hpp" // for GpuVertex layout
|
||||||
|
|
||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
// Generated at build time by CMake + glslc (see cmake/spv_to_header.cmake)
|
// 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 "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
|
#endif
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
@@ -204,9 +207,9 @@ vertex BallVOut ball_instanced_vs(BallInstance inst [[stage_in]],
|
|||||||
// GpuPipeline implementation
|
// GpuPipeline implementation
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
bool GpuPipeline::init(SDL_GPUDevice* device,
|
auto GpuPipeline::init(SDL_GPUDevice* device,
|
||||||
SDL_GPUTextureFormat target_format,
|
SDL_GPUTextureFormat target_format,
|
||||||
SDL_GPUTextureFormat offscreen_format) {
|
SDL_GPUTextureFormat offscreen_format) -> bool {
|
||||||
SDL_GPUShaderFormat supported = SDL_GetGPUShaderFormats(device);
|
SDL_GPUShaderFormat supported = SDL_GetGPUShaderFormats(device);
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
if (!(supported & SDL_GPU_SHADERFORMAT_MSL)) {
|
if (!(supported & SDL_GPU_SHADERFORMAT_MSL)) {
|
||||||
@@ -214,7 +217,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (!(supported & SDL_GPU_SHADERFORMAT_SPIRV)) {
|
if ((supported & SDL_GPU_SHADERFORMAT_SPIRV) == 0u) {
|
||||||
SDL_Log("GpuPipeline: SPIRV not supported (format mask=%u)", supported);
|
SDL_Log("GpuPipeline: SPIRV not supported (format mask=%u)", supported);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -224,20 +227,20 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
// Sprite pipeline
|
// Sprite pipeline
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
SDL_GPUShader* sprite_vert = createShader(device, kSpriteVertMSL, "sprite_vs",
|
SDL_GPUShader* sprite_vert = createShader(device, kSpriteVertMSL, "sprite_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||||
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
SDL_GPUShader* sprite_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||||
SDL_GPUShader* sprite_frag = createShader(device, kSpriteFragMSL, "sprite_fs",
|
|
||||||
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
|
||||||
#else
|
#else
|
||||||
SDL_GPUShader* sprite_vert = createShaderSPIRV(device, ksprite_vert_spv, ksprite_vert_spv_size,
|
SDL_GPUShader* sprite_vert = createShaderSPIRV(device, ksprite_vert_spv, ksprite_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||||
"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_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size,
|
|
||||||
"main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
|
||||||
#endif
|
#endif
|
||||||
if (!sprite_vert || !sprite_frag) {
|
if ((sprite_vert == nullptr) || (sprite_frag == nullptr)) {
|
||||||
SDL_Log("GpuPipeline: failed to create sprite shaders");
|
SDL_Log("GpuPipeline: failed to create sprite shaders");
|
||||||
if (sprite_vert) SDL_ReleaseGPUShader(device, sprite_vert);
|
if (sprite_vert != nullptr) {
|
||||||
if (sprite_frag) SDL_ReleaseGPUShader(device, sprite_frag);
|
SDL_ReleaseGPUShader(device, sprite_vert);
|
||||||
|
}
|
||||||
|
if (sprite_frag != nullptr) {
|
||||||
|
SDL_ReleaseGPUShader(device, sprite_frag);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +251,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||||
vb_desc.instance_step_rate = 0;
|
vb_desc.instance_step_rate = 0;
|
||||||
|
|
||||||
SDL_GPUVertexAttribute attrs[3] = {};
|
std::array<SDL_GPUVertexAttribute, 3> attrs = {};
|
||||||
attrs[0].location = 0;
|
attrs[0].location = 0;
|
||||||
attrs[0].buffer_slot = 0;
|
attrs[0].buffer_slot = 0;
|
||||||
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||||
@@ -267,7 +270,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
SDL_GPUVertexInputState vertex_input = {};
|
SDL_GPUVertexInputState vertex_input = {};
|
||||||
vertex_input.vertex_buffer_descriptions = &vb_desc;
|
vertex_input.vertex_buffer_descriptions = &vb_desc;
|
||||||
vertex_input.num_vertex_buffers = 1;
|
vertex_input.num_vertex_buffers = 1;
|
||||||
vertex_input.vertex_attributes = attrs;
|
vertex_input.vertex_attributes = attrs.data();
|
||||||
vertex_input.num_vertex_attributes = 3;
|
vertex_input.num_vertex_attributes = 3;
|
||||||
|
|
||||||
// Alpha blend state (SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
|
// Alpha blend state (SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
|
||||||
@@ -298,7 +301,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
SDL_ReleaseGPUShader(device, sprite_vert);
|
SDL_ReleaseGPUShader(device, sprite_vert);
|
||||||
SDL_ReleaseGPUShader(device, sprite_frag);
|
SDL_ReleaseGPUShader(device, sprite_frag);
|
||||||
|
|
||||||
if (!sprite_pipeline_) {
|
if (sprite_pipeline_ == nullptr) {
|
||||||
SDL_Log("GpuPipeline: sprite pipeline creation failed: %s", SDL_GetError());
|
SDL_Log("GpuPipeline: sprite pipeline creation failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -310,20 +313,20 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
// Targets: offscreen (same as sprite pipeline)
|
// Targets: offscreen (same as sprite pipeline)
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
SDL_GPUShader* ball_vert = createShader(device, kBallInstancedVertMSL, "ball_instanced_vs",
|
SDL_GPUShader* ball_vert = createShader(device, kBallInstancedVertMSL, "ball_instanced_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||||
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
SDL_GPUShader* ball_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||||
SDL_GPUShader* ball_frag = createShader(device, kSpriteFragMSL, "sprite_fs",
|
|
||||||
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
|
||||||
#else
|
#else
|
||||||
SDL_GPUShader* ball_vert = createShaderSPIRV(device, kball_vert_spv, kball_vert_spv_size,
|
SDL_GPUShader* ball_vert = createShaderSPIRV(device, kball_vert_spv, kball_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||||
"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_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size,
|
|
||||||
"main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
|
||||||
#endif
|
#endif
|
||||||
if (!ball_vert || !ball_frag) {
|
if ((ball_vert == nullptr) || (ball_frag == nullptr)) {
|
||||||
SDL_Log("GpuPipeline: failed to create ball instanced shaders");
|
SDL_Log("GpuPipeline: failed to create ball instanced shaders");
|
||||||
if (ball_vert) SDL_ReleaseGPUShader(device, ball_vert);
|
if (ball_vert != nullptr) {
|
||||||
if (ball_frag) SDL_ReleaseGPUShader(device, ball_frag);
|
SDL_ReleaseGPUShader(device, ball_vert);
|
||||||
|
}
|
||||||
|
if (ball_frag != nullptr) {
|
||||||
|
SDL_ReleaseGPUShader(device, ball_frag);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +337,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
ball_vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE;
|
ball_vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE;
|
||||||
ball_vb_desc.instance_step_rate = 1;
|
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
|
// 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].buffer_slot = 0;
|
||||||
@@ -354,7 +357,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
SDL_GPUVertexInputState ball_vertex_input = {};
|
SDL_GPUVertexInputState ball_vertex_input = {};
|
||||||
ball_vertex_input.vertex_buffer_descriptions = &ball_vb_desc;
|
ball_vertex_input.vertex_buffer_descriptions = &ball_vb_desc;
|
||||||
ball_vertex_input.num_vertex_buffers = 1;
|
ball_vertex_input.num_vertex_buffers = 1;
|
||||||
ball_vertex_input.vertex_attributes = ball_attrs;
|
ball_vertex_input.vertex_attributes = ball_attrs.data();
|
||||||
ball_vertex_input.num_vertex_attributes = 3;
|
ball_vertex_input.num_vertex_attributes = 3;
|
||||||
|
|
||||||
SDL_GPUGraphicsPipelineCreateInfo ball_pipe_info = {};
|
SDL_GPUGraphicsPipelineCreateInfo ball_pipe_info = {};
|
||||||
@@ -370,7 +373,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
SDL_ReleaseGPUShader(device, ball_vert);
|
SDL_ReleaseGPUShader(device, ball_vert);
|
||||||
SDL_ReleaseGPUShader(device, ball_frag);
|
SDL_ReleaseGPUShader(device, ball_frag);
|
||||||
|
|
||||||
if (!ball_pipeline_) {
|
if (ball_pipeline_ == nullptr) {
|
||||||
SDL_Log("GpuPipeline: ball instanced pipeline creation failed: %s", SDL_GetError());
|
SDL_Log("GpuPipeline: ball instanced pipeline creation failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -389,20 +392,20 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
// PostFX pipeline
|
// PostFX pipeline
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs",
|
SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||||
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||||
SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs",
|
|
||||||
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
|
||||||
#else
|
#else
|
||||||
SDL_GPUShader* postfx_vert = createShaderSPIRV(device, kpostfx_vert_spv, kpostfx_vert_spv_size,
|
SDL_GPUShader* postfx_vert = createShaderSPIRV(device, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||||
"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_frag = createShaderSPIRV(device, kpostfx_frag_spv, kpostfx_frag_spv_size,
|
|
||||||
"main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
|
||||||
#endif
|
#endif
|
||||||
if (!postfx_vert || !postfx_frag) {
|
if ((postfx_vert == nullptr) || (postfx_frag == nullptr)) {
|
||||||
SDL_Log("GpuPipeline: failed to create postfx shaders");
|
SDL_Log("GpuPipeline: failed to create postfx shaders");
|
||||||
if (postfx_vert) SDL_ReleaseGPUShader(device, postfx_vert);
|
if (postfx_vert != nullptr) {
|
||||||
if (postfx_frag) SDL_ReleaseGPUShader(device, postfx_frag);
|
SDL_ReleaseGPUShader(device, postfx_vert);
|
||||||
|
}
|
||||||
|
if (postfx_frag != nullptr) {
|
||||||
|
SDL_ReleaseGPUShader(device, postfx_frag);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +433,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
SDL_ReleaseGPUShader(device, postfx_vert);
|
SDL_ReleaseGPUShader(device, postfx_vert);
|
||||||
SDL_ReleaseGPUShader(device, postfx_frag);
|
SDL_ReleaseGPUShader(device, postfx_frag);
|
||||||
|
|
||||||
if (!postfx_pipeline_) {
|
if (postfx_pipeline_ == nullptr) {
|
||||||
SDL_Log("GpuPipeline: postfx pipeline creation failed: %s", SDL_GetError());
|
SDL_Log("GpuPipeline: postfx pipeline creation failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -440,19 +443,28 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GpuPipeline::destroy(SDL_GPUDevice* device) {
|
void GpuPipeline::destroy(SDL_GPUDevice* device) {
|
||||||
if (sprite_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, sprite_pipeline_); sprite_pipeline_ = nullptr; }
|
if (sprite_pipeline_ != nullptr) {
|
||||||
if (ball_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, ball_pipeline_); ball_pipeline_ = nullptr; }
|
SDL_ReleaseGPUGraphicsPipeline(device, sprite_pipeline_);
|
||||||
if (postfx_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, postfx_pipeline_); postfx_pipeline_ = nullptr; }
|
sprite_pipeline_ = nullptr;
|
||||||
|
}
|
||||||
|
if (ball_pipeline_ != nullptr) {
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(device, ball_pipeline_);
|
||||||
|
ball_pipeline_ = nullptr;
|
||||||
|
}
|
||||||
|
if (postfx_pipeline_ != nullptr) {
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(device, postfx_pipeline_);
|
||||||
|
postfx_pipeline_ = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_GPUShader* GpuPipeline::createShaderSPIRV(SDL_GPUDevice* device,
|
auto GpuPipeline::createShaderSPIRV(SDL_GPUDevice* device,
|
||||||
const uint8_t* spv_code,
|
const uint8_t* spv_code,
|
||||||
size_t spv_size,
|
size_t spv_size,
|
||||||
const char* entrypoint,
|
const char* entrypoint,
|
||||||
SDL_GPUShaderStage stage,
|
SDL_GPUShaderStage stage,
|
||||||
Uint32 num_samplers,
|
Uint32 num_samplers,
|
||||||
Uint32 num_uniform_buffers,
|
Uint32 num_uniform_buffers,
|
||||||
Uint32 num_storage_buffers) {
|
Uint32 num_storage_buffers) -> SDL_GPUShader* {
|
||||||
SDL_GPUShaderCreateInfo info = {};
|
SDL_GPUShaderCreateInfo info = {};
|
||||||
info.code = spv_code;
|
info.code = spv_code;
|
||||||
info.code_size = spv_size;
|
info.code_size = spv_size;
|
||||||
@@ -464,18 +476,19 @@ SDL_GPUShader* GpuPipeline::createShaderSPIRV(SDL_GPUDevice* device,
|
|||||||
info.num_storage_buffers = num_storage_buffers;
|
info.num_storage_buffers = num_storage_buffers;
|
||||||
info.num_uniform_buffers = num_uniform_buffers;
|
info.num_uniform_buffers = num_uniform_buffers;
|
||||||
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
|
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
|
||||||
if (!shader)
|
if (shader == nullptr) {
|
||||||
SDL_Log("GpuPipeline: SPIRV shader '%s' failed: %s", entrypoint, SDL_GetError());
|
SDL_Log("GpuPipeline: SPIRV shader '%s' failed: %s", entrypoint, SDL_GetError());
|
||||||
|
}
|
||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_GPUShader* GpuPipeline::createShader(SDL_GPUDevice* device,
|
auto GpuPipeline::createShader(SDL_GPUDevice* device,
|
||||||
const char* msl_source,
|
const char* msl_source,
|
||||||
const char* entrypoint,
|
const char* entrypoint,
|
||||||
SDL_GPUShaderStage stage,
|
SDL_GPUShaderStage stage,
|
||||||
Uint32 num_samplers,
|
Uint32 num_samplers,
|
||||||
Uint32 num_uniform_buffers,
|
Uint32 num_uniform_buffers,
|
||||||
Uint32 num_storage_buffers) {
|
Uint32 num_storage_buffers) -> SDL_GPUShader* {
|
||||||
SDL_GPUShaderCreateInfo info = {};
|
SDL_GPUShaderCreateInfo info = {};
|
||||||
info.code = reinterpret_cast<const Uint8*>(msl_source);
|
info.code = reinterpret_cast<const Uint8*>(msl_source);
|
||||||
info.code_size = static_cast<size_t>(strlen(msl_source) + 1);
|
info.code_size = static_cast<size_t>(strlen(msl_source) + 1);
|
||||||
@@ -488,7 +501,7 @@ SDL_GPUShader* GpuPipeline::createShader(SDL_GPUDevice* device,
|
|||||||
info.num_uniform_buffers = num_uniform_buffers;
|
info.num_uniform_buffers = num_uniform_buffers;
|
||||||
|
|
||||||
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
|
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
|
||||||
if (!shader) {
|
if (shader == nullptr) {
|
||||||
SDL_Log("GpuPipeline: shader '%s' failed: %s", entrypoint, SDL_GetError());
|
SDL_Log("GpuPipeline: shader '%s' failed: %s", entrypoint, SDL_GetError());
|
||||||
}
|
}
|
||||||
return shader;
|
return shader;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ struct PostFXUniforms {
|
|||||||
// Accepts PostFXUniforms via fragment uniform buffer slot 0.
|
// Accepts PostFXUniforms via fragment uniform buffer slot 0.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class GpuPipeline {
|
class GpuPipeline {
|
||||||
public:
|
public:
|
||||||
// target_format: pass SDL_GetGPUSwapchainTextureFormat() result.
|
// target_format: pass SDL_GetGPUSwapchainTextureFormat() result.
|
||||||
// offscreen_format: format of the offscreen render target.
|
// offscreen_format: format of the offscreen render target.
|
||||||
bool init(SDL_GPUDevice* device,
|
bool init(SDL_GPUDevice* device,
|
||||||
@@ -39,8 +39,8 @@ public:
|
|||||||
SDL_GPUGraphicsPipeline* ballPipeline() const { return ball_pipeline_; }
|
SDL_GPUGraphicsPipeline* ballPipeline() const { return ball_pipeline_; }
|
||||||
SDL_GPUGraphicsPipeline* postfxPipeline() const { return postfx_pipeline_; }
|
SDL_GPUGraphicsPipeline* postfxPipeline() const { return postfx_pipeline_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_GPUShader* createShader(SDL_GPUDevice* device,
|
static SDL_GPUShader* createShader(SDL_GPUDevice* device,
|
||||||
const char* msl_source,
|
const char* msl_source,
|
||||||
const char* entrypoint,
|
const char* entrypoint,
|
||||||
SDL_GPUShaderStage stage,
|
SDL_GPUShaderStage stage,
|
||||||
@@ -48,7 +48,7 @@ private:
|
|||||||
Uint32 num_uniform_buffers,
|
Uint32 num_uniform_buffers,
|
||||||
Uint32 num_storage_buffers = 0);
|
Uint32 num_storage_buffers = 0);
|
||||||
|
|
||||||
SDL_GPUShader* createShaderSPIRV(SDL_GPUDevice* device,
|
static SDL_GPUShader* createShaderSPIRV(SDL_GPUDevice* device,
|
||||||
const uint8_t* spv_code,
|
const uint8_t* spv_code,
|
||||||
size_t spv_size,
|
size_t spv_size,
|
||||||
const char* entrypoint,
|
const char* entrypoint,
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#include "gpu_sprite_batch.hpp"
|
#include "gpu_sprite_batch.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL_log.h>
|
#include <SDL3/SDL_log.h>
|
||||||
|
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Public interface
|
// 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;
|
max_sprites_ = max_sprites;
|
||||||
// Pre-allocate GPU buffers large enough for (max_sprites_ + 2) quads.
|
// 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.
|
// The +2 reserves one slot for the background quad and one for the fullscreen overlay.
|
||||||
@@ -22,7 +23,7 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
|||||||
vb_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
vb_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||||
vb_info.size = vb_size;
|
vb_info.size = vb_size;
|
||||||
vertex_buf_ = SDL_CreateGPUBuffer(device, &vb_info);
|
vertex_buf_ = SDL_CreateGPUBuffer(device, &vb_info);
|
||||||
if (!vertex_buf_) {
|
if (vertex_buf_ == nullptr) {
|
||||||
SDL_Log("GpuSpriteBatch: vertex buffer creation failed: %s", SDL_GetError());
|
SDL_Log("GpuSpriteBatch: vertex buffer creation failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -32,7 +33,7 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
|||||||
ib_info.usage = SDL_GPU_BUFFERUSAGE_INDEX;
|
ib_info.usage = SDL_GPU_BUFFERUSAGE_INDEX;
|
||||||
ib_info.size = ib_size;
|
ib_info.size = ib_size;
|
||||||
index_buf_ = SDL_CreateGPUBuffer(device, &ib_info);
|
index_buf_ = SDL_CreateGPUBuffer(device, &ib_info);
|
||||||
if (!index_buf_) {
|
if (index_buf_ == nullptr) {
|
||||||
SDL_Log("GpuSpriteBatch: index buffer creation failed: %s", SDL_GetError());
|
SDL_Log("GpuSpriteBatch: index buffer creation failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -43,14 +44,14 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
|||||||
|
|
||||||
tb_info.size = vb_size;
|
tb_info.size = vb_size;
|
||||||
vertex_transfer_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
vertex_transfer_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||||
if (!vertex_transfer_) {
|
if (vertex_transfer_ == nullptr) {
|
||||||
SDL_Log("GpuSpriteBatch: vertex transfer buffer failed: %s", SDL_GetError());
|
SDL_Log("GpuSpriteBatch: vertex transfer buffer failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
tb_info.size = ib_size;
|
tb_info.size = ib_size;
|
||||||
index_transfer_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
index_transfer_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||||
if (!index_transfer_) {
|
if (index_transfer_ == nullptr) {
|
||||||
SDL_Log("GpuSpriteBatch: index transfer buffer failed: %s", SDL_GetError());
|
SDL_Log("GpuSpriteBatch: index transfer buffer failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -61,11 +62,25 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GpuSpriteBatch::destroy(SDL_GPUDevice* device) {
|
void GpuSpriteBatch::destroy(SDL_GPUDevice* device) {
|
||||||
if (!device) return;
|
if (device == nullptr) {
|
||||||
if (vertex_transfer_) { SDL_ReleaseGPUTransferBuffer(device, vertex_transfer_); vertex_transfer_ = nullptr; }
|
return;
|
||||||
if (index_transfer_) { SDL_ReleaseGPUTransferBuffer(device, index_transfer_); index_transfer_ = nullptr; }
|
}
|
||||||
if (vertex_buf_) { SDL_ReleaseGPUBuffer(device, vertex_buf_); vertex_buf_ = nullptr; }
|
if (vertex_transfer_ != nullptr) {
|
||||||
if (index_buf_) { SDL_ReleaseGPUBuffer(device, index_buf_); index_buf_ = 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() {
|
void GpuSpriteBatch::beginFrame() {
|
||||||
@@ -78,38 +93,38 @@ void GpuSpriteBatch::beginFrame() {
|
|||||||
overlay_index_count_ = 0;
|
overlay_index_count_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GpuSpriteBatch::addBackground(float screen_w, float screen_h,
|
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) {
|
||||||
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:
|
// Background is the full screen quad, corners:
|
||||||
// TL(-1, 1) TR(1, 1) → top color
|
// TL(-1, 1) TR(1, 1) → top color
|
||||||
// BL(-1,-1) BR(1,-1) → bottom color
|
// BL(-1,-1) BR(1,-1) → bottom color
|
||||||
// We push it as 4 separate vertices (different colors per row).
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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 + 0);
|
||||||
indices_.push_back(vi + 2); indices_.push_back(vi + 3); 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;
|
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,
|
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) {
|
||||||
float r, float g, float b, float a,
|
|
||||||
float scale,
|
|
||||||
float screen_w, float screen_h) {
|
|
||||||
// Apply scale around the sprite centre
|
// Apply scale around the sprite centre
|
||||||
float scaled_w = w * scale;
|
float scaled_w = w * scale;
|
||||||
float scaled_h = h * scale;
|
float scaled_h = h * scale;
|
||||||
@@ -121,7 +136,10 @@ void GpuSpriteBatch::addSprite(float x, float y, float w, float h,
|
|||||||
float px1 = px0 + scaled_w;
|
float px1 = px0 + scaled_w;
|
||||||
float py1 = py0 + scaled_h;
|
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(px0, py0, screen_w, screen_h, ndx0, ndy0);
|
||||||
toNDC(px1, py1, screen_w, screen_h, ndx1, ndy1);
|
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.
|
// 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().
|
// Escribe directamente sin pasar por el guard de pushQuad().
|
||||||
overlay_index_offset_ = static_cast<int>(indices_.size());
|
overlay_index_offset_ = static_cast<int>(indices_.size());
|
||||||
uint32_t vi = static_cast<uint32_t>(vertices_.size());
|
auto vi = static_cast<uint32_t>(vertices_.size());
|
||||||
vertices_.push_back({ -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f });
|
vertices_.push_back({-1.0f, 1.0f, 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, 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, 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 });
|
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 + 0);
|
||||||
indices_.push_back(vi + 2); indices_.push_back(vi + 3); 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;
|
overlay_index_count_ = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf) {
|
auto GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf) -> bool {
|
||||||
if (vertices_.empty()) return false;
|
if (vertices_.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Uint32 vb_size = static_cast<Uint32>(vertices_.size() * sizeof(GpuVertex));
|
auto vb_size = static_cast<Uint32>(vertices_.size() * sizeof(GpuVertex));
|
||||||
Uint32 ib_size = static_cast<Uint32>(indices_.size() * sizeof(uint32_t));
|
auto ib_size = static_cast<Uint32>(indices_.size() * sizeof(uint32_t));
|
||||||
|
|
||||||
// Map → write → unmap transfer buffers
|
// Map → write → unmap transfer buffers
|
||||||
void* vp = SDL_MapGPUTransferBuffer(device, vertex_transfer_, true /* cycle */);
|
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);
|
memcpy(vp, vertices_.data(), vb_size);
|
||||||
SDL_UnmapGPUTransferBuffer(device, vertex_transfer_);
|
SDL_UnmapGPUTransferBuffer(device, vertex_transfer_);
|
||||||
|
|
||||||
void* ip = SDL_MapGPUTransferBuffer(device, index_transfer_, true /* cycle */);
|
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);
|
memcpy(ip, indices_.data(), ib_size);
|
||||||
SDL_UnmapGPUTransferBuffer(device, index_transfer_);
|
SDL_UnmapGPUTransferBuffer(device, index_transfer_);
|
||||||
|
|
||||||
// Upload via copy pass
|
// Upload via copy pass
|
||||||
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd_buf);
|
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd_buf);
|
||||||
|
|
||||||
SDL_GPUTransferBufferLocation v_src = { vertex_transfer_, 0 };
|
SDL_GPUTransferBufferLocation v_src = {vertex_transfer_, 0};
|
||||||
SDL_GPUBufferRegion v_dst = { vertex_buf_, 0, vb_size };
|
SDL_GPUBufferRegion v_dst = {vertex_buf_, 0, vb_size};
|
||||||
SDL_UploadToGPUBuffer(copy, &v_src, &v_dst, true /* cycle */);
|
SDL_UploadToGPUBuffer(copy, &v_src, &v_dst, true /* cycle */);
|
||||||
|
|
||||||
SDL_GPUTransferBufferLocation i_src = { index_transfer_, 0 };
|
SDL_GPUTransferBufferLocation i_src = {index_transfer_, 0};
|
||||||
SDL_GPUBufferRegion i_dst = { index_buf_, 0, ib_size };
|
SDL_GPUBufferRegion i_dst = {index_buf_, 0, ib_size};
|
||||||
SDL_UploadToGPUBuffer(copy, &i_src, &i_dst, true /* cycle */);
|
SDL_UploadToGPUBuffer(copy, &i_src, &i_dst, true /* cycle */);
|
||||||
|
|
||||||
SDL_EndGPUCopyPass(copy);
|
SDL_EndGPUCopyPass(copy);
|
||||||
@@ -179,26 +209,28 @@ bool GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cm
|
|||||||
// Private helpers
|
// Private helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
void GpuSpriteBatch::toNDC(float px, float py,
|
void GpuSpriteBatch::toNDC(float px, float py, float screen_w, float screen_h, float& ndx, float& ndy) {
|
||||||
float screen_w, float screen_h,
|
|
||||||
float& ndx, float& ndy) const {
|
|
||||||
ndx = (px / screen_w) * 2.0f - 1.0f;
|
ndx = (px / screen_w) * 2.0f - 1.0f;
|
||||||
ndy = 1.0f - (py / screen_h) * 2.0f;
|
ndy = 1.0f - (py / screen_h) * 2.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1,
|
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) {
|
||||||
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.
|
// +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;
|
if (vertices_.size() + 4 > static_cast<size_t>(max_sprites_ + 1) * 4) {
|
||||||
uint32_t vi = static_cast<uint32_t>(vertices_.size());
|
return;
|
||||||
|
}
|
||||||
|
auto vi = static_cast<uint32_t>(vertices_.size());
|
||||||
|
|
||||||
// TL, TR, BR, BL
|
// TL, TR, BR, BL
|
||||||
vertices_.push_back({ ndx0, ndy0, u0, v0, 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, ndy0, u1, v0, r, g, b, a});
|
||||||
vertices_.push_back({ ndx1, ndy1, u1, v1, 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, 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 + 0);
|
||||||
indices_.push_back(vi + 2); indices_.push_back(vi + 3); indices_.push_back(vi + 0);
|
indices_.push_back(vi + 1);
|
||||||
|
indices_.push_back(vi + 2);
|
||||||
|
indices_.push_back(vi + 2);
|
||||||
|
indices_.push_back(vi + 3);
|
||||||
|
indices_.push_back(vi + 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SDL3/SDL_gpu.h>
|
#include <SDL3/SDL_gpu.h>
|
||||||
#include <vector>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// GpuVertex — 8-float vertex layout sent to the GPU.
|
// GpuVertex — 8-float vertex layout sent to the GPU.
|
||||||
@@ -25,7 +26,7 @@ struct GpuVertex {
|
|||||||
// // Then in render pass: bind buffers, draw bg with white tex, draw sprites.
|
// // Then in render pass: bind buffers, draw bg with white tex, draw sprites.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class GpuSpriteBatch {
|
class GpuSpriteBatch {
|
||||||
public:
|
public:
|
||||||
// Default maximum sprites (background + UI overlay each count as one sprite)
|
// Default maximum sprites (background + UI overlay each count as one sprite)
|
||||||
static constexpr int DEFAULT_MAX_SPRITES = 200000;
|
static constexpr int DEFAULT_MAX_SPRITES = 200000;
|
||||||
|
|
||||||
@@ -36,16 +37,11 @@ public:
|
|||||||
|
|
||||||
// Add the full-screen background gradient quad.
|
// Add the full-screen background gradient quad.
|
||||||
// top_* and bot_* are RGB in [0,1].
|
// top_* and bot_* are RGB in [0,1].
|
||||||
void addBackground(float screen_w, float screen_h,
|
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);
|
||||||
float top_r, float top_g, float top_b,
|
|
||||||
float bot_r, float bot_g, float bot_b);
|
|
||||||
|
|
||||||
// Add a sprite quad (pixel coordinates).
|
// Add a sprite quad (pixel coordinates).
|
||||||
// scale: uniform scale around the quad centre.
|
// scale: uniform scale around the quad centre.
|
||||||
void addSprite(float x, float y, float w, float h,
|
void addSprite(float x, float y, float w, float h, float r, float g, float b, float a, float scale, float screen_w, float screen_h);
|
||||||
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).
|
// Add a full-screen overlay quad (e.g. UI surface, NDC −1..1).
|
||||||
void addFullscreenOverlay();
|
void addFullscreenOverlay();
|
||||||
@@ -63,12 +59,9 @@ public:
|
|||||||
int spriteIndexCount() const { return sprite_index_count_; }
|
int spriteIndexCount() const { return sprite_index_count_; }
|
||||||
bool isEmpty() const { return vertices_.empty(); }
|
bool isEmpty() const { return vertices_.empty(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void toNDC(float px, float py, float screen_w, float screen_h,
|
static void toNDC(float px, float py, float screen_w, float screen_h, float& ndx, float& ndy);
|
||||||
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);
|
||||||
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<GpuVertex> vertices_;
|
||||||
std::vector<uint32_t> indices_;
|
std::vector<uint32_t> indices_;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL_log.h>
|
#include <SDL3/SDL_log.h>
|
||||||
#include <SDL3/SDL_pixels.h>
|
#include <SDL3/SDL_pixels.h>
|
||||||
|
|
||||||
|
#include <array> // for std::array
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@
|
|||||||
// Public interface
|
// 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;
|
unsigned char* resource_data = nullptr;
|
||||||
size_t resource_size = 0;
|
size_t resource_size = 0;
|
||||||
|
|
||||||
@@ -22,15 +24,22 @@ bool GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) {
|
|||||||
return false;
|
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(
|
unsigned char* pixels = stbi_load_from_memory(
|
||||||
resource_data, static_cast<int>(resource_size),
|
resource_data,
|
||||||
&w, &h, &orig, STBI_rgb_alpha);
|
static_cast<int>(resource_size),
|
||||||
|
&w,
|
||||||
|
&h,
|
||||||
|
&orig,
|
||||||
|
STBI_rgb_alpha);
|
||||||
delete[] resource_data;
|
delete[] resource_data;
|
||||||
|
|
||||||
if (!pixels) {
|
if (pixels == nullptr) {
|
||||||
SDL_Log("GpuTexture: stbi decode failed for '%s': %s",
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,15 +53,17 @@ bool GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) {
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest) {
|
auto GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest) -> bool {
|
||||||
if (!surface) return false;
|
if (surface == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure RGBA32 format
|
// Ensure RGBA32 format
|
||||||
SDL_Surface* rgba = surface;
|
SDL_Surface* rgba = surface;
|
||||||
bool need_free = false;
|
bool need_free = false;
|
||||||
if (surface->format != SDL_PIXELFORMAT_RGBA32) {
|
if (surface->format != SDL_PIXELFORMAT_RGBA32) {
|
||||||
rgba = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
|
rgba = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
|
||||||
if (!rgba) {
|
if (rgba == nullptr) {
|
||||||
SDL_Log("GpuTexture: SDL_ConvertSurface failed: %s", SDL_GetError());
|
SDL_Log("GpuTexture: SDL_ConvertSurface failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -60,23 +71,24 @@ bool GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool n
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy(device);
|
destroy(device);
|
||||||
bool ok = uploadPixels(device, rgba->pixels, rgba->w, rgba->h,
|
bool ok = uploadPixels(device, rgba->pixels, rgba->w, rgba->h, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
if (ok) {
|
||||||
if (ok) ok = createSampler(device, nearest);
|
ok = createSampler(device, nearest);
|
||||||
|
}
|
||||||
|
|
||||||
if (need_free) SDL_DestroySurface(rgba);
|
if (need_free) {
|
||||||
|
SDL_DestroySurface(rgba);
|
||||||
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h,
|
auto GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h, SDL_GPUTextureFormat format) -> bool {
|
||||||
SDL_GPUTextureFormat format) {
|
|
||||||
destroy(device);
|
destroy(device);
|
||||||
|
|
||||||
SDL_GPUTextureCreateInfo info = {};
|
SDL_GPUTextureCreateInfo info = {};
|
||||||
info.type = SDL_GPU_TEXTURETYPE_2D;
|
info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||||
info.format = format;
|
info.format = format;
|
||||||
info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET
|
info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||||
| SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
|
||||||
info.width = static_cast<Uint32>(w);
|
info.width = static_cast<Uint32>(w);
|
||||||
info.height = static_cast<Uint32>(h);
|
info.height = static_cast<Uint32>(h);
|
||||||
info.layer_count_or_depth = 1;
|
info.layer_count_or_depth = 1;
|
||||||
@@ -84,7 +96,7 @@ bool GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h,
|
|||||||
info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||||
|
|
||||||
texture_ = SDL_CreateGPUTexture(device, &info);
|
texture_ = SDL_CreateGPUTexture(device, &info);
|
||||||
if (!texture_) {
|
if (texture_ == nullptr) {
|
||||||
SDL_Log("GpuTexture: createRenderTarget failed: %s", SDL_GetError());
|
SDL_Log("GpuTexture: createRenderTarget failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -95,20 +107,29 @@ bool GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h,
|
|||||||
return createSampler(device, false);
|
return createSampler(device, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GpuTexture::createWhite(SDL_GPUDevice* device) {
|
auto GpuTexture::createWhite(SDL_GPUDevice* device) -> bool {
|
||||||
destroy(device);
|
destroy(device);
|
||||||
// 1×1 white RGBA pixel
|
// 1×1 white RGBA pixel
|
||||||
const Uint8 white[4] = {255, 255, 255, 255};
|
constexpr std::array<Uint8, 4> WHITE = {255, 255, 255, 255};
|
||||||
bool ok = uploadPixels(device, white, 1, 1,
|
bool ok = uploadPixels(device, WHITE.data(), 1, 1, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
if (ok) {
|
||||||
if (ok) ok = createSampler(device, true);
|
ok = createSampler(device, true);
|
||||||
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GpuTexture::destroy(SDL_GPUDevice* device) {
|
void GpuTexture::destroy(SDL_GPUDevice* device) {
|
||||||
if (!device) return;
|
if (device == nullptr) {
|
||||||
if (sampler_) { SDL_ReleaseGPUSampler(device, sampler_); sampler_ = nullptr; }
|
return;
|
||||||
if (texture_) { SDL_ReleaseGPUTexture(device, texture_); texture_ = nullptr; }
|
}
|
||||||
|
if (sampler_ != nullptr) {
|
||||||
|
SDL_ReleaseGPUSampler(device, sampler_);
|
||||||
|
sampler_ = nullptr;
|
||||||
|
}
|
||||||
|
if (texture_ != nullptr) {
|
||||||
|
SDL_ReleaseGPUTexture(device, texture_);
|
||||||
|
texture_ = nullptr;
|
||||||
|
}
|
||||||
width_ = height_ = 0;
|
width_ = height_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +137,7 @@ void GpuTexture::destroy(SDL_GPUDevice* device) {
|
|||||||
// Private helpers
|
// Private helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
auto GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels, int w, int h, SDL_GPUTextureFormat format) -> bool {
|
||||||
int w, int h, SDL_GPUTextureFormat format) {
|
|
||||||
// Create GPU texture
|
// Create GPU texture
|
||||||
SDL_GPUTextureCreateInfo tex_info = {};
|
SDL_GPUTextureCreateInfo tex_info = {};
|
||||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||||
@@ -130,20 +150,20 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
|||||||
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||||
|
|
||||||
texture_ = SDL_CreateGPUTexture(device, &tex_info);
|
texture_ = SDL_CreateGPUTexture(device, &tex_info);
|
||||||
if (!texture_) {
|
if (texture_ == nullptr) {
|
||||||
SDL_Log("GpuTexture: SDL_CreateGPUTexture failed: %s", SDL_GetError());
|
SDL_Log("GpuTexture: SDL_CreateGPUTexture failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create transfer buffer and upload pixels
|
// 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 = {};
|
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
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);
|
SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||||
if (!transfer) {
|
if (transfer == nullptr) {
|
||||||
SDL_Log("GpuTexture: transfer buffer creation failed: %s", SDL_GetError());
|
SDL_Log("GpuTexture: transfer buffer creation failed: %s", SDL_GetError());
|
||||||
SDL_ReleaseGPUTexture(device, texture_);
|
SDL_ReleaseGPUTexture(device, texture_);
|
||||||
texture_ = nullptr;
|
texture_ = nullptr;
|
||||||
@@ -151,7 +171,7 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void* mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
|
void* mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
|
||||||
if (!mapped) {
|
if (mapped == nullptr) {
|
||||||
SDL_Log("GpuTexture: map failed: %s", SDL_GetError());
|
SDL_Log("GpuTexture: map failed: %s", SDL_GetError());
|
||||||
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
||||||
SDL_ReleaseGPUTexture(device, texture_);
|
SDL_ReleaseGPUTexture(device, texture_);
|
||||||
@@ -190,7 +210,7 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) {
|
auto GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) -> bool {
|
||||||
SDL_GPUSamplerCreateInfo info = {};
|
SDL_GPUSamplerCreateInfo info = {};
|
||||||
info.min_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
|
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.mag_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
|
||||||
@@ -200,7 +220,7 @@ bool GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) {
|
|||||||
info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||||
|
|
||||||
sampler_ = SDL_CreateGPUSampler(device, &info);
|
sampler_ = SDL_CreateGPUSampler(device, &info);
|
||||||
if (!sampler_) {
|
if (sampler_ == nullptr) {
|
||||||
SDL_Log("GpuTexture: SDL_CreateGPUSampler failed: %s", SDL_GetError());
|
SDL_Log("GpuTexture: SDL_CreateGPUSampler failed: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL_gpu.h>
|
#include <SDL3/SDL_gpu.h>
|
||||||
#include <SDL3/SDL_surface.h>
|
#include <SDL3/SDL_surface.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -9,7 +10,7 @@
|
|||||||
// Handles sprite textures, render targets, and the 1×1 white utility texture.
|
// Handles sprite textures, render targets, and the 1×1 white utility texture.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class GpuTexture {
|
class GpuTexture {
|
||||||
public:
|
public:
|
||||||
GpuTexture() = default;
|
GpuTexture() = default;
|
||||||
~GpuTexture() = default;
|
~GpuTexture() = default;
|
||||||
|
|
||||||
@@ -21,8 +22,7 @@ public:
|
|||||||
bool fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest = true);
|
bool fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest = true);
|
||||||
|
|
||||||
// Create an offscreen render target (COLOR_TARGET | SAMPLER usage).
|
// Create an offscreen render target (COLOR_TARGET | SAMPLER usage).
|
||||||
bool createRenderTarget(SDL_GPUDevice* device, int w, int h,
|
bool createRenderTarget(SDL_GPUDevice* device, int w, int h, SDL_GPUTextureFormat format);
|
||||||
SDL_GPUTextureFormat format);
|
|
||||||
|
|
||||||
// Create a 1×1 opaque white texture (used for untextured geometry).
|
// Create a 1×1 opaque white texture (used for untextured geometry).
|
||||||
bool createWhite(SDL_GPUDevice* device);
|
bool createWhite(SDL_GPUDevice* device);
|
||||||
@@ -36,9 +36,8 @@ public:
|
|||||||
int height() const { return height_; }
|
int height() const { return height_; }
|
||||||
bool isValid() const { return texture_ != nullptr; }
|
bool isValid() const { return texture_ != nullptr; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
bool uploadPixels(SDL_GPUDevice* device, const void* pixels, int w, int h, SDL_GPUTextureFormat format);
|
||||||
int w, int h, SDL_GPUTextureFormat format);
|
|
||||||
bool createSampler(SDL_GPUDevice* device, bool nearest);
|
bool createSampler(SDL_GPUDevice* device, bool nearest);
|
||||||
|
|
||||||
SDL_GPUTexture* texture_ = nullptr;
|
SDL_GPUTexture* texture_ = nullptr;
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#include "input_handler.hpp"
|
#include "input_handler.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL_keycode.h> // for SDL_Keycode
|
#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 "defines.hpp" // for KIOSK_NOTIFICATION_TEXT
|
||||||
#include "engine.hpp" // for Engine
|
#include "engine.hpp" // for Engine
|
||||||
#include "external/mouse.hpp" // for Mouse namespace
|
#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;
|
SDL_Event event;
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
// Procesar eventos de ratón (auto-ocultar cursor)
|
// Procesar eventos de ratón (auto-ocultar cursor)
|
||||||
@@ -19,7 +20,7 @@ bool InputHandler::processEvents(Engine& engine) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Procesar eventos de teclado
|
// 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) {
|
switch (event.key.key) {
|
||||||
case SDLK_ESCAPE:
|
case SDLK_ESCAPE:
|
||||||
if (engine.isKioskMode()) {
|
if (engine.isKioskMode()) {
|
||||||
@@ -109,19 +110,17 @@ bool InputHandler::processEvents(Engine& engine) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
// Ciclar temas de color (movido de B a C)
|
// Ciclar temas de color (movido de B a C)
|
||||||
case SDLK_C:
|
case SDLK_C: {
|
||||||
{
|
|
||||||
// Detectar si Shift está presionado
|
// Detectar si Shift está presionado
|
||||||
SDL_Keymod modstate = SDL_GetModState();
|
SDL_Keymod modstate = SDL_GetModState();
|
||||||
if (modstate & SDL_KMOD_SHIFT) {
|
if ((modstate & SDL_KMOD_SHIFT) != 0u) {
|
||||||
// Shift+C: Ciclar hacia atrás (tema anterior)
|
// Shift+C: Ciclar hacia atrás (tema anterior)
|
||||||
engine.cycleTheme(false);
|
engine.cycleTheme(false);
|
||||||
} else {
|
} else {
|
||||||
// C solo: Ciclar hacia adelante (tema siguiente)
|
// C solo: Ciclar hacia adelante (tema siguiente)
|
||||||
engine.cycleTheme(true);
|
engine.cycleTheme(true);
|
||||||
}
|
}
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
// Temas de colores con teclado numérico (con transición suave)
|
// Temas de colores con teclado numérico (con transición suave)
|
||||||
case SDLK_KP_1:
|
case SDLK_KP_1:
|
||||||
@@ -233,25 +232,37 @@ bool InputHandler::processEvents(Engine& engine) {
|
|||||||
|
|
||||||
// Controles de zoom dinámico (solo si no estamos en fullscreen)
|
// Controles de zoom dinámico (solo si no estamos en fullscreen)
|
||||||
case SDLK_F1:
|
case SDLK_F1:
|
||||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
if (engine.isKioskMode()) {
|
||||||
else engine.handleZoomOut();
|
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||||
|
} else {
|
||||||
|
engine.handleZoomOut();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_F2:
|
case SDLK_F2:
|
||||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
if (engine.isKioskMode()) {
|
||||||
else engine.handleZoomIn();
|
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||||
|
} else {
|
||||||
|
engine.handleZoomIn();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Control de pantalla completa
|
// Control de pantalla completa
|
||||||
case SDLK_F3:
|
case SDLK_F3:
|
||||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
if (engine.isKioskMode()) {
|
||||||
else engine.toggleFullscreen();
|
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||||
|
} else {
|
||||||
|
engine.toggleFullscreen();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Modo real fullscreen (cambia resolución interna)
|
// Modo real fullscreen (cambia resolución interna)
|
||||||
case SDLK_F4:
|
case SDLK_F4:
|
||||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
if (engine.isKioskMode()) {
|
||||||
else engine.toggleRealFullscreen();
|
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||||
|
} else {
|
||||||
|
engine.toggleRealFullscreen();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Toggle PostFX activo/inactivo
|
// Toggle PostFX activo/inactivo
|
||||||
@@ -266,19 +277,25 @@ bool InputHandler::processEvents(Engine& engine) {
|
|||||||
|
|
||||||
// Redimensionar campo de juego (tamaño lógico + físico)
|
// Redimensionar campo de juego (tamaño lógico + físico)
|
||||||
case SDLK_F7:
|
case SDLK_F7:
|
||||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
if (engine.isKioskMode()) {
|
||||||
else engine.fieldSizeDown();
|
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||||
|
} else {
|
||||||
|
engine.fieldSizeDown();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_F8:
|
case SDLK_F8:
|
||||||
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
if (engine.isKioskMode()) {
|
||||||
else engine.fieldSizeUp();
|
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
|
||||||
|
} else {
|
||||||
|
engine.fieldSizeUp();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Toggle Modo DEMO COMPLETO (auto-play) o Pausar tema dinámico (Shift+D)
|
// Toggle Modo DEMO COMPLETO (auto-play) o Pausar tema dinámico (Shift+D)
|
||||||
case SDLK_D:
|
case SDLK_D:
|
||||||
// Shift+D = Pausar tema dinámico
|
// Shift+D = Pausar tema dinámico
|
||||||
if (event.key.mod & SDL_KMOD_SHIFT) {
|
if ((event.key.mod & SDL_KMOD_SHIFT) != 0u) {
|
||||||
engine.pauseDynamicTheme();
|
engine.pauseDynamicTheme();
|
||||||
} else {
|
} else {
|
||||||
// D sin Shift = Toggle DEMO ↔ SANDBOX
|
// D sin Shift = Toggle DEMO ↔ SANDBOX
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class InputHandler {
|
|||||||
* @param engine Referencia al engine para ejecutar acciones
|
* @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
|
* @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:
|
private:
|
||||||
// Sin estado interno por ahora - el InputHandler es stateless
|
// Sin estado interno por ahora - el InputHandler es stateless
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#include <iostream>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "engine.hpp"
|
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
|
#include "engine.hpp"
|
||||||
#include "resource_manager.hpp"
|
#include "resource_manager.hpp"
|
||||||
|
|
||||||
// getExecutableDirectory() ya está definido en defines.h como inline
|
// 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";
|
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 width = 0;
|
||||||
int height = 0;
|
int height = 0;
|
||||||
int zoom = 0;
|
int zoom = 0;
|
||||||
@@ -58,7 +59,8 @@ int main(int argc, char* argv[]) {
|
|||||||
if (strcmp(argv[i], "--help") == 0) {
|
if (strcmp(argv[i], "--help") == 0) {
|
||||||
printHelp();
|
printHelp();
|
||||||
return 0;
|
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) {
|
if (i + 1 < argc) {
|
||||||
width = atoi(argv[++i]);
|
width = atoi(argv[++i]);
|
||||||
if (width < 320) {
|
if (width < 320) {
|
||||||
@@ -189,25 +191,29 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
Engine engine;
|
Engine engine;
|
||||||
|
|
||||||
if (custom_balls > 0)
|
if (custom_balls > 0) {
|
||||||
engine.setCustomScenario(custom_balls); // pre-init: asigna campos antes del benchmark
|
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);
|
engine.setMaxBallsOverride(max_balls_override);
|
||||||
else if (skip_benchmark)
|
} else if (skip_benchmark) {
|
||||||
engine.setSkipBenchmark();
|
engine.setSkipBenchmark();
|
||||||
|
}
|
||||||
|
|
||||||
if (initial_postfx >= 0)
|
if (initial_postfx >= 0) {
|
||||||
engine.setInitialPostFX(initial_postfx);
|
engine.setInitialPostFX(initial_postfx);
|
||||||
|
}
|
||||||
|
|
||||||
if (override_vignette >= 0.f || override_chroma >= 0.f) {
|
if (override_vignette >= 0.f || override_chroma >= 0.f) {
|
||||||
if (initial_postfx < 0)
|
if (initial_postfx < 0) {
|
||||||
engine.setInitialPostFX(0);
|
engine.setInitialPostFX(0);
|
||||||
|
}
|
||||||
engine.setPostFXParamOverrides(override_vignette, override_chroma);
|
engine.setPostFXParamOverrides(override_vignette, override_chroma);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!engine.initialize(width, height, zoom, fullscreen, initial_mode)) {
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
#include "resource_manager.hpp"
|
#include "resource_manager.hpp"
|
||||||
#include "resource_pack.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "resource_pack.hpp"
|
||||||
|
|
||||||
// Inicializar estáticos
|
// Inicializar estáticos
|
||||||
ResourcePack* ResourceManager::resourcePack_ = nullptr;
|
ResourcePack* ResourceManager::resourcePack_ = nullptr;
|
||||||
std::map<std::string, std::vector<unsigned char>> ResourceManager::cache_;
|
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
|
// Si ya estaba inicializado, liberar primero
|
||||||
if (resourcePack_ != nullptr) {
|
if (resourcePack_ != nullptr) {
|
||||||
delete resourcePack_;
|
delete resourcePack_;
|
||||||
@@ -18,15 +19,15 @@ bool ResourceManager::init(const std::string& packFilePath) {
|
|||||||
|
|
||||||
// Intentar cargar el pack
|
// Intentar cargar el pack
|
||||||
resourcePack_ = new ResourcePack();
|
resourcePack_ = new ResourcePack();
|
||||||
if (!resourcePack_->loadPack(packFilePath)) {
|
if (!resourcePack_->loadPack(pack_file_path)) {
|
||||||
// Si falla, borrar instancia (usará fallback a disco)
|
// Si falla, borrar instancia (usará fallback a disco)
|
||||||
delete resourcePack_;
|
delete resourcePack_;
|
||||||
resourcePack_ = nullptr;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "resources.pack cargado (" << resourcePack_->getResourceCount() << " recursos)" << std::endl;
|
std::cout << "resources.pack cargado (" << resourcePack_->getResourceCount() << " recursos)" << '\n';
|
||||||
return true;
|
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;
|
data = nullptr;
|
||||||
size = 0;
|
size = 0;
|
||||||
|
|
||||||
// 1. Consultar caché en RAM (sin I/O)
|
// 1. Consultar caché en RAM (sin I/O)
|
||||||
auto it = cache_.find(resourcePath);
|
auto it = cache_.find(resource_path);
|
||||||
if (it != cache_.end()) {
|
if (it != cache_.end()) {
|
||||||
size = it->second.size();
|
size = it->second.size();
|
||||||
data = new unsigned char[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)
|
// 2. Intentar cargar desde pack (si está disponible)
|
||||||
if (resourcePack_ != nullptr) {
|
if (resourcePack_ != nullptr) {
|
||||||
ResourcePack::ResourceData packData = resourcePack_->loadResource(resourcePath);
|
ResourcePack::ResourceData pack_data = resourcePack_->loadResource(resource_path);
|
||||||
if (packData.data != nullptr) {
|
if (pack_data.data != nullptr) {
|
||||||
cache_[resourcePath] = std::vector<unsigned char>(packData.data, packData.data + packData.size);
|
cache_[resource_path] = std::vector<unsigned char>(pack_data.data, pack_data.data + pack_data.size);
|
||||||
data = packData.data;
|
data = pack_data.data;
|
||||||
size = packData.size;
|
size = pack_data.size;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Fallback: cargar desde disco
|
// 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) {
|
if (!file) {
|
||||||
std::string dataPath = "data/" + resourcePath;
|
std::string data_path = "data/" + resource_path;
|
||||||
file.open(dataPath, std::ios::binary | std::ios::ate);
|
file.open(data_path, std::ios::binary | std::ios::ate);
|
||||||
if (!file) { return false; }
|
if (!file) { return false; }
|
||||||
}
|
}
|
||||||
size = static_cast<size_t>(file.tellg());
|
size = static_cast<size_t>(file.tellg());
|
||||||
@@ -76,22 +77,22 @@ bool ResourceManager::loadResource(const std::string& resourcePath, unsigned cha
|
|||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
// Guardar en caché
|
// Guardar en caché
|
||||||
cache_[resourcePath] = std::vector<unsigned char>(data, data + size);
|
cache_[resource_path] = std::vector<unsigned char>(data, data + size);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ResourceManager::isPackLoaded() {
|
auto ResourceManager::isPackLoaded() -> bool {
|
||||||
return resourcePack_ != nullptr;
|
return resourcePack_ != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> ResourceManager::getResourceList() {
|
auto ResourceManager::getResourceList() -> std::vector<std::string> {
|
||||||
if (resourcePack_ != nullptr) {
|
if (resourcePack_ != nullptr) {
|
||||||
return resourcePack_->getResourceList();
|
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) {
|
if (resourcePack_ != nullptr) {
|
||||||
return resourcePack_->getResourceCount();
|
return resourcePack_->getResourceCount();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class ResourcePack;
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
class ResourceManager {
|
class ResourceManager {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Inicializa el sistema de recursos empaquetados
|
* Inicializa el sistema de recursos empaquetados
|
||||||
* Debe llamarse una única vez al inicio del programa
|
* Debe llamarse una única vez al inicio del programa
|
||||||
@@ -33,7 +33,7 @@ public:
|
|||||||
* @param packFilePath Ruta al archivo .pack (ej: "resources.pack")
|
* @param packFilePath Ruta al archivo .pack (ej: "resources.pack")
|
||||||
* @return true si el pack se cargó correctamente, false si no existe (fallback a disco)
|
* @return true si el pack se cargó correctamente, false si no existe (fallback a disco)
|
||||||
*/
|
*/
|
||||||
static bool init(const std::string& packFilePath);
|
static bool init(const std::string& pack_file_path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Libera el sistema de recursos
|
* Libera el sistema de recursos
|
||||||
@@ -49,7 +49,7 @@ public:
|
|||||||
* @param size [out] Tamaño del buffer en bytes
|
* @param size [out] Tamaño del buffer en bytes
|
||||||
* @return true si se cargó correctamente, false si falla
|
* @return true si se cargó correctamente, false si falla
|
||||||
*/
|
*/
|
||||||
static bool loadResource(const std::string& resourcePath, unsigned char*& data, size_t& size);
|
static bool loadResource(const std::string& resource_path, unsigned char*& data, size_t& size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifica si el pack está cargado
|
* Verifica si el pack está cargado
|
||||||
@@ -69,7 +69,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
static size_t getResourceCount();
|
static size_t getResourceCount();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Constructor privado (singleton)
|
// Constructor privado (singleton)
|
||||||
ResourceManager() = default;
|
ResourceManager() = default;
|
||||||
~ResourceManager() = default;
|
~ResourceManager() = default;
|
||||||
|
|||||||
@@ -7,10 +7,8 @@
|
|||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
// Clave XOR para ofuscación simple (puede cambiarse)
|
ResourcePack::ResourcePack()
|
||||||
constexpr uint8_t XOR_KEY = 0x5A;
|
: isLoaded_(false) {}
|
||||||
|
|
||||||
ResourcePack::ResourcePack() : isLoaded_(false) {}
|
|
||||||
|
|
||||||
ResourcePack::~ResourcePack() {
|
ResourcePack::~ResourcePack() {
|
||||||
clear();
|
clear();
|
||||||
@@ -20,54 +18,54 @@ ResourcePack::~ResourcePack() {
|
|||||||
// EMPAQUETADO (herramienta pack_resources)
|
// EMPAQUETADO (herramienta pack_resources)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
bool ResourcePack::addDirectory(const std::string& dirPath, const std::string& prefix) {
|
auto ResourcePack::addDirectory(const std::string& dir_path, const std::string& prefix) -> bool {
|
||||||
if (!fs::exists(dirPath) || !fs::is_directory(dirPath)) {
|
if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
|
||||||
std::cerr << "Error: Directorio no existe: " << dirPath << std::endl;
|
std::cerr << "Error: Directorio no existe: " << dir_path << '\n';
|
||||||
return false;
|
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()) {
|
if (entry.is_regular_file()) {
|
||||||
// Construir ruta relativa desde data/ (ej: "data/ball.png" → "ball.png")
|
// Construir ruta relativa desde data/ (ej: "data/ball.png" → "ball.png")
|
||||||
std::string relativePath = fs::relative(entry.path(), dirPath).string();
|
std::string relative_path = fs::relative(entry.path(), dir_path).string();
|
||||||
std::string fullPath = prefix.empty() ? relativePath : prefix + "/" + relativePath;
|
std::string full_path = prefix.empty() ? relative_path : prefix + "/" + relative_path;
|
||||||
fullPath = normalizePath(fullPath);
|
full_path = normalizePath(full_path);
|
||||||
|
|
||||||
// Leer archivo completo
|
// Leer archivo completo
|
||||||
std::ifstream file(entry.path(), std::ios::binary);
|
std::ifstream file(entry.path(), std::ios::binary);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
std::cerr << "Error: No se pudo abrir: " << entry.path() << std::endl;
|
std::cerr << "Error: No se pudo abrir: " << entry.path() << '\n';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
file.seekg(0, std::ios::end);
|
file.seekg(0, std::ios::end);
|
||||||
size_t fileSize = file.tellg();
|
size_t file_size = file.tellg();
|
||||||
file.seekg(0, std::ios::beg);
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
std::vector<unsigned char> buffer(fileSize);
|
std::vector<unsigned char> buffer(file_size);
|
||||||
file.read(reinterpret_cast<char*>(buffer.data()), fileSize);
|
file.read(reinterpret_cast<char*>(buffer.data()), file_size);
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
// Crear entrada de recurso
|
// Crear entrada de recurso
|
||||||
ResourceEntry resource;
|
ResourceEntry resource;
|
||||||
resource.path = fullPath;
|
resource.path = full_path;
|
||||||
resource.offset = 0; // Se calculará al guardar
|
resource.offset = 0; // Se calculará al guardar
|
||||||
resource.size = static_cast<uint32_t>(fileSize);
|
resource.size = static_cast<uint32_t>(file_size);
|
||||||
resource.checksum = calculateChecksum(buffer.data(), fileSize);
|
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();
|
return !resources_.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ResourcePack::savePack(const std::string& packFilePath) {
|
auto ResourcePack::savePack(const std::string& pack_file_path) -> bool {
|
||||||
std::ofstream packFile(packFilePath, std::ios::binary);
|
std::ofstream pack_file(pack_file_path, std::ios::binary);
|
||||||
if (!packFile) {
|
if (!pack_file) {
|
||||||
std::cerr << "Error: No se pudo crear pack: " << packFilePath << std::endl;
|
std::cerr << "Error: No se pudo crear pack: " << pack_file_path << '\n';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,39 +74,39 @@ bool ResourcePack::savePack(const std::string& packFilePath) {
|
|||||||
std::memcpy(header.magic, "VBE3", 4);
|
std::memcpy(header.magic, "VBE3", 4);
|
||||||
header.version = 1;
|
header.version = 1;
|
||||||
header.fileCount = static_cast<uint32_t>(resources_.size());
|
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)
|
// 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)
|
// Calcular tamaño del índice (cada entrada: uint32_t pathLen + path + 3*uint32_t)
|
||||||
for (const auto& [path, entry] : resources_) {
|
for (const auto& [path, entry] : resources_) {
|
||||||
currentOffset += sizeof(uint32_t); // pathLen
|
current_offset += sizeof(uint32_t); // pathLen
|
||||||
currentOffset += static_cast<uint32_t>(path.size()); // path
|
current_offset += static_cast<uint32_t>(path.size()); // path
|
||||||
currentOffset += sizeof(uint32_t) * 3; // offset, size, checksum
|
current_offset += sizeof(uint32_t) * 3; // offset, size, checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Escribir índice
|
// 3. Escribir índice
|
||||||
for (auto& [path, entry] : resources_) {
|
for (auto& [path, entry] : resources_) {
|
||||||
entry.offset = currentOffset;
|
entry.offset = current_offset;
|
||||||
|
|
||||||
uint32_t pathLen = static_cast<uint32_t>(path.size());
|
auto path_len = static_cast<uint32_t>(path.size());
|
||||||
packFile.write(reinterpret_cast<const char*>(&pathLen), sizeof(uint32_t));
|
pack_file.write(reinterpret_cast<const char*>(&path_len), sizeof(uint32_t));
|
||||||
packFile.write(path.c_str(), pathLen);
|
pack_file.write(path.c_str(), path_len);
|
||||||
packFile.write(reinterpret_cast<const char*>(&entry.offset), sizeof(uint32_t));
|
pack_file.write(reinterpret_cast<const char*>(&entry.offset), sizeof(uint32_t));
|
||||||
packFile.write(reinterpret_cast<const char*>(&entry.size), sizeof(uint32_t));
|
pack_file.write(reinterpret_cast<const char*>(&entry.size), sizeof(uint32_t));
|
||||||
packFile.write(reinterpret_cast<const char*>(&entry.checksum), 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)
|
// 4. Escribir datos de archivos (sin encriptar en pack, se encripta al cargar)
|
||||||
for (const auto& [path, entry] : resources_) {
|
for (const auto& [path, entry] : resources_) {
|
||||||
// Encontrar archivo original en disco
|
// Encontrar archivo original en disco
|
||||||
fs::path originalPath = fs::current_path() / "data" / path;
|
fs::path original_path = fs::current_path() / "data" / path;
|
||||||
std::ifstream file(originalPath, std::ios::binary);
|
std::ifstream file(original_path, std::ios::binary);
|
||||||
if (!file) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,10 +114,10 @@ bool ResourcePack::savePack(const std::string& packFilePath) {
|
|||||||
file.read(reinterpret_cast<char*>(buffer.data()), entry.size);
|
file.read(reinterpret_cast<char*>(buffer.data()), entry.size);
|
||||||
file.close();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,10 +125,10 @@ bool ResourcePack::savePack(const std::string& packFilePath) {
|
|||||||
// DESEMPAQUETADO (juego)
|
// DESEMPAQUETADO (juego)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
bool ResourcePack::loadPack(const std::string& packFilePath) {
|
auto ResourcePack::loadPack(const std::string& pack_file_path) -> bool {
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
packFile_.open(packFilePath, std::ios::binary);
|
packFile_.open(pack_file_path, std::ios::binary);
|
||||||
if (!packFile_) {
|
if (!packFile_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -140,13 +138,13 @@ bool ResourcePack::loadPack(const std::string& packFilePath) {
|
|||||||
packFile_.read(reinterpret_cast<char*>(&header), sizeof(PackHeader));
|
packFile_.read(reinterpret_cast<char*>(&header), sizeof(PackHeader));
|
||||||
|
|
||||||
if (std::memcmp(header.magic, "VBE3", 4) != 0) {
|
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();
|
packFile_.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.version != 1) {
|
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();
|
packFile_.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -155,12 +153,12 @@ bool ResourcePack::loadPack(const std::string& packFilePath) {
|
|||||||
for (uint32_t i = 0; i < header.fileCount; i++) {
|
for (uint32_t i = 0; i < header.fileCount; i++) {
|
||||||
ResourceEntry entry;
|
ResourceEntry entry;
|
||||||
|
|
||||||
uint32_t pathLen;
|
uint32_t path_len;
|
||||||
packFile_.read(reinterpret_cast<char*>(&pathLen), sizeof(uint32_t));
|
packFile_.read(reinterpret_cast<char*>(&path_len), sizeof(uint32_t));
|
||||||
|
|
||||||
std::vector<char> pathBuffer(pathLen + 1, '\0');
|
std::vector<char> path_buffer(path_len + 1, '\0');
|
||||||
packFile_.read(pathBuffer.data(), pathLen);
|
packFile_.read(path_buffer.data(), path_len);
|
||||||
entry.path = std::string(pathBuffer.data());
|
entry.path = std::string(path_buffer.data());
|
||||||
|
|
||||||
packFile_.read(reinterpret_cast<char*>(&entry.offset), sizeof(uint32_t));
|
packFile_.read(reinterpret_cast<char*>(&entry.offset), sizeof(uint32_t));
|
||||||
packFile_.read(reinterpret_cast<char*>(&entry.size), 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourcePack::ResourceData ResourcePack::loadResource(const std::string& resourcePath) {
|
auto ResourcePack::loadResource(const std::string& resource_path) -> ResourcePack::ResourceData {
|
||||||
ResourceData result = {nullptr, 0};
|
ResourceData result = {.data = nullptr, .size = 0};
|
||||||
|
|
||||||
if (!isLoaded_) {
|
if (!isLoaded_) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string normalizedPath = normalizePath(resourcePath);
|
std::string normalized_path = normalizePath(resource_path);
|
||||||
auto it = resources_.find(normalizedPath);
|
auto it = resources_.find(normalized_path);
|
||||||
if (it == resources_.end()) {
|
if (it == resources_.end()) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -197,7 +195,7 @@ ResourcePack::ResourceData ResourcePack::loadResource(const std::string& resourc
|
|||||||
// Verificar checksum
|
// Verificar checksum
|
||||||
uint32_t checksum = calculateChecksum(result.data, entry.size);
|
uint32_t checksum = calculateChecksum(result.data, entry.size);
|
||||||
if (checksum != entry.checksum) {
|
if (checksum != entry.checksum) {
|
||||||
std::cerr << "Warning: Checksum incorrecto para: " << resourcePath << std::endl;
|
std::cerr << "Warning: Checksum incorrecto para: " << resource_path << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -207,15 +205,16 @@ ResourcePack::ResourceData ResourcePack::loadResource(const std::string& resourc
|
|||||||
// UTILIDADES
|
// UTILIDADES
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
std::vector<std::string> ResourcePack::getResourceList() const {
|
auto ResourcePack::getResourceList() const -> std::vector<std::string> {
|
||||||
std::vector<std::string> list;
|
std::vector<std::string> list;
|
||||||
|
list.reserve(resources_.size());
|
||||||
for (const auto& [path, entry] : resources_) {
|
for (const auto& [path, entry] : resources_) {
|
||||||
list.push_back(path);
|
list.push_back(path);
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ResourcePack::getResourceCount() const {
|
auto ResourcePack::getResourceCount() const -> size_t {
|
||||||
return resources_.size();
|
return resources_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +230,7 @@ void ResourcePack::clear() {
|
|||||||
// FUNCIONES AUXILIARES
|
// 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;
|
uint32_t checksum = 0;
|
||||||
for (size_t i = 0; i < size; i++) {
|
for (size_t i = 0; i < size; i++) {
|
||||||
checksum ^= static_cast<uint32_t>(data[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;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ResourcePack::normalizePath(const std::string& path) {
|
auto ResourcePack::normalizePath(const std::string& path) -> std::string {
|
||||||
std::string normalized = path;
|
std::string normalized = path;
|
||||||
|
|
||||||
// Reemplazar \ por /
|
// 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
|
// Buscar "data/" en cualquier parte del path y extraer lo que viene después
|
||||||
size_t data_pos = normalized.find("data/");
|
size_t data_pos = normalized.find("data/");
|
||||||
|
|||||||
@@ -13,30 +13,30 @@
|
|||||||
* único y ofuscado. Proporciona fallback automático a carpeta data/ si no existe pack.
|
* único y ofuscado. Proporciona fallback automático a carpeta data/ si no existe pack.
|
||||||
*/
|
*/
|
||||||
class ResourcePack {
|
class ResourcePack {
|
||||||
public:
|
public:
|
||||||
ResourcePack();
|
ResourcePack();
|
||||||
~ResourcePack();
|
~ResourcePack();
|
||||||
|
|
||||||
// Empaquetado (usado por herramienta pack_resources)
|
// Empaquetado (usado por herramienta pack_resources)
|
||||||
bool addDirectory(const std::string& dirPath, const std::string& prefix = "");
|
bool addDirectory(const std::string& dir_path, const std::string& prefix = "");
|
||||||
bool savePack(const std::string& packFilePath);
|
bool savePack(const std::string& pack_file_path);
|
||||||
|
|
||||||
// Desempaquetado (usado por el juego)
|
// Desempaquetado (usado por el juego)
|
||||||
bool loadPack(const std::string& packFilePath);
|
bool loadPack(const std::string& pack_file_path);
|
||||||
|
|
||||||
// Carga de recursos individuales
|
// Carga de recursos individuales
|
||||||
struct ResourceData {
|
struct ResourceData {
|
||||||
unsigned char* data;
|
unsigned char* data;
|
||||||
size_t size;
|
size_t size;
|
||||||
};
|
};
|
||||||
ResourceData loadResource(const std::string& resourcePath);
|
ResourceData loadResource(const std::string& resource_path);
|
||||||
|
|
||||||
// Utilidades
|
// Utilidades
|
||||||
std::vector<std::string> getResourceList() const;
|
std::vector<std::string> getResourceList() const;
|
||||||
size_t getResourceCount() const;
|
size_t getResourceCount() const;
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Header del pack (12 bytes)
|
// Header del pack (12 bytes)
|
||||||
struct PackHeader {
|
struct PackHeader {
|
||||||
char magic[4]; // "VBE3"
|
char magic[4]; // "VBE3"
|
||||||
@@ -58,6 +58,6 @@ private:
|
|||||||
bool isLoaded_;
|
bool isLoaded_;
|
||||||
|
|
||||||
// Funciones auxiliares
|
// Funciones auxiliares
|
||||||
uint32_t calculateChecksum(const unsigned char* data, size_t size);
|
static uint32_t calculateChecksum(const unsigned char* data, size_t size);
|
||||||
std::string normalizePath(const std::string& path);
|
static std::string normalizePath(const std::string& path);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
#include "scene_manager.hpp"
|
#include "scene_manager.hpp"
|
||||||
|
|
||||||
#include <cstdlib> // for rand
|
#include <cstdlib> // for rand
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "defines.hpp" // for BALL_COUNT_SCENARIOS, GRAVITY_MASS_MIN, etc
|
#include "defines.hpp" // for BALL_COUNT_SCENARIOS, GRAVITY_MASS_MIN, etc
|
||||||
#include "external/texture.hpp" // for Texture
|
#include "external/texture.hpp" // for Texture
|
||||||
#include "theme_manager.hpp" // for ThemeManager
|
#include "theme_manager.hpp" // for ThemeManager
|
||||||
|
|
||||||
SceneManager::SceneManager(int screen_width, int screen_height)
|
SceneManager::SceneManager(int screen_width, int screen_height)
|
||||||
: current_gravity_(GravityDirection::DOWN)
|
: current_gravity_(GravityDirection::DOWN),
|
||||||
, scenario_(0)
|
scenario_(0),
|
||||||
, screen_width_(screen_width)
|
screen_width_(screen_width),
|
||||||
, screen_height_(screen_height)
|
screen_height_(screen_height),
|
||||||
, current_ball_size_(10)
|
current_ball_size_(10),
|
||||||
, texture_(nullptr)
|
texture_(nullptr),
|
||||||
, theme_manager_(nullptr) {
|
theme_manager_(nullptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::initialize(int scenario, std::shared_ptr<Texture> texture, ThemeManager* theme_manager) {
|
void SceneManager::initialize(int scenario, std::shared_ptr<Texture> texture, ThemeManager* theme_manager) {
|
||||||
scenario_ = scenario;
|
scenario_ = scenario;
|
||||||
texture_ = texture;
|
texture_ = std::move(texture);
|
||||||
theme_manager_ = theme_manager;
|
theme_manager_ = theme_manager;
|
||||||
current_ball_size_ = texture_->getWidth();
|
current_ball_size_ = texture_->getWidth();
|
||||||
|
|
||||||
@@ -48,28 +49,31 @@ void SceneManager::changeScenario(int scenario_id, SimulationMode mode) {
|
|||||||
? custom_ball_count_
|
? custom_ball_count_
|
||||||
: BALL_COUNT_SCENARIOS[scenario_id];
|
: BALL_COUNT_SCENARIOS[scenario_id];
|
||||||
for (int i = 0; i < ball_count; ++i) {
|
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)
|
// Inicialización según SimulationMode (RULES.md líneas 23-26)
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case SimulationMode::PHYSICS: {
|
case SimulationMode::PHYSICS: {
|
||||||
// PHYSICS: Parte superior, 75% distribución central en X
|
// PHYSICS: Parte superior, 75% distribución central en X
|
||||||
const int SIGN = ((rand() % 2) * 2) - 1;
|
const int SIGN = ((rand() % 2) * 2) - 1;
|
||||||
const int margin = static_cast<int>(screen_width_ * BALL_SPAWN_MARGIN);
|
const int MARGIN = static_cast<int>(screen_width_ * BALL_SPAWN_MARGIN);
|
||||||
const int spawn_zone_width = screen_width_ - (2 * margin);
|
const int SPAWN_ZONE_WIDTH = screen_width_ - (2 * MARGIN);
|
||||||
X = (rand() % spawn_zone_width) + margin;
|
x = (rand() % SPAWN_ZONE_WIDTH) + MARGIN;
|
||||||
Y = 0.0f; // Parte superior
|
y = 0.0f; // Parte superior
|
||||||
VX = (((rand() % 20) + 10) * 0.1f) * SIGN;
|
vx = (((rand() % 20) + 10) * 0.1f) * SIGN;
|
||||||
VY = ((rand() % 60) - 30) * 0.1f;
|
vy = ((rand() % 60) - 30) * 0.1f;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SimulationMode::SHAPE: {
|
case SimulationMode::SHAPE: {
|
||||||
// SHAPE: Centro de pantalla, sin velocidad inicial
|
// SHAPE: Centro de pantalla, sin velocidad inicial
|
||||||
X = screen_width_ / 2.0f;
|
x = screen_width_ / 2.0f;
|
||||||
Y = screen_height_ / 2.0f; // Centro vertical
|
y = screen_height_ / 2.0f; // Centro vertical
|
||||||
VX = 0.0f;
|
vx = 0.0f;
|
||||||
VY = 0.0f;
|
vy = 0.0f;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,48 +81,57 @@ void SceneManager::changeScenario(int scenario_id, SimulationMode mode) {
|
|||||||
// BOIDS: Posiciones aleatorias, velocidades aleatorias
|
// BOIDS: Posiciones aleatorias, velocidades aleatorias
|
||||||
const int SIGN_X = ((rand() % 2) * 2) - 1;
|
const int SIGN_X = ((rand() % 2) * 2) - 1;
|
||||||
const int SIGN_Y = ((rand() % 2) * 2) - 1;
|
const int SIGN_Y = ((rand() % 2) * 2) - 1;
|
||||||
X = static_cast<float>(rand() % screen_width_);
|
x = static_cast<float>(rand() % screen_width_);
|
||||||
Y = static_cast<float>(rand() % screen_height_); // Posición Y aleatoria
|
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
|
vx = (((rand() % 40) + 10) * 0.1f) * SIGN_X; // 1.0 - 5.0 px/frame
|
||||||
VY = (((rand() % 40) + 10) * 0.1f) * SIGN_Y;
|
vy = (((rand() % 40) + 10) * 0.1f) * SIGN_Y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Fallback a PHYSICS por seguridad
|
// Fallback a PHYSICS por seguridad
|
||||||
const int SIGN = ((rand() % 2) * 2) - 1;
|
const int SIGN = ((rand() % 2) * 2) - 1;
|
||||||
const int margin = static_cast<int>(screen_width_ * BALL_SPAWN_MARGIN);
|
const int MARGIN = static_cast<int>(screen_width_ * BALL_SPAWN_MARGIN);
|
||||||
const int spawn_zone_width = screen_width_ - (2 * margin);
|
const int SPAWN_ZONE_WIDTH = screen_width_ - (2 * MARGIN);
|
||||||
X = (rand() % spawn_zone_width) + margin;
|
x = (rand() % SPAWN_ZONE_WIDTH) + MARGIN;
|
||||||
Y = 0.0f; // Parte superior
|
y = 0.0f; // Parte superior
|
||||||
VX = (((rand() % 20) + 10) * 0.1f) * SIGN;
|
vx = (((rand() % 20) + 10) * 0.1f) * SIGN;
|
||||||
VY = ((rand() % 60) - 30) * 0.1f;
|
vy = ((rand() % 60) - 30) * 0.1f;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seleccionar color de la paleta del tema actual (delegado a ThemeManager)
|
// Seleccionar color de la paleta del tema actual (delegado a ThemeManager)
|
||||||
int random_index = rand();
|
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)
|
// 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>(
|
balls_.emplace_back(std::make_unique<Ball>(
|
||||||
X, Y, VX, VY, COLOR, texture_,
|
x,
|
||||||
screen_width_, screen_height_, current_ball_size_,
|
y,
|
||||||
current_gravity_, mass_factor
|
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) {
|
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
|
// Guardar tamaño antiguo
|
||||||
int old_size = current_ball_size_;
|
int old_size = current_ball_size_;
|
||||||
|
|
||||||
// Actualizar textura y tamaño
|
// Actualizar textura y tamaño
|
||||||
texture_ = new_texture;
|
texture_ = std::move(new_texture);
|
||||||
current_ball_size_ = new_ball_size;
|
current_ball_size_ = new_ball_size;
|
||||||
|
|
||||||
// Actualizar texturas de todas las pelotas
|
// Actualizar texturas de todas las pelotas
|
||||||
@@ -136,7 +149,8 @@ void SceneManager::pushBallsAwayFromGravity() {
|
|||||||
const float LATERAL = (((rand() % 20) + 10) * 0.1f) * SIGNO;
|
const float LATERAL = (((rand() % 20) + 10) * 0.1f) * SIGNO;
|
||||||
const float MAIN = ((rand() % 40) * 0.1f) + 5;
|
const float MAIN = ((rand() % 40) * 0.1f) + 5;
|
||||||
|
|
||||||
float vx = 0, vy = 0;
|
float vx = 0;
|
||||||
|
float vy = 0;
|
||||||
switch (current_gravity_) {
|
switch (current_gravity_) {
|
||||||
case GravityDirection::DOWN: // Impulsar ARRIBA
|
case GravityDirection::DOWN: // Impulsar ARRIBA
|
||||||
vx = LATERAL;
|
vx = LATERAL;
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#include "atom_shape.hpp"
|
#include "atom_shape.hpp"
|
||||||
#include "defines.hpp"
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
void AtomShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
void AtomShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
||||||
num_points_ = num_points;
|
num_points_ = num_points;
|
||||||
nucleus_radius_ = screen_height * ATOM_NUCLEUS_RADIUS_FACTOR;
|
nucleus_radius_ = screen_height * ATOM_NUCLEUS_RADIUS_FACTOR;
|
||||||
@@ -26,13 +29,13 @@ void AtomShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
|||||||
|
|
||||||
// Calcular cuántos puntos para núcleo vs órbitas
|
// Calcular cuántos puntos para núcleo vs órbitas
|
||||||
int nucleus_points = (num_points_ < 10) ? 1 : (num_points_ / 10); // 10% para núcleo
|
int nucleus_points = (num_points_ < 10) ? 1 : (num_points_ / 10); // 10% para núcleo
|
||||||
if (nucleus_points < 1) nucleus_points = 1;
|
nucleus_points = std::max(nucleus_points, 1);
|
||||||
|
|
||||||
// Si estamos en el núcleo
|
// Si estamos en el núcleo
|
||||||
if (index < nucleus_points) {
|
if (index < nucleus_points) {
|
||||||
// Distribuir puntos en esfera pequeña (núcleo)
|
// Distribuir puntos en esfera pequeña (núcleo)
|
||||||
float t = static_cast<float>(index) / static_cast<float>(nucleus_points);
|
float t = static_cast<float>(index) / static_cast<float>(nucleus_points);
|
||||||
float phi = acosf(1.0f - 2.0f * t);
|
float phi = acosf(1.0f - (2.0f * t));
|
||||||
float theta = PI * 2.0f * t * 3.61803398875f; // Golden ratio
|
float theta = PI * 2.0f * t * 3.61803398875f; // Golden ratio
|
||||||
|
|
||||||
float x_nuc = nucleus_radius_ * cosf(theta) * sinf(phi);
|
float x_nuc = nucleus_radius_ * cosf(theta) * sinf(phi);
|
||||||
@@ -51,10 +54,12 @@ void AtomShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
|||||||
// Puntos restantes: distribuir en órbitas
|
// Puntos restantes: distribuir en órbitas
|
||||||
int orbit_points = num_points_ - nucleus_points;
|
int orbit_points = num_points_ - nucleus_points;
|
||||||
int points_per_orbit = orbit_points / num_orbits;
|
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;
|
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;
|
int point_in_orbit = (index - nucleus_points) % points_per_orbit;
|
||||||
|
|
||||||
@@ -73,21 +78,21 @@ void AtomShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
|||||||
// Inclinar el plano orbital (rotación en eje X local)
|
// Inclinar el plano orbital (rotación en eje X local)
|
||||||
float cos_tilt = cosf(orbit_tilt);
|
float cos_tilt = cosf(orbit_tilt);
|
||||||
float sin_tilt = sinf(orbit_tilt);
|
float sin_tilt = sinf(orbit_tilt);
|
||||||
float y_tilted = y_local * cos_tilt - z_local * sin_tilt;
|
float y_tilted = (y_local * cos_tilt) - (z_local * sin_tilt);
|
||||||
float z_tilted = y_local * sin_tilt + z_local * cos_tilt;
|
float z_tilted = (y_local * sin_tilt) + (z_local * cos_tilt);
|
||||||
|
|
||||||
// Aplicar rotación global del átomo (eje Y)
|
// Aplicar rotación global del átomo (eje Y)
|
||||||
float cos_y = cosf(angle_y_);
|
float cos_y = cosf(angle_y_);
|
||||||
float sin_y = sinf(angle_y_);
|
float sin_y = sinf(angle_y_);
|
||||||
float x_rot = x_local * cos_y - z_tilted * sin_y;
|
float x_rot = (x_local * cos_y) - (z_tilted * sin_y);
|
||||||
float z_rot = x_local * sin_y + z_tilted * cos_y;
|
float z_rot = (x_local * sin_y) + (z_tilted * cos_y);
|
||||||
|
|
||||||
x = x_rot;
|
x = x_rot;
|
||||||
y = y_tilted;
|
y = y_tilted;
|
||||||
z = z_rot;
|
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
|
// Factor de escala para física: proporcional al radio de órbita
|
||||||
// Radio órbita base = 72px (0.30 * 240px en resolución 320x240)
|
// Radio órbita base = 72px (0.30 * 240px en resolución 320x240)
|
||||||
const float BASE_RADIUS = 72.0f;
|
const float BASE_RADIUS = 72.0f;
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
// Comportamiento: Núcleo estático + electrones orbitando en planos inclinados
|
// Comportamiento: Núcleo estático + electrones orbitando en planos inclinados
|
||||||
// Efecto: Modelo atómico clásico Bohr
|
// Efecto: Modelo atómico clásico Bohr
|
||||||
class AtomShape : public Shape {
|
class AtomShape : public Shape {
|
||||||
private:
|
private:
|
||||||
float angle_y_ = 0.0f; // Ángulo de rotación global en eje Y (rad)
|
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 orbit_phase_ = 0.0f; // Fase de rotación de electrones (rad)
|
||||||
float nucleus_radius_ = 0.0f; // Radio del núcleo central (píxeles)
|
float nucleus_radius_ = 0.0f; // Radio del núcleo central (píxeles)
|
||||||
float orbit_radius_ = 0.0f; // Radio de las órbitas (píxeles)
|
float orbit_radius_ = 0.0f; // Radio de las órbitas (píxeles)
|
||||||
int num_points_ = 0; // Cantidad total de puntos
|
int num_points_ = 0; // Cantidad total de puntos
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void generatePoints(int num_points, float screen_width, float screen_height) 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 update(float delta_time, float screen_width, float screen_height) override;
|
||||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#include "cube_shape.hpp"
|
#include "cube_shape.hpp"
|
||||||
#include "defines.hpp"
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
void CubeShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
void CubeShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
||||||
num_points_ = num_points;
|
num_points_ = num_points;
|
||||||
size_ = screen_height * CUBE_SIZE_FACTOR;
|
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
|
// Aplicar rotación en eje Z
|
||||||
float cos_z = cosf(angle_z_);
|
float cos_z = cosf(angle_z_);
|
||||||
float sin_z = sinf(angle_z_);
|
float sin_z = sinf(angle_z_);
|
||||||
float x_rot_z = x_base * cos_z - y_base * sin_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 y_rot_z = (x_base * sin_z) + (y_base * cos_z);
|
||||||
float z_rot_z = z_base;
|
float z_rot_z = z_base;
|
||||||
|
|
||||||
// Aplicar rotación en eje Y
|
// Aplicar rotación en eje Y
|
||||||
float cos_y = cosf(angle_y_);
|
float cos_y = cosf(angle_y_);
|
||||||
float sin_y = sinf(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 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
|
// Aplicar rotación en eje X
|
||||||
float cos_x = cosf(angle_x_);
|
float cos_x = cosf(angle_x_);
|
||||||
float sin_x = sinf(angle_x_);
|
float sin_x = sinf(angle_x_);
|
||||||
float x_final = x_rot_y;
|
float x_final = x_rot_y;
|
||||||
float y_final = y_rot_y * cos_x - z_rot_y * sin_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;
|
float z_final = (y_rot_y * sin_x) + (z_rot_y * cos_x);
|
||||||
|
|
||||||
// Retornar coordenadas finales rotadas
|
// Retornar coordenadas finales rotadas
|
||||||
x = x_final;
|
x = x_final;
|
||||||
@@ -76,7 +79,7 @@ void CubeShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
|||||||
z = z_final;
|
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
|
// Factor de escala para física: proporcional al tamaño del cubo
|
||||||
// Tamaño base = 60px (resolución 320x240, factor 0.25)
|
// Tamaño base = 60px (resolución 320x240, factor 0.25)
|
||||||
const float BASE_SIZE = 60.0f;
|
const float BASE_SIZE = 60.0f;
|
||||||
@@ -105,12 +108,24 @@ void CubeShape::generateVerticesAndCenters() {
|
|||||||
|
|
||||||
// 2. Añadir 6 centros de caras
|
// 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)
|
// 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_x_.push_back(-size_); base_y_.push_back(0); base_z_.push_back(0); // -X
|
base_y_.push_back(0);
|
||||||
base_x_.push_back(0); base_y_.push_back(size_); base_z_.push_back(0); // +Y
|
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(-size_);
|
||||||
base_x_.push_back(0); base_y_.push_back(0); base_z_.push_back(size_); // +Z
|
base_y_.push_back(0);
|
||||||
base_x_.push_back(0); base_y_.push_back(0); base_z_.push_back(-size_); // -Z
|
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
|
// 3. Añadir 12 centros de aristas
|
||||||
// Aristas paralelas a X (4), Y (4), Z (4)
|
// Aristas paralelas a X (4), Y (4), Z (4)
|
||||||
@@ -143,16 +158,16 @@ void CubeShape::generateVerticesAndCenters() {
|
|||||||
void CubeShape::generateVolumetricGrid() {
|
void CubeShape::generateVolumetricGrid() {
|
||||||
// Calcular dimensión del grid cúbico: N³ ≈ num_points
|
// Calcular dimensión del grid cúbico: N³ ≈ num_points
|
||||||
int grid_dim = static_cast<int>(ceilf(cbrtf(static_cast<float>(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
|
float step = (2.0f * size_) / (grid_dim - 1); // Espacio entre puntos
|
||||||
|
|
||||||
for (int ix = 0; ix < grid_dim; ix++) {
|
for (int ix = 0; ix < grid_dim; ix++) {
|
||||||
for (int iy = 0; iy < grid_dim; iy++) {
|
for (int iy = 0; iy < grid_dim; iy++) {
|
||||||
for (int iz = 0; iz < grid_dim; iz++) {
|
for (int iz = 0; iz < grid_dim; iz++) {
|
||||||
float x = -size_ + ix * step;
|
float x = -size_ + (ix * step);
|
||||||
float y = -size_ + iy * step;
|
float y = -size_ + (iy * step);
|
||||||
float z = -size_ + iz * step;
|
float z = -size_ + (iz * step);
|
||||||
|
|
||||||
base_x_.push_back(x);
|
base_x_.push_back(x);
|
||||||
base_y_.push_back(y);
|
base_y_.push_back(y);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "shape.hpp"
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "shape.hpp"
|
||||||
|
|
||||||
// Figura: Cubo 3D rotante
|
// Figura: Cubo 3D rotante
|
||||||
// Distribución:
|
// Distribución:
|
||||||
// - 1-8 pelotas: Solo vértices (8 puntos)
|
// - 1-8 pelotas: Solo vértices (8 puntos)
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
// - 27+ pelotas: Grid volumétrico 3D uniforme
|
// - 27+ pelotas: Grid volumétrico 3D uniforme
|
||||||
// Comportamiento: Rotación simultánea en ejes X, Y, Z (efecto Rubik)
|
// Comportamiento: Rotación simultánea en ejes X, Y, Z (efecto Rubik)
|
||||||
class CubeShape : public Shape {
|
class CubeShape : public Shape {
|
||||||
private:
|
private:
|
||||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
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_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 angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad)
|
||||||
@@ -22,14 +23,14 @@ private:
|
|||||||
std::vector<float> base_y_;
|
std::vector<float> base_y_;
|
||||||
std::vector<float> base_z_;
|
std::vector<float> base_z_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void generatePoints(int num_points, float screen_width, float screen_height) 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 update(float delta_time, float screen_width, float screen_height) override;
|
||||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||||
const char* getName() const override { return "CUBE"; }
|
const char* getName() const override { return "CUBE"; }
|
||||||
float getScaleFactor(float screen_height) const override;
|
float getScaleFactor(float screen_height) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Métodos auxiliares para distribución de puntos
|
// Métodos auxiliares para distribución de puntos
|
||||||
void generateVertices(); // 8 vértices
|
void generateVertices(); // 8 vértices
|
||||||
void generateVerticesAndCenters(); // 26 puntos (vértices + caras + aristas)
|
void generateVerticesAndCenters(); // 26 puntos (vértices + caras + aristas)
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
#include "cylinder_shape.hpp"
|
#include "cylinder_shape.hpp"
|
||||||
#include "defines.hpp"
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdlib> // Para rand()
|
#include <cstdlib> // Para rand()
|
||||||
|
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
void CylinderShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
void CylinderShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
||||||
num_points_ = num_points;
|
num_points_ = num_points;
|
||||||
radius_ = screen_height * CYLINDER_RADIUS_FACTOR;
|
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 t = tumble_progress;
|
||||||
float ease = t < 0.5f
|
float ease = t < 0.5f
|
||||||
? 2.0f * t * t
|
? 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_;
|
angle_x_ = ease * tumble_target_;
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
// 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));
|
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;
|
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
|
// Obtener parámetros u (ángulo) y v (altura) del índice
|
||||||
int ring = index / points_per_ring;
|
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;
|
float u = (static_cast<float>(point_in_ring) / static_cast<float>(points_per_ring)) * 2.0f * PI;
|
||||||
|
|
||||||
// Parámetro v (altura normalizada): [-1, 1]
|
// Parámetro v (altura normalizada): [-1, 1]
|
||||||
float v = (static_cast<float>(ring) / static_cast<float>(num_rings - 1)) * 2.0f - 1.0f;
|
float v = ((static_cast<float>(ring) / static_cast<float>(num_rings - 1)) * 2.0f) - 1.0f;
|
||||||
if (num_rings == 1) v = 0.0f;
|
if (num_rings == 1) {
|
||||||
|
v = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
// Ecuaciones paramétricas del cilindro
|
// Ecuaciones paramétricas del cilindro
|
||||||
// x = radius * cos(u)
|
// 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)
|
// Aplicar rotación en eje Y (principal, siempre activa)
|
||||||
float cos_y = cosf(angle_y_);
|
float cos_y = cosf(angle_y_);
|
||||||
float sin_y = sinf(angle_y_);
|
float sin_y = sinf(angle_y_);
|
||||||
float x_rot_y = x_base * cos_y - z_base * sin_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 z_rot_y = (x_base * sin_y) + (z_base * cos_y);
|
||||||
|
|
||||||
// Aplicar rotación en eje X (tumbling ocasional)
|
// Aplicar rotación en eje X (tumbling ocasional)
|
||||||
float cos_x = cosf(angle_x_);
|
float cos_x = cosf(angle_x_);
|
||||||
float sin_x = sinf(angle_x_);
|
float sin_x = sinf(angle_x_);
|
||||||
float y_rot = y_base * cos_x - z_rot_y * sin_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 z_rot = (y_base * sin_x) + (z_rot_y * cos_x);
|
||||||
|
|
||||||
// Retornar coordenadas finales con ambas rotaciones
|
// Retornar coordenadas finales con ambas rotaciones
|
||||||
x = x_rot_y;
|
x = x_rot_y;
|
||||||
@@ -109,7 +114,7 @@ void CylinderShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
|||||||
z = z_rot;
|
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)
|
// Factor de escala para física: proporcional a la dimensión mayor (altura)
|
||||||
// Altura base = 120px (0.5 * 240px en resolución 320x240)
|
// Altura base = 120px (0.5 * 240px en resolución 320x240)
|
||||||
const float BASE_HEIGHT = 120.0f;
|
const float BASE_HEIGHT = 120.0f;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Comportamiento: Superficie cilíndrica con rotación en eje Y + tumbling ocasional en X/Z
|
// 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)
|
// Ecuaciones: x = r*cos(u), y = v, z = r*sin(u)
|
||||||
class CylinderShape : public Shape {
|
class CylinderShape : public Shape {
|
||||||
private:
|
private:
|
||||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
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 angle_x_ = 0.0f; // Ángulo de rotación en eje X (tumbling ocasional)
|
||||||
float radius_ = 0.0f; // Radio del cilindro (píxeles)
|
float radius_ = 0.0f; // Radio del cilindro (píxeles)
|
||||||
@@ -19,7 +19,7 @@ private:
|
|||||||
bool is_tumbling_ = false; // ¿Estamos en modo tumble?
|
bool is_tumbling_ = false; // ¿Estamos en modo tumble?
|
||||||
float tumble_target_ = 0.0f; // Ángulo objetivo del tumble
|
float tumble_target_ = 0.0f; // Ángulo objetivo del tumble
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void generatePoints(int num_points, float screen_width, float screen_height) 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 update(float delta_time, float screen_width, float screen_height) override;
|
||||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#include "helix_shape.hpp"
|
#include "helix_shape.hpp"
|
||||||
#include "defines.hpp"
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
void HelixShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
void HelixShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
||||||
num_points_ = num_points;
|
num_points_ = num_points;
|
||||||
radius_ = screen_height * HELIX_RADIUS_FACTOR;
|
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)
|
// Aplicar rotación en eje Y (horizontal)
|
||||||
float cos_y = cosf(angle_y_);
|
float cos_y = cosf(angle_y_);
|
||||||
float sin_y = sinf(angle_y_);
|
float sin_y = sinf(angle_y_);
|
||||||
float x_rot = x_base * cos_y - z_base * sin_y;
|
float x_rot = (x_base * cos_y) - (z_base * sin_y);
|
||||||
float z_rot = x_base * sin_y + z_base * cos_y;
|
float z_rot = (x_base * sin_y) + (z_base * cos_y);
|
||||||
|
|
||||||
// Retornar coordenadas finales
|
// Retornar coordenadas finales
|
||||||
x = x_rot;
|
x = x_rot;
|
||||||
@@ -50,7 +52,7 @@ void HelixShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
|||||||
z = z_rot;
|
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)
|
// 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)
|
// 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;
|
const float BASE_HEIGHT = 180.0f;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Comportamiento: Rotación en eje Y + animación de fase vertical
|
// Comportamiento: Rotación en eje Y + animación de fase vertical
|
||||||
// Ecuaciones: x = r*cos(t), y = pitch*t + phase, z = r*sin(t)
|
// Ecuaciones: x = r*cos(t), y = pitch*t + phase, z = r*sin(t)
|
||||||
class HelixShape : public Shape {
|
class HelixShape : public Shape {
|
||||||
private:
|
private:
|
||||||
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
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 phase_offset_ = 0.0f; // Offset de fase para animación vertical (rad)
|
||||||
float radius_ = 0.0f; // Radio de la espiral (píxeles)
|
float radius_ = 0.0f; // Radio de la espiral (píxeles)
|
||||||
@@ -14,7 +14,7 @@ private:
|
|||||||
float total_height_ = 0.0f; // Altura total de la espiral (píxeles)
|
float total_height_ = 0.0f; // Altura total de la espiral (píxeles)
|
||||||
int num_points_ = 0; // Cantidad de puntos generados
|
int num_points_ = 0; // Cantidad de puntos generados
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void generatePoints(int num_points, float screen_width, float screen_height) 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 update(float delta_time, float screen_width, float screen_height) override;
|
||||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
#include "icosahedron_shape.hpp"
|
#include "icosahedron_shape.hpp"
|
||||||
#include "defines.hpp"
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
void IcosahedronShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
void IcosahedronShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
||||||
num_points_ = num_points;
|
num_points_ = num_points;
|
||||||
radius_ = screen_height * ICOSAHEDRON_RADIUS_FACTOR;
|
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 {
|
void IcosahedronShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
||||||
// Proporción áurea (golden ratio)
|
// 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
|
// 12 vértices del icosaedro regular normalizado
|
||||||
// Basados en 3 rectángulos áureos ortogonales
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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
|
// Si tenemos 12 o menos puntos, usar solo vértices
|
||||||
if (num_points_ <= 12) {
|
if (num_points_ <= 12) {
|
||||||
int vertex_index = index % 12;
|
int vertex_index = index % 12;
|
||||||
float x_base = vertices[vertex_index][0] / normalization * radius_;
|
float x_base = VERTICES[vertex_index][0] / NORMALIZATION * radius_;
|
||||||
float y_base = vertices[vertex_index][1] / normalization * radius_;
|
float y_base = VERTICES[vertex_index][1] / NORMALIZATION * radius_;
|
||||||
float z_base = vertices[vertex_index][2] / normalization * radius_;
|
float z_base = VERTICES[vertex_index][2] / NORMALIZATION * radius_;
|
||||||
|
|
||||||
// Aplicar rotaciones
|
// Aplicar rotaciones
|
||||||
applyRotations(x_base, y_base, z_base, x, y, z);
|
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)
|
// Distribuir puntos entre vértices (primero) y caras (después)
|
||||||
if (index < 12) {
|
if (index < 12) {
|
||||||
// Primeros 12 puntos: vértices del icosaedro
|
// Primeros 12 puntos: vértices del icosaedro
|
||||||
float x_base = vertices[index][0] / normalization * radius_;
|
float x_base = VERTICES[index][0] / NORMALIZATION * radius_;
|
||||||
float y_base = vertices[index][1] / normalization * radius_;
|
float y_base = VERTICES[index][1] / NORMALIZATION * radius_;
|
||||||
float z_base = vertices[index][2] / normalization * radius_;
|
float z_base = VERTICES[index][2] / NORMALIZATION * radius_;
|
||||||
applyRotations(x_base, y_base, z_base, x, y, z);
|
applyRotations(x_base, y_base, z_base, x, y, z);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -73,38 +76,55 @@ void IcosahedronShape::getPoint3D(int index, float& x, float& y, float& z) const
|
|||||||
// El icosaedro tiene 20 caras triangulares
|
// El icosaedro tiene 20 caras triangulares
|
||||||
int remaining_points = index - 12;
|
int remaining_points = index - 12;
|
||||||
int points_per_face = (num_points_ - 12) / 20;
|
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;
|
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;
|
int point_in_face = remaining_points % points_per_face;
|
||||||
|
|
||||||
// Definir algunas caras del icosaedro (usando índices de vértices)
|
// Definir algunas caras del icosaedro (usando índices de vértices)
|
||||||
// Solo necesitamos generar puntos, no renderizar caras completas
|
// Solo necesitamos generar puntos, no renderizar caras completas
|
||||||
static const int faces[20][3] = {
|
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},
|
{0, 11, 5},
|
||||||
{1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8},
|
{0, 5, 1},
|
||||||
{3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9},
|
{0, 1, 7},
|
||||||
{4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1}
|
{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
|
// Obtener vértices de la cara
|
||||||
int v0 = faces[face_index][0];
|
int v0 = FACES[face_index][0];
|
||||||
int v1 = faces[face_index][1];
|
int v1 = FACES[face_index][1];
|
||||||
int v2 = faces[face_index][2];
|
int v2 = FACES[face_index][2];
|
||||||
|
|
||||||
// Interpolar dentro del triángulo usando coordenadas baricéntricas simples
|
// 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 t = static_cast<float>(point_in_face) / static_cast<float>(points_per_face + 1);
|
||||||
float u = sqrtf(t);
|
float u = sqrtf(t);
|
||||||
float v = t - u;
|
float v = t - u;
|
||||||
|
|
||||||
float x_interp = vertices[v0][0] * (1.0f - u - v) + vertices[v1][0] * u + vertices[v2][0] * 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 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 z_interp = (VERTICES[v0][2] * (1.0f - u - v)) + (VERTICES[v1][2] * u) + (VERTICES[v2][2] * v);
|
||||||
|
|
||||||
// Proyectar a la esfera
|
// 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) {
|
if (len > 0.0001f) {
|
||||||
x_interp /= len;
|
x_interp /= len;
|
||||||
y_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
|
// Aplicar rotación en eje X
|
||||||
float cos_x = cosf(angle_x_);
|
float cos_x = cosf(angle_x_);
|
||||||
float sin_x = sinf(angle_x_);
|
float sin_x = sinf(angle_x_);
|
||||||
float y_rot_x = y_in * cos_x - z_in * sin_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 z_rot_x = (y_in * sin_x) + (z_in * cos_x);
|
||||||
|
|
||||||
// Aplicar rotación en eje Y
|
// Aplicar rotación en eje Y
|
||||||
float cos_y = cosf(angle_y_);
|
float cos_y = cosf(angle_y_);
|
||||||
float sin_y = sinf(angle_y_);
|
float sin_y = sinf(angle_y_);
|
||||||
float x_rot_y = x_in * cos_y - z_rot_x * sin_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 z_rot_y = (x_in * sin_y) + (z_rot_x * cos_y);
|
||||||
|
|
||||||
// Aplicar rotación en eje Z
|
// Aplicar rotación en eje Z
|
||||||
float cos_z = cosf(angle_z_);
|
float cos_z = cosf(angle_z_);
|
||||||
float sin_z = sinf(angle_z_);
|
float sin_z = sinf(angle_z_);
|
||||||
float x_final = x_rot_y * cos_z - y_rot_x * sin_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 y_final = (x_rot_y * sin_z) + (y_rot_x * cos_z);
|
||||||
|
|
||||||
x_out = x_final;
|
x_out = x_final;
|
||||||
y_out = y_final;
|
y_out = y_final;
|
||||||
z_out = z_rot_y;
|
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
|
// Factor de escala para física: proporcional al radio
|
||||||
// Radio base = 72px (0.30 * 240px en resolución 320x240)
|
// Radio base = 72px (0.30 * 240px en resolución 320x240)
|
||||||
const float BASE_RADIUS = 72.0f;
|
const float BASE_RADIUS = 72.0f;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Comportamiento: 12 vértices distribuidos uniformemente con rotación triple
|
// Comportamiento: 12 vértices distribuidos uniformemente con rotación triple
|
||||||
// Geometría: Basado en proporción áurea (golden ratio)
|
// Geometría: Basado en proporción áurea (golden ratio)
|
||||||
class IcosahedronShape : public Shape {
|
class IcosahedronShape : public Shape {
|
||||||
private:
|
private:
|
||||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
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_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 angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad)
|
||||||
@@ -16,7 +16,7 @@ private:
|
|||||||
// Helper para aplicar rotaciones triple XYZ
|
// 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;
|
void applyRotations(float x_in, float y_in, float z_in, float& x_out, float& y_out, float& z_out) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void generatePoints(int num_points, float screen_width, float screen_height) 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 update(float delta_time, float screen_width, float screen_height) override;
|
||||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#include "lissajous_shape.hpp"
|
#include "lissajous_shape.hpp"
|
||||||
#include "defines.hpp"
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
void LissajousShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
void LissajousShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
||||||
num_points_ = num_points;
|
num_points_ = num_points;
|
||||||
amplitude_ = screen_height * LISSAJOUS_SIZE_FACTOR;
|
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)
|
// x(t) = A * sin(freq_x * t + phase_x)
|
||||||
// y(t) = A * sin(freq_y * t)
|
// y(t) = A * sin(freq_y * t)
|
||||||
// z(t) = A * sin(freq_z * t + phase_z)
|
// 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 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
|
// Aplicar rotación global en eje X
|
||||||
float cos_x = cosf(rotation_x_);
|
float cos_x = cosf(rotation_x_);
|
||||||
float sin_x = sinf(rotation_x_);
|
float sin_x = sinf(rotation_x_);
|
||||||
float y_rot = y_local * cos_x - z_local * sin_x;
|
float y_rot = (y_local * cos_x) - (z_local * sin_x);
|
||||||
float z_rot = y_local * sin_x + z_local * cos_x;
|
float z_rot = (y_local * sin_x) + (z_local * cos_x);
|
||||||
|
|
||||||
// Aplicar rotación global en eje Y
|
// Aplicar rotación global en eje Y
|
||||||
float cos_y = cosf(rotation_y_);
|
float cos_y = cosf(rotation_y_);
|
||||||
float sin_y = sinf(rotation_y_);
|
float sin_y = sinf(rotation_y_);
|
||||||
float x_final = x_local * cos_y - z_rot * sin_y;
|
float x_final = (x_local * cos_y) - (z_rot * sin_y);
|
||||||
float z_final = x_local * sin_y + z_rot * cos_y;
|
float z_final = (x_local * sin_y) + (z_rot * cos_y);
|
||||||
|
|
||||||
// Retornar coordenadas rotadas
|
// Retornar coordenadas rotadas
|
||||||
x = x_final;
|
x = x_final;
|
||||||
@@ -55,7 +57,7 @@ void LissajousShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
|||||||
z = z_final;
|
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
|
// Factor de escala para física: proporcional a la amplitud de la curva
|
||||||
// Amplitud base = 84px (0.35 * 240px en resolución 320x240)
|
// Amplitud base = 84px (0.35 * 240px en resolución 320x240)
|
||||||
const float BASE_SIZE = 84.0f;
|
const float BASE_SIZE = 84.0f;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Comportamiento: Curva paramétrica 3D con rotación global y animación de fase
|
// 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)
|
// 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 {
|
class LissajousShape : public Shape {
|
||||||
private:
|
private:
|
||||||
float freq_x_ = 0.0f; // Frecuencia en eje X
|
float freq_x_ = 0.0f; // Frecuencia en eje X
|
||||||
float freq_y_ = 0.0f; // Frecuencia en eje Y
|
float freq_y_ = 0.0f; // Frecuencia en eje Y
|
||||||
float freq_z_ = 0.0f; // Frecuencia en eje Z
|
float freq_z_ = 0.0f; // Frecuencia en eje Z
|
||||||
@@ -17,7 +17,7 @@ private:
|
|||||||
float amplitude_ = 0.0f; // Amplitud de la curva (píxeles)
|
float amplitude_ = 0.0f; // Amplitud de la curva (píxeles)
|
||||||
int num_points_ = 0; // Cantidad total de puntos
|
int num_points_ = 0; // Cantidad total de puntos
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void generatePoints(int num_points, float screen_width, float screen_height) 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 update(float delta_time, float screen_width, float screen_height) override;
|
||||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
#include "png_shape.hpp"
|
#include "png_shape.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include "external/stb_image.h"
|
#include "external/stb_image.h"
|
||||||
#include "resource_manager.hpp"
|
#include "resource_manager.hpp"
|
||||||
#include <cmath>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <iostream>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
PNGShape::PNGShape(const char* png_path) {
|
PNGShape::PNGShape(const char* png_path) {
|
||||||
// Cargar PNG desde path
|
// Cargar PNG desde path
|
||||||
if (!loadPNG(png_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
|
// Fallback: generar un cuadrado simple si falla la carga
|
||||||
image_width_ = 10;
|
image_width_ = 10;
|
||||||
image_height_ = 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);
|
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);
|
std::string fn = std::string(resource_key);
|
||||||
fn = fn.substr(fn.find_last_of("\\/") + 1);
|
fn = fn.substr(fn.find_last_of("\\/") + 1);
|
||||||
@@ -30,15 +32,16 @@ bool PNGShape::loadPNG(const char* resource_key) {
|
|||||||
unsigned char* file_data = nullptr;
|
unsigned char* file_data = nullptr;
|
||||||
size_t file_size = 0;
|
size_t file_size = 0;
|
||||||
if (!ResourceManager::loadResource(resource_key, file_data, file_size)) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
int width, height, channels;
|
int width;
|
||||||
unsigned char* pixels = stbi_load_from_memory(file_data, static_cast<int>(file_size),
|
int height;
|
||||||
&width, &height, &channels, 1);
|
int channels;
|
||||||
|
unsigned char* pixels = stbi_load_from_memory(file_data, static_cast<int>(file_size), &width, &height, &channels, 1);
|
||||||
delete[] file_data;
|
delete[] file_data;
|
||||||
if (!pixels) {
|
if (pixels == nullptr) {
|
||||||
std::cerr << "[PNGShape] ERROR al decodificar PNG: " << stbi_failure_reason() << std::endl;
|
std::cerr << "[PNGShape] ERROR al decodificar PNG: " << stbi_failure_reason() << '\n';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
image_width_ = width;
|
image_width_ = width;
|
||||||
@@ -57,9 +60,11 @@ void PNGShape::detectEdges() {
|
|||||||
// Detectar píxeles del contorno (píxeles blancos con al menos un vecino negro)
|
// Detectar píxeles del contorno (píxeles blancos con al menos un vecino negro)
|
||||||
for (int y = 0; y < image_height_; y++) {
|
for (int y = 0; y < image_height_; y++) {
|
||||||
for (int x = 0; x < image_width_; x++) {
|
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)
|
// Verificar vecinos (arriba, abajo, izq, der)
|
||||||
bool is_edge = false;
|
bool is_edge = false;
|
||||||
@@ -90,7 +95,7 @@ void PNGShape::floodFill() {
|
|||||||
|
|
||||||
for (int y = 0; y < image_height_; y++) {
|
for (int y = 0; y < image_height_; y++) {
|
||||||
for (int x = 0; x < image_width_; x++) {
|
for (int x = 0; x < image_width_; x++) {
|
||||||
int idx = y * image_width_ + x;
|
int idx = (y * image_width_) + x;
|
||||||
if (pixel_data_[idx]) {
|
if (pixel_data_[idx]) {
|
||||||
filled_points_.push_back({static_cast<float>(x), static_cast<float>(y)});
|
filled_points_.push_back({static_cast<float>(x), static_cast<float>(y)});
|
||||||
}
|
}
|
||||||
@@ -123,7 +128,7 @@ void PNGShape::generatePoints(int num_points, float screen_width, float screen_h
|
|||||||
|
|
||||||
// Conjunto de puntos ACTIVO (será modificado por filtros)
|
// Conjunto de puntos ACTIVO (será modificado por filtros)
|
||||||
std::vector<Point2D> active_points_data;
|
std::vector<Point2D> active_points_data;
|
||||||
std::string mode_name = "";
|
std::string mode_name;
|
||||||
|
|
||||||
// === SISTEMA DE DISTRIBUCIÓN ADAPTATIVA ===
|
// === SISTEMA DE DISTRIBUCIÓN ADAPTATIVA ===
|
||||||
// Estrategia: Optimizar según número de pelotas disponibles
|
// 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);
|
std::vector<Point2D> vertices = extractCornerVertices(source_for_vertices);
|
||||||
if (!vertices.empty() && vertices.size() < active_points_data.size()) {
|
if (!vertices.empty() && vertices.size() < active_points_data.size()) {
|
||||||
active_points_data = vertices;
|
active_points_data = vertices;
|
||||||
num_2d_points = active_points_data.size();
|
|
||||||
total_3d_points = num_2d_points * num_layers_;
|
|
||||||
mode_name = "VÉRTICES";
|
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)
|
// Extraer filas alternas de puntos (FUNCIÓN PURA: no modifica parámetros)
|
||||||
// Recibe vector original y devuelve nuevo vector filtrado
|
// 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;
|
std::vector<Point2D> result;
|
||||||
|
|
||||||
if (row_skip <= 1 || source.empty()) {
|
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)
|
// 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;
|
std::vector<Point2D> result;
|
||||||
|
|
||||||
if (source.empty()) {
|
if (source.empty()) {
|
||||||
@@ -386,14 +389,14 @@ void PNGShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
|||||||
// Aplicar rotación en eje Y (horizontal)
|
// Aplicar rotación en eje Y (horizontal)
|
||||||
float cos_y = cosf(angle_y_);
|
float cos_y = cosf(angle_y_);
|
||||||
float sin_y = sinf(angle_y_);
|
float sin_y = sinf(angle_y_);
|
||||||
float x_rot_y = x_base * cos_y - z_base * sin_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 z_rot_y = (x_base * sin_y) + (z_base * cos_y);
|
||||||
|
|
||||||
// Aplicar rotación en eje X (vertical)
|
// Aplicar rotación en eje X (vertical)
|
||||||
float cos_x = cosf(angle_x_);
|
float cos_x = cosf(angle_x_);
|
||||||
float sin_x = sinf(angle_x_);
|
float sin_x = sinf(angle_x_);
|
||||||
float y_rot = y_base * cos_x - z_rot_y * sin_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 z_rot = (y_base * sin_x) + (z_rot_y * cos_x);
|
||||||
|
|
||||||
// Retornar coordenadas finales
|
// Retornar coordenadas finales
|
||||||
x = x_rot_y;
|
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
|
// Escala dinámica según resolución
|
||||||
return PNG_SIZE_FACTOR;
|
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)
|
// 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_) {
|
if (!is_flipping_) {
|
||||||
return 0.0f; // No está flipping, progreso = 0
|
return 0.0f; // No está flipping, progreso = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "shape.hpp"
|
|
||||||
#include "defines.hpp" // Para PNG_IDLE_TIME_MIN/MAX constantes
|
|
||||||
#include <vector>
|
|
||||||
#include <cstdlib> // Para rand()
|
#include <cstdlib> // Para rand()
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "defines.hpp" // Para PNG_IDLE_TIME_MIN/MAX constantes
|
||||||
|
#include "shape.hpp"
|
||||||
|
|
||||||
// Figura: Shape generada desde PNG 1-bit (blanco sobre negro)
|
// Figura: Shape generada desde PNG 1-bit (blanco sobre negro)
|
||||||
// Enfoque A: Extrusión 2D (implementado)
|
// Enfoque A: Extrusión 2D (implementado)
|
||||||
// Enfoque B: Voxelización 3D (preparado para futuro)
|
// Enfoque B: Voxelización 3D (preparado para futuro)
|
||||||
class PNGShape : public Shape {
|
class PNGShape : public Shape {
|
||||||
private:
|
private:
|
||||||
// Datos de la imagen cargada
|
// Datos de la imagen cargada
|
||||||
int image_width_ = 0;
|
int image_width_ = 0;
|
||||||
int image_height_ = 0;
|
int image_height_ = 0;
|
||||||
@@ -59,16 +60,16 @@ private:
|
|||||||
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
|
// Métodos internos
|
||||||
bool loadPNG(const char* path); // Cargar PNG con stb_image
|
bool loadPNG(const char* resource_key); // Cargar PNG con stb_image
|
||||||
void detectEdges(); // Detectar contorno (Enfoque A)
|
void detectEdges(); // Detectar contorno (Enfoque A)
|
||||||
void floodFill(); // Rellenar interior (Enfoque B - futuro)
|
void floodFill(); // Rellenar interior (Enfoque B - futuro)
|
||||||
void generateExtrudedPoints(); // Generar puntos con extrusión 2D
|
void generateExtrudedPoints(); // Generar puntos con extrusión 2D
|
||||||
|
|
||||||
// Métodos de distribución adaptativa (funciones puras, no modifican parámetros)
|
// 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
|
static std::vector<Point2D> extractAlternateRows(const std::vector<Point2D>& source, int row_skip); // Extraer filas alternas
|
||||||
std::vector<Point2D> extractCornerVertices(const std::vector<Point2D>& source); // Extraer vértices/esquinas
|
static std::vector<Point2D> extractCornerVertices(const std::vector<Point2D>& source); // Extraer vértices/esquinas
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor: recibe path relativo al PNG
|
// Constructor: recibe path relativo al PNG
|
||||||
PNGShape(const char* png_path = "data/shapes/jailgames.png");
|
PNGShape(const char* png_path = "data/shapes/jailgames.png");
|
||||||
|
|
||||||
@@ -88,7 +89,10 @@ public:
|
|||||||
int getFlipCount() const { return flip_count_; }
|
int getFlipCount() const { return flip_count_; }
|
||||||
|
|
||||||
// Resetear contador de flips (llamar al entrar a LOGO MODE)
|
// Resetear contador de flips (llamar al entrar a LOGO MODE)
|
||||||
void resetFlipCount() { flip_count_ = 0; was_flipping_last_frame_ = false; }
|
void resetFlipCount() {
|
||||||
|
flip_count_ = 0;
|
||||||
|
was_flipping_last_frame_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Control de modo LOGO (flip intervals más largos)
|
// Control de modo LOGO (flip intervals más largos)
|
||||||
void setLogoMode(bool enable) {
|
void setLogoMode(bool enable) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
// Interfaz abstracta para todas las figuras 3D
|
// Interfaz abstracta para todas las figuras 3D
|
||||||
class Shape {
|
class Shape {
|
||||||
public:
|
public:
|
||||||
virtual ~Shape() = default;
|
virtual ~Shape() = default;
|
||||||
|
|
||||||
// Generar distribución inicial de puntos en la figura
|
// Generar distribución inicial de puntos en la figura
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#include "sphere_shape.hpp"
|
#include "sphere_shape.hpp"
|
||||||
#include "defines.hpp"
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
void SphereShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
void SphereShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
||||||
num_points_ = num_points;
|
num_points_ = num_points;
|
||||||
radius_ = screen_height * ROTOBALL_RADIUS_FACTOR;
|
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 {
|
void SphereShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
||||||
// Algoritmo Fibonacci Sphere para distribución uniforme
|
// Algoritmo Fibonacci Sphere para distribución uniforme
|
||||||
const float golden_ratio = (1.0f + sqrtf(5.0f)) / 2.0f;
|
const float GOLDEN_RATIO = (1.0f + sqrtf(5.0f)) / 2.0f;
|
||||||
const float angle_increment = PI * 2.0f * golden_ratio;
|
const float ANGLE_INCREMENT = PI * 2.0f * GOLDEN_RATIO;
|
||||||
|
|
||||||
float t = static_cast<float>(index) / static_cast<float>(num_points_);
|
float t = static_cast<float>(index) / static_cast<float>(num_points_);
|
||||||
float phi = acosf(1.0f - 2.0f * t); // Latitud
|
float phi = acosf(1.0f - (2.0f * t)); // Latitud
|
||||||
float theta = angle_increment * static_cast<float>(index); // Longitud
|
float theta = ANGLE_INCREMENT * static_cast<float>(index); // Longitud
|
||||||
|
|
||||||
// Convertir coordenadas esféricas a cartesianas
|
// Convertir coordenadas esféricas a cartesianas
|
||||||
float x_base = cosf(theta) * sinf(phi) * radius_;
|
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
|
// Aplicar rotación en eje Y
|
||||||
float cos_y = cosf(angle_y_);
|
float cos_y = cosf(angle_y_);
|
||||||
float sin_y = sinf(angle_y_);
|
float sin_y = sinf(angle_y_);
|
||||||
float x_rot = x_base * cos_y - z_base * sin_y;
|
float x_rot = (x_base * cos_y) - (z_base * sin_y);
|
||||||
float z_rot = x_base * sin_y + z_base * cos_y;
|
float z_rot = (x_base * sin_y) + (z_base * cos_y);
|
||||||
|
|
||||||
// Aplicar rotación en eje X
|
// Aplicar rotación en eje X
|
||||||
float cos_x = cosf(angle_x_);
|
float cos_x = cosf(angle_x_);
|
||||||
float sin_x = sinf(angle_x_);
|
float sin_x = sinf(angle_x_);
|
||||||
float y_rot = y_base * cos_x - z_rot * sin_x;
|
float y_rot = (y_base * cos_x) - (z_rot * sin_x);
|
||||||
float z_final = y_base * sin_x + z_rot * cos_x;
|
float z_final = (y_base * sin_x) + (z_rot * cos_x);
|
||||||
|
|
||||||
// Retornar coordenadas finales rotadas
|
// Retornar coordenadas finales rotadas
|
||||||
x = x_rot;
|
x = x_rot;
|
||||||
@@ -49,7 +51,7 @@ void SphereShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
|||||||
z = z_final;
|
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
|
// Factor de escala para física: proporcional al radio
|
||||||
// Radio base = 80px (resolución 320x240)
|
// Radio base = 80px (resolución 320x240)
|
||||||
const float BASE_RADIUS = 80.0f;
|
const float BASE_RADIUS = 80.0f;
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
// Comportamiento: Rotación dual en ejes X e Y
|
// Comportamiento: Rotación dual en ejes X e Y
|
||||||
// Uso anterior: RotoBall
|
// Uso anterior: RotoBall
|
||||||
class SphereShape : public Shape {
|
class SphereShape : public Shape {
|
||||||
private:
|
private:
|
||||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
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_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
|
||||||
float radius_ = 0.0f; // Radio de la esfera (píxeles)
|
float radius_ = 0.0f; // Radio de la esfera (píxeles)
|
||||||
int num_points_ = 0; // Cantidad de puntos generados
|
int num_points_ = 0; // Cantidad de puntos generados
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void generatePoints(int num_points, float screen_width, float screen_height) 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 update(float delta_time, float screen_width, float screen_height) override;
|
||||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#include "torus_shape.hpp"
|
#include "torus_shape.hpp"
|
||||||
#include "defines.hpp"
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
void TorusShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
void TorusShape::generatePoints(int num_points, float screen_width, float screen_height) {
|
||||||
num_points_ = num_points;
|
num_points_ = num_points;
|
||||||
major_radius_ = screen_height * TORUS_MAJOR_RADIUS_FACTOR;
|
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
|
// Calcular número aproximado de anillos y puntos por anillo
|
||||||
int num_rings = static_cast<int>(sqrtf(static_cast<float>(num_points_) * 0.5f));
|
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;
|
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
|
// Obtener parámetros u y v del índice
|
||||||
int ring = index / points_per_ring;
|
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 cos_u = cosf(u);
|
||||||
float sin_u = sinf(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 x_base = radius_at_v * cos_u;
|
||||||
float y_base = radius_at_v * sin_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
|
// Aplicar rotación en eje X
|
||||||
float cos_x = cosf(angle_x_);
|
float cos_x = cosf(angle_x_);
|
||||||
float sin_x = sinf(angle_x_);
|
float sin_x = sinf(angle_x_);
|
||||||
float y_rot_x = y_base * cos_x - z_base * sin_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 z_rot_x = (y_base * sin_x) + (z_base * cos_x);
|
||||||
|
|
||||||
// Aplicar rotación en eje Y
|
// Aplicar rotación en eje Y
|
||||||
float cos_y = cosf(angle_y_);
|
float cos_y = cosf(angle_y_);
|
||||||
float sin_y = sinf(angle_y_);
|
float sin_y = sinf(angle_y_);
|
||||||
float x_rot_y = x_base * cos_y - z_rot_x * sin_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 z_rot_y = (x_base * sin_y) + (z_rot_x * cos_y);
|
||||||
|
|
||||||
// Aplicar rotación en eje Z
|
// Aplicar rotación en eje Z
|
||||||
float cos_z = cosf(angle_z_);
|
float cos_z = cosf(angle_z_);
|
||||||
float sin_z = sinf(angle_z_);
|
float sin_z = sinf(angle_z_);
|
||||||
float x_final = x_rot_y * cos_z - y_rot_x * sin_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 y_final = (x_rot_y * sin_z) + (y_rot_x * cos_z);
|
||||||
|
|
||||||
// Retornar coordenadas finales rotadas
|
// Retornar coordenadas finales rotadas
|
||||||
x = x_final;
|
x = x_final;
|
||||||
@@ -87,7 +90,7 @@ void TorusShape::getPoint3D(int index, float& x, float& y, float& z) const {
|
|||||||
z = z_rot_y;
|
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
|
// Factor de escala para física: proporcional al radio mayor
|
||||||
// Radio mayor base = 60px (0.25 * 240px en resolución 320x240)
|
// Radio mayor base = 60px (0.25 * 240px en resolución 320x240)
|
||||||
const float BASE_RADIUS = 60.0f;
|
const float BASE_RADIUS = 60.0f;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Comportamiento: Superficie toroidal con rotación triple (X, Y, Z)
|
// 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)
|
// Ecuaciones: x = (R + r*cos(v))*cos(u), y = (R + r*cos(v))*sin(u), z = r*sin(v)
|
||||||
class TorusShape : public Shape {
|
class TorusShape : public Shape {
|
||||||
private:
|
private:
|
||||||
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
|
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_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 angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad)
|
||||||
@@ -14,7 +14,7 @@ private:
|
|||||||
float minor_radius_ = 0.0f; // Radio menor r (grosor del tubo)
|
float minor_radius_ = 0.0f; // Radio menor r (grosor del tubo)
|
||||||
int num_points_ = 0; // Cantidad de puntos generados
|
int num_points_ = 0; // Cantidad de puntos generados
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void generatePoints(int num_points, float screen_width, float screen_height) 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 update(float delta_time, float screen_width, float screen_height) override;
|
||||||
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
void getPoint3D(int index, float& x, float& y, float& z) const override;
|
||||||
|
|||||||
@@ -23,26 +23,24 @@
|
|||||||
#include "shapes/torus_shape.hpp"
|
#include "shapes/torus_shape.hpp"
|
||||||
|
|
||||||
ShapeManager::ShapeManager()
|
ShapeManager::ShapeManager()
|
||||||
: engine_(nullptr)
|
: engine_(nullptr),
|
||||||
, scene_mgr_(nullptr)
|
scene_mgr_(nullptr),
|
||||||
, ui_mgr_(nullptr)
|
ui_mgr_(nullptr),
|
||||||
, state_mgr_(nullptr)
|
state_mgr_(nullptr),
|
||||||
, current_mode_(SimulationMode::PHYSICS)
|
current_mode_(SimulationMode::PHYSICS),
|
||||||
, current_shape_type_(ShapeType::SPHERE)
|
current_shape_type_(ShapeType::SPHERE),
|
||||||
, last_shape_type_(ShapeType::SPHERE)
|
last_shape_type_(ShapeType::SPHERE),
|
||||||
, active_shape_(nullptr)
|
active_shape_(nullptr),
|
||||||
, shape_scale_factor_(1.0f)
|
shape_scale_factor_(1.0f),
|
||||||
, depth_zoom_enabled_(true)
|
depth_zoom_enabled_(true),
|
||||||
, screen_width_(0)
|
screen_width_(0),
|
||||||
, screen_height_(0)
|
screen_height_(0),
|
||||||
, shape_convergence_(0.0f) {
|
shape_convergence_(0.0f) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ShapeManager::~ShapeManager() {
|
ShapeManager::~ShapeManager() = default;
|
||||||
}
|
|
||||||
|
|
||||||
void ShapeManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr,
|
void ShapeManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height) {
|
||||||
StateManager* state_mgr, int screen_width, int screen_height) {
|
|
||||||
engine_ = engine;
|
engine_ = engine;
|
||||||
scene_mgr_ = scene_mgr;
|
scene_mgr_ = scene_mgr;
|
||||||
ui_mgr_ = ui_mgr;
|
ui_mgr_ = ui_mgr;
|
||||||
@@ -66,17 +64,17 @@ void ShapeManager::toggleShapeMode(bool force_gravity_on_exit) {
|
|||||||
activateShapeInternal(last_shape_type_);
|
activateShapeInternal(last_shape_type_);
|
||||||
|
|
||||||
// Si estamos en modo LOGO y la figura es PNG_SHAPE, restaurar configuración LOGO
|
// 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_) {
|
if (active_shape_) {
|
||||||
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
|
auto* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
|
||||||
if (png_shape) {
|
if (png_shape != nullptr) {
|
||||||
png_shape->setLogoMode(true);
|
png_shape->setLogoMode(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si estamos en LOGO MODE, resetear convergencia al entrar
|
// 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;
|
shape_convergence_ = 0.0f;
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
// 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");
|
ui_mgr_->showNotification("Modo física");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,8 +111,8 @@ void ShapeManager::handleShapeScaleChange(bool increase) {
|
|||||||
clampShapeScale();
|
clampShapeScale();
|
||||||
|
|
||||||
// Mostrar notificación si está en modo SANDBOX
|
// 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) {
|
||||||
std::string notification = "Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
|
std::string notification = "Escala " + std::to_string(static_cast<int>((shape_scale_factor_ * 100.0f) + 0.5f)) + "%";
|
||||||
ui_mgr_->showNotification(notification);
|
ui_mgr_->showNotification(notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +123,7 @@ void ShapeManager::resetShapeScale() {
|
|||||||
shape_scale_factor_ = SHAPE_SCALE_DEFAULT;
|
shape_scale_factor_ = SHAPE_SCALE_DEFAULT;
|
||||||
|
|
||||||
// Mostrar notificación si está en modo SANDBOX
|
// 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%");
|
ui_mgr_->showNotification("Escala 100%");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,14 +134,16 @@ void ShapeManager::toggleDepthZoom() {
|
|||||||
depth_zoom_enabled_ = !depth_zoom_enabled_;
|
depth_zoom_enabled_ = !depth_zoom_enabled_;
|
||||||
|
|
||||||
// Mostrar notificación si está en modo SANDBOX
|
// 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");
|
ui_mgr_->showNotification(depth_zoom_enabled_ ? "Profundidad on" : "Profundidad off");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShapeManager::update(float delta_time) {
|
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
|
// Actualizar animación de la figura
|
||||||
active_shape_->update(delta_time, static_cast<float>(screen_width_), static_cast<float>(screen_height_));
|
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
|
// Actualizar cada pelota con física de atracción
|
||||||
for (size_t i = 0; i < balls.size(); i++) {
|
for (size_t i = 0; i < balls.size(); i++) {
|
||||||
// Obtener posición 3D rotada del punto 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);
|
active_shape_->getPoint3D(static_cast<int>(i), x_3d, y_3d, z_3d);
|
||||||
|
|
||||||
// Aplicar escala manual a las coordenadas 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
|
// Aplicar fuerza de atracción física hacia el punto rotado
|
||||||
// Usar constantes SHAPE (mayor pegajosidad que ROTOBALL)
|
// Usar constantes SHAPE (mayor pegajosidad que ROTOBALL)
|
||||||
float shape_size = scale_factor * 80.0f; // 80px = radio base
|
float shape_size = scale_factor * 80.0f; // 80px = radio base
|
||||||
balls[i]->applyShapeForce(target_x, target_y, shape_size, delta_time,
|
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);
|
||||||
SHAPE_SPRING_K, SHAPE_DAMPING_BASE, SHAPE_DAMPING_NEAR,
|
|
||||||
SHAPE_NEAR_THRESHOLD, SHAPE_MAX_FORCE);
|
|
||||||
|
|
||||||
// Calcular brillo según profundidad Z para renderizado
|
// Calcular brillo según profundidad Z para renderizado
|
||||||
// Normalizar Z al rango de la figura (asumiendo simetría ±shape_size)
|
// 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
|
// 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
|
// 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);
|
balls[i]->setDepthScale(depth_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcular convergencia en LOGO MODE (% de pelotas cerca de su objetivo)
|
// 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;
|
int balls_near = 0;
|
||||||
float distance_threshold = LOGO_CONVERGENCE_DISTANCE; // 20px fijo (más permisivo)
|
float distance_threshold = LOGO_CONVERGENCE_DISTANCE; // 20px fijo (más permisivo)
|
||||||
|
|
||||||
@@ -215,7 +215,9 @@ void ShapeManager::update(float delta_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ShapeManager::generateShape() {
|
void ShapeManager::generateShape() {
|
||||||
if (!active_shape_) return;
|
if (!active_shape_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int num_points = static_cast<int>(scene_mgr_->getBallCount());
|
int num_points = static_cast<int>(scene_mgr_->getBallCount());
|
||||||
active_shape_->generatePoints(num_points, static_cast<float>(screen_width_), static_cast<float>(screen_height_));
|
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);
|
scene_mgr_->enableShapeAttractionAll(true);
|
||||||
|
|
||||||
// Mostrar notificación con nombre de figura (solo si NO estamos en modo demo o logo)
|
// 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::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;
|
std::string notification = std::string("Modo ") + shape_name;
|
||||||
ui_mgr_->showNotification(notification);
|
ui_mgr_->showNotification(notification);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ class ShapeManager {
|
|||||||
* @param screen_width Ancho lógico de pantalla
|
* @param screen_width Ancho lógico de pantalla
|
||||||
* @param screen_height Alto lógico de pantalla
|
* @param screen_height Alto lógico de pantalla
|
||||||
*/
|
*/
|
||||||
void initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr,
|
void initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height);
|
||||||
StateManager* state_mgr, int screen_width, int screen_height);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Toggle entre modo PHYSICS y SHAPE
|
* @brief Toggle entre modo PHYSICS y SHAPE
|
||||||
|
|||||||
@@ -1,39 +1,37 @@
|
|||||||
#include "state_manager.hpp"
|
#include "state_manager.hpp"
|
||||||
|
|
||||||
#include <algorithm> // for std::min
|
#include <algorithm> // for std::min
|
||||||
|
#include <array> // for std::array
|
||||||
#include <cstdlib> // for rand
|
#include <cstdlib> // for rand
|
||||||
#include <vector> // for std::vector
|
#include <vector> // for std::vector
|
||||||
|
|
||||||
#include "defines.hpp" // for constantes DEMO/LOGO
|
#include "defines.hpp" // for constantes DEMO/LOGO
|
||||||
#include "engine.hpp" // for Engine (enter/exitShapeMode, texture)
|
#include "engine.hpp" // for Engine (enter/exitShapeMode, texture)
|
||||||
#include "scene/scene_manager.hpp" // for SceneManager
|
#include "scene/scene_manager.hpp" // for SceneManager
|
||||||
#include "shapes_mgr/shape_manager.hpp" // for ShapeManager
|
|
||||||
#include "shapes/png_shape.hpp" // for PNGShape flip detection
|
#include "shapes/png_shape.hpp" // for PNGShape flip detection
|
||||||
|
#include "shapes_mgr/shape_manager.hpp" // for ShapeManager
|
||||||
#include "theme_manager.hpp" // for ThemeManager
|
#include "theme_manager.hpp" // for ThemeManager
|
||||||
|
|
||||||
StateManager::StateManager()
|
StateManager::StateManager()
|
||||||
: engine_(nullptr)
|
: engine_(nullptr),
|
||||||
, scene_mgr_(nullptr)
|
scene_mgr_(nullptr),
|
||||||
, theme_mgr_(nullptr)
|
theme_mgr_(nullptr),
|
||||||
, shape_mgr_(nullptr)
|
shape_mgr_(nullptr),
|
||||||
, current_app_mode_(AppMode::SANDBOX)
|
current_app_mode_(AppMode::SANDBOX),
|
||||||
, previous_app_mode_(AppMode::SANDBOX)
|
previous_app_mode_(AppMode::SANDBOX),
|
||||||
, demo_timer_(0.0f)
|
demo_timer_(0.0f),
|
||||||
, demo_next_action_time_(0.0f)
|
demo_next_action_time_(0.0f),
|
||||||
, logo_convergence_threshold_(0.90f)
|
logo_convergence_threshold_(0.90f),
|
||||||
, logo_min_time_(3.0f)
|
logo_min_time_(3.0f),
|
||||||
, logo_max_time_(5.0f)
|
logo_max_time_(5.0f),
|
||||||
, logo_waiting_for_flip_(false)
|
logo_waiting_for_flip_(false),
|
||||||
, logo_target_flip_number_(0)
|
logo_target_flip_number_(0),
|
||||||
, logo_target_flip_percentage_(0.0f)
|
logo_target_flip_percentage_(0.0f),
|
||||||
, logo_current_flip_count_(0)
|
logo_current_flip_count_(0),
|
||||||
, logo_entered_manually_(false)
|
logo_entered_manually_(false),
|
||||||
, logo_previous_theme_(0)
|
logo_previous_theme_(0),
|
||||||
, logo_previous_texture_index_(0)
|
logo_previous_texture_index_(0),
|
||||||
, logo_previous_shape_scale_(1.0f) {
|
logo_previous_shape_scale_(1.0f) {
|
||||||
}
|
|
||||||
|
|
||||||
StateManager::~StateManager() {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateManager::initialize(Engine* engine, SceneManager* scene_mgr, ThemeManager* theme_mgr, ShapeManager* shape_mgr) {
|
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
|
// ACTUALIZACIÓN DE ESTADOS
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
void StateManager::update(float delta_time, float shape_convergence, Shape* active_shape) {
|
void StateManager::update(float delta_time, float shape_convergence, Shape* active_shape) { // NOLINT(readability-function-cognitive-complexity)
|
||||||
if (current_app_mode_ == AppMode::SANDBOX) return;
|
if (current_app_mode_ == AppMode::SANDBOX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
demo_timer_ += delta_time;
|
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 (current_app_mode_ == AppMode::LOGO) {
|
||||||
if (logo_waiting_for_flip_) {
|
if (logo_waiting_for_flip_) {
|
||||||
// CAMINO B: Esperando a que ocurran flips
|
// 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();
|
int current_flip_count = png_shape->getFlipCount();
|
||||||
|
|
||||||
if (current_flip_count > logo_current_flip_count_) {
|
logo_current_flip_count_ = std::max(current_flip_count, logo_current_flip_count_);
|
||||||
logo_current_flip_count_ = current_flip_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logo_current_flip_count_ + 1 >= logo_target_flip_number_) {
|
if (logo_current_flip_count_ + 1 >= logo_target_flip_number_) {
|
||||||
if (png_shape->isFlipping()) {
|
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_;
|
should_trigger = demo_timer_ >= demo_next_action_time_;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!should_trigger) return;
|
if (!should_trigger) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (current_app_mode_ == AppMode::LOGO) {
|
if (current_app_mode_ == AppMode::LOGO) {
|
||||||
// LOGO MODE: Sistema de acciones variadas con gravedad dinámica
|
// LOGO MODE: Sistema de acciones variadas con gravedad dinámica
|
||||||
@@ -122,8 +122,8 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti
|
|||||||
logo_target_flip_percentage_ = LOGO_FLIP_TRIGGER_MIN + (rand() % 1000) / 1000.0f * (LOGO_FLIP_TRIGGER_MAX - LOGO_FLIP_TRIGGER_MIN);
|
logo_target_flip_percentage_ = LOGO_FLIP_TRIGGER_MIN + (rand() % 1000) / 1000.0f * (LOGO_FLIP_TRIGGER_MAX - LOGO_FLIP_TRIGGER_MIN);
|
||||||
logo_current_flip_count_ = 0;
|
logo_current_flip_count_ = 0;
|
||||||
|
|
||||||
PNGShape* png_shape = dynamic_cast<PNGShape*>(shape_mgr_->getActiveShape());
|
auto* png_shape = dynamic_cast<PNGShape*>(shape_mgr_->getActiveShape());
|
||||||
if (png_shape) {
|
if (png_shape != nullptr) {
|
||||||
png_shape->resetFlipCount();
|
png_shape->resetFlipCount();
|
||||||
}
|
}
|
||||||
// No hacer nada más — esperar a que ocurran los flips
|
// No hacer nada más — esperar a que ocurran los flips
|
||||||
@@ -158,7 +158,7 @@ void StateManager::update(float delta_time, float shape_convergence, Shape* acti
|
|||||||
scene_mgr_->forceBallsGravityOff();
|
scene_mgr_->forceBallsGravityOff();
|
||||||
} else {
|
} else {
|
||||||
// 16%: Cambiar dirección de gravedad
|
// 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_->changeGravityDirection(new_direction);
|
||||||
scene_mgr_->forceBallsGravityOn();
|
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) {
|
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) {
|
if (current_app_mode_ == AppMode::LOGO && new_mode != AppMode::LOGO) {
|
||||||
previous_app_mode_ = new_mode;
|
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;
|
demo_timer_ = 0.0f;
|
||||||
|
|
||||||
if (new_mode == AppMode::DEMO || new_mode == AppMode::DEMO_LITE || new_mode == AppMode::LOGO) {
|
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) {
|
if (new_mode == AppMode::LOGO) {
|
||||||
float resolution_scale = current_screen_height / 720.0f;
|
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
|
// ACCIONES DE DEMO
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
void StateManager::performDemoAction(bool is_lite) {
|
void StateManager::performDemoAction(bool is_lite) { // NOLINT(readability-function-cognitive-complexity)
|
||||||
if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return;
|
if ((engine_ == nullptr) || (scene_mgr_ == nullptr) || (theme_mgr_ == nullptr) || (shape_mgr_ == nullptr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// SALTO AUTOMÁTICO A LOGO MODE (Easter Egg)
|
// SALTO AUTOMÁTICO A LOGO MODE (Easter Egg)
|
||||||
@@ -278,18 +283,18 @@ void StateManager::performDemoAction(bool is_lite) {
|
|||||||
// ACCIONES NORMALES DE DEMO/DEMO_LITE
|
// ACCIONES NORMALES DE DEMO/DEMO_LITE
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
int TOTAL_WEIGHT;
|
int total_weight;
|
||||||
int random_value;
|
int random_value;
|
||||||
int accumulated_weight = 0;
|
int accumulated_weight = 0;
|
||||||
|
|
||||||
if (is_lite) {
|
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;
|
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;
|
random_value = rand() % total_weight;
|
||||||
|
|
||||||
// Cambiar dirección gravedad (25%)
|
// Cambiar dirección gravedad (25%)
|
||||||
accumulated_weight += DEMO_LITE_WEIGHT_GRAVITY_DIR;
|
accumulated_weight += DEMO_LITE_WEIGHT_GRAVITY_DIR;
|
||||||
if (random_value < accumulated_weight) {
|
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);
|
scene_mgr_->changeGravityDirection(new_direction);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -304,8 +309,8 @@ void StateManager::performDemoAction(bool is_lite) {
|
|||||||
// Activar figura 3D (25%) - PNG_SHAPE excluido
|
// Activar figura 3D (25%) - PNG_SHAPE excluido
|
||||||
accumulated_weight += DEMO_LITE_WEIGHT_SHAPE;
|
accumulated_weight += DEMO_LITE_WEIGHT_SHAPE;
|
||||||
if (random_value < accumulated_weight) {
|
if (random_value < accumulated_weight) {
|
||||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
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]);
|
engine_->enterShapeMode(SHAPES[rand() % 8]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,13 +329,13 @@ void StateManager::performDemoAction(bool is_lite) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} 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;
|
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;
|
random_value = rand() % total_weight;
|
||||||
|
|
||||||
// Cambiar dirección gravedad (10%)
|
// Cambiar dirección gravedad (10%)
|
||||||
accumulated_weight += DEMO_WEIGHT_GRAVITY_DIR;
|
accumulated_weight += DEMO_WEIGHT_GRAVITY_DIR;
|
||||||
if (random_value < accumulated_weight) {
|
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);
|
scene_mgr_->changeGravityDirection(new_direction);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -345,8 +350,8 @@ void StateManager::performDemoAction(bool is_lite) {
|
|||||||
// Activar figura 3D (20%) - PNG_SHAPE excluido
|
// Activar figura 3D (20%) - PNG_SHAPE excluido
|
||||||
accumulated_weight += DEMO_WEIGHT_SHAPE;
|
accumulated_weight += DEMO_WEIGHT_SHAPE;
|
||||||
if (random_value < accumulated_weight) {
|
if (random_value < accumulated_weight) {
|
||||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
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]);
|
engine_->enterShapeMode(SHAPES[rand() % 8]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,10 +383,12 @@ void StateManager::performDemoAction(bool is_lite) {
|
|||||||
if (random_value < accumulated_weight) {
|
if (random_value < accumulated_weight) {
|
||||||
int auto_max = std::min(engine_->getMaxAutoScenario(), DEMO_AUTO_MAX_SCENARIO);
|
int auto_max = std::min(engine_->getMaxAutoScenario(), DEMO_AUTO_MAX_SCENARIO);
|
||||||
std::vector<int> candidates;
|
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);
|
candidates.push_back(i);
|
||||||
if (engine_->isCustomScenarioEnabled() && engine_->isCustomAutoAvailable())
|
}
|
||||||
|
if (engine_->isCustomScenarioEnabled() && engine_->isCustomAutoAvailable()) {
|
||||||
candidates.push_back(CUSTOM_SCENARIO_IDX);
|
candidates.push_back(CUSTOM_SCENARIO_IDX);
|
||||||
|
}
|
||||||
int new_scenario = candidates[rand() % candidates.size()];
|
int new_scenario = candidates[rand() % candidates.size()];
|
||||||
SimulationMode current_sim_mode = shape_mgr_->getCurrentMode();
|
SimulationMode current_sim_mode = shape_mgr_->getCurrentMode();
|
||||||
scene_mgr_->changeScenario(new_scenario, current_sim_mode);
|
scene_mgr_->changeScenario(new_scenario, current_sim_mode);
|
||||||
@@ -439,15 +446,15 @@ void StateManager::performDemoAction(bool is_lite) {
|
|||||||
// RANDOMIZACIÓN AL INICIAR DEMO
|
// RANDOMIZACIÓN AL INICIAR DEMO
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
void StateManager::randomizeOnDemoStart(bool is_lite) {
|
void StateManager::randomizeOnDemoStart(bool is_lite) { // NOLINT(readability-function-cognitive-complexity)
|
||||||
if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return;
|
if ((engine_ == nullptr) || (scene_mgr_ == nullptr) || (theme_mgr_ == nullptr) || (shape_mgr_ == nullptr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Si venimos de LOGO con PNG_SHAPE, cambiar figura obligatoriamente
|
// Si venimos de LOGO con PNG_SHAPE, cambiar figura obligatoriamente
|
||||||
if (shape_mgr_->getCurrentShapeType() == ShapeType::PNG_SHAPE) {
|
if (shape_mgr_->getCurrentShapeType() == ShapeType::PNG_SHAPE) {
|
||||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX,
|
constexpr std::array<ShapeType, 8> SHAPES = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||||
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
|
engine_->enterShapeMode(SHAPES[rand() % 8]);
|
||||||
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
|
||||||
engine_->enterShapeMode(shapes[rand() % 8]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_lite) {
|
if (is_lite) {
|
||||||
@@ -457,11 +464,11 @@ void StateManager::randomizeOnDemoStart(bool is_lite) {
|
|||||||
engine_->exitShapeMode(false);
|
engine_->exitShapeMode(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
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]);
|
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);
|
scene_mgr_->changeGravityDirection(new_direction);
|
||||||
if (rand() % 2 == 0) {
|
if (rand() % 2 == 0) {
|
||||||
toggleGravityOnOff();
|
toggleGravityOnOff();
|
||||||
@@ -476,14 +483,14 @@ void StateManager::randomizeOnDemoStart(bool is_lite) {
|
|||||||
engine_->exitShapeMode(false);
|
engine_->exitShapeMode(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
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];
|
ShapeType selected_shape = SHAPES[rand() % 8];
|
||||||
|
|
||||||
// Randomizar profundidad y escala ANTES de activar la figura
|
// Randomizar profundidad y escala ANTES de activar la figura
|
||||||
if (rand() % 2 == 0) {
|
if (rand() % 2 == 0) {
|
||||||
shape_mgr_->setDepthZoomEnabled(!shape_mgr_->isDepthZoomEnabled());
|
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);
|
engine_->enterShapeMode(selected_shape);
|
||||||
}
|
}
|
||||||
@@ -491,10 +498,12 @@ void StateManager::randomizeOnDemoStart(bool is_lite) {
|
|||||||
// 2. Escenario
|
// 2. Escenario
|
||||||
int auto_max = std::min(engine_->getMaxAutoScenario(), DEMO_AUTO_MAX_SCENARIO);
|
int auto_max = std::min(engine_->getMaxAutoScenario(), DEMO_AUTO_MAX_SCENARIO);
|
||||||
std::vector<int> candidates;
|
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);
|
candidates.push_back(i);
|
||||||
if (engine_->isCustomScenarioEnabled() && engine_->isCustomAutoAvailable())
|
}
|
||||||
|
if (engine_->isCustomScenarioEnabled() && engine_->isCustomAutoAvailable()) {
|
||||||
candidates.push_back(CUSTOM_SCENARIO_IDX);
|
candidates.push_back(CUSTOM_SCENARIO_IDX);
|
||||||
|
}
|
||||||
int new_scenario = candidates[rand() % candidates.size()];
|
int new_scenario = candidates[rand() % candidates.size()];
|
||||||
SimulationMode current_sim_mode = shape_mgr_->getCurrentMode();
|
SimulationMode current_sim_mode = shape_mgr_->getCurrentMode();
|
||||||
scene_mgr_->changeScenario(new_scenario, current_sim_mode);
|
scene_mgr_->changeScenario(new_scenario, current_sim_mode);
|
||||||
@@ -513,7 +522,7 @@ void StateManager::randomizeOnDemoStart(bool is_lite) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 5. Gravedad
|
// 5. Gravedad
|
||||||
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
|
auto new_direction = static_cast<GravityDirection>(rand() % 4);
|
||||||
scene_mgr_->changeGravityDirection(new_direction);
|
scene_mgr_->changeGravityDirection(new_direction);
|
||||||
if (rand() % 3 == 0) {
|
if (rand() % 3 == 0) {
|
||||||
toggleGravityOnOff();
|
toggleGravityOnOff();
|
||||||
@@ -526,7 +535,9 @@ void StateManager::randomizeOnDemoStart(bool is_lite) {
|
|||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
void StateManager::toggleGravityOnOff() {
|
void StateManager::toggleGravityOnOff() {
|
||||||
if (!scene_mgr_) return;
|
if (scene_mgr_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bool gravity_enabled = scene_mgr_->hasBalls() &&
|
bool gravity_enabled = scene_mgr_->hasBalls() &&
|
||||||
(scene_mgr_->getFirstBall()->getGravityForce() > 0.0f);
|
(scene_mgr_->getFirstBall()->getGravityForce() > 0.0f);
|
||||||
@@ -543,7 +554,9 @@ void StateManager::toggleGravityOnOff() {
|
|||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
void StateManager::enterLogoMode(bool from_demo, int current_screen_width, int current_screen_height, size_t ball_count) {
|
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;
|
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
|
// Cambiar a tema aleatorio entre: MONOCHROME, LAVENDER, CRIMSON, ESMERALDA
|
||||||
int logo_themes[] = {5, 6, 7, 8};
|
constexpr std::array<int, 4> LOGO_THEMES = {5, 6, 7, 8};
|
||||||
theme_mgr_->switchToTheme(logo_themes[rand() % 4]);
|
theme_mgr_->switchToTheme(LOGO_THEMES[rand() % 4]);
|
||||||
|
|
||||||
// Establecer escala a 120%
|
// Establecer escala a 120%
|
||||||
shape_mgr_->setShapeScaleFactor(LOGO_MODE_SHAPE_SCALE);
|
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);
|
engine_->enterShapeMode(ShapeType::PNG_SHAPE);
|
||||||
|
|
||||||
// Configurar PNG_SHAPE en modo LOGO
|
// Configurar PNG_SHAPE en modo LOGO
|
||||||
PNGShape* png_shape = dynamic_cast<PNGShape*>(shape_mgr_->getActiveShape());
|
auto* png_shape = dynamic_cast<PNGShape*>(shape_mgr_->getActiveShape());
|
||||||
if (png_shape) {
|
if (png_shape != nullptr) {
|
||||||
png_shape->setLogoMode(true);
|
png_shape->setLogoMode(true);
|
||||||
png_shape->resetFlipCount();
|
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) {
|
void StateManager::exitLogoMode(bool return_to_demo) {
|
||||||
if (current_app_mode_ != AppMode::LOGO) return;
|
if (current_app_mode_ != AppMode::LOGO) {
|
||||||
if (!engine_ || !scene_mgr_ || !theme_mgr_ || !shape_mgr_) return;
|
return;
|
||||||
|
}
|
||||||
|
if ((engine_ == nullptr) || (scene_mgr_ == nullptr) || (theme_mgr_ == nullptr) || (shape_mgr_ == nullptr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
logo_entered_manually_ = false;
|
logo_entered_manually_ = false;
|
||||||
|
|
||||||
@@ -624,17 +641,15 @@ void StateManager::exitLogoMode(bool return_to_demo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Desactivar modo LOGO en PNG_SHAPE
|
// Desactivar modo LOGO en PNG_SHAPE
|
||||||
PNGShape* png_shape = dynamic_cast<PNGShape*>(shape_mgr_->getActiveShape());
|
auto* png_shape = dynamic_cast<PNGShape*>(shape_mgr_->getActiveShape());
|
||||||
if (png_shape) {
|
if (png_shape != nullptr) {
|
||||||
png_shape->setLogoMode(false);
|
png_shape->setLogoMode(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si la figura activa es PNG_SHAPE, cambiar a otra figura aleatoria
|
// Si la figura activa es PNG_SHAPE, cambiar a otra figura aleatoria
|
||||||
if (shape_mgr_->getCurrentShapeType() == ShapeType::PNG_SHAPE) {
|
if (shape_mgr_->getCurrentShapeType() == ShapeType::PNG_SHAPE) {
|
||||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX,
|
constexpr std::array<ShapeType, 8> SHAPES = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||||
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
|
engine_->enterShapeMode(SHAPES[rand() % 8]);
|
||||||
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
|
||||||
engine_->enterShapeMode(shapes[rand() % 8]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!return_to_demo) {
|
if (!return_to_demo) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SDL3/SDL_stdinc.h> // for Uint64
|
#include <SDL3/SDL_stdinc.h> // for Uint64
|
||||||
|
|
||||||
#include <cstddef> // for size_t
|
#include <cstddef> // for size_t
|
||||||
|
|
||||||
#include "defines.hpp" // for AppMode, ShapeType, GravityDirection
|
#include "defines.hpp" // for AppMode, ShapeType, GravityDirection
|
||||||
@@ -37,7 +38,7 @@ class StateManager {
|
|||||||
/**
|
/**
|
||||||
* @brief Destructor
|
* @brief Destructor
|
||||||
*/
|
*/
|
||||||
~StateManager();
|
~StateManager() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Inicializa el StateManager con referencias a los subsistemas necesarios
|
* @brief Inicializa el StateManager con referencias a los subsistemas necesarios
|
||||||
|
|||||||
@@ -1,24 +1,32 @@
|
|||||||
#include "textrenderer.hpp"
|
#include "textrenderer.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3_ttf/SDL_ttf.h>
|
#include <SDL3_ttf/SDL_ttf.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "resource_manager.hpp"
|
#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() {
|
TextRenderer::~TextRenderer() {
|
||||||
cleanup();
|
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;
|
renderer_ = renderer;
|
||||||
font_size_ = font_size;
|
font_size_ = font_size;
|
||||||
use_antialiasing_ = use_antialiasing;
|
use_antialiasing_ = use_antialiasing;
|
||||||
font_path_ = font_path; // Guardar ruta para reinitialize()
|
font_path_ = font_path; // Guardar ruta para reinitialize()
|
||||||
|
|
||||||
// Inicializar SDL_ttf si no está inicializado
|
// Inicializar SDL_ttf si no está inicializado
|
||||||
if (!TTF_WasInit()) {
|
if (TTF_WasInit() == 0) {
|
||||||
if (!TTF_Init()) {
|
if (!TTF_Init()) {
|
||||||
SDL_Log("Error al inicializar SDL_ttf: %s", SDL_GetError());
|
SDL_Log("Error al inicializar SDL_ttf: %s", SDL_GetError());
|
||||||
return false;
|
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)
|
// Intentar cargar la fuente desde ResourceManager (pack o disco)
|
||||||
unsigned char* fontData = nullptr;
|
unsigned char* font_data = nullptr;
|
||||||
size_t fontDataSize = 0;
|
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
|
// Crear SDL_IOStream desde memoria
|
||||||
SDL_IOStream* fontIO = SDL_IOFromConstMem(fontData, static_cast<size_t>(fontDataSize));
|
SDL_IOStream* font_io = SDL_IOFromConstMem(font_data, font_data_size);
|
||||||
if (fontIO != nullptr) {
|
if (font_io != nullptr) {
|
||||||
// Cargar fuente desde IOStream
|
// 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) {
|
if (font_ == nullptr) {
|
||||||
SDL_Log("Error al cargar fuente desde memoria '%s': %s", font_path, SDL_GetError());
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CRÍTICO: NO eliminar fontData aquí - SDL_ttf necesita estos datos en memoria
|
// CRÍTICO: NO eliminar fontData aquí - SDL_ttf necesita estos datos en memoria
|
||||||
// mientras la fuente esté abierta. Se liberará en cleanup()
|
// 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);
|
std::string fn = std::string(font_path);
|
||||||
fn = fn.substr(fn.find_last_of("\\/") + 1);
|
fn = fn.substr(fn.find_last_of("\\/") + 1);
|
||||||
std::cout << "[Fuente] " << fn << " (" << (ResourceManager::isPackLoaded() ? "pack" : "disco") << ")\n";
|
std::cout << "[Fuente] " << fn << " (" << (ResourceManager::isPackLoaded() ? "pack" : "disco") << ")\n";
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
delete[] fontData;
|
|
||||||
}
|
}
|
||||||
|
delete[] font_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback final: intentar cargar directamente desde disco (por si falla ResourceManager)
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextRenderer::reinitialize(int new_font_size) {
|
auto TextRenderer::reinitialize(int new_font_size) -> bool {
|
||||||
// Verificar que tenemos todo lo necesario
|
// Verificar que tenemos todo lo necesario
|
||||||
if (renderer_ == nullptr || font_path_.empty()) {
|
if (renderer_ == nullptr || font_path_.empty()) {
|
||||||
SDL_Log("Error: TextRenderer no inicializado correctamente para reinitialize()");
|
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
|
// Intentar cargar la fuente desde ResourceManager con el nuevo tamaño
|
||||||
unsigned char* fontData = nullptr;
|
unsigned char* font_data = nullptr;
|
||||||
size_t fontDataSize = 0;
|
size_t font_data_size = 0;
|
||||||
|
|
||||||
if (ResourceManager::loadResource(font_path_, fontData, fontDataSize)) {
|
if (ResourceManager::loadResource(font_path_, font_data, font_data_size)) {
|
||||||
SDL_IOStream* fontIO = SDL_IOFromConstMem(fontData, static_cast<size_t>(fontDataSize));
|
SDL_IOStream* font_io = SDL_IOFromConstMem(font_data, font_data_size);
|
||||||
if (fontIO != nullptr) {
|
if (font_io != nullptr) {
|
||||||
font_ = TTF_OpenFontIO(fontIO, true, new_font_size);
|
font_ = TTF_OpenFontIO(font_io, true, new_font_size);
|
||||||
|
|
||||||
if (font_ == nullptr) {
|
if (font_ == nullptr) {
|
||||||
SDL_Log("Error al recargar fuente '%s' con tamaño %d: %s",
|
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(),
|
||||||
delete[] fontData; // Liberar solo si falla
|
new_font_size,
|
||||||
|
SDL_GetError());
|
||||||
|
delete[] font_data; // Liberar solo si falla
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mantener buffer en memoria (NO eliminar)
|
// Mantener buffer en memoria (NO eliminar)
|
||||||
font_data_buffer_ = fontData;
|
font_data_buffer_ = font_data;
|
||||||
font_size_ = new_font_size;
|
font_size_ = new_font_size;
|
||||||
{
|
{
|
||||||
std::string fn = font_path_.substr(font_path_.find_last_of("\\/") + 1);
|
std::string fn = font_path_.substr(font_path_.find_last_of("\\/") + 1);
|
||||||
std::cout << "[Fuente] " << fn << " (" << (ResourceManager::isPackLoaded() ? "pack" : "disco") << ")\n";
|
std::cout << "[Fuente] " << fn << " (" << (ResourceManager::isPackLoaded() ? "pack" : "disco") << ")\n";
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
delete[] fontData;
|
|
||||||
}
|
}
|
||||||
|
delete[] font_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: cargar directamente desde disco
|
// Fallback: cargar directamente desde disco
|
||||||
font_ = TTF_OpenFont(font_path_.c_str(), new_font_size);
|
font_ = TTF_OpenFont(font_path_.c_str(), new_font_size);
|
||||||
if (font_ == nullptr) {
|
if (font_ == nullptr) {
|
||||||
SDL_Log("Error al recargar fuente '%s' con tamaño %d: %s",
|
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;
|
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);
|
dest_rect.h = static_cast<float>(text_surface->h);
|
||||||
|
|
||||||
// Deshabilitar temporalmente presentación lógica para renderizar en píxeles físicos
|
// 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_RendererLogicalPresentation presentation_mode;
|
||||||
SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &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);
|
dest_rect.h = static_cast<float>(text_surface->h);
|
||||||
|
|
||||||
// Deshabilitar temporalmente presentación lógica para renderizar en píxeles físicos
|
// 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_RendererLogicalPresentation presentation_mode;
|
||||||
SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &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());
|
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) {
|
if (!isInitialized() || text == nullptr) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -345,7 +357,7 @@ int TextRenderer::getTextWidth(const char* text) {
|
|||||||
return width;
|
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)
|
// Retorna el ancho REAL en píxeles físicos (sin escalado lógico)
|
||||||
// Idéntico a getTextWidth() pero semánticamente diferente:
|
// Idéntico a getTextWidth() pero semánticamente diferente:
|
||||||
// - Este método se usa cuando se necesita el ancho REAL de la fuente
|
// - 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
|
return width; // Ancho real de la textura generada por TTF
|
||||||
}
|
}
|
||||||
|
|
||||||
int TextRenderer::getTextHeight() {
|
auto TextRenderer::getTextHeight() -> int {
|
||||||
if (!isInitialized()) {
|
if (!isInitialized()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -370,7 +382,7 @@ int TextRenderer::getTextHeight() {
|
|||||||
return TTF_GetFontHeight(font_);
|
return TTF_GetFontHeight(font_);
|
||||||
}
|
}
|
||||||
|
|
||||||
int TextRenderer::getGlyphHeight() {
|
auto TextRenderer::getGlyphHeight() -> int {
|
||||||
if (!isInitialized()) {
|
if (!isInitialized()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3_ttf/SDL_ttf.h>
|
#include <SDL3_ttf/SDL_ttf.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class TextRenderer {
|
class TextRenderer {
|
||||||
public:
|
public:
|
||||||
TextRenderer();
|
TextRenderer();
|
||||||
~TextRenderer();
|
~TextRenderer();
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ public:
|
|||||||
// Verifica si está inicializado correctamente
|
// Verifica si está inicializado correctamente
|
||||||
bool isInitialized() const { return font_ != nullptr && renderer_ != nullptr; }
|
bool isInitialized() const { return font_ != nullptr && renderer_ != nullptr; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_Renderer* renderer_;
|
SDL_Renderer* renderer_;
|
||||||
TTF_Font* font_;
|
TTF_Font* font_;
|
||||||
int font_size_;
|
int font_size_;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -54,8 +54,7 @@ class ThemeManager {
|
|||||||
|
|
||||||
// Queries de colores (usado en rendering)
|
// Queries de colores (usado en rendering)
|
||||||
Color getInterpolatedColor(size_t ball_index) const; // Obtiene color interpolado para pelota
|
Color getInterpolatedColor(size_t ball_index) const; // Obtiene color interpolado para pelota
|
||||||
void getBackgroundColors(float& top_r, float& top_g, float& top_b,
|
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
|
||||||
float& bottom_r, float& bottom_g, float& bottom_b) const; // Obtiene colores de fondo degradado
|
|
||||||
|
|
||||||
// Queries de estado (para debug display y lógica)
|
// Queries de estado (para debug display y lógica)
|
||||||
int getCurrentThemeIndex() const { return current_theme_index_; }
|
int getCurrentThemeIndex() const { return current_theme_index_; }
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
#include "dynamic_theme.hpp"
|
#include "dynamic_theme.hpp"
|
||||||
|
|
||||||
#include <algorithm> // for std::min
|
#include <algorithm> // for std::min
|
||||||
|
|
||||||
DynamicTheme::DynamicTheme(const char* name_en, const char* name_es,
|
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)
|
||||||
int text_r, int text_g, int text_b,
|
|
||||||
std::vector<DynamicThemeKeyframe> keyframes,
|
|
||||||
bool loop)
|
|
||||||
: name_en_(name_en),
|
: name_en_(name_en),
|
||||||
name_es_(name_es),
|
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)),
|
keyframes_(std::move(keyframes)),
|
||||||
loop_(loop),
|
loop_(loop) {
|
||||||
current_keyframe_index_(0),
|
|
||||||
target_keyframe_index_(1),
|
|
||||||
transition_progress_(0.0f),
|
|
||||||
paused_(false) {
|
|
||||||
// Validación: mínimo 2 keyframes
|
// Validación: mínimo 2 keyframes
|
||||||
if (keyframes_.size() < 2) {
|
if (keyframes_.size() < 2) {
|
||||||
// Fallback: duplicar primer keyframe si solo hay 1
|
// 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) {
|
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
|
// Obtener duración del keyframe objetivo
|
||||||
float duration = keyframes_[target_keyframe_index_].duration;
|
float duration = keyframes_[target_keyframe_index_].duration;
|
||||||
@@ -74,14 +72,14 @@ void DynamicTheme::advanceToNextKeyframe() {
|
|||||||
transition_progress_ = 0.0f;
|
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
|
// Obtener keyframes actual y objetivo
|
||||||
const auto& current_kf = keyframes_[current_keyframe_index_];
|
const auto& current_kf = keyframes_[current_keyframe_index_];
|
||||||
const auto& target_kf = keyframes_[target_keyframe_index_];
|
const auto& target_kf = keyframes_[target_keyframe_index_];
|
||||||
|
|
||||||
// Si paletas vacías, retornar blanco
|
// Si paletas vacías, retornar blanco
|
||||||
if (current_kf.ball_colors.empty() || target_kf.ball_colors.empty()) {
|
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)
|
// 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)
|
// (progress parámetro será usado en PHASE 3 para LERP externo)
|
||||||
float t = transition_progress_;
|
float t = transition_progress_;
|
||||||
return {
|
return {
|
||||||
static_cast<int>(lerp(c1.r, c2.r, t)),
|
.r = static_cast<int>(lerp(c1.r, c2.r, t)),
|
||||||
static_cast<int>(lerp(c1.g, c2.g, t)),
|
.g = static_cast<int>(lerp(c1.g, c2.g, t)),
|
||||||
static_cast<int>(lerp(c1.b, c2.b, t))
|
.b = static_cast<int>(lerp(c1.b, c2.b, t))};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DynamicTheme::getBackgroundColors(float progress,
|
void DynamicTheme::getBackgroundColors(float progress,
|
||||||
float& tr, float& tg, float& tb,
|
float& tr,
|
||||||
float& br, float& bg, float& bb) const {
|
float& tg,
|
||||||
|
float& tb,
|
||||||
|
float& br,
|
||||||
|
float& bg,
|
||||||
|
float& bb) const {
|
||||||
// Obtener keyframes actual y objetivo
|
// Obtener keyframes actual y objetivo
|
||||||
const auto& current_kf = keyframes_[current_keyframe_index_];
|
const auto& current_kf = keyframes_[current_keyframe_index_];
|
||||||
const auto& target_kf = keyframes_[target_keyframe_index_];
|
const auto& target_kf = keyframes_[target_keyframe_index_];
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "theme.hpp"
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "theme.hpp"
|
||||||
|
|
||||||
// Forward declaration (estructura definida en defines.h)
|
// Forward declaration (estructura definida en defines.h)
|
||||||
struct DynamicThemeKeyframe;
|
struct DynamicThemeKeyframe;
|
||||||
|
|
||||||
@@ -30,10 +31,7 @@ class DynamicTheme : public Theme {
|
|||||||
* @param keyframes: Vector de keyframes (mínimo 2)
|
* @param keyframes: Vector de keyframes (mínimo 2)
|
||||||
* @param loop: ¿Volver al inicio al terminar? (siempre true en esta app)
|
* @param loop: ¿Volver al inicio al terminar? (siempre true en esta app)
|
||||||
*/
|
*/
|
||||||
DynamicTheme(const char* name_en, const char* name_es,
|
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);
|
||||||
int text_r, int text_g, int text_b,
|
|
||||||
std::vector<DynamicThemeKeyframe> keyframes,
|
|
||||||
bool loop = true);
|
|
||||||
|
|
||||||
~DynamicTheme() override = default;
|
~DynamicTheme() override = default;
|
||||||
|
|
||||||
@@ -56,8 +54,12 @@ class DynamicTheme : public Theme {
|
|||||||
|
|
||||||
Color getBallColor(size_t ball_index, float progress) const override;
|
Color getBallColor(size_t ball_index, float progress) const override;
|
||||||
void getBackgroundColors(float progress,
|
void getBackgroundColors(float progress,
|
||||||
float& tr, float& tg, float& tb,
|
float& tr,
|
||||||
float& br, float& bg, float& bb) const override;
|
float& tg,
|
||||||
|
float& tb,
|
||||||
|
float& br,
|
||||||
|
float& bg,
|
||||||
|
float& bb) const override;
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// ANIMACIÓN (soporte completo)
|
// ANIMACIÓN (soporte completo)
|
||||||
|
|||||||
@@ -1,32 +1,39 @@
|
|||||||
#include "static_theme.hpp"
|
#include "static_theme.hpp"
|
||||||
|
|
||||||
StaticTheme::StaticTheme(const char* name_en, const char* name_es,
|
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)
|
||||||
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_en_(name_en),
|
||||||
name_es_(name_es),
|
name_es_(name_es),
|
||||||
text_r_(text_r), text_g_(text_g), text_b_(text_b),
|
text_r_(text_r),
|
||||||
notif_bg_r_(notif_bg_r), notif_bg_g_(notif_bg_g), notif_bg_b_(notif_bg_b),
|
text_g_(text_g),
|
||||||
bg_top_r_(bg_top_r), bg_top_g_(bg_top_g), bg_top_b_(bg_top_b),
|
text_b_(text_b),
|
||||||
bg_bottom_r_(bg_bottom_r), bg_bottom_g_(bg_bottom_g), bg_bottom_b_(bg_bottom_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)) {
|
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
|
// Tema estático: siempre retorna color de paleta según índice
|
||||||
// (progress se ignora aquí, pero será usado en PHASE 3 para LERP externo)
|
// (progress se ignora aquí, pero será usado en PHASE 3 para LERP externo)
|
||||||
if (ball_colors_.empty()) {
|
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()];
|
return ball_colors_[ball_index % ball_colors_.size()];
|
||||||
}
|
}
|
||||||
|
|
||||||
void StaticTheme::getBackgroundColors(float progress,
|
void StaticTheme::getBackgroundColors(float progress,
|
||||||
float& tr, float& tg, float& tb,
|
float& tr,
|
||||||
float& br, float& bg, float& bb) const {
|
float& tg,
|
||||||
|
float& tb,
|
||||||
|
float& br,
|
||||||
|
float& bg,
|
||||||
|
float& bb) const {
|
||||||
// Tema estático: siempre retorna colores de fondo fijos
|
// Tema estático: siempre retorna colores de fondo fijos
|
||||||
// (progress se ignora aquí, pero será usado en PHASE 3 para LERP externo)
|
// (progress se ignora aquí, pero será usado en PHASE 3 para LERP externo)
|
||||||
tr = bg_top_r_;
|
tr = bg_top_r_;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "theme.hpp"
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "theme.hpp"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StaticTheme: Tema estático con 1 keyframe (sin animación)
|
* 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 bg_bottom_r, bg_bottom_g, bg_bottom_b: Color inferior de fondo
|
||||||
* @param ball_colors: Paleta de colores para pelotas
|
* @param ball_colors: Paleta de colores para pelotas
|
||||||
*/
|
*/
|
||||||
StaticTheme(const char* name_en, const char* name_es,
|
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);
|
||||||
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;
|
~StaticTheme() override = default;
|
||||||
|
|
||||||
@@ -60,8 +56,12 @@ class StaticTheme : public Theme {
|
|||||||
|
|
||||||
Color getBallColor(size_t ball_index, float progress) const override;
|
Color getBallColor(size_t ball_index, float progress) const override;
|
||||||
void getBackgroundColors(float progress,
|
void getBackgroundColors(float progress,
|
||||||
float& tr, float& tg, float& tb,
|
float& tr,
|
||||||
float& br, float& bg, float& bb) const override;
|
float& tg,
|
||||||
|
float& tb,
|
||||||
|
float& br,
|
||||||
|
float& bg,
|
||||||
|
float& bb) const override;
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// ANIMACIÓN (sin soporte - tema estático)
|
// ANIMACIÓN (sin soporte - tema estático)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "defines.hpp" // for Color, ThemeKeyframe
|
#include "defines.hpp" // for Color, ThemeKeyframe
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,8 +48,12 @@ class Theme {
|
|||||||
* @param br, bg, bb: Color inferior (out)
|
* @param br, bg, bb: Color inferior (out)
|
||||||
*/
|
*/
|
||||||
virtual void getBackgroundColors(float progress,
|
virtual void getBackgroundColors(float progress,
|
||||||
float& tr, float& tg, float& tb,
|
float& tr,
|
||||||
float& br, float& bg, float& bb) const = 0;
|
float& tg,
|
||||||
|
float& tb,
|
||||||
|
float& br,
|
||||||
|
float& bg,
|
||||||
|
float& bb) const = 0;
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// ANIMACIÓN (solo temas dinámicos)
|
// ANIMACIÓN (solo temas dinámicos)
|
||||||
@@ -58,7 +63,7 @@ class Theme {
|
|||||||
* Actualiza progreso de animación interna (solo dinámicos)
|
* Actualiza progreso de animación interna (solo dinámicos)
|
||||||
* @param delta_time: Tiempo transcurrido desde último frame
|
* @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?
|
* ¿Este tema necesita update() cada frame?
|
||||||
@@ -75,7 +80,7 @@ class Theme {
|
|||||||
/**
|
/**
|
||||||
* Reinicia progreso de animación a 0.0 (usado al activar tema)
|
* Reinicia progreso de animación a 0.0 (usado al activar tema)
|
||||||
*/
|
*/
|
||||||
virtual void resetProgress() { }
|
virtual void resetProgress() {}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// PAUSA (solo temas dinámicos)
|
// PAUSA (solo temas dinámicos)
|
||||||
@@ -90,5 +95,5 @@ class Theme {
|
|||||||
/**
|
/**
|
||||||
* Toggle pausa de animación (solo dinámicos, tecla Shift+D)
|
* Toggle pausa de animación (solo dinámicos, tecla Shift+D)
|
||||||
*/
|
*/
|
||||||
virtual void togglePause() { }
|
virtual void togglePause() {}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "defines.hpp" // for Color
|
#include "defines.hpp" // for Color
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
#include "app_logo.hpp"
|
#include "app_logo.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL_render.h> // for SDL_DestroyTexture, SDL_RenderGeometry, SDL_SetTextureAlphaMod
|
#include <SDL3/SDL_render.h> // for SDL_DestroyTexture, SDL_RenderGeometry, SDL_SetTextureAlphaMod
|
||||||
|
|
||||||
|
#include <array> // for std::array
|
||||||
#include <cmath> // for powf, sinf, cosf
|
#include <cmath> // for powf, sinf, cosf
|
||||||
#include <cstdlib> // for free()
|
#include <cstdlib> // for free()
|
||||||
#include <iostream> // for std::cout
|
#include <iostream> // for std::cout
|
||||||
|
#include <numbers>
|
||||||
|
|
||||||
#include "logo_scaler.hpp" // for LogoScaler
|
|
||||||
#include "defines.hpp" // for APPLOGO_HEIGHT_PERCENT, getResourcesDirectory
|
#include "defines.hpp" // for APPLOGO_HEIGHT_PERCENT, getResourcesDirectory
|
||||||
|
#include "logo_scaler.hpp" // for LogoScaler
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Destructor - Liberar las 4 texturas SDL
|
// Destructor - Liberar las 4 texturas SDL
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
AppLogo::~AppLogo() {
|
AppLogo::~AppLogo() {
|
||||||
if (logo1_base_texture_) {
|
if (logo1_base_texture_ != nullptr) {
|
||||||
SDL_DestroyTexture(logo1_base_texture_);
|
SDL_DestroyTexture(logo1_base_texture_);
|
||||||
logo1_base_texture_ = nullptr;
|
logo1_base_texture_ = nullptr;
|
||||||
}
|
}
|
||||||
if (logo1_native_texture_) {
|
if (logo1_native_texture_ != nullptr) {
|
||||||
SDL_DestroyTexture(logo1_native_texture_);
|
SDL_DestroyTexture(logo1_native_texture_);
|
||||||
logo1_native_texture_ = nullptr;
|
logo1_native_texture_ = nullptr;
|
||||||
}
|
}
|
||||||
if (logo2_base_texture_) {
|
if (logo2_base_texture_ != nullptr) {
|
||||||
SDL_DestroyTexture(logo2_base_texture_);
|
SDL_DestroyTexture(logo2_base_texture_);
|
||||||
logo2_base_texture_ = nullptr;
|
logo2_base_texture_ = nullptr;
|
||||||
}
|
}
|
||||||
if (logo2_native_texture_) {
|
if (logo2_native_texture_ != nullptr) {
|
||||||
SDL_DestroyTexture(logo2_native_texture_);
|
SDL_DestroyTexture(logo2_native_texture_);
|
||||||
logo2_native_texture_ = nullptr;
|
logo2_native_texture_ = nullptr;
|
||||||
}
|
}
|
||||||
@@ -35,11 +38,23 @@ AppLogo::~AppLogo() {
|
|||||||
// Inicialización - Pre-escalar logos a 2 resoluciones (base y nativa)
|
// Inicialización - Pre-escalar logos a 2 resoluciones (base y nativa)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) {
|
auto AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) -> bool {
|
||||||
if (logo1_base_texture_) { SDL_DestroyTexture(logo1_base_texture_); logo1_base_texture_ = nullptr; }
|
if (logo1_base_texture_ != nullptr) {
|
||||||
if (logo1_native_texture_) { SDL_DestroyTexture(logo1_native_texture_); logo1_native_texture_ = nullptr; }
|
SDL_DestroyTexture(logo1_base_texture_);
|
||||||
if (logo2_base_texture_) { SDL_DestroyTexture(logo2_base_texture_); logo2_base_texture_ = nullptr; }
|
logo1_base_texture_ = nullptr;
|
||||||
if (logo2_native_texture_) { SDL_DestroyTexture(logo2_native_texture_); logo2_native_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;
|
renderer_ = renderer;
|
||||||
base_screen_width_ = screen_width;
|
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
|
// 1. Detectar resolución nativa del monitor
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
if (!LogoScaler::detectNativeResolution(native_screen_width_, native_screen_height_)) {
|
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
|
// Fallback: usar resolución base como nativa
|
||||||
native_screen_width_ = screen_width;
|
native_screen_width_ = screen_width;
|
||||||
native_screen_height_ = screen_height;
|
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
|
0, // width calculado automáticamente por aspect ratio
|
||||||
logo_base_target_height,
|
logo_base_target_height,
|
||||||
logo1_base_width_,
|
logo1_base_width_,
|
||||||
logo1_base_height_
|
logo1_base_height_);
|
||||||
);
|
|
||||||
if (logo1_base_data == nullptr) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logo1_base_texture_ = LogoScaler::createTextureFromBuffer(
|
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
|
free(logo1_base_data); // Liberar buffer temporal
|
||||||
|
|
||||||
if (logo1_base_texture_ == nullptr) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,20 +118,21 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
|||||||
0, // width calculado automáticamente
|
0, // width calculado automáticamente
|
||||||
logo_native_target_height,
|
logo_native_target_height,
|
||||||
logo1_native_width_,
|
logo1_native_width_,
|
||||||
logo1_native_height_
|
logo1_native_height_);
|
||||||
);
|
|
||||||
if (logo1_native_data == nullptr) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logo1_native_texture_ = LogoScaler::createTextureFromBuffer(
|
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);
|
free(logo1_native_data);
|
||||||
|
|
||||||
if (logo1_native_texture_ == nullptr) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,20 +149,21 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
|||||||
0,
|
0,
|
||||||
logo_base_target_height,
|
logo_base_target_height,
|
||||||
logo2_base_width_,
|
logo2_base_width_,
|
||||||
logo2_base_height_
|
logo2_base_height_);
|
||||||
);
|
|
||||||
if (logo2_base_data == nullptr) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logo2_base_texture_ = LogoScaler::createTextureFromBuffer(
|
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);
|
free(logo2_base_data);
|
||||||
|
|
||||||
if (logo2_base_texture_ == nullptr) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,20 +175,21 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
|||||||
0,
|
0,
|
||||||
logo_native_target_height,
|
logo_native_target_height,
|
||||||
logo2_native_width_,
|
logo2_native_width_,
|
||||||
logo2_native_height_
|
logo2_native_height_);
|
||||||
);
|
|
||||||
if (logo2_native_data == nullptr) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logo2_native_texture_ = LogoScaler::createTextureFromBuffer(
|
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);
|
free(logo2_native_data);
|
||||||
|
|
||||||
if (logo2_native_texture_ == nullptr) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +210,7 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
|
|||||||
return true;
|
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)
|
// Si estamos en SANDBOX, resetear y no hacer nada (logo desactivado)
|
||||||
if (current_mode == AppMode::SANDBOX) {
|
if (current_mode == AppMode::SANDBOX) {
|
||||||
state_ = AppLogoState::HIDDEN;
|
state_ = AppLogoState::HIDDEN;
|
||||||
@@ -262,8 +281,7 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
|||||||
logo2_rotation_ = 0.0f;
|
logo2_rotation_ = 0.0f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AppLogoAnimationType::ELASTIC_STICK:
|
case AppLogoAnimationType::ELASTIC_STICK: {
|
||||||
{
|
|
||||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||||
float elastic_t1 = easeOutElastic(prog1);
|
float elastic_t1 = easeOutElastic(prog1);
|
||||||
logo1_scale_ = 1.2f - (elastic_t1 * 0.2f);
|
logo1_scale_ = 1.2f - (elastic_t1 * 0.2f);
|
||||||
@@ -279,11 +297,9 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
|||||||
logo2_squash_y_ = 0.6f + (squash_t2 * 0.4f);
|
logo2_squash_y_ = 0.6f + (squash_t2 * 0.4f);
|
||||||
logo2_stretch_x_ = 1.0f + (1.0f - logo2_squash_y_) * 0.5f;
|
logo2_stretch_x_ = 1.0f + (1.0f - logo2_squash_y_) * 0.5f;
|
||||||
logo2_rotation_ = 0.0f;
|
logo2_rotation_ = 0.0f;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case AppLogoAnimationType::ROTATE_SPIRAL:
|
case AppLogoAnimationType::ROTATE_SPIRAL: {
|
||||||
{
|
|
||||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||||
float ease_t1 = easeInOutQuad(prog1);
|
float ease_t1 = easeInOutQuad(prog1);
|
||||||
logo1_scale_ = 0.3f + (ease_t1 * 0.7f);
|
logo1_scale_ = 0.3f + (ease_t1 * 0.7f);
|
||||||
@@ -297,11 +313,9 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
|||||||
logo2_rotation_ = (1.0f - prog2) * 6.28f;
|
logo2_rotation_ = (1.0f - prog2) * 6.28f;
|
||||||
logo2_squash_y_ = 1.0f;
|
logo2_squash_y_ = 1.0f;
|
||||||
logo2_stretch_x_ = 1.0f;
|
logo2_stretch_x_ = 1.0f;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case AppLogoAnimationType::BOUNCE_SQUASH:
|
case AppLogoAnimationType::BOUNCE_SQUASH: {
|
||||||
{
|
|
||||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||||
float bounce_t1 = easeOutBounce(prog1);
|
float bounce_t1 = easeOutBounce(prog1);
|
||||||
logo1_scale_ = 1.0f;
|
logo1_scale_ = 1.0f;
|
||||||
@@ -317,8 +331,7 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
|||||||
logo2_squash_y_ = 1.0f - squash_amount2;
|
logo2_squash_y_ = 1.0f - squash_amount2;
|
||||||
logo2_stretch_x_ = 1.0f + squash_amount2 * 0.5f;
|
logo2_stretch_x_ = 1.0f + squash_amount2 * 0.5f;
|
||||||
logo2_rotation_ = 0.0f;
|
logo2_rotation_ = 0.0f;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,8 +392,7 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
|||||||
logo2_rotation_ = 0.0f;
|
logo2_rotation_ = 0.0f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AppLogoAnimationType::ELASTIC_STICK:
|
case AppLogoAnimationType::ELASTIC_STICK: {
|
||||||
{
|
|
||||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||||
logo1_scale_ = 1.0f + (prog1 * prog1 * 0.2f);
|
logo1_scale_ = 1.0f + (prog1 * prog1 * 0.2f);
|
||||||
logo1_squash_y_ = 1.0f + (prog1 * 0.3f);
|
logo1_squash_y_ = 1.0f + (prog1 * 0.3f);
|
||||||
@@ -392,11 +404,9 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
|||||||
logo2_squash_y_ = 1.0f + (prog2 * 0.3f);
|
logo2_squash_y_ = 1.0f + (prog2 * 0.3f);
|
||||||
logo2_stretch_x_ = 1.0f - (prog2 * 0.2f);
|
logo2_stretch_x_ = 1.0f - (prog2 * 0.2f);
|
||||||
logo2_rotation_ = prog2 * 0.1f;
|
logo2_rotation_ = prog2 * 0.1f;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case AppLogoAnimationType::ROTATE_SPIRAL:
|
case AppLogoAnimationType::ROTATE_SPIRAL: {
|
||||||
{
|
|
||||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||||
float ease_t1 = easeInOutQuad(prog1);
|
float ease_t1 = easeInOutQuad(prog1);
|
||||||
logo1_scale_ = 1.0f - (ease_t1 * 0.7f);
|
logo1_scale_ = 1.0f - (ease_t1 * 0.7f);
|
||||||
@@ -410,11 +420,9 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
|||||||
logo2_rotation_ = prog2 * 6.28f;
|
logo2_rotation_ = prog2 * 6.28f;
|
||||||
logo2_squash_y_ = 1.0f;
|
logo2_squash_y_ = 1.0f;
|
||||||
logo2_stretch_x_ = 1.0f;
|
logo2_stretch_x_ = 1.0f;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case AppLogoAnimationType::BOUNCE_SQUASH:
|
case AppLogoAnimationType::BOUNCE_SQUASH: {
|
||||||
{
|
|
||||||
float prog1 = std::min(1.0f, fade_progress_logo1);
|
float prog1 = std::min(1.0f, fade_progress_logo1);
|
||||||
if (prog1 < 0.2f) {
|
if (prog1 < 0.2f) {
|
||||||
float squash_t = prog1 / 0.2f;
|
float squash_t = prog1 / 0.2f;
|
||||||
@@ -440,8 +448,7 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
|
|||||||
}
|
}
|
||||||
logo2_scale_ = 1.0f + (prog2 * 0.3f);
|
logo2_scale_ = 1.0f + (prog2 * 0.3f);
|
||||||
logo2_rotation_ = 0.0f;
|
logo2_rotation_ = 0.0f;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,7 +484,7 @@ void AppLogo::updateScreenSize(int screen_width, int screen_height) {
|
|||||||
logo2_current_width_ = logo2_native_width_;
|
logo2_current_width_ = logo2_native_width_;
|
||||||
logo2_current_height_ = logo2_native_height_;
|
logo2_current_height_ = logo2_native_height_;
|
||||||
|
|
||||||
std::cout << "AppLogo: Cambiado a texturas NATIVAS" << std::endl;
|
std::cout << "AppLogo: Cambiado a texturas NATIVAS" << '\n';
|
||||||
} else {
|
} else {
|
||||||
// Cambiar a texturas base (ventana redimensionable)
|
// Cambiar a texturas base (ventana redimensionable)
|
||||||
logo1_current_texture_ = logo1_base_texture_;
|
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_width_ = logo2_base_width_;
|
||||||
logo2_current_height_ = logo2_base_height_;
|
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
|
// 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
|
// Funciones de easing para animaciones
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
float AppLogo::easeOutElastic(float t) {
|
auto AppLogo::easeOutElastic(float t) -> float {
|
||||||
// Elastic easing out: bounce elástico al final
|
// 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 == 0.0f) {
|
||||||
if (t == 1.0f) return 1.0f;
|
return 0.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 == 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)
|
// Quadratic easing in/out: aceleración suave (para ROTATE_SPIRAL)
|
||||||
if (t < 0.5f) {
|
if (t < 0.5f) {
|
||||||
return 2.0f * t * t;
|
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
|
// 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)
|
// Generar número aleatorio entre 0 y 3 (4 tipos de animación)
|
||||||
int random_value = rand() % 4;
|
int random_value = rand() % 4;
|
||||||
|
|
||||||
@@ -571,15 +582,23 @@ AppLogoAnimationType AppLogo::getRandomAnimation() {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
void AppLogo::renderWithGeometry(int logo_index) {
|
void AppLogo::renderWithGeometry(int logo_index) {
|
||||||
if (!renderer_) return;
|
if (renderer_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Seleccionar variables según el logo_index (1 = logo1, 2 = logo2)
|
// Seleccionar variables según el logo_index (1 = logo1, 2 = logo2)
|
||||||
SDL_Texture* texture;
|
SDL_Texture* texture;
|
||||||
int base_width, base_height;
|
int base_width;
|
||||||
float scale, squash_y, stretch_x, rotation;
|
int base_height;
|
||||||
|
float scale;
|
||||||
|
float squash_y;
|
||||||
|
float stretch_x;
|
||||||
|
float rotation;
|
||||||
|
|
||||||
if (logo_index == 1) {
|
if (logo_index == 1) {
|
||||||
if (!logo1_current_texture_) return;
|
if (logo1_current_texture_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
texture = logo1_current_texture_;
|
texture = logo1_current_texture_;
|
||||||
base_width = logo1_current_width_;
|
base_width = logo1_current_width_;
|
||||||
base_height = logo1_current_height_;
|
base_height = logo1_current_height_;
|
||||||
@@ -588,7 +607,9 @@ void AppLogo::renderWithGeometry(int logo_index) {
|
|||||||
stretch_x = logo1_stretch_x_;
|
stretch_x = logo1_stretch_x_;
|
||||||
rotation = logo1_rotation_;
|
rotation = logo1_rotation_;
|
||||||
} else if (logo_index == 2) {
|
} else if (logo_index == 2) {
|
||||||
if (!logo2_current_texture_) return;
|
if (logo2_current_texture_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
texture = logo2_current_texture_;
|
texture = logo2_current_texture_;
|
||||||
base_width = logo2_current_width_;
|
base_width = logo2_current_width_;
|
||||||
base_height = logo2_current_height_;
|
base_height = logo2_current_height_;
|
||||||
@@ -628,7 +649,7 @@ void AppLogo::renderWithGeometry(int logo_index) {
|
|||||||
float sin_rot = sinf(rotation);
|
float sin_rot = sinf(rotation);
|
||||||
|
|
||||||
// Crear 4 vértices del quad (centrado en center_x, center_y)
|
// 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
|
// Offset desde el centro
|
||||||
float half_w = width / 2.0f;
|
float half_w = width / 2.0f;
|
||||||
@@ -638,49 +659,49 @@ void AppLogo::renderWithGeometry(int logo_index) {
|
|||||||
{
|
{
|
||||||
float local_x = -half_w;
|
float local_x = -half_w;
|
||||||
float local_y = -half_h;
|
float local_y = -half_h;
|
||||||
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
float rotated_x = (local_x * cos_rot) - (local_y * sin_rot);
|
||||||
float rotated_y = local_x * sin_rot + local_y * cos_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].position = {.x = center_x + rotated_x, .y = center_y + rotated_y};
|
||||||
vertices[0].tex_coord = {0.0f, 0.0f};
|
vertices[0].tex_coord = {.x = 0.0f, .y = 0.0f};
|
||||||
vertices[0].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice
|
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)
|
// Vértice superior derecho (rotado)
|
||||||
{
|
{
|
||||||
float local_x = half_w;
|
float local_x = half_w;
|
||||||
float local_y = -half_h;
|
float local_y = -half_h;
|
||||||
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
float rotated_x = (local_x * cos_rot) - (local_y * sin_rot);
|
||||||
float rotated_y = local_x * sin_rot + local_y * cos_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].position = {.x = center_x + rotated_x, .y = center_y + rotated_y};
|
||||||
vertices[1].tex_coord = {1.0f, 0.0f};
|
vertices[1].tex_coord = {.x = 1.0f, .y = 0.0f};
|
||||||
vertices[1].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice
|
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)
|
// Vértice inferior derecho (rotado)
|
||||||
{
|
{
|
||||||
float local_x = half_w;
|
float local_x = half_w;
|
||||||
float local_y = half_h;
|
float local_y = half_h;
|
||||||
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
float rotated_x = (local_x * cos_rot) - (local_y * sin_rot);
|
||||||
float rotated_y = local_x * sin_rot + local_y * cos_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].position = {.x = center_x + rotated_x, .y = center_y + rotated_y};
|
||||||
vertices[2].tex_coord = {1.0f, 1.0f};
|
vertices[2].tex_coord = {.x = 1.0f, .y = 1.0f};
|
||||||
vertices[2].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice
|
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)
|
// Vértice inferior izquierdo (rotado)
|
||||||
{
|
{
|
||||||
float local_x = -half_w;
|
float local_x = -half_w;
|
||||||
float local_y = half_h;
|
float local_y = half_h;
|
||||||
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
float rotated_x = (local_x * cos_rot) - (local_y * sin_rot);
|
||||||
float rotated_y = local_x * sin_rot + local_y * cos_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].position = {.x = center_x + rotated_x, .y = center_y + rotated_y};
|
||||||
vertices[3].tex_coord = {0.0f, 1.0f};
|
vertices[3].tex_coord = {.x = 0.0f, .y = 1.0f};
|
||||||
vertices[3].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice
|
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
|
// Í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
|
// Renderizar con la textura del logo correspondiente
|
||||||
SDL_RenderGeometry(renderer_, texture, vertices, 4, indices, 6);
|
SDL_RenderGeometry(renderer_, texture, vertices.data(), 4, indices.data(), 6);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,11 +107,11 @@ class AppLogo {
|
|||||||
void renderWithGeometry(int logo_index); // Renderizar logo con vértices deformados (1 o 2)
|
void renderWithGeometry(int logo_index); // Renderizar logo con vértices deformados (1 o 2)
|
||||||
|
|
||||||
// Funciones de easing
|
// Funciones de easing
|
||||||
float easeOutElastic(float t); // Elastic bounce out
|
static float easeOutElastic(float t); // Elastic bounce out
|
||||||
float easeOutBack(float t); // Overshoot out
|
static float easeOutBack(float t); // Overshoot out
|
||||||
float easeOutBounce(float t); // Bounce easing (para BOUNCE_SQUASH)
|
static float easeOutBounce(float t); // Bounce easing (para BOUNCE_SQUASH)
|
||||||
float easeInOutQuad(float t); // Quadratic easing (para ROTATE_SPIRAL)
|
static float easeInOutQuad(float t); // Quadratic easing (para ROTATE_SPIRAL)
|
||||||
|
|
||||||
// Función auxiliar para elegir animación aleatoria
|
// Función auxiliar para elegir animación aleatoria
|
||||||
AppLogoAnimationType getRandomAnimation();
|
static AppLogoAnimationType getRandomAnimation();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "help_overlay.hpp"
|
#include "help_overlay.hpp"
|
||||||
|
|
||||||
#include <algorithm> // for std::min
|
#include <algorithm> // for std::min
|
||||||
|
#include <array> // for std::array
|
||||||
|
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include "text/textrenderer.hpp"
|
#include "text/textrenderer.hpp"
|
||||||
@@ -21,69 +22,69 @@ HelpOverlay::HelpOverlay()
|
|||||||
column2_width_(0),
|
column2_width_(0),
|
||||||
column3_width_(0),
|
column3_width_(0),
|
||||||
cached_texture_(nullptr),
|
cached_texture_(nullptr),
|
||||||
last_category_color_({0, 0, 0, 255}),
|
last_category_color_({.r = 0, .g = 0, .b = 0, .a = 255}),
|
||||||
last_content_color_({0, 0, 0, 255}),
|
last_content_color_({.r = 0, .g = 0, .b = 0, .a = 255}),
|
||||||
last_bg_color_({0, 0, 0, 255}),
|
last_bg_color_({.r = 0, .g = 0, .b = 0, .a = 255}),
|
||||||
texture_needs_rebuild_(true) {
|
texture_needs_rebuild_(true) {
|
||||||
// Llenar lista de controles (organizados por categoría, equilibrado en 3 columnas)
|
// Llenar lista de controles (organizados por categoría, equilibrado en 3 columnas)
|
||||||
key_bindings_ = {
|
key_bindings_ = {
|
||||||
// COLUMNA 1: SIMULACIÓN
|
// COLUMNA 1: SIMULACIÓN
|
||||||
{"SIMULACIÓN", ""},
|
{.key = "SIMULACIÓN", .description = ""},
|
||||||
{"1-8", "Escenarios (10 a 50.000 pelotas)"},
|
{.key = "1-8", .description = "Escenarios (10 a 50.000 pelotas)"},
|
||||||
{"F", "Cambia entre figura y física"},
|
{.key = "F", .description = "Cambia entre figura y física"},
|
||||||
{"B", "Cambia entre boids y física"},
|
{.key = "B", .description = "Cambia entre boids y física"},
|
||||||
{"ESPACIO", "Impulso contra la gravedad"},
|
{.key = "ESPACIO", .description = "Impulso contra la gravedad"},
|
||||||
{"G", "Activar / Desactivar gravedad"},
|
{.key = "G", .description = "Activar / Desactivar gravedad"},
|
||||||
{"CURSORES", "Dirección de la gravedad"},
|
{.key = "CURSORES", .description = "Dirección de la gravedad"},
|
||||||
{"", ""}, // Separador
|
{.key = "", .description = ""}, // Separador
|
||||||
|
|
||||||
// COLUMNA 1: FIGURAS 3D
|
// COLUMNA 1: FIGURAS 3D
|
||||||
{"FIGURAS 3D", ""},
|
{.key = "FIGURAS 3D", .description = ""},
|
||||||
{"Q/W/E/R", "Esfera / Lissajous / Hélice / Toroide"},
|
{.key = "Q/W/E/R", .description = "Esfera / Lissajous / Hélice / Toroide"},
|
||||||
{"T/Y/U/I", "Cubo / Cilindro / Icosaedro / Átomo"},
|
{.key = "T/Y/U/I", .description = "Cubo / Cilindro / Icosaedro / Átomo"},
|
||||||
{"Num+/-", "Escalar figura"},
|
{.key = "Num+/-", .description = "Escalar figura"},
|
||||||
{"Num*", "Reset escala"},
|
{.key = "Num*", .description = "Reset escala"},
|
||||||
{"Num/", "Activar / Desactivar profundidad"},
|
{.key = "Num/", .description = "Activar / Desactivar profundidad"},
|
||||||
{"[new_col]", ""}, // CAMBIO DE COLUMNA -> COLUMNA 2
|
{.key = "[new_col]", .description = ""}, // CAMBIO DE COLUMNA -> COLUMNA 2
|
||||||
|
|
||||||
// COLUMNA 2: MODOS
|
// COLUMNA 2: MODOS
|
||||||
{"MODOS", ""},
|
{.key = "MODOS", .description = ""},
|
||||||
{"D", "Activar / Desactivar modo demo"},
|
{.key = "D", .description = "Activar / Desactivar modo demo"},
|
||||||
{"L", "Activar / Desactivar modo demo lite"},
|
{.key = "L", .description = "Activar / Desactivar modo demo lite"},
|
||||||
{"K", "Activar / Desactivar modo logo"},
|
{.key = "K", .description = "Activar / Desactivar modo logo"},
|
||||||
{"", ""}, // Separador
|
{.key = "", .description = ""}, // Separador
|
||||||
|
|
||||||
// COLUMNA 2: VISUAL
|
// COLUMNA 2: VISUAL
|
||||||
{"VISUAL", ""},
|
{.key = "VISUAL", .description = ""},
|
||||||
{"C", "Tema siguiente"},
|
{.key = "C", .description = "Tema siguiente"},
|
||||||
{"Shift+C", "Tema anterior"},
|
{.key = "Shift+C", .description = "Tema anterior"},
|
||||||
{"NumEnter", "Página de temas"},
|
{.key = "NumEnter", .description = "Página de temas"},
|
||||||
{"Shift+D", "Pausar tema dinámico"},
|
{.key = "Shift+D", .description = "Pausar tema dinámico"},
|
||||||
{"N", "Cambiar tamaño de pelota"},
|
{.key = "N", .description = "Cambiar tamaño de pelota"},
|
||||||
{"X", "Ciclar presets PostFX"},
|
{.key = "X", .description = "Ciclar presets PostFX"},
|
||||||
{"[new_col]", ""}, // CAMBIO DE COLUMNA -> COLUMNA 3
|
{.key = "[new_col]", .description = ""}, // CAMBIO DE COLUMNA -> COLUMNA 3
|
||||||
|
|
||||||
// COLUMNA 3: PANTALLA
|
// COLUMNA 3: PANTALLA
|
||||||
{"PANTALLA", ""},
|
{.key = "PANTALLA", .description = ""},
|
||||||
{"F1", "Disminuye ventana"},
|
{.key = "F1", .description = "Disminuye ventana"},
|
||||||
{"F2", "Aumenta ventana"},
|
{.key = "F2", .description = "Aumenta ventana"},
|
||||||
{"F3", "Pantalla completa"},
|
{.key = "F3", .description = "Pantalla completa"},
|
||||||
{"F4", "Pantalla completa real"},
|
{.key = "F4", .description = "Pantalla completa real"},
|
||||||
{"F5", "Activar / Desactivar PostFX"},
|
{.key = "F5", .description = "Activar / Desactivar PostFX"},
|
||||||
{"F6", "Cambia el escalado de pantalla"},
|
{.key = "F6", .description = "Cambia el escalado de pantalla"},
|
||||||
{"V", "Activar / Desactivar V-Sync"},
|
{.key = "V", .description = "Activar / Desactivar V-Sync"},
|
||||||
{"", ""}, // Separador
|
{.key = "", .description = ""}, // Separador
|
||||||
|
|
||||||
// COLUMNA 3: DEBUG/AYUDA
|
// COLUMNA 3: DEBUG/AYUDA
|
||||||
{"DEBUG / AYUDA", ""},
|
{.key = "DEBUG / AYUDA", .description = ""},
|
||||||
{"F12", "Activar / Desactivar info debug"},
|
{.key = "F12", .description = "Activar / Desactivar info debug"},
|
||||||
{"H", "Esta ayuda"},
|
{.key = "H", .description = "Esta ayuda"},
|
||||||
{"ESC", "Salir"}};
|
{.key = "ESC", .description = "Salir"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
HelpOverlay::~HelpOverlay() {
|
HelpOverlay::~HelpOverlay() {
|
||||||
// Destruir textura cacheada si existe
|
// Destruir textura cacheada si existe
|
||||||
if (cached_texture_) {
|
if (cached_texture_ != nullptr) {
|
||||||
SDL_DestroyTexture(cached_texture_);
|
SDL_DestroyTexture(cached_texture_);
|
||||||
cached_texture_ = nullptr;
|
cached_texture_ = nullptr;
|
||||||
}
|
}
|
||||||
@@ -117,7 +118,9 @@ void HelpOverlay::updatePhysicalWindowSize(int physical_width, int physical_heig
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HelpOverlay::reinitializeFontSize(int new_font_size) {
|
void HelpOverlay::reinitializeFontSize(int new_font_size) {
|
||||||
if (!text_renderer_) return;
|
if (text_renderer_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Reinicializar text renderer con nuevo tamaño
|
// Reinicializar text renderer con nuevo tamaño
|
||||||
text_renderer_->reinitialize(new_font_size);
|
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;
|
physical_height_ = physical_height;
|
||||||
|
|
||||||
// Reinicializar text renderer con nuevo tamaño (si cambió)
|
// Reinicializar text renderer con nuevo tamaño (si cambió)
|
||||||
if (text_renderer_) {
|
if (text_renderer_ != nullptr) {
|
||||||
text_renderer_->reinitialize(font_size);
|
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) {
|
void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
|
||||||
if (!text_renderer_) {
|
if (text_renderer_ == nullptr) {
|
||||||
max_width = 0;
|
max_width = 0;
|
||||||
total_height = 0;
|
total_height = 0;
|
||||||
return;
|
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;
|
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
|
// 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;
|
current_column = 0;
|
||||||
|
|
||||||
for (const auto& binding : key_bindings_) {
|
for (const auto& binding : key_bindings_) {
|
||||||
@@ -240,7 +243,8 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
|
|||||||
|
|
||||||
void HelpOverlay::calculateBoxDimensions() {
|
void HelpOverlay::calculateBoxDimensions() {
|
||||||
// Calcular dimensiones necesarias según el texto
|
// Calcular dimensiones necesarias según el texto
|
||||||
int text_width, text_height;
|
int text_width;
|
||||||
|
int text_height;
|
||||||
calculateTextDimensions(text_width, text_height);
|
calculateTextDimensions(text_width, text_height);
|
||||||
|
|
||||||
// Aplicar límites máximos: 95% ancho, 90% altura
|
// Aplicar límites máximos: 95% ancho, 90% altura
|
||||||
@@ -253,14 +257,15 @@ void HelpOverlay::calculateBoxDimensions() {
|
|||||||
// Centrar en pantalla
|
// Centrar en pantalla
|
||||||
box_x_ = (physical_width_ - box_width_) / 2;
|
box_x_ = (physical_width_ - box_width_) / 2;
|
||||||
box_y_ = (physical_height_ - box_height_) / 2;
|
box_y_ = (physical_height_ - box_height_) / 2;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HelpOverlay::rebuildCachedTexture() {
|
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
|
// Destruir textura anterior si existe
|
||||||
if (cached_texture_) {
|
if (cached_texture_ != nullptr) {
|
||||||
SDL_DestroyTexture(cached_texture_);
|
SDL_DestroyTexture(cached_texture_);
|
||||||
cached_texture_ = nullptr;
|
cached_texture_ = nullptr;
|
||||||
}
|
}
|
||||||
@@ -272,7 +277,7 @@ void HelpOverlay::rebuildCachedTexture() {
|
|||||||
box_width_,
|
box_width_,
|
||||||
box_height_);
|
box_height_);
|
||||||
|
|
||||||
if (!cached_texture_) {
|
if (cached_texture_ == nullptr) {
|
||||||
SDL_Log("Error al crear textura cacheada: %s", SDL_GetError());
|
SDL_Log("Error al crear textura cacheada: %s", SDL_GetError());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -294,42 +299,46 @@ void HelpOverlay::rebuildCachedTexture() {
|
|||||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||||
|
|
||||||
// Obtener colores actuales del tema
|
// 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);
|
theme_mgr_->getCurrentNotificationBackgroundColor(notif_bg_r, notif_bg_g, notif_bg_b);
|
||||||
|
|
||||||
// Renderizar fondo del overlay a la textura
|
// Renderizar fondo del overlay a la textura
|
||||||
float alpha = 0.85f;
|
float alpha = 0.85f;
|
||||||
SDL_Vertex bg_vertices[4];
|
std::array<SDL_Vertex, 4> bg_vertices{};
|
||||||
|
|
||||||
float r = notif_bg_r / 255.0f;
|
float r = notif_bg_r / 255.0f;
|
||||||
float g = notif_bg_g / 255.0f;
|
float g = notif_bg_g / 255.0f;
|
||||||
float b = notif_bg_b / 255.0f;
|
float b = notif_bg_b / 255.0f;
|
||||||
|
|
||||||
// Vértices del fondo (posición relativa 0,0 porque estamos renderizando a textura)
|
// Vértices del fondo (posición relativa 0,0 porque estamos renderizando a textura)
|
||||||
bg_vertices[0].position = {0, 0};
|
bg_vertices[0].position = {.x = 0, .y = 0};
|
||||||
bg_vertices[0].tex_coord = {0.0f, 0.0f};
|
bg_vertices[0].tex_coord = {.x = 0.0f, .y = 0.0f};
|
||||||
bg_vertices[0].color = {r, g, b, alpha};
|
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].position = {.x = static_cast<float>(box_width_), .y = 0};
|
||||||
bg_vertices[1].tex_coord = {1.0f, 0.0f};
|
bg_vertices[1].tex_coord = {.x = 1.0f, .y = 0.0f};
|
||||||
bg_vertices[1].color = {r, g, b, alpha};
|
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].position = {.x = static_cast<float>(box_width_), .y = static_cast<float>(box_height_)};
|
||||||
bg_vertices[2].tex_coord = {1.0f, 1.0f};
|
bg_vertices[2].tex_coord = {.x = 1.0f, .y = 1.0f};
|
||||||
bg_vertices[2].color = {r, g, b, alpha};
|
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].position = {.x = 0, .y = static_cast<float>(box_height_)};
|
||||||
bg_vertices[3].tex_coord = {0.0f, 1.0f};
|
bg_vertices[3].tex_coord = {.x = 0.0f, .y = 1.0f};
|
||||||
bg_vertices[3].color = {r, g, b, alpha};
|
bg_vertices[3].color = {.r = r, .g = g, .b = b, .a = alpha};
|
||||||
|
|
||||||
int bg_indices[6] = {0, 1, 2, 2, 3, 0};
|
std::array<int, 6> bg_indices = {0, 1, 2, 2, 3, 0};
|
||||||
SDL_RenderGeometry(renderer_, nullptr, bg_vertices, 4, bg_indices, 6);
|
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)
|
// 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
|
// Necesito renderizar el texto igual que en renderHelpText() pero con coordenadas ajustadas
|
||||||
|
|
||||||
// Obtener colores para el texto
|
// 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);
|
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};
|
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
|
// Guardar colores actuales para comparación futura
|
||||||
last_category_color_ = category_color;
|
last_category_color_ = category_color;
|
||||||
last_content_color_ = content_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
|
// Configuración de espaciado
|
||||||
int line_height = text_renderer_->getTextHeight();
|
int line_height = text_renderer_->getTextHeight();
|
||||||
@@ -347,13 +356,13 @@ void HelpOverlay::rebuildCachedTexture() {
|
|||||||
int col_gap = padding * 2;
|
int col_gap = padding * 2;
|
||||||
|
|
||||||
// Posición X de inicio de cada columna
|
// Posición X de inicio de cada columna
|
||||||
int col_start[3];
|
std::array<int, 3> col_start{};
|
||||||
col_start[0] = padding;
|
col_start[0] = padding;
|
||||||
col_start[1] = padding + column1_width_ + col_gap;
|
col_start[1] = padding + column1_width_ + col_gap;
|
||||||
col_start[2] = padding + column1_width_ + col_gap + column2_width_ + col_gap;
|
col_start[2] = padding + column1_width_ + col_gap + column2_width_ + col_gap;
|
||||||
|
|
||||||
// Ancho de cada columna (para centrado interno)
|
// 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 glyph_height = text_renderer_->getGlyphHeight();
|
||||||
int current_y = padding;
|
int current_y = padding;
|
||||||
@@ -387,7 +396,7 @@ void HelpOverlay::rebuildCachedTexture() {
|
|||||||
} else {
|
} else {
|
||||||
// Encabezado de sección — centrado en la columna
|
// Encabezado de sección — centrado en la columna
|
||||||
int w = text_renderer_->getTextWidthPhysical(binding.key);
|
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;
|
current_y += line_height;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -397,7 +406,7 @@ void HelpOverlay::rebuildCachedTexture() {
|
|||||||
int key_width = text_renderer_->getTextWidthPhysical(binding.key);
|
int key_width = text_renderer_->getTextWidthPhysical(binding.key);
|
||||||
int desc_width = text_renderer_->getTextWidthPhysical(binding.description);
|
int desc_width = text_renderer_->getTextWidthPhysical(binding.description);
|
||||||
int total_width = key_width + 10 + desc_width;
|
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, current_y, binding.key, category_color);
|
||||||
text_renderer_->printAbsolute(line_x + key_width + 10, current_y, binding.description, content_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) {
|
void HelpOverlay::render(SDL_Renderer* renderer) {
|
||||||
if (!visible_) return;
|
if (!visible_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Obtener colores actuales del tema
|
// 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);
|
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);
|
theme_mgr_->getCurrentThemeTextColor(text_r, text_g, text_b);
|
||||||
|
|
||||||
Color ball_color = theme_mgr_->getInterpolatedColor(0);
|
Color ball_color = theme_mgr_->getInterpolatedColor(0);
|
||||||
@@ -443,12 +458,14 @@ void HelpOverlay::render(SDL_Renderer* renderer) {
|
|||||||
abs(current_content.b - last_content_color_.b) > COLOR_CHANGE_THRESHOLD);
|
abs(current_content.b - last_content_color_.b) > COLOR_CHANGE_THRESHOLD);
|
||||||
|
|
||||||
// Regenerar textura si es necesario (colores cambiaron O flag de rebuild activo)
|
// 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();
|
rebuildCachedTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si no hay textura cacheada (error), salir
|
// 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
|
// CRÍTICO: Habilitar alpha blending para que la transparencia funcione
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
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
|
// 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.w y viewport.h son las dimensiones del área visible
|
||||||
// viewport.x y viewport.y son el offset de las barras negras
|
// viewport.x y viewport.y son el offset de las barras negras
|
||||||
int centered_x = viewport.x + (viewport.w - box_width_) / 2;
|
int centered_x = viewport.x + ((viewport.w - box_width_) / 2);
|
||||||
int centered_y = viewport.y + (viewport.h - box_height_) / 2;
|
int centered_y = viewport.y + ((viewport.h - box_height_) / 2);
|
||||||
|
|
||||||
// Renderizar la textura cacheada centrada en el viewport
|
// Renderizar la textura cacheada centrada en el viewport
|
||||||
SDL_FRect dest_rect;
|
SDL_FRect dest_rect;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
// Detectar resolución nativa del monitor principal
|
// 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;
|
int num_displays = 0;
|
||||||
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
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
|
// Cargar PNG y escalar al tamaño especificado
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
unsigned char* LogoScaler::loadAndScale(const std::string& path,
|
auto LogoScaler::loadAndScale(const std::string& path,
|
||||||
int target_width, int target_height,
|
int target_width,
|
||||||
int& out_width, int& out_height) {
|
int target_height,
|
||||||
|
int& out_width,
|
||||||
|
int& out_height) -> unsigned char* {
|
||||||
// 1. Intentar cargar imagen desde ResourceManager (pack o disco)
|
// 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;
|
unsigned char* orig_data = nullptr;
|
||||||
|
|
||||||
// 1a. Cargar desde ResourceManager
|
// 1a. Cargar desde ResourceManager
|
||||||
unsigned char* resourceData = nullptr;
|
unsigned char* resource_data = nullptr;
|
||||||
size_t resourceSize = 0;
|
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
|
// Descodificar imagen desde memoria usando stb_image
|
||||||
orig_data = stbi_load_from_memory(resourceData, static_cast<int>(resourceSize),
|
orig_data = stbi_load_from_memory(resource_data, static_cast<int>(resource_size), &orig_width, &orig_height, &orig_channels, STBI_rgb_alpha);
|
||||||
&orig_width, &orig_height, &orig_channels, STBI_rgb_alpha);
|
delete[] resource_data; // Liberar buffer temporal
|
||||||
delete[] resourceData; // Liberar buffer temporal
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1b. Si falla todo, error
|
// 1b. Si falla todo, error
|
||||||
@@ -80,7 +83,7 @@ unsigned char* LogoScaler::loadAndScale(const std::string& path,
|
|||||||
out_height = target_height;
|
out_height = target_height;
|
||||||
|
|
||||||
// 3. Alocar buffer para imagen escalada (RGBA = 4 bytes por píxel)
|
// 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) {
|
if (scaled_data == nullptr) {
|
||||||
SDL_Log("Error al alocar memoria para imagen escalada");
|
SDL_Log("Error al alocar memoria para imagen escalada");
|
||||||
stbi_image_free(orig_data);
|
stbi_image_free(orig_data);
|
||||||
@@ -90,8 +93,14 @@ unsigned char* LogoScaler::loadAndScale(const std::string& path,
|
|||||||
// 4. Escalar con stb_image_resize2 (algoritmo Mitchell, espacio sRGB)
|
// 4. Escalar con stb_image_resize2 (algoritmo Mitchell, espacio sRGB)
|
||||||
// La función devuelve el puntero de salida, o nullptr si falla
|
// La función devuelve el puntero de salida, o nullptr si falla
|
||||||
unsigned char* result = stbir_resize_uint8_srgb(
|
unsigned char* result = stbir_resize_uint8_srgb(
|
||||||
orig_data, orig_width, orig_height, 0, // Input
|
orig_data,
|
||||||
scaled_data, out_width, out_height, 0, // Output
|
orig_width,
|
||||||
|
orig_height,
|
||||||
|
0, // Input
|
||||||
|
scaled_data,
|
||||||
|
out_width,
|
||||||
|
out_height,
|
||||||
|
0, // Output
|
||||||
STBIR_RGBA // Formato píxel
|
STBIR_RGBA // Formato píxel
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -111,9 +120,10 @@ unsigned char* LogoScaler::loadAndScale(const std::string& path,
|
|||||||
// Crear textura SDL desde buffer RGBA
|
// Crear textura SDL desde buffer RGBA
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
SDL_Texture* LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer,
|
auto LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer,
|
||||||
unsigned char* data,
|
unsigned char* data,
|
||||||
int width, int height) {
|
int width,
|
||||||
|
int height) -> SDL_Texture* {
|
||||||
if (renderer == nullptr || data == nullptr || width <= 0 || height <= 0) {
|
if (renderer == nullptr || data == nullptr || width <= 0 || height <= 0) {
|
||||||
SDL_Log("Parámetros inválidos para createTextureFromBuffer");
|
SDL_Log("Parámetros inválidos para createTextureFromBuffer");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -124,11 +134,11 @@ SDL_Texture* LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer,
|
|||||||
SDL_PixelFormat pixel_format = SDL_PIXELFORMAT_RGBA32;
|
SDL_PixelFormat pixel_format = SDL_PIXELFORMAT_RGBA32;
|
||||||
|
|
||||||
SDL_Surface* surface = SDL_CreateSurfaceFrom(
|
SDL_Surface* surface = SDL_CreateSurfaceFrom(
|
||||||
width, height,
|
width,
|
||||||
|
height,
|
||||||
pixel_format,
|
pixel_format,
|
||||||
data,
|
data,
|
||||||
pitch
|
pitch);
|
||||||
);
|
|
||||||
|
|
||||||
if (surface == nullptr) {
|
if (surface == nullptr) {
|
||||||
SDL_Log("Error al crear surface: %s", SDL_GetError());
|
SDL_Log("Error al crear surface: %s", SDL_GetError());
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* de pantalla, eliminando el escalado dinámico de SDL y mejorando calidad visual.
|
* de pantalla, eliminando el escalado dinámico de SDL y mejorando calidad visual.
|
||||||
*/
|
*/
|
||||||
class LogoScaler {
|
class LogoScaler {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Detecta la resolución nativa del monitor principal
|
* @brief Detecta la resolución nativa del monitor principal
|
||||||
*
|
*
|
||||||
@@ -42,8 +42,10 @@ public:
|
|||||||
* IMPORTANTE: El caller debe liberar con free() cuando termine
|
* IMPORTANTE: El caller debe liberar con free() cuando termine
|
||||||
*/
|
*/
|
||||||
static unsigned char* loadAndScale(const std::string& path,
|
static unsigned char* loadAndScale(const std::string& path,
|
||||||
int target_width, int target_height,
|
int target_width,
|
||||||
int& out_width, int& out_height);
|
int target_height,
|
||||||
|
int& out_width,
|
||||||
|
int& out_height);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Crea una textura SDL desde un buffer RGBA
|
* @brief Crea una textura SDL desde un buffer RGBA
|
||||||
@@ -57,5 +59,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
static SDL_Texture* createTextureFromBuffer(SDL_Renderer* renderer,
|
static SDL_Texture* createTextureFromBuffer(SDL_Renderer* renderer,
|
||||||
unsigned char* data,
|
unsigned char* data,
|
||||||
int width, int height);
|
int width,
|
||||||
|
int height);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#include "notifier.hpp"
|
#include "notifier.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include "defines.hpp"
|
||||||
#include "text/textrenderer.hpp"
|
#include "text/textrenderer.hpp"
|
||||||
#include "theme_manager.hpp"
|
#include "theme_manager.hpp"
|
||||||
#include "defines.hpp"
|
|
||||||
#include "utils/easing_functions.hpp"
|
#include "utils/easing_functions.hpp"
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// HELPER: Obtener viewport en coordenadas físicas (no lógicas)
|
// HELPER: Obtener viewport en coordenadas físicas (no lógicas)
|
||||||
@@ -11,9 +13,10 @@
|
|||||||
// SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación
|
// SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación
|
||||||
// lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar
|
// lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar
|
||||||
// temporalmente la presentación lógica.
|
// 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
|
// 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_RendererLogicalPresentation presentation_mode;
|
||||||
SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode);
|
SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode);
|
||||||
|
|
||||||
@@ -31,19 +34,19 @@ static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Notifier::Notifier()
|
Notifier::Notifier()
|
||||||
: renderer_(nullptr)
|
: renderer_(nullptr),
|
||||||
, text_renderer_(nullptr)
|
text_renderer_(nullptr),
|
||||||
, theme_manager_(nullptr)
|
theme_manager_(nullptr),
|
||||||
, window_width_(0)
|
window_width_(0),
|
||||||
, window_height_(0)
|
window_height_(0),
|
||||||
, current_notification_(nullptr) {
|
current_notification_(nullptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifier::~Notifier() {
|
Notifier::~Notifier() {
|
||||||
clear();
|
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;
|
renderer_ = renderer;
|
||||||
text_renderer_ = text_renderer;
|
text_renderer_ = text_renderer;
|
||||||
theme_manager_ = theme_manager;
|
theme_manager_ = theme_manager;
|
||||||
@@ -151,28 +154,30 @@ void Notifier::update(Uint64 current_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Notifier::render() {
|
void Notifier::render() {
|
||||||
if (!current_notification_ || !text_renderer_ || !renderer_ || !theme_manager_) {
|
if (!current_notification_ || (text_renderer_ == nullptr) || (renderer_ == nullptr) || (theme_manager_ == nullptr)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtener colores DINÁMICOS desde ThemeManager (incluye LERP automático)
|
// 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);
|
theme_manager_->getCurrentThemeTextColor(text_r, text_g, text_b);
|
||||||
SDL_Color text_color = {
|
SDL_Color text_color = {
|
||||||
static_cast<Uint8>(text_r),
|
static_cast<Uint8>(text_r),
|
||||||
static_cast<Uint8>(text_g),
|
static_cast<Uint8>(text_g),
|
||||||
static_cast<Uint8>(text_b),
|
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);
|
theme_manager_->getCurrentNotificationBackgroundColor(bg_r, bg_g, bg_b);
|
||||||
SDL_Color bg_color = {
|
SDL_Color bg_color = {
|
||||||
static_cast<Uint8>(bg_r),
|
static_cast<Uint8>(bg_r),
|
||||||
static_cast<Uint8>(bg_g),
|
static_cast<Uint8>(bg_g),
|
||||||
static_cast<Uint8>(bg_b),
|
static_cast<Uint8>(bg_b),
|
||||||
255
|
255};
|
||||||
};
|
|
||||||
|
|
||||||
// Calcular dimensiones del texto en píxeles FÍSICOS
|
// Calcular dimensiones del texto en píxeles FÍSICOS
|
||||||
// IMPORTANTE: Usar getTextWidthPhysical() en lugar de getTextWidth()
|
// 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) {
|
void Notifier::renderBackground(int x, int y, int width, int height, float alpha, SDL_Color bg_color) {
|
||||||
if (!renderer_) {
|
if (renderer_ == nullptr) {
|
||||||
return;
|
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);
|
bg_rect.h = static_cast<float>(height);
|
||||||
|
|
||||||
// Color del tema con alpha
|
// 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);
|
SDL_SetRenderDrawColor(renderer_, bg_color.r, bg_color.g, bg_color.b, bg_alpha);
|
||||||
|
|
||||||
// Habilitar blending para transparencia
|
// 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
|
// CRÍTICO: Deshabilitar presentación lógica para renderizar en píxeles físicos absolutos
|
||||||
// (igual que printAbsolute() en TextRenderer)
|
// (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_RendererLogicalPresentation presentation_mode;
|
||||||
SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &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);
|
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Notifier::isActive() const {
|
auto Notifier::isActive() const -> bool {
|
||||||
return (current_notification_ != nullptr);
|
return (current_notification_ != nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <string>
|
|
||||||
#include <queue>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
class TextRenderer;
|
class TextRenderer;
|
||||||
@@ -20,7 +21,7 @@ class ThemeManager;
|
|||||||
* - Texto de tamaño fijo independiente de resolución
|
* - Texto de tamaño fijo independiente de resolución
|
||||||
*/
|
*/
|
||||||
class Notifier {
|
class Notifier {
|
||||||
public:
|
public:
|
||||||
enum class NotificationState {
|
enum class NotificationState {
|
||||||
SLIDING_IN, // Animación de entrada desde arriba
|
SLIDING_IN, // Animación de entrada desde arriba
|
||||||
VISIBLE, // Visible estático
|
VISIBLE, // Visible estático
|
||||||
@@ -89,7 +90,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_Renderer* renderer_;
|
SDL_Renderer* renderer_;
|
||||||
TextRenderer* text_renderer_;
|
TextRenderer* text_renderer_;
|
||||||
ThemeManager* theme_manager_; // Gestor de temas para obtener colores dinámicos con LERP
|
ThemeManager* theme_manager_; // Gestor de temas para obtener colores dinámicos con LERP
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
#include "ui_manager.hpp"
|
#include "ui_manager.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "ball.hpp" // for Ball
|
#include "ball.hpp" // for Ball
|
||||||
#include "defines.hpp" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode
|
#include "defines.hpp" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode
|
||||||
#include "engine.hpp" // for Engine (info de sistema)
|
#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 "scene/scene_manager.hpp" // for SceneManager
|
||||||
#include "shapes/shape.hpp" // for Shape
|
#include "shapes/shape.hpp" // for Shape
|
||||||
#include "text/textrenderer.hpp" // for TextRenderer
|
#include "text/textrenderer.hpp" // for TextRenderer
|
||||||
#include "theme_manager.hpp" // for ThemeManager
|
#include "theme_manager.hpp" // for ThemeManager
|
||||||
#include "notifier.hpp" // for Notifier
|
|
||||||
#include "help_overlay.hpp" // for HelpOverlay
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// HELPER: Obtener viewport en coordenadas físicas (no lógicas)
|
// HELPER: Obtener viewport en coordenadas físicas (no lógicas)
|
||||||
@@ -20,9 +22,10 @@
|
|||||||
// SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación
|
// SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación
|
||||||
// lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar
|
// lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar
|
||||||
// temporalmente la presentación lógica.
|
// 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
|
// 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_RendererLogicalPresentation presentation_mode;
|
||||||
SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode);
|
SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode);
|
||||||
|
|
||||||
@@ -40,23 +43,23 @@ static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UIManager::UIManager()
|
UIManager::UIManager()
|
||||||
: text_renderer_debug_(nullptr)
|
: text_renderer_debug_(nullptr),
|
||||||
, text_renderer_notifier_(nullptr)
|
text_renderer_notifier_(nullptr),
|
||||||
, notifier_(nullptr)
|
notifier_(nullptr),
|
||||||
, help_overlay_(nullptr)
|
help_overlay_(nullptr),
|
||||||
, show_debug_(false)
|
show_debug_(false),
|
||||||
, fps_last_time_(0)
|
fps_last_time_(0),
|
||||||
, fps_frame_count_(0)
|
fps_frame_count_(0),
|
||||||
, fps_current_(0)
|
fps_current_(0),
|
||||||
, fps_text_("FPS: 0")
|
fps_text_("FPS: 0"),
|
||||||
, vsync_text_("VSYNC ON")
|
vsync_text_("VSYNC ON"),
|
||||||
, renderer_(nullptr)
|
renderer_(nullptr),
|
||||||
, theme_manager_(nullptr)
|
theme_manager_(nullptr),
|
||||||
, physical_window_width_(0)
|
physical_window_width_(0),
|
||||||
, physical_window_height_(0)
|
physical_window_height_(0),
|
||||||
, logical_window_width_(0)
|
logical_window_width_(0),
|
||||||
, logical_window_height_(0)
|
logical_window_height_(0),
|
||||||
, current_font_size_(18) { // Tamaño por defecto (medium)
|
current_font_size_(18) { // Tamaño por defecto (medium)
|
||||||
}
|
}
|
||||||
|
|
||||||
UIManager::~UIManager() {
|
UIManager::~UIManager() {
|
||||||
@@ -67,13 +70,15 @@ UIManager::~UIManager() {
|
|||||||
delete help_overlay_;
|
delete help_overlay_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, int physical_width, int physical_height, int logical_width, int logical_height) {
|
||||||
int physical_width, int physical_height,
|
delete text_renderer_debug_;
|
||||||
int logical_width, int logical_height) {
|
text_renderer_debug_ = nullptr;
|
||||||
delete text_renderer_debug_; text_renderer_debug_ = nullptr;
|
delete text_renderer_notifier_;
|
||||||
delete text_renderer_notifier_; text_renderer_notifier_ = nullptr;
|
text_renderer_notifier_ = nullptr;
|
||||||
delete notifier_; notifier_ = nullptr;
|
delete notifier_;
|
||||||
delete help_overlay_; help_overlay_ = nullptr;
|
notifier_ = nullptr;
|
||||||
|
delete help_overlay_;
|
||||||
|
help_overlay_ = nullptr;
|
||||||
|
|
||||||
renderer_ = renderer;
|
renderer_ = renderer;
|
||||||
theme_manager_ = theme_manager;
|
theme_manager_ = theme_manager;
|
||||||
@@ -95,8 +100,7 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
|||||||
|
|
||||||
// Crear y configurar sistema de notificaciones
|
// Crear y configurar sistema de notificaciones
|
||||||
notifier_ = new Notifier();
|
notifier_ = new Notifier();
|
||||||
notifier_->init(renderer, text_renderer_notifier_, theme_manager_,
|
notifier_->init(renderer, text_renderer_notifier_, theme_manager_, physical_width, physical_height);
|
||||||
physical_width, physical_height);
|
|
||||||
|
|
||||||
// Crear y configurar sistema de ayuda (overlay)
|
// Crear y configurar sistema de ayuda (overlay)
|
||||||
help_overlay_ = new HelpOverlay();
|
help_overlay_ = new HelpOverlay();
|
||||||
@@ -138,15 +142,14 @@ void UIManager::render(SDL_Renderer* renderer,
|
|||||||
|
|
||||||
// Renderizar debug HUD si está activo
|
// Renderizar debug HUD si está activo
|
||||||
if (show_debug_) {
|
if (show_debug_) {
|
||||||
renderDebugHUD(engine, scene_manager, current_mode, current_app_mode,
|
renderDebugHUD(engine, scene_manager, current_mode, current_app_mode, active_shape, shape_convergence);
|
||||||
active_shape, shape_convergence);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderizar notificaciones (siempre al final, sobre todo lo demás)
|
// Renderizar notificaciones (siempre al final, sobre todo lo demás)
|
||||||
notifier_->render();
|
notifier_->render();
|
||||||
|
|
||||||
// Renderizar ayuda (siempre última, sobre todo incluso notificaciones)
|
// Renderizar ayuda (siempre última, sobre todo incluso notificaciones)
|
||||||
if (help_overlay_) {
|
if (help_overlay_ != nullptr) {
|
||||||
help_overlay_->render(renderer);
|
help_overlay_->render(renderer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +159,7 @@ void UIManager::toggleDebug() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UIManager::toggleHelp() {
|
void UIManager::toggleHelp() {
|
||||||
if (help_overlay_) {
|
if (help_overlay_ != nullptr) {
|
||||||
help_overlay_->toggle();
|
help_overlay_->toggle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,16 +193,16 @@ void UIManager::updatePhysicalWindowSize(int width, int height, int logical_heig
|
|||||||
current_font_size_ = new_font_size;
|
current_font_size_ = new_font_size;
|
||||||
|
|
||||||
// Reinicializar text renderers con nuevo tamaño
|
// 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));
|
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_);
|
text_renderer_notifier_->reinitialize(current_font_size_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar help overlay con font size actual Y nuevas dimensiones (atómicamente)
|
// 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);
|
help_overlay_->updateAll(std::max(9, current_font_size_ - 1), width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +212,7 @@ void UIManager::updatePhysicalWindowSize(int width, int height, int logical_heig
|
|||||||
|
|
||||||
// === Métodos privados ===
|
// === Métodos privados ===
|
||||||
|
|
||||||
void UIManager::renderDebugHUD(const Engine* engine,
|
void UIManager::renderDebugHUD(const Engine* engine, // NOLINT(readability-function-cognitive-complexity)
|
||||||
const SceneManager* scene_manager,
|
const SceneManager* scene_manager,
|
||||||
SimulationMode current_mode,
|
SimulationMode current_mode,
|
||||||
AppMode current_app_mode,
|
AppMode current_app_mode,
|
||||||
@@ -236,7 +239,7 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
|||||||
if (current_mode == SimulationMode::PHYSICS) {
|
if (current_mode == SimulationMode::PHYSICS) {
|
||||||
simmode_text = "SimMode: PHYSICS";
|
simmode_text = "SimMode: PHYSICS";
|
||||||
} else if (current_mode == SimulationMode::SHAPE) {
|
} else if (current_mode == SimulationMode::SHAPE) {
|
||||||
if (active_shape) {
|
if (active_shape != nullptr) {
|
||||||
simmode_text = std::string("SimMode: SHAPE (") + active_shape->getName() + ")";
|
simmode_text = std::string("SimMode: SHAPE (") + active_shape->getName() + ")";
|
||||||
} else {
|
} else {
|
||||||
simmode_text = "SimMode: SHAPE";
|
simmode_text = "SimMode: SHAPE";
|
||||||
@@ -246,7 +249,7 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string sprite_name = engine->getCurrentTextureName();
|
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;
|
std::string sprite_text = "Sprite: " + sprite_name;
|
||||||
|
|
||||||
size_t ball_count = scene_manager->getBallCount();
|
size_t ball_count = scene_manager->getBallCount();
|
||||||
@@ -256,7 +259,9 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
|||||||
std::string formatted;
|
std::string formatted;
|
||||||
int digits = static_cast<int>(count_str.length());
|
int digits = static_cast<int>(count_str.length());
|
||||||
for (int i = 0; i < digits; i++) {
|
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];
|
formatted += count_str[i];
|
||||||
}
|
}
|
||||||
balls_text = "Balls: " + formatted;
|
balls_text = "Balls: " + formatted;
|
||||||
@@ -275,7 +280,9 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
|||||||
std::string formatted;
|
std::string formatted;
|
||||||
int digits = static_cast<int>(count_str.length());
|
int digits = static_cast<int>(count_str.length());
|
||||||
for (int i = 0; i < digits; i++) {
|
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];
|
formatted += count_str[i];
|
||||||
}
|
}
|
||||||
max_auto_text = "Auto max: " + formatted;
|
max_auto_text = "Auto max: " + formatted;
|
||||||
@@ -303,9 +310,9 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
|||||||
std::string refresh_text;
|
std::string refresh_text;
|
||||||
int num_displays = 0;
|
int num_displays = 0;
|
||||||
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
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]);
|
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";
|
refresh_text = "Refresh: " + std::to_string(static_cast<int>(dm->refresh_rate)) + " Hz";
|
||||||
} else {
|
} else {
|
||||||
refresh_text = "Refresh: N/A";
|
refresh_text = "Refresh: N/A";
|
||||||
@@ -322,9 +329,9 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
|||||||
int hh = static_cast<int>(total_secs / 3600);
|
int hh = static_cast<int>(total_secs / 3600);
|
||||||
int mm = static_cast<int>((total_secs % 3600) / 60);
|
int mm = static_cast<int>((total_secs % 3600) / 60);
|
||||||
int ss = static_cast<int>(total_secs % 60);
|
int ss = static_cast<int>(total_secs % 60);
|
||||||
char elapsed_buf[32];
|
std::array<char, 32> elapsed_buf{};
|
||||||
SDL_snprintf(elapsed_buf, sizeof(elapsed_buf), "Elapsed: %02d:%02d:%02d", hh, mm, ss);
|
SDL_snprintf(elapsed_buf.data(), elapsed_buf.size(), "Elapsed: %02d:%02d:%02d", hh, mm, ss);
|
||||||
std::string elapsed_text(elapsed_buf);
|
std::string elapsed_text(elapsed_buf.data());
|
||||||
|
|
||||||
// --- Construir vector de líneas en orden ---
|
// --- Construir vector de líneas en orden ---
|
||||||
std::vector<std::string> lines;
|
std::vector<std::string> lines;
|
||||||
@@ -344,17 +351,15 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
|||||||
if (!engine->isPostFXEnabled()) {
|
if (!engine->isPostFXEnabled()) {
|
||||||
postfx_text = "PostFX: OFF";
|
postfx_text = "PostFX: OFF";
|
||||||
} else {
|
} else {
|
||||||
static constexpr const char* preset_names[4] = {
|
static constexpr std::array<const char*, 4> PRESET_NAMES = {
|
||||||
"Vinyeta", "Scanlines", "Cromatica", "Complet"
|
"Vinyeta",
|
||||||
};
|
"Scanlines",
|
||||||
|
"Cromatica",
|
||||||
|
"Complet"};
|
||||||
int mode = engine->getPostFXMode();
|
int mode = engine->getPostFXMode();
|
||||||
char buf[64];
|
std::array<char, 64> buf{};
|
||||||
SDL_snprintf(buf, sizeof(buf), "PostFX: %s [V:%.2f C:%.2f S:%.2f]",
|
SDL_snprintf(buf.data(), buf.size(), "PostFX: %s [V:%.2f C:%.2f S:%.2f]", PRESET_NAMES[mode], engine->getPostFXVignette(), engine->getPostFXChroma(), engine->getPostFXScanline());
|
||||||
preset_names[mode],
|
postfx_text = buf.data();
|
||||||
engine->getPostFXVignette(),
|
|
||||||
engine->getPostFXChroma(),
|
|
||||||
engine->getPostFXScanline());
|
|
||||||
postfx_text = buf;
|
|
||||||
}
|
}
|
||||||
lines.push_back(postfx_text);
|
lines.push_back(postfx_text);
|
||||||
lines.push_back(elapsed_text);
|
lines.push_back(elapsed_text);
|
||||||
@@ -366,7 +371,7 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
|||||||
SDL_FRect pos = first_ball->getPosition();
|
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("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("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("Loss: " + std::to_string(first_ball->getLossCoefficient()).substr(0, 4));
|
||||||
lines.push_back("Dir: " + gravityDirectionToString(static_cast<int>(scene_manager->getCurrentGravity())));
|
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 ---
|
// --- Render con desbordamiento a segunda columna ---
|
||||||
int max_lines = (physical_viewport.h - 2 * margin) / line_height;
|
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;
|
int col_width = physical_viewport.w / 2;
|
||||||
|
|
||||||
for (int i = 0; i < static_cast<int>(lines.size()); i++) {
|
for (int i = 0; i < static_cast<int>(lines.size()); i++) {
|
||||||
int col = i / max_lines;
|
int col = i / max_lines;
|
||||||
int row = i % max_lines;
|
int row = i % max_lines;
|
||||||
int x = margin + col * col_width;
|
int x = margin + (col * col_width);
|
||||||
int y = margin + row * line_height;
|
int y = margin + (row * line_height);
|
||||||
text_renderer_debug_->printAbsoluteShadowed(x, y, lines[i].c_str());
|
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) {
|
switch (direction) {
|
||||||
case 0: return "Abajo"; // DOWN
|
case 0:
|
||||||
case 1: return "Arriba"; // UP
|
return "Abajo"; // DOWN
|
||||||
case 2: return "Izquierda"; // LEFT
|
case 1:
|
||||||
case 3: return "Derecha"; // RIGHT
|
return "Arriba"; // UP
|
||||||
default: return "Desconocida";
|
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)
|
// 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
|
// Esto asegura que el tamaño de fuente sea consistente independientemente del zoom de ventana
|
||||||
// - Proporcional en extremos (muy bajo/alto)
|
// - 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
|
// Aplicar límites: mínimo 9px, máximo 72px
|
||||||
if (font_size < 9) font_size = 9;
|
font_size = std::max(font_size, 9);
|
||||||
if (font_size > 72) font_size = 72;
|
font_size = std::min(font_size, 72);
|
||||||
|
|
||||||
return font_size;
|
return font_size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SDL3/SDL_stdinc.h> // for Uint64
|
#include <SDL3/SDL_stdinc.h> // for Uint64
|
||||||
|
|
||||||
#include <string> // for std::string
|
#include <string> // for std::string
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
@@ -49,9 +50,7 @@ class UIManager {
|
|||||||
* @param logical_width Ancho lógico (resolución interna)
|
* @param logical_width Ancho lógico (resolución interna)
|
||||||
* @param logical_height Alto lógico (resolución interna)
|
* @param logical_height Alto lógico (resolución interna)
|
||||||
*/
|
*/
|
||||||
void initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
void initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, int physical_width, int physical_height, int logical_width, int logical_height);
|
||||||
int physical_width, int physical_height,
|
|
||||||
int logical_width, int logical_height);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Actualiza UI (FPS counter, notificaciones, texto obsoleto)
|
* @brief Actualiza UI (FPS counter, notificaciones, texto obsoleto)
|
||||||
@@ -149,14 +148,14 @@ class UIManager {
|
|||||||
* @param direction Dirección como int (cast de GravityDirection)
|
* @param direction Dirección como int (cast de GravityDirection)
|
||||||
* @return String en español ("Abajo", "Arriba", etc.)
|
* @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
|
* @brief Calcula tamaño de fuente apropiado según dimensiones lógicas
|
||||||
* @param logical_height Alto lógico (resolución interna, sin zoom)
|
* @param logical_height Alto lógico (resolución interna, sin zoom)
|
||||||
* @return Tamaño de fuente (9-72px)
|
* @return Tamaño de fuente (9-72px)
|
||||||
*/
|
*/
|
||||||
int calculateFontSize(int logical_height) const;
|
static int calculateFontSize(int logical_height);
|
||||||
|
|
||||||
// === Recursos de renderizado ===
|
// === Recursos de renderizado ===
|
||||||
TextRenderer* text_renderer_debug_; // HUD de debug
|
TextRenderer* text_renderer_debug_; // HUD de debug
|
||||||
|
|||||||
Reference in New Issue
Block a user