| <!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> |
|
|