#include "core/rendering/pixel_reveal.hpp" #include // Para min, ranges::all_of #include // Para iota #include // Para queue (BFS en modo ORDERED) #include // Para mt19937, shuffle #include "core/rendering/surface.hpp" // Para Surface #include "utils/utils.hpp" // Para PaletteColor // Constructor PixelReveal::PixelReveal(int width, int height, float pixels_per_second, float step_duration, int num_steps, bool reverse, RevealMode mode) : cover_surface_(std::make_shared(width, height)), reveal_order_(height), row_step_(height, 0), width_(width), height_(height), pixels_per_second_(pixels_per_second), step_duration_(step_duration), num_steps_(num_steps), reverse_(reverse), mode_(mode) { // En modo normal: empieza negro sólido (se irá revelando a transparente) // En modo inverso: empieza transparente (se irá cubriendo de negro) const auto INITIAL_COLOR = reverse_ ? static_cast(PaletteColor::TRANSPARENT) : static_cast(PaletteColor::BLACK); cover_surface_->clear(INITIAL_COLOR); if (mode_ == RevealMode::ORDERED) { // Calcula offsets por bisección BFS: 0, N/2, N/4, 3N/4, ... std::vector offsets; offsets.push_back(0); std::queue> bq; bq.emplace(0, num_steps_); while (static_cast(offsets.size()) < num_steps_) { auto [lo, hi] = bq.front(); bq.pop(); if (hi - lo <= 1) { continue; } const int MID = (lo + hi) / 2; offsets.push_back(MID); bq.emplace(lo, MID); bq.emplace(MID, hi); } // Genera el orden: para cada offset, todas las columnas col = offset, offset+N, offset+2N, ... std::vector ordered_cols; ordered_cols.reserve(width_); for (const int OFF : offsets) { for (int col = OFF; col < width_; col += num_steps_) { ordered_cols.push_back(col); } } // Todas las filas usan el mismo orden (sin aleatoriedad) for (int r = 0; r < height_; r++) { reveal_order_[r] = ordered_cols; } } else { // Modo RANDOM: orden aleatorio por fila usando la fila como semilla (reproducible) for (int r = 0; r < height_; r++) { reveal_order_[r].resize(width_); std::iota(reveal_order_[r].begin(), reveal_order_[r].end(), 0); std::mt19937 rng(static_cast(r)); std::shuffle(reveal_order_[r].begin(), reveal_order_[r].end(), rng); } } } // Actualiza el estado del revelado void PixelReveal::update(float time_active) { // NOLINT(readability-make-member-function-const) // En modo normal revela (pone transparente); en modo inverso cubre (pone negro) const auto PIXEL_COLOR = reverse_ ? static_cast(PaletteColor::BLACK) : static_cast(PaletteColor::TRANSPARENT); for (int r = 0; r < height_; r++) { const float T_START = static_cast(r) / pixels_per_second_; const float TIME_IN_ROW = time_active - T_START; if (TIME_IN_ROW < 0.0F) { continue; // Esta fila aún no ha empezado } const int STEPS = std::min(num_steps_, static_cast(TIME_IN_ROW / step_duration_)); if (STEPS > row_step_[r]) { // Procesa los píxeles de los pasos pendientes for (int step = row_step_[r]; step < STEPS; step++) { const int START_IDX = step * width_ / num_steps_; const int END_IDX = (step == num_steps_ - 1) ? width_ : (step + 1) * width_ / num_steps_; for (int idx = START_IDX; idx < END_IDX; idx++) { const int COL = reveal_order_[r][idx]; cover_surface_->putPixel(COL, r, PIXEL_COLOR); } } row_step_[r] = STEPS; } } } // Dibuja la máscara en la posición indicada void PixelReveal::render(int dst_x, int dst_y) const { cover_surface_->render(dst_x, dst_y); } // Indica si el revelado ha completado todas las filas auto PixelReveal::isComplete() const -> bool { return std::ranges::all_of(row_step_, [this](int s) -> bool { return s >= num_steps_; }); }