Implementar figura ICOSAHEDRON (D20 poliedro) - Tecla U
- Nueva clase IcosahedronShape con 12 vértices golden ratio - Vértices basados en 3 rectángulos áureos ortogonales - Subdivisión de caras para más de 12 puntos - Rotación triple simultánea (X, Y, Z) - Proyección a esfera circunscrita - Compatible con física spring-damper y z-sorting 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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_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)
|
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 +/-)
|
// Control manual de escala de figuras 3D (Numpad +/-)
|
||||||
constexpr float SHAPE_SCALE_MIN = 0.3f; // Escala mínima (30%)
|
constexpr float SHAPE_SCALE_MIN = 0.3f; // Escala mínima (30%)
|
||||||
constexpr float SHAPE_SCALE_MAX = 3.0f; // Escala máxima (300%)
|
constexpr float SHAPE_SCALE_MAX = 3.0f; // Escala máxima (300%)
|
||||||
|
|||||||
@@ -23,12 +23,13 @@
|
|||||||
#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/sphere_shape.h" // for SphereShape
|
||||||
#include "shapes/cube_shape.h" // for CubeShape
|
#include "shapes/cube_shape.h" // for CubeShape
|
||||||
#include "shapes/helix_shape.h" // for HelixShape
|
#include "shapes/helix_shape.h" // for HelixShape
|
||||||
#include "shapes/wave_grid_shape.h" // for WaveGridShape
|
#include "shapes/wave_grid_shape.h" // for WaveGridShape
|
||||||
#include "shapes/torus_shape.h" // for TorusShape
|
#include "shapes/torus_shape.h" // for TorusShape
|
||||||
#include "shapes/cylinder_shape.h" // for CylinderShape
|
#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
|
// Función auxiliar para obtener la ruta del directorio del ejecutable
|
||||||
std::string getExecutableDirectory() {
|
std::string getExecutableDirectory() {
|
||||||
@@ -1084,6 +1085,9 @@ void Engine::activateShape(ShapeType type) {
|
|||||||
case ShapeType::CYLINDER:
|
case ShapeType::CYLINDER:
|
||||||
active_shape_ = std::make_unique<CylinderShape>();
|
active_shape_ = std::make_unique<CylinderShape>();
|
||||||
break;
|
break;
|
||||||
|
case ShapeType::ICOSAHEDRON:
|
||||||
|
active_shape_ = std::make_unique<IcosahedronShape>();
|
||||||
|
break;
|
||||||
// Futuras figuras se añadirán aquí
|
// Futuras figuras se añadirán aquí
|
||||||
default:
|
default:
|
||||||
active_shape_ = std::make_unique<SphereShape>(); // Fallback
|
active_shape_ = std::make_unique<SphereShape>(); // Fallback
|
||||||
|
|||||||
151
source/shapes/icosahedron_shape.cpp
Normal file
151
source/shapes/icosahedron_shape.cpp
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
#include "icosahedron_shape.h"
|
||||||
|
#include "../defines.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<float>(point_in_face) / static_cast<float>(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;
|
||||||
|
}
|
||||||
25
source/shapes/icosahedron_shape.h
Normal file
25
source/shapes/icosahedron_shape.h
Normal file
@@ -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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user