/*
* Copyright 2003-2010 Tufts University Licensed under the
* Educational Community License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.osedu.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package tufts.vue.shape;
import java.awt.geom.RectangularShape;
import java.awt.geom.Rectangle2D;
import java.awt.geom.PathIterator;
import java.awt.geom.AffineTransform;
/**
* This class implements a polygon shape fit into a specified rectanular region.
* @author Scott Fraize
*/
public abstract class RectangularPoly2D extends RectangularShape
{
public static final int CENTER = 0;
public static final int NORTH = 1;
public static final int NORTH_EAST = 2;
public static final int EAST = 3;
public static final int SOUTH_EAST = 4;
public static final int SOUTH = 5;
public static final int SOUTH_WEST = 6;
public static final int WEST = 7;
public static final int NORTH_WEST = 8;
protected double x;
protected double y;
protected double width;
protected double height;
protected int sides;
protected int layout;
protected double[] xpoints;
protected double[] ypoints;
public RectangularPoly2D(int sides, double x, double y, double width, double height)
{
setSides(sides);
setFrame(x, y, width, height);
}
public RectangularPoly2D(int sides)
{
setSides(sides);
}
/** For persistance */
public RectangularPoly2D() {}
public int getContentGravity() {
return CENTER;
}
protected abstract void computeVertices();
/** a point-up triangle */
public static class Triangle extends RectangularPoly2D {
public Triangle() { setSides(3); }
public int getContentGravity() { return SOUTH; }
protected void computeVertices()
{
xpoints[0] = x + width/2;
ypoints[0] = y;
xpoints[1] = x;
ypoints[1] = y + height;
xpoints[2] = x + width;
ypoints[2] = y + height;
}
}
/** a point-down triangle */
public static class Shield extends RectangularPoly2D {
public Shield() { setSides(3); }
public int getContentGravity() { return NORTH; }
protected void computeVertices()
{
xpoints[0] = x;
ypoints[0] = y;
xpoints[1] = x + width;
ypoints[1] = y;
xpoints[2] = x + width/2;
ypoints[2] = y + height;
}
}
/** a point to the right triangle */
public static class Flag extends RectangularPoly2D {
public Flag() { setSides(3); }
public int getContentGravity() { return WEST; }
protected void computeVertices()
{
xpoints[0] = x;
ypoints[0] = y;
xpoints[1] = x;
ypoints[1] = y + height;
xpoints[2] = x + width;
ypoints[2] = y + height / 2;
}
}
/** a point to the left triangle */
public static class Flag2 extends RectangularPoly2D {
public Flag2() { setSides(3); }
public int getContentGravity() { return EAST; }
protected void computeVertices()
{
xpoints[0] = x;
ypoints[0] = y + height / 2;
xpoints[1] = x + width;
ypoints[1] = y;
xpoints[2] = x + width;
ypoints[2] = y + height;
}
}
/** a 4 sided polygon */
public static class Diamond extends RectangularPoly2D {
public Diamond() { setSides(4); }
protected void computeVertices()
{
xpoints[0] = x + width/2;
ypoints[0] = y;
xpoints[1] = x + width;
ypoints[1] = y + height/2;
xpoints[2] = x + width/2;
ypoints[2] = y + height;
xpoints[3] = x;
ypoints[3] = y + height/2;
}
}
/** a 4 sided polygon */
public static class Rhombus extends RectangularPoly2D {
public Rhombus() { setSides(4); }
protected void computeVertices()
{
xpoints[0] = x + ((double)width-(double)width*0.8);
ypoints[0] = y;
xpoints[1] = x + width;
ypoints[1] = y;
xpoints[3] = x;
ypoints[3] = y + height;
xpoints[2] = x + ((double)width-(double)width*0.2);
ypoints[2] = y + height;
}
}
/** a 5 sided polygon */
public static class Pentagon extends RectangularPoly2D {
public Pentagon() { setSides(5); }
protected void computeVertices()
{
xpoints[0] = x + width/2;
ypoints[0] = y;
xpoints[1] = x + width;
ypoints[1] = y + height/2;
xpoints[2] = x + width*3/4;
ypoints[2] = y + height;
xpoints[3] = x + width/4;
ypoints[3] = y + height;
xpoints[4] = x;
ypoints[4] = y + height/2;
}
}
/** a 6 sided polygon */
public static class Hexagon extends RectangularPoly2D {
public Hexagon() { setSides(6); }
protected void computeVertices()
{
// tan(30) = inset/halfH
// halfH*tan(30) = inset
double halfH = height/2;
//double inset = halfH * Math.tan(Math.PI/6);
double inset = 0.2257085*width;
//System.out.println("HEXAGON size=" + width + "x" + height);
//System.out.println("HEXAGON HALFH=" + halfH);
//System.out.println("HEXAGON INSET=" + inset);
//System.out.println("HEXAGON SEGSIZE TOP=" + (width-(inset*2)));
//System.out.println("HEXAGON SEGSIZE LEFT=" + Math.sqrt(inset*inset+halfH*halfH));
xpoints[0] = x + inset;
ypoints[0] = y;
xpoints[1] = x + (width - inset);
ypoints[1] = y;
xpoints[2] = x + width;
ypoints[2] = y + halfH;
xpoints[3] = x + (width - inset);
ypoints[3] = y + height;
xpoints[4] = x + inset;
ypoints[4] = y + height;
xpoints[5] = x;
ypoints[5] = y + halfH;
}
}
/** an 8 sided polygon */
public static class Octagon extends RectangularPoly2D {
public Octagon() { setSides(8); }
protected void computeVertices()
{
double xInset = width / 3.4;
double yInset = height / 3.4;
xpoints[0] = x + xInset;
ypoints[0] = y;
xpoints[1] = x + (width - xInset);
ypoints[1] = y;
xpoints[2] = x + width;
ypoints[2] = y + yInset;
xpoints[3] = x + width;
ypoints[3] = y + (height - yInset);
xpoints[4] = x + (width - xInset);
ypoints[4] = y + height;
xpoints[5] = x + xInset;
ypoints[5] = y + height;
xpoints[6] = x;
ypoints[6] = y + (height - yInset);
xpoints[7] = x;
ypoints[7] = y + yInset;
}
}
/** a point sideways Chevron */
public static class Chevron extends RectangularPoly2D {
public Chevron() { setSides(6); }
public int getContentGravity() { return CENTER; }
protected void computeVertices()
{
xpoints[0] = x + ((double)width-(double)width*0.2);
ypoints[0] = y;
xpoints[1] = x + width;
ypoints[1] = y + (height/2);
xpoints[2] = x + ((double)width-(double)width*0.2);
ypoints[2] = y + height;
xpoints[3] = x;
ypoints[3] = y + height;
xpoints[4] = x + (double)width*0.2;
ypoints[4] = y + (height/2);
xpoints[5] = x;
ypoints[5] = y;
}
}
public void setSides(int sides)
{
if (sides < 3 || sides > 8)
throw new IllegalArgumentException("RectangularPoly2D: sides not >=3 && <=8: "+sides);
this.sides = sides;
xpoints = new double[sides+1];
ypoints = new double[sides+1];
xpoints[sides] = 0;
ypoints[sides] = 0;
}
public int getSides()
{
return this.sides;
}
public Rectangle2D getBounds2D() {
return new Rectangle2D.Double(x, y, width, height);
}
public double getHeight() { return height; }
public double getWidth() { return width; }
public double getX() { return x; }
public double getY() { return y; }
public boolean isEmpty() {
return width <= 0 || height <= 0;
}
/** does this polygon contain the given point? */
public boolean contains(double px, double py)
{
// fast-reject: check if outside bounding box
if (px < x || py < y || px > x + width || py > y + height)
return false;
// below adapted from java.awt.Polygon
int hits = 0;
double lastx = xpoints[sides - 1];
double lasty = ypoints[sides - 1];
double curx, cury;
// Walk the edges of the polygon
for (int i = 0; i < sides; lastx = curx, lasty = cury, i++)
{
curx = xpoints[i];
cury = ypoints[i];
if (cury == lasty)
continue;
double leftx;
if (curx < lastx) {
if (x >= lastx) {
continue;
}
leftx = curx;
} else {
if (px >= curx) {
continue;
}
leftx = lastx;
}
double test1, test2;
if (cury < lasty) {
if (py < cury || py >= lasty) {
continue;
}
if (px < leftx) {
hits++;
continue;
}
test1 = px - curx;
test2 = py - cury;
} else {
if (py < lasty || py >= cury) {
continue;
}
if (px < leftx) {
hits++;
continue;
}
test1 = px - lastx;
test2 = py - lasty;
}
if (test1 < (test2 / (lasty - cury) * (lastx - curx))) {
hits++;
}
}
return ((hits & 1) != 0);
}
/** does shape entirely contain rectangle? */
public boolean contains(double x, double y, double w, double h)
{
return contains(x, y) && contains(x+w, y+h) && contains(x+w, y) && contains(x,y+h);
// todo: this will work to check the corners for entirely concave regular polygons
// but will need to check crossings to support irregular polygons
// (e.g. a star or cross)
}
/** does any part of shape intersect rectangle? */
public boolean intersects(double x, double y, double w, double h)
{
// fast-reject: check if outside bounding box
if (!(x + w > this.x &&
y + h > this.y &&
x < this.x + this.width &&
y < this.y + this.height))
return false;
// fast-accept: that checks to see if any vertex is within the box
for (int i = 0; i < sides; i++) {
double xp = xpoints[i];
double yp = ypoints[i];
if (xp > x && xp < x + w &&
yp > y && yp < y + h) {
//System.out.println("fast accept vertex" + i + " " + this);
return true;
}
}
Crossings cross = getCrossings(x, y, x+w, y+h);
return (cross == null || !cross.isEmpty());
}
private Crossings getCrossings(double xlo, double ylo,
double xhi, double yhi)
{
Crossings cross = new Crossings.EvenOdd(xlo, ylo, xhi, yhi);
double lastx = xpoints[sides - 1];
double lasty = ypoints[sides - 1];
double curx, cury;
// Walk the edges of the polygon
for (int i = 0; i < sides; i++) {
curx = xpoints[i];
cury = ypoints[i];
if (cross.accumulateLine(lastx, lasty, curx, cury)) {
return null;
}
lastx = curx;
lasty = cury;
}
return cross;
}
public void setFrame(double x, double y, double width, double height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
//System.out.println(this + " POLY setFrame " + x + "," + y + " " + width + "x" + height);
//new Throwable().printStackTrace();
computeVertices();
}
/*
private void computeVertices()
{
if (sides == 3)
computeVertices3();
else if (sides == 4)
computeVertices4();
else if (sides == 5)
computeVertices5();
else if (sides == 6)
computeVertices6();
else if (sides == 8)
computeVertices8();
}
*/
/*
private void computeVertices5()
//final double horizontalOffset = 0.118033989; // verticalOffset * tan(18)
final double horizontalOffset = verticalOffset*verticalOffset;
// try taking into account verticalOffset before computing horizontal offset
double dropDown = height * verticalOffset;
double inset = width * horizontalOffset;
vertices[0] = x + halfW;
vertices[0] = y;
vertices[1] = x + width;
vertices[1] = y + dropDown;
vertices[2] = x + width - inset;
vertices[2] = y + height;
vertices[3] = x + inset;
vertices[3] = y + height;
vertices[4] = x;
vertices[4] = y + dropDown;
}*/
private void new_computeVertices6()
{
// equalateral polygons are inscribed
// inside circles, not rectangles (all points
// are on a containing circle) -- this
// creates an equal hexagon inside a circle
// of the given WIDTH -- height is ignored.
// -- can't we just inscribe it in an ellipse?
double hw = width/2;
double hh = height/2;
double qw = hw/2;
double qh = Math.sqrt(hw*hw-qw*qw);
double cx = x + hw;
double cy = y + hh;
xpoints[0] = cx + qw;
ypoints[0] = cy - qh;
xpoints[1] = x + width;
ypoints[1] = y + hh;
xpoints[2] = cx + qw;
ypoints[2] = cy + qh;
xpoints[3] = cx - qw;
ypoints[3] = cy + qh;
xpoints[4] = x;
ypoints[4] = y + hh;
xpoints[5] = cx - qw;
ypoints[5] = cy - qh;
}
public PathIterator getPathIterator(AffineTransform affineTransform)
{
return new PolyIterator(affineTransform);
}
public Object clone()
{
RectangularPoly2D cloned = (RectangularPoly2D) super.clone();
cloned.xpoints = new double[sides+1];
cloned.ypoints = new double[sides+1];
System.arraycopy(this.xpoints, 0, cloned.xpoints, 0, sides+1);
System.arraycopy(this.ypoints, 0, cloned.ypoints, 0, sides+1);
//cloned.computeVertices();
return cloned;
}
public String toString()
{
return getClass().getName() + "@" + Integer.toHexString(hashCode()) + "[sides=" + sides + " " + x + "," + y + " " + width + ", " + height + "]";
}
class PolyIterator implements PathIterator
{
int index = 0;
AffineTransform affine;
public PolyIterator(AffineTransform affine)
{
this.affine = affine;
}
public int currentSegment(double[] coords) {
coords[0] = xpoints[index];
coords[1] = ypoints[index];
if (affine != null)
affine.transform(coords, 0, coords, 0, 1);
//System.out.println("i"+index + " (double)coords=" +coords[0] + "," + coords[1]);
if (index == 0)
return SEG_MOVETO;
else if (index == sides)
return SEG_CLOSE;
else
return SEG_LINETO;
}
public int currentSegment(float[] coords){
coords[0] = (float) xpoints[index];
coords[1] = (float) ypoints[index];
if (affine != null)
affine.transform(coords, 0, coords, 0, 1);
//System.out.println("i"+index + " (float)coords=" +coords[0] + "," + coords[1]);
if (index == 0)
return SEG_MOVETO;
else if (index == sides)
return SEG_CLOSE;
else
return SEG_LINETO;
}
public int getWindingRule() { return PathIterator.WIND_NON_ZERO; }
public boolean isDone() { return index > sides; }
public void next() { index++ ; }
}
}