feat(transitions): mosaic dissolve fade-out al final de la fase STARTING (configurable a ui.yaml)
This commit is contained in:
1 parent
ca64115bfe
commit
0618452224
9 files changed
+173
-8
No files matched your search
@@ -0,0 +1,71 @@
|
||||
#include "core/rendering/transitions/mosaic.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
#include "core/rendering/screen.hpp"
|
||||
|
||||
namespace Transitions {
|
||||
|
||||
Mosaic::Mosaic(Params params)
|
||||
: params_(params) {}
|
||||
|
||||
void Mosaic::begin(int duration_ms) {
|
||||
duration_ms_ = duration_ms;
|
||||
elapsed_ms_ = 0;
|
||||
}
|
||||
|
||||
void Mosaic::tick(int delta_ms) {
|
||||
if (duration_ms_ <= 0) { return; }
|
||||
elapsed_ms_ = std::min(elapsed_ms_ + delta_ms, duration_ms_);
|
||||
}
|
||||
|
||||
auto Mosaic::progress() const -> float {
|
||||
if (duration_ms_ <= 0) { return 0.0F; }
|
||||
return std::clamp(static_cast<float>(elapsed_ms_) / static_cast<float>(duration_ms_), 0.0F, 1.0F);
|
||||
}
|
||||
|
||||
void Mosaic::render() const {
|
||||
if (duration_ms_ <= 0) { return; }
|
||||
const float P = progress();
|
||||
const int MAX_BLOCK = std::max(1, params_.max_block_size);
|
||||
// Blocs de mida creixent al llarg del fade. A p=0 → 1 px (sense
|
||||
// efecte visible); a p=1 → max_block. Es fa cap-amunt amb ceil
|
||||
// perquè el primer increment arribi aviat (el salt visual de
|
||||
// 1→2 ja es nota).
|
||||
const int BLOCK = std::clamp(static_cast<int>(std::ceil(static_cast<float>(MAX_BLOCK) * P)), 1, MAX_BLOCK);
|
||||
|
||||
auto& screen = Screen::get();
|
||||
std::uint32_t* canvas = screen.getCanvasArgb();
|
||||
const int W = screen.getCanvasWidth();
|
||||
const int H = screen.getCanvasHeight();
|
||||
|
||||
if (BLOCK > 1) {
|
||||
// Sample-and-fill in-place. Per a cada bloc, mostregem el
|
||||
// píxel central (les vores poden caure fora del canvas en
|
||||
// blocs de la última columna/fila → clamp).
|
||||
for (int by = 0; by < H; by += BLOCK) {
|
||||
const int Y_END = std::min(by + BLOCK, H);
|
||||
const int SY = std::min(by + (BLOCK / 2), H - 1);
|
||||
for (int bx = 0; bx < W; bx += BLOCK) {
|
||||
const int X_END = std::min(bx + BLOCK, W);
|
||||
const int SX = std::min(bx + (BLOCK / 2), W - 1);
|
||||
const std::uint32_t COLOR = canvas[(SY * W) + SX];
|
||||
for (int yy = by; yy < Y_END; ++yy) {
|
||||
std::uint32_t* row = canvas + (static_cast<std::ptrdiff_t>(yy) * W);
|
||||
std::fill(row + bx, row + X_END, COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fade-to-black quadràtic per damunt: la mascara fosca arriba
|
||||
// tard (a p=0.5 només 25% d'alpha) perquè es vegin els blocs.
|
||||
const auto ALPHA = static_cast<std::uint8_t>(std::round(255.0F * P * P));
|
||||
if (ALPHA > 0) {
|
||||
screen.blendRect(0, 0, W, H, 0x00000000U, ALPHA);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Transitions
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
namespace Transitions {
|
||||
|
||||
// Fade-out estil "mosaic dissolve": al llarg del temps els píxels del
|
||||
// canvas es reagrupen en blocs cada cop més grans (1×1 → max_block_size×
|
||||
// max_block_size). Cada bloc es pinta amb el píxel central del què
|
||||
// tenia abans, donant aquell aspecte chunky d'arcade. Al damunt
|
||||
// s'aplica un fade-to-black quadràtic (alpha = p² × 255) perquè el
|
||||
// resultat acabi en negre opac.
|
||||
//
|
||||
// La classe NO sap res del Screen abans de `render()`. El caller hi
|
||||
// crida després de pintar la seva escena: l'efecte llegeix els
|
||||
// píxels actuals del canvas, els rebina i escriu pixels modificats
|
||||
// al mateix canvas. És, per tant, un post-procés in-place.
|
||||
class Mosaic {
|
||||
public:
|
||||
struct Params {
|
||||
int max_block_size;
|
||||
};
|
||||
|
||||
explicit Mosaic(Params params);
|
||||
|
||||
// Arrenca un fade nou. Si ja n'hi havia un actiu, el reemplaça.
|
||||
// `duration_ms <= 0` deixa l'efecte inactiu.
|
||||
void begin(int duration_ms);
|
||||
|
||||
void tick(int delta_ms);
|
||||
|
||||
[[nodiscard]] auto isActive() const -> bool { return duration_ms_ > 0 && elapsed_ms_ < duration_ms_; }
|
||||
[[nodiscard]] auto isFinished() const -> bool { return duration_ms_ > 0 && elapsed_ms_ >= duration_ms_; }
|
||||
[[nodiscard]] auto progress() const -> float;
|
||||
|
||||
// Aplica el mosaic + alpha negre sobre el canvas actual de Screen.
|
||||
// Cridar després del render normal de l'escena, abans de
|
||||
// `Screen::present()`.
|
||||
void render() const;
|
||||
|
||||
private:
|
||||
Params params_;
|
||||
int duration_ms_{0};
|
||||
int elapsed_ms_{0};
|
||||
};
|
||||
|
||||
} // namespace Transitions
|
||||
@@ -104,6 +104,8 @@ UiConfig::UiConfig()
|
||||
title_blink_hidden_ms_(Defaults::UI::Title::BLINK_HIDDEN_MS),
|
||||
title_starting_ms_(Defaults::UI::Title::STARTING_MS),
|
||||
title_starting_blink_speedup_(Defaults::UI::Title::STARTING_BLINK_SPEEDUP),
|
||||
title_fade_out_ms_(Defaults::UI::Title::FADE_OUT_MS),
|
||||
title_fade_out_max_block_(Defaults::UI::Title::FADE_OUT_MAX_BLOCK),
|
||||
title_heat_haze_{
|
||||
.enabled = Defaults::UI::Title::HeatHaze::ENABLED,
|
||||
.amplitude_px = Defaults::UI::Title::HeatHaze::AMPLITUDE_PX,
|
||||
@@ -228,6 +230,8 @@ void UiConfig::load(const std::string& file_path) {
|
||||
readField(t, "blink_hidden_ms", title_blink_hidden_ms_);
|
||||
readField(t, "starting_ms", title_starting_ms_);
|
||||
readField(t, "starting_blink_speedup", title_starting_blink_speedup_);
|
||||
readField(t, "fade_out_ms", title_fade_out_ms_);
|
||||
readField(t, "fade_out_max_block", title_fade_out_max_block_);
|
||||
if (t.contains("heat_haze")) {
|
||||
const auto& hh = t["heat_haze"];
|
||||
readField(hh, "enabled", title_heat_haze_.enabled);
|
||||
|
||||
@@ -97,6 +97,8 @@ class UiConfig {
|
||||
[[nodiscard]] auto titleBlinkHiddenMs() const -> int { return title_blink_hidden_ms_; }
|
||||
[[nodiscard]] auto titleStartingMs() const -> int { return title_starting_ms_; }
|
||||
[[nodiscard]] auto titleStartingBlinkSpeedup() const -> float { return title_starting_blink_speedup_; }
|
||||
[[nodiscard]] auto titleFadeOutMs() const -> int { return title_fade_out_ms_; }
|
||||
[[nodiscard]] auto titleFadeOutMaxBlock() const -> int { return title_fade_out_max_block_; }
|
||||
[[nodiscard]] auto titleHeatHaze() const -> const TitleHeatHaze& { return title_heat_haze_; }
|
||||
|
||||
// Corbes d'easing triades al YAML. Si el nom no és conegut, s'usa
|
||||
@@ -171,6 +173,8 @@ class UiConfig {
|
||||
int title_blink_hidden_ms_;
|
||||
int title_starting_ms_;
|
||||
float title_starting_blink_speedup_;
|
||||
int title_fade_out_ms_;
|
||||
int title_fade_out_max_block_;
|
||||
TitleHeatHaze title_heat_haze_;
|
||||
|
||||
std::string menu_select_sound_;
|
||||
|
||||
Reference in new issue
Block a user