From a7d04d2bbc59a3ec61181181890cf442f991b3b0 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 19 Mar 2026 09:53:42 +0100 Subject: [PATCH] treballant en ending2 --- CMakeLists.txt | 1 + source/core/rendering/surface.cpp | 96 ++++++++++ source/core/rendering/surface.hpp | 8 + .../rendering/surface_dissolve_sprite.cpp | 169 ++++++++++++++++++ .../rendering/surface_dissolve_sprite.hpp | 53 ++++++ source/core/rendering/surface_sprite.cpp | 13 ++ source/core/rendering/surface_sprite.hpp | 2 + source/game/scene_manager.hpp | 2 +- source/game/scenes/ending2.cpp | 39 +--- source/game/scenes/ending2.hpp | 1 + 10 files changed, 352 insertions(+), 32 deletions(-) create mode 100644 source/core/rendering/surface_dissolve_sprite.cpp create mode 100644 source/core/rendering/surface_dissolve_sprite.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 33b3a2d..f82f3de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ set(APP_SOURCES source/core/rendering/screen.cpp source/core/rendering/surface.cpp source/core/rendering/surface_animated_sprite.cpp + source/core/rendering/surface_dissolve_sprite.cpp source/core/rendering/surface_moving_sprite.cpp source/core/rendering/surface_sprite.cpp source/core/rendering/text.cpp diff --git a/source/core/rendering/surface.cpp b/source/core/rendering/surface.cpp index 27dff12..d3d9bed 100644 --- a/source/core/rendering/surface.cpp +++ b/source/core/rendering/surface.cpp @@ -428,6 +428,102 @@ void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 tar } } +// Hash 2D estable per a dithering sense flickering +static auto pixelThreshold(int col, int row) -> float { + auto h = static_cast(col) * 2246822519U ^ static_cast(row) * 2654435761U; + h ^= (h >> 13); + h *= 1274126177U; + h ^= (h >> 16); + return static_cast(h & 0xFFFFU) / 65536.0F; +} + +// Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig) +void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect) { + const int SX = src_rect ? static_cast(src_rect->x) : 0; + const int SY = src_rect ? static_cast(src_rect->y) : 0; + const int SW = src_rect ? static_cast(src_rect->w) : static_cast(surface_data_->width); + const int SH = src_rect ? static_cast(src_rect->h) : static_cast(surface_data_->height); + + auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData(); + + for (int row = 0; row < SH; row++) { + const int SCREEN_Y = y + row; + if (SCREEN_Y < 0 || SCREEN_Y >= static_cast(surface_data_dest->height)) { + continue; + } + + float density = 0.0F; + if (SCREEN_Y < fade_h) { + density = static_cast(fade_h - SCREEN_Y) / static_cast(fade_h); + } else if (SCREEN_Y >= canvas_height - fade_h) { + density = static_cast(SCREEN_Y - (canvas_height - fade_h)) / static_cast(fade_h); + } + + for (int col = 0; col < SW; col++) { + const int SCREEN_X = x + col; + if (SCREEN_X < 0 || SCREEN_X >= static_cast(surface_data_dest->width)) { + continue; + } + + const Uint8 COLOR = surface_data_->data[(SY + row) * static_cast(surface_data_->width) + (SX + col)]; + if (static_cast(COLOR) == transparent_color_) { + continue; + } + + if (pixelThreshold(col, row) < density) { + continue; // Pixel tapat per la zona de fade + } + + surface_data_dest->data[SCREEN_X + (SCREEN_Y * static_cast(surface_data_dest->width))] = sub_palette_[COLOR]; + } + } +} + +// Idem però reemplaçant un color índex +void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, + Uint8 source_color, Uint8 target_color, + SDL_FRect* src_rect) { + const int SX = src_rect ? static_cast(src_rect->x) : 0; + const int SY = src_rect ? static_cast(src_rect->y) : 0; + const int SW = src_rect ? static_cast(src_rect->w) : static_cast(surface_data_->width); + const int SH = src_rect ? static_cast(src_rect->h) : static_cast(surface_data_->height); + + auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData(); + + for (int row = 0; row < SH; row++) { + const int SCREEN_Y = y + row; + if (SCREEN_Y < 0 || SCREEN_Y >= static_cast(surface_data_dest->height)) { + continue; + } + + float density = 0.0F; + if (SCREEN_Y < fade_h) { + density = static_cast(fade_h - SCREEN_Y) / static_cast(fade_h); + } else if (SCREEN_Y >= canvas_height - fade_h) { + density = static_cast(SCREEN_Y - (canvas_height - fade_h)) / static_cast(fade_h); + } + + for (int col = 0; col < SW; col++) { + const int SCREEN_X = x + col; + if (SCREEN_X < 0 || SCREEN_X >= static_cast(surface_data_dest->width)) { + continue; + } + + const Uint8 COLOR = surface_data_->data[(SY + row) * static_cast(surface_data_->width) + (SX + col)]; + if (static_cast(COLOR) == transparent_color_) { + continue; + } + + if (pixelThreshold(col, row) < density) { + continue; // Pixel tapat per la zona de fade + } + + const Uint8 OUT_COLOR = (COLOR == source_color) ? target_color : sub_palette_[COLOR]; + surface_data_dest->data[SCREEN_X + (SCREEN_Y * static_cast(surface_data_dest->width))] = OUT_COLOR; + } + } +} + // Vuelca la superficie a una textura void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) { if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) { diff --git a/source/core/rendering/surface.hpp b/source/core/rendering/surface.hpp index 7f96914..4118295 100644 --- a/source/core/rendering/surface.hpp +++ b/source/core/rendering/surface.hpp @@ -84,6 +84,14 @@ class Surface { // Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE); + // Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig) + void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect = nullptr); + + // Idem però reemplaçant un color índex (per a sprites sobre fons del mateix color) + void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, + Uint8 source_color, Uint8 target_color, + SDL_FRect* src_rect = nullptr); + // Establece un color en la paleta void setColor(int index, Uint32 color); diff --git a/source/core/rendering/surface_dissolve_sprite.cpp b/source/core/rendering/surface_dissolve_sprite.cpp new file mode 100644 index 0000000..e05d22d --- /dev/null +++ b/source/core/rendering/surface_dissolve_sprite.cpp @@ -0,0 +1,169 @@ +#include "core/rendering/surface_dissolve_sprite.hpp" + +#include // Para min +#include // Para uint32_t + +#include "core/rendering/surface.hpp" // Para Surface + +// Hash 2D estable per a dithering (rank aleatori per posició de píxel) +static auto pixelRank(int col, int row) -> float { + auto h = static_cast(col) * 2246822519U ^ static_cast(row) * 2654435761U; + h ^= (h >> 13); + h *= 1274126177U; + h ^= (h >> 16); + return static_cast(h & 0xFFFFU) / 65536.0F; +} + +// Rang per a un píxel tenint en compte direcció (70% direccional + 30% aleatori) +auto SurfaceDissolveSprite::computePixelRank(int col, int row, int frame_h, + DissolveDirection dir) -> float { + const float RANDOM = pixelRank(col, row); + if (dir == DissolveDirection::NONE || frame_h <= 0) { + return RANDOM; + } + + float y_factor = 0.0F; + if (dir == DissolveDirection::DOWN) { + y_factor = static_cast(row) / static_cast(frame_h); + } else { + y_factor = static_cast(frame_h - 1 - row) / static_cast(frame_h); + } + + return y_factor * 0.7F + RANDOM * 0.3F; +} + +// Constructor +SurfaceDissolveSprite::SurfaceDissolveSprite(const AnimationResource& data) + : SurfaceAnimatedSprite(data) { + if (surface_) { + const int W = static_cast(surface_->getWidth()); + const int H = static_cast(surface_->getHeight()); + surface_display_ = std::make_shared(W, H); + surface_display_->setTransparentColor(surface_->getTransparentColor()); + // Inicialitza tots els píxels com a transparents + surface_display_->clear(surface_->getTransparentColor()); + } +} + +// Reconstrueix la surface_display_ filtrant píxels per progress_ +void SurfaceDissolveSprite::rebuildDisplaySurface() { + if (!surface_ || !surface_display_) { + return; + } + + const SDL_FRect CLIP = clip_; + const int SX = static_cast(CLIP.x); + const int SY = static_cast(CLIP.y); + const int SW = static_cast(CLIP.w); + const int SH = static_cast(CLIP.h); + + if (SW <= 0 || SH <= 0) { + return; + } + + auto src_data = surface_->getSurfaceData(); + auto dst_data = surface_display_->getSurfaceData(); + + const int SRC_W = static_cast(src_data->width); + const int DST_W = static_cast(dst_data->width); + const Uint8 TRANSPARENT = surface_->getTransparentColor(); + + // Esborra frame anterior si ha canviat + if (prev_clip_.w > 0 && prev_clip_.h > 0 && + (prev_clip_.x != CLIP.x || prev_clip_.y != CLIP.y || + prev_clip_.w != CLIP.w || prev_clip_.h != CLIP.h)) { + surface_display_->fillRect(&prev_clip_, TRANSPARENT); + } + + // Esborra la zona del frame actual (reconstrucció neta) + surface_display_->fillRect(&CLIP, TRANSPARENT); + + // Copia píxels filtrats per progress_ + for (int row = 0; row < SH; ++row) { + for (int col = 0; col < SW; ++col) { + const Uint8 COLOR = src_data->data[(SY + row) * SRC_W + (SX + col)]; + if (COLOR == TRANSPARENT) { + continue; + } + const float RANK = computePixelRank(col, row, SH, direction_); + if (RANK >= progress_) { + dst_data->data[(SY + row) * DST_W + (SX + col)] = COLOR; + } + } + } + + prev_clip_ = CLIP; + needs_rebuild_ = false; +} + +// Actualitza animació, moviment i transició temporal +void SurfaceDissolveSprite::update(float delta_time) { + const SDL_FRect OLD_CLIP = clip_; + SurfaceAnimatedSprite::update(delta_time); + + // Detecta canvi de frame d'animació + if (clip_.x != OLD_CLIP.x || clip_.y != OLD_CLIP.y || + clip_.w != OLD_CLIP.w || clip_.h != OLD_CLIP.h) { + needs_rebuild_ = true; + } + + // Actualitza transició temporal si activa + if (transition_mode_ != TransitionMode::NONE) { + transition_elapsed_ += delta_time * 1000.0F; + const float T = std::min(transition_elapsed_ / transition_duration_, 1.0F); + progress_ = (transition_mode_ == TransitionMode::DISSOLVING) ? T : (1.0F - T); + needs_rebuild_ = true; + if (T >= 1.0F) { + transition_mode_ = TransitionMode::NONE; + } + } + + if (needs_rebuild_) { + rebuildDisplaySurface(); + } +} + +// Renderitza: usa surface_display_ si hi ha dissolució activa +void SurfaceDissolveSprite::render() { + if (progress_ <= 0.0F || !surface_display_) { + SurfaceAnimatedSprite::render(); + return; + } + surface_display_->render(static_cast(pos_.x), static_cast(pos_.y), &clip_, flip_); +} + +// Estableix el progrés manualment +void SurfaceDissolveSprite::setProgress(float progress) { + progress_ = std::min(std::max(progress, 0.0F), 1.0F); + needs_rebuild_ = true; +} + +// Inicia dissolució temporal (visible → invisible) +void SurfaceDissolveSprite::startDissolve(float duration_ms, DissolveDirection dir) { + direction_ = dir; + transition_mode_ = TransitionMode::DISSOLVING; + transition_duration_ = duration_ms; + transition_elapsed_ = 0.0F; + progress_ = 0.0F; + needs_rebuild_ = true; +} + +// Inicia generació temporal (invisible → visible) +void SurfaceDissolveSprite::startGenerate(float duration_ms, DissolveDirection dir) { + direction_ = dir; + transition_mode_ = TransitionMode::GENERATING; + transition_duration_ = duration_ms; + transition_elapsed_ = 0.0F; + progress_ = 1.0F; + needs_rebuild_ = true; +} + +// Atura la transició temporal +void SurfaceDissolveSprite::stopTransition() { + transition_mode_ = TransitionMode::NONE; +} + +// Retorna si la transició ha acabat +auto SurfaceDissolveSprite::isTransitionDone() const -> bool { + return transition_mode_ == TransitionMode::NONE; +} diff --git a/source/core/rendering/surface_dissolve_sprite.hpp b/source/core/rendering/surface_dissolve_sprite.hpp new file mode 100644 index 0000000..48b65ea --- /dev/null +++ b/source/core/rendering/surface_dissolve_sprite.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include // Para shared_ptr + +#include "core/rendering/surface_animated_sprite.hpp" // Para SurfaceAnimatedSprite + +class Surface; + +// Direcció de la dissolució +enum class DissolveDirection { NONE, DOWN, UP }; + +// Sprite que pot dissoldre's o generar-se de forma aleatòria en X mil·lisegons. +// progress_ va de 0.0 (totalment visible) a 1.0 (totalment invisible). +class SurfaceDissolveSprite : public SurfaceAnimatedSprite { + public: + explicit SurfaceDissolveSprite(const AnimationResource& data); + ~SurfaceDissolveSprite() override = default; + + void update(float delta_time) override; + void render() override; + + // Progrés manual [0.0 = totalment visible, 1.0 = totalment invisible] + void setProgress(float progress); + [[nodiscard]] auto getProgress() const -> float { return progress_; } + + // Inicia una dissolució temporal (visible → invisible en duration_ms) + void startDissolve(float duration_ms, DissolveDirection dir = DissolveDirection::NONE); + + // Inicia una generació temporal (invisible → visible en duration_ms) + void startGenerate(float duration_ms, DissolveDirection dir = DissolveDirection::NONE); + + void stopTransition(); + [[nodiscard]] auto isTransitionDone() const -> bool; + + private: + enum class TransitionMode { NONE, DISSOLVING, GENERATING }; + + std::shared_ptr surface_display_; // Superfície amb els píxels filtrats + + float progress_{0.0F}; // [0=visible, 1=invisible] + DissolveDirection direction_{DissolveDirection::NONE}; + TransitionMode transition_mode_{TransitionMode::NONE}; + float transition_duration_{0.0F}; + float transition_elapsed_{0.0F}; + SDL_FRect prev_clip_{0, 0, 0, 0}; + bool needs_rebuild_{false}; + + void rebuildDisplaySurface(); + [[nodiscard]] static auto computePixelRank(int col, int row, int frame_h, + DissolveDirection dir) -> float; +}; diff --git a/source/core/rendering/surface_sprite.cpp b/source/core/rendering/surface_sprite.cpp index d96b84e..f882ca3 100644 --- a/source/core/rendering/surface_sprite.cpp +++ b/source/core/rendering/surface_sprite.cpp @@ -31,6 +31,19 @@ void SurfaceSprite::render(Uint8 source_color, Uint8 target_color) { surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_); } +void SurfaceSprite::renderWithVerticalFade(int fade_h, int canvas_height) { + surface_->renderWithVerticalFade( + static_cast(pos_.x), static_cast(pos_.y), + fade_h, canvas_height, &clip_); +} + +void SurfaceSprite::renderWithVerticalFade(int fade_h, int canvas_height, + Uint8 source_color, Uint8 target_color) { + surface_->renderWithVerticalFade( + static_cast(pos_.x), static_cast(pos_.y), + fade_h, canvas_height, source_color, target_color, &clip_); +} + // Establece la posición del objeto void SurfaceSprite::setPosition(float x, float y) { pos_.x = x; diff --git a/source/core/rendering/surface_sprite.hpp b/source/core/rendering/surface_sprite.hpp index 8cdad48..26d495a 100644 --- a/source/core/rendering/surface_sprite.hpp +++ b/source/core/rendering/surface_sprite.hpp @@ -22,6 +22,8 @@ class SurfaceSprite { virtual void update(float delta_time); // Actualiza el estado del sprite (time-based) virtual void render(); // Muestra el sprite por pantalla virtual void render(Uint8 source_color, Uint8 target_color); // Renderiza con reemplazo de color + virtual void renderWithVerticalFade(int fade_h, int canvas_height); // Renderiza amb dissolució vertical (hash 2D, sense parpelleig) + virtual void renderWithVerticalFade(int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color); // Idem amb reemplaç de color // Gestión de estado virtual void clear(); // Reinicia las variables a cero diff --git a/source/game/scene_manager.hpp b/source/game/scene_manager.hpp index f5f3dfb..d54a8d2 100644 --- a/source/game/scene_manager.hpp +++ b/source/game/scene_manager.hpp @@ -34,7 +34,7 @@ enum class Options { // --- Variables de estado globales --- #ifdef _DEBUG -inline Scene current = Scene::CREDITS; // Escena actual +inline Scene current = Scene::ENDING2; // Escena actual inline Options options = Options::LOGO_TO_LOADING_SCREEN; // Opciones de la escena actual #else inline Scene current = Scene::LOGO; // Escena actual diff --git a/source/game/scenes/ending2.cpp b/source/game/scenes/ending2.cpp index 99e2f73..908387d 100644 --- a/source/game/scenes/ending2.cpp +++ b/source/game/scenes/ending2.cpp @@ -90,27 +90,6 @@ void Ending2::render() { // Dibuja los sprites con el texto del final renderTexts(); - // Dibuja una trama arriba y abajo - auto color = static_cast(PaletteColor::BLACK); - auto surface = Screen::get()->getRendererSurface(); - for (int i = 0; i < 256; i += 2) { - surface->putPixel(i + 0, 0, color); - surface->putPixel(i + 1, 1, color); - surface->putPixel(i + 0, 2, color); - surface->putPixel(i + 1, 3, color); - - surface->putPixel(i, 4, color); - surface->putPixel(i, 6, color); - - surface->putPixel(i + 0, 191, color); - surface->putPixel(i + 1, 190, color); - surface->putPixel(i + 0, 189, color); - surface->putPixel(i + 1, 188, color); - - surface->putPixel(i, 187, color); - surface->putPixel(i, 185, color); - } - // Vuelca el contenido del renderizador en pantalla Screen::get()->render(); } @@ -317,28 +296,26 @@ void Ending2::updateTexts(float delta) { // Dibuja los sprites void Ending2::renderSprites() { - const auto COLOR_A = static_cast(PaletteColor::RED); - for (const auto& sprite : sprites_) { + for (size_t i = 0; i < sprites_.size(); ++i) { + const auto& sprite = sprites_[i]; const bool A = sprite->getRect().y + sprite->getRect().h > 0; const bool B = sprite->getRect().y < Options::game.height; if (A && B) { - sprite->render(1, COLOR_A); + const Uint8 COLOR = colors_[i % colors_.size()]; + sprite->renderWithVerticalFade(FADE_H, Options::game.height, 1, COLOR); } } - - // Pinta el ultimo elemento de otro color - const auto COLOR_B = static_cast(PaletteColor::WHITE); - sprites_.back()->render(1, COLOR_B); } // Dibuja los sprites con el texto void Ending2::renderSpriteTexts() { - const auto COLOR = static_cast(PaletteColor::WHITE); - for (const auto& sprite : sprite_texts_) { + for (size_t i = 0; i < sprite_texts_.size(); ++i) { + const auto& sprite = sprite_texts_[i]; const bool A = sprite->getRect().y + sprite->getRect().h > 0; const bool B = sprite->getRect().y < Options::game.height; if (A && B) { - sprite->render(1, COLOR); + const Uint8 COLOR = colors_[i % colors_.size()]; + sprite->renderWithVerticalFade(FADE_H, Options::game.height, 1, COLOR); } } } diff --git a/source/game/scenes/ending2.hpp b/source/game/scenes/ending2.hpp index 1213503..77c97a0 100644 --- a/source/game/scenes/ending2.hpp +++ b/source/game/scenes/ending2.hpp @@ -42,6 +42,7 @@ class Ending2 { static constexpr int DIST_SPRITE_SPRITE = 0; // Distancia entre dos sprites de la misma columna static constexpr int INITIAL_Y_OFFSET = 40; // Offset inicial en Y para posicionar sprites static constexpr int SCREEN_MESH_HEIGHT = 8; // Altura de la malla superior/inferior de la pantalla + static constexpr int FADE_H = 24; // Alçada de la zona de dissolució als cantons (files) static constexpr int TEXT_SPACING_MULTIPLIER = 2; // Multiplicador para espaciado entre líneas de texto // Constantes de tiempo (basadas en tiempo real, no en frames)