Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,13 @@ Sol is not for navigation, occultation prediction, mission safety or operational
space-weather warnings. `space_weather_operational` remains false. Project code is
MIT OR Apache-2.0; third-party data attribution and outstanding coefficient notice
correspondence are documented separately and are not resolved by that project license.

### Optional Enhanced Earth

Solar System → View controls → Earth layers → **Enhanced Earth** adds darker
offshore oceans and an elevated historical NASA cloud layer with Sun-directed
shadows. Clouds drift independently during Play time and freeze when paused.
The option is off by default; cloud height, motion and lighting are illustrative.
MODIS swaths and the scientific sea-ice palette suspend the enhancement.
See [RFC 0008](docs/rfcs/0008-enhanced-earth.md) and the
[validation record](docs/validation/enhanced-earth/README.md).
4 changes: 4 additions & 0 deletions apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
<link rel="modulepreload" href="js/data.js?v=dcca6290db">
<link rel="modulepreload" href="js/dataBundle.js">
<link rel="modulepreload" href="js/illustrativeHaze.js">
<link rel="modulepreload" href="js/enhancedEarth.js">
<link rel="modulepreload" href="js/earthOceanMask.js">
<link rel="modulepreload" href="js/sourceAttribution.js">
<link rel="modulepreload" href="js/viewEvidence.js">
<link rel="modulepreload" href="js/dom.js?v=dcca6290db">
Expand Down Expand Up @@ -455,6 +457,8 @@ <h3 class="inspector-chapter">Tools</h3>
<details class="orrery-group">
<summary>Earth layers</summary>
<div class="orrery-group-body">
<label class="orrery-check"><input id="orreryEarthEnhanced" type="checkbox" aria-describedby="orreryEarthEnhancedHelp"> Enhanced Earth</label>
<p id="orreryEarthEnhancedHelp" class="time-frame-label">Deeper oceans and raised clouds with Sun-aligned shadows. Clouds drift independently during Play time. Color, nominal 8 km cloud height and motion are illustrative; the NASA imagery keeps its historical date. Uses the Blue Marble cloud layer; suspended for MODIS swaths and the sea-ice analysis palette.</p>
<div class="orrery-check-grid">
<label class="orrery-check"><input id="orreryEarthNight" type="checkbox" checked> Night lights</label>
<label class="orrery-check"><input id="orreryEarthWeather" type="checkbox" checked> Clouds</label>
Expand Down
10 changes: 5 additions & 5 deletions apps/web/js/atmosphereColumnField.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,16 +213,16 @@ vec3 atmosphereCombinedTail(float impact,float x){
// Rationalize altitude before lookup: subtracting a rounded body-sized radius
// quantizes a short interval's height increment and corrupts its column mass.
float radius=length(vec2(impact,x)),height=((impact-u_atmosphereRadiusKm)*(impact+u_atmosphereRadiusKm)+x*x)/(radius+u_atmosphereRadiusKm);
vec3 packed;
vec3 columnValues;
if(height<0.0){
float ground=sqrt(max(0.0,u_atmosphereRadiusKm*u_atmosphereRadiusKm-impact*impact));
float below=max(0.0,ground-x);
packed=atmosphereOutwardPacked(0.0,ground/u_atmosphereRadiusKm)+vec3(below,below,below*atmosphereOzoneDensity(0.0));
}else packed=atmosphereOutwardPacked(height,x/max(radius,1e-9));
columnValues=atmosphereOutwardPacked(0.0,ground/u_atmosphereRadiusKm)+vec3(below,below,below*atmosphereOzoneDensity(0.0));
}else columnValues=atmosphereOutwardPacked(height,x/max(radius,1e-9));
// A uniform scale, not a literal zero, keeps the admitted sampler from being
// constant-folded out of the generator. The runtime value stays 0.
packed.xy+=texelFetch(u_atmosphereColumnField,ivec2(0),0).rg*u_atmosphereColumnKeep;
return packed;
columnValues.xy+=texelFetch(u_atmosphereColumnField,ivec2(0),0).rg*u_atmosphereColumnKeep;
return columnValues;
}
vec2 atmosphereOutwardColumns(float height,float mu){return atmosphereOutwardPacked(height,mu).rg;}
float atmosphereOzoneOutward(float height,float mu){return atmosphereOutwardPacked(height,mu).b;}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/js/atmosphereColumnManifest.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions apps/web/js/earthOceanMask.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 77 additions & 0 deletions apps/web/js/enhancedEarth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Illustrative presentation only. Source images, epochs and ephemerides are immutable.
import {OCEAN_MASK_WIDTH,OCEAN_MASK_HEIGHT,OCEAN_MASK_RUNS} from './earthOceanMask.js';
import {rotationDisplayStepSeconds} from './orreryTime.js';

export const EARTH_CLOUD_HEIGHT_KM=8;
export const ENHANCED_EARTH_UNIFORMS=['u_earthEnhanced','u_earthCloudShadow','u_cloudPhase','u_cloudScale','u_oceanMask'];

export function enhancedEarthSelected(state){
return state.earthEnhanced===true&&state.useTextures!==false&&state.earthCloudSource!=='daily'&&state.earthIce!==true&&!state.galaxy;
}

export function advanceEarthCloudPhase(phase,realSeconds,simulatedSeconds,rotationHours,enabled){
if(!enabled||!Number.isFinite(realSeconds)||realSeconds<=0)return phase;
const seconds=rotationDisplayStepSeconds(realSeconds,simulatedSeconds,rotationHours);
if(seconds===0)return phase;
const drift=seconds/(Math.abs(rotationHours)*3600)*.03;
if(!Number.isFinite(drift))return phase;
const step=Math.sign(drift)*Math.min(Math.abs(drift),.002*realSeconds);
return ((phase+step)%1+1)%1;
}

export function oceanMaskPixels(){
const pixels=new Uint8Array(OCEAN_MASK_WIDTH*OCEAN_MASK_HEIGHT);
let offset=0,value=0;
for(const run of OCEAN_MASK_RUNS){pixels.fill(value,offset,offset+run);offset+=run;value=255-value;}
return pixels;
}

export function uploadOceanMask(gl){
const texture=gl.createTexture();
if(!texture)throw new Error('Ocean mask allocation failed');
try{
gl.activeTexture(gl.TEXTURE0+11);gl.bindTexture(gl.TEXTURE_2D,texture);
gl.texImage2D(gl.TEXTURE_2D,0,gl.R8,OCEAN_MASK_WIDTH,OCEAN_MASK_HEIGHT,0,gl.RED,gl.UNSIGNED_BYTE,oceanMaskPixels());
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);
if(gl.getError()!==gl.NO_ERROR)throw new Error('Ocean mask upload failed');
return texture;
}catch(error){gl.deleteTexture(texture);throw error;}
finally{gl.activeTexture(gl.TEXTURE0);}
}

// Input p is on the unit sphere before oblate scaling. Return a point on the
// homothetic cloud ellipsoid in those same unflattened coordinates. The rational
// root avoids subtractive cancellation for a thin shell and an overhead Sun.
export const EARTH_CLOUD_GEOMETRY_GLSL=`
vec3 earthCloudHit(vec3 p,vec3 light,float oblate,float shell){
vec3 d=vec3(light.xy,light.z/oblate);
float a=dot(d,d),b=dot(p,d),c=dot(p,p)-shell*shell;
float root=sqrt(max(0.0,b*b-a*c));
float t=b>=0.0 ? -c/max(root+b,1e-12) : (root-b)/max(a,1e-12);
return p+d*t;
}
`;

// 0 on the night side, and 1 only after the cloud is actually lit. The 0.05
// start keeps a dark twilight veil from covering city lights while cloud RGB
// is still the 0.001 night floor.
export const EARTH_CLOUD_COVER_GLSL=`
float earthCloudCover(float sun){
return smoothstep(0.05, 0.22, max(sun, 0.0));
}
`;

// The mask excludes coarse land/ice/coastal margins. Dark-blue gating additionally
// protects bright ice, land-like colors and islands omitted by the coarse vectors.
export const EARTH_OCEAN_GLSL=`
vec3 enhancedOceanColor(vec3 c,float ocean){
float blue=max(0.0,c.z-max(c.x,c.y));
float bright=max(c.x,max(c.y,c.z));
float weight=ocean*min(1.0,blue/.03)*min(1.0,max(0.0,(.25-bright)/.1));
float luminance=dot(c,vec3(.2126,.7152,.0722));
return c*(1.0-weight)+(c*.8+vec3(luminance)*.2)*.55*weight;
}
`;
Loading
Loading