Compare commits
10 Commits
dec8d431f5
...
ac57755bd2
| Author | SHA1 | Date | |
|---|---|---|---|
| ac57755bd2 | |||
| 7b24e387b7 | |||
| 9908165104 | |||
| 8a05f69442 | |||
| 036268f135 | |||
| a798811d23 | |||
| e15c1f5349 | |||
| 19e1c414c2 | |||
| cada46f732 | |||
| 78656cf17d |
268
CLAUDE.md
268
CLAUDE.md
@@ -1,76 +1,256 @@
|
||||
# Claude Code Session - ViBe1 Delta
|
||||
# Claude Code Session - ViBe3 Physics
|
||||
|
||||
## Estado del Proyecto
|
||||
|
||||
**Proyecto:** ViBe1 Delta - Simulador de sprites con fisica
|
||||
**Objetivo:** Implementar delta time para separar bucle de proceso del de renderizado
|
||||
**Proyecto:** ViBe3 Physics - Simulador de sprites con físicas avanzadas
|
||||
**Objetivo:** Implementar nuevas físicas experimentales expandiendo sobre el sistema de delta time
|
||||
**Base:** Migrado desde vibe1_delta con sistema delta time ya implementado
|
||||
|
||||
## Progreso Actual
|
||||
|
||||
### ✅ Completado
|
||||
|
||||
1. **Configuracion inicial**
|
||||
- Reestructurar codigo: movido utilidades a `source/external/`
|
||||
- Filtro nearest neighbor para texturas pixel-perfect
|
||||
- Compilacion funcionando (Make y CMake)
|
||||
#### 1. **Migración y Setup Inicial**
|
||||
- ✅ Renombrado vibe1_delta → vibe3_physics en todos los archivos
|
||||
- ✅ Carpeta resources → data
|
||||
- ✅ Actualizado CMakeLists.txt, .gitignore, defines.h, README.md
|
||||
- ✅ Añadido .claude/ al .gitignore
|
||||
- ✅ Sistema de compilación CMake funcionando
|
||||
|
||||
2. **Sistema de metricas**
|
||||
- Contador FPS en esquina superior derecha (amarillo)
|
||||
- Control V-Sync con tecla "V" (ON/OFF)
|
||||
- Display V-Sync en esquina superior izquierda (cian)
|
||||
#### 2. **Sistema de Físicas Base (Heredado)**
|
||||
- ✅ **Delta time implementado** - Física independiente del framerate
|
||||
- ✅ Contador FPS en tiempo real (esquina superior derecha, amarillo)
|
||||
- ✅ Control V-Sync dinámico con tecla "V" (ON/OFF)
|
||||
- ✅ Display V-Sync (esquina superior izquierda, cian)
|
||||
- ✅ **Sistema de temas visuales** - 4 temas (SUNSET/OCEAN/NEON/FOREST)
|
||||
- ✅ **Batch rendering optimizado** - Maneja hasta 100,000 sprites
|
||||
|
||||
3. **Mejoras visuales**
|
||||
- Tamaño pelota: 8x8 → 10x10 pixels
|
||||
- Fondo aclarado: (32,32,32) → (64,64,64)
|
||||
- Textura pelota redibujada con mejor calidad
|
||||
#### 3. **NUEVA CARACTERÍSTICA: Gravedad Direccional** 🎯
|
||||
- ✅ **Enum GravityDirection** (UP/DOWN/LEFT/RIGHT) en defines.h
|
||||
- ✅ **Ball class actualizada** para física multi-direccional
|
||||
- ✅ **Detección de superficie inteligente** - Adaptada a cada dirección
|
||||
- ✅ **Fricción direccional** - Se aplica en la superficie correcta
|
||||
- ✅ **Controles de cursor** - Cambio dinámico de gravedad
|
||||
- ✅ **Debug display actualizado** - Muestra dirección actual
|
||||
|
||||
### 🚧 En Proceso
|
||||
|
||||
- **Proximos pasos:** Implementar sistema delta time
|
||||
- **Problema detectado:** Caracteres extraños en README.md (encoding)
|
||||
#### 4. **NUEVA CARACTERÍSTICA: Coeficientes de Rebote Variables** ⚡
|
||||
- ✅ **Rango ampliado** - De 0.60-0.89 a 0.30-0.95 (+120% variabilidad)
|
||||
- ✅ **Comportamientos diversos** - Desde pelotas super rebotonas a muy amortiguadas
|
||||
- ✅ **Debug display** - Muestra coeficiente LOSS de primera pelota
|
||||
- ✅ **Física realista** - Elimina sincronización entre pelotas
|
||||
|
||||
### 📋 Controles Actuales
|
||||
|
||||
| Tecla | Accion |
|
||||
|-------|---------|
|
||||
| Tecla | Acción |
|
||||
|-------|--------|
|
||||
| **↑** | **Gravedad hacia ARRIBA** |
|
||||
| **↓** | **Gravedad hacia ABAJO** |
|
||||
| **←** | **Gravedad hacia IZQUIERDA** |
|
||||
| **→** | **Gravedad hacia DERECHA** |
|
||||
| V | Alternar V-Sync ON/OFF |
|
||||
| 1-8 | Cambiar numero de pelotas (1 a 100,000) |
|
||||
| H | **Toggle debug display (FPS, V-Sync, física, gravedad)** |
|
||||
| Num 1-4 | Selección directa de tema (Atardecer/Océano/Neón/Bosque) |
|
||||
| T | Ciclar entre temas de colores |
|
||||
| 1-8 | Cambiar número de pelotas (1 a 100,000) |
|
||||
| ESPACIO | Impulsar pelotas hacia arriba |
|
||||
| G | Alternar direccion gravedad |
|
||||
| G | Alternar gravedad ON/OFF (mantiene dirección) |
|
||||
| ESC | Salir |
|
||||
|
||||
### 🎯 Debug Display (Tecla H)
|
||||
|
||||
Cuando está activado muestra:
|
||||
```
|
||||
FPS: 75 # Esquina superior derecha (amarillo)
|
||||
VSYNC ON # Esquina superior izquierda (cian)
|
||||
GRAV 720 # Magnitud gravedad (magenta)
|
||||
VY -145 # Velocidad Y primera pelota (magenta)
|
||||
SURFACE YES # En superficie (magenta)
|
||||
LOSS 0.73 # Coeficiente rebote primera pelota (magenta)
|
||||
GRAVITY DOWN # Dirección actual (amarillo)
|
||||
THEME SUNSET # Tema activo (amarillo claro)
|
||||
```
|
||||
|
||||
## Arquitectura Actual
|
||||
|
||||
```
|
||||
source/
|
||||
├── main.cpp # Bucle principal + FPS/V-Sync
|
||||
├── ball.h/.cpp # Logica fisica pelotas
|
||||
├── defines.h # Constantes (BALL_SIZE=10)
|
||||
└── external/ # Utilidades externas
|
||||
├── texture.h/.cpp # Gestion texturas + nearest filter
|
||||
├── sprite.h/.cpp # Sistema sprites
|
||||
├── dbgtxt.h # Debug text + nearest filter
|
||||
└── stb_image.h # Carga imagenes
|
||||
vibe3_physics/
|
||||
├── source/
|
||||
│ ├── main.cpp # Bucle principal + controles + debug
|
||||
│ ├── ball.h/.cpp # Clase Ball con física direccional
|
||||
│ ├── defines.h # Constantes + enum GravityDirection
|
||||
│ └── external/ # Utilidades externas
|
||||
│ ├── texture.h/.cpp # Gestión texturas + nearest filter
|
||||
│ ├── sprite.h/.cpp # Sistema sprites
|
||||
│ ├── dbgtxt.h # Debug text + nearest filter
|
||||
│ └── stb_image.h # Carga imágenes
|
||||
├── data/ # Recursos (antes resources/)
|
||||
│ └── ball.png # Textura pelota 10x10px
|
||||
├── CMakeLists.txt # Build system
|
||||
└── CLAUDE.md # Este archivo de seguimiento
|
||||
```
|
||||
|
||||
## Bucket Actual: FPS Acoplado
|
||||
## Sistema de Gravedad Direccional
|
||||
|
||||
El sistema usa bucle acoplado a 60 FPS:
|
||||
### 🔧 Implementación Técnica
|
||||
|
||||
#### Enum y Estados
|
||||
```cpp
|
||||
if (SDL_GetTicks() - ticks > DEMO_SPEED) { // 16.67ms
|
||||
// Solo aqui se actualiza fisica
|
||||
}
|
||||
enum class GravityDirection {
|
||||
DOWN, // ↓ Gravedad hacia abajo (por defecto)
|
||||
UP, // ↑ Gravedad hacia arriba
|
||||
LEFT, // ← Gravedad hacia la izquierda
|
||||
RIGHT // → Gravedad hacia la derecha
|
||||
};
|
||||
```
|
||||
|
||||
**Problema:** Fisica dependiente del framerate → Inconsistencia cross-platform
|
||||
#### Lógica de Física por Dirección
|
||||
- **DOWN**: Pelotas caen hacia abajo, fricción en suelo inferior
|
||||
- **UP**: Pelotas "caen" hacia arriba, fricción en techo
|
||||
- **LEFT**: Pelotas "caen" hacia izquierda, fricción en pared izquierda
|
||||
- **RIGHT**: Pelotas "caen" hacia derecha, fricción en pared derecha
|
||||
|
||||
## Delta Time - Plan de Implementacion
|
||||
#### Cambios en Ball Class
|
||||
- `on_floor_` → `on_surface_` (más genérico)
|
||||
- `gravity_direction_` (nuevo miembro)
|
||||
- `setGravityDirection()` (nueva función)
|
||||
- `update()` completamente reescrito para lógica direccional
|
||||
|
||||
1. **Sistema timing independiente**
|
||||
2. **Bucle desacoplado** logica vs renderizado
|
||||
3. **Interpolacion** para renderizado suave
|
||||
4. **Optimizaciones** rendimiento
|
||||
## Lecciones Aprendidas de ViBe2 Modules
|
||||
|
||||
### ✅ Éxitos de Modularización
|
||||
- **C++20 modules** son viables para código propio
|
||||
- **CMake + Ninja** funciona bien para modules
|
||||
- **Separación clara** de responsabilidades mejora arquitectura
|
||||
|
||||
### ❌ Limitaciones Encontradas
|
||||
- **SDL3 + modules** generan conflictos irresolubles
|
||||
- **Bibliotecas externas** requieren includes tradicionales
|
||||
- **Enfoque híbrido** (modules propios + includes externos) es más práctico
|
||||
|
||||
### 🎯 Decisión para ViBe3 Physics
|
||||
- **Headers tradicionales** (.h/.cpp) por compatibilidad
|
||||
- **Enfoque en características** antes que arquitectura
|
||||
- **Organización por clases** en lugar de modules inicialmente
|
||||
|
||||
## Sistema de Coeficientes de Rebote Variables
|
||||
|
||||
### 🔧 Implementación Técnica
|
||||
|
||||
#### Problema Anterior
|
||||
```cpp
|
||||
// Sistema ANTIGUO - Poca variabilidad
|
||||
loss_ = ((rand() % 30) * 0.01f) + 0.6f; // 0.60 - 0.89 (diferencia: 0.29)
|
||||
```
|
||||
|
||||
**Resultado**: Pelotas con comportamientos muy similares → Sincronización visible
|
||||
|
||||
#### Solución Implementada
|
||||
```cpp
|
||||
// Sistema NUEVO - Alta variabilidad
|
||||
loss_ = ((rand() % 66) * 0.01f) + 0.30f; // 0.30 - 0.95 (diferencia: 0.65)
|
||||
```
|
||||
|
||||
### 🎯 Tipos de Comportamiento
|
||||
|
||||
#### Categorías de Materiales
|
||||
- **🏀 Super Rebotona** (0.85-0.95): Casi no pierde energía, rebota muchas veces
|
||||
- **⚽ Normal** (0.65-0.85): Comportamiento estándar equilibrado
|
||||
- **🎾 Amortiguada** (0.45-0.65): Pierde energía moderada, se estabiliza
|
||||
- **🏐 Muy Amortiguada** (0.30-0.45): Se para rápidamente, pocas rebotes
|
||||
|
||||
### ✅ Beneficios Conseguidos
|
||||
- **+120% variabilidad** en coeficientes de rebote
|
||||
- **Eliminación de sincronización** entre pelotas
|
||||
- **Comportamientos diversos** visibles inmediatamente
|
||||
- **Física más realista** con materiales diferentes
|
||||
- **Debug display** para monitoreo en tiempo real
|
||||
|
||||
## 🚀 Próximos Pasos - Físicas Avanzadas
|
||||
|
||||
### Ideas Pendientes de Implementación
|
||||
|
||||
#### 1. **Colisiones Entre Partículas**
|
||||
- Detección de colisión ball-to-ball
|
||||
- Física de rebotes entre pelotas
|
||||
- Conservación de momentum
|
||||
|
||||
#### 2. **Materiales y Propiedades**
|
||||
- Diferentes coeficientes de rebote por pelota
|
||||
- Fricción variable por material
|
||||
- Densidad y masa como propiedades
|
||||
|
||||
#### 3. **Fuerzas Externas**
|
||||
- **Viento** - Fuerza horizontal constante
|
||||
- **Campos magnéticos** - Atracción/repulsión a puntos
|
||||
- **Turbulencia** - Fuerzas aleatorias localizadas
|
||||
|
||||
#### 4. **Interactividad Avanzada**
|
||||
- Click para aplicar fuerzas puntuales
|
||||
- Arrastrar para crear campos de fuerza
|
||||
- Herramientas de "pincel" de física
|
||||
|
||||
#### 5. **Visualización Avanzada**
|
||||
- **Trails** - Estelas de movimiento
|
||||
- **Heatmaps** - Visualización de velocidad/energía
|
||||
- **Vectores de fuerza** - Visualizar gravedad y fuerzas
|
||||
|
||||
#### 6. **Optimizaciones**
|
||||
- Spatial partitioning para colisiones
|
||||
- Level-of-detail para muchas partículas
|
||||
- GPU compute shaders para física masiva
|
||||
|
||||
### 🎮 Controles Futuros Sugeridos
|
||||
```
|
||||
Mouse Click: Aplicar fuerza puntual
|
||||
Mouse Drag: Crear campo de fuerza
|
||||
Mouse Wheel: Ajustar intensidad
|
||||
R: Reset todas las pelotas
|
||||
P: Pausa/Resume física
|
||||
M: Modo materiales
|
||||
W: Toggle viento
|
||||
```
|
||||
|
||||
## Métricas del Proyecto
|
||||
|
||||
### ✅ Logros Actuales
|
||||
- **Compilación exitosa** con CMake
|
||||
- **Commit inicial** creado (dec8d43)
|
||||
- **17 archivos** versionados
|
||||
- **9,767 líneas** de código
|
||||
- **Física direccional** 100% funcional
|
||||
- **Coeficientes variables** implementados
|
||||
|
||||
### 🎯 Objetivos Cumplidos
|
||||
- ✅ Migración limpia desde vibe1_delta
|
||||
- ✅ Sistema de gravedad direccional implementado
|
||||
- ✅ Coeficientes de rebote variables (+120% diversidad)
|
||||
- ✅ Debug display completo y funcional
|
||||
- ✅ Controles intuitivos con teclas de cursor
|
||||
- ✅ Eliminación de sincronización entre pelotas
|
||||
|
||||
---
|
||||
|
||||
*Archivo de seguimiento para sesiones Claude Code*
|
||||
## Comandos Útiles
|
||||
|
||||
### Compilación
|
||||
```bash
|
||||
mkdir -p build && cd build && cmake .. && cmake --build .
|
||||
```
|
||||
|
||||
### Ejecución
|
||||
```bash
|
||||
./vibe3_physics.exe # Windows
|
||||
./vibe3_physics # Linux/macOS
|
||||
```
|
||||
|
||||
### Git
|
||||
```bash
|
||||
git status # Ver cambios
|
||||
git add . # Añadir archivos
|
||||
git commit -m "..." # Crear commit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Archivo de seguimiento para sesiones Claude Code - ViBe3 Physics*
|
||||
*Actualizado: Implementación de gravedad direccional completada*
|
||||
@@ -16,8 +16,9 @@ if (NOT SDL3_FOUND)
|
||||
message(FATAL_ERROR "SDL3 no encontrado. Por favor, verifica su instalación.")
|
||||
endif()
|
||||
|
||||
# Archivos fuente
|
||||
# Archivos fuente (excluir main_old.cpp)
|
||||
file(GLOB SOURCE_FILES source/*.cpp source/external/*.cpp)
|
||||
list(REMOVE_ITEM SOURCE_FILES "${CMAKE_SOURCE_DIR}/source/main_old.cpp")
|
||||
|
||||
# Comprobar si se encontraron archivos fuente
|
||||
if(NOT SOURCE_FILES)
|
||||
|
||||
55
README.md
55
README.md
@@ -8,7 +8,10 @@ El nombre refleja su proposito: **ViBe** (vibe-coding experimental) + **Physics*
|
||||
|
||||
- **Simulacion de fisica**: Gravedad, rebotes y colisiones con perdida de energia
|
||||
- **Multiples escenarios**: 8 configuraciones predefinidas (1 a 100,000 pelotas)
|
||||
- **Sistema de temas visuales**: 4 temas de colores con fondos degradados y paletas tematicas
|
||||
- **Sistema de temas visuales**: 5 temas de colores con fondos degradados y paletas tematicas
|
||||
- **Sistema de zoom dinamico**: F1/F2 para ajustar el zoom de ventana (1x-10x)
|
||||
- **Modos fullscreen**: F3 para fullscreen normal, F4 para real fullscreen con resolucion nativa
|
||||
- **Gravedad multidireccional**: Gravedad hacia abajo, arriba, izquierda o derecha
|
||||
- **Interactividad**: Controles de teclado para modificar el comportamiento
|
||||
- **Renderizado batch optimizado**: Sistema de batch rendering con SDL_RenderGeometry para 50K+ sprites
|
||||
- **Colores tematicos**: Paletas de 8 colores por tema aplicadas proceduralmente
|
||||
@@ -17,16 +20,37 @@ El nombre refleja su proposito: **ViBe** (vibe-coding experimental) + **Physics*
|
||||
|
||||
## 🎮 Controles
|
||||
|
||||
### Controles de Sistema
|
||||
| Tecla | Accion |
|
||||
|-------|--------|
|
||||
| `H` | **Alternar debug display (FPS, V-Sync, valores fisica)** |
|
||||
| `V` | **Alternar V-Sync ON/OFF** |
|
||||
| `F1-F4` | **Seleccion directa de tema de colores (Atardecer/Oceano/Neon/Bosque)** |
|
||||
| `T` | **Ciclar entre temas de colores** |
|
||||
| `ESC` | Salir del programa |
|
||||
|
||||
### Controles de Ventana
|
||||
| Tecla | Accion |
|
||||
|-------|--------|
|
||||
| `F1` | **Zoom out (reducir zoom ventana)** |
|
||||
| `F2` | **Zoom in (aumentar zoom ventana)** |
|
||||
| `F3` | **Toggle fullscreen normal** |
|
||||
| `F4` | **Toggle real fullscreen (resolucion nativa)** |
|
||||
|
||||
### Controles de Temas
|
||||
| Tecla | Accion |
|
||||
|-------|--------|
|
||||
| `KP_1` | **Tema ATARDECER (colores calidos)** |
|
||||
| `KP_2` | **Tema OCEANO (azules y cianes)** |
|
||||
| `KP_3` | **Tema NEON (colores vibrantes)** |
|
||||
| `KP_4` | **Tema BOSQUE (verdes naturales)** |
|
||||
| `KP_5` | **Tema RGB (fondo blanco, colores matematicos)** |
|
||||
| `T` | **Ciclar entre todos los temas** |
|
||||
|
||||
### Controles de Simulacion
|
||||
| Tecla | Accion |
|
||||
|-------|--------|
|
||||
| `1-8` | Cambiar numero de pelotas (1, 10, 100, 500, 1K, 10K, 50K, 100K) |
|
||||
| `ESPACIO` | Impulsar todas las pelotas hacia arriba |
|
||||
| `G` | Alternar direccion de la gravedad |
|
||||
| `ESC` | Salir del programa |
|
||||
| `G` | Alternar direccion de la gravedad (↓↑←→) |
|
||||
|
||||
## 📊 Informacion en Pantalla
|
||||
|
||||
@@ -40,24 +64,25 @@ Cuando se activa el debug display con la tecla `H`:
|
||||
- **Esquina superior izquierda**: Estado V-Sync (VSYNC ON/OFF) en **cian**
|
||||
- **Esquina superior derecha**: Contador FPS en tiempo real en **amarillo**
|
||||
- **Lineas 3-5**: Valores fisica primera pelota (GRAV, VY, FLOOR) en **magenta**
|
||||
- **Linea 6**: Tema activo (THEME SUNSET/OCEAN/NEON/FOREST) en **amarillo claro**
|
||||
- **Linea 6**: Tema activo (THEME SUNSET/OCEAN/NEON/FOREST/RGB) en **amarillo claro**
|
||||
|
||||
## 🎨 Sistema de Temas de Colores
|
||||
|
||||
**ViBe1 Delta** incluye 4 temas visuales que transforman completamente la apariencia del simulador:
|
||||
**ViBe1 Delta** incluye 5 temas visuales que transforman completamente la apariencia del simulador:
|
||||
|
||||
### Temas Disponibles
|
||||
|
||||
| Tecla | Tema | Descripcion | Fondo | Paleta de Pelotas |
|
||||
|-------|------|-------------|-------|-------------------|
|
||||
| `F1` | **ATARDECER** | Colores calidos de puesta de sol | Degradado naranja-rojo | Tonos naranjas, rojos y amarillos |
|
||||
| `F2` | **OCEANO** | Ambiente marino refrescante | Degradado azul-cian | Azules, cianes y verdes agua |
|
||||
| `F3` | **NEON** | Colores vibrantes futuristas | Degradado magenta-cian | Magentas, cianes y rosas brillantes |
|
||||
| `F4` | **BOSQUE** | Naturaleza verde relajante | Degradado verde oscuro-claro | Verdes naturales y tierra |
|
||||
| `KP_1` | **ATARDECER** | Colores calidos de puesta de sol | Degradado naranja-rojo | Tonos naranjas, rojos y amarillos |
|
||||
| `KP_2` | **OCEANO** | Ambiente marino refrescante | Degradado azul-cian | Azules, cianes y verdes agua |
|
||||
| `KP_3` | **NEON** | Colores vibrantes futuristas | Degradado magenta-cian | Magentas, cianes y rosas brillantes |
|
||||
| `KP_4` | **BOSQUE** | Naturaleza verde relajante | Degradado verde oscuro-claro | Verdes naturales y tierra |
|
||||
| `KP_5` | **RGB** | Colores matematicos puros | Fondo blanco solido | RGB puros y subdivisiones matematicas |
|
||||
|
||||
### Controles de Temas
|
||||
|
||||
- **Seleccion directa**: Usa `F1`, `F2`, `F3` o `F4` para cambiar inmediatamente al tema deseado
|
||||
- **Seleccion directa**: Usa `KP_1`, `KP_2`, `KP_3`, `KP_4` o `KP_5` para cambiar inmediatamente al tema deseado
|
||||
- **Ciclado secuencial**: Presiona `T` para avanzar al siguiente tema en orden
|
||||
- **Indicador visual**: El nombre del tema aparece temporalmente en el centro de la pantalla con colores tematicos
|
||||
- **Regeneracion automatica**: Las pelotas adoptan automaticamente la nueva paleta de colores al cambiar tema
|
||||
@@ -143,7 +168,7 @@ make
|
||||
## 📊 Detalles Tecnicos
|
||||
|
||||
### Configuracion Actual
|
||||
- **Resolucion**: 320x240 pixeles (escalado x3 = 960x720)
|
||||
- **Resolucion**: 640x360 pixeles (escalado x2 = 1280x720)
|
||||
- **Sistema de timing**: Delta time independiente del framerate
|
||||
- **Fisica**: Gravedad constante (0.2f), rebotes con perdida de energia
|
||||
- **Tamaño de pelota**: 10x10 pixeles
|
||||
@@ -349,9 +374,7 @@ Para contribuir al proyecto:
|
||||
|
||||
## 🐛 Problemas Conocidos
|
||||
|
||||
- FPS drops significativos con >10,000 pelotas
|
||||
- Timing dependiente del framerate (solucion en desarrollo)
|
||||
- Sin interpolacion en el renderizado
|
||||
Ninguno conocido. El sistema esta completamente funcional con delta time implementado.
|
||||
|
||||
---
|
||||
|
||||
|
||||
108
source/ball.cpp
108
source/ball.cpp
@@ -7,8 +7,22 @@
|
||||
#include "defines.h" // for BALL_SIZE, Color, SCREEN_HEIGHT, GRAVITY_FORCE
|
||||
class Texture;
|
||||
|
||||
// Función auxiliar para generar pérdida aleatoria en rebotes
|
||||
float generateBounceVariation() {
|
||||
// Genera un valor entre 0 y BOUNCE_RANDOM_LOSS_PERCENT (solo pérdida adicional)
|
||||
float loss = (rand() % 1000) / 1000.0f * BOUNCE_RANDOM_LOSS_PERCENT;
|
||||
return 1.0f - loss; // Retorna multiplicador (ej: 0.90 - 1.00 para 10% max pérdida)
|
||||
}
|
||||
|
||||
// Función auxiliar para generar pérdida lateral aleatoria
|
||||
float generateLateralLoss() {
|
||||
// Genera un valor entre 0 y LATERAL_LOSS_PERCENT
|
||||
float loss = (rand() % 1000) / 1000.0f * LATERAL_LOSS_PERCENT;
|
||||
return 1.0f - loss; // Retorna multiplicador (ej: 0.98 - 1.0 para 0-2% pérdida)
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Ball::Ball(float x, float vx, float vy, Color color, std::shared_ptr<Texture> texture, GravityDirection gravity_dir)
|
||||
Ball::Ball(float x, float vx, float vy, Color color, std::shared_ptr<Texture> texture, int screen_width, int screen_height, GravityDirection gravity_dir, float mass_factor)
|
||||
: sprite_(std::make_unique<Sprite>(texture)),
|
||||
pos_({x, 0.0f, BALL_SIZE, BALL_SIZE}) {
|
||||
// Convertir velocidades de píxeles/frame a píxeles/segundo (multiplicar por 60)
|
||||
@@ -20,10 +34,14 @@ Ball::Ball(float x, float vx, float vy, Color color, std::shared_ptr<Texture> te
|
||||
color_ = color;
|
||||
// Convertir gravedad de píxeles/frame² a píxeles/segundo² (multiplicar por 60²)
|
||||
gravity_force_ = GRAVITY_FORCE * 60.0f * 60.0f;
|
||||
gravity_mass_factor_ = mass_factor; // Factor de masa individual para esta pelota
|
||||
gravity_direction_ = gravity_dir;
|
||||
screen_width_ = screen_width; // Dimensiones del terreno de juego
|
||||
screen_height_ = screen_height;
|
||||
on_surface_ = false;
|
||||
stopped_ = false;
|
||||
loss_ = ((rand() % 30) * 0.01f) + 0.6f;
|
||||
// Coeficiente base IGUAL para todas las pelotas (solo variación por rebote individual)
|
||||
loss_ = BASE_BOUNCE_COEFFICIENT; // Coeficiente fijo para todas las pelotas
|
||||
}
|
||||
|
||||
// Actualiza la lógica de la clase
|
||||
@@ -34,18 +52,20 @@ void Ball::update(float deltaTime) {
|
||||
|
||||
// Aplica la gravedad según la dirección (píxeles/segundo²)
|
||||
if (!on_surface_) {
|
||||
// Aplicar gravedad multiplicada por factor de masa individual
|
||||
float effective_gravity = gravity_force_ * gravity_mass_factor_ * deltaTime;
|
||||
switch (gravity_direction_) {
|
||||
case GravityDirection::DOWN:
|
||||
vy_ += gravity_force_ * deltaTime;
|
||||
vy_ += effective_gravity;
|
||||
break;
|
||||
case GravityDirection::UP:
|
||||
vy_ -= gravity_force_ * deltaTime;
|
||||
vy_ -= effective_gravity;
|
||||
break;
|
||||
case GravityDirection::LEFT:
|
||||
vx_ -= gravity_force_ * deltaTime;
|
||||
vx_ -= effective_gravity;
|
||||
break;
|
||||
case GravityDirection::RIGHT:
|
||||
vx_ += gravity_force_ * deltaTime;
|
||||
vx_ += effective_gravity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -58,7 +78,7 @@ void Ball::update(float deltaTime) {
|
||||
// Si está en superficie, mantener posición según dirección de gravedad
|
||||
switch (gravity_direction_) {
|
||||
case GravityDirection::DOWN:
|
||||
pos_.y = SCREEN_HEIGHT - pos_.h;
|
||||
pos_.y = screen_height_ - pos_.h;
|
||||
pos_.x += vx_ * deltaTime; // Seguir moviéndose en X
|
||||
break;
|
||||
case GravityDirection::UP:
|
||||
@@ -70,7 +90,7 @@ void Ball::update(float deltaTime) {
|
||||
pos_.y += vy_ * deltaTime; // Seguir moviéndose en Y
|
||||
break;
|
||||
case GravityDirection::RIGHT:
|
||||
pos_.x = SCREEN_WIDTH - pos_.w;
|
||||
pos_.x = screen_width_ - pos_.w;
|
||||
pos_.y += vy_ * deltaTime; // Seguir moviéndose en Y
|
||||
break;
|
||||
}
|
||||
@@ -80,64 +100,72 @@ void Ball::update(float deltaTime) {
|
||||
if (pos_.x < 0) {
|
||||
pos_.x = 0;
|
||||
if (gravity_direction_ == GravityDirection::LEFT) {
|
||||
// Colisión con superficie de gravedad
|
||||
vx_ = -vx_ * loss_;
|
||||
// Colisión con superficie de gravedad - aplicar variación aleatoria
|
||||
vx_ = -vx_ * loss_ * generateBounceVariation();
|
||||
if (std::fabs(vx_) < 6.0f) {
|
||||
vx_ = 0.0f;
|
||||
on_surface_ = true;
|
||||
}
|
||||
} else {
|
||||
// Rebote normal
|
||||
vx_ = -vx_;
|
||||
// Rebote normal - con pérdida lateral aleatoria
|
||||
vx_ = -vx_ * generateLateralLoss();
|
||||
}
|
||||
// Pérdida lateral en velocidad vertical también
|
||||
vy_ *= generateLateralLoss();
|
||||
}
|
||||
|
||||
// Comprueba las colisiones con el lateral derecho
|
||||
if (pos_.x + pos_.w > SCREEN_WIDTH) {
|
||||
pos_.x = SCREEN_WIDTH - pos_.w;
|
||||
if (pos_.x + pos_.w > screen_width_) {
|
||||
pos_.x = screen_width_ - pos_.w;
|
||||
if (gravity_direction_ == GravityDirection::RIGHT) {
|
||||
// Colisión con superficie de gravedad
|
||||
vx_ = -vx_ * loss_;
|
||||
// Colisión con superficie de gravedad - aplicar variación aleatoria
|
||||
vx_ = -vx_ * loss_ * generateBounceVariation();
|
||||
if (std::fabs(vx_) < 6.0f) {
|
||||
vx_ = 0.0f;
|
||||
on_surface_ = true;
|
||||
}
|
||||
} else {
|
||||
// Rebote normal
|
||||
vx_ = -vx_;
|
||||
// Rebote normal - con pérdida lateral aleatoria
|
||||
vx_ = -vx_ * generateLateralLoss();
|
||||
}
|
||||
// Pérdida lateral en velocidad vertical también
|
||||
vy_ *= generateLateralLoss();
|
||||
}
|
||||
|
||||
// Comprueba las colisiones con la parte superior
|
||||
if (pos_.y < 0) {
|
||||
pos_.y = 0;
|
||||
if (gravity_direction_ == GravityDirection::UP) {
|
||||
// Colisión con superficie de gravedad
|
||||
vy_ = -vy_ * loss_;
|
||||
// Colisión con superficie de gravedad - aplicar variación aleatoria
|
||||
vy_ = -vy_ * loss_ * generateBounceVariation();
|
||||
if (std::fabs(vy_) < 6.0f) {
|
||||
vy_ = 0.0f;
|
||||
on_surface_ = true;
|
||||
}
|
||||
} else {
|
||||
// Rebote normal
|
||||
vy_ = -vy_;
|
||||
// Rebote normal - con pérdida lateral aleatoria
|
||||
vy_ = -vy_ * generateLateralLoss();
|
||||
}
|
||||
// Pérdida lateral en velocidad horizontal también
|
||||
vx_ *= generateLateralLoss();
|
||||
}
|
||||
|
||||
// Comprueba las colisiones con la parte inferior
|
||||
if (pos_.y + pos_.h > SCREEN_HEIGHT) {
|
||||
pos_.y = SCREEN_HEIGHT - pos_.h;
|
||||
if (pos_.y + pos_.h > screen_height_) {
|
||||
pos_.y = screen_height_ - pos_.h;
|
||||
if (gravity_direction_ == GravityDirection::DOWN) {
|
||||
// Colisión con superficie de gravedad
|
||||
vy_ = -vy_ * loss_;
|
||||
// Colisión con superficie de gravedad - aplicar variación aleatoria
|
||||
vy_ = -vy_ * loss_ * generateBounceVariation();
|
||||
if (std::fabs(vy_) < 6.0f) {
|
||||
vy_ = 0.0f;
|
||||
on_surface_ = true;
|
||||
}
|
||||
} else {
|
||||
// Rebote normal
|
||||
vy_ = -vy_;
|
||||
// Rebote normal - con pérdida lateral aleatoria
|
||||
vy_ = -vy_ * generateLateralLoss();
|
||||
}
|
||||
// Pérdida lateral en velocidad horizontal también
|
||||
vx_ *= generateLateralLoss();
|
||||
}
|
||||
|
||||
// Aplica rozamiento al estar en superficie
|
||||
@@ -197,4 +225,28 @@ void Ball::setGravityDirection(GravityDirection direction) {
|
||||
gravity_direction_ = direction;
|
||||
on_surface_ = false; // Ya no está en superficie al cambiar dirección
|
||||
stopped_ = false; // Reactivar movimiento
|
||||
}
|
||||
|
||||
// Aplica un pequeño empuje lateral aleatorio
|
||||
void Ball::applyRandomLateralPush() {
|
||||
// Generar velocidad lateral aleatoria (nunca 0)
|
||||
float lateral_speed = GRAVITY_CHANGE_LATERAL_MIN + (rand() % 1000) / 1000.0f * (GRAVITY_CHANGE_LATERAL_MAX - GRAVITY_CHANGE_LATERAL_MIN);
|
||||
|
||||
// Signo aleatorio (+ o -)
|
||||
int sign = ((rand() % 2) * 2) - 1;
|
||||
lateral_speed *= sign;
|
||||
|
||||
// Aplicar según la dirección de gravedad actual
|
||||
switch (gravity_direction_) {
|
||||
case GravityDirection::UP:
|
||||
case GravityDirection::DOWN:
|
||||
// Gravedad vertical -> empuje lateral en X
|
||||
vx_ += lateral_speed * 60.0f; // Convertir a píxeles/segundo
|
||||
break;
|
||||
case GravityDirection::LEFT:
|
||||
case GravityDirection::RIGHT:
|
||||
// Gravedad horizontal -> empuje lateral en Y
|
||||
vy_ += lateral_speed * 60.0f; // Convertir a píxeles/segundo
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,11 @@ class Ball {
|
||||
std::unique_ptr<Sprite> sprite_; // Sprite para pintar la clase
|
||||
SDL_FRect pos_; // Posición y tamaño de la pelota
|
||||
float vx_, vy_; // Velocidad
|
||||
float gravity_force_; // Gravedad
|
||||
float gravity_force_; // Gravedad base
|
||||
float gravity_mass_factor_; // Factor de masa individual (0.7-1.3, afecta gravedad)
|
||||
GravityDirection gravity_direction_; // Direcci\u00f3n de la gravedad
|
||||
int screen_width_; // Ancho del terreno de juego
|
||||
int screen_height_; // Alto del terreno de juego
|
||||
Color color_; // Color de la pelota
|
||||
bool on_surface_; // Indica si la pelota est\u00e1 en la superficie (suelo/techo/pared)
|
||||
bool stopped_; // Indica si la pelota ha terminado de moverse;
|
||||
@@ -22,7 +25,7 @@ class Ball {
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Ball(float x, float vx, float vy, Color color, std::shared_ptr<Texture> texture, GravityDirection gravity_dir = GravityDirection::DOWN);
|
||||
Ball(float x, float vx, float vy, Color color, std::shared_ptr<Texture> texture, int screen_width, int screen_height, GravityDirection gravity_dir = GravityDirection::DOWN, float mass_factor = 1.0f);
|
||||
|
||||
// Destructor
|
||||
~Ball() = default;
|
||||
@@ -42,10 +45,14 @@ class Ball {
|
||||
// Cambia la direcci\u00f3n de gravedad
|
||||
void setGravityDirection(GravityDirection direction);
|
||||
|
||||
// Aplica un peque\u00f1o empuje lateral aleatorio
|
||||
void applyRandomLateralPush();
|
||||
|
||||
// Getters para debug
|
||||
float getVelocityY() const { return vy_; }
|
||||
float getVelocityX() const { return vx_; }
|
||||
float getGravityForce() const { return gravity_force_; }
|
||||
float getLossCoefficient() const { return loss_; }
|
||||
GravityDirection getGravityDirection() const { return gravity_direction_; }
|
||||
bool isOnSurface() const { return on_surface_; }
|
||||
|
||||
|
||||
@@ -1,24 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
// Configuración de ventana y pantalla
|
||||
constexpr char WINDOW_CAPTION[] = "vibe3_physics";
|
||||
|
||||
constexpr int SCREEN_WIDTH = 320;
|
||||
constexpr int SCREEN_HEIGHT = 240;
|
||||
constexpr int WINDOW_SIZE = 3;
|
||||
constexpr int BALL_SIZE = 10;
|
||||
constexpr float GRAVITY_FORCE = 0.2f;
|
||||
constexpr int SCREEN_WIDTH = 640; // Ancho de la pantalla lógica (píxeles)
|
||||
constexpr int SCREEN_HEIGHT = 360; // Alto de la pantalla lógica (píxeles)
|
||||
constexpr int WINDOW_ZOOM = 2; // Zoom inicial de la ventana
|
||||
constexpr int BALL_SIZE = 10; // Tamaño de las pelotas (píxeles)
|
||||
|
||||
// DEMO_SPEED eliminado - ya no se usa con delta time
|
||||
constexpr Uint64 TEXT_DURATION = 2000;
|
||||
// Configuración de zoom dinámico de ventana
|
||||
constexpr int WINDOW_ZOOM_MIN = 1; // Zoom mínimo (320x240)
|
||||
constexpr int WINDOW_ZOOM_MAX = 10; // Zoom máximo teórico (3200x2400)
|
||||
constexpr int WINDOW_DESKTOP_MARGIN = 10; // Margen mínimo con bordes del escritorio
|
||||
constexpr int WINDOW_DECORATION_HEIGHT = 30; // Altura estimada de decoraciones del SO
|
||||
|
||||
// Configuración de física
|
||||
constexpr float GRAVITY_FORCE = 0.2f; // Fuerza de gravedad (píxeles/frame²)
|
||||
|
||||
// Configuración de interfaz
|
||||
constexpr Uint64 TEXT_DURATION = 2000; // Duración del texto informativo (ms)
|
||||
|
||||
// Configuración de pérdida aleatoria en rebotes
|
||||
constexpr float BASE_BOUNCE_COEFFICIENT = 0.75f; // Coeficiente base IGUAL para todas las pelotas
|
||||
constexpr float BOUNCE_RANDOM_LOSS_PERCENT = 0.1f; // 0-10% pérdida adicional aleatoria en cada rebote
|
||||
constexpr float LATERAL_LOSS_PERCENT = 0.02f; // ±2% pérdida lateral en rebotes
|
||||
|
||||
// Configuración de masa/peso individual por pelota
|
||||
constexpr float GRAVITY_MASS_MIN = 0.7f; // Factor mínimo de masa (pelota ligera - 70% gravedad)
|
||||
constexpr float GRAVITY_MASS_MAX = 1.3f; // Factor máximo de masa (pelota pesada - 130% gravedad)
|
||||
|
||||
// Configuración de velocidad lateral al cambiar gravedad (muy sutil)
|
||||
constexpr float GRAVITY_CHANGE_LATERAL_MIN = 0.04f; // Velocidad lateral mínima (2.4 px/s)
|
||||
constexpr float GRAVITY_CHANGE_LATERAL_MAX = 0.08f; // Velocidad lateral máxima (4.8 px/s)
|
||||
|
||||
// Configuración de spawn inicial de pelotas
|
||||
constexpr float BALL_SPAWN_MARGIN = 0.15f; // Margen lateral para spawn (0.25 = 25% a cada lado)
|
||||
|
||||
// Estructura para representar colores RGB
|
||||
struct Color {
|
||||
int r, g, b;
|
||||
int r, g, b; // Componentes rojo, verde, azul (0-255)
|
||||
};
|
||||
|
||||
// Enum para direcci\u00f3n de gravedad
|
||||
// Enum para dirección de gravedad
|
||||
enum class GravityDirection {
|
||||
DOWN, // \u2193 Gravedad hacia abajo (por defecto)
|
||||
UP, // \u2191 Gravedad hacia arriba
|
||||
LEFT, // \u2190 Gravedad hacia la izquierda
|
||||
RIGHT // \u2192 Gravedad hacia la derecha
|
||||
DOWN, // ↓ Gravedad hacia abajo (por defecto)
|
||||
UP, // ↑ Gravedad hacia arriba
|
||||
LEFT, // ← Gravedad hacia la izquierda
|
||||
RIGHT // → Gravedad hacia la derecha
|
||||
};
|
||||
|
||||
// Enum para temas de colores (seleccionables con teclado numérico)
|
||||
enum class ColorTheme {
|
||||
SUNSET = 0, // Naranjas, rojos, amarillos, rosas
|
||||
OCEAN = 1, // Azules, turquesas, blancos
|
||||
NEON = 2, // Cian, magenta, verde lima, amarillo vibrante
|
||||
FOREST = 3, // Verdes, marrones, amarillos otoño
|
||||
RGB = 4 // RGB puros y subdivisiones matemáticas (fondo blanco)
|
||||
};
|
||||
691
source/engine.cpp
Normal file
691
source/engine.cpp
Normal file
@@ -0,0 +1,691 @@
|
||||
#include "engine.h"
|
||||
|
||||
#include <SDL3/SDL_error.h> // for SDL_GetError
|
||||
#include <SDL3/SDL_events.h> // for SDL_Event, SDL_PollEvent
|
||||
#include <SDL3/SDL_init.h> // for SDL_Init, SDL_Quit, SDL_INIT_VIDEO
|
||||
#include <SDL3/SDL_keycode.h> // for SDL_Keycode
|
||||
#include <SDL3/SDL_render.h> // for SDL_SetRenderDrawColor, SDL_RenderPresent
|
||||
#include <SDL3/SDL_timer.h> // for SDL_GetTicks
|
||||
#include <SDL3/SDL_video.h> // for SDL_CreateWindow, SDL_DestroyWindow, SDL_GetDisplayBounds
|
||||
|
||||
#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() {
|
||||
bool success = true;
|
||||
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
std::cout << "¡SDL no se pudo inicializar! Error de SDL: " << SDL_GetError() << std::endl;
|
||||
success = false;
|
||||
} else {
|
||||
// Crear ventana principal
|
||||
window_ = SDL_CreateWindow(WINDOW_CAPTION, SCREEN_WIDTH * WINDOW_ZOOM, SCREEN_HEIGHT * WINDOW_ZOOM, SDL_WINDOW_OPENGL);
|
||||
if (window_ == nullptr) {
|
||||
std::cout << "¡No se pudo crear la ventana! Error de SDL: " << SDL_GetError() << std::endl;
|
||||
success = false;
|
||||
} else {
|
||||
// Crear renderizador
|
||||
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
||||
if (renderer_ == nullptr) {
|
||||
std::cout << "¡No se pudo crear el renderizador! Error de SDL: " << SDL_GetError() << std::endl;
|
||||
success = false;
|
||||
} else {
|
||||
// Establecer color inicial del renderizador
|
||||
SDL_SetRenderDrawColor(renderer_, 0xFF, 0xFF, 0xFF, 0xFF);
|
||||
|
||||
// Establecer tamaño lógico para el renderizado
|
||||
SDL_SetRenderLogicalPresentation(renderer_, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
||||
|
||||
// Configurar V-Sync inicial
|
||||
SDL_SetRenderVSync(renderer_, vsync_enabled_ ? 1 : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar otros componentes si SDL se inicializó correctamente
|
||||
if (success) {
|
||||
// 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>(renderer_, texture_path);
|
||||
srand(static_cast<unsigned>(time(nullptr)));
|
||||
dbg_init(renderer_);
|
||||
initBalls(scenario_);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void Engine::run() {
|
||||
while (!should_exit_) {
|
||||
calculateDeltaTime();
|
||||
update();
|
||||
handleEvents();
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::shutdown() {
|
||||
// Limpiar recursos SDL
|
||||
if (renderer_) {
|
||||
SDL_DestroyRenderer(renderer_);
|
||||
renderer_ = nullptr;
|
||||
}
|
||||
if (window_) {
|
||||
SDL_DestroyWindow(window_);
|
||||
window_ = nullptr;
|
||||
}
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
pushUpBalls();
|
||||
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() {
|
||||
// Renderizar fondo degradado en lugar de color sólido
|
||||
renderGradientBackground();
|
||||
|
||||
// Limpiar batches del frame anterior
|
||||
batch_vertices_.clear();
|
||||
batch_indices_.clear();
|
||||
|
||||
// Recopilar datos de todas las bolas para batch rendering
|
||||
for (auto &ball : balls_) {
|
||||
// En lugar de ball->render(), obtener datos para batch
|
||||
SDL_FRect pos = ball->getPosition();
|
||||
Color color = ball->getColor();
|
||||
addSpriteToBatch(pos.x, pos.y, pos.w, pos.h, color.r, color.g, color.b);
|
||||
}
|
||||
|
||||
// Renderizar todas las bolas en una sola llamada
|
||||
if (!batch_vertices_.empty()) {
|
||||
SDL_RenderGeometry(renderer_, texture_->getSDLTexture(), batch_vertices_.data(), static_cast<int>(batch_vertices_.size()), batch_indices_.data(), static_cast<int>(batch_indices_.size()));
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
|
||||
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() % 8; // 8 colores por tema
|
||||
const Color COLOR = {theme.ball_colors[color_index][0],
|
||||
theme.ball_colors[color_index][1],
|
||||
theme.ball_colors[color_index][2]};
|
||||
// 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::pushUpBalls() {
|
||||
for (auto &ball : balls_) {
|
||||
const int SIGNO = ((rand() % 2) * 2) - 1;
|
||||
const float VX = (((rand() % 20) + 10) * 0.1f) * SIGNO;
|
||||
const float VY = ((rand() % 40) * 0.1f) + 5;
|
||||
ball->modVel(VX, -VY); // Modifica la velocidad de la bola
|
||||
}
|
||||
}
|
||||
|
||||
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 renderizador
|
||||
SDL_SetRenderVSync(renderer_, vsync_enabled_ ? 1 : 0);
|
||||
}
|
||||
|
||||
void Engine::toggleFullscreen() {
|
||||
// Si está en modo real fullscreen, primero salir de él
|
||||
if (real_fullscreen_enabled_) {
|
||||
toggleRealFullscreen(); // Esto lo desactiva
|
||||
}
|
||||
|
||||
fullscreen_enabled_ = !fullscreen_enabled_;
|
||||
SDL_SetWindowFullscreen(window_, fullscreen_enabled_);
|
||||
}
|
||||
|
||||
void Engine::toggleRealFullscreen() {
|
||||
// Si está en modo fullscreen normal, primero desactivarlo
|
||||
if (fullscreen_enabled_) {
|
||||
fullscreen_enabled_ = false;
|
||||
SDL_SetWindowFullscreen(window_, false);
|
||||
}
|
||||
|
||||
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_);
|
||||
SDL_SetWindowFullscreen(window_, true);
|
||||
|
||||
// Actualizar presentación lógica del renderizador
|
||||
SDL_SetRenderLogicalPresentation(renderer_, current_screen_width_, current_screen_height_, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
||||
|
||||
// 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);
|
||||
SDL_SetWindowSize(window_, SCREEN_WIDTH * WINDOW_ZOOM, SCREEN_HEIGHT * WINDOW_ZOOM);
|
||||
|
||||
// Restaurar presentación lógica original
|
||||
SDL_SetRenderLogicalPresentation(renderer_, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
||||
|
||||
// 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() {
|
||||
// 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_)];
|
||||
|
||||
float top_r = theme.bg_top_r;
|
||||
float top_g = theme.bg_top_g;
|
||||
float top_b = theme.bg_top_b;
|
||||
|
||||
float bottom_r = theme.bg_bottom_r;
|
||||
float bottom_g = theme.bg_bottom_g;
|
||||
float bottom_b = theme.bg_bottom_b;
|
||||
|
||||
// Vértice superior izquierdo
|
||||
bg_vertices[0].position = {0, 0};
|
||||
bg_vertices[0].tex_coord = {0.0f, 0.0f};
|
||||
bg_vertices[0].color = {top_r, top_g, top_b, 1.0f};
|
||||
|
||||
// Vértice superior derecho
|
||||
bg_vertices[1].position = {static_cast<float>(current_screen_width_), 0};
|
||||
bg_vertices[1].tex_coord = {1.0f, 0.0f};
|
||||
bg_vertices[1].color = {top_r, top_g, top_b, 1.0f};
|
||||
|
||||
// Vértice inferior derecho
|
||||
bg_vertices[2].position = {static_cast<float>(current_screen_width_), static_cast<float>(current_screen_height_)};
|
||||
bg_vertices[2].tex_coord = {1.0f, 1.0f};
|
||||
bg_vertices[2].color = {bottom_r, bottom_g, bottom_b, 1.0f};
|
||||
|
||||
// Vértice inferior izquierdo
|
||||
bg_vertices[3].position = {0, static_cast<float>(current_screen_height_)};
|
||||
bg_vertices[3].tex_coord = {0.0f, 1.0f};
|
||||
bg_vertices[3].color = {bottom_r, bottom_g, bottom_b, 1.0f};
|
||||
|
||||
// Índices para 2 triángulos
|
||||
int bg_indices[6] = {0, 1, 2, 2, 3, 0};
|
||||
|
||||
// Renderizar sin textura (nullptr)
|
||||
SDL_RenderGeometry(renderer_, nullptr, bg_vertices, 4, bg_indices, 6);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Sistema de zoom dinámico
|
||||
int Engine::calculateMaxWindowZoom() const {
|
||||
// Obtener información del display usando el método de Coffee Crisis
|
||||
int num_displays = 0;
|
||||
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
|
||||
if (displays == nullptr || num_displays == 0) {
|
||||
return WINDOW_ZOOM_MIN; // Fallback si no se puede obtener
|
||||
}
|
||||
|
||||
// Obtener el modo de display actual
|
||||
const auto *dm = SDL_GetCurrentDisplayMode(displays[0]);
|
||||
if (dm == nullptr) {
|
||||
SDL_free(displays);
|
||||
return WINDOW_ZOOM_MIN;
|
||||
}
|
||||
|
||||
// Calcular zoom máximo usando la fórmula de Coffee Crisis
|
||||
const int MAX_ZOOM = std::min(dm->w / SCREEN_WIDTH, (dm->h - WINDOW_DECORATION_HEIGHT) / SCREEN_HEIGHT);
|
||||
|
||||
SDL_free(displays);
|
||||
|
||||
// Aplicar límites
|
||||
return std::max(WINDOW_ZOOM_MIN, std::min(MAX_ZOOM, WINDOW_ZOOM_MAX));
|
||||
}
|
||||
|
||||
void Engine::setWindowZoom(int new_zoom) {
|
||||
// Validar zoom
|
||||
int max_zoom = calculateMaxWindowZoom();
|
||||
new_zoom = std::max(WINDOW_ZOOM_MIN, std::min(new_zoom, max_zoom));
|
||||
|
||||
if (new_zoom == current_window_zoom_) {
|
||||
return; // No hay cambio
|
||||
}
|
||||
|
||||
// Obtener posición actual del centro de la ventana
|
||||
int current_x, current_y;
|
||||
SDL_GetWindowPosition(window_, ¤t_x, ¤t_y);
|
||||
int current_center_x = current_x + (SCREEN_WIDTH * current_window_zoom_) / 2;
|
||||
int current_center_y = current_y + (SCREEN_HEIGHT * current_window_zoom_) / 2;
|
||||
|
||||
// Calcular nuevo tamaño
|
||||
int new_width = SCREEN_WIDTH * new_zoom;
|
||||
int new_height = SCREEN_HEIGHT * new_zoom;
|
||||
|
||||
// Calcular nueva posición (centrada en el punto actual)
|
||||
int new_x = current_center_x - new_width / 2;
|
||||
int new_y = current_center_y - new_height / 2;
|
||||
|
||||
// Obtener límites del escritorio para no salirse
|
||||
SDL_Rect display_bounds;
|
||||
if (SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &display_bounds) == 0) {
|
||||
// Aplicar márgenes
|
||||
int min_x = WINDOW_DESKTOP_MARGIN;
|
||||
int min_y = WINDOW_DESKTOP_MARGIN;
|
||||
int max_x = display_bounds.w - new_width - WINDOW_DESKTOP_MARGIN;
|
||||
int max_y = display_bounds.h - new_height - WINDOW_DESKTOP_MARGIN - WINDOW_DECORATION_HEIGHT;
|
||||
|
||||
// Limitar posición
|
||||
new_x = std::max(min_x, std::min(new_x, max_x));
|
||||
new_y = std::max(min_y, std::min(new_y, max_y));
|
||||
}
|
||||
|
||||
// Aplicar cambios
|
||||
SDL_SetWindowSize(window_, new_width, new_height);
|
||||
SDL_SetWindowPosition(window_, new_x, new_y);
|
||||
current_window_zoom_ = new_zoom;
|
||||
}
|
||||
|
||||
void Engine::zoomIn() {
|
||||
setWindowZoom(current_window_zoom_ + 1);
|
||||
}
|
||||
|
||||
void Engine::zoomOut() {
|
||||
setWindowZoom(current_window_zoom_ - 1);
|
||||
}
|
||||
129
source/engine.h
Normal file
129
source/engine.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_events.h> // for SDL_Event
|
||||
#include <SDL3/SDL_render.h> // for SDL_Renderer
|
||||
#include <SDL3/SDL_stdinc.h> // for Uint64
|
||||
#include <SDL3/SDL_video.h> // for SDL_Window
|
||||
|
||||
#include <array> // for array
|
||||
#include <memory> // for unique_ptr, shared_ptr
|
||||
#include <string> // for string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "defines.h" // for GravityDirection, ColorTheme
|
||||
#include "ball.h" // for Ball
|
||||
#include "external/texture.h" // for Texture
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
// Interfaz pública
|
||||
bool initialize();
|
||||
void run();
|
||||
void shutdown();
|
||||
|
||||
private:
|
||||
// Recursos SDL
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_Renderer* renderer_ = nullptr;
|
||||
std::shared_ptr<Texture> texture_ = nullptr;
|
||||
|
||||
// Estado del simulador
|
||||
std::vector<std::unique_ptr<Ball>> balls_;
|
||||
std::array<int, 8> test_ = {1, 10, 100, 500, 1000, 10000, 50000, 100000};
|
||||
GravityDirection current_gravity_ = GravityDirection::DOWN;
|
||||
int scenario_ = 0;
|
||||
bool should_exit_ = false;
|
||||
|
||||
// Sistema de timing
|
||||
Uint64 last_frame_time_ = 0;
|
||||
float delta_time_ = 0.0f;
|
||||
|
||||
// UI y debug
|
||||
bool show_debug_ = false;
|
||||
bool show_text_ = true;
|
||||
|
||||
// Sistema de zoom dinámico
|
||||
int current_window_zoom_ = WINDOW_ZOOM;
|
||||
std::string text_;
|
||||
int text_pos_ = 0;
|
||||
Uint64 text_init_time_ = 0;
|
||||
|
||||
// FPS y V-Sync
|
||||
Uint64 fps_last_time_ = 0;
|
||||
int fps_frame_count_ = 0;
|
||||
int fps_current_ = 0;
|
||||
std::string fps_text_ = "FPS: 0";
|
||||
bool vsync_enabled_ = true;
|
||||
std::string vsync_text_ = "VSYNC ON";
|
||||
bool fullscreen_enabled_ = false;
|
||||
bool real_fullscreen_enabled_ = false;
|
||||
|
||||
// Resolución dinámica para modo real fullscreen
|
||||
int current_screen_width_ = SCREEN_WIDTH;
|
||||
int current_screen_height_ = SCREEN_HEIGHT;
|
||||
|
||||
// Sistema de temas
|
||||
ColorTheme current_theme_ = ColorTheme::SUNSET;
|
||||
|
||||
// Estructura de tema de colores
|
||||
struct ThemeColors {
|
||||
float bg_top_r, bg_top_g, bg_top_b;
|
||||
float bg_bottom_r, bg_bottom_g, bg_bottom_b;
|
||||
int ball_colors[8][3];
|
||||
};
|
||||
|
||||
// Temas de colores definidos
|
||||
ThemeColors themes_[5] = {
|
||||
// SUNSET: Naranjas, rojos, amarillos, rosas
|
||||
{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
|
||||
{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
|
||||
{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
|
||||
{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: Colores RGB puros y subdivisiones matemáticas
|
||||
{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, 255, 0}, {0, 0, 255}, {255, 255, 0}, {0, 255, 255}, {255, 0, 255}, {128, 255, 128}, {255, 128, 128}}}
|
||||
};
|
||||
|
||||
// Batch rendering
|
||||
std::vector<SDL_Vertex> batch_vertices_;
|
||||
std::vector<int> batch_indices_;
|
||||
|
||||
// Métodos principales del loop
|
||||
void calculateDeltaTime();
|
||||
void update();
|
||||
void handleEvents();
|
||||
void render();
|
||||
|
||||
// Métodos auxiliares
|
||||
void initBalls(int value);
|
||||
void setText();
|
||||
void pushUpBalls();
|
||||
void switchBallsGravity();
|
||||
void changeGravityDirection(GravityDirection direction);
|
||||
void toggleVSync();
|
||||
void toggleFullscreen();
|
||||
void toggleRealFullscreen();
|
||||
std::string gravityDirectionToString(GravityDirection direction) const;
|
||||
|
||||
// Sistema de zoom dinámico
|
||||
int calculateMaxWindowZoom() const;
|
||||
void setWindowZoom(int new_zoom);
|
||||
void zoomIn();
|
||||
void zoomOut();
|
||||
|
||||
// Rendering
|
||||
void renderGradientBackground();
|
||||
void addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b);
|
||||
};
|
||||
4
source/external/dbgtxt.h
vendored
4
source/external/dbgtxt.h
vendored
@@ -5,7 +5,7 @@ SDL_Texture* dbg_tex = nullptr;
|
||||
SDL_Renderer* dbg_ren = nullptr;
|
||||
} // namespace
|
||||
|
||||
void dbg_init(SDL_Renderer* renderer) {
|
||||
inline void dbg_init(SDL_Renderer* renderer) {
|
||||
dbg_ren = renderer;
|
||||
Uint8 font[448] = {0x42, 0x4D, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x01, 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x18, 0xF3, 0x83, 0x83, 0xCF, 0x83, 0x87, 0x00, 0x00, 0xF3, 0x39, 0x39, 0xCF, 0x79, 0xF3, 0x00, 0x00, 0x01, 0xF9, 0x39, 0xCF, 0x61, 0xF9, 0x00, 0x00, 0x33, 0xF9, 0x03, 0xE7, 0x87, 0x81, 0x00, 0x00, 0x93, 0x03, 0x3F, 0xF3, 0x1B, 0x39, 0x00, 0x00, 0xC3, 0x3F, 0x9F, 0x39, 0x3B, 0x39, 0x00, 0x41, 0xE3, 0x03, 0xC3, 0x01, 0x87, 0x83, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xE7, 0x01, 0xC7, 0x81, 0x01, 0x83, 0x00, 0x00, 0xE7, 0x1F, 0x9B, 0xE7, 0x1F, 0x39, 0x00, 0x00, 0xE7, 0x8F, 0x39, 0xE7, 0x87, 0xF9, 0x00, 0x00, 0xC3, 0xC7, 0x39, 0xE7, 0xC3, 0xC3, 0x00, 0x00, 0x99, 0xE3, 0x39, 0xE7, 0xF1, 0xE7, 0x00, 0x00, 0x99, 0xF1, 0xB3, 0xC7, 0x39, 0xF3, 0x00, 0x00, 0x99, 0x01, 0xC7, 0xE7, 0x83, 0x81, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x83, 0xE7, 0x83, 0xEF, 0x39, 0x39, 0x00, 0x00, 0x39, 0xE7, 0x39, 0xC7, 0x11, 0x11, 0x00, 0x00, 0xF9, 0xE7, 0x39, 0x83, 0x01, 0x83, 0x00, 0x00, 0x83, 0xE7, 0x39, 0x11, 0x01, 0xC7, 0x00, 0x00, 0x3F, 0xE7, 0x39, 0x39, 0x29, 0x83, 0x00, 0x00, 0x33, 0xE7, 0x39, 0x39, 0x39, 0x11, 0x00, 0x00, 0x87, 0x81, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x39, 0x39, 0x83, 0x3F, 0x85, 0x31, 0x00, 0x00, 0x39, 0x31, 0x39, 0x3F, 0x33, 0x23, 0x00, 0x00, 0x29, 0x21, 0x39, 0x03, 0x21, 0x07, 0x00, 0x00, 0x01, 0x01, 0x39, 0x39, 0x39, 0x31, 0x00, 0x00, 0x01, 0x09, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0x11, 0x19, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0x39, 0x39, 0x83, 0x03, 0x83, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xC1, 0x39, 0x81, 0x83, 0x31, 0x01, 0x00, 0x00, 0x99, 0x39, 0xE7, 0x39, 0x23, 0x3F, 0x00, 0x00, 0x39, 0x39, 0xE7, 0xF9, 0x07, 0x3F, 0x00, 0x00, 0x31, 0x01, 0xE7, 0xF9, 0x0F, 0x3F, 0x00, 0x00, 0x3F, 0x39, 0xE7, 0xF9, 0x27, 0x3F, 0x00, 0x00, 0x9F, 0x39, 0xE7, 0xF9, 0x33, 0x3F, 0x00, 0x00, 0xC1, 0x39, 0x81, 0xF9, 0x39, 0x3F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x39, 0x03, 0xC3, 0x07, 0x01, 0x3F, 0x00, 0x00, 0x39, 0x39, 0x99, 0x33, 0x3F, 0x3F, 0x00, 0x00, 0x01, 0x39, 0x3F, 0x39, 0x3F, 0x3F, 0x00, 0x00, 0x39, 0x03, 0x3F, 0x39, 0x03, 0x03, 0x00, 0x00, 0x39, 0x39, 0x3F, 0x39, 0x3F, 0x3F, 0x00, 0x00, 0x93, 0x39, 0x99, 0x33, 0x3F, 0x3F, 0x00, 0x00, 0xC7, 0x03, 0xC3, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
@@ -55,7 +55,7 @@ void dbg_init(SDL_Renderer* renderer) {
|
||||
}
|
||||
}
|
||||
|
||||
void dbg_print(int x, int y, const char* text, Uint8 r, Uint8 g, Uint8 b) {
|
||||
inline void dbg_print(int x, int y, const char* text, Uint8 r, Uint8 g, Uint8 b) {
|
||||
int cc = 0;
|
||||
SDL_SetTextureColorMod(dbg_tex, r, g, b);
|
||||
SDL_FRect src = {0, 0, 8, 8};
|
||||
|
||||
599
source/main.cpp
599
source/main.cpp
@@ -1,596 +1,17 @@
|
||||
#include <SDL3/SDL_error.h> // for SDL_GetError
|
||||
#include <SDL3/SDL_events.h> // for SDL_EventType, SDL_PollEvent, SDL_Event
|
||||
#include <SDL3/SDL_init.h> // for SDL_Init, SDL_Quit, SDL_INIT_VIDEO
|
||||
#include <SDL3/SDL_keycode.h> // for SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5
|
||||
#include <SDL3/SDL_render.h> // for SDL_SetRenderDrawColor, SDL_CreateRend...
|
||||
#include <SDL3/SDL_stdinc.h> // for Uint64
|
||||
#include <SDL3/SDL_timer.h> // for SDL_GetTicks
|
||||
#include <SDL3/SDL_video.h> // for SDL_CreateWindow, SDL_DestroyWindow
|
||||
#include <iostream>
|
||||
|
||||
#include <array> // for array
|
||||
#include <cstdlib> // for rand, srand
|
||||
#include <ctime> // for time
|
||||
#include <iostream> // for basic_ostream, char_traits, operator<<
|
||||
#include <memory> // for unique_ptr, shared_ptr, make_shared
|
||||
#include <string> // for operator+, string, to_string
|
||||
#include <vector> // for vector
|
||||
#include "engine.h"
|
||||
|
||||
#include "ball.h" // for Ball
|
||||
#include "defines.h" // for SCREEN_WIDTH, SCREEN_HEIGHT, WINDOW_SIZE
|
||||
#include "external/dbgtxt.h" // for dbg_init, dbg_print
|
||||
#include "external/texture.h" // for Texture
|
||||
int main() {
|
||||
Engine engine;
|
||||
|
||||
// Variables globales
|
||||
SDL_Window *window = nullptr;
|
||||
SDL_Renderer *renderer = nullptr;
|
||||
std::shared_ptr<Texture> texture = nullptr;
|
||||
std::vector<std::unique_ptr<Ball>> balls;
|
||||
std::array<int, 8> test = {1, 10, 100, 500, 1000, 10000, 50000, 100000};
|
||||
|
||||
bool should_exit = false; // Controla si la aplicación debe cerrarse
|
||||
// ticks eliminado - reemplazado por delta time system
|
||||
int scenario = 0; // Escenario actual basado en el número de bolas
|
||||
std::string text; // Texto a mostrar en pantalla
|
||||
int text_pos = 0; // Posición del texto en la pantalla
|
||||
bool show_text = true; // Determina si el texto se debe mostrar
|
||||
Uint64 text_init_time = 0; // Temporizador para mostrar el texto
|
||||
|
||||
// Variables para contador de FPS
|
||||
Uint64 fps_last_time = 0; // Último tiempo para cálculo de FPS
|
||||
int fps_frame_count = 0; // Contador de frames
|
||||
int fps_current = 0; // FPS actuales calculados
|
||||
std::string fps_text = "FPS: 0"; // Texto del contador de FPS
|
||||
|
||||
// Variables para V-Sync
|
||||
bool vsync_enabled = true; // Estado inicial del V-Sync (activado por defecto)
|
||||
std::string vsync_text = "VSYNC ON"; // Texto del estado V-Sync
|
||||
|
||||
// Variables para Delta Time
|
||||
Uint64 last_frame_time = 0; // Tiempo del último frame en milisegundos
|
||||
float delta_time = 0.0f; // Tiempo transcurrido desde el último frame en segundos
|
||||
|
||||
// Variables para Debug Display
|
||||
bool show_debug = false; // Debug display desactivado por defecto
|
||||
|
||||
// Variable para direcci\u00f3n de gravedad
|
||||
GravityDirection current_gravity = GravityDirection::DOWN; // Gravedad inicial hacia abajo
|
||||
|
||||
// Sistema de temas de colores
|
||||
enum class ColorTheme {
|
||||
SUNSET = 0,
|
||||
OCEAN = 1,
|
||||
NEON = 2,
|
||||
FOREST = 3
|
||||
};
|
||||
|
||||
ColorTheme current_theme = ColorTheme::SUNSET;
|
||||
std::string theme_names[] = {"SUNSET", "OCEAN", "NEON", "FOREST"};
|
||||
|
||||
struct ThemeColors {
|
||||
// Colores de fondo (superior -> inferior)
|
||||
float bg_top_r, bg_top_g, bg_top_b;
|
||||
float bg_bottom_r, bg_bottom_g, bg_bottom_b;
|
||||
|
||||
// Paletas de colores para bolas (RGB 0-255)
|
||||
int ball_colors[8][3]; // 8 colores por tema
|
||||
};
|
||||
|
||||
ThemeColors themes[4] = {
|
||||
// SUNSET: Naranjas, rojos, amarillos, rosas
|
||||
{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, cianes, verdes agua, blancos
|
||||
{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
|
||||
{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
|
||||
{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}}}};
|
||||
|
||||
// Variables para Batch Rendering
|
||||
std::vector<SDL_Vertex> batch_vertices;
|
||||
std::vector<int> batch_indices;
|
||||
|
||||
// Función para renderizar fondo degradado
|
||||
void 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)];
|
||||
|
||||
float top_r = theme.bg_top_r;
|
||||
float top_g = theme.bg_top_g;
|
||||
float top_b = theme.bg_top_b;
|
||||
|
||||
float bottom_r = theme.bg_bottom_r;
|
||||
float bottom_g = theme.bg_bottom_g;
|
||||
float bottom_b = theme.bg_bottom_b;
|
||||
|
||||
// Vértice superior izquierdo
|
||||
bg_vertices[0].position = {0, 0};
|
||||
bg_vertices[0].tex_coord = {0.0f, 0.0f};
|
||||
bg_vertices[0].color = {top_r, top_g, top_b, 1.0f};
|
||||
|
||||
// Vértice superior derecho
|
||||
bg_vertices[1].position = {SCREEN_WIDTH, 0};
|
||||
bg_vertices[1].tex_coord = {1.0f, 0.0f};
|
||||
bg_vertices[1].color = {top_r, top_g, top_b, 1.0f};
|
||||
|
||||
// Vértice inferior derecho
|
||||
bg_vertices[2].position = {SCREEN_WIDTH, SCREEN_HEIGHT};
|
||||
bg_vertices[2].tex_coord = {1.0f, 1.0f};
|
||||
bg_vertices[2].color = {bottom_r, bottom_g, bottom_b, 1.0f};
|
||||
|
||||
// Vértice inferior izquierdo
|
||||
bg_vertices[3].position = {0, SCREEN_HEIGHT};
|
||||
bg_vertices[3].tex_coord = {0.0f, 1.0f};
|
||||
bg_vertices[3].color = {bottom_r, bottom_g, bottom_b, 1.0f};
|
||||
|
||||
// Índices para 2 triángulos
|
||||
int bg_indices[6] = {0, 1, 2, 2, 3, 0};
|
||||
|
||||
// Renderizar sin textura (nullptr)
|
||||
SDL_RenderGeometry(renderer, nullptr, bg_vertices, 4, bg_indices, 6);
|
||||
}
|
||||
|
||||
// Función para añadir un sprite al batch
|
||||
void addSpriteToBatch(float x, float y, float w, float h, Uint8 r, Uint8 g, Uint8 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 Uint8 (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]);
|
||||
if (!engine.initialize()) {
|
||||
std::cout << "¡Error al inicializar el engine!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Añadir índices para 2 triángulos (6 índices por sprite)
|
||||
// Triángulo 1: 0,1,2
|
||||
batch_indices.push_back(vertex_index + 0);
|
||||
batch_indices.push_back(vertex_index + 1);
|
||||
batch_indices.push_back(vertex_index + 2);
|
||||
|
||||
// Triángulo 2: 2,3,0
|
||||
batch_indices.push_back(vertex_index + 2);
|
||||
batch_indices.push_back(vertex_index + 3);
|
||||
batch_indices.push_back(vertex_index + 0);
|
||||
}
|
||||
|
||||
// Establece el texto en pantalla mostrando el número de bolas actuales
|
||||
void setText() {
|
||||
const std::string TEXT_NUMBER = test[scenario] == 1 ? " PELOTA" : " PELOTAS";
|
||||
text = std::to_string(test[scenario]) + TEXT_NUMBER;
|
||||
const int TEXT_SIZE = static_cast<int>(text.size() * 8);
|
||||
text_pos = SCREEN_WIDTH / 2 - TEXT_SIZE / 2;
|
||||
text_init_time = SDL_GetTicks();
|
||||
show_text = true;
|
||||
}
|
||||
|
||||
// Inicializa las bolas según el escenario seleccionado
|
||||
void initBalls(int value) {
|
||||
balls.clear();
|
||||
for (int i = 0; i < test.at(value); ++i) {
|
||||
const int SIGN = ((rand() % 2) * 2) - 1; // Genera un signo aleatorio (+ o -)
|
||||
const float X = (rand() % (SCREEN_WIDTH / 2)) + (SCREEN_WIDTH / 4); // 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() % 8; // 8 colores por tema
|
||||
const Color COLOR = {theme.ball_colors[color_index][0],
|
||||
theme.ball_colors[color_index][1],
|
||||
theme.ball_colors[color_index][2]};
|
||||
balls.emplace_back(std::make_unique<Ball>(X, VX, VY, COLOR, texture, current_gravity));
|
||||
}
|
||||
setText(); // Actualiza el texto
|
||||
}
|
||||
|
||||
// Aumenta la velocidad vertical de las bolas "hacia arriba"
|
||||
void pushUpBalls() {
|
||||
for (auto &ball : balls) {
|
||||
const int SIGNO = ((rand() % 2) * 2) - 1;
|
||||
const float VX = (((rand() % 20) + 10) * 0.1f) * SIGNO;
|
||||
const float VY = ((rand() % 40) * 0.1f) + 5;
|
||||
ball->modVel(VX, -VY); // Modifica la velocidad de la bola
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia la gravedad de todas las bolas
|
||||
void switchBallsGravity() {
|
||||
for (auto &ball : balls) {
|
||||
ball->switchGravity();
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia la direcci\u00f3n de gravedad de todas las bolas
|
||||
void changeGravityDirection(GravityDirection new_direction) {
|
||||
current_gravity = new_direction;
|
||||
for (auto &ball : balls) {
|
||||
ball->setGravityDirection(new_direction);
|
||||
}
|
||||
}
|
||||
|
||||
// Convierte dirección de gravedad a texto para debug
|
||||
std::string gravityDirectionToString(GravityDirection direction) {
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna el estado del V-Sync
|
||||
void toggleVSync() {
|
||||
vsync_enabled = !vsync_enabled;
|
||||
vsync_text = vsync_enabled ? "VSYNC ON" : "VSYNC OFF";
|
||||
|
||||
// Aplicar el cambio de V-Sync al renderizador
|
||||
SDL_SetRenderVSync(renderer, vsync_enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
// Calcula el delta time entre frames
|
||||
void 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; // Usar 60 FPS como fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializa SDL y configura los componentes principales
|
||||
bool init() {
|
||||
bool success = true;
|
||||
|
||||
// Inicializa SDL
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
std::cout << "¡SDL no se pudo inicializar! Error de SDL: " << SDL_GetError() << std::endl;
|
||||
success = false;
|
||||
} else {
|
||||
// Crear ventana principal
|
||||
window = SDL_CreateWindow(WINDOW_CAPTION, SCREEN_WIDTH * WINDOW_SIZE, SCREEN_HEIGHT * WINDOW_SIZE, SDL_WINDOW_OPENGL);
|
||||
if (window == nullptr) {
|
||||
std::cout << "¡No se pudo crear la ventana! Error de SDL: " << SDL_GetError() << std::endl;
|
||||
success = false;
|
||||
} else {
|
||||
// Crear renderizador
|
||||
renderer = SDL_CreateRenderer(window, nullptr);
|
||||
if (renderer == nullptr) {
|
||||
std::cout << "¡No se pudo crear el renderizador! Error de SDL: " << SDL_GetError() << std::endl;
|
||||
success = false;
|
||||
} else {
|
||||
// Establecer color inicial del renderizador
|
||||
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
|
||||
|
||||
// Establecer tamaño lógico para el renderizado
|
||||
SDL_SetRenderLogicalPresentation(renderer, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
||||
|
||||
// Configurar V-Sync inicial
|
||||
SDL_SetRenderVSync(renderer, vsync_enabled ? 1 : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar otros componentes
|
||||
texture = std::make_shared<Texture>(renderer, "data/ball.png");
|
||||
// ticks eliminado - delta time system se inicializa automáticamente
|
||||
srand(static_cast<unsigned>(time(nullptr)));
|
||||
dbg_init(renderer);
|
||||
initBalls(scenario);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Limpia todos los recursos y cierra SDL
|
||||
void close() {
|
||||
SDL_DestroyRenderer(renderer);
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
// Verifica los eventos en la cola
|
||||
void checkEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event) != 0) {
|
||||
// Evento de salida
|
||||
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:
|
||||
pushUpBalls();
|
||||
break;
|
||||
|
||||
case SDLK_G:
|
||||
switchBallsGravity();
|
||||
break;
|
||||
|
||||
// Controles de direcci\u00f3n 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) % 4);
|
||||
initBalls(scenario); // Regenerar bolas con nueva paleta
|
||||
break;
|
||||
|
||||
case SDLK_F1:
|
||||
current_theme = ColorTheme::SUNSET;
|
||||
initBalls(scenario);
|
||||
break;
|
||||
|
||||
case SDLK_F2:
|
||||
current_theme = ColorTheme::OCEAN;
|
||||
initBalls(scenario);
|
||||
break;
|
||||
|
||||
case SDLK_F3:
|
||||
current_theme = ColorTheme::NEON;
|
||||
initBalls(scenario);
|
||||
break;
|
||||
|
||||
case SDLK_F4:
|
||||
current_theme = ColorTheme::FOREST;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza la lógica del juego
|
||||
void 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza el contenido en la pantalla
|
||||
void render() {
|
||||
// Renderizar fondo degradado en lugar de color sólido
|
||||
renderGradientBackground();
|
||||
|
||||
// Limpiar batches del frame anterior
|
||||
batch_vertices.clear();
|
||||
batch_indices.clear();
|
||||
|
||||
// Recopilar datos de todas las bolas para batch rendering
|
||||
for (auto &ball : balls) {
|
||||
// En lugar de ball->render(), obtener datos para batch
|
||||
SDL_FRect pos = ball->getPosition();
|
||||
Color color = ball->getColor();
|
||||
addSpriteToBatch(pos.x, pos.y, pos.w, pos.h, color.r, color.g, color.b);
|
||||
}
|
||||
|
||||
// Renderizar todas las bolas en una sola llamada
|
||||
if (!batch_vertices.empty()) {
|
||||
SDL_RenderGeometry(renderer, texture->getSDLTexture(), batch_vertices.data(), static_cast<int>(batch_vertices.size()), batch_indices.data(), static_cast<int>(batch_indices.size()));
|
||||
}
|
||||
|
||||
if (show_text) {
|
||||
dbg_print(text_pos, 8, text.c_str(), 255, 255, 255);
|
||||
|
||||
// Mostrar nombre del tema en castellano debajo del número de pelotas
|
||||
std::string theme_names_es[] = {"ATARDECER", "OCEANO", "NEON", "BOSQUE"};
|
||||
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 = (SCREEN_WIDTH - theme_text_width) / 2; // Centrar horizontalmente
|
||||
|
||||
// Colores acordes a cada 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
|
||||
};
|
||||
int theme_idx = static_cast<int>(current_theme);
|
||||
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 = 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: Dirección de gravedad
|
||||
std::string gravity_dir_text = "GRAVITY " + gravityDirectionToString(current_gravity);
|
||||
dbg_print(8, 48, 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, 56, theme_text.c_str(), 255, 255, 128); // Amarillo claro para tema
|
||||
}
|
||||
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
// Función principal
|
||||
int main(int argc, char *args[]) {
|
||||
if (!init()) {
|
||||
std::cout << "Ocurrió un error durante la inicialización." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (!should_exit) {
|
||||
// 1. Calcular delta time antes de actualizar la lógica
|
||||
calculateDeltaTime();
|
||||
|
||||
// 2. Actualizar lógica del juego con delta time
|
||||
update();
|
||||
|
||||
// 3. Procesar eventos de entrada
|
||||
checkEvents();
|
||||
|
||||
// 4. Renderizar la escena
|
||||
render();
|
||||
}
|
||||
|
||||
close();
|
||||
engine.run();
|
||||
engine.shutdown();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user