Complexity: ⭐ Beginner-Intermediate
Concepts: 3D geometry, hierarchical transformations, texture mapping, orbital mechanics
Create an interactive, scientifically-accurate model of our solar system where planets orbit the Sun at correct relative speeds and distances. This project teaches fundamental 3D graphics concepts including object hierarchies (parent-child relationships), transformation matrices, and the animation loop pattern. You'll learn how complex motion (orbital paths) can be achieved through simple rotations when objects are properly parented. This is the foundation for understanding scene graphs, which are used in every 3D engine from Three.js to Unity to Unreal Engine.
Step 1: Understanding Object Hierarchies
The key insight for orbital motion is the parent-child relationship. Instead of calculating circular paths with trigonometry (x = radius * cos(angle), y = radius * sin(angle)), we use a smarter approach: create an invisible "pivot" object at the Sun's center, position the planet at the desired orbital radius from that pivot, then rotate the pivot. The planet automatically follows a circular path! This is how transform hierarchies work: child objects inherit their parent's transformations. When you rotate a parent, all children rotate around the parent's origin. This concept extends beyond orbits—it's used for character skeletons (bones), vehicle wheels, robotic arms, and any articulated system.
Step 2: Creating Planet Geometry
Start with THREE.SphereGeometry(radius, widthSegments, heightSegments). The radius determines size (use realistic relative scales: Jupiter should be ~11x Earth's diameter). The segment parameters control sphere tessellation (subdivision)—more segments = smoother sphere but more polygons. For planets, 32 segments is usually sufficient. Wrap spheres with THREE.MeshStandardMaterial (not MeshBasicMaterial) because MeshStandardMaterial responds to lighting, making planets look three-dimensional. Load planet textures with THREE.TextureLoader: new THREE.TextureLoader().load('earth.jpg', texture => material.map = texture).
Step 3: Setting Up the Orbit System
For each planet: (1) Create an empty THREE.Object3D() called the orbit pivot. Add it to the scene at position (0,0,0). (2) Create the planet mesh and position it at (orbitalRadius, 0, 0) relative to the pivot—this sets how far from the Sun the planet orbits. (3) Add the planet mesh as a child of the pivot: pivot.add(planetMesh). (4) In your animation loop, update pivot.rotation.y += orbitalSpeed. The planet automatically orbits! For realistic speeds, use Kepler's laws: orbital period ∝ radius^(3/2). Mercury orbits faster than Neptune.
Step 4: Adding Axial Rotation
Planets don't just orbit—they also spin on their axes. This is independent of orbital motion. In your animation loop, after updating the pivot rotation, also update the planet mesh's own rotation: planetMesh.rotation.y += spinSpeed. Now you have two simultaneous rotations: the pivot rotates (orbit), and the mesh rotates relative to the pivot (spin). This demonstrates transformation composition: the planet's final world-space rotation is pivot_rotation × mesh_rotation. For realism, tilt planet axes: planetMesh.rotation.z = axialtilt (Earth's is 23.5° = 0.41 radians).
Step 5: Adding Lighting and Atmosphere
Add a THREE.PointLight at the Sun's position to simulate sunlight: new THREE.PointLight(0xffffff, 2, 0). The intensity (2) and distance (0 = infinite) control brightness falloff. For the Sun itself, use THREE.MeshBasicMaterial with an emissive color so it glows without needing light. For atmospheric glow on planets like Earth, add a slightly larger, semi-transparent sphere mesh with a blue-tinted material around the planet: new THREE.MeshBasicMaterial({ color: 0x4488ff, transparent: true, opacity: 0.2, side: THREE.BackSide }). The BackSide makes it only visible from outside.
Core APIs:
-
THREE.SphereGeometry(radius, widthSegments, heightSegments): Creates UV-mapped sphere meshes. The UV mapping (texture coordinates) is automatically generated with poles at top/bottom and equator at center. Perfect for planets. Documentation: https://threejs.org/docs/#api/en/geometries/SphereGeometry -
THREE.Object3D: The base class for all objects in Three.js. Even though it's invisible and has no geometry, it still has position, rotation, and scale properties, making it perfect for pivot points. All transform properties areTHREE.Vector3orTHREE.Eulerobjects. Documentation: https://threejs.org/docs/#api/en/core/Object3D -
THREE.TextureLoader: Asynchronously loads image files as textures. Usage:loader.load(url, onLoad, onProgress, onError). The texture can be applied to material properties likemap(diffuse color),normalMap(surface detail),roughnessMap, etc. Supports JPG, PNG, and other web formats. Documentation: https://threejs.org/docs/#api/en/loaders/TextureLoader -
THREE.MeshStandardMaterial: A physically-based rendering (PBR) material that responds realistically to lights. Key properties:map(color texture),roughness(0=mirror, 1=matte),metalness(0=dielectric, 1=metal),emissive(glow color),normalMap(fake surface detail). Much more realistic thanMeshBasicMaterial. Documentation: https://threejs.org/docs/#api/en/materials/MeshStandardMaterial
Texture Resources:
- Solar System Scope Textures (https://www.solarsystemscope.com/textures/): Free, high-quality 2K and 4K planet textures. Includes diffuse, normal, and specular maps for all planets and moons.
- NASA's Visible Earth (https://visibleearth.nasa.gov/): Real satellite imagery of Earth. Great for custom Earth textures.
Challenge 1: Scale vs. Visibility Real solar system proportions are unusable in 3D visualization. If Earth is 1 unit in diameter, the Sun should be 109 units (you'd never see them in the same view), and Neptune's orbit should be 30,000 units away (takes forever to reach). Solution: Use logarithmic scaling or artistic liberty. Make orbital distances 1-2 orders of magnitude smaller than realistic. Sacrifice accuracy for usability—this is common in educational visualizations and games like Kerbal Space Program.
Challenge 2: Camera Control
With objects spread across vast distances, the default OrbitControls can be frustrating (zoom too fast, can't reach distant planets). Solution: (1) Implement custom camera controls with variable speed based on distance to target. (2) Add UI buttons to "jump" camera to each planet. (3) Use camera.lookAt(planet.position) and smoothly interpolate camera position with camera.position.lerp(targetPosition, 0.1) for cinematic transitions. Linear interpolation (lerp) creates smooth, exponential ease-out: newValue = currentValue + (target - currentValue) * alpha.
Challenge 3: Performance with High-Res Textures
4K planet textures (4096×2048 pixels) use 32MB of GPU memory each. Eight planets = 256MB, which can cause stuttering or crashes on mobile devices. Solution: (1) Use texture compression (Three.js supports GPU-compressed formats like KTX2). (2) Load lower-resolution textures on mobile (detect with navigator.userAgent or screen size). (3) Implement level-of-detail (LOD): load high-res textures only for close planets, low-res for distant ones. Three.js provides THREE.LOD helper for this.
Challenge 4: Realistic Lighting
A single point light at the Sun creates harsh shadows and doesn't match reality (where space has ambient starlight, and planets have atmospheric scattering). Solution: Add a very dim THREE.AmbientLight(0x222222, 0.1) for subtle fill light. For Earth's blue atmospheric scattering, you'll need custom shaders (Project 4 teaches this). For accurate planetary phases (crescents), ensure MeshStandardMaterial is used (it calculates light falloff correctly).
Kepler's Third Law relates orbital period to radius:
T² ∝ r³ (period squared proportional to radius cubed)
For animation, we need angular velocity (ω) in radians per frame:
// If we want Earth to orbit in 60 seconds at 60 FPS:
const earthPeriod = 60; // seconds
const framesPerOrbit = earthPeriod * 60; // frames
const earthAngularVelocity = (2 * Math.PI) / framesPerOrbit; // radians/frame
// For other planets, scale by Kepler's law:
const marsRadius = 1.524; // AU (Earth = 1.0)
const marsAngularVelocity = earthAngularVelocity * Math.pow(marsRadius, -1.5);The -1.5 exponent comes from T² ∝ r³, so T ∝ r^(3/2), and angular velocity ω = 2π/T ∝ r^(-3/2).
Parent-child transformation math:
// World position of a child object:
worldPosition = parentMatrix × childLocalPosition
// In Three.js this happens automatically:
pivot.rotation.y = angle; // Rotate parent
child.position.x = radius; // Position child locally
child.updateMatrixWorld(); // Compute world transform
const worldPos = child.getWorldPosition(new THREE.Vector3());
// worldPos now contains the orbiting position
// Create orbit system for a planet
function createPlanet(name, radius, orbitalRadius, orbitalSpeed, spinSpeed, textureUrl) {
// Pivot point for orbit (parent)
const orbitPivot = new THREE.Object3D();
orbitPivot.name = `${name}_orbit`;
scene.add(orbitPivot);
// Planet geometry and material
const geometry = new THREE.SphereGeometry(radius, 32, 32);
const material = new THREE.MeshStandardMaterial({
map: new THREE.TextureLoader().load(textureUrl),
roughness: 0.7,
metalness: 0.0
});
// Planet mesh (child)
const planetMesh = new THREE.Mesh(geometry, material);
planetMesh.position.x = orbitalRadius; // Distance from Sun
planetMesh.name = name;
orbitPivot.add(planetMesh); // Parent the planet to the pivot
// Return handles for animation
return { orbitPivot, planetMesh, orbitalSpeed, spinSpeed };
}
// Create Earth
const earth = createPlanet(
'Earth',
1.0, // radius (relative units)
10.0, // orbital radius
0.001, // orbital speed (radians/frame)
0.01, // spin speed
'textures/earth.jpg'
);
// In animation loop:
function animate() {
requestAnimationFrame(animate);
// Orbit (rotate pivot)
earth.orbitPivot.rotation.y += earth.orbitalSpeed;
// Spin (rotate planet mesh)
earth.planetMesh.rotation.y += earth.spinSpeed;
renderer.render(scene, camera);
}This project aims to create an interactive, scientifically-accurate model of our solar system using Three.js. The planets will orbit the Sun at correct relative speeds and distances, providing a visual representation of orbital mechanics and 3D graphics concepts.
The project is organized into several key files and directories:
- index.html: The main entry point for the web application, linking to the Three.js library and the main JavaScript file.
- script.js: Responsible for initializing the Three.js scene, camera, and renderer, as well as handling the animation loop.
- src/: Contains the main logic for the Three.js scene, including:
- main.js: Sets up the renderer, camera, and adds the scene to the HTML document.
- scene.js: Manages the Three.js scene, including lights and environment configuration.
- controls.js: Handles user interactions with camera controls.
- planets/: Contains files related to planet creation and data.
- createPlanet.js: Function to create individual planets with geometry, material, and texture.
- planetData.js: Stores data related to the planets, such as names, sizes, and textures.
- shaders/: Contains GLSL code for custom shaders, enhancing visual effects.
- assets/: Holds texture and model files for the planets and other objects.
- .gitignore: Specifies files and directories to be ignored by Git.
- package.json: Configuration file for npm, listing project dependencies and scripts.
- Clone the Repository: If applicable, clone the repository to your local machine.
- Install Dependencies: Run
npm installto install Three.js and any other required libraries. - Run the Project: Use a local server to serve the
index.htmlfile and view the project in your browser.
- Implement additional planets and moons.
- Add user interface elements for planet information.
- Enhance visual effects with custom shaders and atmospheric effects.
This project utilizes Three.js, a powerful library for 3D graphics in the browser. Special thanks to the creators of the textures and models used in this project.
by ASTRA MATRIX