8 Commits

Author SHA1 Message Date
7609b9ef5c feat: Animaciones de logos sincronizadas con retraso + easing en alpha
Implementa sistema de sincronización elegante entre logos con efecto de
"seguimiento" y fade suavizado para eliminar desincronización visual.

Cambios principales:

1. **Animación sincronizada**: Ambos logos usan la MISMA animación
   - Eliminadas 4 variables independientes (logo1/logo2 × entry/exit)
   - Una sola variable `current_animation_` compartida
   - Misma animación para entrada y salida (simetría)

2. **Retraso de Logo 2**: 0.25 segundos detrás de Logo 1
   - Logo 1 empieza en t=0.00s
   - Logo 2 empieza en t=0.25s
   - Efecto visual de "eco" o "seguimiento"

3. **Alpha independiente con retraso**:
   - `logo1_alpha_` y `logo2_alpha_` separados
   - Logo 2 aparece/desaparece más tarde visualmente

4. **Easing en alpha** (NUEVO):
   - Aplicado `easeInOutQuad()` al fade de alpha
   - Elimina problema de "logo deformado esperando a desvanecerse"
   - Sincronización visual perfecta entre animación y fade
   - Curva suave: lento al inicio, rápido en medio, lento al final

Comportamiento resultante:

FADE_IN:
- t=0.00s: Logo 1 empieza (alpha con easing)
- t=0.25s: Logo 2 empieza (alpha con easing + retraso)
- t=0.50s: Logo 1 completamente visible
- t=0.75s: Logo 2 completamente visible

FADE_OUT:
- t=0.00s: Logo 1 empieza a desaparecer (misma animación)
- t=0.25s: Logo 2 empieza a desaparecer
- t=0.50s: Logo 1 completamente invisible
- t=0.75s: Logo 2 completamente invisible

Archivos modificados:
- source/defines.h: +APPLOGO_LOGO2_DELAY
- source/app_logo.h: Reestructuración de variables de animación/alpha
- source/app_logo.cpp: Implementación de retraso + easing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 12:33:09 +02:00
ad3f5a00e4 feat: Sistema de pre-escalado de logos con stb_image_resize2
Implementa pre-escalado de alta calidad para eliminar artefactos de
escalado dinámico de SDL y mejorar la nitidez visual de los logos.

Características:
- 4 texturas pre-escaladas (2 logos × 2 resoluciones: base + nativa)
- Detección automática de resolución nativa del monitor
- Switching dinámico entre texturas al cambiar resolución (F4)
- Renderizado 1:1 sin escalado adicional (máxima calidad)
- Algoritmo Mitchell en espacio sRGB (balance calidad/velocidad)
- Todo en RAM, sin archivos temporales

Archivos nuevos:
- source/external/stb_image_resize2.h: Biblioteca de escalado stb
- source/logo_scaler.h/cpp: Clase helper para pre-escalado

Cambios en AppLogo:
- Reemplazadas shared_ptr<Texture> por SDL_Texture* raw pointers
- initialize(): Pre-escala logos a 2 resoluciones al inicio
- updateScreenSize(): Cambia entre texturas según resolución
- render(): Simplificado, siempre usa renderWithGeometry()
- ~AppLogo(): Libera 4 texturas SDL manualmente

El sistema detecta la resolución nativa al inicio y crea versiones
optimizadas. Al presionar F4, cambia automáticamente a la textura
nativa para calidad perfecta en fullscreen.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 10:36:26 +02:00
c91cb1ca56 feat: Sistema dual de logos con animaciones independientes + ajuste de tamaño/posición
Implementación de sistema de 2 logos superpuestos con animaciones completamente independientes:

**Nuevas características:**
- Dos logos superpuestos (logo1.png + logo2.png) con animaciones independientes
- 4 tipos de animación: ZOOM_ONLY, ELASTIC_STICK, ROTATE_SPIRAL, BOUNCE_SQUASH
- Aleatorización independiente para entrada y salida de cada logo
- 256 combinaciones posibles (4×4 entrada × 4×4 salida)

**Ajuste de tamaño y posición:**
- Nueva constante APPLOGO_HEIGHT_PERCENT (40%) - altura del logo respecto a pantalla
- Nueva constante APPLOGO_PADDING_PERCENT (10%) - padding desde esquina inferior-derecha
- Logo anclado a esquina en lugar de centrado en cuadrante
- Valores fácilmente ajustables mediante constantes en defines.h

**Cambios técnicos:**
- Variables duplicadas para logo1 y logo2 (scale, squash, stretch, rotation)
- Variables compartidas para sincronización (state, timer, alpha)
- renderWithGeometry() acepta parámetro logo_index (1 o 2)
- Logo1 renderizado primero (fondo), Logo2 encima (overlay)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 10:01:32 +02:00
8d608357b4 feat: Animación elástica tipo pegatina para AppLogo
Implementación de deformación elástica con vértices para el logo:

FADE IN (0.5s):
- Scale: 120% → 100% con easing elástico (bounce)
- Squash Y: 0.6 → 1.0 con easing back (aplastamiento)
- Stretch X: compensación automática
- Efecto: logo se "pega" aplastándose y rebotando

FADE OUT (0.5s):
- Scale: 100% → 120% (aceleración cuadrática)
- Squash Y: 1.0 → 1.3 (estiramiento vertical)
- Stretch X: 1.0 → 0.8 (compresión horizontal)
- Rotación: 0° → ~5.7° (torsión sutil)
- Efecto: logo se "despega" estirándose y girando

Características técnicas:
- Enum AppLogoAnimationType (ZOOM_ONLY / ELASTIC_STICK)
- Renderizado con SDL_RenderGeometry para deformaciones
- Funciones de easing: easeOutElastic() y easeOutBack()
- Transformación de vértices con rotación y escala 2D
- Actualmente fijo en ELASTIC_STICK para testing

Limpieza adicional:
- Eliminado dbgtxt.h (no utilizado)
- Removidos SDL_Log de debug en HelpOverlay
- Comentada variable no usada en ShapeManager

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 01:46:56 +02:00
f73a133756 feat: Sistema de logo periódico con fade in/out
- Nuevo sistema AppLogo que muestra el logo cada 20 segundos por 5 segundos
- Fade in/out suave de 0.5 segundos con alpha blending
- Máquina de estados: HIDDEN → FADE_IN → VISIBLE → FADE_OUT
- Logo posicionado en cuadrante inferior derecho (1/4 de pantalla)
- Añadido método setAlpha() a Texture para control de transparencia
- Habilitado SDL_BLENDMODE_BLEND en todas las texturas
- Filtrado LINEAR para suavizado del logo escalado
- Desactivado automáticamente en modo SANDBOX

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 01:31:29 +02:00
de23327861 fix: Mantener gravedad OFF al cambiar escenario en modo BOIDS
Problema:
- Al cambiar de escenario (teclas 1-8) en modo BOIDS, la gravedad
  se reseteaba a 720 en lugar de mantenerse en 0
- SceneManager::changeScenario() reinicializa bolas con gravedad default
- Esto rompía el invariante: "modo BOIDS = gravedad OFF siempre"

Solución:
- Añadido check en Engine::changeScenario() para forzar gravedad OFF
  después del cambio de escenario si estamos en modo BOIDS
- Mantiene consistencia con el comportamiento de SHAPE mode

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 00:14:08 +02:00
f6402084eb feat: Bordes como obstáculos + Variables BOIDS ajustables + Fix tecla G
**1. Bordes como obstáculos (no más wrapping):**
- Implementada fuerza de repulsión cuando boids se acercan a bordes
- Nueva regla: Boundary Avoidance (evitar bordes)
- Fuerza proporcional a cercanía (0% en margen, 100% en colisión)
- Constantes: BOID_BOUNDARY_MARGIN (50px), BOID_BOUNDARY_WEIGHT (7200 px/s²)

**2. Variables ajustables en runtime:**
- Añadidas 11 variables miembro en BoidManager (inicializadas con defines.h)
- Permite modificar comportamiento sin recompilar
- Variables: radios (separation/alignment/cohesion), weights, speeds, boundary
- Base para futuras herramientas de debug/tweaking visual

**3. Fix tecla G (BOIDS → PHYSICS):**
- Corregido: toggleBoidsMode() ahora acepta parámetro force_gravity_on
- handleGravityToggle() pasa explícitamente false para preservar inercia
- Transición BOIDS→PHYSICS ahora mantiene gravedad OFF correctamente

**Implementación:**
- defines.h: +2 constantes (BOUNDARY_MARGIN, BOUNDARY_WEIGHT)
- boid_manager.h: +11 variables miembro ajustables
- boid_manager.cpp:
  - Constructor inicializa variables
  - Todas las funciones usan variables en lugar de constantes
  - applyBoundaries() completamente reescrito (repulsión vs wrapping)
- engine.h/cpp: toggleBoidsMode() con parámetro opcional

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 20:09:33 +02:00
9909d4c12d feat: Convertir BOIDS a sistema time-based (independiente de framerate)
- Conversión completa de físicas BOIDS de frame-based a time-based
- Velocidades: ×60 (px/frame → px/s)
- Aceleraciones (Separation, Cohesion): ×3600 (px/frame² → px/s²)
- Steering proporcional (Alignment): ×60
- Límites de velocidad: ×60

Constantes actualizadas en defines.h:
- BOID_SEPARATION_WEIGHT: 1.5 → 5400.0 (aceleración)
- BOID_COHESION_WEIGHT: 0.001 → 3.6 (aceleración)
- BOID_ALIGNMENT_WEIGHT: 1.0 → 60.0 (steering)
- BOID_MAX_SPEED: 2.5 → 150.0 px/s
- BOID_MIN_SPEED: 0.3 → 18.0 px/s
- BOID_MAX_FORCE: 0.05 → 3.0 px/s

Física ahora consistente en 60Hz, 144Hz, 240Hz screens.
Transiciones BOIDS↔PHYSICS preservan velocidad correctamente.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 20:05:49 +02:00
18 changed files with 11888 additions and 176 deletions

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

689
source/app_logo.cpp Normal file
View File

@@ -0,0 +1,689 @@
#include "app_logo.h"
#include <SDL3/SDL_render.h> // for SDL_DestroyTexture, SDL_RenderGeometry, SDL_SetTextureAlphaMod
#include <cmath> // for powf, sinf, cosf
#include <cstdlib> // for free()
#include <iostream> // for std::cout
#include "logo_scaler.h" // for LogoScaler
#include "defines.h" // for APPLOGO_HEIGHT_PERCENT, getResourcesDirectory
// ============================================================================
// Destructor - Liberar las 4 texturas SDL
// ============================================================================
AppLogo::~AppLogo() {
if (logo1_base_texture_) {
SDL_DestroyTexture(logo1_base_texture_);
logo1_base_texture_ = nullptr;
}
if (logo1_native_texture_) {
SDL_DestroyTexture(logo1_native_texture_);
logo1_native_texture_ = nullptr;
}
if (logo2_base_texture_) {
SDL_DestroyTexture(logo2_base_texture_);
logo2_base_texture_ = nullptr;
}
if (logo2_native_texture_) {
SDL_DestroyTexture(logo2_native_texture_);
logo2_native_texture_ = nullptr;
}
}
// ============================================================================
// Inicialización - Pre-escalar logos a 2 resoluciones (base y nativa)
// ============================================================================
bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) {
renderer_ = renderer;
base_screen_width_ = screen_width;
base_screen_height_ = screen_height;
screen_width_ = screen_width;
screen_height_ = screen_height;
std::string resources_dir = getResourcesDirectory();
// ========================================================================
// 1. Detectar resolución nativa del monitor
// ========================================================================
if (!LogoScaler::detectNativeResolution(native_screen_width_, native_screen_height_)) {
std::cout << "No se pudo detectar resolución nativa, usando solo base" << std::endl;
// Fallback: usar resolución base como nativa
native_screen_width_ = screen_width;
native_screen_height_ = screen_height;
}
// ========================================================================
// 2. Calcular alturas finales para ambas resoluciones
// ========================================================================
int logo_base_target_height = static_cast<int>(base_screen_height_ * APPLOGO_HEIGHT_PERCENT);
int logo_native_target_height = static_cast<int>(native_screen_height_ * APPLOGO_HEIGHT_PERCENT);
std::cout << "Pre-escalando logos:" << std::endl;
std::cout << " Base: " << base_screen_width_ << "x" << base_screen_height_
<< " (altura logo: " << logo_base_target_height << "px)" << std::endl;
std::cout << " Nativa: " << native_screen_width_ << "x" << native_screen_height_
<< " (altura logo: " << logo_native_target_height << "px)" << std::endl;
// ========================================================================
// 3. Cargar y escalar LOGO1 (data/logo/logo.png) a 2 versiones
// ========================================================================
std::string logo1_path = resources_dir + "/data/logo/logo.png";
// 3a. Versión BASE de logo1
unsigned char* logo1_base_data = LogoScaler::loadAndScale(
logo1_path,
0, // width calculado automáticamente por aspect ratio
logo_base_target_height,
logo1_base_width_,
logo1_base_height_
);
if (logo1_base_data == nullptr) {
std::cout << "Error: No se pudo escalar logo1 (base)" << std::endl;
return false;
}
logo1_base_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo1_base_data, logo1_base_width_, logo1_base_height_
);
free(logo1_base_data); // Liberar buffer temporal
if (logo1_base_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo1 (base)" << std::endl;
return false;
}
// Habilitar alpha blending
SDL_SetTextureBlendMode(logo1_base_texture_, SDL_BLENDMODE_BLEND);
// 3b. Versión NATIVA de logo1
unsigned char* logo1_native_data = LogoScaler::loadAndScale(
logo1_path,
0, // width calculado automáticamente
logo_native_target_height,
logo1_native_width_,
logo1_native_height_
);
if (logo1_native_data == nullptr) {
std::cout << "Error: No se pudo escalar logo1 (nativa)" << std::endl;
return false;
}
logo1_native_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo1_native_data, logo1_native_width_, logo1_native_height_
);
free(logo1_native_data);
if (logo1_native_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo1 (nativa)" << std::endl;
return false;
}
SDL_SetTextureBlendMode(logo1_native_texture_, SDL_BLENDMODE_BLEND);
// ========================================================================
// 4. Cargar y escalar LOGO2 (data/logo/logo2.png) a 2 versiones
// ========================================================================
std::string logo2_path = resources_dir + "/data/logo/logo2.png";
// 4a. Versión BASE de logo2
unsigned char* logo2_base_data = LogoScaler::loadAndScale(
logo2_path,
0,
logo_base_target_height,
logo2_base_width_,
logo2_base_height_
);
if (logo2_base_data == nullptr) {
std::cout << "Error: No se pudo escalar logo2 (base)" << std::endl;
return false;
}
logo2_base_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo2_base_data, logo2_base_width_, logo2_base_height_
);
free(logo2_base_data);
if (logo2_base_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo2 (base)" << std::endl;
return false;
}
SDL_SetTextureBlendMode(logo2_base_texture_, SDL_BLENDMODE_BLEND);
// 4b. Versión NATIVA de logo2
unsigned char* logo2_native_data = LogoScaler::loadAndScale(
logo2_path,
0,
logo_native_target_height,
logo2_native_width_,
logo2_native_height_
);
if (logo2_native_data == nullptr) {
std::cout << "Error: No se pudo escalar logo2 (nativa)" << std::endl;
return false;
}
logo2_native_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo2_native_data, logo2_native_width_, logo2_native_height_
);
free(logo2_native_data);
if (logo2_native_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo2 (nativa)" << std::endl;
return false;
}
SDL_SetTextureBlendMode(logo2_native_texture_, SDL_BLENDMODE_BLEND);
// ========================================================================
// 5. Inicialmente usar texturas BASE (la resolución de inicio)
// ========================================================================
logo1_current_texture_ = logo1_base_texture_;
logo1_current_width_ = logo1_base_width_;
logo1_current_height_ = logo1_base_height_;
logo2_current_texture_ = logo2_base_texture_;
logo2_current_width_ = logo2_base_width_;
logo2_current_height_ = logo2_base_height_;
std::cout << "Logos pre-escalados exitosamente (4 texturas creadas)" << std::endl;
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;
logo1_alpha_ = 0;
logo2_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;
logo1_alpha_ = 0;
logo2_alpha_ = 0;
// Elegir UNA animación aleatoria (misma para ambos logos, misma entrada y salida)
current_animation_ = getRandomAnimation();
}
break;
case AppLogoState::FADE_IN:
// Fade in: alpha de 0 a 255, con Logo 2 retrasado 0.25s
{
// Calcular progreso de cada logo (Logo 2 con retraso)
float fade_progress_logo1 = timer_ / APPLOGO_FADE_DURATION;
float fade_progress_logo2 = std::max(0.0f, (timer_ - APPLOGO_LOGO2_DELAY) / APPLOGO_FADE_DURATION);
// Verificar si fade in completado (cuando logo2 también termina)
if (fade_progress_logo2 >= 1.0f) {
// Fade in completado para ambos logos
state_ = AppLogoState::VISIBLE;
timer_ = 0.0f;
logo1_alpha_ = 255;
logo2_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 con retraso + easing para suavidad
float eased_prog1 = easeInOutQuad(std::min(1.0f, fade_progress_logo1));
float eased_prog2 = easeInOutQuad(std::min(1.0f, fade_progress_logo2));
logo1_alpha_ = static_cast<int>(eased_prog1 * 255.0f);
logo2_alpha_ = static_cast<int>(eased_prog2 * 255.0f);
// ================================================================
// Aplicar MISMA animación (current_animation_) a ambos logos
// con sus respectivos progresos
// ================================================================
switch (current_animation_) {
case AppLogoAnimationType::ZOOM_ONLY:
logo1_scale_ = 1.2f - (std::min(1.0f, fade_progress_logo1) * 0.2f);
logo1_squash_y_ = 1.0f;
logo1_stretch_x_ = 1.0f;
logo1_rotation_ = 0.0f;
logo2_scale_ = 1.2f - (std::min(1.0f, fade_progress_logo2) * 0.2f);
logo2_squash_y_ = 1.0f;
logo2_stretch_x_ = 1.0f;
logo2_rotation_ = 0.0f;
break;
case AppLogoAnimationType::ELASTIC_STICK:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
float elastic_t1 = easeOutElastic(prog1);
logo1_scale_ = 1.2f - (elastic_t1 * 0.2f);
float squash_t1 = easeOutBack(prog1);
logo1_squash_y_ = 0.6f + (squash_t1 * 0.4f);
logo1_stretch_x_ = 1.0f + (1.0f - logo1_squash_y_) * 0.5f;
logo1_rotation_ = 0.0f;
float prog2 = std::min(1.0f, fade_progress_logo2);
float elastic_t2 = easeOutElastic(prog2);
logo2_scale_ = 1.2f - (elastic_t2 * 0.2f);
float squash_t2 = easeOutBack(prog2);
logo2_squash_y_ = 0.6f + (squash_t2 * 0.4f);
logo2_stretch_x_ = 1.0f + (1.0f - logo2_squash_y_) * 0.5f;
logo2_rotation_ = 0.0f;
}
break;
case AppLogoAnimationType::ROTATE_SPIRAL:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
float ease_t1 = easeInOutQuad(prog1);
logo1_scale_ = 0.3f + (ease_t1 * 0.7f);
logo1_rotation_ = (1.0f - prog1) * 6.28f;
logo1_squash_y_ = 1.0f;
logo1_stretch_x_ = 1.0f;
float prog2 = std::min(1.0f, fade_progress_logo2);
float ease_t2 = easeInOutQuad(prog2);
logo2_scale_ = 0.3f + (ease_t2 * 0.7f);
logo2_rotation_ = (1.0f - prog2) * 6.28f;
logo2_squash_y_ = 1.0f;
logo2_stretch_x_ = 1.0f;
}
break;
case AppLogoAnimationType::BOUNCE_SQUASH:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
float bounce_t1 = easeOutBounce(prog1);
logo1_scale_ = 1.0f;
float squash_amount1 = (1.0f - bounce_t1) * 0.3f;
logo1_squash_y_ = 1.0f - squash_amount1;
logo1_stretch_x_ = 1.0f + squash_amount1 * 0.5f;
logo1_rotation_ = 0.0f;
float prog2 = std::min(1.0f, fade_progress_logo2);
float bounce_t2 = easeOutBounce(prog2);
logo2_scale_ = 1.0f;
float squash_amount2 = (1.0f - bounce_t2) * 0.3f;
logo2_squash_y_ = 1.0f - squash_amount2;
logo2_stretch_x_ = 1.0f + squash_amount2 * 0.5f;
logo2_rotation_ = 0.0f;
}
break;
}
}
}
break;
case AppLogoState::VISIBLE:
// Logo completamente visible, esperando duración
if (timer_ >= APPLOGO_DISPLAY_DURATION) {
state_ = AppLogoState::FADE_OUT;
timer_ = 0.0f;
logo1_alpha_ = 255;
logo2_alpha_ = 255;
// NO elegir nueva animación - reutilizar current_animation_ (simetría entrada/salida)
}
break;
case AppLogoState::FADE_OUT:
// Fade out: alpha de 255 a 0, con Logo 2 retrasado 0.25s (misma animación que entrada)
{
// Calcular progreso de cada logo (Logo 2 con retraso)
float fade_progress_logo1 = timer_ / APPLOGO_FADE_DURATION;
float fade_progress_logo2 = std::max(0.0f, (timer_ - APPLOGO_LOGO2_DELAY) / APPLOGO_FADE_DURATION);
// Verificar si fade out completado (cuando logo2 también termina)
if (fade_progress_logo2 >= 1.0f) {
// Fade out completado, volver a HIDDEN
state_ = AppLogoState::HIDDEN;
timer_ = 0.0f;
logo1_alpha_ = 0;
logo2_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 con retraso + easing para suavidad (255 → 0)
float eased_prog1 = easeInOutQuad(std::min(1.0f, fade_progress_logo1));
float eased_prog2 = easeInOutQuad(std::min(1.0f, fade_progress_logo2));
logo1_alpha_ = static_cast<int>((1.0f - eased_prog1) * 255.0f);
logo2_alpha_ = static_cast<int>((1.0f - eased_prog2) * 255.0f);
// ================================================================
// Aplicar MISMA animación (current_animation_) de forma invertida
// ================================================================
switch (current_animation_) {
case AppLogoAnimationType::ZOOM_ONLY:
logo1_scale_ = 1.0f + (std::min(1.0f, fade_progress_logo1) * 0.2f);
logo1_squash_y_ = 1.0f;
logo1_stretch_x_ = 1.0f;
logo1_rotation_ = 0.0f;
logo2_scale_ = 1.0f + (std::min(1.0f, fade_progress_logo2) * 0.2f);
logo2_squash_y_ = 1.0f;
logo2_stretch_x_ = 1.0f;
logo2_rotation_ = 0.0f;
break;
case AppLogoAnimationType::ELASTIC_STICK:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
logo1_scale_ = 1.0f + (prog1 * prog1 * 0.2f);
logo1_squash_y_ = 1.0f + (prog1 * 0.3f);
logo1_stretch_x_ = 1.0f - (prog1 * 0.2f);
logo1_rotation_ = prog1 * 0.1f;
float prog2 = std::min(1.0f, fade_progress_logo2);
logo2_scale_ = 1.0f + (prog2 * prog2 * 0.2f);
logo2_squash_y_ = 1.0f + (prog2 * 0.3f);
logo2_stretch_x_ = 1.0f - (prog2 * 0.2f);
logo2_rotation_ = prog2 * 0.1f;
}
break;
case AppLogoAnimationType::ROTATE_SPIRAL:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
float ease_t1 = easeInOutQuad(prog1);
logo1_scale_ = 1.0f - (ease_t1 * 0.7f);
logo1_rotation_ = prog1 * 6.28f;
logo1_squash_y_ = 1.0f;
logo1_stretch_x_ = 1.0f;
float prog2 = std::min(1.0f, fade_progress_logo2);
float ease_t2 = easeInOutQuad(prog2);
logo2_scale_ = 1.0f - (ease_t2 * 0.7f);
logo2_rotation_ = prog2 * 6.28f;
logo2_squash_y_ = 1.0f;
logo2_stretch_x_ = 1.0f;
}
break;
case AppLogoAnimationType::BOUNCE_SQUASH:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
if (prog1 < 0.2f) {
float squash_t = prog1 / 0.2f;
logo1_squash_y_ = 1.0f - (squash_t * 0.3f);
logo1_stretch_x_ = 1.0f + (squash_t * 0.2f);
} else {
float jump_t = (prog1 - 0.2f) / 0.8f;
logo1_squash_y_ = 0.7f + (jump_t * 0.5f);
logo1_stretch_x_ = 1.2f - (jump_t * 0.2f);
}
logo1_scale_ = 1.0f + (prog1 * 0.3f);
logo1_rotation_ = 0.0f;
float prog2 = std::min(1.0f, fade_progress_logo2);
if (prog2 < 0.2f) {
float squash_t = prog2 / 0.2f;
logo2_squash_y_ = 1.0f - (squash_t * 0.3f);
logo2_stretch_x_ = 1.0f + (squash_t * 0.2f);
} else {
float jump_t = (prog2 - 0.2f) / 0.8f;
logo2_squash_y_ = 0.7f + (jump_t * 0.5f);
logo2_stretch_x_ = 1.2f - (jump_t * 0.2f);
}
logo2_scale_ = 1.0f + (prog2 * 0.3f);
logo2_rotation_ = 0.0f;
}
break;
}
}
}
break;
}
}
void AppLogo::render() {
// Renderizar si NO está en estado HIDDEN (incluye FADE_IN, VISIBLE, FADE_OUT)
if (state_ != AppLogoState::HIDDEN) {
// Renderizar LOGO1 primero (fondo), luego LOGO2 (encima)
renderWithGeometry(1);
renderWithGeometry(2);
}
}
void AppLogo::updateScreenSize(int screen_width, int screen_height) {
screen_width_ = screen_width;
screen_height_ = screen_height;
// ========================================================================
// Detectar si coincide con resolución nativa o base, cambiar texturas
// ========================================================================
bool is_native = (screen_width == native_screen_width_ && screen_height == native_screen_height_);
if (is_native) {
// Cambiar a texturas nativas (F4 fullscreen)
logo1_current_texture_ = logo1_native_texture_;
logo1_current_width_ = logo1_native_width_;
logo1_current_height_ = logo1_native_height_;
logo2_current_texture_ = logo2_native_texture_;
logo2_current_width_ = logo2_native_width_;
logo2_current_height_ = logo2_native_height_;
std::cout << "AppLogo: Cambiado a texturas NATIVAS" << std::endl;
} else {
// Cambiar a texturas base (ventana redimensionable)
logo1_current_texture_ = logo1_base_texture_;
logo1_current_width_ = logo1_base_width_;
logo1_current_height_ = logo1_base_height_;
logo2_current_texture_ = logo2_base_texture_;
logo2_current_width_ = logo2_base_width_;
logo2_current_height_ = logo2_base_height_;
std::cout << "AppLogo: Cambiado a texturas BASE" << std::endl;
}
// Nota: No es necesario recalcular escalas porque las texturas están pre-escaladas
// al tamaño exacto de pantalla. Solo renderizamos al 100% (o con deformaciones de animación).
}
// ============================================================================
// 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 (para todos los logos, con deformaciones)
// ============================================================================
void AppLogo::renderWithGeometry(int logo_index) {
if (!renderer_) return;
// Seleccionar variables según el logo_index (1 = logo1, 2 = logo2)
SDL_Texture* texture;
int base_width, base_height;
float scale, squash_y, stretch_x, rotation;
if (logo_index == 1) {
if (!logo1_current_texture_) return;
texture = logo1_current_texture_;
base_width = logo1_current_width_;
base_height = logo1_current_height_;
scale = logo1_scale_;
squash_y = logo1_squash_y_;
stretch_x = logo1_stretch_x_;
rotation = logo1_rotation_;
} else if (logo_index == 2) {
if (!logo2_current_texture_) return;
texture = logo2_current_texture_;
base_width = logo2_current_width_;
base_height = logo2_current_height_;
scale = logo2_scale_;
squash_y = logo2_squash_y_;
stretch_x = logo2_stretch_x_;
rotation = logo2_rotation_;
} else {
return; // Índice inválido
}
// Aplicar alpha específico de cada logo (con retraso para logo2)
int alpha = (logo_index == 1) ? logo1_alpha_ : logo2_alpha_;
SDL_SetTextureAlphaMod(texture, static_cast<Uint8>(alpha));
// Calcular tamaño con escala y deformaciones aplicadas
// (base_width y base_height ya están pre-escalados al tamaño correcto de pantalla)
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, vertices, 4, indices, 6);
}

117
source/app_logo.h Normal file
View File

@@ -0,0 +1,117 @@
#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(); // Necesario para liberar las 4 texturas SDL
// 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 pre-escaladas (4 texturas: 2 logos × 2 resoluciones)
// ====================================================================
SDL_Texture* logo1_base_texture_ = nullptr; // Logo1 para resolución base
SDL_Texture* logo1_native_texture_ = nullptr; // Logo1 para resolución nativa (F4)
SDL_Texture* logo2_base_texture_ = nullptr; // Logo2 para resolución base
SDL_Texture* logo2_native_texture_ = nullptr; // Logo2 para resolución nativa (F4)
// Dimensiones pre-calculadas para cada textura
int logo1_base_width_ = 0, logo1_base_height_ = 0;
int logo1_native_width_ = 0, logo1_native_height_ = 0;
int logo2_base_width_ = 0, logo2_base_height_ = 0;
int logo2_native_width_ = 0, logo2_native_height_ = 0;
// Texturas actualmente en uso (apuntan a base o native según resolución)
SDL_Texture* logo1_current_texture_ = nullptr;
SDL_Texture* logo2_current_texture_ = nullptr;
int logo1_current_width_ = 0, logo1_current_height_ = 0;
int logo2_current_width_ = 0, logo2_current_height_ = 0;
// Resoluciones conocidas
int base_screen_width_ = 0, base_screen_height_ = 0; // Resolución inicial
int native_screen_width_ = 0, native_screen_height_ = 0; // Resolución nativa (F4)
// ====================================================================
// 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
// Alpha INDEPENDIENTE para cada logo (Logo 2 con retraso)
int logo1_alpha_ = 0; // Alpha de Logo 1 (0-255)
int logo2_alpha_ = 0; // Alpha de Logo 2 (0-255, con retraso)
// Animación COMPARTIDA (misma para ambos logos, misma entrada y salida)
AppLogoAnimationType current_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();
};

View File

@@ -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);
} }
} }

View File

@@ -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
}; };

View File

@@ -288,17 +288,32 @@ 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%
constexpr float APPLOGO_LOGO2_DELAY = 0.25f; // Retraso de Logo 2 respecto a Logo 1 (segundos)
// 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)

View File

@@ -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,6 +342,11 @@ 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 ===
@@ -343,7 +356,7 @@ void Engine::handleGravityToggle() {
// Si estamos en modo boids, salir a modo física CON GRAVEDAD OFF // 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" // Según RULES.md: "BOIDS a PHYSICS: Pulsando la tecla G: Gravedad OFF"
if (current_mode_ == SimulationMode::BOIDS) { if (current_mode_ == SimulationMode::BOIDS) {
toggleBoidsMode(); // Cambiar a PHYSICS (preserva inercia, gravedad ya está OFF desde activateBoids) toggleBoidsMode(false); // Cambiar a PHYSICS sin activar gravedad (preserva inercia)
// NO llamar a forceBallsGravityOff() porque aplica impulsos que destruyen la inercia de BOIDS // 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 // La gravedad ya está desactivada por BoidManager::activateBoids() y se mantiene al salir
showNotificationForAction("Modo Física - Gravedad Off"); showNotificationForAction("Modo Física - Gravedad Off");
@@ -364,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);
} }
@@ -435,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) {
@@ -522,6 +536,11 @@ void Engine::changeScenario(int scenario_id, const char* notification_text) {
} }
} }
// Si estamos en modo BOIDS, desactivar gravedad (modo BOIDS = gravedad OFF siempre)
if (current_mode_ == SimulationMode::BOIDS) {
scene_manager_->forceBallsGravityOff();
}
showNotificationForAction(notification_text); showNotificationForAction(notification_text);
} }
@@ -710,6 +729,11 @@ void Engine::render() {
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_);
} }
@@ -791,6 +815,11 @@ void Engine::toggleRealFullscreen() {
// 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 // Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones
if (current_mode_ == SimulationMode::SHAPE) { if (current_mode_ == SimulationMode::SHAPE) {
generateShape(); // Regenerar figura con nuevas dimensiones de pantalla generateShape(); // Regenerar figura con nuevas dimensiones de pantalla
@@ -824,6 +853,11 @@ void Engine::toggleRealFullscreen() {
scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_); scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
scene_manager_->changeScenario(scene_manager_->getCurrentScenario(), current_mode_); 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 // Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones
if (current_mode_ == SimulationMode::SHAPE) { if (current_mode_ == SimulationMode::SHAPE) {
generateShape(); // Regenerar figura con nuevas dimensiones de pantalla generateShape(); // Regenerar figura con nuevas dimensiones de pantalla
@@ -1359,6 +1393,18 @@ void Engine::executeDemoAction(bool is_lite) {
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, current_mode_); 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;
} }
@@ -1573,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());

View File

@@ -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);
@@ -105,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;
@@ -160,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)

View File

@@ -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;
}
}

10630
source/external/stb_image_resize2.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}

View File

@@ -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_; }
}; };

144
source/logo_scaler.cpp Normal file
View File

@@ -0,0 +1,144 @@
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "logo_scaler.h"
#include <SDL3/SDL_error.h> // Para SDL_GetError
#include <SDL3/SDL_log.h> // Para SDL_Log
#include <SDL3/SDL_pixels.h> // Para SDL_PixelFormat
#include <SDL3/SDL_render.h> // Para SDL_CreateTexture
#include <SDL3/SDL_surface.h> // Para SDL_CreateSurfaceFrom
#include <SDL3/SDL_video.h> // Para SDL_GetDisplays
#include <cstdlib> // Para free()
#include <iostream> // Para std::cout
#include "external/stb_image.h" // Para stbi_load, stbi_image_free
#include "external/stb_image_resize2.h" // Para stbir_resize_uint8_srgb
// ============================================================================
// Detectar resolución nativa del monitor principal
// ============================================================================
bool LogoScaler::detectNativeResolution(int& native_width, int& native_height) {
int num_displays = 0;
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
if (displays == nullptr || num_displays == 0) {
SDL_Log("Error al obtener displays: %s", SDL_GetError());
return false;
}
// Obtener resolución del display principal (displays[0])
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
if (dm == nullptr) {
SDL_Log("Error al obtener modo del display: %s", SDL_GetError());
SDL_free(displays);
return false;
}
native_width = dm->w;
native_height = dm->h;
SDL_free(displays);
std::cout << "Resolución nativa detectada: " << native_width << "x" << native_height << std::endl;
return true;
}
// ============================================================================
// Cargar PNG y escalar al tamaño especificado
// ============================================================================
unsigned char* LogoScaler::loadAndScale(const std::string& path,
int target_width, int target_height,
int& out_width, int& out_height) {
// 1. Cargar imagen original con stb_image
int orig_width, orig_height, orig_channels;
unsigned char* orig_data = stbi_load(path.c_str(), &orig_width, &orig_height, &orig_channels, STBI_rgb_alpha);
if (orig_data == nullptr) {
SDL_Log("Error al cargar imagen %s: %s", path.c_str(), stbi_failure_reason());
return nullptr;
}
std::cout << "Imagen cargada: " << path << " (" << orig_width << "x" << orig_height << ")" << std::endl;
// 2. Calcular tamaño final manteniendo aspect ratio
// El alto está fijado por target_height (APPLOGO_HEIGHT_PERCENT)
// Calcular ancho proporcional al aspect ratio original
float aspect_ratio = static_cast<float>(orig_width) / static_cast<float>(orig_height);
out_width = static_cast<int>(target_height * aspect_ratio);
out_height = target_height;
std::cout << " Escalando a: " << out_width << "x" << out_height << std::endl;
// 3. Alocar buffer para imagen escalada (RGBA = 4 bytes por píxel)
unsigned char* scaled_data = static_cast<unsigned char*>(malloc(out_width * out_height * 4));
if (scaled_data == nullptr) {
SDL_Log("Error al alocar memoria para imagen escalada");
stbi_image_free(orig_data);
return nullptr;
}
// 4. Escalar con stb_image_resize2 (algoritmo Mitchell, espacio sRGB)
// La función devuelve el puntero de salida, o nullptr si falla
unsigned char* result = stbir_resize_uint8_srgb(
orig_data, orig_width, orig_height, 0, // Input
scaled_data, out_width, out_height, 0, // Output
STBIR_RGBA // Formato píxel
);
// Liberar imagen original (ya no la necesitamos)
stbi_image_free(orig_data);
if (result == nullptr) {
SDL_Log("Error al escalar imagen");
free(scaled_data);
return nullptr;
}
std::cout << " Escalado completado correctamente" << std::endl;
return scaled_data;
}
// ============================================================================
// Crear textura SDL desde buffer RGBA
// ============================================================================
SDL_Texture* LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer,
unsigned char* data,
int width, int height) {
if (renderer == nullptr || data == nullptr || width <= 0 || height <= 0) {
SDL_Log("Parámetros inválidos para createTextureFromBuffer");
return nullptr;
}
// 1. Crear surface SDL desde buffer RGBA
int pitch = width * 4; // 4 bytes por píxel (RGBA)
SDL_PixelFormat pixel_format = SDL_PIXELFORMAT_RGBA32;
SDL_Surface* surface = SDL_CreateSurfaceFrom(
width, height,
pixel_format,
data,
pitch
);
if (surface == nullptr) {
SDL_Log("Error al crear surface: %s", SDL_GetError());
return nullptr;
}
// 2. Crear textura desde surface
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
if (texture == nullptr) {
SDL_Log("Error al crear textura: %s", SDL_GetError());
SDL_DestroySurface(surface);
return nullptr;
}
// 3. Liberar surface (la textura ya tiene los datos)
SDL_DestroySurface(surface);
return texture;
}

61
source/logo_scaler.h Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include <SDL3/SDL_render.h> // Para SDL_Renderer, SDL_Texture
#include <SDL3/SDL_video.h> // Para SDL_DisplayID, SDL_GetDisplays
#include <string> // Para std::string
/**
* @brief Helper class para pre-escalar logos usando stb_image_resize2
*
* Proporciona funciones para:
* - Detectar resolución nativa del monitor
* - Cargar PNG y escalar a tamaño específico con algoritmos de alta calidad
* - Crear texturas SDL desde buffers escalados
*
* Usado por AppLogo para pre-generar versiones de logos al tamaño exacto
* de pantalla, eliminando el escalado dinámico de SDL y mejorando calidad visual.
*/
class LogoScaler {
public:
/**
* @brief Detecta la resolución nativa del monitor principal
*
* @param native_width [out] Ancho nativo del display en píxeles
* @param native_height [out] Alto nativo del display en píxeles
* @return true si se pudo detectar, false si hubo error
*/
static bool detectNativeResolution(int& native_width, int& native_height);
/**
* @brief Carga un PNG y lo escala al tamaño especificado
*
* Usa stb_image para cargar y stb_image_resize2 para escalar con
* algoritmo Mitchell (balance calidad/velocidad) en espacio sRGB.
*
* @param path Ruta al archivo PNG (ej: "data/logo/logo.png")
* @param target_width Ancho destino en píxeles
* @param target_height Alto destino en píxeles
* @param out_width [out] Ancho real de la imagen escalada
* @param out_height [out] Alto real de la imagen escalada
* @return Buffer RGBA (4 bytes por píxel) o nullptr si falla
* IMPORTANTE: El caller debe liberar con free() cuando termine
*/
static unsigned char* loadAndScale(const std::string& path,
int target_width, int target_height,
int& out_width, int& out_height);
/**
* @brief Crea una textura SDL desde un buffer RGBA
*
* @param renderer Renderizador SDL activo
* @param data Buffer RGBA (4 bytes por píxel)
* @param width Ancho del buffer en píxeles
* @param height Alto del buffer en píxeles
* @return Textura SDL creada o nullptr si falla
* IMPORTANTE: El caller debe destruir con SDL_DestroyTexture()
*/
static SDL_Texture* createTextureFromBuffer(SDL_Renderer* renderer,
unsigned char* data,
int width, int height);
};

View File

@@ -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 {

View File

@@ -88,9 +88,6 @@ HelpOverlay::~HelpOverlay() {
void HelpOverlay::toggle() { void HelpOverlay::toggle() {
visible_ = !visible_; visible_ = !visible_;
SDL_Log("HelpOverlay::toggle() - visible=%s, box_pos=(%d,%d), box_size=%dx%d, physical=%dx%d",
visible_ ? "TRUE" : "FALSE", box_x_, box_y_, box_width_, box_height_,
physical_width_, physical_height_);
} }
void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height, int font_size) { void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height, int font_size) {
@@ -103,12 +100,7 @@ void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, in
text_renderer_ = new TextRenderer(); text_renderer_ = new TextRenderer();
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", font_size, true); text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", font_size, true);
SDL_Log("HelpOverlay::initialize() - physical=%dx%d, font_size=%d", physical_width, physical_height, font_size);
calculateBoxDimensions(); calculateBoxDimensions();
SDL_Log("HelpOverlay::initialize() - AFTER calculateBoxDimensions: box_pos=(%d,%d), box_size=%dx%d",
box_x_, box_y_, box_width_, box_height_);
} }
void HelpOverlay::updatePhysicalWindowSize(int physical_width, int physical_height) { void HelpOverlay::updatePhysicalWindowSize(int physical_width, int physical_height) {
@@ -135,11 +127,6 @@ void HelpOverlay::reinitializeFontSize(int new_font_size) {
} }
void HelpOverlay::updateAll(int font_size, int physical_width, int physical_height) { void HelpOverlay::updateAll(int font_size, int physical_width, int physical_height) {
SDL_Log("HelpOverlay::updateAll() - INPUT: font_size=%d, physical=%dx%d",
font_size, physical_width, physical_height);
SDL_Log("HelpOverlay::updateAll() - BEFORE: box_pos=(%d,%d), box_size=%dx%d",
box_x_, box_y_, box_width_, box_height_);
// Actualizar dimensiones físicas PRIMERO // Actualizar dimensiones físicas PRIMERO
physical_width_ = physical_width; physical_width_ = physical_width;
physical_height_ = physical_height; physical_height_ = physical_height;
@@ -154,9 +141,6 @@ void HelpOverlay::updateAll(int font_size, int physical_width, int physical_heig
// Marcar textura para regeneración completa // Marcar textura para regeneración completa
texture_needs_rebuild_ = true; texture_needs_rebuild_ = true;
SDL_Log("HelpOverlay::updateAll() - AFTER: box_pos=(%d,%d), box_size=%dx%d",
box_x_, box_y_, box_width_, box_height_);
} }
void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) { void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
@@ -246,15 +230,10 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
} }
void HelpOverlay::calculateBoxDimensions() { void HelpOverlay::calculateBoxDimensions() {
SDL_Log("HelpOverlay::calculateBoxDimensions() START - physical=%dx%d", physical_width_, physical_height_);
// Calcular dimensiones necesarias según el texto // Calcular dimensiones necesarias según el texto
int text_width, text_height; int text_width, text_height;
calculateTextDimensions(text_width, text_height); calculateTextDimensions(text_width, text_height);
SDL_Log("HelpOverlay::calculateBoxDimensions() - text_width=%d, text_height=%d, col1_width=%d, col2_width=%d",
text_width, text_height, column1_width_, column2_width_);
// Usar directamente el ancho y altura calculados según el contenido // Usar directamente el ancho y altura calculados según el contenido
box_width_ = text_width; box_width_ = text_width;
@@ -265,17 +244,11 @@ void HelpOverlay::calculateBoxDimensions() {
// Centrar en pantalla // Centrar en pantalla
box_x_ = (physical_width_ - box_width_) / 2; box_x_ = (physical_width_ - box_width_) / 2;
box_y_ = (physical_height_ - box_height_) / 2; box_y_ = (physical_height_ - box_height_) / 2;
SDL_Log("HelpOverlay::calculateBoxDimensions() END - box_pos=(%d,%d), box_size=%dx%d, max_height=%d",
box_x_, box_y_, box_width_, box_height_, max_height);
} }
void HelpOverlay::rebuildCachedTexture() { void HelpOverlay::rebuildCachedTexture() {
if (!renderer_ || !theme_mgr_ || !text_renderer_) return; if (!renderer_ || !theme_mgr_ || !text_renderer_) return;
SDL_Log("HelpOverlay::rebuildCachedTexture() - Regenerando textura: box_size=%dx%d, box_pos=(%d,%d)",
box_width_, box_height_, box_x_, box_y_);
// Destruir textura anterior si existe // Destruir textura anterior si existe
if (cached_texture_) { if (cached_texture_) {
SDL_DestroyTexture(cached_texture_); SDL_DestroyTexture(cached_texture_);
@@ -471,9 +444,6 @@ void HelpOverlay::render(SDL_Renderer* renderer) {
int centered_x = viewport.x + (viewport.w - box_width_) / 2; int centered_x = viewport.x + (viewport.w - box_width_) / 2;
int centered_y = viewport.y + (viewport.h - box_height_) / 2; int centered_y = viewport.y + (viewport.h - box_height_) / 2;
SDL_Log("HelpOverlay::render() - viewport=(%d,%d,%dx%d), centered_pos=(%d,%d), box_size=%dx%d",
viewport.x, viewport.y, viewport.w, viewport.h, centered_x, centered_y, box_width_, box_height_);
// Renderizar la textura cacheada centrada en el viewport // Renderizar la textura cacheada centrada en el viewport
SDL_FRect dest_rect; SDL_FRect dest_rect;
dest_rect.x = static_cast<float>(centered_x); dest_rect.x = static_cast<float>(centered_x);

View File

@@ -277,6 +277,27 @@ void UIManager::renderDebugHUD(const Engine* engine,
text_renderer_debug_->printAbsolute(margin, left_y, simmode_text.c_str(), {0, 255, 255, 255}); // Cian text_renderer_debug_->printAbsolute(margin, left_y, simmode_text.c_str(), {0, 255, 255, 255}); // Cian
left_y += line_height; 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 // V-Sync
text_renderer_debug_->printAbsolute(margin, left_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian text_renderer_debug_->printAbsolute(margin, left_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian
left_y += line_height; left_y += line_height;