// border.cpp - Implementació del border del playfield // © 2026 JailDesigner #include "core/graphics/border.hpp" #include #include #include "core/defaults.hpp" #include "core/rendering/line_renderer.hpp" namespace Graphics { Border::Border(Rendering::Renderer* renderer) : renderer_(renderer) {} void Border::update(float delta_time) { for (auto& side : sides_) { // Desplaçament decau cap a 0 amb ritme constant (lineal). const float DEC = Defaults::Border::DISPLACEMENT_RECOVERY_PER_S * delta_time; side.displacement_px = std::max(0.0F, side.displacement_px - DEC); } } void Border::bumpAt(Vec2 contact_point, float strength) { const SDL_FRect& zone = Defaults::Zones::PLAYAREA; const std::array DISTANCES = { /* TOP */ std::abs(contact_point.y - zone.y), /* RIGHT */ std::abs((zone.x + zone.w) - contact_point.x), /* BOTTOM */ std::abs((zone.y + zone.h) - contact_point.y), /* LEFT */ std::abs(contact_point.x - zone.x)}; int closest_idx = 0; float closest_dist = DISTANCES[0]; for (int i = 1; i < SIDE_COUNT; i++) { if (DISTANCES[i] < closest_dist) { closest_dist = DISTANCES[i]; closest_idx = i; } } applyBump(closest_idx, strength); } void Border::applyBump(int side_idx, float strength) { const float S = std::clamp(strength, 0.0F, 1.0F); SideState& side = sides_[static_cast(side_idx)]; side.displacement_px = std::min( Defaults::Border::MAX_DISPLACEMENT_PX, side.displacement_px + (S * Defaults::Border::MAX_DISPLACEMENT_PX)); } namespace { // Lerp del color base verd fòsfor cap a un color "flash" en funció de // f ∈ [0, 1]. Retorna sempre amb alpha>0 perquè el line_renderer l'usi // directament (sense caure al fallback DEFAULT_LINE_COLOR). auto lerpColor(SDL_Color flash, float f) -> SDL_Color { const float CLAMPED = std::clamp(f, 0.0F, 1.0F); constexpr SDL_Color BASE = Rendering::DEFAULT_LINE_COLOR; const auto LERP_U8 = [&](unsigned char a, unsigned char b) { const float OUT = (static_cast(a) * (1.0F - CLAMPED)) + (static_cast(b) * CLAMPED); return static_cast(OUT); }; return SDL_Color{ .r = LERP_U8(BASE.r, flash.r), .g = LERP_U8(BASE.g, flash.g), .b = LERP_U8(BASE.b, flash.b), .a = 255}; } } // namespace void Border::draw() const { const SDL_FRect& zone = Defaults::Zones::PLAYAREA; const int X1 = static_cast(zone.x); const int Y1 = static_cast(zone.y); const int X2 = static_cast(zone.x + zone.w); const int Y2 = static_cast(zone.y + zone.h); const int OFF_TOP = static_cast(sides_[SIDE_TOP].displacement_px); const int OFF_RIGHT = static_cast(sides_[SIDE_RIGHT].displacement_px); const int OFF_BOTTOM = static_cast(sides_[SIDE_BOTTOM].displacement_px); const int OFF_LEFT = static_cast(sides_[SIDE_LEFT].displacement_px); // Color per costat: lerp(oscil·lador → flash) en funció del desplaçament. const SDL_Color FLASH = { .r = Defaults::Border::FLASH_COLOR_R, .g = Defaults::Border::FLASH_COLOR_G, .b = Defaults::Border::FLASH_COLOR_B, .a = 255}; const float MAX_D = Defaults::Border::MAX_DISPLACEMENT_PX; const bool DO_FLASH = Defaults::Border::FLASH_ENABLED; const SDL_Color C_TOP = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_TOP].displacement_px / MAX_D) : SDL_Color{}; const SDL_Color C_RIGHT = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_RIGHT].displacement_px / MAX_D) : SDL_Color{}; const SDL_Color C_BOTTOM = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_BOTTOM].displacement_px / MAX_D) : SDL_Color{}; const SDL_Color C_LEFT = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_LEFT].displacement_px / MAX_D) : SDL_Color{}; // Una sola línia per costat (brillo 1.0). Si DO_FLASH = false → alpha = 0 → usa // el color global de l'oscil·lador. Rendering::linea(renderer_, X1, Y1 - OFF_TOP, X2, Y1 - OFF_TOP, 1.0F, 0.0F, C_TOP); Rendering::linea(renderer_, X2 + OFF_RIGHT, Y1, X2 + OFF_RIGHT, Y2, 1.0F, 0.0F, C_RIGHT); Rendering::linea(renderer_, X1, Y2 + OFF_BOTTOM, X2, Y2 + OFF_BOTTOM, 1.0F, 0.0F, C_BOTTOM); Rendering::linea(renderer_, X1 - OFF_LEFT, Y1, X1 - OFF_LEFT, Y2, 1.0F, 0.0F, C_LEFT); } } // namespace Graphics