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>
This commit is contained in:
2025-10-18 01:46:56 +02:00
parent f73a133756
commit 8d608357b4
5 changed files with 232 additions and 141 deletions

View File

@@ -1,11 +1,13 @@
#include "app_logo.h"
#include <SDL3/SDL_render.h> // for SDL_SCALEMODE_LINEAR
#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;
@@ -39,10 +41,12 @@ bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_he
float scale_y = quadrant_height / logo_height;
float scale = (scale_x < scale_y) ? scale_x : scale_y;
// Aplicar escala
float scaled_width = logo_width * scale;
float scaled_height = logo_height * scale;
logo_sprite_->setSize(scaled_width, scaled_height);
// Calcular tamaño base (guardarlo para animaciones de zoom)
base_width_ = logo_width * scale;
base_height_ = logo_height * scale;
// Aplicar escala inicial
logo_sprite_->setSize(base_width_, base_height_);
// Posicionar logo en el centro del cuadrante inferior derecho
updateLogoPosition();
@@ -73,7 +77,7 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
break;
case AppLogoState::FADE_IN:
// Fade in: alpha de 0 a 255
// Fade in: alpha de 0 a 255, scale de 120% a 100%
{
float fade_progress = timer_ / APPLOGO_FADE_DURATION;
if (fade_progress >= 1.0f) {
@@ -81,9 +85,37 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
state_ = AppLogoState::VISIBLE;
timer_ = 0.0f;
current_alpha_ = 255;
current_scale_ = 1.0f;
squash_y_ = 1.0f;
stretch_x_ = 1.0f;
rotation_ = 0.0f;
} else {
// Interpolar alpha linealmente
// Interpolar alpha linealmente (0 → 255)
current_alpha_ = static_cast<int>(fade_progress * 255.0f);
if (animation_type_ == AppLogoAnimationType::ELASTIC_STICK) {
// Animación elástica tipo "pegatina"
// Usar easing elástico para el scale (bounce al final)
float elastic_t = easeOutElastic(fade_progress);
current_scale_ = 1.2f - (elastic_t * 0.2f);
// Squash vertical al principio, luego se normaliza con overshoot
// Empieza aplastado (0.6), termina normal (1.0) con bounce
float squash_t = easeOutBack(fade_progress);
squash_y_ = 0.6f + (squash_t * 0.4f);
// Compensar squash con stretch horizontal (conservación de área)
stretch_x_ = 1.0f + (1.0f - squash_y_) * 0.5f;
// Sin rotación en fade in
rotation_ = 0.0f;
} else {
// Animación simple (solo zoom)
current_scale_ = 1.2f - (fade_progress * 0.2f);
squash_y_ = 1.0f;
stretch_x_ = 1.0f;
rotation_ = 0.0f;
}
}
}
break;
@@ -98,7 +130,7 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
break;
case AppLogoState::FADE_OUT:
// Fade out: alpha de 255 a 0
// Fade out: alpha de 255 a 0, scale de 100% a 120%
{
float fade_progress = timer_ / APPLOGO_FADE_DURATION;
if (fade_progress >= 1.0f) {
@@ -106,24 +138,65 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
state_ = AppLogoState::HIDDEN;
timer_ = 0.0f;
current_alpha_ = 0;
current_scale_ = 1.0f;
squash_y_ = 1.0f;
stretch_x_ = 1.0f;
rotation_ = 0.0f;
} else {
// Interpolar alpha linealmente (inverso)
// Interpolar alpha linealmente (255 → 0)
current_alpha_ = static_cast<int>((1.0f - fade_progress) * 255.0f);
if (animation_type_ == AppLogoAnimationType::ELASTIC_STICK) {
// Animación elástica tipo "despegar pegatina"
// Scale crece con easing out (más rápido al principio)
current_scale_ = 1.0f + (fade_progress * fade_progress * 0.2f);
// Stretch vertical (estiramiento al despegarse)
squash_y_ = 1.0f + (fade_progress * 0.3f);
// Squash horizontal (se comprime al estirarse verticalmente)
stretch_x_ = 1.0f - (fade_progress * 0.2f);
// Rotación sutil al despegarse (parece que se tuerce)
rotation_ = fade_progress * 0.1f; // ~5.7 grados máximo
} else {
// Animación simple (solo zoom)
current_scale_ = 1.0f + (fade_progress * 0.2f);
squash_y_ = 1.0f;
stretch_x_ = 1.0f;
rotation_ = 0.0f;
}
}
}
break;
}
// Aplicar alpha a la textura
// Aplicar alpha y scale al logo
if (logo_texture_) {
logo_texture_->setAlpha(current_alpha_);
}
if (logo_sprite_) {
// Aplicar escala animada al tamaño
float scaled_width = base_width_ * current_scale_;
float scaled_height = base_height_ * current_scale_;
logo_sprite_->setSize(scaled_width, scaled_height);
// Recentrar con el nuevo tamaño (importante para que el zoom sea desde el centro)
updateLogoPosition();
}
}
void AppLogo::render() {
// Renderizar si NO está en estado HIDDEN (incluye FADE_IN, VISIBLE, FADE_OUT)
if (state_ != AppLogoState::HIDDEN && logo_sprite_) {
logo_sprite_->render();
if (state_ != AppLogoState::HIDDEN) {
if (animation_type_ == AppLogoAnimationType::ELASTIC_STICK) {
// Usar renderizado con geometría para deformaciones
renderWithGeometry();
} else if (logo_sprite_) {
// Usar renderizado simple con Sprite
logo_sprite_->render();
}
}
}
@@ -131,7 +204,7 @@ void AppLogo::updateScreenSize(int screen_width, int screen_height) {
screen_width_ = screen_width;
screen_height_ = screen_height;
// Recalcular escala y posición del logo
// Recalcular tamaño base del logo para la nueva resolución
if (logo_sprite_ && logo_texture_) {
float logo_width = static_cast<float>(logo_texture_->getWidth());
float logo_height = static_cast<float>(logo_texture_->getHeight());
@@ -144,32 +217,26 @@ void AppLogo::updateScreenSize(int screen_width, int screen_height) {
float scale_y = quadrant_height / logo_height;
float scale = (scale_x < scale_y) ? scale_x : scale_y;
// Aplicar escala
float scaled_width = logo_width * scale;
float scaled_height = logo_height * scale;
// Recalcular tamaño base
base_width_ = logo_width * scale;
base_height_ = logo_height * scale;
// Aplicar escala actual (respeta la animación en curso)
float scaled_width = base_width_ * current_scale_;
float scaled_height = base_height_ * current_scale_;
logo_sprite_->setSize(scaled_width, scaled_height);
// Posicionar logo
// Reposicionar logo
updateLogoPosition();
}
}
void AppLogo::updateLogoPosition() {
if (!logo_sprite_ || !logo_texture_) return;
if (!logo_sprite_) return;
// Calcular tamaño escalado del logo (ya configurado en setSize)
float logo_width = static_cast<float>(logo_texture_->getWidth());
float logo_height = static_cast<float>(logo_texture_->getHeight());
float quadrant_width = screen_width_ / 2.0f;
float quadrant_height = screen_height_ / 2.0f;
float scale_x = quadrant_width / logo_width;
float scale_y = quadrant_height / logo_height;
float scale = (scale_x < scale_y) ? scale_x : scale_y;
float scaled_width = logo_width * scale;
float scaled_height = logo_height * scale;
// Usar el tamaño actual del logo (base_width/height * current_scale_)
float current_width = base_width_ * current_scale_;
float current_height = base_height_ * current_scale_;
// Centro del cuadrante inferior derecho
// Cuadrante inferior derecho va de (width/2, height/2) a (width, height)
@@ -178,8 +245,111 @@ void AppLogo::updateLogoPosition() {
float quadrant_center_y = screen_height_ * 0.75f;
// Centrar el logo en ese punto (sprite se posiciona por esquina superior izquierda)
float pos_x = quadrant_center_x - (scaled_width / 2.0f);
float pos_y = quadrant_center_y - (scaled_height / 2.0f);
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
// ============================================================================
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);
}
// ============================================================================
// Renderizado con geometría deformada (para animación ELASTIC_STICK)
// ============================================================================
void AppLogo::renderWithGeometry() {
if (!logo_texture_ || !renderer_) return;
// Calcular tamaño con escala y deformaciones aplicadas
float width = base_width_ * current_scale_ * stretch_x_;
float height = base_height_ * current_scale_ * squash_y_;
// Centro del cuadrante inferior derecho (mismo que updateLogoPosition)
float quadrant_center_x = screen_width_ * 0.75f;
float quadrant_center_y = screen_height_ * 0.75f;
// Calcular posición centrada
float center_x = quadrant_center_x;
float center_y = quadrant_center_y;
// 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
SDL_RenderGeometry(renderer_, logo_texture_->getSDLTexture(), vertices, 4, indices, 6);
}