#include "app_logo.h" #include // for SDL_SCALEMODE_LINEAR, SDL_RenderGeometry #include // 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; // Cargar textura del logo desde data/logo/logo.png std::string resources_dir = getResourcesDirectory(); std::string logo_path = resources_dir + "/data/logo/logo.png"; logo_texture_ = std::make_shared(renderer, logo_path); if (logo_texture_->getWidth() == 0 || logo_texture_->getHeight() == 0) { // Error al cargar textura return false; } // Configurar filtrado LINEAR para suavizado (mejor para logos escalados) logo_texture_->setScaleMode(SDL_SCALEMODE_LINEAR); // Crear sprite con la textura logo_sprite_ = std::make_unique(logo_texture_); // IMPORTANTE: Configurar el clip para que use toda la textura float logo_width = static_cast(logo_texture_->getWidth()); float logo_height = static_cast(logo_texture_->getHeight()); logo_sprite_->setClip({0.0f, 0.0f, logo_width, logo_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 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; // 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(); 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; } break; case AppLogoState::FADE_IN: // Fade in: alpha de 0 a 255, scale de 120% a 100% { float fade_progress = timer_ / APPLOGO_FADE_DURATION; if (fade_progress >= 1.0f) { // Fade in completado 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 (0 → 255) current_alpha_ = static_cast(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; case AppLogoState::VISIBLE: // Logo completamente visible, esperando duración if (timer_ >= APPLOGO_DISPLAY_DURATION) { state_ = AppLogoState::FADE_OUT; timer_ = 0.0f; current_alpha_ = 255; } break; case AppLogoState::FADE_OUT: // Fade out: alpha de 255 a 0, scale de 100% a 120% { 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; current_scale_ = 1.0f; squash_y_ = 1.0f; stretch_x_ = 1.0f; rotation_ = 0.0f; } else { // Interpolar alpha linealmente (255 → 0) current_alpha_ = static_cast((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 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) { 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(); } } } void AppLogo::updateScreenSize(int screen_width, int screen_height) { screen_width_ = screen_width; screen_height_ = screen_height; // Recalcular tamaño base del logo para la nueva resolución if (logo_sprite_ && logo_texture_) { float logo_width = static_cast(logo_texture_->getWidth()); float logo_height = static_cast(logo_texture_->getHeight()); // Calcular factor de escala para que el logo ocupe 1/4 de la pantalla 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; // 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); // Reposicionar logo updateLogoPosition(); } } void AppLogo::updateLogoPosition() { if (!logo_sprite_) return; // 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) // Su centro está en (3/4 * width, 3/4 * height) float quadrant_center_x = screen_width_ * 0.75f; 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 - (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); }