diff --git a/source/defines.h b/source/defines.h index 7fbc2df..772ef46 100644 --- a/source/defines.h +++ b/source/defines.h @@ -126,6 +126,12 @@ constexpr float CYLINDER_RADIUS_FACTOR = 0.25f; // Radio del cilindro (propor constexpr float CYLINDER_HEIGHT_FACTOR = 0.5f; // Altura del cilindro (proporción de altura) constexpr float CYLINDER_ROTATION_SPEED_Y = 1.0f; // Velocidad rotación eje Y (rad/s) +// Configuración de Icosahedron (icosaedro D20) +constexpr float ICOSAHEDRON_RADIUS_FACTOR = 0.30f; // Radio de la esfera circunscrita +constexpr float ICOSAHEDRON_ROTATION_SPEED_X = 0.4f; // Velocidad rotación eje X (rad/s) +constexpr float ICOSAHEDRON_ROTATION_SPEED_Y = 0.7f; // Velocidad rotación eje Y (rad/s) +constexpr float ICOSAHEDRON_ROTATION_SPEED_Z = 0.2f; // Velocidad rotación eje Z (rad/s) + // Control manual de escala de figuras 3D (Numpad +/-) constexpr float SHAPE_SCALE_MIN = 0.3f; // Escala mínima (30%) constexpr float SHAPE_SCALE_MAX = 3.0f; // Escala máxima (300%) diff --git a/source/engine.cpp b/source/engine.cpp index a68b00f..89f78e1 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -23,12 +23,13 @@ #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 -#include "shapes/helix_shape.h" // for HelixShape -#include "shapes/wave_grid_shape.h" // for WaveGridShape -#include "shapes/torus_shape.h" // for TorusShape -#include "shapes/cylinder_shape.h" // for CylinderShape +#include "shapes/sphere_shape.h" // for SphereShape +#include "shapes/cube_shape.h" // for CubeShape +#include "shapes/helix_shape.h" // for HelixShape +#include "shapes/wave_grid_shape.h" // for WaveGridShape +#include "shapes/torus_shape.h" // for TorusShape +#include "shapes/cylinder_shape.h" // for CylinderShape +#include "shapes/icosahedron_shape.h" // for IcosahedronShape // Función auxiliar para obtener la ruta del directorio del ejecutable std::string getExecutableDirectory() { @@ -1084,6 +1085,9 @@ void Engine::activateShape(ShapeType type) { case ShapeType::CYLINDER: active_shape_ = std::make_unique(); break; + case ShapeType::ICOSAHEDRON: + active_shape_ = std::make_unique(); + break; // Futuras figuras se añadirán aquí default: active_shape_ = std::make_unique(); // Fallback diff --git a/source/shapes/icosahedron_shape.cpp b/source/shapes/icosahedron_shape.cpp new file mode 100644 index 0000000..5fac70f --- /dev/null +++ b/source/shapes/icosahedron_shape.cpp @@ -0,0 +1,151 @@ +#include "icosahedron_shape.h" +#include "../defines.h" +#include +#include + +void IcosahedronShape::generatePoints(int num_points, float screen_width, float screen_height) { + num_points_ = num_points; + radius_ = screen_height * ICOSAHEDRON_RADIUS_FACTOR; + // Los 12 vértices del icosaedro se calculan en getPoint3D() +} + +void IcosahedronShape::update(float delta_time, float screen_width, float screen_height) { + // Recalcular radio por si cambió resolución (F4) + radius_ = screen_height * ICOSAHEDRON_RADIUS_FACTOR; + + // Actualizar ángulos de rotación (triple rotación XYZ) + angle_x_ += ICOSAHEDRON_ROTATION_SPEED_X * delta_time; + angle_y_ += ICOSAHEDRON_ROTATION_SPEED_Y * delta_time; + angle_z_ += ICOSAHEDRON_ROTATION_SPEED_Z * delta_time; +} + +void IcosahedronShape::getPoint3D(int index, float& x, float& y, float& z) const { + // Proporción áurea (golden ratio) + const float phi = (1.0f + sqrtf(5.0f)) / 2.0f; + + // 12 vértices del icosaedro regular normalizado + // Basados en 3 rectángulos áureos ortogonales + static const float vertices[12][3] = { + // Rectángulo XY + {-1.0f, phi, 0.0f}, + { 1.0f, phi, 0.0f}, + {-1.0f, -phi, 0.0f}, + { 1.0f, -phi, 0.0f}, + // Rectángulo YZ + { 0.0f, -1.0f, phi}, + { 0.0f, 1.0f, phi}, + { 0.0f, -1.0f, -phi}, + { 0.0f, 1.0f, -phi}, + // Rectángulo ZX + { phi, 0.0f, -1.0f}, + { phi, 0.0f, 1.0f}, + {-phi, 0.0f, -1.0f}, + {-phi, 0.0f, 1.0f} + }; + + // Normalizar para esfera circunscrita + const float normalization = sqrtf(1.0f + phi * phi); + + // Si tenemos 12 o menos puntos, usar solo vértices + if (num_points_ <= 12) { + int vertex_index = index % 12; + float x_base = vertices[vertex_index][0] / normalization * radius_; + float y_base = vertices[vertex_index][1] / normalization * radius_; + float z_base = vertices[vertex_index][2] / normalization * radius_; + + // Aplicar rotaciones + applyRotations(x_base, y_base, z_base, x, y, z); + return; + } + + // Para más de 12 puntos: subdividir caras triangulares + // Distribuir puntos entre vértices (primero) y caras (después) + if (index < 12) { + // Primeros 12 puntos: vértices del icosaedro + float x_base = vertices[index][0] / normalization * radius_; + float y_base = vertices[index][1] / normalization * radius_; + float z_base = vertices[index][2] / normalization * radius_; + applyRotations(x_base, y_base, z_base, x, y, z); + return; + } + + // Puntos restantes: distribuir en caras usando interpolación + // El icosaedro tiene 20 caras triangulares + int remaining_points = index - 12; + int points_per_face = (num_points_ - 12) / 20; + if (points_per_face < 1) points_per_face = 1; + + int face_index = remaining_points / points_per_face; + if (face_index >= 20) face_index = 19; + + int point_in_face = remaining_points % points_per_face; + + // Definir algunas caras del icosaedro (usando índices de vértices) + // Solo necesitamos generar puntos, no renderizar caras completas + static const int faces[20][3] = { + {0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11}, + {1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8}, + {3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9}, + {4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1} + }; + + // Obtener vértices de la cara + int v0 = faces[face_index][0]; + int v1 = faces[face_index][1]; + int v2 = faces[face_index][2]; + + // Interpolar dentro del triángulo usando coordenadas baricéntricas simples + float t = static_cast(point_in_face) / static_cast(points_per_face + 1); + float u = sqrtf(t); + float v = t - u; + + float x_interp = vertices[v0][0] * (1.0f - u - v) + vertices[v1][0] * u + vertices[v2][0] * v; + float y_interp = vertices[v0][1] * (1.0f - u - v) + vertices[v1][1] * u + vertices[v2][1] * v; + float z_interp = vertices[v0][2] * (1.0f - u - v) + vertices[v1][2] * u + vertices[v2][2] * v; + + // Proyectar a la esfera + float len = sqrtf(x_interp * x_interp + y_interp * y_interp + z_interp * z_interp); + if (len > 0.0001f) { + x_interp /= len; + y_interp /= len; + z_interp /= len; + } + + float x_base = x_interp * radius_; + float y_base = y_interp * radius_; + float z_base = z_interp * radius_; + + applyRotations(x_base, y_base, z_base, x, y, z); +} + +void IcosahedronShape::applyRotations(float x_in, float y_in, float z_in, float& x_out, float& y_out, float& z_out) const { + // Aplicar rotación en eje X + float cos_x = cosf(angle_x_); + float sin_x = sinf(angle_x_); + float y_rot_x = y_in * cos_x - z_in * sin_x; + float z_rot_x = y_in * sin_x + z_in * cos_x; + + // Aplicar rotación en eje Y + float cos_y = cosf(angle_y_); + float sin_y = sinf(angle_y_); + float x_rot_y = x_in * cos_y - z_rot_x * sin_y; + float z_rot_y = x_in * sin_y + z_rot_x * cos_y; + + // Aplicar rotación en eje Z + float cos_z = cosf(angle_z_); + float sin_z = sinf(angle_z_); + float x_final = x_rot_y * cos_z - y_rot_x * sin_z; + float y_final = x_rot_y * sin_z + y_rot_x * cos_z; + + x_out = x_final; + y_out = y_final; + z_out = z_rot_y; +} + +float IcosahedronShape::getScaleFactor(float screen_height) const { + // Factor de escala para física: proporcional al radio + // Radio base = 72px (0.30 * 240px en resolución 320x240) + const float BASE_RADIUS = 72.0f; + float current_radius = screen_height * ICOSAHEDRON_RADIUS_FACTOR; + return current_radius / BASE_RADIUS; +} diff --git a/source/shapes/icosahedron_shape.h b/source/shapes/icosahedron_shape.h new file mode 100644 index 0000000..e5d7a3c --- /dev/null +++ b/source/shapes/icosahedron_shape.h @@ -0,0 +1,25 @@ +#pragma once + +#include "shape.h" + +// Figura: Icosaedro 3D (D20, poliedro regular de 20 caras) +// Comportamiento: 12 vértices distribuidos uniformemente con rotación triple +// Geometría: Basado en proporción áurea (golden ratio) +class IcosahedronShape : 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 radius_ = 0.0f; // Radio de la esfera circunscrita (píxeles) + int num_points_ = 0; // Cantidad de puntos generados + + // Helper para aplicar rotaciones triple XYZ + void applyRotations(float x_in, float y_in, float z_in, float& x_out, float& y_out, float& z_out) const; + +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 "ICOSAHEDRON"; } + float getScaleFactor(float screen_height) const override; +};