src.gov.nasa.worldwind.layers.SkyGradientLayer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind.layers;
import gov.nasa.worldwind.View;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.util.*;
import javax.media.opengl.*;
import java.awt.*;
/**
* Renders an atmosphere around the globe and a sky dome at low altitude.
*
* Note : based on a spherical globe.
Issue : Ellipsoidal globe doesnt match the spherical atmosphere everywhere.
*
* @author Patrick Murris
* @version $Id: SkyGradientLayer.java 1892 2014-04-04 01:14:40Z tgaskins $
*/
public class SkyGradientLayer extends AbstractLayer
{
protected final static int STACKS = 12;
protected final static int SLICES = 64;
// TODO: make configurable
protected double thickness = 100e3; // Atmosphere thickness
//protected float[] horizonColor = new float[] { 0.66f, 0.70f, 0.81f, 1.0f }; // horizon color (same as fog)
protected float[] horizonColor = new float[] {0.76f, 0.76f, 0.80f, 1.0f}; // horizon color
protected float[] zenithColor = new float[] {0.26f, 0.47f, 0.83f, 1.0f}; // zenith color
/** Renders an atmosphere around the globe */
public SkyGradientLayer()
{
this.setPickEnabled(false);
}
/**
* Get the atmosphere thickness in meter
*
* @return the atmosphere thickness in meter
*/
public double getAtmosphereThickness()
{
return this.thickness;
}
/**
* Set the atmosphere thickness in meter
*
* @param thickness the atmosphere thickness in meter
*/
public void setAtmosphereThickness(double thickness)
{
if (thickness < 0)
{
String msg = Logging.getMessage("generic.ArgumentOutOfRange");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.thickness = thickness;
}
/**
* Get the horizon color
*
* @return the horizon color
*/
public Color getHorizonColor()
{
return new Color(this.horizonColor[0], this.horizonColor[1], this.horizonColor[2], this.horizonColor[3]);
}
/**
* Set the horizon color
*
* @param color the horizon color
*/
public void setHorizonColor(Color color)
{
if (color == null)
{
String msg = Logging.getMessage("nullValue.ColorIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
color.getColorComponents(this.horizonColor);
}
/**
* Get the zenith color
*
* @return the zenith color
*/
public Color getZenithColor()
{
return new Color(this.zenithColor[0], this.zenithColor[1], this.zenithColor[2], this.zenithColor[3]);
}
/**
* Set the zenith color
*
* @param color the zenith color
*/
public void setZenithColor(Color color)
{
if (color == null)
{
String msg = Logging.getMessage("nullValue.ColorIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
color.getColorComponents(this.zenithColor);
}
@Override
public void doRender(DrawContext dc)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
OGLStackHandler ogsh = new OGLStackHandler();
try
{
ogsh.pushAttrib(gl, GL2.GL_TRANSFORM_BIT);
gl.glDisable(GL.GL_DEPTH_TEST);
gl.glDepthMask(false);
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
gl.glEnable(GL.GL_BLEND);
this.applyDrawProjection(dc, ogsh);
this.applyDrawTransform(dc, ogsh);
// Draw sky
this.updateSkyDome(dc);
}
finally
{
dc.restoreDefaultDepthTesting();
dc.restoreDefaultBlending();
dc.restoreDefaultCurrentColor();
ogsh.pop(gl);
}
}
protected void applyDrawTransform(DrawContext dc, OGLStackHandler ogsh)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
View view = dc.getView();
ogsh.pushModelview(gl);
// Place sky - TODO: find another ellipsoid friendlier way (the sky dome is not exactly normal...
// to the ground at higher latitude)
Vec4 camPoint = view.getEyePoint();
Vec4 camPosFromPoint = CartesianToSpherical(camPoint.x, camPoint.y, camPoint.z);
gl.glRotatef((float) (Angle.fromRadians(camPosFromPoint.z).degrees), 0.0f, 1.0f, 0.0f);
gl.glRotatef((float) (-Angle.fromRadians(camPosFromPoint.y).degrees + 90), 1.0f, 0.0f, 0.0f);
gl.glTranslatef(0.0f, (float) (view.getEyePoint().getLength3()), 0.0f);
}
protected void applyDrawProjection(DrawContext dc, OGLStackHandler ogsh)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
View view = dc.getView();
double viewportWidth = view.getViewport().getWidth();
double viewportHeight = view.getViewport().getHeight();
// If either the viewport width or height is zero, then treat the dimension as if it had value 1.
if (viewportWidth <= 0)
viewportWidth = 1;
if (viewportHeight <= 0)
viewportHeight = 1;
Matrix projection = Matrix.fromPerspective(view.getFieldOfView(), viewportWidth, viewportHeight,
100, view.getHorizonDistance() + 10e3);
double[] matrixArray = new double[16];
projection.toArray(matrixArray, 0, false);
ogsh.pushProjection(gl);
gl.glLoadMatrixd(matrixArray, 0);
}
protected void updateSkyDome(DrawContext dc)
{
View view = dc.getView();
double tangentialDistance = view.getHorizonDistance();
double distToCenterOfPlanet = view.getEyePoint().getLength3();
Position camPos = dc.getGlobe().computePositionFromPoint(view.getEyePoint());
double worldRadius = dc.getGlobe().getRadiusAt(camPos);
double camAlt = camPos.getElevation();
// horizon latitude degrees
double horizonLat = (-Math.PI / 2 + Math.acos(tangentialDistance / distToCenterOfPlanet))
* 180 / Math.PI;
// zenith latitude degrees
double zenithLat = 90;
float zenithOpacity = 1f;
float gradientBias = 2f;
if (camAlt >= thickness)
{
// Eye is above atmosphere
double tangentalDistanceZenith = Math.sqrt(distToCenterOfPlanet * distToCenterOfPlanet
- (worldRadius + thickness) * (worldRadius + thickness));
zenithLat = (-Math.PI / 2 + Math.acos(tangentalDistanceZenith / distToCenterOfPlanet)) * 180 / Math.PI;
zenithOpacity = 0f;
gradientBias = 1f;
}
if (camAlt < thickness && camAlt > thickness * 0.7)
{
// Eye is entering atmosphere - outer 30%
double factor = (thickness - camAlt) / (thickness - thickness * 0.7);
zenithLat = factor * 90;
zenithOpacity = (float) factor;
gradientBias = 1f + (float) factor;
}
this.drawSkyDome(dc, (float)tangentialDistance, horizonLat, zenithLat, SLICES, STACKS, zenithOpacity, gradientBias);
}
/**
* Draws the sky dome
*
* @param dc the current DrawContext
* @param radius the sky dome radius
* @param startLat the horizon latitude
* @param endLat the zenith latitude
* @param slices the number of slices - vertical divisions
* @param stacks the nuber os stacks - horizontal divisions
* @param zenithOpacity the sky opacity at zenith
* @param gradientBias determines how fast the sky goes from the horizon color to the zenith color. A value of
* 1
with produce a balanced gradient, a value greater then 1
will
* have the zenith color dominate and a value less then 1
will have the opposite
* effect.
*/
protected void drawSkyDome(DrawContext dc, float radius, double startLat, double endLat,
int slices, int stacks, float zenithOpacity, float gradientBias)
{
double latitude, longitude, latitudeTop = endLat;
// GL setup
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
// TODO: Simplify code
double linear, linearTop, k, kTop, colorFactorZ, colorFactorZTop = 0;
double colorFactorH, colorFactorHTop = 0;
double alphaFactor, alphaFactorTop = 0;
// bottom fade
latitude = startLat - Math.max((endLat - startLat) / 4, 3);
gl.glBegin(GL2.GL_QUAD_STRIP);
for (int slice = 0; slice <= slices; slice++)
{
longitude = 180 - ((float) slice / slices * (float) 360);
Vec4 v = SphericalToCartesian(latitude, longitude, radius);
gl.glColor4d(zenithColor[0], zenithColor[1], zenithColor[2], 0);
gl.glVertex3d(v.getX(), v.getY(), v.getZ());
v = SphericalToCartesian(startLat, longitude, radius);
gl.glColor4d(horizonColor[0], horizonColor[1], horizonColor[2], horizonColor[3]);
gl.glVertex3d(v.getX(), v.getY(), v.getZ());
}
gl.glEnd();
// stacks and slices
for (int stack = 1; stack < stacks - 1; stack++)
{
// bottom vertex
linear = (float) (stack - 1) / (stacks - 1f);
k = 1 - Math.cos(linear * Math.PI / 2);
latitude = startLat + k * (endLat - startLat);
colorFactorZ = Math.min(1f, linear * gradientBias); // coef zenith color
colorFactorH = 1 - colorFactorZ; // coef horizon color
alphaFactor = 1 - Math.pow(linear, 4) * (1 - zenithOpacity); // coef alpha transparency
// top vertex
linearTop = (float) (stack) / (stacks - 1f);
kTop = 1 - Math.cos(linearTop * Math.PI / 2);
latitudeTop = startLat + kTop * (endLat - startLat);
colorFactorZTop = Math.min(1f, linearTop * gradientBias); // coef zenith color
colorFactorHTop = 1 - colorFactorZTop; // coef horizon color
alphaFactorTop = 1 - Math.pow(linearTop, 4) * (1 - zenithOpacity); // coef alpha transparency
// Draw stack
gl.glBegin(GL2.GL_QUAD_STRIP);
for (int slice = 0; slice <= slices; slice++)
{
longitude = 180 - ((float) slice / slices * (float) 360);
Vec4 v = SphericalToCartesian(latitude, longitude, radius);
gl.glColor4d(
(horizonColor[0] * colorFactorH + zenithColor[0] * colorFactorZ),
(horizonColor[1] * colorFactorH + zenithColor[1] * colorFactorZ),
(horizonColor[2] * colorFactorH + zenithColor[2] * colorFactorZ),
(horizonColor[3] * colorFactorH + zenithColor[3] * colorFactorZ) * alphaFactor);
gl.glVertex3d(v.getX(), v.getY(), v.getZ());
v = SphericalToCartesian(latitudeTop, longitude, radius);
gl.glColor4d(
(horizonColor[0] * colorFactorHTop + zenithColor[0] * colorFactorZTop),
(horizonColor[1] * colorFactorHTop + zenithColor[1] * colorFactorZTop),
(horizonColor[2] * colorFactorHTop + zenithColor[2] * colorFactorZTop),
(horizonColor[3] * colorFactorHTop + zenithColor[3] * colorFactorZTop) * alphaFactorTop);
gl.glVertex3d(v.getX(), v.getY(), v.getZ());
}
gl.glEnd();
}
// Top fade
gl.glBegin(GL2.GL_QUAD_STRIP);
for (int slice = 0; slice <= slices; slice++)
{
longitude = 180 - ((float) slice / slices * (float) 360);
Vec4 v = SphericalToCartesian(latitudeTop, longitude, radius);
gl.glColor4d(
(horizonColor[0] * colorFactorHTop + zenithColor[0] * colorFactorZTop),
(horizonColor[1] * colorFactorHTop + zenithColor[1] * colorFactorZTop),
(horizonColor[2] * colorFactorHTop + zenithColor[2] * colorFactorZTop),
(horizonColor[3] * colorFactorHTop + zenithColor[3] * colorFactorZTop) * alphaFactorTop);
gl.glVertex3d(v.getX(), v.getY(), v.getZ());
v = SphericalToCartesian(endLat, longitude, radius);
gl.glColor4d(zenithColor[0], zenithColor[1], zenithColor[2], zenithOpacity < 1 ? 0 : zenithColor[3]);
gl.glVertex3d(v.getX(), v.getY(), v.getZ());
}
gl.glColor4d(1d, 1d, 1d, 1d); // restore the default OpenGL color
gl.glEnd();
}
/**
* Draws the positive three axes - x is red, y is green and z is blue
*
* @param dc the current DrawContext
* @param length the lenght of the axes lines
*/
// private static void DrawAxis(DrawContext dc, float length) {
// GL gl = dc.getGL();
// gl.glBegin(GL.GL_LINES);
//
// // Draw 3 axis
// gl.glColor3f(0f, 0f, 1f); // Z Blue
// gl.glVertex3d(0d, 0d, 0d);
// gl.glVertex3d(0d, 0d, length);
// gl.glColor3f(0f, 1f, 0f); // Y Green
// gl.glVertex3d(0d, 0d, 0d);
// gl.glVertex3d(0d, length, 0d);
// gl.glColor3f(1f, 0f, 0f); // X Red
// gl.glVertex3d(0d, 0d, 0d);
// gl.glVertex3d(length, 0d, 0d);
//
// gl.glEnd();
// }
/**
* Converts position in spherical coordinates (lat/lon/altitude) to cartesian (XYZ) coordinates.
*
* @param latitude Latitude in decimal degrees
* @param longitude Longitude in decimal degrees
* @param radius Radius
*
* @return the corresponding Point
*/
protected static Vec4 SphericalToCartesian(double latitude, double longitude, double radius)
{
latitude *= Math.PI / 180.0f;
longitude *= Math.PI / 180.0f;
double radCosLat = radius * Math.cos(latitude);
return new Vec4(
radCosLat * Math.sin(longitude),
radius * Math.sin(latitude),
radCosLat * Math.cos(longitude));
}
/**
* Converts position in cartesian coordinates (XYZ) to spherical (radius, lat, lon) coordinates.
*
* @param x X coordinate
* @param y Y coordinate
* @param z Z coordinate
*
* @return a Vec4
point for the spherical coordinates {radius, lat, lon}
*/
protected static Vec4 CartesianToSpherical(double x, double y, double z)
{
double rho = Math.sqrt(x * x + y * y + z * z);
double longitude = Math.atan2(x, z);
double latitude = Math.asin(y / rho);
return new Vec4(rho, latitude, longitude);
}
@Override
public String toString()
{
return Logging.getMessage("layers.Earth.SkyGradientLayer.Name");
}
}