com.github.mathiewz.slick.geom.GeomUtil 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.util.ArrayList;
/**
* A set of utilities to play with geometry
*
* @author kevin
*/
public class GeomUtil {
/** The tolerance for determining changes and steps */
public static final float EPSILON = 0.0001f;
/** The tolerance for determining direction change */
public static final float EDGE_SCALE = 1f;
/** The maximum number of points returned by an operation - prevents full lockups */
public static final int MAX_POINTS = 10000;
/** The listener to notify of operations */
private GeomUtilListener listener;
/**
* Subtract one shape from another - note this is experimental and doesn't
* currently handle islands
*
* @param targetArg
* The target to be subtracted from
* @param missingArg
* The shape to subtract
* @return The newly created shapes
*/
public Shape[] subtract(Shape targetArg, Shape missingArg) {
Shape target = targetArg.transform(new Transform());
Shape missing = missingArg.transform(new Transform());
int count = 0;
for (int i = 0; i < target.getPointCount(); i++) {
if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
count++;
}
}
if (count == target.getPointCount()) {
return new Shape[0];
}
if (!target.intersects(missing)) {
return new Shape[] { target };
}
int found = 0;
for (int i = 0; i < missing.getPointCount(); i++) {
if (target.contains(missing.getPoint(i)[0], missing.getPoint(i)[1])) {
if (!onPath(target, missing.getPoint(i)[0], missing.getPoint(i)[1])) {
found++;
}
}
}
for (int i = 0; i < target.getPointCount(); i++) {
if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
if (!onPath(missing, target.getPoint(i)[0], target.getPoint(i)[1])) {
found++;
}
}
}
if (found < 1) {
return new Shape[] { target };
}
return combine(target, missing, true);
}
/**
* Check if the given point is on the path
*
* @param path
* The path to check
* @param x
* The x coordinate of the point to check
* @param y
* The y coordiante of teh point to check
* @return True if the point is on the path
*/
private boolean onPath(Shape path, float x, float y) {
for (int i = 0; i < path.getPointCount() + 1; i++) {
int n = rationalPoint(path, i + 1);
Line line = getLine(path, rationalPoint(path, i), n);
if (line.distance(new Vector2f(x, y)) < EPSILON * 100) {
return true;
}
}
return false;
}
/**
* Set the listener to be notified of geometry based operations
*
* @param listener
* The listener to be notified of geometry based operations
*/
public void setListener(GeomUtilListener listener) {
this.listener = listener;
}
/**
* Join to shapes together. Note that the shapes must be touching
* for this method to work.
*
* @param targetArg
* The target shape to union with
* @param otherArg
* The additional shape to union
* @return The newly created shapes
*/
public Shape[] union(Shape targetArg, Shape otherArg) {
Shape target = targetArg.transform(new Transform());
Shape other = otherArg.transform(new Transform());
if (!target.intersects(other)) {
return new Shape[] { target, other };
}
// handle the case where intersects is true but really we're talking
// about edge points
boolean touches = false;
int buttCount = 0;
for (int i = 0; i < target.getPointCount(); i++) {
if (other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
touches = true;
break;
}
}
if (other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
buttCount++;
}
}
for (int i = 0; i < other.getPointCount(); i++) {
if (target.contains(other.getPoint(i)[0], other.getPoint(i)[1])) {
if (!target.hasVertex(other.getPoint(i)[0], other.getPoint(i)[1])) {
touches = true;
break;
}
}
}
if (!touches && buttCount < 2) {
return new Shape[] { target, other };
}
// so they are definitely touching, consider the union
return combine(target, other, false);
}
/**
* Perform the combination
*
* @param target
* The target shape we're updating
* @param other
* The other shape in the operation
* @param subtract
* True if it's a subtract operation, otherwise it's union
* @return The set of shapes produced
*/
private Shape[] combine(Shape target, Shape other, boolean subtract) {
if (subtract) {
ArrayList shapes = new ArrayList<>();
ArrayList used = new ArrayList<>();
// remove any points that are contianed in the shape we're removing, these
// are implicitly used
for (int i = 0; i < target.getPointCount(); i++) {
float[] point = target.getPoint(i);
if (other.contains(point[0], point[1])) {
used.add(new Vector2f(point[0], point[1]));
if (listener != null) {
listener.pointExcluded(point[0], point[1]);
}
}
}
for (int i = 0; i < target.getPointCount(); i++) {
float[] point = target.getPoint(i);
Vector2f pt = new Vector2f(point[0], point[1]);
if (!used.contains(pt)) {
Shape result = combineSingle(target, other, true, i);
shapes.add(result);
for (int j = 0; j < result.getPointCount(); j++) {
float[] kpoint = result.getPoint(j);
Vector2f kpt = new Vector2f(kpoint[0], kpoint[1]);
used.add(kpt);
}
}
}
return shapes.toArray(new Shape[0]);
} else {
for (int i = 0; i < target.getPointCount(); i++) {
if (!other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
Shape shape = combineSingle(target, other, false, i);
return new Shape[] { shape };
}
}
}
return new Shape[] { other };
}
}
/**
* Combine two shapes
*
* @param target
* The target shape
* @param missing
* The second shape to apply
* @param subtract
* True if we should subtract missing from target, otherwise union
* @param start
* The point to start at
* @return The newly created shape
*/
private Shape combineSingle(Shape target, Shape missing, boolean subtract, int start) {
Shape current = target;
Shape other = missing;
int point = start;
int dir = 1;
Polygon poly = new Polygon();
boolean first = true;
int loop = 0;
// while we've not reached the same point
float px = current.getPoint(point)[0];
float py = current.getPoint(point)[1];
while (!poly.hasVertex(px, py) || first || current != target) {
first = false;
loop++;
if (loop > MAX_POINTS) {
break;
}
// add the current point to the result shape
poly.addPoint(px, py);
if (listener != null) {
listener.pointUsed(px, py);
}
// if the line between the current point and the next one intersect the
// other shape work out where on the other shape and start traversing it's
// path instead
Line line = getLine(current, px, py, rationalPoint(current, point + dir));
HitResult hit = intersect(other, line);
if (hit != null) {
Line hitLine = hit.line;
Vector2f pt = hit.pt;
px = pt.getX();
py = pt.getY();
if (listener != null) {
listener.pointIntersected(px, py);
}
if (other.hasVertex(px, py)) {
point = other.indexOf(pt.getX(), pt.getY());
dir = 1;
px = pt.getX();
py = pt.getY();
Shape temp = current;
current = other;
other = temp;
continue;
}
float dx = hitLine.getDX() / hitLine.length();
float dy = hitLine.getDY() / hitLine.length();
dx *= EDGE_SCALE;
dy *= EDGE_SCALE;
if (current.contains(pt.getX() + dx, pt.getY() + dy)) {
// the point is the next one, we need to take the first and traverse
// the path backwards
if (subtract && current == missing || !subtract && current == target) {
point = hit.p2;
dir = -1;
} else {
point = hit.p1;
dir = 1;
}
// swap the shapes over, we'll traverse the other one
Shape temp = current;
current = other;
other = temp;
} else if (current.contains(pt.getX() - dx, pt.getY() - dy)) {
if (subtract) {
if (current == target) {
point = hit.p2;
dir = -1;
} else {
point = hit.p1;
dir = 1;
}
} else {
point = hit.p1;
dir = 1;
}
// swap the shapes over, we'll traverse the other one
Shape temp = current;
current = other;
other = temp;
} else {
// give up
if (subtract) {
break;
} else {
point = hit.p1;
dir = 1;
Shape temp = current;
current = other;
other = temp;
point = rationalPoint(current, point + dir);
px = current.getPoint(point)[0];
py = current.getPoint(point)[1];
}
}
} else {
// otherwise just move to the next point in the current shape
point = rationalPoint(current, point + dir);
px = current.getPoint(point)[0];
py = current.getPoint(point)[1];
}
}
poly.addPoint(px, py);
if (listener != null)
{
listener.pointUsed(px, py);
}
return poly;
}
/**
* Intersect a line with a shape
*
* @param shape
* The shape to compare
* @param line
* The line to intersect against the shape
* @return The result describing the intersection or null if none
*/
public HitResult intersect(Shape shape, Line line) {
float distance = Float.MAX_VALUE;
HitResult hit = null;
for (int i = 0; i < shape.getPointCount(); i++) {
int next = rationalPoint(shape, i + 1);
Line local = getLine(shape, i, next);
Vector2f pt = line.intersect(local, true);
if (pt != null) {
float newDis = pt.distance(line.getStart());
if (newDis < distance && newDis > EPSILON) {
hit = new HitResult();
hit.pt = pt;
hit.line = local;
hit.p1 = i;
hit.p2 = next;
distance = newDis;
}
}
}
return hit;
}
/**
* Rationalise a point in terms of a given shape
*
* @param shape
* The shape
* @param p
* The index of the point
* @return The index that is rational for the shape
*/
public static int rationalPoint(Shape shape, int p) {
int r = p;
while (r < 0) {
r += shape.getPointCount();
}
while (r >= shape.getPointCount()) {
r -= shape.getPointCount();
}
return r;
}
/**
* Get a line between two points in a shape
*
* @param shape
* The shape
* @param s
* The index of the start point
* @param e
* The index of the end point
* @return The line between the two points
*/
public Line getLine(Shape shape, int s, int e) {
float[] start = shape.getPoint(s);
float[] end = shape.getPoint(e);
return new Line(start[0], start[1], end[0], end[1]);
}
/**
* Get a line between two points in a shape
*
* @param shape
* The shape
* @param sx
* The x coordinate of the start point
* @param sy
* The y coordinate of the start point
* @param e
* The index of the end point
* @return The line between the two points
*/
public Line getLine(Shape shape, float sx, float sy, int e) {
float[] end = shape.getPoint(e);
return new Line(sx, sy, end[0], end[1]);
}
/**
* A lightweigtht description of a intersection between a shape and
* line.
*/
public class HitResult {
/** The line on the target shape that intersected */
private Line line;
/** The index of the first point on the target shape that forms the line */
private int p1;
/** The index of the second point on the target shape that forms the line */
private int p2;
/** The position of the intersection */
private Vector2f pt;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy