#include "card_sprite.hpp" #include // Para std::clamp #include // Para function #include // Para move #include "texture.hpp" // Para Texture #include "utils.hpp" // Para easeOutBounce, easeOutCubic // Constructor CardSprite::CardSprite(std::shared_ptr texture) : MovingSprite(std::move(texture)), entry_easing_(easeOutBounce) {} // Inicia la animación de entrada (solo si está en IDLE) auto CardSprite::enable() -> bool { if (state_ != CardState::IDLE) { return false; } state_ = CardState::ENTERING; entry_elapsed_ = 0.0F; first_touch_ = false; // Posición inicial (borde de pantalla) setPos(entry_start_x_, entry_start_y_); // Zoom inicial grande (como si estuviera cerca de la cámara) horizontal_zoom_ = start_zoom_; vertical_zoom_ = start_zoom_; // Ángulo inicial rotate_.angle = start_angle_; rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F}; shadow_visible_ = true; return true; } // Inicia la animación de salida (solo si está en LANDED) void CardSprite::startExit() { if (state_ != CardState::LANDED) { return; } state_ = CardState::EXITING; shadow_visible_ = true; // Velocidad y aceleración de salida vx_ = exit_vx_; vy_ = exit_vy_; ax_ = exit_ax_; ay_ = exit_ay_; // Rotación continua rotate_.enabled = true; rotate_.amount = exit_rotate_amount_; rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F}; } // Actualiza según el estado void CardSprite::update(float delta_time) { switch (state_) { case CardState::ENTERING: updateEntering(delta_time); break; case CardState::EXITING: updateExiting(delta_time); break; default: break; } } // Animación de entrada: interpola posición, zoom y ángulo void CardSprite::updateEntering(float delta_time) { entry_elapsed_ += delta_time; float progress = std::clamp(entry_elapsed_ / entry_duration_s_, 0.0F, 1.0F); double eased = entry_easing_(static_cast(progress)); // Zoom: de start_zoom_ a 1.0 con rebote auto current_zoom = static_cast(start_zoom_ + (1.0 - start_zoom_) * eased); horizontal_zoom_ = current_zoom; vertical_zoom_ = current_zoom; // Ángulo: de start_angle_ a 0 con rebote rotate_.angle = start_angle_ * (1.0 - eased); // Posición: de entry_start a landing con easing suave (sin rebote) // Usamos easeOutCubic para que el desplazamiento sea fluido double pos_eased = easeOutCubic(static_cast(progress)); auto current_x = static_cast(entry_start_x_ + (landing_x_ - entry_start_x_) * pos_eased); auto current_y = static_cast(entry_start_y_ + (landing_y_ - entry_start_y_) * pos_eased); setPos(current_x, current_y); // Detecta el primer toque (cuando el easing alcanza ~1.0 por primera vez) if (!first_touch_ && eased >= FIRST_TOUCH_THRESHOLD) { first_touch_ = true; } // Transición a LANDED cuando termina la animación completa if (progress >= 1.0F) { horizontal_zoom_ = 1.0F; vertical_zoom_ = 1.0F; rotate_.angle = 0.0; setPos(landing_x_, landing_y_); state_ = CardState::LANDED; first_touch_ = true; } } // Animación de salida: movimiento + rotación continua + zoom opcional void CardSprite::updateExiting(float delta_time) { move(delta_time); rotate(delta_time); // Ganar altura gradualmente (zoom hacia el objetivo) if (exit_zoom_speed_ > 0.0F && horizontal_zoom_ < exit_target_zoom_) { float new_zoom = horizontal_zoom_ + exit_zoom_speed_ * delta_time; if (new_zoom > exit_target_zoom_) { new_zoom = exit_target_zoom_; } horizontal_zoom_ = new_zoom; vertical_zoom_ = new_zoom; } if (isOffScreen()) { state_ = CardState::FINISHED; } } // Renderiza el sprite y su sombra void CardSprite::render() { if (state_ == CardState::IDLE || state_ == CardState::FINISHED) { return; } // Sombra primero (debajo de la tarjeta) if (shadow_visible_ && shadow_texture_) { renderShadow(); } // Tarjeta MovingSprite::render(); } // Renderiza la sombra con efecto de perspectiva 2D→3D (efecto helicóptero) // // Fuente de luz en la esquina superior izquierda (0,0). // La sombra se mueve con la tarjeta pero desplazada en dirección opuesta a la luz // (abajo-derecha a 45°). Cuanto más alta la tarjeta (zoom > 1.0): // - Más separada de la tarjeta (offset grande) // - Más pequeña (proyección lejana) // Cuando la tarjeta está en la mesa (zoom=1.0): // - Sombra pegada con offset base // - Tamaño real void CardSprite::renderShadow() { // Altura sobre la mesa: 0.0 = en la mesa, 0.8 = alta (zoom 1.8) float height = horizontal_zoom_ - 1.0F; // Escala: más pequeña cuanto más alta float shadow_zoom = 1.0F / horizontal_zoom_; // Offset respecto a la tarjeta: base + extra proporcional a la altura // La sombra se aleja en diagonal abajo-derecha (opuesta a la luz en 0,0) float offset_x = shadow_offset_x_ + height * SHADOW_HEIGHT_MULTIPLIER; float offset_y = shadow_offset_y_ + height * SHADOW_HEIGHT_MULTIPLIER; shadow_texture_->render( pos_.x + offset_x, pos_.y + offset_y, &sprite_clip_, shadow_zoom, shadow_zoom, rotate_.angle, &rotate_.center, flip_); } // Comprueba si el sprite está fuera de pantalla auto CardSprite::isOffScreen() const -> bool { float effective_width = pos_.w * horizontal_zoom_; float effective_height = pos_.h * vertical_zoom_; return (pos_.x + effective_width < -OFF_SCREEN_MARGIN || pos_.x > screen_width_ + OFF_SCREEN_MARGIN || pos_.y + effective_height < -OFF_SCREEN_MARGIN || pos_.y > screen_height_ + OFF_SCREEN_MARGIN); } // --- Consultas de estado --- auto CardSprite::hasLanded() const -> bool { return state_ == CardState::LANDED || state_ == CardState::EXITING || state_ == CardState::FINISHED; } auto CardSprite::hasFirstTouch() const -> bool { return first_touch_; } auto CardSprite::hasFinished() const -> bool { return state_ == CardState::FINISHED; } auto CardSprite::isExiting() const -> bool { return state_ == CardState::EXITING; } auto CardSprite::getState() const -> CardState { return state_; } // --- Configuración --- void CardSprite::setEntryParams(float start_zoom, double start_angle, float duration_s, std::function easing) { start_zoom_ = start_zoom; start_angle_ = start_angle; entry_duration_s_ = duration_s; entry_easing_ = std::move(easing); } void CardSprite::setEntryPosition(float start_x, float start_y) { entry_start_x_ = start_x; entry_start_y_ = start_y; } void CardSprite::setLandingPosition(float x, float y) { landing_x_ = x; landing_y_ = y; } void CardSprite::setExitParams(float vx, float vy, float ax, float ay, double rotate_amount) { exit_vx_ = vx; exit_vy_ = vy; exit_ax_ = ax; exit_ay_ = ay; exit_rotate_amount_ = rotate_amount; } void CardSprite::setExitLift(float target_zoom, float zoom_speed) { exit_target_zoom_ = target_zoom; exit_zoom_speed_ = zoom_speed; } void CardSprite::setShadowTexture(std::shared_ptr texture) { shadow_texture_ = std::move(texture); } void CardSprite::setShadowOffset(float offset_x, float offset_y) { shadow_offset_x_ = offset_x; shadow_offset_y_ = offset_y; } void CardSprite::setScreenBounds(float width, float height) { screen_width_ = width; screen_height_ = height; }