Compare commits
20 Commits
3d26bfc6fa
...
c91cb1ca56
| Author | SHA1 | Date | |
|---|---|---|---|
| c91cb1ca56 | |||
| 8d608357b4 | |||
| f73a133756 | |||
| de23327861 | |||
| f6402084eb | |||
| 9909d4c12d | |||
| a929346463 | |||
| c4075f68db | |||
| 399650f8da | |||
| 9b8afa1219 | |||
| 5b674c8ea6 | |||
| 7fac103c51 | |||
| bcceb94c9e | |||
| 1b3d32ba84 | |||
| 7c0a60f140 | |||
| 250b1a640d | |||
| 795fa33e50 | |||
| e7dc8f6d13 | |||
| 9cabbd867f | |||
| 8c2a8857fc |
128
RULES.md
Normal file
128
RULES.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
Documento de especificaciones de ViBe3 Physics
|
||||||
|
|
||||||
|
# Codigo
|
||||||
|
* Se preferira el uso de #pragma once a #ifndef
|
||||||
|
* Se preferira el uso de C++ frente a C
|
||||||
|
* Se preferirá el uso de verisiones mas moderdas de C++ frente a las mas viejas, es decir, C++20 frente a C++17, por ejemplo
|
||||||
|
* Se preferirá el uso de smart pointers frente a new/delete y sobretodo antes que malloc/free
|
||||||
|
* Los archivos de cabecera que definan clases, colocaran primero la parte publica y luego la privada. Agruparan los metodos por categorias. Todas las variables, constantes, estructuras, enumeraciones, metodos, llevaran el comentario a la derecha
|
||||||
|
* Se respetarán las reglas definidas en los ficheros .clang-tidy y .clang-format que hay en la raíz o en las subcarpetas
|
||||||
|
|
||||||
|
# Funcionamiento
|
||||||
|
* El programa tiene modos de funcionamiento (AppMode). El funcionamiento de cada uno de ellos se describirá mas adelante. Son estados exclusivos que van automatizando cambios en el SimulationMode, Theme y Scene y serian:
|
||||||
|
* SANDBOX
|
||||||
|
* DEMO
|
||||||
|
* DEMO LITE
|
||||||
|
* LOGO
|
||||||
|
* LOGO LITE
|
||||||
|
* El progama tiene otros modos de funcionamiento (SimulationMode). El funcionamiento de cada uno de ellos se describirá mas adelante. Son estados exclusivos:
|
||||||
|
* PHYISICS
|
||||||
|
* FIGURE
|
||||||
|
* BOIDS
|
||||||
|
* El programa tiene un gestor de temas (Theme) que cambia los colores de lo que se ve en pantalla. Hay temas estáticos y dinamicos. El cambio de tema se realiza mediante LERP y no afecta en nada ni al AppMode ni al SimulationMode, es decir, no modifica sus estados.
|
||||||
|
* El programa tiene escenarios (Scene). Cada escena tiene un numero de pelotas. Cuando se cambia el escenario, se elimina el vector de pelotas y se crea uno nuevo. En funcion del SimulationMode actual se inicializan las pelotas de manera distinta:
|
||||||
|
* PHYSICS: Se crean todas las pelotas cerca de la parte superior de la pantalla distribuidas en el 75% central del eje X (es como está ahora)
|
||||||
|
* FIGURE: Se crean todas las pelotas en el punto central de la pantalla
|
||||||
|
* BOIDS: Se crean todas las pelotas en posiciones al azar de la pantalla con velocidades y direcciones aleatorias
|
||||||
|
* El cambio de SimulationMode ha de preservar la inercia (velocidad, aceleracion, direccion) de cada pelota. El cambio se produce tanto de forma manual (pulsacion de una tecla por el usuario) como de manera automatica (cualquier AppMode que no sea SANDBOX)
|
||||||
|
* PHYSICS a FIGURE:
|
||||||
|
* Pulsando la tecla de la figura correspondiente
|
||||||
|
* Pulsando la tecla F (ultima figura seleccionada)
|
||||||
|
* PHYSICS a BOIDS:
|
||||||
|
* Pulsando la tecla B
|
||||||
|
* FIGURE a PHYSICS:
|
||||||
|
* Pulsando los cursores: Gravedad ON en la direccion del cursor
|
||||||
|
* Pulsando la tecla G: Gravedad OFF
|
||||||
|
* Pulsando la tecla F: Ultima gravedad seleccionada (direccion o OFF)
|
||||||
|
* FIGURE a BOIDS:
|
||||||
|
* Pulsando la tecla B
|
||||||
|
* BOIDS a PHYSICS:
|
||||||
|
* Pulsando la tecla G: Gravedad OFF
|
||||||
|
* Pulsando los cursores: Gravedad ON en la direccion del cursor
|
||||||
|
* BOIDS a FIGURE:
|
||||||
|
* Pulsando la tecla de la figura
|
||||||
|
* Pulsando la tecla F (ultima figura)
|
||||||
|
|
||||||
|
# AppMode
|
||||||
|
* SANDBOX
|
||||||
|
* No hay ningun automatismo. El usuario va pulsando teclas para ejecutar acciones.
|
||||||
|
* Si pulsa una de estas teclas, cambia de modo:
|
||||||
|
* D: DEMO
|
||||||
|
* L: DEMO LITE
|
||||||
|
* K: LOGO
|
||||||
|
* DEMO
|
||||||
|
* En el modo DEMO el programa va cambiando el SimulationMode de manera automatica (como está ahora es correcto)
|
||||||
|
* Se inicializa con un Theme al azar, Scene al azar, SimulationMode al azar. Restringido FIGURE->PNG_SHAPE
|
||||||
|
* Va cambiando de Theme
|
||||||
|
* Va cambiando de Scene
|
||||||
|
* Cambia la escala de la Figure
|
||||||
|
* Cambia el Sprite de las pelotas
|
||||||
|
* NO PUEDE cambiar a la figura PNG_SHAPE
|
||||||
|
* Eventualmente puede cambiar de manera automatica a LOGO LITE, sin restricciones
|
||||||
|
* El usuario puede cambiar el SimulationMode, el Theme o el Scene. Esto no hace que se salga del modo DEMO
|
||||||
|
* El usuario puede cambiar de AppMode pulsando:
|
||||||
|
* D: SANDBOX
|
||||||
|
* L: DEMO LITE
|
||||||
|
* K: LOGO
|
||||||
|
* DEMO LITE
|
||||||
|
* En el modo DEMO el programa va cambiando el SimulationMode de manera automatica (como está ahora es correcto)
|
||||||
|
* Se inicializa con un Theme al azar, Scene al azar, SimulationMode al azar. Restringido FIGURE->PNG_SHAPE
|
||||||
|
* Este modo es exactamente igual a DEMO pero NO PUEDE:
|
||||||
|
* Cambiar de Scene
|
||||||
|
* Cambiar de Theme
|
||||||
|
* Cambiar el Sprite de las pelotas
|
||||||
|
* Eventualmente puede cambiar de manera automatica a LOGO LITE, sin restricciones
|
||||||
|
* NO PUEDE cambiar a la figura PNG_SHAPE
|
||||||
|
* El usuario puede cambiar el SimulationMode, el Theme o el Scene. Esto no hace que se salga del modo DEMO LITE
|
||||||
|
* El usuario puede cambiar de AppMode pulsando:
|
||||||
|
* D: DEMO
|
||||||
|
* L: SANDBOX
|
||||||
|
* K: LOGO
|
||||||
|
* LOGO
|
||||||
|
* Se inicializa con la Scene de 5.000 pelotas, con el tamaño de Sprite->Small, con SimulationMode en FIGURE->PNG_SHAPE, con un tema al azar de los permitidos
|
||||||
|
* No cambia de Scene
|
||||||
|
* No cambia el tamaño de Sprite
|
||||||
|
* No cambia la escala de FIGURE
|
||||||
|
* Los temas permitidos son MONOCROMO, LAVANDA, CARMESI, ESMERALDA o cualquiera de los temas dinamicos
|
||||||
|
* En este modo SOLO aparece la figura PNG_SHAPE
|
||||||
|
* Solo cambiara a los temas permitidos
|
||||||
|
* Cambia el SimulationMode de PHYSICS a FIGURE (como hace ahora) pero no a BOIDS. BOIDS prohibido
|
||||||
|
* El usuario puede cambiar el SimulationMode, el Theme o el Scene. Esto no hace que se salga del modo LOGO. Incluso puede poner un Theme no permitido o otro Scene.
|
||||||
|
* El automatismo no cambia nunca de Theme así que se mantiene el del usuario.
|
||||||
|
* El automatismo no cambia nunca de Scene asi que se mantiene el del usuario.
|
||||||
|
* El usuario puede cambiar de AppMode pulsando:
|
||||||
|
* D: DEMO
|
||||||
|
* L: DEMO LITE
|
||||||
|
* K: SANDBOX
|
||||||
|
* B: SANDBOX->BOIDS
|
||||||
|
* LOGO LITE
|
||||||
|
* Este modo es exactamente igual al modo LOGO pero con unas pequeñas diferencias:
|
||||||
|
* Solo se accede a el de manera automatica, el usuario no puede invocarlo. No hay tecla
|
||||||
|
* Como se accede de manera automatica solo se puede llegar a él desde DEMO o DEMO LITE. Hay que guardar el estado en el que se encontraba AppMode, EngindeMode, Scene, Theme, Sprite, Scale... etc
|
||||||
|
* Este modo tiene una muy alta probabilidad de terminar, volviendo al estado anterior desde donde se invocó.
|
||||||
|
* El usuario puede cambiar de AppMode pulsando:
|
||||||
|
* D: Si el modo anterior era DEMO -> SANDBOX, else -> DEMO)
|
||||||
|
* L: Si el modo anterior era DEMO LITE -> SANDBOX, else -> DEMO LITE)
|
||||||
|
* K: LOGO
|
||||||
|
* B: SANDBOX->BOIDS
|
||||||
|
|
||||||
|
|
||||||
|
# Debug Hud
|
||||||
|
* En el debug hud hay que añadir que se vea SIEMPRE el AppMode (actualmente aparece centrado, hay que ponerlo a la izquierda) y no solo cietos AppModes
|
||||||
|
* Tiene que aparecer tambien el SimulationMode
|
||||||
|
* El modo de Vsync
|
||||||
|
* El modo de escalado entero, stretched, ventana
|
||||||
|
* la resolucion fisica
|
||||||
|
* la resolucion logica
|
||||||
|
* el refresco del panel
|
||||||
|
* El resto de cosas que salen
|
||||||
|
|
||||||
|
# Ventana de ayuda
|
||||||
|
* La ventana de ayuda actualmente es cuadrada
|
||||||
|
* Esa es la anchura minima que ha de tener
|
||||||
|
* Hay que ver cual es la linea mas larga, multiplicarla por el numero de columnas, añadirle los paddings y que ese sea el nuevo ancho
|
||||||
|
* Actualmente se renderiza a cada frame. El rendimiento cae de los 1200 frames por segundo a 200 frames por segundo. Habria que renderizarla a una textura o algo. El problema es que el cambio de Theme con LERP afecta a los colores de la ventana. Hay que investigar qué se puede hacer.
|
||||||
|
|
||||||
|
# Bugs actuales
|
||||||
|
* En el modo LOGO, si se pulsa un cursor, se activa la gravedad y deja de funcionar los automatismos. Incluso he llegado a ver como sale solo del modo LOGO sin pulsar nada
|
||||||
|
* En el modo BOIDS, pulsar la G activa la gravedad. La G deberia pasar al modo PHYSICS con la gravedad en OFF y que las pelotas mantuvieran el momento/inercia
|
||||||
BIN
data/logo/logo.png
Normal file
BIN
data/logo/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
BIN
data/logo/logo2.png
Normal file
BIN
data/logo/logo2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
635
source/app_logo.cpp
Normal file
635
source/app_logo.cpp
Normal file
@@ -0,0 +1,635 @@
|
|||||||
|
#include "app_logo.h"
|
||||||
|
|
||||||
|
#include <SDL3/SDL_render.h> // for SDL_SCALEMODE_LINEAR, SDL_RenderGeometry
|
||||||
|
#include <cmath> // for powf, sinf, cosf
|
||||||
|
|
||||||
|
#include "external/sprite.h" // for Sprite
|
||||||
|
#include "external/texture.h" // for Texture
|
||||||
|
|
||||||
|
bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) {
|
||||||
|
renderer_ = renderer;
|
||||||
|
screen_width_ = screen_width;
|
||||||
|
screen_height_ = screen_height;
|
||||||
|
|
||||||
|
std::string resources_dir = getResourcesDirectory();
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Cargar LOGO1 desde data/logo/logo.png
|
||||||
|
// ========================================================================
|
||||||
|
std::string logo1_path = resources_dir + "/data/logo/logo.png";
|
||||||
|
logo1_texture_ = std::make_shared<Texture>(renderer, logo1_path);
|
||||||
|
if (logo1_texture_->getWidth() == 0 || logo1_texture_->getHeight() == 0) {
|
||||||
|
// Error al cargar textura logo1
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurar filtrado LINEAR para suavizado
|
||||||
|
logo1_texture_->setScaleMode(SDL_SCALEMODE_LINEAR);
|
||||||
|
|
||||||
|
// Crear sprite con la textura
|
||||||
|
logo1_sprite_ = std::make_unique<Sprite>(logo1_texture_);
|
||||||
|
|
||||||
|
// Configurar el clip para que use toda la textura
|
||||||
|
float logo1_width = static_cast<float>(logo1_texture_->getWidth());
|
||||||
|
float logo1_height = static_cast<float>(logo1_texture_->getHeight());
|
||||||
|
logo1_sprite_->setClip({0.0f, 0.0f, logo1_width, logo1_height});
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Cargar LOGO2 desde data/logo/logo2.png
|
||||||
|
// ========================================================================
|
||||||
|
std::string logo2_path = resources_dir + "/data/logo/logo2.png";
|
||||||
|
logo2_texture_ = std::make_shared<Texture>(renderer, logo2_path);
|
||||||
|
if (logo2_texture_->getWidth() == 0 || logo2_texture_->getHeight() == 0) {
|
||||||
|
// Error al cargar textura logo2
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurar filtrado LINEAR para suavizado
|
||||||
|
logo2_texture_->setScaleMode(SDL_SCALEMODE_LINEAR);
|
||||||
|
|
||||||
|
// Crear sprite con la textura
|
||||||
|
logo2_sprite_ = std::make_unique<Sprite>(logo2_texture_);
|
||||||
|
|
||||||
|
// Configurar el clip para que use toda la textura
|
||||||
|
float logo2_width = static_cast<float>(logo2_texture_->getWidth());
|
||||||
|
float logo2_height = static_cast<float>(logo2_texture_->getHeight());
|
||||||
|
logo2_sprite_->setClip({0.0f, 0.0f, logo2_width, logo2_height});
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Calcular tamaño base (asumimos mismo tamaño para ambos logos)
|
||||||
|
// El logo debe tener una altura de APPLOGO_HEIGHT_PERCENT (40%) de la pantalla
|
||||||
|
// ========================================================================
|
||||||
|
float target_height = screen_height_ * APPLOGO_HEIGHT_PERCENT;
|
||||||
|
float scale = target_height / logo1_height;
|
||||||
|
|
||||||
|
base_width_ = logo1_width * scale;
|
||||||
|
base_height_ = target_height; // = logo1_height * scale
|
||||||
|
|
||||||
|
// Aplicar escala inicial a ambos sprites
|
||||||
|
logo1_sprite_->setSize(base_width_, base_height_);
|
||||||
|
logo2_sprite_->setSize(base_width_, base_height_);
|
||||||
|
|
||||||
|
// Posicionar ambos logos en el centro del cuadrante inferior derecho (superpuestos)
|
||||||
|
updateLogoPosition();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||||
|
// Si estamos en SANDBOX, resetear y no hacer nada (logo desactivado)
|
||||||
|
if (current_mode == AppMode::SANDBOX) {
|
||||||
|
state_ = AppLogoState::HIDDEN;
|
||||||
|
timer_ = 0.0f;
|
||||||
|
current_alpha_ = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Máquina de estados con fade in/out
|
||||||
|
timer_ += delta_time;
|
||||||
|
|
||||||
|
switch (state_) {
|
||||||
|
case AppLogoState::HIDDEN:
|
||||||
|
// Esperando el intervalo de espera
|
||||||
|
if (timer_ >= APPLOGO_DISPLAY_INTERVAL) {
|
||||||
|
state_ = AppLogoState::FADE_IN;
|
||||||
|
timer_ = 0.0f;
|
||||||
|
current_alpha_ = 0;
|
||||||
|
// Elegir animaciones de entrada aleatorias (independientes para cada logo)
|
||||||
|
logo1_entry_animation_ = getRandomAnimation();
|
||||||
|
logo2_entry_animation_ = getRandomAnimation();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoState::FADE_IN:
|
||||||
|
// Fade in: alpha de 0 a 255, animaciones independientes para logo1 y logo2
|
||||||
|
{
|
||||||
|
float fade_progress = timer_ / APPLOGO_FADE_DURATION;
|
||||||
|
if (fade_progress >= 1.0f) {
|
||||||
|
// Fade in completado
|
||||||
|
state_ = AppLogoState::VISIBLE;
|
||||||
|
timer_ = 0.0f;
|
||||||
|
current_alpha_ = 255;
|
||||||
|
// Resetear variables de ambos logos
|
||||||
|
logo1_scale_ = 1.0f;
|
||||||
|
logo1_squash_y_ = 1.0f;
|
||||||
|
logo1_stretch_x_ = 1.0f;
|
||||||
|
logo1_rotation_ = 0.0f;
|
||||||
|
logo2_scale_ = 1.0f;
|
||||||
|
logo2_squash_y_ = 1.0f;
|
||||||
|
logo2_stretch_x_ = 1.0f;
|
||||||
|
logo2_rotation_ = 0.0f;
|
||||||
|
} else {
|
||||||
|
// Interpolar alpha linealmente (0 → 255) - compartido
|
||||||
|
current_alpha_ = static_cast<int>(fade_progress * 255.0f);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// Aplicar animación de LOGO1 según logo1_entry_animation_
|
||||||
|
// ================================================================
|
||||||
|
switch (logo1_entry_animation_) {
|
||||||
|
case AppLogoAnimationType::ZOOM_ONLY:
|
||||||
|
logo1_scale_ = 1.2f - (fade_progress * 0.2f);
|
||||||
|
logo1_squash_y_ = 1.0f;
|
||||||
|
logo1_stretch_x_ = 1.0f;
|
||||||
|
logo1_rotation_ = 0.0f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::ELASTIC_STICK:
|
||||||
|
{
|
||||||
|
float elastic_t = easeOutElastic(fade_progress);
|
||||||
|
logo1_scale_ = 1.2f - (elastic_t * 0.2f);
|
||||||
|
float squash_t = easeOutBack(fade_progress);
|
||||||
|
logo1_squash_y_ = 0.6f + (squash_t * 0.4f);
|
||||||
|
logo1_stretch_x_ = 1.0f + (1.0f - logo1_squash_y_) * 0.5f;
|
||||||
|
logo1_rotation_ = 0.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||||
|
{
|
||||||
|
float ease_t = easeInOutQuad(fade_progress);
|
||||||
|
logo1_scale_ = 0.3f + (ease_t * 0.7f);
|
||||||
|
logo1_rotation_ = (1.0f - fade_progress) * 6.28f;
|
||||||
|
logo1_squash_y_ = 1.0f;
|
||||||
|
logo1_stretch_x_ = 1.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||||
|
{
|
||||||
|
float bounce_t = easeOutBounce(fade_progress);
|
||||||
|
logo1_scale_ = 1.0f;
|
||||||
|
float squash_amount = (1.0f - bounce_t) * 0.3f;
|
||||||
|
logo1_squash_y_ = 1.0f - squash_amount;
|
||||||
|
logo1_stretch_x_ = 1.0f + squash_amount * 0.5f;
|
||||||
|
logo1_rotation_ = 0.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// Aplicar animación de LOGO2 según logo2_entry_animation_
|
||||||
|
// ================================================================
|
||||||
|
switch (logo2_entry_animation_) {
|
||||||
|
case AppLogoAnimationType::ZOOM_ONLY:
|
||||||
|
logo2_scale_ = 1.2f - (fade_progress * 0.2f);
|
||||||
|
logo2_squash_y_ = 1.0f;
|
||||||
|
logo2_stretch_x_ = 1.0f;
|
||||||
|
logo2_rotation_ = 0.0f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::ELASTIC_STICK:
|
||||||
|
{
|
||||||
|
float elastic_t = easeOutElastic(fade_progress);
|
||||||
|
logo2_scale_ = 1.2f - (elastic_t * 0.2f);
|
||||||
|
float squash_t = easeOutBack(fade_progress);
|
||||||
|
logo2_squash_y_ = 0.6f + (squash_t * 0.4f);
|
||||||
|
logo2_stretch_x_ = 1.0f + (1.0f - logo2_squash_y_) * 0.5f;
|
||||||
|
logo2_rotation_ = 0.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||||
|
{
|
||||||
|
float ease_t = easeInOutQuad(fade_progress);
|
||||||
|
logo2_scale_ = 0.3f + (ease_t * 0.7f);
|
||||||
|
logo2_rotation_ = (1.0f - fade_progress) * 6.28f;
|
||||||
|
logo2_squash_y_ = 1.0f;
|
||||||
|
logo2_stretch_x_ = 1.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||||
|
{
|
||||||
|
float bounce_t = easeOutBounce(fade_progress);
|
||||||
|
logo2_scale_ = 1.0f;
|
||||||
|
float squash_amount = (1.0f - bounce_t) * 0.3f;
|
||||||
|
logo2_squash_y_ = 1.0f - squash_amount;
|
||||||
|
logo2_stretch_x_ = 1.0f + squash_amount * 0.5f;
|
||||||
|
logo2_rotation_ = 0.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoState::VISIBLE:
|
||||||
|
// Logo completamente visible, esperando duración
|
||||||
|
if (timer_ >= APPLOGO_DISPLAY_DURATION) {
|
||||||
|
state_ = AppLogoState::FADE_OUT;
|
||||||
|
timer_ = 0.0f;
|
||||||
|
current_alpha_ = 255;
|
||||||
|
// Elegir animaciones de salida aleatorias (independientes para cada logo)
|
||||||
|
logo1_exit_animation_ = getRandomAnimation();
|
||||||
|
logo2_exit_animation_ = getRandomAnimation();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoState::FADE_OUT:
|
||||||
|
// Fade out: alpha de 255 a 0, animaciones independientes para logo1 y logo2
|
||||||
|
{
|
||||||
|
float fade_progress = timer_ / APPLOGO_FADE_DURATION;
|
||||||
|
if (fade_progress >= 1.0f) {
|
||||||
|
// Fade out completado, volver a HIDDEN
|
||||||
|
state_ = AppLogoState::HIDDEN;
|
||||||
|
timer_ = 0.0f;
|
||||||
|
current_alpha_ = 0;
|
||||||
|
// Resetear variables de ambos logos
|
||||||
|
logo1_scale_ = 1.0f;
|
||||||
|
logo1_squash_y_ = 1.0f;
|
||||||
|
logo1_stretch_x_ = 1.0f;
|
||||||
|
logo1_rotation_ = 0.0f;
|
||||||
|
logo2_scale_ = 1.0f;
|
||||||
|
logo2_squash_y_ = 1.0f;
|
||||||
|
logo2_stretch_x_ = 1.0f;
|
||||||
|
logo2_rotation_ = 0.0f;
|
||||||
|
} else {
|
||||||
|
// Interpolar alpha linealmente (255 → 0) - compartido
|
||||||
|
current_alpha_ = static_cast<int>((1.0f - fade_progress) * 255.0f);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// Aplicar animación de LOGO1 según logo1_exit_animation_
|
||||||
|
// ================================================================
|
||||||
|
switch (logo1_exit_animation_) {
|
||||||
|
case AppLogoAnimationType::ZOOM_ONLY:
|
||||||
|
logo1_scale_ = 1.0f + (fade_progress * 0.2f);
|
||||||
|
logo1_squash_y_ = 1.0f;
|
||||||
|
logo1_stretch_x_ = 1.0f;
|
||||||
|
logo1_rotation_ = 0.0f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::ELASTIC_STICK:
|
||||||
|
logo1_scale_ = 1.0f + (fade_progress * fade_progress * 0.2f);
|
||||||
|
logo1_squash_y_ = 1.0f + (fade_progress * 0.3f);
|
||||||
|
logo1_stretch_x_ = 1.0f - (fade_progress * 0.2f);
|
||||||
|
logo1_rotation_ = fade_progress * 0.1f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||||
|
{
|
||||||
|
float ease_t = easeInOutQuad(fade_progress);
|
||||||
|
logo1_scale_ = 1.0f - (ease_t * 0.7f);
|
||||||
|
logo1_rotation_ = fade_progress * 6.28f;
|
||||||
|
logo1_squash_y_ = 1.0f;
|
||||||
|
logo1_stretch_x_ = 1.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||||
|
{
|
||||||
|
if (fade_progress < 0.2f) {
|
||||||
|
float squash_t = fade_progress / 0.2f;
|
||||||
|
logo1_squash_y_ = 1.0f - (squash_t * 0.3f);
|
||||||
|
logo1_stretch_x_ = 1.0f + (squash_t * 0.2f);
|
||||||
|
} else {
|
||||||
|
float jump_t = (fade_progress - 0.2f) / 0.8f;
|
||||||
|
logo1_squash_y_ = 0.7f + (jump_t * 0.5f);
|
||||||
|
logo1_stretch_x_ = 1.2f - (jump_t * 0.2f);
|
||||||
|
}
|
||||||
|
logo1_scale_ = 1.0f + (fade_progress * 0.3f);
|
||||||
|
logo1_rotation_ = 0.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// Aplicar animación de LOGO2 según logo2_exit_animation_
|
||||||
|
// ================================================================
|
||||||
|
switch (logo2_exit_animation_) {
|
||||||
|
case AppLogoAnimationType::ZOOM_ONLY:
|
||||||
|
logo2_scale_ = 1.0f + (fade_progress * 0.2f);
|
||||||
|
logo2_squash_y_ = 1.0f;
|
||||||
|
logo2_stretch_x_ = 1.0f;
|
||||||
|
logo2_rotation_ = 0.0f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::ELASTIC_STICK:
|
||||||
|
logo2_scale_ = 1.0f + (fade_progress * fade_progress * 0.2f);
|
||||||
|
logo2_squash_y_ = 1.0f + (fade_progress * 0.3f);
|
||||||
|
logo2_stretch_x_ = 1.0f - (fade_progress * 0.2f);
|
||||||
|
logo2_rotation_ = fade_progress * 0.1f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||||
|
{
|
||||||
|
float ease_t = easeInOutQuad(fade_progress);
|
||||||
|
logo2_scale_ = 1.0f - (ease_t * 0.7f);
|
||||||
|
logo2_rotation_ = fade_progress * 6.28f;
|
||||||
|
logo2_squash_y_ = 1.0f;
|
||||||
|
logo2_stretch_x_ = 1.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||||
|
{
|
||||||
|
if (fade_progress < 0.2f) {
|
||||||
|
float squash_t = fade_progress / 0.2f;
|
||||||
|
logo2_squash_y_ = 1.0f - (squash_t * 0.3f);
|
||||||
|
logo2_stretch_x_ = 1.0f + (squash_t * 0.2f);
|
||||||
|
} else {
|
||||||
|
float jump_t = (fade_progress - 0.2f) / 0.8f;
|
||||||
|
logo2_squash_y_ = 0.7f + (jump_t * 0.5f);
|
||||||
|
logo2_stretch_x_ = 1.2f - (jump_t * 0.2f);
|
||||||
|
}
|
||||||
|
logo2_scale_ = 1.0f + (fade_progress * 0.3f);
|
||||||
|
logo2_rotation_ = 0.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplicar alpha a ambos logos (compartido - sincronizado)
|
||||||
|
if (logo1_texture_) {
|
||||||
|
logo1_texture_->setAlpha(current_alpha_);
|
||||||
|
}
|
||||||
|
if (logo2_texture_) {
|
||||||
|
logo2_texture_->setAlpha(current_alpha_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplicar escala animada INDEPENDIENTE a cada logo
|
||||||
|
if (logo1_sprite_) {
|
||||||
|
float scaled_width = base_width_ * logo1_scale_;
|
||||||
|
float scaled_height = base_height_ * logo1_scale_;
|
||||||
|
logo1_sprite_->setSize(scaled_width, scaled_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logo2_sprite_) {
|
||||||
|
float scaled_width = base_width_ * logo2_scale_;
|
||||||
|
float scaled_height = base_height_ * logo2_scale_;
|
||||||
|
logo2_sprite_->setSize(scaled_width, scaled_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recentrar ambos logos (están superpuestos, misma posición)
|
||||||
|
updateLogoPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppLogo::render() {
|
||||||
|
// Renderizar si NO está en estado HIDDEN (incluye FADE_IN, VISIBLE, FADE_OUT)
|
||||||
|
if (state_ != AppLogoState::HIDDEN) {
|
||||||
|
// Determinar animaciones actuales para cada logo
|
||||||
|
AppLogoAnimationType logo1_anim = (state_ == AppLogoState::FADE_IN) ? logo1_entry_animation_ : logo1_exit_animation_;
|
||||||
|
AppLogoAnimationType logo2_anim = (state_ == AppLogoState::FADE_IN) ? logo2_entry_animation_ : logo2_exit_animation_;
|
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Renderizar LOGO1 primero (fondo)
|
||||||
|
// ====================================================================
|
||||||
|
if (logo1_anim != AppLogoAnimationType::ZOOM_ONLY) {
|
||||||
|
// Usar renderizado con geometría para deformaciones/rotación
|
||||||
|
renderWithGeometry(1);
|
||||||
|
} else if (logo1_sprite_) {
|
||||||
|
// Usar renderizado simple con Sprite (solo ZOOM_ONLY)
|
||||||
|
logo1_sprite_->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Renderizar LOGO2 después (encima de logo1)
|
||||||
|
// ====================================================================
|
||||||
|
if (logo2_anim != AppLogoAnimationType::ZOOM_ONLY) {
|
||||||
|
// Usar renderizado con geometría para deformaciones/rotación
|
||||||
|
renderWithGeometry(2);
|
||||||
|
} else if (logo2_sprite_) {
|
||||||
|
// Usar renderizado simple con Sprite (solo ZOOM_ONLY)
|
||||||
|
logo2_sprite_->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppLogo::updateScreenSize(int screen_width, int screen_height) {
|
||||||
|
screen_width_ = screen_width;
|
||||||
|
screen_height_ = screen_height;
|
||||||
|
|
||||||
|
// Recalcular tamaño base para la nueva resolución (asumimos mismo tamaño para ambos logos)
|
||||||
|
if (logo1_sprite_ && logo1_texture_) {
|
||||||
|
float logo_width = static_cast<float>(logo1_texture_->getWidth());
|
||||||
|
float logo_height = static_cast<float>(logo1_texture_->getHeight());
|
||||||
|
|
||||||
|
// El logo debe tener una altura de APPLOGO_HEIGHT_PERCENT (40%) de la pantalla
|
||||||
|
float target_height = screen_height_ * APPLOGO_HEIGHT_PERCENT;
|
||||||
|
float scale = target_height / logo_height;
|
||||||
|
|
||||||
|
// Recalcular tamaño base
|
||||||
|
base_width_ = logo_width * scale;
|
||||||
|
base_height_ = target_height; // = logo_height * scale
|
||||||
|
|
||||||
|
// Aplicar escala actual a AMBOS logos (respeta la animación en curso)
|
||||||
|
if (logo1_sprite_) {
|
||||||
|
float scaled_width = base_width_ * logo1_scale_;
|
||||||
|
float scaled_height = base_height_ * logo1_scale_;
|
||||||
|
logo1_sprite_->setSize(scaled_width, scaled_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logo2_sprite_) {
|
||||||
|
float scaled_width = base_width_ * logo2_scale_;
|
||||||
|
float scaled_height = base_height_ * logo2_scale_;
|
||||||
|
logo2_sprite_->setSize(scaled_width, scaled_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reposicionar ambos logos
|
||||||
|
updateLogoPosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppLogo::updateLogoPosition() {
|
||||||
|
// Calcular padding desde bordes derecho e inferior
|
||||||
|
float padding_x = screen_width_ * APPLOGO_PADDING_PERCENT;
|
||||||
|
float padding_y = screen_height_ * APPLOGO_PADDING_PERCENT;
|
||||||
|
|
||||||
|
// Posicionar LOGO1 (anclado a esquina inferior derecha con padding)
|
||||||
|
if (logo1_sprite_) {
|
||||||
|
float logo1_width = base_width_ * logo1_scale_;
|
||||||
|
float logo1_height = base_height_ * logo1_scale_;
|
||||||
|
float pos_x = screen_width_ - logo1_width - padding_x;
|
||||||
|
float pos_y = screen_height_ - logo1_height - padding_y;
|
||||||
|
logo1_sprite_->setPos({pos_x, pos_y});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Posicionar LOGO2 (anclado a esquina inferior derecha con padding, superpuesto a logo1)
|
||||||
|
if (logo2_sprite_) {
|
||||||
|
float logo2_width = base_width_ * logo2_scale_;
|
||||||
|
float logo2_height = base_height_ * logo2_scale_;
|
||||||
|
float pos_x = screen_width_ - logo2_width - padding_x;
|
||||||
|
float pos_y = screen_height_ - logo2_height - padding_y;
|
||||||
|
logo2_sprite_->setPos({pos_x, pos_y});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Funciones de easing para animaciones
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
float AppLogo::easeOutElastic(float t) {
|
||||||
|
// Elastic easing out: bounce elástico al final
|
||||||
|
const float c4 = (2.0f * 3.14159f) / 3.0f;
|
||||||
|
|
||||||
|
if (t == 0.0f) return 0.0f;
|
||||||
|
if (t == 1.0f) return 1.0f;
|
||||||
|
|
||||||
|
return powf(2.0f, -10.0f * t) * sinf((t * 10.0f - 0.75f) * c4) + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float AppLogo::easeOutBack(float t) {
|
||||||
|
// Back easing out: overshoot suave al final
|
||||||
|
const float c1 = 1.70158f;
|
||||||
|
const float c3 = c1 + 1.0f;
|
||||||
|
|
||||||
|
return 1.0f + c3 * powf(t - 1.0f, 3.0f) + c1 * powf(t - 1.0f, 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float AppLogo::easeOutBounce(float t) {
|
||||||
|
// Bounce easing out: rebotes decrecientes (para BOUNCE_SQUASH)
|
||||||
|
const float n1 = 7.5625f;
|
||||||
|
const float d1 = 2.75f;
|
||||||
|
|
||||||
|
if (t < 1.0f / d1) {
|
||||||
|
return n1 * t * t;
|
||||||
|
} else if (t < 2.0f / d1) {
|
||||||
|
t -= 1.5f / d1;
|
||||||
|
return n1 * t * t + 0.75f;
|
||||||
|
} else if (t < 2.5f / d1) {
|
||||||
|
t -= 2.25f / d1;
|
||||||
|
return n1 * t * t + 0.9375f;
|
||||||
|
} else {
|
||||||
|
t -= 2.625f / d1;
|
||||||
|
return n1 * t * t + 0.984375f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float AppLogo::easeInOutQuad(float t) {
|
||||||
|
// Quadratic easing in/out: aceleración suave (para ROTATE_SPIRAL)
|
||||||
|
if (t < 0.5f) {
|
||||||
|
return 2.0f * t * t;
|
||||||
|
} else {
|
||||||
|
return 1.0f - powf(-2.0f * t + 2.0f, 2.0f) / 2.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Función auxiliar para aleatorización
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
AppLogoAnimationType AppLogo::getRandomAnimation() {
|
||||||
|
// Generar número aleatorio entre 0 y 3 (4 tipos de animación)
|
||||||
|
int random_value = rand() % 4;
|
||||||
|
|
||||||
|
switch (random_value) {
|
||||||
|
case 0:
|
||||||
|
return AppLogoAnimationType::ZOOM_ONLY;
|
||||||
|
case 1:
|
||||||
|
return AppLogoAnimationType::ELASTIC_STICK;
|
||||||
|
case 2:
|
||||||
|
return AppLogoAnimationType::ROTATE_SPIRAL;
|
||||||
|
case 3:
|
||||||
|
default:
|
||||||
|
return AppLogoAnimationType::BOUNCE_SQUASH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Renderizado con geometría deformada (para animación ELASTIC_STICK)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void AppLogo::renderWithGeometry(int logo_index) {
|
||||||
|
if (!renderer_) return;
|
||||||
|
|
||||||
|
// Seleccionar variables según el logo_index (1 = logo1, 2 = logo2)
|
||||||
|
std::shared_ptr<Texture> texture;
|
||||||
|
float scale, squash_y, stretch_x, rotation;
|
||||||
|
|
||||||
|
if (logo_index == 1) {
|
||||||
|
if (!logo1_texture_) return;
|
||||||
|
texture = logo1_texture_;
|
||||||
|
scale = logo1_scale_;
|
||||||
|
squash_y = logo1_squash_y_;
|
||||||
|
stretch_x = logo1_stretch_x_;
|
||||||
|
rotation = logo1_rotation_;
|
||||||
|
} else if (logo_index == 2) {
|
||||||
|
if (!logo2_texture_) return;
|
||||||
|
texture = logo2_texture_;
|
||||||
|
scale = logo2_scale_;
|
||||||
|
squash_y = logo2_squash_y_;
|
||||||
|
stretch_x = logo2_stretch_x_;
|
||||||
|
rotation = logo2_rotation_;
|
||||||
|
} else {
|
||||||
|
return; // Índice inválido
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcular tamaño con escala y deformaciones aplicadas
|
||||||
|
float width = base_width_ * scale * stretch_x;
|
||||||
|
float height = base_height_ * scale * squash_y;
|
||||||
|
|
||||||
|
// Calcular padding desde bordes derecho e inferior
|
||||||
|
float padding_x = screen_width_ * APPLOGO_PADDING_PERCENT;
|
||||||
|
float padding_y = screen_height_ * APPLOGO_PADDING_PERCENT;
|
||||||
|
|
||||||
|
// Calcular esquina del logo (anclado a esquina inferior derecha con padding)
|
||||||
|
float corner_x = screen_width_ - width - padding_x;
|
||||||
|
float corner_y = screen_height_ - height - padding_y;
|
||||||
|
|
||||||
|
// Centro del logo (para rotación) = esquina + mitad del tamaño
|
||||||
|
float center_x = corner_x + (width / 2.0f);
|
||||||
|
float center_y = corner_y + (height / 2.0f);
|
||||||
|
|
||||||
|
// Pre-calcular seno y coseno de rotación
|
||||||
|
float cos_rot = cosf(rotation);
|
||||||
|
float sin_rot = sinf(rotation);
|
||||||
|
|
||||||
|
// Crear 4 vértices del quad (centrado en center_x, center_y)
|
||||||
|
SDL_Vertex vertices[4];
|
||||||
|
|
||||||
|
// Offset desde el centro
|
||||||
|
float half_w = width / 2.0f;
|
||||||
|
float half_h = height / 2.0f;
|
||||||
|
|
||||||
|
// Vértice superior izquierdo (rotado)
|
||||||
|
{
|
||||||
|
float local_x = -half_w;
|
||||||
|
float local_y = -half_h;
|
||||||
|
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
||||||
|
float rotated_y = local_x * sin_rot + local_y * cos_rot;
|
||||||
|
vertices[0].position = {center_x + rotated_x, center_y + rotated_y};
|
||||||
|
vertices[0].tex_coord = {0.0f, 0.0f};
|
||||||
|
vertices[0].color = {1.0f, 1.0f, 1.0f, 1.0f}; // Color blanco (textura se modula con alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vértice superior derecho (rotado)
|
||||||
|
{
|
||||||
|
float local_x = half_w;
|
||||||
|
float local_y = -half_h;
|
||||||
|
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
||||||
|
float rotated_y = local_x * sin_rot + local_y * cos_rot;
|
||||||
|
vertices[1].position = {center_x + rotated_x, center_y + rotated_y};
|
||||||
|
vertices[1].tex_coord = {1.0f, 0.0f};
|
||||||
|
vertices[1].color = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vértice inferior derecho (rotado)
|
||||||
|
{
|
||||||
|
float local_x = half_w;
|
||||||
|
float local_y = half_h;
|
||||||
|
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
||||||
|
float rotated_y = local_x * sin_rot + local_y * cos_rot;
|
||||||
|
vertices[2].position = {center_x + rotated_x, center_y + rotated_y};
|
||||||
|
vertices[2].tex_coord = {1.0f, 1.0f};
|
||||||
|
vertices[2].color = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vértice inferior izquierdo (rotado)
|
||||||
|
{
|
||||||
|
float local_x = -half_w;
|
||||||
|
float local_y = half_h;
|
||||||
|
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
||||||
|
float rotated_y = local_x * sin_rot + local_y * cos_rot;
|
||||||
|
vertices[3].position = {center_x + rotated_x, center_y + rotated_y};
|
||||||
|
vertices[3].tex_coord = {0.0f, 1.0f};
|
||||||
|
vertices[3].color = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Índices para 2 triángulos
|
||||||
|
int indices[6] = {0, 1, 2, 2, 3, 0};
|
||||||
|
|
||||||
|
// Renderizar con la textura del logo correspondiente
|
||||||
|
SDL_RenderGeometry(renderer_, texture->getSDLTexture(), vertices, 4, indices, 6);
|
||||||
|
}
|
||||||
97
source/app_logo.h
Normal file
97
source/app_logo.h
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL_render.h> // for SDL_Renderer
|
||||||
|
|
||||||
|
#include <memory> // for unique_ptr, shared_ptr
|
||||||
|
|
||||||
|
#include "defines.h" // for AppMode
|
||||||
|
|
||||||
|
class Texture;
|
||||||
|
class Sprite;
|
||||||
|
|
||||||
|
// Estados de la máquina de estados del logo
|
||||||
|
enum class AppLogoState {
|
||||||
|
HIDDEN, // Logo oculto, esperando APPLOGO_DISPLAY_INTERVAL
|
||||||
|
FADE_IN, // Apareciendo (alpha 0 → 255)
|
||||||
|
VISIBLE, // Completamente visible, esperando APPLOGO_DISPLAY_DURATION
|
||||||
|
FADE_OUT // Desapareciendo (alpha 255 → 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tipo de animación de entrada/salida
|
||||||
|
enum class AppLogoAnimationType {
|
||||||
|
ZOOM_ONLY, // A: Solo zoom simple (120% → 100% → 120%)
|
||||||
|
ELASTIC_STICK, // B: Zoom + deformación elástica tipo "pegatina"
|
||||||
|
ROTATE_SPIRAL, // C: Rotación en espiral (entra girando, sale girando)
|
||||||
|
BOUNCE_SQUASH // D: Rebote con aplastamiento (cae rebotando, salta)
|
||||||
|
};
|
||||||
|
|
||||||
|
class AppLogo {
|
||||||
|
public:
|
||||||
|
AppLogo() = default;
|
||||||
|
~AppLogo() = default;
|
||||||
|
|
||||||
|
// Inicializar textura y sprite del logo
|
||||||
|
bool initialize(SDL_Renderer* renderer, int screen_width, int screen_height);
|
||||||
|
|
||||||
|
// Actualizar temporizadores y estado de visibilidad
|
||||||
|
void update(float delta_time, AppMode current_mode);
|
||||||
|
|
||||||
|
// Renderizar logo si está visible
|
||||||
|
void render();
|
||||||
|
|
||||||
|
// Actualizar tamaño de pantalla (reposicionar logo)
|
||||||
|
void updateScreenSize(int screen_width, int screen_height);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Texturas y sprites (x2 - logo1 y logo2 superpuestos)
|
||||||
|
std::shared_ptr<Texture> logo1_texture_; // Textura del logo1 (data/logo/logo.png)
|
||||||
|
std::unique_ptr<Sprite> logo1_sprite_; // Sprite para renderizar logo1
|
||||||
|
std::shared_ptr<Texture> logo2_texture_; // Textura del logo2 (data/logo/logo2.png)
|
||||||
|
std::unique_ptr<Sprite> logo2_sprite_; // Sprite para renderizar logo2
|
||||||
|
|
||||||
|
// Variables COMPARTIDAS (sincronización de ambos logos)
|
||||||
|
AppLogoState state_ = AppLogoState::HIDDEN; // Estado actual de la máquina de estados
|
||||||
|
float timer_ = 0.0f; // Contador de tiempo para estado actual
|
||||||
|
int current_alpha_ = 0; // Alpha actual (0-255)
|
||||||
|
|
||||||
|
// Animaciones INDEPENDIENTES para cada logo
|
||||||
|
AppLogoAnimationType logo1_entry_animation_ = AppLogoAnimationType::ZOOM_ONLY;
|
||||||
|
AppLogoAnimationType logo1_exit_animation_ = AppLogoAnimationType::ZOOM_ONLY;
|
||||||
|
AppLogoAnimationType logo2_entry_animation_ = AppLogoAnimationType::ZOOM_ONLY;
|
||||||
|
AppLogoAnimationType logo2_exit_animation_ = AppLogoAnimationType::ZOOM_ONLY;
|
||||||
|
|
||||||
|
// Variables de deformación INDEPENDIENTES para logo1
|
||||||
|
float logo1_scale_ = 1.0f; // Escala actual de logo1 (1.0 = 100%)
|
||||||
|
float logo1_squash_y_ = 1.0f; // Factor de aplastamiento vertical logo1
|
||||||
|
float logo1_stretch_x_ = 1.0f; // Factor de estiramiento horizontal logo1
|
||||||
|
float logo1_rotation_ = 0.0f; // Rotación en radianes logo1
|
||||||
|
|
||||||
|
// Variables de deformación INDEPENDIENTES para logo2
|
||||||
|
float logo2_scale_ = 1.0f; // Escala actual de logo2 (1.0 = 100%)
|
||||||
|
float logo2_squash_y_ = 1.0f; // Factor de aplastamiento vertical logo2
|
||||||
|
float logo2_stretch_x_ = 1.0f; // Factor de estiramiento horizontal logo2
|
||||||
|
float logo2_rotation_ = 0.0f; // Rotación en radianes logo2
|
||||||
|
|
||||||
|
int screen_width_ = 0; // Ancho de pantalla (para centrar)
|
||||||
|
int screen_height_ = 0; // Alto de pantalla (para centrar)
|
||||||
|
|
||||||
|
// Tamaño base del logo (calculado una vez)
|
||||||
|
float base_width_ = 0.0f;
|
||||||
|
float base_height_ = 0.0f;
|
||||||
|
|
||||||
|
// SDL renderer (necesario para renderizado con geometría)
|
||||||
|
SDL_Renderer* renderer_ = nullptr;
|
||||||
|
|
||||||
|
// Métodos privados auxiliares
|
||||||
|
void updateLogoPosition(); // Centrar ambos logos en pantalla (superpuestos)
|
||||||
|
void renderWithGeometry(int logo_index); // Renderizar logo con vértices deformados (1 o 2)
|
||||||
|
|
||||||
|
// Funciones de easing
|
||||||
|
float easeOutElastic(float t); // Elastic bounce out
|
||||||
|
float easeOutBack(float t); // Overshoot out
|
||||||
|
float easeOutBounce(float t); // Bounce easing (para BOUNCE_SQUASH)
|
||||||
|
float easeInOutQuad(float t); // Quadratic easing (para ROTATE_SPIRAL)
|
||||||
|
|
||||||
|
// Función auxiliar para elegir animación aleatoria
|
||||||
|
AppLogoAnimationType getRandomAnimation();
|
||||||
|
};
|
||||||
@@ -22,9 +22,9 @@ float generateLateralLoss() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Ball::Ball(float x, 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, 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, 0.0f, static_cast<float>(ball_size), static_cast<float>(ball_size)}) {
|
pos_({x, y, static_cast<float>(ball_size), 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;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class Ball {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
Ball(float x, 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, 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;
|
||||||
|
|||||||
@@ -17,7 +17,18 @@ BoidManager::BoidManager()
|
|||||||
, 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)
|
||||||
|
, cohesion_radius_(BOID_COHESION_RADIUS)
|
||||||
|
, separation_weight_(BOID_SEPARATION_WEIGHT)
|
||||||
|
, alignment_weight_(BOID_ALIGNMENT_WEIGHT)
|
||||||
|
, cohesion_weight_(BOID_COHESION_WEIGHT)
|
||||||
|
, max_speed_(BOID_MAX_SPEED)
|
||||||
|
, min_speed_(BOID_MIN_SPEED)
|
||||||
|
, max_force_(BOID_MAX_FORCE)
|
||||||
|
, boundary_margin_(BOID_BOUNDARY_MARGIN)
|
||||||
|
, boundary_weight_(BOID_BOUNDARY_WEIGHT) {
|
||||||
}
|
}
|
||||||
|
|
||||||
BoidManager::~BoidManager() {
|
BoidManager::~BoidManager() {
|
||||||
@@ -57,9 +68,9 @@ void BoidManager::activateBoids() {
|
|||||||
float vx, vy;
|
float vx, 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 -1 y 1
|
// Velocidad aleatoria entre -60 y +60 px/s (time-based)
|
||||||
vx = (rand() % 200 - 100) / 100.0f;
|
vx = ((rand() % 200 - 100) / 100.0f) * 60.0f;
|
||||||
vy = (rand() % 200 - 100) / 100.0f;
|
vy = ((rand() % 200 - 100) / 100.0f) * 60.0f;
|
||||||
ball->setVelocity(vx, vy);
|
ball->setVelocity(vx, vy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,14 +129,14 @@ void BoidManager::update(float delta_time) {
|
|||||||
limitSpeed(ball.get());
|
limitSpeed(ball.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar posiciones con velocidades resultantes
|
// Actualizar posiciones con velocidades resultantes (time-based)
|
||||||
for (auto& ball : balls) {
|
for (auto& ball : balls) {
|
||||||
float vx, vy;
|
float vx, vy;
|
||||||
ball->getVelocity(vx, vy);
|
ball->getVelocity(vx, vy);
|
||||||
|
|
||||||
SDL_FRect pos = ball->getPosition();
|
SDL_FRect pos = ball->getPosition();
|
||||||
pos.x += vx;
|
pos.x += vx * delta_time; // time-based
|
||||||
pos.y += vy;
|
pos.y += vy * delta_time;
|
||||||
|
|
||||||
ball->setPosition(pos.x, pos.y);
|
ball->setPosition(pos.x, pos.y);
|
||||||
}
|
}
|
||||||
@@ -146,7 +157,7 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
|
|||||||
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, BOID_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
|
||||||
@@ -159,10 +170,10 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
|
|||||||
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 < BOID_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)
|
||||||
// Fuerza proporcional a cercanía: 0% en radio máximo, 100% en colisión
|
// Fuerza proporcional a cercanía: 0% en radio máximo, 100% en colisión
|
||||||
float separation_strength = (BOID_SEPARATION_RADIUS - distance) / BOID_SEPARATION_RADIUS;
|
float separation_strength = (separation_radius_ - distance) / separation_radius_;
|
||||||
steer_x += (dx / distance) * separation_strength;
|
steer_x += (dx / distance) * separation_strength;
|
||||||
steer_y += (dy / distance) * separation_strength;
|
steer_y += (dy / distance) * separation_strength;
|
||||||
count++;
|
count++;
|
||||||
@@ -177,8 +188,8 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
|
|||||||
// Aplicar fuerza de separación
|
// Aplicar fuerza de separación
|
||||||
float vx, vy;
|
float vx, vy;
|
||||||
boid->getVelocity(vx, vy);
|
boid->getVelocity(vx, vy);
|
||||||
vx += steer_x * BOID_SEPARATION_WEIGHT * delta_time;
|
vx += steer_x * separation_weight_ * delta_time;
|
||||||
vy += steer_y * BOID_SEPARATION_WEIGHT * delta_time;
|
vy += steer_y * separation_weight_ * delta_time;
|
||||||
boid->setVelocity(vx, vy);
|
boid->setVelocity(vx, vy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +205,7 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
|
|||||||
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, BOID_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;
|
||||||
@@ -207,7 +218,7 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
|
|||||||
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 < BOID_ALIGNMENT_RADIUS) {
|
if (distance < alignment_radius_) {
|
||||||
float other_vx, other_vy;
|
float other_vx, other_vy;
|
||||||
other->getVelocity(other_vx, other_vy);
|
other->getVelocity(other_vx, other_vy);
|
||||||
avg_vx += other_vx;
|
avg_vx += other_vx;
|
||||||
@@ -224,14 +235,14 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
|
|||||||
// Steering hacia la velocidad promedio
|
// Steering hacia la velocidad promedio
|
||||||
float vx, vy;
|
float vx, vy;
|
||||||
boid->getVelocity(vx, vy);
|
boid->getVelocity(vx, vy);
|
||||||
float steer_x = (avg_vx - vx) * BOID_ALIGNMENT_WEIGHT * delta_time;
|
float steer_x = (avg_vx - vx) * alignment_weight_ * delta_time;
|
||||||
float steer_y = (avg_vy - vy) * BOID_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 > BOID_MAX_FORCE) {
|
if (steer_mag > max_force_) {
|
||||||
steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE;
|
steer_x = (steer_x / steer_mag) * max_force_;
|
||||||
steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE;
|
steer_y = (steer_y / steer_mag) * max_force_;
|
||||||
}
|
}
|
||||||
|
|
||||||
vx += steer_x;
|
vx += steer_x;
|
||||||
@@ -251,7 +262,7 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
|||||||
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, BOID_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;
|
||||||
@@ -264,7 +275,7 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
|||||||
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 < BOID_COHESION_RADIUS) {
|
if (distance < cohesion_radius_) {
|
||||||
center_of_mass_x += other_x;
|
center_of_mass_x += other_x;
|
||||||
center_of_mass_y += other_y;
|
center_of_mass_y += other_y;
|
||||||
count++;
|
count++;
|
||||||
@@ -284,14 +295,14 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
|||||||
// 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) {
|
||||||
// Normalizar vector dirección (fuerza independiente de distancia)
|
// Normalizar vector dirección (fuerza independiente de distancia)
|
||||||
float steer_x = (dx_to_center / distance_to_center) * BOID_COHESION_WEIGHT * delta_time;
|
float steer_x = (dx_to_center / distance_to_center) * cohesion_weight_ * delta_time;
|
||||||
float steer_y = (dy_to_center / distance_to_center) * BOID_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 > BOID_MAX_FORCE) {
|
if (steer_mag > max_force_) {
|
||||||
steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE;
|
steer_x = (steer_x / steer_mag) * max_force_;
|
||||||
steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE;
|
steer_y = (steer_y / steer_mag) * max_force_;
|
||||||
}
|
}
|
||||||
|
|
||||||
float vx, vy;
|
float vx, vy;
|
||||||
@@ -304,32 +315,69 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BoidManager::applyBoundaries(Ball* boid) {
|
void BoidManager::applyBoundaries(Ball* boid) {
|
||||||
// Mantener boids dentro de los límites de la pantalla
|
// NUEVA IMPLEMENTACIÓN: Bordes como obstáculos (repulsión en lugar de wrapping)
|
||||||
// Comportamiento "wrapping" (teletransporte al otro lado)
|
// 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;
|
||||||
|
|
||||||
bool wrapped = false;
|
float steer_x = 0.0f;
|
||||||
|
float steer_y = 0.0f;
|
||||||
|
|
||||||
if (center_x < 0) {
|
// Borde izquierdo (x < boundary_margin_)
|
||||||
pos.x = screen_width_ - pos.w / 2.0f;
|
if (center_x < boundary_margin_) {
|
||||||
wrapped = true;
|
float distance = center_x; // Distancia al borde (0 = colisión)
|
||||||
} else if (center_x > screen_width_) {
|
if (distance < boundary_margin_) {
|
||||||
pos.x = -pos.w / 2.0f;
|
// Fuerza proporcional a cercanía: 0% en margen, 100% en colisión
|
||||||
wrapped = true;
|
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
|
||||||
|
steer_x += repulsion_strength; // Empujar hacia la derecha
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (center_y < 0) {
|
// Borde derecho (x > screen_width_ - boundary_margin_)
|
||||||
pos.y = screen_height_ - pos.h / 2.0f;
|
if (center_x > screen_width_ - boundary_margin_) {
|
||||||
wrapped = true;
|
float distance = screen_width_ - center_x;
|
||||||
} else if (center_y > screen_height_) {
|
if (distance < boundary_margin_) {
|
||||||
pos.y = -pos.h / 2.0f;
|
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
|
||||||
wrapped = true;
|
steer_x -= repulsion_strength; // Empujar hacia la izquierda
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wrapped) {
|
// Borde superior (y < boundary_margin_)
|
||||||
boid->setPosition(pos.x, pos.y);
|
if (center_y < boundary_margin_) {
|
||||||
|
float distance = center_y;
|
||||||
|
if (distance < boundary_margin_) {
|
||||||
|
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
|
||||||
|
steer_y += repulsion_strength; // Empujar hacia abajo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borde inferior (y > screen_height_ - boundary_margin_)
|
||||||
|
if (center_y > screen_height_ - boundary_margin_) {
|
||||||
|
float distance = screen_height_ - center_y;
|
||||||
|
if (distance < boundary_margin_) {
|
||||||
|
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
|
||||||
|
steer_y -= repulsion_strength; // Empujar hacia arriba
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplicar fuerza de repulsión si hay alguna
|
||||||
|
if (steer_x != 0.0f || steer_y != 0.0f) {
|
||||||
|
float vx, vy;
|
||||||
|
boid->getVelocity(vx, vy);
|
||||||
|
|
||||||
|
// Normalizar fuerza de repulsión (para que todas las direcciones tengan la misma intensidad)
|
||||||
|
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
|
||||||
|
if (steer_mag > 0.0f) {
|
||||||
|
steer_x /= steer_mag;
|
||||||
|
steer_y /= steer_mag;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplicar aceleración de repulsión (time-based)
|
||||||
|
// boundary_weight_ es más fuerte que separation para garantizar que no escapen
|
||||||
|
vx += steer_x * boundary_weight_ * (1.0f / 60.0f); // Simular delta_time fijo para independencia
|
||||||
|
vy += steer_y * boundary_weight_ * (1.0f / 60.0f);
|
||||||
|
boid->setVelocity(vx, vy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,16 +389,16 @@ void BoidManager::limitSpeed(Ball* boid) {
|
|||||||
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 > BOID_MAX_SPEED) {
|
if (speed > max_speed_) {
|
||||||
vx = (vx / speed) * BOID_MAX_SPEED;
|
vx = (vx / speed) * max_speed_;
|
||||||
vy = (vy / speed) * BOID_MAX_SPEED;
|
vy = (vy / speed) * max_speed_;
|
||||||
boid->setVelocity(vx, vy);
|
boid->setVelocity(vx, vy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FASE 1.2: Aplicar velocidad mínima (evitar boids estáticos)
|
// FASE 1.2: Aplicar velocidad mínima (evitar boids estáticos)
|
||||||
if (speed > 0.0f && speed < BOID_MIN_SPEED) {
|
if (speed > 0.0f && speed < min_speed_) {
|
||||||
vx = (vx / speed) * BOID_MIN_SPEED;
|
vx = (vx / speed) * min_speed_;
|
||||||
vy = (vy / speed) * BOID_MIN_SPEED;
|
vy = (vy / speed) * min_speed_;
|
||||||
boid->setVelocity(vx, vy);
|
boid->setVelocity(vx, vy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,10 +103,24 @@ class BoidManager {
|
|||||||
// FASE 2: Grid reutilizable para búsquedas de vecinos
|
// FASE 2: Grid reutilizable para búsquedas de vecinos
|
||||||
SpatialGrid spatial_grid_;
|
SpatialGrid spatial_grid_;
|
||||||
|
|
||||||
|
// === Parámetros ajustables en runtime (inicializados con valores de defines.h) ===
|
||||||
|
// Permite modificar comportamiento sin recompilar (para tweaking/debug visual)
|
||||||
|
float separation_radius_; // Radio de separación (evitar colisiones)
|
||||||
|
float alignment_radius_; // Radio de alineación (matching de velocidad)
|
||||||
|
float cohesion_radius_; // Radio de cohesión (centro de masa)
|
||||||
|
float separation_weight_; // Peso fuerza de separación (aceleración px/s²)
|
||||||
|
float alignment_weight_; // Peso fuerza de alineación (steering proporcional)
|
||||||
|
float cohesion_weight_; // Peso fuerza de cohesión (aceleración px/s²)
|
||||||
|
float max_speed_; // Velocidad máxima (px/s)
|
||||||
|
float min_speed_; // Velocidad mínima (px/s)
|
||||||
|
float max_force_; // Fuerza máxima de steering (px/s)
|
||||||
|
float boundary_margin_; // Margen para repulsión de bordes (px)
|
||||||
|
float boundary_weight_; // Peso fuerza de repulsión de bordes (aceleración px/s²)
|
||||||
|
|
||||||
// Métodos privados para las reglas de Reynolds
|
// Métodos privados para las reglas de Reynolds
|
||||||
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); // Mantener boids dentro de pantalla
|
void applyBoundaries(Ball* boid); // Repulsión de bordes (ya no wrapping)
|
||||||
void limitSpeed(Ball* boid); // Limitar velocidad máxima
|
void limitSpeed(Ball* boid); // Limitar velocidad máxima
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -288,17 +288,31 @@ constexpr float LOGO_FLIP_TRIGGER_MIN = 0.20f; // 20% mínimo de progres
|
|||||||
constexpr float LOGO_FLIP_TRIGGER_MAX = 0.80f; // 80% máximo de progreso de flip para trigger
|
constexpr float LOGO_FLIP_TRIGGER_MAX = 0.80f; // 80% máximo de progreso de flip para trigger
|
||||||
constexpr int LOGO_FLIP_WAIT_PROBABILITY = 50; // 50% probabilidad de elegir el camino "esperar flip"
|
constexpr int LOGO_FLIP_WAIT_PROBABILITY = 50; // 50% probabilidad de elegir el camino "esperar flip"
|
||||||
|
|
||||||
|
// Configuración de AppLogo (logo periódico en pantalla)
|
||||||
|
constexpr float APPLOGO_DISPLAY_INTERVAL = 20.0f; // Intervalo entre apariciones del logo (segundos)
|
||||||
|
constexpr float APPLOGO_DISPLAY_DURATION = 5.0f; // Duración de visibilidad del logo (segundos)
|
||||||
|
constexpr float APPLOGO_FADE_DURATION = 0.5f; // Duración del fade in/out (segundos)
|
||||||
|
constexpr float APPLOGO_HEIGHT_PERCENT = 0.4f; // Altura del logo = 40% de la altura de pantalla
|
||||||
|
constexpr float APPLOGO_PADDING_PERCENT = 0.1f; // Padding desde esquina inferior-derecha = 10%
|
||||||
|
|
||||||
// Configuración de Modo BOIDS (comportamiento de enjambre)
|
// Configuración de Modo BOIDS (comportamiento de enjambre)
|
||||||
// FASE 1.1 REVISADA: Parámetros ajustados tras detectar cohesión mal normalizada
|
// TIME-BASED CONVERSION (frame-based → time-based):
|
||||||
|
// - Radios: sin cambios (píxeles)
|
||||||
|
// - Velocidades (MAX_SPEED, MIN_SPEED): ×60 (px/frame → px/s)
|
||||||
|
// - Aceleraciones puras (SEPARATION, COHESION): ×60² = ×3600 (px/frame² → px/s²)
|
||||||
|
// - Steering proporcional (ALIGNMENT): ×60 (proporcional a velocidad)
|
||||||
|
// - Límite velocidad (MAX_FORCE): ×60 (px/frame → px/s)
|
||||||
constexpr float BOID_SEPARATION_RADIUS = 30.0f; // Radio para evitar colisiones (píxeles)
|
constexpr float BOID_SEPARATION_RADIUS = 30.0f; // Radio para evitar colisiones (píxeles)
|
||||||
constexpr float BOID_ALIGNMENT_RADIUS = 50.0f; // Radio para alinear velocidad con vecinos
|
constexpr float BOID_ALIGNMENT_RADIUS = 50.0f; // Radio para alinear velocidad con vecinos
|
||||||
constexpr float BOID_COHESION_RADIUS = 80.0f; // Radio para moverse hacia centro del grupo
|
constexpr float BOID_COHESION_RADIUS = 80.0f; // Radio para moverse hacia centro del grupo
|
||||||
constexpr float BOID_SEPARATION_WEIGHT = 1.5f; // Peso de separación
|
constexpr float BOID_SEPARATION_WEIGHT = 5400.0f; // Aceleración de separación (px/s²) [era 1.5 × 3600]
|
||||||
constexpr float BOID_ALIGNMENT_WEIGHT = 1.0f; // Peso de alineación
|
constexpr float BOID_ALIGNMENT_WEIGHT = 60.0f; // Steering de alineación (proporcional) [era 1.0 × 60]
|
||||||
constexpr float BOID_COHESION_WEIGHT = 0.001f; // Peso de cohesión (MICRO - 1000x menor por falta de normalización)
|
constexpr float BOID_COHESION_WEIGHT = 3.6f; // Aceleración de cohesión (px/s²) [era 0.001 × 3600]
|
||||||
constexpr float BOID_MAX_SPEED = 2.5f; // Velocidad máxima (píxeles/frame - REDUCIDA)
|
constexpr float BOID_MAX_SPEED = 150.0f; // Velocidad máxima (px/s) [era 2.5 × 60]
|
||||||
constexpr float BOID_MAX_FORCE = 0.05f; // Fuerza máxima de steering (REDUCIDA para evitar aceleración excesiva)
|
constexpr float BOID_MAX_FORCE = 3.0f; // Fuerza máxima de steering (px/s) [era 0.05 × 60]
|
||||||
constexpr float BOID_MIN_SPEED = 0.3f; // Velocidad mínima (evita boids estáticos)
|
constexpr float BOID_MIN_SPEED = 18.0f; // Velocidad mínima (px/s) [era 0.3 × 60]
|
||||||
|
constexpr float BOID_BOUNDARY_MARGIN = 50.0f; // Distancia a borde para activar repulsión (píxeles)
|
||||||
|
constexpr float BOID_BOUNDARY_WEIGHT = 7200.0f; // Aceleración de repulsión de bordes (px/s²) [más fuerte que separation]
|
||||||
|
|
||||||
// FASE 2: Spatial Hash Grid para optimización O(n²) → O(n)
|
// FASE 2: Spatial Hash Grid para optimización O(n²) → O(n)
|
||||||
constexpr float BOID_GRID_CELL_SIZE = 100.0f; // Tamaño de celda del grid (píxeles)
|
constexpr float BOID_GRID_CELL_SIZE = 100.0f; // Tamaño de celda del grid (píxeles)
|
||||||
|
|||||||
@@ -254,6 +254,14 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
|||||||
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)
|
||||||
|
app_logo_ = std::make_unique<AppLogo>();
|
||||||
|
if (!app_logo_->initialize(renderer_, current_screen_width_, current_screen_height_)) {
|
||||||
|
std::cerr << "Advertencia: No se pudo inicializar AppLogo (logo periódico)" << std::endl;
|
||||||
|
// No es crítico, continuar sin logo
|
||||||
|
app_logo_.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
@@ -334,16 +342,25 @@ void Engine::update() {
|
|||||||
|
|
||||||
// Actualizar transiciones de temas (delegado a ThemeManager)
|
// Actualizar transiciones de temas (delegado a ThemeManager)
|
||||||
theme_manager_->update(delta_time_);
|
theme_manager_->update(delta_time_);
|
||||||
|
|
||||||
|
// Actualizar AppLogo (logo periódico)
|
||||||
|
if (app_logo_) {
|
||||||
|
app_logo_->update(delta_time_, state_manager_->getCurrentMode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === IMPLEMENTACIÓN DE MÉTODOS PÚBLICOS PARA INPUT HANDLER ===
|
// === IMPLEMENTACIÓN DE MÉTODOS PÚBLICOS PARA INPUT HANDLER ===
|
||||||
|
|
||||||
// Gravedad y física
|
// Gravedad y física
|
||||||
void Engine::handleGravityToggle() {
|
void Engine::handleGravityToggle() {
|
||||||
// Si estamos en modo boids, salir a modo física primero
|
// Si estamos en modo boids, salir a modo física CON GRAVEDAD OFF
|
||||||
|
// Según RULES.md: "BOIDS a PHYSICS: Pulsando la tecla G: Gravedad OFF"
|
||||||
if (current_mode_ == SimulationMode::BOIDS) {
|
if (current_mode_ == SimulationMode::BOIDS) {
|
||||||
toggleBoidsMode(); // Esto cambia a PHYSICS y activa gravedad
|
toggleBoidsMode(false); // Cambiar a PHYSICS sin activar gravedad (preserva inercia)
|
||||||
return; // La notificación ya se muestra en toggleBoidsMode
|
// NO llamar a forceBallsGravityOff() porque aplica impulsos que destruyen la inercia de BOIDS
|
||||||
|
// La gravedad ya está desactivada por BoidManager::activateBoids() y se mantiene al salir
|
||||||
|
showNotificationForAction("Modo Física - Gravedad Off");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si estamos en modo figura, salir a modo física SIN GRAVEDAD
|
// Si estamos en modo figura, salir a modo física SIN GRAVEDAD
|
||||||
@@ -360,18 +377,19 @@ void Engine::handleGravityToggle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Engine::handleGravityDirectionChange(GravityDirection direction, const char* notification_text) {
|
void Engine::handleGravityDirectionChange(GravityDirection direction, const char* notification_text) {
|
||||||
// Si estamos en modo boids, salir a modo física primero
|
// Si estamos en modo boids, salir a modo física primero PRESERVANDO VELOCIDAD
|
||||||
if (current_mode_ == SimulationMode::BOIDS) {
|
if (current_mode_ == SimulationMode::BOIDS) {
|
||||||
toggleBoidsMode(); // Esto cambia a PHYSICS y activa gravedad
|
current_mode_ = SimulationMode::PHYSICS;
|
||||||
// Continuar para aplicar la dirección de gravedad
|
boid_manager_->deactivateBoids(false); // NO activar gravedad aún (preservar momentum)
|
||||||
|
scene_manager_->forceBallsGravityOn(); // Activar gravedad SIN impulsos (preserva velocidad)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si estamos en modo figura, salir a modo física CON gravedad
|
// Si estamos en modo figura, salir a modo física CON gravedad
|
||||||
if (current_mode_ == SimulationMode::SHAPE) {
|
else if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
toggleShapeModeInternal(); // Desactivar figura (activa gravedad automáticamente)
|
toggleShapeModeInternal(); // Desactivar figura (activa gravedad automáticamente)
|
||||||
} else {
|
} else {
|
||||||
scene_manager_->enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
scene_manager_->enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||||
}
|
}
|
||||||
|
|
||||||
scene_manager_->changeGravityDirection(direction);
|
scene_manager_->changeGravityDirection(direction);
|
||||||
showNotificationForAction(notification_text);
|
showNotificationForAction(notification_text);
|
||||||
}
|
}
|
||||||
@@ -431,11 +449,11 @@ void Engine::toggleDepthZoom() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Boids (comportamiento de enjambre)
|
// Boids (comportamiento de enjambre)
|
||||||
void Engine::toggleBoidsMode() {
|
void Engine::toggleBoidsMode(bool force_gravity_on) {
|
||||||
if (current_mode_ == SimulationMode::BOIDS) {
|
if (current_mode_ == SimulationMode::BOIDS) {
|
||||||
// Salir del modo boids
|
// Salir del modo boids (velocidades ya son time-based, no requiere conversión)
|
||||||
current_mode_ = SimulationMode::PHYSICS;
|
current_mode_ = SimulationMode::PHYSICS;
|
||||||
boid_manager_->deactivateBoids();
|
boid_manager_->deactivateBoids(force_gravity_on); // Pasar parámetro para control preciso
|
||||||
} else {
|
} else {
|
||||||
// Entrar al modo boids (desde PHYSICS o SHAPE)
|
// Entrar al modo boids (desde PHYSICS o SHAPE)
|
||||||
if (current_mode_ == SimulationMode::SHAPE) {
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
@@ -504,13 +522,25 @@ void Engine::switchTexture() {
|
|||||||
|
|
||||||
// Escenarios (número de pelotas)
|
// Escenarios (número de pelotas)
|
||||||
void Engine::changeScenario(int scenario_id, const char* notification_text) {
|
void Engine::changeScenario(int scenario_id, const char* notification_text) {
|
||||||
// Resetear modo SHAPE si está activo
|
// Pasar el modo actual al SceneManager para inicialización correcta
|
||||||
|
scene_manager_->changeScenario(scenario_id, current_mode_);
|
||||||
|
|
||||||
|
// Si estamos en modo SHAPE, regenerar la figura con nuevo número de pelotas
|
||||||
if (current_mode_ == SimulationMode::SHAPE) {
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
current_mode_ = SimulationMode::PHYSICS;
|
generateShape();
|
||||||
active_shape_.reset();
|
|
||||||
|
// Activar atracción física en las bolas nuevas (crítico tras changeScenario)
|
||||||
|
auto& balls = scene_manager_->getBallsMutable();
|
||||||
|
for (auto& ball : balls) {
|
||||||
|
ball->enableShapeAttraction(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si estamos en modo BOIDS, desactivar gravedad (modo BOIDS = gravedad OFF siempre)
|
||||||
|
if (current_mode_ == SimulationMode::BOIDS) {
|
||||||
|
scene_manager_->forceBallsGravityOff();
|
||||||
}
|
}
|
||||||
|
|
||||||
scene_manager_->changeScenario(scenario_id);
|
|
||||||
showNotificationForAction(notification_text);
|
showNotificationForAction(notification_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,10 +725,15 @@ void Engine::render() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Renderizar UI (debug HUD, texto obsoleto, notificaciones) - delegado a UIManager
|
// Renderizar UI (debug HUD, texto obsoleto, notificaciones) - delegado a UIManager
|
||||||
ui_manager_->render(renderer_, scene_manager_.get(), current_mode_, state_manager_->getCurrentMode(),
|
ui_manager_->render(renderer_, this, scene_manager_.get(), current_mode_, state_manager_->getCurrentMode(),
|
||||||
active_shape_.get(), shape_convergence_,
|
active_shape_.get(), shape_convergence_,
|
||||||
physical_window_width_, physical_window_height_, current_screen_width_);
|
physical_window_width_, physical_window_height_, current_screen_width_);
|
||||||
|
|
||||||
|
// Renderizar AppLogo (logo periódico) - después de UI, antes de present
|
||||||
|
if (app_logo_) {
|
||||||
|
app_logo_->render();
|
||||||
|
}
|
||||||
|
|
||||||
SDL_RenderPresent(renderer_);
|
SDL_RenderPresent(renderer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,6 +768,12 @@ void Engine::toggleFullscreen() {
|
|||||||
fullscreen_enabled_ = !fullscreen_enabled_;
|
fullscreen_enabled_ = !fullscreen_enabled_;
|
||||||
SDL_SetWindowFullscreen(window_, fullscreen_enabled_);
|
SDL_SetWindowFullscreen(window_, fullscreen_enabled_);
|
||||||
|
|
||||||
|
// Si acabamos de salir de fullscreen, restaurar tamaño de ventana
|
||||||
|
if (!fullscreen_enabled_) {
|
||||||
|
SDL_SetWindowSize(window_, base_screen_width_ * current_window_zoom_, base_screen_height_ * current_window_zoom_);
|
||||||
|
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||||
|
}
|
||||||
|
|
||||||
// Actualizar dimensiones físicas después del cambio
|
// Actualizar dimensiones físicas después del cambio
|
||||||
updatePhysicalWindowSize();
|
updatePhysicalWindowSize();
|
||||||
}
|
}
|
||||||
@@ -769,10 +810,26 @@ void Engine::toggleRealFullscreen() {
|
|||||||
|
|
||||||
// Reinicar la escena con nueva resolución
|
// Reinicar la escena con nueva resolución
|
||||||
scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||||
scene_manager_->changeScenario(scene_manager_->getCurrentScenario());
|
scene_manager_->changeScenario(scene_manager_->getCurrentScenario(), current_mode_);
|
||||||
|
|
||||||
// Actualizar tamaño de pantalla para boids (wrapping boundaries)
|
// Actualizar tamaño de pantalla para boids (wrapping boundaries)
|
||||||
boid_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
boid_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||||
|
|
||||||
|
// Actualizar AppLogo con nueva resolución
|
||||||
|
if (app_logo_) {
|
||||||
|
app_logo_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones
|
||||||
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
|
generateShape(); // Regenerar figura con nuevas dimensiones de pantalla
|
||||||
|
|
||||||
|
// Activar atracción física en las bolas nuevas (crítico tras changeScenario)
|
||||||
|
auto& balls = scene_manager_->getBallsMutable();
|
||||||
|
for (auto& ball : balls) {
|
||||||
|
ball->enableShapeAttraction(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SDL_free(displays);
|
SDL_free(displays);
|
||||||
}
|
}
|
||||||
@@ -794,7 +851,23 @@ void Engine::toggleRealFullscreen() {
|
|||||||
|
|
||||||
// Reinicar la escena con resolución original
|
// Reinicar la escena con resolución original
|
||||||
scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||||
scene_manager_->changeScenario(scene_manager_->getCurrentScenario());
|
scene_manager_->changeScenario(scene_manager_->getCurrentScenario(), current_mode_);
|
||||||
|
|
||||||
|
// Actualizar AppLogo con resolución restaurada
|
||||||
|
if (app_logo_) {
|
||||||
|
app_logo_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones
|
||||||
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
|
generateShape(); // Regenerar figura con nuevas dimensiones de pantalla
|
||||||
|
|
||||||
|
// Activar atracción física en las bolas nuevas (crítico tras changeScenario)
|
||||||
|
auto& balls = scene_manager_->getBallsMutable();
|
||||||
|
for (auto& ball : balls) {
|
||||||
|
ball->enableShapeAttraction(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1129,20 +1202,26 @@ void Engine::performLogoAction(bool logo_waiting_for_flip) {
|
|||||||
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
|
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Logo animado (PHYSICS) → 3 opciones posibles
|
// Logo animado (PHYSICS) → 4 opciones posibles
|
||||||
if (action < 60) {
|
if (action < 50) {
|
||||||
// 60%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones)
|
// 50%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones)
|
||||||
toggleShapeModeInternal(false);
|
toggleShapeModeInternal(false);
|
||||||
|
|
||||||
// Resetear variables de espera de flips al volver a SHAPE
|
// Resetear variables de espera de flips al volver a SHAPE
|
||||||
logo_waiting_for_flip_ = false;
|
logo_waiting_for_flip_ = false;
|
||||||
logo_current_flip_count_ = 0;
|
logo_current_flip_count_ = 0;
|
||||||
} else if (action < 80) {
|
} else if (action < 68) {
|
||||||
// 20%: Forzar gravedad ON (empezar a caer mientras da vueltas)
|
// 18%: Forzar gravedad ON (empezar a caer mientras da vueltas)
|
||||||
scene_manager_->forceBallsGravityOn();
|
scene_manager_->forceBallsGravityOn();
|
||||||
} else {
|
} else if (action < 84) {
|
||||||
// 20%: Forzar gravedad OFF (flotar mientras da vueltas)
|
// 16%: Forzar gravedad OFF (flotar mientras da vueltas)
|
||||||
scene_manager_->forceBallsGravityOff();
|
scene_manager_->forceBallsGravityOff();
|
||||||
|
} else {
|
||||||
|
// 16%: Cambiar dirección de gravedad (nueva variación)
|
||||||
|
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
|
||||||
|
scene_manager_->changeGravityDirection(new_direction);
|
||||||
|
// Si la gravedad está OFF, activarla para que el cambio sea visible
|
||||||
|
scene_manager_->forceBallsGravityOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resetear timer con intervalos escalados
|
// Resetear timer con intervalos escalados
|
||||||
@@ -1154,7 +1233,7 @@ void Engine::performLogoAction(bool logo_waiting_for_flip) {
|
|||||||
// Solo salir automáticamente si la entrada a LOGO fue automática (desde DEMO)
|
// Solo salir automáticamente si la entrada a LOGO fue automática (desde DEMO)
|
||||||
// No salir si el usuario entró manualmente con tecla K
|
// No salir si el usuario entró manualmente con tecla K
|
||||||
// Probabilidad de salir: 60% en cada acción → sale rápido (relación DEMO:LOGO = 6:1)
|
// Probabilidad de salir: 60% en cada acción → sale rápido (relación DEMO:LOGO = 6:1)
|
||||||
if (!logo_entered_manually_ && rand() % 100 < 60) {
|
if (!state_manager_->getLogoEnteredManually() && rand() % 100 < 60) {
|
||||||
state_manager_->exitLogoMode(true); // Volver a DEMO/DEMO_LITE
|
state_manager_->exitLogoMode(true); // Volver a DEMO/DEMO_LITE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1313,7 +1392,19 @@ void Engine::executeDemoAction(bool is_lite) {
|
|||||||
// Escenarios válidos: índices 1, 2, 3, 4, 5 (10, 100, 500, 1000, 10000 pelotas)
|
// Escenarios válidos: índices 1, 2, 3, 4, 5 (10, 100, 500, 1000, 10000 pelotas)
|
||||||
int valid_scenarios[] = {1, 2, 3, 4, 5};
|
int valid_scenarios[] = {1, 2, 3, 4, 5};
|
||||||
int new_scenario = valid_scenarios[rand() % 5];
|
int new_scenario = valid_scenarios[rand() % 5];
|
||||||
scene_manager_->changeScenario(new_scenario);
|
scene_manager_->changeScenario(new_scenario, current_mode_);
|
||||||
|
|
||||||
|
// Si estamos en modo SHAPE, regenerar la figura con nuevo número de pelotas
|
||||||
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
|
generateShape();
|
||||||
|
|
||||||
|
// Activar atracción física en las bolas nuevas (crítico tras changeScenario)
|
||||||
|
auto& balls = scene_manager_->getBallsMutable();
|
||||||
|
for (auto& ball : balls) {
|
||||||
|
ball->enableShapeAttraction(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1398,7 +1489,7 @@ void Engine::executeRandomizeOnDemoStart(bool is_lite) {
|
|||||||
// 1. Escenario (excluir índices 0, 6, 7)
|
// 1. Escenario (excluir índices 0, 6, 7)
|
||||||
int valid_scenarios[] = {1, 2, 3, 4, 5};
|
int valid_scenarios[] = {1, 2, 3, 4, 5};
|
||||||
int new_scenario = valid_scenarios[rand() % 5];
|
int new_scenario = valid_scenarios[rand() % 5];
|
||||||
scene_manager_->changeScenario(new_scenario);
|
scene_manager_->changeScenario(new_scenario, current_mode_);
|
||||||
|
|
||||||
// 2. Tema (elegir entre TODOS los 15 temas)
|
// 2. Tema (elegir entre TODOS los 15 temas)
|
||||||
int random_theme_index = rand() % 15;
|
int random_theme_index = rand() % 15;
|
||||||
@@ -1463,7 +1554,7 @@ void Engine::executeEnterLogoMode(size_t ball_count) {
|
|||||||
// Verificar mínimo de pelotas
|
// Verificar mínimo de pelotas
|
||||||
if (static_cast<int>(ball_count) < LOGO_MODE_MIN_BALLS) {
|
if (static_cast<int>(ball_count) < LOGO_MODE_MIN_BALLS) {
|
||||||
// Ajustar a 5000 pelotas automáticamente
|
// Ajustar a 5000 pelotas automáticamente
|
||||||
scene_manager_->changeScenario(5); // Escenario 5000 pelotas (índice 5 en BALL_COUNT_SCENARIOS)
|
scene_manager_->changeScenario(5, current_mode_); // Escenario 5000 pelotas (índice 5 en BALL_COUNT_SCENARIOS)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guardar estado previo (para restaurar al salir)
|
// Guardar estado previo (para restaurar al salir)
|
||||||
@@ -1528,6 +1619,15 @@ void Engine::executeExitLogoMode() {
|
|||||||
clampShapeScale();
|
clampShapeScale();
|
||||||
generateShape();
|
generateShape();
|
||||||
|
|
||||||
|
// Activar atracción física si estamos en modo SHAPE
|
||||||
|
// (crítico para que las bolas se muevan hacia la figura restaurada)
|
||||||
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
|
auto& balls = scene_manager_->getBallsMutable();
|
||||||
|
for (auto& ball : balls) {
|
||||||
|
ball->enableShapeAttraction(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Desactivar modo LOGO en PNG_SHAPE (volver a flip intervals normales)
|
// Desactivar modo LOGO en PNG_SHAPE (volver a flip intervals normales)
|
||||||
if (active_shape_) {
|
if (active_shape_) {
|
||||||
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
|
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
|
#include "app_logo.h" // for AppLogo
|
||||||
#include "ball.h" // for Ball
|
#include "ball.h" // for Ball
|
||||||
#include "boids_mgr/boid_manager.h" // for BoidManager
|
#include "boids_mgr/boid_manager.h" // for BoidManager
|
||||||
#include "defines.h" // for GravityDirection, ColorTheme, ShapeType
|
#include "defines.h" // for GravityDirection, ColorTheme, ShapeType
|
||||||
@@ -49,7 +50,7 @@ class Engine {
|
|||||||
void toggleDepthZoom();
|
void toggleDepthZoom();
|
||||||
|
|
||||||
// Boids (comportamiento de enjambre)
|
// Boids (comportamiento de enjambre)
|
||||||
void toggleBoidsMode();
|
void toggleBoidsMode(bool force_gravity_on = true);
|
||||||
|
|
||||||
// Temas de colores
|
// Temas de colores
|
||||||
void cycleTheme(bool forward);
|
void cycleTheme(bool forward);
|
||||||
@@ -87,6 +88,16 @@ class Engine {
|
|||||||
void executeEnterLogoMode(size_t ball_count);
|
void executeEnterLogoMode(size_t ball_count);
|
||||||
void executeExitLogoMode();
|
void executeExitLogoMode();
|
||||||
|
|
||||||
|
// === Getters públicos para UIManager (Debug HUD) ===
|
||||||
|
bool getVSyncEnabled() const { return vsync_enabled_; }
|
||||||
|
bool getFullscreenEnabled() const { return fullscreen_enabled_; }
|
||||||
|
bool getRealFullscreenEnabled() const { return real_fullscreen_enabled_; }
|
||||||
|
ScalingMode getCurrentScalingMode() const { return current_scaling_mode_; }
|
||||||
|
int getCurrentScreenWidth() const { return current_screen_width_; }
|
||||||
|
int getCurrentScreenHeight() const { return current_screen_height_; }
|
||||||
|
int getBaseScreenWidth() const { return base_screen_width_; }
|
||||||
|
int getBaseScreenHeight() const { return base_screen_height_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// === Componentes del sistema (Composición) ===
|
// === Componentes del sistema (Composición) ===
|
||||||
std::unique_ptr<InputHandler> input_handler_; // Manejo de entradas SDL
|
std::unique_ptr<InputHandler> input_handler_; // Manejo de entradas SDL
|
||||||
@@ -95,6 +106,7 @@ class Engine {
|
|||||||
std::unique_ptr<BoidManager> boid_manager_; // Gestión de comportamiento boids
|
std::unique_ptr<BoidManager> boid_manager_; // Gestión de comportamiento boids
|
||||||
std::unique_ptr<StateManager> state_manager_; // Gestión de estados (DEMO/LOGO)
|
std::unique_ptr<StateManager> state_manager_; // Gestión de estados (DEMO/LOGO)
|
||||||
std::unique_ptr<UIManager> ui_manager_; // Gestión de UI (HUD, FPS, notificaciones)
|
std::unique_ptr<UIManager> ui_manager_; // Gestión de UI (HUD, FPS, notificaciones)
|
||||||
|
std::unique_ptr<AppLogo> app_logo_; // Gestión de logo periódico en pantalla
|
||||||
|
|
||||||
// Recursos SDL
|
// Recursos SDL
|
||||||
SDL_Window* window_ = nullptr;
|
SDL_Window* window_ = nullptr;
|
||||||
@@ -150,7 +162,6 @@ class Engine {
|
|||||||
// Sistema de Modo DEMO (auto-play) y LOGO
|
// Sistema de Modo DEMO (auto-play) y LOGO
|
||||||
// NOTA: Engine mantiene estado de implementación para callbacks performLogoAction()
|
// NOTA: Engine mantiene estado de implementación para callbacks performLogoAction()
|
||||||
// StateManager coordina los triggers y timers, Engine ejecuta las acciones
|
// StateManager coordina los triggers y timers, Engine ejecuta las acciones
|
||||||
AppMode previous_app_mode_ = AppMode::SANDBOX; // Modo previo antes de entrar a LOGO
|
|
||||||
float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción
|
float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción
|
||||||
float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos)
|
float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos)
|
||||||
|
|
||||||
@@ -168,9 +179,9 @@ class Engine {
|
|||||||
float logo_target_flip_percentage_ = 0.0f; // % de flip a esperar (0.2-0.8)
|
float logo_target_flip_percentage_ = 0.0f; // % de flip a esperar (0.2-0.8)
|
||||||
int logo_current_flip_count_ = 0; // Flips observados hasta ahora
|
int logo_current_flip_count_ = 0; // Flips observados hasta ahora
|
||||||
|
|
||||||
// Control de entrada manual vs automática a LOGO MODE
|
// NOTA: logo_entered_manually_ fue eliminado de Engine (duplicado)
|
||||||
// Determina si LOGO debe salir automáticamente o esperar input del usuario
|
// Ahora se obtiene de StateManager con state_manager_->getLogoEnteredManually()
|
||||||
bool logo_entered_manually_ = false; // true si se activó con tecla K, false si automático desde DEMO
|
// Esto evita desincronización entre Engine y StateManager
|
||||||
|
|
||||||
// Estado previo antes de entrar a Logo Mode (para restaurar al salir)
|
// Estado previo antes de entrar a Logo Mode (para restaurar al salir)
|
||||||
// Guardado por executeEnterLogoMode(), restaurado por executeExitLogoMode()
|
// Guardado por executeEnterLogoMode(), restaurado por executeExitLogoMode()
|
||||||
|
|||||||
78
source/external/dbgtxt.h
vendored
78
source/external/dbgtxt.h
vendored
@@ -1,78 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
SDL_Texture* dbg_tex = nullptr;
|
|
||||||
SDL_Renderer* dbg_ren = nullptr;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
inline void dbg_init(SDL_Renderer* renderer) {
|
|
||||||
dbg_ren = renderer;
|
|
||||||
Uint8 font[448] = {0x42, 0x4D, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x01, 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x18, 0xF3, 0x83, 0x83, 0xCF, 0x83, 0x87, 0x00, 0x00, 0xF3, 0x39, 0x39, 0xCF, 0x79, 0xF3, 0x00, 0x00, 0x01, 0xF9, 0x39, 0xCF, 0x61, 0xF9, 0x00, 0x00, 0x33, 0xF9, 0x03, 0xE7, 0x87, 0x81, 0x00, 0x00, 0x93, 0x03, 0x3F, 0xF3, 0x1B, 0x39, 0x00, 0x00, 0xC3, 0x3F, 0x9F, 0x39, 0x3B, 0x39, 0x00, 0x41, 0xE3, 0x03, 0xC3, 0x01, 0x87, 0x83, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xE7, 0x01, 0xC7, 0x81, 0x01, 0x83, 0x00, 0x00, 0xE7, 0x1F, 0x9B, 0xE7, 0x1F, 0x39, 0x00, 0x00, 0xE7, 0x8F, 0x39, 0xE7, 0x87, 0xF9, 0x00, 0x00, 0xC3, 0xC7, 0x39, 0xE7, 0xC3, 0xC3, 0x00, 0x00, 0x99, 0xE3, 0x39, 0xE7, 0xF1, 0xE7, 0x00, 0x00, 0x99, 0xF1, 0xB3, 0xC7, 0x39, 0xF3, 0x00, 0x00, 0x99, 0x01, 0xC7, 0xE7, 0x83, 0x81, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x83, 0xE7, 0x83, 0xEF, 0x39, 0x39, 0x00, 0x00, 0x39, 0xE7, 0x39, 0xC7, 0x11, 0x11, 0x00, 0x00, 0xF9, 0xE7, 0x39, 0x83, 0x01, 0x83, 0x00, 0x00, 0x83, 0xE7, 0x39, 0x11, 0x01, 0xC7, 0x00, 0x00, 0x3F, 0xE7, 0x39, 0x39, 0x29, 0x83, 0x00, 0x00, 0x33, 0xE7, 0x39, 0x39, 0x39, 0x11, 0x00, 0x00, 0x87, 0x81, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x39, 0x39, 0x83, 0x3F, 0x85, 0x31, 0x00, 0x00, 0x39, 0x31, 0x39, 0x3F, 0x33, 0x23, 0x00, 0x00, 0x29, 0x21, 0x39, 0x03, 0x21, 0x07, 0x00, 0x00, 0x01, 0x01, 0x39, 0x39, 0x39, 0x31, 0x00, 0x00, 0x01, 0x09, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0x11, 0x19, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0x39, 0x39, 0x83, 0x03, 0x83, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xC1, 0x39, 0x81, 0x83, 0x31, 0x01, 0x00, 0x00, 0x99, 0x39, 0xE7, 0x39, 0x23, 0x3F, 0x00, 0x00, 0x39, 0x39, 0xE7, 0xF9, 0x07, 0x3F, 0x00, 0x00, 0x31, 0x01, 0xE7, 0xF9, 0x0F, 0x3F, 0x00, 0x00, 0x3F, 0x39, 0xE7, 0xF9, 0x27, 0x3F, 0x00, 0x00, 0x9F, 0x39, 0xE7, 0xF9, 0x33, 0x3F, 0x00, 0x00, 0xC1, 0x39, 0x81, 0xF9, 0x39, 0x3F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x39, 0x03, 0xC3, 0x07, 0x01, 0x3F, 0x00, 0x00, 0x39, 0x39, 0x99, 0x33, 0x3F, 0x3F, 0x00, 0x00, 0x01, 0x39, 0x3F, 0x39, 0x3F, 0x3F, 0x00, 0x00, 0x39, 0x03, 0x3F, 0x39, 0x03, 0x03, 0x00, 0x00, 0x39, 0x39, 0x3F, 0x39, 0x3F, 0x3F, 0x00, 0x00, 0x93, 0x39, 0x99, 0x33, 0x3F, 0x3F, 0x00, 0x00, 0xC7, 0x03, 0xC3, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
|
|
||||||
// Cargar surface del bitmap font
|
|
||||||
SDL_Surface* font_surface = SDL_LoadBMP_IO(SDL_IOFromMem(font, 448), 1);
|
|
||||||
if (font_surface != nullptr) {
|
|
||||||
// Crear una nueva surface de 32 bits con canal alpha
|
|
||||||
SDL_Surface* rgba_surface = SDL_CreateSurface(font_surface->w, font_surface->h, SDL_PIXELFORMAT_RGBA8888);
|
|
||||||
if (rgba_surface != nullptr) {
|
|
||||||
// Obtener píxeles de ambas surfaces
|
|
||||||
Uint8* src_pixels = (Uint8*)font_surface->pixels;
|
|
||||||
Uint32* dst_pixels = (Uint32*)rgba_surface->pixels;
|
|
||||||
|
|
||||||
int width = font_surface->w;
|
|
||||||
int height = font_surface->h;
|
|
||||||
|
|
||||||
// Procesar cada píxel
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
int byte_index = y * font_surface->pitch + (x / 8);
|
|
||||||
int bit_index = 7 - (x % 8);
|
|
||||||
|
|
||||||
// Extraer bit del bitmap monocromo
|
|
||||||
bool is_white = (src_pixels[byte_index] >> bit_index) & 1;
|
|
||||||
|
|
||||||
if (is_white) // Fondo blanco original -> transparente
|
|
||||||
{
|
|
||||||
dst_pixels[y * width + x] = 0x00000000; // Transparente
|
|
||||||
} else // Texto negro original -> blanco opaco
|
|
||||||
{
|
|
||||||
dst_pixels[y * width + x] = 0xFFFFFFFF; // Blanco opaco
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dbg_tex = SDL_CreateTextureFromSurface(dbg_ren, rgba_surface);
|
|
||||||
SDL_DestroySurface(rgba_surface);
|
|
||||||
}
|
|
||||||
SDL_DestroySurface(font_surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configurar filtro nearest neighbor para píxel perfect del texto
|
|
||||||
if (dbg_tex != nullptr) {
|
|
||||||
SDL_SetTextureScaleMode(dbg_tex, SDL_SCALEMODE_NEAREST);
|
|
||||||
// Configurar blend mode para transparencia normal
|
|
||||||
SDL_SetTextureBlendMode(dbg_tex, SDL_BLENDMODE_BLEND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void dbg_print(int x, int y, const char* text, Uint8 r, Uint8 g, Uint8 b) {
|
|
||||||
int cc = 0;
|
|
||||||
SDL_SetTextureColorMod(dbg_tex, r, g, b);
|
|
||||||
SDL_FRect src = {0, 0, 8, 8};
|
|
||||||
SDL_FRect dst = {static_cast<float>(x), static_cast<float>(y), 8, 8};
|
|
||||||
while (text[cc] != 0) {
|
|
||||||
if (text[cc] != 32) {
|
|
||||||
if (text[cc] >= 65) {
|
|
||||||
src.x = ((text[cc] - 65) % 6) * 8;
|
|
||||||
src.y = ((text[cc] - 65) / 6) * 8;
|
|
||||||
} else {
|
|
||||||
src.x = ((text[cc] - 22) % 6) * 8;
|
|
||||||
src.y = ((text[cc] - 22) / 6) * 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_RenderTexture(dbg_ren, dbg_tex, &src, &dst);
|
|
||||||
}
|
|
||||||
cc++;
|
|
||||||
dst.x += 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
source/external/texture.cpp
vendored
17
source/external/texture.cpp
vendored
@@ -128,6 +128,9 @@ bool Texture::loadFromFile(const std::string &file_path) {
|
|||||||
|
|
||||||
// Configurar filtro nearest neighbor para píxel perfect
|
// Configurar filtro nearest neighbor para píxel perfect
|
||||||
SDL_SetTextureScaleMode(new_texture, SDL_SCALEMODE_NEAREST);
|
SDL_SetTextureScaleMode(new_texture, SDL_SCALEMODE_NEAREST);
|
||||||
|
|
||||||
|
// Habilitar alpha blending para transparencias
|
||||||
|
SDL_SetTextureBlendMode(new_texture, SDL_BLENDMODE_BLEND);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destruye la superficie cargada
|
// Destruye la superficie cargada
|
||||||
@@ -169,3 +172,17 @@ int Texture::getHeight() {
|
|||||||
void Texture::setColor(int r, int g, int b) {
|
void Texture::setColor(int r, int g, int b) {
|
||||||
SDL_SetTextureColorMod(texture_, r, g, b);
|
SDL_SetTextureColorMod(texture_, r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modula el alpha de la textura
|
||||||
|
void Texture::setAlpha(int alpha) {
|
||||||
|
if (texture_ != nullptr) {
|
||||||
|
SDL_SetTextureAlphaMod(texture_, static_cast<Uint8>(alpha));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurar modo de escalado
|
||||||
|
void Texture::setScaleMode(SDL_ScaleMode mode) {
|
||||||
|
if (texture_ != nullptr) {
|
||||||
|
SDL_SetTextureScaleMode(texture_, mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
6
source/external/texture.h
vendored
6
source/external/texture.h
vendored
@@ -44,6 +44,12 @@ class Texture {
|
|||||||
// Modula el color de la textura
|
// Modula el color de la textura
|
||||||
void setColor(int r, int g, int b);
|
void setColor(int r, int g, int b);
|
||||||
|
|
||||||
|
// Modula el alpha (transparencia) de la textura
|
||||||
|
void setAlpha(int alpha);
|
||||||
|
|
||||||
|
// Configurar modo de escalado (NEAREST para pixel art, LINEAR para suavizado)
|
||||||
|
void setScaleMode(SDL_ScaleMode mode);
|
||||||
|
|
||||||
// Getter para batch rendering
|
// Getter para batch rendering
|
||||||
SDL_Texture *getSDLTexture() const { return texture_; }
|
SDL_Texture *getSDLTexture() const { return texture_; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ void SceneManager::initialize(int scenario, std::shared_ptr<Texture> texture, Th
|
|||||||
theme_manager_ = theme_manager;
|
theme_manager_ = theme_manager;
|
||||||
current_ball_size_ = texture_->getWidth();
|
current_ball_size_ = texture_->getWidth();
|
||||||
|
|
||||||
// Crear bolas iniciales
|
// Crear bolas iniciales (siempre en modo PHYSICS al inicio)
|
||||||
changeScenario(scenario_);
|
changeScenario(scenario_, SimulationMode::PHYSICS);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::update(float delta_time) {
|
void SceneManager::update(float delta_time) {
|
||||||
@@ -33,7 +33,7 @@ void SceneManager::update(float delta_time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::changeScenario(int scenario_id) {
|
void SceneManager::changeScenario(int scenario_id, SimulationMode mode) {
|
||||||
// Guardar escenario
|
// Guardar escenario
|
||||||
scenario_ = scenario_id;
|
scenario_ = scenario_id;
|
||||||
|
|
||||||
@@ -45,14 +45,53 @@ void SceneManager::changeScenario(int scenario_id) {
|
|||||||
|
|
||||||
// Crear las bolas según el escenario
|
// Crear las bolas según el escenario
|
||||||
for (int i = 0; i < BALL_COUNT_SCENARIOS[scenario_id]; ++i) {
|
for (int i = 0; i < BALL_COUNT_SCENARIOS[scenario_id]; ++i) {
|
||||||
const int SIGN = ((rand() % 2) * 2) - 1; // Genera un signo aleatorio (+ o -)
|
float X, Y, VX, VY;
|
||||||
|
|
||||||
// Calcular spawn zone: margen a cada lado, zona central para spawn
|
// Inicialización según SimulationMode (RULES.md líneas 23-26)
|
||||||
|
switch (mode) {
|
||||||
|
case SimulationMode::PHYSICS: {
|
||||||
|
// PHYSICS: Parte superior, 75% distribución central en X
|
||||||
|
const int SIGN = ((rand() % 2) * 2) - 1;
|
||||||
const int margin = static_cast<int>(screen_width_ * BALL_SPAWN_MARGIN);
|
const int 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);
|
||||||
const float X = (rand() % spawn_zone_width) + margin; // Posición inicial en X
|
X = (rand() % spawn_zone_width) + margin;
|
||||||
const float VX = (((rand() % 20) + 10) * 0.1f) * SIGN; // Velocidad en X
|
Y = 0.0f; // Parte superior
|
||||||
const float VY = ((rand() % 60) - 30) * 0.1f; // Velocidad en Y
|
VX = (((rand() % 20) + 10) * 0.1f) * SIGN;
|
||||||
|
VY = ((rand() % 60) - 30) * 0.1f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SimulationMode::SHAPE: {
|
||||||
|
// SHAPE: Centro de pantalla, sin velocidad inicial
|
||||||
|
X = screen_width_ / 2.0f;
|
||||||
|
Y = screen_height_ / 2.0f; // Centro vertical
|
||||||
|
VX = 0.0f;
|
||||||
|
VY = 0.0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SimulationMode::BOIDS: {
|
||||||
|
// BOIDS: Posiciones aleatorias, velocidades aleatorias
|
||||||
|
const int SIGN_X = ((rand() % 2) * 2) - 1;
|
||||||
|
const int SIGN_Y = ((rand() % 2) * 2) - 1;
|
||||||
|
X = static_cast<float>(rand() % screen_width_);
|
||||||
|
Y = static_cast<float>(rand() % screen_height_); // Posición Y aleatoria
|
||||||
|
VX = (((rand() % 40) + 10) * 0.1f) * SIGN_X; // 1.0 - 5.0 px/frame
|
||||||
|
VY = (((rand() % 40) + 10) * 0.1f) * SIGN_Y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Fallback a PHYSICS por seguridad
|
||||||
|
const int SIGN = ((rand() % 2) * 2) - 1;
|
||||||
|
const int margin = static_cast<int>(screen_width_ * BALL_SPAWN_MARGIN);
|
||||||
|
const int spawn_zone_width = screen_width_ - (2 * margin);
|
||||||
|
X = (rand() % spawn_zone_width) + margin;
|
||||||
|
Y = 0.0f; // Parte superior
|
||||||
|
VX = (((rand() % 20) + 10) * 0.1f) * SIGN;
|
||||||
|
VY = ((rand() % 60) - 30) * 0.1f;
|
||||||
|
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();
|
||||||
@@ -62,7 +101,7 @@ void SceneManager::changeScenario(int scenario_id) {
|
|||||||
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, VX, VY, COLOR, texture_,
|
X, Y, VX, VY, COLOR, texture_,
|
||||||
screen_width_, screen_height_, current_ball_size_,
|
screen_width_, screen_height_, current_ball_size_,
|
||||||
current_gravity_, mass_factor
|
current_gravity_, mass_factor
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ class SceneManager {
|
|||||||
/**
|
/**
|
||||||
* @brief Cambia el número de bolas según escenario
|
* @brief Cambia el número de bolas según escenario
|
||||||
* @param scenario_id Índice del escenario (0-7 para 10 a 50,000 bolas)
|
* @param scenario_id Índice del escenario (0-7 para 10 a 50,000 bolas)
|
||||||
|
* @param mode Modo de simulación actual (afecta inicialización)
|
||||||
*/
|
*/
|
||||||
void changeScenario(int scenario_id);
|
void changeScenario(int scenario_id, SimulationMode mode);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Actualiza textura y tamaño de todas las bolas
|
* @brief Actualiza textura y tamaño de todas las bolas
|
||||||
|
|||||||
@@ -76,8 +76,10 @@ void ShapeManager::toggleShapeMode(bool force_gravity_on_exit) {
|
|||||||
|
|
||||||
// Si estamos en LOGO MODE, generar threshold aleatorio de convergencia (75-100%)
|
// Si estamos en LOGO MODE, generar threshold aleatorio de convergencia (75-100%)
|
||||||
if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO) {
|
if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO) {
|
||||||
|
/*
|
||||||
float logo_convergence_threshold = LOGO_CONVERGENCE_MIN +
|
float logo_convergence_threshold = LOGO_CONVERGENCE_MIN +
|
||||||
(rand() % 1000) / 1000.0f * (LOGO_CONVERGENCE_MAX - LOGO_CONVERGENCE_MIN);
|
(rand() % 1000) / 1000.0f * (LOGO_CONVERGENCE_MAX - LOGO_CONVERGENCE_MIN);
|
||||||
|
*/
|
||||||
shape_convergence_ = 0.0f; // Reset convergencia al entrar
|
shape_convergence_ = 0.0f; // Reset convergencia al entrar
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -119,6 +119,11 @@ class StateManager {
|
|||||||
*/
|
*/
|
||||||
float getLogoPreviousShapeScale() const { return logo_previous_shape_scale_; }
|
float getLogoPreviousShapeScale() const { return logo_previous_shape_scale_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Obtiene si LOGO fue activado manualmente (tecla K) o automáticamente (desde DEMO)
|
||||||
|
*/
|
||||||
|
bool getLogoEnteredManually() const { return logo_entered_manually_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Establece valores previos de LOGO (llamado por Engine antes de entrar)
|
* @brief Establece valores previos de LOGO (llamado por Engine antes de entrar)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ bool TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_
|
|||||||
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()
|
||||||
|
|
||||||
// Inicializar SDL_ttf si no está inicializado
|
// Inicializar SDL_ttf si no está inicializado
|
||||||
if (!TTF_WasInit()) {
|
if (!TTF_WasInit()) {
|
||||||
@@ -32,6 +33,38 @@ bool TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TextRenderer::reinitialize(int new_font_size) {
|
||||||
|
// Verificar que tenemos todo lo necesario
|
||||||
|
if (renderer_ == nullptr || font_path_.empty()) {
|
||||||
|
SDL_Log("Error: TextRenderer no inicializado correctamente para reinitialize()");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si el tamaño es el mismo, no hacer nada
|
||||||
|
if (new_font_size == font_size_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cerrar fuente actual
|
||||||
|
if (font_ != nullptr) {
|
||||||
|
TTF_CloseFont(font_);
|
||||||
|
font_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cargar fuente con nuevo tamaño
|
||||||
|
font_ = TTF_OpenFont(font_path_.c_str(), new_font_size);
|
||||||
|
if (font_ == nullptr) {
|
||||||
|
SDL_Log("Error al recargar fuente '%s' con tamaño %d: %s",
|
||||||
|
font_path_.c_str(), new_font_size, SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar tamaño almacenado
|
||||||
|
font_size_ = new_font_size;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void TextRenderer::cleanup() {
|
void TextRenderer::cleanup() {
|
||||||
if (font_ != nullptr) {
|
if (font_ != nullptr) {
|
||||||
TTF_CloseFont(font_);
|
TTF_CloseFont(font_);
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ public:
|
|||||||
// Inicializa el renderizador de texto con una fuente
|
// Inicializa el renderizador de texto con una fuente
|
||||||
bool init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing = true);
|
bool init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing = true);
|
||||||
|
|
||||||
|
// Reinicializa el renderizador con un nuevo tamaño de fuente
|
||||||
|
bool reinitialize(int new_font_size);
|
||||||
|
|
||||||
// Libera recursos
|
// Libera recursos
|
||||||
void cleanup();
|
void cleanup();
|
||||||
|
|
||||||
@@ -46,4 +49,5 @@ private:
|
|||||||
TTF_Font* font_;
|
TTF_Font* font_;
|
||||||
int font_size_;
|
int font_size_;
|
||||||
bool use_antialiasing_;
|
bool use_antialiasing_;
|
||||||
|
std::string font_path_; // Almacenar ruta para reinitialize()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,9 +12,17 @@ HelpOverlay::HelpOverlay()
|
|||||||
physical_width_(0),
|
physical_width_(0),
|
||||||
physical_height_(0),
|
physical_height_(0),
|
||||||
visible_(false),
|
visible_(false),
|
||||||
box_size_(0),
|
box_width_(0),
|
||||||
|
box_height_(0),
|
||||||
box_x_(0),
|
box_x_(0),
|
||||||
box_y_(0) {
|
box_y_(0),
|
||||||
|
column1_width_(0),
|
||||||
|
column2_width_(0),
|
||||||
|
cached_texture_(nullptr),
|
||||||
|
last_category_color_({0, 0, 0, 255}),
|
||||||
|
last_content_color_({0, 0, 0, 255}),
|
||||||
|
last_bg_color_({0, 0, 0, 255}),
|
||||||
|
texture_needs_rebuild_(true) {
|
||||||
// Llenar lista de controles (organizados por categoría, equilibrado en 2 columnas)
|
// Llenar lista de controles (organizados por categoría, equilibrado en 2 columnas)
|
||||||
key_bindings_ = {
|
key_bindings_ = {
|
||||||
// COLUMNA 1: SIMULACIÓN
|
// COLUMNA 1: SIMULACIÓN
|
||||||
@@ -70,18 +78,27 @@ HelpOverlay::HelpOverlay()
|
|||||||
}
|
}
|
||||||
|
|
||||||
HelpOverlay::~HelpOverlay() {
|
HelpOverlay::~HelpOverlay() {
|
||||||
|
// Destruir textura cacheada si existe
|
||||||
|
if (cached_texture_) {
|
||||||
|
SDL_DestroyTexture(cached_texture_);
|
||||||
|
cached_texture_ = nullptr;
|
||||||
|
}
|
||||||
delete text_renderer_;
|
delete text_renderer_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height) {
|
void HelpOverlay::toggle() {
|
||||||
|
visible_ = !visible_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height, int font_size) {
|
||||||
renderer_ = renderer;
|
renderer_ = renderer;
|
||||||
theme_mgr_ = theme_mgr;
|
theme_mgr_ = theme_mgr;
|
||||||
physical_width_ = physical_width;
|
physical_width_ = physical_width;
|
||||||
physical_height_ = physical_height;
|
physical_height_ = physical_height;
|
||||||
|
|
||||||
// Crear renderer de texto con tamaño reducido (18px en lugar de 24px)
|
// Crear renderer de texto con tamaño dinámico
|
||||||
text_renderer_ = new TextRenderer();
|
text_renderer_ = new TextRenderer();
|
||||||
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", 18, true);
|
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", font_size, true);
|
||||||
|
|
||||||
calculateBoxDimensions();
|
calculateBoxDimensions();
|
||||||
}
|
}
|
||||||
@@ -90,69 +107,218 @@ void HelpOverlay::updatePhysicalWindowSize(int physical_width, int physical_heig
|
|||||||
physical_width_ = physical_width;
|
physical_width_ = physical_width;
|
||||||
physical_height_ = physical_height;
|
physical_height_ = physical_height;
|
||||||
calculateBoxDimensions();
|
calculateBoxDimensions();
|
||||||
|
|
||||||
|
// Marcar textura para regeneración (dimensiones han cambiado)
|
||||||
|
texture_needs_rebuild_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HelpOverlay::reinitializeFontSize(int new_font_size) {
|
||||||
|
if (!text_renderer_) return;
|
||||||
|
|
||||||
|
// Reinicializar text renderer con nuevo tamaño
|
||||||
|
text_renderer_->reinitialize(new_font_size);
|
||||||
|
|
||||||
|
// NOTA: NO recalcular dimensiones aquí porque physical_width_ y physical_height_
|
||||||
|
// pueden tener valores antiguos. updatePhysicalWindowSize() se llamará después
|
||||||
|
// con las dimensiones correctas y recalculará todo apropiadamente.
|
||||||
|
|
||||||
|
// Marcar textura para regeneración completa
|
||||||
|
texture_needs_rebuild_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HelpOverlay::updateAll(int font_size, int physical_width, int physical_height) {
|
||||||
|
// Actualizar dimensiones físicas PRIMERO
|
||||||
|
physical_width_ = physical_width;
|
||||||
|
physical_height_ = physical_height;
|
||||||
|
|
||||||
|
// Reinicializar text renderer con nuevo tamaño (si cambió)
|
||||||
|
if (text_renderer_) {
|
||||||
|
text_renderer_->reinitialize(font_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalcular dimensiones del box con nuevo font y nuevas dimensiones
|
||||||
|
calculateBoxDimensions();
|
||||||
|
|
||||||
|
// Marcar textura para regeneración completa
|
||||||
|
texture_needs_rebuild_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
|
||||||
|
if (!text_renderer_) {
|
||||||
|
max_width = 0;
|
||||||
|
total_height = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int line_height = text_renderer_->getTextHeight();
|
||||||
|
int padding = 25;
|
||||||
|
|
||||||
|
// Calcular ancho máximo por columna
|
||||||
|
int max_col1_width = 0;
|
||||||
|
int max_col2_width = 0;
|
||||||
|
int current_column = 0;
|
||||||
|
|
||||||
|
for (const auto& binding : key_bindings_) {
|
||||||
|
// Cambio de columna
|
||||||
|
if (strcmp(binding.key, "[new_col]") == 0) {
|
||||||
|
current_column = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separador vacío (no tiene key ni description)
|
||||||
|
if (binding.key[0] == '\0') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int line_width = 0;
|
||||||
|
|
||||||
|
if (binding.description[0] == '\0') {
|
||||||
|
// Es un encabezado (solo tiene key, sin description)
|
||||||
|
line_width = text_renderer_->getTextWidthPhysical(binding.key);
|
||||||
|
} else {
|
||||||
|
// Es una línea normal con key + description
|
||||||
|
int key_width = text_renderer_->getTextWidthPhysical(binding.key);
|
||||||
|
int desc_width = text_renderer_->getTextWidthPhysical(binding.description);
|
||||||
|
line_width = key_width + 10 + desc_width; // 10px de separación
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar máximo de columna correspondiente
|
||||||
|
if (current_column == 0) {
|
||||||
|
max_col1_width = std::max(max_col1_width, line_width);
|
||||||
|
} else {
|
||||||
|
max_col2_width = std::max(max_col2_width, line_width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Almacenar anchos de columnas en miembros para uso posterior
|
||||||
|
column1_width_ = max_col1_width;
|
||||||
|
column2_width_ = max_col2_width;
|
||||||
|
|
||||||
|
// Ancho total: 2 columnas + 3 paddings (izq, medio, der)
|
||||||
|
max_width = max_col1_width + max_col2_width + padding * 3;
|
||||||
|
|
||||||
|
// Altura: contar líneas REALES en cada columna
|
||||||
|
int col1_lines = 0;
|
||||||
|
int col2_lines = 0;
|
||||||
|
current_column = 0;
|
||||||
|
|
||||||
|
for (const auto& binding : key_bindings_) {
|
||||||
|
// Cambio de columna
|
||||||
|
if (strcmp(binding.key, "[new_col]") == 0) {
|
||||||
|
current_column = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separador vacío no cuenta como línea
|
||||||
|
if (binding.key[0] == '\0') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contar línea (ya sea encabezado o contenido)
|
||||||
|
if (current_column == 0) {
|
||||||
|
col1_lines++;
|
||||||
|
} else {
|
||||||
|
col2_lines++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usar la columna más larga para calcular altura
|
||||||
|
int max_column_lines = std::max(col1_lines, col2_lines);
|
||||||
|
|
||||||
|
// Altura: título (2 líneas) + contenido + padding superior e inferior
|
||||||
|
total_height = line_height * 2 + max_column_lines * line_height + padding * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HelpOverlay::calculateBoxDimensions() {
|
void HelpOverlay::calculateBoxDimensions() {
|
||||||
// 90% de la dimensión más corta (cuadrado)
|
// Calcular dimensiones necesarias según el texto
|
||||||
int min_dimension = std::min(physical_width_, physical_height_);
|
int text_width, text_height;
|
||||||
box_size_ = static_cast<int>(min_dimension * 0.9f);
|
calculateTextDimensions(text_width, text_height);
|
||||||
|
|
||||||
|
// Usar directamente el ancho y altura calculados según el contenido
|
||||||
|
box_width_ = text_width;
|
||||||
|
|
||||||
|
// Altura: 90% de altura física o altura calculada, el que sea menor
|
||||||
|
int max_height = static_cast<int>(physical_height_ * 0.9f);
|
||||||
|
box_height_ = std::min(text_height, max_height);
|
||||||
|
|
||||||
// Centrar en pantalla
|
// Centrar en pantalla
|
||||||
box_x_ = (physical_width_ - box_size_) / 2;
|
box_x_ = (physical_width_ - box_width_) / 2;
|
||||||
box_y_ = (physical_height_ - box_size_) / 2;
|
box_y_ = (physical_height_ - box_height_) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HelpOverlay::render(SDL_Renderer* renderer) {
|
void HelpOverlay::rebuildCachedTexture() {
|
||||||
if (!visible_) return;
|
if (!renderer_ || !theme_mgr_ || !text_renderer_) return;
|
||||||
|
|
||||||
// CRÍTICO: Habilitar alpha blending para que la transparencia funcione
|
// Destruir textura anterior si existe
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
if (cached_texture_) {
|
||||||
|
SDL_DestroyTexture(cached_texture_);
|
||||||
|
cached_texture_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Obtener color de notificación del tema actual (para el fondo)
|
// Crear nueva textura del tamaño del overlay
|
||||||
|
cached_texture_ = SDL_CreateTexture(renderer_,
|
||||||
|
SDL_PIXELFORMAT_RGBA8888,
|
||||||
|
SDL_TEXTUREACCESS_TARGET,
|
||||||
|
box_width_,
|
||||||
|
box_height_);
|
||||||
|
|
||||||
|
if (!cached_texture_) {
|
||||||
|
SDL_Log("Error al crear textura cacheada: %s", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Habilitar alpha blending en la textura
|
||||||
|
SDL_SetTextureBlendMode(cached_texture_, SDL_BLENDMODE_BLEND);
|
||||||
|
|
||||||
|
// Guardar render target actual
|
||||||
|
SDL_Texture* prev_target = SDL_GetRenderTarget(renderer_);
|
||||||
|
|
||||||
|
// Cambiar render target a la textura cacheada
|
||||||
|
SDL_SetRenderTarget(renderer_, cached_texture_);
|
||||||
|
|
||||||
|
// Limpiar textura (completamente transparente)
|
||||||
|
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
|
||||||
|
SDL_RenderClear(renderer_);
|
||||||
|
|
||||||
|
// Habilitar alpha blending
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||||
|
|
||||||
|
// Obtener colores actuales del tema
|
||||||
int notif_bg_r, notif_bg_g, notif_bg_b;
|
int notif_bg_r, notif_bg_g, 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 semitransparente usando SDL_RenderGeometry (soporta alpha real)
|
// Renderizar fondo del overlay a la textura
|
||||||
float alpha = 0.85f;
|
float alpha = 0.85f;
|
||||||
SDL_Vertex bg_vertices[4];
|
SDL_Vertex bg_vertices[4];
|
||||||
|
|
||||||
// Convertir RGB a float [0.0, 1.0]
|
|
||||||
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értice superior izquierdo
|
// Vértices del fondo (posición relativa 0,0 porque estamos renderizando a textura)
|
||||||
bg_vertices[0].position = {static_cast<float>(box_x_), static_cast<float>(box_y_)};
|
bg_vertices[0].position = {0, 0};
|
||||||
bg_vertices[0].tex_coord = {0.0f, 0.0f};
|
bg_vertices[0].tex_coord = {0.0f, 0.0f};
|
||||||
bg_vertices[0].color = {r, g, b, alpha};
|
bg_vertices[0].color = {r, g, b, alpha};
|
||||||
|
|
||||||
// Vértice superior derecho
|
bg_vertices[1].position = {static_cast<float>(box_width_), 0};
|
||||||
bg_vertices[1].position = {static_cast<float>(box_x_ + box_size_), static_cast<float>(box_y_)};
|
|
||||||
bg_vertices[1].tex_coord = {1.0f, 0.0f};
|
bg_vertices[1].tex_coord = {1.0f, 0.0f};
|
||||||
bg_vertices[1].color = {r, g, b, alpha};
|
bg_vertices[1].color = {r, g, b, alpha};
|
||||||
|
|
||||||
// Vértice inferior derecho
|
bg_vertices[2].position = {static_cast<float>(box_width_), static_cast<float>(box_height_)};
|
||||||
bg_vertices[2].position = {static_cast<float>(box_x_ + box_size_), static_cast<float>(box_y_ + box_size_)};
|
|
||||||
bg_vertices[2].tex_coord = {1.0f, 1.0f};
|
bg_vertices[2].tex_coord = {1.0f, 1.0f};
|
||||||
bg_vertices[2].color = {r, g, b, alpha};
|
bg_vertices[2].color = {r, g, b, alpha};
|
||||||
|
|
||||||
// Vértice inferior izquierdo
|
bg_vertices[3].position = {0, static_cast<float>(box_height_)};
|
||||||
bg_vertices[3].position = {static_cast<float>(box_x_), static_cast<float>(box_y_ + box_size_)};
|
|
||||||
bg_vertices[3].tex_coord = {0.0f, 1.0f};
|
bg_vertices[3].tex_coord = {0.0f, 1.0f};
|
||||||
bg_vertices[3].color = {r, g, b, alpha};
|
bg_vertices[3].color = {r, g, b, alpha};
|
||||||
|
|
||||||
// Índices para 2 triángulos
|
|
||||||
int bg_indices[6] = {0, 1, 2, 2, 3, 0};
|
int bg_indices[6] = {0, 1, 2, 2, 3, 0};
|
||||||
|
SDL_RenderGeometry(renderer_, nullptr, bg_vertices, 4, bg_indices, 6);
|
||||||
|
|
||||||
// Renderizar sin textura (nullptr) con alpha blending
|
// Renderizar texto del overlay (ajustando coordenadas para que sean relativas a 0,0)
|
||||||
SDL_RenderGeometry(renderer, nullptr, bg_vertices, 4, bg_indices, 6);
|
// Necesito renderizar el texto igual que en renderHelpText() pero con coordenadas ajustadas
|
||||||
|
|
||||||
// Renderizar texto de ayuda
|
// Obtener colores para el texto
|
||||||
renderHelpText();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HelpOverlay::renderHelpText() {
|
|
||||||
// Obtener 2 colores del tema para diferenciación visual
|
|
||||||
int text_r, text_g, text_b;
|
int text_r, text_g, 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};
|
||||||
@@ -160,75 +326,130 @@ void HelpOverlay::renderHelpText() {
|
|||||||
Color ball_color = theme_mgr_->getInterpolatedColor(0);
|
Color ball_color = theme_mgr_->getInterpolatedColor(0);
|
||||||
SDL_Color content_color = {static_cast<Uint8>(ball_color.r), static_cast<Uint8>(ball_color.g), static_cast<Uint8>(ball_color.b), 255};
|
SDL_Color content_color = {static_cast<Uint8>(ball_color.r), static_cast<Uint8>(ball_color.g), static_cast<Uint8>(ball_color.b), 255};
|
||||||
|
|
||||||
|
// Guardar colores actuales para comparación futura
|
||||||
|
last_category_color_ = category_color;
|
||||||
|
last_content_color_ = content_color;
|
||||||
|
last_bg_color_ = {static_cast<Uint8>(notif_bg_r), static_cast<Uint8>(notif_bg_g), static_cast<Uint8>(notif_bg_b), 255};
|
||||||
|
|
||||||
// Configuración de espaciado
|
// Configuración de espaciado
|
||||||
int line_height = text_renderer_->getTextHeight();
|
int line_height = text_renderer_->getTextHeight();
|
||||||
int padding = 25; // Equilibrio entre espacio y márgenes
|
int padding = 25;
|
||||||
int column_width = (box_size_ - padding * 3) / 2; // Ancho de cada columna (2 columnas)
|
|
||||||
|
|
||||||
int current_x = box_x_ + padding;
|
int current_x = padding; // Coordenadas relativas a la textura (0,0)
|
||||||
int current_y = box_y_ + padding;
|
int current_y = padding;
|
||||||
int current_column = 0; // 0 = izquierda, 1 = derecha
|
int current_column = 0;
|
||||||
|
|
||||||
// Título principal
|
// Título principal
|
||||||
const char* title = "CONTROLES - ViBe3 Physics";
|
const char* title = "CONTROLES - ViBe3 Physics";
|
||||||
int title_width = text_renderer_->getTextWidthPhysical(title);
|
int title_width = text_renderer_->getTextWidthPhysical(title);
|
||||||
text_renderer_->printAbsolute(
|
text_renderer_->printAbsolute(box_width_ / 2 - title_width / 2, current_y, title, category_color);
|
||||||
box_x_ + box_size_ / 2 - title_width / 2,
|
current_y += line_height * 2;
|
||||||
current_y,
|
|
||||||
title,
|
|
||||||
category_color);
|
|
||||||
current_y += line_height * 2; // Espacio después del título
|
|
||||||
|
|
||||||
// Guardar Y inicial de contenido (después del título)
|
|
||||||
int content_start_y = current_y;
|
int content_start_y = current_y;
|
||||||
|
|
||||||
// Renderizar cada línea
|
// Renderizar cada línea
|
||||||
for (const auto& binding : key_bindings_) {
|
for (const auto& binding : key_bindings_) {
|
||||||
// Si es un separador (descripción vacía), cambiar de columna
|
|
||||||
if (strcmp(binding.key, "[new_col]") == 0 && binding.description[0] == '\0') {
|
if (strcmp(binding.key, "[new_col]") == 0 && binding.description[0] == '\0') {
|
||||||
if (current_column == 0) {
|
if (current_column == 0) {
|
||||||
// Cambiar a columna derecha
|
|
||||||
current_column = 1;
|
current_column = 1;
|
||||||
current_x = box_x_ + padding + column_width + padding;
|
current_x = padding + column1_width_ + padding; // Usar ancho real de columna 1
|
||||||
current_y = content_start_y; // Reset Y a posición inicial de contenido
|
current_y = content_start_y;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si es un encabezado de categoría (descripción vacía pero key no vacía)
|
// CHECK PADDING INFERIOR ANTES de escribir la línea (AMBAS COLUMNAS)
|
||||||
|
// Verificar si la PRÓXIMA línea cabrá dentro del box con padding inferior
|
||||||
|
if (current_y + line_height >= box_height_ - padding) {
|
||||||
|
if (current_column == 0) {
|
||||||
|
// Columna 0 llena: cambiar a columna 1
|
||||||
|
current_column = 1;
|
||||||
|
current_x = padding + column1_width_ + padding;
|
||||||
|
current_y = content_start_y;
|
||||||
|
} else {
|
||||||
|
// Columna 1 llena: omitir resto de texto (no cabe)
|
||||||
|
// Preferible omitir que sobresalir del overlay
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (binding.description[0] == '\0') {
|
if (binding.description[0] == '\0') {
|
||||||
// Renderizar encabezado con color de categoría
|
text_renderer_->printAbsolute(current_x, current_y, binding.key, category_color);
|
||||||
text_renderer_->printAbsolute(
|
current_y += line_height + 2;
|
||||||
current_x,
|
|
||||||
current_y,
|
|
||||||
binding.key,
|
|
||||||
category_color);
|
|
||||||
current_y += line_height + 2; // Espacio extra después de encabezado
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderizar tecla con color de contenido
|
text_renderer_->printAbsolute(current_x, current_y, binding.key, content_color);
|
||||||
text_renderer_->printAbsolute(
|
|
||||||
current_x,
|
|
||||||
current_y,
|
|
||||||
binding.key,
|
|
||||||
content_color);
|
|
||||||
|
|
||||||
// Renderizar descripción con color de contenido
|
|
||||||
int key_width = text_renderer_->getTextWidthPhysical(binding.key);
|
int key_width = text_renderer_->getTextWidthPhysical(binding.key);
|
||||||
text_renderer_->printAbsolute(
|
text_renderer_->printAbsolute(current_x + key_width + 10, current_y, binding.description, content_color);
|
||||||
current_x + key_width + 10, // Espacio entre tecla y descripción
|
|
||||||
current_y,
|
|
||||||
binding.description,
|
|
||||||
content_color);
|
|
||||||
|
|
||||||
current_y += line_height;
|
current_y += line_height;
|
||||||
|
}
|
||||||
|
|
||||||
// Si nos pasamos del borde inferior del recuadro, cambiar de columna
|
// Restaurar render target original
|
||||||
if (current_y > box_y_ + box_size_ - padding && current_column == 0) {
|
SDL_SetRenderTarget(renderer_, prev_target);
|
||||||
current_column = 1;
|
|
||||||
current_x = box_x_ + padding + column_width + padding;
|
// Marcar que ya no necesita rebuild
|
||||||
current_y = content_start_y; // Reset Y a inicio de contenido
|
texture_needs_rebuild_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HelpOverlay::render(SDL_Renderer* renderer) {
|
||||||
|
if (!visible_) return;
|
||||||
|
|
||||||
|
// Obtener colores actuales del tema
|
||||||
|
int 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;
|
||||||
|
theme_mgr_->getCurrentThemeTextColor(text_r, text_g, text_b);
|
||||||
|
|
||||||
|
Color ball_color = theme_mgr_->getInterpolatedColor(0);
|
||||||
|
|
||||||
|
// Crear colores actuales para comparación
|
||||||
|
SDL_Color current_bg = {static_cast<Uint8>(notif_bg_r), static_cast<Uint8>(notif_bg_g), static_cast<Uint8>(notif_bg_b), 255};
|
||||||
|
SDL_Color current_category = {static_cast<Uint8>(text_r), static_cast<Uint8>(text_g), static_cast<Uint8>(text_b), 255};
|
||||||
|
SDL_Color current_content = {static_cast<Uint8>(ball_color.r), static_cast<Uint8>(ball_color.g), static_cast<Uint8>(ball_color.b), 255};
|
||||||
|
|
||||||
|
// Detectar si los colores han cambiado significativamente (umbral: 5/255)
|
||||||
|
constexpr int COLOR_CHANGE_THRESHOLD = 5;
|
||||||
|
bool colors_changed =
|
||||||
|
(abs(current_bg.r - last_bg_color_.r) > COLOR_CHANGE_THRESHOLD ||
|
||||||
|
abs(current_bg.g - last_bg_color_.g) > COLOR_CHANGE_THRESHOLD ||
|
||||||
|
abs(current_bg.b - last_bg_color_.b) > COLOR_CHANGE_THRESHOLD ||
|
||||||
|
abs(current_category.r - last_category_color_.r) > COLOR_CHANGE_THRESHOLD ||
|
||||||
|
abs(current_category.g - last_category_color_.g) > COLOR_CHANGE_THRESHOLD ||
|
||||||
|
abs(current_category.b - last_category_color_.b) > COLOR_CHANGE_THRESHOLD ||
|
||||||
|
abs(current_content.r - last_content_color_.r) > COLOR_CHANGE_THRESHOLD ||
|
||||||
|
abs(current_content.g - last_content_color_.g) > COLOR_CHANGE_THRESHOLD ||
|
||||||
|
abs(current_content.b - last_content_color_.b) > COLOR_CHANGE_THRESHOLD);
|
||||||
|
|
||||||
|
// Regenerar textura si es necesario (colores cambiaron O flag de rebuild activo)
|
||||||
|
if (texture_needs_rebuild_ || colors_changed || !cached_texture_) {
|
||||||
|
rebuildCachedTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Si no hay textura cacheada (error), salir
|
||||||
|
if (!cached_texture_) return;
|
||||||
|
|
||||||
|
// CRÍTICO: Habilitar alpha blending para que la transparencia funcione
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
|
|
||||||
|
// Obtener viewport actual (en modo letterbox F3 tiene offset para centrar imagen)
|
||||||
|
SDL_Rect viewport;
|
||||||
|
SDL_GetRenderViewport(renderer, &viewport);
|
||||||
|
|
||||||
|
// Calcular posición centrada dentro del VIEWPORT, no de la pantalla física
|
||||||
|
// viewport.w y viewport.h son las dimensiones del área visible
|
||||||
|
// viewport.x y viewport.y son el offset de las barras negras
|
||||||
|
int centered_x = viewport.x + (viewport.w - box_width_) / 2;
|
||||||
|
int centered_y = viewport.y + (viewport.h - box_height_) / 2;
|
||||||
|
|
||||||
|
// Renderizar la textura cacheada centrada en el viewport
|
||||||
|
SDL_FRect dest_rect;
|
||||||
|
dest_rect.x = static_cast<float>(centered_x);
|
||||||
|
dest_rect.y = static_cast<float>(centered_y);
|
||||||
|
dest_rect.w = static_cast<float>(box_width_);
|
||||||
|
dest_rect.h = static_cast<float>(box_height_);
|
||||||
|
|
||||||
|
SDL_RenderTexture(renderer, cached_texture_, nullptr, &dest_rect);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class HelpOverlay {
|
|||||||
/**
|
/**
|
||||||
* @brief Inicializa el overlay con renderer y theme manager
|
* @brief Inicializa el overlay con renderer y theme manager
|
||||||
*/
|
*/
|
||||||
void initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height);
|
void initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height, int font_size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Renderiza el overlay si está visible
|
* @brief Renderiza el overlay si está visible
|
||||||
@@ -36,10 +36,23 @@ class HelpOverlay {
|
|||||||
*/
|
*/
|
||||||
void updatePhysicalWindowSize(int physical_width, int physical_height);
|
void updatePhysicalWindowSize(int physical_width, int physical_height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reinitializa el tamaño de fuente (cuando cambia el tamaño de ventana)
|
||||||
|
*/
|
||||||
|
void reinitializeFontSize(int new_font_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Actualiza font size Y dimensiones físicas de forma atómica
|
||||||
|
* @param font_size Tamaño de fuente actual
|
||||||
|
* @param physical_width Nueva anchura física
|
||||||
|
* @param physical_height Nueva altura física
|
||||||
|
*/
|
||||||
|
void updateAll(int font_size, int physical_width, int physical_height);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Toggle visibilidad del overlay
|
* @brief Toggle visibilidad del overlay
|
||||||
*/
|
*/
|
||||||
void toggle() { visible_ = !visible_; }
|
void toggle();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Consulta si el overlay está visible
|
* @brief Consulta si el overlay está visible
|
||||||
@@ -54,16 +67,31 @@ class HelpOverlay {
|
|||||||
int physical_height_;
|
int physical_height_;
|
||||||
bool visible_;
|
bool visible_;
|
||||||
|
|
||||||
// Dimensiones calculadas del recuadro (90% de dimensión menor, cuadrado, centrado)
|
// Dimensiones calculadas del recuadro (anchura dinámica según texto, centrado)
|
||||||
int box_size_;
|
int box_width_;
|
||||||
|
int box_height_;
|
||||||
int box_x_;
|
int box_x_;
|
||||||
int box_y_;
|
int box_y_;
|
||||||
|
|
||||||
// Calcular dimensiones del recuadro según tamaño de ventana
|
// Anchos individuales de cada columna (para evitar solapamiento)
|
||||||
|
int column1_width_;
|
||||||
|
int column2_width_;
|
||||||
|
|
||||||
|
// Sistema de caché para optimización de rendimiento
|
||||||
|
SDL_Texture* cached_texture_; // Textura cacheada del overlay completo
|
||||||
|
SDL_Color last_category_color_; // Último color de categorías renderizado
|
||||||
|
SDL_Color last_content_color_; // Último color de contenido renderizado
|
||||||
|
SDL_Color last_bg_color_; // Último color de fondo renderizado
|
||||||
|
bool texture_needs_rebuild_; // Flag para forzar regeneración de textura
|
||||||
|
|
||||||
|
// Calcular dimensiones del texto más largo
|
||||||
|
void calculateTextDimensions(int& max_width, int& total_height);
|
||||||
|
|
||||||
|
// Calcular dimensiones del recuadro según tamaño de ventana y texto
|
||||||
void calculateBoxDimensions();
|
void calculateBoxDimensions();
|
||||||
|
|
||||||
// Renderizar texto de ayuda dentro del recuadro
|
// Regenerar textura cacheada del overlay
|
||||||
void renderHelpText();
|
void rebuildCachedTexture();
|
||||||
|
|
||||||
// Estructura para par tecla-descripción
|
// Estructura para par tecla-descripción
|
||||||
struct KeyBinding {
|
struct KeyBinding {
|
||||||
|
|||||||
@@ -5,6 +5,31 @@
|
|||||||
#include "../utils/easing_functions.h"
|
#include "../utils/easing_functions.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// HELPER: Obtener viewport en coordenadas físicas (no lógicas)
|
||||||
|
// ============================================================================
|
||||||
|
// SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación
|
||||||
|
// lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar
|
||||||
|
// temporalmente la presentación lógica.
|
||||||
|
static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) {
|
||||||
|
// Guardar estado actual de presentación lógica
|
||||||
|
int logical_w = 0, logical_h = 0;
|
||||||
|
SDL_RendererLogicalPresentation presentation_mode;
|
||||||
|
SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode);
|
||||||
|
|
||||||
|
// Deshabilitar presentación lógica temporalmente
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED);
|
||||||
|
|
||||||
|
// Obtener viewport en coordenadas físicas (píxeles reales)
|
||||||
|
SDL_Rect physical_viewport;
|
||||||
|
SDL_GetRenderViewport(renderer, &physical_viewport);
|
||||||
|
|
||||||
|
// Restaurar presentación lógica
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, presentation_mode);
|
||||||
|
|
||||||
|
return physical_viewport;
|
||||||
|
}
|
||||||
|
|
||||||
Notifier::Notifier()
|
Notifier::Notifier()
|
||||||
: renderer_(nullptr)
|
: renderer_(nullptr)
|
||||||
, text_renderer_(nullptr)
|
, text_renderer_(nullptr)
|
||||||
@@ -159,10 +184,14 @@ void Notifier::render() {
|
|||||||
int bg_width = text_width + (NOTIFICATION_PADDING * 2);
|
int bg_width = text_width + (NOTIFICATION_PADDING * 2);
|
||||||
int bg_height = text_height + (NOTIFICATION_PADDING * 2);
|
int bg_height = text_height + (NOTIFICATION_PADDING * 2);
|
||||||
|
|
||||||
// Centrar en la ventana FÍSICA (no usar viewport lógico)
|
// Obtener viewport FÍSICO (píxeles reales, no lógicos)
|
||||||
// CRÍTICO: Como renderizamos en píxeles físicos absolutos (bypass de presentación lógica),
|
// CRÍTICO: En F3, SDL_GetRenderViewport() devuelve coordenadas LÓGICAS,
|
||||||
// debemos centrar usando dimensiones físicas, no el viewport lógico de SDL
|
// pero printAbsolute() trabaja en píxeles FÍSICOS. Usar helper para obtener
|
||||||
int x = (window_width_ / 2) - (bg_width / 2);
|
// viewport en coordenadas físicas.
|
||||||
|
SDL_Rect physical_viewport = getPhysicalViewport(renderer_);
|
||||||
|
|
||||||
|
// Centrar en el viewport físico (coordenadas relativas al viewport)
|
||||||
|
int x = (physical_viewport.w / 2) - (bg_width / 2);
|
||||||
int y = NOTIFICATION_TOP_MARGIN + static_cast<int>(current_notification_->y_offset);
|
int y = NOTIFICATION_TOP_MARGIN + static_cast<int>(current_notification_->y_offset);
|
||||||
|
|
||||||
// Renderizar fondo semitransparente (con bypass de presentación lógica)
|
// Renderizar fondo semitransparente (con bypass de presentación lógica)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include "../ball.h" // for Ball
|
#include "../ball.h" // for Ball
|
||||||
#include "../defines.h" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode
|
#include "../defines.h" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode
|
||||||
|
#include "../engine.h" // for Engine (info de sistema)
|
||||||
#include "../scene/scene_manager.h" // for SceneManager
|
#include "../scene/scene_manager.h" // for SceneManager
|
||||||
#include "../shapes/shape.h" // for Shape
|
#include "../shapes/shape.h" // for Shape
|
||||||
#include "../text/textrenderer.h" // for TextRenderer
|
#include "../text/textrenderer.h" // for TextRenderer
|
||||||
@@ -12,6 +13,31 @@
|
|||||||
#include "notifier.h" // for Notifier
|
#include "notifier.h" // for Notifier
|
||||||
#include "help_overlay.h" // for HelpOverlay
|
#include "help_overlay.h" // for HelpOverlay
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// HELPER: Obtener viewport en coordenadas físicas (no lógicas)
|
||||||
|
// ============================================================================
|
||||||
|
// SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación
|
||||||
|
// lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar
|
||||||
|
// temporalmente la presentación lógica.
|
||||||
|
static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) {
|
||||||
|
// Guardar estado actual de presentación lógica
|
||||||
|
int logical_w = 0, logical_h = 0;
|
||||||
|
SDL_RendererLogicalPresentation presentation_mode;
|
||||||
|
SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode);
|
||||||
|
|
||||||
|
// Deshabilitar presentación lógica temporalmente
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED);
|
||||||
|
|
||||||
|
// Obtener viewport en coordenadas físicas (píxeles reales)
|
||||||
|
SDL_Rect physical_viewport;
|
||||||
|
SDL_GetRenderViewport(renderer, &physical_viewport);
|
||||||
|
|
||||||
|
// Restaurar presentación lógica
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, presentation_mode);
|
||||||
|
|
||||||
|
return physical_viewport;
|
||||||
|
}
|
||||||
|
|
||||||
UIManager::UIManager()
|
UIManager::UIManager()
|
||||||
: text_renderer_(nullptr)
|
: text_renderer_(nullptr)
|
||||||
, text_renderer_debug_(nullptr)
|
, text_renderer_debug_(nullptr)
|
||||||
@@ -31,7 +57,8 @@ UIManager::UIManager()
|
|||||||
, 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)
|
||||||
|
, current_font_size_(18) { // Tamaño por defecto (medium)
|
||||||
}
|
}
|
||||||
|
|
||||||
UIManager::~UIManager() {
|
UIManager::~UIManager() {
|
||||||
@@ -50,16 +77,18 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
|||||||
physical_window_width_ = physical_width;
|
physical_window_width_ = physical_width;
|
||||||
physical_window_height_ = physical_height;
|
physical_window_height_ = physical_height;
|
||||||
|
|
||||||
|
// Calcular tamaño de fuente apropiado según dimensiones físicas
|
||||||
|
current_font_size_ = calculateFontSize(physical_width, physical_height);
|
||||||
|
|
||||||
// Crear renderers de texto
|
// Crear renderers de texto
|
||||||
text_renderer_ = new TextRenderer();
|
text_renderer_ = new TextRenderer();
|
||||||
text_renderer_debug_ = new TextRenderer();
|
text_renderer_debug_ = new TextRenderer();
|
||||||
text_renderer_notifier_ = new TextRenderer();
|
text_renderer_notifier_ = new TextRenderer();
|
||||||
|
|
||||||
// Inicializar renderers
|
// Inicializar renderers con tamaño dinámico
|
||||||
// (el tamaño se configura dinámicamente en Engine según resolución)
|
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true);
|
||||||
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", 18, true);
|
text_renderer_debug_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true);
|
||||||
text_renderer_debug_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", 18, true);
|
text_renderer_notifier_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true);
|
||||||
text_renderer_notifier_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", 18, true);
|
|
||||||
|
|
||||||
// Crear y configurar sistema de notificaciones
|
// Crear y configurar sistema de notificaciones
|
||||||
notifier_ = new Notifier();
|
notifier_ = new Notifier();
|
||||||
@@ -68,7 +97,7 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
|||||||
|
|
||||||
// Crear y configurar sistema de ayuda (overlay)
|
// Crear y configurar sistema de ayuda (overlay)
|
||||||
help_overlay_ = new HelpOverlay();
|
help_overlay_ = new HelpOverlay();
|
||||||
help_overlay_->initialize(renderer, theme_manager_, physical_width, physical_height);
|
help_overlay_->initialize(renderer, theme_manager_, physical_width, physical_height, current_font_size_);
|
||||||
|
|
||||||
// Inicializar FPS counter
|
// Inicializar FPS counter
|
||||||
fps_last_time_ = SDL_GetTicks();
|
fps_last_time_ = SDL_GetTicks();
|
||||||
@@ -96,6 +125,7 @@ void UIManager::update(Uint64 current_time, float delta_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UIManager::render(SDL_Renderer* renderer,
|
void UIManager::render(SDL_Renderer* renderer,
|
||||||
|
const Engine* engine,
|
||||||
const SceneManager* scene_manager,
|
const SceneManager* scene_manager,
|
||||||
SimulationMode current_mode,
|
SimulationMode current_mode,
|
||||||
AppMode current_app_mode,
|
AppMode current_app_mode,
|
||||||
@@ -115,7 +145,7 @@ 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(scene_manager, current_mode, current_app_mode,
|
renderDebugHUD(engine, scene_manager, current_mode, current_app_mode,
|
||||||
active_shape, shape_convergence);
|
active_shape, shape_convergence);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,10 +182,33 @@ void UIManager::updateVSyncText(bool enabled) {
|
|||||||
void UIManager::updatePhysicalWindowSize(int width, int height) {
|
void UIManager::updatePhysicalWindowSize(int width, int height) {
|
||||||
physical_window_width_ = width;
|
physical_window_width_ = width;
|
||||||
physical_window_height_ = height;
|
physical_window_height_ = height;
|
||||||
notifier_->updateWindowSize(width, height);
|
|
||||||
if (help_overlay_) {
|
// Calcular nuevo tamaño de fuente apropiado
|
||||||
help_overlay_->updatePhysicalWindowSize(width, height);
|
int new_font_size = calculateFontSize(width, height);
|
||||||
|
|
||||||
|
// Si el tamaño cambió, reinicializar todos los text renderers
|
||||||
|
if (new_font_size != current_font_size_) {
|
||||||
|
current_font_size_ = new_font_size;
|
||||||
|
|
||||||
|
// Reinicializar text renderers con nuevo tamaño
|
||||||
|
if (text_renderer_) {
|
||||||
|
text_renderer_->reinitialize(current_font_size_);
|
||||||
}
|
}
|
||||||
|
if (text_renderer_debug_) {
|
||||||
|
text_renderer_debug_->reinitialize(current_font_size_);
|
||||||
|
}
|
||||||
|
if (text_renderer_notifier_) {
|
||||||
|
text_renderer_notifier_->reinitialize(current_font_size_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar help overlay con font size actual Y nuevas dimensiones (atómicamente)
|
||||||
|
if (help_overlay_) {
|
||||||
|
help_overlay_->updateAll(current_font_size_, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar otros componentes de UI con nuevas dimensiones
|
||||||
|
notifier_->updateWindowSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIManager::setTextObsolete(const std::string& text, int pos, int current_screen_width) {
|
void UIManager::setTextObsolete(const std::string& text, int pos, int current_screen_width) {
|
||||||
@@ -167,7 +220,8 @@ void UIManager::setTextObsolete(const std::string& text, int pos, int current_sc
|
|||||||
|
|
||||||
// === Métodos privados ===
|
// === Métodos privados ===
|
||||||
|
|
||||||
void UIManager::renderDebugHUD(const SceneManager* scene_manager,
|
void UIManager::renderDebugHUD(const Engine* engine,
|
||||||
|
const SceneManager* scene_manager,
|
||||||
SimulationMode current_mode,
|
SimulationMode current_mode,
|
||||||
AppMode current_app_mode,
|
AppMode current_app_mode,
|
||||||
const Shape* active_shape,
|
const Shape* active_shape,
|
||||||
@@ -175,92 +229,198 @@ void UIManager::renderDebugHUD(const SceneManager* scene_manager,
|
|||||||
// Obtener altura de línea para espaciado dinámico
|
// Obtener altura de línea para espaciado dinámico
|
||||||
int line_height = text_renderer_debug_->getTextHeight();
|
int line_height = text_renderer_debug_->getTextHeight();
|
||||||
int margin = 8; // Margen constante en píxeles físicos
|
int margin = 8; // Margen constante en píxeles físicos
|
||||||
int current_y = margin; // Y inicial en píxeles físicos
|
|
||||||
|
|
||||||
// Mostrar contador de FPS en esquina superior derecha
|
// Obtener viewport FÍSICO (píxeles reales, no lógicos)
|
||||||
|
// CRÍTICO: En F3, SDL_GetRenderViewport() devuelve coordenadas LÓGICAS,
|
||||||
|
// pero printAbsolute() trabaja en píxeles FÍSICOS. Usar helper para obtener
|
||||||
|
// viewport en coordenadas físicas.
|
||||||
|
SDL_Rect physical_viewport = getPhysicalViewport(renderer_);
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// COLUMNA LEFT (Sistema)
|
||||||
|
// ===========================
|
||||||
|
int left_y = margin;
|
||||||
|
|
||||||
|
// AppMode (antes estaba centrado, ahora va a la izquierda)
|
||||||
|
std::string appmode_text;
|
||||||
|
SDL_Color appmode_color = {255, 255, 255, 255}; // Blanco por defecto
|
||||||
|
|
||||||
|
if (current_app_mode == AppMode::LOGO) {
|
||||||
|
appmode_text = "AppMode: LOGO";
|
||||||
|
appmode_color = {255, 128, 0, 255}; // Naranja
|
||||||
|
} else if (current_app_mode == AppMode::DEMO) {
|
||||||
|
appmode_text = "AppMode: DEMO";
|
||||||
|
appmode_color = {255, 165, 0, 255}; // Naranja
|
||||||
|
} else if (current_app_mode == AppMode::DEMO_LITE) {
|
||||||
|
appmode_text = "AppMode: DEMO LITE";
|
||||||
|
appmode_color = {255, 200, 0, 255}; // Amarillo-naranja
|
||||||
|
} else {
|
||||||
|
appmode_text = "AppMode: SANDBOX";
|
||||||
|
appmode_color = {0, 255, 128, 255}; // Verde claro
|
||||||
|
}
|
||||||
|
text_renderer_debug_->printAbsolute(margin, left_y, appmode_text.c_str(), appmode_color);
|
||||||
|
left_y += line_height;
|
||||||
|
|
||||||
|
// SimulationMode
|
||||||
|
std::string simmode_text;
|
||||||
|
if (current_mode == SimulationMode::PHYSICS) {
|
||||||
|
simmode_text = "SimMode: PHYSICS";
|
||||||
|
} else if (current_mode == SimulationMode::SHAPE) {
|
||||||
|
if (active_shape) {
|
||||||
|
simmode_text = std::string("SimMode: SHAPE (") + active_shape->getName() + ")";
|
||||||
|
} else {
|
||||||
|
simmode_text = "SimMode: SHAPE";
|
||||||
|
}
|
||||||
|
} else if (current_mode == SimulationMode::BOIDS) {
|
||||||
|
simmode_text = "SimMode: BOIDS";
|
||||||
|
}
|
||||||
|
text_renderer_debug_->printAbsolute(margin, left_y, simmode_text.c_str(), {0, 255, 255, 255}); // Cian
|
||||||
|
left_y += line_height;
|
||||||
|
|
||||||
|
// Número de pelotas (escenario actual)
|
||||||
|
size_t ball_count = scene_manager->getBallCount();
|
||||||
|
std::string balls_text;
|
||||||
|
if (ball_count >= 1000) {
|
||||||
|
// Formatear con separador de miles (ejemplo: 5,000 o 50,000)
|
||||||
|
std::string count_str = std::to_string(ball_count);
|
||||||
|
std::string formatted;
|
||||||
|
int digits = count_str.length();
|
||||||
|
for (int i = 0; i < digits; i++) {
|
||||||
|
if (i > 0 && (digits - i) % 3 == 0) {
|
||||||
|
formatted += ',';
|
||||||
|
}
|
||||||
|
formatted += count_str[i];
|
||||||
|
}
|
||||||
|
balls_text = "Balls: " + formatted;
|
||||||
|
} else {
|
||||||
|
balls_text = "Balls: " + std::to_string(ball_count);
|
||||||
|
}
|
||||||
|
text_renderer_debug_->printAbsolute(margin, left_y, balls_text.c_str(), {128, 255, 128, 255}); // Verde claro
|
||||||
|
left_y += line_height;
|
||||||
|
|
||||||
|
// V-Sync
|
||||||
|
text_renderer_debug_->printAbsolute(margin, left_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian
|
||||||
|
left_y += line_height;
|
||||||
|
|
||||||
|
// Modo de escalado (INTEGER/LETTERBOX/STRETCH o WINDOWED si no está en fullscreen)
|
||||||
|
std::string scaling_text;
|
||||||
|
if (engine->getFullscreenEnabled() || engine->getRealFullscreenEnabled()) {
|
||||||
|
ScalingMode scaling = engine->getCurrentScalingMode();
|
||||||
|
if (scaling == ScalingMode::INTEGER) {
|
||||||
|
scaling_text = "Scaling: INTEGER";
|
||||||
|
} else if (scaling == ScalingMode::LETTERBOX) {
|
||||||
|
scaling_text = "Scaling: LETTERBOX";
|
||||||
|
} else if (scaling == ScalingMode::STRETCH) {
|
||||||
|
scaling_text = "Scaling: STRETCH";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scaling_text = "Scaling: WINDOWED";
|
||||||
|
}
|
||||||
|
text_renderer_debug_->printAbsolute(margin, left_y, scaling_text.c_str(), {255, 255, 0, 255}); // Amarillo
|
||||||
|
left_y += line_height;
|
||||||
|
|
||||||
|
// Resolución física (píxeles reales de la ventana)
|
||||||
|
std::string phys_res_text = "Physical: " + std::to_string(physical_window_width_) + "x" + std::to_string(physical_window_height_);
|
||||||
|
text_renderer_debug_->printAbsolute(margin, left_y, phys_res_text.c_str(), {255, 128, 255, 255}); // Magenta claro
|
||||||
|
left_y += line_height;
|
||||||
|
|
||||||
|
// Resolución lógica (resolución interna del renderizador)
|
||||||
|
std::string logic_res_text = "Logical: " + std::to_string(engine->getCurrentScreenWidth()) + "x" + std::to_string(engine->getCurrentScreenHeight());
|
||||||
|
text_renderer_debug_->printAbsolute(margin, left_y, logic_res_text.c_str(), {255, 128, 255, 255}); // Magenta claro
|
||||||
|
left_y += line_height;
|
||||||
|
|
||||||
|
// Display refresh rate (obtener de SDL)
|
||||||
|
std::string refresh_text;
|
||||||
|
int num_displays = 0;
|
||||||
|
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
||||||
|
if (displays && num_displays > 0) {
|
||||||
|
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
|
||||||
|
if (dm) {
|
||||||
|
refresh_text = "Refresh: " + std::to_string(static_cast<int>(dm->refresh_rate)) + " Hz";
|
||||||
|
} else {
|
||||||
|
refresh_text = "Refresh: N/A";
|
||||||
|
}
|
||||||
|
SDL_free(displays);
|
||||||
|
} else {
|
||||||
|
refresh_text = "Refresh: N/A";
|
||||||
|
}
|
||||||
|
text_renderer_debug_->printAbsolute(margin, left_y, refresh_text.c_str(), {255, 255, 128, 255}); // Amarillo claro
|
||||||
|
left_y += line_height;
|
||||||
|
|
||||||
|
// Tema actual (delegado a ThemeManager)
|
||||||
|
std::string theme_text = std::string("Theme: ") + theme_manager_->getCurrentThemeNameEN();
|
||||||
|
text_renderer_debug_->printAbsolute(margin, left_y, theme_text.c_str(), {128, 255, 255, 255}); // Cian claro
|
||||||
|
left_y += line_height;
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// COLUMNA RIGHT (Primera pelota)
|
||||||
|
// ===========================
|
||||||
|
int right_y = margin;
|
||||||
|
|
||||||
|
// FPS counter (esquina superior derecha)
|
||||||
int fps_text_width = text_renderer_debug_->getTextWidthPhysical(fps_text_.c_str());
|
int fps_text_width = text_renderer_debug_->getTextWidthPhysical(fps_text_.c_str());
|
||||||
int fps_x = physical_window_width_ - fps_text_width - margin;
|
int fps_x = physical_viewport.w - fps_text_width - margin;
|
||||||
text_renderer_debug_->printAbsolute(fps_x, current_y, fps_text_.c_str(), {255, 255, 0, 255}); // Amarillo
|
text_renderer_debug_->printAbsolute(fps_x, right_y, fps_text_.c_str(), {255, 255, 0, 255}); // Amarillo
|
||||||
|
right_y += line_height;
|
||||||
|
|
||||||
// Mostrar estado V-Sync en esquina superior izquierda
|
// Info de la primera pelota (si existe)
|
||||||
text_renderer_debug_->printAbsolute(margin, current_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian
|
|
||||||
current_y += line_height;
|
|
||||||
|
|
||||||
// Debug: Mostrar valores de la primera pelota (si existe)
|
|
||||||
const Ball* first_ball = scene_manager->getFirstBall();
|
const Ball* first_ball = scene_manager->getFirstBall();
|
||||||
if (first_ball != nullptr) {
|
if (first_ball != nullptr) {
|
||||||
// Línea 1: Gravedad
|
// Posición X, Y
|
||||||
int grav_int = static_cast<int>(first_ball->getGravityForce());
|
SDL_FRect pos = first_ball->getPosition();
|
||||||
std::string grav_text = "Gravedad: " + std::to_string(grav_int);
|
std::string pos_text = "Pos: (" + std::to_string(static_cast<int>(pos.x)) + ", " + std::to_string(static_cast<int>(pos.y)) + ")";
|
||||||
text_renderer_debug_->printAbsolute(margin, current_y, grav_text.c_str(), {255, 0, 255, 255}); // Magenta
|
int pos_width = text_renderer_debug_->getTextWidthPhysical(pos_text.c_str());
|
||||||
current_y += line_height;
|
text_renderer_debug_->printAbsolute(physical_viewport.w - pos_width - margin, right_y, pos_text.c_str(), {255, 128, 128, 255}); // Rojo claro
|
||||||
|
right_y += line_height;
|
||||||
|
|
||||||
// Línea 2: Velocidad Y
|
// Velocidad X
|
||||||
|
int vx_int = static_cast<int>(first_ball->getVelocityX());
|
||||||
|
std::string vx_text = "VelX: " + std::to_string(vx_int);
|
||||||
|
int vx_width = text_renderer_debug_->getTextWidthPhysical(vx_text.c_str());
|
||||||
|
text_renderer_debug_->printAbsolute(physical_viewport.w - vx_width - margin, right_y, vx_text.c_str(), {128, 255, 128, 255}); // Verde claro
|
||||||
|
right_y += line_height;
|
||||||
|
|
||||||
|
// Velocidad Y
|
||||||
int vy_int = static_cast<int>(first_ball->getVelocityY());
|
int vy_int = static_cast<int>(first_ball->getVelocityY());
|
||||||
std::string vy_text = "Velocidad Y: " + std::to_string(vy_int);
|
std::string vy_text = "VelY: " + std::to_string(vy_int);
|
||||||
text_renderer_debug_->printAbsolute(margin, current_y, vy_text.c_str(), {255, 0, 255, 255}); // Magenta
|
int vy_width = text_renderer_debug_->getTextWidthPhysical(vy_text.c_str());
|
||||||
current_y += line_height;
|
text_renderer_debug_->printAbsolute(physical_viewport.w - vy_width - margin, right_y, vy_text.c_str(), {128, 255, 128, 255}); // Verde claro
|
||||||
|
right_y += line_height;
|
||||||
|
|
||||||
// Línea 3: Estado superficie
|
// Fuerza de gravedad
|
||||||
std::string surface_text = first_ball->isOnSurface() ? "Superficie: Sí" : "Superficie: No";
|
int grav_int = static_cast<int>(first_ball->getGravityForce());
|
||||||
text_renderer_debug_->printAbsolute(margin, current_y, surface_text.c_str(), {255, 0, 255, 255}); // Magenta
|
std::string grav_text = "Gravity: " + std::to_string(grav_int);
|
||||||
current_y += line_height;
|
int grav_width = text_renderer_debug_->getTextWidthPhysical(grav_text.c_str());
|
||||||
|
text_renderer_debug_->printAbsolute(physical_viewport.w - grav_width - margin, right_y, grav_text.c_str(), {255, 255, 128, 255}); // Amarillo claro
|
||||||
|
right_y += line_height;
|
||||||
|
|
||||||
// Línea 4: Coeficiente de rebote (loss)
|
// Estado superficie
|
||||||
|
std::string surface_text = first_ball->isOnSurface() ? "Surface: YES" : "Surface: NO";
|
||||||
|
int surface_width = text_renderer_debug_->getTextWidthPhysical(surface_text.c_str());
|
||||||
|
text_renderer_debug_->printAbsolute(physical_viewport.w - surface_width - margin, right_y, surface_text.c_str(), {255, 200, 128, 255}); // Naranja claro
|
||||||
|
right_y += line_height;
|
||||||
|
|
||||||
|
// Coeficiente de rebote (loss)
|
||||||
float loss_val = first_ball->getLossCoefficient();
|
float loss_val = first_ball->getLossCoefficient();
|
||||||
std::string loss_text = "Rebote: " + std::to_string(loss_val).substr(0, 4);
|
std::string loss_text = "Loss: " + std::to_string(loss_val).substr(0, 4);
|
||||||
text_renderer_debug_->printAbsolute(margin, current_y, loss_text.c_str(), {255, 0, 255, 255}); // Magenta
|
int loss_width = text_renderer_debug_->getTextWidthPhysical(loss_text.c_str());
|
||||||
current_y += line_height;
|
text_renderer_debug_->printAbsolute(physical_viewport.w - loss_width - margin, right_y, loss_text.c_str(), {255, 128, 255, 255}); // Magenta
|
||||||
|
right_y += line_height;
|
||||||
|
|
||||||
// Línea 5: Dirección de gravedad
|
// Dirección de gravedad
|
||||||
std::string gravity_dir_text = "Dirección: " + gravityDirectionToString(static_cast<int>(scene_manager->getCurrentGravity()));
|
std::string gravity_dir_text = "Dir: " + gravityDirectionToString(static_cast<int>(scene_manager->getCurrentGravity()));
|
||||||
text_renderer_debug_->printAbsolute(margin, current_y, gravity_dir_text.c_str(), {255, 255, 0, 255}); // Amarillo
|
int dir_width = text_renderer_debug_->getTextWidthPhysical(gravity_dir_text.c_str());
|
||||||
current_y += line_height;
|
text_renderer_debug_->printAbsolute(physical_viewport.w - dir_width - margin, right_y, gravity_dir_text.c_str(), {128, 255, 255, 255}); // Cian claro
|
||||||
|
right_y += line_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: Mostrar tema actual (delegado a ThemeManager)
|
// Convergencia en modo LOGO (solo cuando está activo) - Parte inferior derecha
|
||||||
std::string theme_text = std::string("Tema: ") + theme_manager_->getCurrentThemeNameEN();
|
|
||||||
text_renderer_debug_->printAbsolute(margin, current_y, theme_text.c_str(), {255, 255, 128, 255}); // Amarillo claro
|
|
||||||
current_y += line_height;
|
|
||||||
|
|
||||||
// Debug: Mostrar modo de simulación actual
|
|
||||||
std::string mode_text;
|
|
||||||
if (current_mode == SimulationMode::PHYSICS) {
|
|
||||||
mode_text = "Modo: Física";
|
|
||||||
} else if (active_shape) {
|
|
||||||
mode_text = std::string("Modo: ") + active_shape->getName();
|
|
||||||
} else {
|
|
||||||
mode_text = "Modo: Forma";
|
|
||||||
}
|
|
||||||
text_renderer_debug_->printAbsolute(margin, current_y, mode_text.c_str(), {0, 255, 128, 255}); // Verde claro
|
|
||||||
current_y += line_height;
|
|
||||||
|
|
||||||
// Debug: Mostrar convergencia en modo LOGO (solo cuando está activo)
|
|
||||||
if (current_app_mode == AppMode::LOGO && current_mode == SimulationMode::SHAPE) {
|
if (current_app_mode == AppMode::LOGO && current_mode == SimulationMode::SHAPE) {
|
||||||
int convergence_percent = static_cast<int>(shape_convergence * 100.0f);
|
int convergence_percent = static_cast<int>(shape_convergence * 100.0f);
|
||||||
std::string convergence_text = "Convergencia: " + std::to_string(convergence_percent) + "%";
|
std::string convergence_text = "Convergence: " + std::to_string(convergence_percent) + "%";
|
||||||
text_renderer_debug_->printAbsolute(margin, current_y, convergence_text.c_str(), {255, 128, 0, 255}); // Naranja
|
int conv_width = text_renderer_debug_->getTextWidthPhysical(convergence_text.c_str());
|
||||||
current_y += line_height;
|
text_renderer_debug_->printAbsolute(physical_viewport.w - conv_width - margin, right_y, convergence_text.c_str(), {255, 128, 0, 255}); // Naranja
|
||||||
}
|
right_y += line_height;
|
||||||
|
|
||||||
// Debug: Mostrar modo DEMO/LOGO activo (siempre visible cuando debug está ON)
|
|
||||||
// FIJO en tercera fila (no se mueve con otros elementos del HUD)
|
|
||||||
int fixed_y = margin + (line_height * 2); // Tercera fila fija
|
|
||||||
if (current_app_mode == AppMode::LOGO) {
|
|
||||||
const char* logo_text = "Modo Logo";
|
|
||||||
int logo_text_width = text_renderer_debug_->getTextWidthPhysical(logo_text);
|
|
||||||
int logo_x = (physical_window_width_ - logo_text_width) / 2;
|
|
||||||
text_renderer_debug_->printAbsolute(logo_x, fixed_y, logo_text, {255, 128, 0, 255}); // Naranja
|
|
||||||
} else if (current_app_mode == AppMode::DEMO) {
|
|
||||||
const char* demo_text = "Modo Demo";
|
|
||||||
int demo_text_width = text_renderer_debug_->getTextWidthPhysical(demo_text);
|
|
||||||
int demo_x = (physical_window_width_ - demo_text_width) / 2;
|
|
||||||
text_renderer_debug_->printAbsolute(demo_x, fixed_y, demo_text, {255, 165, 0, 255}); // Naranja
|
|
||||||
} else if (current_app_mode == AppMode::DEMO_LITE) {
|
|
||||||
const char* lite_text = "Modo Demo Lite";
|
|
||||||
int lite_text_width = text_renderer_debug_->getTextWidthPhysical(lite_text);
|
|
||||||
int lite_x = (physical_window_width_ - lite_text_width) / 2;
|
|
||||||
text_renderer_debug_->printAbsolute(lite_x, fixed_y, lite_text, {255, 200, 0, 255}); // Amarillo-naranja
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,3 +454,21 @@ std::string UIManager::gravityDirectionToString(int direction) const {
|
|||||||
default: return "Desconocida";
|
default: return "Desconocida";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int UIManager::calculateFontSize(int physical_width, int physical_height) const {
|
||||||
|
// Calcular área física de la ventana
|
||||||
|
int area = physical_width * physical_height;
|
||||||
|
|
||||||
|
// Stepped scaling con 3 tamaños:
|
||||||
|
// - SMALL: < 800x600 (480,000 pixels) → 14px
|
||||||
|
// - MEDIUM: 800x600 a 1920x1080 (2,073,600 pixels) → 18px
|
||||||
|
// - LARGE: > 1920x1080 → 24px
|
||||||
|
|
||||||
|
if (area < 480000) {
|
||||||
|
return 14; // Ventanas pequeñas
|
||||||
|
} else if (area < 2073600) {
|
||||||
|
return 18; // Ventanas medianas (default)
|
||||||
|
} else {
|
||||||
|
return 24; // Ventanas grandes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class ThemeManager;
|
|||||||
class TextRenderer;
|
class TextRenderer;
|
||||||
class Notifier;
|
class Notifier;
|
||||||
class HelpOverlay;
|
class HelpOverlay;
|
||||||
|
class Engine;
|
||||||
enum class SimulationMode;
|
enum class SimulationMode;
|
||||||
enum class AppMode;
|
enum class AppMode;
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ class UIManager {
|
|||||||
/**
|
/**
|
||||||
* @brief Renderiza todos los elementos UI
|
* @brief Renderiza todos los elementos UI
|
||||||
* @param renderer Renderizador SDL3
|
* @param renderer Renderizador SDL3
|
||||||
|
* @param engine Puntero a Engine (para info de sistema)
|
||||||
* @param scene_manager SceneManager (para info de debug)
|
* @param scene_manager SceneManager (para info de debug)
|
||||||
* @param current_mode Modo de simulación actual (PHYSICS/SHAPE)
|
* @param current_mode Modo de simulación actual (PHYSICS/SHAPE)
|
||||||
* @param current_app_mode Modo de aplicación (SANDBOX/DEMO/LOGO)
|
* @param current_app_mode Modo de aplicación (SANDBOX/DEMO/LOGO)
|
||||||
@@ -69,6 +71,7 @@ class UIManager {
|
|||||||
* @param current_screen_width Ancho lógico de pantalla (para texto centrado)
|
* @param current_screen_width Ancho lógico de pantalla (para texto centrado)
|
||||||
*/
|
*/
|
||||||
void render(SDL_Renderer* renderer,
|
void render(SDL_Renderer* renderer,
|
||||||
|
const Engine* engine,
|
||||||
const SceneManager* scene_manager,
|
const SceneManager* scene_manager,
|
||||||
SimulationMode current_mode,
|
SimulationMode current_mode,
|
||||||
AppMode current_app_mode,
|
AppMode current_app_mode,
|
||||||
@@ -136,13 +139,15 @@ class UIManager {
|
|||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @brief Renderiza HUD de debug (solo si show_debug_ == true)
|
* @brief Renderiza HUD de debug (solo si show_debug_ == true)
|
||||||
|
* @param engine Puntero a Engine (para info de sistema)
|
||||||
* @param scene_manager SceneManager (para info de pelotas)
|
* @param scene_manager SceneManager (para info de pelotas)
|
||||||
* @param current_mode Modo de simulación (PHYSICS/SHAPE)
|
* @param current_mode Modo de simulación (PHYSICS/SHAPE)
|
||||||
* @param current_app_mode Modo de aplicación (SANDBOX/DEMO/LOGO)
|
* @param current_app_mode Modo de aplicación (SANDBOX/DEMO/LOGO)
|
||||||
* @param active_shape Figura 3D activa (puede ser nullptr)
|
* @param active_shape Figura 3D activa (puede ser nullptr)
|
||||||
* @param shape_convergence % de convergencia en LOGO mode
|
* @param shape_convergence % de convergencia en LOGO mode
|
||||||
*/
|
*/
|
||||||
void renderDebugHUD(const SceneManager* scene_manager,
|
void renderDebugHUD(const Engine* engine,
|
||||||
|
const SceneManager* scene_manager,
|
||||||
SimulationMode current_mode,
|
SimulationMode current_mode,
|
||||||
AppMode current_app_mode,
|
AppMode current_app_mode,
|
||||||
const Shape* active_shape,
|
const Shape* active_shape,
|
||||||
@@ -161,6 +166,14 @@ class UIManager {
|
|||||||
*/
|
*/
|
||||||
std::string gravityDirectionToString(int direction) const;
|
std::string gravityDirectionToString(int direction) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calcula tamaño de fuente apropiado según dimensiones físicas
|
||||||
|
* @param physical_width Ancho físico de ventana
|
||||||
|
* @param physical_height Alto físico de ventana
|
||||||
|
* @return Tamaño de fuente (14px/18px/24px)
|
||||||
|
*/
|
||||||
|
int calculateFontSize(int physical_width, int physical_height) const;
|
||||||
|
|
||||||
// === Recursos de renderizado ===
|
// === Recursos de renderizado ===
|
||||||
TextRenderer* text_renderer_; // Texto obsoleto (DEPRECATED)
|
TextRenderer* text_renderer_; // Texto obsoleto (DEPRECATED)
|
||||||
TextRenderer* text_renderer_debug_; // HUD de debug
|
TextRenderer* text_renderer_debug_; // HUD de debug
|
||||||
@@ -189,4 +202,7 @@ class UIManager {
|
|||||||
ThemeManager* theme_manager_; // Gestor de temas (para colores)
|
ThemeManager* theme_manager_; // Gestor de temas (para colores)
|
||||||
int physical_window_width_; // Ancho físico de ventana (píxeles reales)
|
int physical_window_width_; // Ancho físico de ventana (píxeles reales)
|
||||||
int physical_window_height_; // Alto físico de ventana (píxeles reales)
|
int physical_window_height_; // Alto físico de ventana (píxeles reales)
|
||||||
|
|
||||||
|
// === Sistema de escalado dinámico de texto ===
|
||||||
|
int current_font_size_; // Tamaño de fuente actual (14/18/24)
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user