4 Commits

Author SHA1 Message Date
0d49a6e814 Mejorar DEMO MODE + Añadir DEMO LITE MODE (Tecla L)
MEJORAS DEMO MODE (Tecla D):
 **Randomización completa al activar**: escenario, tema, sprite, física/figura, gravedad, profundidad, escala
 **Excluye escenarios problemáticos**: 1, 50K, 100K pelotas (índices 0, 6, 7)
 **Nuevas acciones dinámicas**:
   - Toggle gravedad ON/OFF (8%)
   - Toggle física ↔ figura (12%)
   - Re-generar misma figura (8%)
 **Intervalos más rápidos**: 2-6s (antes 3-8s)
 **SIN TEXTOS** durante demo (solo "DEMO MODE")
 **Pesos rebalanceados**: Más variedad y dinamismo

NUEVO: DEMO LITE MODE (Tecla L):
 **Solo física/figuras**: NO cambia escenario, tema, sprite, profundidad, escala
 **Randomización inicial lite**: física/figura + gravedad
 **Acciones lite**:
   - Cambiar dirección gravedad (25%)
   - Toggle gravedad ON/OFF (20%)
   - Activar figura 3D (25%)
   - Toggle física ↔ figura (20%)
   - Aplicar impulso (10%)
 **Intervalos ultra-rápidos**: 1.5-4s
 **Display visual**: "DEMO LITE" en azul claro (128, 200, 255)
 **Mutuamente excluyente**: D y L no pueden estar activos a la vez

CAMBIOS TÉCNICOS:
- Nuevas constantes en defines.h: DEMO_LITE_* (intervalos + pesos)
- Nuevos métodos:
  * `randomizeOnDemoStart(bool is_lite)` - Randomización inicial
  * `toggleGravityOnOff()` - Activar/desactivar gravedad
- `performDemoAction()` ahora recibe parámetro `is_lite`
- Suprimidos textos en: setText(), startThemeTransition(), switchTexture(), toggleShapeMode(), activateShape()
- DEMO MODE nunca cambia dimensiones de ventana ni modo pantalla

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-04 11:47:20 +02:00
d0b144dddc Actualizar ROADMAP: Modo DEMO completado + mejoras animaciones 3D 2025-10-04 11:29:06 +02:00
06aabc53c0 Implementar Modo DEMO (auto-play) con tecla D
CAMBIOS PRINCIPALES:
-  **Modo DEMO toggleable con tecla D** - Auto-play inteligente
-  **Sistema de acciones aleatorias** - Cada 3-8 segundos
-  **Totalmente interactivo** - Usuario puede seguir controlando
-  **Eliminado sistema auto-restart antiguo** - Ya no reinicia al pararse

CARACTERÍSTICAS DEMO MODE:
- **Acciones parametrizables** con pesos de probabilidad:
  * Cambiar gravedad (UP/DOWN/LEFT/RIGHT) - 15%
  * Activar figuras 3D (8 figuras) - 25%
  * Cambiar temas de colores (6 temas) - 20%
  * Cambiar número de pelotas (1-100K) - 15%
  * Impulsos (SPACE) - 10%
  * Toggle profundidad Z - 5%
  * Cambiar escala de figura - 5%
  * Cambiar sprite - 5%

- **Display visual**: "DEMO MODE" centrado en naranja brillante
- **Textos de feedback**: "DEMO MODE ON/OFF" al togglear

CÓDIGO ELIMINADO:
-  `checkAutoRestart()` y `performRandomRestart()` (ya no necesarios)
-  `Ball::isStopped()` y variable `stopped_` (sin uso)
-  Variables `all_balls_stopped_start_time_`, `all_balls_were_stopped_`

CONSTANTES CONFIGURABLES (defines.h):
- `DEMO_ACTION_INTERVAL_MIN/MAX` (3-8s entre acciones)
- `DEMO_WEIGHT_*` (pesos para priorizar acciones)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-04 11:28:16 +02:00
2ae515592d Mejoras animaciones 3D: tumbling en cilindro + pivoteo en wave grid
- **CylinderShape**: Añadido sistema de tumbling ocasional
  - Volteretas de 90° en eje X cada 3-5 segundos
  - Interpolación suave con ease-in-out (1.5s)
  - Rotación Y continua + tumbling X ocasional = más dinámico

- **WaveGridShape**: Reemplazada rotación por pivoteo sutil
  - Eliminada rotación completa en eje Y
  - Pivoteo en esquinas (oscilación lenta 0.3/0.5 rad/s)
  - Grid paralelo a pantalla con efecto "sábana ondeando"
  - Ondas sinusoidales + pivoteo = movimiento más orgánico

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-04 11:14:29 +02:00
10 changed files with 534 additions and 109 deletions

View File

@@ -56,21 +56,47 @@
- Estados: NORMAL, TRANSITION, ALTERNATIVE
- Interpolación suave entre modos de rotación
### 2. Modo DEMO (Auto-play)
### 2. Modo DEMO (Auto-play)
**Descripción:** Modo demostración automática con acciones aleatorias
**Prioridad:** Alta
**Estimación:** 1.5 horas
**Estado:** ✅ COMPLETADO
**Detalles:**
- Toggle con tecla `D`
- Timer que ejecuta acciones cada 3-8 segundos
- Acciones: cambiar gravedad, activar figura, cambiar tema, impulso
- Secuencia pseudo-aleatoria pero visualmente coherente
- Pausable con cualquier tecla de usuario
- Indicador visual "DEMO MODE" en pantalla
- Toggle con tecla `D`
- Timer que ejecuta acciones cada 3-8 segundos (configurable)
- Acciones: gravedad, figuras, temas, escenarios, impulso, profundidad, escala, sprite
- Secuencia pseudo-aleatoria con pesos configurables (defines.h)
- ✅ Totalmente interactivo - usuario puede seguir usando controles
- Indicador visual "DEMO MODE" centrado en pantalla (naranja)
- ✅ Eliminado sistema auto-restart antiguo (ya no necesario)
### 3. ⏳ Sistema de Release
**Descripción:** Empaquetado para distribución standalone
### 3. ⏳ Resolución Lógica Configurable
**Descripción:** Especificar resolución lógica por parámetros de línea de comandos
**Prioridad:** Media
**Estimación:** 1 hora
**Detalles:**
- Parámetros `--width <px> --height <px>` o `-w <px> -h <px>`
- Parámetro `--fullscreen` o `-f` (opcional)
- Defaults: 1280x720 en ventana
- Ejemplo: `./vibe3_physics -w 1920 -h 1080 -f`
- Validación de valores (mínimo 640x480, máximo según SDL_DisplayMode)
- Help text con `-h` o `--help`
### 4. 🐛 Corregir Escalado de Pelotas en Reposo
**Descripción:** Las pelotas cambian de tamaño cuando están quietas (bug visual)
**Prioridad:** Alta (bug visible)
**Estimación:** 30 minutos
**Detalles:**
- **Síntoma:** Pelotas en reposo (velocidad ≈ 0) se escalan incorrectamente
- **Posible causa:**
- Scale factor calculado desde velocidad o energía
- División por cero o valor muy pequeño
- Interpolación incorrecta en modo transición
- **Investigar:** Ball::render(), scale calculations, depth brightness
- **Solución esperada:** Tamaño constante independiente de velocidad
### 5. ⏳ Sistema de Release
**Descripción:** Empaquetado para distribución standalone
**Prioridad:** Baja
**Estimación:** 30 minutos
**Detalles:**
- Crear carpeta `release/`
@@ -110,6 +136,15 @@
## Historial de Cambios
### 2025-10-04 (Sesión 4) - Modo DEMO
-**Implementado Modo DEMO (auto-play)** - Tecla D para toggle
- ✅ Sistema de acciones aleatorias cada 3-8 segundos (configurable)
- ✅ 8 tipos de acciones con pesos de probabilidad ajustables
- ✅ Totalmente interactivo - usuario puede seguir controlando
- ✅ Display visual "DEMO MODE" centrado en naranja
- ✅ Mejoras animaciones 3D: tumbling en cilindro + pivoteo en wave grid
- ❌ Eliminado sistema auto-restart antiguo (ya no necesario)
### 2025-10-04 (Sesión 3)
- ✅ Implementado tema MONOCHROME (6º tema)
- ✅ Sistema LERP para transiciones suaves de temas (0.5s)

View File

@@ -39,7 +39,6 @@ Ball::Ball(float x, float vx, float vy, Color color, std::shared_ptr<Texture> te
screen_width_ = screen_width; // Dimensiones del terreno de juego
screen_height_ = screen_height;
on_surface_ = false;
stopped_ = false;
// Coeficiente base IGUAL para todas las pelotas (solo variación por rebote individual)
loss_ = BASE_BOUNCE_COEFFICIENT; // Coeficiente fijo para todas las pelotas
@@ -56,10 +55,6 @@ Ball::Ball(float x, float vx, float vy, Color color, std::shared_ptr<Texture> te
// Actualiza la lógica de la clase
void Ball::update(float deltaTime) {
if (stopped_) {
return;
}
// Aplica la gravedad según la dirección (píxeles/segundo²)
if (!on_surface_) {
// Aplicar gravedad multiplicada por factor de masa individual
@@ -190,7 +185,6 @@ void Ball::update(float deltaTime) {
vx_ = vx_ * friction_factor;
if (std::fabs(vx_) < 6.0f) {
vx_ = 0.0f;
stopped_ = true;
}
break;
case GravityDirection::LEFT:
@@ -199,7 +193,6 @@ void Ball::update(float deltaTime) {
vy_ = vy_ * friction_factor;
if (std::fabs(vy_) < 6.0f) {
vy_ = 0.0f;
stopped_ = true;
}
break;
}
@@ -220,7 +213,6 @@ void Ball::modVel(float vx, float vy) {
vx_ = vx_ + (vx * 60.0f); // Convertir a píxeles/segundo
vy_ = vy_ + (vy * 60.0f); // Convertir a píxeles/segundo
on_surface_ = false;
stopped_ = false;
}
// Cambia la gravedad (usa la versión convertida)
@@ -249,7 +241,6 @@ void Ball::forceGravityOff() {
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
@@ -309,7 +300,6 @@ void Ball::enableRotoBallAttraction(bool enable) {
// Al activar atracción, resetear flags de superficie para permitir física completa
if (enable) {
on_surface_ = false;
stopped_ = false;
}
}

View File

@@ -20,7 +20,6 @@ class Ball {
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;
float loss_; // Coeficiente de rebote. Pérdida de energía en cada rebote
// Datos para modo RotoBall (esfera 3D)
@@ -71,7 +70,6 @@ class Ball {
float getLossCoefficient() const { return loss_; }
GravityDirection getGravityDirection() const { return gravity_direction_; }
bool isOnSurface() const { return on_surface_; }
bool isStopped() const { return stopped_; }
// Getters/Setters para batch rendering
SDL_FRect getPosition() const { return pos_; }

View File

@@ -153,4 +153,34 @@ constexpr float SHAPE_SCALE_MAX = 3.0f; // Escala máxima (300%)
constexpr float SHAPE_SCALE_STEP = 0.1f; // Incremento por pulsación
constexpr float SHAPE_SCALE_DEFAULT = 1.0f; // Escala por defecto (100%)
// Configuración de Modo DEMO (auto-play completo)
constexpr float DEMO_ACTION_INTERVAL_MIN = 2.0f; // Tiempo mínimo entre acciones (segundos)
constexpr float DEMO_ACTION_INTERVAL_MAX = 6.0f; // Tiempo máximo entre acciones (segundos)
// Pesos de probabilidad DEMO MODE (valores relativos, se normalizan)
constexpr int DEMO_WEIGHT_GRAVITY_DIR = 10; // Cambiar dirección gravedad (10%)
constexpr int DEMO_WEIGHT_GRAVITY_TOGGLE = 8; // Toggle gravedad ON/OFF (8%)
constexpr int DEMO_WEIGHT_SHAPE = 20; // Activar figura 3D (20%)
constexpr int DEMO_WEIGHT_TOGGLE_PHYSICS = 12; // Toggle física ↔ figura (12%)
constexpr int DEMO_WEIGHT_REGENERATE_SHAPE = 8; // Re-generar misma figura (8%)
constexpr int DEMO_WEIGHT_THEME = 15; // Cambiar tema de colores (15%)
constexpr int DEMO_WEIGHT_SCENARIO = 10; // Cambiar número de pelotas (10%)
constexpr int DEMO_WEIGHT_IMPULSE = 10; // Aplicar impulso (SPACE) (10%)
constexpr int DEMO_WEIGHT_DEPTH_ZOOM = 3; // Toggle profundidad (3%)
constexpr int DEMO_WEIGHT_SHAPE_SCALE = 2; // Cambiar escala figura (2%)
constexpr int DEMO_WEIGHT_SPRITE = 2; // Cambiar sprite (2%)
// TOTAL: 100
// Configuración de Modo DEMO LITE (solo física/figuras)
constexpr float DEMO_LITE_ACTION_INTERVAL_MIN = 1.5f; // Más rápido que demo completo
constexpr float DEMO_LITE_ACTION_INTERVAL_MAX = 4.0f;
// Pesos de probabilidad DEMO LITE (solo física/figuras, sin cambios de escenario/tema)
constexpr int DEMO_LITE_WEIGHT_GRAVITY_DIR = 25; // Cambiar dirección gravedad (25%)
constexpr int DEMO_LITE_WEIGHT_GRAVITY_TOGGLE = 20;// Toggle gravedad ON/OFF (20%)
constexpr int DEMO_LITE_WEIGHT_SHAPE = 25; // Activar figura 3D (25%)
constexpr int DEMO_LITE_WEIGHT_TOGGLE_PHYSICS = 20;// Toggle física ↔ figura (20%)
constexpr int DEMO_LITE_WEIGHT_IMPULSE = 10; // Aplicar impulso (10%)
// TOTAL: 100
constexpr float PI = 3.14159265358979323846f; // Constante PI

View File

@@ -166,9 +166,6 @@ void Engine::update() {
for (auto &ball : balls_) {
ball->update(delta_time_); // Pasar delta time a cada pelota
}
// Verificar auto-reinicio cuando todas las pelotas están quietas (solo en modo física)
checkAutoRestart();
} else if (current_mode_ == SimulationMode::SHAPE) {
// Modo Figura 3D: actualizar figura polimórfica
updateShape();
@@ -179,6 +176,9 @@ void Engine::update() {
show_text_ = !(SDL_GetTicks() - text_init_time_ > TEXT_DURATION);
}
// Actualizar Modo DEMO (auto-play)
updateDemoMode();
// Actualizar transición de tema (LERP)
if (transitioning_) {
transition_progress_ += delta_time_ / transition_duration_;
@@ -468,6 +468,60 @@ void Engine::handleEvents() {
case SDLK_F5:
toggleIntegerScaling();
break;
// Toggle Modo DEMO COMPLETO (auto-play)
case SDLK_D:
demo_mode_enabled_ = !demo_mode_enabled_;
if (demo_mode_enabled_) {
// Desactivar demo lite si estaba activo (mutuamente excluyentes)
demo_lite_enabled_ = false;
// Randomizar TODO al activar
randomizeOnDemoStart(false);
// Inicializar timer con primer intervalo aleatorio
demo_timer_ = 0.0f;
float interval_range = DEMO_ACTION_INTERVAL_MAX - DEMO_ACTION_INTERVAL_MIN;
demo_next_action_time_ = DEMO_ACTION_INTERVAL_MIN + (rand() % 1000) / 1000.0f * interval_range;
// Mostrar texto de activación
text_ = "DEMO MODE ON";
show_text_ = true;
text_init_time_ = SDL_GetTicks();
} else {
// Al desactivar: mostrar texto
text_ = "DEMO MODE OFF";
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
break;
// Toggle Modo DEMO LITE (solo física/figuras)
case SDLK_L:
demo_lite_enabled_ = !demo_lite_enabled_;
if (demo_lite_enabled_) {
// Desactivar demo completo si estaba activo (mutuamente excluyentes)
demo_mode_enabled_ = false;
// Randomizar solo física/figura (mantiene escenario y tema)
randomizeOnDemoStart(true);
// Inicializar timer con primer intervalo aleatorio (más rápido)
demo_timer_ = 0.0f;
float interval_range = DEMO_LITE_ACTION_INTERVAL_MAX - DEMO_LITE_ACTION_INTERVAL_MIN;
demo_next_action_time_ = DEMO_LITE_ACTION_INTERVAL_MIN + (rand() % 1000) / 1000.0f * interval_range;
// Mostrar texto de activación
text_ = "DEMO LITE ON";
show_text_ = true;
text_init_time_ = SDL_GetTicks();
} else {
// Al desactivar: mostrar texto
text_ = "DEMO LITE OFF";
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
break;
}
}
}
@@ -608,6 +662,21 @@ void Engine::render() {
dbg_print(8, 72, mode_text.c_str(), 0, 255, 128); // Verde claro para modo
}
// Mostrar indicador "DEMO MODE" o "DEMO LITE" permanente (independiente de show_debug_)
if (demo_mode_enabled_) {
// Centrar texto horizontalmente
const char* demo_text = "DEMO MODE";
int demo_text_width = static_cast<int>(strlen(demo_text) * 8); // 8 píxeles por carácter
int demo_x = (current_screen_width_ - demo_text_width) / 2;
dbg_print(demo_x, 8, demo_text, 255, 128, 0); // Naranja brillante
} else if (demo_lite_enabled_) {
// Centrar texto horizontalmente
const char* demo_lite_text = "DEMO LITE";
int demo_lite_text_width = static_cast<int>(strlen(demo_lite_text) * 8);
int demo_lite_x = (current_screen_width_ - demo_lite_text_width) / 2;
dbg_print(demo_lite_x, 8, demo_lite_text, 128, 200, 255); // Azul claro
}
SDL_RenderPresent(renderer_);
}
@@ -645,6 +714,9 @@ void Engine::initBalls(int value) {
}
void Engine::setText() {
// Suprimir textos durante modos demo
if (demo_mode_enabled_ || demo_lite_enabled_) return;
int num_balls = test_.at(scenario_);
if (num_balls == 1) {
text_ = "1 PELOTA";
@@ -1137,12 +1209,14 @@ void Engine::startThemeTransition(ColorTheme new_theme) {
transitioning_ = true;
transition_progress_ = 0.0f;
// Mostrar nombre del tema (igual que selección directa con KP_1-6)
ThemeColors& theme = themes_[static_cast<int>(new_theme)];
text_ = theme.name_es;
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
// Mostrar nombre del tema (solo si NO estamos en modo demo)
if (!demo_mode_enabled_ && !demo_lite_enabled_) {
ThemeColors& theme = themes_[static_cast<int>(new_theme)];
text_ = theme.name_es;
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
}
Color Engine::getInterpolatedColor(size_t ball_index) const {
@@ -1167,48 +1241,282 @@ Color Engine::getInterpolatedColor(size_t ball_index) const {
};
}
void Engine::checkAutoRestart() {
// Verificar si TODAS las pelotas están paradas
bool all_stopped = true;
for (const auto &ball : balls_) {
if (!ball->isStopped()) {
all_stopped = false;
break;
}
}
// Sistema de Modo DEMO (auto-play)
void Engine::updateDemoMode() {
// Verificar si algún modo está activo
bool is_demo_active = demo_mode_enabled_ || demo_lite_enabled_;
if (!is_demo_active) return;
if (all_stopped) {
if (!all_balls_were_stopped_) {
// Primera vez que se detecta que todas están paradas
all_balls_stopped_start_time_ = SDL_GetTicks();
all_balls_were_stopped_ = true;
} else {
// Ya estaban paradas, verificar tiempo transcurrido
Uint64 current_time = SDL_GetTicks();
if (current_time - all_balls_stopped_start_time_ >= AUTO_RESTART_DELAY) {
performRandomRestart();
}
}
} else {
// Al menos una pelota se está moviendo - resetear temporizador
all_balls_were_stopped_ = false;
all_balls_stopped_start_time_ = 0;
// Actualizar timer
demo_timer_ += delta_time_;
// Si es hora de ejecutar acción
if (demo_timer_ >= demo_next_action_time_) {
// Ejecutar acción según modo activo
bool is_lite = demo_lite_enabled_;
performDemoAction(is_lite);
// Resetear timer y calcular próximo intervalo aleatorio
demo_timer_ = 0.0f;
// Usar intervalos diferentes según modo
float interval_min = is_lite ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN;
float interval_max = is_lite ? DEMO_LITE_ACTION_INTERVAL_MAX : DEMO_ACTION_INTERVAL_MAX;
float interval_range = interval_max - interval_min;
demo_next_action_time_ = interval_min + (rand() % 1000) / 1000.0f * interval_range;
}
}
void Engine::performRandomRestart() {
// Escenario aleatorio usando tamaño del array
scenario_ = rand() % test_.size();
void Engine::performDemoAction(bool is_lite) {
int TOTAL_WEIGHT;
int random_value;
int accumulated_weight = 0;
// Tema aleatorio usando tamaño del array de temas
current_theme_ = static_cast<ColorTheme>(rand() % (sizeof(themes_) / sizeof(themes_[0])));
if (is_lite) {
// DEMO LITE: Solo física/figuras
TOTAL_WEIGHT = DEMO_LITE_WEIGHT_GRAVITY_DIR + DEMO_LITE_WEIGHT_GRAVITY_TOGGLE
+ DEMO_LITE_WEIGHT_SHAPE + DEMO_LITE_WEIGHT_TOGGLE_PHYSICS
+ DEMO_LITE_WEIGHT_IMPULSE;
random_value = rand() % TOTAL_WEIGHT;
// Reinicializar pelotas con nuevo escenario y tema
initBalls(scenario_);
// Cambiar dirección gravedad (25%)
accumulated_weight += DEMO_LITE_WEIGHT_GRAVITY_DIR;
if (random_value < accumulated_weight) {
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
changeGravityDirection(new_direction);
return;
}
// Resetear temporizador
all_balls_were_stopped_ = false;
all_balls_stopped_start_time_ = 0;
// Toggle gravedad ON/OFF (20%)
accumulated_weight += DEMO_LITE_WEIGHT_GRAVITY_TOGGLE;
if (random_value < accumulated_weight) {
toggleGravityOnOff();
return;
}
// Activar figura 3D (25%)
accumulated_weight += DEMO_LITE_WEIGHT_SHAPE;
if (random_value < accumulated_weight) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
int shape_index = rand() % 8;
activateShape(shapes[shape_index]);
return;
}
// Toggle física ↔ figura (20%)
accumulated_weight += DEMO_LITE_WEIGHT_TOGGLE_PHYSICS;
if (random_value < accumulated_weight) {
toggleShapeMode(false); // NO forzar gravedad al salir
return;
}
// Aplicar impulso (10%)
accumulated_weight += DEMO_LITE_WEIGHT_IMPULSE;
if (random_value < accumulated_weight) {
pushBallsAwayFromGravity();
return;
}
} else {
// DEMO COMPLETO: Todas las acciones
TOTAL_WEIGHT = DEMO_WEIGHT_GRAVITY_DIR + DEMO_WEIGHT_GRAVITY_TOGGLE + DEMO_WEIGHT_SHAPE
+ DEMO_WEIGHT_TOGGLE_PHYSICS + DEMO_WEIGHT_REGENERATE_SHAPE + DEMO_WEIGHT_THEME
+ DEMO_WEIGHT_SCENARIO + DEMO_WEIGHT_IMPULSE + DEMO_WEIGHT_DEPTH_ZOOM
+ DEMO_WEIGHT_SHAPE_SCALE + DEMO_WEIGHT_SPRITE;
random_value = rand() % TOTAL_WEIGHT;
// Cambiar dirección gravedad (10%)
accumulated_weight += DEMO_WEIGHT_GRAVITY_DIR;
if (random_value < accumulated_weight) {
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
changeGravityDirection(new_direction);
return;
}
// Toggle gravedad ON/OFF (8%)
accumulated_weight += DEMO_WEIGHT_GRAVITY_TOGGLE;
if (random_value < accumulated_weight) {
toggleGravityOnOff();
return;
}
// Activar figura 3D (20%)
accumulated_weight += DEMO_WEIGHT_SHAPE;
if (random_value < accumulated_weight) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
int shape_index = rand() % 8;
activateShape(shapes[shape_index]);
return;
}
// Toggle física ↔ figura (12%)
accumulated_weight += DEMO_WEIGHT_TOGGLE_PHYSICS;
if (random_value < accumulated_weight) {
toggleShapeMode(false); // NO forzar gravedad al salir
return;
}
// Re-generar misma figura (8%)
accumulated_weight += DEMO_WEIGHT_REGENERATE_SHAPE;
if (random_value < accumulated_weight) {
if (current_mode_ == SimulationMode::SHAPE && active_shape_) {
generateShape(); // Re-generar sin cambiar tipo
}
return;
}
// Cambiar tema (15%)
accumulated_weight += DEMO_WEIGHT_THEME;
if (random_value < accumulated_weight) {
ColorTheme new_theme = static_cast<ColorTheme>(rand() % 6);
startThemeTransition(new_theme);
return;
}
// Cambiar escenario (10%) - EXCLUIR índices 0, 6, 7 (1, 50K, 100K pelotas)
accumulated_weight += DEMO_WEIGHT_SCENARIO;
if (random_value < accumulated_weight) {
// Escenarios válidos: índices 1, 2, 3, 4, 5 (10, 100, 500, 1000, 10000 pelotas)
int valid_scenarios[] = {1, 2, 3, 4, 5};
scenario_ = valid_scenarios[rand() % 5];
initBalls(scenario_);
return;
}
// Aplicar impulso (10%)
accumulated_weight += DEMO_WEIGHT_IMPULSE;
if (random_value < accumulated_weight) {
pushBallsAwayFromGravity();
return;
}
// Toggle profundidad (3%)
accumulated_weight += DEMO_WEIGHT_DEPTH_ZOOM;
if (random_value < accumulated_weight) {
if (current_mode_ == SimulationMode::SHAPE) {
depth_zoom_enabled_ = !depth_zoom_enabled_;
}
return;
}
// Cambiar escala de figura (2%)
accumulated_weight += DEMO_WEIGHT_SHAPE_SCALE;
if (random_value < accumulated_weight) {
if (current_mode_ == SimulationMode::SHAPE) {
int scale_action = rand() % 3;
if (scale_action == 0) {
shape_scale_factor_ += SHAPE_SCALE_STEP;
} else if (scale_action == 1) {
shape_scale_factor_ -= SHAPE_SCALE_STEP;
} else {
shape_scale_factor_ = SHAPE_SCALE_DEFAULT;
}
clampShapeScale();
generateShape();
}
return;
}
// Cambiar sprite (2%)
accumulated_weight += DEMO_WEIGHT_SPRITE;
if (random_value < accumulated_weight) {
switchTexture();
return;
}
}
}
// Randomizar todo al iniciar modo DEMO
void Engine::randomizeOnDemoStart(bool is_lite) {
if (is_lite) {
// DEMO LITE: Solo randomizar física/figura + gravedad
// Elegir aleatoriamente entre modo física o figura
if (rand() % 2 == 0) {
// Modo física
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeMode(false); // Salir a física sin forzar gravedad
}
} else {
// Modo figura: elegir figura aleatoria
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
activateShape(shapes[rand() % 8]);
}
// Randomizar gravedad: dirección + ON/OFF
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
changeGravityDirection(new_direction);
if (rand() % 2 == 0) {
toggleGravityOnOff(); // 50% probabilidad de desactivar gravedad
}
} else {
// DEMO COMPLETO: Randomizar TODO
// 1. Escenario (excluir índices 0, 6, 7)
int valid_scenarios[] = {1, 2, 3, 4, 5};
scenario_ = valid_scenarios[rand() % 5];
initBalls(scenario_);
// 2. Tema
ColorTheme new_theme = static_cast<ColorTheme>(rand() % 6);
startThemeTransition(new_theme);
// 3. Sprite
if (rand() % 2 == 0) {
switchTexture();
}
// 4. Física o Figura
if (rand() % 2 == 0) {
// Modo física
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeMode(false);
}
} else {
// Modo figura: elegir figura aleatoria
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
activateShape(shapes[rand() % 8]);
// 5. Profundidad (solo si estamos en figura)
if (rand() % 2 == 0) {
depth_zoom_enabled_ = !depth_zoom_enabled_;
}
// 6. Escala de figura (aleatoria entre 0.5x y 2.0x)
shape_scale_factor_ = 0.5f + (rand() % 1500) / 1000.0f;
clampShapeScale();
generateShape();
}
// 7. Gravedad: dirección + ON/OFF
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
changeGravityDirection(new_direction);
if (rand() % 3 == 0) { // 33% probabilidad de desactivar gravedad
toggleGravityOnOff();
}
}
}
// Toggle gravedad ON/OFF para todas las pelotas
void Engine::toggleGravityOnOff() {
// Alternar entre activar/desactivar gravedad
bool first_ball_gravity_enabled = (balls_.empty() || balls_[0]->getGravityForce() > 0.0f);
if (first_ball_gravity_enabled) {
// Desactivar gravedad
forceBallsGravityOff();
} else {
// Activar gravedad
forceBallsGravityOn();
}
}
// Sistema de cambio de sprites dinámico
@@ -1277,12 +1585,14 @@ void Engine::switchTexture() {
// Ajustar posiciones según el cambio de tamaño
updateBallSizes(old_size, new_size);
// Mostrar texto informativo
std::string texture_name = (current_texture_index_ == 0) ? "NORMAL" : "SMALL";
text_ = "SPRITE: " + texture_name;
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
// Mostrar texto informativo (solo si NO estamos en modo demo)
if (!demo_mode_enabled_ && !demo_lite_enabled_) {
std::string texture_name = (current_texture_index_ == 0) ? "NORMAL" : "SMALL";
text_ = "SPRITE: " + texture_name;
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
}
// Sistema de Figuras 3D - Alternar entre modo física y última figura (Toggle con tecla F)
@@ -1305,12 +1615,14 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) {
forceBallsGravityOn();
}
// Mostrar texto informativo
text_ = "MODO FISICA";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_init_time_ = SDL_GetTicks();
show_text_ = true;
// Mostrar texto informativo (solo si NO estamos en modo demo)
if (!demo_mode_enabled_ && !demo_lite_enabled_) {
text_ = "MODO FISICA";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_init_time_ = SDL_GetTicks();
show_text_ = true;
}
}
}
@@ -1365,8 +1677,8 @@ void Engine::activateShape(ShapeType type) {
ball->enableRotoBallAttraction(true);
}
// Mostrar texto informativo con nombre de figura
if (active_shape_) {
// Mostrar texto informativo con nombre de figura (solo si NO estamos en modo demo)
if (active_shape_ && !demo_mode_enabled_ && !demo_lite_enabled_) {
text_ = std::string("MODO ") + active_shape_->getName();
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;

View File

@@ -63,11 +63,6 @@ private:
bool real_fullscreen_enabled_ = false;
ScalingMode current_scaling_mode_ = ScalingMode::INTEGER; // Modo de escalado actual (F5)
// Auto-restart system
Uint64 all_balls_stopped_start_time_ = 0; // Momento cuando todas se pararon
bool all_balls_were_stopped_ = false; // Flag de estado anterior
static constexpr Uint64 AUTO_RESTART_DELAY = 5000; // 5 segundos en ms
// Resolución dinámica para modo real fullscreen
int current_screen_width_ = SCREEN_WIDTH;
int current_screen_height_ = SCREEN_HEIGHT;
@@ -100,6 +95,12 @@ private:
float shape_scale_factor_ = 1.0f; // Factor de escala manual (Numpad +/-)
bool depth_zoom_enabled_ = true; // Zoom por profundidad Z activado
// Sistema de Modo DEMO (auto-play)
bool demo_mode_enabled_ = false; // ¿Está activo el modo demo completo?
bool demo_lite_enabled_ = false; // ¿Está activo el modo demo lite?
float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción
float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos)
// Batch rendering
std::vector<SDL_Vertex> batch_vertices_;
std::vector<int> batch_indices_;
@@ -125,8 +126,12 @@ private:
void toggleIntegerScaling();
std::string gravityDirectionToString(GravityDirection direction) const;
void initializeThemes();
void checkAutoRestart();
void performRandomRestart();
// Sistema de Modo DEMO
void updateDemoMode();
void performDemoAction(bool is_lite);
void randomizeOnDemoStart(bool is_lite);
void toggleGravityOnOff();
// Sistema de transiciones LERP
float lerp(float a, float b, float t) const { return a + (b - a) * t; }

View File

@@ -1,11 +1,15 @@
#include "cylinder_shape.h"
#include "../defines.h"
#include <cmath>
#include <cstdlib> // Para rand()
void CylinderShape::generatePoints(int num_points, float screen_width, float screen_height) {
num_points_ = num_points;
radius_ = screen_height * CYLINDER_RADIUS_FACTOR;
height_ = screen_height * CYLINDER_HEIGHT_FACTOR;
// Inicializar timer de tumbling con valor aleatorio (3-5 segundos)
tumble_timer_ = 3.0f + (rand() % 2000) / 1000.0f;
// Las posiciones 3D se calculan en getPoint3D() usando ecuaciones paramétricas del cilindro
}
@@ -14,8 +18,39 @@ void CylinderShape::update(float delta_time, float screen_width, float screen_he
radius_ = screen_height * CYLINDER_RADIUS_FACTOR;
height_ = screen_height * CYLINDER_HEIGHT_FACTOR;
// Actualizar ángulo de rotación en eje Y
// Actualizar ángulo de rotación en eje Y (siempre activo)
angle_y_ += CYLINDER_ROTATION_SPEED_Y * delta_time;
// Sistema de tumbling ocasional
if (is_tumbling_) {
// Estamos en tumble: animar angle_x hacia el objetivo
tumble_duration_ += delta_time;
float tumble_progress = tumble_duration_ / 1.5f; // 1.5 segundos de duración
if (tumble_progress >= 1.0f) {
// Tumble completado
angle_x_ = tumble_target_;
is_tumbling_ = false;
tumble_timer_ = 3.0f + (rand() % 2000) / 1000.0f; // Nuevo timer (3-5s)
} else {
// Interpolación suave con ease-in-out
float t = tumble_progress;
float ease = t < 0.5f
? 2.0f * t * t
: 1.0f - (-2.0f * t + 2.0f) * (-2.0f * t + 2.0f) / 2.0f;
angle_x_ = ease * tumble_target_;
}
} else {
// No estamos en tumble: contar tiempo
tumble_timer_ -= delta_time;
if (tumble_timer_ <= 0.0f) {
// Iniciar nuevo tumble
is_tumbling_ = true;
tumble_duration_ = 0.0f;
// Objetivo: PI/2 radianes (90°) o -PI/2
tumble_target_ = angle_x_ + ((rand() % 2) == 0 ? PI * 0.5f : -PI * 0.5f);
}
}
}
void CylinderShape::getPoint3D(int index, float& x, float& y, float& z) const {
@@ -56,15 +91,21 @@ void CylinderShape::getPoint3D(int index, float& x, float& y, float& z) const {
float y_base = (height_ * 0.5f) * v; // Centrar verticalmente
float z_base = radius_ * sinf(u);
// Aplicar rotación en eje Y
// Aplicar rotación en eje Y (principal, siempre activa)
float cos_y = cosf(angle_y_);
float sin_y = sinf(angle_y_);
float x_rot = x_base * cos_y - z_base * sin_y;
float z_rot = x_base * sin_y + z_base * cos_y;
float x_rot_y = x_base * cos_y - z_base * sin_y;
float z_rot_y = x_base * sin_y + z_base * cos_y;
// Retornar coordenadas finales rotadas
x = x_rot;
y = y_base;
// Aplicar rotación en eje X (tumbling ocasional)
float cos_x = cosf(angle_x_);
float sin_x = sinf(angle_x_);
float y_rot = y_base * cos_x - z_rot_y * sin_x;
float z_rot = y_base * sin_x + z_rot_y * cos_x;
// Retornar coordenadas finales con ambas rotaciones
x = x_rot_y;
y = y_rot;
z = z_rot;
}

View File

@@ -3,15 +3,22 @@
#include "shape.h"
// Figura: Cilindro 3D rotante
// Comportamiento: Superficie cilíndrica con rotación en eje Y
// Comportamiento: Superficie cilíndrica con rotación en eje Y + tumbling ocasional en X/Z
// Ecuaciones: x = r*cos(u), y = v, z = r*sin(u)
class CylinderShape : public Shape {
private:
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (tumbling ocasional)
float radius_ = 0.0f; // Radio del cilindro (píxeles)
float height_ = 0.0f; // Altura del cilindro (píxeles)
int num_points_ = 0; // Cantidad de puntos generados
// Sistema de tumbling ocasional
float tumble_timer_ = 0.0f; // Temporizador para próximo tumble
float tumble_duration_ = 0.0f; // Duración del tumble actual
bool is_tumbling_ = false; // ¿Estamos en modo tumble?
float tumble_target_ = 0.0f; // Ángulo objetivo del tumble
public:
void generatePoints(int num_points, float screen_width, float screen_height) override;
void update(float delta_time, float screen_width, float screen_height) override;

View File

@@ -34,8 +34,10 @@ void WaveGridShape::update(float delta_time, float screen_width, float screen_he
grid_size_ = screen_height * WAVE_GRID_SIZE_FACTOR;
amplitude_ = screen_height * WAVE_GRID_AMPLITUDE;
// Actualizar rotación en eje Y (horizontal)
angle_y_ += WAVE_GRID_ROTATION_SPEED_Y * delta_time;
// Pivoteo sutil en ejes X e Y (esquinas adelante/atrás, izq/der)
// Usamos velocidades lentas para movimiento sutil y orgánico
tilt_x_ += 0.3f * delta_time; // Pivoteo vertical (esquinas arriba/abajo)
tilt_y_ += 0.5f * delta_time; // Pivoteo horizontal (esquinas izq/der)
// Actualizar fase de las ondas (animación)
phase_ += WAVE_GRID_PHASE_SPEED * delta_time;
@@ -70,18 +72,22 @@ void WaveGridShape::getPoint3D(int index, float& x, float& y, float& z) const {
// z = amplitude * sin(frequency * x + phase) * cos(frequency * y + phase)
float kx = WAVE_GRID_FREQUENCY * PI; // Frecuencia en X
float ky = WAVE_GRID_FREQUENCY * PI; // Frecuencia en Y
float z_base = amplitude_ * sinf(kx * u + phase_) * cosf(ky * v + phase_);
float z_wave = amplitude_ * sinf(kx * u + phase_) * cosf(ky * v + phase_);
// Aplicar rotación en eje Y (horizontal)
float cos_y = cosf(angle_y_);
float sin_y = sinf(angle_y_);
float x_rot = x_base * cos_y - z_base * sin_y;
float z_rot = x_base * sin_y + z_base * cos_y;
// Añadir pivoteo sutil: esquinas se mueven adelante/atrás según posición
// tilt_x oscila esquinas arriba/abajo, tilt_y oscila esquinas izq/der
float tilt_amount_x = sinf(tilt_x_) * 0.15f; // Máximo 15% del grid_size
float tilt_amount_y = sinf(tilt_y_) * 0.1f; // Máximo 10% del grid_size
// Retornar coordenadas finales
x = x_rot;
float z_tilt = (u * tilt_amount_y + v * tilt_amount_x) * grid_size_;
// Z final = ondas + pivoteo
float z_final = z_wave + z_tilt;
// Retornar coordenadas (grid paralelo a pantalla, sin rotación global)
x = x_base;
y = y_base;
z = z_rot;
z = z_final;
}
float WaveGridShape::getScaleFactor(float screen_height) const {

View File

@@ -3,11 +3,12 @@
#include "shape.h"
// Figura: Malla ondeante 3D (Wave Grid)
// Comportamiento: Grid 2D con ondas sinusoidales en Z + rotación
// Ecuaciones: z = A * sin(kx*x + phase) * cos(ky*y + phase)
// Comportamiento: Grid 2D paralelo a pantalla con ondas + pivoteo sutil en esquinas
// Ecuaciones: z = A * sin(kx*x + phase) * cos(ky*y + phase) + pivoteo
class WaveGridShape : public Shape {
private:
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
float tilt_x_ = 0.0f; // Ángulo de pivoteo en eje X (esquinas adelante/atrás)
float tilt_y_ = 0.0f; // Ángulo de pivoteo en eje Y (esquinas izq/der)
float phase_ = 0.0f; // Fase de animación de ondas (rad)
float grid_size_ = 0.0f; // Tamaño del grid (píxeles)
float amplitude_ = 0.0f; // Amplitud de las ondas (píxeles)