release: bump version to 0.42.0
This commit is contained in:
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
58
frontend/public/earth/js/controls.js
vendored
58
frontend/public/earth/js/controls.js
vendored
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user