Files
planet/frontend/public/earth/js/earth.js
rayd1o c2eba54da0 refactor: 整理资源文件,添加legacy路由
- 将原版文件移到frontend/legacy/3dearthmult/
- 纹理文件移到frontend/public/earth/assets/
- vite.config添加/legacy/earth路由支持
- earth.js纹理路径改为assets/
2026-03-19 11:10:33 +08:00

241 lines
6.5 KiB
JavaScript

// earth.js - 3D Earth creation module
import * as THREE from 'three';
import { 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 = 23.5 * Math.PI / 180;
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;
}