diff --git a/source/core/defaults/playfield.hpp b/source/core/defaults/playfield.hpp index b0c36f2..fd0146b 100644 --- a/source/core/defaults/playfield.hpp +++ b/source/core/defaults/playfield.hpp @@ -16,9 +16,13 @@ namespace Defaults::Playfield { // Animació de creació amb timer intern del Playfield. // L'animació total cobreix tot l'INIT_HUD (3 s). Cada línia es pinta en - // LINE_GROWTH_DURATION_S; l'spawn de les línies es distribueix perquè - // l'última acabe just al final de TOTAL_ANIMATION_DURATION_S. + // LINE_GROWTH_DURATION_S; els spawns es distribueixen amb sweep des del + // centre perquè verticals i horitzontals propaguen cap als extrems. constexpr float LINE_GROWTH_DURATION_S = 0.4F; constexpr float TOTAL_ANIMATION_DURATION_S = 3.0F; // = Defaults::Game::INIT_HUD_DURATION + // Cap brillant de la línia mentre creix (extrem que avança). + constexpr float HEAD_LENGTH_PX = 8.0F; // longitud en píxels lògics del tram brillant + constexpr float HEAD_BRIGHTNESS = 0.0F; // brillo del cap (= border) + } // namespace Defaults::Playfield diff --git a/source/core/graphics/playfield.cpp b/source/core/graphics/playfield.cpp index d5f001a..145bd35 100644 --- a/source/core/graphics/playfield.cpp +++ b/source/core/graphics/playfield.cpp @@ -4,24 +4,27 @@ #include "core/graphics/playfield.hpp" #include +#include +#include #include "core/defaults.hpp" #include "core/rendering/line_renderer.hpp" namespace Graphics { + namespace { + + // Easing cubic-out: t → 1 - (1-t)^3. Decelera prop del final. + auto easeOutCubic(float t) -> float { + const float INV = 1.0F - t; + return 1.0F - (INV * INV * INV); + } + + } // namespace + Playfield::Playfield(Rendering::Renderer* renderer) : renderer_(renderer) { buildLines(); - total_slots_ = static_cast(lines_.size()); - - // Distribuïm els spawns de manera que la última línia acabe just a TOTAL_ANIMATION_DURATION_S. - // last_line_start = (N-1) * spawn_interval - // last_line_end = last_line_start + LINE_GROWTH_DURATION_S = TOTAL_ANIMATION_DURATION_S - if (total_slots_ > 1) { - spawn_interval_s_ = - (Defaults::Playfield::TOTAL_ANIMATION_DURATION_S - Defaults::Playfield::LINE_GROWTH_DURATION_S) / static_cast(total_slots_ - 1); - } } void Playfield::update(float delta_time) { @@ -40,8 +43,7 @@ namespace Graphics { std::vector verticals; std::vector horizontals; - // Verticals: posicions i ∈ [1, SUB_VERTS-1]. Si i % SUBDIVISIONS == 0 → línia - // de la graella principal; si no, sub-graella. + // Verticals: posicions i ∈ [1, SUB_VERTS-1]. for (int i = 1; i < SUB_VERTS; i++) { const float X = zona.x + (static_cast(i) * SUB_W); const bool IS_MAIN = (i % Defaults::Playfield::SUBDIVISIONS) == 0; @@ -52,10 +54,10 @@ namespace Graphics { .start = {.x = X, .y = zona.y}, .end = {.x = X, .y = zona.y + zona.h}, .brightness = BRIGHTNESS, - .slot = 0}); + .spawn_time_s = 0.0F}); } - // Horitzontals: posicions j ∈ [1, SUB_HORIZ-1]. Mateix criteri main/sub. + // Horitzontals: posicions j ∈ [1, SUB_HORIZ-1]. for (int j = 1; j < SUB_HORIZ; j++) { const float Y = zona.y + (static_cast(j) * SUB_H); const bool IS_MAIN = (j % Defaults::Playfield::SUBDIVISIONS) == 0; @@ -66,46 +68,76 @@ namespace Graphics { .start = {.x = zona.x, .y = Y}, .end = {.x = zona.x + zona.w, .y = Y}, .brightness = BRIGHTNESS, - .slot = 0}); + .spawn_time_s = 0.0F}); } - // Verticals ja venen ordenats per x ascendent (loop sobre i). Assignem slots 0..N-1. - // Horitzontals ja venen ordenats per y ascendent. Assignem slots N..total-1. + // Ona diagonal: la línia esquerra/superior naix a t=0 i les següents + // propaguen cap a la dreta/inferior, en paral·lel. Verticals i + // horitzontals comparteixen la finestra temporal així el front arriba + // a la cantonada inferior-dreta alhora. + const float SPAWN_WINDOW = + Defaults::Playfield::TOTAL_ANIMATION_DURATION_S - Defaults::Playfield::LINE_GROWTH_DURATION_S; + const int NUM_V = static_cast(verticals.size()); + const int NUM_H = static_cast(horizontals.size()); + const float INTERVAL_V = (NUM_V > 1) ? SPAWN_WINDOW / static_cast(NUM_V - 1) : 0.0F; + const float INTERVAL_H = (NUM_H > 1) ? SPAWN_WINDOW / static_cast(NUM_H - 1) : 0.0F; + lines_.clear(); lines_.reserve(verticals.size() + horizontals.size()); - int slot = 0; - for (auto& line : verticals) { - line.slot = slot++; - lines_.push_back(line); + for (int i = 0; i < NUM_V; i++) { + verticals[i].spawn_time_s = static_cast(i) * INTERVAL_V; + lines_.push_back(verticals[i]); } - for (auto& line : horizontals) { - line.slot = slot++; - lines_.push_back(line); + for (int i = 0; i < NUM_H; i++) { + horizontals[i].spawn_time_s = static_cast(i) * INTERVAL_H; + lines_.push_back(horizontals[i]); } } - auto Playfield::computeLineProgress(int slot) const -> float { - const float LINE_START = static_cast(slot) * spawn_interval_s_; - const float LINE_ELAPSED = elapsed_s_ - LINE_START; + auto Playfield::computeLineProgress(const Line& line) const -> float { + const float LINE_ELAPSED = elapsed_s_ - line.spawn_time_s; return std::clamp(LINE_ELAPSED / Defaults::Playfield::LINE_GROWTH_DURATION_S, 0.0F, 1.0F); } void Playfield::draw() const { for (const auto& line : lines_) { - const float P = computeLineProgress(line.slot); - if (P <= 0.0F) { + const float RAW_P = computeLineProgress(line); + if (RAW_P <= 0.0F) { continue; } - const float END_X = line.start.x + ((line.end.x - line.start.x) * P); - const float END_Y = line.start.y + ((line.end.y - line.start.y) * P); + const float P = easeOutCubic(RAW_P); + + const float DX = line.end.x - line.start.x; + const float DY = line.end.y - line.start.y; + const float CURRENT_X = line.start.x + (DX * P); + const float CURRENT_Y = line.start.y + (DY * P); + + // Tram base (brillo de la línia). Rendering::linea( renderer_, static_cast(line.start.x), static_cast(line.start.y), - static_cast(END_X), - static_cast(END_Y), + static_cast(CURRENT_X), + static_cast(CURRENT_Y), line.brightness); + + // Cap brillant mentre creix: l'últim tram de la línia es repinta més brillant. + if (P < 1.0F) { + const float LENGTH = std::sqrt((DX * DX) + (DY * DY)); + if (LENGTH > 0.0F) { + const float HEAD_T = std::max(0.0F, P - (Defaults::Playfield::HEAD_LENGTH_PX / LENGTH)); + const float HEAD_X = line.start.x + (DX * HEAD_T); + const float HEAD_Y = line.start.y + (DY * HEAD_T); + Rendering::linea( + renderer_, + static_cast(HEAD_X), + static_cast(HEAD_Y), + static_cast(CURRENT_X), + static_cast(CURRENT_Y), + Defaults::Playfield::HEAD_BRIGHTNESS); + } + } } } diff --git a/source/core/graphics/playfield.hpp b/source/core/graphics/playfield.hpp index 2eaab55..5baf9aa 100644 --- a/source/core/graphics/playfield.hpp +++ b/source/core/graphics/playfield.hpp @@ -30,19 +30,17 @@ namespace Graphics { private: struct Line { - Vec2 start; // top (verticals) o left (horitzontals) - Vec2 end; // bottom (verticals) o right (horitzontals) - float brightness; // base (GRID_BRIGHTNESS o SUBGRID_BRIGHTNESS) - int slot; // posició a la timeline 0..total_slots-1 + Vec2 start; // top (verticals) o left (horitzontals) + Vec2 end; // bottom (verticals) o right (horitzontals) + float brightness; // base (GRID_BRIGHTNESS o SUBGRID_BRIGHTNESS) + float spawn_time_s; // moment de naixement (verticals i horitzontals tenen ritmes independents) }; void buildLines(); - [[nodiscard]] auto computeLineProgress(int slot) const -> float; + [[nodiscard]] auto computeLineProgress(const Line& line) const -> float; Rendering::Renderer* renderer_; - std::vector lines_; // verticals primer (ordenats per x), després horitzontals (ordenats per y) - int total_slots_{0}; - float spawn_interval_s_{0.0F}; // calculat a buildLines() perquè la última línia acabi a TOTAL_ANIMATION_DURATION_S + std::vector lines_; float elapsed_s_{0.0F}; };