97 lines
3.6 KiB
C++
97 lines
3.6 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <random>
|
|
|
|
class Surface;
|
|
|
|
namespace Effects {
|
|
|
|
// Revelat tipus "scanline CRT" d'una Surface. Una banda lluminosa
|
|
// baixa des de dalt cap a baix de la imatge: les files damunt de la
|
|
// banda es renderitzen normalment (ja revelades), les files sota
|
|
// queden invisibles, i a la banda mateixa s'aplica un overlay
|
|
// blanc semi-transparent. Al voltant de la banda les files reben un
|
|
// jitter horitzontal aleatori (falloff lineal amb la distància) i
|
|
// damunt la part ja revelada apareixen ocasionalment "bursts VHS" —
|
|
// franges horitzontals desplaçades durant uns ms — per a un toc
|
|
// demoscene/VHS deteriorat.
|
|
//
|
|
// Quan la banda surt per sota del logo i s'esgota `duration_ms`,
|
|
// dispara un flash blanc breu. Si `tail_glitch_ms > 0`, després del
|
|
// flash el logo queda totalment visible mentre els bursts VHS
|
|
// continuen saltant durant aquest temps (glitch tail). Quan el tail
|
|
// s'exhaureix `isFinished()=true`. El caller decideix què fa amb el
|
|
// control de fase a partir d'aquí.
|
|
//
|
|
// Ús:
|
|
// ScanReveal sr;
|
|
// sr.begin({.duration_ms = 1500, ...});
|
|
// // cada frame:
|
|
// sr.tick(delta_ms);
|
|
// sr.render(*logo, x, y);
|
|
// if (sr.isFinished()) { ... canvi de fase ... }
|
|
class ScanReveal {
|
|
public:
|
|
struct Params {
|
|
int duration_ms;
|
|
int band_height_px;
|
|
int band_alpha;
|
|
float band_pulse_hz;
|
|
float band_pulse_depth;
|
|
int glitch_amplitude_px;
|
|
int glitch_band_radius_px;
|
|
int vhs_burst_per_sec;
|
|
int vhs_burst_duration_ms;
|
|
int vhs_burst_height_px;
|
|
int vhs_burst_amplitude_px;
|
|
int flash_ms;
|
|
// Glitch tail post-flash: el logo queda visible sencer i els
|
|
// bursts VHS continuen saltant durant aquest temps. 0 ho
|
|
// desactiva (efecte acaba al flash).
|
|
int tail_glitch_ms;
|
|
};
|
|
|
|
ScanReveal();
|
|
|
|
void begin(const Params& params);
|
|
void tick(int delta_ms);
|
|
|
|
// Renderitza un frame del revelat sobre la Surface `src`. El
|
|
// caller s'encarrega de pintar negre per sota (les files no
|
|
// revelades simplement no es dibuixen — queda el que hi haja al
|
|
// canvas, normalment negre).
|
|
void render(const Surface& src, int x, int y) const;
|
|
|
|
[[nodiscard]] auto isActive() const -> bool;
|
|
[[nodiscard]] auto isFinished() const -> bool;
|
|
|
|
private:
|
|
Params params_{};
|
|
int elapsed_ms_{0};
|
|
// Flash post-scan: arranca quan el scan toca fons; decreix amb
|
|
// `delta_ms` fins a 0. Mentre `flash_active_` és true encara no
|
|
// hem reportat finished (donant marge per pintar el flash sencer).
|
|
int flash_remaining_ms_{0};
|
|
bool flash_started_{false};
|
|
|
|
// Burst VHS actiu (com a màxim un alhora — simplifica i ja és
|
|
// suficient per al ritme visual). y_norm ∈ [0,1] es resol en
|
|
// píxels al `render()` perquè aquí no coneixem l'alçada del logo.
|
|
int burst_remaining_ms_{0};
|
|
float burst_y_norm_{0.0F};
|
|
int burst_dx_{0};
|
|
|
|
// Glitch tail post-flash: arranca quan el flash s'esgota i
|
|
// permet que els bursts continuen saltant amb el logo ja del tot
|
|
// visible. `isFinished()` espera al final d'aquest tail.
|
|
bool tail_started_{false};
|
|
int tail_remaining_ms_{0};
|
|
|
|
// RNG per als bursts. Seed des de random_device a `begin()`
|
|
// perquè cada execució sigui visualment diferent.
|
|
std::minstd_rand rng_;
|
|
};
|
|
|
|
} // namespace Effects
|