feat(logo): timeline lineal de 7 fases amb glitch tail i durades al YAML
This commit is contained in:
1 parent
7d5a4cee8e
commit
f10671d9c0
7 files changed
+204
-76
No files matched your search
@@ -50,6 +50,8 @@ namespace Effects {
|
||||
burst_remaining_ms_ = 0;
|
||||
burst_y_norm_ = 0.0F;
|
||||
burst_dx_ = 0;
|
||||
tail_started_ = false;
|
||||
tail_remaining_ms_ = 0;
|
||||
}
|
||||
|
||||
void ScanReveal::tick(int delta_ms) {
|
||||
@@ -63,9 +65,13 @@ namespace Effects {
|
||||
burst_remaining_ms_ = std::max(0, burst_remaining_ms_ - delta_ms);
|
||||
}
|
||||
|
||||
// 3) Spawn d'un nou burst (només durant el scan, no al flash).
|
||||
// 3) Spawn d'un nou burst. Permès durant el scan i durant el
|
||||
// glitch tail post-flash; bloquejat exactament durant el
|
||||
// flash perquè aquell és un punt visual de tancament net.
|
||||
// Probabilitat per frame ≈ (per_sec/1000) · delta_ms.
|
||||
if (burst_remaining_ms_ == 0 && elapsed_ms_ < params_.duration_ms && params_.vhs_burst_per_sec > 0 && params_.vhs_burst_duration_ms > 0) {
|
||||
const bool TAIL_ACTIVE = tail_started_ && tail_remaining_ms_ > 0;
|
||||
const bool SCAN_ONGOING = elapsed_ms_ < params_.duration_ms;
|
||||
if (burst_remaining_ms_ == 0 && (SCAN_ONGOING || TAIL_ACTIVE) && params_.vhs_burst_per_sec > 0 && params_.vhs_burst_duration_ms > 0) {
|
||||
const float P = (static_cast<float>(params_.vhs_burst_per_sec) / 1000.0F) * static_cast<float>(delta_ms);
|
||||
std::uniform_real_distribution<float> dist01(0.0F, 1.0F);
|
||||
if (dist01(rng_) < P) {
|
||||
@@ -86,6 +92,17 @@ namespace Effects {
|
||||
if (flash_remaining_ms_ > 0) {
|
||||
flash_remaining_ms_ = std::max(0, flash_remaining_ms_ - delta_ms);
|
||||
}
|
||||
|
||||
// 5) Glitch tail: arranca un sol cop quan el scan ha acabat i el
|
||||
// flash s'ha esgotat. Si no hi havia flash configurat, també
|
||||
// arranca tot just acabar el scan.
|
||||
if (!tail_started_ && elapsed_ms_ >= params_.duration_ms && flash_remaining_ms_ <= 0 && params_.tail_glitch_ms > 0) {
|
||||
tail_started_ = true;
|
||||
tail_remaining_ms_ = params_.tail_glitch_ms;
|
||||
}
|
||||
if (tail_remaining_ms_ > 0) {
|
||||
tail_remaining_ms_ = std::max(0, tail_remaining_ms_ - delta_ms);
|
||||
}
|
||||
}
|
||||
|
||||
auto ScanReveal::isActive() const -> bool {
|
||||
@@ -94,8 +111,12 @@ namespace Effects {
|
||||
|
||||
auto ScanReveal::isFinished() const -> bool {
|
||||
if (params_.duration_ms <= 0) { return true; }
|
||||
// Acabat = scan al final + flash exhaurit (o sense flash).
|
||||
return elapsed_ms_ >= params_.duration_ms && flash_remaining_ms_ <= 0;
|
||||
// Acabat = scan al final + flash exhaurit + glitch tail
|
||||
// exhaurit (o sense tail configurat).
|
||||
const bool SCAN_DONE = elapsed_ms_ >= params_.duration_ms;
|
||||
const bool FLASH_DONE = flash_remaining_ms_ <= 0;
|
||||
const bool TAIL_DONE = (params_.tail_glitch_ms <= 0) || (tail_started_ && tail_remaining_ms_ <= 0);
|
||||
return SCAN_DONE && FLASH_DONE && TAIL_DONE;
|
||||
}
|
||||
|
||||
void ScanReveal::render(const Surface& src, int x, int y) const {
|
||||
@@ -118,15 +139,21 @@ namespace Effects {
|
||||
const int TIME_BUCKET = elapsed_ms_ / JITTER_TIME_BUCKET_MS;
|
||||
const int BURST_Y_PX = static_cast<int>(burst_y_norm_ * static_cast<float>(H));
|
||||
const bool BURST_ACTIVE = burst_remaining_ms_ > 0;
|
||||
// Durant el glitch tail post-flash, BAND_TOP queda just damunt
|
||||
// del peu del logo, fet que (amb el codi de jitter que segueix)
|
||||
// generaria jitter residual a les últimes files. Aquí el
|
||||
// suprimim explícitament: el tail només viu de bursts VHS.
|
||||
const bool TAIL_ACTIVE = tail_started_ && tail_remaining_ms_ > 0;
|
||||
|
||||
// Render fila a fila (només files revelades).
|
||||
for (int row = 0; row <= BAND_BOT && row < H; ++row) {
|
||||
int dx = 0;
|
||||
|
||||
// Jitter de banda: amplitud màxima a la banda, falloff
|
||||
// lineal cap als marges del radi de glitch.
|
||||
// lineal cap als marges del radi de glitch. Inactiu durant
|
||||
// el glitch tail (no hi ha banda).
|
||||
const int DIST_TO_BAND = std::abs(row - BAND_TOP);
|
||||
if (params_.glitch_band_radius_px > 0 && DIST_TO_BAND <= params_.glitch_band_radius_px && params_.glitch_amplitude_px > 0) {
|
||||
if (!TAIL_ACTIVE && params_.glitch_band_radius_px > 0 && DIST_TO_BAND <= params_.glitch_band_radius_px && params_.glitch_amplitude_px > 0) {
|
||||
const float FALLOFF = 1.0F - (static_cast<float>(DIST_TO_BAND) / static_cast<float>(params_.glitch_band_radius_px));
|
||||
const float JIT = rowJitter(row, TIME_BUCKET);
|
||||
dx += static_cast<int>(std::lround(
|
||||
|
||||
@@ -18,8 +18,11 @@ namespace Effects {
|
||||
// demoscene/VHS deteriorat.
|
||||
//
|
||||
// Quan la banda surt per sota del logo i s'esgota `duration_ms`,
|
||||
// dispara un flash blanc breu i passa a `isFinished()=true`. El
|
||||
// caller decideix què fa amb el control de fase a partir d'aquí.
|
||||
// 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;
|
||||
@@ -43,6 +46,10 @@ namespace Effects {
|
||||
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();
|
||||
@@ -75,6 +82,12 @@ namespace Effects {
|
||||
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_;
|
||||
|
||||
@@ -131,6 +131,7 @@ UiConfig::UiConfig()
|
||||
},
|
||||
logo_scan_{
|
||||
.duration_ms = Defaults::UI::Logo::Scan::DURATION_MS,
|
||||
.tail_glitch_ms = Defaults::UI::Logo::Scan::TAIL_GLITCH_MS,
|
||||
.band_height_px = Defaults::UI::Logo::Scan::BAND_HEIGHT_PX,
|
||||
.band_alpha = Defaults::UI::Logo::Scan::BAND_ALPHA,
|
||||
.band_pulse_hz = Defaults::UI::Logo::Scan::BAND_PULSE_HZ,
|
||||
@@ -143,6 +144,13 @@ UiConfig::UiConfig()
|
||||
.vhs_burst_amplitude_px = Defaults::UI::Logo::Scan::VHS_BURST_AMPLITUDE_PX,
|
||||
.flash_ms = Defaults::UI::Logo::Scan::FLASH_MS,
|
||||
},
|
||||
logo_timeline_{
|
||||
.black_intro_ms = Defaults::UI::Logo::Timeline::BLACK_INTRO_MS,
|
||||
.hold_silent_ms = Defaults::UI::Logo::Timeline::HOLD_SILENT_MS,
|
||||
.hold_with_music_ms = Defaults::UI::Logo::Timeline::HOLD_WITH_MUSIC_MS,
|
||||
.fade_out_ms = Defaults::UI::Logo::Timeline::FADE_OUT_MS,
|
||||
.black_outro_ms = Defaults::UI::Logo::Timeline::BLACK_OUTRO_MS,
|
||||
},
|
||||
menu_select_sound_(Defaults::UI::MENU_SELECT_SOUND),
|
||||
menu_accept_sound_(Defaults::UI::MENU_ACCEPT_SOUND),
|
||||
player_start_sound_(Defaults::UI::PLAYER_START_SOUND),
|
||||
@@ -301,6 +309,7 @@ void UiConfig::load(const std::string& file_path) {
|
||||
if (l.contains("scan")) {
|
||||
const auto& s = l["scan"];
|
||||
readField(s, "duration_ms", logo_scan_.duration_ms);
|
||||
readField(s, "tail_glitch_ms", logo_scan_.tail_glitch_ms);
|
||||
readField(s, "band_height_px", logo_scan_.band_height_px);
|
||||
readField(s, "band_alpha", logo_scan_.band_alpha);
|
||||
readField(s, "band_pulse_hz", logo_scan_.band_pulse_hz);
|
||||
@@ -313,6 +322,14 @@ void UiConfig::load(const std::string& file_path) {
|
||||
readField(s, "vhs_burst_amplitude_px", logo_scan_.vhs_burst_amplitude_px);
|
||||
readField(s, "flash_ms", logo_scan_.flash_ms);
|
||||
}
|
||||
if (l.contains("timeline")) {
|
||||
const auto& tl = l["timeline"];
|
||||
readField(tl, "black_intro_ms", logo_timeline_.black_intro_ms);
|
||||
readField(tl, "hold_silent_ms", logo_timeline_.hold_silent_ms);
|
||||
readField(tl, "hold_with_music_ms", logo_timeline_.hold_with_music_ms);
|
||||
readField(tl, "fade_out_ms", logo_timeline_.fade_out_ms);
|
||||
readField(tl, "black_outro_ms", logo_timeline_.black_outro_ms);
|
||||
}
|
||||
}
|
||||
|
||||
if (yaml.contains("notifier")) {
|
||||
|
||||
@@ -124,9 +124,12 @@ class UiConfig {
|
||||
[[nodiscard]] auto titleHeatHaze() const -> const TitleHeatHaze& { return title_heat_haze_; }
|
||||
|
||||
// Escena LOGO — paràmetres del scanline reveal del logo de l'editora.
|
||||
// Tot s'aplica només a la fase SCAN (substitueix l'antic FADE_IN).
|
||||
// `duration_ms` és la durada de la línia descendent; `tail_glitch_ms`
|
||||
// és el temps post-flash en què el logo queda totalment visible
|
||||
// però els bursts VHS encara hi salten (glitch tail).
|
||||
struct LogoScan {
|
||||
int duration_ms;
|
||||
int tail_glitch_ms;
|
||||
int band_height_px;
|
||||
int band_alpha;
|
||||
float band_pulse_hz;
|
||||
@@ -142,6 +145,19 @@ class UiConfig {
|
||||
|
||||
[[nodiscard]] auto logoScan() const -> const LogoScan& { return logo_scan_; }
|
||||
|
||||
// Timeline lineal de la LogoScene. Cada camp és la durada (ms) d'una
|
||||
// fase. La SCAN no apareix aquí perquè la marca el propi
|
||||
// `LogoScan` (duration_ms + tail_glitch_ms + flash_ms).
|
||||
struct LogoTimeline {
|
||||
int black_intro_ms;
|
||||
int hold_silent_ms;
|
||||
int hold_with_music_ms;
|
||||
int fade_out_ms;
|
||||
int black_outro_ms;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto logoTimeline() const -> const LogoTimeline& { return logo_timeline_; }
|
||||
|
||||
// Corbes d'easing triades al YAML. Si el nom no és conegut, s'usa
|
||||
// Easing::linear com a fallback.
|
||||
[[nodiscard]] auto pauseEnterCurve() const -> CurveFn { return pause_enter_curve_; }
|
||||
@@ -229,6 +245,7 @@ class UiConfig {
|
||||
TitleIntro title_intro_;
|
||||
TitleHeatHaze title_heat_haze_;
|
||||
LogoScan logo_scan_;
|
||||
LogoTimeline logo_timeline_;
|
||||
|
||||
std::string menu_select_sound_;
|
||||
std::string menu_accept_sound_;
|
||||
|
||||
Reference in new issue
Block a user