diff --git a/src/gov/nasa/worldwind/render/SurfaceCircle.java b/src/gov/nasa/worldwind/render/SurfaceCircle.java index 715dd2df64..e538636ad8 100644 --- a/src/gov/nasa/worldwind/render/SurfaceCircle.java +++ b/src/gov/nasa/worldwind/render/SurfaceCircle.java @@ -104,6 +104,25 @@ public SurfaceCircle(ShapeAttributes normalAttrs, LatLon center, double radius, { super(normalAttrs, center, radius, radius, Angle.ZERO, intervals); } + + /** + * Constructs a new surface circle with the specified normal (as opposed to highlight) attributes, the specified + * center location, radius (in meters), and initial number of geometry intervals. Modifying the attribute reference + * after calling this constructor causes this shape's appearance to change accordingly. + * + * @param normalAttrs the normal attributes. May be null, in which case default attributes are used. + * @param center the circle's center location. + * @param radius the circle's radius, in meters. + * @param intervals the initial number of intervals (or slices) defining the circle's geometry. + * @param theta the angle defining the start and end of the circle's geometry. + * + * @throws IllegalArgumentException if the center is null, if the radius is negative, or if the number of intervals + * is less than 8. + */ + public SurfaceCircle(ShapeAttributes normalAttrs, LatLon center, double radius, int intervals, Angle theta) + { + super(normalAttrs, center, radius, radius, Angle.ZERO, intervals, theta); + } public double getRadius() { diff --git a/src/gov/nasa/worldwind/render/SurfaceEllipse.java b/src/gov/nasa/worldwind/render/SurfaceEllipse.java index fe5dd757e8..f05b57af67 100644 --- a/src/gov/nasa/worldwind/render/SurfaceEllipse.java +++ b/src/gov/nasa/worldwind/render/SurfaceEllipse.java @@ -24,6 +24,7 @@ public class SurfaceEllipse extends AbstractSurfaceShape protected double majorRadius; protected double minorRadius; protected Angle heading = Angle.ZERO; + protected Angle theta = Angle.POS360; private int intervals = DEFAULT_NUM_INTERVALS; /** @@ -252,6 +253,38 @@ public SurfaceEllipse(ShapeAttributes normalAttrs, LatLon center, double majorRa this.intervals = intervals; } + + /** + * Constructs a new surface ellipse with the specified normal (as opposed to highlight) attributes, the specified + * center location, radii (in meters), heading clockwise from North, and initial number of geometry intervals. + * Modifying the attribute reference after calling this constructor causes this shape's appearance to change + * accordingly. + * + * @param normalAttrs the normal attributes. May be null, in which case default attributes are used. + * @param center the ellipse's center location. + * @param majorRadius the ellipse's major radius, in meters. + * @param minorRadius the ellipse's minor radius, in meters. + * @param heading the ellipse's heading, clockwise from North. + * @param intervals the initial number of intervals (or slices) defining the ellipse's geometry. + * @param theta the angle defining the start and end of the ellipse's geometry. + * + * @throws IllegalArgumentException if the center or heading are null, if either radii is negative, or if the number + * of intervals is less than 8. + */ + public SurfaceEllipse(ShapeAttributes normalAttrs, LatLon center, double majorRadius, double minorRadius, + Angle heading, int intervals, Angle theta) + { + this(normalAttrs, center, majorRadius, minorRadius, heading, intervals); + + if (theta == null) + { + String message = Logging.getMessage("nullValue.ThetaIsNull"); + Logging.logger().severe(message); + throw new IllegalArgumentException(message); + } + + this.theta = theta; + } public LatLon getCenter() { @@ -348,6 +381,24 @@ public void setIntervals(int intervals) this.intervals = intervals; this.onShapeChanged(); } + + public Angle getTheta() + { + return this.theta; + } + + public void setTheta(Angle theta) + { + if (theta == null) + { + String message = Logging.getMessage("nullValue.ThetaIsNull"); + Logging.logger().severe(message); + throw new IllegalArgumentException(message); + } + + this.theta = theta; + this.onShapeChanged(); + } /** * {@inheritDoc} @@ -408,27 +459,48 @@ protected List computeLocations(Globe globe, int intervals) if (this.majorRadius == 0 && this.minorRadius == 0) return null; + + boolean closed = this.theta.equals(Angle.POS360); - int numLocations = 1 + Math.max(MIN_NUM_INTERVALS, intervals); - double da = (2 * Math.PI) / (numLocations - 1); + int numIntervals = Math.max(MIN_NUM_INTERVALS, intervals); + int numLocations = 1 + numIntervals; + double da = (this.theta.radians) / (numLocations - 1); double globeRadius = globe.getRadiusAt(this.center.getLatitude(), this.center.getLongitude()); - LatLon[] locations = new LatLon[numLocations]; + List locations = new ArrayList(numLocations); + + // If the ellipse is not closed, start drawing from the center-position. + if (!closed) { + locations.add(this.center); + } for (int i = 0; i < numLocations; i++) { - double angle = (i != numLocations - 1) ? i * da : 0; + double angle = 0.0; + // If the ellipse is closed, snap angle to 0-degrees on final location. + if (closed) { + angle = (i != numIntervals) ? i * da : 0; + } else { + angle = (i != numIntervals) ? i * da : this.theta.radians; + } + double xLength = this.majorRadius * Math.cos(angle); double yLength = this.minorRadius * Math.sin(angle); double distance = Math.sqrt(xLength * xLength + yLength * yLength); - // azimuth runs positive clockwise from north and through 360 degrees. + + // azimuth runs positive clockwise from north and through theta degrees. double azimuth = (Math.PI / 2.0) - (Math.acos(xLength / distance) * Math.signum(yLength) - this.heading.radians); - locations[i] = LatLon.greatCircleEndPosition(this.center, azimuth, distance / globeRadius); + locations.add(LatLon.rhumbEndPosition(this.center, Angle.fromRadians(azimuth), Angle.fromRadians(distance / globeRadius))); + } + + // If the ellipse is not closed, end at the center-position. + if (!closed) { + locations.add(this.center); } - return Arrays.asList(locations); + return locations; } protected List> createGeometry(Globe globe, double edgeIntervalsPerDegree) @@ -469,7 +541,7 @@ protected int computeNumEdgeIntervals(Globe globe, double edgeIntervalsPerDegree int numPositions = 1 + Math.max(MIN_NUM_INTERVALS, intervals); double radius = Math.max(this.majorRadius, this.minorRadius); - double da = (2 * Math.PI) / (numPositions - 1); + double da = (this.theta.radians) / (numPositions - 1); Angle edgePathLength = Angle.fromRadians(da * radius / globe.getRadiusAt(this.center)); double edgeIntervals = WWMath.clamp(edgeIntervalsPerDegree * edgePathLength.degrees, @@ -491,6 +563,7 @@ protected void doGetRestorableState(RestorableSupport rs, RestorableSupport.Stat rs.addStateValueAsDouble(context, "minorRadius", this.getMinorRadius()); rs.addStateValueAsDouble(context, "headingDegrees", this.getHeading().degrees); rs.addStateValueAsInteger(context, "intervals", this.getIntervals()); + rs.addStateValueAsDouble(context, "theta", this.getTheta().degrees); } protected void doRestoreState(RestorableSupport rs, RestorableSupport.StateObject context) @@ -516,6 +589,10 @@ protected void doRestoreState(RestorableSupport rs, RestorableSupport.StateObjec Integer i = rs.getStateValueAsInteger(context, "intervals"); if (d != null) this.setIntervals(i); + + d = rs.getStateValueAsDouble(context, "theta"); + if (d != null) + this.setTheta(Angle.fromDegrees(d)); } protected void legacyRestoreState(RestorableSupport rs, RestorableSupport.StateObject context) diff --git a/src/gov/nasa/worldwind/util/MessageStrings.properties b/src/gov/nasa/worldwind/util/MessageStrings.properties index 1c5558f23f..b2d92333c8 100644 --- a/src/gov/nasa/worldwind/util/MessageStrings.properties +++ b/src/gov/nasa/worldwind/util/MessageStrings.properties @@ -679,6 +679,7 @@ nullValue.TextureDataIsNull=TextureData is null nullValue.TextureIsNull=Texture is null nullValue.TextureCacheIsNull=Texture cache is null nullValue.TextureCoordinateComputerIsNull=Texture coordinate computer is null +nullValue.ThetaIsNull=Theta is null nullValue.ThreadIsNull=Thread is null nullValue.ThrowableIsNull=Throwable is null nullValue.TileIsNull=Tile is null