4 Commits

Author SHA1 Message Date
5391e0cddf Implementar figura ATOM (núcleo + órbitas) - Tecla I - TODAS LAS FIGURAS COMPLETADAS
- Nueva clase AtomShape con núcleo central + 3 órbitas
- Núcleo: esfera pequeña con distribución Fibonacci
- Órbitas: planos inclinados con electrones animados
- Rotación global + rotación orbital independiente
- Modelo atómico clásico de Bohr
- Compatible con física spring-damper y z-sorting

 TODAS LAS 8 FIGURAS 3D IMPLEMENTADAS:
Q-Sphere, W-WaveGrid, E-Helix, R-Torus, T-Cube, Y-Cylinder, U-Icosahedron, I-Atom

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-04 06:44:02 +02:00
fb788666cc 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>
2025-10-04 06:41:38 +02:00
ac3309ffd1 Implementar figura CYLINDER (cilindro 3D) - Tecla Y
- Nueva clase CylinderShape con ecuaciones paramétricas
- Distribución uniforme en anillos y circunferencia
- Rotación simple en eje Y
- Dimensiones: radius=0.25, height=0.5 (proporción altura)
- 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>
2025-10-04 06:38:58 +02:00
8b642f6903 Implementar figura TORUS (toroide/donut 3D) - Tecla R
- Nueva clase TorusShape con ecuaciones paramétricas
- Distribución uniforme en anillos y puntos por anillo
- Rotación triple simultánea (X, Y, Z)
- Radios: major=0.25, minor=0.12 (proporción altura)
- Compatible con física spring-damper y z-sorting
- Escalable con Numpad +/-

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-04 06:36:46 +02:00
10 changed files with 556 additions and 5 deletions

View File

@@ -114,6 +114,31 @@ constexpr float WAVE_GRID_FREQUENCY = 3.0f; // Frecuencia de ondas (ciclo
constexpr float WAVE_GRID_PHASE_SPEED = 2.0f; // Velocidad de animación de ondas (rad/s)
constexpr float WAVE_GRID_ROTATION_SPEED_Y = 0.4f; // Velocidad rotación eje Y (rad/s)
// Configuración de Torus (toroide/donut 3D)
constexpr float TORUS_MAJOR_RADIUS_FACTOR = 0.25f; // Radio mayor R (centro torus a centro tubo)
constexpr float TORUS_MINOR_RADIUS_FACTOR = 0.12f; // Radio menor r (grosor del tubo)
constexpr float TORUS_ROTATION_SPEED_X = 0.6f; // Velocidad rotación eje X (rad/s)
constexpr float TORUS_ROTATION_SPEED_Y = 0.9f; // Velocidad rotación eje Y (rad/s)
constexpr float TORUS_ROTATION_SPEED_Z = 0.3f; // Velocidad rotación eje Z (rad/s)
// Configuración de Cylinder (cilindro 3D)
constexpr float CYLINDER_RADIUS_FACTOR = 0.25f; // Radio 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)
// 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)
// Configuración de Atom (núcleo con órbitas electrónicas)
constexpr float ATOM_NUCLEUS_RADIUS_FACTOR = 0.08f; // Radio del núcleo central
constexpr float ATOM_ORBIT_RADIUS_FACTOR = 0.30f; // Radio de las órbitas
constexpr float ATOM_NUM_ORBITS = 3; // Número de órbitas
constexpr float ATOM_ORBIT_ROTATION_SPEED = 2.0f; // Velocidad de electrones (rad/s)
constexpr float ATOM_ROTATION_SPEED_Y = 0.5f; // Velocidad rotación global (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%)

View File

@@ -23,10 +23,14 @@
#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/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
#include "shapes/atom_shape.h" // for AtomShape
// Función auxiliar para obtener la ruta del directorio del ejecutable
std::string getExecutableDirectory() {
@@ -1073,10 +1077,21 @@ void Engine::activateShape(ShapeType type) {
case ShapeType::HELIX:
active_shape_ = std::make_unique<HelixShape>();
break;
case ShapeType::TORUS:
active_shape_ = std::make_unique<TorusShape>();
break;
case ShapeType::WAVE_GRID:
active_shape_ = std::make_unique<WaveGridShape>();
break;
// Futuras figuras se añadirán aquí
case ShapeType::CYLINDER:
active_shape_ = std::make_unique<CylinderShape>();
break;
case ShapeType::ICOSAHEDRON:
active_shape_ = std::make_unique<IcosahedronShape>();
break;
case ShapeType::ATOM:
active_shape_ = std::make_unique<AtomShape>();
break;
default:
active_shape_ = std::make_unique<SphereShape>(); // Fallback
break;

View File

@@ -0,0 +1,96 @@
#include "atom_shape.h"
#include "../defines.h"
#include <cmath>
void AtomShape::generatePoints(int num_points, float screen_width, float screen_height) {
num_points_ = num_points;
nucleus_radius_ = screen_height * ATOM_NUCLEUS_RADIUS_FACTOR;
orbit_radius_ = screen_height * ATOM_ORBIT_RADIUS_FACTOR;
// Las posiciones se calculan en getPoint3D()
}
void AtomShape::update(float delta_time, float screen_width, float screen_height) {
// Recalcular dimensiones por si cambió resolución (F4)
nucleus_radius_ = screen_height * ATOM_NUCLEUS_RADIUS_FACTOR;
orbit_radius_ = screen_height * ATOM_ORBIT_RADIUS_FACTOR;
// Actualizar rotación global del átomo
angle_y_ += ATOM_ROTATION_SPEED_Y * delta_time;
// Actualizar fase de rotación de electrones en órbitas
orbit_phase_ += ATOM_ORBIT_ROTATION_SPEED * delta_time;
}
void AtomShape::getPoint3D(int index, float& x, float& y, float& z) const {
int num_orbits = static_cast<int>(ATOM_NUM_ORBITS);
// Calcular cuántos puntos para núcleo vs órbitas
int nucleus_points = (num_points_ < 10) ? 1 : (num_points_ / 10); // 10% para núcleo
if (nucleus_points < 1) nucleus_points = 1;
// Si estamos en el núcleo
if (index < nucleus_points) {
// Distribuir puntos en esfera pequeña (núcleo)
float t = static_cast<float>(index) / static_cast<float>(nucleus_points);
float phi = acosf(1.0f - 2.0f * t);
float theta = PI * 2.0f * t * 3.61803398875f; // Golden ratio
float x_nuc = nucleus_radius_ * cosf(theta) * sinf(phi);
float y_nuc = nucleus_radius_ * sinf(theta) * sinf(phi);
float z_nuc = nucleus_radius_ * cosf(phi);
// Aplicar rotación global
float cos_y = cosf(angle_y_);
float sin_y = sinf(angle_y_);
x = x_nuc * cos_y - z_nuc * sin_y;
y = y_nuc;
z = x_nuc * sin_y + z_nuc * cos_y;
return;
}
// Puntos restantes: distribuir en órbitas
int orbit_points = num_points_ - nucleus_points;
int points_per_orbit = orbit_points / num_orbits;
if (points_per_orbit < 1) points_per_orbit = 1;
int orbit_index = (index - nucleus_points) / points_per_orbit;
if (orbit_index >= num_orbits) orbit_index = num_orbits - 1;
int point_in_orbit = (index - nucleus_points) % points_per_orbit;
// Ángulo del electrón en su órbita
float electron_angle = (static_cast<float>(point_in_orbit) / static_cast<float>(points_per_orbit)) * 2.0f * PI;
electron_angle += orbit_phase_; // Añadir rotación animada
// Inclinación del plano orbital (cada órbita en ángulo diferente)
float orbit_tilt = (static_cast<float>(orbit_index) / static_cast<float>(num_orbits)) * PI;
// Posición del electrón en su órbita (plano XY local)
float x_local = orbit_radius_ * cosf(electron_angle);
float y_local = orbit_radius_ * sinf(electron_angle);
float z_local = 0.0f;
// Inclinar el plano orbital (rotación en eje X local)
float cos_tilt = cosf(orbit_tilt);
float sin_tilt = sinf(orbit_tilt);
float y_tilted = y_local * cos_tilt - z_local * sin_tilt;
float z_tilted = y_local * sin_tilt + z_local * cos_tilt;
// Aplicar rotación global del átomo (eje Y)
float cos_y = cosf(angle_y_);
float sin_y = sinf(angle_y_);
float x_rot = x_local * cos_y - z_tilted * sin_y;
float z_rot = x_local * sin_y + z_tilted * cos_y;
x = x_rot;
y = y_tilted;
z = z_rot;
}
float AtomShape::getScaleFactor(float screen_height) const {
// Factor de escala para física: proporcional al radio de órbita
// Radio órbita base = 72px (0.30 * 240px en resolución 320x240)
const float BASE_RADIUS = 72.0f;
float current_radius = screen_height * ATOM_ORBIT_RADIUS_FACTOR;
return current_radius / BASE_RADIUS;
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include "shape.h"
// Figura: Átomo con núcleo central y órbitas electrónicas
// Comportamiento: Núcleo estático + electrones orbitando en planos inclinados
// Efecto: Modelo atómico clásico Bohr
class AtomShape : public Shape {
private:
float angle_y_ = 0.0f; // Ángulo de rotación global en eje Y (rad)
float orbit_phase_ = 0.0f; // Fase de rotación de electrones (rad)
float nucleus_radius_ = 0.0f; // Radio del núcleo central (píxeles)
float orbit_radius_ = 0.0f; // Radio de las órbitas (píxeles)
int num_points_ = 0; // Cantidad total de puntos
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 "ATOM"; }
float getScaleFactor(float screen_height) const override;
};

View File

@@ -0,0 +1,77 @@
#include "cylinder_shape.h"
#include "../defines.h"
#include <cmath>
void CylinderShape::generatePoints(int num_points, float screen_width, float screen_height) {
num_points_ = num_points;
radius_ = screen_height * CYLINDER_RADIUS_FACTOR;
height_ = screen_height * CYLINDER_HEIGHT_FACTOR;
// Las posiciones 3D se calculan en getPoint3D() usando ecuaciones paramétricas del cilindro
}
void CylinderShape::update(float delta_time, float screen_width, float screen_height) {
// Recalcular dimensiones por si cambió resolución (F4)
radius_ = screen_height * CYLINDER_RADIUS_FACTOR;
height_ = screen_height * CYLINDER_HEIGHT_FACTOR;
// Actualizar ángulo de rotación en eje Y
angle_y_ += CYLINDER_ROTATION_SPEED_Y * delta_time;
}
void CylinderShape::getPoint3D(int index, float& x, float& y, float& z) const {
// Distribuir puntos uniformemente en la superficie del cilindro
// Calcular número de anillos (altura) y puntos por anillo (circunferencia)
int num_rings = static_cast<int>(sqrtf(static_cast<float>(num_points_) * 0.5f));
if (num_rings < 2) num_rings = 2;
int points_per_ring = num_points_ / num_rings;
if (points_per_ring < 3) points_per_ring = 3;
// Obtener parámetros u (ángulo) y v (altura) del índice
int ring = index / points_per_ring;
int point_in_ring = index % points_per_ring;
// Si nos pasamos del número de anillos, usar el último
if (ring >= num_rings) {
ring = num_rings - 1;
point_in_ring = index - (ring * points_per_ring);
if (point_in_ring >= points_per_ring) {
point_in_ring = points_per_ring - 1;
}
}
// Parámetro u (ángulo alrededor del cilindro): [0, 2π]
float u = (static_cast<float>(point_in_ring) / static_cast<float>(points_per_ring)) * 2.0f * PI;
// Parámetro v (altura normalizada): [-1, 1]
float v = (static_cast<float>(ring) / static_cast<float>(num_rings - 1)) * 2.0f - 1.0f;
if (num_rings == 1) v = 0.0f;
// Ecuaciones paramétricas del cilindro
// x = radius * cos(u)
// y = height * v
// z = radius * sin(u)
float x_base = radius_ * cosf(u);
float y_base = (height_ * 0.5f) * v; // Centrar verticalmente
float z_base = radius_ * sinf(u);
// 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;
// Retornar coordenadas finales rotadas
x = x_rot;
y = y_base;
z = z_rot;
}
float CylinderShape::getScaleFactor(float screen_height) const {
// Factor de escala para física: proporcional a la dimensión mayor (altura)
// Altura base = 120px (0.5 * 240px en resolución 320x240)
const float BASE_HEIGHT = 120.0f;
float current_height = screen_height * CYLINDER_HEIGHT_FACTOR;
return current_height / BASE_HEIGHT;
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "shape.h"
// Figura: Cilindro 3D rotante
// Comportamiento: Superficie cilíndrica con rotación en eje Y
// Ecuaciones: x = r*cos(u), y = v, z = r*sin(u)
class CylinderShape : public Shape {
private:
float angle_y_ = 0.0f; // Ángulo de rotación en eje Y (rad)
float radius_ = 0.0f; // Radio del cilindro (píxeles)
float height_ = 0.0f; // Altura del cilindro (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 "CYLINDER"; }
float getScaleFactor(float screen_height) const override;
};

View 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;
}

View 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;
};

View File

@@ -0,0 +1,96 @@
#include "torus_shape.h"
#include "../defines.h"
#include <cmath>
void TorusShape::generatePoints(int num_points, float screen_width, float screen_height) {
num_points_ = num_points;
major_radius_ = screen_height * TORUS_MAJOR_RADIUS_FACTOR;
minor_radius_ = screen_height * TORUS_MINOR_RADIUS_FACTOR;
// Las posiciones 3D se calculan en getPoint3D() usando ecuaciones paramétricas del torus
}
void TorusShape::update(float delta_time, float screen_width, float screen_height) {
// Recalcular radios por si cambió resolución (F4)
major_radius_ = screen_height * TORUS_MAJOR_RADIUS_FACTOR;
minor_radius_ = screen_height * TORUS_MINOR_RADIUS_FACTOR;
// Actualizar ángulos de rotación (triple rotación XYZ)
angle_x_ += TORUS_ROTATION_SPEED_X * delta_time;
angle_y_ += TORUS_ROTATION_SPEED_Y * delta_time;
angle_z_ += TORUS_ROTATION_SPEED_Z * delta_time;
}
void TorusShape::getPoint3D(int index, float& x, float& y, float& z) const {
// Distribuir puntos uniformemente en la superficie del torus
// Usamos distribución aproximadamente uniforme basada en área
// Calcular número aproximado de anillos y puntos por anillo
int num_rings = static_cast<int>(sqrtf(static_cast<float>(num_points_) * 0.5f));
if (num_rings < 2) num_rings = 2;
int points_per_ring = num_points_ / num_rings;
if (points_per_ring < 3) points_per_ring = 3;
// Obtener parámetros u y v del índice
int ring = index / points_per_ring;
int point_in_ring = index % points_per_ring;
// Si nos pasamos del número de anillos, usar el último
if (ring >= num_rings) {
ring = num_rings - 1;
point_in_ring = index - (ring * points_per_ring);
if (point_in_ring >= points_per_ring) {
point_in_ring = points_per_ring - 1;
}
}
// Parámetros u y v normalizados [0, 2π]
float u = (static_cast<float>(ring) / static_cast<float>(num_rings)) * 2.0f * PI;
float v = (static_cast<float>(point_in_ring) / static_cast<float>(points_per_ring)) * 2.0f * PI;
// Ecuaciones paramétricas del torus
// x = (R + r*cos(v)) * cos(u)
// y = (R + r*cos(v)) * sin(u)
// z = r * sin(v)
float cos_v = cosf(v);
float sin_v = sinf(v);
float cos_u = cosf(u);
float sin_u = sinf(u);
float radius_at_v = major_radius_ + minor_radius_ * cos_v;
float x_base = radius_at_v * cos_u;
float y_base = radius_at_v * sin_u;
float z_base = minor_radius_ * sin_v;
// Aplicar rotación en eje X
float cos_x = cosf(angle_x_);
float sin_x = sinf(angle_x_);
float y_rot_x = y_base * cos_x - z_base * sin_x;
float z_rot_x = y_base * sin_x + z_base * 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_base * cos_y - z_rot_x * sin_y;
float z_rot_y = x_base * 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;
// Retornar coordenadas finales rotadas
x = x_final;
y = y_final;
z = z_rot_y;
}
float TorusShape::getScaleFactor(float screen_height) const {
// Factor de escala para física: proporcional al radio mayor
// Radio mayor base = 60px (0.25 * 240px en resolución 320x240)
const float BASE_RADIUS = 60.0f;
float current_radius = screen_height * TORUS_MAJOR_RADIUS_FACTOR;
return current_radius / BASE_RADIUS;
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include "shape.h"
// Figura: Torus/Toroide 3D (donut/rosquilla)
// Comportamiento: Superficie toroidal con rotación triple (X, Y, Z)
// Ecuaciones: x = (R + r*cos(v))*cos(u), y = (R + r*cos(v))*sin(u), z = r*sin(v)
class TorusShape : 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 major_radius_ = 0.0f; // Radio mayor R (del centro al tubo)
float minor_radius_ = 0.0f; // Radio menor r (grosor del tubo)
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 "TORUS"; }
float getScaleFactor(float screen_height) const override;
};