//******************************************************************************
//
// File: Bow.java
// Package: edu.rit.draw.item
// Unit: Class edu.rit.draw.item.Bow
//
// 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.BasicStroke;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Class Bow provides a {@linkplain DrawingItem} that joins two points with a
* curved (bowed) line. The curve is specified by a "curve factor." If the curve
* factor is positive, the line curves to the right when going from the starting
* point to the ending point. If the curve factor is negative, the line curves
* to the left. The larger the magnitude of the curve factor, the farther the
* line curves. (The curve factor is the tangent of the angle at which the line
* leaves the starting point and approaches the ending point.) The line may have
* an {@linkplain Arrow} at either or both ends. The line has an outline but no
* filled interior.
*
* The static defaultCurve(), defaultStartArrow(), and
* defaultEndArrow() methods are provided to set the default curve
* factor, starting arrow, and ending arrow. If the curve factor, starting
* arrow, or ending arrow is not specified, the current default curve factor,
* starting arrow, or ending arrow is used.
*
* @author Alan Kaminsky
* @version 23-Jun-2008
*/
public class Bow
extends OutlinedItem
implements Externalizable
{
// Exported constants.
/**
* The normal starting arrow: None.
*/
public static final Arrow NORMAL_START_ARROW = Arrow.NONE;
/**
* The normal ending arrow: None.
*/
public static final Arrow NORMAL_END_ARROW = Arrow.NONE;
/**
* The normal curve factor (0.5).
*/
public static final double NORMAL_CURVE = 0.5;
// Hidden data members.
private static final long serialVersionUID = 7585879104799512917L;
// Line endpoints.
Point myStartPoint;
Point myEndPoint;
// Attributes.
Arrow myStartArrow = theDefaultStartArrow;
Arrow myEndArrow = theDefaultEndArrow;
double myCurve = theDefaultCurve;
// The default attributes.
static Arrow theDefaultStartArrow = NORMAL_START_ARROW;
static Arrow theDefaultEndArrow = NORMAL_END_ARROW;
static double theDefaultCurve = NORMAL_CURVE;
// For computing the bounding box.
Point myNw;
Point mySe;
Size mySize;
// For drawing the line and arrows.
double x1, y1, x2, y2, x3, y3, xb, yb;
// Exported constructors.
/**
* Construct a new bowed line. The line has no points initially. The default
* outline, starting arrow, ending arrow, and curve factor are used.
*/
public Bow()
{
super();
}
/**
* Construct a bowed line with the same points, outline, starting arrow,
* ending arrow, and curve factor as the given line.
*
* @param theBow Bowed line.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theBow is null.
*/
public Bow
(Bow theBow)
{
super (theBow);
myStartPoint = theBow.myStartPoint;
myEndPoint = theBow.myEndPoint;
myStartArrow = theBow.myStartArrow;
myEndArrow = theBow.myEndArrow;
myCurve = theBow.myCurve;
}
// Exported operations.
/**
* Returns the default starting arrow.
*
* @return Default starting arrow.
*/
public static Arrow defaultStartArrow()
{
return theDefaultStartArrow;
}
/**
* Set the default starting arrow. Before calling this method the first
* time, the default starting arrow is Arrow.NONE.
*
* @param theArrow Default starting arrow.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theArrow is null.
*/
public static void defaultStartArrow
(Arrow theArrow)
{
if (theArrow == null) throw new NullPointerException();
theDefaultStartArrow = theArrow;
}
/**
* Returns the default ending arrow.
*
* @return Default ending arrow.
*/
public static Arrow defaultEndArrow()
{
return theDefaultEndArrow;
}
/**
* Set the default ending arrow. Before calling this method the first
* time, the default ending arrow is Arrow.NONE.
*
* @param theArrow Default ending arrow.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theArrow is null.
*/
public static void defaultEndArrow
(Arrow theArrow)
{
if (theArrow == null) throw new NullPointerException();
theDefaultEndArrow = theArrow;
}
/**
* Returns the default curve factor.
*
* @return Default curve factor.
*/
public static double defaultCurve()
{
return theDefaultCurve;
}
/**
* Set the default curve factor. Before calling this method the first time,
* the default curve factor is 0.5.
*
* @param theCurve Default curve factor.
*/
public static void defaultCurve
(double theCurve)
{
theDefaultCurve = theCurve;
}
/**
* 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 bowed line's outline.
*
* @param theOutline Outline, or Outline.NONE.
*
* @return This bowed line.
*/
public Bow outline
(Outline theOutline)
{
doOutline (theOutline);
return this;
}
/**
* Returns this bowed line's starting point.
*
* @return Starting point.
*/
public Point start()
{
return myStartPoint;
}
/**
* Set this bowed line's starting point.
*
* @param x Starting point X coordinate.
* @param y Starting point Y coordinate.
*
* @return This bowed line.
*/
public Bow start
(double x,
double y)
{
myStartPoint = new Point (x, y);
return this;
}
/**
* Set this bowed line's starting point.
*
* @param thePoint Starting point.
*
* @return This bowed line.
*
* @exception NullPointerException
* (unchecked exception) Thrown if thePoint is null.
*/
public Bow start
(Point thePoint)
{
if (thePoint == null) throw new NullPointerException();
myStartPoint = thePoint;
return this;
}
/**
* Returns this bowed line's ending point.
*
* @return Ending point.
*/
public Point end()
{
return myEndPoint;
}
/**
* Set this bowed line's ending point.
*
* @param x Ending point X coordinate.
* @param y Ending point Y coordinate.
*
* @return This bowed line.
*/
public Bow end
(double x,
double y)
{
myEndPoint = new Point (x, y);
return this;
}
/**
* Set this bowed line's ending point.
*
* @param thePoint Ending point.
*
* @return This bowed line.
*
* @exception NullPointerException
* (unchecked exception) Thrown if thePoint is null.
*/
public Bow end
(Point thePoint)
{
if (thePoint == null) throw new NullPointerException();
myEndPoint = thePoint;
return this;
}
/**
* Returns this bowed line's starting arrow.
*
* @return Starting arrow.
*/
public Arrow startArrow()
{
return myStartArrow;
}
/**
* Set this bowed line's starting arrow.
*
* @param theArrow Starting arrow.
*
* @return This bowed line.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theArrow is null.
*/
public Bow startArrow
(Arrow theArrow)
{
if (theArrow == null) throw new NullPointerException();
myStartArrow = theArrow;
return this;
}
/**
* Returns this bowed line's ending arrow.
*
* @return Ending arrow.
*/
public Arrow endArrow()
{
return myEndArrow;
}
/**
* Set this bowed line's ending arrow.
*
* @param theArrow Ending arrow.
*
* @return This bowed line.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theArrow is null.
*/
public Bow endArrow
(Arrow theArrow)
{
if (theArrow == null) throw new NullPointerException();
myEndArrow = theArrow;
return this;
}
/**
* Returns this bowed line's curve factor.
*
* @return Curve factor.
*/
public double curve()
{
return myCurve;
}
/**
* Set this bowed line's curve factor.
*
* @param theCurve Curve factor.
*
* @return This bowed line.
*/
public Bow curve
(double theCurve)
{
myCurve = theCurve;
return this;
}
/**
* Flip this bowed line from curving right to curving left or vice versa.
*
* @return This bowed line.
*/
public Bow flip()
{
myCurve = -myCurve;
return this;
}
/**
* Add this bowed line to the end of the default drawing's sequence of
* drawing items.
*
* @return This bowed line.
*
* @exception NullPointerException
* (unchecked exception) Thrown if there is no default drawing.
*
* @see Drawing#defaultDrawing()
*/
public Bow add()
{
doAdd (Drawing.defaultDrawing());
return this;
}
/**
* Add this bowed line to the end of the given drawing's sequence of drawing
* items.
*
* @param theDrawing Drawing.
*
* @return This bowed line.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theDrawing is null.
*/
public Bow add
(Drawing theDrawing)
{
doAdd (theDrawing);
return this;
}
/**
* Add this bowed line to the beginning of the default drawing's sequence of
* drawing items.
*
* @return This bowed line.
*
* @exception NullPointerException
* (unchecked exception) Thrown if there is no default drawing.
*
* @see Drawing#defaultDrawing()
*/
public Bow addFirst()
{
doAddFirst (Drawing.defaultDrawing());
return this;
}
/**
* Add this bowed line to the beginning of the given drawing's sequence of
* drawing items.
*
* @param theDrawing Drawing.
*
* @return This bowed line.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theDrawing is null.
*/
public Bow addFirst
(Drawing theDrawing)
{
doAddFirst (theDrawing);
return this;
}
/**
* Write this bowed line 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 (myStartPoint);
out.writeObject (myEndPoint);
out.writeObject (myStartArrow);
out.writeObject (myEndArrow);
out.writeDouble (myCurve);
}
/**
* Read this bowed line 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);
myStartPoint = (Point) in.readObject();
myEndPoint = (Point) in.readObject();
myStartArrow = (Arrow) in.readObject();
myEndArrow = (Arrow) in.readObject();
myCurve = in.readDouble();
myNw = null;
mySe = null;
mySize = null;
}
/**
* 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 line.
GeneralPath path = computePath();
myOutline.setGraphicsContext (g2d);
g2d.draw (path);
// Return if there are no arrows.
if (myStartArrow == null && myEndArrow == null) return;
// Prepare to draw arrows. Set stroke to a solid stroke the same width
// as the current outline.
float width = myOutline.getStrokeWidth();
g2d.setStroke (new BasicStroke (width));
double dx;
double dy;
double phi;
if (myStartArrow != null)
{
dx = x1 - x2;
dy = y1 - y2;
phi =
dx == 0.0 && dy == 0.0 ?
0.0 :
Math.atan2 (dy, dx);
myStartArrow.draw (g2d, width, x1, y1, phi);
}
if (myEndArrow != null)
{
dx = x3 - x2;
dy = y3 - y2;
phi =
dx == 0.0 && dy == 0.0 ?
0.0 :
Math.atan2 (dy, dx);
myEndArrow.draw (g2d, width, x3, y3, phi);
}
}
// Hidden operations.
/**
* Compute this line's three control points and store their (x,y)
* coordinates in x1, y1, x2, y2, x3, and y3.
*/
private void computeControlPoints()
{
// Get starting and ending coordinates.
x1 = myStartPoint.x();
y1 = myStartPoint.y();
x3 = myEndPoint.x();
y3 = myEndPoint.y();
// Get midpoint coordinates.
double xm = 0.5 * (x1 + x3);
double ym = 0.5 * (y1 + y3);
// Get unit vector from starting point to ending point.
double dx = x3 - x1;
double dy = y3 - y1;
double d = Math.sqrt (dx*dx + dy*dy);
double ux = dx / d;
double uy = dy / d;
// Get unit vector perpendicular to the above vector.
double vx = -uy;
double vy = ux;
// Get second control point.
x2 = xm + 0.5 * d * myCurve * vx;
y2 = ym + 0.5 * d * myCurve * vy;
// Get point through which curve passes, for bounding box.
xb = xm + 0.25 * d * myCurve * vx;
yb = ym + 0.25 * d * myCurve * vy;
}
/**
* Compute this line's bounding box from its control points. The results are
* stored in myNw, mySe, and mySize.
*/
private void computeBoundingBox()
{
if (myNw == null)
{
computeControlPoints();
myNw = new Point
(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
mySe = new Point
(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
Point p = new Point (x1, y1);
myNw = myNw.min (p);
mySe = mySe.max (p);
p = new Point (xb, yb);
myNw = myNw.min (p);
mySe = mySe.max (p);
p = new Point (x3, y3);
myNw = myNw.min (p);
mySe = mySe.max (p);
mySize = mySe.difference (myNw);
}
}
/**
* Compute this line's path from its control points.
*/
private GeneralPath computePath()
{
computeControlPoints();
GeneralPath path = new GeneralPath();
path.moveTo ((float) x1, (float) y1);
path.quadTo ((float) x2, (float) y2, (float) x3, (float) y3);
return path;
}
}
Ads help maintain this website.