#pragma once #include #include 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