Implementar sistema polimórfico de figuras 3D (Sphere + Cube)
- Crear interfaz abstracta Shape con métodos virtuales - Refactorizar RotoBall → SphereShape (clase polimórfica) - Implementar CubeShape con triple rotación (X/Y/Z) - Distribución inteligente en cubo: vértices/centros/grid 3D - Cambiar controles: F=toggle, Q/W/E/R/T/Y/U/I=figuras, B=temas - Actualizar SimulationMode: ROTOBALL → SHAPE - Añadir enum ShapeType (8 figuras: Sphere/Cube/Helix/Torus/etc.) - Incluir source/shapes/*.cpp en CMakeLists.txt - Física compartida escalable entre todas las figuras - Roadmap: 6 figuras pendientes (Helix/Torus/Wave/Cylinder/Icosahedron/Atom) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,8 @@
|
||||
#include "ball.h" // for Ball
|
||||
#include "external/dbgtxt.h" // for dbg_init, dbg_print
|
||||
#include "external/texture.h" // for Texture
|
||||
#include "shapes/sphere_shape.h" // for SphereShape
|
||||
#include "shapes/cube_shape.h" // for CubeShape
|
||||
|
||||
// Función auxiliar para obtener la ruta del directorio del ejecutable
|
||||
std::string getExecutableDirectory() {
|
||||
@@ -148,9 +150,9 @@ void Engine::update() {
|
||||
|
||||
// Verificar auto-reinicio cuando todas las pelotas están quietas (solo en modo física)
|
||||
checkAutoRestart();
|
||||
} else if (current_mode_ == SimulationMode::ROTOBALL) {
|
||||
// Modo RotoBall: actualizar esfera 3D rotante
|
||||
updateRotoBall();
|
||||
} else if (current_mode_ == SimulationMode::SHAPE) {
|
||||
// Modo Figura 3D: actualizar figura polimórfica
|
||||
updateShape();
|
||||
}
|
||||
|
||||
// Actualizar texto (sin cambios en la lógica)
|
||||
@@ -180,9 +182,9 @@ void Engine::handleEvents() {
|
||||
break;
|
||||
|
||||
case SDLK_G:
|
||||
// Si estamos en RotoBall, salir a modo física SIN GRAVEDAD
|
||||
if (current_mode_ == SimulationMode::ROTOBALL) {
|
||||
toggleRotoBallMode(false); // Desactivar RotoBall sin forzar gravedad ON
|
||||
// Si estamos en modo figura, salir a modo física SIN GRAVEDAD
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
toggleShapeMode(false); // Desactivar figura sin forzar gravedad ON
|
||||
} else {
|
||||
switchBallsGravity(); // Toggle normal en modo física
|
||||
}
|
||||
@@ -190,9 +192,9 @@ void Engine::handleEvents() {
|
||||
|
||||
// Controles de dirección de gravedad con teclas de cursor
|
||||
case SDLK_UP:
|
||||
// Si estamos en RotoBall, salir a modo física CON gravedad
|
||||
if (current_mode_ == SimulationMode::ROTOBALL) {
|
||||
toggleRotoBallMode(); // Desactivar RotoBall (activa gravedad automáticamente)
|
||||
// Si estamos en modo figura, salir a modo física CON gravedad
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
|
||||
} else {
|
||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||
}
|
||||
@@ -200,9 +202,9 @@ void Engine::handleEvents() {
|
||||
break;
|
||||
|
||||
case SDLK_DOWN:
|
||||
// Si estamos en RotoBall, salir a modo física CON gravedad
|
||||
if (current_mode_ == SimulationMode::ROTOBALL) {
|
||||
toggleRotoBallMode(); // Desactivar RotoBall (activa gravedad automáticamente)
|
||||
// Si estamos en modo figura, salir a modo física CON gravedad
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
|
||||
} else {
|
||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||
}
|
||||
@@ -210,9 +212,9 @@ void Engine::handleEvents() {
|
||||
break;
|
||||
|
||||
case SDLK_LEFT:
|
||||
// Si estamos en RotoBall, salir a modo física CON gravedad
|
||||
if (current_mode_ == SimulationMode::ROTOBALL) {
|
||||
toggleRotoBallMode(); // Desactivar RotoBall (activa gravedad automáticamente)
|
||||
// Si estamos en modo figura, salir a modo física CON gravedad
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
|
||||
} else {
|
||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||
}
|
||||
@@ -220,9 +222,9 @@ void Engine::handleEvents() {
|
||||
break;
|
||||
|
||||
case SDLK_RIGHT:
|
||||
// Si estamos en RotoBall, salir a modo física CON gravedad
|
||||
if (current_mode_ == SimulationMode::ROTOBALL) {
|
||||
toggleRotoBallMode(); // Desactivar RotoBall (activa gravedad automáticamente)
|
||||
// Si estamos en modo figura, salir a modo física CON gravedad
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
|
||||
} else {
|
||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||
}
|
||||
@@ -237,11 +239,46 @@ void Engine::handleEvents() {
|
||||
show_debug_ = !show_debug_;
|
||||
break;
|
||||
|
||||
case SDLK_C:
|
||||
toggleRotoBallMode();
|
||||
// Toggle Física ↔ Última Figura (antes era C)
|
||||
case SDLK_F:
|
||||
toggleShapeMode();
|
||||
break;
|
||||
|
||||
// Selección directa de figuras 3D
|
||||
case SDLK_Q:
|
||||
activateShape(ShapeType::SPHERE);
|
||||
break;
|
||||
|
||||
case SDLK_W:
|
||||
activateShape(ShapeType::WAVE_GRID);
|
||||
break;
|
||||
|
||||
case SDLK_E:
|
||||
activateShape(ShapeType::HELIX);
|
||||
break;
|
||||
|
||||
case SDLK_R:
|
||||
activateShape(ShapeType::TORUS);
|
||||
break;
|
||||
|
||||
case SDLK_T:
|
||||
activateShape(ShapeType::CUBE);
|
||||
break;
|
||||
|
||||
case SDLK_Y:
|
||||
activateShape(ShapeType::CYLINDER);
|
||||
break;
|
||||
|
||||
case SDLK_U:
|
||||
activateShape(ShapeType::ICOSAHEDRON);
|
||||
break;
|
||||
|
||||
case SDLK_I:
|
||||
activateShape(ShapeType::ATOM);
|
||||
break;
|
||||
|
||||
// Ciclar temas de color (movido de T a B)
|
||||
case SDLK_B:
|
||||
// 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
|
||||
@@ -348,8 +385,8 @@ void Engine::render() {
|
||||
batch_vertices_.clear();
|
||||
batch_indices_.clear();
|
||||
|
||||
if (current_mode_ == SimulationMode::ROTOBALL) {
|
||||
// MODO ROTOBALL: Ordenar por profundidad Z (Painter's Algorithm)
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
// MODO FIGURA 3D: Ordenar por profundidad Z (Painter's Algorithm)
|
||||
// Las pelotas con menor depth_brightness (más lejos/oscuras) se renderizan primero
|
||||
|
||||
// Crear vector de índices para ordenamiento
|
||||
@@ -462,7 +499,14 @@ void Engine::render() {
|
||||
dbg_print(8, 64, theme_text.c_str(), 255, 255, 128); // Amarillo claro para tema
|
||||
|
||||
// Debug: Mostrar modo de simulación actual
|
||||
std::string mode_text = current_mode_ == SimulationMode::PHYSICS ? "MODE PHYSICS" : "MODE ROTOBALL";
|
||||
std::string mode_text;
|
||||
if (current_mode_ == SimulationMode::PHYSICS) {
|
||||
mode_text = "MODE PHYSICS";
|
||||
} else if (active_shape_) {
|
||||
mode_text = std::string("MODE ") + active_shape_->getName();
|
||||
} else {
|
||||
mode_text = "MODE SHAPE";
|
||||
}
|
||||
dbg_print(8, 72, mode_text.c_str(), 0, 255, 128); // Verde claro para modo
|
||||
}
|
||||
|
||||
@@ -470,11 +514,10 @@ void Engine::render() {
|
||||
}
|
||||
|
||||
void Engine::initBalls(int value) {
|
||||
// Si estamos en modo RotoBall, desactivarlo antes de regenerar pelotas
|
||||
if (current_mode_ == SimulationMode::ROTOBALL) {
|
||||
// Si estamos en modo figura 3D, desactivarlo antes de regenerar pelotas
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
current_mode_ = SimulationMode::PHYSICS;
|
||||
rotoball_.transitioning = false;
|
||||
rotoball_.transition_progress = 0.0f;
|
||||
active_shape_.reset(); // Liberar figura actual
|
||||
}
|
||||
|
||||
// Limpiar las bolas actuales
|
||||
@@ -922,39 +965,14 @@ void Engine::performRandomRestart() {
|
||||
all_balls_stopped_start_time_ = 0;
|
||||
}
|
||||
|
||||
// Sistema RotoBall - Alternar entre modo física y esfera 3D
|
||||
void Engine::toggleRotoBallMode(bool force_gravity_on_exit) {
|
||||
// Sistema de Figuras 3D - Alternar entre modo física y última figura (Toggle con tecla F)
|
||||
void Engine::toggleShapeMode(bool force_gravity_on_exit) {
|
||||
if (current_mode_ == SimulationMode::PHYSICS) {
|
||||
// Cambiar a modo RotoBall
|
||||
current_mode_ = SimulationMode::ROTOBALL;
|
||||
rotoball_.transitioning = true;
|
||||
rotoball_.transition_progress = 0.0f;
|
||||
rotoball_.angle_y = 0.0f;
|
||||
rotoball_.angle_x = 0.0f;
|
||||
|
||||
// Desactivar gravedad al entrar en modo figura
|
||||
forceBallsGravityOff();
|
||||
|
||||
// Generar esfera 3D
|
||||
generateRotoBallSphere();
|
||||
|
||||
// Activar atracción física en todas las pelotas
|
||||
// Las pelotas mantienen su velocidad actual y son atraídas hacia la esfera
|
||||
for (auto& ball : balls_) {
|
||||
ball->enableRotoBallAttraction(true);
|
||||
}
|
||||
|
||||
// Mostrar texto informativo
|
||||
text_ = "MODO ROTOBALL";
|
||||
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;
|
||||
// Cambiar a modo figura (usar última figura seleccionada)
|
||||
activateShape(last_shape_type_);
|
||||
} else {
|
||||
// Volver a modo física normal
|
||||
current_mode_ = SimulationMode::PHYSICS;
|
||||
rotoball_.transitioning = false;
|
||||
rotoball_.transition_progress = 0.0f;
|
||||
|
||||
// Desactivar atracción - las pelotas conservan su velocidad tangencial actual
|
||||
for (auto& ball : balls_) {
|
||||
@@ -975,53 +993,67 @@ void Engine::toggleRotoBallMode(bool force_gravity_on_exit) {
|
||||
}
|
||||
}
|
||||
|
||||
// Generar esfera 3D usando algoritmo Fibonacci Sphere
|
||||
void Engine::generateRotoBallSphere() {
|
||||
int num_points = static_cast<int>(balls_.size());
|
||||
if (num_points == 0) return;
|
||||
// Activar figura específica (llamado por teclas Q/W/E/R/Y/U/I o por toggleShapeMode)
|
||||
void Engine::activateShape(ShapeType type) {
|
||||
// Guardar como última figura seleccionada
|
||||
last_shape_type_ = type;
|
||||
current_shape_type_ = type;
|
||||
|
||||
// Calcular radio dinámico proporcional a la altura de pantalla
|
||||
float radius = current_screen_height_ * ROTOBALL_RADIUS_FACTOR;
|
||||
// Cambiar a modo figura
|
||||
current_mode_ = SimulationMode::SHAPE;
|
||||
|
||||
// Constante Golden Ratio para Fibonacci sphere
|
||||
const float golden_ratio = (1.0f + sqrtf(5.0f)) / 2.0f;
|
||||
const float angle_increment = PI * 2.0f * golden_ratio;
|
||||
// Desactivar gravedad al entrar en modo figura
|
||||
forceBallsGravityOff();
|
||||
|
||||
for (int i = 0; i < num_points; i++) {
|
||||
// Distribución uniforme usando Fibonacci sphere
|
||||
float t = static_cast<float>(i) / static_cast<float>(num_points);
|
||||
float phi = acosf(1.0f - 2.0f * t); // Latitud
|
||||
float theta = angle_increment * static_cast<float>(i); // Longitud
|
||||
// Crear instancia polimórfica de la figura correspondiente
|
||||
switch (type) {
|
||||
case ShapeType::SPHERE:
|
||||
active_shape_ = std::make_unique<SphereShape>();
|
||||
break;
|
||||
case ShapeType::CUBE:
|
||||
active_shape_ = std::make_unique<CubeShape>();
|
||||
break;
|
||||
// Futuras figuras se añadirán aquí
|
||||
default:
|
||||
active_shape_ = std::make_unique<SphereShape>(); // Fallback
|
||||
break;
|
||||
}
|
||||
|
||||
// Convertir coordenadas esféricas a cartesianas
|
||||
float x = cosf(theta) * sinf(phi) * radius;
|
||||
float y = sinf(theta) * sinf(phi) * radius;
|
||||
float z = cosf(phi) * radius;
|
||||
// Generar puntos de la figura
|
||||
generateShape();
|
||||
|
||||
// Guardar posición 3D en la pelota
|
||||
balls_[i]->setRotoBallPosition3D(x, y, z);
|
||||
// Activar atracción física en todas las pelotas
|
||||
for (auto& ball : balls_) {
|
||||
ball->enableRotoBallAttraction(true);
|
||||
}
|
||||
|
||||
// Calcular posición 2D inicial (centro de pantalla)
|
||||
float center_x = current_screen_width_ / 2.0f;
|
||||
float center_y = current_screen_height_ / 2.0f;
|
||||
balls_[i]->setRotoBallTarget2D(center_x + x, center_y + y);
|
||||
|
||||
// Calcular brillo inicial según profundidad Z
|
||||
float z_normalized = (z + radius) / (2.0f * radius);
|
||||
balls_[i]->setDepthBrightness(z_normalized);
|
||||
// Mostrar texto informativo con nombre de figura
|
||||
if (active_shape_) {
|
||||
text_ = std::string("MODO ") + active_shape_->getName();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar esfera RotoBall con física de atracción
|
||||
void Engine::updateRotoBall() {
|
||||
if (current_mode_ != SimulationMode::ROTOBALL) return;
|
||||
// Generar puntos de la figura activa
|
||||
void Engine::generateShape() {
|
||||
if (!active_shape_) return;
|
||||
|
||||
// Calcular radio dinámico proporcional a la altura de pantalla
|
||||
float radius = current_screen_height_ * ROTOBALL_RADIUS_FACTOR;
|
||||
int num_points = static_cast<int>(balls_.size());
|
||||
active_shape_->generatePoints(num_points, static_cast<float>(current_screen_width_), static_cast<float>(current_screen_height_));
|
||||
}
|
||||
|
||||
// Actualizar ángulos de rotación de la esfera
|
||||
rotoball_.angle_y += ROTOBALL_ROTATION_SPEED_Y * delta_time_;
|
||||
rotoball_.angle_x += ROTOBALL_ROTATION_SPEED_X * delta_time_;
|
||||
// Actualizar figura activa (rotación, animación, etc.)
|
||||
void Engine::updateShape() {
|
||||
if (!active_shape_ || current_mode_ != SimulationMode::SHAPE) return;
|
||||
|
||||
// Actualizar animación de la figura
|
||||
active_shape_->update(delta_time_, static_cast<float>(current_screen_width_), static_cast<float>(current_screen_height_));
|
||||
|
||||
// Obtener factor de escala para física
|
||||
float scale_factor = active_shape_->getScaleFactor(static_cast<float>(current_screen_height_));
|
||||
|
||||
// Centro de la pantalla
|
||||
float center_x = current_screen_width_ / 2.0f;
|
||||
@@ -1029,40 +1061,22 @@ void Engine::updateRotoBall() {
|
||||
|
||||
// Actualizar cada pelota con física de atracción
|
||||
for (size_t i = 0; i < balls_.size(); i++) {
|
||||
// Recalcular posición 3D original usando Fibonacci sphere
|
||||
int num_points = static_cast<int>(balls_.size());
|
||||
const float golden_ratio = (1.0f + sqrtf(5.0f)) / 2.0f;
|
||||
const float angle_increment = PI * 2.0f * golden_ratio;
|
||||
|
||||
float t = static_cast<float>(i) / static_cast<float>(num_points);
|
||||
float phi = acosf(1.0f - 2.0f * t);
|
||||
float theta = angle_increment * static_cast<float>(i);
|
||||
|
||||
float x = cosf(theta) * sinf(phi) * radius;
|
||||
float y = sinf(theta) * sinf(phi) * radius;
|
||||
float z = cosf(phi) * radius;
|
||||
|
||||
// Aplicar rotación en eje Y
|
||||
float cos_y = cosf(rotoball_.angle_y);
|
||||
float sin_y = sinf(rotoball_.angle_y);
|
||||
float x_rot = x * cos_y - z * sin_y;
|
||||
float z_rot = x * sin_y + z * cos_y;
|
||||
|
||||
// Aplicar rotación en eje X
|
||||
float cos_x = cosf(rotoball_.angle_x);
|
||||
float sin_x = sinf(rotoball_.angle_x);
|
||||
float y_rot = y * cos_x - z_rot * sin_x;
|
||||
float z_final = y * sin_x + z_rot * cos_x;
|
||||
// Obtener posición 3D rotada del punto i
|
||||
float x_3d, y_3d, z_3d;
|
||||
active_shape_->getPoint3D(static_cast<int>(i), x_3d, y_3d, z_3d);
|
||||
|
||||
// Proyección 2D ortográfica (punto objetivo móvil)
|
||||
float target_x = center_x + x_rot;
|
||||
float target_y = center_y + y_rot;
|
||||
float target_x = center_x + x_3d;
|
||||
float target_y = center_y + y_3d;
|
||||
|
||||
// Aplicar fuerza de atracción física hacia el punto rotado
|
||||
balls_[i]->applyRotoBallForce(target_x, target_y, radius, delta_time_);
|
||||
// Pasar el tamaño de la figura para escalar fuerzas
|
||||
float shape_size = scale_factor * 80.0f; // 80px = radio base
|
||||
balls_[i]->applyRotoBallForce(target_x, target_y, shape_size, delta_time_);
|
||||
|
||||
// Calcular brillo según profundidad Z para renderizado
|
||||
float z_normalized = (z_final + radius) / (2.0f * radius);
|
||||
// Normalizar Z al rango de la figura (asumiendo simetría ±shape_size)
|
||||
float z_normalized = (z_3d + shape_size) / (2.0f * shape_size);
|
||||
z_normalized = std::max(0.0f, std::min(1.0f, z_normalized));
|
||||
balls_[i]->setDepthBrightness(z_normalized);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user