release: bump version to 0.27.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
rayd1o
2026-04-14 07:37:45 +08:00
parent 2ee4773f4f
commit 7cd29cf9c0
24 changed files with 1565 additions and 630 deletions

View File

@@ -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);
});
}