Updated to worldwind release 20070817
[worldwind-tracker.git] / gov / nasa / worldwind / render / SurfaceShape.java
blobbf86ec1f4719e8ae55813c79aae0d7f24ff451d7
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.render;
9 import com.sun.opengl.util.texture.TextureData;
10 import gov.nasa.worldwind.*;
11 import gov.nasa.worldwind.globes.Globe;
12 import gov.nasa.worldwind.geom.*;
13 import gov.nasa.worldwind.layers.TextureTile;
14 import gov.nasa.worldwind.util.Logging;
16 import javax.media.opengl.GL;
17 import java.awt.*;
18 import java.awt.image.*;
19 import java.util.*;
21 /**
22 * @author tag
23 * @version $Id: SurfaceShape.java 2570 2007-08-16 22:31:33Z tgaskins $
25 public abstract class SurfaceShape implements Renderable, Disposable, Movable
27 public static Dimension TEXTURE_SIZE_1024 = new Dimension(1024, 1024);
28 public static Dimension TEXTURE_SIZE_512 = new Dimension(512, 512);
29 public static Dimension TEXTURE_SIZE_256 = new Dimension(256, 256);
30 public static Dimension TEXTURE_SIZE_128 = new Dimension(128, 128);
31 public static Dimension TEXTURE_SIZE_64 = new Dimension(64, 64);
32 public static Dimension TEXTURE_SIZE_32 = new Dimension(32, 32);
33 public static Dimension TEXTURE_SIZE_16 = new Dimension(16, 16);
34 public static Dimension TEXTURE_SIZE_8 = new Dimension(8, 8);
36 private static final Color DEFAULT_COLOR = new Color(1f, 1f, 0f, 0.4f);
37 private static final Color DEFAULT_BORDER_COLOR = new Color(1f, 1f, 0f, 0.7f);
38 private static final Dimension DEFAULT_TEXTURE_SIZE = TEXTURE_SIZE_64;
39 private static final double DEFAULT_NUM_EDGE_INTERVALS_PER_DEGREE = 1;
41 private ArrayList<TextureTile> tiles = new ArrayList<TextureTile>();
42 private Dimension textureSize = DEFAULT_TEXTURE_SIZE;
43 protected Globe globe;
44 private Paint paint;
45 private Color borderColor;
46 private Stroke stroke = new BasicStroke();
47 private boolean drawBorder = true;
48 private boolean drawInterior = true;
49 private boolean antiAlias = false;
50 private double numEdgeIntervalsPerDegree = DEFAULT_NUM_EDGE_INTERVALS_PER_DEGREE;
51 protected ArrayList<LatLon> positions = new ArrayList<LatLon>();
53 protected abstract BufferedImage drawShape(Globe globe, Sector sector, BufferedImage image);
55 public SurfaceShape(Iterable<LatLon> positions, Color color, Color borderColor, Dimension textureSize)
57 if (positions == null)
59 String message = Logging.getMessage("nullValue.PositionsListIsNull");
60 Logging.logger().severe(message);
61 throw new IllegalArgumentException(message);
64 if (textureSize != null)
65 this.textureSize = textureSize;
67 // Set draw attributes
68 this.paint = color != null ? color : DEFAULT_COLOR;
69 this.borderColor = borderColor != null ? borderColor : DEFAULT_BORDER_COLOR;
71 // Copy positions list.
72 this.replacePositions(positions);
74 // Make tile(s)
75 createTextureTiles();
78 private void replacePositions(Iterable<LatLon> newPositions)
80 this.positions.clear();
81 for (LatLon position : newPositions)
83 this.positions.add(position);
87 protected void createTextureTiles()
89 this.tiles.clear();
90 if (!LatLon.positionsCrossDateLine(this.getPositions()))
92 this.tiles.add(new TextureTile(Sector.boundingSector(this.getPositions()), null));
94 else
96 Sector[] sectors = this.computeSplitSectors(this.getPositions());
97 this.tiles.add(new TextureTile(sectors[0], null));
98 this.tiles.add(new TextureTile(sectors[1], null));
103 * Returns two 'mirror' sectors each on one side of the longitude boundary - for boundary crossing shapes
105 * @param positions the shape positions
106 * @return an array of two sectors representing the shape
108 private Sector[] computeSplitSectors(Iterable<LatLon> positions)
110 Sector[] sectors = new Sector[2];
111 // Find out longitude extremes for each sides
112 double maxWest = Angle.NEG180.getDegrees();
113 double minEast = Angle.POS180.getDegrees();
114 // Find out absolute latitude extremes
115 double minSouth = Angle.POS90.getDegrees();
116 double maxNorth = Angle.NEG90.getDegrees();
118 LatLon lastPos = null;
119 for (LatLon pos : positions)
121 double lat = pos.getLatitude().getDegrees();
122 if (lat > maxNorth)
123 maxNorth = lat;
124 if (lat < minSouth)
125 minSouth = lat;
127 double lon = pos.getLongitude().getDegrees();
128 if (lon <= 0 && lon > maxWest)
129 maxWest = lon;
130 if (lon >= 0 && lon < minEast)
131 minEast = lon;
133 if (lastPos != null)
135 double lastLon = lastPos.getLongitude().getDegrees();
136 if (Math.signum(lon) != Math.signum(lastLon))
137 if (Math.abs(lon - lastLon) < 180)
139 // Crossing the zero longitude line too
140 maxWest = 0;
141 minEast = 0;
144 lastPos = pos;
146 // Mirror the two sectors - same longitude span
147 maxWest = minEast < -maxWest ? -minEast : maxWest;
148 minEast = minEast > -maxWest ? -maxWest : minEast;
150 sectors[0] = Sector.fromDegrees(minSouth, maxNorth, minEast, 180d); // East side
151 sectors[1] = Sector.fromDegrees(minSouth, maxNorth, -180d, maxWest); // West side
153 return sectors;
156 public void dispose()
158 tiles.clear();
161 public ArrayList<Sector> getSectors()
163 ArrayList<Sector> sectors = new ArrayList<Sector>();
164 for (TextureTile tile : this.tiles)
166 sectors.add(tile.getSector());
168 return sectors;
171 public Iterable<LatLon> getPositions()
173 return this.positions;
176 public void setPositions(Iterable<LatLon> positions)
178 this.replacePositions(positions);
179 this.clearTextureData();
182 public Paint getPaint()
184 return paint;
187 public void setPaint(Paint paint)
189 this.paint = paint;
190 this.clearTextureData();
193 public Color getBorderColor()
195 return borderColor;
198 public void setBorderColor(Color borderColor)
200 this.borderColor = borderColor;
201 this.clearTextureData();
204 public Dimension getTextureSize()
206 return textureSize;
209 public void setTextureSize(Dimension textureSize)
211 this.textureSize = textureSize;
212 this.clearTextureData();
215 public Stroke getStroke()
217 return stroke;
220 public void setStroke(Stroke stroke)
222 this.stroke = stroke;
223 this.clearTextureData();
226 public boolean isDrawBorder()
228 return drawBorder;
231 public void setDrawBorder(boolean drawBorder)
233 this.drawBorder = drawBorder;
234 this.clearTextureData();
237 public boolean isDrawInterior()
239 return drawInterior;
242 public void setDrawInterior(boolean drawInterior)
244 this.drawInterior = drawInterior;
245 this.clearTextureData();
248 public boolean isAntiAlias()
250 return antiAlias;
253 public void setAntiAlias(boolean antiAlias)
255 this.antiAlias = antiAlias;
256 this.clearTextureData();
259 public double getNumEdgeIntervalsPerDegree()
261 return numEdgeIntervalsPerDegree;
264 public void setNumEdgeIntervalsPerDegree(double numEdgeIntervals)
266 this.numEdgeIntervalsPerDegree = numEdgeIntervals;
267 this.clearTextureData();
270 private boolean intersects(Sector sector)
272 for (TextureTile tile : this.tiles)
274 if (tile.getSector().intersects(sector))
275 return true;
277 return false;
280 public void render(DrawContext dc)
282 this.globe = dc.getGlobe(); // retain the globe used, for potential subsequent move
284 if (this.tiles.size() == 0)
285 this.createTextureTiles();
287 if (!this.intersects(dc.getVisibleSector()))
288 return;
290 if (!this.tiles.get(0).isTextureInMemory(dc.getTextureCache()))
291 makeTextureData(dc, this.textureSize);
293 GL gl = dc.getGL();
295 gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT);
299 if (!dc.isPickingMode())
301 gl.glEnable(GL.GL_BLEND);
302 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
305 gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
306 gl.glEnable(GL.GL_CULL_FACE);
307 gl.glCullFace(GL.GL_BACK);
309 dc.getGeographicSurfaceTileRenderer().renderTiles(dc, this.tiles);
311 finally
313 gl.glPopAttrib();
317 private void makeTextureData(DrawContext dc, Dimension size)
319 for (TextureTile tile : this.tiles)
321 BufferedImage image = new BufferedImage((int) size.getWidth(), (int) size.getHeight(),
322 BufferedImage.TYPE_4BYTE_ABGR);
324 TextureData td = new TextureData(GL.GL_RGBA, GL.GL_RGBA, false,
325 this.drawShape(dc.getGlobe(), tile.getSector(), image));
326 td.setMustFlipVertically(false);
327 tile.setTextureData(td);
331 private void clearTextureData()
333 tiles.clear();
336 public Position getReferencePosition()
338 LatLon centroid = this.tiles.get(0).getSector().getCentroid();
339 return new Position(centroid, 0);
342 public void move(Position delta)
344 if (delta == null)
346 String msg = Logging.getMessage("nullValue.PositionIsNull");
347 Logging.logger().severe(msg);
348 throw new IllegalArgumentException(msg);
351 this.moveTo(this.getReferencePosition().add(delta));
355 * Move the shape over the sphereoid surface without maintaining its original azimuth -- its orientation relative to
356 * North.
358 * @param position the new position to move the shapes reference position to.
360 public void shiftTo(Position position)
362 if (this.globe == null)
363 return;
365 if (position == null)
367 String msg = Logging.getMessage("nullValue.PositionIsNull");
368 Logging.logger().severe(msg);
369 throw new IllegalArgumentException(msg);
372 Vec4 p1 = globe.computePointFromPosition(this.getReferencePosition().getLatitude(),
373 this.getReferencePosition().getLongitude(), 0);
374 Vec4 p2 = globe.computePointFromPosition(position.getLatitude(), position.getLongitude(), 0);
375 Vec4 delta = p2.subtract3(p1);
377 for (int i = 0; i < this.positions.size(); i++)
379 LatLon ll = this.positions.get(i);
380 Vec4 p = globe.computePointFromPosition(ll.getLatitude(), ll.getLongitude(), 0);
381 p = p.add3(delta);
382 Position pos = globe.computePositionFromPoint(p);
384 this.positions.set(i, new LatLon(pos.getLatitude(), pos.getLongitude()));
387 this.createTextureTiles();
391 * Move the shape over the sphereoid surface while maintaining its original azimuth -- its orientation relative to
392 * North.
394 * @param position the new position to move the shapes reference position to.
396 public void moveTo(Position position)
398 if (LatLon.positionsCrossDateLine(this.positions))
400 // TODO: Replace this hack by figuring out how to *accurately* move date-line crossing shapes using the
401 // distance/azimuth method used below for shapes that do not cross the dateline.
402 shiftTo(position);
403 return;
406 LatLon oldRef = this.getReferencePosition().getLatLon();
407 LatLon newRef = position.getLatLon();
409 for (int i = 0; i < this.positions.size(); i++)
411 LatLon p = this.positions.get(i);
412 double distance = LatLon.sphericalDistance(oldRef, p).radians;
413 double azimuth = LatLon.azimuth(oldRef, p).radians;
414 LatLon pp = LatLon.endPosition(newRef, azimuth, distance);
415 this.positions.set(i, pp);
418 this.createTextureTiles();
421 public static SurfaceShape createEllipse(Globe globe, LatLon center, double majorAxisLength,
422 double minorAxisLength, Angle orientation, int intervals, Color interiorColor, Color borderColor,
423 Dimension textureSize)
425 if (orientation == null)
426 orientation = Angle.ZERO;
428 if (majorAxisLength <= 0)
430 String message = Logging.getMessage("Geom.MajorAxisInvalid");
431 Logging.logger().severe(message);
432 throw new IllegalArgumentException(message);
435 if (minorAxisLength <= 0)
437 String message = Logging.getMessage("Geom.MajorAxisInvalid");
438 Logging.logger().severe(message);
439 throw new IllegalArgumentException(message);
442 int numPositions = 1 + Math.max(intervals, 4);
443 final ArrayList<LatLon> positions = new ArrayList<LatLon>();
445 double radius = globe.getRadiusAt(center.getLatitude(), center.getLongitude());
446 double da = 2 * Math.PI / (numPositions - 1);
447 for (int i = 0; i < numPositions; i++)
449 // azimuth runs positive clockwise from north and through 360ยก.
450 double angle = (i != numPositions - 1) ? i * da : 0;
451 double azimuth = Math.PI / 2 - (angle + orientation.radians);
452 double xLength = majorAxisLength * Math.cos(angle);
453 double yLength = minorAxisLength * Math.sin(angle);
454 double distance = Math.sqrt(xLength * xLength + yLength * yLength);
455 LatLon p = LatLon.endPosition(center, azimuth, distance / radius);
456 positions.add(p);
459 return new SurfacePolygon(positions, interiorColor, borderColor, textureSize);