release: bump version to 0.27.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// earth.js - 3D Earth creation module
|
||||
|
||||
import * as THREE from 'three';
|
||||
import { CONFIG, EARTH_CONFIG } from './constants.js';
|
||||
import { CONFIG, EARTH_CONFIG, EARTH_MATERIAL_CONFIG } from './constants.js';
|
||||
import { latLonToVector3 } from './utils.js';
|
||||
|
||||
export let earth = null;
|
||||
@@ -9,82 +9,108 @@ export let clouds = null;
|
||||
export let terrain = null;
|
||||
|
||||
const textureLoader = new THREE.TextureLoader();
|
||||
let _earthMaterial = null;
|
||||
|
||||
export function createEarth(scene) {
|
||||
const geometry = new THREE.SphereGeometry(CONFIG.earthRadius, 128, 128);
|
||||
|
||||
|
||||
const C = EARTH_MATERIAL_CONFIG;
|
||||
|
||||
const material = new THREE.MeshPhongMaterial({
|
||||
color: 0xffffff,
|
||||
specular: 0x111111,
|
||||
shininess: 10,
|
||||
emissive: 0x000000,
|
||||
color: C.color,
|
||||
specular: C.specular,
|
||||
shininess: C.shininess,
|
||||
emissive: C.emissive,
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
side: THREE.DoubleSide
|
||||
opacity: C.opacity,
|
||||
side: THREE.FrontSide,
|
||||
depthWrite: true,
|
||||
depthTest: true,
|
||||
});
|
||||
|
||||
_earthMaterial = material;
|
||||
|
||||
earth = new THREE.Mesh(geometry, material);
|
||||
earth.renderOrder = 0;
|
||||
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';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Depth-mask occluder — invisible sphere slightly inside the earth,
|
||||
// writes to the depth buffer so far-side cables/satellites are occluded.
|
||||
const occluderGeometry = new THREE.SphereGeometry(
|
||||
CONFIG.earthRadius * C.occluderRadiusFactor,
|
||||
C.occluderSegments,
|
||||
C.occluderSegments,
|
||||
);
|
||||
|
||||
const occluderMaterial = new THREE.MeshBasicMaterial({
|
||||
colorWrite: false,
|
||||
side: THREE.FrontSide,
|
||||
});
|
||||
const occluder = new THREE.Mesh(occluderGeometry, occluderMaterial);
|
||||
occluder.renderOrder = -1;
|
||||
earth.add(occluder);
|
||||
|
||||
// Shared Fresnel vertex shader for both atmosphere layers
|
||||
const ATMOS_VERTEX_SHADER = `
|
||||
varying vec3 vNormal;
|
||||
void main() {
|
||||
vNormal = normalize(normalMatrix * normal);
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
// Fresnel atmosphere — inner rim
|
||||
const [ir, ig, ib] = C.atmosInnerColor;
|
||||
const atmosInnerGeo = new THREE.SphereGeometry(
|
||||
CONFIG.earthRadius * C.atmosInnerRadiusFactor,
|
||||
C.atmosInnerSegments,
|
||||
C.atmosInnerSegments,
|
||||
);
|
||||
const atmosInnerMat = new THREE.ShaderMaterial({
|
||||
vertexShader: ATMOS_VERTEX_SHADER,
|
||||
fragmentShader: `
|
||||
varying vec3 vNormal;
|
||||
void main() {
|
||||
float rim = 1.0 - abs(dot(vNormal, vec3(0.0, 0.0, 1.0)));
|
||||
float intensity = pow(rim, ${C.atmosInnerRimPower.toFixed(1)});
|
||||
gl_FragColor = vec4(${ir.toFixed(2)}, ${ig.toFixed(2)}, ${ib.toFixed(2)}, intensity * ${C.atmosInnerIntensity.toFixed(2)});
|
||||
}
|
||||
`,
|
||||
blending: THREE.AdditiveBlending,
|
||||
side: THREE.BackSide,
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
});
|
||||
const atmosInner = new THREE.Mesh(atmosInnerGeo, atmosInnerMat);
|
||||
atmosInner.renderOrder = 1;
|
||||
earth.add(atmosInner);
|
||||
|
||||
// Fresnel atmosphere — outer corona
|
||||
const [outerR, outerG, outerB] = C.atmosOuterColor;
|
||||
const atmosOuterGeo = new THREE.SphereGeometry(
|
||||
CONFIG.earthRadius * C.atmosOuterRadiusFactor,
|
||||
C.atmosOuterSegments,
|
||||
C.atmosOuterSegments,
|
||||
);
|
||||
const atmosOuterMat = new THREE.ShaderMaterial({
|
||||
vertexShader: ATMOS_VERTEX_SHADER,
|
||||
fragmentShader: `
|
||||
varying vec3 vNormal;
|
||||
void main() {
|
||||
float rim = 1.0 - abs(dot(vNormal, vec3(0.0, 0.0, 1.0)));
|
||||
float intensity = pow(rim, ${C.atmosOuterRimPower.toFixed(1)});
|
||||
gl_FragColor = vec4(${outerR.toFixed(2)}, ${outerG.toFixed(2)}, ${outerB.toFixed(2)}, intensity * ${C.atmosOuterIntensity.toFixed(2)});
|
||||
}
|
||||
`,
|
||||
blending: THREE.AdditiveBlending,
|
||||
side: THREE.BackSide,
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
});
|
||||
const atmosOuter = new THREE.Mesh(atmosOuterGeo, atmosOuterMat);
|
||||
atmosOuter.renderOrder = 1;
|
||||
earth.add(atmosOuter);
|
||||
|
||||
// Texture is loaded separately via loadEarthTexture() for staged loading
|
||||
return earth;
|
||||
}
|
||||
|
||||
@@ -238,3 +264,40 @@ export function getEarth() {
|
||||
export function getClouds() {
|
||||
return clouds;
|
||||
}
|
||||
|
||||
export function clearEarthTexture() {
|
||||
if (!_earthMaterial) return;
|
||||
_earthMaterial.map = null;
|
||||
_earthMaterial.needsUpdate = true;
|
||||
}
|
||||
|
||||
export function loadEarthTexture() {
|
||||
return new Promise((resolve) => {
|
||||
if (!_earthMaterial) { resolve(); return; }
|
||||
|
||||
const urls = EARTH_MATERIAL_CONFIG.textureUrls;
|
||||
const tryLoad = (index) => {
|
||||
if (index >= urls.length) {
|
||||
console.warn('所有地球纹理加载失败');
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
textureLoader.load(
|
||||
urls[index],
|
||||
(texture) => {
|
||||
texture.wrapS = THREE.RepeatWrapping;
|
||||
texture.wrapT = THREE.ClampToEdgeWrapping;
|
||||
texture.anisotropy = 16;
|
||||
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
||||
texture.magFilter = THREE.LinearFilter;
|
||||
_earthMaterial.map = texture;
|
||||
_earthMaterial.needsUpdate = true;
|
||||
resolve();
|
||||
},
|
||||
null,
|
||||
() => tryLoad(index + 1),
|
||||
);
|
||||
};
|
||||
tryLoad(0);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user