Update to Worldwind release 0.4.0
[worldwind-tracker.git] / gov / nasa / worldwind / layers / Earth / SkyGradientLayer.java
blob660120ef6176acc175941708260873c56208fcad
1 /*
2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
5 All Rights Reserved.
6 */
7 package gov.nasa.worldwind.layers.Earth;
9 import javax.media.opengl.GL;
10 import javax.media.opengl.GLContext;
12 import gov.nasa.worldwind.util.Logging;
13 import gov.nasa.worldwind.render.*;
14 import gov.nasa.worldwind.geom.*;
15 import gov.nasa.worldwind.layers.*;
17 import java.awt.*;
19 /**
20 * Renders an atmosphere around the globe and the sky at low altitude.
21 * <p>
22 * Ported from my WW plugin SkyGradient and from WW2DPlusOne.
23 * </p>
24 * Note : based on a spherical globe.<br />
25 * Issues : Ellipsoidal globe doesnt match the spherical atmosphere everywhere.
26 * Doesnt behave properly when the eye is at negative elevation.
27 * @author Patrick Murris
28 * @version $Id$
30 public class SkyGradientLayer extends AbstractLayer
32 private final static int STACKS = 12;
33 private final static int SLICES = 64;
35 protected int glListId = -1; // GL list id
36 // TODO: make configurable
37 protected double thickness = 100e3; // Atmosphere thickness
38 protected float[] horizonColor = new float[] { 0.66f, 0.70f, 0.81f, 1.0f }; // horizon color (same as fog)
39 protected float[] zenithColor = new float[]{0.26f, 0.47f, 0.83f, 1.0f}; // zenith color
40 protected double lastRebuildAltitude = 0;
42 /**
43 * Renders an atmosphere around the globe
45 public SkyGradientLayer() {
46 this.setName(Logging.getMessage("layers.Earth.SkyGradientLayer.Name"));
49 /**
50 * Get the atmosphere thickness in meter
51 * @return the atmosphere thickness in meter
53 public double getAtmosphereThickness()
55 return this.thickness;
58 /**
59 * Set the atmosphere thickness in meter
60 * @param thickness the atmosphere thickness in meter
62 public void setAtmosphereThickness(double thickness)
64 if (thickness < 0)
66 String msg = Logging.getMessage("generic.ArgumentOutOfRange");
67 Logging.logger().severe(msg);
68 throw new IllegalArgumentException(msg);
70 this.thickness = thickness;
73 /**
74 * Get the horizon color
75 * @return the horizon color
77 public Color getHorizonColor()
79 return new Color(this.horizonColor[0], this.horizonColor[1], this.horizonColor[2], this.horizonColor[3]);
82 /**
83 * Set the horizon color
84 * @param color the horizon color
86 public void setHorizonColor(Color color)
88 if (color == null)
90 String msg = Logging.getMessage("nullValue.ColorIsNull");
91 Logging.logger().severe(msg);
92 throw new IllegalArgumentException(msg);
94 color.getColorComponents(this.horizonColor);
97 /**
98 * Get the zenith color
99 * @return the zenith color
101 public Color getZenithColor()
103 return new Color(this.zenithColor[0], this.zenithColor[1], this.zenithColor[2], this.zenithColor[3]);
107 * Set the zenith color
108 * @param color the zenith color
110 public void setZenithColor(Color color)
112 if (color == null)
114 String msg = Logging.getMessage("nullValue.ColorIsNull");
115 Logging.logger().severe(msg);
116 throw new IllegalArgumentException(msg);
118 color.getColorComponents(this.zenithColor);
121 @Override
122 public void doRender(DrawContext dc)
124 GL gl = dc.getGL();
125 boolean attribsPushed = false;
126 boolean modelviewPushed = false;
128 try {
129 Position camPos = dc.getGlobe().computePositionFromPoint(dc.getView().getEyePoint());
130 double worldRadius = dc.getGlobe().getRadiusAt(camPos.getLatLon());
131 double distToCenterOfPlanet = dc.getView().getEyePoint().getLength3();
132 double camAlt = camPos.getElevation();
133 double tangentalDistance = distToCenterOfPlanet > worldRadius ?
134 Math.max(Math.sqrt(distToCenterOfPlanet * distToCenterOfPlanet - worldRadius * worldRadius), 10000)
135 : 10000;
136 // Dome radius
137 double domeRadius = tangentalDistance;
138 // horizon latitude degrees
139 double horizonLat = camAlt > 0 ? (-Math.PI / 2 + Math.acos(tangentalDistance / distToCenterOfPlanet)) * 180 / Math.PI
140 : 0;
141 // zenith latitude degrees
142 double zenithLat = 90;
143 if (camAlt >= thickness) {
144 double tangentalDistanceZenith = Math.sqrt(distToCenterOfPlanet * distToCenterOfPlanet - (worldRadius + thickness) * (worldRadius + thickness));
145 zenithLat = (-Math.PI / 2 + Math.acos(tangentalDistanceZenith / distToCenterOfPlanet)) * 180 / Math.PI;
147 if (camAlt < thickness && camAlt > thickness * 0.7) {
148 zenithLat = (thickness - camAlt) / (thickness - thickness * 0.7) * 90;
151 // Build or rebuild sky dome if altitude changed more then 10m
152 // Note: increasing this threshold may produce artefacts like far clipping at very low altitude
153 if (this.glListId == -1 || Math.abs(this.lastRebuildAltitude - camAlt) > 10)
155 if (this.glListId != -1)
156 gl.glDeleteLists(this.glListId, 1);
157 this.makeSkyDome(dc, (float) (domeRadius - 3e3), horizonLat, zenithLat, SLICES, STACKS); // have smaller dome radius to avoid far clipping
158 this.lastRebuildAltitude = camAlt;
161 // GL set up
162 gl.glPushAttrib(GL.GL_POLYGON_BIT); // Temporary hack around aliased sky.
163 gl.glPopAttrib();
165 gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT
166 | GL.GL_POLYGON_BIT | GL.GL_TEXTURE_BIT | GL.GL_ENABLE_BIT
167 | GL.GL_CURRENT_BIT);
168 attribsPushed = true;
169 gl.glDisable(GL.GL_TEXTURE_2D); // no textures
171 gl.glMatrixMode(GL.GL_MODELVIEW);
172 gl.glPushMatrix();
173 modelviewPushed = true;
174 //gl.glLoadIdentity();
175 dc.getView().pushReferenceCenter(dc, Vec4.ZERO);
176 // Place sky - TODO: find another ellipsoid friendlier way (the sky dome is not exactly normal to the ground at higher latitude)
177 Vec4 camPoint = dc.getView().getEyePoint();
178 Vec4 camPosFromPoint = CartesianToSpherical(camPoint.x, camPoint.y, camPoint.z);
179 gl.glRotatef((float) (Angle.fromRadians(camPosFromPoint.z).degrees), 0.0f, 1.0f, 0.0f);
180 gl.glRotatef((float) (-Angle.fromRadians(camPosFromPoint.y).degrees + 90), 1.0f, 0.0f, 0.0f);
181 gl.glTranslatef(0.0f, (float) (distToCenterOfPlanet - 4e3f), 0.0f); // place dome 4km below normal level
183 // Draw sky
184 if (this.glListId != -1)
185 gl.glCallList(this.glListId);
187 dc.getView().popReferenceCenter(dc);
189 catch (Exception e) {
190 //System.out.println(Logging.getMessage("generic.ExceptionWhileRenderingLayer") + e.getStackTrace());
192 finally {
193 // Restore GL state
194 if (modelviewPushed)
196 gl.glMatrixMode(GL.GL_MODELVIEW);
197 gl.glPopMatrix();
199 if (attribsPushed)
200 gl.glPopAttrib();
205 * Build sky dome and draw into a glList
207 * @param dc the current DrawContext
209 private void makeSkyDome(DrawContext dc, float radius, double startLat, double endLat,
210 int slices, int stacks)
212 GL gl = dc.getGL();
213 this.glListId = gl.glGenLists(1);
214 gl.glNewList(this.glListId, GL.GL_COMPILE);
215 this.drawSkyGradient(dc, radius, startLat, endLat, slices, stacks);
216 gl.glEndList();
220 * Draws the sky dome
222 * @param dc the current DrawContext
223 * @param radius the sky dome radius
224 * @param startLat the horizon latitude
225 * @param endLat the zenith latitude
226 * @param slices the number of slices - vertical divisions
227 * @param stacks the nuber os stacks - horizontal divisions
229 private void drawSkyGradient(DrawContext dc, float radius, double startLat, double endLat,
230 int slices, int stacks) {
231 double latitude, longitude, latitudeTop = endLat;
233 // GL setup
234 GL gl = dc.getGL();
235 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
236 gl.glEnable(GL.GL_BLEND);
237 gl.glDisable(GL.GL_TEXTURE_2D);
238 //gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE); // wireframe
240 // TODO: Simplify code
241 double linear, linearTop, k, kTop, colorFactorZ = 0, colorFactorZTop = 0;
242 double colorFactorH = 0, colorFactorHTop = 0, alphaFactor = 0, alphaFactorTop = 0;
244 // bottom fade
245 latitude = startLat - Math.max((endLat - startLat) / 4, 2);
246 gl.glBegin(GL.GL_QUAD_STRIP);
247 for (int slice = 0; slice <= slices; slice++) {
248 longitude = 180 - ((float) slice / slices * (float) 360);
249 Vec4 v = SphericalToCartesian(latitude, longitude, radius);
250 gl.glColor4d(zenithColor[0], zenithColor[1], zenithColor[2], 0);
251 gl.glVertex3d(v.getX(), v.getY(), v.getZ());
252 v = SphericalToCartesian(startLat, longitude, radius);
253 gl.glColor4d(horizonColor[0], horizonColor[1], horizonColor[2], .9 * horizonColor[3]);
254 gl.glVertex3d(v.getX(), v.getY(), v.getZ());
256 gl.glEnd();
258 // stacks and slices
259 for (int stack = 1; stack < stacks - 1; stack++) {
260 // bottom vertex
261 linear = (float) (stack - 1) / (stacks - 1f);
262 k = 1 - Math.cos(linear * Math.PI / 2);
263 latitude = startLat + Math.pow(k, 3) * (endLat - startLat);
264 colorFactorZ = linear; // coef zenith color
265 colorFactorH = 1 - colorFactorZ; // coef horizon color
266 alphaFactor = 1 - Math.pow(linear, 4); // coef alpha transparency
267 if (alphaFactor > .9) alphaFactor = .9f;
268 // top vertex
269 linearTop = (float) (stack) / (stacks - 1f);
270 kTop = 1 - Math.cos(linearTop * Math.PI / 2);
271 latitudeTop = startLat + Math.pow(kTop, 3) * (endLat - startLat);
272 colorFactorZTop = linearTop; // coef zenith color
273 colorFactorHTop = 1 - colorFactorZTop; // coef horizon color
274 alphaFactorTop = 1 - Math.pow(linearTop, 4); // coef alpha transparency
275 if (alphaFactorTop > .9) alphaFactorTop = .9f;
276 // Draw stack
277 gl.glBegin(GL.GL_QUAD_STRIP);
278 for (int slice = 0; slice <= slices; slice++) {
279 longitude = 180 - ((float) slice / slices * (float) 360);
280 Vec4 v = SphericalToCartesian(latitude, longitude, radius);
281 gl.glColor4d(
282 (horizonColor[0] * colorFactorH + zenithColor[0] * colorFactorZ),
283 (horizonColor[1] * colorFactorH + zenithColor[1] * colorFactorZ),
284 (horizonColor[2] * colorFactorH + zenithColor[2] * colorFactorZ),
285 (horizonColor[3] * colorFactorH + zenithColor[3] * colorFactorZ) * alphaFactor);
286 gl.glVertex3d(v.getX(), v.getY(), v.getZ());
287 v = SphericalToCartesian(latitudeTop, longitude, radius);
288 gl.glColor4d(
289 (horizonColor[0] * colorFactorHTop + zenithColor[0] * colorFactorZTop),
290 (horizonColor[1] * colorFactorHTop + zenithColor[1] * colorFactorZTop),
291 (horizonColor[2] * colorFactorHTop + zenithColor[2] * colorFactorZTop),
292 (horizonColor[3] * colorFactorHTop + zenithColor[3] * colorFactorZTop) * alphaFactorTop);
293 gl.glVertex3d(v.getX(), v.getY(), v.getZ());
295 gl.glEnd();
298 // Top fade
299 if (endLat < 90) {
300 gl.glBegin(GL.GL_QUAD_STRIP);
301 for (int slice = 0; slice <= slices; slice++) {
302 longitude = 180 - ((float) slice / slices * (float) 360);
303 Vec4 v = SphericalToCartesian(latitudeTop, longitude, radius);
304 gl.glColor4d(
305 (horizonColor[0] * colorFactorHTop + zenithColor[0] * colorFactorZTop),
306 (horizonColor[1] * colorFactorHTop + zenithColor[1] * colorFactorZTop),
307 (horizonColor[2] * colorFactorHTop + zenithColor[2] * colorFactorZTop),
308 (horizonColor[3] * colorFactorHTop + zenithColor[3] * colorFactorZTop) * alphaFactorTop);
309 gl.glVertex3d(v.getX(), v.getY(), v.getZ());
310 v = SphericalToCartesian(endLat, longitude, radius);
311 gl.glColor4d(zenithColor[0], zenithColor[1], zenithColor[2], 0);
312 gl.glVertex3d(v.getX(), v.getY(), v.getZ());
314 gl.glEnd();
317 gl.glEnable(GL.GL_TEXTURE_2D);
318 gl.glDisable(GL.GL_BLEND);
322 * Draws the positive three axes - x is red, y is green and z is blue
324 * @param dc the current DrawContext
325 * @param length the lenght of the axes lines
327 private static void DrawAxis(DrawContext dc, float length) {
328 GL gl = dc.getGL();
329 gl.glBegin(GL.GL_LINES);
331 // Draw 3 axis
332 gl.glColor3f(0f, 0f, 1f); // Z Blue
333 gl.glVertex3d(0d, 0d, 0d);
334 gl.glVertex3d(0d, 0d, length);
335 gl.glColor3f(0f, 1f, 0f); // Y Green
336 gl.glVertex3d(0d, 0d, 0d);
337 gl.glVertex3d(0d, length, 0d);
338 gl.glColor3f(1f, 0f, 0f); // X Red
339 gl.glVertex3d(0d, 0d, 0d);
340 gl.glVertex3d(length, 0d, 0d);
342 gl.glEnd();
346 * Converts position in spherical coordinates (lat/lon/altitude)
347 * to cartesian (XYZ) coordinates.
349 * @param latitude Latitude in decimal degrees
350 * @param longitude Longitude in decimal degrees
351 * @param radius Radius
352 * @return the corresponding Point
354 private static Vec4 SphericalToCartesian(double latitude, double longitude, double radius) {
355 latitude *= Math.PI / 180.0f;
356 longitude *= Math.PI / 180.0f;
358 double radCosLat = radius * Math.cos(latitude);
360 return new Vec4(
361 radCosLat * Math.sin(longitude),
362 radius * Math.sin(latitude),
363 radCosLat * Math.cos(longitude));
367 * Converts position in cartesian coordinates (XYZ)
368 * to spherical (radius, lat, lon) coordinates.
370 * @param x X coordinate
371 * @param y Y coordinate
372 * @param z Z coordinate
373 * @return a Vec4 for the spherical coordinates {radius, lat, lon}
375 private static Vec4 CartesianToSpherical(double x, double y, double z) {
376 double rho = Math.sqrt(x * x + y * y + z * z);
377 double longitude = Math.atan2(x, z);
378 double latitude = Math.asin(y / rho);
380 return new Vec4(rho, latitude, longitude);
383 public void dispose()
385 if (this.glListId < 0)
386 return;
388 GLContext glc = GLContext.getCurrent();
389 if (glc == null)
390 return;
392 glc.getGL().glDeleteLists(this.glListId, 1);
393 this.glListId = -1;
396 @Override
397 public String toString() {
398 return this.getName();