diff --git a/CMakeLists.txt b/CMakeLists.txt index 07c6bf2..59ee506 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ if (NOT SDL3_FOUND) endif() # 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") # Comprobar si se encontraron archivos fuente diff --git a/source/defines.h b/source/defines.h index 115b28c..8879ff1 100644 --- a/source/defines.h +++ b/source/defines.h @@ -1,5 +1,7 @@ #pragma once +#include // for Uint64 + // Configuración de ventana y pantalla constexpr char WINDOW_CAPTION[] = "vibe3_physics"; @@ -58,10 +60,23 @@ enum class ColorTheme { 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 class SimulationMode { 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) @@ -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_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_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_NEAR_THRESHOLD = 5.0f; // Distancia "cerca" en píxeles 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 \ No newline at end of file diff --git a/source/engine.cpp b/source/engine.cpp index be7f4ab..f1e8366 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -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((static_cast(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(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(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(i) / static_cast(num_points); - float phi = acosf(1.0f - 2.0f * t); // Latitud - float theta = angle_increment * static_cast(i); // Longitud + // Crear instancia polimórfica de la figura correspondiente + switch (type) { + case ShapeType::SPHERE: + active_shape_ = std::make_unique(); + break; + case ShapeType::CUBE: + active_shape_ = std::make_unique(); + break; + // Futuras figuras se añadirán aquí + default: + active_shape_ = std::make_unique(); // 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(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(balls_.size()); + active_shape_->generatePoints(num_points, static_cast(current_screen_width_), static_cast(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(current_screen_width_), static_cast(current_screen_height_)); + + // Obtener factor de escala para física + float scale_factor = active_shape_->getScaleFactor(static_cast(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(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(i) / static_cast(num_points); - float phi = acosf(1.0f - 2.0f * t); - float theta = angle_increment * static_cast(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(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); } diff --git a/source/engine.h b/source/engine.h index 4f8b32c..ee6efdf 100644 --- a/source/engine.h +++ b/source/engine.h @@ -10,9 +10,10 @@ #include // for string #include // for vector -#include "defines.h" // for GravityDirection, ColorTheme +#include "defines.h" // for GravityDirection, ColorTheme, ShapeType #include "ball.h" // for Ball #include "external/texture.h" // for Texture +#include "shapes/shape.h" // for Shape (interfaz polimórfica) class Engine { public: @@ -80,15 +81,11 @@ private: // Temas de colores definidos ThemeColors themes_[5]; - // Sistema RotoBall (esfera 3D rotante) + // Sistema de Figuras 3D (polimórfico) SimulationMode current_mode_ = SimulationMode::PHYSICS; - struct RotoBallData { - float angle_y = 0.0f; // Ángulo de rotación en eje Y - float angle_x = 0.0f; // Ángulo de rotación en eje X - float transition_progress = 0.0f; // Progreso de transición (0.0-1.0) - bool transitioning = false; // ¿Está en transición? - }; - RotoBallData rotoball_; + ShapeType current_shape_type_ = ShapeType::SPHERE; // Tipo de figura actual + ShapeType last_shape_type_ = ShapeType::SPHERE; // Última figura para toggle F + std::unique_ptr active_shape_; // Puntero polimórfico a figura activa // Batch rendering std::vector batch_vertices_; @@ -127,8 +124,9 @@ private: void renderGradientBackground(); void addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b); - // Sistema RotoBall - void toggleRotoBallMode(bool force_gravity_on_exit = true); - void generateRotoBallSphere(); - void updateRotoBall(); -}; \ No newline at end of file + // Sistema de Figuras 3D + void toggleShapeMode(bool force_gravity_on_exit = true); // Toggle PHYSICS ↔ última figura (tecla F) + void activateShape(ShapeType type); // Activar figura específica (teclas Q/W/E/R/Y/U/I) + void updateShape(); // Actualizar figura activa + void generateShape(); // Generar puntos de figura activa +}; diff --git a/source/shapes/cube_shape.cpp b/source/shapes/cube_shape.cpp new file mode 100644 index 0000000..0fb84cd --- /dev/null +++ b/source/shapes/cube_shape.cpp @@ -0,0 +1,168 @@ +#include "cube_shape.h" +#include "../defines.h" +#include + +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(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(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(ceilf(cbrtf(static_cast(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(base_x_.size()) >= num_points_) { + return; + } + } + } + } +} diff --git a/source/shapes/cube_shape.h b/source/shapes/cube_shape.h new file mode 100644 index 0000000..72a549d --- /dev/null +++ b/source/shapes/cube_shape.h @@ -0,0 +1,37 @@ +#pragma once + +#include "shape.h" +#include + +// 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 base_x_; + std::vector base_y_; + std::vector 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 +}; diff --git a/source/shapes/shape.h b/source/shapes/shape.h new file mode 100644 index 0000000..836c93d --- /dev/null +++ b/source/shapes/shape.h @@ -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; +}; diff --git a/source/shapes/sphere_shape.cpp b/source/shapes/sphere_shape.cpp new file mode 100644 index 0000000..c4b8df3 --- /dev/null +++ b/source/shapes/sphere_shape.cpp @@ -0,0 +1,58 @@ +#include "sphere_shape.h" +#include "../defines.h" +#include + +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(index) / static_cast(num_points_); + float phi = acosf(1.0f - 2.0f * t); // Latitud + float theta = angle_increment * static_cast(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; +} diff --git a/source/shapes/sphere_shape.h b/source/shapes/sphere_shape.h new file mode 100644 index 0000000..ebb1948 --- /dev/null +++ b/source/shapes/sphere_shape.h @@ -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; +};