#include "core/rendering/effects/heat_haze.hpp" #include #include #include #include "core/rendering/surface.hpp" namespace Effects { namespace { constexpr float TWO_PI = 2.0F * std::numbers::pi_v; } HeatHaze::HeatHaze(Params params) : params_(params) {} void HeatHaze::tick(int delta_ms) { const float DT = static_cast(delta_ms) / 1000.0F; phase_ = std::fmod(phase_ + (TWO_PI * params_.speed_hz * DT), TWO_PI); } void HeatHaze::render(const Surface& src, int x, int y) const { // Pas 1 — blit pla de tota la Surface al destí. És la base que // tapa els marges que les files desplaçades deixarien al // descobert, evitant haver de demanar al caller un pre-fill. Per // a fons amb gradient continu (cels, sorres) la costura amb el // pas 2 no es nota; per a fons d'alt contrast cal moderar // l'amplitud. src.render(x, y); // Pas 2 — re-blit fila a fila amb desplaçament horitzontal sin. // Wavelength <=0 dóna divisió absurda: cau a 1 px com a salvaguarda; // a la pràctica el caller passa un valor >0 vingut d'ui.yaml. const float WL = (params_.wavelength_px > 0.0F) ? params_.wavelength_px : 1.0F; const int H = src.getHeight(); const auto W = static_cast(src.getWidth()); for (int row = 0; row < H; ++row) { const float ARG = (TWO_PI * static_cast(row) / WL) + phase_; const int DX = static_cast(std::lround(params_.amplitude_px * std::sin(ARG))); if (DX == 0) { continue; } // El pas 1 ja ha pintat aquesta fila on toca. SDL_FRect row_rect{0.0F, static_cast(row), W, 1.0F}; src.render(x + DX, y + row, &row_rect); } } } // namespace Effects