// earth.js - 3D Earth creation module import * as THREE from 'three'; import { CONFIG, EARTH_CONFIG } from './constants.js'; import { latLonToVector3 } from './utils.js'; export let earth = null; export let clouds = null; export let terrain = null; const textureLoader = new THREE.TextureLoader(); export function createEarth(scene) { const geometry = new THREE.SphereGeometry(CONFIG.earthRadius, 128, 128); const material = new THREE.MeshPhongMaterial({ color: 0xffffff, specular: 0x111111, shininess: 10, emissive: 0x000000, transparent: true, opacity: 0.8, side: THREE.DoubleSide }); earth = new THREE.Mesh(geometry, material); earth.rotation.x = EARTH_CONFIG.tiltRad; scene.add(earth); const textureUrls = [ './assets/8k_earth_daymap.jpg', 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/planets/earth_atmos_2048.jpg', 'https://threejs.org/examples/textures/planets/earth_atmos_2048.jpg', 'https://assets.codepen.io/982762/earth_texture_2048.jpg' ]; let textureLoaded = false; textureLoader.load( textureUrls[0], function(texture) { console.log('高分辨率地球纹理加载成功'); textureLoaded = true; texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.ClampToEdgeWrapping; texture.anisotropy = 16; texture.minFilter = THREE.LinearMipmapLinearFilter; texture.magFilter = THREE.LinearFilter; material.map = texture; material.needsUpdate = true; document.getElementById('loading').style.display = 'none'; }, function(xhr) { console.log('纹理加载中: ' + (xhr.loaded / xhr.total * 100) + '%'); }, function(err) { console.log('第一个纹理加载失败,尝试第二个...'); textureLoader.load( textureUrls[1], function(texture) { console.log('第二个纹理加载成功'); textureLoaded = true; texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.ClampToEdgeWrapping; texture.anisotropy = 16; texture.minFilter = THREE.LinearMipmapLinearFilter; texture.magFilter = THREE.LinearFilter; material.map = texture; material.needsUpdate = true; document.getElementById('loading').style.display = 'none'; }, null, function(err) { console.log('所有纹理加载失败'); document.getElementById('loading').style.display = 'none'; } ); } ); return earth; } export function createClouds(scene, earthObj) { const geometry = new THREE.SphereGeometry(CONFIG.earthRadius + 3, 64, 64); const material = new THREE.MeshPhongMaterial({ transparent: true, linewidth: 2, opacity: 0.15, depthTest: true, depthWrite: false, blending: THREE.AdditiveBlending, side: THREE.DoubleSide }); clouds = new THREE.Mesh(geometry, material); earthObj.add(clouds); textureLoader.load( 'https://threejs.org/examples/textures/planets/earth_clouds_1024.png', function(texture) { material.map = texture; material.needsUpdate = true; }, undefined, function(err) { console.log('云层纹理加载失败'); } ); return clouds; } export function createTerrain(scene, earthObj, simplex) { const geometry = new THREE.SphereGeometry(CONFIG.earthRadius, 128, 128); const positionAttribute = geometry.getAttribute('position'); for (let i = 0; i < positionAttribute.count; i++) { const x = positionAttribute.getX(i); const y = positionAttribute.getY(i); const z = positionAttribute.getZ(i); const noise = simplex(x / 20, y / 20, z / 20); const height = 1 + noise * 0.02; positionAttribute.setXYZ(i, x * height, y * height, z * height); } geometry.computeVertexNormals(); const material = new THREE.MeshPhongMaterial({ color: 0x00aa00, flatShading: true, transparent: true, opacity: 0.7 }); terrain = new THREE.Mesh(geometry, material); terrain.visible = false; earthObj.add(terrain); return terrain; } export function toggleTerrain(visible) { if (terrain) { terrain.visible = visible; } } export function createStars(scene) { const starGeometry = new THREE.BufferGeometry(); const starCount = 8000; const starPositions = new Float32Array(starCount * 3); for (let i = 0; i < starCount * 3; i += 3) { const r = 800 + Math.random() * 200; const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); starPositions[i] = r * Math.sin(phi) * Math.cos(theta); starPositions[i + 1] = r * Math.sin(phi) * Math.sin(theta); starPositions[i + 2] = r * Math.cos(phi); } starGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3)); const starMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 0.5, transparent: true, blending: THREE.AdditiveBlending }); const stars = new THREE.Points(starGeometry, starMaterial); scene.add(stars); return stars; } let latitudeLines = []; let longitudeLines = []; export function createGridLines(scene, earthObj) { latitudeLines.forEach(line => scene.remove(line)); longitudeLines.forEach(line => scene.remove(line)); latitudeLines = []; longitudeLines = []; const earthRadius = 100.1; const gridMaterial = new THREE.LineBasicMaterial({ color: 0x44aaff, transparent: true, opacity: 0.2, linewidth: 1 }); for (let lat = -75; lat <= 75; lat += 15) { const points = []; for (let lon = -180; lon <= 180; lon += 5) { const point = latLonToVector3(lat, lon, earthRadius); points.push(point); } const geometry = new THREE.BufferGeometry().setFromPoints(points); const line = new THREE.Line(geometry, gridMaterial); line.userData = { type: 'latitude', value: lat }; earthObj.add(line); latitudeLines.push(line); } for (let lon = -180; lon <= 180; lon += 30) { const points = []; for (let lat = -90; lat <= 90; lat += 5) { const point = latLonToVector3(lat, lon, earthRadius); points.push(point); } const geometry = new THREE.BufferGeometry().setFromPoints(points); const line = new THREE.Line(geometry, gridMaterial); line.userData = { type: 'longitude', value: lon }; earthObj.add(line); longitudeLines.push(line); } } export function getEarth() { return earth; } export function getClouds() { return clouds; }