// ship_animator.cpp - Implementació del sistema d'animació de naus // © 2025 Port a C++20 amb SDL3 #include "ship_animator.hpp" #include #include "core/defaults.hpp" #include "core/graphics/shape_loader.hpp" #include "core/math/easing.hpp" namespace Title { ShipAnimator::ShipAnimator(SDL_Renderer* renderer) : renderer_(renderer) { } void ShipAnimator::inicialitzar() { // Carregar formes de naus amb perspectiva pre-calculada auto forma_p1 = Graphics::ShapeLoader::load("ship_starfield.shp"); // Perspectiva esquerra auto forma_p2 = Graphics::ShapeLoader::load("ship2_starfield.shp"); // Perspectiva dreta // Configurar nau P1 naus_[0].jugador_id = 1; naus_[0].forma = forma_p1; configurar_nau_p1(naus_[0]); // Configurar nau P2 naus_[1].jugador_id = 2; naus_[1].forma = forma_p2; configurar_nau_p2(naus_[1]); } void ShipAnimator::actualitzar(float delta_time) { // Dispatcher segons estat de cada nau for (auto& nau : naus_) { if (!nau.visible) continue; switch (nau.estat) { case EstatNau::ENTERING: actualitzar_entering(nau, delta_time); break; case EstatNau::FLOATING: actualitzar_floating(nau, delta_time); break; case EstatNau::EXITING: actualitzar_exiting(nau, delta_time); break; } } } void ShipAnimator::dibuixar() const { for (const auto& nau : naus_) { if (!nau.visible) continue; // Renderitzar nau (perspectiva ja incorporada a la forma) Rendering::render_shape( renderer_, nau.forma, nau.posicio_actual, 0.0f, // angle (rotació 2D no utilitzada) nau.escala_actual, true, // dibuixar 1.0f, // progress (sempre visible) 1.0f // brightness (brillantor màxima) ); } } void ShipAnimator::start_entry_animation() { using namespace Defaults::Title::Ships; // Configurar nau P1 per a l'animació d'entrada naus_[0].estat = EstatNau::ENTERING; naus_[0].temps_estat = 0.0f; naus_[0].posicio_inicial = calcular_posicio_fora_pantalla(CLOCK_8_ANGLE); naus_[0].posicio_actual = naus_[0].posicio_inicial; naus_[0].escala_actual = naus_[0].escala_inicial; // Configurar nau P2 per a l'animació d'entrada naus_[1].estat = EstatNau::ENTERING; naus_[1].temps_estat = 0.0f; naus_[1].posicio_inicial = calcular_posicio_fora_pantalla(CLOCK_4_ANGLE); naus_[1].posicio_actual = naus_[1].posicio_inicial; naus_[1].escala_actual = naus_[1].escala_inicial; } void ShipAnimator::trigger_exit_animation() { // Configurar ambdues naus per a l'animació de sortida for (auto& nau : naus_) { // Canviar estat a EXITING nau.estat = EstatNau::EXITING; nau.temps_estat = 0.0f; // Preservar posició actual (pot estar a mig camí si START es prem durant ENTERING) nau.posicio_inicial = nau.posicio_actual; // La escala objectiu es preserva per a calcular la interpolació // (escala_actual pot ser diferent si està en ENTERING) } } void ShipAnimator::trigger_exit_animation_for_player(int jugador_id) { // Trobar la nau del jugador especificat for (auto& nau : naus_) { if (nau.jugador_id == jugador_id) { // Canviar estat a EXITING només per aquesta nau nau.estat = EstatNau::EXITING; nau.temps_estat = 0.0f; // Preservar posició actual (pot estar a mig camí si START es prem durant ENTERING) nau.posicio_inicial = nau.posicio_actual; // La escala objectiu es preserva per a calcular la interpolació // (escala_actual pot ser diferent si està en ENTERING) break; // Només una nau per jugador } } } void ShipAnimator::set_visible(bool visible) { for (auto& nau : naus_) { nau.visible = visible; } } bool ShipAnimator::is_animation_complete() const { // Comprovar si totes les naus són invisibles (han completat l'animació de sortida) for (const auto& nau : naus_) { if (nau.visible) { return false; // Encara hi ha alguna nau visible } } return true; // Totes les naus són invisibles } // Mètodes d'animació (stubs) void ShipAnimator::actualitzar_entering(NauTitol& nau, float delta_time) { using namespace Defaults::Title::Ships; nau.temps_estat += delta_time; // Esperar al delay abans de començar l'animació if (nau.temps_estat < nau.entry_delay) { // Encara en delay: la nau es queda fora de pantalla (posició inicial) nau.posicio_actual = nau.posicio_inicial; nau.escala_actual = nau.escala_inicial; return; } // Càlcul del progrés (restant el delay) float elapsed = nau.temps_estat - nau.entry_delay; float progress = std::min(1.0f, elapsed / ENTRY_DURATION); // Aplicar easing (ease_out_quad per arribada suau) float eased_progress = Easing::ease_out_quad(progress); // Lerp posició (inicial → objectiu) nau.posicio_actual.x = Easing::lerp(nau.posicio_inicial.x, nau.posicio_objectiu.x, eased_progress); nau.posicio_actual.y = Easing::lerp(nau.posicio_inicial.y, nau.posicio_objectiu.y, eased_progress); // Lerp escala (gran → normal) nau.escala_actual = Easing::lerp(nau.escala_inicial, nau.escala_objectiu, eased_progress); // Transicionar a FLOATING quan completi if (elapsed >= ENTRY_DURATION) { nau.estat = EstatNau::FLOATING; nau.temps_estat = 0.0f; nau.fase_oscilacio = 0.0f; // Reiniciar fase d'oscil·lació } } void ShipAnimator::actualitzar_floating(NauTitol& nau, float delta_time) { using namespace Defaults::Title::Ships; // Actualitzar temps i fase d'oscil·lació nau.temps_estat += delta_time; nau.fase_oscilacio += delta_time; // Oscil·lació sinusoïdal X/Y (paràmetres específics per nau) float offset_x = nau.amplitude_x * std::sin(2.0f * Defaults::Math::PI * nau.frequency_x * nau.fase_oscilacio); float offset_y = nau.amplitude_y * std::sin(2.0f * Defaults::Math::PI * nau.frequency_y * nau.fase_oscilacio + FLOAT_PHASE_OFFSET); // Aplicar oscil·lació a la posició objectiu nau.posicio_actual.x = nau.posicio_objectiu.x + offset_x; nau.posicio_actual.y = nau.posicio_objectiu.y + offset_y; // Escala constant (sense "breathing" per ara) nau.escala_actual = nau.escala_objectiu; } void ShipAnimator::actualitzar_exiting(NauTitol& nau, float delta_time) { using namespace Defaults::Title::Ships; nau.temps_estat += delta_time; // Calcular progrés (0.0 → 1.0) float progress = std::min(1.0f, nau.temps_estat / EXIT_DURATION); // Aplicar easing (ease_in_quad per acceleració cap al punt de fuga) float eased_progress = Easing::ease_in_quad(progress); // Punt de fuga (centre del starfield) constexpr Punt punt_fuga{VANISHING_POINT_X, VANISHING_POINT_Y}; // Lerp posició cap al punt de fuga (preservar posició inicial actual) // Nota: posicio_inicial conté la posició on estava quan es va activar EXITING nau.posicio_actual.x = Easing::lerp(nau.posicio_inicial.x, punt_fuga.x, eased_progress); nau.posicio_actual.y = Easing::lerp(nau.posicio_inicial.y, punt_fuga.y, eased_progress); // Escala redueix a 0 (simula Z → infinit) nau.escala_actual = nau.escala_objectiu * (1.0f - eased_progress); // Marcar invisible quan l'animació completi if (progress >= 1.0f) { nau.visible = false; } } // Configuració void ShipAnimator::configurar_nau_p1(NauTitol& nau) { using namespace Defaults::Title::Ships; // Estat inicial: FLOATING (per test estàtic) nau.estat = EstatNau::FLOATING; nau.temps_estat = 0.0f; // Posicions (clock 8, bottom-left) nau.posicio_objectiu = {P1_TARGET_X(), P1_TARGET_Y()}; // Calcular posició inicial (fora de pantalla) nau.posicio_inicial = calcular_posicio_fora_pantalla(CLOCK_8_ANGLE); nau.posicio_actual = nau.posicio_inicial; // Començar fora de pantalla // Escales nau.escala_objectiu = FLOATING_SCALE; nau.escala_actual = FLOATING_SCALE; nau.escala_inicial = ENTRY_SCALE_START; // Flotació nau.fase_oscilacio = 0.0f; // Paràmetres d'entrada nau.entry_delay = P1_ENTRY_DELAY; // Paràmetres d'oscil·lació específics P1 nau.amplitude_x = FLOAT_AMPLITUDE_X; nau.amplitude_y = FLOAT_AMPLITUDE_Y; nau.frequency_x = FLOAT_FREQUENCY_X_BASE * P1_FREQUENCY_MULTIPLIER; nau.frequency_y = FLOAT_FREQUENCY_Y_BASE * P1_FREQUENCY_MULTIPLIER; // Visibilitat nau.visible = true; } void ShipAnimator::configurar_nau_p2(NauTitol& nau) { using namespace Defaults::Title::Ships; // Estat inicial: FLOATING (per test estàtic) nau.estat = EstatNau::FLOATING; nau.temps_estat = 0.0f; // Posicions (clock 4, bottom-right) nau.posicio_objectiu = {P2_TARGET_X(), P2_TARGET_Y()}; // Calcular posició inicial (fora de pantalla) nau.posicio_inicial = calcular_posicio_fora_pantalla(CLOCK_4_ANGLE); nau.posicio_actual = nau.posicio_inicial; // Començar fora de pantalla // Escales nau.escala_objectiu = FLOATING_SCALE; nau.escala_actual = FLOATING_SCALE; nau.escala_inicial = ENTRY_SCALE_START; // Flotació nau.fase_oscilacio = 0.0f; // Paràmetres d'entrada nau.entry_delay = P2_ENTRY_DELAY; // Paràmetres d'oscil·lació específics P2 nau.amplitude_x = FLOAT_AMPLITUDE_X; nau.amplitude_y = FLOAT_AMPLITUDE_Y; nau.frequency_x = FLOAT_FREQUENCY_X_BASE * P2_FREQUENCY_MULTIPLIER; nau.frequency_y = FLOAT_FREQUENCY_Y_BASE * P2_FREQUENCY_MULTIPLIER; // Visibilitat nau.visible = true; } Punt ShipAnimator::calcular_posicio_fora_pantalla(float angle_rellotge) const { using namespace Defaults::Title::Ships; // Convertir angle del rellotge a radians (per exemple: 240° per clock 8) // Calcular posició en direcció radial des del centre, però més lluny // ENTRY_OFFSET es calcula automàticament: (SHIP_MAX_RADIUS * ENTRY_SCALE_START) + ENTRY_OFFSET_MARGIN float extended_radius = CLOCK_RADIUS + ENTRY_OFFSET; float x = Defaults::Game::WIDTH / 2.0f + extended_radius * std::cos(angle_rellotge); float y = Defaults::Game::HEIGHT / 2.0f + extended_radius * std::sin(angle_rellotge); return {x, y}; } } // namespace Title