- Añadir SDL_SetTextureScaleMode(render_texture_, SDL_SCALEMODE_NEAREST) para pixel-perfect - Implementar sistema de fallback robusto para detección de display - Restaurar funcionalidad completa de zoom con centrado y guardas - Las texturas ahora se ven pixel-perfect sin blur - El zoom respeta límites del escritorio correctamente 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
840 lines
31 KiB
C++
840 lines
31 KiB
C++
#include "engine.h"
|
|
|
|
#include <SDL3/SDL_error.h> // for SDL_GetError
|
|
#include <SDL3/SDL_events.h> // for SDL_Event, SDL_PollEvent
|
|
#include <SDL3/SDL_keycode.h> // for SDL_Keycode
|
|
#include <SDL3/SDL_timer.h> // for SDL_GetTicks
|
|
#include <SDL3/SDL_render.h> // for SDL_Renderer, SDL_Texture
|
|
|
|
#include <algorithm> // for std::min, std::max
|
|
#include <cstdlib> // for rand, srand
|
|
#include <ctime> // for time
|
|
#include <iostream> // for cout
|
|
#include <string> // for string
|
|
#include <filesystem> // for path operations
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h> // for GetModuleFileName
|
|
#endif
|
|
|
|
#include "ball.h" // for Ball
|
|
#include "external/dbgtxt.h" // for dbg_init, dbg_print
|
|
#include "external/texture.h" // for Texture
|
|
|
|
// Función auxiliar para obtener la ruta del directorio del ejecutable
|
|
std::string getExecutableDirectory() {
|
|
#ifdef _WIN32
|
|
char buffer[MAX_PATH];
|
|
GetModuleFileNameA(NULL, buffer, MAX_PATH);
|
|
std::filesystem::path exe_path(buffer);
|
|
return exe_path.parent_path().string();
|
|
#else
|
|
// Para Linux/macOS se podría usar readlink("/proc/self/exe") o dladdr
|
|
return "."; // Fallback para otros sistemas
|
|
#endif
|
|
}
|
|
|
|
// Implementación de métodos públicos
|
|
bool Engine::initialize() {
|
|
// Crear y configurar el window manager
|
|
window_manager_ = std::make_unique<vibe4::WindowManager>();
|
|
|
|
if (!window_manager_->initialize(WINDOW_CAPTION, SCREEN_WIDTH, SCREEN_HEIGHT, WINDOW_ZOOM)) {
|
|
std::cout << "¡No se pudo inicializar el WindowManager!" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// Configurar efectos CRT iniciales
|
|
crt_params_.scanline_intensity = 0.5f;
|
|
crt_params_.curvature_x = 0.1f;
|
|
crt_params_.curvature_y = 0.1f;
|
|
crt_params_.bloom_factor = 1.2f;
|
|
crt_params_.mask_brightness = 0.8f;
|
|
crt_params_.enable_scanlines = true;
|
|
crt_params_.enable_curvature = true;
|
|
crt_params_.enable_bloom = true;
|
|
|
|
// Aplicar parámetros CRT al renderer
|
|
auto* renderer = window_manager_->getRenderer();
|
|
if (renderer) {
|
|
renderer->setCRTParams(crt_params_);
|
|
renderer->enableCRT(crt_effects_enabled_);
|
|
renderer->setVSync(vsync_enabled_);
|
|
}
|
|
|
|
// Inicializar otros componentes
|
|
srand(static_cast<unsigned>(time(nullptr)));
|
|
|
|
// Cargar textura para sprites e inicializar debug text
|
|
auto* sdl_renderer = window_manager_->getSDLRenderer();
|
|
if (sdl_renderer) {
|
|
// Construir ruta absoluta a la imagen
|
|
std::string exe_dir = getExecutableDirectory();
|
|
std::string texture_path = exe_dir + "/data/ball.png";
|
|
texture_ = std::make_shared<Texture>(sdl_renderer, texture_path);
|
|
|
|
// Inicializar sistema de texto debug
|
|
dbg_init(sdl_renderer);
|
|
} else {
|
|
std::cout << "¡Advertencia: No se pudo obtener SDL_Renderer para texturas!" << std::endl;
|
|
}
|
|
|
|
initializeThemes();
|
|
initBalls(scenario_);
|
|
|
|
std::cout << "Engine inicializado con backend: " << getBackendInfo() << std::endl;
|
|
return true;
|
|
}
|
|
|
|
void Engine::run() {
|
|
while (!should_exit_) {
|
|
calculateDeltaTime();
|
|
update();
|
|
handleEvents();
|
|
render();
|
|
}
|
|
}
|
|
|
|
void Engine::shutdown() {
|
|
// El WindowManager se encarga de toda la limpieza
|
|
if (window_manager_) {
|
|
window_manager_->shutdown();
|
|
window_manager_.reset();
|
|
}
|
|
|
|
// La textura se limpia automáticamente con shared_ptr
|
|
}
|
|
|
|
// Métodos privados - esqueleto básico por ahora
|
|
void Engine::calculateDeltaTime() {
|
|
Uint64 current_time = SDL_GetTicks();
|
|
|
|
// En el primer frame, inicializar el tiempo anterior
|
|
if (last_frame_time_ == 0) {
|
|
last_frame_time_ = current_time;
|
|
delta_time_ = 1.0f / 60.0f; // Asumir 60 FPS para el primer frame
|
|
return;
|
|
}
|
|
|
|
// Calcular delta time en segundos
|
|
delta_time_ = (current_time - last_frame_time_) / 1000.0f;
|
|
last_frame_time_ = current_time;
|
|
|
|
// Limitar delta time para evitar saltos grandes (pausa larga, depuración, etc.)
|
|
if (delta_time_ > 0.05f) { // Máximo 50ms (20 FPS mínimo)
|
|
delta_time_ = 1.0f / 60.0f; // Fallback a 60 FPS
|
|
}
|
|
}
|
|
|
|
void Engine::update() {
|
|
// Calcular FPS
|
|
fps_frame_count_++;
|
|
Uint64 current_time = SDL_GetTicks();
|
|
if (current_time - fps_last_time_ >= 1000) // Actualizar cada segundo
|
|
{
|
|
fps_current_ = fps_frame_count_;
|
|
fps_frame_count_ = 0;
|
|
fps_last_time_ = current_time;
|
|
fps_text_ = "FPS: " + std::to_string(fps_current_);
|
|
}
|
|
|
|
// ¡DELTA TIME! Actualizar física siempre, usando tiempo transcurrido
|
|
for (auto &ball : balls_) {
|
|
ball->update(delta_time_); // Pasar delta time a cada pelota
|
|
}
|
|
|
|
// Actualizar texto (sin cambios en la lógica)
|
|
if (show_text_) {
|
|
show_text_ = !(SDL_GetTicks() - text_init_time_ > TEXT_DURATION);
|
|
}
|
|
|
|
// Verificar auto-reinicio cuando todas las pelotas están quietas
|
|
checkAutoRestart();
|
|
}
|
|
|
|
void Engine::handleEvents() {
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event)) {
|
|
// Salir del bucle si se detecta una petición de cierre
|
|
if (event.type == SDL_EVENT_QUIT) {
|
|
should_exit_ = true;
|
|
break;
|
|
}
|
|
|
|
// Procesar eventos de teclado
|
|
if (event.type == SDL_EVENT_KEY_DOWN && event.key.repeat == 0) {
|
|
switch (event.key.key) {
|
|
case SDLK_ESCAPE:
|
|
should_exit_ = true;
|
|
break;
|
|
|
|
case SDLK_SPACE:
|
|
pushBallsAwayFromGravity();
|
|
break;
|
|
|
|
case SDLK_G:
|
|
switchBallsGravity();
|
|
break;
|
|
|
|
// Controles de dirección de gravedad con teclas de cursor
|
|
case SDLK_UP:
|
|
changeGravityDirection(GravityDirection::UP);
|
|
break;
|
|
|
|
case SDLK_DOWN:
|
|
changeGravityDirection(GravityDirection::DOWN);
|
|
break;
|
|
|
|
case SDLK_LEFT:
|
|
changeGravityDirection(GravityDirection::LEFT);
|
|
break;
|
|
|
|
case SDLK_RIGHT:
|
|
changeGravityDirection(GravityDirection::RIGHT);
|
|
break;
|
|
|
|
case SDLK_V:
|
|
toggleVSync();
|
|
break;
|
|
|
|
case SDLK_H:
|
|
show_debug_ = !show_debug_;
|
|
break;
|
|
|
|
case SDLK_T:
|
|
// 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
|
|
break;
|
|
|
|
// Temas de colores con teclado numérico
|
|
case SDLK_KP_1:
|
|
current_theme_ = ColorTheme::SUNSET;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_KP_2:
|
|
current_theme_ = ColorTheme::OCEAN;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_KP_3:
|
|
current_theme_ = ColorTheme::NEON;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_KP_4:
|
|
current_theme_ = ColorTheme::FOREST;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_KP_5:
|
|
current_theme_ = ColorTheme::RGB;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_1:
|
|
scenario_ = 0;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_2:
|
|
scenario_ = 1;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_3:
|
|
scenario_ = 2;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_4:
|
|
scenario_ = 3;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_5:
|
|
scenario_ = 4;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_6:
|
|
scenario_ = 5;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_7:
|
|
scenario_ = 6;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
case SDLK_8:
|
|
scenario_ = 7;
|
|
initBalls(scenario_);
|
|
break;
|
|
|
|
// Controles de zoom dinámico (solo si no estamos en fullscreen)
|
|
case SDLK_F1:
|
|
if (!fullscreen_enabled_ && !real_fullscreen_enabled_) {
|
|
zoomOut();
|
|
}
|
|
break;
|
|
|
|
case SDLK_F2:
|
|
if (!fullscreen_enabled_ && !real_fullscreen_enabled_) {
|
|
zoomIn();
|
|
}
|
|
break;
|
|
|
|
// Control de pantalla completa
|
|
case SDLK_F3:
|
|
toggleFullscreen();
|
|
break;
|
|
|
|
// Modo real fullscreen (cambia resolución interna)
|
|
case SDLK_F4:
|
|
toggleRealFullscreen();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Engine::render() {
|
|
if (!window_manager_) return;
|
|
|
|
// PASO 1: Activar renderizado a textura lógica
|
|
if (!window_manager_->setRenderTarget()) {
|
|
return;
|
|
}
|
|
|
|
// PASO 2: Renderizar fondo degradado a la textura
|
|
renderGradientBackground();
|
|
|
|
// PASO 3: Limpiar batches del frame anterior
|
|
batch_vertices_.clear();
|
|
batch_indices_.clear();
|
|
|
|
// PASO 4: Recopilar datos de todas las bolas para batch rendering
|
|
for (auto &ball : balls_) {
|
|
SDL_FRect pos = ball->getPosition();
|
|
Color color = ball->getColor();
|
|
addSpriteToBatch(pos.x, pos.y, pos.w, pos.h, color.r, color.g, color.b);
|
|
}
|
|
|
|
// PASO 5: Renderizar todas las bolas a la textura
|
|
auto* sdl_renderer = window_manager_->getSDLRenderer();
|
|
if (!batch_vertices_.empty() && sdl_renderer && texture_) {
|
|
SDL_RenderGeometry(sdl_renderer, texture_->getSDLTexture(),
|
|
batch_vertices_.data(), static_cast<int>(batch_vertices_.size()),
|
|
batch_indices_.data(), static_cast<int>(batch_indices_.size()));
|
|
}
|
|
|
|
// PASO 5.5: Renderizar texto a la textura (antes de presentar)
|
|
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)
|
|
};
|
|
int theme_idx = static_cast<int>(current_theme_);
|
|
|
|
// 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]);
|
|
|
|
// 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
|
|
|
|
// 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]);
|
|
}
|
|
|
|
// Debug display (solo si está activado con tecla H)
|
|
if (show_debug_) {
|
|
// Mostrar contador de FPS en esquina superior derecha
|
|
int fps_text_width = static_cast<int>(fps_text_.length() * 8); // 8 píxeles por carácter
|
|
int fps_x = current_screen_width_ - fps_text_width - 8; // 8 píxeles de margen
|
|
dbg_print(fps_x, 8, fps_text_.c_str(), 255, 255, 0); // Amarillo para distinguir
|
|
|
|
// Mostrar estado V-Sync en esquina superior izquierda
|
|
dbg_print(8, 8, vsync_text_.c_str(), 0, 255, 255); // Cian para distinguir
|
|
|
|
// Debug: Mostrar valores de la primera pelota (si existe)
|
|
if (!balls_.empty()) {
|
|
// Línea 1: Gravedad (solo números enteros)
|
|
int grav_int = static_cast<int>(balls_[0]->getGravityForce());
|
|
std::string grav_text = "GRAV " + std::to_string(grav_int);
|
|
dbg_print(8, 24, grav_text.c_str(), 255, 0, 255); // Magenta para debug
|
|
|
|
// Línea 2: Velocidad Y (solo números enteros)
|
|
int vy_int = static_cast<int>(balls_[0]->getVelocityY());
|
|
std::string vy_text = "VY " + std::to_string(vy_int);
|
|
dbg_print(8, 32, vy_text.c_str(), 255, 0, 255); // Magenta para debug
|
|
|
|
// Línea 3: Estado superficie
|
|
std::string surface_text = balls_[0]->isOnSurface() ? "SURFACE YES" : "SURFACE NO";
|
|
dbg_print(8, 40, surface_text.c_str(), 255, 0, 255); // Magenta para debug
|
|
|
|
// Línea 4: Coeficiente de rebote (loss)
|
|
float loss_val = balls_[0]->getLossCoefficient();
|
|
std::string loss_text = "LOSS " + std::to_string(loss_val).substr(0, 4); // Solo 2 decimales
|
|
dbg_print(8, 48, loss_text.c_str(), 255, 0, 255); // Magenta para debug
|
|
|
|
// Línea 5: Dirección de gravedad
|
|
std::string gravity_dir_text = "GRAVITY " + gravityDirectionToString(current_gravity_);
|
|
dbg_print(8, 56, gravity_dir_text.c_str(), 255, 255, 0); // Amarillo para dirección
|
|
}
|
|
|
|
// Debug: Mostrar tema actual
|
|
std::string theme_names[] = {"SUNSET", "OCEAN", "NEON", "FOREST"};
|
|
std::string theme_text = "THEME " + theme_names[static_cast<int>(current_theme_)];
|
|
dbg_print(8, 64, theme_text.c_str(), 255, 255, 128); // Amarillo claro para tema
|
|
}
|
|
|
|
// PASO 6: Presentar la textura final con zoom/postprocesado
|
|
window_manager_->presentFrame();
|
|
}
|
|
|
|
void Engine::initBalls(int value) {
|
|
// Limpiar las bolas actuales
|
|
balls_.clear();
|
|
|
|
// Resetear gravedad al estado por defecto (DOWN) al cambiar escenario
|
|
changeGravityDirection(GravityDirection::DOWN);
|
|
|
|
// Crear las bolas según el escenario
|
|
for (int i = 0; i < test_.at(value); ++i) {
|
|
const int SIGN = ((rand() % 2) * 2) - 1; // Genera un signo aleatorio (+ o -)
|
|
// Calcular spawn zone: margen a cada lado, zona central para spawn
|
|
const int margin = static_cast<int>(current_screen_width_ * BALL_SPAWN_MARGIN);
|
|
const int spawn_zone_width = current_screen_width_ - (2 * margin);
|
|
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];
|
|
// 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_gravity_, mass_factor));
|
|
}
|
|
setText(); // Actualiza el texto
|
|
}
|
|
|
|
void Engine::setText() {
|
|
int num_balls = test_.at(scenario_);
|
|
if (num_balls == 1) {
|
|
text_ = "1 PELOTA";
|
|
} else {
|
|
text_ = std::to_string(num_balls) + " PELOTAS";
|
|
}
|
|
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; // Centrar texto
|
|
show_text_ = true;
|
|
text_init_time_ = SDL_GetTicks();
|
|
}
|
|
|
|
void Engine::pushBallsAwayFromGravity() {
|
|
for (auto &ball : balls_) {
|
|
const int SIGNO = ((rand() % 2) * 2) - 1;
|
|
const float LATERAL = (((rand() % 20) + 10) * 0.1f) * SIGNO;
|
|
const float MAIN = ((rand() % 40) * 0.1f) + 5;
|
|
|
|
float vx = 0, vy = 0;
|
|
switch (current_gravity_) {
|
|
case GravityDirection::DOWN: // Impulsar ARRIBA
|
|
vx = LATERAL;
|
|
vy = -MAIN;
|
|
break;
|
|
case GravityDirection::UP: // Impulsar ABAJO
|
|
vx = LATERAL;
|
|
vy = MAIN;
|
|
break;
|
|
case GravityDirection::LEFT: // Impulsar DERECHA
|
|
vx = MAIN;
|
|
vy = LATERAL;
|
|
break;
|
|
case GravityDirection::RIGHT: // Impulsar IZQUIERDA
|
|
vx = -MAIN;
|
|
vy = LATERAL;
|
|
break;
|
|
}
|
|
ball->modVel(vx, vy); // Modifica la velocidad según dirección de gravedad
|
|
}
|
|
}
|
|
|
|
void Engine::switchBallsGravity() {
|
|
for (auto &ball : balls_) {
|
|
ball->switchGravity();
|
|
}
|
|
}
|
|
|
|
void Engine::changeGravityDirection(GravityDirection direction) {
|
|
current_gravity_ = direction;
|
|
for (auto &ball : balls_) {
|
|
ball->setGravityDirection(direction);
|
|
ball->applyRandomLateralPush(); // Aplicar empuje lateral aleatorio
|
|
}
|
|
}
|
|
|
|
void Engine::toggleVSync() {
|
|
vsync_enabled_ = !vsync_enabled_;
|
|
vsync_text_ = vsync_enabled_ ? "VSYNC ON" : "VSYNC OFF";
|
|
|
|
// Aplicar el cambio de V-Sync al backend activo
|
|
auto* renderer = window_manager_ ? window_manager_->getRenderer() : nullptr;
|
|
if (renderer) {
|
|
renderer->setVSync(vsync_enabled_);
|
|
}
|
|
}
|
|
|
|
void Engine::toggleFullscreen() {
|
|
if (window_manager_) {
|
|
// Si está en modo real fullscreen, primero salir de él
|
|
if (real_fullscreen_enabled_) {
|
|
toggleRealFullscreen(); // Esto lo desactiva
|
|
}
|
|
|
|
fullscreen_enabled_ = !fullscreen_enabled_;
|
|
window_manager_->setFullscreen(fullscreen_enabled_);
|
|
}
|
|
}
|
|
|
|
void Engine::toggleRealFullscreen() {
|
|
// Si está en modo fullscreen normal, primero desactivarlo
|
|
if (fullscreen_enabled_) {
|
|
fullscreen_enabled_ = false;
|
|
// SDL_SetWindowFullscreen(window_, false); // TODO: Migrar a WindowManager
|
|
}
|
|
|
|
real_fullscreen_enabled_ = !real_fullscreen_enabled_;
|
|
|
|
if (real_fullscreen_enabled_) {
|
|
// Obtener resolución del escritorio
|
|
int num_displays = 0;
|
|
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
|
|
if (displays != nullptr && num_displays > 0) {
|
|
const auto *dm = SDL_GetCurrentDisplayMode(displays[0]);
|
|
if (dm != nullptr) {
|
|
// Cambiar a resolución nativa del escritorio
|
|
current_screen_width_ = dm->w;
|
|
current_screen_height_ = dm->h;
|
|
|
|
// Recrear ventana con nueva resolución
|
|
// SDL_SetWindowSize(window_, current_screen_width_, current_screen_height_); // TODO: Migrar a WindowManager
|
|
// SDL_SetWindowFullscreen(window_, true); // TODO: Migrar a WindowManager
|
|
|
|
// Actualizar presentación lógica del renderizador
|
|
// SDL_SetRenderLogicalPresentation(renderer_, current_screen_width_, current_screen_height_, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE); // TODO: Migrar a WindowManager
|
|
|
|
// Reinicar la escena con nueva resolución
|
|
initBalls(scenario_);
|
|
}
|
|
SDL_free(displays);
|
|
}
|
|
} else {
|
|
// Volver a resolución original
|
|
current_screen_width_ = SCREEN_WIDTH;
|
|
current_screen_height_ = SCREEN_HEIGHT;
|
|
|
|
// Restaurar ventana normal
|
|
// SDL_SetWindowFullscreen(window_, false); // TODO: Migrar a WindowManager
|
|
// SDL_SetWindowSize(window_, SCREEN_WIDTH * WINDOW_ZOOM, SCREEN_HEIGHT * WINDOW_ZOOM); // TODO: Migrar a WindowManager
|
|
|
|
// Restaurar presentación lógica original
|
|
// SDL_SetRenderLogicalPresentation(renderer_, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE); // TODO: Migrar a WindowManager
|
|
|
|
// Reinicar la escena con resolución original
|
|
initBalls(scenario_);
|
|
}
|
|
}
|
|
|
|
std::string Engine::gravityDirectionToString(GravityDirection direction) const {
|
|
switch (direction) {
|
|
case GravityDirection::DOWN: return "DOWN";
|
|
case GravityDirection::UP: return "UP";
|
|
case GravityDirection::LEFT: return "LEFT";
|
|
case GravityDirection::RIGHT: return "RIGHT";
|
|
default: return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
void Engine::renderGradientBackground() {
|
|
ThemeColors &theme = themes_[static_cast<int>(current_theme_)];
|
|
|
|
auto* sdl_renderer = window_manager_ ? window_manager_->getSDLRenderer() : nullptr;
|
|
if (!sdl_renderer) {
|
|
return;
|
|
}
|
|
|
|
// Crear gradiente suave usando SDL_RenderGeometry como en la implementación original
|
|
SDL_Vertex bg_vertices[4];
|
|
|
|
// Vértice superior izquierdo
|
|
bg_vertices[0].position = {0, 0};
|
|
bg_vertices[0].tex_coord = {0.0f, 0.0f};
|
|
bg_vertices[0].color = {theme.bg_top_r, theme.bg_top_g, theme.bg_top_b, 1.0f};
|
|
|
|
// Vértice superior derecho
|
|
bg_vertices[1].position = {static_cast<float>(SCREEN_WIDTH), 0};
|
|
bg_vertices[1].tex_coord = {1.0f, 0.0f};
|
|
bg_vertices[1].color = {theme.bg_top_r, theme.bg_top_g, theme.bg_top_b, 1.0f};
|
|
|
|
// Vértice inferior derecho
|
|
bg_vertices[2].position = {static_cast<float>(SCREEN_WIDTH), static_cast<float>(SCREEN_HEIGHT)};
|
|
bg_vertices[2].tex_coord = {1.0f, 1.0f};
|
|
bg_vertices[2].color = {theme.bg_bottom_r, theme.bg_bottom_g, theme.bg_bottom_b, 1.0f};
|
|
|
|
// Vértice inferior izquierdo
|
|
bg_vertices[3].position = {0, static_cast<float>(SCREEN_HEIGHT)};
|
|
bg_vertices[3].tex_coord = {0.0f, 1.0f};
|
|
bg_vertices[3].color = {theme.bg_bottom_r, theme.bg_bottom_g, theme.bg_bottom_b, 1.0f};
|
|
|
|
// Índices para 2 triángulos
|
|
int bg_indices[6] = {0, 1, 2, 2, 3, 0};
|
|
|
|
// Renderizar gradiente sin textura (nullptr)
|
|
SDL_RenderGeometry(sdl_renderer, nullptr, bg_vertices, 4, bg_indices, 6);
|
|
}
|
|
|
|
// Método addSpriteToBatch antiguo eliminado - ahora se usa el del nuevo sistema
|
|
|
|
void Engine::initializeThemes() {
|
|
// SUNSET: Naranjas, rojos, amarillos, rosas (8 colores)
|
|
themes_[0] = {
|
|
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}}
|
|
};
|
|
|
|
// OCEAN: Azules, turquesas, blancos (8 colores)
|
|
themes_[1] = {
|
|
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}}
|
|
};
|
|
|
|
// NEON: Cian, magenta, verde lima, amarillo vibrante (8 colores)
|
|
themes_[2] = {
|
|
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}}
|
|
};
|
|
|
|
// FOREST: Verdes, marrones, amarillos otoño (8 colores)
|
|
themes_[3] = {
|
|
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}}
|
|
};
|
|
|
|
// RGB: Círculo cromático con 24 puntos (cada 15°) - Ultra precisión matemática
|
|
themes_[4] = {
|
|
1.0f, 1.0f, 1.0f, // Fondo superior (blanco puro)
|
|
1.0f, 1.0f, 1.0f, // Fondo inferior (blanco puro) - sin degradado
|
|
{
|
|
{255, 0, 0}, // 0° - Rojo puro
|
|
{255, 64, 0}, // 15° - Rojo-Naranja
|
|
{255, 128, 0}, // 30° - Naranja
|
|
{255, 191, 0}, // 45° - Naranja-Amarillo
|
|
{255, 255, 0}, // 60° - Amarillo puro
|
|
{191, 255, 0}, // 75° - Amarillo-Verde claro
|
|
{128, 255, 0}, // 90° - Verde-Amarillo
|
|
{64, 255, 0}, // 105° - Verde claro-Amarillo
|
|
{0, 255, 0}, // 120° - Verde puro
|
|
{0, 255, 64}, // 135° - Verde-Cian claro
|
|
{0, 255, 128}, // 150° - Verde-Cian
|
|
{0, 255, 191}, // 165° - Verde claro-Cian
|
|
{0, 255, 255}, // 180° - Cian puro
|
|
{0, 191, 255}, // 195° - Cian-Azul claro
|
|
{0, 128, 255}, // 210° - Azul-Cian
|
|
{0, 64, 255}, // 225° - Azul claro-Cian
|
|
{0, 0, 255}, // 240° - Azul puro
|
|
{64, 0, 255}, // 255° - Azul-Magenta claro
|
|
{128, 0, 255}, // 270° - Azul-Magenta
|
|
{191, 0, 255}, // 285° - Azul claro-Magenta
|
|
{255, 0, 255}, // 300° - Magenta puro
|
|
{255, 0, 191}, // 315° - Magenta-Rojo claro
|
|
{255, 0, 128}, // 330° - Magenta-Rojo
|
|
{255, 0, 64} // 345° - Magenta claro-Rojo
|
|
}
|
|
};
|
|
}
|
|
|
|
void Engine::checkAutoRestart() {
|
|
// Verificar si TODAS las pelotas están paradas
|
|
bool all_stopped = true;
|
|
for (const auto &ball : balls_) {
|
|
if (!ball->isStopped()) {
|
|
all_stopped = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (all_stopped) {
|
|
if (!all_balls_were_stopped_) {
|
|
// Primera vez que se detecta que todas están paradas
|
|
all_balls_stopped_start_time_ = SDL_GetTicks();
|
|
all_balls_were_stopped_ = true;
|
|
} else {
|
|
// Ya estaban paradas, verificar tiempo transcurrido
|
|
Uint64 current_time = SDL_GetTicks();
|
|
if (current_time - all_balls_stopped_start_time_ >= AUTO_RESTART_DELAY) {
|
|
performRandomRestart();
|
|
}
|
|
}
|
|
} else {
|
|
// Al menos una pelota se está moviendo - resetear temporizador
|
|
all_balls_were_stopped_ = false;
|
|
all_balls_stopped_start_time_ = 0;
|
|
}
|
|
}
|
|
|
|
void Engine::performRandomRestart() {
|
|
// Escenario aleatorio usando tamaño del array
|
|
scenario_ = rand() % test_.size();
|
|
|
|
// Tema aleatorio usando tamaño del array de temas
|
|
current_theme_ = static_cast<ColorTheme>(rand() % (sizeof(themes_) / sizeof(themes_[0])));
|
|
|
|
// Reinicializar pelotas con nuevo escenario y tema
|
|
initBalls(scenario_);
|
|
|
|
// Resetear temporizador
|
|
all_balls_were_stopped_ = false;
|
|
all_balls_stopped_start_time_ = 0;
|
|
}
|
|
|
|
// Métodos del nuevo sistema de renderizado
|
|
|
|
void Engine::zoomIn() {
|
|
if (window_manager_) {
|
|
window_manager_->zoomIn();
|
|
}
|
|
}
|
|
|
|
void Engine::zoomOut() {
|
|
if (window_manager_) {
|
|
window_manager_->zoomOut();
|
|
}
|
|
}
|
|
|
|
void Engine::toggleCRTEffects() {
|
|
crt_effects_enabled_ = !crt_effects_enabled_;
|
|
|
|
auto* renderer = window_manager_ ? window_manager_->getRenderer() : nullptr;
|
|
if (renderer) {
|
|
renderer->enableCRT(crt_effects_enabled_);
|
|
}
|
|
|
|
std::cout << "Efectos CRT: " << (crt_effects_enabled_ ? "ON" : "OFF") << std::endl;
|
|
}
|
|
|
|
void Engine::adjustScanlineIntensity(float delta) {
|
|
crt_params_.scanline_intensity = std::max(0.0f, std::min(1.0f, crt_params_.scanline_intensity + delta));
|
|
|
|
auto* renderer = window_manager_ ? window_manager_->getRenderer() : nullptr;
|
|
if (renderer) {
|
|
renderer->setCRTParams(crt_params_);
|
|
}
|
|
|
|
std::cout << "Intensidad scanlines: " << crt_params_.scanline_intensity << std::endl;
|
|
}
|
|
|
|
void Engine::adjustCurvature(float delta) {
|
|
crt_params_.curvature_x = std::max(0.0f, std::min(0.5f, crt_params_.curvature_x + delta));
|
|
crt_params_.curvature_y = crt_params_.curvature_x; // Mantener proporción
|
|
|
|
auto* renderer = window_manager_ ? window_manager_->getRenderer() : nullptr;
|
|
if (renderer) {
|
|
renderer->setCRTParams(crt_params_);
|
|
}
|
|
|
|
std::cout << "Curvatura CRT: " << crt_params_.curvature_x << std::endl;
|
|
}
|
|
|
|
void Engine::adjustBloom(float delta) {
|
|
crt_params_.bloom_factor = std::max(1.0f, std::min(3.0f, crt_params_.bloom_factor + delta));
|
|
|
|
auto* renderer = window_manager_ ? window_manager_->getRenderer() : nullptr;
|
|
if (renderer) {
|
|
renderer->setCRTParams(crt_params_);
|
|
}
|
|
|
|
std::cout << "Factor bloom: " << crt_params_.bloom_factor << std::endl;
|
|
}
|
|
|
|
void Engine::switchRenderingBackend() {
|
|
// En una implementación completa, esto cambiaría entre backends disponibles
|
|
std::cout << "Cambio de backend no implementado aún" << std::endl;
|
|
}
|
|
|
|
std::string Engine::getBackendInfo() const {
|
|
if (window_manager_ && window_manager_->getRenderer()) {
|
|
return std::string(window_manager_->getBackendName());
|
|
}
|
|
return "None";
|
|
}
|
|
|
|
void Engine::clearSpriteBatch() {
|
|
sprite_batch_.clear();
|
|
}
|
|
|
|
void Engine::renderSpriteBatch() {
|
|
auto* renderer = window_manager_ ? window_manager_->getRenderer() : nullptr;
|
|
if (renderer && !sprite_batch_.empty() && texture_) {
|
|
void* texture_data = static_cast<void*>(texture_->getSDLTexture());
|
|
renderer->renderSpriteBatch(sprite_batch_, texture_data);
|
|
}
|
|
}
|
|
|
|
void Engine::addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b) {
|
|
int vertex_index = static_cast<int>(batch_vertices_.size());
|
|
|
|
// Crear 4 vértices para el quad (2 triángulos)
|
|
SDL_Vertex vertices[4];
|
|
|
|
// Convertir colores de int (0-255) a float (0.0-1.0)
|
|
float rf = r / 255.0f;
|
|
float gf = g / 255.0f;
|
|
float bf = b / 255.0f;
|
|
|
|
// Vértice superior izquierdo
|
|
vertices[0].position = {x, y};
|
|
vertices[0].tex_coord = {0.0f, 0.0f};
|
|
vertices[0].color = {rf, gf, bf, 1.0f};
|
|
|
|
// Vértice superior derecho
|
|
vertices[1].position = {x + w, y};
|
|
vertices[1].tex_coord = {1.0f, 0.0f};
|
|
vertices[1].color = {rf, gf, bf, 1.0f};
|
|
|
|
// Vértice inferior derecho
|
|
vertices[2].position = {x + w, y + h};
|
|
vertices[2].tex_coord = {1.0f, 1.0f};
|
|
vertices[2].color = {rf, gf, bf, 1.0f};
|
|
|
|
// Vértice inferior izquierdo
|
|
vertices[3].position = {x, y + h};
|
|
vertices[3].tex_coord = {0.0f, 1.0f};
|
|
vertices[3].color = {rf, gf, bf, 1.0f};
|
|
|
|
// Añadir vértices al batch
|
|
for (int i = 0; i < 4; i++) {
|
|
batch_vertices_.push_back(vertices[i]);
|
|
}
|
|
|
|
// Añadir índices para 2 triángulos
|
|
batch_indices_.push_back(vertex_index + 0);
|
|
batch_indices_.push_back(vertex_index + 1);
|
|
batch_indices_.push_back(vertex_index + 2);
|
|
batch_indices_.push_back(vertex_index + 2);
|
|
batch_indices_.push_back(vertex_index + 3);
|
|
batch_indices_.push_back(vertex_index + 0);
|
|
} |