Respuesta rápida
¿Cómo se hace un sistema solar 3D con Three.js?
Un sistema solar 3D en Three.js se construye con mecánica orbital real de Kepler (semieje, excentricidad, inclinación de la NASA), un motor que resuelve la ecuación M = E − e·sin(E) por Newton-Raphson, y técnicas de rendimiento como frustum culling y LOD para mantener 60fps con planetas, 95+ lunas y 200.000 estrellas. El mío tiene 8.087 líneas en un solo archivo, sin bundler ni frameworks, solo Three.js r128.
Por qué un Sistema Solar
Cuando decidí que mi portfolio necesitaba gadgets interactivos que fueran más allá de un CRUD, quise algo que combinase complejidad técnica visible con wow factor inmediato. Un simulador astronómico cumple ambas: el usuario ve planetas orbitando en 3D y puede interactuar con ellos, mientras por debajo corre mecánica orbital real, shaders personalizados y un sistema de gestión de estado complejo.
El resultado: 8.087 líneas de JavaScript puro en un solo archivo (solar-system-pro.js), sin bundler, sin React, sin TypeScript. Solo Three.js r128, Launch Library 2 API, y mucha trigonometría.
Arquitectura General
El archivo se organiza en bloques funcionales claros:
| Bloque | Líneas | Función |
|---|---|---|
| Datos NASA | ~800 | Constantes orbitales de planetas, lunas y NEOs |
| Scene Setup | ~400 | Renderer, cámara, luces, post-processing |
| Planet Factory | ~600 | Generación procedural de texturas y geometrías |
| Moon System | ~500 | 95+ lunas con órbitas independientes |
| Orbit Engine | ~700 | Kepler + interpolación temporal |
| UI/Controls | ~900 | Panel de control, sliders, teclado |
| Interstellar Mode | ~400 | Skybox, 200K estrellas, nebulosas |
| Missions & NEOs | ~350 | Trayectorias de Voyager, Cassini, etc. |
| Info Panels | ~500 | HUD con datos de cada cuerpo celeste |
| Constellations | ~300 | 88 constelaciones con líneas y nombres |
Mecánica Orbital de Kepler
El corazón del simulador es la implementación de las leyes de Kepler. Cada planeta tiene datos orbitales reales de la NASA: semieje mayor, excentricidad, inclinación, longitud del nodo ascendente, argumento del periastro y anomalía media en J2000.
La Ecuación de Kepler
El problema fundamental es convertir el tiempo en una posición. Kepler lo resuelve con la anomalía excéntrica (E), que se calcula iterativamente:
// Resolver ecuación de Kepler: M = E - e·sin(E)
function solveKepler(M, e, tolerance = 1e-8) {
let E = M; // Aproximación inicial
for (let i = 0; i < 50; i++) {
const dE = (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
E -= dE;
if (Math.abs(dE) < tolerance) break;
}
return E;
}
// De anomalía excéntrica a coordenadas 3D
function keplerToCartesian(a, e, i, omega, Omega, E) {
const cosE = Math.cos(E), sinE = Math.sin(E);
const r = a * (1 - e * cosE); // Distancia al Sol
const v = Math.atan2(
Math.sqrt(1 - e*e) * sinE,
cosE - e
); // Anomalía verdadera
// Rotación al plano eclíptico
const cosV = Math.cos(v + omega);
const sinV = Math.sin(v + omega);
const cosO = Math.cos(Omega);
const sinO = Math.sin(Omega);
const cosI = Math.cos(i);
const sinI = Math.sin(i);
return new THREE.Vector3(
r * (cosO * cosV - sinO * sinV * cosI),
r * sinV * sinI,
r * (sinO * cosV + cosO * sinV * cosI)
);
}
Este método Newton-Raphson converge en 3-5 iteraciones para órbitas planetarias. Para cometas con alta excentricidad (e > 0.9), necesita más iteraciones — pero funciona.
Control Temporal
El usuario puede acelerar el tiempo desde 1x (tiempo real) hasta 1.000.000x. A 100.000x, Mercurio orbita en ~7 segundos. El sistema mantiene un julianDate que avanza según el multiplicador:
const dt = clock.getDelta() * timeMultiplier;
julianDate += dt / 86400; // Convertir a días julianos
planets.forEach(p => p.updatePosition(julianDate));
95+ Lunas con Órbitas Independientes
Cada luna tiene su propia órbita alrededor de su planeta padre, con datos reales: Io, Europa, Ganímedes, Calisto, Titán, Encélado... 95 en total. El truco de rendimiento es que las lunas de planetas no visibles (fuera de cámara) no se calculan:
// Frustum culling para lunas
const frustum = new THREE.Frustum();
frustum.setFromProjectionMatrix(
new THREE.Matrix4().multiplyMatrices(
camera.projectionMatrix,
camera.matrixWorldInverse
)
);
moons.forEach(moon => {
const parentVisible = frustum.containsPoint(moon.parent.position);
moon.mesh.visible = parentVisible;
if (parentVisible) moon.updateOrbit(julianDate);
});
El Modo Interestelar
Al activar el modo interestelar, la cámara sale del sistema solar y aparece un campo de 200.000 estrellas generadas proceduralmente con THREE.Points y un shader personalizado que controla brillo y color por temperatura estelar:
// Distribución de estrellas con aspecto de Vía Láctea
for (let i = 0; i < 200000; i++) {
const theta = Math.random() * Math.PI * 2;
const r = Math.pow(Math.random(), 0.5) * 500; // Disco galáctico
const h = (Math.random() - 0.5) * 20 * Math.exp(-r / 100);
positions.push(r * Math.cos(theta), h, r * Math.sin(theta));
// Color por temperatura: azul (caliente) → blanco → rojo (fría)
const temp = Math.random();
colors.push(
0.5 + temp * 0.5, // R
0.5 + temp * 0.3, // G
0.8 + (1-temp) * 0.2 // B
);
}
Rendimiento: 60fps con Todo Encendido
El mayor desafío fue mantener 60fps con planetas + lunas + estrellas + anillos + órbitas + etiquetas + partículas. Las técnicas clave:
- LOD (Level of Detail): Los planetas lejanos usan geometrías con menos segmentos
- Frustum culling manual para lunas y etiquetas
- Object pooling para partículas de anillos
- requestAnimationFrame sincronizado con el monitor
- Lazy init: Las constelaciones y misiones no se crean hasta que el usuario las activa
v7.0: 6 Experiencias Que Nadie Más Tiene
Con la base técnica sólida, me propuse crear features que ningún otro simulador de portfolio ofrece. La idea: competir en ingenio, no en fuerza bruta. No puedo igualar a NASA Eyes con su equipo de JPL, pero sí puedo crear experiencias que ellos no han pensado hacer.
Y aquí es importante ser transparente: yo no soy un programador experto en Three.js. Este proyecto lo construí íntegramente apoyándome en Claude de Anthropic (modelos Opus 4.5 y 4.6) como mi pair programmer. Yo aporté la visión de producto, las decisiones de diseño y el criterio para validar cada resultado. Claude aportó el conocimiento técnico profundo: mecánica orbital, shaders, optimización WebGL, integración de APIs. Cada línea de código fue una conversación entre los dos. Es la demostración práctica de que una persona con criterio y una IA potente pueden competir con equipos enteros.
Pale Blue Dot (tecla B) — La cámara viaja hasta Voyager 1 a 23,5 mil millones de km. La Tierra se convierte en un punto de 0.12 píxeles. Aparece el texto de Carl Sagan con fade progresivo. Es puro storytelling cinemático en Three.js: animación de cámara interpolada, el planeta se encoge en tiempo real, y el efecto emocional es inmediato.
Humanity's Reach (tecla U) — Seguimiento en tiempo real de las 5 sondas más lejanas de la humanidad. Los datos de referencia vienen de NASA JPL Horizons (posición y velocidad en fecha conocida), y la distancia se extrapola segundo a segundo: distAU = refDist + velocity × elapsed. Voyager 1 está a ~165 AU y se aleja 3.6 AU/año.
Space Race (tecla L) — Dashboard con el ranking de lanzamientos 2024 (SpaceX: 131, China: 68...) renderizado con Canvas 2D, y un panel de próximos lanzamientos que se alimenta en vivo de Launch Library 2 API (lldev.thespacedevs.com) con countdown T-minus actualizándose cada segundo. Si la API no responde, cae a un donut chart de distribución orbital como fallback.
Light-Time — Selecciona un planeta, pulsa "Enviar señal": un pulso de luz viaja de la Tierra al destino en la escena 3D, con countdown del tiempo real de la luz (4.3 min a Marte, 5.5 horas a Plutón) comprimido a una animación de 2-12s. Factoids contextuales rotan cada 3s: "En 43 minutos podrías cocinar un plato completo".
Lo Que Aprendí
Construir este simulador me enseñó más sobre gestión de complejidad que cualquier curso:
- Un archivo de 8.000+ líneas necesita convenciones estrictas para no perderse
- Los datos reales son desordenados — las órbitas de la NASA vienen en distintos formatos y épocas
- Three.js es increíblemente capaz, pero hay que entender WebGL para optimizar bien
- El "wow factor" importa: un reclutador dedica 30 segundos a tu portfolio — un sistema solar interactivo retiene la atención
- Las APIs externas fallan — siempre necesitas un fallback sólido con datos estáticos curados
- La colaboración humano-IA funciona de verdad: todo el código de este proyecto fue escrito con Claude de Anthropic (Opus 4.5 y 4.6). Yo dirigí, validé y decidí; Claude programó, optimizó y resolvió los problemas técnicos. 8.087 líneas en un solo archivo, cero líneas copiadas de Stack Overflow
Si puedes simular el sistema solar con mecánica orbital real en un navegador, puedes construir cualquier dashboard interactivo que te pidan en producción.
El código completo está en el portfolio en cristiancorrales.com. Abre Solar System Pro y dale a "OBSERVATORIO" para la experiencia completa. Prueba las teclas B, U, Y, L para las experiencias exclusivas.
{label}
- Three.js docs: clase Frustum (setFromProjectionMatrix, containsPoint) — El frustum culling manual de lunas con la API oficial de Three.js
- NASA JPL: elementos keplerianos aproximados de los planetas — Datos orbitales J2000 y resolución iterativa de la ecuación de Kepler
- MDN: requestAnimationFrame — rAF sincronizado con el refresco del monitor y pausa en pestañas ocultas
¿Tu web no trae clientes?
Diseño webs que cargan rápido y convierten, para negocios de Tarragona y alrededores. Precios claros desde el primer día.
Ver el servicio de diseño web →¿Quieres una cifra orientativa ya? Configura tu web y ve el precio al instante →
¿Quieres ver casos reales de IA en pymes?
Te escribo solo cuando tengo un caso con costes y resultados reales, o una herramienta que merece la pena. Sin humo, sin spam y baja en un clic.



