Implementar transiciones suaves de temas con LERP
Características: - Sistema LERP para interpolar colores de fondo y sprites - Transiciones de 0.5 segundos sin interrumpir física - Variables de estado: target_theme, transitioning, transition_progress - getInterpolatedColor() para colores en tiempo real - Actualización automática de colores al finalizar transición - setColor() añadido a Ball class - Teclas B y Numpad 1-6 activan transiciones suaves - Ya no reinicia pelotas al cambiar tema 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include <algorithm> // for std::min, std::max, std::sort
|
||||
#include <cmath> // for sqrtf, acosf, cosf, sinf (funciones matemáticas)
|
||||
#include <cstdlib> // for rand, srand
|
||||
#include <cstring> // for strlen
|
||||
#include <ctime> // for time
|
||||
#include <iostream> // for cout
|
||||
#include <string> // for string
|
||||
@@ -165,6 +166,25 @@ void Engine::update() {
|
||||
if (show_text_) {
|
||||
show_text_ = !(SDL_GetTicks() - text_init_time_ > TEXT_DURATION);
|
||||
}
|
||||
|
||||
// Actualizar transición de tema (LERP)
|
||||
if (transitioning_) {
|
||||
transition_progress_ += delta_time_ / transition_duration_;
|
||||
|
||||
if (transition_progress_ >= 1.0f) {
|
||||
// Transición completa
|
||||
transition_progress_ = 1.0f;
|
||||
current_theme_ = target_theme_;
|
||||
transitioning_ = false;
|
||||
|
||||
// Actualizar colores de las pelotas al tema final
|
||||
const ThemeColors& theme = themes_[static_cast<int>(current_theme_)];
|
||||
for (size_t i = 0; i < balls_.size(); i++) {
|
||||
size_t color_index = i % theme.ball_colors.size();
|
||||
balls_[i]->setColor(theme.ball_colors[color_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::handleEvents() {
|
||||
@@ -285,40 +305,36 @@ void Engine::handleEvents() {
|
||||
|
||||
// Ciclar temas de color (movido de T a B)
|
||||
case SDLK_B:
|
||||
// Ciclar al siguiente tema
|
||||
current_theme_ = static_cast<ColorTheme>((static_cast<int>(current_theme_) + 1) % (sizeof(themes_) / sizeof(themes_[0])));
|
||||
initBalls(scenario_); // Regenerar bolas con nueva paleta
|
||||
// Ciclar al siguiente tema con transición suave (LERP)
|
||||
{
|
||||
ColorTheme next_theme = static_cast<ColorTheme>((static_cast<int>(current_theme_) + 1) % (sizeof(themes_) / sizeof(themes_[0])));
|
||||
startThemeTransition(next_theme);
|
||||
}
|
||||
break;
|
||||
|
||||
// Temas de colores con teclado numérico
|
||||
// Temas de colores con teclado numérico (con transición suave)
|
||||
case SDLK_KP_1:
|
||||
current_theme_ = ColorTheme::SUNSET;
|
||||
initBalls(scenario_);
|
||||
startThemeTransition(ColorTheme::SUNSET);
|
||||
break;
|
||||
|
||||
case SDLK_KP_2:
|
||||
current_theme_ = ColorTheme::OCEAN;
|
||||
initBalls(scenario_);
|
||||
startThemeTransition(ColorTheme::OCEAN);
|
||||
break;
|
||||
|
||||
case SDLK_KP_3:
|
||||
current_theme_ = ColorTheme::NEON;
|
||||
initBalls(scenario_);
|
||||
startThemeTransition(ColorTheme::NEON);
|
||||
break;
|
||||
|
||||
case SDLK_KP_4:
|
||||
current_theme_ = ColorTheme::FOREST;
|
||||
initBalls(scenario_);
|
||||
startThemeTransition(ColorTheme::FOREST);
|
||||
break;
|
||||
|
||||
case SDLK_KP_5:
|
||||
current_theme_ = ColorTheme::RGB;
|
||||
initBalls(scenario_);
|
||||
startThemeTransition(ColorTheme::RGB);
|
||||
break;
|
||||
|
||||
case SDLK_KP_6:
|
||||
current_theme_ = ColorTheme::MONOCHROME;
|
||||
initBalls(scenario_);
|
||||
startThemeTransition(ColorTheme::MONOCHROME);
|
||||
break;
|
||||
|
||||
// Control de escala de figura (solo en modo SHAPE)
|
||||
@@ -463,7 +479,7 @@ void Engine::render() {
|
||||
// Renderizar en orden de profundidad (fondo → frente)
|
||||
for (size_t idx : render_order) {
|
||||
SDL_FRect pos = balls_[idx]->getPosition();
|
||||
Color color = balls_[idx]->getColor();
|
||||
Color color = getInterpolatedColor(idx); // Usar color interpolado (LERP)
|
||||
float brightness = balls_[idx]->getDepthBrightness();
|
||||
float depth_scale = balls_[idx]->getDepthScale();
|
||||
|
||||
@@ -480,10 +496,12 @@ void Engine::render() {
|
||||
}
|
||||
} else {
|
||||
// MODO PHYSICS: Renderizar en orden normal del vector (sin escala de profundidad)
|
||||
size_t idx = 0;
|
||||
for (auto &ball : balls_) {
|
||||
SDL_FRect pos = ball->getPosition();
|
||||
Color color = ball->getColor();
|
||||
Color color = getInterpolatedColor(idx); // Usar color interpolado (LERP)
|
||||
addSpriteToBatch(pos.x, pos.y, pos.w, pos.h, color.r, color.g, color.b, 1.0f);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,27 +511,19 @@ void Engine::render() {
|
||||
}
|
||||
|
||||
if (show_text_) {
|
||||
// Colores acordes a cada tema (para texto del número de pelotas y nombre del tema)
|
||||
int theme_colors[][3] = {
|
||||
{255, 140, 60}, // ATARDECER: Naranja cálido
|
||||
{80, 200, 255}, // OCEANO: Azul océano
|
||||
{255, 60, 255}, // NEON: Magenta brillante
|
||||
{100, 255, 100}, // BOSQUE: Verde natural
|
||||
{100, 100, 100} // RGB: Gris oscuro (para contraste con fondo blanco)
|
||||
};
|
||||
// Obtener tema actual
|
||||
int theme_idx = static_cast<int>(current_theme_);
|
||||
const ThemeColors& current = themes_[theme_idx];
|
||||
|
||||
// Texto del número de pelotas con color del tema
|
||||
dbg_print(text_pos_, 8, text_.c_str(), theme_colors[theme_idx][0], theme_colors[theme_idx][1], theme_colors[theme_idx][2]);
|
||||
dbg_print(text_pos_, 8, text_.c_str(), current.text_color_r, current.text_color_g, current.text_color_b);
|
||||
|
||||
// Mostrar nombre del tema en castellano debajo del número de pelotas
|
||||
std::string theme_names_es[] = {"ATARDECER", "OCEANO", "NEON", "BOSQUE", "RGB"};
|
||||
std::string theme_name = theme_names_es[static_cast<int>(current_theme_)];
|
||||
int theme_text_width = static_cast<int>(theme_name.length() * 8); // 8 píxeles por carácter
|
||||
int theme_x = (current_screen_width_ - theme_text_width) / 2; // Centrar horizontalmente
|
||||
int theme_text_width = static_cast<int>(strlen(current.name_es) * 8); // 8 píxeles por carácter
|
||||
int theme_x = (current_screen_width_ - theme_text_width) / 2; // Centrar horizontalmente
|
||||
|
||||
// Texto del nombre del tema con el mismo color
|
||||
dbg_print(theme_x, 24, theme_name.c_str(), theme_colors[theme_idx][0], theme_colors[theme_idx][1], theme_colors[theme_idx][2]);
|
||||
dbg_print(theme_x, 24, current.name_es, current.text_color_r, current.text_color_g, current.text_color_b);
|
||||
}
|
||||
|
||||
// Debug display (solo si está activado con tecla H)
|
||||
@@ -553,8 +563,7 @@ void Engine::render() {
|
||||
}
|
||||
|
||||
// Debug: Mostrar tema actual
|
||||
std::string theme_names[] = {"SUNSET", "OCEAN", "NEON", "FOREST", "RGB"};
|
||||
std::string theme_text = "THEME " + theme_names[static_cast<int>(current_theme_)];
|
||||
std::string theme_text = std::string("THEME ") + themes_[static_cast<int>(current_theme_)].name_en;
|
||||
dbg_print(8, 64, theme_text.c_str(), 255, 255, 128); // Amarillo claro para tema
|
||||
|
||||
// Debug: Mostrar modo de simulación actual
|
||||
@@ -759,16 +768,32 @@ void Engine::renderGradientBackground() {
|
||||
// Crear quad de pantalla completa con degradado
|
||||
SDL_Vertex bg_vertices[4];
|
||||
|
||||
// Obtener colores del tema actual
|
||||
ThemeColors &theme = themes_[static_cast<int>(current_theme_)];
|
||||
// Obtener colores (con LERP si estamos en transición)
|
||||
float top_r, top_g, top_b, bottom_r, bottom_g, bottom_b;
|
||||
|
||||
float top_r = theme.bg_top_r;
|
||||
float top_g = theme.bg_top_g;
|
||||
float top_b = theme.bg_top_b;
|
||||
if (transitioning_) {
|
||||
// Interpolar entre tema actual y tema destino
|
||||
ThemeColors ¤t = themes_[static_cast<int>(current_theme_)];
|
||||
ThemeColors &target = themes_[static_cast<int>(target_theme_)];
|
||||
|
||||
float bottom_r = theme.bg_bottom_r;
|
||||
float bottom_g = theme.bg_bottom_g;
|
||||
float bottom_b = theme.bg_bottom_b;
|
||||
top_r = lerp(current.bg_top_r, target.bg_top_r, transition_progress_);
|
||||
top_g = lerp(current.bg_top_g, target.bg_top_g, transition_progress_);
|
||||
top_b = lerp(current.bg_top_b, target.bg_top_b, transition_progress_);
|
||||
|
||||
bottom_r = lerp(current.bg_bottom_r, target.bg_bottom_r, transition_progress_);
|
||||
bottom_g = lerp(current.bg_bottom_g, target.bg_bottom_g, transition_progress_);
|
||||
bottom_b = lerp(current.bg_bottom_b, target.bg_bottom_b, transition_progress_);
|
||||
} else {
|
||||
// Sin transición: usar tema actual directamente
|
||||
ThemeColors &theme = themes_[static_cast<int>(current_theme_)];
|
||||
top_r = theme.bg_top_r;
|
||||
top_g = theme.bg_top_g;
|
||||
top_b = theme.bg_top_b;
|
||||
|
||||
bottom_r = theme.bg_bottom_r;
|
||||
bottom_g = theme.bg_bottom_g;
|
||||
bottom_b = theme.bg_bottom_b;
|
||||
}
|
||||
|
||||
// Vértice superior izquierdo
|
||||
bg_vertices[0].position = {0, 0};
|
||||
@@ -927,6 +952,8 @@ void Engine::zoomOut() {
|
||||
void Engine::initializeThemes() {
|
||||
// SUNSET: Naranjas, rojos, amarillos, rosas (8 colores)
|
||||
themes_[0] = {
|
||||
"SUNSET", "ATARDECER", // Nombres (inglés, español)
|
||||
255, 140, 60, // Color texto: naranja cálido
|
||||
180.0f / 255.0f, 140.0f / 255.0f, 100.0f / 255.0f, // Fondo superior (naranja suave)
|
||||
40.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior (púrpura oscuro)
|
||||
{{255, 140, 0}, {255, 69, 0}, {255, 215, 0}, {255, 20, 147}, {255, 99, 71}, {255, 165, 0}, {255, 192, 203}, {220, 20, 60}}
|
||||
@@ -934,6 +961,8 @@ void Engine::initializeThemes() {
|
||||
|
||||
// OCEAN: Azules, turquesas, blancos (8 colores)
|
||||
themes_[1] = {
|
||||
"OCEAN", "OCEANO", // Nombres (inglés, español)
|
||||
80, 200, 255, // Color texto: azul océano
|
||||
100.0f / 255.0f, 150.0f / 255.0f, 200.0f / 255.0f, // Fondo superior (azul cielo)
|
||||
20.0f / 255.0f, 40.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior (azul marino)
|
||||
{{0, 191, 255}, {0, 255, 255}, {32, 178, 170}, {176, 224, 230}, {70, 130, 180}, {0, 206, 209}, {240, 248, 255}, {64, 224, 208}}
|
||||
@@ -941,6 +970,8 @@ void Engine::initializeThemes() {
|
||||
|
||||
// NEON: Cian, magenta, verde lima, amarillo vibrante (8 colores)
|
||||
themes_[2] = {
|
||||
"NEON", "NEON", // Nombres (inglés, español)
|
||||
255, 60, 255, // Color texto: magenta brillante
|
||||
20.0f / 255.0f, 20.0f / 255.0f, 40.0f / 255.0f, // Fondo superior (negro azulado)
|
||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior (negro)
|
||||
{{0, 255, 255}, {255, 0, 255}, {50, 205, 50}, {255, 255, 0}, {255, 20, 147}, {0, 255, 127}, {138, 43, 226}, {255, 69, 0}}
|
||||
@@ -948,6 +979,8 @@ void Engine::initializeThemes() {
|
||||
|
||||
// FOREST: Verdes, marrones, amarillos otoño (8 colores)
|
||||
themes_[3] = {
|
||||
"FOREST", "BOSQUE", // Nombres (inglés, español)
|
||||
100, 255, 100, // Color texto: verde natural
|
||||
144.0f / 255.0f, 238.0f / 255.0f, 144.0f / 255.0f, // Fondo superior (verde claro)
|
||||
101.0f / 255.0f, 67.0f / 255.0f, 33.0f / 255.0f, // Fondo inferior (marrón tierra)
|
||||
{{34, 139, 34}, {107, 142, 35}, {154, 205, 50}, {255, 215, 0}, {210, 180, 140}, {160, 82, 45}, {218, 165, 32}, {50, 205, 50}}
|
||||
@@ -955,6 +988,8 @@ void Engine::initializeThemes() {
|
||||
|
||||
// RGB: Círculo cromático con 24 puntos (cada 15°) - Ultra precisión matemática
|
||||
themes_[4] = {
|
||||
"RGB", "RGB", // Nombres (inglés, español)
|
||||
100, 100, 100, // Color texto: gris oscuro (contraste con fondo blanco)
|
||||
1.0f, 1.0f, 1.0f, // Fondo superior (blanco puro)
|
||||
1.0f, 1.0f, 1.0f, // Fondo inferior (blanco puro) - sin degradado
|
||||
{
|
||||
@@ -987,21 +1022,53 @@ void Engine::initializeThemes() {
|
||||
|
||||
// MONOCHROME: Fondo negro degradado, sprites blancos monocromáticos (8 tonos grises)
|
||||
themes_[5] = {
|
||||
"MONOCHROME", "MONOCROMO", // Nombres (inglés, español)
|
||||
200, 200, 200, // Color texto: gris claro
|
||||
20.0f / 255.0f, 20.0f / 255.0f, 20.0f / 255.0f, // Fondo superior (gris muy oscuro)
|
||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior (negro puro)
|
||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior (negro)
|
||||
{
|
||||
{255, 255, 255}, // Blanco puro
|
||||
{230, 230, 230}, // Gris muy claro
|
||||
{200, 200, 200}, // Gris claro
|
||||
{170, 170, 170}, // Gris medio-claro
|
||||
{140, 140, 140}, // Gris medio
|
||||
{110, 110, 110}, // Gris medio-oscuro
|
||||
{80, 80, 80}, // Gris oscuro
|
||||
{50, 50, 50} // Gris muy oscuro
|
||||
{255, 255, 255}, // Blanco puro - todas las pelotas del mismo color
|
||||
{255, 255, 255},
|
||||
{255, 255, 255},
|
||||
{255, 255, 255},
|
||||
{255, 255, 255},
|
||||
{255, 255, 255},
|
||||
{255, 255, 255},
|
||||
{255, 255, 255}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void Engine::startThemeTransition(ColorTheme new_theme) {
|
||||
if (new_theme == current_theme_) return; // Ya estamos en ese tema
|
||||
|
||||
target_theme_ = new_theme;
|
||||
transitioning_ = true;
|
||||
transition_progress_ = 0.0f;
|
||||
}
|
||||
|
||||
Color Engine::getInterpolatedColor(size_t ball_index) const {
|
||||
if (!transitioning_) {
|
||||
// Sin transición: devolver color actual
|
||||
return balls_[ball_index]->getColor();
|
||||
}
|
||||
|
||||
// En transición: interpolar entre color actual y color destino
|
||||
Color current_color = balls_[ball_index]->getColor();
|
||||
|
||||
// Obtener el color destino (mismo índice de color en el tema destino)
|
||||
const ThemeColors& target_theme = themes_[static_cast<int>(target_theme_)];
|
||||
size_t color_index = ball_index % target_theme.ball_colors.size();
|
||||
Color target_color = target_theme.ball_colors[color_index];
|
||||
|
||||
// Interpolar RGB
|
||||
return {
|
||||
static_cast<Uint8>(lerp(static_cast<float>(current_color.r), static_cast<float>(target_color.r), transition_progress_)),
|
||||
static_cast<Uint8>(lerp(static_cast<float>(current_color.g), static_cast<float>(target_color.g), transition_progress_)),
|
||||
static_cast<Uint8>(lerp(static_cast<float>(current_color.b), static_cast<float>(target_color.b), transition_progress_))
|
||||
};
|
||||
}
|
||||
|
||||
void Engine::checkAutoRestart() {
|
||||
// Verificar si TODAS las pelotas están paradas
|
||||
bool all_stopped = true;
|
||||
|
||||
Reference in New Issue
Block a user