src.gov.nasa.worldwind.render.airspaces.Curtain Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind.render.airspaces;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.cache.Cacheable;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.geom.Box;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.util.*;
import javax.media.opengl.*;
import java.util.*;
/**
* A curtain is a series of adjacent rectangular planes. The upper edges of the planes are the connecting line segments
* between the vertices of a polyline. The lower edges of the planes are parallel to the upper edges at a specified
* altitude.
*
* @author tag
* @version $Id: Curtain.java 1171 2013-02-11 21:45:02Z dcollins $
*/
public class Curtain extends AbstractAirspace
{
protected List locations = new ArrayList();
protected String pathType = AVKey.GREAT_CIRCLE;
protected double splitThreshold = 2000.0; // 2 km
protected boolean applyPositionAltitude = false;
public Curtain(Iterable extends LatLon> locations)
{
this.addLocations(locations);
this.makeDefaultDetailLevels();
}
public Curtain(AirspaceAttributes attributes)
{
super(attributes);
this.makeDefaultDetailLevels();
}
public Curtain()
{
this.makeDefaultDetailLevels();
}
protected void makeDefaultDetailLevels()
{
List levels = new ArrayList();
double[] ramp = ScreenSizeDetailLevel.computeDefaultScreenSizeRamp(5);
DetailLevel level;
level = new ScreenSizeDetailLevel(ramp[0], "Detail-Level-0");
level.setValue(SPLIT_THRESHOLD, 1000.0);
level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
levels.add(level);
level = new ScreenSizeDetailLevel(ramp[1], "Detail-Level-1");
level.setValue(SPLIT_THRESHOLD, 2000.0);
level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
levels.add(level);
level = new ScreenSizeDetailLevel(ramp[2], "Detail-Level-2");
level.setValue(SPLIT_THRESHOLD, 10000.0);
level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
levels.add(level);
level = new ScreenSizeDetailLevel(ramp[3], "Detail-Level-3");
level.setValue(SPLIT_THRESHOLD, 100000.0);
level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
levels.add(level);
level = new ScreenSizeDetailLevel(ramp[4], "Detail-Level-4");
level.setValue(SPLIT_THRESHOLD, 1000000.0);
level.setValue(DISABLE_TERRAIN_CONFORMANCE, true);
levels.add(level);
this.setDetailLevels(levels);
}
/**
* Returns the curtain's locations.
*
* @return the curtain's locations in geographic coordinates.
*/
public Iterable getLocations()
{
return Collections.unmodifiableList(this.locations);
}
/**
* Sets the curtain's locations, in geographic coordinates.
*
* @param locations a list of geographic coordinates (latitude and longitude) specifying the upper edge of the
* shape.
*
* @throws IllegalArgumentException if the locations list is null or contains fewer than two points.
*/
public void setLocations(Iterable extends LatLon> locations)
{
this.locations.clear();
this.addLocations(locations);
}
protected void addLocations(Iterable extends LatLon> newLocations)
{
if (newLocations != null)
for (LatLon ll : newLocations)
{
if (ll != null)
this.locations.add(ll);
}
this.setExtentOutOfDate();
}
public String getPathType()
{
return this.pathType;
}
public void setPathType(String pathType)
{
if (pathType == null)
{
String message = "nullValue.PathTypeIsNull";
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.pathType = pathType;
this.setExtentOutOfDate();
}
public boolean isApplyPositionAltitude()
{
return applyPositionAltitude;
}
public void setApplyPositionAltitude(boolean applyPositionAltitude)
{
this.applyPositionAltitude = applyPositionAltitude;
}
public Position getReferencePosition()
{
return this.computeReferencePosition(this.locations, this.getAltitudes());
}
protected Extent computeExtent(Globe globe, double verticalExaggeration)
{
List points = this.computeMinimalGeometry(globe, verticalExaggeration);
if (points == null || points.isEmpty())
return null;
return Box.computeBoundingBox(points);
}
@Override
protected List computeMinimalGeometry(Globe globe, double verticalExaggeration)
{
ArrayList tessellatedLocations = new ArrayList();
this.makeTessellatedLocations(globe, tessellatedLocations);
if (tessellatedLocations.isEmpty())
return null;
ArrayList points = new ArrayList();
this.makeExtremePoints(globe, verticalExaggeration, tessellatedLocations, points);
return points;
}
protected void doMoveTo(Position oldRef, Position newRef)
{
if (oldRef == null)
{
String message = "nullValue.OldRefIsNull";
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (newRef == null)
{
String message = "nullValue.NewRefIsNull";
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
super.doMoveTo(oldRef, newRef);
int count = this.locations.size();
LatLon[] newLocations = new LatLon[count];
for (int i = 0; i < count; i++)
{
LatLon ll = this.locations.get(i);
double distance = LatLon.greatCircleDistance(oldRef, ll).radians;
double azimuth = LatLon.greatCircleAzimuth(oldRef, ll).radians;
newLocations[i] = LatLon.greatCircleEndPosition(newRef, azimuth, distance);
}
this.setLocations(Arrays.asList(newLocations));
}
protected double getSplitThreshold()
{
return this.splitThreshold;
}
protected void setSplitThreshold(double splitThreshold)
{
if (splitThreshold <= 0.0)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", "splitThreshold=" + splitThreshold);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.splitThreshold = splitThreshold;
}
//**************************************************************//
//******************** Geometry Rendering ********************//
//**************************************************************//
protected Vec4 computeReferenceCenter(DrawContext dc)
{
Extent extent = this.getExtent(dc);
return extent != null ? extent.getCenter() : null;
}
protected void doRenderGeometry(DrawContext dc, String drawStyle)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (dc.getGL() == null)
{
String message = Logging.getMessage("nullValue.DrawingContextGLIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
int count = locations.size();
LatLon[] locationArray = new LatLon[count];
this.locations.toArray(locationArray);
double[] altitudes = this.getAltitudes(dc.getVerticalExaggeration());
boolean[] terrainConformant = this.isTerrainConforming();
String pathType = this.getPathType();
double splitThreshold = this.splitThreshold;
if (this.isEnableLevelOfDetail())
{
DetailLevel level = this.computeDetailLevel(dc);
Object o = level.getValue(SPLIT_THRESHOLD);
if (o != null && o instanceof Double)
splitThreshold = (Double) o;
o = level.getValue(DISABLE_TERRAIN_CONFORMANCE);
if (o != null && o instanceof Boolean && (Boolean) o)
terrainConformant[0] = terrainConformant[1] = false;
}
Vec4 referenceCenter = this.computeReferenceCenter(dc);
this.setExpiryTime(this.nextExpiryTime(dc, terrainConformant));
this.clearElevationMap();
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
int[] lightModelTwoSide = new int[1];
try
{
gl.glGetIntegerv(GL2.GL_LIGHT_MODEL_TWO_SIDE, lightModelTwoSide, 0);
dc.getView().pushReferenceCenter(dc, referenceCenter);
if (Airspace.DRAW_STYLE_FILL.equals(drawStyle))
{
gl.glLightModeli(GL2.GL_LIGHT_MODEL_TWO_SIDE, GL2.GL_TRUE);
this.drawCurtainFill(dc, count, locationArray, pathType, splitThreshold, altitudes, terrainConformant,
referenceCenter);
}
else if (Airspace.DRAW_STYLE_OUTLINE.equals(drawStyle))
{
this.drawCurtainOutline(dc, count, locationArray, pathType, splitThreshold, altitudes,
terrainConformant, referenceCenter);
}
}
finally
{
dc.getView().popReferenceCenter(dc);
gl.glLightModeli(GL2.GL_LIGHT_MODEL_TWO_SIDE, lightModelTwoSide[0]);
}
}
//**************************************************************//
//******************** Curtain ********************//
//**************************************************************//
protected static class CurtainGeometry implements Cacheable
{
private Geometry fillIndexGeometry;
private Geometry outlineIndexGeometry;
private Geometry vertexGeometry;
public CurtainGeometry()
{
this.fillIndexGeometry = new Geometry();
this.outlineIndexGeometry = new Geometry();
this.vertexGeometry = new Geometry();
}
public Geometry getFillIndexGeometry()
{
return this.fillIndexGeometry;
}
public Geometry getOutlineIndexGeometry()
{
return this.outlineIndexGeometry;
}
public Geometry getVertexGeometry()
{
return this.vertexGeometry;
}
public long getSizeInBytes()
{
long sizeInBytes = 0L;
sizeInBytes += (this.fillIndexGeometry != null) ? this.fillIndexGeometry.getSizeInBytes() : 0L;
sizeInBytes += (this.outlineIndexGeometry != null) ? this.outlineIndexGeometry.getSizeInBytes() : 0L;
sizeInBytes += (this.vertexGeometry != null) ? this.vertexGeometry.getSizeInBytes() : 0L;
return sizeInBytes;
}
}
protected CurtainGeometry getCurtainGeometry(DrawContext dc, int count, LatLon[] locations, String pathType,
double splitThreshold,
double[] altitudes, boolean[] terrainConformant,
Vec4 referenceCenter)
{
Object cacheKey = new Geometry.CacheKey(dc.getGlobe(), this.getClass(), "Curtain",
locations, pathType, altitudes[0], altitudes[1], terrainConformant[0], terrainConformant[1],
splitThreshold, referenceCenter);
CurtainGeometry geom = (CurtainGeometry) this.getGeometryCache().getObject(cacheKey);
if (geom == null || this.isExpired(dc, geom.getVertexGeometry()))
{
if (geom == null)
geom = new CurtainGeometry();
this.makeCurtainGeometry(dc, count, locations, pathType, splitThreshold, altitudes, terrainConformant,
referenceCenter, geom);
this.updateExpiryCriteria(dc, geom.getVertexGeometry());
this.getGeometryCache().add(cacheKey, geom);
}
return geom;
}
protected void drawCurtainFill(DrawContext dc, int count, LatLon[] locations, String pathType,
double splitThreshold,
double[] altitudes, boolean[] terrainConformant,
Vec4 referenceCenter)
{
CurtainGeometry geom = this.getCurtainGeometry(dc, count, locations, pathType, splitThreshold,
altitudes, terrainConformant, referenceCenter);
this.getRenderer().drawGeometry(dc, geom.getFillIndexGeometry(), geom.getVertexGeometry());
}
protected void drawCurtainOutline(DrawContext dc, int count, LatLon[] locations, String pathType,
double splitThreshold,
double[] altitudes, boolean[] terrainConformant,
Vec4 referenceCenter)
{
CurtainGeometry geom = this.getCurtainGeometry(dc, count, locations, pathType, splitThreshold,
altitudes, terrainConformant, referenceCenter);
this.getRenderer().drawGeometry(dc, geom.getOutlineIndexGeometry(), geom.getVertexGeometry());
}
protected void makeCurtainGeometry(DrawContext dc, int count, LatLon[] locations, String pathType,
double splitThreshold,
double[] altitudes, boolean[] terrainConformant,
Vec4 referenceCenter,
CurtainGeometry dest)
{
int sections = count - 1;
int[] counts = new int[3];
SectionRenderInfo[] ri = new SectionRenderInfo[sections];
this.makeSectionInfo(dc, count, locations, pathType, splitThreshold, ri, counts);
int fillDrawMode = this.getSectionFillDrawMode();
int outlineDrawMode = this.getSectionOutlineDrawMode();
int[] fillIndices = new int[counts[0]];
int[] outlineIndices = new int[counts[1]];
float[] verts = new float[3 * counts[2]];
float[] norms = new float[3 * counts[2]];
for (int s = 0; s < sections; s++)
{
this.makeSectionFillIndices(ri[s].pillars, ri[s].firstVertex, ri[s].firstFillIndex, fillIndices);
this.makeSectionOutlineIndices(ri[s].pillars, ri[s].firstVertex, ri[s].firstOutlineIndex, outlineIndices);
this.makeSectionVertices(dc, ri[s].begin, ri[s].end, ri[s].pathType, altitudes, terrainConformant,
ri[s].pillars, ri[s].firstVertex, verts, referenceCenter);
this.getGeometryBuilder().makeIndexedTriangleStripNormals(ri[s].firstFillIndex, ri[s].fillIndexCount,
fillIndices, ri[s].firstVertex, ri[s].vertexCount, verts, norms);
}
dest.getFillIndexGeometry().setElementData(fillDrawMode, counts[0], fillIndices);
dest.getOutlineIndexGeometry().setElementData(outlineDrawMode, counts[1], outlineIndices);
dest.getVertexGeometry().setVertexData(counts[2], verts);
dest.getVertexGeometry().setNormalData(counts[2], norms);
}
//**************************************************************//
//******************** Section ********************//
//**************************************************************//
protected static class SectionRenderInfo
{
LatLon begin, end;
String pathType;
int pillars;
int firstVertex, vertexCount;
int firstFillIndex, fillIndexCount;
int firstOutlineIndex, outlineIndexCount;
private SectionRenderInfo(LatLon begin, LatLon end, String pathType)
{
this.begin = begin;
this.end = end;
this.pathType = pathType;
}
}
protected void makeSectionInfo(DrawContext dc, int count, LatLon[] locations, String pathType,
double splitThreshold,
SectionRenderInfo[] ri, int[] counts)
{
int sectionCount = count - 1;
for (int i = 0; i < sectionCount; i++)
{
ri[i] = new SectionRenderInfo(locations[i], locations[i + 1], pathType);
ri[i].pillars = this.getSectionPillarCount(dc, ri[i].begin, ri[i].end, ri[i].pathType, splitThreshold);
ri[i].firstFillIndex = counts[0];
ri[i].firstOutlineIndex = counts[1];
ri[i].firstVertex = counts[2];
ri[i].fillIndexCount = this.getSectionFillIndexCount(ri[i].pillars);
ri[i].outlineIndexCount = this.getSectionOutlineIndexCount(ri[i].pillars);
ri[i].vertexCount = this.getSectionVertexCount(ri[i].pillars);
counts[0] += ri[i].fillIndexCount;
counts[1] += ri[i].outlineIndexCount;
counts[2] += ri[i].vertexCount;
}
}
protected int getSectionPillarCount(DrawContext dc, LatLon begin, LatLon end, String pathType,
double splitThreshold)
{
Globe globe;
double arcLength, distance;
int pillars;
globe = dc.getGlobe();
if (AVKey.RHUMB_LINE.equalsIgnoreCase(pathType) || AVKey.LOXODROME.equalsIgnoreCase(pathType))
{
arcLength = LatLon.rhumbDistance(begin, end).radians;
}
else // (AVKey.GREAT_CIRCLE.equalsIgnoreCase(pathType)
{
arcLength = LatLon.greatCircleDistance(begin, end).radians;
}
distance = arcLength * globe.getRadius();
pillars = (int) Math.ceil(distance / splitThreshold) - 1;
pillars = Math.max(1, pillars);
return pillars;
}
protected int getSectionFillDrawMode()
{
return GL.GL_TRIANGLE_STRIP;
}
protected int getSectionOutlineDrawMode()
{
return GL.GL_LINES;
}
protected int getSectionFillIndexCount(int pillars)
{
return 2 * (pillars + 1);
}
protected int getSectionOutlineIndexCount(int pillars)
{
return 4 * (pillars + 1);
}
protected int getSectionVertexCount(int pillars)
{
return 2 * (pillars + 1);
}
protected void makeSectionFillIndices(int pillars, int vertexPos, int indexPos, int[] dest)
{
int p;
int index, vertex;
index = indexPos;
for (p = 0; p <= pillars; p++)
{
vertex = vertexPos + 2 * p;
dest[index++] = vertex + 1;
dest[index++] = vertex;
}
}
protected void makeSectionOutlineIndices(int pillars, int vertexPos, int indexPos, int[] dest)
{
int p;
int index, vertex;
index = indexPos;
vertex = vertexPos;
dest[index++] = vertex + 1;
dest[index++] = vertex;
for (p = 0; p < pillars; p++)
{
vertex = vertexPos + 2 * p;
dest[index++] = vertex;
dest[index++] = vertex + 2;
dest[index++] = vertex + 1;
dest[index++] = vertex + 3;
}
vertex = vertexPos + 2 * pillars;
dest[index++] = vertex + 1;
dest[index] = vertex;
}
protected void makeSectionVertices(DrawContext dc, LatLon begin, LatLon end, String pathType,
double[] altitude, boolean terrainConformant[],
int pillars, int vertexPos, float[] dest, Vec4 referenceCenter)
{
Globe globe = dc.getGlobe();
double arcLength, azimuth;
if (AVKey.RHUMB_LINE.equalsIgnoreCase(pathType) || AVKey.LOXODROME.equalsIgnoreCase(pathType))
{
arcLength = LatLon.rhumbDistance(begin, end).radians;
azimuth = LatLon.rhumbAzimuth(begin, end).radians;
}
else // (AVKey.GREAT_CIRCLE.equalsIgnoreCase(pathType)
{
arcLength = LatLon.greatCircleDistance(begin, end).radians;
azimuth = LatLon.greatCircleAzimuth(begin, end).radians;
}
double dlength = arcLength / (double) pillars;
// Set up to take altitude from the curtain positions if Positions are specified.
double alt0 = 0;
Double dAlt = null;
if (this.isApplyPositionAltitude() && begin instanceof Position && end instanceof Position)
{
alt0 = ((Position) begin).getAltitude();
dAlt = (((Position) end).getAltitude() - alt0) / (double) pillars;
}
for (int p = 0; p <= pillars; p++)
{
double length = p * dlength;
LatLon ll;
if (AVKey.RHUMB_LINE.equalsIgnoreCase(pathType) || AVKey.LOXODROME.equalsIgnoreCase(pathType))
ll = LatLon.rhumbEndPosition(begin, azimuth, length);
else // (AVKey.GREAT_CIRCLE.equalsIgnoreCase(pathType)
ll = LatLon.greatCircleEndPosition(begin, azimuth, length);
for (int s = 0; s < 2; s++)
{
int index = s + 2 * p;
index = 3 * (vertexPos + index);
// For upper altitude, use the Position's if specified, otherwise the curtain's upper altitude.
double elevation = (dAlt != null && s == 1) ? alt0 + p * dAlt : altitude[s];
if (terrainConformant[s])
elevation += this.computeElevationAt(dc, ll.getLatitude(), ll.getLongitude());
Vec4 vec = globe.computePointFromPosition(ll.getLatitude(), ll.getLongitude(), elevation);
dest[index] = (float) (vec.x - referenceCenter.x);
dest[index + 1] = (float) (vec.y - referenceCenter.y);
dest[index + 2] = (float) (vec.z - referenceCenter.z);
}
}
}
protected void makeTessellatedLocations(Globe globe, List tessellatedLocations)
{
if (this.getLocations() == null)
return;
Iterator iter = this.getLocations().iterator();
if (!iter.hasNext())
return;
LatLon locA = iter.next();
tessellatedLocations.add(locA); // Add the curtain's first location.
while (iter.hasNext())
{
LatLon locB = iter.next();
this.makeSegment(globe, locA, locB, tessellatedLocations);
locA = locB;
}
}
protected void makeSegment(Globe globe, LatLon locA, LatLon locB, List tessellatedLocations)
{
Angle segmentAzimuth;
Angle segmentDistance;
boolean isRhumbSegment = AVKey.RHUMB_LINE.equalsIgnoreCase(this.getPathType())
|| AVKey.LOXODROME.equalsIgnoreCase(this.getPathType());
if (isRhumbSegment)
{
segmentAzimuth = LatLon.rhumbAzimuth(locA, locB);
segmentDistance = LatLon.rhumbDistance(locA, locB);
}
else // Default to a great circle segment.
{
segmentAzimuth = LatLon.greatCircleAzimuth(locA, locB);
segmentDistance = LatLon.greatCircleDistance(locA, locB);
}
double arcLength = segmentDistance.radians * globe.getRadius();
if (arcLength <= this.getSplitThreshold())
{
tessellatedLocations.add(locB);
return;
}
int numSubsegments = (int) Math.ceil(arcLength / this.getSplitThreshold());
double segmentIncrement = segmentDistance.radians / (double) numSubsegments;
for (double s = 0; s < segmentDistance.radians; )
{
// If we've reached or passed the second location, then add the second location and break. We handle this
// case specially to ensure that the actual second location is added, instead of a computed location very
// close to it.
s += segmentIncrement;
if (s >= segmentDistance.radians)
{
tessellatedLocations.add(locB);
break;
}
LatLon ll;
if (isRhumbSegment)
{
ll = LatLon.rhumbEndPosition(locA, segmentAzimuth, Angle.fromRadians(s));
}
else // Default to a great circle segment.
{
ll = LatLon.greatCircleEndPosition(locA, segmentAzimuth, Angle.fromRadians(s));
}
tessellatedLocations.add(ll);
}
}
//**************************************************************//
//******************** END Geometry Rendering ****************//
//**************************************************************//
@Override
protected void doGetRestorableState(RestorableSupport rs, RestorableSupport.StateObject context)
{
super.doGetRestorableState(rs, context);
if (this.locations != null)
rs.addStateValueAsLatLonList(context, "locations", this.locations);
rs.addStateValueAsString(context, "pathType", this.getPathType());
}
@Override
protected void doRestoreState(RestorableSupport rs, RestorableSupport.StateObject context)
{
super.doRestoreState(rs, context);
List locations = rs.getStateValueAsLatLonList(context, "locations");
if (locations != null)
this.setLocations(locations);
String s = rs.getStateValueAsString(context, "pathType");
if (s != null)
this.setPathType(s);
}
}