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>
This commit is contained in:
2025-10-18 10:01:32 +02:00
parent 8d608357b4
commit c91cb1ca56
5 changed files with 454 additions and 152 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
data/logo/logo2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

@@ -11,44 +11,65 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
screen_width_ = screen_width; screen_width_ = screen_width;
screen_height_ = screen_height; screen_height_ = screen_height;
// Cargar textura del logo desde data/logo/logo.png
std::string resources_dir = getResourcesDirectory(); std::string resources_dir = getResourcesDirectory();
std::string logo_path = resources_dir + "/data/logo/logo.png";
logo_texture_ = std::make_shared<Texture>(renderer, logo_path); // ========================================================================
if (logo_texture_->getWidth() == 0 || logo_texture_->getHeight() == 0) { // Cargar LOGO1 desde data/logo/logo.png
// Error al cargar textura // ========================================================================
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; return false;
} }
// Configurar filtrado LINEAR para suavizado (mejor para logos escalados) // Configurar filtrado LINEAR para suavizado
logo_texture_->setScaleMode(SDL_SCALEMODE_LINEAR); logo1_texture_->setScaleMode(SDL_SCALEMODE_LINEAR);
// Crear sprite con la textura // Crear sprite con la textura
logo_sprite_ = std::make_unique<Sprite>(logo_texture_); logo1_sprite_ = std::make_unique<Sprite>(logo1_texture_);
// IMPORTANTE: Configurar el clip para que use toda la textura // Configurar el clip para que use toda la textura
float logo_width = static_cast<float>(logo_texture_->getWidth()); float logo1_width = static_cast<float>(logo1_texture_->getWidth());
float logo_height = static_cast<float>(logo_texture_->getHeight()); float logo1_height = static_cast<float>(logo1_texture_->getHeight());
logo_sprite_->setClip({0.0f, 0.0f, logo_width, logo_height}); logo1_sprite_->setClip({0.0f, 0.0f, logo1_width, logo1_height});
// Calcular factor de escala para que el logo ocupe 1/4 de la pantalla (un cuadrante) // ========================================================================
// El logo debe caber en width/2 x height/2 // Cargar LOGO2 desde data/logo/logo2.png
float quadrant_width = screen_width_ / 2.0f; // ========================================================================
float quadrant_height = screen_height_ / 2.0f; 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;
}
float scale_x = quadrant_width / logo_width; // Configurar filtrado LINEAR para suavizado
float scale_y = quadrant_height / logo_height; logo2_texture_->setScaleMode(SDL_SCALEMODE_LINEAR);
float scale = (scale_x < scale_y) ? scale_x : scale_y;
// Calcular tamaño base (guardarlo para animaciones de zoom) // Crear sprite con la textura
base_width_ = logo_width * scale; logo2_sprite_ = std::make_unique<Sprite>(logo2_texture_);
base_height_ = logo_height * scale;
// Aplicar escala inicial // Configurar el clip para que use toda la textura
logo_sprite_->setSize(base_width_, base_height_); 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});
// Posicionar logo en el centro del cuadrante inferior derecho // ========================================================================
// 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(); updateLogoPosition();
return true; return true;
@@ -73,11 +94,14 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
state_ = AppLogoState::FADE_IN; state_ = AppLogoState::FADE_IN;
timer_ = 0.0f; timer_ = 0.0f;
current_alpha_ = 0; current_alpha_ = 0;
// Elegir animaciones de entrada aleatorias (independientes para cada logo)
logo1_entry_animation_ = getRandomAnimation();
logo2_entry_animation_ = getRandomAnimation();
} }
break; break;
case AppLogoState::FADE_IN: case AppLogoState::FADE_IN:
// Fade in: alpha de 0 a 255, scale de 120% a 100% // Fade in: alpha de 0 a 255, animaciones independientes para logo1 y logo2
{ {
float fade_progress = timer_ / APPLOGO_FADE_DURATION; float fade_progress = timer_ / APPLOGO_FADE_DURATION;
if (fade_progress >= 1.0f) { if (fade_progress >= 1.0f) {
@@ -85,36 +109,105 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
state_ = AppLogoState::VISIBLE; state_ = AppLogoState::VISIBLE;
timer_ = 0.0f; timer_ = 0.0f;
current_alpha_ = 255; current_alpha_ = 255;
current_scale_ = 1.0f; // Resetear variables de ambos logos
squash_y_ = 1.0f; logo1_scale_ = 1.0f;
stretch_x_ = 1.0f; logo1_squash_y_ = 1.0f;
rotation_ = 0.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 { } else {
// Interpolar alpha linealmente (0 → 255) // Interpolar alpha linealmente (0 → 255) - compartido
current_alpha_ = static_cast<int>(fade_progress * 255.0f); current_alpha_ = static_cast<int>(fade_progress * 255.0f);
if (animation_type_ == AppLogoAnimationType::ELASTIC_STICK) { // ================================================================
// Animación elástica tipo "pegatina" // Aplicar animación de LOGO1 según logo1_entry_animation_
// Usar easing elástico para el scale (bounce al final) // ================================================================
float elastic_t = easeOutElastic(fade_progress); switch (logo1_entry_animation_) {
current_scale_ = 1.2f - (elastic_t * 0.2f); 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;
// Squash vertical al principio, luego se normaliza con overshoot case AppLogoAnimationType::ELASTIC_STICK:
// Empieza aplastado (0.6), termina normal (1.0) con bounce {
float squash_t = easeOutBack(fade_progress); float elastic_t = easeOutElastic(fade_progress);
squash_y_ = 0.6f + (squash_t * 0.4f); 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;
// Compensar squash con stretch horizontal (conservación de área) case AppLogoAnimationType::ROTATE_SPIRAL:
stretch_x_ = 1.0f + (1.0f - squash_y_) * 0.5f; {
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;
// Sin rotación en fade in case AppLogoAnimationType::BOUNCE_SQUASH:
rotation_ = 0.0f; {
} else { float bounce_t = easeOutBounce(fade_progress);
// Animación simple (solo zoom) logo1_scale_ = 1.0f;
current_scale_ = 1.2f - (fade_progress * 0.2f); float squash_amount = (1.0f - bounce_t) * 0.3f;
squash_y_ = 1.0f; logo1_squash_y_ = 1.0f - squash_amount;
stretch_x_ = 1.0f; logo1_stretch_x_ = 1.0f + squash_amount * 0.5f;
rotation_ = 0.0f; 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;
} }
} }
} }
@@ -126,11 +219,14 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
state_ = AppLogoState::FADE_OUT; state_ = AppLogoState::FADE_OUT;
timer_ = 0.0f; timer_ = 0.0f;
current_alpha_ = 255; current_alpha_ = 255;
// Elegir animaciones de salida aleatorias (independientes para cada logo)
logo1_exit_animation_ = getRandomAnimation();
logo2_exit_animation_ = getRandomAnimation();
} }
break; break;
case AppLogoState::FADE_OUT: case AppLogoState::FADE_OUT:
// Fade out: alpha de 255 a 0, scale de 100% a 120% // Fade out: alpha de 255 a 0, animaciones independientes para logo1 y logo2
{ {
float fade_progress = timer_ / APPLOGO_FADE_DURATION; float fade_progress = timer_ / APPLOGO_FADE_DURATION;
if (fade_progress >= 1.0f) { if (fade_progress >= 1.0f) {
@@ -138,64 +234,165 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
state_ = AppLogoState::HIDDEN; state_ = AppLogoState::HIDDEN;
timer_ = 0.0f; timer_ = 0.0f;
current_alpha_ = 0; current_alpha_ = 0;
current_scale_ = 1.0f; // Resetear variables de ambos logos
squash_y_ = 1.0f; logo1_scale_ = 1.0f;
stretch_x_ = 1.0f; logo1_squash_y_ = 1.0f;
rotation_ = 0.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 { } else {
// Interpolar alpha linealmente (255 → 0) // Interpolar alpha linealmente (255 → 0) - compartido
current_alpha_ = static_cast<int>((1.0f - fade_progress) * 255.0f); current_alpha_ = static_cast<int>((1.0f - fade_progress) * 255.0f);
if (animation_type_ == AppLogoAnimationType::ELASTIC_STICK) { // ================================================================
// Animación elástica tipo "despegar pegatina" // Aplicar animación de LOGO1 según logo1_exit_animation_
// Scale crece con easing out (más rápido al principio) // ================================================================
current_scale_ = 1.0f + (fade_progress * fade_progress * 0.2f); 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;
// Stretch vertical (estiramiento al despegarse) case AppLogoAnimationType::ELASTIC_STICK:
squash_y_ = 1.0f + (fade_progress * 0.3f); 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;
// Squash horizontal (se comprime al estirarse verticalmente) case AppLogoAnimationType::ROTATE_SPIRAL:
stretch_x_ = 1.0f - (fade_progress * 0.2f); {
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;
// Rotación sutil al despegarse (parece que se tuerce) case AppLogoAnimationType::BOUNCE_SQUASH:
rotation_ = fade_progress * 0.1f; // ~5.7 grados máximo {
} else { if (fade_progress < 0.2f) {
// Animación simple (solo zoom) float squash_t = fade_progress / 0.2f;
current_scale_ = 1.0f + (fade_progress * 0.2f); logo1_squash_y_ = 1.0f - (squash_t * 0.3f);
squash_y_ = 1.0f; logo1_stretch_x_ = 1.0f + (squash_t * 0.2f);
stretch_x_ = 1.0f; } else {
rotation_ = 0.0f; 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; break;
} }
// Aplicar alpha y scale al logo // Aplicar alpha a ambos logos (compartido - sincronizado)
if (logo_texture_) { if (logo1_texture_) {
logo_texture_->setAlpha(current_alpha_); logo1_texture_->setAlpha(current_alpha_);
}
if (logo2_texture_) {
logo2_texture_->setAlpha(current_alpha_);
} }
if (logo_sprite_) { // Aplicar escala animada INDEPENDIENTE a cada logo
// Aplicar escala animada al tamaño if (logo1_sprite_) {
float scaled_width = base_width_ * current_scale_; float scaled_width = base_width_ * logo1_scale_;
float scaled_height = base_height_ * current_scale_; float scaled_height = base_height_ * logo1_scale_;
logo_sprite_->setSize(scaled_width, scaled_height); logo1_sprite_->setSize(scaled_width, scaled_height);
// Recentrar con el nuevo tamaño (importante para que el zoom sea desde el centro)
updateLogoPosition();
} }
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() { void AppLogo::render() {
// Renderizar si NO está en estado HIDDEN (incluye FADE_IN, VISIBLE, FADE_OUT) // Renderizar si NO está en estado HIDDEN (incluye FADE_IN, VISIBLE, FADE_OUT)
if (state_ != AppLogoState::HIDDEN) { if (state_ != AppLogoState::HIDDEN) {
if (animation_type_ == AppLogoAnimationType::ELASTIC_STICK) { // Determinar animaciones actuales para cada logo
// Usar renderizado con geometría para deformaciones AppLogoAnimationType logo1_anim = (state_ == AppLogoState::FADE_IN) ? logo1_entry_animation_ : logo1_exit_animation_;
renderWithGeometry(); AppLogoAnimationType logo2_anim = (state_ == AppLogoState::FADE_IN) ? logo2_entry_animation_ : logo2_exit_animation_;
} else if (logo_sprite_) {
// Usar renderizado simple con Sprite // ====================================================================
logo_sprite_->render(); // 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();
} }
} }
} }
@@ -204,55 +401,63 @@ void AppLogo::updateScreenSize(int screen_width, int screen_height) {
screen_width_ = screen_width; screen_width_ = screen_width;
screen_height_ = screen_height; screen_height_ = screen_height;
// Recalcular tamaño base del logo para la nueva resolución // Recalcular tamaño base para la nueva resolución (asumimos mismo tamaño para ambos logos)
if (logo_sprite_ && logo_texture_) { if (logo1_sprite_ && logo1_texture_) {
float logo_width = static_cast<float>(logo_texture_->getWidth()); float logo_width = static_cast<float>(logo1_texture_->getWidth());
float logo_height = static_cast<float>(logo_texture_->getHeight()); float logo_height = static_cast<float>(logo1_texture_->getHeight());
// Calcular factor de escala para que el logo ocupe 1/4 de la pantalla // El logo debe tener una altura de APPLOGO_HEIGHT_PERCENT (40%) de la pantalla
float quadrant_width = screen_width_ / 2.0f; float target_height = screen_height_ * APPLOGO_HEIGHT_PERCENT;
float quadrant_height = screen_height_ / 2.0f; float scale = target_height / logo_height;
float scale_x = quadrant_width / logo_width;
float scale_y = quadrant_height / logo_height;
float scale = (scale_x < scale_y) ? scale_x : scale_y;
// Recalcular tamaño base // Recalcular tamaño base
base_width_ = logo_width * scale; base_width_ = logo_width * scale;
base_height_ = logo_height * scale; base_height_ = target_height; // = logo_height * scale
// Aplicar escala actual (respeta la animación en curso) // Aplicar escala actual a AMBOS logos (respeta la animación en curso)
float scaled_width = base_width_ * current_scale_; if (logo1_sprite_) {
float scaled_height = base_height_ * current_scale_; float scaled_width = base_width_ * logo1_scale_;
logo_sprite_->setSize(scaled_width, scaled_height); float scaled_height = base_height_ * logo1_scale_;
logo1_sprite_->setSize(scaled_width, scaled_height);
}
// Reposicionar logo 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(); updateLogoPosition();
} }
} }
void AppLogo::updateLogoPosition() { void AppLogo::updateLogoPosition() {
if (!logo_sprite_) return; // Calcular padding desde bordes derecho e inferior
float padding_x = screen_width_ * APPLOGO_PADDING_PERCENT;
float padding_y = screen_height_ * APPLOGO_PADDING_PERCENT;
// Usar el tamaño actual del logo (base_width/height * current_scale_) // Posicionar LOGO1 (anclado a esquina inferior derecha con padding)
float current_width = base_width_ * current_scale_; if (logo1_sprite_) {
float current_height = base_height_ * current_scale_; 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});
}
// Centro del cuadrante inferior derecho // Posicionar LOGO2 (anclado a esquina inferior derecha con padding, superpuesto a logo1)
// Cuadrante inferior derecho va de (width/2, height/2) a (width, height) if (logo2_sprite_) {
// Su centro está en (3/4 * width, 3/4 * height) float logo2_width = base_width_ * logo2_scale_;
float quadrant_center_x = screen_width_ * 0.75f; float logo2_height = base_height_ * logo2_scale_;
float quadrant_center_y = screen_height_ * 0.75f; float pos_x = screen_width_ - logo2_width - padding_x;
float pos_y = screen_height_ - logo2_height - padding_y;
// Centrar el logo en ese punto (sprite se posiciona por esquina superior izquierda) logo2_sprite_->setPos({pos_x, pos_y});
float pos_x = quadrant_center_x - (current_width / 2.0f); }
float pos_y = quadrant_center_y - (current_height / 2.0f);
logo_sprite_->setPos({pos_x, pos_y});
} }
// ============================================================================ // ============================================================================
// Funciones de easing para animaciones elásticas // Funciones de easing para animaciones
// ============================================================================ // ============================================================================
float AppLogo::easeOutElastic(float t) { float AppLogo::easeOutElastic(float t) {
@@ -273,28 +478,103 @@ float AppLogo::easeOutBack(float t) {
return 1.0f + c3 * powf(t - 1.0f, 3.0f) + c1 * powf(t - 1.0f, 2.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) // Renderizado con geometría deformada (para animación ELASTIC_STICK)
// ============================================================================ // ============================================================================
void AppLogo::renderWithGeometry() { void AppLogo::renderWithGeometry(int logo_index) {
if (!logo_texture_ || !renderer_) return; 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 // Calcular tamaño con escala y deformaciones aplicadas
float width = base_width_ * current_scale_ * stretch_x_; float width = base_width_ * scale * stretch_x;
float height = base_height_ * current_scale_ * squash_y_; float height = base_height_ * scale * squash_y;
// Centro del cuadrante inferior derecho (mismo que updateLogoPosition) // Calcular padding desde bordes derecho e inferior
float quadrant_center_x = screen_width_ * 0.75f; float padding_x = screen_width_ * APPLOGO_PADDING_PERCENT;
float quadrant_center_y = screen_height_ * 0.75f; float padding_y = screen_height_ * APPLOGO_PADDING_PERCENT;
// Calcular posición centrada // Calcular esquina del logo (anclado a esquina inferior derecha con padding)
float center_x = quadrant_center_x; float corner_x = screen_width_ - width - padding_x;
float center_y = quadrant_center_y; 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 // Pre-calcular seno y coseno de rotación
float cos_rot = cosf(rotation_); float cos_rot = cosf(rotation);
float sin_rot = sinf(rotation_); float sin_rot = sinf(rotation);
// Crear 4 vértices del quad (centrado en center_x, center_y) // Crear 4 vértices del quad (centrado en center_x, center_y)
SDL_Vertex vertices[4]; SDL_Vertex vertices[4];
@@ -350,6 +630,6 @@ void AppLogo::renderWithGeometry() {
// Índices para 2 triángulos // Índices para 2 triángulos
int indices[6] = {0, 1, 2, 2, 3, 0}; int indices[6] = {0, 1, 2, 2, 3, 0};
// Renderizar con la textura del logo // Renderizar con la textura del logo correspondiente
SDL_RenderGeometry(renderer_, logo_texture_->getSDLTexture(), vertices, 4, indices, 6); SDL_RenderGeometry(renderer_, texture->getSDLTexture(), vertices, 4, indices, 6);
} }

View File

@@ -19,8 +19,10 @@ enum class AppLogoState {
// Tipo de animación de entrada/salida // Tipo de animación de entrada/salida
enum class AppLogoAnimationType { enum class AppLogoAnimationType {
ZOOM_ONLY, // Solo zoom simple (120% → 100% → 120%) ZOOM_ONLY, // A: Solo zoom simple (120% → 100% → 120%)
ELASTIC_STICK // Zoom + deformación elástica tipo "pegatina" 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 { class AppLogo {
@@ -41,21 +43,34 @@ class AppLogo {
void updateScreenSize(int screen_width, int screen_height); void updateScreenSize(int screen_width, int screen_height);
private: private:
std::shared_ptr<Texture> logo_texture_; // Textura del logo // Texturas y sprites (x2 - logo1 y logo2 superpuestos)
std::unique_ptr<Sprite> logo_sprite_; // Sprite para renderizar 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 AppLogoState state_ = AppLogoState::HIDDEN; // Estado actual de la máquina de estados
float timer_ = 0.0f; // Contador de tiempo para estado actual float timer_ = 0.0f; // Contador de tiempo para estado actual
int current_alpha_ = 0; // Alpha actual (0-255) int current_alpha_ = 0; // Alpha actual (0-255)
float current_scale_ = 1.0f; // Escala actual (1.0 = 100%)
// Tipo de animación (FIJO para testing: ELASTIC_STICK) // Animaciones INDEPENDIENTES para cada logo
AppLogoAnimationType animation_type_ = AppLogoAnimationType::ELASTIC_STICK; 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 elástica (para ELASTIC_STICK) // Variables de deformación INDEPENDIENTES para logo1
float squash_y_ = 1.0f; // Factor de aplastamiento vertical (1.0 = normal) float logo1_scale_ = 1.0f; // Escala actual de logo1 (1.0 = 100%)
float stretch_x_ = 1.0f; // Factor de estiramiento horizontal (1.0 = normal) float logo1_squash_y_ = 1.0f; // Factor de aplastamiento vertical logo1
float rotation_ = 0.0f; // Rotación en radianes (para efecto despegar) 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_width_ = 0; // Ancho de pantalla (para centrar)
int screen_height_ = 0; // Alto de pantalla (para centrar) int screen_height_ = 0; // Alto de pantalla (para centrar)
@@ -68,10 +83,15 @@ class AppLogo {
SDL_Renderer* renderer_ = nullptr; SDL_Renderer* renderer_ = nullptr;
// Métodos privados auxiliares // Métodos privados auxiliares
void updateLogoPosition(); // Centrar logo en pantalla void updateLogoPosition(); // Centrar ambos logos en pantalla (superpuestos)
void renderWithGeometry(); // Renderizar con vértices deformados void renderWithGeometry(int logo_index); // Renderizar logo con vértices deformados (1 o 2)
// Funciones de easing // Funciones de easing
float easeOutElastic(float t); // Elastic bounce out float easeOutElastic(float t); // Elastic bounce out
float easeOutBack(float t); // Overshoot 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

@@ -289,9 +289,11 @@ constexpr float LOGO_FLIP_TRIGGER_MAX = 0.80f; // 80% máximo de progres
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) // 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_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_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_FADE_DURATION = 0.5f; // Duración del fade in/out (segundos)
constexpr float APPLOGO_HEIGHT_PERCENT = 0.4f; // Altura del logo = 40% de la altura de pantalla
constexpr float APPLOGO_PADDING_PERCENT = 0.1f; // Padding desde esquina inferior-derecha = 10%
// Configuración de Modo BOIDS (comportamiento de enjambre) // Configuración de Modo BOIDS (comportamiento de enjambre)
// TIME-BASED CONVERSION (frame-based → time-based): // TIME-BASED CONVERSION (frame-based → time-based):