//******************************************************************************
//
// File: Arc.java
// Package: edu.rit.draw.Arc
// Unit: Class edu.rit.draw.item.Arc
//
// This Java source file is copyright (C) 2008 by Alan Kaminsky. All rights
// reserved. For further information, contact the author, Alan Kaminsky, at
// ark@cs.rit.edu.
//
// This Java source file is part of the Parallel Java Library ("PJ"). PJ is free
// software; you can redistribute it and/or modify it under the terms of the GNU
// General Public License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// PJ is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// A copy of the GNU General Public License is provided in the file gpl.txt. You
// may also obtain a copy of the GNU General Public License on the World Wide
// Web at http://www.gnu.org/licenses/gpl.html.
//
//******************************************************************************
package edu.rit.draw.item;
import edu.rit.draw.Drawing;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Arc2D;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Class Arc provides a {@linkplain DrawingItem} that is a circular arc. The arc
* has an outline but no filled interior. The arc is defined by the following
* attributes:
*
* -
* center() -- The arc's center point. The default is (0,0).
*
* -
* radius() -- The arc's radius. The default is 1.
*
* -
* start() -- The arc's starting angle in radians. This is the angle
* from the positive X axis to the arc's starting point. The default is 0.
*
* -
* extent() -- The arc's angular extent in radians. This is the angle
* from the arc's starting point to the arc's ending point. The default is
* π/2.
*
*
* Angles increase from the positive X axis towards the positive Y axis. Since
* the positive X axis points to the right and the positive Y axis points down,
* angles increase in the clockwise direction. (This is the opposite of the
* usual mathematical convention, where the positive Y axis points upwards.)
*
* The static defaultCenter(), defaultRadius(),
* defaultStart(), and defaultExtent() methods are provided to
* set the default center point, radius, starting angle, and angular extent. If
* the center point, radius, starting angle, or angular extent is not specified,
* the current default center point, radius, starting angle, or angular extent
* is used.
*
* @author Alan Kaminsky
* @version 23-Jun-2008
*/
public class Arc
extends OutlinedItem
implements Externalizable
{
// Exported constants.
/**
* The normal center point: (0,0).
*/
public static final Point NORMAL_CENTER = new Point (0, 0);
/**
* The normal radius: 1.
*/
public static final double NORMAL_RADIUS = 1.0;
/**
* The normal starting angle: 0.
*/
public static final double NORMAL_START = 0.0;
/**
* The normal angular extent: π/2.
*/
public static final double NORMAL_EXTENT = Math.PI / 2.0;
// Hidden data members.
private static final long serialVersionUID = 4876674535249335766L;
private static final double DEGREES_PER_RADIAN = 180.0 / Math.PI;
private static final double A_PI_2 = Math.PI / 2.0;
private static final double A_PI = Math.PI;
private static final double A_3_PI_2 = 3.0 * Math.PI / 2.0;
private static final double A_2_PI = 2.0 * Math.PI;
private static final double A_5_PI_2 = 5.0 * Math.PI / 2.0;
private static final double A_3_PI = 3.0 * Math.PI;
private static final double A_7_PI_2 = 7.0 * Math.PI / 2.0;
// Attributes.
Point myCenter = theDefaultCenter;
double myRadius = theDefaultRadius;
double myStart = theDefaultStart;
double myExtent = theDefaultExtent;
// The default attributes.
static Point theDefaultCenter = NORMAL_CENTER;
static double theDefaultRadius = NORMAL_RADIUS;
static double theDefaultStart = NORMAL_START;
static double theDefaultExtent = NORMAL_EXTENT;
// For computing the bounding box.
Point myNw;
Point mySe;
Size mySize;
// Exported constructors.
/**
* Construct a new arc. The default center point, radius, starting angle,
* and angular extent are used.
*/
public Arc()
{
super();
}
/**
* Construct a new arc with the same center point, radius, starting angle,
* and angular extent as the given arc.
*
* @param theArc Arc.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theArc is null.
*/
public Arc
(Arc theArc)
{
super (theArc);
myCenter = theArc.myCenter;
myRadius = theArc.myRadius;
myStart = theArc.myStart;
myExtent = theArc.myExtent;
}
// Exported operations.
/**
* Returns the default center point.
*
* @return Default center point.
*/
public static Point defaultCenter()
{
return theDefaultCenter;
}
/**
* Set the default center point. Before calling this method the first time,
* the default center point is (0,0).
*
* @param theCenter Default center point.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theCenter is null.
*/
public static void defaultCenter
(Point theCenter)
{
if (theCenter == null) throw new NullPointerException();
theDefaultCenter = theCenter;
}
/**
* Returns the default radius.
*
* @return Default radius.
*/
public static double defaultRadius()
{
return theDefaultRadius;
}
/**
* Set the default radius. Before calling this method the first time, the
* default radius is 1.
*
* @param theRadius Default radius.
*
* @exception IllegalArgumentException
* (unchecked exception) Thrown if theRadius <= 0.
*/
public static void defaultRadius
(double theRadius)
{
if (theRadius <= 0.0) throw new IllegalArgumentException();
theDefaultRadius = theRadius;
}
/**
* Returns the default starting angle.
*
* @return Default starting angle (radians).
*/
public static double defaultStart()
{
return theDefaultStart;
}
/**
* Set the default starting angle. Before calling this method the first
* time, the default starting angle is 0.
*
* @param theStart Default starting angle (radians).
*/
public static void defaultStart
(double theStart)
{
theDefaultStart = theStart;
}
/**
* Returns the default angular extent.
*
* @return Default angular extent (radians).
*/
public static double defaultExtent()
{
return theDefaultExtent;
}
/**
* Set the default angular extent. Before calling this method the first
* time, the default angular extent is π/2.
*
* @param theExtent Default angular extent (radians).
*/
public static void defaultExtent
(double theExtent)
{
theDefaultExtent = theExtent;
}
/**
* Returns the size of this drawing item's bounding box.
*
* @return Size.
*/
public Size size()
{
computeBoundingBox();
return mySize;
}
/**
* Returns the width of this drawing item's bounding box.
*
* @return Width.
*/
public double width()
{
computeBoundingBox();
return mySize.width;
}
/**
* Returns the height of this drawing item's bounding box.
*
* @return Height.
*/
public double height()
{
computeBoundingBox();
return mySize.height;
}
/**
* Returns the northwest corner point of this drawing item's bounding box.
*
* @return Northwest corner point.
*/
public Point nw()
{
computeBoundingBox();
return myNw;
}
/**
* Set this arc's outline.
*
* @param theOutline Outline, or Outline.NONE.
*
* @return This arc.
*/
public Arc outline
(Outline theOutline)
{
doOutline (theOutline);
return this;
}
/**
* Returns this arc's center point.
*
* @return Center point.
*/
public Point center()
{
return myCenter;
}
/**
* Set this arc's center point.
*
* @param theCenter Center point.
*
* @return This arc.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theCenter is null.
*/
public Arc center
(Point theCenter)
{
if (theCenter == null) throw new NullPointerException();
myCenter = theCenter;
return this;
}
/**
* Returns this arc's radius.
*
* @return Radius.
*/
public double radius()
{
return myRadius;
}
/**
* Set this arc's radius.
*
* @param theRadius Radius.
*
* @return This arc.
*
* @exception IllegalArgumentException
* (unchecked exception) Thrown if theRadius <= 0.
*/
public Arc radius
(double theRadius)
{
if (theRadius <= 0.0) throw new IllegalArgumentException();
myRadius = theRadius;
return this;
}
/**
* Returns this arc's starting angle.
*
* @return Starting angle (radians).
*/
public double start()
{
return myStart;
}
/**
* Set this arc's starting angle.
*
* @param theStart Starting angle (radians).
*
* @return This arc.
*/
public Arc start
(double theStart)
{
myStart = theStart;
return this;
}
/**
* Returns this arc's angular extent.
*
* @return Angular extent (radians).
*/
public double extent()
{
return myExtent;
}
/**
* Set this arc's angular extent.
*
* @param theExtent Angular extent (radians).
*
* @return This arc.
*/
public Arc extent
(double theExtent)
{
myExtent = theExtent;
return this;
}
/**
* Add this arc to the end of the default drawing's sequence of drawing
* items.
*
* @return This arc.
*
* @exception NullPointerException
* (unchecked exception) Thrown if there is no default drawing.
*
* @see Drawing#defaultDrawing()
*/
public Arc add()
{
doAdd (Drawing.defaultDrawing());
return this;
}
/**
* Add this arc to the end of the given drawing's sequence of drawing
* items.
*
* @param theDrawing Drawing.
*
* @return This arc.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theDrawing is null.
*/
public Arc add
(Drawing theDrawing)
{
doAdd (theDrawing);
return this;
}
/**
* Add this arc to the beginning of the default drawing's sequence of
* drawing items.
*
* @return This arc.
*
* @exception NullPointerException
* (unchecked exception) Thrown if there is no default drawing.
*
* @see Drawing#defaultDrawing()
*/
public Arc addFirst()
{
doAddFirst (Drawing.defaultDrawing());
return this;
}
/**
* Add this arc to the beginning of the given drawing's sequence of drawing
* items.
*
* @param theDrawing Drawing.
*
* @return This arc.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theDrawing is null.
*/
public Arc addFirst
(Drawing theDrawing)
{
doAddFirst (theDrawing);
return this;
}
/**
* Write this arc to the given object output stream.
*
* @param out Object output stream.
*
* @exception IOException
* Thrown if an I/O error occurred.
*/
public void writeExternal
(ObjectOutput out)
throws IOException
{
super.writeExternal (out);
out.writeObject (myCenter);
out.writeDouble (myRadius);
out.writeDouble (myStart);
out.writeDouble (myExtent);
}
/**
* Read this arc from the given object input stream.
*
* @param in Object input stream.
*
* @exception IOException
* Thrown if an I/O error occurred.
* @exception ClassNotFoundException
* Thrown if any class needed to deserialize this line cannot be found.
*/
public void readExternal
(ObjectInput in)
throws IOException, ClassNotFoundException
{
super.readExternal (in);
myCenter = (Point) in.readObject();
myRadius = in.readDouble();
myStart = in.readDouble();
myExtent = in.readDouble();
}
/**
* Draw this drawing item in the given graphics context. This method is
* allowed to change the graphics context's paint, stroke, and transform,
* and it doesn't have to change them back.
*
* @param g2d 2-D graphics context.
*/
public void draw
(Graphics2D g2d)
{
super.draw (g2d);
// Draw arc.
Shape shape = computeShape();
myOutline.setGraphicsContext (g2d);
g2d.draw (shape);
}
// Hidden operations.
/**
* Compute this arc's bounding box. The results are stored in
* myNw, mySe, and mySize.
*/
private void computeBoundingBox()
{
if (myNw == null)
{
double extent, start1, start2, sx, sy, ex, ey, x1, y1, x2, y2;
// Make the angular extent positive if necessary.
if (myExtent >= 0.0)
{
extent = myExtent;
start1 = myStart;
}
else
{
extent = - myExtent;
start1 = myStart - extent;
}
// Compute starting angle (mod 2 pi).
start2 = Math.abs (start1);
start2 = start2 - Math.floor (start2 / A_2_PI) * A_2_PI;
if (start1 < 0.0) start2 = A_2_PI - start2;
// Compute starting point.
sx = myCenter.x() + myRadius * Math.cos (start2);
sy = myCenter.y() + myRadius * Math.sin (start2);
// Compute ending point.
ex = myCenter.x() + myRadius * Math.cos (start2 + extent);
ey = myCenter.y() + myRadius * Math.sin (start2 + extent);
// Compute initial bounding box from starting and ending points.
x1 = Math.min (sx, ex);
y1 = Math.min (sy, ey);
x2 = Math.max (sx, ex);
y2 = Math.max (sy, ey);
// Adjust bounding box if arc crosses the positive X axis.
if (start2 < A_2_PI && start2 + extent > A_2_PI)
{
x2 = myCenter.x() + myRadius;
}
// Adjust bounding box if arc crosses the positive Y axis.
if
((start2 < A_PI_2 && start2 + extent > A_PI_2) ||
(start2 < A_5_PI_2 && start2 + extent > A_5_PI_2))
{
y2 = myCenter.y() + myRadius;
}
// Adjust bounding box if arc crosses the negative X axis.
if
((start2 < A_PI && start2 + extent > A_PI) ||
(start2 < A_3_PI && start2 + extent > A_3_PI))
{
x1 = myCenter.x() - myRadius;
}
// Adjust bounding box if arc crosses the negative Y axis.
if
((start2 < A_3_PI_2 && start2 + extent > A_3_PI_2) ||
(start2 < A_7_PI_2 && start2 + extent > A_7_PI_2))
{
y1 = myCenter.y() - myRadius;
}
// Compute final bounding box.
myNw = new Point (x1, y1);
mySe = new Point (x2, y2);
mySize = mySe.difference (myNw);
}
}
/**
* Compute this arc's shape from its attributes.
*/
private Shape computeShape()
{
return
new Arc2D.Double
(/*x */ myCenter.x() - myRadius,
/*y */ myCenter.y() - myRadius,
/*w */ 2 * myRadius,
/*h */ 2 * myRadius,
/*start */ -myStart * DEGREES_PER_RADIAN,
/*extent*/ -myExtent * DEGREES_PER_RADIAN,
/*type */ Arc2D.OPEN);
}
}
Ads help maintain this website.