Current Date:
// Planetary data with scientific accuracy but visual adjustments for better display
const planetaryData = {
sun: {
name: "Sun",
radius: 696340, // km
mass: 1.989e30, // kg
color: 0xffcc00,
info: {
type: "Star (G-type main-sequence)",
diameter: "1,392,684 km",
mass: "1.989 × 10^30 kg",
surfaceTemp: "5,500°C (surface)",
rotationPeriod: "25-35 days",
distanceFromEarth: "149.6 million km",
composition: "Hydrogen (73%), Helium (25%), other elements (2%)"
}
},
mercury: {
name: "Mercury",
radius: 2439.7, // km
semiMajorAxis: 0.387 * 149597870.7, // AU to km
eccentricity: 0.2056,
inclination: 7.0, // degrees
orbitalPeriod: 87.97, // days
color: 0xc8c8c8,
info: {
type: "Terrestrial Planet",
diameter: "4,879.4 km",
mass: "3.3011 × 10^23 kg",
surfaceTemp: "-173°C to 427°C",
rotationPeriod: "58.65 days",
orbitalPeriod: "87.97 days",
distanceFromSun: "57.91 million km",
atmosphere: "Minimal - sodium, potassium, oxygen"
}
},
venus: {
name: "Venus",
radius: 6051.8, // km
semiMajorAxis: 0.723 * 149597870.7, // AU to km
eccentricity: 0.0068,
inclination: 3.39, // degrees
orbitalPeriod: 224.7, // days
color: 0xffd699,
info: {
type: "Terrestrial Planet",
diameter: "12,104 km",
mass: "4.8675 × 10^24 kg",
surfaceTemp: "462°C (average)",
rotationPeriod: "243 days (retrograde)",
orbitalPeriod: "224.7 days",
distanceFromSun: "108.2 million km",
atmosphere: "Dense CO2, nitrogen, sulfuric acid clouds"
}
},
earth: {
name: "Earth",
radius: 6371, // km
semiMajorAxis: 1.0 * 149597870.7, // AU to km
eccentricity: 0.0167,
inclination: 0.0, // degrees (reference plane)
orbitalPeriod: 365.25, // days
color: 0x4da6ff,
info: {
type: "Terrestrial Planet",
diameter: "12,742 km",
mass: "5.972 × 10^24 kg",
surfaceTemp: "15°C (average)",
rotationPeriod: "23.93 hours",
orbitalPeriod: "365.25 days",
distanceFromSun: "149.6 million km",
atmosphere: "Nitrogen (78%), Oxygen (21%), other gases (1%)",
moons: "1 (Luna)"
}
},
mars: {
name: "Mars",
radius: 3389.5, // km
semiMajorAxis: 1.524 * 149597870.7, // AU to km
eccentricity: 0.0934,
inclination: 1.85, // degrees
orbitalPeriod: 686.98, // days
color: 0xff6b4a,
info: {
type: "Terrestrial Planet",
diameter: "6,779 km",
mass: "6.4171 × 10^23 kg",
surfaceTemp: "-87°C to -5°C",
rotationPeriod: "24.62 hours",
orbitalPeriod: "687 days",
distanceFromSun: "227.9 million km",
atmosphere: "Thin - CO2 (95%), nitrogen, argon",
moons: "2 (Phobos, Deimos)"
}
},
jupiter: {
name: "Jupiter",
radius: 69911, // km
semiMajorAxis: 5.204 * 149597870.7, // AU to km
eccentricity: 0.0489,
inclination: 1.31, // degrees
orbitalPeriod: 4332.59, // days
color: 0xffb347,
info: {
type: "Gas Giant",
diameter: "139,820 km",
mass: "1.898 × 10^27 kg",
surfaceTemp: "-145°C (cloud top)",
rotationPeriod: "9.93 hours",
orbitalPeriod: "11.86 years",
distanceFromSun: "778.5 million km",
atmosphere: "Hydrogen, helium, methane, ammonia",
moons: "79+ known moons"
}
},
saturn: {
name: "Saturn",
radius: 58232, // km
semiMajorAxis: 9.582 * 149597870.7, // AU to km
eccentricity: 0.0565,
inclination: 2.49, // degrees
orbitalPeriod: 10759.22, // days
color: 0xffd700,
hasRings: true,
info: {
type: "Gas Giant",
diameter: "116,460 km",
mass: "5.683 × 10^26 kg",
surfaceTemp: "-178°C (cloud top)",
rotationPeriod: "10.7 hours",
orbitalPeriod: "29.46 years",
distanceFromSun: "1.43 billion km",
atmosphere: "Hydrogen, helium, methane",
rings: "Extensive ring system (ice particles)",
moons: "82+ known moons"
}
},
uranus: {
name: "Uranus",
radius: 25362, // km
semiMajorAxis: 19.201 * 149597870.7, // AU to km
eccentricity: 0.0457,
inclination: 0.77, // degrees
orbitalPeriod: 30688.5, // days
color: 0x40e0d0,
info: {
type: "Ice Giant",
diameter: "50,724 km",
mass: "8.681 × 10^25 kg",
surfaceTemp: "-224°C (cloud top)",
rotationPeriod: "17.24 hours (retrograde)",
orbitalPeriod: "84.01 years",
distanceFromSun: "2.87 billion km",
atmosphere: "Hydrogen, helium, methane (gives blue color)",
moons: "27 known moons"
}
},
neptune: {
name: "Neptune",
radius: 24622, // km
semiMajorAxis: 30.047 * 149597870.7, // AU to km
eccentricity: 0.0113,
inclination: 1.77, // degrees
orbitalPeriod: 60195, // days
color: 0x4169e1,
info: {
type: "Ice Giant",
diameter: "49,244 km",
mass: "1.024 × 10^26 kg",
surfaceTemp: "-218°C (cloud top)",
rotationPeriod: "16.11 hours",
orbitalPeriod: "164.8 years",
distanceFromSun: "4.5 billion km",
atmosphere: "Hydrogen, helium, methane (gives blue color)",
moons: "14 known moons"
}
}
};
// Scale factors for visualization - adjusted for better visibility
const SCALE_FACTOR = 1000; // Much smaller scale factor to bring planets closer
const SIZE_SCALE = 2000; // Much larger size scale for better visibility
const SUN_SCALE = 0.8; // Smaller sun for better balance
// Spacing adjustment factors for more balanced view - more dramatic spacing
const SPACING_ADJUSTMENT = {
mercury: 1.2,
venus: 2,
earth: 3,
mars: 4,
jupiter: 5.5,
saturn: 7,
uranus: 8.5,
neptune: 10
};
// Global variables
let scene, camera, renderer, controls;
let planets = {};
let planetLabels = {};
let orbits = {};
let clock = new THREE.Clock();
let timeSpeed = 1; // Days per second
let isPlaying = true;
let showOrbits = true; // Show orbits by default
let showLabels = true; // Show labels by default
let currentDate = new Date();
let startDate = new Date();
let elapsedDays = 0;
let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
let hoveredPlanet = null;
let assetsLoaded = 0;
let totalAssets = 10; // Sun + 8 planets + starfield
// Initialize the scene
function init() {
updateLoadingProgress(10);
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000033); // Dark blue background for space
// Create camera
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 50000);
camera.position.set(0, 300, 500); // Better initial view to see all planets
// Create renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.getElementById('canvas-container').appendChild(renderer.domElement);
updateLoadingProgress(20);
// Add orbit controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.maxDistance = 5000; // Limit how far users can zoom out
// Add ambient light
const ambientLight = new THREE.AmbientLight(0x888888); // Brighter ambient light
scene.add(ambientLight);
updateLoadingProgress(30);
// Create starfield background
createStarfield();
updateLoadingProgress(50);
// Create sun with point light
createSun();
updateLoadingProgress(60);
// Create planets
createPlanets();
updateLoadingProgress(80);
// Add event listeners
window.addEventListener('resize', onWindowResize);
window.addEventListener('mousemove', onMouseMove);
document.getElementById('play-pause').addEventListener('click', togglePlayPause);
document.getElementById('speed-slider').addEventListener('input', updateSpeed);
document.getElementById('go-to-date').addEventListener('click', goToDate);
document.getElementById('reset-date').addEventListener('click', resetDate);
document.getElementById('toggle-orbits').addEventListener('click', toggleOrbits);
document.getElementById('toggle-labels').addEventListener('click', toggleLabels);
document.getElementById('reset-view').addEventListener('click', resetView);
// Initialize date picker with today's date
const today = new Date();
const dateString = today.toISOString().split('T')[0];
document.getElementById('date-picker').value = dateString;
updateLoadingProgress(90);
// Start animation loop
animate();
}
function assetLoaded() {
assetsLoaded++;
const progress = Math.floor((assetsLoaded / totalAssets) * 100);
updateLoadingProgress(progress);
if (assetsLoaded >= totalAssets) {
// Hide loading screen after a short delay
setTimeout(() => {
document.getElementById('loading-screen').style.display = 'none';
}, 500);
}
}
function updateLoadingProgress(percent) {
document.getElementById('loading-bar').style.width = `${percent}%`;
}
function createStarfield() {
const starGeometry = new THREE.BufferGeometry();
const starCount = 2000;
const positions = new Float32Array(starCount * 3);
const sizes = new Float32Array(starCount);
for (let i = 0; i < starCount * 3; i += 3) {
// Random position in a sphere
const radius = 4000;
const theta = 2 * Math.PI * Math.random();
const phi = Math.acos(2 * Math.random() - 1);
positions[i] = radius * Math.sin(phi) * Math.cos(theta);
positions[i + 1] = radius * Math.sin(phi) * Math.sin(theta);
positions[i + 2] = radius * Math.cos(phi);
// Random sizes for stars
sizes[i/3] = Math.random() * 2 + 1;
}
starGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
starGeometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
const starMaterial = new THREE.PointsMaterial({
color: 0xffffff,
size: 2,
sizeAttenuation: true
});
const stars = new THREE.Points(starGeometry, starMaterial);
scene.add(stars);
assetLoaded(); // Count starfield as loaded
}
function createSun() {
const sunGeometry = new THREE.SphereGeometry(planetaryData.sun.radius / SCALE_FACTOR * SUN_SCALE, 32, 32);
const sunTexture = new THREE.TextureLoader().load(
'https://cdn.simulationtheory.ai/gasset/?asset=img&prompt=cartoon sun texture with orange and yellow swirls&w=512&h=512',
function() { assetLoaded(); } // Callback when texture is loaded
);
const sunMaterial = new THREE.MeshBasicMaterial({
map: sunTexture,
color: 0xffdd00,
emissive: 0xff8800,
emissiveIntensity: 0.5
});
const sun = new THREE.Mesh(sunGeometry, sunMaterial);
scene.add(sun);
planets.sun = sun;
// Add point light at sun's position
const sunLight = new THREE.PointLight(0xffffff, 2, 5000);
sun.add(sunLight);
}
function createPlanets() {
// Create each planet
for (const [key, data] of Object.entries(planetaryData)) {
if (key === 'sun') continue; // Skip sun as it's already created
// Create planet geometry and material
const planetGeometry = new THREE.SphereGeometry(
Math.max(data.radius / SCALE_FACTOR * SIZE_SCALE, 10), // Minimum size for visibility
24, 24
);
// Use a more cartoony material with some texture
const planetTexture = new THREE.TextureLoader().load(
`https://cdn.simulationtheory.ai/gasset/?asset=img&prompt=cartoon ${key} planet texture bright colorful&w=256&h=256`,
function() { assetLoaded(); } // Callback when texture is loaded
);
// Brighter, more saturated colors for a cartoony look
const planetColor = new THREE.Color(data.color);
planetColor.multiplyScalar(1.5); // Make colors brighter
const planetMaterial = new THREE.MeshLambertMaterial({
map: planetTexture,
color: planetColor
});
const planet = new THREE.Mesh(planetGeometry, planetMaterial);
// Add planet to scene
scene.add(planet);
planets[key] = planet;
// Create orbit path with adjusted spacing
createOrbitPath(data, key);
// Create planet label
createPlanetLabel(key, data.name);
// Add Saturn's rings if applicable
if (data.hasRings) {
const ringGeometry = new THREE.RingGeometry(
planet.geometry.parameters.radius * 1.3,
planet.geometry.parameters.radius * 2.2,
64
);
const ringTexture = new THREE.TextureLoader().load(
'https://cdn.simulationtheory.ai/gasset/?asset=img&prompt=cartoon saturn rings texture colorful&w=512&h=512'
);
const ringMaterial = new THREE.MeshBasicMaterial({
map: ringTexture,
color: 0xf8e8c0,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.9
});
const ring = new THREE.Mesh(ringGeometry, ringMaterial);
ring.rotation.x = Math.PI / 2;
planet.add(ring);
}
}
}
function createOrbitPath(planetData, planetKey) {
// Create elliptical orbit path with adjusted spacing
const orbitPoints = [];
const segments = 128;
// Apply spacing adjustment for better visibility
const adjustmentFactor = SPACING_ADJUSTMENT[planetKey] || 1;
const adjustedSemiMajorAxis = (planetData.semiMajorAxis / SCALE_FACTOR) * adjustmentFactor;
for (let i = 0; i <= segments; i++) {
const theta = (i / segments) * Math.PI * 2;
const semiMinorAxis = adjustedSemiMajorAxis * Math.sqrt(1 - Math.pow(planetData.eccentricity, 2));
// Calculate position on ellipse
const x = adjustedSemiMajorAxis * Math.cos(theta);
const y = 0; // Orbit plane
const z = semiMinorAxis * Math.sin(theta);
// Apply inclination
const inclination = planetData.inclination * Math.PI / 180;
const rotatedY = y * Math.cos(inclination) - z * Math.sin(inclination);
const rotatedZ = y * Math.sin(inclination) + z * Math.cos(inclination);
orbitPoints.push(new THREE.Vector3(x, rotatedY, rotatedZ));
}
const orbitGeometry = new THREE.BufferGeometry().setFromPoints(orbitPoints);
// More visible orbit paths with planet-specific colors
const orbitColor = new THREE.Color(planetData.color);
orbitColor.multiplyScalar(0.7); // Slightly darker than planet
const orbitMaterial = new THREE.LineBasicMaterial({
color: orbitColor,
transparent: true,
opacity: 0.8,
linewidth: 2
});
const orbit = new THREE.Line(orbitGeometry, orbitMaterial);
orbit.visible = showOrbits;
scene.add(orbit);
orbits[planetKey] = orbit;
}
function createPlanetLabel(planetKey, planetName) {
// Create div element for the label
const labelDiv = document.createElement('div');
labelDiv.className = 'planet-label';
labelDiv.textContent = planetName;
labelDiv.style.display = showLabels ? 'block' : 'none';
document.body.appendChild(labelDiv);
planetLabels[planetKey] = labelDiv;
}
function updatePlanetPositions(days) {
// Update each planet's position based on orbital parameters
for (const [key, data] of Object.entries(planetaryData)) {
if (key === 'sun') continue; // Skip sun
const planet = planets[key];
if (!planet) continue;
// Apply spacing adjustment for better visibility
const adjustmentFactor = SPACING_ADJUSTMENT[key] || 1;
const adjustedSemiMajorAxis = (data.semiMajorAxis / SCALE_FACTOR) * adjustmentFactor;
// Calculate position in orbit
const semiMinorAxis = adjustedSemiMajorAxis * Math.sqrt(1 - Math.pow(data.eccentricity, 2));
const meanAnomaly = (2 * Math.PI / data.orbitalPeriod) * days;
// Solve Kepler's equation to get eccentric anomaly (E)
let E = meanAnomaly;
const e = data.eccentricity;
// Newton-Raphson iteration to solve Kepler's equation
for (let i = 0; i < 10; i++) {
E = E - (E - e * Math.sin(E) - meanAnomaly) / (1 - e * Math.cos(E));
}
// Calculate position in orbital plane
const x = adjustedSemiMajorAxis * (Math.cos(E) - e);
const z = semiMinorAxis * Math.sin(E);
// Apply inclination
const inclination = data.inclination * Math.PI / 180;
const y = z * Math.sin(inclination);
const rotatedZ = z * Math.cos(inclination);
// Update planet position
planet.position.set(x, y, rotatedZ);
// Add a small rotation to make planets spin
planet.rotation.y += 0.005;
// Update planet label position
updateLabelPosition(key, planet);
}
}
function updateLabelPosition(planetKey, planet) {
const label = planetLabels[planetKey];
if (!label) return;
// Convert 3D position to screen coordinates
const position = planet.position.clone();
position.project(camera);
// Convert to screen coordinates
const x = (position.x * 0.5 + 0.5) * window.innerWidth;
const y = (- position.y * 0.5 + 0.5) * window.innerHeight;
// Update label position
label.style.transform = `translate(-50%, -50%) translate(${x}px, ${y}px)`;
// Hide label if planet is behind the camera
if (position.z > 1) {
label.style.display = 'none';
} else {
label.style.display = showLabels ? 'block' : 'none';
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onMouseMove(event) {
// Calculate mouse position in normalized device coordinates
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
// Update raycaster
raycaster.setFromCamera(mouse, camera);
// Get intersected objects
const intersects = raycaster.intersectObjects(Object.values(planets));
// Reset previously hovered planet
if (hoveredPlanet) {
const planetKey = Object.keys(planets).find(key => planets[key] === hoveredPlanet);
// Reset scale with smooth transition
gsap.to(hoveredPlanet.scale, {
x: 1,
y: 1,
z: 1,
duration: 0.3
});
hoveredPlanet = null;
document.getElementById('info-panel').style.display = 'none';
}
// Handle new hover
if (intersects.length > 0) {
const object = intersects[0].object;
const planetKey = Object.keys(planets).find(key => planets[key] === object);
if (planetKey) {
hoveredPlanet = object;
// Scale up with smooth transition
gsap.to(hoveredPlanet.scale, {
x: 1.2,
y: 1.2,
z: 1.2,
duration: 0.3
});
// Show info panel
showPlanetInfo(planetKey);
}
}
}
function showPlanetInfo(planetKey) {
const planetData = planetaryData[planetKey];
const infoPanel = document.getElementById('info-panel');
const planetName = document.getElementById('planet-name');
const planetDetails = document.getElementById('planet-details');
// Set planet name
planetName.textContent = planetData.name;
// Clear previous details
planetDetails.innerHTML = '';
// Add planet details
for (const [key, value] of Object.entries(planetData.info)) {
const detailItem = document.createElement('p');
detailItem.innerHTML = `${key.charAt(0).toUpperCase() + key.slice(1)}: ${value}`;
planetDetails.appendChild(detailItem);
}
// Show info panel
infoPanel.style.display = 'block';
}
function togglePlayPause() {
isPlaying = !isPlaying;
const button = document.getElementById('play-pause');
button.textContent = isPlaying ? 'Pause' : 'Play';
button.classList.toggle('active', isPlaying);
if (isPlaying) {
clock.start();
} else {
clock.stop();
}
}
function updateSpeed() {
const slider = document.getElementById('speed-slider');
timeSpeed = parseFloat(slider.value);
document.getElementById('speed-value').textContent = `${timeSpeed}x`;
}
function goToDate() {
const datePicker = document.getElementById('date-picker');
const selectedDate = new Date(datePicker.value);
if (!isNaN(selectedDate.getTime())) {
// Calculate days difference
const diffTime = selectedDate.getTime() - startDate.getTime();
elapsedDays = diffTime / (1000 * 60 * 60 * 24);
// Update current date
currentDate = new Date(selectedDate);
updateDateDisplay();
// Update planet positions
updatePlanetPositions(elapsedDays);
}
}
function resetDate() {
// Reset to current date
startDate = new Date();
currentDate = new Date();
elapsedDays = 0;
// Update date picker
const dateString = currentDate.toISOString().split('T')[0];
document.getElementById('date-picker').value = dateString;
// Update display
updateDateDisplay();
// Update planet positions
updatePlanetPositions(elapsedDays);
}
function toggleOrbits() {
showOrbits = !showOrbits;
document.getElementById('toggle-orbits').classList.toggle('active', showOrbits);
// Update orbit visibility
for (const orbit of Object.values(orbits)) {
orbit.visible = showOrbits;
}
}
function toggleLabels() {
showLabels = !showLabels;
document.getElementById('toggle-labels').classList.toggle('active', showLabels);
// Update label visibility
for (const label of Object.values(planetLabels)) {
label.style.display = showLabels ? 'block' : 'none';
}
}
function resetView() {
// Reset camera position and controls to see all planets
gsap.to(camera.position, {
x: 0,
y: 300,
z: 500,
duration: 1,
onUpdate: function() {
camera.lookAt(0, 0, 0);
}
});
controls.reset();
}
function updateDateDisplay() {
document.getElementById('current-date').textContent = currentDate.toLocaleDateString();
}
function animate() {
requestAnimationFrame(animate);
if (isPlaying) {
const delta = clock.getDelta();
const daysElapsed = delta * timeSpeed;
// Update elapsed days and current date
elapsedDays += daysElapsed;
currentDate = new Date(startDate.getTime() + elapsedDays * 24 * 60 * 60 * 1000);
// Update date display
updateDateDisplay();
// Update planet positions
updatePlanetPositions(elapsedDays);
}
// Update controls
controls.update();
// Render scene
renderer.render(scene, camera);
}
// Initialize the scene when the page loads
window.addEventListener('load', init);
// GSAP for animations (minimal implementation)
const gsap = {
to: function(target, options) {
const duration = options.duration || 1;
const startTime = Date.now();
const startValues = {};
const endValues = {};
// Store start values and calculate changes
for (const key in options) {
if (key !== 'duration' && key !== 'onUpdate') {
startValues[key] = target[key];
endValues[key] = options[key];
}
}
function update() {
const elapsed = (Date.now() - startTime) / 1000;
const progress = Math.min(elapsed / duration, 1);
// Update values
for (const key in startValues) {
target[key] = startValues[key] + (endValues[key] - startValues[key]) * progress;
}
// Call onUpdate if provided
if (options.onUpdate) {
options.onUpdate();
}
// Continue animation if not complete
if (progress < 1) {
requestAnimationFrame(update);
}
}
// Start animation
update();
}
};