com.github.mathiewz.slick.geom.Shape Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of modernized-slick Show documentation
Show all versions of modernized-slick Show documentation
The main purpose of this libraryis to modernize and maintain the slick2D library.
The newest version!
package com.github.mathiewz.slick.geom;
import java.io.Serializable;
/**
* The description of any 2D shape that can be transformed. The points provided approximate the intent
* of the shape.
*
* @author Mark
*/
public abstract class Shape implements Serializable {
/** The points representing this polygon. */
protected Float[] points;
/** Center point of the polygon. */
protected float[] center;
/** The left most point of this shape. */
protected float x;
/** The top most point of this shape. */
protected float y;
/** The right most point of this shape */
protected float maxX;
/** The bottom most point of this shape */
protected float maxY;
/** The left most point of this shape. */
protected float minX;
/** The top most point of this shape. */
protected float minY;
/** Radius of a circle that can completely enclose this shape. */
protected float boundingCircleRadius;
/** Flag to tell whether points need to be generated */
protected boolean pointsDirty;
/** The triangles that define the shape */
protected transient Triangulator tris;
/** True if the triangles need updating */
protected boolean trianglesDirty;
/**
* Shape constructor.
*
*/
public Shape() {
pointsDirty = true;
}
/**
* Set the top-left location of this shape
*
* @param x
* The x coordinate of the new location of the shape
* @param y
* The y coordinate of the new location of the shape
*/
public void setLocation(float x, float y) {
setX(x);
setY(y);
}
/**
* Apply a transformation and return a new shape. This will not alter the current shape but will
* return the transformed shape.
*
* @param transform
* The transform to be applied
* @return The transformed shape.
*/
public abstract Shape transform(Transform transform);
/**
* Subclasses implement this to create the points of the shape.
*
*/
protected abstract void createPoints();
/**
* Get the x location of the left side of this shape.
*
* @return The x location of the left side of this shape.
*/
public float getX() {
checkPoints();
return x;
}
/**
* Set the x position of the left side this shape.
*
* @param x
* The new x position of the left side this shape.
*/
public void setX(float x) {
if (x != this.x) {
float dx = x - this.x;
this.x = x;
if (points == null || center == null) {
checkPoints();
}
// update the points in the special case
for (int i = 0; i < points.length / 2; i++) {
points[i * 2] += dx;
}
center[0] += dx;
maxX += dx;
minX += dx;
trianglesDirty = true;
}
}
public void addX(float delta) {
setX(getX() + delta);
}
/**
* Get the y position of the top of this shape.
*
* @return The y position of the top of this shape.
*/
public float getY() {
checkPoints();
return y;
}
/**
* Set the y position of the top of this shape.
*
* @param y
* The new y position of the top of this shape.
*/
public void setY(float y) {
if (y != this.y) {
float dy = y - this.y;
this.y = y;
if (points == null || center == null) {
checkPoints();
}
// update the points in the special case
for (int i = 0; i < points.length / 2; i++) {
points[i * 2 + 1] += dy;
}
center[1] += dy;
maxY += dy;
minY += dy;
trianglesDirty = true;
}
}
public void addY(float delta) {
setY(getY() + delta);
}
/**
* Get the top-left location of this shape.
*
* @return The coordinate of the top-left of this shape
*/
public Vector2f getLocation() {
return new Vector2f(getX(), getY());
}
/**
* Set the top-left location of this shape
*
* @param loc
* The new coordinate of the top-left of this shape
*/
public void setLocation(Vector2f loc) {
setX(loc.getX());
setY(loc.getY());
}
/**
* Get the x center of this shape.
*
* @return The x center of this shape.
*/
public float getCenterX() {
checkPoints();
return center[0];
}
/**
* Set the x center of this shape.
*
* @param centerX
* The center point to set.
*/
public void setCenterX(float centerX) {
if (points == null || center == null) {
checkPoints();
}
float xDiff = centerX - getCenterX();
setX(x + xDiff);
}
/**
* Get the y center of this shape.
*
* @return The y center of this shape.
*/
public float getCenterY() {
checkPoints();
return center[1];
}
/**
* Set the y center of this shape.
*
* @param centerY
* The center point to set.
*/
public void setCenterY(float centerY) {
if (points == null || center == null) {
checkPoints();
}
float yDiff = centerY - getCenterY();
setY(y + yDiff);
}
/**
* Get the right most point of this shape.
*
* @return The right most point of this shape.
*/
public float getMaxX() {
checkPoints();
return maxX;
}
/**
* Get the bottom most point of this shape.
*
* @return The bottom most point of this shape.
*/
public float getMaxY() {
checkPoints();
return maxY;
}
/**
* Get the left most point of this shape.
*
* @return The left most point of this shape.
*/
public float getMinX() {
checkPoints();
return minX;
}
/**
* Get the top most point of this shape.
*
* @return The top most point of this shape.
*/
public float getMinY() {
checkPoints();
return minY;
}
/**
* Get the radius of a circle that can completely enclose this shape.
*
* @return The radius of the circle.
*/
public float getBoundingCircleRadius() {
checkPoints();
return boundingCircleRadius;
}
/**
* Get the point closet to the center of all the points in this Shape
*
* @return The x,y coordinates of the center.
*/
public float[] getCenter() {
checkPoints();
return center;
}
/**
* Get the points that outline this shape. Use CW winding rule
*
* @return an array of x,y points
*/
public Float[] getPoints() {
checkPoints();
return points;
}
/**
* Get the number of points in this polygon
*
* @return The number of points in this polygon
*/
public int getPointCount() {
checkPoints();
return points.length / 2;
}
/**
* Get a single point in this polygon
*
* @param index
* The index of the point to retrieve
* @return The point's coordinates
*/
public float[] getPoint(int index) {
checkPoints();
float[] result = new float[2];
result[0] = points[index * 2];
result[1] = points[index * 2 + 1];
return result;
}
/**
* Get the combine normal of a given point
*
* @param index
* The index of the point whose normal should be retrieved
* @return The combined normal of a given point
*/
public float[] getNormal(int index) {
float[] current = getPoint(index);
float[] prev = getPoint(index - 1 < 0 ? getPointCount() - 1 : index - 1);
float[] next = getPoint(index + 1 >= getPointCount() ? 0 : index + 1);
float[] t1 = getNormal(prev, current);
float[] t2 = getNormal(current, next);
if (index == 0 && !closed()) {
return t2;
}
if (index == getPointCount() - 1 && !closed()) {
return t1;
}
float tx = (t1[0] + t2[0]) / 2;
float ty = (t1[1] + t2[1]) / 2;
float len = (float) Math.sqrt(tx * tx + ty * ty);
return new float[] { tx / len, ty / len };
}
/**
* Check if the shape passed is entirely contained within
* this shape.
*
* @param other
* The other shape to test against this one
* @return True if the other shape supplied is entirely contained
* within this one.
*/
public boolean contains(Shape other) {
if (other.intersects(this)) {
return false;
}
for (int i = 0; i < other.getPointCount(); i++) {
float[] pt = other.getPoint(i);
if (!contains(pt[0], pt[1])) {
return false;
}
}
return true;
}
/**
* Get the normal of the line between two points
*
* @param start
* The start point
* @param end
* The end point
* @return The normal of the line between the two points
*/
private float[] getNormal(float[] start, float[] end) {
float dx = start[0] - end[0];
float dy = start[1] - end[1];
float len = (float) Math.sqrt(dx * dx + dy * dy);
dx /= len;
dy /= len;
return new float[] { -dy, dx };
}
/**
* Check if the given point is part of the path that
* forms this shape
*
* @param x
* The x position of the point to check
* @param y
* The y position of the point to check
* @return True if the point is includes in the path of the polygon
*/
public boolean includes(float x, float y) {
if (points.length == 0) {
return false;
}
checkPoints();
Line testLine = new Line(0, 0, 0, 0);
Vector2f pt = new Vector2f(x, y);
for (int i = 0; i < points.length; i += 2) {
int n = i + 2;
if (n >= points.length) {
n = 0;
}
testLine.set(points[i], points[i + 1], points[n], points[n + 1]);
if (testLine.on(pt)) {
return true;
}
}
return false;
}
/**
* Get the index of a given point
*
* @param x
* The x coordinate of the point
* @param y
* The y coordinate of the point
* @return The index of the point or -1 if the point is not part of this shape path
*/
public int indexOf(float x, float y) {
for (int i = 0; i < points.length; i += 2) {
if (points[i] == x && points[i + 1] == y) {
return i / 2;
}
}
return -1;
}
/**
* Check if this polygon contains the given point
*
* @param x
* The x position of the point to check
* @param y
* The y position of the point to check
* @return True if the point is contained in the polygon
*/
public boolean contains(float x, float y) {
checkPoints();
if (points.length == 0) {
return false;
}
boolean result = false;
int npoints = points.length;
float xold = points[npoints - 2];
float yold = points[npoints - 1];
for (int i = 0; i < npoints; i += 2) {
float x1;
float y1;
float x2;
float y2;
float xnew = points[i];
float ynew = points[i + 1];
if (xnew > xold) {
x1 = xold;
x2 = xnew;
y1 = yold;
y2 = ynew;
} else {
x1 = xnew;
x2 = xold;
y1 = ynew;
y2 = yold;
}
if (xnew < x == x <= xold /* edge "open" at one end */
&& ((double) y - (double) y1) * (x2 - x1) < ((double) y2 - (double) y1) * (x - x1)) {
result = !result;
}
xold = xnew;
yold = ynew;
}
return result;
}
/**
* Check if this shape intersects with the shape provided.
*
* @param shape
* The shape to check if it intersects with this one.
* @return True if the shapes do intersect, false otherwise.
*/
public boolean intersects(Shape shape) {
/*
* Intersection formula used:
* (x4 - x3)(y1 - y3) - (y4 - y3)(x1 - x3)
* UA = ---------------------------------------
* (y4 - y3)(x2 - x1) - (x4 - x3)(y2 - y1)
* (x2 - x1)(y1 - y3) - (y2 - y1)(x1 - x3)
* UB = ---------------------------------------
* (y4 - y3)(x2 - x1) - (x4 - x3)(y2 - y1)
* if UA and UB are both between 0 and 1 then the lines intersect.
* Source: http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/
*/
checkPoints();
Float[] thisPoints = getPoints(); // (x3, y3) and (x4, y4)
Float[] thatPoints = shape.getPoints(); // (x1, y1) and (x2, y2)
int length = closed() ? thisPoints.length - 2 : thisPoints.length;
int thatLength = shape.closed() ? thatPoints.length - 2 : thatPoints.length;
for (int i = 0; i < length; i += 2) {
int iNext = i + 2;
if (iNext >= thisPoints.length) {
iNext = 0;
}
for (int j = 0; j < thatLength; j += 2) {
int jNext = j + 2;
if (jNext >= thatPoints.length) {
jNext = 0;
}
double unknownA = ((thisPoints[iNext] - thisPoints[i]) * (double) (thatPoints[j + 1] - thisPoints[i + 1]) - (thisPoints[iNext + 1] - thisPoints[i + 1]) * (thatPoints[j] - thisPoints[i])) / ((thisPoints[iNext + 1] - thisPoints[i + 1]) * (thatPoints[jNext] - thatPoints[j]) - (thisPoints[iNext] - thisPoints[i]) * (thatPoints[jNext + 1] - thatPoints[j + 1]));
double unknownB = ((thatPoints[jNext] - thatPoints[j]) * (double) (thatPoints[j + 1] - thisPoints[i + 1]) - (thatPoints[jNext + 1] - thatPoints[j + 1]) * (thatPoints[j] - thisPoints[i])) / ((thisPoints[iNext + 1] - thisPoints[i + 1]) * (thatPoints[jNext] - thatPoints[j]) - (thisPoints[iNext] - thisPoints[i]) * (thatPoints[jNext + 1] - thatPoints[j + 1]));
if (unknownA >= 0 && unknownA <= 1 && unknownB >= 0 && unknownB <= 1) {
return true;
}
}
}
return false;
}
/**
* Check if a particular location is a vertex of this polygon
*
* @param x
* The x coordinate to check
* @param y
* The y coordinate to check
* @return True if the cordinates supplied are a vertex of this polygon
*/
public boolean hasVertex(float x, float y) {
if (points.length == 0) {
return false;
}
checkPoints();
for (int i = 0; i < points.length; i += 2) {
if (points[i] == x && points[i + 1] == y) {
return true;
}
}
return false;
}
/**
* Get the center of this polygon.
*
*/
protected void findCenter() {
center = new float[] { 0, 0 };
int length = points.length;
for (int i = 0; i < length; i += 2) {
center[0] += points[i];
center[1] += points[i + 1];
}
center[0] /= length / 2f;
center[1] /= length / 2f;
}
/**
* Calculate the radius of a circle that can completely enclose this shape.
*
*/
protected void calculateRadius() {
boundingCircleRadius = 0;
for (int i = 0; i < points.length; i += 2) {
float temp = (points[i] - center[0]) * (points[i] - center[0]) + (points[i + 1] - center[1]) * (points[i + 1] - center[1]);
boundingCircleRadius = boundingCircleRadius > temp ? boundingCircleRadius : temp;
}
boundingCircleRadius = (float) Math.sqrt(boundingCircleRadius);
}
/**
* Calculate the triangles that can fill this shape
*/
protected void calculateTriangles() {
if (!trianglesDirty && tris != null) {
return;
}
if (points.length >= 6) {
tris = new NeatTriangulator();
for (int i = 0; i < points.length; i += 2) {
tris.addPolyPoint(points[i], points[i + 1]);
}
tris.triangulate();
}
trianglesDirty = false;
}
/**
* Increase triangulation
*/
public void increaseTriangulation() {
checkPoints();
calculateTriangles();
tris = new OverTriangulator(tris);
}
/**
* The triangles that define the filled version of this shape
*
* @return The triangles that define the
*/
public Triangulator getTriangles() {
checkPoints();
calculateTriangles();
return tris;
}
/**
* Check the dirty flag and create points as necessary.
*/
protected final void checkPoints() {
if (pointsDirty) {
createPoints();
findCenter();
calculateRadius();
if (points.length > 0) {
maxX = points[0];
maxY = points[1];
minX = points[0];
minY = points[1];
for (int i = 0; i < points.length / 2; i++) {
maxX = Math.max(points[i * 2], maxX);
maxY = Math.max(points[i * 2 + 1], maxY);
minX = Math.min(points[i * 2], minX);
minY = Math.min(points[i * 2 + 1], minY);
}
x = minX;
y = minY;
}
pointsDirty = false;
trianglesDirty = true;
}
}
/**
* Cause all internal state to be generated and cached
*/
public void preCache() {
checkPoints();
getTriangles();
}
/**
* True if this is a closed shape
*
* @return True if this is a closed shape
*/
public boolean closed() {
return true;
}
/**
* Prune any required points in this shape
*
* @return The new shape with points pruned
*/
public Shape prune() {
Polygon result = new Polygon();
for (int i = 0; i < getPointCount(); i++) {
int next = i + 1 >= getPointCount() ? 0 : i + 1;
int prev = i - 1 < 0 ? getPointCount() - 1 : i - 1;
float dx1 = getPoint(i)[0] - getPoint(prev)[0];
float dy1 = getPoint(i)[1] - getPoint(prev)[1];
float dx2 = getPoint(next)[0] - getPoint(i)[0];
float dy2 = getPoint(next)[1] - getPoint(i)[1];
float len1 = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1);
float len2 = (float) Math.sqrt(dx2 * dx2 + dy2 * dy2);
dx1 /= len1;
dy1 /= len1;
dx2 /= len2;
dy2 /= len2;
if (dx1 != dx2 || dy1 != dy2) {
result.addPoint(getPoint(i)[0], getPoint(i)[1]);
}
}
return result;
}
/**
* Subtract the given shape from this one. Note that this method only deals
* with edges, it will not create holes in polygons.
*
* @param other
* The other shape to subtract from this one
* @return The newly created set of shapes resulting from the operation
*/
public Shape[] subtract(Shape other) {
return new GeomUtil().subtract(this, other);
}
/**
* Join this shape with another.
*
* @param other
* The other shape to join with this one
* @return The newly created set of shapes resulting from the operation
*/
public Shape[] union(Shape other) {
return new GeomUtil().union(this, other);
}
/**
* Get the width of the shape
*
* @return The width of the shape
*/
public float getWidth() {
return maxX - minX;
}
/**
* Get the height of the shape
*
* @return The height of the shape
*/
public float getHeight() {
return maxY - minY;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy