package com.kartoflane.superluminal2.components;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
/**
* A class representing a drawable convex polygon.
*
* @author kartoFlane
*
*/
public class Polygon {
/**
* An array of alternating x and y values<br>
* Even indices = x<br>
* Odd indices = y
*/
private int[] points;
/** Number of the polygon's vertices */
private int vertices;
/** Cached rectangle containing the polygon */
private Rectangle bounds;
/**
* Creates a polygon from two arrays, one specifying the vertices' x coordinates,
* the other - y coordinates.<br>
* The arrays' lengths have to be equal, and greater than 2.
*
* @param pointsX
* @param pointsY
*/
public Polygon(int[] pointsX, int[] pointsY) {
set(pointsX, pointsY);
}
/**
* Creates a polygon from an array of alternating x and y values.<br>
* The array's length has to be greater than 2.
*
* @param points
* an array of alternating x and y values
*/
public Polygon(int[] points) {
set(points);
}
/**
* Creates a polygon from an array of points.<br>
* The array's length has to be greater than 2.
*
* @param points
*/
public Polygon(Point[] points) {
set(points);
}
/**
* Creates a polygon with the specified number of vertices, but all values at 0.
*
* @param vertices
*/
public Polygon(int vertices) {
if (vertices <= 2)
throw new IllegalArgumentException("Number of vertices is too low (3 minimum)");
this.vertices = vertices;
this.points = new int[vertices * 2];
}
/**
* Constructs a deep copy of the specified polygon.
*/
public Polygon(Polygon poly) {
points = poly.toArray();
vertices = poly.vertices;
bounds = poly.getBounds();
}
/**
* @return the center of the polygon's bounds
*/
public Point getCenter() {
return new Point(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
}
/**
* @return the number of vertices this polygon has.
*/
public int getVertexCount() {
return vertices;
}
/**
* Creates a polygon from two arrays, one specifying the vertices' x coordinates,
* the other - y coordinates.<br>
* The arrays' lengths have to be equal, and greater than 2.
*
* @param pointsX
* @param pointsY
*/
public void set(int[] pointsX, int[] pointsY) {
if (pointsX.length != pointsY.length)
throw new IllegalArgumentException("Coordinate arrays are not equal.");
if (pointsX.length <= 2)
throw new IllegalArgumentException("The array is too small to create a polygon.");
this.vertices = pointsX.length;
this.points = new int[vertices * 2];
for (int i = 0; i < vertices; i++) {
points[i * 2] = pointsX[i];
points[i * 2 + 1] = pointsY[i];
}
calculateBounds();
}
/**
* Creates a polygon from an array of alternating x and y values.<br>
* The array's length has to be greater than 2.
*
* @param points
* an array of alternating x and y values
*/
public void set(int[] points) {
if (points.length % 2 != 0)
throw new IllegalArgumentException("Array length must be even.");
if (points.length <= 2)
throw new IllegalArgumentException("The array is too small to create a polygon.");
this.vertices = points.length / 2;
this.points = points.clone();
for (int i = 0; i < vertices; i++) {
points[i * 2] = points[i * 2];
points[i * 2 + 1] = points[i * 2 + 1];
}
calculateBounds();
}
/**
* Creates a polygon from an array of points.<br>
* The array's length has to be greater than 2.
*
* @param points
*/
public void set(Point[] points) {
if (points.length <= 2)
throw new IllegalArgumentException("The array is too small to create a polygon.");
this.vertices = points.length;
this.points = new int[vertices * 2];
for (int i = 0; i < vertices; i++) {
this.points[i * 2] = points[i].x;
this.points[i * 2 + 1] = points[i].y;
}
calculateBounds();
}
/**
* Moves the polygon by the specified vector.
*/
public void translate(int dx, int dy) {
for (int i = 0; i < vertices; i++) {
points[i * 2] += dx;
points[i * 2 + 1] += dy;
}
bounds.x += dx;
bounds.y += dy;
}
/**
* Centers the polygon at the specified location.
*
* @see #getCenter()
*/
public void setLocation(int x, int y) {
Point center = getCenter();
translate(x - center.x, y - center.y);
}
/**
* @return the polygon represented as an array of alternating x and y values
*/
public int[] toArray() {
return points.clone();
}
/**
* Rotates the polygon around the coordinates specified in the argument.
*
* @param radians
* angle in radians. 0 means north.
* @param centerX
* x coodinate of the point around which the polygon will be rotated
* @param centerY
* y coodinate of the point around which the polygon will be rotated
*/
public void rotate(float radians, int centerX, int centerY) {
double sin = Math.sin(radians);
double cos = Math.cos(radians);
for (int i = 0; i < vertices; i++) {
// Translate point back to origin
points[i * 2] -= centerX;
points[i * 2 + 1] -= centerY;
// Rotate point
int newx = (int) (points[i * 2] * cos - points[i * 2 + 1] * sin);
int newy = (int) (points[i * 2] * sin + points[i * 2 + 1] * cos);
// Translate point back
points[i * 2] = newx + centerX;
points[i * 2 + 1] = newy + centerY;
}
calculateBounds();
}
/**
* @see #rotate(float, int, int)
*/
public void rotate(float radians, Point p) {
rotate(radians, p.x, p.y);
}
/**
* Rotates the polygon around its {@link #getCenter center point}.
*
* @param radians
* angle in radians. 0 means north.
*/
public void rotate(float radians) {
Point c = getCenter();
rotate(radians, c.x, c.y);
}
/**
* @return the smallest rectangle that can contain the polygon.
*/
public Rectangle getBounds() {
return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
}
/**
* Draw the polygon using {@link GC#drawPolygon(int[])}
*/
public void draw(PaintEvent e) {
e.gc.drawPolygon(points);
}
/**
* Draw the polygon using {@link GC#fillPolygon(int[])}
*/
public void fill(PaintEvent e) {
e.gc.fillPolygon(points);
}
/**
* Checks whether the polygon contains the specified point.<br>
* <br>
* StackOverflow magic: http://stackoverflow.com/questions/8721406/how-to-determine-if-a-point-is-inside-a-2d-convex-polygon
*
* @return true if the polygon contains the point, false otherwise
*/
public boolean contains(int x, int y) {
boolean result = false;
for (int i = 0, j = vertices - 1; i < vertices; j = i++) {
if ((points[i * 2 + 1] > y) != (points[j * 2 + 1] > y) &&
(x < (points[j * 2] - points[i * 2]) * (y - points[i * 2 + 1]) / (points[j * 2 + 1] - points[i * 2 + 1]) + points[i * 2])) {
result = !result;
}
}
return result;
}
/**
* @see #contains(int, int)
*/
public boolean contains(Point p) {
return contains(p.x, p.y);
}
/**
* Taken from {@link java.awt.Polygon#calculateBounds(int[], int[], int)}
*/
private void calculateBounds() {
int boundsMinX = Integer.MAX_VALUE;
int boundsMinY = Integer.MAX_VALUE;
int boundsMaxX = Integer.MIN_VALUE;
int boundsMaxY = Integer.MIN_VALUE;
for (int i = 0; i < vertices; i++) {
int x = points[i * 2];
boundsMinX = Math.min(boundsMinX, x);
boundsMaxX = Math.max(boundsMaxX, x);
int y = points[i * 2 + 1];
boundsMinY = Math.min(boundsMinY, y);
boundsMaxY = Math.max(boundsMaxY, y);
}
bounds = new Rectangle(boundsMinX, boundsMinY, boundsMaxX - boundsMinX, boundsMaxY - boundsMinY);
}
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("Polygon: { ");
for (int i = 0; i < vertices; i++) {
buf.append(points[i * 2] + ", " + points[i * 2 + 1]);
if (i != vertices - 1)
buf.append(" ; ");
}
buf.append(" }");
return buf.toString();
}
public int hashCode() {
return bounds.hashCode() ^ points.hashCode();
}
}