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
.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
;
18 import java
.awt
.image
.*;
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
;
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
);
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()
90 // this.tiles.clear();
91 // if (!LatLon.positionsCrossDateLine(this.getPositions()))
94 // new TextureTile(this.computeProportionedSector(Sector.boundingSectorfromLatLons(this.getPositions()))));
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])));
104 protected void createTextureTiles()
107 if (!LatLon
.positionsCrossDateLine(this.getPositions()))
110 new TextureTile(Sector
.boundingSectorfromLatLons(this.getPositions())));
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
);
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
);
163 new Sector(sector
.getMinLatitude().subtract(southMargin
), sector
.getMaxLatitude().add(northMargin
),
164 sector
.getMinLongitude().subtract(westDelta
), sector
.getMaxLongitude().add(eastDelta
));
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
);
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
);
201 new Sector(sector
.getMinLatitude().subtract(southDelta
), sector
.getMaxLatitude().add(northDelta
),
202 sector
.getMinLongitude().subtract(westMargin
), sector
.getMaxLongitude().add(eastMargin
));
207 sector
= new Sector(sector
.getMinLatitude().subtract(halfDelta
), sector
.getMaxLatitude().add(halfDelta
),
208 sector
.getMinLongitude().subtract(westMargin
), sector
.getMaxLongitude().add(eastMargin
));
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());
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();
258 double lon
= pos
.getLongitude().getDegrees();
259 if (lon
<= 0 && lon
> maxWest
)
261 if (lon
>= 0 && lon
< minEast
)
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
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
287 public void dispose()
292 public ArrayList
<Sector
> getSectors()
294 ArrayList
<Sector
> sectors
= new ArrayList
<Sector
>();
295 for (TextureTile tile
: this.tiles
)
297 sectors
.add(tile
.getSector());
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()
318 public void setPaint(Paint paint
)
321 this.clearTextureData();
324 public Color
getBorderColor()
329 public void setBorderColor(Color borderColor
)
331 this.borderColor
= borderColor
;
332 this.clearTextureData();
335 public Dimension
getTextureSize()
340 public void setTextureSize(Dimension textureSize
)
342 this.textureSize
= textureSize
;
343 this.createTextureTiles(); // Rebuild tile(s) sectors
346 public Stroke
getStroke()
351 public void setStroke(Stroke stroke
)
353 this.stroke
= stroke
;
354 this.clearTextureData();
357 public boolean isDrawBorder()
362 public void setDrawBorder(boolean drawBorder
)
364 this.drawBorder
= drawBorder
;
365 this.clearTextureData();
368 public boolean isDrawInterior()
373 public void setDrawInterior(boolean drawInterior
)
375 this.drawInterior
= drawInterior
;
376 this.clearTextureData();
379 public boolean isAntiAlias()
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
))
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()))
421 if (!this.tiles
.get(0).isTextureInMemory(dc
.getTextureCache()))
422 makeTextureData(dc
, this.textureSize
);
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
);
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());
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()
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
)
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
493 * @param position the new position to move the shapes reference position to.
495 public void shiftTo(Position position
)
497 if (this.globe
== null)
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);
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
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.
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
);
594 return new SurfacePolygon(positions
, interiorColor
, borderColor
, textureSize
);