All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.render.Polyline Maven / Gradle / Ivy

The newest version!
/*
 * 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;

import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.drag.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.geom.Cylinder;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.util.*;
import gov.nasa.worldwind.util.measure.LengthMeasurer;

import com.jogamp.opengl.*;
import java.awt.*;
import java.util.*;
import java.util.List;

/**
 * @author tag
 * @version $Id: Polyline.java 2188 2014-07-30 15:01:16Z tgaskins $
 * @deprecated Use {@link Path} instead.
 *             

* When drawn on a 2D globe, this shape uses either a {@link SurfacePolyline} or {@link SurfacePolygon} to * represent itself. */ public class Polyline extends AVListImpl implements Renderable, OrderedRenderable, Movable, Restorable, MeasurableLength, ExtentHolder, PreRenderable, Highlightable, Draggable { public final static int GREAT_CIRCLE = WorldWind.GREAT_CIRCLE; public final static int LINEAR = WorldWind.LINEAR; public final static int RHUMB_LINE = WorldWind.RHUMB_LINE; public final static int LOXODROME = RHUMB_LINE; public final static int ANTIALIAS_DONT_CARE = WorldWind.ANTIALIAS_DONT_CARE; public final static int ANTIALIAS_FASTEST = WorldWind.ANTIALIAS_FASTEST; public final static int ANTIALIAS_NICEST = WorldWind.ANTIALIAS_NICEST; protected ArrayList positions; protected Vec4 referenceCenterPoint; protected int antiAliasHint = GL.GL_FASTEST; protected Color color = Color.WHITE; protected double lineWidth = 1; protected boolean filled = false; // makes it a polygon protected boolean closed = false; // connect last point to first protected boolean followTerrain = false; protected double offset = 0; protected double terrainConformance = 10; protected int pathType = GREAT_CIRCLE; protected List> currentSpans; protected short stipplePattern = (short) 0xAAAA; protected int stippleFactor = 0; protected int numSubsegments = 10; protected boolean highlighted = false; protected boolean dragEnabled = true; protected DraggableSupport draggableSupport = null; protected Color highlightColor = new Color(1f, 1f, 1f, 0.5f); protected Object delegateOwner; protected LengthMeasurer measurer = new LengthMeasurer(); protected long geomGenTimeStamp = -Long.MAX_VALUE; protected double geomGenVE = 1; protected double eyeDistance; protected PickSupport pickSupport = new PickSupport(); protected long frameNumber = -1; // identifies frame used to calculate these values protected Layer pickLayer; protected SurfaceShape surfaceShape; // Manage an extent for each globe the polyline's associated with. protected static class ExtentInfo { // The extent depends on the state of the globe used to compute it, and the vertical exaggeration. protected Extent extent; protected double verticalExaggeration; protected Globe globe; protected Object globeStateKey; public ExtentInfo(Extent extent, DrawContext dc) { this.extent = extent; this.verticalExaggeration = dc.getVerticalExaggeration(); this.globe = dc.getGlobe(); this.globeStateKey = dc.getGlobe().getStateKey(dc); } protected boolean isValid(DrawContext dc) { return this.verticalExaggeration == dc.getVerticalExaggeration() && this.globe == dc.getGlobe() && globeStateKey.equals(dc.getGlobe().getStateKey(dc)); } } protected HashMap extents = new HashMap(2); // usually only 1, but few at most public Polyline() { this.setPositions(null); this.measurer.setFollowTerrain(this.followTerrain); this.measurer.setPathType(this.pathType); } public Polyline(Iterable positions) { this.setPositions(positions); this.measurer.setFollowTerrain(this.followTerrain); this.measurer.setPathType(this.pathType); } public Polyline(Iterable positions, double elevation) { this.setPositions(positions, elevation); this.measurer.setFollowTerrain(this.followTerrain); this.measurer.setPathType(this.pathType); } private void reset() { if (this.currentSpans != null) this.currentSpans.clear(); this.currentSpans = null; } public Color getColor() { return color; } public void setColor(Color color) { if (color == null) { String msg = Logging.getMessage("nullValue.ColorIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.color = color; if (this.surfaceShape != null) { ShapeAttributes attrs = this.surfaceShape.getAttributes(); attrs.setOutlineMaterial(new Material(this.color)); attrs.setOutlineOpacity(this.color.getAlpha() / 255.0); attrs.setInteriorMaterial(attrs.getOutlineMaterial()); attrs.setInteriorOpacity(attrs.getOutlineOpacity()); } } public int getAntiAliasHint() { return antiAliasHint; } public void setAntiAliasHint(int hint) { if (!(hint == ANTIALIAS_DONT_CARE || hint == ANTIALIAS_FASTEST || hint == ANTIALIAS_NICEST)) { String msg = Logging.getMessage("generic.InvalidHint"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.antiAliasHint = hint; } public boolean isFilled() { return filled; } public void setFilled(boolean filled) { if (this.surfaceShape != null && filled != this.filled) { if (filled) this.surfaceShape = new SurfacePolygon(this.getPositions()); else this.surfaceShape = new SurfacePolyline(this.getPositions()); this.setSurfaceShapeAttributes(); } this.filled = filled; } public int getPathType() { return pathType; } public String getPathTypeString() { return this.getPathType() == GREAT_CIRCLE ? AVKey.GREAT_CIRCLE : this.getPathType() == RHUMB_LINE ? AVKey.RHUMB_LINE : AVKey.LINEAR; } /** * Sets the type of path to draw, one of {@link #GREAT_CIRCLE}, which draws each segment of the path as a great * circle, {@link #LINEAR}, which determines the intermediate positions between segments by interpolating the * segment endpoints, or {@link #RHUMB_LINE}, which draws each segment of the path as a line of constant heading. * * @param pathType the type of path to draw. * * @see Path Types */ public void setPathType(int pathType) { this.reset(); this.pathType = pathType; this.measurer.setPathType(pathType); if (this.surfaceShape != null) this.surfaceShape.setPathType(this.pathType == GREAT_CIRCLE ? AVKey.GREAT_CIRCLE : pathType == RHUMB_LINE ? AVKey.RHUMB_LINE : AVKey.LINEAR); } /** * Sets the type of path to draw, one of {@link AVKey#GREAT_CIRCLE}, which draws each segment of the path as a great * circle, {@link AVKey#LINEAR}, which determines the intermediate positions between segments by interpolating the * segment endpoints, or {@link AVKey#RHUMB_LINE}, which draws each segment of the path as a line of constant * heading. * * @param pathType the type of path to draw. * * @see Path Types */ public void setPathType(String pathType) { if (pathType == null) { String msg = Logging.getMessage("nullValue.PathTypeIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.setPathType(pathType.equals(AVKey.GREAT_CIRCLE) ? GREAT_CIRCLE : pathType.equals(AVKey.RHUMB_LINE) || pathType.equals(AVKey.LOXODROME) ? RHUMB_LINE : LINEAR); } public boolean isFollowTerrain() { return followTerrain; } /** * Indicates whether the path should follow the terrain's surface. If the value is true, the elevation * values in this path's positions are ignored and the path is drawn on the terrain surface. Otherwise the path is * drawn according to the elevations given in the path's positions. If following the terrain, the path may also have * an offset. See {@link #setOffset(double)}; * * @param followTerrain true to follow the terrain, otherwise false. */ public void setFollowTerrain(boolean followTerrain) { this.reset(); this.followTerrain = followTerrain; this.measurer.setFollowTerrain(followTerrain); this.extents.clear(); } public double getOffset() { return offset; } /** * Specifies an offset, in meters, to add to the path points when the path's follow-terrain attribute is true. See * {@link #setFollowTerrain(boolean)}. * * @param offset the path pffset in meters. */ public void setOffset(double offset) { this.reset(); this.offset = offset; this.extents.clear(); } public double getTerrainConformance() { return terrainConformance; } /** * Specifies the precision to which the path follows the terrain when the follow-terrain attribute is true. The * conformance value indicates the approximate length of each sub-segment of the path as it's drawn, in pixels. * Lower values specify higher precision, but at the cost of performance. * * @param terrainConformance the path conformance in pixels. */ public void setTerrainConformance(double terrainConformance) { this.terrainConformance = terrainConformance; } public double getLineWidth() { return this.lineWidth; } public void setLineWidth(double lineWidth) { this.lineWidth = lineWidth; if (this.surfaceShape != null) this.surfaceShape.getAttributes().setOutlineWidth(this.getLineWidth()); } /** * Returns the length of the line as drawn. If the path follows the terrain, the length returned is the distance one * would travel if on the surface. If the path does not follow the terrain, the length returned is the distance * along the full length of the path at the path's elevations and current path type. * * @return the path's length in meters. */ public double getLength() { Iterator infos = this.extents.values().iterator(); return infos.hasNext() ? this.measurer.getLength(infos.next().globe) : 0; } public double getLength(Globe globe) { // The length measurer will throw an exception and log the error if globe is null return this.measurer.getLength(globe); } public LengthMeasurer getMeasurer() { return this.measurer; } public short getStipplePattern() { return stipplePattern; } /** * Sets the stipple pattern for specifying line types other than solid. See the OpenGL specification or programming * guides for a description of this parameter. Stipple is also affected by the path's stipple factor, {@link * #setStippleFactor(int)}. * * @param stipplePattern the stipple pattern. */ public void setStipplePattern(short stipplePattern) { this.stipplePattern = stipplePattern; if (this.surfaceShape != null) this.surfaceShape.getAttributes().setOutlineStipplePattern(this.stipplePattern); } public int getStippleFactor() { return stippleFactor; } /** * Sets the stipple factor for specifying line types other than solid. See the OpenGL specification or programming * guides for a description of this parameter. Stipple is also affected by the path's stipple pattern, {@link * #setStipplePattern(short)}. * * @param stippleFactor the stipple factor. */ public void setStippleFactor(int stippleFactor) { this.stippleFactor = stippleFactor; if (this.surfaceShape != null) this.surfaceShape.getAttributes().setOutlineStippleFactor(this.stippleFactor); } public int getNumSubsegments() { return numSubsegments; } /** * Specifies the number of intermediate segments to draw for each segment between positions. The end points of the * intermediate segments are calculated according to the current path type and follow-terrain setting. * * @param numSubsegments the number of intermediate subsegments. */ public void setNumSubsegments(int numSubsegments) { this.reset(); this.numSubsegments = numSubsegments; } public boolean isHighlighted() { return highlighted; } public void setHighlighted(boolean highlighted) { this.highlighted = highlighted; } public Color getHighlightColor() { return this.highlightColor; } public void setHighlightColor(Color highlightColor) { if (highlightColor == null) { String message = Logging.getMessage("nullValue.ColorIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } this.highlightColor = highlightColor; if (this.surfaceShape != null) this.surfaceShape.getHighlightAttributes().setOutlineMaterial(new Material(this.highlightColor)); } /** * Specifies the path's positions. * * @param inPositions the path positions. */ public void setPositions(Iterable inPositions) { this.reset(); this.positions = new ArrayList(); this.extents.clear(); if (inPositions != null) { for (Position position : inPositions) { this.positions.add(position); } this.measurer.setPositions(this.positions); if (this.surfaceShape != null) this.setSurfaceShapeLocations(); } if ((this.filled && this.positions.size() < 3)) { String msg = Logging.getMessage("generic.InsufficientPositions"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } } /** * Sets the paths positions as latitude and longitude values at a constant altitude. * * @param inPositions the latitudes and longitudes of the positions. * @param altitude the elevation to assign each position. */ public void setPositions(Iterable inPositions, double altitude) { this.reset(); this.positions = new ArrayList(); this.extents.clear(); if (inPositions != null) { for (LatLon position : inPositions) { this.positions.add(new Position(position, altitude)); } this.measurer.setPositions(this.positions); if (this.surfaceShape != null) this.setSurfaceShapeLocations(); } if (this.filled && this.positions.size() < 3) { String msg = Logging.getMessage("generic.InsufficientPositions"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } } public Iterable getPositions() { return this.positions; } public boolean isClosed() { return closed; } public void setClosed(boolean closed) { this.closed = closed; } /** * Returns the delegate owner of this Polyline. If non-null, the returned object replaces the Polyline as the * pickable object returned during picking. If null, the Polyline itself is the pickable object returned during * picking. * * @return the object used as the pickable object returned during picking, or null to indicate that the Polyline is * returned during picking. */ public Object getDelegateOwner() { return this.delegateOwner; } /** * Specifies the delegate owner of this Polyline. If non-null, the delegate owner replaces the Polyline as the * pickable object returned during picking. If null, the Polyline itself is the pickable object returned during * picking. * * @param owner the object to use as the pickable object returned during picking, or null to return the Polyline. */ public void setDelegateOwner(Object owner) { this.delegateOwner = owner; } /** * Returns this Polyline's enclosing volume as an {@link gov.nasa.worldwind.geom.Extent} in model coordinates, given * a specified {@link gov.nasa.worldwind.globes.Globe} and vertical exaggeration (see {@link * gov.nasa.worldwind.SceneController#getVerticalExaggeration()}. * * @param globe the Globe this Polyline is related to. * @param verticalExaggeration the vertical exaggeration to apply. * * @return this Polyline's Extent in model coordinates. * * @throws IllegalArgumentException if the Globe is null. */ public Extent getExtent(Globe globe, double verticalExaggeration) { if (globe == null) { String message = Logging.getMessage("nullValue.GlobeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return this.computeExtent(globe, verticalExaggeration); } /** * Returns this Polyline's enclosing volume as an {@link gov.nasa.worldwind.geom.Extent} in model coordinates, given * a specified {@link gov.nasa.worldwind.render.DrawContext}. The returned Extent may be different than the Extent * returned by calling {@link #getExtent(gov.nasa.worldwind.globes.Globe, double)} with the DrawContext's Globe and * vertical exaggeration. Additionally, this may cache the computed extent and is therefore potentially faster than * calling {@link #getExtent(gov.nasa.worldwind.globes.Globe, double)}. * * @param dc the current DrawContext. * * @return this Polyline's Extent in model coordinates. * * @throws IllegalArgumentException if the DrawContext is null, or if the Globe held by the DrawContext is null. */ public Extent getExtent(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (dc.getGlobe() == null) { String message = Logging.getMessage("nullValue.DrawingContextGlobeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } ExtentInfo extentInfo = this.extents.get(dc.getGlobe()); if (extentInfo != null && extentInfo.isValid(dc)) { return extentInfo.extent; } else { extentInfo = new ExtentInfo(this.computeExtent(dc), dc); this.extents.put(dc.getGlobe(), extentInfo); return extentInfo.extent; } } protected Extent computeExtent(Globe globe, double verticalExaggeration) { Sector sector = Sector.boundingSector(this.getPositions()); double[] minAndMaxElevations; if (this.isFollowTerrain()) { minAndMaxElevations = globe.getMinAndMaxElevations(sector); } else { minAndMaxElevations = computeElevationExtremes(this.getPositions()); } minAndMaxElevations[0] += this.getOffset(); minAndMaxElevations[1] += this.getOffset(); return Sector.computeBoundingBox(globe, verticalExaggeration, sector, minAndMaxElevations[0], minAndMaxElevations[1]); } protected Extent computeExtent(DrawContext dc) { return this.computeExtent(dc.getGlobe(), dc.getVerticalExaggeration()); } protected static double[] computeElevationExtremes(Iterable positions) { double[] extremes = new double[] {Double.MAX_VALUE, -Double.MAX_VALUE}; for (Position pos : positions) { if (extremes[0] > pos.getElevation()) extremes[0] = pos.getElevation(); // min if (extremes[1] < pos.getElevation()) extremes[1] = pos.getElevation(); // max } return extremes; } public double getDistanceFromEye() { return this.eyeDistance; } protected void setSurfaceShapeAttributes() { ShapeAttributes attrs = new BasicShapeAttributes(); attrs.setOutlineMaterial(new Material(this.color)); attrs.setOutlineOpacity(this.color.getAlpha() / 255.0); attrs.setInteriorMaterial(attrs.getOutlineMaterial()); attrs.setInteriorOpacity(attrs.getOutlineOpacity()); attrs.setOutlineWidth(this.getLineWidth()); attrs.setOutlineStipplePattern(this.stipplePattern); attrs.setOutlineStippleFactor(this.stippleFactor); this.surfaceShape.setAttributes(attrs); attrs = new BasicShapeAttributes(attrs); attrs.setOutlineMaterial(new Material(this.highlightColor)); attrs.setInteriorMaterial(attrs.getOutlineMaterial()); this.surfaceShape.setHighlightAttributes(attrs); } protected void setSurfaceShapeLocations() { Iterable locations; if (!this.isClosed()) { locations = this.getPositions(); } else { ArrayList temp = new ArrayList(); Position firstPosition = null; for (Position pos : this.getPositions()) { temp.add(pos); if (firstPosition == null) firstPosition = pos; } temp.add(firstPosition); locations = temp; } if (this.isFilled()) ((SurfacePolygon) this.surfaceShape).setLocations(locations); else ((SurfacePolyline) this.surfaceShape).setLocations(locations); } public void preRender(DrawContext dc) { if (dc.is2DGlobe()) { if (this.surfaceShape == null) { if (this.isFilled()) this.surfaceShape = new SurfacePolygon(); else this.surfaceShape = new SurfacePolyline(); this.setSurfaceShapeLocations(); this.setSurfaceShapeAttributes(); this.surfaceShape.setPathType(this.pathType == GREAT_CIRCLE ? AVKey.GREAT_CIRCLE : pathType == RHUMB_LINE ? AVKey.RHUMB_LINE : AVKey.LINEAR); } this.surfaceShape.setHighlighted(this.isHighlighted()); Object o = this.getDelegateOwner(); this.surfaceShape.setDelegateOwner(o != null ? o : this); this.surfaceShape.preRender(dc); } } public void pick(DrawContext dc, Point pickPoint) { // This method is called only when ordered renderables are being drawn. // Arg checked within call to render. this.pickSupport.clearPickList(); try { this.pickSupport.beginPicking(dc); this.render(dc); } finally { this.pickSupport.endPicking(dc); this.pickSupport.resolvePick(dc, pickPoint, this.pickLayer); } } public void render(DrawContext dc) { // This render method is called three times during frame generation. It's first called as a {@link Renderable} // during Renderable picking. It's called again during normal rendering. And it's called a third // time as an OrderedRenderable. The first two calls determine whether to add the polyline to the ordered // renderable list during pick and render. The third call just draws the ordered renderable. if (dc == null) { String msg = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (dc.getSurfaceGeometry() == null) return; if (dc.is2DGlobe() && this.surfaceShape != null) { this.surfaceShape.render(dc); return; } this.draw(dc); } /** * If the scene controller is rendering ordered renderables, this method draws this placemark's image as an ordered * renderable. Otherwise the method determines whether this instance should be added to the ordered renderable * list. *

* The Cartesian and screen points of the placemark are computed during the first call per frame and re-used in * subsequent calls of that frame. * * @param dc the current draw context. */ protected void draw(DrawContext dc) { if (dc.isOrderedRenderingMode()) { this.drawOrderedRenderable(dc); return; } // The rest of the code in this method determines whether to queue an ordered renderable for the polyline. if (this.positions.size() < 2) return; // vertices potentially computed every frame to follow terrain changes if (this.currentSpans == null || (this.followTerrain && this.geomGenTimeStamp != dc.getFrameTimeStamp()) || this.geomGenVE != dc.getVerticalExaggeration()) { // Reference center must be computed prior to computing vertices. this.computeReferenceCenter(dc); this.eyeDistance = this.referenceCenterPoint.distanceTo3(dc.getView().getEyePoint()); this.makeVertices(dc); this.geomGenTimeStamp = dc.getFrameTimeStamp(); this.geomGenVE = dc.getVerticalExaggeration(); } if (this.currentSpans == null || this.currentSpans.size() < 1) return; if (this.intersectsFrustum(dc)) { if (dc.isPickingMode()) this.pickLayer = dc.getCurrentLayer(); dc.addOrderedRenderable(this); // add the ordered renderable } } protected void drawOrderedRenderable(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. int attrBits = GL2.GL_HINT_BIT | GL2.GL_CURRENT_BIT | GL2.GL_LINE_BIT; if (!dc.isPickingMode()) { if (this.color.getAlpha() != 255) attrBits |= GL.GL_COLOR_BUFFER_BIT; } gl.glPushAttrib(attrBits); dc.getView().pushReferenceCenter(dc, this.referenceCenterPoint); boolean projectionOffsetPushed = false; // keep track for error recovery try { if (!dc.isPickingMode()) { if (this.color.getAlpha() != 255) { gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); } gl.glColor4ub((byte) this.color.getRed(), (byte) this.color.getGreen(), (byte) this.color.getBlue(), (byte) this.color.getAlpha()); } else { // We cannot depend on the layer to set a pick color for us because this Polyline is picked during ordered // rendering. Therefore we set the pick color ourselves. Color pickColor = dc.getUniquePickColor(); Object userObject = this.getDelegateOwner() != null ? this.getDelegateOwner() : this; this.pickSupport.addPickableObject(pickColor.getRGB(), userObject, null); gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue()); } if (this.stippleFactor > 0) { gl.glEnable(GL2.GL_LINE_STIPPLE); gl.glLineStipple(this.stippleFactor, this.stipplePattern); } else { gl.glDisable(GL2.GL_LINE_STIPPLE); } int hintAttr = GL2.GL_LINE_SMOOTH_HINT; if (this.filled) hintAttr = GL2.GL_POLYGON_SMOOTH_HINT; gl.glHint(hintAttr, this.antiAliasHint); int primType = GL2.GL_LINE_STRIP; if (this.filled) primType = GL2.GL_POLYGON; if (dc.isPickingMode()) gl.glLineWidth((float) this.getLineWidth() + 8); else gl.glLineWidth((float) this.getLineWidth()); if (this.followTerrain) { dc.pushProjectionOffest(0.99); projectionOffsetPushed = true; } if (this.currentSpans == null) return; for (List span : this.currentSpans) { if (span == null) continue; // Since segments can very often be very short -- two vertices -- use explicit rendering. The // overhead of batched rendering, e.g., gl.glDrawArrays, is too high because it requires copying // the vertices into a DoubleBuffer, and DoubleBuffer creation and access performs relatively poorly. gl.glBegin(primType); for (Vec4 p : span) { gl.glVertex3d(p.x, p.y, p.z); } gl.glEnd(); } if (this.isHighlighted()) { if (!dc.isPickingMode()) { if (this.highlightColor.getAlpha() != 255) { gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); } gl.glColor4ub((byte) this.highlightColor.getRed(), (byte) this.highlightColor.getGreen(), (byte) this.highlightColor.getBlue(), (byte) this.highlightColor.getAlpha()); gl.glLineWidth((float) this.getLineWidth() + 2); for (List span : this.currentSpans) { if (span == null) continue; gl.glBegin(primType); for (Vec4 p : span) { gl.glVertex3d(p.x, p.y, p.z); } gl.glEnd(); } } } } finally { if (projectionOffsetPushed) dc.popProjectionOffest(); gl.glPopAttrib(); dc.getView().popReferenceCenter(dc); } } /** * Indicates whether the shape is visible in the current view. * * @param dc the draw context. * * @return true if the shape is visible, otherwise false. */ protected boolean intersectsFrustum(DrawContext dc) { Extent extent = this.getExtent(dc); if (extent == null) return true; // don't know the visibility, shape hasn't been computed yet if (dc.isPickingMode()) return dc.getPickFrustums().intersectsAny(extent); return dc.getView().getFrustumInModelCoordinates().intersects(extent); } protected void makeVertices(DrawContext dc) { if (this.currentSpans == null) this.currentSpans = new ArrayList>(); else this.currentSpans.clear(); if (this.positions.size() < 1) return; Position posA = this.positions.get(0); Vec4 ptA = this.computePoint(dc, posA, true); for (int i = 1; i <= this.positions.size(); i++) { Position posB; if (i < this.positions.size()) posB = this.positions.get(i); else if (this.closed) posB = this.positions.get(0); else break; Vec4 ptB = this.computePoint(dc, posB, true); if (this.followTerrain && !this.isSegmentVisible(dc, posA, posB, ptA, ptB)) { posA = posB; ptA = ptB; continue; } ArrayList span; span = this.makeSegment(dc, posA, posB, ptA, ptB); if (span != null) this.addSpan(span); posA = posB; ptA = ptB; } } protected void addSpan(ArrayList span) { if (span != null && span.size() > 0) this.currentSpans.add(span); } protected boolean isSegmentVisible(DrawContext dc, Position posA, Position posB, Vec4 ptA, Vec4 ptB) { Frustum f = dc.getView().getFrustumInModelCoordinates(); if (f.contains(ptA)) return true; if (f.contains(ptB)) return true; if (ptA.equals(ptB)) return false; Position posC = Position.interpolateRhumb(0.5, posA, posB); Vec4 ptC = this.computePoint(dc, posC, true); if (f.contains(ptC)) return true; double r = Line.distanceToSegment(ptA, ptB, ptC); Cylinder cyl = new Cylinder(ptA, ptB, r == 0 ? 1 : r); return cyl.intersects(dc.getView().getFrustumInModelCoordinates()); } protected Vec4 computePoint(DrawContext dc, Position pos, boolean applyOffset) { if (this.followTerrain) { double height = !applyOffset ? 0 : this.offset; // computeTerrainPoint will apply vertical exaggeration return dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), height); } else { double height = pos.getElevation() + (applyOffset ? this.offset : 0); return dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), height * dc.getVerticalExaggeration()); } } protected double computeSegmentLength(DrawContext dc, Position posA, Position posB) { LatLon llA = new LatLon(posA.getLatitude(), posA.getLongitude()); LatLon llB = new LatLon(posB.getLatitude(), posB.getLongitude()); Angle ang = LatLon.greatCircleDistance(llA, llB); if (this.followTerrain) { return ang.radians * (dc.getGlobe().getRadius() + this.offset * dc.getVerticalExaggeration()); } else { double height = this.offset + 0.5 * (posA.getElevation() + posB.getElevation()); return ang.radians * (dc.getGlobe().getRadius() + height * dc.getVerticalExaggeration()); } } protected ArrayList makeSegment(DrawContext dc, Position posA, Position posB, Vec4 ptA, Vec4 ptB) { ArrayList span = null; double arcLength = this.computeSegmentLength(dc, posA, posB); if (arcLength <= 0) // points differing only in altitude { span = this.addPointToSpan(ptA, span); if (!ptA.equals(ptB)) span = this.addPointToSpan(ptB, span); return span; } // Variables for great circle and rhumb computation. Angle segmentAzimuth = null; Angle segmentDistance = null; for (double s = 0, p = 0; s < 1; ) { if (this.followTerrain) p += this.terrainConformance * dc.getView().computePixelSizeAtDistance( ptA.distanceTo3(dc.getView().getEyePoint())); else p += arcLength / this.numSubsegments; s = p / arcLength; Position pos; if (s >= 1) { pos = posB; } else if (this.pathType == LINEAR) { if (segmentAzimuth == null) { segmentAzimuth = LatLon.linearAzimuth(posA, posB); segmentDistance = LatLon.linearDistance(posA, posB); } Angle distance = Angle.fromRadians(s * segmentDistance.radians); LatLon latLon = LatLon.linearEndPosition(posA, segmentAzimuth, distance); pos = new Position(latLon, (1 - s) * posA.getElevation() + s * posB.getElevation()); } else if (this.pathType == RHUMB_LINE) // or LOXODROME (note that loxodrome is translated to RHUMB_LINE in setPathType) { if (segmentAzimuth == null) { segmentAzimuth = LatLon.rhumbAzimuth(posA, posB); segmentDistance = LatLon.rhumbDistance(posA, posB); } Angle distance = Angle.fromRadians(s * segmentDistance.radians); LatLon latLon = LatLon.rhumbEndPosition(posA, segmentAzimuth, distance); pos = new Position(latLon, (1 - s) * posA.getElevation() + s * posB.getElevation()); } else // GREAT_CIRCLE { if (segmentAzimuth == null) { segmentAzimuth = LatLon.greatCircleAzimuth(posA, posB); segmentDistance = LatLon.greatCircleDistance(posA, posB); } Angle distance = Angle.fromRadians(s * segmentDistance.radians); LatLon latLon = LatLon.greatCircleEndPosition(posA, segmentAzimuth, distance); pos = new Position(latLon, (1 - s) * posA.getElevation() + s * posB.getElevation()); } ptB = this.computePoint(dc, pos, true); span = this.clipAndAdd(dc, ptA, ptB, span); ptA = ptB; } return span; } @SuppressWarnings({"UnusedDeclaration"}) protected ArrayList clipAndAdd(DrawContext dc, Vec4 ptA, Vec4 ptB, ArrayList span) { // Line clipping appears to be useful only for long lines with few segments. It's costly otherwise. // TODO: Investigate trade-off of line clipping. // if (Line.clipToFrustum(ptA, ptB, dc.getView().getFrustumInModelCoordinates()) == null) // { // if (span != null) // { // this.addSpan(span); // span = null; // } // return span; // } if (span == null) span = this.addPointToSpan(ptA, span); return this.addPointToSpan(ptB, span); } protected ArrayList addPointToSpan(Vec4 p, ArrayList span) { if (span == null) span = new ArrayList(); span.add(p.subtract3(this.referenceCenterPoint)); return span; } protected void computeReferenceCenter(DrawContext dc) { // The reference position is null if this Polyline has no positions. In this case computing the Polyline's // Cartesian reference point is meaningless because the Polyline has no geographic location. Therefore we exit // without updating the reference point. Position refPos = this.getReferencePosition(); if (refPos == null) return; this.referenceCenterPoint = dc.computeTerrainPoint(refPos.getLatitude(), refPos.getLongitude(), this.offset); } public Position getReferencePosition() { if (this.positions.size() < 1) { return null; } else if (this.positions.size() < 3) { return this.positions.get(0); } else { return this.positions.get(this.positions.size() / 2); } } public void move(Position delta) { if (delta == null) { String msg = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } Position refPos = this.getReferencePosition(); // The reference position is null if this Polyline has no positions. In this case moving the Polyline by a // relative delta is meaningless because the Polyline has no geographic location. Therefore we fail softly by // exiting and doing nothing. if (refPos == null) return; this.moveTo(refPos.add(delta)); } public void moveTo(Position position) { if (position == null) { String msg = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.reset(); this.extents.clear(); Position oldRef = this.getReferencePosition(); // The reference position is null if this Polyline has no positions. In this case moving the Polyline to a new // reference position is meaningless because the Polyline has no geographic location. Therefore we fail softly // by exiting and doing nothing. if (oldRef == null) return; double elevDelta = position.getElevation() - oldRef.getElevation(); for (int i = 0; i < this.positions.size(); i++) { Position pos = this.positions.get(i); Angle distance = LatLon.greatCircleDistance(oldRef, pos); Angle azimuth = LatLon.greatCircleAzimuth(oldRef, pos); LatLon newLocation = LatLon.greatCircleEndPosition(position, azimuth, distance); double newElev = pos.getElevation() + elevDelta; this.positions.set(i, new Position(newLocation, newElev)); } } @Override public boolean isDragEnabled() { return this.dragEnabled; } @Override public void setDragEnabled(boolean enabled) { this.dragEnabled = enabled; } @Override public void drag(DragContext dragContext) { if (!this.dragEnabled) return; if (this.draggableSupport == null) this.draggableSupport = new DraggableSupport(this, this.isFollowTerrain() ? WorldWind.RELATIVE_TO_GROUND : WorldWind.ABSOLUTE); this.doDrag(dragContext); } protected void doDrag(DragContext dragContext) { this.draggableSupport.dragGlobeSizeConstant(dragContext); } /** * Returns an XML state document String describing the public attributes of this Polyline. * * @return XML state document string describing this Polyline. */ public String getRestorableState() { RestorableSupport rs = RestorableSupport.newRestorableSupport(); // Creating a new RestorableSupport failed. RestorableSupport logged the problem, so just return null. if (rs == null) return null; if (this.color != null) { String encodedColor = RestorableSupport.encodeColor(this.color); if (encodedColor != null) rs.addStateValueAsString("color", encodedColor); } if (this.highlightColor != null) { String encodedColor = RestorableSupport.encodeColor(this.highlightColor); if (encodedColor != null) rs.addStateValueAsString("highlightColor", encodedColor); } if (this.positions != null) { // Create the base "positions" state object. RestorableSupport.StateObject positionsStateObj = rs.addStateObject("positions"); if (positionsStateObj != null) { for (Position p : this.positions) { // Save each position only if all parts (latitude, longitude, and elevation) can be // saved. We will not save a partial iconPosition (for example, just the elevation). if (p != null && p.getLatitude() != null && p.getLongitude() != null) { // Create a nested "position" element underneath the base "positions". RestorableSupport.StateObject pStateObj = rs.addStateObject(positionsStateObj, "position"); if (pStateObj != null) { rs.addStateValueAsDouble(pStateObj, "latitudeDegrees", p.getLatitude().degrees); rs.addStateValueAsDouble(pStateObj, "longitudeDegrees", p.getLongitude().degrees); rs.addStateValueAsDouble(pStateObj, "elevation", p.getElevation()); } } } } } rs.addStateValueAsInteger("antiAliasHint", this.antiAliasHint); rs.addStateValueAsBoolean("filled", this.filled); rs.addStateValueAsBoolean("closed", this.closed); rs.addStateValueAsBoolean("highlighted", this.isHighlighted()); rs.addStateValueAsInteger("pathType", this.pathType); rs.addStateValueAsBoolean("followTerrain", this.followTerrain); rs.addStateValueAsDouble("offset", this.offset); rs.addStateValueAsDouble("terrainConformance", this.terrainConformance); rs.addStateValueAsDouble("lineWidth", this.getLineWidth()); rs.addStateValueAsInteger("stipplePattern", this.stipplePattern); rs.addStateValueAsInteger("stippleFactor", this.stippleFactor); rs.addStateValueAsInteger("numSubsegments", this.numSubsegments); RestorableSupport.StateObject so = rs.addStateObject(null, "avlist"); for (Map.Entry avp : this.getEntries()) { this.getRestorableStateForAVPair(avp.getKey(), avp.getValue() != null ? avp.getValue() : "", rs, so); } return rs.getStateAsXml(); } /** * Restores publicly settable attribute values found in the specified XML state document String. The document * specified by stateInXml must be a well formed XML document String, or this will throw an * IllegalArgumentException. Unknown structures in stateInXml are benign, because they will simply be * ignored. * * @param stateInXml an XML document String describing a Polyline. * * @throws IllegalArgumentException If stateInXml is null, or if stateInXml is not a well * formed XML document String. */ public void restoreState(String stateInXml) { if (stateInXml == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } RestorableSupport restorableSupport; try { restorableSupport = RestorableSupport.parse(stateInXml); } catch (Exception e) { // Parsing the document specified by stateInXml failed. String message = Logging.getMessage("generic.ExceptionAttemptingToParseStateXml", stateInXml); Logging.logger().severe(message); throw new IllegalArgumentException(message, e); } String colorState = restorableSupport.getStateValueAsString("color"); if (colorState != null) { Color color = RestorableSupport.decodeColor(colorState); if (color != null) setColor(color); } colorState = restorableSupport.getStateValueAsString("highlightColor"); if (colorState != null) { Color color = RestorableSupport.decodeColor(colorState); if (color != null) setHighlightColor(color); } // Get the base "positions" state object. RestorableSupport.StateObject positionsStateObj = restorableSupport.getStateObject("positions"); if (positionsStateObj != null) { ArrayList newPositions = new ArrayList(); // Get the nested "position" states beneath the base "positions". RestorableSupport.StateObject[] positionStateArray = restorableSupport.getAllStateObjects(positionsStateObj, "position"); if (positionStateArray != null && positionStateArray.length != 0) { for (RestorableSupport.StateObject pStateObj : positionStateArray) { if (pStateObj != null) { // Restore each position only if all parts are available. // We will not restore a partial position (for example, just the elevation). Double latitudeState = restorableSupport.getStateValueAsDouble(pStateObj, "latitudeDegrees"); Double longitudeState = restorableSupport.getStateValueAsDouble(pStateObj, "longitudeDegrees"); Double elevationState = restorableSupport.getStateValueAsDouble(pStateObj, "elevation"); if (latitudeState != null && longitudeState != null && elevationState != null) newPositions.add(Position.fromDegrees(latitudeState, longitudeState, elevationState)); } } } // Even if there are no actual positions specified, we set positions as an empty list. // An empty set of positions is still a valid state. setPositions(newPositions); } Integer antiAliasHintState = restorableSupport.getStateValueAsInteger("antiAliasHint"); if (antiAliasHintState != null) setAntiAliasHint(antiAliasHintState); Boolean isFilledState = restorableSupport.getStateValueAsBoolean("filled"); if (isFilledState != null) setFilled(isFilledState); Boolean isClosedState = restorableSupport.getStateValueAsBoolean("closed"); if (isClosedState != null) setClosed(isClosedState); Boolean isHighlightedState = restorableSupport.getStateValueAsBoolean("highlighted"); if (isHighlightedState != null) setHighlighted(isHighlightedState); Integer pathTypeState = restorableSupport.getStateValueAsInteger("pathType"); if (pathTypeState != null) setPathType(pathTypeState); Boolean isFollowTerrainState = restorableSupport.getStateValueAsBoolean("followTerrain"); if (isFollowTerrainState != null) setFollowTerrain(isFollowTerrainState); Double offsetState = restorableSupport.getStateValueAsDouble("offset"); if (offsetState != null) setOffset(offsetState); Double terrainConformanceState = restorableSupport.getStateValueAsDouble("terrainConformance"); if (terrainConformanceState != null) setTerrainConformance(terrainConformanceState); Double lineWidthState = restorableSupport.getStateValueAsDouble("lineWidth"); if (lineWidthState != null) setLineWidth(lineWidthState); Integer stipplePatternState = restorableSupport.getStateValueAsInteger("stipplePattern"); if (stipplePatternState != null) setStipplePattern(stipplePatternState.shortValue()); Integer stippleFactorState = restorableSupport.getStateValueAsInteger("stippleFactor"); if (stippleFactorState != null) setStippleFactor(stippleFactorState); Integer numSubsegmentsState = restorableSupport.getStateValueAsInteger("numSubsegments"); if (numSubsegmentsState != null) setNumSubsegments(numSubsegmentsState); RestorableSupport.StateObject so = restorableSupport.getStateObject(null, "avlist"); if (so != null) { RestorableSupport.StateObject[] avpairs = restorableSupport.getAllStateObjects(so, ""); if (avpairs != null) { for (RestorableSupport.StateObject avp : avpairs) { if (avp != null) this.setValue(avp.getName(), avp.getValue()); } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy