|
|
|
|
@@ -206,6 +206,7 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
|
|
|
|
srand(static_cast<unsigned>(time(nullptr)));
|
|
|
|
|
dbg_init(renderer_);
|
|
|
|
|
initializeThemes();
|
|
|
|
|
initializeDynamicThemes();
|
|
|
|
|
initBalls(scenario_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -307,6 +308,9 @@ void Engine::update() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Actualizar tema dinámico (animación de keyframes)
|
|
|
|
|
updateDynamicTheme();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Engine::handleEvents() {
|
|
|
|
|
@@ -470,6 +474,19 @@ void Engine::handleEvents() {
|
|
|
|
|
startThemeTransition(ColorTheme::LAVENDER);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Temas dinámicos (animados) - Solo Numpad 8/9/0 (teclas normales usadas para escenarios)
|
|
|
|
|
case SDLK_KP_8:
|
|
|
|
|
activateDynamicTheme(0); // SUNRISE
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SDLK_KP_9:
|
|
|
|
|
activateDynamicTheme(1); // OCEAN WAVES
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SDLK_KP_0:
|
|
|
|
|
activateDynamicTheme(2); // NEON PULSE
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Cambio de sprite/textura dinámico
|
|
|
|
|
case SDLK_N:
|
|
|
|
|
switchTexture();
|
|
|
|
|
@@ -590,23 +607,29 @@ void Engine::handleEvents() {
|
|
|
|
|
toggleIntegerScaling();
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Toggle Modo DEMO COMPLETO (auto-play)
|
|
|
|
|
// Toggle Modo DEMO COMPLETO (auto-play) o Pausar tema dinámico (Shift+D)
|
|
|
|
|
case SDLK_D:
|
|
|
|
|
if (current_app_mode_ == AppMode::DEMO) {
|
|
|
|
|
// Desactivar DEMO → MANUAL
|
|
|
|
|
setState(AppMode::MANUAL);
|
|
|
|
|
text_ = "DEMO MODE OFF";
|
|
|
|
|
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
|
|
|
|
|
show_text_ = true;
|
|
|
|
|
text_init_time_ = SDL_GetTicks();
|
|
|
|
|
// Shift+D = Pausar tema dinámico
|
|
|
|
|
if (event.key.mod & SDL_KMOD_SHIFT) {
|
|
|
|
|
pauseDynamicTheme();
|
|
|
|
|
} else {
|
|
|
|
|
// Activar DEMO (desde cualquier otro modo)
|
|
|
|
|
setState(AppMode::DEMO);
|
|
|
|
|
randomizeOnDemoStart(false);
|
|
|
|
|
text_ = "DEMO MODE ON";
|
|
|
|
|
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
|
|
|
|
|
show_text_ = true;
|
|
|
|
|
text_init_time_ = SDL_GetTicks();
|
|
|
|
|
// D sin Shift = Toggle modo DEMO
|
|
|
|
|
if (current_app_mode_ == AppMode::DEMO) {
|
|
|
|
|
// Desactivar DEMO → MANUAL
|
|
|
|
|
setState(AppMode::MANUAL);
|
|
|
|
|
text_ = "DEMO MODE OFF";
|
|
|
|
|
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
|
|
|
|
|
show_text_ = true;
|
|
|
|
|
text_init_time_ = SDL_GetTicks();
|
|
|
|
|
} else {
|
|
|
|
|
// Activar DEMO (desde cualquier otro modo)
|
|
|
|
|
setState(AppMode::DEMO);
|
|
|
|
|
randomizeOnDemoStart(false);
|
|
|
|
|
text_ = "DEMO MODE ON";
|
|
|
|
|
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
|
|
|
|
|
show_text_ = true;
|
|
|
|
|
text_init_time_ = SDL_GetTicks();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
@@ -710,22 +733,38 @@ void Engine::render() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (show_text_) {
|
|
|
|
|
// Obtener tema actual
|
|
|
|
|
int theme_idx = static_cast<int>(current_theme_);
|
|
|
|
|
const ThemeColors& current = themes_[theme_idx];
|
|
|
|
|
// Obtener datos del tema actual (estático o dinámico)
|
|
|
|
|
int text_color_r, text_color_g, text_color_b;
|
|
|
|
|
const char* theme_name_es = nullptr;
|
|
|
|
|
|
|
|
|
|
if (dynamic_theme_active_ && current_dynamic_theme_index_ >= 0) {
|
|
|
|
|
// Tema dinámico activo
|
|
|
|
|
const DynamicTheme& dyn_theme = dynamic_themes_[current_dynamic_theme_index_];
|
|
|
|
|
text_color_r = dyn_theme.text_color_r;
|
|
|
|
|
text_color_g = dyn_theme.text_color_g;
|
|
|
|
|
text_color_b = dyn_theme.text_color_b;
|
|
|
|
|
theme_name_es = dyn_theme.name_es;
|
|
|
|
|
} else {
|
|
|
|
|
// Tema estático
|
|
|
|
|
int theme_idx = static_cast<int>(current_theme_);
|
|
|
|
|
const ThemeColors& current = themes_[theme_idx];
|
|
|
|
|
text_color_r = current.text_color_r;
|
|
|
|
|
text_color_g = current.text_color_g;
|
|
|
|
|
text_color_b = current.text_color_b;
|
|
|
|
|
theme_name_es = current.name_es;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Texto del número de pelotas con color del tema
|
|
|
|
|
dbg_print(text_pos_, 8, text_.c_str(), current.text_color_r, current.text_color_g, current.text_color_b);
|
|
|
|
|
dbg_print(text_pos_, 8, text_.c_str(), text_color_r, text_color_g, text_color_b);
|
|
|
|
|
|
|
|
|
|
// Mostrar nombre del tema en castellano debajo del número de pelotas
|
|
|
|
|
// (solo si text_ NO es ya el nombre del tema actual o destino, para evitar duplicación durante LERP)
|
|
|
|
|
const ThemeColors& target = themes_[static_cast<int>(target_theme_)];
|
|
|
|
|
if (text_ != current.name_es && text_ != target.name_es) {
|
|
|
|
|
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
|
|
|
|
|
// (solo si text_ NO es ya el nombre del tema, para evitar duplicación)
|
|
|
|
|
if (theme_name_es != nullptr && text_ != theme_name_es) {
|
|
|
|
|
int theme_text_width = static_cast<int>(strlen(theme_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, current.name_es, current.text_color_r, current.text_color_g, current.text_color_b);
|
|
|
|
|
dbg_print(theme_x, 24, theme_name_es, text_color_r, text_color_g, text_color_b);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -766,7 +805,12 @@ void Engine::render() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Debug: Mostrar tema actual
|
|
|
|
|
std::string theme_text = std::string("THEME ") + themes_[static_cast<int>(current_theme_)].name_en;
|
|
|
|
|
std::string theme_text;
|
|
|
|
|
if (dynamic_theme_active_ && current_dynamic_theme_index_ >= 0) {
|
|
|
|
|
theme_text = std::string("THEME ") + dynamic_themes_[current_dynamic_theme_index_].name_en;
|
|
|
|
|
} else {
|
|
|
|
|
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
|
|
|
|
|
@@ -821,10 +865,21 @@ void Engine::initBalls(int value) {
|
|
|
|
|
const float X = (rand() % spawn_zone_width) + margin; // Posición inicial en X
|
|
|
|
|
const float VX = (((rand() % 20) + 10) * 0.1f) * SIGN; // Velocidad en X
|
|
|
|
|
const float VY = ((rand() % 60) - 30) * 0.1f; // Velocidad en Y
|
|
|
|
|
// Seleccionar color de la paleta del tema actual
|
|
|
|
|
ThemeColors& theme = themes_[static_cast<int>(current_theme_)];
|
|
|
|
|
int color_index = rand() % theme.ball_colors.size(); // Cantidad variable de colores por tema
|
|
|
|
|
const Color COLOR = theme.ball_colors[color_index];
|
|
|
|
|
|
|
|
|
|
// Seleccionar color de la paleta del tema actual (estático o dinámico)
|
|
|
|
|
Color COLOR;
|
|
|
|
|
if (dynamic_theme_active_ && current_dynamic_theme_index_ >= 0) {
|
|
|
|
|
// Tema dinámico: usar colores del keyframe actual
|
|
|
|
|
const DynamicTheme& dyn_theme = dynamic_themes_[current_dynamic_theme_index_];
|
|
|
|
|
const DynamicThemeKeyframe& current_kf = dyn_theme.keyframes[current_keyframe_index_];
|
|
|
|
|
int color_index = rand() % current_kf.ball_colors.size();
|
|
|
|
|
COLOR = current_kf.ball_colors[color_index];
|
|
|
|
|
} else {
|
|
|
|
|
// Tema estático
|
|
|
|
|
ThemeColors& theme = themes_[static_cast<int>(current_theme_)];
|
|
|
|
|
int color_index = rand() % theme.ball_colors.size();
|
|
|
|
|
COLOR = theme.ball_colors[color_index];
|
|
|
|
|
}
|
|
|
|
|
// Generar factor de masa aleatorio (0.7 = ligera, 1.3 = pesada)
|
|
|
|
|
float mass_factor = GRAVITY_MASS_MIN + (rand() % 1000) / 1000.0f * (GRAVITY_MASS_MAX - GRAVITY_MASS_MIN);
|
|
|
|
|
balls_.emplace_back(std::make_unique<Ball>(X, VX, VY, COLOR, texture_, current_screen_width_, current_screen_height_, current_ball_size_, current_gravity_, mass_factor));
|
|
|
|
|
@@ -1060,8 +1115,21 @@ void Engine::renderGradientBackground() {
|
|
|
|
|
// Obtener colores (con LERP si estamos en transición)
|
|
|
|
|
float top_r, top_g, top_b, bottom_r, bottom_g, bottom_b;
|
|
|
|
|
|
|
|
|
|
if (transitioning_) {
|
|
|
|
|
// Interpolar entre tema actual y tema destino
|
|
|
|
|
if (dynamic_theme_active_ && current_dynamic_theme_index_ >= 0) {
|
|
|
|
|
// Tema dinámico activo: interpolar entre keyframes
|
|
|
|
|
DynamicTheme& theme = dynamic_themes_[current_dynamic_theme_index_];
|
|
|
|
|
const DynamicThemeKeyframe& current_kf = theme.keyframes[current_keyframe_index_];
|
|
|
|
|
const DynamicThemeKeyframe& target_kf = theme.keyframes[target_keyframe_index_];
|
|
|
|
|
|
|
|
|
|
top_r = lerp(current_kf.bg_top_r, target_kf.bg_top_r, dynamic_transition_progress_);
|
|
|
|
|
top_g = lerp(current_kf.bg_top_g, target_kf.bg_top_g, dynamic_transition_progress_);
|
|
|
|
|
top_b = lerp(current_kf.bg_top_b, target_kf.bg_top_b, dynamic_transition_progress_);
|
|
|
|
|
|
|
|
|
|
bottom_r = lerp(current_kf.bg_bottom_r, target_kf.bg_bottom_r, dynamic_transition_progress_);
|
|
|
|
|
bottom_g = lerp(current_kf.bg_bottom_g, target_kf.bg_bottom_g, dynamic_transition_progress_);
|
|
|
|
|
bottom_b = lerp(current_kf.bg_bottom_b, target_kf.bg_bottom_b, dynamic_transition_progress_);
|
|
|
|
|
} else if (transitioning_) {
|
|
|
|
|
// Transición estática: interpolar entre tema actual y tema destino
|
|
|
|
|
ThemeColors& current = themes_[static_cast<int>(current_theme_)];
|
|
|
|
|
ThemeColors& target = themes_[static_cast<int>(target_theme_)];
|
|
|
|
|
|
|
|
|
|
@@ -1073,7 +1141,7 @@ void Engine::renderGradientBackground() {
|
|
|
|
|
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
|
|
|
|
|
// Sin transición: usar tema estático actual directamente
|
|
|
|
|
ThemeColors& theme = themes_[static_cast<int>(current_theme_)];
|
|
|
|
|
top_r = theme.bg_top_r;
|
|
|
|
|
top_g = theme.bg_top_g;
|
|
|
|
|
@@ -1386,9 +1454,155 @@ void Engine::initializeThemes() {
|
|
|
|
|
{255, 215, 0}}};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Engine::initializeDynamicThemes() {
|
|
|
|
|
// ========================================================================
|
|
|
|
|
// DYNAMIC_1: "SUNRISE" (Amanecer) - Noche → Alba → Día → Loop
|
|
|
|
|
// ========================================================================
|
|
|
|
|
dynamic_themes_[0] = {
|
|
|
|
|
"SUNRISE",
|
|
|
|
|
"AMANECER",
|
|
|
|
|
255, 200, 100, // Color texto: amarillo cálido
|
|
|
|
|
{
|
|
|
|
|
// Keyframe 0: Noche oscura (estado inicial)
|
|
|
|
|
{
|
|
|
|
|
20.0f / 255.0f, 25.0f / 255.0f, 60.0f / 255.0f, // Fondo superior: azul medianoche
|
|
|
|
|
10.0f / 255.0f, 10.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
|
|
|
|
{{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160},
|
|
|
|
|
{95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165}}, // Pelotas azules tenues
|
|
|
|
|
0.0f // Sin transición (estado inicial)
|
|
|
|
|
},
|
|
|
|
|
// Keyframe 1: Alba naranja-rosa
|
|
|
|
|
{
|
|
|
|
|
180.0f / 255.0f, 100.0f / 255.0f, 120.0f / 255.0f, // Fondo superior: naranja-rosa
|
|
|
|
|
255.0f / 255.0f, 140.0f / 255.0f, 100.0f / 255.0f, // Fondo inferior: naranja cálido
|
|
|
|
|
{{255, 180, 100}, {255, 160, 80}, {255, 200, 120}, {255, 150, 90},
|
|
|
|
|
{255, 190, 110}, {255, 170, 95}, {255, 185, 105}, {255, 165, 88}}, // Pelotas naranjas
|
|
|
|
|
4.0f // 4 segundos para llegar aquí
|
|
|
|
|
},
|
|
|
|
|
// Keyframe 2: Día brillante amarillo
|
|
|
|
|
{
|
|
|
|
|
255.0f / 255.0f, 240.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: amarillo claro
|
|
|
|
|
255.0f / 255.0f, 255.0f / 255.0f, 220.0f / 255.0f, // Fondo inferior: amarillo muy claro
|
|
|
|
|
{{255, 255, 200}, {255, 255, 180}, {255, 255, 220}, {255, 255, 190},
|
|
|
|
|
{255, 255, 210}, {255, 255, 185}, {255, 255, 205}, {255, 255, 195}}, // Pelotas amarillas brillantes
|
|
|
|
|
3.0f // 3 segundos para llegar aquí
|
|
|
|
|
},
|
|
|
|
|
// Keyframe 3: Vuelta a noche (para loop)
|
|
|
|
|
{
|
|
|
|
|
20.0f / 255.0f, 25.0f / 255.0f, 60.0f / 255.0f, // Fondo superior: azul medianoche
|
|
|
|
|
10.0f / 255.0f, 10.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
|
|
|
|
{{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160},
|
|
|
|
|
{95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165}}, // Pelotas azules tenues
|
|
|
|
|
5.0f // 5 segundos para volver a noche
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
true // Loop = true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
|
// DYNAMIC_2: "OCEAN WAVES" (Olas Oceánicas) - Azul oscuro ↔ Turquesa
|
|
|
|
|
// ========================================================================
|
|
|
|
|
dynamic_themes_[1] = {
|
|
|
|
|
"OCEAN WAVES",
|
|
|
|
|
"OLAS OCEANICAS",
|
|
|
|
|
100, 220, 255, // Color texto: cian claro
|
|
|
|
|
{
|
|
|
|
|
// Keyframe 0: Profundidad oceánica (azul oscuro)
|
|
|
|
|
{
|
|
|
|
|
20.0f / 255.0f, 50.0f / 255.0f, 100.0f / 255.0f, // Fondo superior: azul marino
|
|
|
|
|
10.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
|
|
|
|
{{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175},
|
|
|
|
|
{65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168}}, // Pelotas azul oscuro
|
|
|
|
|
0.0f // Estado inicial
|
|
|
|
|
},
|
|
|
|
|
// Keyframe 1: Aguas poco profundas (turquesa brillante)
|
|
|
|
|
{
|
|
|
|
|
100.0f / 255.0f, 200.0f / 255.0f, 230.0f / 255.0f, // Fondo superior: turquesa claro
|
|
|
|
|
50.0f / 255.0f, 150.0f / 255.0f, 200.0f / 255.0f, // Fondo inferior: turquesa medio
|
|
|
|
|
{{100, 220, 255}, {90, 210, 245}, {110, 230, 255}, {95, 215, 250},
|
|
|
|
|
{105, 225, 255}, {98, 218, 248}, {102, 222, 252}, {92, 212, 242}}, // Pelotas turquesa brillante
|
|
|
|
|
4.0f // 4 segundos para llegar
|
|
|
|
|
},
|
|
|
|
|
// Keyframe 2: Vuelta a profundidad (para loop)
|
|
|
|
|
{
|
|
|
|
|
20.0f / 255.0f, 50.0f / 255.0f, 100.0f / 255.0f, // Fondo superior: azul marino
|
|
|
|
|
10.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
|
|
|
|
{{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175},
|
|
|
|
|
{65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168}}, // Pelotas azul oscuro
|
|
|
|
|
4.0f // 4 segundos para volver
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
true // Loop = true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
|
// DYNAMIC_3: "NEON PULSE" (Pulso Neón) - Negro → Neón brillante (rápido)
|
|
|
|
|
// ========================================================================
|
|
|
|
|
dynamic_themes_[2] = {
|
|
|
|
|
"NEON PULSE",
|
|
|
|
|
"PULSO NEON",
|
|
|
|
|
255, 60, 255, // Color texto: magenta brillante
|
|
|
|
|
{
|
|
|
|
|
// Keyframe 0: Apagado (negro)
|
|
|
|
|
{
|
|
|
|
|
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: negro
|
|
|
|
|
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
|
|
|
|
{{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48},
|
|
|
|
|
{42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}}, // Pelotas grises muy oscuras
|
|
|
|
|
0.0f // Estado inicial
|
|
|
|
|
},
|
|
|
|
|
// Keyframe 1: Encendido (neón cian-magenta)
|
|
|
|
|
{
|
|
|
|
|
20.0f / 255.0f, 20.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: azul oscuro
|
|
|
|
|
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
|
|
|
|
{{0, 255, 255}, {255, 0, 255}, {0, 255, 200}, {255, 50, 255},
|
|
|
|
|
{50, 255, 255}, {255, 0, 200}, {0, 255, 230}, {255, 80, 255}}, // Pelotas neón vibrante
|
|
|
|
|
1.5f // 1.5 segundos para encender (rápido)
|
|
|
|
|
},
|
|
|
|
|
// Keyframe 2: Vuelta a apagado (para loop ping-pong)
|
|
|
|
|
{
|
|
|
|
|
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: negro
|
|
|
|
|
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
|
|
|
|
{{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48},
|
|
|
|
|
{42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}}, // Pelotas grises muy oscuras
|
|
|
|
|
1.5f // 1.5 segundos para apagar
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
true // Loop = true
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Engine::startThemeTransition(ColorTheme new_theme) {
|
|
|
|
|
if (new_theme == current_theme_) return; // Ya estamos en ese tema
|
|
|
|
|
|
|
|
|
|
// Si venimos de tema dinámico, hacer transición instantánea (sin LERP)
|
|
|
|
|
// porque current_theme_ apunta a DYNAMIC_X (índice >= 7) y no existe en themes_[]
|
|
|
|
|
if (dynamic_theme_active_) {
|
|
|
|
|
dynamic_theme_active_ = false;
|
|
|
|
|
current_dynamic_theme_index_ = -1;
|
|
|
|
|
|
|
|
|
|
// Cambio instantáneo: sin transición LERP
|
|
|
|
|
current_theme_ = new_theme;
|
|
|
|
|
transitioning_ = false;
|
|
|
|
|
|
|
|
|
|
// Actualizar colores de pelotas al tema final
|
|
|
|
|
const ThemeColors& theme = themes_[static_cast<int>(new_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]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mostrar nombre del tema
|
|
|
|
|
if (current_app_mode_ == AppMode::MANUAL) {
|
|
|
|
|
text_ = theme.name_es;
|
|
|
|
|
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
|
|
|
|
|
show_text_ = true;
|
|
|
|
|
text_init_time_ = SDL_GetTicks();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transición normal entre temas estáticos
|
|
|
|
|
target_theme_ = new_theme;
|
|
|
|
|
transitioning_ = true;
|
|
|
|
|
transition_progress_ = 0.0f;
|
|
|
|
|
@@ -1403,25 +1617,127 @@ void Engine::startThemeTransition(ColorTheme new_theme) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Engine::updateDynamicTheme() {
|
|
|
|
|
if (!dynamic_theme_active_ || current_dynamic_theme_index_ < 0) return;
|
|
|
|
|
if (dynamic_theme_paused_) return; // Pausado con Shift+D
|
|
|
|
|
|
|
|
|
|
DynamicTheme& theme = dynamic_themes_[current_dynamic_theme_index_];
|
|
|
|
|
|
|
|
|
|
// Obtener keyframe destino para calcular duración
|
|
|
|
|
const DynamicThemeKeyframe& target_kf = theme.keyframes[target_keyframe_index_];
|
|
|
|
|
|
|
|
|
|
// Avanzar progreso de transición
|
|
|
|
|
dynamic_transition_progress_ += delta_time_ / target_kf.duration;
|
|
|
|
|
|
|
|
|
|
if (dynamic_transition_progress_ >= 1.0f) {
|
|
|
|
|
// Transición completa: avanzar al siguiente keyframe
|
|
|
|
|
dynamic_transition_progress_ = 0.0f;
|
|
|
|
|
current_keyframe_index_ = target_keyframe_index_;
|
|
|
|
|
|
|
|
|
|
// Calcular siguiente keyframe destino
|
|
|
|
|
target_keyframe_index_++;
|
|
|
|
|
if (target_keyframe_index_ >= theme.keyframes.size()) {
|
|
|
|
|
if (theme.loop) {
|
|
|
|
|
target_keyframe_index_ = 0; // Volver al inicio
|
|
|
|
|
} else {
|
|
|
|
|
target_keyframe_index_ = theme.keyframes.size() - 1; // Quedarse en el último
|
|
|
|
|
dynamic_theme_active_ = false; // Detener animación
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NOTA: No se actualiza Ball::color_ aquí porque getInterpolatedColor()
|
|
|
|
|
// calcula el color directamente desde los keyframes cada frame.
|
|
|
|
|
// Cuando progress=1.0, getInterpolatedColor() devuelve exactamente el color destino.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Engine::activateDynamicTheme(int index) {
|
|
|
|
|
if (index < 0 || index >= 3) return; // Validar índice
|
|
|
|
|
|
|
|
|
|
// Desactivar transición estática si estaba activa
|
|
|
|
|
if (transitioning_) {
|
|
|
|
|
transitioning_ = false;
|
|
|
|
|
current_theme_ = target_theme_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Activar tema dinámico
|
|
|
|
|
dynamic_theme_active_ = true;
|
|
|
|
|
current_dynamic_theme_index_ = index;
|
|
|
|
|
current_keyframe_index_ = 0;
|
|
|
|
|
target_keyframe_index_ = 1;
|
|
|
|
|
dynamic_transition_progress_ = 0.0f;
|
|
|
|
|
dynamic_theme_paused_ = false;
|
|
|
|
|
|
|
|
|
|
// NOTA: No actualizamos current_theme_ porque cuando dynamic_theme_active_=true,
|
|
|
|
|
// todo el código usa current_dynamic_theme_index_ en su lugar
|
|
|
|
|
|
|
|
|
|
// Establecer colores iniciales del keyframe 0
|
|
|
|
|
DynamicTheme& theme = dynamic_themes_[index];
|
|
|
|
|
const DynamicThemeKeyframe& initial_kf = theme.keyframes[0];
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < balls_.size(); i++) {
|
|
|
|
|
size_t color_index = i % initial_kf.ball_colors.size();
|
|
|
|
|
balls_[i]->setColor(initial_kf.ball_colors[color_index]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mostrar nombre del tema (solo si NO estamos en modo demo)
|
|
|
|
|
if (current_app_mode_ == AppMode::MANUAL) {
|
|
|
|
|
text_ = theme.name_es;
|
|
|
|
|
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
|
|
|
|
|
show_text_ = true;
|
|
|
|
|
text_init_time_ = SDL_GetTicks();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Engine::pauseDynamicTheme() {
|
|
|
|
|
if (!dynamic_theme_active_) return; // Solo funciona si hay tema dinámico activo
|
|
|
|
|
|
|
|
|
|
dynamic_theme_paused_ = !dynamic_theme_paused_;
|
|
|
|
|
|
|
|
|
|
// Mostrar estado de pausa (solo si NO estamos en modo demo)
|
|
|
|
|
if (current_app_mode_ == AppMode::MANUAL) {
|
|
|
|
|
text_ = dynamic_theme_paused_ ? "PAUSADO" : "REANUDADO";
|
|
|
|
|
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
|
|
|
|
|
show_text_ = true;
|
|
|
|
|
text_init_time_ = SDL_GetTicks();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Color Engine::getInterpolatedColor(size_t ball_index) const {
|
|
|
|
|
if (!transitioning_) {
|
|
|
|
|
if (dynamic_theme_active_ && current_dynamic_theme_index_ >= 0) {
|
|
|
|
|
// Tema dinámico activo: interpolar entre keyframes
|
|
|
|
|
const DynamicTheme& theme = dynamic_themes_[current_dynamic_theme_index_];
|
|
|
|
|
const DynamicThemeKeyframe& current_kf = theme.keyframes[current_keyframe_index_];
|
|
|
|
|
const DynamicThemeKeyframe& target_kf = theme.keyframes[target_keyframe_index_];
|
|
|
|
|
|
|
|
|
|
// Obtener colores desde keyframes (NO desde Ball::color_)
|
|
|
|
|
size_t color_index = ball_index % current_kf.ball_colors.size();
|
|
|
|
|
Color current_color = current_kf.ball_colors[color_index]; // Color del keyframe actual
|
|
|
|
|
Color target_color = target_kf.ball_colors[color_index]; // Color del keyframe destino
|
|
|
|
|
|
|
|
|
|
// Interpolar RGB entre keyframes
|
|
|
|
|
return {
|
|
|
|
|
static_cast<int>(lerp(static_cast<float>(current_color.r), static_cast<float>(target_color.r), dynamic_transition_progress_)),
|
|
|
|
|
static_cast<int>(lerp(static_cast<float>(current_color.g), static_cast<float>(target_color.g), dynamic_transition_progress_)),
|
|
|
|
|
static_cast<int>(lerp(static_cast<float>(current_color.b), static_cast<float>(target_color.b), dynamic_transition_progress_))};
|
|
|
|
|
} else if (transitioning_) {
|
|
|
|
|
// Transición estática: interpolar entre tema actual y tema 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<int>(lerp(static_cast<float>(current_color.r), static_cast<float>(target_color.r), transition_progress_)),
|
|
|
|
|
static_cast<int>(lerp(static_cast<float>(current_color.g), static_cast<float>(target_color.g), transition_progress_)),
|
|
|
|
|
static_cast<int>(lerp(static_cast<float>(current_color.b), static_cast<float>(target_color.b), transition_progress_))};
|
|
|
|
|
} else {
|
|
|
|
|
// 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_))};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|