6 Commits

Author SHA1 Message Date
d2f170d313 Fix: HUD debug usa coordenadas físicas absolutas (como notificaciones)
Migra el sistema de renderizado de HUD debug desde printPhysical()
(coordenadas lógicas escaladas) a printAbsolute() (píxeles físicos absolutos).

## Cambios

**engine.cpp (líneas 830-951):**
- Eliminadas líneas de cálculo de factores de escala (text_scale_x/y)
- Todas las coordenadas ahora en píxeles físicos absolutos
- FPS: `physical_window_width_ - text_width - margin` (esquina derecha física)
- 10 llamadas printPhysical() → printAbsolute() con SDL_Color
- 4 llamadas getTextWidth() → getTextWidthPhysical()

## Resultado

 HUD de tamaño fijo independiente de resolución lógica
 FPS siempre pegado a esquina derecha física
 Espaciado constante entre líneas
 Funciona en modo ventana y F4 (stretch fullscreen)
⚠️  PENDIENTE: Ajustar offset para modo F3 con letterbox

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-10 09:39:17 +02:00
aa57ac7012 Fix: Sistema de zoom y fullscreen con parámetros CLI
Corrige bugs críticos en el manejo de ventanas cuando se inician
con parámetros de línea de comandos (-w, -h, -z).

## Problemas Resueltos

**1. Zoom incorrecto con parámetros CLI**
- El zoom calculado no se guardaba en current_window_zoom_
- F1/F2 usaban valor default (3) en lugar del zoom actual
- Resultado: Posicionamiento erróneo de ventana al hacer zoom

**2. Ventana no centrada al iniciar**
- Faltaba SDL_SetWindowPosition() después de crear ventana
- Ventana aparecía en posición aleatoria

**3. F4 restauraba tamaño incorrecto**
- toggleRealFullscreen() usaba DEFAULT_WINDOW_ZOOM hardcoded
- Al salir de fullscreen real, ventana cambiaba de tamaño
- No re-centraba ventana después de restaurar

## Cambios Implementados

**engine.cpp:initialize() línea 86-87:**
- Guardar zoom calculado en current_window_zoom_ antes de crear ventana
- Asegura consistencia entre zoom real y zoom guardado

**engine.cpp:initialize() línea 114-117:**
- Centrar ventana con SDL_WINDOWPOS_CENTERED al iniciar
- Solo si no está en modo fullscreen

**engine.cpp:toggleRealFullscreen() línea 1174-1175:**
- Usar current_window_zoom_ en lugar de DEFAULT_WINDOW_ZOOM
- Re-centrar ventana con SDL_WINDOWPOS_CENTERED al salir de F4

## Casos de Prueba Verificados

 Sin parámetros: vibe3_physics.exe
 Con resolución: vibe3_physics.exe -w 640 -h 480
 Con zoom: vibe3_physics.exe -z 2
 Combinado: vibe3_physics.exe -w 1920 -h 1080 -z 1

## Teclas Afectadas

- F1 (Zoom Out):  Funciona correctamente
- F2 (Zoom In):  Funciona correctamente
- F3 (Fullscreen Toggle):  Funciona correctamente
- F4 (Real Fullscreen):  Ahora restaura tamaño correcto

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-10 09:20:24 +02:00
0d069da29d Refactor: Traduce todas las notificaciones a castellano
Unifica el idioma de todas las notificaciones del sistema a castellano
para mantener consistencia en la interfaz de usuario.

## Traducciones Realizadas

### Gravedad
- "Gravity Off/On" → "Gravedad Off/On"
- "Gravity Up" → "Gravedad Arriba"
- "Gravity Down" → "Gravedad Abajo"
- "Gravity Left" → "Gravedad Izquierda"
- "Gravity Right" → "Gravedad Derecha"

### Modos
- "Physics Mode" → "Modo Física"

### Figuras 3D (array shape_names[] + notificaciones)
- "None" → "Ninguna"
- "Sphere" → "Esfera"
- "Cube" → "Cubo"
- "Helix" → "Hélice"
- "Torus" → "Toroide"
- "Lissajous" → "Lissajous" (mantiene nombre técnico)
- "Cylinder" → "Cilindro"
- "Icosahedron" → "Icosaedro"
- "Atom" → "Átomo"
- "PNG Shape" → "Forma PNG"

### Profundidad
- "Depth Zoom On/Off" → "Profundidad On/Off"

## Mantienen Inglés

- **Sprite**: Término técnico común en desarrollo
- **Nombres de temas**: Usan getCurrentThemeNameES() (ya en español)
- **Modos de aplicación**: Ya estaban en español
- **Número de pelotas**: Ya estaban en español
- **Escala**: Ya estaba en español
- **Páginas**: Ya estaban en español

## Resultado

 Interfaz de usuario 100% en castellano
 Consistencia en todas las notificaciones
 Mantiene términos técnicos apropiados (Lissajous, Sprite)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-10 09:02:11 +02:00
eb3dd03579 Fix: LOGO sale incorrectamente a DEMO al pulsar F manualmente
Corrige bug donde pulsar F en modo LOGO (llegando desde DEMO) causaba
salida automática a DEMO debido a uso incorrecto de previous_app_mode_
como flag de "¿puede salir automáticamente?".

## Problema

**Flujo con bug:**
1. SANDBOX → D → DEMO
2. DEMO → K → LOGO (guarda previous_app_mode_ = DEMO)
3. LOGO (SHAPE) → F → LOGO (PHYSICS) ← Acción MANUAL
4. updateDemoMode() ejecuta lógica de LOGO
5. Línea 1628: `if (previous_app_mode_ != SANDBOX && rand() < 60%)`
6. Como previous_app_mode_ == DEMO → Sale a DEMO 

**Causa raíz:**
La variable previous_app_mode_ se usaba para dos propósitos:
- Guardar a dónde volver (correcto)
- Decidir si puede salir automáticamente (incorrecto)

Esto causaba que acciones manuales del usuario (como F) activaran
la probabilidad de salida automática.

## Solución Implementada

**Nueva variable explícita:**
```cpp
bool logo_entered_manually_;  // true si tecla K, false si desde DEMO
```

**Asignación en enterLogoMode():**
```cpp
logo_entered_manually_ = !from_demo;
```

**Condición corregida en updateDemoMode():**
```cpp
// ANTES (incorrecto):
if (previous_app_mode_ != AppMode::SANDBOX && rand() % 100 < 60)

// AHORA (correcto):
if (!logo_entered_manually_ && rand() % 100 < 60)
```

## Ventajas

 **Separación de responsabilidades:**
- previous_app_mode_: Solo para saber a dónde volver
- logo_entered_manually_: Solo para control de salida automática

 **Semántica clara:**
- Código más legible y expresivo

 **Más robusto:**
- No depende de comparaciones indirectas

## Flujos Verificados

**Flujo 1 (Manual desde SANDBOX):**
- SANDBOX → K → LOGO (logo_entered_manually_ = true)
- LOGO → F → PHYSICS
- No sale automáticamente 

**Flujo 2 (Manual desde DEMO):**
- SANDBOX → D → DEMO → K → LOGO (logo_entered_manually_ = true)
- LOGO → F → PHYSICS
- No sale automáticamente 

**Flujo 3 (Automático desde DEMO):**
- SANDBOX → D → DEMO → auto → LOGO (logo_entered_manually_ = false)
- LOGO ejecuta acciones automáticas
- Sale a DEMO con 60% probabilidad 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-10 08:58:20 +02:00
a1e2c03efd Fix: Notificación tecla F muestra nombre correcto de figura
Corrige desajuste entre el orden del enum ShapeType y el array
de nombres shape_names[] en el handler de tecla F (toggle).

## Problema

Al pulsar F para toggle PHYSICS ↔ SHAPE, la notificación mostraba
nombre incorrecto de la figura debido a que el array shape_names[]
NO coincidía con el orden del enum ShapeType.

**Enum ShapeType (defines.h):**
0=NONE, 1=SPHERE, 2=CUBE, 3=HELIX, 4=TORUS, 5=LISSAJOUS,
6=CYLINDER, 7=ICOSAHEDRON, 8=ATOM, 9=PNG_SHAPE

**Array previo (incorrecto):**
{"Sphere", "Lissajous", "Helix", "Torus", "Cube", ...}

Orden erróneo causaba que al activar CUBE (enum=2) mostrara
"Helix" (array[2]), etc.

## Solución

Reordenar array para coincidir exactamente con enum ShapeType:

```cpp
const char* shape_names[] = {
    "None",        // 0 = NONE
    "Sphere",      // 1 = SPHERE
    "Cube",        // 2 = CUBE
    "Helix",       // 3 = HELIX
    "Torus",       // 4 = TORUS
    "Lissajous",   // 5 = LISSAJOUS
    "Cylinder",    // 6 = CYLINDER
    "Icosahedron", // 7 = ICOSAHEDRON
    "Atom",        // 8 = ATOM
    "PNG Shape"    // 9 = PNG_SHAPE
};
```

## Resultado

 Tecla F muestra nombre correcto al activar cada figura
 Comentario documentando correspondencia con enum
 "None" añadido en índice 0 (nunca usado, pero completa array)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-10 08:45:18 +02:00
684ac9823b Add: Reglas DEMO/LOGO - PNG_SHAPE exclusivo y temas aleatorios
Implementa restricciones para modos DEMO y LOGO garantizando que
PNG_SHAPE sea exclusivo del modo LOGO y nunca aparezca en DEMO/DEMO_LITE.

## Cambios en Modo LOGO (enterLogoMode)

**Textura:**
- Cambiado de "tiny" a "small" como textura obligatoria

**Tema aleatorio:**
- Antes: Siempre MONOCHROME (tema 5)
- Ahora: Selección aleatoria entre 4 temas:
  - MONOCHROME (5)
  - LAVENDER (6)
  - CRIMSON (7)
  - ESMERALDA (8)

**Comportamiento:**
- No cambia de tema automáticamente durante ejecución
- Mantiene tema seleccionado hasta salir del modo

## Cambios en Transición LOGO → DEMO

**exitLogoMode (automático):**
- Al volver automáticamente a DEMO desde LOGO
- Si figura activa es PNG_SHAPE → cambia a figura aleatoria válida
- Excluye PNG_SHAPE de selección (8 figuras disponibles)

**randomizeOnDemoStart (manual):**
- Al entrar manualmente a DEMO/DEMO_LITE con tecla D/L
- Check inicial: si current_shape_type_ == PNG_SHAPE
- Fuerza cambio a figura aleatoria antes de randomización
- Soluciona bug: D → DEMO → K → LOGO → D dejaba PNG_SHAPE activa

## Garantías Implementadas

 PNG_SHAPE nunca aparece en acciones aleatorias de DEMO/DEMO_LITE
 PNG_SHAPE se cambia automáticamente al salir de LOGO (manual o auto)
 Modo LOGO elige tema aleatorio al entrar (4 opciones monocromáticas)
 Modo LOGO usa textura SMALL en lugar de TINY

## Flujos Verificados

- Manual: DEMO → LOGO → DEMO (tecla D) 
- Manual: DEMO_LITE → LOGO → DEMO_LITE (tecla L) 
- Automático: DEMO → LOGO → DEMO (5% probabilidad) 
- Dentro DEMO: PNG_SHAPE nunca seleccionada 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-10 08:41:13 +02:00
2 changed files with 92 additions and 57 deletions

View File

@@ -83,6 +83,9 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
window_zoom = 1; window_zoom = 1;
} }
// Guardar zoom calculado ANTES de crear la ventana (para F1/F2/F3/F4)
current_window_zoom_ = window_zoom;
// Calcular tamaño de ventana // Calcular tamaño de ventana
int window_width = logical_width * window_zoom; int window_width = logical_width * window_zoom;
int window_height = logical_height * window_zoom; int window_height = logical_height * window_zoom;
@@ -108,6 +111,11 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
std::cout << "¡No se pudo crear la ventana! Error de SDL: " << SDL_GetError() << std::endl; std::cout << "¡No se pudo crear la ventana! Error de SDL: " << SDL_GetError() << std::endl;
success = false; success = false;
} else { } else {
// Centrar ventana en pantalla si no está en fullscreen
if (!fullscreen) {
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
// Crear renderizador // Crear renderizador
renderer_ = SDL_CreateRenderer(window_, nullptr); renderer_ = SDL_CreateRenderer(window_, nullptr);
if (renderer_ == nullptr) { if (renderer_ == nullptr) {
@@ -330,12 +338,12 @@ void Engine::handleEvents() {
// Si estamos en modo figura, salir a modo física SIN GRAVEDAD // Si estamos en modo figura, salir a modo física SIN GRAVEDAD
if (current_mode_ == SimulationMode::SHAPE) { if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeMode(false); // Desactivar figura sin forzar gravedad ON toggleShapeMode(false); // Desactivar figura sin forzar gravedad ON
showNotificationForAction("Gravity Off"); showNotificationForAction("Gravedad Off");
} else { } else {
switchBallsGravity(); // Toggle normal en modo física switchBallsGravity(); // Toggle normal en modo física
// Determinar estado actual de gravedad (gravity_force_ != 0.0f significa ON) // Determinar estado actual de gravedad (gravity_force_ != 0.0f significa ON)
bool gravity_on = balls_.empty() ? true : (balls_[0]->getGravityForce() != 0.0f); bool gravity_on = balls_.empty() ? true : (balls_[0]->getGravityForce() != 0.0f);
showNotificationForAction(gravity_on ? "Gravity On" : "Gravity Off"); showNotificationForAction(gravity_on ? "Gravedad On" : "Gravedad Off");
} }
break; break;
@@ -348,7 +356,7 @@ void Engine::handleEvents() {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
} }
changeGravityDirection(GravityDirection::UP); changeGravityDirection(GravityDirection::UP);
showNotificationForAction("Gravity Up"); showNotificationForAction("Gravedad Arriba");
break; break;
case SDLK_DOWN: case SDLK_DOWN:
@@ -359,7 +367,7 @@ void Engine::handleEvents() {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
} }
changeGravityDirection(GravityDirection::DOWN); changeGravityDirection(GravityDirection::DOWN);
showNotificationForAction("Gravity Down"); showNotificationForAction("Gravedad Abajo");
break; break;
case SDLK_LEFT: case SDLK_LEFT:
@@ -370,7 +378,7 @@ void Engine::handleEvents() {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
} }
changeGravityDirection(GravityDirection::LEFT); changeGravityDirection(GravityDirection::LEFT);
showNotificationForAction("Gravity Left"); showNotificationForAction("Gravedad Izquierda");
break; break;
case SDLK_RIGHT: case SDLK_RIGHT:
@@ -381,7 +389,7 @@ void Engine::handleEvents() {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
} }
changeGravityDirection(GravityDirection::RIGHT); changeGravityDirection(GravityDirection::RIGHT);
showNotificationForAction("Gravity Right"); showNotificationForAction("Gravedad Derecha");
break; break;
case SDLK_V: case SDLK_V:
@@ -397,10 +405,11 @@ void Engine::handleEvents() {
toggleShapeMode(); toggleShapeMode();
// Mostrar notificación según el modo actual después del toggle // Mostrar notificación según el modo actual después del toggle
if (current_mode_ == SimulationMode::PHYSICS) { if (current_mode_ == SimulationMode::PHYSICS) {
showNotificationForAction("Physics Mode"); showNotificationForAction("Modo Física");
} else { } else {
// Mostrar nombre de la figura actual // Mostrar nombre de la figura actual (orden debe coincidir con enum ShapeType)
const char* shape_names[] = {"Sphere", "Lissajous", "Helix", "Torus", "Cube", "Cylinder", "Icosahedron", "Atom", "PNG Shape"}; // Índices: 0=NONE, 1=SPHERE, 2=CUBE, 3=HELIX, 4=TORUS, 5=LISSAJOUS, 6=CYLINDER, 7=ICOSAHEDRON, 8=ATOM, 9=PNG_SHAPE
const char* shape_names[] = {"Ninguna", "Esfera", "Cubo", "Hélice", "Toroide", "Lissajous", "Cilindro", "Icosaedro", "Átomo", "Forma PNG"};
showNotificationForAction(shape_names[static_cast<int>(current_shape_type_)]); showNotificationForAction(shape_names[static_cast<int>(current_shape_type_)]);
} }
break; break;
@@ -408,7 +417,7 @@ void Engine::handleEvents() {
// Selección directa de figuras 3D // Selección directa de figuras 3D
case SDLK_Q: case SDLK_Q:
activateShape(ShapeType::SPHERE); activateShape(ShapeType::SPHERE);
showNotificationForAction("Sphere"); showNotificationForAction("Esfera");
break; break;
case SDLK_W: case SDLK_W:
@@ -418,37 +427,37 @@ void Engine::handleEvents() {
case SDLK_E: case SDLK_E:
activateShape(ShapeType::HELIX); activateShape(ShapeType::HELIX);
showNotificationForAction("Helix"); showNotificationForAction("Hélice");
break; break;
case SDLK_R: case SDLK_R:
activateShape(ShapeType::TORUS); activateShape(ShapeType::TORUS);
showNotificationForAction("Torus"); showNotificationForAction("Toroide");
break; break;
case SDLK_T: case SDLK_T:
activateShape(ShapeType::CUBE); activateShape(ShapeType::CUBE);
showNotificationForAction("Cube"); showNotificationForAction("Cubo");
break; break;
case SDLK_Y: case SDLK_Y:
activateShape(ShapeType::CYLINDER); activateShape(ShapeType::CYLINDER);
showNotificationForAction("Cylinder"); showNotificationForAction("Cilindro");
break; break;
case SDLK_U: case SDLK_U:
activateShape(ShapeType::ICOSAHEDRON); activateShape(ShapeType::ICOSAHEDRON);
showNotificationForAction("Icosahedron"); showNotificationForAction("Icosaedro");
break; break;
case SDLK_I: case SDLK_I:
activateShape(ShapeType::ATOM); activateShape(ShapeType::ATOM);
showNotificationForAction("Atom"); showNotificationForAction("Átomo");
break; break;
case SDLK_O: case SDLK_O:
activateShape(ShapeType::PNG_SHAPE); activateShape(ShapeType::PNG_SHAPE);
showNotificationForAction("PNG Shape"); showNotificationForAction("Forma PNG");
break; break;
// Ciclar temas de color (movido de T a B) // Ciclar temas de color (movido de T a B)
@@ -594,7 +603,7 @@ void Engine::handleEvents() {
case SDLK_KP_DIVIDE: case SDLK_KP_DIVIDE:
if (current_mode_ == SimulationMode::SHAPE) { if (current_mode_ == SimulationMode::SHAPE) {
depth_zoom_enabled_ = !depth_zoom_enabled_; depth_zoom_enabled_ = !depth_zoom_enabled_;
showNotificationForAction(depth_zoom_enabled_ ? "Depth Zoom On" : "Depth Zoom Off"); showNotificationForAction(depth_zoom_enabled_ ? "Profundidad On" : "Profundidad Off");
} }
break; break;
@@ -818,10 +827,6 @@ void Engine::render() {
SDL_RenderGeometry(renderer_, texture_->getSDLTexture(), batch_vertices_.data(), static_cast<int>(batch_vertices_.size()), batch_indices_.data(), static_cast<int>(batch_indices_.size())); SDL_RenderGeometry(renderer_, texture_->getSDLTexture(), batch_vertices_.data(), static_cast<int>(batch_vertices_.size()), batch_indices_.data(), static_cast<int>(batch_indices_.size()));
} }
// Calcular factores de escala lógica → física para texto absoluto
float text_scale_x = static_cast<float>(physical_window_width_) / static_cast<float>(current_screen_width_);
float text_scale_y = static_cast<float>(physical_window_height_) / static_cast<float>(current_screen_height_);
// SISTEMA DE TEXTO ANTIGUO DESHABILITADO // SISTEMA DE TEXTO ANTIGUO DESHABILITADO
// Reemplazado completamente por el sistema de notificaciones (Notifier) // Reemplazado completamente por el sistema de notificaciones (Notifier)
// El doble renderizado causaba que aparecieran textos duplicados detrás de las notificaciones // El doble renderizado causaba que aparecieran textos duplicados detrás de las notificaciones
@@ -856,16 +861,16 @@ void Engine::render() {
if (show_debug_) { if (show_debug_) {
// Obtener altura de línea para espaciado dinámico (usando fuente debug) // Obtener altura de línea para espaciado dinámico (usando fuente debug)
int line_height = text_renderer_debug_.getTextHeight(); int line_height = text_renderer_debug_.getTextHeight();
int margin = 8; // Margen constante int margin = 8; // Margen constante en píxeles físicos
int current_y = margin; // Y inicial int current_y = margin; // Y inicial en píxeles físicos
// Mostrar contador de FPS en esquina superior derecha // Mostrar contador de FPS en esquina superior derecha
int fps_text_width = text_renderer_debug_.getTextWidth(fps_text_.c_str()); int fps_text_width = text_renderer_debug_.getTextWidthPhysical(fps_text_.c_str());
int fps_x = current_screen_width_ - fps_text_width - margin; int fps_x = physical_window_width_ - fps_text_width - margin;
text_renderer_debug_.printPhysical(fps_x, current_y, fps_text_.c_str(), 255, 255, 0, text_scale_x, text_scale_y); // Amarillo text_renderer_debug_.printAbsolute(fps_x, current_y, fps_text_.c_str(), {255, 255, 0, 255}); // Amarillo
// Mostrar estado V-Sync en esquina superior izquierda // Mostrar estado V-Sync en esquina superior izquierda
text_renderer_debug_.printPhysical(margin, current_y, vsync_text_.c_str(), 0, 255, 255, text_scale_x, text_scale_y); // Cian text_renderer_debug_.printAbsolute(margin, current_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian
current_y += line_height; current_y += line_height;
// Debug: Mostrar valores de la primera pelota (si existe) // Debug: Mostrar valores de la primera pelota (si existe)
@@ -873,35 +878,35 @@ void Engine::render() {
// Línea 1: Gravedad // Línea 1: Gravedad
int grav_int = static_cast<int>(balls_[0]->getGravityForce()); int grav_int = static_cast<int>(balls_[0]->getGravityForce());
std::string grav_text = "Gravedad: " + std::to_string(grav_int); std::string grav_text = "Gravedad: " + std::to_string(grav_int);
text_renderer_debug_.printPhysical(margin, current_y, grav_text.c_str(), 255, 0, 255, text_scale_x, text_scale_y); // Magenta text_renderer_debug_.printAbsolute(margin, current_y, grav_text.c_str(), {255, 0, 255, 255}); // Magenta
current_y += line_height; current_y += line_height;
// Línea 2: Velocidad Y // Línea 2: Velocidad Y
int vy_int = static_cast<int>(balls_[0]->getVelocityY()); int vy_int = static_cast<int>(balls_[0]->getVelocityY());
std::string vy_text = "Velocidad Y: " + std::to_string(vy_int); std::string vy_text = "Velocidad Y: " + std::to_string(vy_int);
text_renderer_debug_.printPhysical(margin, current_y, vy_text.c_str(), 255, 0, 255, text_scale_x, text_scale_y); // Magenta text_renderer_debug_.printAbsolute(margin, current_y, vy_text.c_str(), {255, 0, 255, 255}); // Magenta
current_y += line_height; current_y += line_height;
// Línea 3: Estado superficie // Línea 3: Estado superficie
std::string surface_text = balls_[0]->isOnSurface() ? "Superficie: Sí" : "Superficie: No"; std::string surface_text = balls_[0]->isOnSurface() ? "Superficie: Sí" : "Superficie: No";
text_renderer_debug_.printPhysical(margin, current_y, surface_text.c_str(), 255, 0, 255, text_scale_x, text_scale_y); // Magenta text_renderer_debug_.printAbsolute(margin, current_y, surface_text.c_str(), {255, 0, 255, 255}); // Magenta
current_y += line_height; current_y += line_height;
// Línea 4: Coeficiente de rebote (loss) // Línea 4: Coeficiente de rebote (loss)
float loss_val = balls_[0]->getLossCoefficient(); float loss_val = balls_[0]->getLossCoefficient();
std::string loss_text = "Rebote: " + std::to_string(loss_val).substr(0, 4); std::string loss_text = "Rebote: " + std::to_string(loss_val).substr(0, 4);
text_renderer_debug_.printPhysical(margin, current_y, loss_text.c_str(), 255, 0, 255, text_scale_x, text_scale_y); // Magenta text_renderer_debug_.printAbsolute(margin, current_y, loss_text.c_str(), {255, 0, 255, 255}); // Magenta
current_y += line_height; current_y += line_height;
// Línea 5: Dirección de gravedad // Línea 5: Dirección de gravedad
std::string gravity_dir_text = "Dirección: " + gravityDirectionToString(current_gravity_); std::string gravity_dir_text = "Dirección: " + gravityDirectionToString(current_gravity_);
text_renderer_debug_.printPhysical(margin, current_y, gravity_dir_text.c_str(), 255, 255, 0, text_scale_x, text_scale_y); // Amarillo text_renderer_debug_.printAbsolute(margin, current_y, gravity_dir_text.c_str(), {255, 255, 0, 255}); // Amarillo
current_y += line_height; current_y += line_height;
} }
// Debug: Mostrar tema actual (delegado a ThemeManager) // Debug: Mostrar tema actual (delegado a ThemeManager)
std::string theme_text = std::string("Tema: ") + theme_manager_->getCurrentThemeNameEN(); std::string theme_text = std::string("Tema: ") + theme_manager_->getCurrentThemeNameEN();
text_renderer_debug_.printPhysical(margin, current_y, theme_text.c_str(), 255, 255, 128, text_scale_x, text_scale_y); // Amarillo claro text_renderer_debug_.printAbsolute(margin, current_y, theme_text.c_str(), {255, 255, 128, 255}); // Amarillo claro
current_y += line_height; current_y += line_height;
// Debug: Mostrar modo de simulación actual // Debug: Mostrar modo de simulación actual
@@ -913,14 +918,14 @@ void Engine::render() {
} else { } else {
mode_text = "Modo: Forma"; mode_text = "Modo: Forma";
} }
text_renderer_debug_.printPhysical(margin, current_y, mode_text.c_str(), 0, 255, 128, text_scale_x, text_scale_y); // Verde claro text_renderer_debug_.printAbsolute(margin, current_y, mode_text.c_str(), {0, 255, 128, 255}); // Verde claro
current_y += line_height; current_y += line_height;
// Debug: Mostrar convergencia en modo LOGO (solo cuando está activo) // Debug: Mostrar convergencia en modo LOGO (solo cuando está activo)
if (current_app_mode_ == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) { if (current_app_mode_ == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) {
int convergence_percent = static_cast<int>(shape_convergence_ * 100.0f); int convergence_percent = static_cast<int>(shape_convergence_ * 100.0f);
std::string convergence_text = "Convergencia: " + std::to_string(convergence_percent) + "%"; std::string convergence_text = "Convergencia: " + std::to_string(convergence_percent) + "%";
text_renderer_debug_.printPhysical(margin, current_y, convergence_text.c_str(), 255, 128, 0, text_scale_x, text_scale_y); // Naranja text_renderer_debug_.printAbsolute(margin, current_y, convergence_text.c_str(), {255, 128, 0, 255}); // Naranja
current_y += line_height; current_y += line_height;
} }
@@ -929,19 +934,19 @@ void Engine::render() {
int fixed_y = margin + (line_height * 2); // Tercera fila fija int fixed_y = margin + (line_height * 2); // Tercera fila fija
if (current_app_mode_ == AppMode::LOGO) { if (current_app_mode_ == AppMode::LOGO) {
const char* logo_text = "Modo Logo"; const char* logo_text = "Modo Logo";
int logo_text_width = text_renderer_debug_.getTextWidth(logo_text); int logo_text_width = text_renderer_debug_.getTextWidthPhysical(logo_text);
int logo_x = (current_screen_width_ - logo_text_width) / 2; int logo_x = (physical_window_width_ - logo_text_width) / 2;
text_renderer_debug_.printPhysical(logo_x, fixed_y, logo_text, 255, 128, 0, text_scale_x, text_scale_y); // Naranja text_renderer_debug_.printAbsolute(logo_x, fixed_y, logo_text, {255, 128, 0, 255}); // Naranja
} else if (current_app_mode_ == AppMode::DEMO) { } else if (current_app_mode_ == AppMode::DEMO) {
const char* demo_text = "Modo Demo"; const char* demo_text = "Modo Demo";
int demo_text_width = text_renderer_debug_.getTextWidth(demo_text); int demo_text_width = text_renderer_debug_.getTextWidthPhysical(demo_text);
int demo_x = (current_screen_width_ - demo_text_width) / 2; int demo_x = (physical_window_width_ - demo_text_width) / 2;
text_renderer_debug_.printPhysical(demo_x, fixed_y, demo_text, 255, 165, 0, text_scale_x, text_scale_y); // Naranja text_renderer_debug_.printAbsolute(demo_x, fixed_y, demo_text, {255, 165, 0, 255}); // Naranja
} else if (current_app_mode_ == AppMode::DEMO_LITE) { } else if (current_app_mode_ == AppMode::DEMO_LITE) {
const char* lite_text = "Modo Demo Lite"; const char* lite_text = "Modo Demo Lite";
int lite_text_width = text_renderer_debug_.getTextWidth(lite_text); int lite_text_width = text_renderer_debug_.getTextWidthPhysical(lite_text);
int lite_x = (current_screen_width_ - lite_text_width) / 2; int lite_x = (physical_window_width_ - lite_text_width) / 2;
text_renderer_debug_.printPhysical(lite_x, fixed_y, lite_text, 255, 200, 0, text_scale_x, text_scale_y); // Amarillo-naranja text_renderer_debug_.printAbsolute(lite_x, fixed_y, lite_text, {255, 200, 0, 255}); // Amarillo-naranja
} }
} }
@@ -1160,9 +1165,10 @@ void Engine::toggleRealFullscreen() {
current_screen_width_ = base_screen_width_; current_screen_width_ = base_screen_width_;
current_screen_height_ = base_screen_height_; current_screen_height_ = base_screen_height_;
// Restaurar ventana normal // Restaurar ventana normal con el zoom actual (no hardcoded)
SDL_SetWindowFullscreen(window_, false); SDL_SetWindowFullscreen(window_, false);
SDL_SetWindowSize(window_, base_screen_width_ * DEFAULT_WINDOW_ZOOM, base_screen_height_ * DEFAULT_WINDOW_ZOOM); SDL_SetWindowSize(window_, base_screen_width_ * current_window_zoom_, base_screen_height_ * current_window_zoom_);
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
// Restaurar presentación lógica base // Restaurar presentación lógica base
SDL_SetRenderLogicalPresentation(renderer_, base_screen_width_, base_screen_height_, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE); SDL_SetRenderLogicalPresentation(renderer_, base_screen_width_, base_screen_height_, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
@@ -1622,9 +1628,10 @@ void Engine::updateDemoMode() {
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range; demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
} }
// Solo salir automáticamente si NO llegamos desde MANUAL // Solo salir automáticamente si la entrada a LOGO fue automática (desde DEMO)
// No salir si el usuario entró manualmente con tecla K
// Probabilidad de salir: 60% en cada acción → sale rápido (relación DEMO:LOGO = 6:1) // Probabilidad de salir: 60% en cada acción → sale rápido (relación DEMO:LOGO = 6:1)
if (previous_app_mode_ != AppMode::SANDBOX && rand() % 100 < 60) { if (!logo_entered_manually_ && rand() % 100 < 60) {
exitLogoMode(true); // Volver a DEMO/DEMO_LITE exitLogoMode(true); // Volver a DEMO/DEMO_LITE
} }
} }
@@ -1831,6 +1838,15 @@ void Engine::performDemoAction(bool is_lite) {
// Randomizar todo al iniciar modo DEMO // Randomizar todo al iniciar modo DEMO
void Engine::randomizeOnDemoStart(bool is_lite) { void Engine::randomizeOnDemoStart(bool is_lite) {
// Si venimos de LOGO con PNG_SHAPE, cambiar figura obligatoriamente
// PNG_SHAPE es exclusivo del modo LOGO y no debe aparecer en DEMO/DEMO_LITE
if (current_shape_type_ == ShapeType::PNG_SHAPE) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
activateShape(shapes[rand() % 8]);
}
if (is_lite) { if (is_lite) {
// DEMO LITE: Solo randomizar física/figura + gravedad // DEMO LITE: Solo randomizar física/figura + gravedad
// Elegir aleatoriamente entre modo física o figura // Elegir aleatoriamente entre modo física o figura
@@ -1932,18 +1948,18 @@ void Engine::enterLogoMode(bool from_demo) {
logo_previous_texture_index_ = current_texture_index_; logo_previous_texture_index_ = current_texture_index_;
logo_previous_shape_scale_ = shape_scale_factor_; logo_previous_shape_scale_ = shape_scale_factor_;
// Buscar índice de textura "tiny" // Buscar índice de textura "small"
size_t tiny_index = current_texture_index_; // Por defecto mantener actual size_t small_index = current_texture_index_; // Por defecto mantener actual
for (size_t i = 0; i < texture_names_.size(); i++) { for (size_t i = 0; i < texture_names_.size(); i++) {
if (texture_names_[i] == "tiny") { if (texture_names_[i] == "small") {
tiny_index = i; small_index = i;
break; break;
} }
} }
// Aplicar configuración fija del Modo Logo // Aplicar configuración fija del Modo Logo
if (tiny_index != current_texture_index_) { if (small_index != current_texture_index_) {
current_texture_index_ = tiny_index; current_texture_index_ = small_index;
int old_size = current_ball_size_; int old_size = current_ball_size_;
current_ball_size_ = textures_[current_texture_index_]->getWidth(); current_ball_size_ = textures_[current_texture_index_]->getWidth();
updateBallSizes(old_size, current_ball_size_); updateBallSizes(old_size, current_ball_size_);
@@ -1955,8 +1971,10 @@ void Engine::enterLogoMode(bool from_demo) {
} }
} }
// Cambiar a tema MONOCHROME // Cambiar a tema aleatorio entre: MONOCHROME, LAVENDER, CRIMSON, ESMERALDA
theme_manager_->switchToTheme(5); // MONOCHROME int logo_themes[] = {5, 6, 7, 8}; // MONOCHROME, LAVENDER, CRIMSON, ESMERALDA
int random_theme = logo_themes[rand() % 4];
theme_manager_->switchToTheme(random_theme);
// Establecer escala a 120% // Establecer escala a 120%
shape_scale_factor_ = LOGO_MODE_SHAPE_SCALE; shape_scale_factor_ = LOGO_MODE_SHAPE_SCALE;
@@ -1980,6 +1998,9 @@ void Engine::enterLogoMode(bool from_demo) {
logo_target_flip_percentage_ = 0.0f; logo_target_flip_percentage_ = 0.0f;
logo_current_flip_count_ = 0; logo_current_flip_count_ = 0;
// Guardar si entrada fue manual (tecla K) o automática (desde DEMO)
logo_entered_manually_ = !from_demo;
// Cambiar a modo LOGO (guarda previous_app_mode_ automáticamente) // Cambiar a modo LOGO (guarda previous_app_mode_ automáticamente)
setState(AppMode::LOGO); setState(AppMode::LOGO);
} }
@@ -2017,12 +2038,23 @@ void Engine::exitLogoMode(bool return_to_demo) {
} }
} }
// Resetear flag de entrada manual
logo_entered_manually_ = false;
if (!return_to_demo) { if (!return_to_demo) {
// Salida manual (tecla K): volver a MANUAL // Salida manual (tecla K): volver a MANUAL
setState(AppMode::SANDBOX); setState(AppMode::SANDBOX);
} else { } else {
// Volver al modo previo (DEMO o DEMO_LITE) // Volver al modo previo (DEMO o DEMO_LITE)
setState(previous_app_mode_); setState(previous_app_mode_);
// Si la figura activa es PNG_SHAPE, cambiar a otra figura aleatoria
if (current_shape_type_ == ShapeType::PNG_SHAPE) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
activateShape(shapes[rand() % 8]);
}
} }
} }

View File

@@ -120,6 +120,9 @@ class Engine {
float logo_target_flip_percentage_ = 0.0f; // % de flip a esperar (0.2-0.8) float logo_target_flip_percentage_ = 0.0f; // % de flip a esperar (0.2-0.8)
int logo_current_flip_count_ = 0; // Flips observados hasta ahora int logo_current_flip_count_ = 0; // Flips observados hasta ahora
// Control de entrada manual vs automática a LOGO MODE
bool logo_entered_manually_ = false; // true si se activó con tecla K, false si automático desde DEMO
// Estado previo antes de entrar a Logo Mode (para restaurar al salir) // Estado previo antes de entrar a Logo Mode (para restaurar al salir)
int logo_previous_theme_ = 0; // Índice de tema (0-9) int logo_previous_theme_ = 0; // Índice de tema (0-9)
size_t logo_previous_texture_index_ = 0; size_t logo_previous_texture_index_ = 0;