#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; 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(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(logo1_texture_); // Configurar el clip para que use toda la textura float logo1_width = static_cast(logo1_texture_->getWidth()); float logo1_height = static_cast(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(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(logo2_texture_); // Configurar el clip para que use toda la textura float logo2_width = static_cast(logo2_texture_->getWidth()); float logo2_height = static_cast(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(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((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(logo1_texture_->getWidth()); float logo_height = static_cast(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; 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); }