// IWYU pragma: no_include #include "path_sprite.hpp" #include // Para abs #include // Para function #include // Para move // Constructor para paths por puntos (convertido a segundos) Path::Path(const std::vector& spots_init, float waiting_time_s_init) : spots(spots_init), is_point_path(true) { waiting_time_s = waiting_time_s_init; } // Devuelve un vector con los puntos que conforman la ruta auto createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function& easing_function) -> std::vector { std::vector v; v.reserve(steps); for (int i = 0; i < steps; ++i) { double t = static_cast(i) / (steps - 1); double value = start + ((end - start) * easing_function(t)); if ((start > 0 && end < 0) || (start < 0 && end > 0)) { value = start + (end > 0 ? 1 : -1) * std::abs(end - start) * easing_function(t); } switch (type) { case PathType::HORIZONTAL: v.emplace_back(SDL_FPoint{static_cast(value), fixed_pos}); break; case PathType::VERTICAL: v.emplace_back(SDL_FPoint{fixed_pos, static_cast(value)}); break; default: break; } } return v; } // Actualiza la posición y comprueba si ha llegado a su destino void PathSprite::update(float delta_time) { if (enabled_ && !has_finished_) { moveThroughCurrentPath(delta_time); goToNextPathOrDie(); } } // Muestra el sprite por pantalla void PathSprite::render() { if (enabled_) { Sprite::render(); } } // Determina el tipo de centrado basado en el tipo de path auto PathSprite::determineCenteringType(const Path& path, bool centered) -> PathCentered { if (!centered) { return PathCentered::NONE; } if (path.is_point_path) { // Lógica de centrado para paths por PUNTOS if (!path.spots.empty()) { // Si X es constante, es un path Vertical, centramos en X return (path.spots.back().x == path.spots.front().x) ? PathCentered::ON_X : PathCentered::ON_Y; } return PathCentered::NONE; } // Lógica de centrado para paths GENERADOS // Si el tipo es Vertical, centramos en X return (path.type == PathType::VERTICAL) ? PathCentered::ON_X : PathCentered::ON_Y; } // Aplica centrado en el eje X (para paths verticales) void PathSprite::centerPathOnX(Path& path, float offset) { if (path.is_point_path) { const float X_BASE = !path.spots.empty() ? path.spots.front().x : 0.0F; const float X = X_BASE - offset; for (auto& spot : path.spots) { spot.x = X; } } else { // Es un path generado, ajustamos la posición fija (que es X) path.fixed_pos -= offset; } } // Aplica centrado en el eje Y (para paths horizontales) void PathSprite::centerPathOnY(Path& path, float offset) { if (path.is_point_path) { const float Y_BASE = !path.spots.empty() ? path.spots.front().y : 0.0F; const float Y = Y_BASE - offset; for (auto& spot : path.spots) { spot.y = Y; } } else { // Es un path generado, ajustamos la posición fija (que es Y) path.fixed_pos -= offset; } } // Añade un recorrido void PathSprite::addPath(Path path, bool centered) { PathCentered path_centered = determineCenteringType(path, centered); switch (path_centered) { case PathCentered::ON_X: centerPathOnX(path, pos_.w / 2.0F); break; case PathCentered::ON_Y: centerPathOnY(path, pos_.h / 2.0F); break; case PathCentered::NONE: break; } paths_.emplace_back(std::move(path)); } // Añade un recorrido generado (en segundos) void PathSprite::addPath(float start, float end, PathType type, float fixed_pos, float duration_s, const std::function& easing_function, float waiting_time_s) { paths_.emplace_back(start, end, type, fixed_pos, duration_s, waiting_time_s, easing_function); } // Añade un recorrido por puntos (en segundos) void PathSprite::addPath(const std::vector& spots, float waiting_time_s) { paths_.emplace_back(spots, waiting_time_s); } // Habilita el objeto void PathSprite::enable() { if (paths_.empty() || enabled_) { return; } enabled_ = true; // Establece la posición inicial auto& path = paths_.at(current_path_); if (path.is_point_path) { const auto& p = path.spots.at(path.counter); setPosition(p); } else { // Para paths generados, establecer posición inicial SDL_FPoint initial_pos; if (path.type == PathType::HORIZONTAL) { initial_pos = {.x = path.start_pos, .y = path.fixed_pos}; } else { initial_pos = {.x = path.fixed_pos, .y = path.start_pos}; } setPosition(initial_pos); } } // Coloca el sprite en los diferentes puntos del recorrido void PathSprite::moveThroughCurrentPath(float delta_time) { auto& path = paths_.at(current_path_); if (path.is_point_path) { // Lógica para paths por puntos (compatibilidad) const auto& p = path.spots.at(path.counter); setPosition(p); if (!path.on_destination) { ++path.counter; if (path.counter >= static_cast(path.spots.size())) { path.on_destination = true; path.counter = static_cast(path.spots.size()) - 1; } } if (path.on_destination) { path.waiting_elapsed += delta_time; if (path.waiting_elapsed >= path.waiting_time_s) { path.finished = true; } } } else { // Lógica para paths generados en tiempo real if (!path.on_destination) { path.elapsed_time += delta_time; // Calcular progreso (0.0 a 1.0) float progress = path.elapsed_time / path.duration_s; if (progress >= 1.0F) { progress = 1.0F; path.on_destination = true; } // Aplicar función de easing double eased_progress = path.easing_function(progress); // Calcular posición actual float current_pos = path.start_pos + ((path.end_pos - path.start_pos) * static_cast(eased_progress)); // Establecer posición según el tipo SDL_FPoint position; if (path.type == PathType::HORIZONTAL) { position = {.x = current_pos, .y = path.fixed_pos}; } else { position = {.x = path.fixed_pos, .y = current_pos}; } setPosition(position); } else { // Esperar en destino path.waiting_elapsed += delta_time; if (path.waiting_elapsed >= path.waiting_time_s) { path.finished = true; } } } } // Cambia de recorrido o finaliza void PathSprite::goToNextPathOrDie() { // Comprueba si ha terminado el recorrdo actual if (paths_.at(current_path_).finished) { ++current_path_; } // Comprueba si quedan mas recorridos if (current_path_ >= static_cast(paths_.size())) { has_finished_ = true; current_path_ = 0; } } // Indica si ha terminado todos los recorridos auto PathSprite::hasFinished() const -> bool { return has_finished_; }