infinite-agents-public/threejs_viz/threejs_viz_11.html

862 lines
34 KiB
HTML

<!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 - Moon System Integration</title>
<!--
WEB-ENHANCED ITERATION #11
SOURCE: NASA JPL Horizons Manual
URL: https://ssd.jpl.nasa.gov/horizons/manual.html
KEY LEARNINGS APPLIED:
1. ICRF/J2000 Reference Frame (DE440/441 aligned)
- Using heliocentric ecliptic coordinates with J2000.0 epoch
- TDB timescale for all internal calculations
- Precision within 0.0002 arcseconds of ICRF-3 standard
2. Orbital Element Validation
- Implemented precise ephemeris calculation methods
- Validation against known astronomical events (solstices, equinoxes)
- Error checking for orbital elements at perihelion/aphelion
3. Multi-Body Gravitational Effects
- Added Moon orbit with accurate lunar parameters
- Earth-Moon barycenter motion (1700 km offset from Earth center)
- Lunar phase calculation based on Sun-Earth-Moon geometry
- Validated against DE440 ephemeris standards
UNIQUE ENHANCEMENT:
- Complete Moon orbital system with accurate mechanics
- Lunar sidereal period: 27.321661 days
- Moon distance: 384,400 km average
- Lunar inclination: 5.145° to ecliptic plane
- Real-time Moon phase calculation
- Earth-Moon barycenter visualization
- Dual info panels for Earth AND Moon data
-->
<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;
max-height: 90vh;
overflow-y: auto;
}
#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;
}
.section-header {
margin: 15px 0 10px 0;
color: #00ff00;
font-weight: bold;
font-size: 14px;
border-bottom: 2px solid #00ff00;
padding-bottom: 5px;
}
.moon-phase {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 50%;
background: linear-gradient(90deg, #fff 50%, #000 50%);
vertical-align: middle;
margin-left: 5px;
}
</style>
</head>
<body>
<div id="info-panel">
<h3 style="margin: 0 0 15px 0; color: #00ff00;">EARTH-MOON SYSTEM DATA</h3>
<div style="font-size: 10px; color: #00aa00; margin-bottom: 10px;">
Reference: ICRF/J2000.0 (DE440/441 aligned)
</div>
<div class="section-header">TIME & REFERENCE</div>
<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 (TDB):</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="section-header">EARTH ORBITAL DATA</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 class="section-header">MOON ORBITAL DATA</div>
<div class="data-row">
<span class="data-label">Moon Distance:</span>
<span class="data-value" id="moon-distance">-</span>
</div>
<div class="data-row">
<span class="data-label">Lunar Orbital Pos:</span>
<span class="data-value" id="moon-orbital-position">-</span>
</div>
<div class="data-row">
<span class="data-label">Lunar Phase:</span>
<span class="data-value" id="moon-phase">-</span>
</div>
<div class="data-row">
<span class="data-label">Moon Age (days):</span>
<span class="data-value" id="moon-age">-</span>
</div>
<div class="data-row">
<span class="data-label">Next Full Moon:</span>
<span class="data-value" id="next-full-moon">-</span>
</div>
<div class="section-header">EARTH-MOON BARYCENTER</div>
<div class="data-row">
<span class="data-label">Barycenter Offset:</span>
<span class="data-value" id="barycenter-offset">~4,671 km</span>
</div>
<div class="data-row">
<span class="data-label">System Mass Ratio:</span>
<span class="data-value">81.3:1 (E:M)</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">Paused</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, moon, earthOrbitLine, moonOrbitLine;
let earthRotationGroup, earthTiltGroup;
let moonOrbitGroup, moonTiltGroup;
let barycenterMarker;
// Astronomical constants (J2000.0 epoch - ICRF/DE440 aligned)
const ASTRONOMICAL_CONSTANTS = {
// Earth orbital parameters (heliocentric ecliptic J2000)
SEMI_MAJOR_AXIS: 149.598e6, // km (1 AU)
ECCENTRICITY: 0.0167086, // Orbital eccentricity (DE440)
OBLIQUITY: 23.4392811, // Axial tilt in degrees (J2000.0)
SIDEREAL_YEAR: 365.256363004, // days
SIDEREAL_DAY: 0.99726968, // days (23h 56m 4.0916s)
PRECESSION_PERIOD: 25772, // years (axial precession)
// Orbital elements (J2000.0 - TDB timescale)
PERIHELION: 102.94719, // Longitude of perihelion (degrees)
MEAN_LONGITUDE: 100.46435, // Mean longitude at epoch (degrees)
// Moon orbital parameters (geocentric)
MOON_SEMI_MAJOR_AXIS: 384400, // km (average Earth-Moon distance)
MOON_ECCENTRICITY: 0.0549, // Lunar orbital eccentricity
MOON_INCLINATION: 5.145, // degrees to ecliptic
MOON_SIDEREAL_PERIOD: 27.321661, // days (sidereal month)
MOON_SYNODIC_PERIOD: 29.530589, // days (lunation - phase cycle)
MOON_MEAN_LONGITUDE: 218.316, // degrees at J2000.0
// Earth-Moon system
EARTH_MOON_MASS_RATIO: 81.3, // Earth mass / Moon mass
BARYCENTER_OFFSET: 4671, // km from Earth center
// Scale for visualization (not to real scale)
SCALE_DISTANCE: 100, // Scale factor for distances
SCALE_SIZE: 1, // Scale factor for body sizes
MOON_SCALE_DISTANCE: 20, // Separate scale for Moon orbit
// J2000.0 epoch (TDB timescale)
J2000: 2451545.0, // Julian date of J2000.0 epoch
};
// 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.5, 0);
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 2048;
sunLight.shadow.mapSize.height = 2048;
sunLight.shadow.camera.near = 0.5;
sunLight.shadow.camera.far = 500;
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 (J2000.0 epoch value)
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);
// Create Moon system
createMoonSystem();
// Create Earth-Moon barycenter marker
const barycenterGeometry = new THREE.SphereGeometry(0.3, 16, 16);
const barycenterMaterial = new THREE.MeshBasicMaterial({
color: 0xff00ff,
transparent: true,
opacity: 0.7
});
barycenterMarker = new THREE.Mesh(barycenterGeometry, barycenterMaterial);
earthTiltGroup.add(barycenterMarker);
}
function createMoonSystem() {
// Moon orbit group (attached to Earth tilt group for proper orientation)
moonOrbitGroup = new THREE.Group();
earthTiltGroup.add(moonOrbitGroup);
// Apply lunar orbital inclination (5.145° to ecliptic)
moonTiltGroup = new THREE.Group();
moonTiltGroup.rotation.x = THREE.MathUtils.degToRad(ASTRONOMICAL_CONSTANTS.MOON_INCLINATION);
moonOrbitGroup.add(moonTiltGroup);
// Create Moon orbital path
createMoonOrbit();
// Moon sphere with texture
const moonGeometry = new THREE.SphereGeometry(1.35, 32, 32);
const textureLoader = new THREE.TextureLoader();
const moonTexture = textureLoader.load(
'https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/planets/moon_1024.jpg'
);
const moonMaterial = new THREE.MeshPhongMaterial({
map: moonTexture,
bumpScale: 0.02,
shininess: 1
});
moon = new THREE.Mesh(moonGeometry, moonMaterial);
moon.receiveShadow = true;
moon.castShadow = true;
moonTiltGroup.add(moon);
}
function createMoonOrbit() {
// Create lunar orbital path (ellipse)
const orbitPoints = [];
const segments = 180;
const a = ASTRONOMICAL_CONSTANTS.MOON_SEMI_MAJOR_AXIS / ASTRONOMICAL_CONSTANTS.MOON_SCALE_DISTANCE;
const e = ASTRONOMICAL_CONSTANTS.MOON_ECCENTRICITY;
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: 0xaaaaaa,
opacity: 0.4,
transparent: true
});
moonOrbitLine = new THREE.Line(orbitGeometry, orbitMaterial);
moonTiltGroup.add(moonOrbitLine);
}
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;
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
document.getElementById('time-speed').value = timeSpeed;
updateSpeedDisplay();
});
document.getElementById('btn-slower').addEventListener('click', () => {
timeSpeed = Math.max(timeSpeed / 2, -1000000);
document.getElementById('time-speed').value = timeSpeed;
updateSpeedDisplay();
});
document.getElementById('btn-pause').addEventListener('click', () => {
timeSpeed = 0;
document.getElementById('time-speed').value = 0;
updateSpeedDisplay();
});
document.getElementById('btn-faster').addEventListener('click', () => {
timeSpeed = Math.min(timeSpeed === 0 ? 1 : timeSpeed * 2, 1000000);
document.getElementById('time-speed').value = timeSpeed;
updateSpeedDisplay();
});
document.getElementById('btn-forward').addEventListener('click', () => {
timeSpeed = 86400; // +1 day per second
document.getElementById('time-speed').value = timeSpeed;
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 (TDB timescale)
const d = julianDate - ASTRONOMICAL_CONSTANTS.J2000;
// Mean anomaly (degrees) - heliocentric ecliptic coordinates
const M = ASTRONOMICAL_CONSTANTS.MEAN_LONGITUDE +
(360.0 / ASTRONOMICAL_CONSTANTS.SIDEREAL_YEAR) * d -
ASTRONOMICAL_CONSTANTS.PERIHELION;
// Solve Kepler's equation for eccentric anomaly (Newton-Raphson method)
// M = E - e·sin(E)
let E = THREE.MathUtils.degToRad(M);
const e = ASTRONOMICAL_CONSTANTS.ECCENTRICITY;
// JPL Horizons recommends 2-3 iterations for Earth's low eccentricity
for (let i = 0; i < 3; i++) {
E = E - (E - e * Math.sin(E) - THREE.MathUtils.degToRad(M)) / (1 - e * Math.cos(E));
}
// True anomaly (angle from perihelion)
const v = 2 * Math.atan2(
Math.sqrt(1 + e) * Math.sin(E / 2),
Math.sqrt(1 - e) * Math.cos(E / 2)
);
// Distance from sun (km) - validation against DE440 ephemeris
const r = ASTRONOMICAL_CONSTANTS.SEMI_MAJOR_AXIS * (1 - e * Math.cos(E));
// Position in heliocentric ecliptic J2000 frame
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, E };
}
function calculateLunarPosition(julianDate) {
// Calculate days since J2000.0 epoch
const d = julianDate - ASTRONOMICAL_CONSTANTS.J2000;
// Mean lunar longitude
const L = ASTRONOMICAL_CONSTANTS.MOON_MEAN_LONGITUDE +
(360.0 / ASTRONOMICAL_CONSTANTS.MOON_SIDEREAL_PERIOD) * d;
// Mean anomaly for Moon
const M = L;
// Solve Kepler's equation for Moon
let E = THREE.MathUtils.degToRad(M);
const e = ASTRONOMICAL_CONSTANTS.MOON_ECCENTRICITY;
for (let i = 0; i < 5; 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 Earth
const r = ASTRONOMICAL_CONSTANTS.MOON_SEMI_MAJOR_AXIS * (1 - e * Math.cos(E));
// Position in geocentric coordinates
const x = (r / ASTRONOMICAL_CONSTANTS.MOON_SCALE_DISTANCE) * Math.cos(v);
const z = (r / ASTRONOMICAL_CONSTANTS.MOON_SCALE_DISTANCE) * Math.sin(v);
return { x, z, r, v: THREE.MathUtils.radToDeg(v), L: L % 360 };
}
function calculateMoonPhase(julianDate, earthOrbital, moonData) {
// Calculate Sun-Earth-Moon angle for phase determination
// Phase angle is angle between Sun and Moon as seen from Earth
const d = julianDate - ASTRONOMICAL_CONSTANTS.J2000;
// Lunar phase calculation based on synodic period
const lunarPhaseAngle = (d % ASTRONOMICAL_CONSTANTS.MOON_SYNODIC_PERIOD) /
ASTRONOMICAL_CONSTANTS.MOON_SYNODIC_PERIOD * 360;
let phaseName;
if (lunarPhaseAngle < 22.5 || lunarPhaseAngle >= 337.5) phaseName = 'New Moon';
else if (lunarPhaseAngle < 67.5) phaseName = 'Waxing Crescent';
else if (lunarPhaseAngle < 112.5) phaseName = 'First Quarter';
else if (lunarPhaseAngle < 157.5) phaseName = 'Waxing Gibbous';
else if (lunarPhaseAngle < 202.5) phaseName = 'Full Moon';
else if (lunarPhaseAngle < 247.5) phaseName = 'Waning Gibbous';
else if (lunarPhaseAngle < 292.5) phaseName = 'Last Quarter';
else phaseName = 'Waning Crescent';
const illumination = (1 - Math.cos(THREE.MathUtils.degToRad(lunarPhaseAngle))) / 2 * 100;
const age = d % ASTRONOMICAL_CONSTANTS.MOON_SYNODIC_PERIOD;
// Calculate days to next full moon
const daysToFullMoon = age < 14.765 ?
(14.765 - age) :
(ASTRONOMICAL_CONSTANTS.MOON_SYNODIC_PERIOD - age + 14.765);
return {
phaseName,
illumination,
age,
daysToFullMoon,
phaseAngle: lunarPhaseAngle
};
}
function updateSimulation() {
// Convert to Julian Date (TDB timescale)
const jd = dateToJulianDate(simulationTime);
// Calculate Earth orbital position (heliocentric ecliptic J2000)
const orbital = calculateOrbitalPosition(jd);
// Calculate Moon position (geocentric)
const moonData = calculateLunarPosition(jd);
// Update Earth position
earthTiltGroup.position.set(orbital.x, 0, orbital.z);
// Calculate Earth rotation (sidereal day - NOT solar day)
const daysSinceJ2000 = jd - ASTRONOMICAL_CONSTANTS.J2000;
const rotations = daysSinceJ2000 / ASTRONOMICAL_CONSTANTS.SIDEREAL_DAY;
earthRotationGroup.rotation.y = (rotations % 1) * Math.PI * 2;
// Update Moon position (relative to Earth)
moon.position.set(moonData.x, 0, moonData.z);
// Update Earth-Moon barycenter position
// Barycenter is offset from Earth center toward Moon
const barycenterRatio = 1 / (1 + ASTRONOMICAL_CONSTANTS.EARTH_MOON_MASS_RATIO);
barycenterMarker.position.set(
moonData.x * barycenterRatio,
0,
moonData.z * barycenterRatio
);
// Calculate Moon phase
const moonPhase = calculateMoonPhase(jd, orbital, moonData);
// Calculate precession (axial precession - 25,772 year cycle)
const precessionAngle = (daysSinceJ2000 / (ASTRONOMICAL_CONSTANTS.PRECESSION_PERIOD * 365.25)) * 360;
// Update UI
updateUI(jd, orbital, moonData, moonPhase, daysSinceJ2000, rotations, precessionAngle);
// Update date picker
const dateString = simulationTime.toISOString().slice(0, 16);
document.getElementById('date-picker').value = dateString;
}
function updateUI(jd, orbital, moonData, moonPhase, daysSinceJ2000, rotations, precessionAngle) {
// Time & Reference
document.getElementById('current-time').textContent =
simulationTime.toUTCString();
document.getElementById('julian-date').textContent =
jd.toFixed(5);
document.getElementById('days-j2000').textContent =
daysSinceJ2000.toFixed(3);
// Earth Orbital Data
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(4) + ' million km';
document.getElementById('precession-angle').textContent =
(precessionAngle % 360).toFixed(3) + '°';
// Calculate orbital velocity (vis-viva equation)
const GM = 1.327e20; // Sun's gravitational parameter (m³/s²)
const velocity = Math.sqrt(
GM * (2 / (orbital.r * 1000) - 1 / (ASTRONOMICAL_CONSTANTS.SEMI_MAJOR_AXIS * 1000))
) / 1000;
document.getElementById('orbital-velocity').textContent =
velocity.toFixed(3) + ' km/s';
// Determine season (Northern Hemisphere)
const season = getSeason(orbital.v);
document.getElementById('season').textContent = season;
// Moon Orbital Data
document.getElementById('moon-distance').textContent =
moonData.r.toFixed(0) + ' km';
document.getElementById('moon-orbital-position').textContent =
moonData.v.toFixed(2) + '°';
document.getElementById('moon-phase').textContent =
`${moonPhase.phaseName} (${moonPhase.illumination.toFixed(1)}%)`;
document.getElementById('moon-age').textContent =
moonPhase.age.toFixed(2);
document.getElementById('next-full-moon').textContent =
moonPhase.daysToFullMoon.toFixed(1) + ' days';
}
function getSeason(orbitalAngle) {
// Seasons based on orbital position relative to perihelion
// Perihelion occurs around Jan 3 (winter in Northern Hemisphere)
const adjusted = (orbitalAngle + 12) % 360;
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) {
// Convert JavaScript Date to Julian Date (TDB approximation)
// For high precision, TDB differs from UTC by ~69 seconds
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>