| <!DOCTYPE html> |
| <html lang="zh-TW"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>3D 太陽系互動模擬器</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/gsap@3.11.4/dist/gsap.min.js"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| body { |
| margin: 0; |
| overflow: hidden; |
| font-family: 'Arial', sans-serif; |
| color: white; |
| } |
| |
| #container { |
| position: absolute; |
| width: 100%; |
| height: 100%; |
| } |
| |
| #ui { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| pointer-events: none; |
| z-index: 10; |
| } |
| |
| .panel { |
| pointer-events: all; |
| background: rgba(0, 0, 0, 0.7); |
| border-radius: 10px; |
| padding: 15px; |
| backdrop-filter: blur(5px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); |
| } |
| |
| .control-btn { |
| background: rgba(255, 255, 255, 0.1); |
| border: none; |
| color: white; |
| border-radius: 50%; |
| width: 40px; |
| height: 40px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: all 0.3s; |
| } |
| |
| .control-btn:hover { |
| background: rgba(255, 255, 255, 0.3); |
| transform: scale(1.1); |
| } |
| |
| .planet-info { |
| opacity: 0; |
| transition: opacity 0.5s; |
| } |
| |
| .planet-info.active { |
| opacity: 1; |
| } |
| |
| .orbit { |
| position: absolute; |
| border: 1px solid rgba(255, 255, 255, 0.2); |
| border-radius: 50%; |
| transform-origin: center; |
| } |
| |
| .loading-screen { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: #000; |
| display: flex; |
| flex-direction: column; |
| justify-content: center; |
| align-items: center; |
| z-index: 100; |
| transition: opacity 1s; |
| } |
| |
| .loading-bar { |
| width: 300px; |
| height: 3px; |
| background: rgba(255, 255, 255, 0.2); |
| margin-top: 20px; |
| overflow: hidden; |
| } |
| |
| .loading-progress { |
| height: 100%; |
| background: linear-gradient(90deg, #00d2ff, #3a7bd5); |
| width: 0%; |
| transition: width 0.3s; |
| } |
| |
| .tooltip { |
| position: absolute; |
| background: rgba(0, 0, 0, 0.8); |
| padding: 5px 10px; |
| border-radius: 5px; |
| font-size: 12px; |
| pointer-events: none; |
| opacity: 0; |
| transition: opacity 0.3s; |
| z-index: 20; |
| } |
| |
| .star { |
| position: absolute; |
| background: white; |
| border-radius: 50%; |
| pointer-events: none; |
| } |
| |
| |
| .planet-label { |
| position: absolute; |
| color: white; |
| font-size: 12px; |
| text-shadow: 0 0 5px black; |
| white-space: nowrap; |
| pointer-events: none; |
| transform: translate(-50%, 0); |
| opacity: 0; |
| transition: opacity 0.3s; |
| } |
| |
| .planet-label.active { |
| opacity: 1; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="loading-screen"> |
| <h1 class="text-3xl font-bold mb-4 text-white">太陽系模擬器</h1> |
| <p class="text-gray-300 mb-6">正在載入宇宙數據...</p> |
| <div class="loading-bar"> |
| <div class="loading-progress" id="loadingProgress"></div> |
| </div> |
| </div> |
|
|
| <div id="container"></div> |
| |
| <div id="ui"> |
| |
| <div class="panel absolute top-4 left-4 w-64"> |
| <h2 class="text-xl font-bold mb-4">太陽系控制中心</h2> |
| <div class="flex justify-between mb-4"> |
| <button id="playPauseBtn" class="control-btn"> |
| <i class="fas fa-play"></i> |
| </button> |
| <button id="speedDownBtn" class="control-btn"> |
| <i class="fas fa-backward"></i> |
| </button> |
| <button id="speedUpBtn" class="control-btn"> |
| <i class="fas fa-forward"></i> |
| </button> |
| <button id="resetBtn" class="control-btn"> |
| <i class="fas fa-redo"></i> |
| </button> |
| </div> |
| <div class="mb-2"> |
| <label class="block text-sm mb-1">時間流速: <span id="speedValue">1x</span></label> |
| <input type="range" id="speedSlider" min="0.1" max="10" step="0.1" value="1" class="w-full"> |
| </div> |
| <div class="mb-2"> |
| <label class="block text-sm mb-1">視角距離: <span id="distanceValue">100%</span></label> |
| <input type="range" id="distanceSlider" min="20" max="200" value="100" class="w-full"> |
| </div> |
| <div class="flex items-center mt-4"> |
| <input type="checkbox" id="showOrbits" checked class="mr-2"> |
| <label for="showOrbits" class="text-sm">顯示軌道</label> |
| </div> |
| <div class="flex items-center mt-2"> |
| <input type="checkbox" id="showLabels" checked class="mr-2"> |
| <label for="showLabels" class="text-sm">顯示標籤</label> |
| </div> |
| <div class="flex items-center mt-2"> |
| <input type="checkbox" id="showMoon" checked class="mr-2"> |
| <label for="showMoon" class="text-sm">顯示月球</label> |
| </div> |
| </div> |
| |
| |
| <div class="panel absolute bottom-4 right-4 w-72 planet-info"> |
| <div class="flex items-center mb-3"> |
| <div id="planetIcon" class="w-10 h-10 rounded-full mr-3"></div> |
| <h2 id="planetName" class="text-xl font-bold">行星名稱</h2> |
| </div> |
| <div class="grid grid-cols-2 gap-2 text-sm"> |
| <div> |
| <div class="text-gray-400">類型</div> |
| <div id="planetType">類地行星</div> |
| </div> |
| <div> |
| <div class="text-gray-400">直徑</div> |
| <div id="planetDiameter">12,742 km</div> |
| </div> |
| <div> |
| <div class="text-gray-400">質量</div> |
| <div id="planetMass">5.97 × 10²⁴ kg</div> |
| </div> |
| <div> |
| <div class="text-gray-400">重力</div> |
| <div id="planetGravity">9.8 m/s²</div> |
| </div> |
| <div> |
| <div class="text-gray-400">軌道周期</div> |
| <div id="planetOrbitPeriod">365.25 天</div> |
| </div> |
| <div> |
| <div class="text-gray-400">自轉周期</div> |
| <div id="planetRotationPeriod">24 小時</div> |
| </div> |
| <div class="col-span-2"> |
| <div class="text-gray-400">平均溫度</div> |
| <div id="planetTemperature">15°C</div> |
| </div> |
| <div class="col-span-2"> |
| <div class="text-gray-400">衛星數量</div> |
| <div id="planetMoons">1 (月球)</div> |
| </div> |
| </div> |
| <button id="focusPlanetBtn" class="mt-3 w-full py-2 bg-blue-600 hover:bg-blue-700 rounded transition"> |
| <i class="fas fa-crosshairs mr-2"></i>聚焦此行星 |
| </button> |
| </div> |
| |
| |
| <div class="panel absolute bottom-4 left-4"> |
| <div class="text-sm text-gray-400">模擬日期</div> |
| <div id="simulationDate" class="text-xl">2023年1月1日</div> |
| <div id="simulationTime" class="text-sm">00:00:00</div> |
| </div> |
| |
| |
| <div class="panel absolute top-1/2 right-4 transform -translate-y-1/2"> |
| <div class="flex flex-col space-y-2"> |
| <button data-planet="sun" class="control-btn bg-yellow-500 hover:bg-yellow-600" title="太陽"> |
| <i class="fas fa-sun"></i> |
| </button> |
| <button data-planet="mercury" class="control-btn bg-gray-400 hover:bg-gray-500" title="水星"> |
| <i class="fas fa-circle"></i> |
| </button> |
| <button data-planet="venus" class="control-btn bg-yellow-200 hover:bg-yellow-300" title="金星"> |
| <i class="fas fa-circle"></i> |
| </button> |
| <button data-planet="earth" class="control-btn bg-blue-500 hover:bg-blue-600" title="地球"> |
| <i class="fas fa-circle"></i> |
| </button> |
| <button data-planet="mars" class="control-btn bg-red-500 hover:bg-red-600" title="火星"> |
| <i class="fas fa-circle"></i> |
| </button> |
| <button data-planet="jupiter" class="control-btn bg-yellow-700 hover:bg-yellow-800" title="木星"> |
| <i class="fas fa-circle"></i> |
| </button> |
| <button data-planet="saturn" class="control-btn bg-yellow-300 hover:bg-yellow-400" title="土星"> |
| <i class="fas fa-circle"></i> |
| </button> |
| <button data-planet="uranus" class="control-btn bg-teal-300 hover:bg-teal-400" title="天王星"> |
| <i class="fas fa-circle"></i> |
| </button> |
| <button data-planet="neptune" class="control-btn bg-blue-400 hover:bg-blue-500" title="海王星"> |
| <i class="fas fa-circle"></i> |
| </button> |
| <button data-planet="pluto" class="control-btn bg-gray-600 hover:bg-gray-700" title="冥王星"> |
| <i class="fas fa-circle"></i> |
| </button> |
| </div> |
| </div> |
| |
| |
| <div id="planetLabels"></div> |
| |
| |
| <div id="tooltip" class="tooltip"></div> |
| </div> |
| |
| <script> |
| |
| let progress = 0; |
| const loadingInterval = setInterval(() => { |
| progress += Math.random() * 10; |
| if (progress > 100) progress = 100; |
| document.getElementById('loadingProgress').style.width = `${progress}%`; |
| |
| if (progress === 100) { |
| clearInterval(loadingInterval); |
| setTimeout(() => { |
| document.querySelector('.loading-screen').style.opacity = '0'; |
| setTimeout(() => { |
| document.querySelector('.loading-screen').style.display = 'none'; |
| }, 1000); |
| }, 500); |
| } |
| }, 100); |
| |
| |
| const scene = new THREE.Scene(); |
| scene.background = new THREE.Color(0x000000); |
| |
| |
| const starsGeometry = new THREE.BufferGeometry(); |
| const starsMaterial = new THREE.PointsMaterial({ |
| color: 0xffffff, |
| size: 0.05, |
| transparent: true, |
| opacity: 0.8 |
| }); |
| |
| const starsVertices = []; |
| for (let i = 0; i < 5000; i++) { |
| const x = (Math.random() - 0.5) * 2000; |
| const y = (Math.random() - 0.5) * 2000; |
| const z = (Math.random() - 0.5) * 2000; |
| starsVertices.push(x, y, z); |
| } |
| |
| starsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starsVertices, 3)); |
| const stars = new THREE.Points(starsGeometry, starsMaterial); |
| scene.add(stars); |
| |
| |
| const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
| camera.position.set(0, 50, 100); |
| |
| |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| renderer.shadowMap.enabled = true; |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; |
| document.getElementById('container').appendChild(renderer.domElement); |
| |
| |
| const sunLight = new THREE.PointLight(0xffffff, 1.5, 500); |
| sunLight.castShadow = true; |
| sunLight.shadow.mapSize.width = 2048; |
| sunLight.shadow.mapSize.height = 2048; |
| sunLight.shadow.bias = -0.0001; |
| scene.add(sunLight); |
| |
| const ambientLight = new THREE.AmbientLight(0x404040); |
| scene.add(ambientLight); |
| |
| |
| const orbitHelpers = new THREE.Group(); |
| scene.add(orbitHelpers); |
| |
| |
| const planetsData = { |
| sun: { |
| name: "太陽", |
| type: "恆星", |
| radius: 6.96, |
| distance: 0, |
| orbitSpeed: 0, |
| rotationSpeed: 0.002, |
| color: 0xFDB813, |
| tilt: 7.25, |
| info: { |
| diameter: "1,392,700 km", |
| mass: "1.989 × 10³⁰ kg", |
| gravity: "274 m/s²", |
| orbitPeriod: "N/A", |
| rotationPeriod: "25.05 天", |
| temperature: "5,500°C", |
| moons: "0" |
| } |
| }, |
| mercury: { |
| name: "水星", |
| type: "類地行星", |
| radius: 0.244, |
| distance: 15, |
| orbitSpeed: 0.04, |
| rotationSpeed: 0.004, |
| color: 0xA9A9A9, |
| tilt: 0.034, |
| info: { |
| diameter: "4,880 km", |
| mass: "3.30 × 10²³ kg", |
| gravity: "3.7 m/s²", |
| orbitPeriod: "88 天", |
| rotationPeriod: "58.6 天", |
| temperature: "167°C", |
| moons: "0" |
| } |
| }, |
| venus: { |
| name: "金星", |
| type: "類地行星", |
| radius: 0.605, |
| distance: 28, |
| orbitSpeed: 0.015, |
| rotationSpeed: -0.002, |
| color: 0xE6E6FA, |
| tilt: 177.4, |
| info: { |
| diameter: "12,104 km", |
| mass: "4.87 × 10²⁴ kg", |
| gravity: "8.87 m/s²", |
| orbitPeriod: "225 天", |
| rotationPeriod: "243 天", |
| temperature: "464°C", |
| moons: "0" |
| } |
| }, |
| earth: { |
| name: "地球", |
| type: "類地行星", |
| radius: 0.637, |
| distance: 39, |
| orbitSpeed: 0.01, |
| rotationSpeed: 0.02, |
| color: 0x1E90FF, |
| tilt: 23.44, |
| info: { |
| diameter: "12,742 km", |
| mass: "5.97 × 10²⁴ kg", |
| gravity: "9.8 m/s²", |
| orbitPeriod: "365.25 天", |
| rotationPeriod: "24 小時", |
| temperature: "15°C", |
| moons: "1 (月球)" |
| } |
| }, |
| mars: { |
| name: "火星", |
| type: "類地行星", |
| radius: 0.339, |
| distance: 59, |
| orbitSpeed: 0.008, |
| rotationSpeed: 0.018, |
| color: 0xC1440E, |
| tilt: 25.19, |
| info: { |
| diameter: "6,779 km", |
| mass: "6.39 × 10²³ kg", |
| gravity: "3.71 m/s²", |
| orbitPeriod: "687 天", |
| rotationPeriod: "24.6 小時", |
| temperature: "-63°C", |
| moons: "2 (火衛一、火衛二)" |
| } |
| }, |
| jupiter: { |
| name: "木星", |
| type: "氣態巨行星", |
| radius: 6.99, |
| distance: 100, |
| orbitSpeed: 0.002, |
| rotationSpeed: 0.04, |
| color: 0xDAA520, |
| tilt: 3.13, |
| info: { |
| diameter: "139,820 km", |
| mass: "1.90 × 10²⁷ kg", |
| gravity: "24.79 m/s²", |
| orbitPeriod: "4,333 天", |
| rotationPeriod: "9.9 小時", |
| temperature: "-108°C", |
| moons: "79" |
| } |
| }, |
| saturn: { |
| name: "土星", |
| type: "氣態巨行星", |
| radius: 5.82, |
| distance: 183, |
| orbitSpeed: 0.0009, |
| rotationSpeed: 0.038, |
| color: 0xF5DEB3, |
| tilt: 26.73, |
| info: { |
| diameter: "116,460 km", |
| mass: "5.68 × 10²⁶ kg", |
| gravity: "10.44 m/s²", |
| orbitPeriod: "10,759 天", |
| rotationPeriod: "10.7 小時", |
| temperature: "-139°C", |
| moons: "82" |
| } |
| }, |
| uranus: { |
| name: "天王星", |
| type: "冰巨星", |
| radius: 2.54, |
| distance: 263, |
| orbitSpeed: 0.0004, |
| rotationSpeed: 0.03, |
| color: 0xAFEEEE, |
| tilt: 97.77, |
| info: { |
| diameter: "50,724 km", |
| mass: "8.68 × 10²⁵ kg", |
| gravity: "8.69 m/s²", |
| orbitPeriod: "30,687 天", |
| rotationPeriod: "17.2 小時", |
| temperature: "-197°C", |
| moons: "27" |
| } |
| }, |
| neptune: { |
| name: "海王星", |
| type: "冰巨星", |
| radius: 2.46, |
| distance: 320, |
| orbitSpeed: 0.0001, |
| rotationSpeed: 0.032, |
| color: 0x4169E1, |
| tilt: 28.32, |
| info: { |
| diameter: "49,244 km", |
| mass: "1.02 × 10²⁶ kg", |
| gravity: "11.15 m/s²", |
| orbitPeriod: "60,190 天", |
| rotationPeriod: "16.1 小時", |
| temperature: "-201°C", |
| moons: "14" |
| } |
| }, |
| pluto: { |
| name: "冥王星", |
| type: "矮行星", |
| radius: 0.118, |
| distance: 395, |
| orbitSpeed: 0.00006, |
| rotationSpeed: 0.01, |
| color: 0x808080, |
| tilt: 122.5, |
| info: { |
| diameter: "2,377 km", |
| mass: "1.30 × 10²² kg", |
| gravity: "0.62 m/s²", |
| orbitPeriod: "90,560 天", |
| rotationPeriod: "6.4 天", |
| temperature: "-225°C", |
| moons: "5" |
| } |
| } |
| }; |
| |
| |
| const planets = {}; |
| const planetMeshes = {}; |
| const planetLabels = {}; |
| let moon = null; |
| |
| Object.keys(planetsData).forEach(planetName => { |
| const planetData = planetsData[planetName]; |
| const geometry = new THREE.SphereGeometry(planetData.radius, 64, 64); |
| |
| |
| let material; |
| if (planetName === 'sun') { |
| |
| material = new THREE.MeshBasicMaterial({ |
| color: planetData.color, |
| emissive: planetData.color, |
| emissiveIntensity: 1 |
| }); |
| } else { |
| material = new THREE.MeshPhongMaterial({ |
| color: planetData.color, |
| shininess: planetName === 'earth' ? 5 : 10, |
| specular: 0x111111 |
| }); |
| } |
| |
| const planet = new THREE.Mesh(geometry, material); |
| planet.castShadow = planetName !== 'sun'; |
| planet.receiveShadow = true; |
| planet.userData.name = planetName; |
| |
| |
| planet.rotation.z = planetData.tilt * Math.PI / 180; |
| |
| if (planetName === 'sun') { |
| planet.position.set(0, 0, 0); |
| sunLight.position.set(0, 0, 0); |
| } else { |
| planet.position.set(planetData.distance, 0, 0); |
| |
| |
| const orbitGeometry = new THREE.BufferGeometry(); |
| const orbitMaterial = new THREE.LineBasicMaterial({ |
| color: 0xffffff, |
| transparent: true, |
| opacity: 0.3, |
| linewidth: 1 |
| }); |
| |
| const points = []; |
| const segments = 256; |
| for (let i = 0; i <= segments; i++) { |
| const theta = (i / segments) * Math.PI * 2; |
| points.push(new THREE.Vector3( |
| Math.cos(theta) * planetData.distance, |
| 0, |
| Math.sin(theta) * planetData.distance |
| )); |
| } |
| |
| orbitGeometry.setFromPoints(points); |
| const orbit = new THREE.Line(orbitGeometry, orbitMaterial); |
| orbit.userData.name = planetName; |
| orbitHelpers.add(orbit); |
| |
| |
| const label = document.createElement('div'); |
| label.className = 'planet-label'; |
| label.textContent = planetData.name; |
| label.dataset.planet = planetName; |
| document.getElementById('planetLabels').appendChild(label); |
| planetLabels[planetName] = label; |
| } |
| |
| scene.add(planet); |
| planets[planetName] = planet; |
| planetMeshes[planetName] = planet; |
| |
| |
| if (planetName === 'saturn') { |
| const ringGeometry = new THREE.RingGeometry(planetData.radius * 1.5, planetData.radius * 2.3, 64); |
| const ringMaterial = new THREE.MeshPhongMaterial({ |
| color: 0xD2B48C, |
| side: THREE.DoubleSide, |
| transparent: true, |
| opacity: 0.8, |
| shininess: 30 |
| }); |
| const ring = new THREE.Mesh(ringGeometry, ringMaterial); |
| ring.rotation.x = Math.PI / 2; |
| ring.rotation.z = Math.PI / 4; |
| planet.add(ring); |
| } |
| }); |
| |
| |
| function createMoon() { |
| const moonGeometry = new THREE.SphereGeometry(0.1737, 32, 32); |
| const moonMaterial = new THREE.MeshPhongMaterial({ |
| color: 0xDDDDDD, |
| shininess: 5 |
| }); |
| |
| moon = new THREE.Mesh(moonGeometry, moonMaterial); |
| moon.castShadow = true; |
| moon.receiveShadow = true; |
| moon.position.set(5, 0, 0); |
| planets.earth.add(moon); |
| } |
| |
| createMoon(); |
| |
| |
| let targetPosition = new THREE.Vector3(0, 50, 100); |
| let currentPosition = new THREE.Vector3().copy(targetPosition); |
| let targetLookAt = new THREE.Vector3(0, 0, 0); |
| let currentLookAt = new THREE.Vector3().copy(targetLookAt); |
| |
| |
| let simulationSpeed = 1; |
| let isPlaying = true; |
| let simulationDate = new Date(2023, 0, 1); |
| |
| |
| function updateDateDisplay() { |
| const options = { year: 'numeric', month: 'long', day: 'numeric' }; |
| document.getElementById('simulationDate').textContent = simulationDate.toLocaleDateString('zh-TW', options); |
| document.getElementById('simulationTime').textContent = simulationDate.toLocaleTimeString('zh-TW'); |
| } |
| |
| |
| function showPlanetInfo(planetName) { |
| const planetData = planetsData[planetName]; |
| const planetInfo = document.querySelector('.planet-info'); |
| |
| document.getElementById('planetName').textContent = planetData.name; |
| document.getElementById('planetType').textContent = planetData.type; |
| document.getElementById('planetDiameter').textContent = planetData.info.diameter; |
| document.getElementById('planetMass').textContent = planetData.info.mass; |
| document.getElementById('planetGravity').textContent = planetData.info.gravity; |
| document.getElementById('planetOrbitPeriod').textContent = planetData.info.orbitPeriod; |
| document.getElementById('planetRotationPeriod').textContent = planetData.info.rotationPeriod; |
| document.getElementById('planetTemperature').textContent = planetData.info.temperature; |
| document.getElementById('planetMoons').textContent = planetData.info.moons; |
| |
| document.getElementById('planetIcon').style.backgroundColor = planetName === 'sun' ? |
| '#FDB813' : `#${planetData.color.toString(16)}`; |
| |
| planetInfo.classList.add('active'); |
| } |
| |
| |
| function focusPlanet(planetName) { |
| const planet = planets[planetName]; |
| const planetData = planetsData[planetName]; |
| const distance = planetName === 'sun' ? |
| 30 : |
| planetData.distance * 0.7 + planetData.radius * 2; |
| |
| targetPosition.set( |
| planet.position.x, |
| planet.position.y + distance * 0.3, |
| planet.position.z + distance |
| ); |
| |
| targetLookAt.copy(planet.position); |
| |
| showPlanetInfo(planetName); |
| } |
| |
| |
| function updateLabels() { |
| Object.keys(planetLabels).forEach(planetName => { |
| const planet = planets[planetName]; |
| const label = planetLabels[planetName]; |
| |
| if (!label || !planet) return; |
| |
| |
| const vector = new THREE.Vector3(); |
| vector.setFromMatrixPosition(planet.matrixWorld); |
| vector.project(camera); |
| |
| const x = (vector.x * 0.5 + 0.5) * window.innerWidth; |
| const y = (vector.y * -0.5 + 0.5) * window.innerHeight; |
| |
| label.style.left = `${x}px`; |
| label.style.top = `${y}px`; |
| |
| |
| const distance = camera.position.distanceTo(planet.position); |
| const scale = Math.min(1, 100 / distance); |
| label.style.transform = `translate(-50%, 0) scale(${scale})`; |
| }); |
| } |
| |
| |
| document.getElementById('playPauseBtn').addEventListener('click', () => { |
| isPlaying = !isPlaying; |
| document.getElementById('playPauseBtn').innerHTML = isPlaying ? |
| '<i class="fas fa-pause"></i>' : '<i class="fas fa-play"></i>'; |
| }); |
| |
| document.getElementById('speedUpBtn').addEventListener('click', () => { |
| simulationSpeed = Math.min(simulationSpeed + 0.5, 10); |
| updateSpeedDisplay(); |
| }); |
| |
| document.getElementById('speedDownBtn').addEventListener('click', () => { |
| simulationSpeed = Math.max(simulationSpeed - 0.5, 0.1); |
| updateSpeedDisplay(); |
| }); |
| |
| document.getElementById('resetBtn').addEventListener('click', () => { |
| simulationSpeed = 1; |
| updateSpeedDisplay(); |
| simulationDate = new Date(2023, 0, 1); |
| updateDateDisplay(); |
| focusPlanet('sun'); |
| }); |
| |
| document.getElementById('speedSlider').addEventListener('input', (e) => { |
| simulationSpeed = parseFloat(e.target.value); |
| updateSpeedDisplay(); |
| }); |
| |
| document.getElementById('distanceSlider').addEventListener('input', (e) => { |
| const value = parseInt(e.target.value); |
| document.getElementById('distanceValue').textContent = `${value}%`; |
| |
| |
| const baseDistance = 100; |
| const newDistance = baseDistance * (value / 100); |
| targetPosition.z = newDistance; |
| }); |
| |
| document.getElementById('showOrbits').addEventListener('change', (e) => { |
| orbitHelpers.visible = e.target.checked; |
| }); |
| |
| document.getElementById('showLabels').addEventListener('change', (e) => { |
| const labels = document.querySelectorAll('.planet-label'); |
| labels.forEach(label => { |
| if (e.target.checked) { |
| label.classList.add('active'); |
| } else { |
| label.classList.remove('active'); |
| } |
| }); |
| }); |
| |
| document.getElementById('showMoon').addEventListener('change', (e) => { |
| if (moon) { |
| moon.visible = e.target.checked; |
| } |
| }); |
| |
| document.getElementById('focusPlanetBtn').addEventListener('click', () => { |
| const activePlanet = document.querySelector('.planet-info.active'); |
| if (activePlanet) { |
| const planetName = document.getElementById('planetName').textContent.toLowerCase(); |
| focusPlanet(planetName); |
| } |
| }); |
| |
| |
| document.querySelectorAll('[data-planet]').forEach(btn => { |
| btn.addEventListener('click', () => { |
| const planetName = btn.getAttribute('data-planet'); |
| focusPlanet(planetName); |
| }); |
| |
| |
| btn.addEventListener('mouseenter', () => { |
| const tooltip = document.getElementById('tooltip'); |
| tooltip.textContent = btn.getAttribute('title'); |
| tooltip.style.left = `${btn.offsetLeft + btn.offsetWidth / 2}px`; |
| tooltip.style.top = `${btn.offsetTop - 30}px`; |
| tooltip.style.opacity = '1'; |
| }); |
| |
| btn.addEventListener('mouseleave', () => { |
| document.getElementById('tooltip').style.opacity = '0'; |
| }); |
| }); |
| |
| |
| function updateSpeedDisplay() { |
| document.getElementById('speedValue').textContent = `${simulationSpeed.toFixed(1)}x`; |
| document.getElementById('speedSlider').value = simulationSpeed; |
| } |
| |
| |
| window.addEventListener('resize', () => { |
| camera.aspect = window.innerWidth / window.innerHeight; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| }); |
| |
| |
| let mouseX = 0; |
| let mouseY = 0; |
| let isMouseDown = false; |
| |
| window.addEventListener('mousedown', () => { |
| isMouseDown = true; |
| }); |
| |
| window.addEventListener('mouseup', () => { |
| isMouseDown = false; |
| }); |
| |
| window.addEventListener('mousemove', (e) => { |
| mouseX = (e.clientX / window.innerWidth) * 2 - 1; |
| mouseY = -(e.clientY / window.innerHeight) * 2 + 1; |
| |
| if (isMouseDown) { |
| targetPosition.x -= mouseX * 2; |
| targetPosition.y -= mouseY * 2; |
| targetLookAt.x -= mouseX * 2; |
| targetLookAt.y -= mouseY * 2; |
| } |
| }); |
| |
| |
| window.addEventListener('wheel', (e) => { |
| e.preventDefault(); |
| targetPosition.z += e.deltaY * 0.1; |
| }); |
| |
| |
| window.addEventListener('click', (e) => { |
| if (!isMouseDown) { |
| const mouse = new THREE.Vector2( |
| (e.clientX / window.innerWidth) * 2 - 1, |
| -(e.clientY / window.innerHeight) * 2 + 1 |
| ); |
| |
| const raycaster = new THREE.Raycaster(); |
| raycaster.setFromCamera(mouse, camera); |
| |
| const intersects = raycaster.intersectObjects(Object.values(planetMeshes)); |
| |
| if (intersects.length > 0) { |
| const planet = intersects[0].object; |
| focusPlanet(planet.userData.name); |
| } |
| } |
| }); |
| |
| |
| function animate() { |
| requestAnimationFrame(animate); |
| |
| |
| currentPosition.lerp(targetPosition, 0.05); |
| currentLookAt.lerp(targetLookAt, 0.05); |
| |
| camera.position.copy(currentPosition); |
| camera.lookAt(currentLookAt); |
| |
| |
| if (isPlaying) { |
| Object.keys(planetsData).forEach(planetName => { |
| const planetData = planetsData[planetName]; |
| const planet = planets[planetName]; |
| |
| |
| planet.rotation.y += planetData.rotationSpeed * simulationSpeed; |
| |
| |
| if (planetName !== 'sun') { |
| planet.position.x = Math.cos(Date.now() * 0.0001 * planetData.orbitSpeed * simulationSpeed) * planetData.distance; |
| planet.position.z = Math.sin(Date.now() * 0.0001 * planetData.orbitSpeed * simulationSpeed) * planetData.distance; |
| } |
| }); |
| |
| |
| if (moon) { |
| moon.position.x = Math.cos(Date.now() * 0.0005 * simulationSpeed) * 5; |
| moon.position.z = Math.sin(Date.now() * 0.0005 * simulationSpeed) * 5; |
| moon.rotation.y += 0.001 * simulationSpeed; |
| } |
| |
| |
| simulationDate = new Date(simulationDate.getTime() + 3600000 * simulationSpeed); |
| updateDateDisplay(); |
| } |
| |
| |
| updateLabels(); |
| |
| renderer.render(scene, camera); |
| } |
| |
| |
| showPlanetInfo('sun'); |
| updateSpeedDisplay(); |
| updateDateDisplay(); |
| |
| |
| animate(); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=engerl/sun9" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> |
| </html> |