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