2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
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
.*;
20 * Renders an atmosphere around the globe and the sky at low altitude.
22 * Ported from my WW plugin SkyGradient and from WW2DPlusOne.
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
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;
43 * Renders an atmosphere around the globe
45 public SkyGradientLayer() {
46 this.setName(Logging
.getMessage("layers.Earth.SkyGradientLayer.Name"));
50 * Get the atmosphere thickness in meter
51 * @return the atmosphere thickness in meter
53 public double getAtmosphereThickness()
55 return this.thickness
;
59 * Set the atmosphere thickness in meter
60 * @param thickness the atmosphere thickness in meter
62 public void setAtmosphereThickness(double thickness
)
66 String msg
= Logging
.getMessage("generic.ArgumentOutOfRange");
67 Logging
.logger().severe(msg
);
68 throw new IllegalArgumentException(msg
);
70 this.thickness
= thickness
;
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]);
83 * Set the horizon color
84 * @param color the horizon color
86 public void setHorizonColor(Color color
)
90 String msg
= Logging
.getMessage("nullValue.ColorIsNull");
91 Logging
.logger().severe(msg
);
92 throw new IllegalArgumentException(msg
);
94 color
.getColorComponents(this.horizonColor
);
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
)
114 String msg
= Logging
.getMessage("nullValue.ColorIsNull");
115 Logging
.logger().severe(msg
);
116 throw new IllegalArgumentException(msg
);
118 color
.getColorComponents(this.zenithColor
);
122 public void doRender(DrawContext dc
)
125 boolean attribsPushed
= false;
126 boolean modelviewPushed
= false;
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)
137 double domeRadius
= tangentalDistance
;
138 // horizon latitude degrees
139 double horizonLat
= camAlt
> 0 ?
(-Math
.PI
/ 2 + Math
.acos(tangentalDistance
/ distToCenterOfPlanet
)) * 180 / Math
.PI
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
;
162 gl
.glPushAttrib(GL
.GL_POLYGON_BIT
); // Temporary hack around aliased sky.
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
);
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
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());
196 gl
.glMatrixMode(GL
.GL_MODELVIEW
);
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
)
213 this.glListId
= gl
.glGenLists(1);
214 gl
.glNewList(this.glListId
, GL
.GL_COMPILE
);
215 this.drawSkyGradient(dc
, radius
, startLat
, endLat
, slices
, stacks
);
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
;
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;
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());
259 for (int stack
= 1; stack
< stacks
- 1; stack
++) {
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
;
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
;
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
);
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
);
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());
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
);
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());
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
) {
329 gl
.glBegin(GL
.GL_LINES
);
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
);
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
);
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)
388 GLContext glc
= GLContext
.getCurrent();
392 glc
.getGL().glDeleteLists(this.glListId
, 1);
397 public String
toString() {
398 return this.getName();