Update to Worldwind release 0.4.1
[worldwind-tracker.git] / gov / nasa / worldwind / render / SurfaceShape.java
blobc8dbb2b5a4c563adde4284e52b494eb338f64417
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 3407 2007-10-28 08:44:01Z 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;
40 private static final double TEXTURE_MARGIN_PIXELS = 3;
42 private ArrayList<TextureTile> tiles = new ArrayList<TextureTile>();
43 private Dimension textureSize = DEFAULT_TEXTURE_SIZE;
44 protected Globe globe;
45 private Paint paint;
46 private Color borderColor;
47 private Stroke stroke = new BasicStroke();
48 private boolean drawBorder = true;
49 private boolean drawInterior = true;
50 private boolean antiAlias = true;
51 private double numEdgeIntervalsPerDegree = DEFAULT_NUM_EDGE_INTERVALS_PER_DEGREE;
52 protected ArrayList<LatLon> positions = new ArrayList<LatLon>();
54 protected abstract BufferedImage drawShape(Globe globe, Sector sector, BufferedImage image);
56 public SurfaceShape(Iterable<LatLon> positions, Color color, Color borderColor, Dimension textureSize)
58 if (positions == null)
60 String message = Logging.getMessage("nullValue.PositionsListIsNull");
61 Logging.logger().severe(message);
62 throw new IllegalArgumentException(message);
65 if (textureSize != null)
66 this.textureSize = textureSize;
68 // Set draw attributes
69 this.paint = color != null ? color : DEFAULT_COLOR;
70 this.borderColor = borderColor != null ? borderColor : DEFAULT_BORDER_COLOR;
72 // Copy positions list.
73 this.replacePositions(positions);
75 // Make tile(s)
76 createTextureTiles();
79 private void replacePositions(Iterable<LatLon> newPositions)
81 this.positions.clear();
82 for (LatLon position : newPositions)
84 this.positions.add(position);
88 // protected void createTextureTiles()
89 // {
90 // this.tiles.clear();
91 // if (!LatLon.positionsCrossDateLine(this.getPositions()))
92 // {
93 // this.tiles.add(
94 // new TextureTile(this.computeProportionedSector(Sector.boundingSectorfromLatLons(this.getPositions()))));
95 // }
96 // else
97 // {
98 // Sector[] sectors = this.computeSplitSectors(this.getPositions());
99 // this.tiles.add(new TextureTile(this.computeProportionedSector(sectors[0])));
100 // this.tiles.add(new TextureTile(this.computeProportionedSector(sectors[1])));
101 // }
102 // }
104 protected void createTextureTiles()
106 this.tiles.clear();
107 if (!LatLon.positionsCrossDateLine(this.getPositions()))
109 this.tiles.add(
110 new TextureTile(Sector.boundingSectorfromLatLons(this.getPositions())));
112 else
114 Sector[] sectors = this.computeSplitSectors(this.getPositions());
115 this.tiles.add(new TextureTile(sectors[0]));
116 this.tiles.add(new TextureTile(sectors[1]));
121 * Returns a sector that will have the same apparent proportions as the texture when projected on the globe, and
122 * that contains a given sector. Also adds a margin around.
124 * @param sector the sector to be included
125 * @return the appropriate sector
127 private Sector computeProportionedSector(Sector sector)
129 //if(true) return sector; // uncomment to disable
130 // Make it look the same aspect ratio as the texture - without going over the edges
131 // taking into account the sector centroid latitude distortion
132 Angle latSpan = sector.getDeltaLat();
133 Angle lonSpan = sector.getDeltaLon();
134 Double midLatCos = sector.getCentroid().getLatitude().cos();
135 Double aspectRatio = this.getTextureSize().getWidth() / this.getTextureSize().getHeight();
136 if (lonSpan.degrees / latSpan.degrees * midLatCos < aspectRatio)
138 // Adjust longitude extent
139 Angle halfDelta = latSpan.divide(midLatCos).multiply(aspectRatio).subtract(lonSpan).divide(2d);
140 if (halfDelta.degrees * 2 > 360 - lonSpan.degrees)
141 halfDelta = Angle.fromDegrees((360 - lonSpan.degrees) / 2);
142 // Add latitude margin
143 Angle latMargin = latSpan.divide(this.getTextureSize().getHeight()).multiply(TEXTURE_MARGIN_PIXELS);
144 Angle northMargin = sector.getMaxLatitude().add(latMargin).degrees > 90 ? Angle.fromDegrees(
145 90 - sector.getMaxLatitude().degrees) : latMargin;
146 Angle southMargin = sector.getMinLatitude().subtract(latMargin).degrees < -90 ? Angle.fromDegrees(
147 sector.getMinLatitude().degrees + 90) : latMargin;
148 if (sector.getMinLongitude().degrees - halfDelta.degrees < -180)
150 // -180 degrees longitude crossing
151 Angle westDelta = Angle.fromDegrees(sector.getMinLongitude().degrees + 180);
152 Angle eastDelta = halfDelta.add(halfDelta).subtract(westDelta);
153 sector =
154 new Sector(sector.getMinLatitude().subtract(southMargin), sector.getMaxLatitude().add(northMargin),
155 sector.getMinLongitude().subtract(westDelta), sector.getMaxLongitude().add(eastDelta));
157 else if (sector.getMaxLongitude().degrees + halfDelta.degrees > 180)
159 // +180 degrees longitude crossing
160 Angle eastDelta = Angle.fromDegrees(180 - sector.getMaxLongitude().degrees);
161 Angle westDelta = halfDelta.add(halfDelta).subtract(eastDelta);
162 sector =
163 new Sector(sector.getMinLatitude().subtract(southMargin), sector.getMaxLatitude().add(northMargin),
164 sector.getMinLongitude().subtract(westDelta), sector.getMaxLongitude().add(eastDelta));
166 else
168 // No edge crossing
169 sector =
170 new Sector(sector.getMinLatitude().subtract(southMargin), sector.getMaxLatitude().add(northMargin),
171 sector.getMinLongitude().subtract(halfDelta), sector.getMaxLongitude().add(halfDelta));
174 else if (lonSpan.degrees / latSpan.degrees * midLatCos > aspectRatio)
176 // Adjust latitude extent
177 Angle halfDelta = lonSpan.multiply(midLatCos).divide(aspectRatio).subtract(latSpan).divide(2d);
178 if (halfDelta.degrees * 2 > 180 - latSpan.degrees)
179 halfDelta = Angle.fromDegrees((180 - latSpan.degrees) / 2);
180 // Add longitude margin
181 Angle lonMargin = lonSpan.divide(this.getTextureSize().getWidth()).multiply(TEXTURE_MARGIN_PIXELS);
182 Angle eastMargin = sector.getMaxLongitude().add(lonMargin).degrees > 180 ? Angle.fromDegrees(
183 180 - sector.getMaxLongitude().degrees) : lonMargin;
184 Angle westMargin = sector.getMinLongitude().subtract(lonMargin).degrees < -180 ? Angle.fromDegrees(
185 sector.getMinLongitude().degrees + 180) : lonMargin;
186 if (sector.getMinLatitude().degrees - halfDelta.degrees < -90)
188 // -90 degrees latitude crossing
189 Angle southDelta = Angle.fromDegrees(sector.getMinLatitude().degrees + 90);
190 Angle northDelta = halfDelta.add(halfDelta).subtract(southDelta);
191 sector =
192 new Sector(sector.getMinLatitude().subtract(southDelta), sector.getMaxLatitude().add(northDelta),
193 sector.getMinLongitude().subtract(westMargin), sector.getMaxLongitude().add(eastMargin));
195 else if (sector.getMaxLatitude().degrees + halfDelta.degrees > 90)
197 // +90 degrees latitude crossing
198 Angle northDelta = Angle.fromDegrees(90 - sector.getMaxLatitude().degrees);
199 Angle southDelta = halfDelta.add(halfDelta).subtract(northDelta);
200 sector =
201 new Sector(sector.getMinLatitude().subtract(southDelta), sector.getMaxLatitude().add(northDelta),
202 sector.getMinLongitude().subtract(westMargin), sector.getMaxLongitude().add(eastMargin));
204 else
206 // No edge crossing
207 sector = new Sector(sector.getMinLatitude().subtract(halfDelta), sector.getMaxLatitude().add(halfDelta),
208 sector.getMinLongitude().subtract(westMargin), sector.getMaxLongitude().add(eastMargin));
211 else
213 // Proportions ok, just add margin
214 // Add latitude margin
215 Angle latMargin = latSpan.divide(this.getTextureSize().getHeight()).multiply(TEXTURE_MARGIN_PIXELS);
216 Angle northMargin = sector.getMaxLatitude().add(latMargin).degrees > 90 ? Angle.fromDegrees(
217 90 - sector.getMaxLatitude().degrees) : latMargin;
218 Angle southMargin = sector.getMinLatitude().subtract(latMargin).degrees < -90 ? Angle.fromDegrees(
219 sector.getMinLatitude().degrees + 90) : latMargin;
220 // Add longitude margin
221 Angle lonMargin = lonSpan.divide(this.getTextureSize().getWidth()).multiply(TEXTURE_MARGIN_PIXELS);
222 Angle eastMargin = sector.getMaxLongitude().add(lonMargin).degrees > 180 ? Angle.fromDegrees(
223 180 - sector.getMaxLongitude().degrees) : lonMargin;
224 Angle westMargin = sector.getMinLongitude().subtract(lonMargin).degrees < -180 ? Angle.fromDegrees(
225 sector.getMinLongitude().degrees + 180) : lonMargin;
226 sector = new Sector(sector.getMinLatitude().subtract(southMargin), sector.getMaxLatitude().add(northMargin),
227 sector.getMinLongitude().subtract(westMargin), sector.getMaxLongitude().add(eastMargin));
229 //System.out.println(sector.toString());
230 return sector;
234 * Returns two 'mirror' sectors each on one side of the longitude boundary - for boundary crossing shapes
236 * @param positions the shape positions
237 * @return an array of two sectors representing the shape
239 private Sector[] computeSplitSectors(Iterable<LatLon> positions)
241 Sector[] sectors = new Sector[2];
242 // Find out longitude extremes for each sides
243 double maxWest = Angle.NEG180.getDegrees();
244 double minEast = Angle.POS180.getDegrees();
245 // Find out absolute latitude extremes
246 double minSouth = Angle.POS90.getDegrees();
247 double maxNorth = Angle.NEG90.getDegrees();
249 LatLon lastPos = null;
250 for (LatLon pos : positions)
252 double lat = pos.getLatitude().getDegrees();
253 if (lat > maxNorth)
254 maxNorth = lat;
255 if (lat < minSouth)
256 minSouth = lat;
258 double lon = pos.getLongitude().getDegrees();
259 if (lon <= 0 && lon > maxWest)
260 maxWest = lon;
261 if (lon >= 0 && lon < minEast)
262 minEast = lon;
264 if (lastPos != null)
266 double lastLon = lastPos.getLongitude().getDegrees();
267 if (Math.signum(lon) != Math.signum(lastLon))
268 if (Math.abs(lon - lastLon) < 180)
270 // Crossing the zero longitude line too
271 maxWest = 0;
272 minEast = 0;
275 lastPos = pos;
277 // Mirror the two sectors - same longitude span
278 maxWest = minEast < -maxWest ? -minEast : maxWest;
279 minEast = minEast > -maxWest ? -maxWest : minEast;
281 sectors[0] = Sector.fromDegrees(minSouth, maxNorth, minEast, 180d); // East side
282 sectors[1] = Sector.fromDegrees(minSouth, maxNorth, -180d, maxWest); // West side
284 return sectors;
287 public void dispose()
289 tiles.clear();
292 public ArrayList<Sector> getSectors()
294 ArrayList<Sector> sectors = new ArrayList<Sector>();
295 for (TextureTile tile : this.tiles)
297 sectors.add(tile.getSector());
299 return sectors;
302 public Iterable<LatLon> getPositions()
304 return this.positions;
307 public void setPositions(Iterable<LatLon> positions)
309 this.replacePositions(positions);
310 this.createTextureTiles();
313 public Paint getPaint()
315 return paint;
318 public void setPaint(Paint paint)
320 this.paint = paint;
321 this.clearTextureData();
324 public Color getBorderColor()
326 return borderColor;
329 public void setBorderColor(Color borderColor)
331 this.borderColor = borderColor;
332 this.clearTextureData();
335 public Dimension getTextureSize()
337 return textureSize;
340 public void setTextureSize(Dimension textureSize)
342 this.textureSize = textureSize;
343 this.createTextureTiles(); // Rebuild tile(s) sectors
346 public Stroke getStroke()
348 return stroke;
351 public void setStroke(Stroke stroke)
353 this.stroke = stroke;
354 this.clearTextureData();
357 public boolean isDrawBorder()
359 return drawBorder;
362 public void setDrawBorder(boolean drawBorder)
364 this.drawBorder = drawBorder;
365 this.clearTextureData();
368 public boolean isDrawInterior()
370 return drawInterior;
373 public void setDrawInterior(boolean drawInterior)
375 this.drawInterior = drawInterior;
376 this.clearTextureData();
379 public boolean isAntiAlias()
381 return antiAlias;
384 public void setAntiAlias(boolean antiAlias)
386 this.antiAlias = antiAlias;
387 this.clearTextureData();
390 public double getNumEdgeIntervalsPerDegree()
392 return numEdgeIntervalsPerDegree;
395 public void setNumEdgeIntervalsPerDegree(double numEdgeIntervals)
397 this.numEdgeIntervalsPerDegree = numEdgeIntervals;
398 this.clearTextureData();
401 private boolean intersects(Sector sector)
403 for (TextureTile tile : this.tiles)
405 if (tile.getSector().intersects(sector))
406 return true;
408 return false;
411 public void render(DrawContext dc)
413 this.globe = dc.getGlobe(); // retain the globe used, for potential subsequent move
415 if (this.tiles.size() == 0)
416 this.createTextureTiles();
418 if (!this.intersects(dc.getVisibleSector()))
419 return;
421 if (!this.tiles.get(0).isTextureInMemory(dc.getTextureCache()))
422 makeTextureData(dc, this.textureSize);
424 GL gl = dc.getGL();
426 gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT);
430 if (!dc.isPickingMode())
432 gl.glEnable(GL.GL_BLEND);
433 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
436 gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
437 gl.glEnable(GL.GL_CULL_FACE);
438 gl.glCullFace(GL.GL_BACK);
440 dc.getGeographicSurfaceTileRenderer().renderTiles(dc, this.tiles);
442 finally
444 gl.glPopAttrib();
448 private void makeTextureData(DrawContext dc, Dimension size)
450 for (TextureTile tile : this.tiles)
452 BufferedImage image = new BufferedImage((int) size.getWidth(), (int) size.getHeight(),
453 BufferedImage.TYPE_4BYTE_ABGR);
454 /*// Debug - show tile extent with fill color
455 Graphics2D g2 = image.createGraphics();
456 g2.setPaint(this.getPaint());
457 g2.fillRect(0, 0, (int) size.getWidth(), (int) size.getHeight());
458 // end debug */
459 TextureData td = new TextureData(GL.GL_RGBA, GL.GL_RGBA, false,
460 this.drawShape(dc.getGlobe(), tile.getSector(), image));
461 td.setMustFlipVertically(false);
462 tile.setTextureData(td);
466 private void clearTextureData()
468 tiles.clear();
471 public Position getReferencePosition()
473 LatLon centroid = this.tiles.get(0).getSector().getCentroid();
474 return new Position(centroid, 0);
477 public void move(Position delta)
479 if (delta == null)
481 String msg = Logging.getMessage("nullValue.PositionIsNull");
482 Logging.logger().severe(msg);
483 throw new IllegalArgumentException(msg);
486 this.moveTo(this.getReferencePosition().add(delta));
490 * Move the shape over the sphereoid surface without maintaining its original azimuth -- its orientation relative to
491 * North.
493 * @param position the new position to move the shapes reference position to.
495 public void shiftTo(Position position)
497 if (this.globe == null)
498 return;
500 if (position == null)
502 String msg = Logging.getMessage("nullValue.PositionIsNull");
503 Logging.logger().severe(msg);
504 throw new IllegalArgumentException(msg);
507 Vec4 p1 = globe.computePointFromPosition(this.getReferencePosition().getLatitude(),
508 this.getReferencePosition().getLongitude(), 0);
509 Vec4 p2 = globe.computePointFromPosition(position.getLatitude(), position.getLongitude(), 0);
510 Vec4 delta = p2.subtract3(p1);
512 for (int i = 0; i < this.positions.size(); i++)
514 LatLon ll = this.positions.get(i);
515 Vec4 p = globe.computePointFromPosition(ll.getLatitude(), ll.getLongitude(), 0);
516 p = p.add3(delta);
517 Position pos = globe.computePositionFromPoint(p);
519 this.positions.set(i, new LatLon(pos.getLatitude(), pos.getLongitude()));
522 this.createTextureTiles();
526 * Move the shape over the sphereoid surface while maintaining its original azimuth -- its orientation relative to
527 * North.
529 * @param position the new position to move the shapes reference position to.
531 public void moveTo(Position position)
533 if (LatLon.positionsCrossDateLine(this.positions))
535 // TODO: Replace this hack by figuring out how to *accurately* move date-line crossing shapes using the
536 // distance/azimuth method used below for shapes that do not cross the dateline.
537 shiftTo(position);
538 return;
541 LatLon oldRef = this.getReferencePosition().getLatLon();
542 LatLon newRef = position.getLatLon();
544 for (int i = 0; i < this.positions.size(); i++)
546 LatLon p = this.positions.get(i);
547 double distance = LatLon.sphericalDistance(oldRef, p).radians;
548 double azimuth = LatLon.azimuth(oldRef, p).radians;
549 LatLon pp = LatLon.endPosition(newRef, azimuth, distance);
550 this.positions.set(i, pp);
553 this.createTextureTiles();
556 public static SurfaceShape createEllipse(Globe globe, LatLon center, double majorAxisLength,
557 double minorAxisLength, Angle orientation, int intervals, Color interiorColor, Color borderColor,
558 Dimension textureSize)
560 if (orientation == null)
561 orientation = Angle.ZERO;
563 if (majorAxisLength <= 0)
565 String message = Logging.getMessage("Geom.MajorAxisInvalid");
566 Logging.logger().severe(message);
567 throw new IllegalArgumentException(message);
570 if (minorAxisLength <= 0)
572 String message = Logging.getMessage("Geom.MajorAxisInvalid");
573 Logging.logger().severe(message);
574 throw new IllegalArgumentException(message);
577 int numPositions = 1 + Math.max(intervals, 4);
578 final ArrayList<LatLon> positions = new ArrayList<LatLon>();
580 double radius = globe.getRadiusAt(center.getLatitude(), center.getLongitude());
581 double da = 2 * Math.PI / (numPositions - 1);
582 for (int i = 0; i < numPositions; i++)
584 // azimuth runs positive clockwise from north and through 360ยก.
585 double angle = (i != numPositions - 1) ? i * da : 0;
586 double azimuth = Math.PI / 2 - (angle + orientation.radians);
587 double xLength = majorAxisLength * Math.cos(angle);
588 double yLength = minorAxisLength * Math.sin(angle);
589 double distance = Math.sqrt(xLength * xLength + yLength * yLength);
590 LatLon p = LatLon.endPosition(center, azimuth, distance / radius);
591 positions.add(p);
594 return new SurfacePolygon(positions, interiorColor, borderColor, textureSize);