release: bump version to 0.42.0

This commit is contained in:
rayd1o
2026-04-28 04:27:18 +08:00
parent eeee788530
commit b4e8afb272
53 changed files with 6863 additions and 87 deletions

View File

@@ -725,8 +725,8 @@
<span class="earth-mobile-settings-subtitle">可选自身发光或真实地表覆盖两种选中表现</span>
</div>
<div class="earth-mobile-settings-segmented" role="group" aria-label="移动端选择卫星显示风格">
<button type="button" class="earth-mobile-settings-pill is-active" data-satellite-display-style="self_glow" aria-pressed="true">自身发光</button>
<button type="button" class="earth-mobile-settings-pill" data-satellite-display-style="ground_footprint" aria-pressed="false">真实地表覆盖</button>
<button type="button" class="earth-mobile-settings-pill" data-satellite-display-style="self_glow" aria-pressed="false">自身发光</button>
<button type="button" class="earth-mobile-settings-pill is-active" data-satellite-display-style="ground_footprint" aria-pressed="true">真实地表覆盖</button>
</div>
</div>
</div>
@@ -926,17 +926,17 @@
<div class="earth-settings-segmented" role="group" aria-label="选择卫星显示风格">
<button
type="button"
class="earth-settings-segmented-btn is-active"
class="earth-settings-segmented-btn"
data-satellite-display-style="self_glow"
aria-pressed="true"
aria-pressed="false"
>
自身发光
</button>
<button
type="button"
class="earth-settings-segmented-btn"
class="earth-settings-segmented-btn is-active"
data-satellite-display-style="ground_footprint"
aria-pressed="false"
aria-pressed="true"
>
真实地表覆盖
</button>

View File

@@ -31,7 +31,7 @@ export const SATELLITE_DISPLAY_STYLES = {
};
export const DEFAULT_SATELLITE_DISPLAY_STYLE =
SATELLITE_DISPLAY_STYLES.SELF_GLOW;
SATELLITE_DISPLAY_STYLES.GROUND_FOOTPRINT;
export const CRUISE_CONFIG = {
dwellMs: 7_000,
@@ -159,11 +159,11 @@ export const TERRAIN_CONFIG = {
exaggeration: 34,
landRevealFadeMeters: 220,
maxConcurrentRequests: 10,
opacity: 0.62,
color: 0x7f9d7f,
emissive: 0x061008,
specular: 0x233126,
shininess: 10,
opacity: 0.68,
color: 0x8aa884,
emissive: 0x030704,
specular: 0x344438,
shininess: 16,
urlTemplate:
"/api/v1/visualization/terrain/terrarium/{z}/{x}/{y}.png",
};
@@ -459,6 +459,8 @@ export const EARTH_MATERIAL_CONFIG = {
textureOverlayAltitudeOffset: 0.1,
textureOverlayOpacity: 0.88,
textureOverlayRenderOrder: 0.96,
textureOverlaySpecular: 0x05080d,
textureOverlayShininess: 4,
// Depth-mask occluder keeps far-side objects hidden behind the earth
occluderRadiusFactor: 0.999,
@@ -472,11 +474,19 @@ export const EARTH_MATERIAL_CONFIG = {
atmosInnerIntensity: 0.18,
// Fresnel atmosphere glow — outer corona
atmosOuterRadiusFactor: 1.016,
atmosOuterRadiusFactor: 1.0025,
atmosOuterSegments: 48,
atmosOuterColor: [0.18, 0.45, 0.9],
atmosOuterRimPower: 5.0,
atmosOuterIntensity: 0.02,
atmosOuterRimPower: 9.0,
atmosOuterIntensity: 0.0025,
// Subtle Fresnel edge cue shown when the high-res texture is hidden or unavailable.
rimGlowColor: [0.42, 0.72, 1.0],
rimGlowRadiusFactor: 1.0035,
rimGlowPower: 3.4,
rimGlowIntensity: 0.24,
rimGlowSegments: 96,
rimGlowRenderOrder: 1.08,
// Texture candidates — tried in order, first success wins
textureUrls: [
@@ -488,12 +498,12 @@ export const EARTH_MATERIAL_CONFIG = {
dayNight: {
enabled: true,
sunDirection: { x: 1, y: 0.2, z: 0.4 },
nightFloor: 0.32,
dayBoost: 0.94,
twilightWidth: 0.24,
nightFloor: 0.24,
dayBoost: 1.12,
twilightWidth: 0.2,
twilightIntensity: 0.14,
twilightColor: 0x4ea0ff,
nightTintColor: 0x0b1830,
nightTintIntensity: 0.05,
nightTintIntensity: 0.08,
},
};

View File

@@ -110,6 +110,10 @@ const SETTINGS_SHEET_MAX_SCALE_X = 0.22;
const SETTINGS_SHEET_MAX_SCALE_Y = 0.18;
const EARTH_SETTINGS_STORAGE_KEY = "planet.earth.settings.v2";
const LEGACY_EARTH_SETTINGS_STORAGE_KEY = "planet.earth.settings.v1";
const EARTH_SETTINGS_VERSION = 5;
const GRID_LINES_DEFAULT_VERSION = 3;
const SATELLITE_DISPLAY_DEFAULT_VERSION = 4;
const MEDIA_PANEL_DEFAULT_VERSION = 5;
const DEFAULT_EARTH_ZOOM_STEP = 0.01;
let settingsModalTimer = null;
let settingsSheetAnimation = null;
@@ -126,6 +130,7 @@ let activeMobileDrawerId = null;
let mobileDrawerOpen = false;
let mobileDrawerCard = "layers";
let mobileDrawerHintTimer = null;
let toolbarHubController = null;
const ALLOWED_CRUISE_MODULES = new Set(Object.values(CRUISE_MODULES));
const ALLOWED_SATELLITE_DISPLAY_STYLES = new Set(
Object.values(SATELLITE_DISPLAY_STYLES),
@@ -722,7 +727,7 @@ function captureEarthSettingsDefaults() {
const panelVisibility = getCurrentPanelVisibilitySnapshot();
const shared = getCurrentSharedSettingsSnapshot();
earthSettingsDefaults = {
version: 2,
version: EARTH_SETTINGS_VERSION,
shared: {
...shared,
layerVisibility: getDefaultLayerVisibilitySnapshot(),
@@ -742,7 +747,7 @@ function captureEarthSettingsDefaults() {
function cloneEarthSettings(settings) {
return {
version: 3,
version: EARTH_SETTINGS_VERSION,
shared: {
rotationMode: settings.shared.rotationMode,
cruiseModules: [...(settings.shared.cruiseModules || DEFAULT_CRUISE_MODULES)],
@@ -815,9 +820,12 @@ function normalizeEarthSettings(rawSettings, defaults) {
}
});
if ((rawSettings?.version || 0) < 3 && inputLayerVisibility.gridLines === true) {
if ((rawSettings?.version || 0) < GRID_LINES_DEFAULT_VERSION && inputLayerVisibility.gridLines === true) {
normalizedLayerVisibility.gridLines = defaults.shared.layerVisibility.gridLines;
}
if ((rawSettings?.version || 0) < MEDIA_PANEL_DEFAULT_VERSION) {
normalizedDesktopPanelVisibility["media-panel"] = true;
}
const nextRotationMode =
sharedSettings?.rotationMode === ROTATION_MODE.CRUISE
@@ -831,11 +839,17 @@ function normalizeEarthSettings(rawSettings, defaults) {
requestedCruiseModules.filter((moduleId) => ALLOWED_CRUISE_MODULES.has(moduleId)),
),
);
const nextSatelliteDisplayStyle = ALLOWED_SATELLITE_DISPLAY_STYLES.has(
let nextSatelliteDisplayStyle = ALLOWED_SATELLITE_DISPLAY_STYLES.has(
sharedSettings?.satelliteDisplayStyle,
)
? sharedSettings.satelliteDisplayStyle
: defaults.shared.satelliteDisplayStyle;
if (
(rawSettings?.version || 0) < SATELLITE_DISPLAY_DEFAULT_VERSION &&
nextSatelliteDisplayStyle === SATELLITE_DISPLAY_STYLES.SELF_GLOW
) {
nextSatelliteDisplayStyle = defaults.shared.satelliteDisplayStyle;
}
const nextTerrainOpacity = Number.parseFloat(sharedSettings?.terrainOpacity);
const nextDayNightEnabled = typeof sharedSettings?.dayNightEnabled === "boolean"
? sharedSettings.dayNightEnabled
@@ -845,7 +859,7 @@ function normalizeEarthSettings(rawSettings, defaults) {
);
return {
version: 3,
version: EARTH_SETTINGS_VERSION,
shared: {
rotationMode: nextRotationMode,
cruiseModules: nextCruiseModules.length > 0
@@ -3051,6 +3065,11 @@ function setupKeyboardControls() {
return;
}
if (toolbarHubController?.isOpen?.()) {
toolbarHubController.close();
return;
}
clearLockedObjectAndInfo();
});
}
@@ -3241,6 +3260,12 @@ function setupToolbarHubCluster() {
scheduleExpandedToolbarBoundsRefresh();
};
const closePinnedToolbar = () => {
hubPinnedOpen = false;
cancelCollapse();
setExpanded(false);
};
const scheduleCollapse = () => {
if (hubPinnedOpen) return;
if (collapseTimer) clearTimeout(collapseTimer);
@@ -3263,6 +3288,9 @@ function setupToolbarHubCluster() {
cancelAnimationFrame(refreshBoundsFrameId);
refreshBoundsFrameId = 0;
}
if (toolbarHubController?.cluster === cluster) {
toolbarHubController = null;
}
});
// Start collapsed — hub acts as the hover target to reveal the arc
@@ -3285,13 +3313,12 @@ function setupToolbarHubCluster() {
event.preventDefault();
event.stopPropagation();
cancelCollapse();
if (isMobileLayout()) {
hubPinnedOpen = !cluster.classList.contains("is-expanded");
setExpanded(hubPinnedOpen);
return;
if (hubPinnedOpen) {
closePinnedToolbar();
} else {
hubPinnedOpen = true;
setExpanded(true);
}
hubPinnedOpen = !cluster.classList.contains("is-expanded");
setExpanded(hubPinnedOpen);
});
const HOVER_PADDING_PX = 12;
@@ -3404,9 +3431,14 @@ function setupToolbarHubCluster() {
if (!hubPinnedOpen) return;
if (!(event.target instanceof Element)) return;
if (event.target.closest("#toolbar-cluster")) return;
hubPinnedOpen = false;
setExpanded(false);
closePinnedToolbar();
});
toolbarHubController = {
cluster,
isOpen: () => hubPinnedOpen || cluster.classList.contains("is-expanded"),
close: closePinnedToolbar,
};
}
export function teardownControls() {

View File

@@ -22,10 +22,11 @@ const textureLoader = new THREE.TextureLoader();
let _earthMaterial = null;
let _earthTextureOverlay = null;
let _earthTextureOverlayMaterial = null;
let _earthShader = null;
let _earthShaders = [];
let _dayNightEnabled = true;
let _loadedTexture = null;
let _textureVisible = true;
let _earthRimGlow = null;
const _earthSunDirection = new THREE.Vector3(
EARTH_MATERIAL_CONFIG.dayNight.sunDirection.x,
EARTH_MATERIAL_CONFIG.dayNight.sunDirection.y,
@@ -39,7 +40,7 @@ function applyEarthDayNightShader(material) {
const nightTintColor = new THREE.Color(EARTH_MATERIAL_CONFIG.dayNight.nightTintColor);
material.onBeforeCompile = (shader) => {
_earthShader = shader;
_earthShaders.push(shader);
shader.uniforms.uSunDirectionWorld = { value: _earthSunDirection.clone() };
shader.uniforms.uNightFloor = { value: EARTH_MATERIAL_CONFIG.dayNight.nightFloor };
shader.uniforms.uDayBoost = { value: EARTH_MATERIAL_CONFIG.dayNight.dayBoost };
@@ -92,6 +93,7 @@ uniform float uDayNightEnabled;`,
dnLight *= mix(uNightFloor, uDayBoost, daylight);
dnLight += uTwilightColor * twilight * uTwilightIntensity;
dnLight += uNightTintColor * (1.0 - daylight) * uNightTintIntensity;
dnLight = dnLight / (vec3(1.0) + max(dnLight - vec3(0.68), vec3(0.0)) * 0.86);
// dn=0: emissive base (from material, set in JS) * camera-facing boost → always readable
// dn=1: full day/night solar lighting
@@ -107,6 +109,7 @@ uniform float uDayNightEnabled;`,
}
export function createEarth(scene) {
_earthShaders = [];
const geometry = new THREE.SphereGeometry(CONFIG.earthRadius, 128, 128);
const C = EARTH_MATERIAL_CONFIG;
@@ -137,14 +140,15 @@ export function createEarth(scene) {
);
_earthTextureOverlayMaterial = new THREE.MeshPhongMaterial({
color: 0xffffff,
specular: C.specular,
shininess: C.shininess,
specular: C.textureOverlaySpecular,
shininess: C.textureOverlayShininess,
transparent: true,
opacity: C.textureOverlayOpacity,
side: THREE.FrontSide,
depthWrite: false,
depthTest: true,
});
applyEarthDayNightShader(_earthTextureOverlayMaterial);
_earthTextureOverlay = new THREE.Mesh(
textureOverlayGeometry,
_earthTextureOverlayMaterial,
@@ -169,7 +173,9 @@ export function createEarth(scene) {
occluder.renderOrder = -1;
earth.add(occluder);
// Shared Fresnel vertex shader for both atmosphere layers
// Keep the original atmosphere shells on the legacy camera-facing shader so
// they stay as a soft edge cue instead of becoming a visible transparent hull
// at close zoom levels.
const ATMOS_VERTEX_SHADER = `
varying vec3 vNormal;
void main() {
@@ -178,6 +184,17 @@ export function createEarth(scene) {
}
`;
const RIM_VERTEX_SHADER = `
varying vec3 vNormal;
varying vec3 vViewDirection;
void main() {
vNormal = normalize(normalMatrix * normal);
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vViewDirection = normalize(-mvPosition.xyz);
gl_Position = projectionMatrix * mvPosition;
}
`;
// Fresnel atmosphere — inner rim
const [ir, ig, ib] = C.atmosInnerColor;
const atmosInnerGeo = new THREE.SphereGeometry(
@@ -230,6 +247,39 @@ export function createEarth(scene) {
atmosOuter.renderOrder = 1;
earth.add(atmosOuter);
// Fresnel rim cue: an outer shell keeps the edge tied to the globe while bypassing
// the darker fill layers that can otherwise hide a same-radius glow. Unlike the
// legacy atmosphere shells, this one uses the real view direction so its highlight
// stays attached to the visible globe edge while zooming.
const [rr, rg, rb] = C.rimGlowColor;
const rimGlowGeo = new THREE.SphereGeometry(
CONFIG.earthRadius * C.rimGlowRadiusFactor,
C.rimGlowSegments,
C.rimGlowSegments,
);
const rimGlowMat = new THREE.ShaderMaterial({
vertexShader: RIM_VERTEX_SHADER,
fragmentShader: `
varying vec3 vNormal;
varying vec3 vViewDirection;
void main() {
float viewFacing = max(dot(normalize(vNormal), normalize(vViewDirection)), 0.0);
float rim = 1.0 - viewFacing;
float alpha = pow(rim, ${C.rimGlowPower.toFixed(1)}) * ${C.rimGlowIntensity.toFixed(2)};
gl_FragColor = vec4(${rr.toFixed(2)}, ${rg.toFixed(2)}, ${rb.toFixed(2)}, alpha);
}
`,
blending: THREE.AdditiveBlending,
side: THREE.FrontSide,
transparent: true,
depthTest: false,
depthWrite: false,
});
_earthRimGlow = new THREE.Mesh(rimGlowGeo, rimGlowMat);
_earthRimGlow.name = "earth-rim-glow";
_earthRimGlow.renderOrder = C.rimGlowRenderOrder;
earth.add(_earthRimGlow);
// Texture is loaded separately via loadEarthTexture() for staged loading
return earth;
}
@@ -435,21 +485,26 @@ export function clearEarthTexture() {
if (_earthTextureOverlay) {
_earthTextureOverlay.visible = false;
}
if (_earthRimGlow) {
_earthRimGlow.visible = true;
}
}
export function setEarthSunDirection(direction) {
if (!direction) return;
_earthSunDirection.copy(direction).normalize();
if (_earthShader?.uniforms?.uSunDirectionWorld) {
_earthShader.uniforms.uSunDirectionWorld.value.copy(_earthSunDirection);
}
_earthShaders.forEach((shader) => {
shader?.uniforms?.uSunDirectionWorld?.value?.copy(_earthSunDirection);
});
}
export function setDayNightEnabled(enabled) {
_dayNightEnabled = enabled;
if (_earthShader?.uniforms?.uDayNightEnabled) {
_earthShader.uniforms.uDayNightEnabled.value = enabled ? 1.0 : 0.0;
}
_earthShaders.forEach((shader) => {
if (shader?.uniforms?.uDayNightEnabled) {
shader.uniforms.uDayNightEnabled.value = enabled ? 1.0 : 0.0;
}
});
if (_earthMaterial) {
if (enabled) {
// Restore normal Phong lighting + custom day/night shader
@@ -491,6 +546,9 @@ export function loadEarthTexture() {
if (_earthTextureOverlay) {
_earthTextureOverlay.visible = _textureVisible;
}
if (_earthRimGlow) {
_earthRimGlow.visible = !_textureVisible;
}
resolve();
},
null,
@@ -503,13 +561,17 @@ export function loadEarthTexture() {
export function setEarthTextureVisible(visible) {
_textureVisible = Boolean(visible);
const textureShowing = _textureVisible && Boolean(_loadedTexture);
if (_earthTextureOverlay) {
_earthTextureOverlay.visible = _textureVisible && Boolean(_loadedTexture);
_earthTextureOverlay.visible = textureShowing;
}
if (_earthTextureOverlayMaterial) {
_earthTextureOverlayMaterial.map = _loadedTexture || null;
_earthTextureOverlayMaterial.needsUpdate = true;
}
if (_earthRimGlow) {
_earthRimGlow.visible = !textureShowing;
}
}
export function getEarthTextureVisible() {