|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>3D Solar System Simulation</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<style> |
|
|
#scene-container { |
|
|
width: 100vw; |
|
|
height: 100vh; |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
#planet-info { |
|
|
display: none; |
|
|
position: absolute; |
|
|
bottom: 5vh; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
background: rgba(0,0,0,0.8); |
|
|
backdrop-filter: blur(10px); |
|
|
color: white; |
|
|
padding: 1.5rem; |
|
|
border-radius: 1rem; |
|
|
max-width: 90%; |
|
|
width: 500px; |
|
|
box-sizing: border-box; |
|
|
z-index: 1000; |
|
|
box-shadow: 0 10px 25px rgba(0,0,0,0.5); |
|
|
border: 1px solid rgba(255,255,255,0.1); |
|
|
} |
|
|
#planet-info h2 { |
|
|
margin-top: 0; |
|
|
font-size: 1.75rem; |
|
|
color: #fff; |
|
|
border-bottom: 1px solid rgba(255,255,255,0.2); |
|
|
padding-bottom: 0.5rem; |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
#planet-info p { |
|
|
margin: 0.5rem 0; |
|
|
line-height: 1.6; |
|
|
} |
|
|
#planet-info button { |
|
|
margin-top: 1rem; |
|
|
background: rgba(255,255,255,0.1); |
|
|
color: white; |
|
|
border: none; |
|
|
padding: 0.5rem 1rem; |
|
|
border-radius: 0.375rem; |
|
|
cursor: pointer; |
|
|
font-size: 0.875rem; |
|
|
transition: all 0.3s ease; |
|
|
width: 100%; |
|
|
} |
|
|
#planet-info button:hover { |
|
|
background: rgba(255,255,255,0.2); |
|
|
transform: translateY(-2px); |
|
|
} |
|
|
.loading-overlay { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
background: #000; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
z-index: 1001; |
|
|
color: white; |
|
|
} |
|
|
.loading-spinner { |
|
|
width: 50px; |
|
|
height: 50px; |
|
|
border: 5px solid rgba(255,255,255,0.1); |
|
|
border-radius: 50%; |
|
|
border-top-color: #fff; |
|
|
animation: spin 1s ease-in-out infinite; |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
@keyframes spin { |
|
|
to { transform: rotate(360deg); } |
|
|
} |
|
|
.hud { |
|
|
position: absolute; |
|
|
top: 20px; |
|
|
left: 20px; |
|
|
z-index: 100; |
|
|
color: white; |
|
|
background: rgba(0,0,0,0.5); |
|
|
padding: 10px 15px; |
|
|
border-radius: 8px; |
|
|
font-family: 'Arial', sans-serif; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-black overflow-hidden"> |
|
|
<div class="loading-overlay" id="loading"> |
|
|
<div class="loading-spinner"></div> |
|
|
<div>Loading Solar System...</div> |
|
|
</div> |
|
|
|
|
|
<div id="scene-container"></div> |
|
|
<div id="planet-info"></div> |
|
|
|
|
|
<div class="hud"> |
|
|
<div class="text-xl font-bold mb-2">Solar System</div> |
|
|
<div class="text-sm">Click on planets for info</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/three@0.155.0/build/three.min.js"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/controls/OrbitControls.js"></script> |
|
|
|
|
|
<script> |
|
|
|
|
|
const earthRealRadius = 6371; |
|
|
const earthScaledRadius = 1; |
|
|
const scaleAU = 200; |
|
|
const simSpeed = 1000; |
|
|
const planetScale = earthScaledRadius / earthRealRadius; |
|
|
|
|
|
|
|
|
const planets = [ |
|
|
{ |
|
|
name: "Mercury", |
|
|
radius: 2440, |
|
|
orbitAU: 0.39, |
|
|
orbitalPeriod: 87.97, |
|
|
rotationPeriod: 58.646, |
|
|
texture: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/7e/Mercury_radar_topographic_map.jpg/1280px-Mercury_radar_topographic_map.jpg", |
|
|
desc: "The closest planet to the Sun. It has a thin exosphere and extreme temperature variations." |
|
|
}, |
|
|
{ |
|
|
name: "Venus", |
|
|
radius: 6052, |
|
|
orbitAU: 0.72, |
|
|
orbitalPeriod: 224.7, |
|
|
rotationPeriod: 243.01, |
|
|
texture: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/79/Venus_globe_renders_v1_0.jpg/1280px-Venus_globe_renders_v1_0.jpg", |
|
|
desc: "Similar in size to Earth but with a thick CO₂ atmosphere creating a runaway greenhouse effect (462°C average)." |
|
|
}, |
|
|
{ |
|
|
name: "Earth", |
|
|
radius: 6371, |
|
|
orbitAU: 1.0, |
|
|
orbitalPeriod: 365.25, |
|
|
rotationPeriod: 1.0, |
|
|
texture: "https://threejs.org/examples/textures/earth_atmos_2048.jpg", |
|
|
desc: "The only known planet with life. Has abundant liquid water and an oxygen-rich atmosphere." |
|
|
}, |
|
|
{ |
|
|
name: "Mars", |
|
|
radius: 3390, |
|
|
orbitAU: 1.52, |
|
|
orbitalPeriod: 686.98, |
|
|
rotationPeriod: 1.025, |
|
|
texture: "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/Mars_globe_renders_v1_0.jpg/1280px-Mars_globe_renders_v1_0.jpg", |
|
|
desc: "The 'Red Planet' with polar ice caps and evidence of past liquid water." |
|
|
}, |
|
|
{ |
|
|
name: "Jupiter", |
|
|
radius: 69911, |
|
|
orbitAU: 5.2, |
|
|
orbitalPeriod: 4332.59, |
|
|
rotationPeriod: 0.4135, |
|
|
texture: "https://threejs.org/examples/textures/jupiter.jpg", |
|
|
desc: "The largest planet - a gas giant with a Great Red Spot (persistent storm larger than Earth)." |
|
|
}, |
|
|
{ |
|
|
name: "Saturn", |
|
|
radius: 58232, |
|
|
orbitAU: 9.5, |
|
|
orbitalPeriod: 10759.22, |
|
|
rotationPeriod: 0.444, |
|
|
texture: "https://threejs.org/examples/textures/saturn.jpg", |
|
|
desc: "Famous for its spectacular ring system composed of ice and rock particles." |
|
|
}, |
|
|
{ |
|
|
name: "Uranus", |
|
|
radius: 25362, |
|
|
orbitAU: 19.2, |
|
|
orbitalPeriod: 30588.8, |
|
|
rotationPeriod: 0.7183, |
|
|
texture: "https://threejs.org/examples/textures/uranus.jpg", |
|
|
desc: "An ice giant that rotates on its side (98° axial tilt) with a pale blue hue from methane." |
|
|
}, |
|
|
{ |
|
|
name: "Neptune", |
|
|
radius: 24622, |
|
|
orbitAU: 30.1, |
|
|
orbitalPeriod: 60182.0, |
|
|
rotationPeriod: 0.6685, |
|
|
texture: "https://threejs.org/examples/textures/neptune.jpg", |
|
|
desc: "The windiest planet with speeds up to 2,100 km/h - an ice giant with active weather." |
|
|
} |
|
|
]; |
|
|
|
|
|
|
|
|
const scene = new THREE.Scene(); |
|
|
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000); |
|
|
camera.position.set(0, 500, 5000); |
|
|
|
|
|
const renderer = new THREE.WebGLRenderer({ |
|
|
antialias: true, |
|
|
alpha: true |
|
|
}); |
|
|
renderer.setPixelRatio(window.devicePixelRatio); |
|
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
|
renderer.setClearColor(0x000000, 1); |
|
|
document.getElementById('scene-container').appendChild(renderer.domElement); |
|
|
|
|
|
|
|
|
const ambientLight = new THREE.HemisphereLight(0x8888ff, 0x333333, 0.8); |
|
|
scene.add(ambientLight); |
|
|
|
|
|
const dirLight = new THREE.DirectionalLight(0xffffff, 1); |
|
|
dirLight.position.set(5000, 5000, 5000); |
|
|
scene.add(dirLight); |
|
|
|
|
|
|
|
|
const starsGeometry = new THREE.BufferGeometry(); |
|
|
const starsMaterial = new THREE.PointsMaterial({ |
|
|
color: 0xffffff, |
|
|
size: 1, |
|
|
transparent: true, |
|
|
opacity: 0.8 |
|
|
}); |
|
|
|
|
|
const starsVertices = []; |
|
|
for (let i = 0; i < 10000; i++) { |
|
|
const x = (Math.random() - 0.5) * 20000; |
|
|
const y = (Math.random() - 0.5) * 20000; |
|
|
const z = (Math.random() - 0.5) * 20000; |
|
|
starsVertices.push(x, y, z); |
|
|
} |
|
|
|
|
|
starsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starsVertices, 3)); |
|
|
const stars = new THREE.Points(starsGeometry, starsMaterial); |
|
|
scene.add(stars); |
|
|
|
|
|
|
|
|
const controls = new THREE.OrbitControls(camera, renderer.domElement); |
|
|
controls.enableDamping = true; |
|
|
controls.dampingFactor = 0.05; |
|
|
controls.minDistance = 100; |
|
|
controls.maxDistance = 10000; |
|
|
controls.autoRotate = true; |
|
|
controls.autoRotateSpeed = 0.5; |
|
|
|
|
|
|
|
|
const textureLoader = new THREE.TextureLoader(); |
|
|
let allTextures = []; |
|
|
let loadErrors = 0; |
|
|
|
|
|
function loadTextures(callback) { |
|
|
loadErrors = 0; |
|
|
allTextures = []; |
|
|
const total = planets.length + 1; |
|
|
let loaded = 0; |
|
|
|
|
|
|
|
|
textureLoader.load( |
|
|
"https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Sun_rasterized_2017.jpg/1280px-Sun_rasterized_2017.jpg", |
|
|
(texture) => { |
|
|
allTextures.push({ type: "sun", texture }); |
|
|
loaded++; |
|
|
checkComplete(); |
|
|
}, |
|
|
undefined, |
|
|
(err) => { |
|
|
console.error("Sun texture loading error:", err); |
|
|
loadErrors++; |
|
|
checkComplete(); |
|
|
} |
|
|
); |
|
|
|
|
|
|
|
|
planets.forEach(planet => { |
|
|
textureLoader.load( |
|
|
planet.texture, |
|
|
(texture) => { |
|
|
allTextures.push({ type: "planet", planet, texture }); |
|
|
loaded++; |
|
|
checkComplete(); |
|
|
}, |
|
|
undefined, |
|
|
(err) => { |
|
|
console.error(`${planet.name} texture loading error:`, err); |
|
|
loadErrors++; |
|
|
checkComplete(); |
|
|
} |
|
|
); |
|
|
}); |
|
|
|
|
|
function checkComplete() { |
|
|
if (loaded + loadErrors === total) { |
|
|
if (loadErrors > 0) { |
|
|
console.error(`Failed to load ${loadErrors} texture(s). Check URLs or your connection.`); |
|
|
} |
|
|
callback(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function createSun() { |
|
|
const sunData = allTextures.find(t => t.type === "sun"); |
|
|
const sunRadius = 696340 * planetScale; |
|
|
const sunGeo = new THREE.SphereGeometry(sunRadius, 64, 64); |
|
|
const sunMat = new THREE.MeshStandardMaterial({ |
|
|
map: sunData.texture, |
|
|
emissive: 0xffd700, |
|
|
emissiveIntensity: 1, |
|
|
roughness: 0.8 |
|
|
}); |
|
|
const sun = new THREE.Mesh(sunGeo, sunMat); |
|
|
|
|
|
|
|
|
const glowGeometry = new THREE.SphereGeometry(sunRadius * 1.2, 32, 32); |
|
|
const glowMaterial = new THREE.MeshBasicMaterial({ |
|
|
color: 0xffd700, |
|
|
transparent: true, |
|
|
opacity: 0.3, |
|
|
blending: THREE.AdditiveBlending |
|
|
}); |
|
|
const glow = new THREE.Mesh(glowGeometry, glowMaterial); |
|
|
sun.add(glow); |
|
|
|
|
|
scene.add(sun); |
|
|
return sun; |
|
|
} |
|
|
|
|
|
|
|
|
function createPlanet(planet, texture) { |
|
|
const scaledRadius = planet.radius * planetScale; |
|
|
const orbitRadius = planet.orbitAU * scaleAU; |
|
|
|
|
|
|
|
|
const rotSpeed = (Math.PI * 2) / (planet.rotationPeriod * 86400) * simSpeed; |
|
|
const orbSpeed = (Math.PI * 2) / (planet.orbitalPeriod * 86400) * simSpeed; |
|
|
|
|
|
|
|
|
const geo = new THREE.SphereGeometry(scaledRadius, 64, 64); |
|
|
const mat = new THREE.MeshStandardMaterial({ |
|
|
map: texture, |
|
|
roughness: 0.8, |
|
|
metalness: 0.1 |
|
|
}); |
|
|
const mesh = new THREE.Mesh(geo, mat); |
|
|
|
|
|
|
|
|
mesh.userData = { |
|
|
type: "planet", |
|
|
name: planet.name, |
|
|
diameter: `${(2 * planet.radius).toLocaleString()} km`, |
|
|
distance: `${planet.orbitAU.toFixed(2)} AU`, |
|
|
period: `${planet.orbitalPeriod.toFixed(1)} days`, |
|
|
rotationSpeed: rotSpeed, |
|
|
orbitSpeed: orbSpeed, |
|
|
orbitRadius: orbitRadius, |
|
|
angle: Math.random() * Math.PI * 2, |
|
|
rotationAngle: 0, |
|
|
desc: planet.desc |
|
|
}; |
|
|
|
|
|
|
|
|
const orbitGeo = new THREE.CircleGeometry(orbitRadius, 200); |
|
|
orbitGeo.rotateX(Math.PI / 2); |
|
|
const orbitMat = new THREE.LineBasicMaterial({ |
|
|
color: 0x4a5568, |
|
|
transparent: true, |
|
|
opacity: 0.5 |
|
|
}); |
|
|
const orbitMesh = new THREE.LineLoop(orbitGeo, orbitMat); |
|
|
scene.add(orbitMesh); |
|
|
|
|
|
|
|
|
mesh.position.x = orbitRadius; |
|
|
scene.add(mesh); |
|
|
return { mesh, data: mesh.userData }; |
|
|
} |
|
|
|
|
|
|
|
|
loadTextures(() => { |
|
|
document.getElementById('loading').style.display = 'none'; |
|
|
|
|
|
if (loadErrors === 0) { |
|
|
createSun(); |
|
|
const planetObjects = []; |
|
|
|
|
|
allTextures.forEach(t => { |
|
|
if (t.type === "planet") { |
|
|
const obj = createPlanet(t.planet, t.texture); |
|
|
planetObjects.push(obj); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const clock = new THREE.Clock(); |
|
|
function animate() { |
|
|
requestAnimationFrame(animate); |
|
|
const delta = clock.getDelta(); |
|
|
|
|
|
controls.update(delta); |
|
|
|
|
|
|
|
|
planetObjects.forEach(obj => { |
|
|
const data = obj.data; |
|
|
|
|
|
|
|
|
data.rotationAngle += data.rotationSpeed * delta; |
|
|
obj.mesh.rotation.y = data.rotationAngle % (Math.PI * 2); |
|
|
|
|
|
|
|
|
data.angle += data.orbitSpeed * delta; |
|
|
const x = data.orbitRadius * Math.cos(data.angle); |
|
|
const z = data.orbitRadius * Math.sin(data.angle); |
|
|
obj.mesh.position.set(x, 0, z); |
|
|
}); |
|
|
|
|
|
renderer.render(scene, camera); |
|
|
} |
|
|
animate(); |
|
|
|
|
|
|
|
|
window.addEventListener('resize', () => { |
|
|
camera.aspect = window.innerWidth / window.innerHeight; |
|
|
camera.updateProjectionMatrix(); |
|
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const infoDiv = document.getElementById('planet-info'); |
|
|
renderer.domElement.addEventListener('click', (event) => { |
|
|
const mouse = new THREE.Vector2(); |
|
|
mouse.x = (event.clientX / window.innerWidth) * 2 - 1; |
|
|
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; |
|
|
|
|
|
const raycaster = new THREE.Raycaster(); |
|
|
raycaster.setFromCamera(mouse, camera); |
|
|
|
|
|
const intersects = raycaster.intersectObjects(scene.children, true); |
|
|
const clickedPlanet = intersects.find(inter => inter.object.userData?.type === "planet"); |
|
|
|
|
|
if (clickedPlanet && clickedPlanet.object.userData) { |
|
|
const data = clickedPlanet.object.userData; |
|
|
infoDiv.innerHTML = ` |
|
|
<h2>${data.name}</h2> |
|
|
<p><strong>Diameter:</strong> ${data.diameter}</p> |
|
|
<p><strong>Distance from Sun:</strong> ${data.distance}</p> |
|
|
<p><strong>Orbital Period:</strong> ${data.period}</p> |
|
|
<p>${data.desc}</p> |
|
|
<button onclick="document.getElementById('planet-info').style.display = 'none'">Close</button> |
|
|
`; |
|
|
infoDiv.style.display = "block"; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
infoDiv.style.display = "none"; |
|
|
}, 10000); |
|
|
} else { |
|
|
infoDiv.style.display = "none"; |
|
|
} |
|
|
}); |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|