33 KiB
Earth Orbit Simulator - Astronomical Accuracy Specification
Core Challenge
Create a scientifically accurate Three.js simulation of Earth's orbit around the Sun with precise astronomical parameters including obliquity, eccentricity, precession, rotation, and accurate solar illumination. The simulation must include time controls to visualize Earth's position and orientation at any point in time.
Project Overview
Build a single-file HTML application that demonstrates:
- Accurate Orbital Mechanics: Kepler's laws, elliptical orbit with correct eccentricity
- Earth's Rotation: Sidereal day (23h 56m 4.0916s) at accurate rate
- Axial Tilt (Obliquity): ~23.44° (precisely 23.4392811° for J2000.0 epoch)
- Orbital Eccentricity: ~0.0167 (Earth's orbit is slightly elliptical)
- Axial Precession: ~26,000 year cycle (precession of the equinoxes)
- Realistic Lighting: Sun as light source with accurate Earth day/night terminator
- Time Simulation: Ability to speed up/slow down/reverse time
- Interactive Controls: Slider to jump to any date/time
Output Requirements
File Naming: earth_orbit_simulator.html
Content Structure: Self-contained HTML file with astronomical simulation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Earth Orbit Simulator - Astronomical Accuracy</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Courier New', monospace;
background: #000000;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
#info-panel {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.85);
color: #00ff00;
padding: 20px;
border-radius: 8px;
font-size: 13px;
min-width: 300px;
border: 1px solid #00ff00;
font-family: 'Courier New', monospace;
}
#time-controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.85);
padding: 20px 30px;
border-radius: 8px;
border: 1px solid #00ff00;
min-width: 600px;
}
.control-group {
margin: 10px 0;
}
.control-group label {
color: #00ff00;
display: block;
margin-bottom: 5px;
font-size: 12px;
}
.control-group input[type="range"] {
width: 100%;
margin: 5px 0;
}
.button-group {
display: flex;
gap: 10px;
margin-top: 15px;
}
button {
background: #003300;
color: #00ff00;
border: 1px solid #00ff00;
padding: 8px 16px;
cursor: pointer;
border-radius: 4px;
font-family: 'Courier New', monospace;
}
button:hover {
background: #005500;
}
button.active {
background: #00ff00;
color: #000000;
}
.data-row {
display: flex;
justify-content: space-between;
margin: 5px 0;
padding: 5px 0;
border-bottom: 1px solid #003300;
}
.data-label {
color: #00aa00;
}
.data-value {
color: #00ff00;
font-weight: bold;
}
</style>
</head>
<body>
<div id="info-panel">
<h3 style="margin: 0 0 15px 0; color: #00ff00;">EARTH ORBITAL DATA</h3>
<div class="data-row">
<span class="data-label">Current Date/Time:</span>
<span class="data-value" id="current-time">-</span>
</div>
<div class="data-row">
<span class="data-label">Julian Date:</span>
<span class="data-value" id="julian-date">-</span>
</div>
<div class="data-row">
<span class="data-label">Days since J2000:</span>
<span class="data-value" id="days-j2000">-</span>
</div>
<div class="data-row">
<span class="data-label">Rotation Angle:</span>
<span class="data-value" id="rotation-angle">-</span>
</div>
<div class="data-row">
<span class="data-label">Axial Tilt:</span>
<span class="data-value" id="axial-tilt">23.4393°</span>
</div>
<div class="data-row">
<span class="data-label">Orbital Position:</span>
<span class="data-value" id="orbital-position">-</span>
</div>
<div class="data-row">
<span class="data-label">Distance from Sun:</span>
<span class="data-value" id="sun-distance">-</span>
</div>
<div class="data-row">
<span class="data-label">Orbital Velocity:</span>
<span class="data-value" id="orbital-velocity">-</span>
</div>
<div class="data-row">
<span class="data-label">Precession Angle:</span>
<span class="data-value" id="precession-angle">-</span>
</div>
<div class="data-row">
<span class="data-label">Season:</span>
<span class="data-value" id="season">-</span>
</div>
</div>
<div id="time-controls">
<div class="control-group">
<label>Time Travel (Date/Time)</label>
<input type="datetime-local" id="date-picker" />
</div>
<div class="control-group">
<label>Time Speed: <span id="speed-value">1x Real-time</span></label>
<input type="range" id="time-speed" min="-1000000" max="1000000" value="0" step="100" />
<div style="display: flex; justify-content: space-between; font-size: 10px; color: #00aa00; margin-top: 5px;">
<span>← 1M days/sec</span>
<span>Paused</span>
<span>1M days/sec →</span>
</div>
</div>
<div class="button-group">
<button id="btn-reverse">◄◄ Reverse</button>
<button id="btn-slower">◄ Slower</button>
<button id="btn-pause" class="active">⏸ Pause</button>
<button id="btn-faster">Faster ►</button>
<button id="btn-forward">Forward ►►</button>
<button id="btn-reset">↺ Reset to Now</button>
</div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Scene, camera, renderer setup
let camera, scene, renderer, controls;
let sun, earth, earthOrbitLine;
let earthRotationGroup, earthTiltGroup;
// Astronomical constants (J2000.0 epoch)
const ASTRONOMICAL_CONSTANTS = {
// Earth orbital parameters
SEMI_MAJOR_AXIS: 149.598e6, // km (1 AU)
ECCENTRICITY: 0.0167086, // Orbital eccentricity
OBLIQUITY: 23.4392811, // Axial tilt in degrees (J2000)
SIDEREAL_YEAR: 365.256363004, // days
SIDEREAL_DAY: 0.99726968, // days (23h 56m 4.0916s)
PRECESSION_PERIOD: 25772, // years (axial precession)
// Orbital elements (J2000.0)
PERIHELION: 102.94719, // Longitude of perihelion (degrees)
MEAN_LONGITUDE: 100.46435, // Mean longitude at epoch (degrees)
// Scale for visualization (not to real scale)
SCALE_DISTANCE: 100, // Scale factor for distances
SCALE_SIZE: 1, // Scale factor for body sizes
// J2000.0 epoch
J2000: 2451545.0, // Julian date of J2000.0 epoch (Jan 1, 2000, 12:00 TT)
};
// Simulation state
let simulationTime = new Date(); // Current simulation time
let timeSpeed = 0; // Time multiplier (0 = paused)
let lastFrameTime = performance.now();
init();
animate();
function init() {
// Camera setup
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
10000
);
camera.position.set(0, 150, 250);
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// OrbitControls
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 10;
controls.maxDistance = 1000;
// Create solar system
createSolarSystem();
// Setup UI controls
setupControls();
// Add starfield background
createStarfield();
// Handle resize
window.addEventListener('resize', onWindowResize);
// Initialize to current date/time
resetToNow();
}
function createSolarSystem() {
// Sun (light source)
const sunGeometry = new THREE.SphereGeometry(10, 64, 64);
const sunMaterial = new THREE.MeshBasicMaterial({
color: 0xffff00,
emissive: 0xffff00,
emissiveIntensity: 1
});
sun = new THREE.Mesh(sunGeometry, sunMaterial);
scene.add(sun);
// Sun point light (primary light source)
const sunLight = new THREE.PointLight(0xffffff, 2, 0);
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 2048;
sunLight.shadow.mapSize.height = 2048;
sun.add(sunLight);
// Earth orbital path (ellipse)
createEarthOrbit();
// Earth group hierarchy for proper rotation and tilt
// Structure: earthTiltGroup -> earthRotationGroup -> earth
earthTiltGroup = new THREE.Group();
scene.add(earthTiltGroup);
earthRotationGroup = new THREE.Group();
earthTiltGroup.add(earthRotationGroup);
// Earth sphere with texture
const earthGeometry = new THREE.SphereGeometry(5, 64, 64);
// Load Earth texture
const textureLoader = new THREE.TextureLoader();
const earthTexture = textureLoader.load(
'https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/planets/earth_atmos_2048.jpg'
);
const earthBumpMap = textureLoader.load(
'https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/planets/earth_normal_2048.jpg'
);
const earthMaterial = new THREE.MeshPhongMaterial({
map: earthTexture,
bumpMap: earthBumpMap,
bumpScale: 0.05,
specular: 0x333333,
shininess: 5
});
earth = new THREE.Mesh(earthGeometry, earthMaterial);
earth.receiveShadow = true;
earth.castShadow = true;
earthRotationGroup.add(earth);
// Set axial tilt
earthTiltGroup.rotation.z = THREE.MathUtils.degToRad(ASTRONOMICAL_CONSTANTS.OBLIQUITY);
// Add atmosphere glow
const atmosphereGeometry = new THREE.SphereGeometry(5.2, 64, 64);
const atmosphereMaterial = new THREE.MeshBasicMaterial({
color: 0x6699ff,
transparent: true,
opacity: 0.15,
side: THREE.BackSide
});
const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
earth.add(atmosphere);
}
function createEarthOrbit() {
// Create elliptical orbit path
const orbitPoints = [];
const segments = 360;
const a = ASTRONOMICAL_CONSTANTS.SEMI_MAJOR_AXIS / ASTRONOMICAL_CONSTANTS.SCALE_DISTANCE;
const e = ASTRONOMICAL_CONSTANTS.ECCENTRICITY;
const b = a * Math.sqrt(1 - e * e); // Semi-minor axis
for (let i = 0; i <= segments; i++) {
const angle = (i / segments) * Math.PI * 2;
const r = (a * (1 - e * e)) / (1 + e * Math.cos(angle));
const x = r * Math.cos(angle);
const z = r * Math.sin(angle);
orbitPoints.push(new THREE.Vector3(x, 0, z));
}
const orbitGeometry = new THREE.BufferGeometry().setFromPoints(orbitPoints);
const orbitMaterial = new THREE.LineBasicMaterial({
color: 0x00ff00,
opacity: 0.3,
transparent: true
});
earthOrbitLine = new THREE.Line(orbitGeometry, orbitMaterial);
scene.add(earthOrbitLine);
}
function createStarfield() {
const starsGeometry = new THREE.BufferGeometry();
const starCount = 5000;
const positions = new Float32Array(starCount * 3);
for (let i = 0; i < starCount * 3; i += 3) {
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(Math.random() * 2 - 1);
const r = 500 + Math.random() * 500;
positions[i] = r * Math.sin(phi) * Math.cos(theta);
positions[i + 1] = r * Math.sin(phi) * Math.sin(theta);
positions[i + 2] = r * Math.cos(phi);
}
starsGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const starsMaterial = new THREE.PointsMaterial({
color: 0xffffff,
size: 0.7,
transparent: true,
opacity: 0.8
});
const stars = new THREE.Points(starsGeometry, starsMaterial);
scene.add(stars);
}
function setupControls() {
const datePicker = document.getElementById('date-picker');
const timeSpeedSlider = document.getElementById('time-speed');
const speedValue = document.getElementById('speed-value');
// Date picker
datePicker.addEventListener('change', (e) => {
simulationTime = new Date(e.target.value);
updateSimulation();
});
// Time speed slider
timeSpeedSlider.addEventListener('input', (e) => {
timeSpeed = parseFloat(e.target.value);
updateSpeedDisplay();
});
// Buttons
document.getElementById('btn-reverse').addEventListener('click', () => {
timeSpeed = -86400; // -1 day per second
updateSpeedDisplay();
});
document.getElementById('btn-slower').addEventListener('click', () => {
timeSpeed = Math.max(timeSpeed / 2, -1000000);
timeSpeedSlider.value = timeSpeed;
updateSpeedDisplay();
});
document.getElementById('btn-pause').addEventListener('click', () => {
timeSpeed = 0;
timeSpeedSlider.value = 0;
updateSpeedDisplay();
});
document.getElementById('btn-faster').addEventListener('click', () => {
timeSpeed = Math.min(timeSpeed === 0 ? 1 : timeSpeed * 2, 1000000);
timeSpeedSlider.value = timeSpeed;
updateSpeedDisplay();
});
document.getElementById('btn-forward').addEventListener('click', () => {
timeSpeed = 86400; // +1 day per second
updateSpeedDisplay();
});
document.getElementById('btn-reset').addEventListener('click', resetToNow);
}
function resetToNow() {
simulationTime = new Date();
timeSpeed = 0;
document.getElementById('time-speed').value = 0;
updateSpeedDisplay();
updateSimulation();
}
function updateSpeedDisplay() {
const speedValue = document.getElementById('speed-value');
if (timeSpeed === 0) {
speedValue.textContent = 'Paused';
} else if (Math.abs(timeSpeed) < 1) {
speedValue.textContent = `${timeSpeed.toFixed(3)}x Real-time`;
} else if (Math.abs(timeSpeed) < 86400) {
speedValue.textContent = `${(timeSpeed / 3600).toFixed(1)} hours/sec`;
} else {
speedValue.textContent = `${(timeSpeed / 86400).toFixed(1)} days/sec`;
}
}
function calculateOrbitalPosition(julianDate) {
// Calculate days since J2000.0 epoch
const d = julianDate - ASTRONOMICAL_CONSTANTS.J2000;
// Mean anomaly (degrees)
const M = ASTRONOMICAL_CONSTANTS.MEAN_LONGITUDE +
(360.0 / ASTRONOMICAL_CONSTANTS.SIDEREAL_YEAR) * d -
ASTRONOMICAL_CONSTANTS.PERIHELION;
// Solve Kepler's equation for eccentric anomaly (iterative)
let E = THREE.MathUtils.degToRad(M);
const e = ASTRONOMICAL_CONSTANTS.ECCENTRICITY;
for (let i = 0; i < 10; i++) {
E = E - (E - e * Math.sin(E) - THREE.MathUtils.degToRad(M)) / (1 - e * Math.cos(E));
}
// True anomaly
const v = 2 * Math.atan2(
Math.sqrt(1 + e) * Math.sin(E / 2),
Math.sqrt(1 - e) * Math.cos(E / 2)
);
// Distance from sun
const r = ASTRONOMICAL_CONSTANTS.SEMI_MAJOR_AXIS * (1 - e * Math.cos(E));
// Position in orbital plane
const x = (r / ASTRONOMICAL_CONSTANTS.SCALE_DISTANCE) * Math.cos(v);
const z = (r / ASTRONOMICAL_CONSTANTS.SCALE_DISTANCE) * Math.sin(v);
return { x, z, r, v: THREE.MathUtils.radToDeg(v), d };
}
function updateSimulation() {
// Convert to Julian Date
const jd = dateToJulianDate(simulationTime);
// Calculate orbital position
const orbital = calculateOrbitalPosition(jd);
// Update Earth position
earthTiltGroup.position.set(orbital.x, 0, orbital.z);
// Calculate Earth rotation (sidereal day)
const daysSinceJ2000 = jd - ASTRONOMICAL_CONSTANTS.J2000;
const rotations = daysSinceJ2000 / ASTRONOMICAL_CONSTANTS.SIDEREAL_DAY;
earthRotationGroup.rotation.y = (rotations % 1) * Math.PI * 2;
// Calculate precession (very slow, ~26,000 year cycle)
const precessionAngle = (daysSinceJ2000 / (ASTRONOMICAL_CONSTANTS.PRECESSION_PERIOD * 365.25)) * 360;
// Update UI
updateUI(jd, orbital, daysSinceJ2000, rotations, precessionAngle);
// Update date picker
const dateString = simulationTime.toISOString().slice(0, 16);
document.getElementById('date-picker').value = dateString;
}
function updateUI(jd, orbital, daysSinceJ2000, rotations, precessionAngle) {
document.getElementById('current-time').textContent =
simulationTime.toUTCString();
document.getElementById('julian-date').textContent =
jd.toFixed(2);
document.getElementById('days-j2000').textContent =
daysSinceJ2000.toFixed(2);
document.getElementById('rotation-angle').textContent =
((rotations % 1) * 360).toFixed(2) + '°';
document.getElementById('orbital-position').textContent =
orbital.v.toFixed(2) + '°';
document.getElementById('sun-distance').textContent =
(orbital.r / 1e6).toFixed(3) + ' million km';
document.getElementById('precession-angle').textContent =
(precessionAngle % 360).toFixed(2) + '°';
// Calculate orbital velocity (simplified)
const velocity = Math.sqrt(
1.327e20 * (2 / orbital.r - 1 / ASTRONOMICAL_CONSTANTS.SEMI_MAJOR_AXIS)
) / 1000;
document.getElementById('orbital-velocity').textContent =
velocity.toFixed(2) + ' km/s';
// Determine season (Northern Hemisphere)
const season = getSeason(orbital.v);
document.getElementById('season').textContent = season;
}
function getSeason(orbitalAngle) {
// Approximate seasons based on orbital position
// 0° = Perihelion (early January)
const adjusted = (orbitalAngle + 12) % 360; // Adjust for season alignment
if (adjusted < 90) return 'Winter (N) / Summer (S)';
if (adjusted < 180) return 'Spring (N) / Autumn (S)';
if (adjusted < 270) return 'Summer (N) / Winter (S)';
return 'Autumn (N) / Spring (S)';
}
function dateToJulianDate(date) {
return (date.getTime() / 86400000) + 2440587.5;
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
const currentTime = performance.now();
const deltaTime = (currentTime - lastFrameTime) / 1000; // seconds
lastFrameTime = currentTime;
// Update simulation time based on speed
if (timeSpeed !== 0) {
simulationTime = new Date(simulationTime.getTime() + (timeSpeed * deltaTime * 1000));
updateSimulation();
}
controls.update();
renderer.render(scene, camera);
}
</script>
</body>
</html>
Astronomical Accuracy Requirements
1. Orbital Mechanics (Kepler's Laws)
Elliptical Orbit:
- Semi-major axis: 149.598 million km (1 AU)
- Eccentricity: 0.0167086
- Perihelion: ~147.1 million km (early January)
- Aphelion: ~152.1 million km (early July)
Orbital Period:
- Sidereal year: 365.256363004 days
- Tropical year: 365.24219 days
Mathematical Implementation:
- Use Kepler's equation to solve for position:
M = E - e·sin(E) - Calculate true anomaly from eccentric anomaly
- Convert to Cartesian coordinates in orbital plane
2. Earth's Rotation
Rotational Period:
- Sidereal day: 23h 56m 4.0916s (0.99726968 days)
- NOT 24 hours (that's solar day)
Implementation:
- Rotation angle = (days since epoch / sidereal day) × 360°
- Rotate Earth mesh around Y-axis
3. Axial Tilt (Obliquity)
Current Value:
- 23.4392811° (J2000.0 epoch reference)
- Changes slowly over time (~23.1° to 24.5° over 41,000 years)
Implementation:
- Apply tilt to Earth's rotation group
- Tilt is relative to orbital plane normal
- Keep tilt direction fixed in space (relative to stars)
4. Axial Precession
Precession Period:
- ~25,772 years (precession of the equinoxes)
- Earth's axis traces a cone in space
Implementation:
- Very slow wobble of tilt direction
- Barely noticeable in simulation unless sped up significantly
- Affects which star is the "North Star" over millennia
5. Realistic Lighting
Sun as Point Light:
- Position at origin (0, 0, 0)
- Intensity sufficient to light Earth realistically
- Enable shadows for accurate terminator
Day/Night Terminator:
- Should be perpendicular to Sun-Earth line
- Proper shadow mapping on Earth surface
- Atmospheric scattering effect (optional enhancement)
UI/UX Requirements
Information Panel (Top Right)
Display real-time astronomical data:
- Current Date/Time: UTC format
- Julian Date: Astronomical time standard
- Days since J2000: Days since January 1, 2000, 12:00 TT
- Rotation Angle: Current rotation in degrees
- Axial Tilt: 23.4393° (display constant)
- Orbital Position: Angle from perihelion in degrees
- Distance from Sun: Current distance in million km
- Orbital Velocity: Current orbital speed in km/s
- Precession Angle: Current precession phase
- Season: Northern/Southern hemisphere season
Time Controls (Bottom)
Date/Time Picker:
- Jump to any specific date/time
- Use HTML5
<input type="datetime-local">
Time Speed Slider:
- Range: -1,000,000 to +1,000,000 (days per second)
- Center (0): Paused
- Negative: Reverse time
- Positive: Forward time
- Display current speed multiplier
Quick Control Buttons:
- Reverse: Go backwards at 1 day/second
- Slower: Halve current speed
- Pause: Stop time (speed = 0)
- Faster: Double current speed
- Forward: Go forward at 1 day/second
- Reset: Return to current real-world time
Technical Implementation
Recommended Libraries/APIs
Option 1: astronomy-engine (NPM package)
// Highly accurate astronomical calculations
// https://github.com/cosinekitty/astronomy
import * as Astronomy from 'https://cdn.jsdelivr.net/npm/astronomy-engine@2.1.19/+esm'
// Get Earth's position at specific time
const time = new Astronomy.AstroTime(new Date());
const earthPos = Astronomy.HelioVector(Astronomy.Body.Earth, time);
Pros:
- Extremely accurate (JPL ephemeris quality)
- Easy to use
- Handles all orbital mechanics automatically
Option 2: Manual Calculation (Recommended for learning)
// Implement Kepler's laws manually
// Uses mean orbital elements
function calculateOrbitalPosition(date) {
// 1. Calculate Julian Date
// 2. Days since J2000 epoch
// 3. Mean anomaly = mean longitude - longitude of perihelion
// 4. Solve Kepler's equation iteratively for eccentric anomaly
// 5. Calculate true anomaly
// 6. Convert to Cartesian coordinates
// 7. Apply orbital plane inclination (0° for Earth)
}
Pros:
- Complete control
- Educational value
- No external dependencies beyond Three.js
Option 3: NASA JPL Horizons API
// Query NASA's HORIZONS system for precise ephemeris
// https://ssd.jpl.nasa.gov/api/horizons.api
fetch('https://ssd.jpl.nasa.gov/api/horizons.api?...')
Pros:
- Most accurate possible
- Official NASA data
Cons:
- Requires API calls (network dependency)
- Rate limited
- More complex setup
Coordinate Systems
Three.js Scene:
- Sun at origin (0, 0, 0)
- Orbital plane in XZ plane (Y = 0)
- +X axis: Vernal equinox direction
- +Y axis: North ecliptic pole
- +Z axis: 90° from vernal equinox
Earth Orientation:
- Use nested groups: Sun → Orbit → Tilt → Rotation → Earth
- Orbit group: position along orbital path
- Tilt group: apply 23.44° tilt around Z-axis
- Rotation group: rotate around Y-axis (Earth's axis)
Texture Resources
Earth Textures:
- Day texture:
earth_atmos_2048.jpgorearth_daymap_4096.jpg - Night texture (optional):
earth_nightmap_2048.jpg - Normal/bump map:
earth_normal_2048.jpg - Specular map (optional):
earth_specular_2048.jpg
Sources:
- Three.js examples:
https://github.com/mrdoob/three.js/tree/dev/examples/textures/planets - NASA Visible Earth:
https://visibleearth.nasa.gov/ - Solar System Scope:
https://www.solarsystemscope.com/textures/
Quality Standards
Astronomical Accuracy
Required Precision:
- Orbital position: ±0.1° accuracy
- Rotation angle: ±1° accuracy
- Axial tilt: ±0.01° accuracy
- Distance from Sun: ±0.1 million km
Validation Methods:
- Compare with JPL Horizons data for specific dates
- Verify solstice/equinox dates align with real dates
- Check perihelion/aphelion dates (Jan 3 / July 4)
- Validate orbital velocity at known points
Performance
Target:
- 60fps smooth animation
- Responsive time controls (no lag)
- Smooth camera controls
- Fast date jumping (no recalculation delay)
Visual Quality
Required:
- Realistic Earth texture with good resolution
- Accurate day/night terminator
- Smooth lighting falloff
- No visual artifacts or popping
- Clean, readable UI
Optional Enhancements:
- Atmospheric glow around Earth
- Clouds layer (animated)
- City lights on night side
- Lens flare from Sun
- Orbital trail visualization
Advanced Features (Optional)
1. Multi-Body System
- Add Moon with accurate orbit
- Add other planets
- Show relative positions
2. Enhanced Accuracy
- Nutation (nodding motion)
- Lunar perturbations
- Gravitational effects of other planets
- General relativity corrections
3. Educational Features
- Highlight equinoxes and solstices
- Show tropics and polar circles
- Indicate subsolar point
- Display constellation background
- Show celestial equator
4. Data Visualization
- Plot orbital velocity over time
- Graph distance from Sun
- Show axial tilt variation over millennia
- Display Earth-Sun-Moon geometry
Testing & Validation
Test Cases
Known Astronomical Events:
-
March Equinox 2024: March 20, 03:06 UTC
- Earth at ~90° from perihelion
- Day/night equal length
-
June Solstice 2024: June 20, 20:51 UTC
- Earth at ~180° from perihelion (aphelion nearby)
- Maximum northern tilt toward Sun
-
September Equinox 2024: September 22, 12:44 UTC
- Earth at ~270° from perihelion
- Day/night equal length
-
December Solstice 2024: December 21, 09:21 UTC
- Earth near perihelion
- Maximum southern tilt toward Sun
Validation Procedure:
- Set simulation to test date
- Verify orbital position matches expected angle
- Check tilt direction relative to Sun
- Validate distance from Sun
- Confirm season displayed correctly
Comparison Sources
Official Ephemeris:
- NASA JPL Horizons:
https://ssd.jpl.nasa.gov/horizons/ - US Naval Observatory:
https://aa.usno.navy.mil/data/ - IAU SOFA:
http://www.iausofa.org/
Success Criteria
A successful Earth orbit simulator must:
- ✅ Accurate Orbital Motion: Earth follows elliptical path with correct eccentricity
- ✅ Correct Rotation: Sidereal day (23h 56m 4s), not solar day
- ✅ Precise Tilt: 23.44° axial tilt maintained relative to stars
- ✅ Realistic Lighting: Day/night terminator perpendicular to Sun direction
- ✅ Time Control: Smooth forward/reverse/pause/jump controls
- ✅ Live Data Display: All orbital parameters updated in real-time
- ✅ Validation: Matches known astronomical events (solstices, equinoxes)
- ✅ Performance: 60fps with smooth animations
- ✅ Self-Contained: Single HTML file, works offline after initial load
- ✅ Educational Value: Clearly demonstrates astronomical concepts
Reference Implementation Notes
Kepler's Equation Solver
function solveKeplerEquation(M, e, iterations = 10) {
// M = mean anomaly (radians)
// e = eccentricity
// Solve: M = E - e·sin(E) for E (eccentric anomaly)
let E = M; // Initial guess
for (let i = 0; i < iterations; i++) {
E = E - (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
}
return E;
}
function eccentricToTrueAnomaly(E, e) {
// Convert eccentric anomaly to true anomaly
return 2 * Math.atan2(
Math.sqrt(1 + e) * Math.sin(E / 2),
Math.sqrt(1 - e) * Math.cos(E / 2)
);
}
Julian Date Conversion
function dateToJulianDate(date) {
// Convert JavaScript Date to Julian Date
return (date.getTime() / 86400000) + 2440587.5;
}
function julianDateToDate(jd) {
// Convert Julian Date to JavaScript Date
return new Date((jd - 2440587.5) * 86400000);
}
Documentation Requirements
The simulation should include:
- Comments explaining astronomical concepts
- References to equations used
- Links to validation sources
- Instructions for use in info panel
Future Enhancements
Potential additions for advanced versions:
- Apsidal precession (orbital ellipse rotation)
- Milankovitch cycles visualization
- Historical Earth positions (dinosaur era, ice ages)
- Comparison with other planets
- Accurate Moon system
- Eclipse prediction
- Satellite tracking
Generate an astronomically accurate, interactive Earth orbit simulator that demonstrates the beauty of celestial mechanics and serves as both a scientific tool and educational resource.