Files
orni-attack/source/core/graphics/playfield.cpp
T

145 lines
5.8 KiB
C++

// playfield.cpp - Implementació del fons del playfield
// © 2026 JailDesigner
#include "core/graphics/playfield.hpp"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#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();
}
void Playfield::update(float delta_time) {
elapsed_s_ += delta_time;
}
void Playfield::buildLines() {
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
const float CELL_W = zona.w / static_cast<float>(Defaults::Playfield::COLUMNS);
const float CELL_H = zona.h / static_cast<float>(Defaults::Playfield::ROWS);
const float SUB_W = CELL_W / static_cast<float>(Defaults::Playfield::SUBDIVISIONS);
const float SUB_H = CELL_H / static_cast<float>(Defaults::Playfield::SUBDIVISIONS);
const int SUB_VERTS = Defaults::Playfield::COLUMNS * Defaults::Playfield::SUBDIVISIONS;
const int SUB_HORIZ = Defaults::Playfield::ROWS * Defaults::Playfield::SUBDIVISIONS;
std::vector<Line> verticals;
std::vector<Line> horizontals;
// Verticals: posicions i ∈ [1, SUB_VERTS-1].
for (int i = 1; i < SUB_VERTS; i++) {
const float X = zona.x + (static_cast<float>(i) * SUB_W);
const bool IS_MAIN = (i % Defaults::Playfield::SUBDIVISIONS) == 0;
const float BRIGHTNESS = IS_MAIN
? Defaults::Playfield::GRID_BRIGHTNESS
: Defaults::Playfield::SUBGRID_BRIGHTNESS;
verticals.push_back(Line{
.start = {.x = X, .y = zona.y},
.end = {.x = X, .y = zona.y + zona.h},
.brightness = BRIGHTNESS,
.spawn_time_s = 0.0F});
}
// Horitzontals: posicions j ∈ [1, SUB_HORIZ-1].
for (int j = 1; j < SUB_HORIZ; j++) {
const float Y = zona.y + (static_cast<float>(j) * SUB_H);
const bool IS_MAIN = (j % Defaults::Playfield::SUBDIVISIONS) == 0;
const float BRIGHTNESS = IS_MAIN
? Defaults::Playfield::GRID_BRIGHTNESS
: Defaults::Playfield::SUBGRID_BRIGHTNESS;
horizontals.push_back(Line{
.start = {.x = zona.x, .y = Y},
.end = {.x = zona.x + zona.w, .y = Y},
.brightness = BRIGHTNESS,
.spawn_time_s = 0.0F});
}
// 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<int>(verticals.size());
const int NUM_H = static_cast<int>(horizontals.size());
const float INTERVAL_V = (NUM_V > 1) ? SPAWN_WINDOW / static_cast<float>(NUM_V - 1) : 0.0F;
const float INTERVAL_H = (NUM_H > 1) ? SPAWN_WINDOW / static_cast<float>(NUM_H - 1) : 0.0F;
lines_.clear();
lines_.reserve(verticals.size() + horizontals.size());
for (int i = 0; i < NUM_V; i++) {
verticals[i].spawn_time_s = static_cast<float>(i) * INTERVAL_V;
lines_.push_back(verticals[i]);
}
for (int i = 0; i < NUM_H; i++) {
horizontals[i].spawn_time_s = static_cast<float>(i) * INTERVAL_H;
lines_.push_back(horizontals[i]);
}
}
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 RAW_P = computeLineProgress(line);
if (RAW_P <= 0.0F) {
continue;
}
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<int>(line.start.x),
static_cast<int>(line.start.y),
static_cast<int>(CURRENT_X),
static_cast<int>(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<int>(HEAD_X),
static_cast<int>(HEAD_Y),
static_cast<int>(CURRENT_X),
static_cast<int>(CURRENT_Y),
Defaults::Playfield::HEAD_BRIGHTNESS);
}
}
}
}
} // namespace Graphics