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:
2025-10-03 20:20:10 +02:00
parent b196683e4a
commit a7ec764ebc
9 changed files with 484 additions and 137 deletions

View File

@@ -17,7 +17,7 @@ if (NOT SDL3_FOUND)
endif() endif()
# Archivos fuente (excluir main_old.cpp) # Archivos fuente (excluir main_old.cpp)
file(GLOB SOURCE_FILES source/*.cpp source/external/*.cpp) file(GLOB SOURCE_FILES source/*.cpp source/external/*.cpp source/shapes/*.cpp)
list(REMOVE_ITEM SOURCE_FILES "${CMAKE_SOURCE_DIR}/source/main_old.cpp") list(REMOVE_ITEM SOURCE_FILES "${CMAKE_SOURCE_DIR}/source/main_old.cpp")
# Comprobar si se encontraron archivos fuente # Comprobar si se encontraron archivos fuente

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <SDL3/SDL_stdinc.h> // for Uint64
// Configuración de ventana y pantalla // Configuración de ventana y pantalla
constexpr char WINDOW_CAPTION[] = "vibe3_physics"; constexpr char WINDOW_CAPTION[] = "vibe3_physics";
@@ -58,10 +60,23 @@ enum class ColorTheme {
RGB = 4 // RGB puros y subdivisiones matemáticas (fondo blanco) RGB = 4 // RGB puros y subdivisiones matemáticas (fondo blanco)
}; };
// Enum para tipo de figura 3D
enum class ShapeType {
NONE, // Sin figura (modo física pura)
SPHERE, // Esfera Fibonacci (antiguo RotoBall)
CUBE, // Cubo rotante
HELIX, // Espiral 3D (futuro)
TORUS, // Toroide/donut (futuro)
WAVE_GRID, // Malla ondeante (futuro)
CYLINDER, // Cilindro rotante (futuro)
ICOSAHEDRON, // Icosaedro D20 (futuro)
ATOM // Átomo con órbitas (futuro)
};
// Enum para modo de simulación // Enum para modo de simulación
enum class SimulationMode { enum class SimulationMode {
PHYSICS, // Modo física normal con gravedad PHYSICS, // Modo física normal con gravedad
ROTOBALL // Modo esfera 3D rotante (demoscene effect) SHAPE // Modo figura 3D (Shape polimórfico)
}; };
// Configuración de RotoBall (esfera 3D rotante) // Configuración de RotoBall (esfera 3D rotante)
@@ -72,11 +87,17 @@ constexpr float ROTOBALL_TRANSITION_TIME = 1.5f; // Tiempo de transición (seg
constexpr int ROTOBALL_MIN_BRIGHTNESS = 50; // Brillo mínimo (fondo, 0-255) constexpr int ROTOBALL_MIN_BRIGHTNESS = 50; // Brillo mínimo (fondo, 0-255)
constexpr int ROTOBALL_MAX_BRIGHTNESS = 255; // Brillo máximo (frente, 0-255) constexpr int ROTOBALL_MAX_BRIGHTNESS = 255; // Brillo máximo (frente, 0-255)
// Física de atracción RotoBall (sistema de resorte) // Física de atracción para figuras 3D (sistema de resorte compartido)
constexpr float ROTOBALL_SPRING_K = 300.0f; // Constante de rigidez del resorte (N/m) constexpr float ROTOBALL_SPRING_K = 300.0f; // Constante de rigidez del resorte (N/m)
constexpr float ROTOBALL_DAMPING_BASE = 35.0f; // Amortiguación base (amortiguamiento crítico ≈ 2*√k*m) constexpr float ROTOBALL_DAMPING_BASE = 35.0f; // Amortiguación base (amortiguamiento crítico ≈ 2*√k*m)
constexpr float ROTOBALL_DAMPING_NEAR = 80.0f; // Amortiguación cerca del punto (absorción rápida) constexpr float ROTOBALL_DAMPING_NEAR = 80.0f; // Amortiguación cerca del punto (absorción rápida)
constexpr float ROTOBALL_NEAR_THRESHOLD = 5.0f; // Distancia "cerca" en píxeles constexpr float ROTOBALL_NEAR_THRESHOLD = 5.0f; // Distancia "cerca" en píxeles
constexpr float ROTOBALL_MAX_FORCE = 1000.0f; // Fuerza máxima aplicable (evita explosiones) constexpr float ROTOBALL_MAX_FORCE = 1000.0f; // Fuerza máxima aplicable (evita explosiones)
// Configuración del Cubo (cubo 3D rotante)
constexpr float CUBE_SIZE_FACTOR = 0.25f; // Tamaño como proporción de altura (60/240 = 0.25)
constexpr float CUBE_ROTATION_SPEED_X = 0.5f; // Velocidad rotación eje X (rad/s)
constexpr float CUBE_ROTATION_SPEED_Y = 0.7f; // Velocidad rotación eje Y (rad/s)
constexpr float CUBE_ROTATION_SPEED_Z = 0.3f; // Velocidad rotación eje Z (rad/s)
constexpr float PI = 3.14159265358979323846f; // Constante PI constexpr float PI = 3.14159265358979323846f; // Constante PI

View File

@@ -23,6 +23,8 @@
#include "ball.h" // for Ball #include "ball.h" // for Ball
#include "external/dbgtxt.h" // for dbg_init, dbg_print #include "external/dbgtxt.h" // for dbg_init, dbg_print
#include "external/texture.h" // for Texture #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 // Función auxiliar para obtener la ruta del directorio del ejecutable
std::string getExecutableDirectory() { 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) // Verificar auto-reinicio cuando todas las pelotas están quietas (solo en modo física)
checkAutoRestart(); checkAutoRestart();
} else if (current_mode_ == SimulationMode::ROTOBALL) { } else if (current_mode_ == SimulationMode::SHAPE) {
// Modo RotoBall: actualizar esfera 3D rotante // Modo Figura 3D: actualizar figura polimórfica
updateRotoBall(); updateShape();
} }
// Actualizar texto (sin cambios en la lógica) // Actualizar texto (sin cambios en la lógica)
@@ -180,9 +182,9 @@ void Engine::handleEvents() {
break; break;
case SDLK_G: case SDLK_G:
// Si estamos en RotoBall, salir a modo física SIN GRAVEDAD // Si estamos en modo figura, salir a modo física SIN GRAVEDAD
if (current_mode_ == SimulationMode::ROTOBALL) { if (current_mode_ == SimulationMode::SHAPE) {
toggleRotoBallMode(false); // Desactivar RotoBall sin forzar gravedad ON toggleShapeMode(false); // Desactivar figura sin forzar gravedad ON
} else { } else {
switchBallsGravity(); // Toggle normal en modo física switchBallsGravity(); // Toggle normal en modo física
} }
@@ -190,9 +192,9 @@ void Engine::handleEvents() {
// Controles de dirección de gravedad con teclas de cursor // Controles de dirección de gravedad con teclas de cursor
case SDLK_UP: case SDLK_UP:
// Si estamos en RotoBall, salir a modo física CON gravedad // Si estamos en modo figura, salir a modo física CON gravedad
if (current_mode_ == SimulationMode::ROTOBALL) { if (current_mode_ == SimulationMode::SHAPE) {
toggleRotoBallMode(); // Desactivar RotoBall (activa gravedad automáticamente) toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
} else { } else {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
} }
@@ -200,9 +202,9 @@ void Engine::handleEvents() {
break; break;
case SDLK_DOWN: case SDLK_DOWN:
// Si estamos en RotoBall, salir a modo física CON gravedad // Si estamos en modo figura, salir a modo física CON gravedad
if (current_mode_ == SimulationMode::ROTOBALL) { if (current_mode_ == SimulationMode::SHAPE) {
toggleRotoBallMode(); // Desactivar RotoBall (activa gravedad automáticamente) toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
} else { } else {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
} }
@@ -210,9 +212,9 @@ void Engine::handleEvents() {
break; break;
case SDLK_LEFT: case SDLK_LEFT:
// Si estamos en RotoBall, salir a modo física CON gravedad // Si estamos en modo figura, salir a modo física CON gravedad
if (current_mode_ == SimulationMode::ROTOBALL) { if (current_mode_ == SimulationMode::SHAPE) {
toggleRotoBallMode(); // Desactivar RotoBall (activa gravedad automáticamente) toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
} else { } else {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
} }
@@ -220,9 +222,9 @@ void Engine::handleEvents() {
break; break;
case SDLK_RIGHT: case SDLK_RIGHT:
// Si estamos en RotoBall, salir a modo física CON gravedad // Si estamos en modo figura, salir a modo física CON gravedad
if (current_mode_ == SimulationMode::ROTOBALL) { if (current_mode_ == SimulationMode::SHAPE) {
toggleRotoBallMode(); // Desactivar RotoBall (activa gravedad automáticamente) toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
} else { } else {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
} }
@@ -237,11 +239,46 @@ void Engine::handleEvents() {
show_debug_ = !show_debug_; show_debug_ = !show_debug_;
break; break;
case SDLK_C: // Toggle Física ↔ Última Figura (antes era C)
toggleRotoBallMode(); 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; break;
case SDLK_T: 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 // Ciclar al siguiente tema
current_theme_ = static_cast<ColorTheme>((static_cast<int>(current_theme_) + 1) % (sizeof(themes_) / sizeof(themes_[0]))); current_theme_ = static_cast<ColorTheme>((static_cast<int>(current_theme_) + 1) % (sizeof(themes_) / sizeof(themes_[0])));
initBalls(scenario_); // Regenerar bolas con nueva paleta initBalls(scenario_); // Regenerar bolas con nueva paleta
@@ -348,8 +385,8 @@ void Engine::render() {
batch_vertices_.clear(); batch_vertices_.clear();
batch_indices_.clear(); batch_indices_.clear();
if (current_mode_ == SimulationMode::ROTOBALL) { if (current_mode_ == SimulationMode::SHAPE) {
// MODO ROTOBALL: Ordenar por profundidad Z (Painter's Algorithm) // MODO FIGURA 3D: Ordenar por profundidad Z (Painter's Algorithm)
// Las pelotas con menor depth_brightness (más lejos/oscuras) se renderizan primero // Las pelotas con menor depth_brightness (más lejos/oscuras) se renderizan primero
// Crear vector de índices para ordenamiento // 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 dbg_print(8, 64, theme_text.c_str(), 255, 255, 128); // Amarillo claro para tema
// Debug: Mostrar modo de simulación actual // 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 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) { void Engine::initBalls(int value) {
// Si estamos en modo RotoBall, desactivarlo antes de regenerar pelotas // Si estamos en modo figura 3D, desactivarlo antes de regenerar pelotas
if (current_mode_ == SimulationMode::ROTOBALL) { if (current_mode_ == SimulationMode::SHAPE) {
current_mode_ = SimulationMode::PHYSICS; current_mode_ = SimulationMode::PHYSICS;
rotoball_.transitioning = false; active_shape_.reset(); // Liberar figura actual
rotoball_.transition_progress = 0.0f;
} }
// Limpiar las bolas actuales // Limpiar las bolas actuales
@@ -922,39 +965,14 @@ void Engine::performRandomRestart() {
all_balls_stopped_start_time_ = 0; all_balls_stopped_start_time_ = 0;
} }
// Sistema RotoBall - Alternar entre modo física y esfera 3D // Sistema de Figuras 3D - Alternar entre modo física y última figura (Toggle con tecla F)
void Engine::toggleRotoBallMode(bool force_gravity_on_exit) { void Engine::toggleShapeMode(bool force_gravity_on_exit) {
if (current_mode_ == SimulationMode::PHYSICS) { if (current_mode_ == SimulationMode::PHYSICS) {
// Cambiar a modo RotoBall // Cambiar a modo figura (usar última figura seleccionada)
current_mode_ = SimulationMode::ROTOBALL; activateShape(last_shape_type_);
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;
} else { } else {
// Volver a modo física normal // Volver a modo física normal
current_mode_ = SimulationMode::PHYSICS; current_mode_ = SimulationMode::PHYSICS;
rotoball_.transitioning = false;
rotoball_.transition_progress = 0.0f;
// Desactivar atracción - las pelotas conservan su velocidad tangencial actual // Desactivar atracción - las pelotas conservan su velocidad tangencial actual
for (auto& ball : balls_) { for (auto& ball : balls_) {
@@ -975,53 +993,67 @@ void Engine::toggleRotoBallMode(bool force_gravity_on_exit) {
} }
} }
// Generar esfera 3D usando algoritmo Fibonacci Sphere // Activar figura específica (llamado por teclas Q/W/E/R/Y/U/I o por toggleShapeMode)
void Engine::generateRotoBallSphere() { void Engine::activateShape(ShapeType type) {
int num_points = static_cast<int>(balls_.size()); // Guardar como última figura seleccionada
if (num_points == 0) return; last_shape_type_ = type;
current_shape_type_ = type;
// Calcular radio dinámico proporcional a la altura de pantalla // Cambiar a modo figura
float radius = current_screen_height_ * ROTOBALL_RADIUS_FACTOR; current_mode_ = SimulationMode::SHAPE;
// Constante Golden Ratio para Fibonacci sphere // Desactivar gravedad al entrar en modo figura
const float golden_ratio = (1.0f + sqrtf(5.0f)) / 2.0f; forceBallsGravityOff();
const float angle_increment = PI * 2.0f * golden_ratio;
for (int i = 0; i < num_points; i++) { // Crear instancia polimórfica de la figura correspondiente
// Distribución uniforme usando Fibonacci sphere switch (type) {
float t = static_cast<float>(i) / static_cast<float>(num_points); case ShapeType::SPHERE:
float phi = acosf(1.0f - 2.0f * t); // Latitud active_shape_ = std::make_unique<SphereShape>();
float theta = angle_increment * static_cast<float>(i); // Longitud 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 // Generar puntos de la figura
float x = cosf(theta) * sinf(phi) * radius; generateShape();
float y = sinf(theta) * sinf(phi) * radius;
float z = cosf(phi) * radius;
// Guardar posición 3D en la pelota // Activar atracción física en todas las pelotas
balls_[i]->setRotoBallPosition3D(x, y, z); for (auto& ball : balls_) {
ball->enableRotoBallAttraction(true);
}
// Calcular posición 2D inicial (centro de pantalla) // Mostrar texto informativo con nombre de figura
float center_x = current_screen_width_ / 2.0f; if (active_shape_) {
float center_y = current_screen_height_ / 2.0f; text_ = std::string("MODO ") + active_shape_->getName();
balls_[i]->setRotoBallTarget2D(center_x + x, center_y + y); int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
// Calcular brillo inicial según profundidad Z text_init_time_ = SDL_GetTicks();
float z_normalized = (z + radius) / (2.0f * radius); show_text_ = true;
balls_[i]->setDepthBrightness(z_normalized);
} }
} }
// Actualizar esfera RotoBall con física de atracción // Generar puntos de la figura activa
void Engine::updateRotoBall() { void Engine::generateShape() {
if (current_mode_ != SimulationMode::ROTOBALL) return; if (!active_shape_) return;
// Calcular radio dinámico proporcional a la altura de pantalla int num_points = static_cast<int>(balls_.size());
float radius = current_screen_height_ * ROTOBALL_RADIUS_FACTOR; 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 // Actualizar figura activa (rotación, animación, etc.)
rotoball_.angle_y += ROTOBALL_ROTATION_SPEED_Y * delta_time_; void Engine::updateShape() {
rotoball_.angle_x += ROTOBALL_ROTATION_SPEED_X * delta_time_; 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 // Centro de la pantalla
float center_x = current_screen_width_ / 2.0f; float center_x = current_screen_width_ / 2.0f;
@@ -1029,40 +1061,22 @@ void Engine::updateRotoBall() {
// Actualizar cada pelota con física de atracción // Actualizar cada pelota con física de atracción
for (size_t i = 0; i < balls_.size(); i++) { for (size_t i = 0; i < balls_.size(); i++) {
// Recalcular posición 3D original usando Fibonacci sphere // Obtener posición 3D rotada del punto i
int num_points = static_cast<int>(balls_.size()); float x_3d, y_3d, z_3d;
const float golden_ratio = (1.0f + sqrtf(5.0f)) / 2.0f; active_shape_->getPoint3D(static_cast<int>(i), x_3d, y_3d, z_3d);
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;
// Proyección 2D ortográfica (punto objetivo móvil) // Proyección 2D ortográfica (punto objetivo móvil)
float target_x = center_x + x_rot; float target_x = center_x + x_3d;
float target_y = center_y + y_rot; float target_y = center_y + y_3d;
// Aplicar fuerza de atracción física hacia el punto rotado // 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 // 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)); z_normalized = std::max(0.0f, std::min(1.0f, z_normalized));
balls_[i]->setDepthBrightness(z_normalized); balls_[i]->setDepthBrightness(z_normalized);
} }

View File

@@ -10,9 +10,10 @@
#include <string> // for string #include <string> // for string
#include <vector> // for vector #include <vector> // for vector
#include "defines.h" // for GravityDirection, ColorTheme #include "defines.h" // for GravityDirection, ColorTheme, ShapeType
#include "ball.h" // for Ball #include "ball.h" // for Ball
#include "external/texture.h" // for Texture #include "external/texture.h" // for Texture
#include "shapes/shape.h" // for Shape (interfaz polimórfica)
class Engine { class Engine {
public: public:
@@ -80,15 +81,11 @@ private:
// Temas de colores definidos // Temas de colores definidos
ThemeColors themes_[5]; ThemeColors themes_[5];
// Sistema RotoBall (esfera 3D rotante) // Sistema de Figuras 3D (polimórfico)
SimulationMode current_mode_ = SimulationMode::PHYSICS; SimulationMode current_mode_ = SimulationMode::PHYSICS;
struct RotoBallData { ShapeType current_shape_type_ = ShapeType::SPHERE; // Tipo de figura actual
float angle_y = 0.0f; // Ángulo de rotación en eje Y ShapeType last_shape_type_ = ShapeType::SPHERE; // Última figura para toggle F
float angle_x = 0.0f; // Ángulo de rotación en eje X std::unique_ptr<Shape> active_shape_; // Puntero polimórfico a figura activa
float transition_progress = 0.0f; // Progreso de transición (0.0-1.0)
bool transitioning = false; // ¿Está en transición?
};
RotoBallData rotoball_;
// Batch rendering // Batch rendering
std::vector<SDL_Vertex> batch_vertices_; std::vector<SDL_Vertex> batch_vertices_;
@@ -127,8 +124,9 @@ private:
void renderGradientBackground(); void renderGradientBackground();
void addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b); void addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b);
// Sistema RotoBall // Sistema de Figuras 3D
void toggleRotoBallMode(bool force_gravity_on_exit = true); void toggleShapeMode(bool force_gravity_on_exit = true); // Toggle PHYSICS ↔ última figura (tecla F)
void generateRotoBallSphere(); void activateShape(ShapeType type); // Activar figura específica (teclas Q/W/E/R/Y/U/I)
void updateRotoBall(); void updateShape(); // Actualizar figura activa
}; void generateShape(); // Generar puntos de figura activa
};

View File

@@ -0,0 +1,168 @@
#include "cube_shape.h"
#include "../defines.h"
#include <cmath>
void CubeShape::generatePoints(int num_points, float screen_width, float screen_height) {
num_points_ = num_points;
size_ = screen_height * CUBE_SIZE_FACTOR;
// Limpiar vectores anteriores
base_x_.clear();
base_y_.clear();
base_z_.clear();
// Seleccionar estrategia según cantidad de pelotas
if (num_points <= 8) {
generateVertices();
} else if (num_points <= 26) {
generateVerticesAndCenters();
} else {
generateVolumetricGrid();
}
// Si sobran posiciones, repetir en espiral (distribución uniforme)
while (static_cast<int>(base_x_.size()) < num_points) {
base_x_.push_back(base_x_[base_x_.size() % base_x_.size()]);
base_y_.push_back(base_y_[base_y_.size() % base_y_.size()]);
base_z_.push_back(base_z_[base_z_.size() % base_z_.size()]);
}
}
void CubeShape::update(float delta_time, float screen_width, float screen_height) {
// Recalcular tamaño por si cambió resolución (F4)
size_ = screen_height * CUBE_SIZE_FACTOR;
// Actualizar ángulos de rotación en los 3 ejes (efecto Rubik)
angle_x_ += CUBE_ROTATION_SPEED_X * delta_time;
angle_y_ += CUBE_ROTATION_SPEED_Y * delta_time;
angle_z_ += CUBE_ROTATION_SPEED_Z * delta_time;
}
void CubeShape::getPoint3D(int index, float& x, float& y, float& z) const {
if (index >= static_cast<int>(base_x_.size())) {
x = y = z = 0.0f;
return;
}
// Obtener posición base
float x_base = base_x_[index];
float y_base = base_y_[index];
float z_base = base_z_[index];
// Aplicar rotación en eje Z
float cos_z = cosf(angle_z_);
float sin_z = sinf(angle_z_);
float x_rot_z = x_base * cos_z - y_base * sin_z;
float y_rot_z = x_base * sin_z + y_base * cos_z;
float z_rot_z = z_base;
// Aplicar rotación en eje Y
float cos_y = cosf(angle_y_);
float sin_y = sinf(angle_y_);
float x_rot_y = x_rot_z * cos_y + z_rot_z * sin_y;
float y_rot_y = y_rot_z;
float z_rot_y = -x_rot_z * sin_y + z_rot_z * cos_y;
// Aplicar rotación en eje X
float cos_x = cosf(angle_x_);
float sin_x = sinf(angle_x_);
float x_final = x_rot_y;
float y_final = y_rot_y * cos_x - z_rot_y * sin_x;
float z_final = y_rot_y * sin_x + z_rot_y * cos_x;
// Retornar coordenadas finales rotadas
x = x_final;
y = y_final;
z = z_final;
}
float CubeShape::getScaleFactor(float screen_height) const {
// Factor de escala para física: proporcional al tamaño del cubo
// Tamaño base = 60px (resolución 320x240, factor 0.25)
const float BASE_SIZE = 60.0f;
float current_size = screen_height * CUBE_SIZE_FACTOR;
return current_size / BASE_SIZE;
}
// Métodos auxiliares privados
void CubeShape::generateVertices() {
// 8 vértices del cubo: todas las combinaciones de (±size, ±size, ±size)
for (int x_sign : {-1, 1}) {
for (int y_sign : {-1, 1}) {
for (int z_sign : {-1, 1}) {
base_x_.push_back(x_sign * size_);
base_y_.push_back(y_sign * size_);
base_z_.push_back(z_sign * size_);
}
}
}
}
void CubeShape::generateVerticesAndCenters() {
// 1. Añadir 8 vértices
generateVertices();
// 2. Añadir 6 centros de caras
// Caras: X=±size (Y,Z varían), Y=±size (X,Z varían), Z=±size (X,Y varían)
base_x_.push_back(size_); base_y_.push_back(0); base_z_.push_back(0); // +X
base_x_.push_back(-size_); base_y_.push_back(0); base_z_.push_back(0); // -X
base_x_.push_back(0); base_y_.push_back(size_); base_z_.push_back(0); // +Y
base_x_.push_back(0); base_y_.push_back(-size_);base_z_.push_back(0); // -Y
base_x_.push_back(0); base_y_.push_back(0); base_z_.push_back(size_); // +Z
base_x_.push_back(0); base_y_.push_back(0); base_z_.push_back(-size_); // -Z
// 3. Añadir 12 centros de aristas
// Aristas paralelas a X (4), Y (4), Z (4)
// Paralelas a X (Y y Z en vértices, X=0)
for (int y_sign : {-1, 1}) {
for (int z_sign : {-1, 1}) {
base_x_.push_back(0);
base_y_.push_back(y_sign * size_);
base_z_.push_back(z_sign * size_);
}
}
// Paralelas a Y (X y Z en vértices, Y=0)
for (int x_sign : {-1, 1}) {
for (int z_sign : {-1, 1}) {
base_x_.push_back(x_sign * size_);
base_y_.push_back(0);
base_z_.push_back(z_sign * size_);
}
}
// Paralelas a Z (X y Y en vértices, Z=0)
for (int x_sign : {-1, 1}) {
for (int y_sign : {-1, 1}) {
base_x_.push_back(x_sign * size_);
base_y_.push_back(y_sign * size_);
base_z_.push_back(0);
}
}
}
void CubeShape::generateVolumetricGrid() {
// Calcular dimensión del grid cúbico: N³ ≈ num_points
int grid_dim = static_cast<int>(ceilf(cbrtf(static_cast<float>(num_points_))));
if (grid_dim < 3) grid_dim = 3; // Mínimo grid 3x3x3
float step = (2.0f * size_) / (grid_dim - 1); // Espacio entre puntos
for (int ix = 0; ix < grid_dim; ix++) {
for (int iy = 0; iy < grid_dim; iy++) {
for (int iz = 0; iz < grid_dim; iz++) {
float x = -size_ + ix * step;
float y = -size_ + iy * step;
float z = -size_ + iz * step;
base_x_.push_back(x);
base_y_.push_back(y);
base_z_.push_back(z);
// Si ya tenemos suficientes puntos, salir
if (static_cast<int>(base_x_.size()) >= num_points_) {
return;
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include "shape.h"
#include <vector>
// Figura: Cubo 3D rotante
// Distribución:
// - 1-8 pelotas: Solo vértices (8 puntos)
// - 9-26 pelotas: Vértices + centros de caras + centros de aristas (26 puntos)
// - 27+ pelotas: Grid volumétrico 3D uniforme
// Comportamiento: Rotación simultánea en ejes X, Y, Z (efecto Rubik)
class CubeShape : public Shape {
private:
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
float angle_z_ = 0.0f; // Ángulo de rotación en eje Z (rad)
float size_ = 0.0f; // Mitad del lado del cubo (píxeles)
int num_points_ = 0; // Cantidad de puntos generados
// Posiciones base 3D (sin rotar) - se calculan en generatePoints()
std::vector<float> base_x_;
std::vector<float> base_y_;
std::vector<float> base_z_;
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;
void getPoint3D(int index, float& x, float& y, float& z) const override;
const char* getName() const override { return "CUBE"; }
float getScaleFactor(float screen_height) const override;
private:
// Métodos auxiliares para distribución de puntos
void generateVertices(); // 8 vértices
void generateVerticesAndCenters(); // 26 puntos (vértices + caras + aristas)
void generateVolumetricGrid(); // Grid 3D para muchas pelotas
};

30
source/shapes/shape.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
// Interfaz abstracta para todas las figuras 3D
class Shape {
public:
virtual ~Shape() = default;
// Generar distribución inicial de puntos en la figura
// num_points: cantidad de pelotas a distribuir
// screen_width/height: dimensiones del área de juego (para escalar)
virtual void generatePoints(int num_points, float screen_width, float screen_height) = 0;
// Actualizar animación de la figura (rotación, deformación, etc.)
// delta_time: tiempo transcurrido desde último frame
// screen_width/height: dimensiones actuales (puede cambiar con F4)
virtual void update(float delta_time, float screen_width, float screen_height) = 0;
// Obtener posición 3D del punto i después de transformaciones (rotación, etc.)
// index: índice del punto (0 a num_points-1)
// x, y, z: coordenadas 3D en espacio mundo (centradas en 0,0,0)
virtual void getPoint3D(int index, float& x, float& y, float& z) const = 0;
// Obtener nombre de la figura para debug display
virtual const char* getName() const = 0;
// Obtener factor de escala para ajustar física según tamaño de figura
// screen_height: altura actual de pantalla
// Retorna: factor multiplicador para constantes de física (spring_k, damping, etc.)
virtual float getScaleFactor(float screen_height) const = 0;
};

View File

@@ -0,0 +1,58 @@
#include "sphere_shape.h"
#include "../defines.h"
#include <cmath>
void SphereShape::generatePoints(int num_points, float screen_width, float screen_height) {
num_points_ = num_points;
radius_ = screen_height * ROTOBALL_RADIUS_FACTOR;
// Las posiciones 3D se calculan en getPoint3D() usando Fibonacci Sphere
}
void SphereShape::update(float delta_time, float screen_width, float screen_height) {
// Recalcular radio por si cambió resolución (F4)
radius_ = screen_height * ROTOBALL_RADIUS_FACTOR;
// Actualizar ángulos de rotación
angle_y_ += ROTOBALL_ROTATION_SPEED_Y * delta_time;
angle_x_ += ROTOBALL_ROTATION_SPEED_X * delta_time;
}
void SphereShape::getPoint3D(int index, float& x, float& y, float& z) const {
// Algoritmo Fibonacci Sphere para distribución uniforme
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>(index) / static_cast<float>(num_points_);
float phi = acosf(1.0f - 2.0f * t); // Latitud
float theta = angle_increment * static_cast<float>(index); // Longitud
// Convertir coordenadas esféricas a cartesianas
float x_base = cosf(theta) * sinf(phi) * radius_;
float y_base = sinf(theta) * sinf(phi) * radius_;
float z_base = cosf(phi) * radius_;
// Aplicar rotación en eje Y
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;
// Aplicar rotación en eje X
float cos_x = cosf(angle_x_);
float sin_x = sinf(angle_x_);
float y_rot = y_base * cos_x - z_rot * sin_x;
float z_final = y_base * sin_x + z_rot * cos_x;
// Retornar coordenadas finales rotadas
x = x_rot;
y = y_rot;
z = z_final;
}
float SphereShape::getScaleFactor(float screen_height) const {
// Factor de escala para física: proporcional al radio
// Radio base = 80px (resolución 320x240)
const float BASE_RADIUS = 80.0f;
float current_radius = screen_height * ROTOBALL_RADIUS_FACTOR;
return current_radius / BASE_RADIUS;
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "shape.h"
// Figura: Esfera 3D con distribución uniforme (Fibonacci Sphere Algorithm)
// Comportamiento: Rotación dual en ejes X e Y
// Uso anterior: RotoBall
class SphereShape : public Shape {
private:
float angle_x_ = 0.0f; // Ángulo de rotación en eje X (rad)
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
float radius_ = 0.0f; // Radio de la esfera (píxeles)
int num_points_ = 0; // Cantidad de puntos generados
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;
void getPoint3D(int index, float& x, float& y, float& z) const override;
const char* getName() const override { return "SPHERE"; }
float getScaleFactor(float screen_height) const override;
};