reestructuració
This commit is contained in:
575
source/core/rendering/background.cpp
Normal file
575
source/core/rendering/background.cpp
Normal file
@@ -0,0 +1,575 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "background.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_SetRenderTarget, SDL_CreateTexture, SDL_DestroyTexture, SDL_GetRenderTarget, SDL_RenderTexture, SDL_SetTextureAlphaMod, SDL_SetTextureBlendMode, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_RenderClear, SDL_SetRenderDrawColor, SDL_TextureAccess, SDL_FPoint
|
||||
|
||||
#include <algorithm> // Para clamp, min, max
|
||||
#include <cmath> // Para M_PI, cos, sin
|
||||
#include <string> // Para basic_string
|
||||
#include <utility> // Para move
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "moving_sprite.hpp" // Para MovingSprite
|
||||
#include "param.hpp" // Para Param, ParamBackground, param
|
||||
#include "resource.hpp" // Para Resource
|
||||
#include "screen.hpp" // Para Screen
|
||||
#include "sprite.hpp" // Para Sprite
|
||||
#include "texture.hpp" // Para Texture
|
||||
#include "utils.hpp" // Para easeOutCubic
|
||||
|
||||
// Constructor
|
||||
Background::Background(float total_progress_to_complete)
|
||||
: renderer_(Screen::get()->getRenderer()),
|
||||
|
||||
buildings_texture_(Resource::get()->getTexture("game_buildings.png")),
|
||||
top_clouds_texture_(Resource::get()->getTexture("game_clouds1.png")),
|
||||
bottom_clouds_texture_(Resource::get()->getTexture("game_clouds2.png")),
|
||||
gradients_texture_(Resource::get()->getTexture("game_sky_colors.png")),
|
||||
sun_texture_(Resource::get()->getTexture("game_sun.png")),
|
||||
moon_texture_(Resource::get()->getTexture("game_moon.png")),
|
||||
grass_sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("game_grass.png"), Resource::get()->getAnimation("game_grass.ani"))),
|
||||
|
||||
total_progress_to_complete_(total_progress_to_complete),
|
||||
progress_per_stage_(total_progress_to_complete_ / STAGES),
|
||||
sun_completion_progress_(total_progress_to_complete_ * SUN_COMPLETION_FACTOR),
|
||||
minimum_completed_progress_(total_progress_to_complete_ * MINIMUM_COMPLETED_PROGRESS_PERCENTAGE),
|
||||
|
||||
rect_(SDL_FRect{.x = 0, .y = 0, .w = static_cast<float>(gradients_texture_->getWidth() / 2), .h = static_cast<float>(gradients_texture_->getHeight() / 2)}),
|
||||
src_rect_({.x = 0, .y = 0, .w = 320, .h = 240}),
|
||||
dst_rect_({.x = 0, .y = 0, .w = 320, .h = 240}),
|
||||
attenuate_color_(Color(param.background.attenuate_color.r, param.background.attenuate_color.g, param.background.attenuate_color.b)),
|
||||
|
||||
alpha_color_texture_(param.background.attenuate_color.a),
|
||||
previous_alpha_color_texture_(param.background.attenuate_color.a),
|
||||
base_(rect_.h) {
|
||||
initializePaths();
|
||||
initializeRects();
|
||||
initializeSprites();
|
||||
initializeSpriteProperties();
|
||||
initializeTextures();
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Background::~Background() {
|
||||
SDL_DestroyTexture(canvas_);
|
||||
SDL_DestroyTexture(color_texture_);
|
||||
}
|
||||
|
||||
// Inicializa las rutas del sol y la luna
|
||||
void Background::initializePaths() {
|
||||
createSunPath();
|
||||
createMoonPath();
|
||||
}
|
||||
|
||||
// Inicializa los rectángulos de gradientes y nubes
|
||||
void Background::initializeRects() {
|
||||
gradient_rect_[0] = {.x = 0, .y = 0, .w = rect_.w, .h = rect_.h};
|
||||
gradient_rect_[1] = {.x = rect_.w, .y = 0, .w = rect_.w, .h = rect_.h};
|
||||
gradient_rect_[2] = {.x = 0, .y = rect_.h, .w = rect_.w, .h = rect_.h};
|
||||
gradient_rect_[3] = {.x = rect_.w, .y = rect_.h, .w = rect_.w, .h = rect_.h};
|
||||
|
||||
const float TOP_CLOUDS_TEXTURE_HEIGHT = top_clouds_texture_->getHeight() / 4;
|
||||
const float BOTTOM_CLOUDS_TEXTURE_HEIGHT = bottom_clouds_texture_->getHeight() / 4;
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
top_clouds_rect_[i] = {.x = 0, .y = i * TOP_CLOUDS_TEXTURE_HEIGHT, .w = static_cast<float>(top_clouds_texture_->getWidth()), .h = TOP_CLOUDS_TEXTURE_HEIGHT};
|
||||
bottom_clouds_rect_[i] = {.x = 0, .y = i * BOTTOM_CLOUDS_TEXTURE_HEIGHT, .w = static_cast<float>(bottom_clouds_texture_->getWidth()), .h = BOTTOM_CLOUDS_TEXTURE_HEIGHT};
|
||||
}
|
||||
}
|
||||
|
||||
// Crea los sprites
|
||||
void Background::initializeSprites() {
|
||||
const float TOP_CLOUDS_Y = base_ - 165;
|
||||
const float BOTTOM_CLOUDS_Y = base_ - 101;
|
||||
|
||||
top_clouds_sprite_a_ = std::make_unique<MovingSprite>(top_clouds_texture_, (SDL_FRect){.x = 0, .y = TOP_CLOUDS_Y, .w = rect_.w, .h = static_cast<float>(top_clouds_texture_->getHeight())});
|
||||
top_clouds_sprite_b_ = std::make_unique<MovingSprite>(top_clouds_texture_, (SDL_FRect){.x = rect_.w, .y = TOP_CLOUDS_Y, .w = rect_.w, .h = static_cast<float>(top_clouds_texture_->getHeight())});
|
||||
|
||||
bottom_clouds_sprite_a_ = std::make_unique<MovingSprite>(bottom_clouds_texture_, (SDL_FRect){.x = 0, .y = BOTTOM_CLOUDS_Y, .w = rect_.w, .h = static_cast<float>(bottom_clouds_texture_->getHeight())});
|
||||
bottom_clouds_sprite_b_ = std::make_unique<MovingSprite>(bottom_clouds_texture_, (SDL_FRect){.x = rect_.w, .y = BOTTOM_CLOUDS_Y, .w = rect_.w, .h = static_cast<float>(bottom_clouds_texture_->getHeight())});
|
||||
|
||||
buildings_sprite_ = std::make_unique<Sprite>(buildings_texture_);
|
||||
gradient_sprite_ = std::make_unique<Sprite>(gradients_texture_, 0, 0, rect_.w, rect_.h);
|
||||
sun_sprite_ = std::make_unique<Sprite>(sun_texture_);
|
||||
moon_sprite_ = std::make_unique<Sprite>(moon_texture_);
|
||||
}
|
||||
|
||||
// Configura las propiedades iniciales de los sprites
|
||||
void Background::initializeSpriteProperties() {
|
||||
// Velocidades iniciales que coinciden con updateCloudsSpeed() cuando progress=0
|
||||
constexpr float INITIAL_TOP_CLOUDS_SPEED_PX_PER_S = 0.05F * 60.0F; // 3.0 píxeles/segundo (coincide con CLOUDS_INITIAL_SPEED)
|
||||
constexpr float INITIAL_BOTTOM_CLOUDS_SPEED_PX_PER_S = 0.05F * 60.0F / 2.0F; // 1.5 píxeles/segundo (mitad de velocidad)
|
||||
|
||||
top_clouds_sprite_a_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight());
|
||||
top_clouds_sprite_a_->setVelX(-INITIAL_TOP_CLOUDS_SPEED_PX_PER_S);
|
||||
|
||||
top_clouds_sprite_b_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight());
|
||||
top_clouds_sprite_b_->setVelX(-INITIAL_TOP_CLOUDS_SPEED_PX_PER_S);
|
||||
|
||||
bottom_clouds_sprite_a_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight());
|
||||
bottom_clouds_sprite_a_->setVelX(-INITIAL_BOTTOM_CLOUDS_SPEED_PX_PER_S);
|
||||
|
||||
bottom_clouds_sprite_b_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight());
|
||||
bottom_clouds_sprite_b_->setVelX(-INITIAL_BOTTOM_CLOUDS_SPEED_PX_PER_S);
|
||||
|
||||
// grass_sprite_->setY(base_ - grass_sprite_->getHeight());
|
||||
// grass_sprite_->resetAnimation();
|
||||
grass_sprite_->setPos(0.0F, base_ - 10.0F);
|
||||
grass_sprite_->setWidth(320.0F);
|
||||
grass_sprite_->setHeight(10.0F);
|
||||
// grass_sprite_->setCurrentAnimation(0);
|
||||
|
||||
buildings_sprite_->setY(base_ - buildings_sprite_->getHeight());
|
||||
sun_sprite_->setPosition(sun_path_.front());
|
||||
moon_sprite_->setPosition(moon_path_.front());
|
||||
}
|
||||
|
||||
// Inicializa las texturas de renderizado
|
||||
void Background::initializeTextures() {
|
||||
canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, rect_.w, rect_.h);
|
||||
SDL_SetTextureBlendMode(canvas_, SDL_BLENDMODE_BLEND);
|
||||
|
||||
color_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, rect_.w, rect_.h);
|
||||
SDL_SetTextureBlendMode(color_texture_, SDL_BLENDMODE_BLEND);
|
||||
setColor(attenuate_color_);
|
||||
SDL_SetTextureAlphaMod(color_texture_, alpha_color_texture_);
|
||||
}
|
||||
|
||||
// Actualiza la lógica del objeto
|
||||
void Background::update(float delta_time) {
|
||||
// Actualiza la progresión y calcula transiciones
|
||||
if (!manual_mode_) {
|
||||
updateProgression(delta_time);
|
||||
}
|
||||
|
||||
// Actualiza el valor de alpha
|
||||
updateAlphaColorTexture(delta_time);
|
||||
|
||||
// Actualiza las nubes
|
||||
updateClouds(delta_time);
|
||||
|
||||
// Actualiza el sprite con la hierba
|
||||
grass_sprite_->update(delta_time);
|
||||
|
||||
// Calcula el valor de alpha
|
||||
alpha_ = std::max((255 - static_cast<int>(255 * transition_)), 0);
|
||||
|
||||
// Mueve el sol y la luna según la progresión
|
||||
sun_sprite_->setPosition(sun_path_.at(sun_index_));
|
||||
moon_sprite_->setPosition(moon_path_.at(moon_index_));
|
||||
|
||||
// Compone todos los elementos del fondo en la textura
|
||||
fillCanvas();
|
||||
}
|
||||
|
||||
// Incrementa la progresión interna
|
||||
void Background::incrementProgress(float amount) {
|
||||
if (state_ == State::NORMAL) {
|
||||
float old_progress = progress_;
|
||||
progress_ += amount;
|
||||
progress_ = std::min(progress_, total_progress_to_complete_);
|
||||
|
||||
// Notifica el cambio si hay callback y el progreso cambió
|
||||
if (progress_callback_ && progress_ != old_progress) {
|
||||
progress_callback_(progress_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Establece la progresión absoluta
|
||||
void Background::setProgress(float absolute_progress) {
|
||||
float old_progress = progress_;
|
||||
progress_ = std::clamp(absolute_progress, 0.0F, total_progress_to_complete_);
|
||||
|
||||
// Notifica el cambio si hay callback y el progreso cambió
|
||||
if (progress_callback_ && progress_ != old_progress) {
|
||||
progress_callback_(progress_);
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia el estado del fondo
|
||||
void Background::setState(State new_state) {
|
||||
// Si entra en estado completado, inicializar variables de transición
|
||||
if (new_state == State::COMPLETED && state_ != State::COMPLETED) {
|
||||
completion_initial_progress_ = progress_;
|
||||
completion_transition_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
state_ = new_state;
|
||||
}
|
||||
|
||||
// Reinicia la progresión
|
||||
void Background::reset() {
|
||||
float old_progress = progress_;
|
||||
progress_ = 0.0F;
|
||||
state_ = State::NORMAL;
|
||||
manual_mode_ = false;
|
||||
gradient_number_ = 0;
|
||||
transition_ = 0.0F;
|
||||
sun_index_ = 0;
|
||||
moon_index_ = 0;
|
||||
|
||||
// Resetear variables de transición de completado
|
||||
completion_transition_timer_ = 0.0F;
|
||||
completion_initial_progress_ = 0.0F;
|
||||
|
||||
// Notifica el cambio si hay callback
|
||||
if (progress_callback_ && progress_ != old_progress) {
|
||||
progress_callback_(progress_);
|
||||
}
|
||||
}
|
||||
|
||||
// Activa/desactiva el modo manual
|
||||
void Background::setManualMode(bool manual) {
|
||||
manual_mode_ = manual;
|
||||
}
|
||||
|
||||
// Establece callback para sincronización automática
|
||||
void Background::setProgressCallback(ProgressCallback callback) {
|
||||
progress_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
// Elimina el callback
|
||||
void Background::removeProgressCallback() {
|
||||
progress_callback_ = nullptr;
|
||||
}
|
||||
|
||||
// Ajusta la velocidad de las nubes
|
||||
void Background::setCloudsSpeed(float value) {
|
||||
clouds_speed_ = value;
|
||||
|
||||
// En modo manual, aplicar la velocidad directamente
|
||||
// Las nubes inferiores van a la mitad de velocidad que las superiores
|
||||
top_clouds_sprite_a_->setVelX(value);
|
||||
top_clouds_sprite_b_->setVelX(value);
|
||||
bottom_clouds_sprite_a_->setVelX(value / 2.0F);
|
||||
bottom_clouds_sprite_b_->setVelX(value / 2.0F);
|
||||
}
|
||||
|
||||
// Establece el degradado de fondo
|
||||
void Background::setGradientNumber(int value) {
|
||||
gradient_number_ = value % STAGES;
|
||||
}
|
||||
|
||||
// Ajusta la transición entre texturas
|
||||
void Background::setTransition(float value) {
|
||||
transition_ = std::clamp(value, 0.0F, 1.0F);
|
||||
}
|
||||
|
||||
// Establece la posición del sol
|
||||
void Background::setSunProgression(float progress) {
|
||||
progress = std::clamp(progress, 0.0F, 1.0F);
|
||||
sun_index_ = static_cast<size_t>(progress * (sun_path_.size() - 1));
|
||||
}
|
||||
|
||||
// Establece la posición de la luna
|
||||
void Background::setMoonProgression(float progress) {
|
||||
progress = std::clamp(progress, 0.0F, 1.0F);
|
||||
moon_index_ = static_cast<size_t>(progress * (moon_path_.size() - 1));
|
||||
}
|
||||
|
||||
// Actualiza la progresión y calcula las transiciones
|
||||
void Background::updateProgression(float delta_time) {
|
||||
// Si el juego está completado, hacer transición suave con easing
|
||||
if (state_ == State::COMPLETED) {
|
||||
completion_transition_timer_ += delta_time;
|
||||
|
||||
// Calcular progreso normalizado de la transición (0.0 a 1.0)
|
||||
float t = std::min(completion_transition_timer_ / COMPLETION_TRANSITION_DURATION_S, 1.0F);
|
||||
|
||||
if (t < 1.0F) {
|
||||
// Usar easeOutCubic para transición suave (rápido al inicio, lento al final)
|
||||
float eased_t = easeOutCubic(static_cast<double>(t));
|
||||
|
||||
// Interpolación desde progreso inicial hasta mínimo
|
||||
float progress_range = completion_initial_progress_ - minimum_completed_progress_;
|
||||
progress_ = completion_initial_progress_ - (progress_range * eased_t);
|
||||
} else {
|
||||
// Transición completada, fijar al valor mínimo
|
||||
progress_ = minimum_completed_progress_;
|
||||
}
|
||||
}
|
||||
|
||||
// Calcula la transición de los diferentes fondos
|
||||
const float GRADIENT_NUMBER_FLOAT = std::min(progress_ / progress_per_stage_, 3.0F);
|
||||
const float PERCENT = GRADIENT_NUMBER_FLOAT - static_cast<int>(GRADIENT_NUMBER_FLOAT);
|
||||
|
||||
gradient_number_ = static_cast<size_t>(GRADIENT_NUMBER_FLOAT);
|
||||
transition_ = PERCENT;
|
||||
|
||||
// Calcula la posición del sol
|
||||
const float SUN_PROGRESSION = std::min(progress_ / sun_completion_progress_, 1.0F);
|
||||
sun_index_ = static_cast<size_t>(SUN_PROGRESSION * (sun_path_.size() - 1));
|
||||
|
||||
// Calcula la posición de la luna
|
||||
const float MOON_PROGRESSION = std::min(progress_ / total_progress_to_complete_, 1.0F);
|
||||
moon_index_ = static_cast<size_t>(MOON_PROGRESSION * (moon_path_.size() - 1));
|
||||
|
||||
// Actualiza la velocidad de las nubes
|
||||
updateCloudsSpeed();
|
||||
}
|
||||
|
||||
// Actualiza la velocidad de las nubes según el estado y progresión
|
||||
void Background::updateCloudsSpeed() {
|
||||
// Cálculo de velocidad según progreso (convertido de frame-based a time-based)
|
||||
constexpr float CLOUDS_INITIAL_SPEED_PX_PER_S = 0.05F * 60.0F; // 3.0 píxeles/segundo (era 0.05 px/frame @ 60fps)
|
||||
constexpr float CLOUDS_TOTAL_SPEED_PX_PER_S = 2.00F * 60.0F; // 120.0 píxeles/segundo (era 2.00 px/frame @ 60fps)
|
||||
constexpr float CLOUDS_FINAL_SPEED_RANGE_PX_PER_S = CLOUDS_TOTAL_SPEED_PX_PER_S - CLOUDS_INITIAL_SPEED_PX_PER_S; // 117.0 píxeles/segundo
|
||||
|
||||
// Velocidad base según progreso (de -3.0 a -120.0 píxeles/segundo, igual que la versión original)
|
||||
float base_clouds_speed = (-CLOUDS_INITIAL_SPEED_PX_PER_S) +
|
||||
(-CLOUDS_FINAL_SPEED_RANGE_PX_PER_S * (progress_ / total_progress_to_complete_));
|
||||
|
||||
// En estado completado, las nubes se ralentizan gradualmente
|
||||
if (state_ == State::COMPLETED) {
|
||||
float completion_factor = (progress_ - minimum_completed_progress_) /
|
||||
(total_progress_to_complete_ - minimum_completed_progress_);
|
||||
completion_factor = std::max(0.1F, completion_factor);
|
||||
base_clouds_speed *= completion_factor;
|
||||
}
|
||||
|
||||
// Aplicar velocidades diferentes para nubes superiores e inferiores
|
||||
const float TOP_CLOUDS_SPEED = base_clouds_speed;
|
||||
const float BOTTOM_CLOUDS_SPEED = base_clouds_speed / 2.0F;
|
||||
|
||||
// Aplicar las velocidades a los sprites correspondientes
|
||||
top_clouds_sprite_a_->setVelX(TOP_CLOUDS_SPEED);
|
||||
top_clouds_sprite_b_->setVelX(TOP_CLOUDS_SPEED);
|
||||
bottom_clouds_sprite_a_->setVelX(BOTTOM_CLOUDS_SPEED);
|
||||
bottom_clouds_sprite_b_->setVelX(BOTTOM_CLOUDS_SPEED);
|
||||
|
||||
// Guardar la velocidad principal
|
||||
clouds_speed_ = TOP_CLOUDS_SPEED;
|
||||
}
|
||||
|
||||
// Actualiza las nubes
|
||||
void Background::updateClouds(float delta_time) {
|
||||
// Mueve las nubes
|
||||
top_clouds_sprite_a_->update(delta_time);
|
||||
top_clouds_sprite_b_->update(delta_time);
|
||||
bottom_clouds_sprite_a_->update(delta_time);
|
||||
bottom_clouds_sprite_b_->update(delta_time);
|
||||
|
||||
// Calcula el offset de las nubes
|
||||
if (top_clouds_sprite_a_->getPosX() < -top_clouds_sprite_a_->getWidth()) {
|
||||
top_clouds_sprite_a_->setPosX(top_clouds_sprite_a_->getWidth());
|
||||
}
|
||||
|
||||
if (top_clouds_sprite_b_->getPosX() < -top_clouds_sprite_b_->getWidth()) {
|
||||
top_clouds_sprite_b_->setPosX(top_clouds_sprite_b_->getWidth());
|
||||
}
|
||||
|
||||
if (bottom_clouds_sprite_a_->getPosX() < -bottom_clouds_sprite_a_->getWidth()) {
|
||||
bottom_clouds_sprite_a_->setPosX(bottom_clouds_sprite_a_->getWidth());
|
||||
}
|
||||
|
||||
if (bottom_clouds_sprite_b_->getPosX() < -bottom_clouds_sprite_b_->getWidth()) {
|
||||
bottom_clouds_sprite_b_->setPosX(bottom_clouds_sprite_b_->getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja el gradiente de fondo
|
||||
void Background::renderGradient() {
|
||||
// Dibuja el gradiente de detras
|
||||
gradients_texture_->setAlpha(255);
|
||||
gradient_sprite_->setSpriteClip(gradient_rect_[(gradient_number_ + 1) % 4]);
|
||||
gradient_sprite_->render();
|
||||
|
||||
// Dibuja el gradiente de delante con una opacidad cada vez menor
|
||||
gradients_texture_->setAlpha(alpha_);
|
||||
gradient_sprite_->setSpriteClip(gradient_rect_[gradient_number_]);
|
||||
gradient_sprite_->render();
|
||||
}
|
||||
|
||||
// Dibuja las nubes de arriba
|
||||
void Background::renderTopClouds() {
|
||||
// Dibuja el primer conjunto de nubes, las de detras
|
||||
top_clouds_texture_->setAlpha(255);
|
||||
top_clouds_sprite_a_->setSpriteClip(top_clouds_rect_[(gradient_number_ + 1) % 4]);
|
||||
top_clouds_sprite_b_->setSpriteClip(top_clouds_rect_[(gradient_number_ + 1) % 4]);
|
||||
top_clouds_sprite_a_->render();
|
||||
top_clouds_sprite_b_->render();
|
||||
|
||||
// Dibuja el segundo conjunto de nubes, las de delante
|
||||
top_clouds_texture_->setAlpha(alpha_);
|
||||
top_clouds_sprite_a_->setSpriteClip(top_clouds_rect_[gradient_number_]);
|
||||
top_clouds_sprite_b_->setSpriteClip(top_clouds_rect_[gradient_number_]);
|
||||
top_clouds_sprite_a_->render();
|
||||
top_clouds_sprite_b_->render();
|
||||
}
|
||||
|
||||
// Dibuja las nubes de abajo
|
||||
void Background::renderBottomClouds() {
|
||||
// Dibuja el primer conjunto de nubes, las de detras
|
||||
bottom_clouds_texture_->setAlpha(255);
|
||||
bottom_clouds_sprite_a_->setSpriteClip(bottom_clouds_rect_[(gradient_number_ + 1) % 4]);
|
||||
bottom_clouds_sprite_b_->setSpriteClip(bottom_clouds_rect_[(gradient_number_ + 1) % 4]);
|
||||
bottom_clouds_sprite_a_->render();
|
||||
bottom_clouds_sprite_b_->render();
|
||||
|
||||
// Dibuja el segundo conjunto de nubes, las de delante
|
||||
bottom_clouds_texture_->setAlpha(alpha_);
|
||||
bottom_clouds_sprite_a_->setSpriteClip(bottom_clouds_rect_[gradient_number_]);
|
||||
bottom_clouds_sprite_b_->setSpriteClip(bottom_clouds_rect_[gradient_number_]);
|
||||
bottom_clouds_sprite_a_->render();
|
||||
bottom_clouds_sprite_b_->render();
|
||||
}
|
||||
|
||||
// Compone todos los elementos del fondo en la textura
|
||||
void Background::fillCanvas() {
|
||||
// Cambia el destino del renderizador
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, canvas_);
|
||||
|
||||
// Dibuja el gradiente de fondo
|
||||
renderGradient();
|
||||
|
||||
// Dibuja los astros
|
||||
sun_sprite_->render();
|
||||
moon_sprite_->render();
|
||||
|
||||
// Dibuja las nubes de arriba
|
||||
renderTopClouds();
|
||||
|
||||
// Dibuja las nubes de abajo
|
||||
renderBottomClouds();
|
||||
|
||||
// Dibuja los edificios
|
||||
buildings_sprite_->render();
|
||||
|
||||
// Dibuja la hierba
|
||||
grass_sprite_->render();
|
||||
|
||||
// Deja el renderizador apuntando donde estaba
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
// Dibuja el objeto
|
||||
void Background::render() {
|
||||
// Fondo
|
||||
SDL_RenderTexture(renderer_, canvas_, &src_rect_, &dst_rect_);
|
||||
|
||||
// Atenuación
|
||||
SDL_RenderTexture(renderer_, color_texture_, &src_rect_, &dst_rect_);
|
||||
}
|
||||
|
||||
// Establece la posición del objeto
|
||||
void Background::setPos(SDL_FRect pos) {
|
||||
dst_rect_ = pos;
|
||||
|
||||
// Si cambian las medidas del destino, hay que cambiar las del origen para evitar deformar la imagen
|
||||
src_rect_.x = 0;
|
||||
src_rect_.y = rect_.h - pos.h;
|
||||
src_rect_.w = pos.w;
|
||||
src_rect_.h = pos.h;
|
||||
}
|
||||
|
||||
// Establece el color de atenuación
|
||||
void Background::setColor(Color color) {
|
||||
attenuate_color_ = color;
|
||||
|
||||
// Colorea la textura
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, color_texture_);
|
||||
|
||||
SDL_SetRenderDrawColor(renderer_, attenuate_color_.r, attenuate_color_.g, attenuate_color_.b, 255);
|
||||
SDL_RenderClear(renderer_);
|
||||
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
// Establece la transparencia de la atenuación
|
||||
void Background::setAlpha(int alpha) {
|
||||
// Evita que se asignen valores fuera de rango
|
||||
alpha = std::clamp(alpha, 0, 255);
|
||||
|
||||
// Guarda el valor actual y establece el nuevo valor
|
||||
previous_alpha_color_texture_ = alpha_color_texture_;
|
||||
alpha_color_texture_ = alpha;
|
||||
}
|
||||
|
||||
// Actualiza el valor de alpha (time-based)
|
||||
void Background::updateAlphaColorTexture(float delta_time) {
|
||||
// 1. Si ya hemos llegado al destino, no hacemos nada.
|
||||
if (alpha_color_texture_ == previous_alpha_color_texture_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Define la velocidad del cambio (p. ej., 150 unidades de alfa por segundo).
|
||||
// Puedes ajustar este valor para que la transición sea más rápida o lenta.
|
||||
constexpr float ALPHA_TRANSITION_SPEED = 150.0F;
|
||||
|
||||
// 3. Determina la dirección del cambio (subir o bajar el alfa)
|
||||
if (alpha_color_texture_ > previous_alpha_color_texture_) {
|
||||
// Aumentar el alfa
|
||||
current_alpha_float_ += ALPHA_TRANSITION_SPEED * delta_time;
|
||||
// Nos aseguramos de no pasarnos del objetivo
|
||||
current_alpha_float_ = std::min(current_alpha_float_, static_cast<float>(alpha_color_texture_));
|
||||
} else {
|
||||
// Disminuir el alfa
|
||||
current_alpha_float_ -= ALPHA_TRANSITION_SPEED * delta_time;
|
||||
// Nos aseguramos de no quedarnos cortos del objetivo
|
||||
current_alpha_float_ = std::max(current_alpha_float_, static_cast<float>(alpha_color_texture_));
|
||||
}
|
||||
|
||||
// 4. Actualiza el valor entero solo si ha cambiado lo suficiente
|
||||
// Usamos std::round para un redondeo más natural.
|
||||
const auto NEW_ALPHA = static_cast<size_t>(std::round(current_alpha_float_));
|
||||
|
||||
if (NEW_ALPHA != previous_alpha_color_texture_) {
|
||||
previous_alpha_color_texture_ = NEW_ALPHA;
|
||||
// SDL espera un Uint8 (0-255), así que hacemos un cast seguro.
|
||||
SDL_SetTextureAlphaMod(color_texture_, static_cast<Uint8>(previous_alpha_color_texture_));
|
||||
}
|
||||
}
|
||||
|
||||
// Precalcula el vector con el recorrido del sol
|
||||
void Background::createSunPath() {
|
||||
constexpr float CENTER_X = 170;
|
||||
const float CENTER_Y = base_ - 80;
|
||||
constexpr float RADIUS = 120;
|
||||
|
||||
// Generar puntos de la curva desde 90 a 180 grados
|
||||
constexpr double STEP = 0.01;
|
||||
const int NUM_STEPS = static_cast<int>((M_PI - M_PI / 2) / STEP) + 1;
|
||||
|
||||
for (int i = 0; i < NUM_STEPS; ++i) {
|
||||
double theta = M_PI / 2 + (i * STEP);
|
||||
float x = CENTER_X + (RADIUS * cos(theta));
|
||||
float y = CENTER_Y - (RADIUS * sin(theta));
|
||||
sun_path_.push_back({.x = x, .y = y});
|
||||
}
|
||||
|
||||
// Agregar puntos en línea recta después de la curva
|
||||
constexpr int EXTRA_PIXELS = 40;
|
||||
SDL_FPoint last_point = sun_path_.back();
|
||||
for (int i = 1; i <= EXTRA_PIXELS; ++i) {
|
||||
sun_path_.push_back({.x = last_point.x, .y = last_point.y + i});
|
||||
}
|
||||
}
|
||||
|
||||
// Precalcula el vector con el recorrido de la luna
|
||||
void Background::createMoonPath() {
|
||||
constexpr float CENTER_X = 100;
|
||||
const float CENTER_Y = base_ - 50;
|
||||
constexpr float RADIUS = 140;
|
||||
|
||||
constexpr double STEP = 0.01;
|
||||
const int NUM_STEPS = static_cast<int>((M_PI / 2) / STEP) + 1;
|
||||
|
||||
constexpr float FREEZE_PERCENTAGE = 0.2F; // Porcentaje final del recorrido que se mantiene fijo
|
||||
|
||||
const int FREEZE_START_INDEX = static_cast<int>(NUM_STEPS * (1.0F - FREEZE_PERCENTAGE));
|
||||
|
||||
for (int i = 0; i < NUM_STEPS; ++i) {
|
||||
double theta = i * STEP;
|
||||
float x = CENTER_X + (RADIUS * cos(theta));
|
||||
float y = CENTER_Y - (RADIUS * sin(theta));
|
||||
|
||||
if (i >= FREEZE_START_INDEX && !moon_path_.empty()) {
|
||||
moon_path_.push_back(moon_path_.back()); // Repite el último punto válido
|
||||
} else {
|
||||
moon_path_.push_back({.x = x, .y = y});
|
||||
}
|
||||
}
|
||||
}
|
||||
140
source/core/rendering/background.hpp
Normal file
140
source/core/rendering/background.hpp
Normal file
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_FPoint, SDL_Texture, SDL_Renderer
|
||||
|
||||
#include <array> // Para array
|
||||
#include <cstddef> // Para size_t
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para unique_ptr, shared_ptr
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "color.hpp" // Para Color
|
||||
|
||||
class MovingSprite;
|
||||
class Sprite;
|
||||
class Texture;
|
||||
class AnimatedSprite;
|
||||
|
||||
// --- Clase Background: gestiona el fondo de la sección jugable ---
|
||||
class Background {
|
||||
public:
|
||||
// --- Enums ---
|
||||
enum class State {
|
||||
NORMAL, // Progresión normal del día
|
||||
COMPLETED // Reducción gradual de la actividad
|
||||
};
|
||||
|
||||
// --- Tipos ---
|
||||
using ProgressCallback = std::function<void(float)>; // Callback para sincronización
|
||||
|
||||
// --- Constructor y destructor ---
|
||||
Background(float total_progress_to_complete = 6100.0F); // Constructor principal
|
||||
~Background(); // Destructor
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time); // Actualiza la lógica del objeto
|
||||
void render(); // Dibuja el objeto
|
||||
void reset(); // Reinicia la progresión
|
||||
|
||||
// --- Configuración ---
|
||||
void setPos(SDL_FRect pos); // Establece la posición del objeto
|
||||
void setState(State new_state); // Cambia el estado del fondo
|
||||
void setProgressCallback(ProgressCallback callback); // Establece callback para sincronización
|
||||
void removeProgressCallback(); // Elimina el callback
|
||||
void setManualMode(bool manual); // Activa/desactiva el modo manual
|
||||
void setCloudsSpeed(float value); // Ajusta la velocidad de las nubes
|
||||
void setGradientNumber(int value); // Establece el degradado de fondo
|
||||
void setTransition(float value); // Ajusta la transición entre texturas
|
||||
void setSunProgression(float progress); // Establece la posición del sol
|
||||
void setMoonProgression(float progress); // Establece la posición de la luna
|
||||
void setColor(Color color); // Establece el color de atenuación
|
||||
void setAlpha(int alpha); // Ajusta la transparencia del fondo
|
||||
|
||||
// --- Control de progresión ---
|
||||
void incrementProgress(float amount = 1.0F); // Incrementa la progresión interna
|
||||
void setProgress(float absolute_progress); // Establece la progresión absoluta
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getProgress() const -> float { return progress_; } // Obtiene el progreso actual
|
||||
[[nodiscard]] auto getState() const -> State { return state_; } // Obtiene el estado actual
|
||||
[[nodiscard]] auto getCurrentGradient() const -> int { return static_cast<int>(gradient_number_); } // Obtiene el gradiente actual
|
||||
|
||||
private:
|
||||
// --- Constantes ---
|
||||
static constexpr size_t STAGES = 4; // Número de etapas
|
||||
static constexpr float MINIMUM_COMPLETED_PROGRESS_PERCENTAGE = 0.05F; // Porcentaje mínimo completado (10%)
|
||||
static constexpr float SUN_COMPLETION_FACTOR = 0.5F; // Factor de completado del sol
|
||||
static constexpr float COMPLETION_TRANSITION_DURATION_S = 3.0F; // Duración de la transición de completado en segundos
|
||||
|
||||
// --- Objetos y punteros ---
|
||||
SDL_Renderer* renderer_; // Renderizador de la ventana
|
||||
SDL_Texture* canvas_; // Textura para componer el fondo
|
||||
SDL_Texture* color_texture_; // Textura para atenuar el fondo
|
||||
std::shared_ptr<Texture> buildings_texture_; // Textura de edificios
|
||||
std::shared_ptr<Texture> top_clouds_texture_; // Textura de nubes superiores
|
||||
std::shared_ptr<Texture> bottom_clouds_texture_; // Textura de nubes inferiores
|
||||
std::shared_ptr<Texture> gradients_texture_; // Textura de gradientes
|
||||
std::shared_ptr<Texture> sun_texture_; // Textura del sol
|
||||
std::shared_ptr<Texture> moon_texture_; // Textura de la luna
|
||||
std::unique_ptr<MovingSprite> top_clouds_sprite_a_; // Sprite de nubes superiores A
|
||||
std::unique_ptr<MovingSprite> top_clouds_sprite_b_; // Sprite de nubes superiores B
|
||||
std::unique_ptr<MovingSprite> bottom_clouds_sprite_a_; // Sprite de nubes inferiores A
|
||||
std::unique_ptr<MovingSprite> bottom_clouds_sprite_b_; // Sprite de nubes inferiores B
|
||||
std::unique_ptr<Sprite> buildings_sprite_; // Sprite de edificios
|
||||
std::unique_ptr<Sprite> gradient_sprite_; // Sprite de gradiente
|
||||
std::unique_ptr<Sprite> sun_sprite_; // Sprite del sol
|
||||
std::unique_ptr<Sprite> moon_sprite_; // Sprite de la luna
|
||||
std::unique_ptr<AnimatedSprite> grass_sprite_; // Sprite con la hierba
|
||||
|
||||
// --- Variables de configuración ---
|
||||
const float total_progress_to_complete_; // Progreso total para completar
|
||||
const float progress_per_stage_; // Progreso por etapa
|
||||
const float sun_completion_progress_; // Progreso de completado del sol
|
||||
const float minimum_completed_progress_; // Progreso mínimo calculado dinámicamente
|
||||
ProgressCallback progress_callback_; // Callback para notificar cambios de progreso
|
||||
|
||||
// --- Variables de estado ---
|
||||
std::vector<SDL_FPoint> sun_path_; // Recorrido del sol
|
||||
std::vector<SDL_FPoint> moon_path_; // Recorrido de la luna
|
||||
std::array<SDL_FRect, STAGES> gradient_rect_; // Fondos degradados
|
||||
std::array<SDL_FRect, 4> top_clouds_rect_; // Nubes superiores
|
||||
std::array<SDL_FRect, 4> bottom_clouds_rect_; // Nubes inferiores
|
||||
SDL_FRect rect_; // Tamaño del objeto
|
||||
SDL_FRect src_rect_; // Parte del objeto para copiar en pantalla
|
||||
SDL_FRect dst_rect_; // Posición en pantalla donde se copia el objeto
|
||||
Color attenuate_color_; // Color de atenuación
|
||||
State state_ = State::NORMAL; // Estado actual
|
||||
float progress_ = 0.0F; // Progresión interna
|
||||
float clouds_speed_ = 0; // Velocidad de las nubes
|
||||
float transition_ = 0; // Porcentaje de transición
|
||||
float current_alpha_float_ = 0.0F; // Acumulador para el valor alfa preciso
|
||||
size_t gradient_number_ = 0; // Índice de fondo degradado
|
||||
size_t alpha_color_texture_ = 0; // Transparencia de atenuación
|
||||
size_t previous_alpha_color_texture_ = 0; // Transparencia anterior
|
||||
size_t sun_index_ = 0; // Índice del recorrido del sol
|
||||
size_t moon_index_ = 0; // Índice del recorrido de la luna
|
||||
int base_ = 0; // Posición base del fondo
|
||||
Uint8 alpha_ = 0; // Transparencia entre fases
|
||||
bool manual_mode_ = false; // Si está en modo manual
|
||||
|
||||
// --- Variables para transición suave de completado ---
|
||||
float completion_transition_timer_ = 0.0F; // Timer para la transición de completado
|
||||
float completion_initial_progress_ = 0.0F; // Progreso inicial al entrar en estado completado
|
||||
|
||||
// --- Métodos internos ---
|
||||
void initializePaths(); // Inicializa las rutas del sol y la luna
|
||||
void initializeRects(); // Inicializa los rectángulos de gradientes y nubes
|
||||
void initializeSprites(); // Crea los sprites
|
||||
void initializeSpriteProperties(); // Configura las propiedades iniciales de los sprites
|
||||
void initializeTextures(); // Inicializa las texturas de renderizado
|
||||
void updateProgression(float delta_time); // Actualiza la progresión y calcula transiciones
|
||||
void updateCloudsSpeed(); // Actualiza la velocidad de las nubes según el estado
|
||||
void renderGradient(); // Dibuja el gradiente de fondo
|
||||
void renderTopClouds(); // Dibuja las nubes superiores
|
||||
void renderBottomClouds(); // Dibuja las nubes inferiores
|
||||
void fillCanvas(); // Compone todos los elementos en la textura
|
||||
void updateAlphaColorTexture(float delta_time); // Actualiza el alpha de la textura de atenuación
|
||||
void updateClouds(float delta_time); // Actualiza el movimiento de las nubes (time-based)
|
||||
void createSunPath(); // Precalcula el recorrido del sol
|
||||
void createMoonPath(); // Precalcula el recorrido de la luna
|
||||
};
|
||||
551
source/core/rendering/fade.cpp
Normal file
551
source/core/rendering/fade.cpp
Normal file
@@ -0,0 +1,551 @@
|
||||
#include "fade.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
|
||||
#include "color.hpp"
|
||||
#include "param.hpp"
|
||||
#include "screen.hpp"
|
||||
|
||||
// Constructor
|
||||
Fade::Fade()
|
||||
: renderer_(Screen::get()->getRenderer()) {
|
||||
// Crea la textura donde dibujar el fade
|
||||
backbuffer_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
|
||||
SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Inicializa las variables
|
||||
init();
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Fade::~Fade() {
|
||||
SDL_DestroyTexture(backbuffer_);
|
||||
}
|
||||
|
||||
// Inicializa las variables
|
||||
void Fade::init() {
|
||||
type_ = Type::CENTER;
|
||||
mode_ = Mode::OUT;
|
||||
r_ = 0;
|
||||
g_ = 0;
|
||||
b_ = 0;
|
||||
a_ = 0;
|
||||
post_duration_ = 0;
|
||||
post_start_time_ = 0;
|
||||
pre_duration_ = 0;
|
||||
pre_start_time_ = 0;
|
||||
fading_duration_ = param.fade.random_squares_duration_ms; // Duración por defecto para FADING
|
||||
fading_start_time_ = 0;
|
||||
num_squares_width_ = param.fade.num_squares_width;
|
||||
num_squares_height_ = param.fade.num_squares_height;
|
||||
square_transition_duration_ = fading_duration_ / 4; // 25% del tiempo total para la transición individual
|
||||
}
|
||||
|
||||
// Resetea algunas variables para volver a hacer el fade sin perder ciertos parametros
|
||||
void Fade::reset() {
|
||||
state_ = State::NOT_ENABLED;
|
||||
post_start_time_ = 0;
|
||||
pre_start_time_ = 0;
|
||||
fading_start_time_ = 0;
|
||||
value_ = 0;
|
||||
// La duración del fade se mantiene, se puede cambiar con setDuration()
|
||||
}
|
||||
|
||||
// Pinta una transición en pantalla
|
||||
void Fade::render() {
|
||||
if (state_ != State::NOT_ENABLED) {
|
||||
// Para fade IN terminado, no renderizar (auto-desactivación visual)
|
||||
if (state_ == State::FINISHED && mode_ == Mode::IN) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_RenderTexture(renderer_, backbuffer_, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las variables internas
|
||||
void Fade::update(float delta_time) {
|
||||
switch (state_) {
|
||||
case State::PRE:
|
||||
updatePreState();
|
||||
break;
|
||||
case State::FADING:
|
||||
updateFadingState();
|
||||
break;
|
||||
case State::POST:
|
||||
updatePostState();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::updatePreState() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - pre_start_time_;
|
||||
|
||||
if (std::cmp_greater_equal(elapsed_time, pre_duration_)) {
|
||||
state_ = State::FADING;
|
||||
fading_start_time_ = SDL_GetTicks(); // Inicia el temporizador del fade AQUI
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::updateFadingState() {
|
||||
switch (type_) {
|
||||
case Type::FULLSCREEN:
|
||||
updateFullscreenFade();
|
||||
break;
|
||||
case Type::CENTER:
|
||||
updateCenterFade();
|
||||
break;
|
||||
case Type::RANDOM_SQUARE:
|
||||
updateRandomSquareFade();
|
||||
break;
|
||||
case Type::RANDOM_SQUARE2:
|
||||
updateRandomSquare2Fade();
|
||||
break;
|
||||
case Type::DIAGONAL:
|
||||
updateDiagonalFade();
|
||||
break;
|
||||
case Type::VENETIAN:
|
||||
updateVenetianFade();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::changeToPostState() {
|
||||
state_ = State::POST;
|
||||
post_start_time_ = SDL_GetTicks();
|
||||
}
|
||||
|
||||
void Fade::updatePostState() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - post_start_time_;
|
||||
|
||||
if (std::cmp_greater_equal(elapsed_time, post_duration_)) {
|
||||
state_ = State::FINISHED;
|
||||
}
|
||||
|
||||
// Mantener el estado final del fade
|
||||
Uint8 post_alpha = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
|
||||
cleanBackbuffer(r_, g_, b_, post_alpha);
|
||||
}
|
||||
|
||||
void Fade::updateFullscreenFade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
float progress = std::min(static_cast<float>(elapsed_time) / fading_duration_, 1.0F);
|
||||
|
||||
// Modifica la transparencia basada en el progreso
|
||||
auto current_alpha = static_cast<Uint8>(progress * 255.0F);
|
||||
a_ = (mode_ == Mode::OUT) ? current_alpha : 255 - current_alpha;
|
||||
SDL_SetTextureAlphaMod(backbuffer_, a_);
|
||||
|
||||
value_ = static_cast<int>(progress * 100);
|
||||
|
||||
// Comprueba si ha terminado
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::updateCenterFade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
float progress = std::min(static_cast<float>(elapsed_time) / fading_duration_, 1.0F);
|
||||
|
||||
// Calcula la altura de las barras
|
||||
float rect_height = progress * (param.game.height / 2.0F);
|
||||
|
||||
if (mode_ == Mode::IN) {
|
||||
rect_height = (param.game.height / 2.0F) - rect_height;
|
||||
}
|
||||
|
||||
rect1_.h = rect_height;
|
||||
rect2_.h = rect_height;
|
||||
rect2_.y = param.game.height - rect_height;
|
||||
|
||||
drawCenterFadeRectangles();
|
||||
value_ = static_cast<int>(progress * 100);
|
||||
|
||||
// Comprueba si ha terminado
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
a_ = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::drawCenterFadeRectangles() {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
cleanBackbuffer(r_, g_, b_, 0); // Limpiar para modo IN
|
||||
SDL_SetRenderDrawColor(renderer_, r_, g_, b_, 255);
|
||||
|
||||
SDL_RenderFillRect(renderer_, &rect1_);
|
||||
SDL_RenderFillRect(renderer_, &rect2_);
|
||||
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
void Fade::updateRandomSquareFade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
float progress = std::min(static_cast<float>(elapsed_time) / fading_duration_, 1.0F);
|
||||
|
||||
// Calcula cuántos cuadrados deberían estar activos
|
||||
int total_squares = num_squares_width_ * num_squares_height_;
|
||||
int active_squares = static_cast<int>(progress * total_squares);
|
||||
|
||||
drawRandomSquares(active_squares);
|
||||
|
||||
value_ = static_cast<int>(progress * 100);
|
||||
|
||||
// Comprueba si ha terminado
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::updateRandomSquare2Fade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
int total_squares = num_squares_width_ * num_squares_height_;
|
||||
|
||||
int activation_time = fading_duration_ - square_transition_duration_;
|
||||
activation_time = std::max(activation_time, square_transition_duration_);
|
||||
|
||||
int squares_to_activate = 0;
|
||||
if (mode_ == Mode::OUT) {
|
||||
if (std::cmp_less(elapsed_time, activation_time)) {
|
||||
float activation_progress = static_cast<float>(elapsed_time) / activation_time;
|
||||
squares_to_activate = static_cast<int>(activation_progress * total_squares);
|
||||
} else {
|
||||
squares_to_activate = total_squares;
|
||||
}
|
||||
for (int i = 0; i < squares_to_activate; ++i) {
|
||||
if (square_age_[i] == -1) { square_age_[i] = elapsed_time; }
|
||||
}
|
||||
} else {
|
||||
squares_to_activate = total_squares;
|
||||
float activation_progress = static_cast<float>(elapsed_time) / activation_time;
|
||||
int squares_starting_transition = std::min(total_squares, std::max(1, static_cast<int>(activation_progress * total_squares)));
|
||||
for (int i = 0; i < squares_starting_transition; ++i) {
|
||||
if (square_age_[i] == -1) { square_age_[i] = elapsed_time; }
|
||||
}
|
||||
}
|
||||
|
||||
drawRandomSquares2();
|
||||
value_ = calculateValue(0, total_squares, squares_to_activate);
|
||||
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
Uint8 final_alpha = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
cleanBackbuffer(r_, g_, b_, final_alpha);
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::updateDiagonalFade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
|
||||
int activation_time = fading_duration_ - square_transition_duration_;
|
||||
activation_time = std::max(activation_time, square_transition_duration_);
|
||||
|
||||
int max_diagonal = num_squares_width_ + num_squares_height_ - 1;
|
||||
int active_diagonals = 0;
|
||||
if (mode_ == Mode::OUT) {
|
||||
if (std::cmp_less(elapsed_time, activation_time)) {
|
||||
float activation_progress = static_cast<float>(elapsed_time) / activation_time;
|
||||
active_diagonals = static_cast<int>(activation_progress * max_diagonal);
|
||||
} else {
|
||||
active_diagonals = max_diagonal;
|
||||
}
|
||||
for (int diagonal = 0; diagonal < active_diagonals; ++diagonal) {
|
||||
activateDiagonal(diagonal, elapsed_time);
|
||||
}
|
||||
} else {
|
||||
active_diagonals = max_diagonal;
|
||||
if (std::cmp_less(elapsed_time, activation_time)) {
|
||||
float activation_progress = static_cast<float>(elapsed_time) / activation_time;
|
||||
int diagonals_starting_transition = static_cast<int>(activation_progress * max_diagonal);
|
||||
for (int diagonal = 0; diagonal < diagonals_starting_transition; ++diagonal) {
|
||||
activateDiagonal(diagonal, elapsed_time);
|
||||
}
|
||||
} else {
|
||||
for (int diagonal = 0; diagonal < max_diagonal; ++diagonal) {
|
||||
activateDiagonal(diagonal, elapsed_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawDiagonal();
|
||||
value_ = calculateValue(0, max_diagonal, active_diagonals);
|
||||
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
Uint8 final_alpha = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
cleanBackbuffer(r_, g_, b_, final_alpha);
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::activateDiagonal(int diagonal_index, Uint32 current_time) {
|
||||
for (int x = 0; x < num_squares_width_; ++x) {
|
||||
int y = diagonal_index - x;
|
||||
if (y >= 0 && y < num_squares_height_) {
|
||||
int index = (y * num_squares_width_) + x;
|
||||
if (index >= 0 && std::cmp_less(index, square_age_.size()) && square_age_[index] == -1) {
|
||||
square_age_[index] = current_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::drawDiagonal() {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_BlendMode blend_mode;
|
||||
SDL_GetRenderDrawBlendMode(renderer_, &blend_mode);
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||
|
||||
Uint32 current_time = SDL_GetTicks() - fading_start_time_;
|
||||
for (size_t i = 0; i < square_.size(); ++i) {
|
||||
Uint8 current_alpha = 0;
|
||||
if (square_age_[i] == -1) {
|
||||
current_alpha = (mode_ == Mode::OUT) ? 0 : a_;
|
||||
} else {
|
||||
Uint32 square_elapsed = current_time - square_age_[i];
|
||||
float progress = std::min(static_cast<float>(square_elapsed) / square_transition_duration_, 1.0F);
|
||||
current_alpha = (mode_ == Mode::OUT) ? static_cast<Uint8>(progress * a_) : static_cast<Uint8>((1.0F - progress) * a_);
|
||||
}
|
||||
if (current_alpha > 0) {
|
||||
SDL_SetRenderDrawColor(renderer_, r_, g_, b_, current_alpha);
|
||||
SDL_RenderFillRect(renderer_, &square_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer_, blend_mode);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
void Fade::drawRandomSquares(int active_count) {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
|
||||
// El fondo se prepara en activate()
|
||||
SDL_BlendMode blend_mode;
|
||||
SDL_GetRenderDrawBlendMode(renderer_, &blend_mode);
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_NONE);
|
||||
SDL_SetRenderDrawColor(renderer_, r_, g_, b_, a_);
|
||||
|
||||
// Dibuja solo los cuadrados activos
|
||||
for (int i = 0; i < active_count && std::cmp_less(i, square_.size()); ++i) {
|
||||
SDL_RenderFillRect(renderer_, &square_[i]);
|
||||
}
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer_, blend_mode);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
void Fade::drawRandomSquares2() {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_BlendMode blend_mode;
|
||||
SDL_GetRenderDrawBlendMode(renderer_, &blend_mode);
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||
|
||||
Uint32 current_time = SDL_GetTicks() - fading_start_time_;
|
||||
for (size_t i = 0; i < square_.size(); ++i) {
|
||||
Uint8 current_alpha = 0;
|
||||
if (square_age_[i] == -1) {
|
||||
current_alpha = (mode_ == Mode::OUT) ? 0 : a_;
|
||||
} else {
|
||||
Uint32 square_elapsed = current_time - square_age_[i];
|
||||
float progress = std::min(static_cast<float>(square_elapsed) / square_transition_duration_, 1.0F);
|
||||
current_alpha = (mode_ == Mode::OUT) ? static_cast<Uint8>(progress * a_) : static_cast<Uint8>((1.0F - progress) * a_);
|
||||
}
|
||||
if (current_alpha > 0) {
|
||||
SDL_SetRenderDrawColor(renderer_, r_, g_, b_, current_alpha);
|
||||
SDL_RenderFillRect(renderer_, &square_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer_, blend_mode);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
void Fade::updateVenetianFade() {
|
||||
Uint32 elapsed_time = SDL_GetTicks() - fading_start_time_;
|
||||
float progress = std::min(static_cast<float>(elapsed_time) / fading_duration_, 1.0F);
|
||||
|
||||
// Calcula la altura de las persianas
|
||||
float rect_height = progress * param.fade.venetian_size;
|
||||
if (mode_ == Mode::IN) {
|
||||
rect_height = param.fade.venetian_size - rect_height;
|
||||
}
|
||||
|
||||
for (auto& rect : square_) {
|
||||
rect.h = rect_height;
|
||||
}
|
||||
|
||||
drawVenetianBlinds();
|
||||
value_ = static_cast<int>(progress * 100);
|
||||
|
||||
// Comprueba si ha terminado
|
||||
if (std::cmp_greater_equal(elapsed_time, fading_duration_)) {
|
||||
changeToPostState();
|
||||
}
|
||||
}
|
||||
|
||||
void Fade::drawVenetianBlinds() {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
|
||||
// Limpia la textura con el color base (transparente para OUT, opaco para IN)
|
||||
Uint8 initial_alpha = (mode_ == Mode::OUT) ? 0 : 255;
|
||||
cleanBackbuffer(r_, g_, b_, initial_alpha);
|
||||
|
||||
SDL_BlendMode blend_mode;
|
||||
SDL_GetRenderDrawBlendMode(renderer_, &blend_mode);
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_NONE);
|
||||
|
||||
// Dibuja las persianas con el color opuesto al fondo
|
||||
Uint8 draw_alpha = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
SDL_SetRenderDrawColor(renderer_, r_, g_, b_, draw_alpha);
|
||||
|
||||
for (const auto& rect : square_) {
|
||||
SDL_RenderFillRect(renderer_, &rect);
|
||||
}
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer_, blend_mode);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
// Activa el fade
|
||||
void Fade::activate() {
|
||||
if (state_ != State::NOT_ENABLED) {
|
||||
return;
|
||||
}
|
||||
state_ = State::PRE;
|
||||
pre_start_time_ = SDL_GetTicks();
|
||||
value_ = 0;
|
||||
|
||||
// Preparación inicial de cada tipo
|
||||
switch (type_) {
|
||||
/*case Type::FULLSCREEN:
|
||||
cleanBackbuffer(r_, g_, b_, (mode_ == Mode::OUT) ? 0 : 255);
|
||||
SDL_SetTextureAlphaMod(backbuffer_, (mode_ == Mode::OUT) ? 255 : 0);
|
||||
break;*/
|
||||
|
||||
case Type::FULLSCREEN: {
|
||||
// La textura en sí siempre debe ser de un color sólido y opaco.
|
||||
// La transparencia se gestionará con la modulación de alfa.
|
||||
cleanBackbuffer(r_, g_, b_, 255);
|
||||
|
||||
// Ahora, inicializamos la modulación de alfa correctamente:
|
||||
// - IN: Empieza opaco (255) y se desvanece a transparente.
|
||||
// - OUT: Empieza transparente (0) y se desvanece a opaco.
|
||||
const Uint8 INITIAL_ALPHA = (mode_ == Mode::IN) ? 255 : 0;
|
||||
SDL_SetTextureAlphaMod(backbuffer_, INITIAL_ALPHA);
|
||||
break;
|
||||
}
|
||||
|
||||
case Type::CENTER:
|
||||
rect1_ = {.x = 0, .y = 0, .w = param.game.width, .h = 0};
|
||||
rect2_ = {.x = 0, .y = param.game.height, .w = param.game.width, .h = 0};
|
||||
a_ = 255;
|
||||
break;
|
||||
|
||||
case Type::RANDOM_SQUARE: {
|
||||
rect1_ = {.x = 0, .y = 0, .w = static_cast<float>(param.game.width / num_squares_width_), .h = static_cast<float>(param.game.height / num_squares_height_)};
|
||||
square_.clear();
|
||||
for (int i = 0; i < num_squares_width_ * num_squares_height_; ++i) {
|
||||
rect1_.x = (i % num_squares_width_) * rect1_.w;
|
||||
rect1_.y = (i / num_squares_width_) * rect1_.h;
|
||||
square_.push_back(rect1_);
|
||||
}
|
||||
auto num = square_.size();
|
||||
while (num > 1) {
|
||||
auto num_arreu = rand() % num;
|
||||
std::swap(square_[num_arreu], square_[--num]);
|
||||
}
|
||||
a_ = (mode_ == Mode::OUT) ? 255 : 0;
|
||||
cleanBackbuffer(r_, g_, b_, (mode_ == Mode::OUT) ? 0 : 255);
|
||||
break;
|
||||
}
|
||||
|
||||
case Type::RANDOM_SQUARE2:
|
||||
case Type::DIAGONAL: {
|
||||
rect1_ = {.x = 0, .y = 0, .w = static_cast<float>(param.game.width / num_squares_width_), .h = static_cast<float>(param.game.height / num_squares_height_)};
|
||||
square_.clear();
|
||||
square_age_.assign(num_squares_width_ * num_squares_height_, -1);
|
||||
for (int i = 0; i < num_squares_width_ * num_squares_height_; ++i) {
|
||||
rect1_.x = (i % num_squares_width_) * rect1_.w;
|
||||
rect1_.y = (i / num_squares_width_) * rect1_.h;
|
||||
square_.push_back(rect1_);
|
||||
}
|
||||
|
||||
if (type_ == Type::RANDOM_SQUARE2) {
|
||||
auto num = square_.size();
|
||||
while (num > 1) {
|
||||
auto num_arreu = rand() % num;
|
||||
std::swap(square_[num_arreu], square_[--num]);
|
||||
// No es necesario desordenar square_age_ ya que todos son -1
|
||||
}
|
||||
}
|
||||
Uint8 initial_alpha = (mode_ == Mode::OUT) ? 0 : 255;
|
||||
cleanBackbuffer(r_, g_, b_, initial_alpha);
|
||||
a_ = 255;
|
||||
square_transition_duration_ = std::max(fading_duration_ / 4, 100);
|
||||
break;
|
||||
}
|
||||
|
||||
case Type::VENETIAN: {
|
||||
square_.clear();
|
||||
rect1_ = {.x = 0, .y = 0, .w = param.game.width, .h = (mode_ == Mode::OUT) ? 0.0F : param.fade.venetian_size};
|
||||
const int MAX = param.game.height / param.fade.venetian_size;
|
||||
for (int i = 0; i < MAX; ++i) {
|
||||
rect1_.y = i * param.fade.venetian_size;
|
||||
square_.push_back(rect1_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el color del fade
|
||||
void Fade::setColor(Uint8 r, Uint8 g, Uint8 b) {
|
||||
r_ = r;
|
||||
g_ = g;
|
||||
b_ = b;
|
||||
}
|
||||
|
||||
// Establece el color del fade
|
||||
void Fade::setColor(Color color) {
|
||||
r_ = color.r;
|
||||
g_ = color.g;
|
||||
b_ = color.b;
|
||||
}
|
||||
|
||||
// Limpia el backbuffer
|
||||
void Fade::cleanBackbuffer(Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, backbuffer_);
|
||||
SDL_SetRenderDrawColor(renderer_, r, g, b, a);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
// Calcula el valor del estado del fade
|
||||
auto Fade::calculateValue(int min, int max, int current) -> int {
|
||||
if (current <= min) {
|
||||
return 0;
|
||||
}
|
||||
if (current >= max) {
|
||||
return 100;
|
||||
}
|
||||
return static_cast<int>(100.0 * (current - min) / (max - min));
|
||||
}
|
||||
111
source/core/rendering/fade.hpp
Normal file
111
source/core/rendering/fade.hpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para Uint8, SDL_FRect, SDL_Renderer, SDL_Texture, Uint32
|
||||
|
||||
#include <vector> // Para vector
|
||||
|
||||
struct Color;
|
||||
|
||||
// --- Clase Fade: gestor de transiciones de fundido ---
|
||||
class Fade {
|
||||
public:
|
||||
// --- Enums ---
|
||||
enum class Type : Uint8 {
|
||||
FULLSCREEN = 0, // Fundido de pantalla completa
|
||||
CENTER = 1, // Fundido desde el centro
|
||||
RANDOM_SQUARE = 2, // Fundido con cuadrados aleatorios
|
||||
RANDOM_SQUARE2 = 3, // Fundido con cuadrados aleatorios (variante 2)
|
||||
DIAGONAL = 4, // Fundido diagonal desde esquina superior izquierda
|
||||
VENETIAN = 5, // Fundido tipo persiana veneciana
|
||||
};
|
||||
|
||||
enum class Mode : Uint8 {
|
||||
IN = 0, // Fundido de entrada
|
||||
OUT = 1, // Fundido de salida
|
||||
};
|
||||
|
||||
enum class State : Uint8 {
|
||||
NOT_ENABLED = 0, // No activado
|
||||
PRE = 1, // Estado previo
|
||||
FADING = 2, // Fundiendo
|
||||
POST = 3, // Estado posterior
|
||||
FINISHED = 4, // Finalizado
|
||||
};
|
||||
|
||||
// --- Constructores y destructor ---
|
||||
Fade();
|
||||
~Fade();
|
||||
|
||||
// --- Métodos principales ---
|
||||
void reset(); // Resetea variables para reutilizar el fade
|
||||
void render(); // Dibuja la transición en pantalla
|
||||
void update(float delta_time = 0.0F); // Actualiza el estado interno
|
||||
void activate(); // Activa el fade
|
||||
|
||||
// --- Configuración ---
|
||||
void setColor(Uint8 r, Uint8 g, Uint8 b); // Establece el color RGB del fade
|
||||
void setColor(Color color); // Establece el color del fade
|
||||
void setType(Type type) { type_ = type; } // Establece el tipo de fade
|
||||
void setMode(Mode mode) { mode_ = mode; } // Establece el modo de fade
|
||||
void setDuration(int milliseconds) { fading_duration_ = milliseconds; } // Duración del estado FADING en milisegundos
|
||||
void setPostDuration(int milliseconds) { post_duration_ = milliseconds; } // Duración posterior al fade en milisegundos
|
||||
void setPreDuration(int milliseconds) { pre_duration_ = milliseconds; } // Duración previa al fade en milisegundos
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getValue() const -> int { return value_; }
|
||||
[[nodiscard]] auto isEnabled() const -> bool { return state_ != State::NOT_ENABLED; }
|
||||
[[nodiscard]] auto hasEnded() const -> bool { return state_ == State::FINISHED; }
|
||||
|
||||
private:
|
||||
// --- Objetos y punteros ---
|
||||
SDL_Renderer* renderer_; // Renderizador de la ventana
|
||||
SDL_Texture* backbuffer_; // Backbuffer para efectos
|
||||
|
||||
// --- Variables de estado ---
|
||||
std::vector<SDL_FRect> square_; // Vector de cuadrados
|
||||
std::vector<int> square_age_; // Edad de cada cuadrado (para RANDOM_SQUARE2 y DIAGONAL)
|
||||
SDL_FRect rect1_, rect2_; // Rectángulos para efectos
|
||||
Type type_; // Tipo de fade
|
||||
Mode mode_; // Modo de fade
|
||||
State state_ = State::NOT_ENABLED; // Estado actual
|
||||
Uint8 r_, g_, b_, a_; // Color del fade (RGBA)
|
||||
int num_squares_width_; // Cuadrados en horizontal
|
||||
int num_squares_height_; // Cuadrados en vertical
|
||||
int square_transition_duration_; // Duración de transición de cada cuadrado en ms
|
||||
int fading_duration_{0}; // Duración del estado FADING en milisegundos
|
||||
Uint32 fading_start_time_ = 0; // Tiempo de inicio del estado FADING
|
||||
int post_duration_ = 0; // Duración posterior en milisegundos
|
||||
Uint32 post_start_time_ = 0; // Tiempo de inicio del estado POST
|
||||
int pre_duration_ = 0; // Duración previa en milisegundos
|
||||
Uint32 pre_start_time_ = 0; // Tiempo de inicio del estado PRE
|
||||
int value_ = 0; // Estado del fade (0-100)
|
||||
|
||||
// --- Inicialización y limpieza ---
|
||||
void init(); // Inicializa variables
|
||||
void cleanBackbuffer(Uint8 r, Uint8 g, Uint8 b, Uint8 a); // Limpia el backbuffer con un color RGBA
|
||||
|
||||
// --- Utilidades generales ---
|
||||
static auto calculateValue(int min, int max, int current) -> int; // Calcula el valor del fade entre dos límites
|
||||
|
||||
// --- Lógica de estado ---
|
||||
void updatePreState(); // Actualiza el estado previo al fade
|
||||
void updateFadingState(); // Actualiza el estado durante el fade
|
||||
void updatePostState(); // Actualiza el estado posterior al fade
|
||||
void changeToPostState(); // Cambia al estado POST e inicializa el tiempo
|
||||
|
||||
// --- Efectos de fundido (fade) ---
|
||||
void updateFullscreenFade(); // Actualiza el fundido de pantalla completa
|
||||
void updateCenterFade(); // Actualiza el fundido desde el centro
|
||||
void updateRandomSquareFade(); // Actualiza el fundido con cuadrados aleatorios
|
||||
void updateRandomSquare2Fade(); // Actualiza el fundido con cuadrados aleatorios (variante 2)
|
||||
void updateDiagonalFade(); // Actualiza el fundido diagonal
|
||||
void updateVenetianFade(); // Actualiza el fundido tipo persiana veneciana
|
||||
|
||||
// --- Dibujo de efectos visuales ---
|
||||
void drawCenterFadeRectangles(); // Dibuja los rectángulos del fundido central
|
||||
void drawRandomSquares(int active_count); // Dibuja los cuadrados aleatorios del fundido
|
||||
void drawRandomSquares2(); // Dibuja los cuadrados con transición de color (RANDOM_SQUARE2)
|
||||
void drawDiagonal(); // Dibuja los cuadrados con patrón diagonal
|
||||
void activateDiagonal(int diagonal_index, Uint32 current_time); // Activa una diagonal específica
|
||||
void drawVenetianBlinds(); // Dibuja las persianas venecianas del fundido
|
||||
};
|
||||
252
source/core/rendering/gif.cpp
Normal file
252
source/core/rendering/gif.cpp
Normal file
@@ -0,0 +1,252 @@
|
||||
#include "gif.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_LogError, SDL_LogCategory, SDL_LogInfo
|
||||
#include <cstring> // Para memcpy, size_t
|
||||
#include <iostream> // Para std::cout
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <string> // Para char_traits, operator==, basic_string, string
|
||||
|
||||
namespace GIF {
|
||||
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size) {
|
||||
std::memcpy(dst, buffer, size);
|
||||
buffer += size;
|
||||
}
|
||||
|
||||
void Gif::decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out) {
|
||||
if (code_length < 2 || code_length > 12) {
|
||||
std::cout << "Invalid LZW code length: " << code_length << '\n';
|
||||
throw std::runtime_error("Invalid LZW code length");
|
||||
}
|
||||
|
||||
int i, bit;
|
||||
int prev = -1;
|
||||
std::vector<DictionaryEntry> dictionary;
|
||||
int dictionary_ind;
|
||||
unsigned int mask = 0x01;
|
||||
int reset_code_length = code_length;
|
||||
int clear_code = 1 << code_length;
|
||||
int stop_code = clear_code + 1;
|
||||
int match_len = 0;
|
||||
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
|
||||
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
||||
dictionary[dictionary_ind].prev = -1;
|
||||
dictionary[dictionary_ind].len = 1;
|
||||
}
|
||||
dictionary_ind += 2;
|
||||
|
||||
while (input_length > 0) {
|
||||
int code = 0;
|
||||
for (i = 0; i < (code_length + 1); i++) {
|
||||
if (input_length <= 0) {
|
||||
std::cout << "Unexpected end of input in decompress" << '\n';
|
||||
throw std::runtime_error("Unexpected end of input in decompress");
|
||||
}
|
||||
bit = ((*input & mask) != 0) ? 1 : 0;
|
||||
mask <<= 1;
|
||||
if (mask == 0x100) {
|
||||
mask = 0x01;
|
||||
input++;
|
||||
input_length--;
|
||||
}
|
||||
code |= (bit << i);
|
||||
}
|
||||
|
||||
if (code == clear_code) {
|
||||
code_length = reset_code_length;
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
|
||||
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
||||
dictionary[dictionary_ind].prev = -1;
|
||||
dictionary[dictionary_ind].len = 1;
|
||||
}
|
||||
dictionary_ind += 2;
|
||||
prev = -1;
|
||||
continue;
|
||||
} else if (code == stop_code) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (prev > -1 && code_length < 12) {
|
||||
if (code > dictionary_ind) {
|
||||
std::cout << "LZW error: code (" << code << ") exceeds dictionary_ind (" << dictionary_ind << ")" << '\n';
|
||||
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
|
||||
}
|
||||
|
||||
int ptr;
|
||||
if (code == dictionary_ind) {
|
||||
ptr = prev;
|
||||
while (dictionary[ptr].prev != -1)
|
||||
ptr = dictionary[ptr].prev;
|
||||
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
||||
} else {
|
||||
ptr = code;
|
||||
while (dictionary[ptr].prev != -1)
|
||||
ptr = dictionary[ptr].prev;
|
||||
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
||||
}
|
||||
dictionary[dictionary_ind].prev = prev;
|
||||
dictionary[dictionary_ind].len = dictionary[prev].len + 1;
|
||||
dictionary_ind++;
|
||||
|
||||
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) {
|
||||
code_length++;
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
}
|
||||
}
|
||||
|
||||
prev = code;
|
||||
|
||||
if (code < 0 || static_cast<size_t>(code) >= dictionary.size()) {
|
||||
std::cout << "Invalid LZW code " << code << ", dictionary size " << static_cast<unsigned long>(dictionary.size()) << '\n';
|
||||
throw std::runtime_error("LZW error: invalid code encountered");
|
||||
}
|
||||
|
||||
int curCode = code;
|
||||
match_len = dictionary[curCode].len;
|
||||
while (curCode != -1) {
|
||||
out[dictionary[curCode].len - 1] = dictionary[curCode].byte;
|
||||
if (dictionary[curCode].prev == curCode) {
|
||||
std::cout << "Internal error; self-reference detected." << '\n';
|
||||
throw std::runtime_error("Internal error in decompress: self-reference");
|
||||
}
|
||||
curCode = dictionary[curCode].prev;
|
||||
}
|
||||
out += match_len;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::readSubBlocks(const uint8_t *&buffer) {
|
||||
std::vector<uint8_t> data;
|
||||
uint8_t block_size = *buffer;
|
||||
buffer++;
|
||||
while (block_size != 0) {
|
||||
data.insert(data.end(), buffer, buffer + block_size);
|
||||
buffer += block_size;
|
||||
block_size = *buffer;
|
||||
buffer++;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits) {
|
||||
ImageDescriptor image_descriptor;
|
||||
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
|
||||
|
||||
uint8_t lzw_code_size;
|
||||
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
|
||||
|
||||
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
|
||||
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
|
||||
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
|
||||
|
||||
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
|
||||
return uncompressed_data;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> Gif::loadPalette(const uint8_t *buffer) {
|
||||
uint8_t header[6];
|
||||
std::memcpy(header, buffer, 6);
|
||||
buffer += 6;
|
||||
|
||||
ScreenDescriptor screen_descriptor;
|
||||
std::memcpy(&screen_descriptor, buffer, sizeof(ScreenDescriptor));
|
||||
buffer += sizeof(ScreenDescriptor);
|
||||
|
||||
std::vector<uint32_t> global_color_table;
|
||||
if (screen_descriptor.fields & 0x80) {
|
||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||
global_color_table.resize(global_color_table_size);
|
||||
for (int i = 0; i < global_color_table_size; ++i) {
|
||||
uint8_t r = buffer[0];
|
||||
uint8_t g = buffer[1];
|
||||
uint8_t b = buffer[2];
|
||||
global_color_table[i] = (r << 16) | (g << 8) | b;
|
||||
buffer += 3;
|
||||
}
|
||||
}
|
||||
return global_color_table;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h) {
|
||||
uint8_t header[6];
|
||||
std::memcpy(header, buffer, 6);
|
||||
buffer += 6;
|
||||
|
||||
std::string headerStr(reinterpret_cast<char *>(header), 6);
|
||||
if (headerStr != "GIF87a" && headerStr != "GIF89a") {
|
||||
std::cout << "Formato de archivo GIF inválido: " << headerStr << '\n';
|
||||
throw std::runtime_error("Formato de archivo GIF inválido.");
|
||||
}
|
||||
|
||||
ScreenDescriptor screen_descriptor;
|
||||
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
|
||||
|
||||
w = screen_descriptor.width;
|
||||
h = screen_descriptor.height;
|
||||
|
||||
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
|
||||
std::vector<RGB> global_color_table;
|
||||
if (screen_descriptor.fields & 0x80) {
|
||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||
global_color_table.resize(global_color_table_size);
|
||||
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
|
||||
buffer += 3 * global_color_table_size;
|
||||
}
|
||||
|
||||
uint8_t block_type = *buffer++;
|
||||
while (block_type != TRAILER) {
|
||||
if (block_type == EXTENSION_INTRODUCER) {
|
||||
uint8_t extension_label = *buffer++;
|
||||
switch (extension_label) {
|
||||
case GRAPHIC_CONTROL: {
|
||||
uint8_t blockSize = *buffer++;
|
||||
buffer += blockSize;
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0) {
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APPLICATION_EXTENSION:
|
||||
case COMMENT_EXTENSION:
|
||||
case PLAINTEXT_EXTENSION: {
|
||||
uint8_t blockSize = *buffer++;
|
||||
buffer += blockSize;
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0) {
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
uint8_t blockSize = *buffer++;
|
||||
buffer += blockSize;
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0) {
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (block_type == IMAGE_DESCRIPTOR) {
|
||||
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
|
||||
} else {
|
||||
std::cout << "Unrecognized block type: 0x" << std::hex << static_cast<int>(block_type) << std::dec << '\n';
|
||||
return std::vector<uint8_t>{};
|
||||
}
|
||||
block_type = *buffer++;
|
||||
}
|
||||
|
||||
return std::vector<uint8_t>{};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) {
|
||||
return processGifStream(buffer, w, h);
|
||||
}
|
||||
|
||||
} // namespace GIF
|
||||
92
source/core/rendering/gif.hpp
Normal file
92
source/core/rendering/gif.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // Para uint8_t, uint16_t, uint32_t
|
||||
#include <vector> // Para vector
|
||||
|
||||
namespace GIF {
|
||||
|
||||
// Constantes definidas con constexpr, en lugar de macros
|
||||
constexpr uint8_t EXTENSION_INTRODUCER = 0x21;
|
||||
constexpr uint8_t IMAGE_DESCRIPTOR = 0x2C;
|
||||
constexpr uint8_t TRAILER = 0x3B;
|
||||
constexpr uint8_t GRAPHIC_CONTROL = 0xF9;
|
||||
constexpr uint8_t APPLICATION_EXTENSION = 0xFF;
|
||||
constexpr uint8_t COMMENT_EXTENSION = 0xFE;
|
||||
constexpr uint8_t PLAINTEXT_EXTENSION = 0x01;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct ScreenDescriptor {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t fields;
|
||||
uint8_t background_color_index;
|
||||
uint8_t pixel_aspect_ratio;
|
||||
};
|
||||
|
||||
struct RGB {
|
||||
uint8_t r, g, b;
|
||||
};
|
||||
|
||||
struct ImageDescriptor {
|
||||
uint16_t image_left_position;
|
||||
uint16_t image_top_position;
|
||||
uint16_t image_width;
|
||||
uint16_t image_height;
|
||||
uint8_t fields;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct DictionaryEntry {
|
||||
uint8_t byte;
|
||||
int prev;
|
||||
int len;
|
||||
};
|
||||
|
||||
struct Extension {
|
||||
uint8_t extension_code;
|
||||
uint8_t block_size;
|
||||
};
|
||||
|
||||
struct GraphicControlExtension {
|
||||
uint8_t fields;
|
||||
uint16_t delay_time;
|
||||
uint8_t transparent_color_index;
|
||||
};
|
||||
|
||||
struct ApplicationExtension {
|
||||
uint8_t application_id[8];
|
||||
uint8_t version[3];
|
||||
};
|
||||
|
||||
struct PlaintextExtension {
|
||||
uint16_t left, top, width, height;
|
||||
uint8_t cell_width, cell_height;
|
||||
uint8_t foreground_color, background_color;
|
||||
};
|
||||
|
||||
class Gif {
|
||||
public:
|
||||
// Descompone (uncompress) el bloque comprimido usando LZW.
|
||||
// Este método puede lanzar std::runtime_error en caso de error.
|
||||
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out);
|
||||
|
||||
// Carga la paleta (global color table) a partir de un buffer,
|
||||
// retornándola en un vector de uint32_t (cada color se compone de R, G, B).
|
||||
std::vector<uint32_t> loadPalette(const uint8_t *buffer);
|
||||
|
||||
// Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y
|
||||
// asigna el ancho y alto mediante referencias.
|
||||
std::vector<uint8_t> loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h);
|
||||
|
||||
private:
|
||||
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
|
||||
std::vector<uint8_t> readSubBlocks(const uint8_t *&buffer);
|
||||
|
||||
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
|
||||
std::vector<uint8_t> processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits);
|
||||
|
||||
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
|
||||
std::vector<uint8_t> processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h);
|
||||
};
|
||||
|
||||
} // namespace GIF
|
||||
624
source/core/rendering/screen.cpp
Normal file
624
source/core/rendering/screen.cpp
Normal file
@@ -0,0 +1,624 @@
|
||||
#include "screen.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_RenderTexture, SDL_SetRenderDrawColor, SDL_SetRenderVSync, SDL_LogCategory, SDL_GetError, SDL_LogError, SDL_LogInfo, SDL_RendererLogicalPresentation, SDL_SetRenderLogicalPresentation, SDL_CreateTexture, SDL_DestroyTexture, SDL_DestroyWindow, SDL_GetDisplayName, SDL_GetTicks, SDL_Quit, SDL_RENDERER_VSYNC_DISABLED, SDL_RenderClear, SDL_CreateRenderer, SDL_CreateWindow, SDL_DestroyRenderer, SDL_DisplayID, SDL_FRect, SDL_GetCurrentDisplayMode, SDL_GetDisplays, SDL_GetRenderTarget, SDL_GetWindowPosition, SDL_GetWindowSize, SDL_Init, SDL_LogWarn, SDL_PixelFormat, SDL_RenderFillRect, SDL_RenderPresent, SDL_SetHint, SDL_SetRenderDrawBlendMode, SDL_SetTextureScaleMode, SDL_SetWindowFullscreen, SDL_SetWindowPosition, SDL_SetWindowSize, SDL_TextureAccess, SDL_free, SDL_BLENDMODE_BLEND, SDL_HINT_RENDER_DRIVER, SDL_INIT_VIDEO, SDL_ScaleMode, SDL_WINDOW_FULLSCREEN, SDL_WindowFlags
|
||||
|
||||
#include <algorithm> // Para min, max
|
||||
#include <cstring> // Para memcpy
|
||||
#include <iostream> // Para std::cout
|
||||
#include <memory> // Para allocator, shared_ptr, unique_ptr, __shared_ptr_access, make_shared, make_unique
|
||||
#include <string> // Para basic_string, operator+, char_traits, to_string, string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "asset.hpp" // Para Asset
|
||||
#include "director.hpp" // Para Director::debug_config
|
||||
#include "mouse.hpp" // Para updateCursorVisibility
|
||||
#include "options.hpp" // Para Video, video, Window, window
|
||||
#include "param.hpp" // Para Param, param, ParamGame, ParamDebug
|
||||
#include "rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
|
||||
#include "resource.hpp" // Para Resource
|
||||
#include "text.hpp" // Para Text
|
||||
#include "texture.hpp" // Para Texture
|
||||
#include "ui/notifier.hpp" // Para Notifier
|
||||
#include "ui/service_menu.hpp" // Para ServiceMenu
|
||||
#include "utils.hpp" // Para toLower
|
||||
|
||||
// Singleton
|
||||
Screen* Screen::instance = nullptr;
|
||||
|
||||
// Inicializa la instancia única del singleton
|
||||
void Screen::init() {
|
||||
Screen::instance = new Screen();
|
||||
Screen::initShaders(); // Llamar aquí para que Screen::get() ya devuelva la instancia
|
||||
}
|
||||
|
||||
// Libera la instancia
|
||||
void Screen::destroy() { delete Screen::instance; }
|
||||
|
||||
// Obtiene la instancia
|
||||
auto Screen::get() -> Screen* { return Screen::instance; }
|
||||
|
||||
// Constructor
|
||||
Screen::Screen()
|
||||
: window_(nullptr),
|
||||
renderer_(nullptr),
|
||||
game_canvas_(nullptr),
|
||||
service_menu_(nullptr),
|
||||
notifier_(nullptr),
|
||||
src_rect_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}),
|
||||
dst_rect_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}) {
|
||||
// Arranca SDL VIDEO, crea la ventana y el renderizador
|
||||
initSDLVideo();
|
||||
|
||||
// Crea la textura de destino
|
||||
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
|
||||
SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
// Inicializar buffer de píxeles para SDL3GPU
|
||||
pixel_buffer_.resize(static_cast<size_t>(param.game.width) * static_cast<size_t>(param.game.height));
|
||||
|
||||
// Crea el objeto de texto
|
||||
createText();
|
||||
|
||||
#ifdef _DEBUG
|
||||
debug_info_.text = text_;
|
||||
setDebugInfoEnabled(Director::debug_config.show_render_info);
|
||||
#endif
|
||||
|
||||
// Renderizar una vez la textura vacía para que tenga contenido válido antes de inicializar los shaders (evita pantalla negra)
|
||||
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
|
||||
|
||||
// Limpiar renderer
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Screen::~Screen() {
|
||||
SDL_DestroyTexture(game_canvas_);
|
||||
SDL_DestroyRenderer(renderer_);
|
||||
SDL_DestroyWindow(window_);
|
||||
}
|
||||
|
||||
// Limpia la pantalla
|
||||
void Screen::clean(Color color) {
|
||||
SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 0xFF);
|
||||
SDL_RenderClear(renderer_);
|
||||
}
|
||||
|
||||
// Prepara para empezar a dibujar en la textura de juego
|
||||
void Screen::start() { SDL_SetRenderTarget(renderer_, game_canvas_); }
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
void Screen::render() {
|
||||
fps_.increment();
|
||||
renderOverlays(); // Renderiza todos los overlays y efectos
|
||||
renderPresent(); // Renderiza el contenido del game_canvas_
|
||||
}
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla exceptuando ciertas partes
|
||||
void Screen::coreRender() {
|
||||
/*fps_.increment();
|
||||
#ifdef _DEBUG
|
||||
renderInfo();
|
||||
#endif*/
|
||||
renderPresent(); // Renderiza el contenido del game_canvas_
|
||||
}
|
||||
|
||||
// Renderiza el contenido del game_canvas_
|
||||
void Screen::renderPresent() {
|
||||
#ifndef NO_SHADERS
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
// Leer píxeles de game_canvas_ con la API SDL3 (devuelve SDL_Surface*)
|
||||
SDL_SetRenderTarget(renderer_, game_canvas_);
|
||||
SDL_Surface* surface = SDL_RenderReadPixels(renderer_, nullptr);
|
||||
if (surface != nullptr) {
|
||||
if (surface->format == SDL_PIXELFORMAT_ARGB8888) {
|
||||
std::memcpy(pixel_buffer_.data(), surface->pixels, pixel_buffer_.size() * sizeof(Uint32));
|
||||
} else {
|
||||
SDL_Surface* converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
|
||||
if (converted != nullptr) {
|
||||
std::memcpy(pixel_buffer_.data(), converted->pixels, pixel_buffer_.size() * sizeof(Uint32));
|
||||
SDL_DestroySurface(converted);
|
||||
}
|
||||
}
|
||||
SDL_DestroySurface(surface);
|
||||
}
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
// Subir a GPU y presentar con PostFX
|
||||
shader_backend_->uploadPixels(pixel_buffer_.data(), param.game.width, param.game.height);
|
||||
shader_backend_->render();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// Fallback: SDL_Renderer
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
clean();
|
||||
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
|
||||
// Establece el modo de video
|
||||
void Screen::setFullscreenMode() {
|
||||
SDL_SetWindowFullscreen(window_, Options::video.fullscreen);
|
||||
}
|
||||
|
||||
// Camibia entre pantalla completa y ventana
|
||||
void Screen::toggleFullscreen() {
|
||||
Options::video.fullscreen = !Options::video.fullscreen;
|
||||
setFullscreenMode();
|
||||
}
|
||||
|
||||
// Cambia el tamaño de la ventana
|
||||
void Screen::setWindowZoom(int zoom) {
|
||||
Options::window.zoom = zoom;
|
||||
adjustWindowSize();
|
||||
}
|
||||
|
||||
// Reduce el tamaño de la ventana
|
||||
auto Screen::decWindowSize() -> bool {
|
||||
if (!Options::video.fullscreen) {
|
||||
const int PREVIOUS_ZOOM = Options::window.zoom;
|
||||
--Options::window.zoom;
|
||||
Options::window.zoom = std::max(Options::window.zoom, 1);
|
||||
|
||||
if (Options::window.zoom != PREVIOUS_ZOOM) {
|
||||
adjustWindowSize();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Aumenta el tamaño de la ventana
|
||||
auto Screen::incWindowSize() -> bool {
|
||||
if (!Options::video.fullscreen) {
|
||||
const int PREVIOUS_ZOOM = Options::window.zoom;
|
||||
++Options::window.zoom;
|
||||
Options::window.zoom = std::min(Options::window.zoom, Options::window.max_zoom);
|
||||
|
||||
if (Options::window.zoom != PREVIOUS_ZOOM) {
|
||||
adjustWindowSize();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recibe deltaTime de las secciones y actualiza la lógica
|
||||
void Screen::update(float delta_time) {
|
||||
fps_.calculate(SDL_GetTicks());
|
||||
shake_effect_.update(src_rect_, dst_rect_, delta_time);
|
||||
flash_effect_.update(delta_time);
|
||||
if (service_menu_ != nullptr) {
|
||||
service_menu_->update(delta_time);
|
||||
}
|
||||
if (notifier_ != nullptr) {
|
||||
notifier_->update(delta_time);
|
||||
}
|
||||
Mouse::updateCursorVisibility();
|
||||
}
|
||||
|
||||
// Actualiza los elementos mínimos
|
||||
void Screen::coreUpdate() {
|
||||
fps_.calculate(SDL_GetTicks());
|
||||
Mouse::updateCursorVisibility();
|
||||
}
|
||||
|
||||
// Actualiza y dibuja el efecto de flash en la pantalla
|
||||
void Screen::renderFlash() {
|
||||
if (flash_effect_.isRendarable()) {
|
||||
SDL_SetRenderDrawColor(renderer_, flash_effect_.color.r, flash_effect_.color.g, flash_effect_.color.b, 0xFF);
|
||||
SDL_RenderClear(renderer_);
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica el efecto de agitar la pantalla
|
||||
void Screen::renderShake() {
|
||||
if (shake_effect_.enabled) {
|
||||
// Guarda el renderizador actual para dejarlo despues como estaba
|
||||
auto* current_target = SDL_GetRenderTarget(renderer_);
|
||||
|
||||
// Crea una textura temporal
|
||||
auto* temp_texture = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
|
||||
|
||||
// Vuelca game_canvas_ a la textura temporal
|
||||
SDL_SetRenderTarget(renderer_, temp_texture);
|
||||
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
|
||||
|
||||
// Vuelca textura temporal a game_canvas_
|
||||
SDL_SetRenderTarget(renderer_, game_canvas_);
|
||||
SDL_RenderTexture(renderer_, temp_texture, &src_rect_, &dst_rect_);
|
||||
|
||||
// Elimina la textura temporal
|
||||
SDL_DestroyTexture(temp_texture);
|
||||
|
||||
// Restaura el renderizador de destino original
|
||||
SDL_SetRenderTarget(renderer_, current_target);
|
||||
}
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
// Muestra información por pantalla
|
||||
void Screen::renderInfo() const {
|
||||
if (debug_info_.show) {
|
||||
const Color GOLD(0xFF, 0xD7, 0x00);
|
||||
const Color GOLD_SHADOW = GOLD.DARKEN(150);
|
||||
|
||||
// Construir texto: fps - driver - preset
|
||||
std::string info_text = std::to_string(fps_.last_value) + " fps";
|
||||
|
||||
// Driver GPU
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
const std::string DRIVER = shader_backend_->getDriverName();
|
||||
if (!DRIVER.empty()) {
|
||||
info_text += " - " + toLower(DRIVER);
|
||||
}
|
||||
} else {
|
||||
info_text += " - sdl";
|
||||
}
|
||||
|
||||
// Shader + preset
|
||||
if (Options::video.shader.enabled) {
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
const std::string PRESET_NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
|
||||
info_text += " - crtpi " + toLower(PRESET_NAME);
|
||||
} else {
|
||||
const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
||||
info_text += " - postfx " + toLower(PRESET_NAME);
|
||||
if (Options::video.supersampling.enabled) { info_text += " (ss)"; }
|
||||
}
|
||||
}
|
||||
|
||||
// Centrado arriba
|
||||
const int TEXT_WIDTH = debug_info_.text->length(info_text);
|
||||
const int X_POS = (static_cast<int>(param.game.width) - TEXT_WIDTH) / 2;
|
||||
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, X_POS, 1, info_text, 1, GOLD, 1, GOLD_SHADOW);
|
||||
|
||||
#ifdef RECORDING
|
||||
const std::string REC_TEXT = "recording";
|
||||
const int REC_WIDTH = debug_info_.text->length(REC_TEXT);
|
||||
const int REC_X = (static_cast<int>(param.game.width) - REC_WIDTH) / 2;
|
||||
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, REC_X, 1 + debug_info_.text->getCharacterSize(), REC_TEXT, 1, GOLD, 1, GOLD_SHADOW);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Inicializa shaders (SDL3GPU)
|
||||
void Screen::initShaders() {
|
||||
#ifndef NO_SHADERS
|
||||
auto* self = Screen::get();
|
||||
if (self == nullptr) {
|
||||
std::cout << "Screen::initShaders: instance is null, skipping" << '\n';
|
||||
return;
|
||||
}
|
||||
if (!self->shader_backend_) {
|
||||
self->shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||
const std::string FALLBACK_DRIVER = "none";
|
||||
self->shader_backend_->setPreferredDriver(
|
||||
Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER);
|
||||
}
|
||||
if (!self->shader_backend_->isHardwareAccelerated()) {
|
||||
const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
|
||||
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (ok ? "OK" : "FAILED") << '\n';
|
||||
}
|
||||
if (self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
||||
self->shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
|
||||
self->shader_backend_->setDownscaleAlgo(Options::video.supersampling.downscale_algo);
|
||||
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
|
||||
if (!Options::video.shader.enabled) {
|
||||
// Passthrough: POSTFX con parámetros a cero
|
||||
self->shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||
self->shader_backend_->setPostFXParams(Rendering::PostFXParams{});
|
||||
} else if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
self->shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
|
||||
self->applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
self->shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Calcula el tamaño de la ventana
|
||||
void Screen::adjustWindowSize() {
|
||||
if (!Options::video.fullscreen) {
|
||||
// Establece el nuevo tamaño
|
||||
const int WIDTH = param.game.width * Options::window.zoom;
|
||||
const int HEIGHT = param.game.height * Options::window.zoom;
|
||||
|
||||
int old_width;
|
||||
int old_height;
|
||||
SDL_GetWindowSize(window_, &old_width, &old_height);
|
||||
|
||||
int old_pos_x;
|
||||
int old_pos_y;
|
||||
SDL_GetWindowPosition(window_, &old_pos_x, &old_pos_y);
|
||||
|
||||
const int NEW_POS_X = old_pos_x + ((old_width - WIDTH) / 2);
|
||||
const int NEW_POS_Y = old_pos_y + ((old_height - HEIGHT) / 2);
|
||||
|
||||
SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS), std::max(NEW_POS_Y, 0));
|
||||
SDL_SetWindowSize(window_, WIDTH, HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza todos los overlays y efectos
|
||||
void Screen::renderOverlays() {
|
||||
// Dibuja efectos y elementos sobre el game_canvas_
|
||||
renderShake();
|
||||
renderFlash();
|
||||
renderAttenuate();
|
||||
service_menu_->render();
|
||||
notifier_->render();
|
||||
#ifdef _DEBUG
|
||||
renderInfo();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Atenua la pantalla
|
||||
void Screen::renderAttenuate() {
|
||||
if (attenuate_effect_) {
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 64);
|
||||
SDL_RenderFillRect(renderer_, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Arranca SDL VIDEO y crea la ventana
|
||||
auto Screen::initSDLVideo() -> bool {
|
||||
// Inicializar SDL
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
std::cout << "FATAL: Failed to initialize SDL_VIDEO! SDL Error: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Obtener información de la pantalla
|
||||
getDisplayInfo();
|
||||
|
||||
// Configurar hint para renderizado
|
||||
#ifdef __APPLE__
|
||||
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal")) {
|
||||
std::cout << "Warning: Failed to set Metal hint!" << '\n';
|
||||
}
|
||||
#endif
|
||||
|
||||
// Crear ventana
|
||||
#ifdef __APPLE__
|
||||
SDL_WindowFlags window_flags = SDL_WINDOW_METAL;
|
||||
#else
|
||||
SDL_WindowFlags window_flags = 0;
|
||||
#endif
|
||||
if (Options::video.fullscreen) {
|
||||
window_flags |= SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
window_flags |= SDL_WINDOW_HIDDEN;
|
||||
window_ = SDL_CreateWindow(
|
||||
Options::window.caption.c_str(),
|
||||
param.game.width * Options::window.zoom,
|
||||
param.game.height * Options::window.zoom,
|
||||
window_flags);
|
||||
|
||||
if (window_ == nullptr) {
|
||||
std::cout << "FATAL: Failed to create window! SDL Error: " << SDL_GetError() << '\n';
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Crear renderer
|
||||
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
||||
if (renderer_ == nullptr) {
|
||||
std::cout << "FATAL: Failed to create renderer! SDL Error: " << SDL_GetError() << '\n';
|
||||
SDL_DestroyWindow(window_);
|
||||
window_ = nullptr;
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configurar renderer
|
||||
SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Obtiene información sobre la pantalla
|
||||
void Screen::getDisplayInfo() {
|
||||
int num_displays = 0;
|
||||
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
||||
if (displays != nullptr) {
|
||||
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
|
||||
|
||||
// Guarda información del monitor en display_monitor_
|
||||
const char* first_display_name = SDL_GetDisplayName(displays[0]);
|
||||
display_monitor_.name = (first_display_name != nullptr) ? first_display_name : "Unknown";
|
||||
display_monitor_.width = static_cast<int>(dm->w);
|
||||
display_monitor_.height = static_cast<int>(dm->h);
|
||||
display_monitor_.refresh_rate = static_cast<int>(dm->refresh_rate);
|
||||
|
||||
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
|
||||
Options::window.max_zoom = std::min(dm->w / param.game.width, dm->h / param.game.height);
|
||||
Options::window.zoom = std::min(Options::window.zoom, Options::window.max_zoom);
|
||||
|
||||
// Obtiene la cadena con la información sobre la resolución y el refresco
|
||||
Options::video.info = std::to_string(dm->w) + "x" +
|
||||
std::to_string(dm->h) + " @ " +
|
||||
std::to_string(static_cast<int>(dm->refresh_rate)) + " Hz";
|
||||
|
||||
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
|
||||
const int MAX_ZOOM = std::min(dm->w / param.game.width, (dm->h - WINDOWS_DECORATIONS) / param.game.height);
|
||||
|
||||
// Normaliza los valores de zoom
|
||||
Options::window.zoom = std::min(Options::window.zoom, MAX_ZOOM);
|
||||
|
||||
SDL_free(displays);
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna activar/desactivar shaders
|
||||
void Screen::toggleShaders() {
|
||||
Options::video.shader.enabled = !Options::video.shader.enabled;
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr) {
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
self->applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia entre PostFX y CrtPi
|
||||
void Screen::nextShader() {
|
||||
auto* self = Screen::get();
|
||||
if (self == nullptr || !self->shader_backend_ || !self->shader_backend_->isHardwareAccelerated()) { return; }
|
||||
|
||||
const auto NEXT = (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX)
|
||||
? Rendering::ShaderType::CRTPI
|
||||
: Rendering::ShaderType::POSTFX;
|
||||
|
||||
Options::video.shader.current_shader = NEXT;
|
||||
self->shader_backend_->setActiveShader(NEXT);
|
||||
|
||||
if (NEXT == Rendering::ShaderType::CRTPI) {
|
||||
self->applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
|
||||
// Avanza al siguiente preset PostFX
|
||||
void Screen::nextPostFXPreset() {
|
||||
if (Options::postfx_presets.empty()) { return; }
|
||||
Options::video.shader.current_postfx_preset = (Options::video.shader.current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr) {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
|
||||
// Avanza al siguiente preset CrtPi
|
||||
void Screen::nextCrtPiPreset() {
|
||||
if (Options::crtpi_presets.empty()) { return; }
|
||||
Options::video.shader.current_crtpi_preset = (Options::video.shader.current_crtpi_preset + 1) % static_cast<int>(Options::crtpi_presets.size());
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr) {
|
||||
self->applyCurrentCrtPiPreset();
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna supersampling
|
||||
void Screen::toggleSupersampling() {
|
||||
Options::video.supersampling.enabled = !Options::video.supersampling.enabled;
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
||||
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
self->applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica el preset PostFX activo al backend
|
||||
void Screen::applyCurrentPostFXPreset() {
|
||||
if (!shader_backend_) { return; }
|
||||
shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
Rendering::PostFXParams p{};
|
||||
if (Options::video.shader.enabled && !Options::postfx_presets.empty()) {
|
||||
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset));
|
||||
p.vignette = preset.vignette;
|
||||
p.scanlines = preset.scanlines;
|
||||
p.chroma = preset.chroma;
|
||||
p.mask = preset.mask;
|
||||
p.gamma = preset.gamma;
|
||||
p.curvature = preset.curvature;
|
||||
p.bleeding = preset.bleeding;
|
||||
p.flicker = preset.flicker;
|
||||
std::cout << "Screen::applyCurrentPostFXPreset: preset='" << preset.name << "' scan=" << p.scanlines << " vign=" << p.vignette << " chroma=" << p.chroma << '\n';
|
||||
}
|
||||
shader_backend_->setPostFXParams(p);
|
||||
}
|
||||
|
||||
// Aplica el preset CrtPi activo al backend
|
||||
void Screen::applyCurrentCrtPiPreset() {
|
||||
if (!shader_backend_) { return; }
|
||||
if (Options::video.shader.enabled && !Options::crtpi_presets.empty()) {
|
||||
const auto& preset = Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset));
|
||||
Rendering::CrtPiParams p{
|
||||
.scanline_weight = preset.scanline_weight,
|
||||
.scanline_gap_brightness = preset.scanline_gap_brightness,
|
||||
.bloom_factor = preset.bloom_factor,
|
||||
.input_gamma = preset.input_gamma,
|
||||
.output_gamma = preset.output_gamma,
|
||||
.mask_brightness = preset.mask_brightness,
|
||||
.curvature_x = preset.curvature_x,
|
||||
.curvature_y = preset.curvature_y,
|
||||
.mask_type = preset.mask_type,
|
||||
.enable_scanlines = preset.enable_scanlines,
|
||||
.enable_multisample = preset.enable_multisample,
|
||||
.enable_gamma = preset.enable_gamma,
|
||||
.enable_curvature = preset.enable_curvature,
|
||||
.enable_sharper = preset.enable_sharper,
|
||||
};
|
||||
shader_backend_->setCrtPiParams(p);
|
||||
std::cout << "Screen::applyCurrentCrtPiPreset: preset='" << preset.name << "'" << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna entre activar y desactivar el escalado entero
|
||||
void Screen::toggleIntegerScale() {
|
||||
Options::video.integer_scale = !Options::video.integer_scale;
|
||||
SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
if (shader_backend_) {
|
||||
shader_backend_->setScaleMode(Options::video.integer_scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna entre activar y desactivar el V-Sync
|
||||
void Screen::toggleVSync() {
|
||||
Options::video.vsync = !Options::video.vsync;
|
||||
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
if (shader_backend_) {
|
||||
shader_backend_->setVSync(Options::video.vsync);
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el estado del V-Sync
|
||||
void Screen::setVSync(bool enabled) {
|
||||
Options::video.vsync = enabled;
|
||||
SDL_SetRenderVSync(renderer_, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
}
|
||||
|
||||
// Obtiene los punteros a los singletones
|
||||
void Screen::getSingletons() {
|
||||
service_menu_ = ServiceMenu::get();
|
||||
notifier_ = Notifier::get();
|
||||
#ifdef _DEBUG
|
||||
// Actualizar la fuente de debug a 8bithud (ahora Resource está disponible)
|
||||
if (Resource::get() != nullptr) {
|
||||
auto hud_text = Resource::get()->getText("8bithud");
|
||||
if (hud_text) {
|
||||
debug_info_.text = hud_text;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Aplica los valores de las opciones
|
||||
void Screen::applySettings() {
|
||||
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
SDL_SetRenderLogicalPresentation(Screen::get()->getRenderer(), param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
setFullscreenMode();
|
||||
adjustWindowSize();
|
||||
}
|
||||
|
||||
// Crea el objeto de texto
|
||||
void Screen::createText() {
|
||||
auto texture = std::make_shared<Texture>(getRenderer(), Asset::get()->getPath("aseprite.png"));
|
||||
text_ = std::make_shared<Text>(texture, Asset::get()->getPath("aseprite.txt"));
|
||||
}
|
||||
254
source/core/rendering/screen.hpp
Normal file
254
source/core/rendering/screen.hpp
Normal file
@@ -0,0 +1,254 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_HideWindow, SDL_Renderer, SDL_ShowWindow, Uint32, SDL_Texture, SDL_Window
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "color.hpp" // Para Color
|
||||
#include "options.hpp" // Para VideoOptions, video
|
||||
#include "rendering/shader_backend.hpp" // Para Rendering::ShaderType
|
||||
|
||||
// Forward declarations
|
||||
class Notifier;
|
||||
class ServiceMenu;
|
||||
class Text;
|
||||
|
||||
// --- Clase Screen: gestiona la ventana, el renderizador y los efectos visuales globales (singleton) ---
|
||||
class Screen {
|
||||
public:
|
||||
// --- Métodos de singleton ---
|
||||
static void init(); // Inicializa el objeto Screen
|
||||
static void destroy(); // Libera el objeto Screen
|
||||
static auto get() -> Screen*; // Obtiene el puntero al objeto Screen
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time); // Recibe deltaTime de las secciones y actualiza la lógica
|
||||
void coreUpdate(); // Actualiza los elementos mínimos
|
||||
void clean(Color color = Color(0x00, 0x00, 0x00)); // Limpia la pantalla
|
||||
void start(); // Prepara para empezar a dibujar en la textura de juego
|
||||
void render(); // Vuelca el contenido del renderizador en pantalla
|
||||
void coreRender(); // Vuelca el contenido del renderizador en pantalla exceptuando ciertas partes
|
||||
|
||||
// --- Configuración de ventana y render ---
|
||||
void setFullscreenMode(); // Establece el modo de pantalla completa
|
||||
void toggleFullscreen(); // Cambia entre pantalla completa y ventana
|
||||
void setWindowZoom(int zoom); // Cambia el tamaño de la ventana
|
||||
auto decWindowSize() -> bool; // Reduce el tamaño de la ventana
|
||||
auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana
|
||||
void applySettings(); // Aplica los valores de las opciones
|
||||
static void initShaders(); // Inicializa shaders (SDL3GPU)
|
||||
|
||||
// --- Efectos visuales ---
|
||||
void shake(int desp = 2, float delay_s = 0.05F, float duration_s = 0.133F) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay_s, duration_s); }
|
||||
void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); }
|
||||
static void toggleShaders(); // Alterna activar/desactivar shaders
|
||||
static void nextShader(); // Cambia entre PostFX y CrtPi
|
||||
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
|
||||
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
|
||||
static void toggleSupersampling(); // Alterna supersampling
|
||||
void toggleIntegerScale();
|
||||
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
|
||||
void setVSync(bool enabled); // Establece el estado del V-Sync
|
||||
void attenuate(bool value) { attenuate_effect_ = value; } // Atenúa la pantalla
|
||||
|
||||
// --- Getters ---
|
||||
auto getRenderer() -> SDL_Renderer* { return renderer_; } // Obtiene el renderizador
|
||||
void show() { SDL_ShowWindow(window_); } // Muestra la ventana
|
||||
void hide() { SDL_HideWindow(window_); } // Oculta la ventana
|
||||
void getSingletons(); // Obtiene los punteros a los singletones
|
||||
[[nodiscard]] static auto getVSync() -> bool { return Options::video.vsync; } // Obtiene el valor de V-Sync
|
||||
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; } // Obtiene el puntero al texto de Screen
|
||||
|
||||
// --- Display Monitor getters ---
|
||||
[[nodiscard]] auto getDisplayMonitorName() const -> std::string { return display_monitor_.name; }
|
||||
[[nodiscard]] auto getDisplayMonitorWidth() const -> int { return display_monitor_.width; }
|
||||
[[nodiscard]] auto getDisplayMonitorHeight() const -> int { return display_monitor_.height; }
|
||||
[[nodiscard]] auto getDisplayMonitorRefreshRate() const -> int { return display_monitor_.refresh_rate; }
|
||||
|
||||
#ifdef _DEBUG
|
||||
// --- Debug ---
|
||||
void toggleDebugInfo() { debug_info_.show = !debug_info_.show; }
|
||||
void setDebugInfoEnabled(bool value) { debug_info_.show = value; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
// --- Constantes ---
|
||||
static constexpr int WINDOWS_DECORATIONS = 35; // Decoraciones de la ventana
|
||||
|
||||
// --- Estructuras privadas ---
|
||||
struct DisplayMonitor {
|
||||
std::string name;
|
||||
int width;
|
||||
int height;
|
||||
int refresh_rate;
|
||||
};
|
||||
struct FPS {
|
||||
Uint32 ticks{0}; // Tiempo en milisegundos desde que se comenzó a contar.
|
||||
int frame_count{0}; // Número acumulado de frames en el intervalo.
|
||||
int last_value{0}; // Número de frames calculado en el último segundo.
|
||||
|
||||
FPS() = default;
|
||||
void increment() { frame_count++; }
|
||||
auto calculate(Uint32 current_ticks) -> int {
|
||||
if (current_ticks - ticks >= 1000) {
|
||||
last_value = frame_count;
|
||||
frame_count = 0;
|
||||
ticks = current_ticks;
|
||||
}
|
||||
return last_value;
|
||||
}
|
||||
};
|
||||
|
||||
// Efecto de flash en pantalla: pinta la pantalla de un color durante un tiempo
|
||||
struct FlashEffect {
|
||||
bool enabled; // Indica si el efecto está activo
|
||||
float duration_s; // Duración total del efecto en segundos
|
||||
float delay_s; // Retraso antes de mostrar el flash en segundos
|
||||
float timer_s; // Timer en segundos (contador decreciente)
|
||||
Color color; // Color del flash
|
||||
|
||||
explicit FlashEffect(bool enabled = false, float duration_s = 0.0F, float delay_s = 0.0F, Color color = Color(0xFF, 0xFF, 0xFF))
|
||||
: enabled(enabled),
|
||||
duration_s(duration_s),
|
||||
delay_s(delay_s),
|
||||
timer_s(duration_s),
|
||||
color(color) {}
|
||||
|
||||
void update(float delta_time) {
|
||||
if (enabled && timer_s > 0.0F) {
|
||||
timer_s -= delta_time;
|
||||
if (timer_s <= 0.0F) {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
[[nodiscard]] auto isRendarable() const -> bool { return enabled && timer_s < duration_s - delay_s; }
|
||||
};
|
||||
|
||||
// Efecto de sacudida/agitación de pantalla: mueve la imagen para simular un temblor
|
||||
struct ShakeEffect {
|
||||
int desp; // Desplazamiento máximo de la sacudida (en píxeles)
|
||||
float delay_s; // Segundos entre cada movimiento de sacudida
|
||||
float counter_s; // Timer para el siguiente movimiento (decreciente)
|
||||
float duration_s; // Duración total del efecto en segundos
|
||||
float remaining_s; // Tiempo restante de sacudida
|
||||
int original_pos; // Posición original de la imagen (x)
|
||||
int original_width; // Ancho original de la imagen
|
||||
bool enabled; // Indica si el efecto está activo
|
||||
|
||||
explicit ShakeEffect(bool en = false, int dp = 2, float dl_s = 0.05F, float cnt_s = 0.0F, float len_s = 0.133F, float rem_s = 0.0F, int orig_pos = 0, int orig_width = 800)
|
||||
: desp(dp),
|
||||
delay_s(dl_s),
|
||||
counter_s(cnt_s),
|
||||
duration_s(len_s),
|
||||
remaining_s(rem_s),
|
||||
original_pos(orig_pos),
|
||||
original_width(orig_width),
|
||||
enabled(en) {}
|
||||
|
||||
// Activa el efecto de sacudida y guarda la posición y tamaño originales
|
||||
void enable(SDL_FRect& src_rect, SDL_FRect& dst_rect, int new_desp = -1, float new_delay_s = -1.0F, float new_duration_s = -1.0F) {
|
||||
if (!enabled) {
|
||||
enabled = true;
|
||||
original_pos = src_rect.x;
|
||||
original_width = src_rect.w;
|
||||
|
||||
// Usar nuevos valores si se proporcionan, sino mantener los actuales
|
||||
if (new_desp != -1) {
|
||||
desp = new_desp;
|
||||
}
|
||||
if (new_delay_s >= 0.0F) {
|
||||
delay_s = new_delay_s;
|
||||
}
|
||||
if (new_duration_s >= 0.0F) {
|
||||
duration_s = new_duration_s;
|
||||
}
|
||||
|
||||
src_rect.w -= desp;
|
||||
dst_rect.w = src_rect.w;
|
||||
}
|
||||
remaining_s = duration_s;
|
||||
counter_s = delay_s;
|
||||
}
|
||||
|
||||
// Actualiza el estado del efecto de sacudida
|
||||
void update(SDL_FRect& src_rect, SDL_FRect& dst_rect, float delta_time) {
|
||||
if (enabled) {
|
||||
counter_s -= delta_time;
|
||||
if (counter_s <= 0.0F) {
|
||||
counter_s = delay_s;
|
||||
// Alternar desplazamiento basado en tiempo restante
|
||||
const bool SHAKE_LEFT = static_cast<int>(remaining_s * 30.0F) % 2 == 0; // ~30 cambios por segundo
|
||||
const auto SRC_DESP = SHAKE_LEFT ? 0 : desp;
|
||||
const auto DST_DESP = SHAKE_LEFT ? desp : 0;
|
||||
src_rect.x = original_pos + SRC_DESP;
|
||||
dst_rect.x = original_pos + DST_DESP;
|
||||
|
||||
remaining_s -= delay_s;
|
||||
if (remaining_s <= 0.0F) {
|
||||
enabled = false;
|
||||
src_rect.x = original_pos;
|
||||
src_rect.w = original_width;
|
||||
dst_rect.x = original_pos;
|
||||
dst_rect.w = original_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto isEnabled() const -> bool { return enabled; }
|
||||
};
|
||||
|
||||
#ifdef _DEBUG
|
||||
struct Debug {
|
||||
std::shared_ptr<Text> text;
|
||||
bool show = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// --- Objetos y punteros ---
|
||||
SDL_Window* window_; // Ventana de la aplicación
|
||||
SDL_Renderer* renderer_; // El renderizador de la ventana
|
||||
SDL_Texture* game_canvas_; // Textura donde se dibuja todo antes de volcarse al renderizador
|
||||
ServiceMenu* service_menu_; // Objeto para mostrar el menú de servicio
|
||||
Notifier* notifier_; // Objeto para mostrar las notificaciones por pantalla
|
||||
std::shared_ptr<Text> text_; // Objeto para escribir texto en pantalla
|
||||
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (SDL3GPU)
|
||||
|
||||
// --- Variables de estado ---
|
||||
SDL_FRect src_rect_; // Coordenadas de origen para dibujar la textura del juego
|
||||
SDL_FRect dst_rect_; // Coordenadas destino para dibujar la textura del juego
|
||||
std::vector<Uint32> pixel_buffer_; // Buffer de píxeles para SDL_RenderReadPixels
|
||||
FPS fps_; // Gestión de frames por segundo
|
||||
FlashEffect flash_effect_; // Efecto de flash en pantalla
|
||||
ShakeEffect shake_effect_; // Efecto de agitar la pantalla
|
||||
bool attenuate_effect_ = false; // Indica si la pantalla ha de estar atenuada
|
||||
DisplayMonitor display_monitor_; // Información del monitor actual
|
||||
#ifdef _DEBUG
|
||||
Debug debug_info_; // Información de debug
|
||||
#endif
|
||||
|
||||
// --- Métodos internos ---
|
||||
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
||||
void renderFlash(); // Dibuja el efecto de flash en la pantalla
|
||||
void renderShake(); // Aplica el efecto de agitar la pantalla
|
||||
void renderInfo() const; // Muestra información por pantalla
|
||||
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
|
||||
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
|
||||
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
|
||||
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||
void renderOverlays(); // Renderiza todos los overlays y efectos
|
||||
void renderAttenuate(); // Atenúa la pantalla
|
||||
void createText(); // Crea el objeto de texto
|
||||
|
||||
// --- Constructores y destructor privados (singleton) ---
|
||||
Screen(); // Constructor privado
|
||||
~Screen(); // Destructor privado
|
||||
|
||||
// --- Instancia singleton ---
|
||||
static Screen* instance; // Instancia única de Screen
|
||||
};
|
||||
10361
source/core/rendering/sdl3gpu/crtpi_frag_spv.h
Normal file
10361
source/core/rendering/sdl3gpu/crtpi_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
4253
source/core/rendering/sdl3gpu/downscale_frag_spv.h
Normal file
4253
source/core/rendering/sdl3gpu/downscale_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
11245
source/core/rendering/sdl3gpu/postfx_frag_spv.h
Normal file
11245
source/core/rendering/sdl3gpu/postfx_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
1449
source/core/rendering/sdl3gpu/postfx_vert_spv.h
Normal file
1449
source/core/rendering/sdl3gpu/postfx_vert_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
1206
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
1206
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
File diff suppressed because it is too large
Load Diff
154
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
154
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "rendering/shader_backend.hpp"
|
||||
|
||||
// PostFX uniforms pushed to fragment stage each frame.
|
||||
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
float gamma_strength;
|
||||
float curvature;
|
||||
float bleeding;
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float oversample;
|
||||
float flicker;
|
||||
};
|
||||
|
||||
// CrtPi uniforms pushed to fragment stage each frame.
|
||||
// 16 fields = 64 bytes — 4 × 16-byte alignment.
|
||||
struct CrtPiUniforms {
|
||||
float scanline_weight;
|
||||
float scanline_gap_brightness;
|
||||
float bloom_factor;
|
||||
float input_gamma;
|
||||
float output_gamma;
|
||||
float mask_brightness;
|
||||
float curvature_x;
|
||||
float curvature_y;
|
||||
int mask_type;
|
||||
int enable_scanlines;
|
||||
int enable_multisample;
|
||||
int enable_gamma;
|
||||
int enable_curvature;
|
||||
int enable_sharper;
|
||||
float texture_width;
|
||||
float texture_height;
|
||||
};
|
||||
|
||||
// Downscale uniforms for Lanczos downscale fragment stage.
|
||||
// 1 int + 3 floats = 16 bytes.
|
||||
struct DownscaleUniforms {
|
||||
int algorithm;
|
||||
float pad0;
|
||||
float pad1;
|
||||
float pad2;
|
||||
};
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/**
|
||||
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
|
||||
*
|
||||
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
|
||||
* → [Upscale →] PostFX/CrtPi render pass → [Lanczos downscale →] swapchain → present
|
||||
*/
|
||||
class SDL3GPUShader : public ShaderBackend {
|
||||
public:
|
||||
SDL3GPUShader() = default;
|
||||
~SDL3GPUShader() override;
|
||||
|
||||
auto init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) -> bool override;
|
||||
|
||||
void render() override;
|
||||
void setTextureSize(float width, float height) override {}
|
||||
void cleanup() final;
|
||||
void destroy();
|
||||
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
||||
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
|
||||
|
||||
void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }
|
||||
void uploadPixels(const Uint32* pixels, int width, int height) override;
|
||||
void setPostFXParams(const PostFXParams& p) override;
|
||||
void setVSync(bool vsync) override;
|
||||
void setScaleMode(bool integer_scale) override;
|
||||
void setOversample(int factor) override;
|
||||
|
||||
void setLinearUpscale(bool linear) override;
|
||||
[[nodiscard]] auto isLinearUpscale() const -> bool override { return linear_upscale_; }
|
||||
void setDownscaleAlgo(int algo) override;
|
||||
[[nodiscard]] auto getDownscaleAlgo() const -> int override { return downscale_algo_; }
|
||||
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
|
||||
|
||||
void setActiveShader(ShaderType type) override;
|
||||
void setCrtPiParams(const CrtPiParams& p) override;
|
||||
[[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }
|
||||
|
||||
private:
|
||||
static auto createShaderMSL(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||
|
||||
static auto createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||
|
||||
auto createPipeline() -> bool;
|
||||
auto createCrtPiPipeline() -> bool;
|
||||
auto reinitTexturesAndBuffer() -> bool;
|
||||
auto recreateScaledTexture(int factor) -> bool;
|
||||
static auto calcSsFactor(float zoom) -> int;
|
||||
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
|
||||
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr;
|
||||
SDL_GPUTexture* scene_texture_ = nullptr;
|
||||
SDL_GPUTexture* scaled_texture_ = nullptr;
|
||||
SDL_GPUTexture* postfx_texture_ = nullptr;
|
||||
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr;
|
||||
SDL_GPUSampler* linear_sampler_ = nullptr;
|
||||
|
||||
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
|
||||
CrtPiUniforms crtpi_uniforms_{.scanline_weight = 6.0F, .scanline_gap_brightness = 0.12F, .bloom_factor = 3.5F, .input_gamma = 2.4F, .output_gamma = 2.2F, .mask_brightness = 0.80F, .curvature_x = 0.05F, .curvature_y = 0.10F, .mask_type = 2, .enable_scanlines = 1, .enable_multisample = 1, .enable_gamma = 1};
|
||||
ShaderType active_shader_ = ShaderType::POSTFX;
|
||||
|
||||
int game_width_ = 0;
|
||||
int game_height_ = 0;
|
||||
int ss_factor_ = 0;
|
||||
int oversample_ = 1;
|
||||
int downscale_algo_ = 1;
|
||||
std::string driver_name_;
|
||||
std::string preferred_driver_;
|
||||
bool is_initialized_ = false;
|
||||
bool vsync_ = true;
|
||||
bool integer_scale_ = false;
|
||||
bool linear_upscale_ = false;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
633
source/core/rendering/sdl3gpu/upscale_frag_spv.h
Normal file
633
source/core/rendering/sdl3gpu/upscale_frag_spv.h
Normal file
@@ -0,0 +1,633 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
static const uint8_t kupscale_frag_spv[] = {
|
||||
0x03,
|
||||
0x02,
|
||||
0x23,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x14,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x53,
|
||||
0x4c,
|
||||
0x2e,
|
||||
0x73,
|
||||
0x74,
|
||||
0x64,
|
||||
0x2e,
|
||||
0x34,
|
||||
0x35,
|
||||
0x30,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0e,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x6d,
|
||||
0x61,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x10,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xc2,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0a,
|
||||
0x00,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x5f,
|
||||
0x47,
|
||||
0x4f,
|
||||
0x4f,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x45,
|
||||
0x5f,
|
||||
0x63,
|
||||
0x70,
|
||||
0x70,
|
||||
0x5f,
|
||||
0x73,
|
||||
0x74,
|
||||
0x79,
|
||||
0x6c,
|
||||
0x65,
|
||||
0x5f,
|
||||
0x6c,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x65,
|
||||
0x5f,
|
||||
0x64,
|
||||
0x69,
|
||||
0x72,
|
||||
0x65,
|
||||
0x63,
|
||||
0x74,
|
||||
0x69,
|
||||
0x76,
|
||||
0x65,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x08,
|
||||
0x00,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x5f,
|
||||
0x47,
|
||||
0x4f,
|
||||
0x4f,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x45,
|
||||
0x5f,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x63,
|
||||
0x6c,
|
||||
0x75,
|
||||
0x64,
|
||||
0x65,
|
||||
0x5f,
|
||||
0x64,
|
||||
0x69,
|
||||
0x72,
|
||||
0x65,
|
||||
0x63,
|
||||
0x74,
|
||||
0x69,
|
||||
0x76,
|
||||
0x65,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x6d,
|
||||
0x61,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x6f,
|
||||
0x75,
|
||||
0x74,
|
||||
0x5f,
|
||||
0x63,
|
||||
0x6f,
|
||||
0x6c,
|
||||
0x6f,
|
||||
0x72,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x73,
|
||||
0x63,
|
||||
0x65,
|
||||
0x6e,
|
||||
0x65,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x76,
|
||||
0x5f,
|
||||
0x75,
|
||||
0x76,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x21,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x22,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x13,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x21,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x16,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x17,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x08,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3b,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x08,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x19,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x0a,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1b,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0a,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0c,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3b,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0c,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x17,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x10,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3b,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x10,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x36,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xf8,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3d,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3d,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x12,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x57,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x13,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x12,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3e,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x13,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xfd,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x38,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00};
|
||||
static const size_t kupscale_frag_spv_size = 628;
|
||||
88
source/core/rendering/shader_backend.hpp
Normal file
88
source/core/rendering/shader_backend.hpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/** @brief Identificador del shader de post-procesado activo */
|
||||
enum class ShaderType { POSTFX,
|
||||
CRTPI };
|
||||
|
||||
/**
|
||||
* @brief Parámetros de intensidad de los efectos PostFX
|
||||
*/
|
||||
struct PostFXParams {
|
||||
float vignette = 0.0F;
|
||||
float scanlines = 0.0F;
|
||||
float chroma = 0.0F;
|
||||
float mask = 0.0F;
|
||||
float gamma = 0.0F;
|
||||
float curvature = 0.0F;
|
||||
float bleeding = 0.0F;
|
||||
float flicker = 0.0F;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Parámetros del shader CRT-Pi (algoritmo de scanlines continuas)
|
||||
*/
|
||||
struct CrtPiParams {
|
||||
float scanline_weight{6.0F};
|
||||
float scanline_gap_brightness{0.12F};
|
||||
float bloom_factor{3.5F};
|
||||
float input_gamma{2.4F};
|
||||
float output_gamma{2.2F};
|
||||
float mask_brightness{0.80F};
|
||||
float curvature_x{0.05F};
|
||||
float curvature_y{0.10F};
|
||||
int mask_type{2};
|
||||
bool enable_scanlines{true};
|
||||
bool enable_multisample{true};
|
||||
bool enable_gamma{true};
|
||||
bool enable_curvature{false};
|
||||
bool enable_sharper{false};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Interfaz abstracta para backends de renderizado con shaders
|
||||
*/
|
||||
class ShaderBackend {
|
||||
public:
|
||||
virtual ~ShaderBackend() = default;
|
||||
|
||||
virtual auto init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) -> bool = 0;
|
||||
|
||||
virtual void render() = 0;
|
||||
virtual void setTextureSize(float width, float height) = 0;
|
||||
virtual void cleanup() = 0;
|
||||
|
||||
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
|
||||
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
|
||||
virtual void setVSync(bool /*vsync*/) {}
|
||||
virtual void setScaleMode(bool /*integer_scale*/) {}
|
||||
virtual void setOversample(int /*factor*/) {}
|
||||
|
||||
virtual void setLinearUpscale(bool /*linear*/) {}
|
||||
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
|
||||
|
||||
virtual void setDownscaleAlgo(int /*algo*/) {}
|
||||
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
|
||||
|
||||
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
|
||||
|
||||
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
||||
|
||||
[[nodiscard]] virtual auto getDriverName() const -> std::string { return {}; }
|
||||
virtual void setPreferredDriver(const std::string& /*driver*/) {}
|
||||
|
||||
virtual void setActiveShader(ShaderType /*type*/) {}
|
||||
virtual void setCrtPiParams(const CrtPiParams& /*p*/) {}
|
||||
[[nodiscard]] virtual auto getActiveShader() const -> ShaderType { return ShaderType::POSTFX; }
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
304
source/core/rendering/sprite/animated_sprite.cpp
Normal file
304
source/core/rendering/sprite/animated_sprite.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
#include "animated_sprite.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_LogWarn, SDL_LogCategory, SDL_LogError, SDL_FRect
|
||||
|
||||
#include <algorithm> // Para min
|
||||
#include <cstddef> // Para size_t
|
||||
#include <fstream> // Para basic_istream, basic_ifstream, istream, basic_ios, ifstream, istringstream, stringstream
|
||||
#include <iostream> // Para std::cout
|
||||
#include <sstream> // Para basic_istringstream, basic_stringstream
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <utility> // Para move, pair
|
||||
|
||||
#include "resource_helper.hpp" // Para loadFile
|
||||
#include "texture.hpp" // Para Texture
|
||||
|
||||
// Carga las animaciones en un vector(Animations) desde un fichero
|
||||
auto loadAnimationsFromFile(const std::string& file_path) -> AnimationsFileBuffer {
|
||||
// Intentar cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
std::istringstream stream;
|
||||
bool using_resource_data = false;
|
||||
|
||||
if (!resource_data.empty()) {
|
||||
std::string content(resource_data.begin(), resource_data.end());
|
||||
stream.str(content);
|
||||
using_resource_data = true;
|
||||
}
|
||||
|
||||
// Fallback a archivo directo
|
||||
std::ifstream file;
|
||||
if (!using_resource_data) {
|
||||
file.open(file_path);
|
||||
if (!file) {
|
||||
std::cout << "Error: Fichero no encontrado " << file_path << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + file_path);
|
||||
}
|
||||
}
|
||||
|
||||
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
||||
|
||||
std::vector<std::string> buffer;
|
||||
std::string line;
|
||||
while (std::getline(input_stream, line)) {
|
||||
// Eliminar caracteres de retorno de carro (\r) al final de la línea
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
if (!line.empty()) {
|
||||
buffer.push_back(line);
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
AnimatedSprite::AnimatedSprite(std::shared_ptr<Texture> texture, const std::string& file_path)
|
||||
: MovingSprite(std::move(texture)) {
|
||||
// Carga las animaciones
|
||||
if (!file_path.empty()) {
|
||||
auto buffer = loadAnimationsFromFile(file_path);
|
||||
loadFromAnimationsFileBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor
|
||||
AnimatedSprite::AnimatedSprite(std::shared_ptr<Texture> texture, const AnimationsFileBuffer& animations)
|
||||
: MovingSprite(std::move(texture)) {
|
||||
if (!animations.empty()) {
|
||||
loadFromAnimationsFileBuffer(animations);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene el índice de la animación a partir del nombre
|
||||
auto AnimatedSprite::getAnimationIndex(const std::string& name) -> int {
|
||||
auto iterator = animation_indices_.find(name);
|
||||
if (iterator != animation_indices_.end()) {
|
||||
// Si se encuentra la animación en el mapa, devuelve su índice
|
||||
return iterator->second;
|
||||
}
|
||||
|
||||
// Si no se encuentra, muestra una advertencia y devuelve -1
|
||||
std::cout << "** Warning: could not find \"" << name << "\" animation" << '\n';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calcula el frame correspondiente a la animación (time-based)
|
||||
void AnimatedSprite::animate(float delta_time) {
|
||||
if (animations_[current_animation_].speed == 0 || animations_[current_animation_].paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Acumular tiempo transcurrido
|
||||
animations_[current_animation_].time_accumulator += delta_time;
|
||||
|
||||
// Verificar si es momento de cambiar frame
|
||||
if (animations_[current_animation_].time_accumulator >= animations_[current_animation_].speed) {
|
||||
animations_[current_animation_].time_accumulator -= animations_[current_animation_].speed;
|
||||
animations_[current_animation_].current_frame++;
|
||||
|
||||
// Si alcanza el final de la animación
|
||||
if (animations_[current_animation_].current_frame >= animations_[current_animation_].frames.size()) {
|
||||
if (animations_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame
|
||||
animations_[current_animation_].current_frame = animations_[current_animation_].frames.size() - 1;
|
||||
animations_[current_animation_].completed = true;
|
||||
} else { // Si hay loop, vuelve al frame indicado
|
||||
animations_[current_animation_].time_accumulator = 0.0F;
|
||||
animations_[current_animation_].current_frame = animations_[current_animation_].loop;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar el sprite clip
|
||||
updateSpriteClip();
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado la animación
|
||||
auto AnimatedSprite::animationIsCompleted() -> bool {
|
||||
return animations_[current_animation_].completed;
|
||||
}
|
||||
|
||||
// Establece la animacion actual
|
||||
void AnimatedSprite::setCurrentAnimation(const std::string& name, bool reset) {
|
||||
const auto NEW_ANIMATION = getAnimationIndex(name);
|
||||
if (current_animation_ != NEW_ANIMATION) {
|
||||
const auto OLD_ANIMATION = current_animation_;
|
||||
current_animation_ = NEW_ANIMATION;
|
||||
if (reset) {
|
||||
animations_[current_animation_].current_frame = 0;
|
||||
animations_[current_animation_].time_accumulator = 0.0F;
|
||||
animations_[current_animation_].completed = false;
|
||||
} else {
|
||||
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size() - 1);
|
||||
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator;
|
||||
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
|
||||
}
|
||||
updateSpriteClip();
|
||||
}
|
||||
}
|
||||
|
||||
// Establece la animacion actual
|
||||
void AnimatedSprite::setCurrentAnimation(int index, bool reset) {
|
||||
const auto NEW_ANIMATION = index;
|
||||
if (current_animation_ != NEW_ANIMATION) {
|
||||
const auto OLD_ANIMATION = current_animation_;
|
||||
current_animation_ = NEW_ANIMATION;
|
||||
if (reset) {
|
||||
animations_[current_animation_].current_frame = 0;
|
||||
animations_[current_animation_].time_accumulator = 0.0F;
|
||||
animations_[current_animation_].completed = false;
|
||||
} else {
|
||||
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size());
|
||||
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator;
|
||||
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
|
||||
}
|
||||
updateSpriteClip();
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las variables del objeto (time-based)
|
||||
void AnimatedSprite::update(float delta_time) {
|
||||
animate(delta_time);
|
||||
MovingSprite::update(delta_time);
|
||||
}
|
||||
|
||||
// Reinicia la animación
|
||||
void AnimatedSprite::resetAnimation() {
|
||||
animations_[current_animation_].current_frame = 0;
|
||||
animations_[current_animation_].time_accumulator = 0.0F;
|
||||
animations_[current_animation_].completed = false;
|
||||
animations_[current_animation_].paused = false;
|
||||
updateSpriteClip();
|
||||
}
|
||||
|
||||
// Carga la animación desde un vector de cadenas
|
||||
void AnimatedSprite::loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source) {
|
||||
AnimationConfig config;
|
||||
|
||||
size_t index = 0;
|
||||
while (index < source.size()) {
|
||||
const std::string& line = source.at(index);
|
||||
|
||||
if (line == "[animation]") {
|
||||
index = processAnimationBlock(source, index, config);
|
||||
} else {
|
||||
processConfigLine(line, config);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
// Pone un valor por defecto
|
||||
setWidth(config.frame_width);
|
||||
setHeight(config.frame_height);
|
||||
|
||||
// Establece el primer frame inmediatamente si hay animaciones
|
||||
if (!animations_.empty()) {
|
||||
current_animation_ = 0;
|
||||
updateSpriteClip();
|
||||
}
|
||||
}
|
||||
|
||||
// Procesa una línea de configuración
|
||||
void AnimatedSprite::processConfigLine(const std::string& line, AnimationConfig& config) {
|
||||
size_t pos = line.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string key = line.substr(0, pos);
|
||||
int value = std::stoi(line.substr(pos + 1));
|
||||
|
||||
if (key == "frame_width") {
|
||||
config.frame_width = value;
|
||||
updateFrameCalculations(config);
|
||||
} else if (key == "frame_height") {
|
||||
config.frame_height = value;
|
||||
updateFrameCalculations(config);
|
||||
} else {
|
||||
std::cout << "Warning: unknown parameter " << key << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza los cálculos basados en las dimensiones del frame
|
||||
void AnimatedSprite::updateFrameCalculations(AnimationConfig& config) {
|
||||
config.frames_per_row = getTexture()->getWidth() / config.frame_width;
|
||||
const int WIDTH = getTexture()->getWidth() / config.frame_width;
|
||||
const int HEIGHT = getTexture()->getHeight() / config.frame_height;
|
||||
config.max_tiles = WIDTH * HEIGHT;
|
||||
}
|
||||
|
||||
// Procesa un bloque completo de animación
|
||||
auto AnimatedSprite::processAnimationBlock(const AnimationsFileBuffer& source, size_t start_index, const AnimationConfig& config) -> size_t {
|
||||
Animation animation;
|
||||
size_t index = start_index + 1; // Salta la línea "[animation]"
|
||||
|
||||
while (index < source.size()) {
|
||||
const std::string& line = source.at(index);
|
||||
|
||||
if (line == "[/animation]") {
|
||||
break;
|
||||
}
|
||||
|
||||
processAnimationParameter(line, animation, config);
|
||||
index++;
|
||||
}
|
||||
|
||||
// Añade la animación al vector de animaciones
|
||||
animations_.emplace_back(animation);
|
||||
|
||||
// Rellena el mapa con el nombre y el nuevo índice
|
||||
animation_indices_[animation.name] = animations_.size() - 1;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
// Procesa un parámetro individual de animación
|
||||
void AnimatedSprite::processAnimationParameter(const std::string& line, Animation& animation, const AnimationConfig& config) {
|
||||
size_t pos = line.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string key = line.substr(0, pos);
|
||||
std::string value = line.substr(pos + 1);
|
||||
|
||||
if (key == "name") {
|
||||
animation.name = value;
|
||||
} else if (key == "speed") {
|
||||
animation.speed = std::stof(value);
|
||||
} else if (key == "loop") {
|
||||
animation.loop = std::stoi(value);
|
||||
} else if (key == "frames") {
|
||||
parseFramesParameter(value, animation, config);
|
||||
} else {
|
||||
std::cout << "Warning: unknown parameter " << key << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Parsea el parámetro de frames (lista separada por comas)
|
||||
void AnimatedSprite::parseFramesParameter(const std::string& frames_str, Animation& animation, const AnimationConfig& config) {
|
||||
std::stringstream stream(frames_str);
|
||||
std::string tmp;
|
||||
SDL_FRect rect = {.x = 0, .y = 0, .w = config.frame_width, .h = config.frame_height};
|
||||
|
||||
while (getline(stream, tmp, ',')) {
|
||||
const int NUM_TILE = std::stoi(tmp);
|
||||
if (NUM_TILE <= config.max_tiles) {
|
||||
rect.x = (NUM_TILE % config.frames_per_row) * config.frame_width;
|
||||
rect.y = (NUM_TILE / config.frames_per_row) * config.frame_height;
|
||||
animation.frames.emplace_back(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Establece la velocidad de la animación
|
||||
void AnimatedSprite::setAnimationSpeed(float value) {
|
||||
animations_[current_animation_].speed = value;
|
||||
}
|
||||
|
||||
// Actualiza el spriteClip con el frame correspondiente
|
||||
void AnimatedSprite::updateSpriteClip() {
|
||||
setSpriteClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
||||
}
|
||||
88
source/core/rendering/sprite/animated_sprite.hpp
Normal file
88
source/core/rendering/sprite/animated_sprite.hpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect
|
||||
|
||||
#include <cstddef> // Para size_t
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para basic_string, string, hash
|
||||
#include <unordered_map> // Para unordered_map
|
||||
#include <utility> // Para move
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "moving_sprite.hpp" // for MovingSprite
|
||||
|
||||
// Declaración adelantada
|
||||
class Texture;
|
||||
|
||||
// --- Estructuras ---
|
||||
struct Animation {
|
||||
static constexpr float DEFAULT_SPEED = 80.0F;
|
||||
|
||||
std::string name; // Nombre de la animación
|
||||
std::vector<SDL_FRect> frames; // Frames que componen la animación
|
||||
float speed{DEFAULT_SPEED}; // Velocidad de reproducción (ms entre frames)
|
||||
int loop{0}; // Frame de vuelta al terminar (-1 para no repetir)
|
||||
bool completed{false}; // Indica si la animación ha finalizado
|
||||
size_t current_frame{0}; // Frame actual en reproducción
|
||||
float time_accumulator{0.0F}; // Acumulador de tiempo para animaciones time-based
|
||||
bool paused{false}; // La animación no avanza
|
||||
|
||||
Animation() = default;
|
||||
};
|
||||
|
||||
struct AnimationConfig {
|
||||
float frame_width = 1.0F;
|
||||
float frame_height = 1.0F;
|
||||
int frames_per_row = 1;
|
||||
int max_tiles = 1;
|
||||
};
|
||||
|
||||
// --- Tipos ---
|
||||
using AnimationsFileBuffer = std::vector<std::string>; // Buffer de animaciones
|
||||
|
||||
// --- Funciones ---
|
||||
auto loadAnimationsFromFile(const std::string& file_path) -> AnimationsFileBuffer; // Carga las animaciones desde un fichero
|
||||
|
||||
// --- Clase AnimatedSprite: sprite animado que hereda de MovingSprite ---
|
||||
class AnimatedSprite : public MovingSprite {
|
||||
public:
|
||||
// --- Constructores y destructor ---
|
||||
AnimatedSprite(std::shared_ptr<Texture> texture, const std::string& file_path);
|
||||
AnimatedSprite(std::shared_ptr<Texture> texture, const AnimationsFileBuffer& animations);
|
||||
explicit AnimatedSprite(std::shared_ptr<Texture> texture)
|
||||
: MovingSprite(std::move(texture)) {}
|
||||
~AnimatedSprite() override = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time) override; // Actualiza la animación (time-based)
|
||||
|
||||
// --- Control de animaciones ---
|
||||
void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre
|
||||
void setCurrentAnimation(int index = 0, bool reset = true); // Establece la animación por índice
|
||||
void resetAnimation(); // Reinicia la animación actual
|
||||
void setAnimationSpeed(float value); // Establece la velocidad de la animación
|
||||
[[nodiscard]] auto getAnimationSpeed() const -> float { return animations_[current_animation_].speed; } // Obtiene la velocidad de la animación actual
|
||||
void animtionPause() { animations_[current_animation_].paused = true; } // Detiene la animación
|
||||
void animationResume() { animations_[current_animation_].paused = false; } // Reanuda la animación
|
||||
[[nodiscard]] auto getCurrentAnimationFrame() const -> size_t { return animations_[current_animation_].current_frame; } // Obtiene el numero de frame de la animación actual
|
||||
|
||||
// --- Consultas ---
|
||||
auto animationIsCompleted() -> bool; // Comprueba si la animación ha terminado
|
||||
auto getAnimationIndex(const std::string& name) -> int; // Obtiene el índice de una animación por nombre
|
||||
|
||||
protected:
|
||||
// --- Variables de estado ---
|
||||
std::vector<Animation> animations_; // Vector de animaciones disponibles
|
||||
std::unordered_map<std::string, int> animation_indices_; // Mapa para búsqueda rápida por nombre
|
||||
int current_animation_ = 0; // Índice de la animación activa
|
||||
|
||||
// --- Métodos internos ---
|
||||
void animate(float delta_time); // Calcula el frame correspondiente a la animación (time-based)
|
||||
void loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source); // Carga la animación desde un vector de cadenas
|
||||
void processConfigLine(const std::string& line, AnimationConfig& config); // Procesa una línea de configuración
|
||||
void updateFrameCalculations(AnimationConfig& config); // Actualiza los cálculos basados en las dimensiones del frame
|
||||
auto processAnimationBlock(const AnimationsFileBuffer& source, size_t start_index, const AnimationConfig& config) -> size_t; // Procesa un bloque completo de animación
|
||||
static void processAnimationParameter(const std::string& line, Animation& animation, const AnimationConfig& config); // Procesa un parámetro individual de animación
|
||||
static void parseFramesParameter(const std::string& frames_str, Animation& animation, const AnimationConfig& config); // Parsea el parámetro de frames (lista separada por comas)
|
||||
void updateSpriteClip(); // Actualiza el spriteClip con el frame correspondiente
|
||||
};
|
||||
255
source/core/rendering/sprite/card_sprite.cpp
Normal file
255
source/core/rendering/sprite/card_sprite.cpp
Normal file
@@ -0,0 +1,255 @@
|
||||
#include "card_sprite.hpp"
|
||||
|
||||
#include <algorithm> // Para std::clamp
|
||||
#include <functional> // Para function
|
||||
#include <utility> // Para move
|
||||
|
||||
#include "texture.hpp" // Para Texture
|
||||
#include "utils.hpp" // Para easeOutBounce, easeOutCubic
|
||||
|
||||
// Constructor
|
||||
CardSprite::CardSprite(std::shared_ptr<Texture> texture)
|
||||
: MovingSprite(std::move(texture)),
|
||||
entry_easing_(easeOutBounce) {}
|
||||
|
||||
// Inicia la animación de entrada (solo si está en IDLE)
|
||||
auto CardSprite::enable() -> bool {
|
||||
if (state_ != CardState::IDLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ = CardState::ENTERING;
|
||||
entry_elapsed_ = 0.0F;
|
||||
first_touch_ = false;
|
||||
|
||||
// Posición inicial (borde de pantalla)
|
||||
setPos(entry_start_x_, entry_start_y_);
|
||||
|
||||
// Zoom inicial grande (como si estuviera cerca de la cámara)
|
||||
horizontal_zoom_ = start_zoom_;
|
||||
vertical_zoom_ = start_zoom_;
|
||||
|
||||
// Ángulo inicial
|
||||
rotate_.angle = start_angle_;
|
||||
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
|
||||
|
||||
shadow_visible_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Inicia la animación de salida (solo si está en LANDED)
|
||||
void CardSprite::startExit() {
|
||||
if (state_ != CardState::LANDED) {
|
||||
return;
|
||||
}
|
||||
|
||||
state_ = CardState::EXITING;
|
||||
shadow_visible_ = true;
|
||||
|
||||
// Velocidad y aceleración de salida
|
||||
vx_ = exit_vx_;
|
||||
vy_ = exit_vy_;
|
||||
ax_ = exit_ax_;
|
||||
ay_ = exit_ay_;
|
||||
|
||||
// Rotación continua
|
||||
rotate_.enabled = true;
|
||||
rotate_.amount = exit_rotate_amount_;
|
||||
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
|
||||
}
|
||||
|
||||
// Actualiza según el estado
|
||||
void CardSprite::update(float delta_time) {
|
||||
switch (state_) {
|
||||
case CardState::ENTERING:
|
||||
updateEntering(delta_time);
|
||||
break;
|
||||
case CardState::EXITING:
|
||||
updateExiting(delta_time);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Animación de entrada: interpola posición, zoom y ángulo
|
||||
void CardSprite::updateEntering(float delta_time) {
|
||||
entry_elapsed_ += delta_time;
|
||||
|
||||
float progress = std::clamp(entry_elapsed_ / entry_duration_s_, 0.0F, 1.0F);
|
||||
double eased = entry_easing_(static_cast<double>(progress));
|
||||
|
||||
// Zoom: de start_zoom_ a 1.0 con rebote
|
||||
auto current_zoom = static_cast<float>(start_zoom_ + (1.0 - start_zoom_) * eased);
|
||||
horizontal_zoom_ = current_zoom;
|
||||
vertical_zoom_ = current_zoom;
|
||||
|
||||
// Ángulo: de start_angle_ a 0 con rebote
|
||||
rotate_.angle = start_angle_ * (1.0 - eased);
|
||||
|
||||
// Posición: de entry_start a landing con easing suave (sin rebote)
|
||||
// Usamos easeOutCubic para que el desplazamiento sea fluido
|
||||
double pos_eased = easeOutCubic(static_cast<double>(progress));
|
||||
auto current_x = static_cast<float>(entry_start_x_ + (landing_x_ - entry_start_x_) * pos_eased);
|
||||
auto current_y = static_cast<float>(entry_start_y_ + (landing_y_ - entry_start_y_) * pos_eased);
|
||||
setPos(current_x, current_y);
|
||||
|
||||
// Detecta el primer toque (cuando el easing alcanza ~1.0 por primera vez)
|
||||
if (!first_touch_ && eased >= FIRST_TOUCH_THRESHOLD) {
|
||||
first_touch_ = true;
|
||||
}
|
||||
|
||||
// Transición a LANDED cuando termina la animación completa
|
||||
if (progress >= 1.0F) {
|
||||
horizontal_zoom_ = 1.0F;
|
||||
vertical_zoom_ = 1.0F;
|
||||
rotate_.angle = 0.0;
|
||||
setPos(landing_x_, landing_y_);
|
||||
state_ = CardState::LANDED;
|
||||
first_touch_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Animación de salida: movimiento + rotación continua + zoom opcional
|
||||
void CardSprite::updateExiting(float delta_time) {
|
||||
move(delta_time);
|
||||
rotate(delta_time);
|
||||
|
||||
// Ganar altura gradualmente (zoom hacia el objetivo)
|
||||
if (exit_zoom_speed_ > 0.0F && horizontal_zoom_ < exit_target_zoom_) {
|
||||
float new_zoom = horizontal_zoom_ + exit_zoom_speed_ * delta_time;
|
||||
if (new_zoom > exit_target_zoom_) {
|
||||
new_zoom = exit_target_zoom_;
|
||||
}
|
||||
horizontal_zoom_ = new_zoom;
|
||||
vertical_zoom_ = new_zoom;
|
||||
}
|
||||
|
||||
if (isOffScreen()) {
|
||||
state_ = CardState::FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza el sprite y su sombra
|
||||
void CardSprite::render() {
|
||||
if (state_ == CardState::IDLE || state_ == CardState::FINISHED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sombra primero (debajo de la tarjeta)
|
||||
if (shadow_visible_ && shadow_texture_) {
|
||||
renderShadow();
|
||||
}
|
||||
|
||||
// Tarjeta
|
||||
MovingSprite::render();
|
||||
}
|
||||
|
||||
// Renderiza la sombra con efecto de perspectiva 2D→3D (efecto helicóptero)
|
||||
//
|
||||
// Fuente de luz en la esquina superior izquierda (0,0).
|
||||
// La sombra se mueve con la tarjeta pero desplazada en dirección opuesta a la luz
|
||||
// (abajo-derecha a 45°). Cuanto más alta la tarjeta (zoom > 1.0):
|
||||
// - Más separada de la tarjeta (offset grande)
|
||||
// - Más pequeña (proyección lejana)
|
||||
// Cuando la tarjeta está en la mesa (zoom=1.0):
|
||||
// - Sombra pegada con offset base
|
||||
// - Tamaño real
|
||||
void CardSprite::renderShadow() {
|
||||
// Altura sobre la mesa: 0.0 = en la mesa, 0.8 = alta (zoom 1.8)
|
||||
float height = horizontal_zoom_ - 1.0F;
|
||||
|
||||
// Escala: más pequeña cuanto más alta
|
||||
float shadow_zoom = 1.0F / horizontal_zoom_;
|
||||
|
||||
// Offset respecto a la tarjeta: base + extra proporcional a la altura
|
||||
// La sombra se aleja en diagonal abajo-derecha (opuesta a la luz en 0,0)
|
||||
float offset_x = shadow_offset_x_ + height * SHADOW_HEIGHT_MULTIPLIER;
|
||||
float offset_y = shadow_offset_y_ + height * SHADOW_HEIGHT_MULTIPLIER;
|
||||
|
||||
shadow_texture_->render(
|
||||
pos_.x + offset_x,
|
||||
pos_.y + offset_y,
|
||||
&sprite_clip_,
|
||||
shadow_zoom,
|
||||
shadow_zoom,
|
||||
rotate_.angle,
|
||||
&rotate_.center,
|
||||
flip_);
|
||||
}
|
||||
|
||||
// Comprueba si el sprite está fuera de pantalla
|
||||
auto CardSprite::isOffScreen() const -> bool {
|
||||
float effective_width = pos_.w * horizontal_zoom_;
|
||||
float effective_height = pos_.h * vertical_zoom_;
|
||||
return (pos_.x + effective_width < -OFF_SCREEN_MARGIN ||
|
||||
pos_.x > screen_width_ + OFF_SCREEN_MARGIN ||
|
||||
pos_.y + effective_height < -OFF_SCREEN_MARGIN ||
|
||||
pos_.y > screen_height_ + OFF_SCREEN_MARGIN);
|
||||
}
|
||||
|
||||
// --- Consultas de estado ---
|
||||
auto CardSprite::hasLanded() const -> bool {
|
||||
return state_ == CardState::LANDED || state_ == CardState::EXITING || state_ == CardState::FINISHED;
|
||||
}
|
||||
|
||||
auto CardSprite::hasFirstTouch() const -> bool {
|
||||
return first_touch_;
|
||||
}
|
||||
|
||||
auto CardSprite::hasFinished() const -> bool {
|
||||
return state_ == CardState::FINISHED;
|
||||
}
|
||||
|
||||
auto CardSprite::isExiting() const -> bool {
|
||||
return state_ == CardState::EXITING;
|
||||
}
|
||||
|
||||
auto CardSprite::getState() const -> CardState {
|
||||
return state_;
|
||||
}
|
||||
|
||||
// --- Configuración ---
|
||||
void CardSprite::setEntryParams(float start_zoom, double start_angle, float duration_s, std::function<double(double)> easing) {
|
||||
start_zoom_ = start_zoom;
|
||||
start_angle_ = start_angle;
|
||||
entry_duration_s_ = duration_s;
|
||||
entry_easing_ = std::move(easing);
|
||||
}
|
||||
|
||||
void CardSprite::setEntryPosition(float start_x, float start_y) {
|
||||
entry_start_x_ = start_x;
|
||||
entry_start_y_ = start_y;
|
||||
}
|
||||
|
||||
void CardSprite::setLandingPosition(float x, float y) {
|
||||
landing_x_ = x;
|
||||
landing_y_ = y;
|
||||
}
|
||||
|
||||
void CardSprite::setExitParams(float vx, float vy, float ax, float ay, double rotate_amount) {
|
||||
exit_vx_ = vx;
|
||||
exit_vy_ = vy;
|
||||
exit_ax_ = ax;
|
||||
exit_ay_ = ay;
|
||||
exit_rotate_amount_ = rotate_amount;
|
||||
}
|
||||
|
||||
void CardSprite::setExitLift(float target_zoom, float zoom_speed) {
|
||||
exit_target_zoom_ = target_zoom;
|
||||
exit_zoom_speed_ = zoom_speed;
|
||||
}
|
||||
|
||||
void CardSprite::setShadowTexture(std::shared_ptr<Texture> texture) {
|
||||
shadow_texture_ = std::move(texture);
|
||||
}
|
||||
|
||||
void CardSprite::setShadowOffset(float offset_x, float offset_y) {
|
||||
shadow_offset_x_ = offset_x;
|
||||
shadow_offset_y_ = offset_y;
|
||||
}
|
||||
|
||||
void CardSprite::setScreenBounds(float width, float height) {
|
||||
screen_width_ = width;
|
||||
screen_height_ = height;
|
||||
}
|
||||
109
source/core/rendering/sprite/card_sprite.hpp
Normal file
109
source/core/rendering/sprite/card_sprite.hpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FPoint
|
||||
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para shared_ptr
|
||||
|
||||
#include "moving_sprite.hpp" // Para MovingSprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Estados de la tarjeta ---
|
||||
enum class CardState {
|
||||
IDLE, // No activada todavía
|
||||
ENTERING, // Animación de entrada (zoom + rotación + desplazamiento con rebote)
|
||||
LANDED, // En reposo sobre la mesa
|
||||
EXITING, // Saliendo de pantalla girando
|
||||
FINISHED, // Fuera de pantalla
|
||||
};
|
||||
|
||||
// --- Clase CardSprite: tarjeta animada con zoom, rotación y sombra integrada ---
|
||||
//
|
||||
// Simula una tarjeta lanzada sobre una mesa desde un borde de la pantalla.
|
||||
// Durante la entrada, interpola posición, zoom y rotación con easing (rebote).
|
||||
// Durante la salida, se desplaza fuera de pantalla girando, sin sombra.
|
||||
class CardSprite : public MovingSprite {
|
||||
public:
|
||||
explicit CardSprite(std::shared_ptr<Texture> texture);
|
||||
~CardSprite() override = default;
|
||||
|
||||
// --- Ciclo principal ---
|
||||
void update(float delta_time) override;
|
||||
void render() override;
|
||||
|
||||
// --- Control de estado ---
|
||||
auto enable() -> bool; // Inicia la animación de entrada (true si se activó)
|
||||
void startExit(); // Inicia la animación de salida
|
||||
|
||||
// --- Consultas de estado ---
|
||||
[[nodiscard]] auto hasLanded() const -> bool; // ¿Ha aterrizado definitivamente?
|
||||
[[nodiscard]] auto hasFirstTouch() const -> bool; // ¿Ha tocado la mesa por primera vez? (primer rebote)
|
||||
[[nodiscard]] auto hasFinished() const -> bool; // ¿Ha terminado completamente?
|
||||
[[nodiscard]] auto isExiting() const -> bool; // ¿Está saliendo de pantalla?
|
||||
[[nodiscard]] auto getState() const -> CardState; // Estado actual
|
||||
|
||||
// --- Configuración de entrada ---
|
||||
void setEntryParams(float start_zoom, double start_angle, float duration_s, std::function<double(double)> easing);
|
||||
void setEntryPosition(float start_x, float start_y); // Posición inicial (borde de pantalla)
|
||||
void setLandingPosition(float x, float y); // Posición final centrada
|
||||
|
||||
// --- Configuración de salida ---
|
||||
void setExitParams(float vx, float vy, float ax, float ay, double rotate_amount);
|
||||
void setExitLift(float target_zoom, float zoom_speed); // Ganar altura al salir (zoom > 1.0)
|
||||
|
||||
// --- Sombra ---
|
||||
void setShadowTexture(std::shared_ptr<Texture> texture);
|
||||
void setShadowOffset(float offset_x, float offset_y);
|
||||
|
||||
// --- Limites de pantalla (para detectar salida) ---
|
||||
void setScreenBounds(float width, float height);
|
||||
|
||||
private:
|
||||
// --- Estado ---
|
||||
CardState state_ = CardState::IDLE;
|
||||
bool first_touch_ = false; // Primer contacto con la mesa (eased >= umbral)
|
||||
|
||||
// --- Umbral para detectar el primer toque ---
|
||||
static constexpr double FIRST_TOUCH_THRESHOLD = 0.98;
|
||||
|
||||
// --- Parámetros de entrada ---
|
||||
float start_zoom_ = 1.8F;
|
||||
double start_angle_ = 15.0;
|
||||
float entry_duration_s_ = 1.5F;
|
||||
float entry_elapsed_ = 0.0F;
|
||||
std::function<double(double)> entry_easing_;
|
||||
float entry_start_x_ = 0.0F; // Posición inicial X (borde)
|
||||
float entry_start_y_ = 0.0F; // Posición inicial Y (borde)
|
||||
float landing_x_ = 0.0F;
|
||||
float landing_y_ = 0.0F;
|
||||
|
||||
// --- Parámetros de salida ---
|
||||
float exit_vx_ = 0.0F;
|
||||
float exit_vy_ = 0.0F;
|
||||
float exit_ax_ = 0.0F;
|
||||
float exit_ay_ = 0.0F;
|
||||
double exit_rotate_amount_ = 0.0;
|
||||
float exit_target_zoom_ = 1.0F; // Zoom objetivo al salir (>1.0 = se eleva)
|
||||
float exit_zoom_speed_ = 0.0F; // Velocidad de cambio de zoom por segundo
|
||||
|
||||
// --- Sombra ---
|
||||
std::shared_ptr<Texture> shadow_texture_;
|
||||
float shadow_offset_x_ = 8.0F;
|
||||
float shadow_offset_y_ = 8.0F;
|
||||
bool shadow_visible_ = true;
|
||||
|
||||
// --- Límites de pantalla ---
|
||||
float screen_width_ = 320.0F;
|
||||
float screen_height_ = 240.0F;
|
||||
|
||||
// --- Constantes ---
|
||||
static constexpr float OFF_SCREEN_MARGIN = 50.0F; // Margen fuera de pantalla para considerar FINISHED
|
||||
static constexpr float SHADOW_HEIGHT_MULTIPLIER = 400.0F; // Pixels de separación de sombra por unidad de altura
|
||||
|
||||
// --- Métodos internos ---
|
||||
void updateEntering(float delta_time);
|
||||
void updateExiting(float delta_time);
|
||||
void renderShadow();
|
||||
[[nodiscard]] auto isOffScreen() const -> bool;
|
||||
};
|
||||
136
source/core/rendering/sprite/moving_sprite.cpp
Normal file
136
source/core/rendering/sprite/moving_sprite.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "moving_sprite.hpp"
|
||||
|
||||
#include <cmath> // Para std::abs
|
||||
#include <utility>
|
||||
|
||||
#include "texture.hpp" // Para Texture
|
||||
|
||||
// Constructor
|
||||
MovingSprite::MovingSprite(std::shared_ptr<Texture> texture, SDL_FRect pos, Rotate rotate, float horizontal_zoom, float vertical_zoom, SDL_FlipMode flip)
|
||||
: Sprite(std::move(texture), pos),
|
||||
rotate_(rotate),
|
||||
flip_(flip),
|
||||
x_(pos.x),
|
||||
y_(pos.y),
|
||||
horizontal_zoom_(horizontal_zoom),
|
||||
vertical_zoom_(vertical_zoom) {}
|
||||
|
||||
MovingSprite::MovingSprite(std::shared_ptr<Texture> texture, SDL_FRect pos)
|
||||
: Sprite(std::move(texture), pos),
|
||||
flip_(SDL_FLIP_NONE),
|
||||
x_(pos.x),
|
||||
y_(pos.y),
|
||||
horizontal_zoom_(1.0F),
|
||||
vertical_zoom_(1.0F) {}
|
||||
|
||||
MovingSprite::MovingSprite(std::shared_ptr<Texture> texture)
|
||||
: Sprite(std::move(texture)),
|
||||
flip_(SDL_FLIP_NONE),
|
||||
horizontal_zoom_(1.0F),
|
||||
vertical_zoom_(1.0F) { Sprite::clear(); }
|
||||
|
||||
// Reinicia todas las variables
|
||||
void MovingSprite::clear() {
|
||||
stop();
|
||||
Sprite::clear();
|
||||
}
|
||||
|
||||
// Elimina el movimiento del sprite
|
||||
void MovingSprite::stop() {
|
||||
x_ = 0.0F; // Posición en el eje X
|
||||
y_ = 0.0F; // Posición en el eje Y
|
||||
|
||||
vx_ = 0.0F; // Velocidad en el eje X. Cantidad de pixeles a desplazarse
|
||||
vy_ = 0.0F; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse
|
||||
|
||||
ax_ = 0.0F; // Aceleración en el eje X. Variación de la velocidad
|
||||
ay_ = 0.0F; // Aceleración en el eje Y. Variación de la velocidad
|
||||
|
||||
rotate_ = Rotate(); // Inicializa la estructura
|
||||
|
||||
horizontal_zoom_ = 1.0F; // Zoom aplicado a la anchura
|
||||
vertical_zoom_ = 1.0F; // Zoom aplicado a la altura
|
||||
|
||||
flip_ = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite
|
||||
}
|
||||
|
||||
// Mueve el sprite (time-based)
|
||||
void MovingSprite::move(float delta_time) {
|
||||
// DeltaTime puro: velocidad (pixels/ms) * tiempo (ms)
|
||||
x_ += vx_ * delta_time;
|
||||
y_ += vy_ * delta_time;
|
||||
|
||||
// Aceleración (pixels/ms²) * tiempo (ms)
|
||||
vx_ += ax_ * delta_time;
|
||||
vy_ += ay_ * delta_time;
|
||||
|
||||
pos_.x = static_cast<int>(x_);
|
||||
pos_.y = static_cast<int>(y_);
|
||||
}
|
||||
|
||||
// Actualiza las variables internas del objeto (time-based)
|
||||
void MovingSprite::update(float delta_time) {
|
||||
move(delta_time);
|
||||
rotate(delta_time);
|
||||
}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void MovingSprite::render() {
|
||||
getTexture()->render(pos_.x, pos_.y, &sprite_clip_, horizontal_zoom_, vertical_zoom_, rotate_.angle, &rotate_.center, flip_);
|
||||
}
|
||||
|
||||
// Establece la rotacion (time-based)
|
||||
void MovingSprite::rotate(float delta_time) {
|
||||
if (rotate_.enabled) {
|
||||
rotate_.angle += rotate_.amount * delta_time;
|
||||
}
|
||||
}
|
||||
|
||||
// Activa o desactiva el efecto de rotación
|
||||
void MovingSprite::setRotate(bool enable) {
|
||||
rotate_.enabled = enable;
|
||||
}
|
||||
|
||||
// Habilita la rotación y establece el centro en el centro del sprite
|
||||
void MovingSprite::startRotate() {
|
||||
rotate_.enabled = true;
|
||||
rotate_.center.x = pos_.w / 2.0F;
|
||||
rotate_.center.y = pos_.h / 2.0F;
|
||||
}
|
||||
|
||||
// Detiene la rotación y resetea el ángulo a cero
|
||||
void MovingSprite::stopRotate(float threshold) {
|
||||
if (threshold == 0.0F || std::abs(rotate_.amount) <= threshold) {
|
||||
rotate_.enabled = false;
|
||||
rotate_.angle = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece la posición y_ el tamaño del objeto
|
||||
void MovingSprite::setPos(SDL_FRect rect) {
|
||||
x_ = rect.x;
|
||||
y_ = rect.y;
|
||||
|
||||
pos_ = rect;
|
||||
}
|
||||
|
||||
// Establece el valor de las variables
|
||||
void MovingSprite::setPos(float pos_x, float pos_y) {
|
||||
x_ = pos_x;
|
||||
y_ = pos_y;
|
||||
|
||||
pos_.x = static_cast<int>(x_);
|
||||
pos_.y = static_cast<int>(y_);
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setPosX(float pos_x) {
|
||||
x_ = pos_x;
|
||||
pos_.x = static_cast<int>(x_);
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setPosY(float pos_y) {
|
||||
y_ = pos_y;
|
||||
pos_.y = static_cast<int>(y_);
|
||||
}
|
||||
83
source/core/rendering/sprite/moving_sprite.hpp
Normal file
83
source/core/rendering/sprite/moving_sprite.hpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FlipMode, SDL_FPoint, SDL_FRect
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
|
||||
#include "sprite.hpp" // for Sprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Clase MovingSprite: añade movimiento y efectos de rotación, zoom y flip al sprite ---
|
||||
class MovingSprite : public Sprite {
|
||||
public:
|
||||
// --- Estructuras ---
|
||||
struct Rotate {
|
||||
bool enabled{false}; // Indica si ha de rotar
|
||||
double angle{0.0}; // Ángulo para dibujarlo
|
||||
float amount{0.0F}; // Cantidad de grados a girar en cada iteración
|
||||
SDL_FPoint center{.x = 0.0F, .y = 0.0F}; // Centro de rotación
|
||||
};
|
||||
|
||||
// --- Constructores y destructor ---
|
||||
MovingSprite(std::shared_ptr<Texture> texture, SDL_FRect pos, MovingSprite::Rotate rotate, float horizontal_zoom, float vertical_zoom, SDL_FlipMode flip);
|
||||
MovingSprite(std::shared_ptr<Texture> texture, SDL_FRect pos);
|
||||
explicit MovingSprite(std::shared_ptr<Texture> texture);
|
||||
~MovingSprite() override = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
virtual void update(float delta_time); // Actualiza las variables internas del objeto (time-based)
|
||||
void clear() override; // Reinicia todas las variables a cero
|
||||
void stop(); // Elimina el movimiento del sprite
|
||||
void render() override; // Muestra el sprite por pantalla
|
||||
|
||||
// --- Configuración ---
|
||||
void setPos(SDL_FRect rect); // Establece la posición y el tamaño del objeto
|
||||
void setPos(float pos_x, float pos_y); // Establece la posición del objeto
|
||||
void setPosX(float pos_x); // Establece la posición X
|
||||
void setPosY(float pos_y); // Establece la posición Y
|
||||
void setVelX(float value) { vx_ = value; } // Establece la velocidad X
|
||||
void setVelY(float value) { vy_ = value; } // Establece la velocidad Y
|
||||
void setAccelX(float value) { ax_ = value; } // Establece la aceleración X
|
||||
void setAccelY(float value) { ay_ = value; } // Establece la aceleración Y
|
||||
void setHorizontalZoom(float value) { horizontal_zoom_ = value; } // Establece el zoom horizontal
|
||||
void setVerticalZoom(float value) { vertical_zoom_ = value; } // Establece el zoom vertical
|
||||
void setAngle(double value) { rotate_.angle = value; } // Establece el ángulo
|
||||
void setRotatingCenter(SDL_FPoint point) { rotate_.center = point; } // Establece el centro de rotación
|
||||
void setRotate(bool enable); // Activa o desactiva el efecto de rotación
|
||||
void startRotate(); // Habilita la rotación con centro automático
|
||||
void stopRotate(float threshold = 0.0F); // Detiene la rotación y resetea ángulo
|
||||
void setRotateAmount(double value) { rotate_.amount = value; } // Establece la velocidad de rotación
|
||||
void scaleRotateAmount(float value) { rotate_.amount *= value; } // Modifica la velocidad de rotacion
|
||||
void switchRotate() { rotate_.amount *= -1; } // Cambia el sentido de la rotación
|
||||
void setFlip(SDL_FlipMode flip) { flip_ = flip; } // Establece el flip
|
||||
void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; } // Cambia el flip
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getPosX() const -> float { return x_; } // Obtiene la posición X
|
||||
[[nodiscard]] auto getPosY() const -> float { return y_; } // Obtiene la posición Y
|
||||
[[nodiscard]] auto getVelX() const -> float { return vx_; } // Obtiene la velocidad X
|
||||
[[nodiscard]] auto getVelY() const -> float { return vy_; } // Obtiene la velocidad Y
|
||||
[[nodiscard]] auto getAccelX() const -> float { return ax_; } // Obtiene la aceleración X
|
||||
[[nodiscard]] auto getAccelY() const -> float { return ay_; } // Obtiene la aceleración Y
|
||||
[[nodiscard]] auto isRotating() const -> bool { return rotate_.enabled; } // Verifica si está rotando
|
||||
auto getFlip() -> SDL_FlipMode { return flip_; } // Obtiene el flip
|
||||
|
||||
protected:
|
||||
// --- Variables de estado ---
|
||||
Rotate rotate_; // Variables usadas para controlar la rotación del sprite
|
||||
SDL_FlipMode flip_; // Indica cómo se voltea el sprite
|
||||
float x_ = 0.0F; // Posición en el eje X
|
||||
float y_ = 0.0F; // Posición en el eje Y
|
||||
float vx_ = 0.0F; // Velocidad en el eje X
|
||||
float vy_ = 0.0F; // Velocidad en el eje Y
|
||||
float ax_ = 0.0F; // Aceleración en el eje X
|
||||
float ay_ = 0.0F; // Aceleración en el eje Y
|
||||
float horizontal_zoom_; // Zoom aplicado a la anchura
|
||||
float vertical_zoom_; // Zoom aplicado a la altura
|
||||
|
||||
// --- Métodos internos ---
|
||||
void updateAngle() { rotate_.angle += rotate_.amount; } // Incrementa el valor del ángulo
|
||||
void move(float delta_time); // Mueve el sprite según velocidad y aceleración (time-based)
|
||||
void rotate(float delta_time); // Rota el sprite según los parámetros de rotación (time-based)
|
||||
};
|
||||
233
source/core/rendering/sprite/path_sprite.cpp
Normal file
233
source/core/rendering/sprite/path_sprite.cpp
Normal file
@@ -0,0 +1,233 @@
|
||||
// IWYU pragma: no_include <bits/std_abs.h>
|
||||
#include "path_sprite.hpp"
|
||||
|
||||
#include <cmath> // Para abs
|
||||
#include <functional> // Para function
|
||||
#include <utility> // Para move
|
||||
|
||||
// Constructor para paths por puntos (convertido a segundos)
|
||||
Path::Path(const std::vector<SDL_FPoint>& spots_init, float waiting_time_s_init)
|
||||
: spots(spots_init),
|
||||
is_point_path(true) {
|
||||
waiting_time_s = waiting_time_s_init;
|
||||
}
|
||||
|
||||
// Devuelve un vector con los puntos que conforman la ruta
|
||||
auto createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function<double(double)>& easing_function) -> std::vector<SDL_FPoint> {
|
||||
std::vector<SDL_FPoint> v;
|
||||
v.reserve(steps);
|
||||
|
||||
for (int i = 0; i < steps; ++i) {
|
||||
double t = static_cast<double>(i) / (steps - 1);
|
||||
double value = start + ((end - start) * easing_function(t));
|
||||
|
||||
if ((start > 0 && end < 0) || (start < 0 && end > 0)) {
|
||||
value = start + ((end > 0 ? 1 : -1) * std::abs(end - start) * easing_function(t));
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case PathType::HORIZONTAL:
|
||||
v.emplace_back(SDL_FPoint{.x = static_cast<float>(value), .y = fixed_pos});
|
||||
break;
|
||||
case PathType::VERTICAL:
|
||||
v.emplace_back(SDL_FPoint{.x = fixed_pos, .y = static_cast<float>(value)});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
// Actualiza la posición y comprueba si ha llegado a su destino
|
||||
void PathSprite::update(float delta_time) {
|
||||
if (enabled_ && !has_finished_) {
|
||||
moveThroughCurrentPath(delta_time);
|
||||
goToNextPathOrDie();
|
||||
}
|
||||
}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void PathSprite::render() {
|
||||
if (enabled_) {
|
||||
Sprite::render();
|
||||
}
|
||||
}
|
||||
|
||||
// Determina el tipo de centrado basado en el tipo de path
|
||||
auto PathSprite::determineCenteringType(const Path& path, bool centered) -> PathCentered {
|
||||
if (!centered) {
|
||||
return PathCentered::NONE;
|
||||
}
|
||||
|
||||
if (path.is_point_path) {
|
||||
// Lógica de centrado para paths por PUNTOS
|
||||
if (!path.spots.empty()) {
|
||||
// Si X es constante, es un path Vertical, centramos en X
|
||||
return (path.spots.back().x == path.spots.front().x) ? PathCentered::ON_X : PathCentered::ON_Y;
|
||||
}
|
||||
return PathCentered::NONE;
|
||||
}
|
||||
|
||||
// Lógica de centrado para paths GENERADOS
|
||||
// Si el tipo es Vertical, centramos en X
|
||||
return (path.type == PathType::VERTICAL) ? PathCentered::ON_X : PathCentered::ON_Y;
|
||||
}
|
||||
|
||||
// Aplica centrado en el eje X (para paths verticales)
|
||||
void PathSprite::centerPathOnX(Path& path, float offset) {
|
||||
if (path.is_point_path) {
|
||||
const float X_BASE = !path.spots.empty() ? path.spots.front().x : 0.0F;
|
||||
const float X = X_BASE - offset;
|
||||
for (auto& spot : path.spots) {
|
||||
spot.x = X;
|
||||
}
|
||||
} else {
|
||||
// Es un path generado, ajustamos la posición fija (que es X)
|
||||
path.fixed_pos -= offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica centrado en el eje Y (para paths horizontales)
|
||||
void PathSprite::centerPathOnY(Path& path, float offset) {
|
||||
if (path.is_point_path) {
|
||||
const float Y_BASE = !path.spots.empty() ? path.spots.front().y : 0.0F;
|
||||
const float Y = Y_BASE - offset;
|
||||
for (auto& spot : path.spots) {
|
||||
spot.y = Y;
|
||||
}
|
||||
} else {
|
||||
// Es un path generado, ajustamos la posición fija (que es Y)
|
||||
path.fixed_pos -= offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Añade un recorrido
|
||||
void PathSprite::addPath(Path path, bool centered) {
|
||||
PathCentered path_centered = determineCenteringType(path, centered);
|
||||
|
||||
switch (path_centered) {
|
||||
case PathCentered::ON_X:
|
||||
centerPathOnX(path, pos_.w / 2.0F);
|
||||
break;
|
||||
case PathCentered::ON_Y:
|
||||
centerPathOnY(path, pos_.h / 2.0F);
|
||||
break;
|
||||
case PathCentered::NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
paths_.emplace_back(std::move(path));
|
||||
}
|
||||
|
||||
// Añade un recorrido generado (en segundos)
|
||||
void PathSprite::addPath(float start, float end, PathType type, float fixed_pos, float duration_s, const std::function<double(double)>& easing_function, float waiting_time_s) {
|
||||
paths_.emplace_back(start, end, type, fixed_pos, duration_s, waiting_time_s, easing_function);
|
||||
}
|
||||
|
||||
// Añade un recorrido por puntos (en segundos)
|
||||
void PathSprite::addPath(const std::vector<SDL_FPoint>& spots, float waiting_time_s) {
|
||||
paths_.emplace_back(spots, waiting_time_s);
|
||||
}
|
||||
|
||||
// Habilita el objeto
|
||||
void PathSprite::enable() {
|
||||
if (paths_.empty() || enabled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabled_ = true;
|
||||
|
||||
// Establece la posición inicial
|
||||
auto& path = paths_.at(current_path_);
|
||||
if (path.is_point_path) {
|
||||
const auto& p = path.spots.at(path.counter);
|
||||
setPosition(p);
|
||||
} else {
|
||||
// Para paths generados, establecer posición inicial
|
||||
SDL_FPoint initial_pos;
|
||||
if (path.type == PathType::HORIZONTAL) {
|
||||
initial_pos = {.x = path.start_pos, .y = path.fixed_pos};
|
||||
} else {
|
||||
initial_pos = {.x = path.fixed_pos, .y = path.start_pos};
|
||||
}
|
||||
setPosition(initial_pos);
|
||||
}
|
||||
}
|
||||
|
||||
// Coloca el sprite en los diferentes puntos del recorrido
|
||||
void PathSprite::moveThroughCurrentPath(float delta_time) {
|
||||
auto& path = paths_.at(current_path_);
|
||||
|
||||
if (path.is_point_path) {
|
||||
// Lógica para paths por puntos (compatibilidad)
|
||||
const auto& p = path.spots.at(path.counter);
|
||||
setPosition(p);
|
||||
|
||||
if (!path.on_destination) {
|
||||
++path.counter;
|
||||
if (std::cmp_greater_equal(path.counter, path.spots.size())) {
|
||||
path.on_destination = true;
|
||||
path.counter = static_cast<int>(path.spots.size()) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.on_destination) {
|
||||
path.waiting_elapsed += delta_time;
|
||||
if (path.waiting_elapsed >= path.waiting_time_s) {
|
||||
path.finished = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Lógica para paths generados en tiempo real
|
||||
if (!path.on_destination) {
|
||||
path.elapsed_time += delta_time;
|
||||
|
||||
// Calcular progreso (0.0 a 1.0)
|
||||
float progress = path.elapsed_time / path.duration_s;
|
||||
if (progress >= 1.0F) {
|
||||
progress = 1.0F;
|
||||
path.on_destination = true;
|
||||
}
|
||||
|
||||
// Aplicar función de easing
|
||||
double eased_progress = path.easing_function(progress);
|
||||
|
||||
// Calcular posición actual
|
||||
float current_pos = path.start_pos + ((path.end_pos - path.start_pos) * static_cast<float>(eased_progress));
|
||||
|
||||
// Establecer posición según el tipo
|
||||
SDL_FPoint position;
|
||||
if (path.type == PathType::HORIZONTAL) {
|
||||
position = {.x = current_pos, .y = path.fixed_pos};
|
||||
} else {
|
||||
position = {.x = path.fixed_pos, .y = current_pos};
|
||||
}
|
||||
setPosition(position);
|
||||
} else {
|
||||
// Esperar en destino
|
||||
path.waiting_elapsed += delta_time;
|
||||
if (path.waiting_elapsed >= path.waiting_time_s) {
|
||||
path.finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia de recorrido o finaliza
|
||||
void PathSprite::goToNextPathOrDie() {
|
||||
// Comprueba si ha terminado el recorrdo actual
|
||||
if (paths_.at(current_path_).finished) {
|
||||
++current_path_;
|
||||
}
|
||||
|
||||
// Comprueba si quedan mas recorridos
|
||||
if (std::cmp_greater_equal(current_path_, paths_.size())) {
|
||||
has_finished_ = true;
|
||||
current_path_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Indica si ha terminado todos los recorridos
|
||||
auto PathSprite::hasFinished() const -> bool { return has_finished_; }
|
||||
101
source/core/rendering/sprite/path_sprite.hpp
Normal file
101
source/core/rendering/sprite/path_sprite.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FPoint
|
||||
|
||||
#include <functional> // Para std::function
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "sprite.hpp" // Para Sprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Enums ---
|
||||
enum class PathType { // Tipos de recorrido
|
||||
VERTICAL,
|
||||
HORIZONTAL,
|
||||
};
|
||||
|
||||
enum class PathCentered { // Centrado del recorrido
|
||||
ON_X,
|
||||
ON_Y,
|
||||
NONE,
|
||||
};
|
||||
|
||||
// --- Estructuras ---
|
||||
struct Path { // Define un recorrido para el sprite
|
||||
float start_pos; // Posición inicial
|
||||
float end_pos; // Posición final
|
||||
PathType type; // Tipo de movimiento (horizontal/vertical)
|
||||
float fixed_pos; // Posición fija en el eje contrario
|
||||
float duration_s; // Duración de la animación en segundos
|
||||
float waiting_time_s; // Tiempo de espera una vez en el destino
|
||||
std::function<double(double)> easing_function; // Función de easing
|
||||
float elapsed_time = 0.0F; // Tiempo transcurrido
|
||||
float waiting_elapsed = 0.0F; // Tiempo de espera transcurrido
|
||||
bool on_destination = false; // Indica si ha llegado al destino
|
||||
bool finished = false; // Indica si ha terminado de esperarse
|
||||
|
||||
// Constructor para paths generados
|
||||
Path(float start, float end, PathType path_type, float fixed, float duration, float waiting, std::function<double(double)> easing)
|
||||
: start_pos(start),
|
||||
end_pos(end),
|
||||
type(path_type),
|
||||
fixed_pos(fixed),
|
||||
duration_s(duration),
|
||||
waiting_time_s(waiting),
|
||||
easing_function(std::move(easing)) {}
|
||||
|
||||
// Constructor para paths por puntos (convertido a segundos)
|
||||
Path(const std::vector<SDL_FPoint>& spots_init, float waiting_time_s_init);
|
||||
|
||||
// Variables para paths por puntos
|
||||
std::vector<SDL_FPoint> spots; // Solo para paths por puntos
|
||||
int counter = 0; // Solo para paths por puntos
|
||||
bool is_point_path = false; // Indica si es un path por puntos
|
||||
};
|
||||
|
||||
// --- Funciones ---
|
||||
auto createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function<double(double)>& easing_function) -> std::vector<SDL_FPoint>; // Devuelve un vector con los puntos que conforman la ruta
|
||||
|
||||
// --- Clase PathSprite: Sprite que sigue uno o varios recorridos ---
|
||||
class PathSprite : public Sprite {
|
||||
public:
|
||||
// --- Constructor y destructor ---
|
||||
explicit PathSprite(std::shared_ptr<Texture> texture)
|
||||
: Sprite(std::move(texture)) {}
|
||||
~PathSprite() override = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time); // Actualiza la posición del sprite según el recorrido (delta_time en segundos)
|
||||
void render() override; // Muestra el sprite por pantalla
|
||||
|
||||
// --- Gestión de recorridos ---
|
||||
void addPath(Path path, bool centered = false); // Añade un recorrido (Path)
|
||||
void addPath(const std::vector<SDL_FPoint>& spots, float waiting_time_s = 0.0F); // Añade un recorrido a partir de puntos
|
||||
void addPath(float start, float end, PathType type, float fixed_pos, float duration_s, const std::function<double(double)>& easing_function, float waiting_time_s = 0.0F); // Añade un recorrido generado
|
||||
|
||||
// --- Estado y control ---
|
||||
void enable(); // Habilita el objeto
|
||||
[[nodiscard]] auto hasFinished() const -> bool; // Indica si ha terminado todos los recorridos
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getCurrentPath() const -> int { return current_path_; } // Devuelve el índice del recorrido actual
|
||||
|
||||
private:
|
||||
// --- Variables internas ---
|
||||
bool enabled_ = false; // Indica si el objeto está habilitado
|
||||
bool has_finished_ = false; // Indica si el objeto ha finalizado el recorrido
|
||||
int current_path_ = 0; // Recorrido que se está recorriendo actualmente
|
||||
std::vector<Path> paths_; // Caminos a recorrer por el sprite
|
||||
|
||||
// --- Métodos internos ---
|
||||
void moveThroughCurrentPath(float delta_time); // Coloca el sprite en los diferentes puntos del recorrido
|
||||
void goToNextPathOrDie(); // Cambia de recorrido o finaliza
|
||||
|
||||
// --- Métodos auxiliares para addPath ---
|
||||
[[nodiscard]] static auto determineCenteringType(const Path& path, bool centered) -> PathCentered; // Determina el tipo de centrado
|
||||
static void centerPathOnX(Path& path, float offset); // Aplica centrado en el eje X
|
||||
static void centerPathOnY(Path& path, float offset); // Aplica centrado en el eje Y
|
||||
};
|
||||
89
source/core/rendering/sprite/smart_sprite.cpp
Normal file
89
source/core/rendering/sprite/smart_sprite.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "smart_sprite.hpp"
|
||||
|
||||
#include "moving_sprite.hpp" // Para MovingSprite
|
||||
|
||||
// Actualiza la posición y comprueba si ha llegado a su destino (time-based)
|
||||
void SmartSprite::update(float delta_time) {
|
||||
if (enabled_) {
|
||||
MovingSprite::update(delta_time);
|
||||
checkMove();
|
||||
checkFinished(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja el sprite
|
||||
void SmartSprite::render() {
|
||||
if (enabled_) {
|
||||
MovingSprite::render();
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba el movimiento
|
||||
void SmartSprite::checkMove() {
|
||||
// Comprueba si se desplaza en el eje X hacia la derecha
|
||||
if (getAccelX() > 0 || getVelX() > 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosX() > dest_x_) {
|
||||
// Lo coloca en posición
|
||||
setPosX(dest_x_);
|
||||
|
||||
// Lo detiene
|
||||
setVelX(0.0F);
|
||||
setAccelX(0.0F);
|
||||
}
|
||||
}
|
||||
// Comprueba si se desplaza en el eje X hacia la izquierda
|
||||
else if (getAccelX() < 0 || getVelX() < 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosX() < dest_x_) {
|
||||
// Lo coloca en posición
|
||||
setPosX(dest_x_);
|
||||
|
||||
// Lo detiene
|
||||
setVelX(0.0F);
|
||||
setAccelX(0.0F);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si se desplaza en el eje Y hacia abajo
|
||||
if (getAccelY() > 0 || getVelY() > 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosY() > dest_y_) {
|
||||
// Lo coloca en posición
|
||||
setPosY(dest_y_);
|
||||
|
||||
// Lo detiene
|
||||
setVelY(0.0F);
|
||||
setAccelY(0.0F);
|
||||
}
|
||||
}
|
||||
// Comprueba si se desplaza en el eje Y hacia arriba
|
||||
else if (getAccelY() < 0 || getVelY() < 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosY() < dest_y_) {
|
||||
// Lo coloca en posición
|
||||
setPosY(dest_y_);
|
||||
|
||||
// Lo detiene
|
||||
setVelY(0.0F);
|
||||
setAccelY(0.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado (time-based)
|
||||
void SmartSprite::checkFinished(float delta_time) {
|
||||
// Comprueba si ha llegado a su destino
|
||||
on_destination_ = (getPosX() == dest_x_ && getPosY() == dest_y_);
|
||||
|
||||
if (on_destination_) {
|
||||
if (finished_delay_ms_ == 0.0F) {
|
||||
finished_ = true;
|
||||
} else {
|
||||
finished_timer_ += delta_time;
|
||||
if (finished_timer_ >= finished_delay_ms_) {
|
||||
finished_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
source/core/rendering/sprite/smart_sprite.hpp
Normal file
47
source/core/rendering/sprite/smart_sprite.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <utility>
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Clase SmartSprite: sprite animado que se mueve hacia un destino y puede deshabilitarse automáticamente ---
|
||||
class SmartSprite : public AnimatedSprite {
|
||||
public:
|
||||
// --- Constructor y destructor ---
|
||||
explicit SmartSprite(std::shared_ptr<Texture> texture)
|
||||
: AnimatedSprite(std::move(texture)) {}
|
||||
~SmartSprite() override = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time) override; // Actualiza la posición y comprueba si ha llegado a su destino (time-based)
|
||||
void render() override; // Dibuja el sprite
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getDestX() const -> int { return dest_x_; } // Obtiene la posición de destino en X
|
||||
[[nodiscard]] auto getDestY() const -> int { return dest_y_; } // Obtiene la posición de destino en Y
|
||||
[[nodiscard]] auto isOnDestination() const -> bool { return on_destination_; } // Indica si está en el destino
|
||||
[[nodiscard]] auto hasFinished() const -> bool { return finished_; } // Indica si ya ha terminado
|
||||
|
||||
// --- Setters ---
|
||||
void setFinishedDelay(float value) { finished_delay_ms_ = value; } // Establece el retraso para deshabilitarlo (ms)
|
||||
void setDestX(int x) { dest_x_ = x; } // Establece la posición de destino en X
|
||||
void setDestY(int y) { dest_y_ = y; } // Establece la posición de destino en Y
|
||||
void setEnabled(bool value) { enabled_ = value; } // Habilita o deshabilita el objeto
|
||||
|
||||
private:
|
||||
// --- Variables de estado ---
|
||||
int dest_x_ = 0; // Posición de destino en el eje X
|
||||
int dest_y_ = 0; // Posición de destino en el eje Y
|
||||
float finished_delay_ms_ = 0.0F; // Retraso para deshabilitarlo (ms)
|
||||
float finished_timer_ = 0.0F; // Timer acumulado (ms)
|
||||
bool on_destination_ = false; // Indica si está en el destino
|
||||
bool finished_ = false; // Indica si ya ha terminado
|
||||
bool enabled_ = false; // Indica si el objeto está habilitado
|
||||
|
||||
// --- Métodos internos ---
|
||||
void checkFinished(float delta_time); // Comprueba si ha terminado (time-based)
|
||||
void checkMove(); // Comprueba el movimiento
|
||||
};
|
||||
54
source/core/rendering/sprite/sprite.cpp
Normal file
54
source/core/rendering/sprite/sprite.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "sprite.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "texture.hpp" // Para Texture
|
||||
|
||||
// Constructor
|
||||
Sprite::Sprite(std::shared_ptr<Texture> texture, float pos_x, float pos_y, float width, float height)
|
||||
: textures_{std::move(texture)},
|
||||
pos_((SDL_FRect){.x = pos_x, .y = pos_y, .w = width, .h = height}),
|
||||
sprite_clip_((SDL_FRect){.x = 0, .y = 0, .w = pos_.w, .h = pos_.h}) {}
|
||||
|
||||
Sprite::Sprite(std::shared_ptr<Texture> texture, SDL_FRect rect)
|
||||
: textures_{std::move(texture)},
|
||||
pos_(rect),
|
||||
sprite_clip_((SDL_FRect){.x = 0, .y = 0, .w = pos_.w, .h = pos_.h}) {}
|
||||
|
||||
Sprite::Sprite(std::shared_ptr<Texture> texture)
|
||||
: textures_{std::move(texture)},
|
||||
|
||||
pos_(SDL_FRect{.x = 0, .y = 0, .w = static_cast<float>(textures_.at(texture_index_)->getWidth()), .h = static_cast<float>(textures_.at(texture_index_)->getHeight())}),
|
||||
sprite_clip_(pos_) {}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void Sprite::render() {
|
||||
textures_.at(texture_index_)->render(pos_.x, pos_.y, &sprite_clip_, zoom_, zoom_);
|
||||
}
|
||||
|
||||
// Establece la posición del objeto
|
||||
void Sprite::setPosition(float pos_x, float pos_y) {
|
||||
pos_.x = pos_x;
|
||||
pos_.y = pos_y;
|
||||
}
|
||||
|
||||
// Establece la posición del objeto
|
||||
void Sprite::setPosition(SDL_FPoint point) {
|
||||
pos_.x = point.x;
|
||||
pos_.y = point.y;
|
||||
}
|
||||
|
||||
// Reinicia las variables a cero
|
||||
void Sprite::clear() {
|
||||
pos_ = {.x = 0, .y = 0, .w = 0, .h = 0};
|
||||
sprite_clip_ = {.x = 0, .y = 0, .w = 0, .h = 0};
|
||||
}
|
||||
|
||||
// Cambia la textura activa por índice
|
||||
auto Sprite::setActiveTexture(size_t index) -> bool {
|
||||
if (index < textures_.size()) {
|
||||
texture_index_ = index;
|
||||
}
|
||||
return index < textures_.size();
|
||||
}
|
||||
72
source/core/rendering/sprite/sprite.hpp
Normal file
72
source/core/rendering/sprite/sprite.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_FPoint
|
||||
|
||||
#include <cstddef> // Para size_t
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Clase Sprite: representa un objeto gráfico básico con posición, tamaño y textura ---
|
||||
class Sprite {
|
||||
public:
|
||||
// --- Constructores y destructor ---
|
||||
Sprite(std::shared_ptr<Texture> texture, float pos_x, float pos_y, float width, float height);
|
||||
Sprite(std::shared_ptr<Texture> texture, SDL_FRect rect);
|
||||
explicit Sprite(std::shared_ptr<Texture> texture);
|
||||
virtual ~Sprite() = default;
|
||||
|
||||
// --- Renderizado y control ---
|
||||
virtual void render(); // Muestra el sprite por pantalla
|
||||
virtual void clear(); // Reinicia las variables a cero
|
||||
|
||||
// --- Getters de posición y tamaño ---
|
||||
[[nodiscard]] auto getX() const -> float { return pos_.x; }
|
||||
[[nodiscard]] auto getY() const -> float { return pos_.y; }
|
||||
[[nodiscard]] auto getWidth() const -> float { return pos_.w; }
|
||||
[[nodiscard]] auto getHeight() const -> float { return pos_.h; }
|
||||
[[nodiscard]] auto getPosition() const -> SDL_FRect { return pos_; }
|
||||
auto getRect() -> SDL_FRect& { return pos_; }
|
||||
|
||||
// --- Setters de posición y tamaño ---
|
||||
void setX(float pos_x) { pos_.x = pos_x; }
|
||||
void setY(float pos_y) { pos_.y = pos_y; }
|
||||
void setWidth(float width) { pos_.w = width; }
|
||||
void setHeight(float height) { pos_.h = height; }
|
||||
void setPosition(float pos_x, float pos_y);
|
||||
void setPosition(SDL_FPoint point);
|
||||
void setPosition(SDL_FRect rect) { pos_ = rect; }
|
||||
|
||||
// --- Zoom ---
|
||||
void setZoom(float zoom) { zoom_ = zoom; }
|
||||
|
||||
// --- Modificación de posición ---
|
||||
void incX(float value) { pos_.x += value; }
|
||||
void incY(float value) { pos_.y += value; }
|
||||
|
||||
// --- Sprite clip ---
|
||||
[[nodiscard]] auto getSpriteClip() const -> SDL_FRect { return sprite_clip_; }
|
||||
void setSpriteClip(SDL_FRect rect) { sprite_clip_ = rect; }
|
||||
void setSpriteClip(float pos_x, float pos_y, float width, float height) { sprite_clip_ = SDL_FRect{.x = pos_x, .y = pos_y, .w = width, .h = height}; }
|
||||
|
||||
// --- Textura ---
|
||||
[[nodiscard]] auto getTexture() const -> std::shared_ptr<Texture> { return textures_.at(texture_index_); }
|
||||
void setTexture(std::shared_ptr<Texture> texture) { textures_.at(texture_index_) = std::move(texture); }
|
||||
void addTexture(const std::shared_ptr<Texture>& texture) { textures_.push_back(texture); }
|
||||
auto setActiveTexture(size_t index) -> bool; // Cambia la textura activa por índice
|
||||
[[nodiscard]] auto getActiveTexture() const -> size_t { return texture_index_; } // Alias para getActiveTextureIndex
|
||||
[[nodiscard]] auto getTextureCount() const -> size_t { return textures_.size(); } // Obtiene el número total de texturas
|
||||
|
||||
protected:
|
||||
// --- Métodos internos ---
|
||||
auto getTextureRef() -> std::shared_ptr<Texture>& { return textures_.at(texture_index_); } // Obtiene referencia a la textura activa
|
||||
|
||||
// --- Variables internas ---
|
||||
std::vector<std::shared_ptr<Texture>> textures_; // Lista de texturas
|
||||
size_t texture_index_ = 0; // Índice de la textura activa
|
||||
SDL_FRect pos_; // Posición y tamaño donde dibujar el sprite
|
||||
SDL_FRect sprite_clip_; // Rectángulo de origen de la textura que se dibujará en pantalla
|
||||
double zoom_ = 1.0F; // Zoom aplicado a la textura
|
||||
};
|
||||
453
source/core/rendering/text.cpp
Normal file
453
source/core/rendering/text.cpp
Normal file
@@ -0,0 +1,453 @@
|
||||
#include "text.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, Uint8, SDL_GetRenderTarget, SDL_RenderClear, SDL_SetRenderDrawColor, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_TextureAccess, SDL_GetTextureAlphaMod
|
||||
|
||||
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream, operator<<, istream, ifstream, istringstream
|
||||
#include <iostream> // Para cerr
|
||||
#include <sstream> // Para basic_istringstream
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <string_view> // Para string_view
|
||||
#include <utility> // Para std::cmp_less_equal
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "color.hpp" // Para Color
|
||||
#include "resource_helper.hpp" // Para loadFile
|
||||
#include "screen.hpp" // Para Screen
|
||||
#include "sprite.hpp" // Para Sprite
|
||||
#include "texture.hpp" // Para Texture
|
||||
#include "utils.hpp" // Para getFileName
|
||||
|
||||
// Constructor
|
||||
Text::Text(const std::shared_ptr<Texture>& texture, const std::string& text_file) {
|
||||
// Carga los offsets desde el fichero
|
||||
auto tf = loadFile(text_file);
|
||||
|
||||
// Inicializa variables desde la estructura
|
||||
box_height_ = tf->box_height;
|
||||
box_width_ = tf->box_width;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset_[i].x = tf->offset[i].x;
|
||||
offset_[i].y = tf->offset[i].y;
|
||||
offset_[i].w = tf->offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
|
||||
// Inicializa variables
|
||||
fixed_width_ = false;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Text::Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Text::File>& text_file) {
|
||||
// Inicializa variables desde la estructura
|
||||
box_height_ = text_file->box_height;
|
||||
box_width_ = text_file->box_width;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset_[i].x = text_file->offset[i].x;
|
||||
offset_[i].y = text_file->offset[i].y;
|
||||
offset_[i].w = text_file->offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
|
||||
// Inicializa variables
|
||||
fixed_width_ = false;
|
||||
}
|
||||
|
||||
// Constructor con textura blanca opcional
|
||||
Text::Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Texture>& white_texture, const std::string& text_file) {
|
||||
// Carga los offsets desde el fichero
|
||||
auto tf = loadFile(text_file);
|
||||
|
||||
// Inicializa variables desde la estructura
|
||||
box_height_ = tf->box_height;
|
||||
box_width_ = tf->box_width;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset_[i].x = tf->offset[i].x;
|
||||
offset_[i].y = tf->offset[i].y;
|
||||
offset_[i].w = tf->offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
white_sprite_ = std::make_unique<Sprite>(white_texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
|
||||
// Inicializa variables
|
||||
fixed_width_ = false;
|
||||
}
|
||||
|
||||
// Constructor con textura blanca opcional
|
||||
Text::Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Texture>& white_texture, const std::shared_ptr<Text::File>& text_file) {
|
||||
// Inicializa variables desde la estructura
|
||||
box_height_ = text_file->box_height;
|
||||
box_width_ = text_file->box_width;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset_[i].x = text_file->offset[i].x;
|
||||
offset_[i].y = text_file->offset[i].y;
|
||||
offset_[i].w = text_file->offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
white_sprite_ = std::make_unique<Sprite>(white_texture, (SDL_FRect){.x = 0, .y = 0, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
|
||||
// Inicializa variables
|
||||
fixed_width_ = false;
|
||||
}
|
||||
|
||||
// Escribe texto en pantalla
|
||||
void Text::write(int x, int y, const std::string& text, int kerning, int length) {
|
||||
int shift = 0;
|
||||
const std::string_view VISIBLE_TEXT = (length == -1) ? std::string_view(text) : std::string_view(text).substr(0, length);
|
||||
|
||||
sprite_->setY(y);
|
||||
for (const auto CH : VISIBLE_TEXT) {
|
||||
const auto INDEX = static_cast<unsigned char>(CH);
|
||||
|
||||
if (INDEX < offset_.size()) {
|
||||
sprite_->setSpriteClip(offset_[INDEX].x, offset_[INDEX].y, box_width_, box_height_);
|
||||
sprite_->setX(x + shift);
|
||||
sprite_->render();
|
||||
shift += offset_[INDEX].w + kerning;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe el texto al doble de tamaño
|
||||
void Text::write2X(int x, int y, const std::string& text, int kerning, int length) {
|
||||
int shift = 0;
|
||||
const std::string_view VISIBLE_TEXT = (length == -1) ? std::string_view(text) : std::string_view(text).substr(0, length);
|
||||
|
||||
for (const auto CH : VISIBLE_TEXT) {
|
||||
const auto INDEX = static_cast<unsigned char>(CH);
|
||||
|
||||
if (INDEX < offset_.size()) {
|
||||
SDL_FRect rect = {
|
||||
.x = static_cast<float>(offset_[INDEX].x),
|
||||
.y = static_cast<float>(offset_[INDEX].y),
|
||||
.w = static_cast<float>(box_width_),
|
||||
.h = static_cast<float>(box_height_)};
|
||||
|
||||
sprite_->getTexture()->render(x + shift, y, &rect, 2.0F, 2.0F);
|
||||
shift += (offset_[INDEX].w + kerning) * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe el texto en una textura
|
||||
auto Text::writeToTexture(const std::string& text, int zoom, int kerning, int length) -> std::shared_ptr<Texture> {
|
||||
auto* renderer = Screen::get()->getRenderer();
|
||||
auto texture = std::make_shared<Texture>(renderer);
|
||||
auto width = Text::length(text, kerning) * zoom;
|
||||
auto height = box_height_ * zoom;
|
||||
auto* temp = SDL_GetRenderTarget(renderer);
|
||||
|
||||
texture->createBlank(width, height, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
|
||||
texture->setBlendMode(SDL_BLENDMODE_BLEND);
|
||||
texture->setAsRenderTarget(renderer);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
zoom == 1 ? write(0, 0, text, kerning) : write2X(0, 0, text, kerning);
|
||||
SDL_SetRenderTarget(renderer, temp);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
// Escribe el texto con extras en una textura
|
||||
auto Text::writeDXToTexture(Uint8 flags, const std::string& text, int kerning, Color text_color, Uint8 shadow_distance, Color shadow_color, int length) -> std::shared_ptr<Texture> {
|
||||
auto* renderer = Screen::get()->getRenderer();
|
||||
auto texture = std::make_shared<Texture>(renderer);
|
||||
|
||||
// Calcula las dimensiones considerando los efectos
|
||||
auto base_width = Text::length(text, kerning);
|
||||
auto base_height = box_height_;
|
||||
auto width = base_width;
|
||||
auto height = base_height;
|
||||
auto offset_x = 0;
|
||||
auto offset_y = 0;
|
||||
|
||||
const auto STROKED = ((flags & Text::STROKE) == Text::STROKE);
|
||||
const auto SHADOWED = ((flags & Text::SHADOW) == Text::SHADOW);
|
||||
|
||||
if (STROKED) {
|
||||
// Para stroke, el texto se expande en todas las direcciones por shadow_distance
|
||||
width = base_width + (shadow_distance * 2);
|
||||
height = base_height + (shadow_distance * 2);
|
||||
offset_x = shadow_distance;
|
||||
offset_y = shadow_distance;
|
||||
} else if (SHADOWED) {
|
||||
// Para shadow, solo se añade espacio a la derecha y abajo
|
||||
width = base_width + shadow_distance;
|
||||
height = base_height + shadow_distance;
|
||||
}
|
||||
|
||||
auto* temp = SDL_GetRenderTarget(renderer);
|
||||
|
||||
texture->createBlank(width, height, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
|
||||
texture->setBlendMode(SDL_BLENDMODE_BLEND);
|
||||
texture->setAsRenderTarget(renderer);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
writeDX(flags, offset_x, offset_y, text, kerning, text_color, shadow_distance, shadow_color, length);
|
||||
SDL_SetRenderTarget(renderer, temp);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
// Escribe el texto con colores
|
||||
void Text::writeColored(int x, int y, const std::string& text, Color color, int kerning, int length) {
|
||||
writeColoredWithSprite(sprite_.get(), x, y, text, color, kerning, length);
|
||||
}
|
||||
|
||||
// Escribe el texto con colores usando un sprite específico
|
||||
void Text::writeColoredWithSprite(Sprite* sprite, int x, int y, const std::string& text, Color color, int kerning, int length) {
|
||||
int shift = 0;
|
||||
const std::string_view VISIBLE_TEXT = (length == -1) ? std::string_view(text) : std::string_view(text).substr(0, length);
|
||||
|
||||
auto* texture = sprite->getTexture().get();
|
||||
|
||||
// Guarda el alpha original y aplica el nuevo
|
||||
Uint8 original_alpha;
|
||||
SDL_GetTextureAlphaMod(texture->getSDLTexture(), &original_alpha);
|
||||
texture->setAlpha(color.a);
|
||||
texture->setColor(color.r, color.g, color.b);
|
||||
|
||||
sprite->setY(y);
|
||||
for (const auto CH : VISIBLE_TEXT) {
|
||||
const auto INDEX = static_cast<unsigned char>(CH);
|
||||
|
||||
if (INDEX < offset_.size()) {
|
||||
sprite->setSpriteClip(offset_[INDEX].x, offset_[INDEX].y, box_width_, box_height_);
|
||||
sprite->setX(x + shift);
|
||||
sprite->render();
|
||||
shift += offset_[INDEX].w + kerning;
|
||||
}
|
||||
}
|
||||
|
||||
// Restaura los valores originales
|
||||
texture->setColor(255, 255, 255);
|
||||
texture->setAlpha(255);
|
||||
}
|
||||
|
||||
// Escribe stroke con alpha correcto usando textura temporal
|
||||
void Text::writeStrokeWithAlpha(int x, int y, const std::string& text, int kerning, Color stroke_color, Uint8 shadow_distance, int length) {
|
||||
auto* renderer = Screen::get()->getRenderer();
|
||||
auto* original_target = SDL_GetRenderTarget(renderer);
|
||||
|
||||
// Calcula dimensiones de la textura temporal
|
||||
auto text_width = Text::length(text, kerning);
|
||||
auto text_height = box_height_;
|
||||
auto temp_width = text_width + (shadow_distance * 2);
|
||||
auto temp_height = text_height + (shadow_distance * 2);
|
||||
|
||||
// Crea textura temporal
|
||||
auto temp_texture = std::make_shared<Texture>(renderer);
|
||||
temp_texture->createBlank(temp_width, temp_height, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
|
||||
temp_texture->setBlendMode(SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Renderiza el stroke en la textura temporal
|
||||
temp_texture->setAsRenderTarget(renderer);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
// Selecciona el sprite apropiado para el stroke
|
||||
auto* stroke_sprite = white_sprite_ ? white_sprite_.get() : sprite_.get();
|
||||
|
||||
// Renderiza stroke sin alpha (sólido) en textura temporal
|
||||
Color solid_color = Color(stroke_color.r, stroke_color.g, stroke_color.b, 255);
|
||||
for (int dist = 1; std::cmp_less_equal(dist, shadow_distance); ++dist) {
|
||||
for (int dy = -dist; dy <= dist; ++dy) {
|
||||
for (int dx = -dist; dx <= dist; ++dx) {
|
||||
writeColoredWithSprite(stroke_sprite, shadow_distance + dx, shadow_distance + dy, text, solid_color, kerning, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restaura render target original
|
||||
SDL_SetRenderTarget(renderer, original_target);
|
||||
|
||||
// Renderiza la textura temporal con el alpha deseado
|
||||
temp_texture->setAlpha(stroke_color.a);
|
||||
temp_texture->render(x - shadow_distance, y - shadow_distance);
|
||||
}
|
||||
|
||||
// Escribe el texto con sombra
|
||||
void Text::writeShadowed(int x, int y, const std::string& text, Color color, Uint8 shadow_distance, int kerning, int length) {
|
||||
writeDX(Text::SHADOW, x, y, text, kerning, color, shadow_distance, color, length);
|
||||
write(x, y, text, kerning, length); // Dibuja el texto principal encima
|
||||
}
|
||||
|
||||
// Escribe el texto centrado en un punto x
|
||||
void Text::writeCentered(int x, int y, const std::string& text, int kerning, int length) {
|
||||
x -= (Text::length(text, kerning) / 2);
|
||||
write(x, y, text, kerning, length);
|
||||
}
|
||||
|
||||
// Renderiza sombra del texto
|
||||
void Text::renderShadow(int x, int y, const std::string& text, Color shadow_color, int kerning, int length, Uint8 shadow_distance) {
|
||||
if (white_sprite_) {
|
||||
writeColoredWithSprite(white_sprite_.get(), x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, length);
|
||||
} else {
|
||||
writeColored(x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, length);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza stroke sólido (método tradicional para stroke sin alpha)
|
||||
void Text::renderSolidStroke(int x, int y, const std::string& text, Color stroke_color, int kerning, int length, Uint8 shadow_distance) {
|
||||
for (int dist = 1; std::cmp_less_equal(dist, shadow_distance); ++dist) {
|
||||
for (int dy = -dist; dy <= dist; ++dy) {
|
||||
for (int dx = -dist; dx <= dist; ++dx) {
|
||||
if (white_sprite_) {
|
||||
writeColoredWithSprite(white_sprite_.get(), x + dx, y + dy, text, stroke_color, kerning, length);
|
||||
} else {
|
||||
writeColored(x + dx, y + dy, text, stroke_color, kerning, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe texto con extras
|
||||
void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning, Color text_color, Uint8 shadow_distance, Color shadow_color, int length) {
|
||||
const auto CENTERED = ((flags & Text::CENTER) == Text::CENTER);
|
||||
const auto SHADOWED = ((flags & Text::SHADOW) == Text::SHADOW);
|
||||
const auto COLORED = ((flags & Text::COLOR) == Text::COLOR);
|
||||
const auto STROKED = ((flags & Text::STROKE) == Text::STROKE);
|
||||
|
||||
if (CENTERED) {
|
||||
x -= (Text::length(text, kerning) / 2);
|
||||
}
|
||||
|
||||
if (SHADOWED) {
|
||||
renderShadow(x, y, text, shadow_color, kerning, length, shadow_distance);
|
||||
}
|
||||
|
||||
if (STROKED) {
|
||||
if (shadow_color.a < 255) {
|
||||
// Usa textura temporal para alpha correcto
|
||||
writeStrokeWithAlpha(x, y, text, kerning, shadow_color, shadow_distance, length);
|
||||
} else {
|
||||
// Método tradicional para stroke sólido
|
||||
renderSolidStroke(x, y, text, shadow_color, kerning, length, shadow_distance);
|
||||
}
|
||||
}
|
||||
|
||||
if (COLORED) {
|
||||
writeColored(x, y, text, text_color, kerning, length);
|
||||
} else {
|
||||
write(x, y, text, kerning, length);
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe texto a partir de un TextStyle
|
||||
void Text::writeStyle(int x, int y, const std::string& text, const Style& style, int length) {
|
||||
writeDX(style.flags, x, y, text, style.kerning, style.text_color, style.shadow_distance, style.shadow_color);
|
||||
}
|
||||
|
||||
// Obtiene la longitud en pixels de una cadena
|
||||
auto Text::length(const std::string& text, int kerning) const -> int {
|
||||
int shift = 0;
|
||||
for (const auto& ch : text) {
|
||||
// Convertimos a unsigned char para obtener el valor ASCII correcto (0-255)
|
||||
const auto INDEX = static_cast<unsigned char>(ch);
|
||||
|
||||
// Verificamos si el carácter está dentro de los límites del array
|
||||
if (INDEX < offset_.size()) {
|
||||
shift += (offset_[INDEX].w + kerning);
|
||||
}
|
||||
}
|
||||
|
||||
// Descuenta el kerning del último caracter si el texto no está vacío
|
||||
return text.empty() ? 0 : shift - kerning;
|
||||
}
|
||||
|
||||
// Devuelve el valor de la variable
|
||||
auto Text::getCharacterSize() const -> int {
|
||||
return box_width_;
|
||||
}
|
||||
|
||||
// Establece si se usa un tamaño fijo de letra
|
||||
void Text::setFixedWidth(bool value) {
|
||||
fixed_width_ = value;
|
||||
}
|
||||
|
||||
// Llena una estructuta TextFile desde un fichero
|
||||
auto Text::loadFile(const std::string& file_path) -> std::shared_ptr<Text::File> {
|
||||
auto tf = std::make_shared<Text::File>();
|
||||
|
||||
// Inicializa a cero el vector con las coordenadas
|
||||
for (auto& i : tf->offset) {
|
||||
i.x = 0;
|
||||
i.y = 0;
|
||||
i.w = 0;
|
||||
tf->box_width = 0;
|
||||
tf->box_height = 0;
|
||||
}
|
||||
|
||||
// Intenta cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
std::istringstream stream;
|
||||
bool using_resource_data = false;
|
||||
|
||||
if (!resource_data.empty()) {
|
||||
std::string content(resource_data.begin(), resource_data.end());
|
||||
stream.str(content);
|
||||
using_resource_data = true;
|
||||
}
|
||||
|
||||
// Fallback a archivo directo
|
||||
std::ifstream file;
|
||||
if (!using_resource_data) {
|
||||
file.open(file_path);
|
||||
}
|
||||
|
||||
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
||||
|
||||
if ((using_resource_data && stream.good()) || (!using_resource_data && file.is_open() && file.good())) {
|
||||
std::string buffer;
|
||||
|
||||
// Lee los dos primeros valores del fichero
|
||||
std::getline(input_stream, buffer);
|
||||
std::getline(input_stream, buffer);
|
||||
tf->box_width = std::stoi(buffer);
|
||||
|
||||
std::getline(input_stream, buffer);
|
||||
std::getline(input_stream, buffer);
|
||||
tf->box_height = std::stoi(buffer);
|
||||
|
||||
// lee el resto de datos del fichero
|
||||
auto index = 32;
|
||||
auto line_read = 0;
|
||||
while (std::getline(input_stream, buffer)) {
|
||||
// Almacena solo las lineas impares
|
||||
if (line_read % 2 == 1) {
|
||||
tf->offset[index++].w = std::stoi(buffer);
|
||||
}
|
||||
|
||||
// Limpia el buffer
|
||||
buffer.clear();
|
||||
line_read++;
|
||||
};
|
||||
|
||||
// Cierra el fichero si se usó
|
||||
if (!using_resource_data && file.is_open()) {
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
// El fichero no se puede abrir
|
||||
else {
|
||||
std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
|
||||
}
|
||||
|
||||
// Establece las coordenadas para cada caracter ascii de la cadena y su ancho
|
||||
for (int i = 32; i < 128; ++i) {
|
||||
tf->offset[i].x = ((i - 32) % 15) * tf->box_width;
|
||||
tf->offset[i].y = ((i - 32) / 15) * tf->box_height;
|
||||
}
|
||||
|
||||
return tf;
|
||||
}
|
||||
102
source/core/rendering/text.hpp
Normal file
102
source/core/rendering/text.hpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para Uint8
|
||||
|
||||
#include <array> // Para array
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
#include "color.hpp" // Para Color
|
||||
#include "sprite.hpp" // Para Sprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Clase Text: pinta texto en pantalla a partir de un bitmap ---
|
||||
class Text {
|
||||
public:
|
||||
// --- Constantes para flags de texto ---
|
||||
static constexpr int COLOR = 1;
|
||||
static constexpr int SHADOW = 2;
|
||||
static constexpr int CENTER = 4;
|
||||
static constexpr int STROKE = 8;
|
||||
|
||||
// --- Estructuras ---
|
||||
struct Offset {
|
||||
int x, y, w;
|
||||
};
|
||||
|
||||
struct File {
|
||||
int box_width; // Anchura de la caja de cada caracter en el png
|
||||
int box_height; // Altura de la caja de cada caracter en el png
|
||||
std::array<Offset, 128> offset = {}; // Vector con las posiciones y ancho de cada letra
|
||||
};
|
||||
|
||||
struct Style {
|
||||
Uint8 flags;
|
||||
Color text_color;
|
||||
Color shadow_color;
|
||||
Uint8 shadow_distance;
|
||||
int kerning;
|
||||
|
||||
// Constructor con argumentos por defecto
|
||||
Style(Uint8 flags = 0,
|
||||
Color text = Color(),
|
||||
Color shadow = Color(),
|
||||
Uint8 distance = 1,
|
||||
int kern = 1)
|
||||
: flags(flags),
|
||||
text_color(text),
|
||||
shadow_color(shadow),
|
||||
shadow_distance(distance),
|
||||
kerning(kern) {}
|
||||
};
|
||||
|
||||
// --- Constructores y destructor ---
|
||||
Text(const std::shared_ptr<Texture>& texture, const std::string& text_file);
|
||||
Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Text::File>& text_file);
|
||||
Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Texture>& white_texture, const std::string& text_file);
|
||||
Text(const std::shared_ptr<Texture>& texture, const std::shared_ptr<Texture>& white_texture, const std::shared_ptr<Text::File>& text_file);
|
||||
~Text() = default;
|
||||
|
||||
// --- Métodos de escritura en pantalla ---
|
||||
void write(int x, int y, const std::string& text, int kerning = 1, int length = -1); // Escribe el texto en pantalla
|
||||
void write2X(int x, int y, const std::string& text, int kerning = 1, int length = -1); // Escribe el texto al doble de tamaño
|
||||
|
||||
// --- Escritura en textura ---
|
||||
auto writeToTexture(const std::string& text, int zoom = 1, int kerning = 1, int length = -1) -> std::shared_ptr<Texture>; // Escribe el texto en una textura
|
||||
auto writeDXToTexture(Uint8 flags, const std::string& text, int kerning = 1, Color text_color = Color(), Uint8 shadow_distance = 1, Color shadow_color = Color(), int length = -1) -> std::shared_ptr<Texture>; // Escribe el texto con extras en una textura
|
||||
|
||||
// --- Métodos de escritura avanzada ---
|
||||
void writeColored(int x, int y, const std::string& text, Color color, int kerning = 1, int length = -1); // Escribe el texto con colores
|
||||
void writeShadowed(int x, int y, const std::string& text, Color color, Uint8 shadow_distance = 1, int kerning = 1, int length = -1); // Escribe el texto con sombra
|
||||
void writeCentered(int x, int y, const std::string& text, int kerning = 1, int length = -1); // Escribe el texto centrado en un punto x
|
||||
void writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning = 1, Color text_color = Color(), Uint8 shadow_distance = 1, Color shadow_color = Color(), int length = -1); // Escribe texto con extras
|
||||
void writeStyle(int x, int y, const std::string& text, const Style& style, int length = -1); // Escribe texto a partir de un TextStyle
|
||||
|
||||
// --- Utilidades ---
|
||||
[[nodiscard]] auto length(const std::string& text, int kerning = 1) const -> int; // Obtiene la longitud en pixels de una cadena
|
||||
[[nodiscard]] auto getCharacterSize() const -> int; // Devuelve el tamaño de caracter actual
|
||||
|
||||
// --- Configuración ---
|
||||
void setFixedWidth(bool value); // Establece si se usa un tamaño fijo de letra
|
||||
|
||||
// --- Métodos estáticos ---
|
||||
static auto loadFile(const std::string& file_path) -> std::shared_ptr<Text::File>; // Llena una estructura Text::File desde un fichero
|
||||
|
||||
// --- Métodos privados ---
|
||||
void writeColoredWithSprite(Sprite* sprite, int x, int y, const std::string& text, Color color, int kerning = 1, int length = -1); // Escribe con un sprite específico
|
||||
void writeStrokeWithAlpha(int x, int y, const std::string& text, int kerning, Color stroke_color, Uint8 shadow_distance, int length = -1); // Escribe stroke con alpha correcto
|
||||
void renderShadow(int x, int y, const std::string& text, Color shadow_color, int kerning, int length, Uint8 shadow_distance); // Renderiza sombra del texto
|
||||
void renderSolidStroke(int x, int y, const std::string& text, Color stroke_color, int kerning, int length, Uint8 shadow_distance); // Renderiza stroke sólido
|
||||
|
||||
private:
|
||||
// --- Objetos y punteros ---
|
||||
std::unique_ptr<Sprite> sprite_ = nullptr; // Objeto con los gráficos para el texto
|
||||
std::unique_ptr<Sprite> white_sprite_ = nullptr; // Objeto con los gráficos en blanco para efectos
|
||||
|
||||
// --- Variables de estado ---
|
||||
std::array<Offset, 128> offset_ = {}; // Vector con las posiciones y ancho de cada letra
|
||||
int box_width_ = 0; // Anchura de la caja de cada caracter en el png
|
||||
int box_height_ = 0; // Altura de la caja de cada caracter en el png
|
||||
bool fixed_width_ = false; // Indica si el texto se ha de escribir con longitud fija
|
||||
};
|
||||
430
source/core/rendering/texture.cpp
Normal file
430
source/core/rendering/texture.cpp
Normal file
@@ -0,0 +1,430 @@
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "texture.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_LogError, SDL_LogCategory, Uint8, SDL_...
|
||||
|
||||
#include <cstdint> // Para uint32_t
|
||||
#include <cstring> // Para memcpy
|
||||
#include <fstream> // Para basic_ifstream, basic_istream, basic_ios
|
||||
#include <iostream> // Para std::cout
|
||||
#include <sstream> // Para basic_istringstream
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <string> // Para basic_string, char_traits, operator+, string
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "color.hpp" // Para getFileName, Color
|
||||
#include "external/gif.hpp" // Para Gif
|
||||
#include "resource_helper.hpp" // Para ResourceHelper
|
||||
#include "stb_image.h" // Para stbi_image_free, stbi_load, STBI_rgb_alpha
|
||||
#include "utils.hpp"
|
||||
|
||||
// Constructor
|
||||
Texture::Texture(SDL_Renderer* renderer, std::string path)
|
||||
: renderer_(renderer),
|
||||
path_(std::move(path)) {
|
||||
// Carga el fichero en la textura
|
||||
if (!path_.empty()) {
|
||||
// Obtiene la extensión
|
||||
const std::string EXTENSION = path_.substr(path_.find_last_of('.') + 1);
|
||||
|
||||
// .png
|
||||
if (EXTENSION == "png") {
|
||||
loadFromFile(path_);
|
||||
}
|
||||
|
||||
// .gif
|
||||
else if (EXTENSION == "gif") {
|
||||
// Crea la surface desde un fichero
|
||||
surface_ = loadSurface(path_);
|
||||
|
||||
// Añade la propia paleta del fichero a la lista
|
||||
addPaletteFromGifFile(path_);
|
||||
|
||||
// Crea la textura, establece el BlendMode y copia la surface a la textura
|
||||
createBlank(width_, height_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING);
|
||||
SDL_SetTextureBlendMode(texture_, SDL_BLENDMODE_BLEND);
|
||||
flipSurface();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Texture::~Texture() {
|
||||
unloadTexture();
|
||||
unloadSurface();
|
||||
palettes_.clear();
|
||||
}
|
||||
|
||||
// Carga una imagen desde un fichero
|
||||
auto Texture::loadFromFile(const std::string& file_path) -> bool {
|
||||
if (file_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int req_format = STBI_rgb_alpha;
|
||||
int width;
|
||||
int height;
|
||||
int orig_format;
|
||||
unsigned char* data = nullptr;
|
||||
|
||||
// Intentar cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
if (!resource_data.empty()) {
|
||||
data = stbi_load_from_memory(resource_data.data(), resource_data.size(), &width, &height, &orig_format, req_format);
|
||||
}
|
||||
|
||||
// Fallback a filesystem directo
|
||||
if (data == nullptr) {
|
||||
data = stbi_load(file_path.c_str(), &width, &height, &orig_format, req_format);
|
||||
}
|
||||
|
||||
if (data == nullptr) {
|
||||
std::cout << "Error: Fichero no encontrado " << getFileName(file_path) << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
|
||||
}
|
||||
|
||||
int pitch;
|
||||
SDL_PixelFormat pixel_format;
|
||||
pitch = 4 * width;
|
||||
pixel_format = SDL_PIXELFORMAT_RGBA32;
|
||||
|
||||
// Limpia
|
||||
unloadTexture();
|
||||
|
||||
// La textura final
|
||||
SDL_Texture* new_texture = nullptr;
|
||||
|
||||
// Carga la imagen desde una ruta específica
|
||||
auto* loaded_surface = SDL_CreateSurfaceFrom(width, height, pixel_format, static_cast<void*>(data), pitch);
|
||||
if (loaded_surface == nullptr) {
|
||||
std::cout << "Unable to load image " << file_path << '\n';
|
||||
} else {
|
||||
// Crea la textura desde los pixels de la surface
|
||||
new_texture = SDL_CreateTextureFromSurface(renderer_, loaded_surface);
|
||||
if (new_texture == nullptr) {
|
||||
std::cout << "Unable to create texture from " << file_path << "! SDL Error: " << SDL_GetError() << '\n';
|
||||
} else {
|
||||
// Obtiene las dimensiones de la imagen
|
||||
width_ = loaded_surface->w;
|
||||
height_ = loaded_surface->h;
|
||||
}
|
||||
|
||||
// Elimina la textura cargada
|
||||
SDL_DestroySurface(loaded_surface);
|
||||
}
|
||||
|
||||
// Return success
|
||||
stbi_image_free(data);
|
||||
texture_ = new_texture;
|
||||
return texture_ != nullptr;
|
||||
}
|
||||
|
||||
// Crea una textura en blanco
|
||||
auto Texture::createBlank(int width, int height, SDL_PixelFormat format, SDL_TextureAccess access) -> bool {
|
||||
// Crea una textura sin inicializar
|
||||
texture_ = SDL_CreateTexture(renderer_, format, access, width, height);
|
||||
if (texture_ == nullptr) {
|
||||
std::cout << "Unable to create blank texture! SDL Error: " << SDL_GetError() << '\n';
|
||||
} else {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
}
|
||||
|
||||
return texture_ != nullptr;
|
||||
}
|
||||
|
||||
// Libera la memoria de la textura
|
||||
void Texture::unloadTexture() {
|
||||
// Libera la textura
|
||||
if (texture_ != nullptr) {
|
||||
SDL_DestroyTexture(texture_);
|
||||
texture_ = nullptr;
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el color para la modulacion
|
||||
void Texture::setColor(Uint8 red, Uint8 green, Uint8 blue) {
|
||||
SDL_SetTextureColorMod(texture_, red, green, blue);
|
||||
}
|
||||
void Texture::setColor(Color color) {
|
||||
SDL_SetTextureColorMod(texture_, color.r, color.g, color.b);
|
||||
}
|
||||
|
||||
// Establece el blending
|
||||
void Texture::setBlendMode(SDL_BlendMode blending) {
|
||||
SDL_SetTextureBlendMode(texture_, blending);
|
||||
}
|
||||
|
||||
// Establece el alpha para la modulación
|
||||
void Texture::setAlpha(Uint8 alpha) {
|
||||
SDL_SetTextureAlphaMod(texture_, alpha);
|
||||
}
|
||||
|
||||
// Renderiza la textura en un punto específico
|
||||
void Texture::render(int x, int y, SDL_FRect* clip, float horizontal_zoom, float vertical_zoom, double angle, SDL_FPoint* center, SDL_FlipMode flip) {
|
||||
// Establece el destino de renderizado en la pantalla
|
||||
SDL_FRect render_quad = {.x = static_cast<float>(x), .y = static_cast<float>(y), .w = static_cast<float>(width_), .h = static_cast<float>(height_)};
|
||||
|
||||
// Obtiene las dimesiones del clip de renderizado
|
||||
if (clip != nullptr) {
|
||||
render_quad.w = clip->w;
|
||||
render_quad.h = clip->h;
|
||||
}
|
||||
|
||||
// Calcula el zoom y las coordenadas
|
||||
if (vertical_zoom != 1.0F || horizontal_zoom != 1.0F) {
|
||||
render_quad.x = render_quad.x + (render_quad.w / 2);
|
||||
render_quad.y = render_quad.y + (render_quad.h / 2);
|
||||
render_quad.w = render_quad.w * horizontal_zoom;
|
||||
render_quad.h = render_quad.h * vertical_zoom;
|
||||
render_quad.x = render_quad.x - (render_quad.w / 2);
|
||||
render_quad.y = render_quad.y - (render_quad.h / 2);
|
||||
}
|
||||
|
||||
// Renderiza a pantalla
|
||||
SDL_RenderTextureRotated(renderer_, texture_, clip, &render_quad, angle, center, flip);
|
||||
}
|
||||
|
||||
// Establece la textura como objetivo de renderizado
|
||||
void Texture::setAsRenderTarget(SDL_Renderer* renderer) {
|
||||
SDL_SetRenderTarget(renderer, texture_);
|
||||
}
|
||||
|
||||
// Obtiene el ancho de la imagen
|
||||
auto Texture::getWidth() const -> int {
|
||||
return width_;
|
||||
}
|
||||
|
||||
// Obtiene el alto de la imagen
|
||||
auto Texture::getHeight() const -> int {
|
||||
return height_;
|
||||
}
|
||||
|
||||
// Recarga la textura
|
||||
auto Texture::reLoad() -> bool {
|
||||
return loadFromFile(path_);
|
||||
}
|
||||
|
||||
// Obtiene la textura
|
||||
auto Texture::getSDLTexture() -> SDL_Texture* {
|
||||
return texture_;
|
||||
}
|
||||
|
||||
// Desencadenar la superficie actual
|
||||
void Texture::unloadSurface() {
|
||||
surface_.reset(); // Resetea el shared_ptr
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
}
|
||||
|
||||
// Crea una surface desde un fichero .gif
|
||||
auto Texture::loadSurface(const std::string& file_path) -> std::shared_ptr<Surface> {
|
||||
// Libera la superficie actual
|
||||
unloadSurface();
|
||||
|
||||
std::vector<Uint8> buffer;
|
||||
|
||||
// Intentar cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
if (!resource_data.empty()) {
|
||||
buffer = resource_data;
|
||||
} else {
|
||||
// Fallback a filesystem directo
|
||||
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
std::cout << "Error: Fichero no encontrado " << file_path << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + file_path);
|
||||
}
|
||||
|
||||
// Obtener el tamaño del archivo
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
// Leer el contenido del archivo en un buffer
|
||||
buffer.resize(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
std::cout << "Error al leer el fichero " << file_path << '\n';
|
||||
throw std::runtime_error("Error al leer el fichero: " + file_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Crear un objeto Gif y llamar a la función loadGif
|
||||
GIF::Gif gif;
|
||||
Uint16 w = 0;
|
||||
Uint16 h = 0;
|
||||
std::vector<Uint8> raw_pixels = gif.loadGif(buffer.data(), w, h);
|
||||
if (raw_pixels.empty()) {
|
||||
std::cout << "Error: No se pudo cargar el GIF " << file_path << '\n';
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Si el constructor de Surface espera un std::shared_ptr<Uint8[]>:
|
||||
size_t pixel_count = raw_pixels.size();
|
||||
auto pixels = std::shared_ptr<Uint8[]>(new Uint8[pixel_count], std::default_delete<Uint8[]>()); // NOLINT(modernize-avoid-c-arrays)
|
||||
std::memcpy(pixels.get(), raw_pixels.data(), pixel_count);
|
||||
|
||||
auto surface = std::make_shared<Surface>(w, h, pixels);
|
||||
|
||||
// Actualizar las dimensiones
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
// Vuelca la surface en la textura
|
||||
void Texture::flipSurface() {
|
||||
// Limpia la textura
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, texture_);
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
|
||||
// Vuelca los datos
|
||||
Uint32* pixels;
|
||||
int pitch;
|
||||
SDL_LockTexture(texture_, nullptr, reinterpret_cast<void**>(&pixels), &pitch);
|
||||
for (int i = 0; i < width_ * height_; ++i) {
|
||||
pixels[i] = palettes_[current_palette_][surface_->data[i]];
|
||||
}
|
||||
SDL_UnlockTexture(texture_);
|
||||
}
|
||||
|
||||
// Establece un color de la paleta
|
||||
void Texture::setPaletteColor(int palette, int index, Uint32 color) {
|
||||
palettes_.at(palette)[index] = color;
|
||||
}
|
||||
|
||||
// Carga una paleta desde un fichero
|
||||
auto Texture::loadPaletteFromFile(const std::string& file_path) -> Palette {
|
||||
Palette palette;
|
||||
|
||||
std::vector<Uint8> buffer;
|
||||
|
||||
// Intentar cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
if (!resource_data.empty()) {
|
||||
buffer = resource_data;
|
||||
} else {
|
||||
// Fallback a filesystem directo
|
||||
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
std::cout << "Error: Fichero no encontrado " << file_path << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + file_path);
|
||||
}
|
||||
|
||||
// Obtener el tamaño del archivo y leerlo en un buffer
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
buffer.resize(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
std::cout << "Error: No se pudo leer completamente el fichero " << file_path << '\n';
|
||||
throw std::runtime_error("Error al leer el fichero: " + file_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Usar la nueva función loadPalette, que devuelve un vector<uint32_t>
|
||||
GIF::Gif gif;
|
||||
std::vector<uint32_t> pal = gif.loadPalette(buffer.data());
|
||||
if (pal.empty()) {
|
||||
std::cout << "Advertencia: No se encontró paleta en el archivo " << file_path << '\n';
|
||||
return palette; // Devuelve un vector vacío si no hay paleta
|
||||
}
|
||||
|
||||
// Modificar la conversión para obtener formato RGBA (0xRRGGBBAA)
|
||||
for (size_t i = 0; i < pal.size() && i < palette.size(); ++i) {
|
||||
palette[i] = (pal[i] << 8) | 0xFF; // Resultado: 0xRRGGBBAA
|
||||
}
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
// Añade una paleta a la lista
|
||||
void Texture::addPaletteFromGifFile(const std::string& path) {
|
||||
palettes_.emplace_back(loadPaletteFromFile(path));
|
||||
setPaletteColor(palettes_.size() - 1, 0, 0x00000000);
|
||||
}
|
||||
|
||||
// Añade una paleta a la lista
|
||||
void Texture::addPaletteFromPalFile(const std::string& path) {
|
||||
palettes_.emplace_back(readPalFile(path));
|
||||
setPaletteColor(palettes_.size() - 1, 0, 0x00000000);
|
||||
}
|
||||
|
||||
// Cambia la paleta de la textura
|
||||
void Texture::setPalette(size_t palette) {
|
||||
if (palette < palettes_.size()) {
|
||||
current_palette_ = palette;
|
||||
flipSurface();
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene el renderizador
|
||||
auto Texture::getRenderer() -> SDL_Renderer* { return renderer_; }
|
||||
|
||||
// Carga una paleta desde un archivo .pal
|
||||
auto Texture::readPalFile(const std::string& file_path) -> Palette {
|
||||
Palette palette{};
|
||||
palette.fill(0); // Inicializar todo con 0 (transparente por defecto)
|
||||
|
||||
// Intentar cargar desde ResourceHelper primero
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
std::istringstream stream;
|
||||
bool using_resource_data = false;
|
||||
|
||||
if (!resource_data.empty()) {
|
||||
std::string content(resource_data.begin(), resource_data.end());
|
||||
stream.str(content);
|
||||
using_resource_data = true;
|
||||
}
|
||||
|
||||
// Fallback a archivo directo
|
||||
std::ifstream file;
|
||||
if (!using_resource_data) {
|
||||
file.open(file_path);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("No se pudo abrir el archivo .pal");
|
||||
}
|
||||
}
|
||||
|
||||
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
||||
|
||||
std::string line;
|
||||
int line_number = 0;
|
||||
int color_index = 0;
|
||||
|
||||
while (std::getline(input_stream, line)) {
|
||||
++line_number;
|
||||
|
||||
// Ignorar las tres primeras líneas del archivo
|
||||
if (line_number <= 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Procesar las líneas restantes con valores RGB
|
||||
std::istringstream ss(line);
|
||||
int r;
|
||||
int g;
|
||||
int b;
|
||||
if (ss >> r >> g >> b) {
|
||||
// Construir el color RGBA (A = 255 por defecto)
|
||||
Uint32 color = (r << 24) | (g << 16) | (b << 8) | 255;
|
||||
palette[color_index++] = color;
|
||||
|
||||
// Limitar a un máximo de 256 colores (opcional)
|
||||
if (color_index >= 256) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!using_resource_data && file.is_open()) {
|
||||
file.close();
|
||||
}
|
||||
return palette;
|
||||
}
|
||||
83
source/core/rendering/texture.hpp
Normal file
83
source/core/rendering/texture.hpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para Uint8, SDL_Renderer, Uint16, SDL_FlipMode, SDL_PixelFormat, SDL_TextureAccess, SDL_Texture, Uint32, SDL_BlendMode, SDL_FPoint, SDL_FRect
|
||||
|
||||
#include <array> // Para array
|
||||
#include <cstddef> // Para size_t
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string, basic_string
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
struct Color;
|
||||
|
||||
// Alias
|
||||
using Palette = std::array<Uint32, 256>;
|
||||
|
||||
// Definición de Surface para imágenes con paleta
|
||||
struct Surface {
|
||||
std::shared_ptr<Uint8[]> data; // NOLINT(modernize-avoid-c-arrays)
|
||||
Uint16 w, h;
|
||||
|
||||
// Constructor
|
||||
Surface(Uint16 width, Uint16 height, std::shared_ptr<Uint8[]> pixels) // NOLINT(modernize-avoid-c-arrays)
|
||||
: data(std::move(pixels)),
|
||||
w(width),
|
||||
h(height) {}
|
||||
};
|
||||
|
||||
// Clase Texture: gestiona texturas, paletas y renderizado
|
||||
class Texture {
|
||||
public:
|
||||
// --- Constructores y destructor ---
|
||||
explicit Texture(SDL_Renderer* renderer, std::string path = std::string());
|
||||
~Texture();
|
||||
|
||||
// --- Carga y creación ---
|
||||
auto loadFromFile(const std::string& path) -> bool; // Carga una imagen desde un fichero
|
||||
auto createBlank(int width, int height, SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA8888, SDL_TextureAccess access = SDL_TEXTUREACCESS_STREAMING) -> bool; // Crea una textura en blanco
|
||||
auto reLoad() -> bool; // Recarga la textura
|
||||
|
||||
// --- Renderizado ---
|
||||
void render(int x, int y, SDL_FRect* clip = nullptr, float horizontal_zoom = 1, float vertical_zoom = 1, double angle = 0.0, SDL_FPoint* center = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE); // Renderiza la textura en un punto específico
|
||||
void setAsRenderTarget(SDL_Renderer* renderer); // Establece la textura como objetivo de renderizado
|
||||
|
||||
// --- Modificadores de color y blending ---
|
||||
void setColor(Uint8 red, Uint8 green, Uint8 blue); // Establece el color para la modulación
|
||||
void setColor(Color color); // Establece el color para la modulación
|
||||
void setBlendMode(SDL_BlendMode blending); // Establece el blending
|
||||
void setAlpha(Uint8 alpha); // Establece el alpha para la modulación
|
||||
|
||||
// --- Paletas ---
|
||||
void addPaletteFromGifFile(const std::string& path); // Añade una paleta a la lista
|
||||
void addPaletteFromPalFile(const std::string& path); // Añade una paleta a la lista
|
||||
void setPaletteColor(int palette, int index, Uint32 color); // Establece un color de la paleta
|
||||
void setPalette(size_t palette); // Cambia la paleta de la textura
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getWidth() const -> int; // Obtiene el ancho de la imagen
|
||||
[[nodiscard]] auto getHeight() const -> int; // Obtiene el alto de la imagen
|
||||
auto getSDLTexture() -> SDL_Texture*; // Obtiene la textura SDL
|
||||
auto getRenderer() -> SDL_Renderer*; // Obtiene el renderizador
|
||||
|
||||
private:
|
||||
// --- Objetos y punteros ---
|
||||
SDL_Renderer* renderer_; // Renderizador donde dibujar la textura
|
||||
SDL_Texture* texture_ = nullptr; // La textura
|
||||
std::shared_ptr<Surface> surface_ = nullptr; // Surface para usar imágenes en formato gif con paleta
|
||||
|
||||
// --- Variables ---
|
||||
std::string path_; // Ruta de la imagen de la textura
|
||||
int width_ = 0; // Ancho de la imagen
|
||||
int height_ = 0; // Alto de la imagen
|
||||
std::vector<Palette> palettes_; // Vector con las diferentes paletas
|
||||
int current_palette_ = 0; // Índice de la paleta en uso
|
||||
|
||||
// --- Métodos internos ---
|
||||
auto loadSurface(const std::string& file_path) -> std::shared_ptr<Surface>; // Crea una surface desde un fichero .gif
|
||||
void flipSurface(); // Vuelca la surface en la textura
|
||||
static auto loadPaletteFromFile(const std::string& file_path) -> Palette; // Carga una paleta desde un fichero
|
||||
void unloadTexture(); // Libera la memoria de la textura
|
||||
void unloadSurface(); // Libera la surface actual
|
||||
static auto readPalFile(const std::string& file_path) -> Palette; // Carga una paleta desde un archivo .pal
|
||||
};
|
||||
165
source/core/rendering/tiled_bg.cpp
Normal file
165
source/core/rendering/tiled_bg.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "tiled_bg.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_CreateTexture, SDL_DestroyTexture, SDL_FRect, SDL_GetRenderTarget, SDL_RenderTexture, SDL_PixelFormat, SDL_TextureAccess
|
||||
|
||||
#include <algorithm> // Para max
|
||||
#include <cmath> // Para cos, pow, sin
|
||||
#include <cstdlib> // Para rand
|
||||
#include <memory> // Para unique_ptr, make_unique
|
||||
#include <numbers> // Para pi
|
||||
#include <string> // Para basic_string
|
||||
|
||||
#include "resource.hpp" // Para Resource
|
||||
#include "screen.hpp" // Para Screen
|
||||
#include "sprite.hpp" // Para Sprite
|
||||
|
||||
// Constructor
|
||||
TiledBG::TiledBG(SDL_FRect pos, TiledBGMode mode)
|
||||
: renderer_(Screen::get()->getRenderer()),
|
||||
pos_(pos),
|
||||
mode_(mode == TiledBGMode::RANDOM ? static_cast<TiledBGMode>(rand() % 2) : mode) {
|
||||
// Crea la textura para el mosaico de fondo
|
||||
canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, pos_.w * 2, pos_.h * 2);
|
||||
|
||||
// Rellena la textura con el contenido
|
||||
fillTexture();
|
||||
|
||||
// Inicializa variables
|
||||
switch (mode_) {
|
||||
case TiledBGMode::STATIC:
|
||||
window_ = {.x = 0, .y = 0, .w = pos_.w, .h = pos_.h};
|
||||
speed_ = 0.0F;
|
||||
break;
|
||||
case TiledBGMode::DIAGONAL:
|
||||
window_ = {.x = 0, .y = 0, .w = pos_.w, .h = pos_.h};
|
||||
break;
|
||||
case TiledBGMode::CIRCLE:
|
||||
window_ = {.x = 128, .y = 128, .w = pos_.w, .h = pos_.h};
|
||||
break;
|
||||
default:
|
||||
window_ = {.x = 0, .y = 0, .w = pos_.w, .h = pos_.h};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
TiledBG::~TiledBG() {
|
||||
SDL_DestroyTexture(canvas_);
|
||||
}
|
||||
|
||||
// Rellena la textura con el contenido
|
||||
void TiledBG::fillTexture() {
|
||||
// Crea los objetos para pintar en la textura de fondo
|
||||
auto tile = std::make_unique<Sprite>(Resource::get()->getTexture("title_bg_tile.png"), (SDL_FRect){.x = 0, .y = 0, .w = TILE_WIDTH, .h = TILE_HEIGHT});
|
||||
|
||||
// Prepara para dibujar sobre la textura
|
||||
auto* temp = SDL_GetRenderTarget(renderer_);
|
||||
SDL_SetRenderTarget(renderer_, canvas_);
|
||||
|
||||
// Rellena la textura con el tile
|
||||
const auto I_MAX = pos_.w * 2 / TILE_WIDTH;
|
||||
const auto J_MAX = pos_.h * 2 / TILE_HEIGHT;
|
||||
tile->setSpriteClip(0, 0, TILE_WIDTH, TILE_HEIGHT);
|
||||
for (int i = 0; i < I_MAX; ++i) {
|
||||
for (int j = 0; j < J_MAX; ++j) {
|
||||
tile->setX(i * TILE_WIDTH);
|
||||
tile->setY(j * TILE_HEIGHT);
|
||||
tile->render();
|
||||
}
|
||||
}
|
||||
|
||||
// Vuelve a colocar el renderizador como estaba
|
||||
SDL_SetRenderTarget(renderer_, temp);
|
||||
}
|
||||
|
||||
// Pinta la clase en pantalla
|
||||
void TiledBG::render() {
|
||||
SDL_RenderTexture(renderer_, canvas_, &window_, &pos_);
|
||||
}
|
||||
|
||||
// Actualiza la lógica de la clase (time-based)
|
||||
void TiledBG::update(float delta_time) {
|
||||
updateSpeedChange(delta_time);
|
||||
updateDesp(delta_time);
|
||||
updateStop(delta_time);
|
||||
|
||||
switch (mode_) {
|
||||
case TiledBGMode::DIAGONAL: {
|
||||
// El tileado de fondo se desplaza en diagonal
|
||||
window_.x = static_cast<int>(desp_) % TILE_WIDTH;
|
||||
window_.y = static_cast<int>(desp_) % TILE_HEIGHT;
|
||||
|
||||
break;
|
||||
}
|
||||
case TiledBGMode::CIRCLE: {
|
||||
// El tileado de fondo se desplaza en circulo
|
||||
const float ANGLE_RAD = (desp_ * std::numbers::pi / 180.0F);
|
||||
|
||||
window_.x = 128 + static_cast<int>(std::cos(ANGLE_RAD) * 128);
|
||||
window_.y = 128 + static_cast<int>(std::sin(-ANGLE_RAD) * 96);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Detiene el desplazamiento de forma ordenada (time-based)
|
||||
void TiledBG::updateStop(float delta_time) {
|
||||
if (stopping_) {
|
||||
const int UMBRAL = STOP_THRESHOLD_FACTOR * speed_;
|
||||
|
||||
// Desacelerar si estamos cerca de completar el ciclo (ventana a punto de regresar a 0)
|
||||
if (window_.x >= TILE_WIDTH - UMBRAL) {
|
||||
// Aplicar desaceleración time-based
|
||||
float frame_rate = 60.0F;
|
||||
float deceleration_per_ms = std::pow(DECELERATION_FACTOR, frame_rate * delta_time / 1000.0F);
|
||||
speed_ /= deceleration_per_ms;
|
||||
|
||||
// Asegura que no baje demasiado
|
||||
speed_ = std::max(speed_, MIN_SPEED);
|
||||
}
|
||||
|
||||
// Si estamos en 0, detener
|
||||
if (window_.x == 0) {
|
||||
speed_ = 0.0F;
|
||||
stopping_ = false; // Desactivamos el estado de "stopping"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia la velocidad gradualmente en X segundos
|
||||
void TiledBG::changeSpeedTo(float target_speed, float duration_s) {
|
||||
if (duration_s <= 0.0F) {
|
||||
// Si la duración es 0 o negativa, cambia inmediatamente
|
||||
speed_ = target_speed;
|
||||
changing_speed_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Configurar el cambio gradual
|
||||
changing_speed_ = true;
|
||||
initial_speed_ = speed_;
|
||||
target_speed_ = target_speed;
|
||||
change_duration_s_ = duration_s;
|
||||
change_timer_s_ = 0.0F;
|
||||
}
|
||||
|
||||
// Actualiza el cambio gradual de velocidad (time-based)
|
||||
void TiledBG::updateSpeedChange(float delta_time) {
|
||||
if (!changing_speed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
change_timer_s_ += delta_time;
|
||||
|
||||
if (change_timer_s_ >= change_duration_s_) {
|
||||
// Cambio completado
|
||||
speed_ = target_speed_;
|
||||
changing_speed_ = false;
|
||||
} else {
|
||||
// Interpolación lineal entre velocidad inicial y objetivo
|
||||
float progress = change_timer_s_ / change_duration_s_;
|
||||
speed_ = initial_speed_ + ((target_speed_ - initial_speed_) * progress);
|
||||
}
|
||||
}
|
||||
70
source/core/rendering/tiled_bg.hpp
Normal file
70
source/core/rendering/tiled_bg.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_SetTextureColorMod, SDL_Renderer, SDL_Texture
|
||||
|
||||
#include "color.hpp" // Para Color
|
||||
|
||||
// --- Enums ---
|
||||
enum class TiledBGMode : int { // Modos de funcionamiento para el tileado de fondo
|
||||
CIRCLE = 0,
|
||||
DIAGONAL = 1,
|
||||
RANDOM = 2,
|
||||
STATIC = 3,
|
||||
};
|
||||
|
||||
// --- Clase TiledBG: dibuja un tileado de fondo con efectos de movimiento ---
|
||||
// Esta clase se sirve de una textura "canvas", que rellena con los tiles.
|
||||
// El rectángulo "window" recorre la textura de diferentes formas para generar el efecto de movimiento.
|
||||
class TiledBG {
|
||||
public:
|
||||
// --- Constructores y destructor ---
|
||||
TiledBG(SDL_FRect pos, TiledBGMode mode);
|
||||
~TiledBG();
|
||||
|
||||
// --- Métodos principales ---
|
||||
void render(); // Pinta la clase en pantalla
|
||||
void update(float delta_time); // Actualiza la lógica de la clase
|
||||
|
||||
// --- Configuración ---
|
||||
void setSpeed(float speed) { speed_ = speed; } // Establece la velocidad
|
||||
void changeSpeedTo(float target_speed, float duration_s); // Cambia la velocidad gradualmente en X segundos
|
||||
void stopGracefully() { stopping_ = true; } // Detiene el desplazamiento de forma ordenada
|
||||
void setColor(Color color) { SDL_SetTextureColorMod(canvas_, color.r, color.g, color.b); } // Cambia el color de la textura
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto isStopped() const -> bool { return speed_ == 0.0F; } // Indica si está parado
|
||||
[[nodiscard]] auto isChangingSpeed() const -> bool { return changing_speed_; } // Indica si está cambiando velocidad gradualmente
|
||||
|
||||
private:
|
||||
// --- Constantes ---
|
||||
static constexpr int TILE_WIDTH = 64; // Ancho del tile
|
||||
static constexpr int TILE_HEIGHT = 64; // Alto del tile
|
||||
static constexpr float STOP_THRESHOLD_FACTOR = 20.0F; // Factor para umbral de parada
|
||||
static constexpr float DECELERATION_FACTOR = 1.05F; // Factor de desaceleración
|
||||
static constexpr float MIN_SPEED = 0.1F; // Velocidad mínima
|
||||
|
||||
// --- Objetos y punteros ---
|
||||
SDL_Renderer* renderer_; // El renderizador de la ventana
|
||||
SDL_Texture* canvas_; // Textura donde dibujar el fondo formado por tiles
|
||||
|
||||
// --- Variables de estado ---
|
||||
SDL_FRect pos_; // Posición y tamaño del mosaico
|
||||
SDL_FRect window_; // Ventana visible para la textura de fondo del título
|
||||
TiledBGMode mode_; // Tipo de movimiento del mosaico
|
||||
float desp_ = 0.0F; // Desplazamiento aplicado
|
||||
float speed_ = 1.0F; // Incremento que se añade al desplazamiento a cada bucle
|
||||
bool stopping_ = false; // Indica si se está deteniendo
|
||||
|
||||
// --- Variables para cambio gradual de velocidad ---
|
||||
bool changing_speed_ = false; // Indica si está cambiando velocidad gradualmente
|
||||
float initial_speed_ = 0.0F; // Velocidad inicial del cambio
|
||||
float target_speed_ = 0.0F; // Velocidad objetivo del cambio
|
||||
float change_duration_s_ = 0.0F; // Duración total del cambio en segundos
|
||||
float change_timer_s_ = 0.0F; // Tiempo transcurrido del cambio
|
||||
|
||||
// --- Métodos internos ---
|
||||
void fillTexture(); // Rellena la textura con el contenido
|
||||
void updateDesp(float delta_time) { desp_ += speed_ * delta_time; } // Actualiza el desplazamiento (time-based)
|
||||
void updateStop(float delta_time); // Detiene el desplazamiento de forma ordenada (time-based)
|
||||
void updateSpeedChange(float delta_time); // Actualiza el cambio gradual de velocidad (time-based)
|
||||
};
|
||||
106
source/core/rendering/writer.cpp
Normal file
106
source/core/rendering/writer.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#include "writer.hpp"
|
||||
|
||||
#include "text.hpp" // Para Text
|
||||
|
||||
// Actualiza el objeto (delta_time en ms)
|
||||
void Writer::update(float delta_time) {
|
||||
if (enabled_) {
|
||||
if (!completed_) {
|
||||
// No completado
|
||||
writing_timer_ += delta_time;
|
||||
if (writing_timer_ >= speed_interval_) {
|
||||
index_++;
|
||||
writing_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
if (index_ == length_) {
|
||||
completed_ = true;
|
||||
}
|
||||
} else {
|
||||
// Completado
|
||||
enabled_timer_ += delta_time;
|
||||
finished_ = enabled_timer_ >= enabled_timer_target_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el objeto (delta_time en segundos)
|
||||
void Writer::updateS(float delta_time) {
|
||||
// Convierte segundos a milisegundos y usa la lógica normal
|
||||
update(delta_time * 1000.0F);
|
||||
}
|
||||
|
||||
// Dibuja el objeto en pantalla
|
||||
void Writer::render() const {
|
||||
if (enabled_) {
|
||||
text_->write(pos_x_, pos_y_, caption_, kerning_, index_);
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setPosX(int value) {
|
||||
pos_x_ = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setPosY(int value) {
|
||||
pos_y_ = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setKerning(int value) {
|
||||
kerning_ = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setCaption(const std::string& text) {
|
||||
caption_ = text;
|
||||
length_ = text.length();
|
||||
}
|
||||
|
||||
// Establece el valor de la variable (frames)
|
||||
void Writer::setSpeed(int value) {
|
||||
// Convierte frames a milisegundos (frames * 16.67ms)
|
||||
constexpr float FRAME_TIME_MS = 16.67F;
|
||||
speed_interval_ = static_cast<float>(value) * FRAME_TIME_MS;
|
||||
writing_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
// Establece la velocidad en segundos entre caracteres
|
||||
void Writer::setSpeedS(float value) {
|
||||
// Convierte segundos a milisegundos para consistencia interna
|
||||
speed_interval_ = value * 1000.0F;
|
||||
writing_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setEnabled(bool value) {
|
||||
enabled_ = value;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
auto Writer::isEnabled() const -> bool {
|
||||
return enabled_;
|
||||
}
|
||||
|
||||
// Establece el temporizador para deshabilitar el objeto (en milisegundos)
|
||||
void Writer::setFinishedTimerMs(float time_ms) {
|
||||
enabled_timer_target_ = time_ms;
|
||||
enabled_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
// Establece el temporizador para deshabilitar el objeto (en segundos)
|
||||
void Writer::setFinishedTimerS(float time_s) {
|
||||
enabled_timer_target_ = time_s * 1000.0F; // Convertir segundos a milisegundos
|
||||
enabled_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
// Centra la cadena de texto a un punto X
|
||||
void Writer::center(int x) {
|
||||
setPosX(x - (text_->length(caption_, kerning_) / 2));
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
auto Writer::hasFinished() const -> bool {
|
||||
return finished_;
|
||||
}
|
||||
57
source/core/rendering/writer.hpp
Normal file
57
source/core/rendering/writer.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <utility>
|
||||
|
||||
class Text;
|
||||
|
||||
// --- Clase Writer: pinta texto en pantalla letra a letra ---
|
||||
class Writer {
|
||||
public:
|
||||
// --- Constructor y destructor ---
|
||||
explicit Writer(std::shared_ptr<Text> text)
|
||||
: text_(std::move(text)) {}
|
||||
~Writer() = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time); // Actualiza el objeto (delta_time en ms)
|
||||
void updateS(float delta_time); // Actualiza el objeto (delta_time en segundos)
|
||||
void render() const; // Dibuja el objeto en pantalla
|
||||
|
||||
// --- Setters ---
|
||||
void setPosX(int value); // Establece la posición X
|
||||
void setPosY(int value); // Establece la posición Y
|
||||
void setKerning(int value); // Establece el kerning (espaciado entre caracteres)
|
||||
void setCaption(const std::string& text); // Establece el texto a escribir
|
||||
void setSpeed(int value); // Establece la velocidad de escritura (frames)
|
||||
void setSpeedS(float value); // Establece la velocidad de escritura (segundos entre caracteres)
|
||||
void setEnabled(bool value); // Habilita o deshabilita el objeto
|
||||
void setFinishedTimerMs(float time_ms); // Establece el temporizador para deshabilitar el objeto (en ms)
|
||||
void setFinishedTimerS(float time_s); // Establece el temporizador para deshabilitar el objeto (en segundos)
|
||||
|
||||
void center(int x); // Centra la cadena de texto a un punto X
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto isEnabled() const -> bool; // Indica si el objeto está habilitado
|
||||
[[nodiscard]] auto hasFinished() const -> bool; // Indica si ya ha terminado
|
||||
|
||||
private:
|
||||
// --- Objetos y punteros ---
|
||||
std::shared_ptr<Text> text_; // Objeto encargado de escribir el texto
|
||||
|
||||
// --- Variables de estado ---
|
||||
std::string caption_; // El texto para escribir
|
||||
int pos_x_ = 0; // Posición en el eje X donde empezar a escribir el texto
|
||||
int pos_y_ = 0; // Posición en el eje Y donde empezar a escribir el texto
|
||||
int kerning_ = 0; // Kerning del texto, es decir, espaciado entre caracteres
|
||||
float speed_interval_ = 0.0F; // Intervalo entre caracteres (ms para compatibilidad)
|
||||
float writing_timer_ = 0.0F; // Temporizador de escritura para cada caracter
|
||||
int index_ = 0; // Posición del texto que se está escribiendo
|
||||
int length_ = 0; // Longitud de la cadena a escribir
|
||||
float enabled_timer_ = 0.0F; // Temporizador para deshabilitar el objeto
|
||||
float enabled_timer_target_ = 0.0F; // Tiempo objetivo para deshabilitar el objeto
|
||||
bool completed_ = false; // Indica si se ha escrito todo el texto
|
||||
bool enabled_ = false; // Indica si el objeto está habilitado
|
||||
bool finished_ = false; // Indica si ya ha terminado
|
||||
};
|
||||
Reference in New Issue
Block a user