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

acm.graphics.GPolygon Maven / Gradle / Ivy

Go to download

This the original Stanford Karel for Java, packaged for Maven. ACM Library is included. See also https://cs.stanford.edu/people/eroberts/karel-the-robot-learns-java.pdf

The newest version!
/*
 * @(#)GPolygon.java   1.99.1 08/12/08
 */

// ************************************************************************
// * Copyright (c) 2008 by the Association for Computing Machinery        *
// *                                                                      *
// * The Java Task Force seeks to impose few restrictions on the use of   *
// * these packages so that users have as much freedom as possible to     *
// * use this software in constructive ways and can make the benefits of  *
// * that work available to others.  In view of the legal complexities    *
// * of software development, however, it is essential for the ACM to     *
// * maintain its copyright to guard against attempts by others to        *
// * claim ownership rights.  The full text of the JTF Software License   *
// * is available at the following URL:                                   *
// *                                                                      *
// *          http://www.acm.org/jtf/jtf-software-license.pdf             *
// *                                                                      *
// ************************************************************************

// REVISION HISTORY
//
// -- V2.0 --
// Code cleanup 28-May-07 (ESR)
//   1. Added generic type tags.
//
// Bug fix 6-Jul-07 (ESR, JTFBug 2007-010, reported by Steve Wolfman)
//   1. Fixed bug in recenter code.
//
// Feature enhancement 26-May-08 (ESR)
//   1. Added support for serialization.

package acm.graphics;

import acm.util.*;
import java.awt.*;
import java.io.*;
import java.util.*;

/* Class: GPolygon */
/**
 * The GPolygon class is a graphical object whose appearance consists
 * of a polygon.
 */
public class GPolygon extends GObject implements GFillable, GScalable {

/* Constructor: GPolygon() */
/**
 * Constructs a new empty polygon at the origin.
 *
 * Example: GPolygon gpoly = new GPolygon();
 */
	public GPolygon() {
		vertices = new VertexList();
		clear();
	}

/* Constructor: GPolygon(x, y) */
/**
 * Constructs a new empty polygon at (x, y).
 *
 * Example: GPolygon gpoly = new GPolygon(x, y);
 * @param x The x-coordinate of the origin of the polygon
 * @param y The y-coordinate of the origin of the polygon
 */
	public GPolygon(double x, double y) {
		this();
		setLocation(x, y);
	}

/* Constructor: GPolygon(points) */
/**
 * Constructs a new polygon from the specified array of GPoint
 * objects.  The polygon is automatically marked as complete.
 *
 * Example: GPolygon gpoly = new GPolygon(points);
 * @param points An array of GPoint objects specifying the vertices
 */
	public GPolygon(GPoint[] points) {
		this();
		vertices.add(points);
		markAsComplete();
	}

/* Method: addVertex(x, y) */
/**
 * Adds a vertex at (x, y) relative to the polygon origin.
 *
 * Example: gpoly.addVertex(x, y);
 * @param x The x-coordinate of the vertex relative to the polygon origin
 * @param y The y-coordinate of the vertex relative to the polygon origin
 */
	public void addVertex(double x, double y) {
		if (complete) {
			throw new ErrorException("You can't add vertices to a GPolygon that has been "
			                         + "marked as complete.");
		}
		vertices.addVertex(x, y);
	}

/* Method: addEdge(dx, dy) */
/**
 * Adds an edge to the polygon whose components are given by the displacements
 * dx and dy from the last vertex.
 *
 * Example: gpoly.addEdge(dx, dy);
 * @param dx The x displacement through which the edge moves
 * @param dy The y displacement through which the edge moves
 */
	public void addEdge(double dx, double dy) {
		if (complete) {
			throw new ErrorException("You can't add edges to a GPolygon that has been "
			                         + "marked as complete.");
		}
		vertices.addEdge(dx, dy);
	}

/* Method: addPolarEdge(r, theta) */
/**
 * Adds an edge to the polygon specified in polar coordinates.  The length of the
 * edge is given by r, and the edge extends in direction theta,
 * measured in degrees counterclockwise from the +x axis.
 *
 * Example: gpoly.addPolarEdge(r, theta);
 * @param r The length of the edge
 * @param theta The angle at which the edge extends measured in degrees
 */
	public final void addPolarEdge(double r, double theta) {
		if (complete) {
			throw new ErrorException("You can't add edges to a GPolygon that has been "
			                         + "marked as complete.");
		}
		vertices.addEdge(r * GMath.cosDegrees(theta), -r * GMath.sinDegrees(theta));
	}

/* Method: addArc(arcWidth, arcHeight, start, sweep) */
/**
 * Adds a series of edges to the polygon that simulates the arc specified by
 * the parameters.  The x and y parameters for the arc bounding
 * box are computed implicitly by figuring out what values would place the
 * current vertex at the starting position.
 *
 * Example: gpoly.addArc(arcWidth, arcHeight, start, sweep);
 * @param arcWidth The width of the oval from which the arc is taken
 * @param arcHeight The height of the oval from which the arc is taken
 * @param start The angle at which the arc begins
 * @param sweep The extent of the arc
 */
	public void addArc(double arcWidth, double arcHeight, double start, double sweep) {
		if (complete) {
			throw new ErrorException("You can't add edges to a GPolygon that has been "
			                         + "marked as complete.");
		}
		vertices.addArc(arcWidth, arcHeight, start, sweep);
	}

/* Method: getCurrentPoint() */
/**
 * Returns the coordinates of the last vertex added to the polygon, or null
 * if the polygon is empty.
 *
 * Example: GPoint vertex = gpoly.getCurrentPoint();
 * @return The last vertex added to the polygon, or null if empty
 */
	public GPoint getCurrentPoint() {
		return vertices.getCurrentPoint();
	}

/* Method: scale(sx, sy) */
/**
 * Scales the polygon by the scale factors sx and sy.
 *
 * Example: gpoly.scale(sx, sy);
 * @param sx The factor used to scale all coordinates in the x direction
 * @param sy The factor used to scale all coordinates in the y direction
 */
	public void scale(double sx, double sy) {
		xScale *= sx;
		yScale *= sy;
		repaint();
	}

/* Method: scale(sf) */
/**
 * Scales the object on the screen by the scale factor sf, which applies
 * in both dimensions.
 *
 * Example: gobj.scale(sf);
 * @param sf The factor used to scale all coordinates in both dimensions
 */
	public final void scale(double sf) {
		scale(sf, sf);
	}

/* Method: rotate(theta) */
/**
 * Rotates the polygon around its origin by the angle theta, measured in degrees.
 *
 * Example: gpoly.rotate(theta);
 * @param theta The angle of rotation in degrees counterclockwise
 */
	public void rotate(double theta) {
		rotation += theta;
		repaint();
	}

/* Method: setFilled(fill) */
/**
 * Sets whether this object is filled.
 *
 * Example: gobj.setFilled(fill);
 * @param fill true if the object should be filled, false for an outline
 */
	public void setFilled(boolean fill) {
		isFilled = fill;
		repaint();
	}

/* Method: isFilled() */
/**
 * Returns whether this object is filled.
 *
 * Example: if (gobj.isFilled()) . . .
 * @return The color used to display the object
 */
	public boolean isFilled() {
		return isFilled;
	}

/* Method: setFillColor(color) */
/**
 * Sets the color used to display the filled region of this object.
 *
 * Example: gobj.setFillColor(color);
 * @param color The color used to display the filled region of this object
 */
	public void setFillColor(Color color) {
		fillColor = color;
		repaint();
	}

/* Method: getFillColor() */
/**
 * Returns the color used to display the filled region of this object.  If
 * none has been set, getFillColor returns the color of the
 * object.
 *
 * Example: Color color = gobj.getFillColor();
 * @return The color used to display the filled region of this object
 */
	public Color getFillColor() {
		return (fillColor == null) ? getColor() : fillColor;
	}

/* Method: getBounds() */
/**
 * Returns the bounding box of this object, which is defined to be the
 * smallest rectangle that covers everything drawn by the figure.
 *
 * Example: GRectangle bounds = gpoly.getBounds();
 * @return The bounding box for this object
 */
	public GRectangle getBounds() {
		return vertices.getBounds(getX(), getY(), xScale, yScale, rotation);
	}

/* Method: contains(x, y) */
/**
 * Checks to see whether a point is inside the object.
 *
 * Example: if (gpoly.contains(x, y)) . . .
 * @param x The x-coordinate of the point being tested
 * @param y The y-coordinate of the point being tested
 * @return true if the point (xy) is inside
 *         the object, and false otherwise
 */
	public boolean contains(double x, double y) {
		return vertices.contains((x - getX()) / xScale, (y - getY()) / yScale);
	}

/* Method: paint(g) */
/**
 * Implements the paint operation for this graphical object.  This method
 * is not called directly by clients.
 *
 */
	public void paint(Graphics g) {
		if (vertices.size() == 0) return;
		Polygon p = getPolygon();
		if (isFilled()) {
			g.setColor(getFillColor());
			g.fillPolygon(p.xpoints, p.ypoints, p.npoints);
			g.setColor(getColor());
		}
		g.drawPolygon(p.xpoints, p.ypoints, p.npoints);
	}

/* Method: recenter() */
/**
 * Recalculates the vertices of the polygon so that they are positioned
 * relative to the geometric center of the object.  This method allows
 * clients to take a polygon drawn using mouse clicks on the screen and
 * then to reformulate it so that it can be displayed relative to its center.
 *
 * Example: gpoly.recenter();
 */
	public void recenter() {
		vertices.recenter();
		cacheValid = false;
	}

/* Method: clone() */
/**
 * Overrides clone in Object to make sure
 * that the vertex list is copied rather than shared.
 *
 */
	public Object clone() {
		try {
			GPolygon clone = (GPolygon) super.clone();
			clone.vertices = new VertexList(clone.vertices);
			return clone;
		} catch (Exception CloneNotSupportedException) {
			throw new ErrorException("Impossible exception");
		}
	}

/* Inherited method: setLocation(x, y) */
/**
 * @inherited GObject#void setLocation(double x, double y)
 * Sets the location of this object to the point (x, y).
 */

/* Inherited method: setLocation(pt) */
/**
 * @inherited GObject#void setLocation(GPoint pt)
 * Sets the location of this object to the specified point.
 */

/* Inherited method: getLocation() */
/**
 * @inherited GObject#GPoint getLocation()
 * Returns the location of this object as a GPoint.
 */

/* Inherited method: getX() */
/**
 * @inherited GObject#double getX()
 * Returns the x-coordinate of the object.
 */

/* Inherited method: getY() */
/**
 * @inherited GObject#double getY()
 * Returns the y-coordinate of the object.
 */

/* Inherited method: move(dx, dy) */
/**
 * @inherited GObject#void move(double dx, double dy)
 * Moves the object on the screen using the displacements dx and dy.
 */

/* Inherited method: movePolar(r, theta) */
/**
 * @inherited GObject#void movePolar(double r, double theta)
 * Moves the object using displacements given in polar coordinates.
 */

/* Inherited method: getSize() */
/**
 * @inherited GObject#GDimension getSize()
 * Returns the size of the bounding box for this object.
 */

/* Inherited method: getWidth() */
/**
 * @inherited GObject#double getWidth()
 * Returns the width of this object, which is defined to be
 * the width of the bounding box.
 */

/* Inherited method: getHeight() */
/**
 * @inherited GObject#double getHeight()
 * Returns the height of this object, which is defined to be
 * the height of the bounding box.
 */

/* Inherited method: contains(pt) */
/**
 * @inherited GObject#boolean contains(GPoint pt)
 * Checks to see whether a point is inside the object.
 */

/* Inherited method: sendToFront() */
/**
 * @inherited GObject#void sendToFront()
 * Moves this object to the front of the display in the z dimension.
 */

/* Inherited method: sendToBack() */
/**
 * @inherited GObject#void sendToBack()
 * Moves this object to the back of the display in the z dimension.
 */

/* Inherited method: sendForward() */
/**
 * @inherited GObject#void sendForward()
 * Moves this object one step toward the front in the z dimension.
 */

/* Inherited method: sendBackward() */
/**
 * @inherited GObject#void sendBackward()
 * Moves this object one step toward the back in the z dimension.
 */

/* Inherited method: setColor(color) */
/**
 * @inherited GObject#void setColor(Color color)
 * Sets the color used to display this object.
 */

/* Inherited method: getColor() */
/**
 * @inherited GObject#Color getColor()
 * Returns the color used to display this object.
 */

/* Inherited method: setVisible(visible) */
/**
 * @inherited GObject#void setVisible(boolean visible)
 * Sets whether this object is visible.
 */

/* Inherited method: isVisible() */
/**
 * @inherited GObject#boolean isVisible()
 * Checks to see whether this object is visible.
 */

/* Inherited method: addMouseListener(listener) */
/**
 * @inherited GObject#void addMouseListener(MouseListener listener)
 * Adds a mouse listener to this graphical object.
 */

/* Inherited method: removeMouseListener(listener) */
/**
 * @inherited GObject#void removeMouseListener(MouseListener listener)
 * Removes a mouse listener from this graphical object.
 */

/* Inherited method: addMouseMotionListener(listener) */
/**
 * @inherited GObject#void addMouseMotionListener(MouseMotionListener listener)
 * Adds a mouse motion listener to this graphical object.
 */

/* Inherited method: removeMouseMotionListener(listener) */
/**
 * @inherited GObject#void removeMouseMotionListener(MouseMotionListener listener)
 * Removes a mouse motion listener from this graphical object.
 */

/* Protected method: repaint() */
/**
 * Overrides repaint in GObject to invalidate the
 * cached polygon.
 *
 */
	protected void repaint() {
		cacheValid = false;
		super.repaint();
	}

/* Protected method: getPolygon() */
/**
 * Returns an AWT Polygon whose points are as close as possible
 * to the ones in this GPolygon.
 *
 * Example: Polygon p = gpoly.getPolygon();
 * @return An AWT polygon corresponding to this object
 */
	protected Polygon getPolygon() {
		if (cacheValid) return poly;
		poly = vertices.createPolygon(getX(), getY(), xScale, yScale, rotation);
		cacheValid = true;
		return poly;

	}

/* Protected method: markAsComplete() */
/**
 * Calling this method makes it illegal to add or remove vertices from the
 * polygon.  Subclasses can invoke this method to protect the integrity of
 * the structure from changes by the client.
 */
	protected void markAsComplete() {
		complete = true;
	}

/* Protected method: clear() */
/**
 * Calling this method deletes all vertices from the polygon and resets the
 * scale and rotation factors to the their default values.  Subclasses can
 * use this method to reconstruct a polygon.
 */
	protected void clear() {
		if (complete) {
			throw new ErrorException("You can't clear a GPolygon that has been "
			                         + "marked as complete.");
		}
		vertices.clear();
		rotation = 0;
		xScale = 1.0;
		yScale = 1.0;
		cacheValid = false;
	}

/* Private instance variables */
	private double xScale;
	private double yScale;
	private double rotation;
	private VertexList vertices;
	private boolean cacheValid;
	private boolean complete;
	private Polygon poly;
	private boolean isFilled;
	private Color fillColor;

/* Serial version UID */
/**
 * The serialization code for this class.  This value should be incremented
 * whenever you change the structure of this class in an incompatible way,
 * typically by adding a new instance variable.
 */
	static final long serialVersionUID = 1L;
}

/* Package class: VertexList */
/**
 * The VertexList class represents a list of vertices.
 */
class VertexList implements Serializable {

/* Constructor: new VertexList() */
/**
 * Creates a new VertexList with no elements.
 */
	public VertexList() {
		vertices = new ArrayList();
		cx = 0;
		cy = 0;
	}

/* Constructor: new VertexList(oldList) */
/**
 * Creates a new VertexList that is a clone of the old one.
 */
	public VertexList(VertexList oldList) {
		this();
		for (int i = 0; i < oldList.vertices.size(); i++) {
			vertices.add(oldList.vertices.get(i));
		}
	}

/* Method: addVertex(x, y) */
/**
 * Adds the specified vertex to the end of the list.
 */
	public synchronized void addVertex(double x, double y) {
		cx = x;
		cy = y;
		vertices.add(new GPoint(cx, cy));
	}

/* Method: addEdge(dx, dy) */
/**
 * Adds the specified edge to the end of the list.
 */
	public synchronized void addEdge(double dx, double dy) {
		cx += dx;
		cy += dy;
		vertices.add(new GPoint(cx, cy));
	}

/* Method: addArc(arcWidth, arcHeight, start, sweep) */
/**
 * Adds a series of edges to the polygon that simulates the arc specified by
 * the parameters.  The x and y parameters for the arc bounding
 * box are computed implicitly by figuring out what values would place the
 * current vertex at the starting position.
 */
	public void addArc(double arcWidth, double arcHeight, double start, double sweep) {
		double aspectRatio = arcHeight / arcWidth;
		double rx = arcWidth / 2.0;
		double ry = arcHeight / 2.0;
		double x0 = cx - rx * GMath.cosDegrees(start);
		double y0 = cy + ry * GMath.sinDegrees(start);
		if (sweep > 359.99) sweep = 360;
		if (sweep < -359.99) sweep = -360;
		double dt = Math.atan2(1, Math.max(arcWidth, arcHeight));
		int nSteps = (int) (GMath.toRadians(Math.abs(sweep)) / dt);
		dt = GMath.toRadians(sweep) / nSteps;
		double theta = GMath.toRadians(start);
		for (int i = 0; i < nSteps; i++) {
			theta += dt;
			double px = x0 + rx * Math.cos(theta);
			double py = y0 - rx * Math.sin(theta) * aspectRatio;
			addVertex(px, py);
		}
	}

/* Method: add(array) */
/**
 * Adds copies of the points to the end of the vertex list.
 */
	public synchronized void add(GPoint[] array) {
		for (int i = 0; i < array.length; i++) {
			vertices.add(new GPoint(array[i].getX(), array[i].getY()));
		}
	}

/* Method: remove(vertex) */
/**
 * Removes the specified vertex from the list.
 */
	public synchronized void remove(GPoint vertex) {
		vertices.remove(vertex);
	}

/* Method: clear() */
/**
 * Removes all vertices from the list.
 */
	public synchronized void clear() {
		vertices.clear();
	}

/* Method: size() */
/**
 * Returns the number of vertices in the list.
 */
	public int size() {
		return vertices.size();
	}

/* Method: getCurrentPoint() */
/**
 * Returns the coordinates of the last vertex added to the polygon, or null
 * if the polygon is empty.
 */
	public GPoint getCurrentPoint() {
		return (vertices.size() == 0) ? null : new GPoint(cx, cy);
	}

/* Method: getBounds(x0, y0, xScale, yScale, rotation) */
/**
 * Returns the bounding box for the polygon.
 */
	public synchronized GRectangle getBounds(double x0, double y0, double xScale, double yScale, double rotation) {
		int nPoints = vertices.size();
		if (nPoints == 0) return new GRectangle();
		double xMin = 0;
		double xMax = 0;
		double yMin = 0;
		double yMax = 0;
		double sinTheta = GMath.sinDegrees(rotation);
		double cosTheta = GMath.cosDegrees(rotation);
		boolean first = true;
		for (int i = 0; i < vertices.size(); i++) {
			GPoint vertex = vertices.get(i);
			double x = x0 + xScale * (cosTheta * vertex.getX() + sinTheta * vertex.getY());
			double y = y0 + yScale * (cosTheta * vertex.getY() - sinTheta * vertex.getX());
			if (first) {
				xMin = x;
				xMax = x;
				yMin = y;
				yMax = y;
				first = false;
			} else {
				xMin = Math.min(xMin, x);
				xMax = Math.max(xMax, x);
				yMin = Math.min(yMin, y);
				yMax = Math.max(yMax, y);
			}
		}
		return new GRectangle(xMin, yMin, xMax - xMin, yMax - yMin);
	}

/* Method: contains(x, y) */
/**
 * Returns true if the polygon described by this
 * VertexList contains the specified point.
 */
	public synchronized boolean contains(double x, double y) {
		int nPoints = vertices.size();
		boolean isContained = false;
		for (int i = 0; i < nPoints; i++) {
			GPoint v1 = vertices.get(i);
			GPoint v2 = vertices.get((i + 1) % nPoints);
			if (((v1.getY() < y) && (v2.getY() >= y)) || ((v2.getY() < y) && (v1.getY() >= y))) {
				if (v1.getX() + (y - v1.getY()) / (v2.getY() - v1.getY()) * (v2.getX() - v1.getX()) < x) {
					isContained = !isContained;
				}
			}
		}
		return isContained;
	}

/* Method: createPolygon(x0, y0, xScale, yScale, rotation) */
/**
 * Creates a java.awt Polygon.
 */
	public synchronized Polygon createPolygon(double x0, double y0, double xScale, double yScale, double rotation) {
		double sinTheta = GMath.sinDegrees(rotation);
		double cosTheta = GMath.cosDegrees(rotation);
		Polygon poly = new Polygon();
		for (int i = 0; i < vertices.size(); i++) {
			GPoint vertex = vertices.get(i);
			double x = x0 + xScale * (cosTheta * vertex.getX() + sinTheta * vertex.getY());
			double y = y0 + yScale * (cosTheta * vertex.getY() - sinTheta * vertex.getX());
			poly.addPoint(GMath.round(x), GMath.round(y));
		}
		return poly;
	}

/* Method: recenter() */
/**
 * Recalculates the vertices of the polygon so that they are positioned
 * relative to the geometric center of the object.  This method allows
 * clients to take a polygon drawn using mouse clicks on the screen and
 * then to reformulate it so that it can be displayed relative to its center.
 */
	public void recenter() {
		double xMin = 0;
		double xMax = 0;
		double yMin = 0;
		double yMax = 0;
		boolean first = true;
		for (int i = 0; i < vertices.size(); i++) {
			GPoint vertex = vertices.get(i);
			if (first) {
				xMin = vertex.getX();
				xMax = vertex.getX();
				yMin = vertex.getY();
				yMax = vertex.getY();
				first = false;
			} else {
				xMin = Math.min(xMin, vertex.getX());
				xMax = Math.max(xMax, vertex.getX());
				yMin = Math.min(yMin, vertex.getY());
				yMax = Math.max(yMax, vertex.getY());
			}
		}
		double xc = (xMin + xMax) / 2;
		double yc = (yMin + yMax) / 2;
		for (int i = 0; i < vertices.size(); i++ ) {
			GPoint vertex = vertices.get(i);
			vertex.translate(-xc, -yc);
		}
	}

/* Private instance variables */
	private ArrayList vertices;
	private double cx;
	private double cy;

};




© 2015 - 2024 Weber Informatics LLC | Privacy Policy