6 Commits

Author SHA1 Message Date
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
15 changed files with 978 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

635
source/app_logo.cpp Normal file
View 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
View 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();
};

View File

@@ -17,7 +17,18 @@ BoidManager::BoidManager()
, screen_width_(0)
, screen_height_(0)
, boids_active_(false)
, spatial_grid_(800, 600, BOID_GRID_CELL_SIZE) { // Tamaño por defecto, se actualiza en initialize()
, 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() {
@@ -57,9 +68,9 @@ void BoidManager::activateBoids() {
float vx, vy;
ball->getVelocity(vx, vy);
if (vx == 0.0f && vy == 0.0f) {
// Velocidad aleatoria entre -1 y 1
vx = (rand() % 200 - 100) / 100.0f;
vy = (rand() % 200 - 100) / 100.0f;
// Velocidad aleatoria entre -60 y +60 px/s (time-based)
vx = ((rand() % 200 - 100) / 100.0f) * 60.0f;
vy = ((rand() % 200 - 100) / 100.0f) * 60.0f;
ball->setVelocity(vx, vy);
}
}
@@ -118,14 +129,14 @@ void BoidManager::update(float delta_time) {
limitSpeed(ball.get());
}
// Actualizar posiciones con velocidades resultantes
// Actualizar posiciones con velocidades resultantes (time-based)
for (auto& ball : balls) {
float vx, vy;
ball->getVelocity(vx, vy);
SDL_FRect pos = ball->getPosition();
pos.x += vx;
pos.y += vy;
pos.x += vx * delta_time; // time-based
pos.y += vy * delta_time;
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;
// 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) {
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 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)
// 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_y += (dy / distance) * separation_strength;
count++;
@@ -177,8 +188,8 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
// Aplicar fuerza de separación
float vx, vy;
boid->getVelocity(vx, vy);
vx += steer_x * BOID_SEPARATION_WEIGHT * delta_time;
vy += steer_y * BOID_SEPARATION_WEIGHT * delta_time;
vx += steer_x * separation_weight_ * delta_time;
vy += steer_y * separation_weight_ * delta_time;
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;
// 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) {
if (other == boid) continue;
@@ -207,7 +218,7 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
float dy = center_y - other_y;
float distance = std::sqrt(dx * dx + dy * dy);
if (distance < BOID_ALIGNMENT_RADIUS) {
if (distance < alignment_radius_) {
float other_vx, other_vy;
other->getVelocity(other_vx, other_vy);
avg_vx += other_vx;
@@ -224,14 +235,14 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
// Steering hacia la velocidad promedio
float vx, vy;
boid->getVelocity(vx, vy);
float steer_x = (avg_vx - vx) * BOID_ALIGNMENT_WEIGHT * delta_time;
float steer_y = (avg_vy - vy) * BOID_ALIGNMENT_WEIGHT * delta_time;
float steer_x = (avg_vx - vx) * alignment_weight_ * delta_time;
float steer_y = (avg_vy - vy) * alignment_weight_ * delta_time;
// Limitar fuerza máxima de steering
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
if (steer_mag > BOID_MAX_FORCE) {
steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE;
steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE;
if (steer_mag > max_force_) {
steer_x = (steer_x / steer_mag) * max_force_;
steer_y = (steer_y / steer_mag) * max_force_;
}
vx += steer_x;
@@ -251,7 +262,7 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
float center_y = pos.y + pos.h / 2.0f;
// FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n))
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, BOID_COHESION_RADIUS);
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, cohesion_radius_);
for (Ball* other : neighbors) {
if (other == boid) continue;
@@ -264,7 +275,7 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
float dy = center_y - other_y;
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_y += other_y;
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)
if (distance_to_center > 0.1f) {
// Normalizar vector dirección (fuerza independiente de distancia)
float steer_x = (dx_to_center / distance_to_center) * BOID_COHESION_WEIGHT * delta_time;
float steer_y = (dy_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) * cohesion_weight_ * delta_time;
// Limitar fuerza máxima de steering
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
if (steer_mag > BOID_MAX_FORCE) {
steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE;
steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE;
if (steer_mag > max_force_) {
steer_x = (steer_x / steer_mag) * max_force_;
steer_y = (steer_y / steer_mag) * max_force_;
}
float vx, vy;
@@ -304,32 +315,69 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
}
void BoidManager::applyBoundaries(Ball* boid) {
// Mantener boids dentro de los límites de la pantalla
// Comportamiento "wrapping" (teletransporte al otro lado)
// NUEVA IMPLEMENTACIÓN: Bordes como obstáculos (repulsión en lugar de wrapping)
// Cuando un boid se acerca a un borde, se aplica una fuerza alejándolo
SDL_FRect pos = boid->getPosition();
float center_x = pos.x + pos.w / 2.0f;
float center_y = pos.y + pos.h / 2.0f;
bool wrapped = false;
float steer_x = 0.0f;
float steer_y = 0.0f;
if (center_x < 0) {
pos.x = screen_width_ - pos.w / 2.0f;
wrapped = true;
} else if (center_x > screen_width_) {
pos.x = -pos.w / 2.0f;
wrapped = true;
// Borde izquierdo (x < boundary_margin_)
if (center_x < boundary_margin_) {
float distance = center_x; // Distancia al borde (0 = colisión)
if (distance < boundary_margin_) {
// Fuerza proporcional a cercanía: 0% en margen, 100% en colisión
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
steer_x += repulsion_strength; // Empujar hacia la derecha
}
}
if (center_y < 0) {
pos.y = screen_height_ - pos.h / 2.0f;
wrapped = true;
} else if (center_y > screen_height_) {
pos.y = -pos.h / 2.0f;
wrapped = true;
// Borde derecho (x > screen_width_ - boundary_margin_)
if (center_x > screen_width_ - boundary_margin_) {
float distance = screen_width_ - center_x;
if (distance < boundary_margin_) {
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
steer_x -= repulsion_strength; // Empujar hacia la izquierda
}
}
if (wrapped) {
boid->setPosition(pos.x, pos.y);
// Borde superior (y < boundary_margin_)
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);
// Limitar velocidad máxima
if (speed > BOID_MAX_SPEED) {
vx = (vx / speed) * BOID_MAX_SPEED;
vy = (vy / speed) * BOID_MAX_SPEED;
if (speed > max_speed_) {
vx = (vx / speed) * max_speed_;
vy = (vy / speed) * max_speed_;
boid->setVelocity(vx, vy);
}
// FASE 1.2: Aplicar velocidad mínima (evitar boids estáticos)
if (speed > 0.0f && speed < BOID_MIN_SPEED) {
vx = (vx / speed) * BOID_MIN_SPEED;
vy = (vy / speed) * BOID_MIN_SPEED;
if (speed > 0.0f && speed < min_speed_) {
vx = (vx / speed) * min_speed_;
vy = (vy / speed) * min_speed_;
boid->setVelocity(vx, vy);
}
}

View File

@@ -103,10 +103,24 @@ class BoidManager {
// FASE 2: Grid reutilizable para búsquedas de vecinos
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
void applySeparation(Ball* boid, float delta_time);
void applyAlignment(Ball* boid, float delta_time);
void applyCohesion(Ball* boid, float delta_time);
void applyBoundaries(Ball* boid); // Mantener boids dentro de pantalla
void applyBoundaries(Ball* boid); // Repulsión de bordes (ya no wrapping)
void limitSpeed(Ball* boid); // Limitar velocidad máxima
};

View File

@@ -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 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)
// 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_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_SEPARATION_WEIGHT = 1.5f; // Peso de separación
constexpr float BOID_ALIGNMENT_WEIGHT = 1.0f; // Peso de alineación
constexpr float BOID_COHESION_WEIGHT = 0.001f; // Peso de cohesión (MICRO - 1000x menor por falta de normalización)
constexpr float BOID_MAX_SPEED = 2.5f; // Velocidad máxima (píxeles/frame - REDUCIDA)
constexpr float BOID_MAX_FORCE = 0.05f; // Fuerza máxima de steering (REDUCIDA para evitar aceleración excesiva)
constexpr float BOID_MIN_SPEED = 0.3f; // Velocidad mínima (evita boids estáticos)
constexpr float BOID_SEPARATION_WEIGHT = 5400.0f; // Aceleración de separación (px/s²) [era 1.5 × 3600]
constexpr float BOID_ALIGNMENT_WEIGHT = 60.0f; // Steering de alineación (proporcional) [era 1.0 × 60]
constexpr float BOID_COHESION_WEIGHT = 3.6f; // Aceleración de cohesión (px/s²) [era 0.001 × 3600]
constexpr float BOID_MAX_SPEED = 150.0f; // Velocidad máxima (px/s) [era 2.5 × 60]
constexpr float BOID_MAX_FORCE = 3.0f; // Fuerza máxima de steering (px/s) [era 0.05 × 60]
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)
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_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(),
current_screen_width_, current_screen_height_);
// Inicializar AppLogo (logo periódico en pantalla)
app_logo_ = std::make_unique<AppLogo>();
if (!app_logo_->initialize(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;
@@ -334,6 +342,11 @@ void Engine::update() {
// Actualizar transiciones de temas (delegado a ThemeManager)
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 ===
@@ -343,7 +356,7 @@ void Engine::handleGravityToggle() {
// 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) {
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
// La gravedad ya está desactivada por BoidManager::activateBoids() y se mantiene al salir
showNotificationForAction("Modo Física - Gravedad Off");
@@ -364,18 +377,19 @@ void Engine::handleGravityToggle() {
}
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) {
toggleBoidsMode(); // Esto cambia a PHYSICS y activa gravedad
// Continuar para aplicar la dirección de gravedad
current_mode_ = SimulationMode::PHYSICS;
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
if (current_mode_ == SimulationMode::SHAPE) {
else if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeModeInternal(); // Desactivar figura (activa gravedad automáticamente)
} else {
scene_manager_->enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
}
scene_manager_->changeGravityDirection(direction);
showNotificationForAction(notification_text);
}
@@ -435,11 +449,11 @@ void Engine::toggleDepthZoom() {
}
// Boids (comportamiento de enjambre)
void Engine::toggleBoidsMode() {
void Engine::toggleBoidsMode(bool force_gravity_on) {
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;
boid_manager_->deactivateBoids();
boid_manager_->deactivateBoids(force_gravity_on); // Pasar parámetro para control preciso
} else {
// Entrar al modo boids (desde PHYSICS o 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);
}
@@ -710,6 +729,11 @@ void Engine::render() {
active_shape_.get(), shape_convergence_,
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_);
}
@@ -791,6 +815,11 @@ void Engine::toggleRealFullscreen() {
// Actualizar tamaño de pantalla para boids (wrapping boundaries)
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
@@ -824,6 +853,11 @@ void Engine::toggleRealFullscreen() {
scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
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
@@ -1359,6 +1393,18 @@ void Engine::executeDemoAction(bool is_lite) {
int valid_scenarios[] = {1, 2, 3, 4, 5};
int new_scenario = valid_scenarios[rand() % 5];
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;
}
@@ -1573,6 +1619,15 @@ void Engine::executeExitLogoMode() {
clampShapeScale();
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)
if (active_shape_) {
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());

View File

@@ -10,6 +10,7 @@
#include <string> // for string
#include <vector> // for vector
#include "app_logo.h" // for AppLogo
#include "ball.h" // for Ball
#include "boids_mgr/boid_manager.h" // for BoidManager
#include "defines.h" // for GravityDirection, ColorTheme, ShapeType
@@ -49,7 +50,7 @@ class Engine {
void toggleDepthZoom();
// Boids (comportamiento de enjambre)
void toggleBoidsMode();
void toggleBoidsMode(bool force_gravity_on = true);
// Temas de colores
void cycleTheme(bool forward);
@@ -105,6 +106,7 @@ class Engine {
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<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
SDL_Window* window_ = nullptr;
@@ -160,7 +162,6 @@ class Engine {
// Sistema de Modo DEMO (auto-play) y LOGO
// NOTA: Engine mantiene estado de implementación para callbacks performLogoAction()
// 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_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;
}
}

View File

@@ -128,6 +128,9 @@ bool Texture::loadFromFile(const std::string &file_path) {
// Configurar filtro nearest neighbor para píxel perfect
SDL_SetTextureScaleMode(new_texture, SDL_SCALEMODE_NEAREST);
// Habilitar alpha blending para transparencias
SDL_SetTextureBlendMode(new_texture, SDL_BLENDMODE_BLEND);
}
// Destruye la superficie cargada
@@ -169,3 +172,17 @@ int Texture::getHeight() {
void Texture::setColor(int r, int g, int 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
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
SDL_Texture *getSDLTexture() const { return texture_; }
};

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%)
if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO) {
/*
float logo_convergence_threshold = LOGO_CONVERGENCE_MIN +
(rand() % 1000) / 1000.0f * (LOGO_CONVERGENCE_MAX - LOGO_CONVERGENCE_MIN);
*/
shape_convergence_ = 0.0f; // Reset convergencia al entrar
}
} else {

View File

@@ -88,9 +88,6 @@ HelpOverlay::~HelpOverlay() {
void HelpOverlay::toggle() {
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) {
@@ -103,12 +100,7 @@ void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, in
text_renderer_ = new TextRenderer();
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();
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) {
@@ -135,11 +127,6 @@ void HelpOverlay::reinitializeFontSize(int new_font_size) {
}
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
physical_width_ = physical_width;
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
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) {
@@ -246,15 +230,10 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
}
void HelpOverlay::calculateBoxDimensions() {
SDL_Log("HelpOverlay::calculateBoxDimensions() START - physical=%dx%d", physical_width_, physical_height_);
// Calcular dimensiones necesarias según el texto
int 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
box_width_ = text_width;
@@ -265,17 +244,11 @@ void HelpOverlay::calculateBoxDimensions() {
// Centrar en pantalla
box_x_ = (physical_width_ - box_width_) / 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() {
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
if (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_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
SDL_FRect dest_rect;
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
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;