Path: blob/master/SLICK_HOME/src/org/newdawn/slick/geom/GeomUtil.java
1461 views
package org.newdawn.slick.geom;12import java.util.ArrayList;34/**5* A set of utilities to play with geometry6*7* @author kevin8*/9public class GeomUtil {10/** The tolerance for determining changes and steps */11public float EPSILON = 0.0001f;12/** The tolerance for determining direction change */13public float EDGE_SCALE = 1f;14/** The maximum number of points returned by an operation - prevents full lockups */15public int MAX_POINTS = 10000;16/** The listener to notify of operations */17public GeomUtilListener listener;1819/**20* Subtract one shape from another - note this is experimental and doesn't21* currently handle islands22*23* @param target The target to be subtracted from24* @param missing The shape to subtract25* @return The newly created shapes26*/27public Shape[] subtract(Shape target, Shape missing) {28target = target.transform(new Transform());29missing = missing.transform(new Transform());3031int count = 0;32for (int i=0;i<target.getPointCount();i++) {33if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {34count++;35}36}3738if (count == target.getPointCount()) {39return new Shape[0];40}4142if (!target.intersects(missing)) {43return new Shape[] {target};44}4546int found = 0;47for (int i=0;i<missing.getPointCount();i++) {48if (target.contains(missing.getPoint(i)[0], missing.getPoint(i)[1])) {49if (!onPath(target, missing.getPoint(i)[0], missing.getPoint(i)[1])) {50found++;51}52}53}54for (int i=0;i<target.getPointCount();i++) {55if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {56if (!onPath(missing, target.getPoint(i)[0], target.getPoint(i)[1]))57{58found++;59}60}61}6263if (found < 1) {64return new Shape[] {target};65}6667return combine(target, missing, true);68}6970/**71* Check if the given point is on the path72*73* @param path The path to check74* @param x The x coordinate of the point to check75* @param y The y coordiante of teh point to check76* @return True if the point is on the path77*/78private boolean onPath(Shape path, float x, float y) {79for (int i=0;i<path.getPointCount()+1;i++) {80int n = rationalPoint(path, i+1);81Line line = getLine(path, rationalPoint(path, i), n);82if (line.distance(new Vector2f(x,y)) < EPSILON*100) {83return true;84}85}8687return false;88}8990/**91* Set the listener to be notified of geometry based operations92*93* @param listener The listener to be notified of geometry based operations94*/95public void setListener(GeomUtilListener listener) {96this.listener = listener;97}9899/**100* Join to shapes together. Note that the shapes must be touching101* for this method to work.102*103* @param target The target shape to union with104* @param other The additional shape to union105* @return The newly created shapes106*/107public Shape[] union(Shape target, Shape other) {108target = target.transform(new Transform());109other = other.transform(new Transform());110111if (!target.intersects(other)) {112return new Shape[] {target, other};113}114115// handle the case where intersects is true but really we're talking116// about edge points117boolean touches = false;118int buttCount = 0;119for (int i=0;i<target.getPointCount();i++) {120if (other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {121if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {122touches = true;123break;124}125}126if (other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {127buttCount++;128}129}130for (int i=0;i<other.getPointCount();i++) {131if (target.contains(other.getPoint(i)[0], other.getPoint(i)[1])) {132if (!target.hasVertex(other.getPoint(i)[0], other.getPoint(i)[1])) {133touches = true;134break;135}136}137}138139if ((!touches) && (buttCount < 2)) {140return new Shape[] {target, other};141}142143// so they are definitely touching, consider the union144return combine(target, other, false);145}146147/**148* Perform the combination149*150* @param target The target shape we're updating151* @param other The other shape in the operation152* @param subtract True if it's a subtract operation, otherwise it's union153* @return The set of shapes produced154*/155private Shape[] combine(Shape target, Shape other, boolean subtract) {156if (subtract) {157ArrayList shapes = new ArrayList();158ArrayList used = new ArrayList();159160// remove any points that are contianed in the shape we're removing, these161// are implicitly used162for (int i=0;i<target.getPointCount();i++) {163float[] point = target.getPoint(i);164if (other.contains(point[0], point[1])) {165used.add(new Vector2f(point[0], point[1]));166if (listener != null) {167listener.pointExcluded(point[0], point[1]);168}169}170}171172for (int i=0;i<target.getPointCount();i++) {173float[] point = target.getPoint(i);174Vector2f pt = new Vector2f(point[0], point[1]);175176if (!used.contains(pt)) {177Shape result = combineSingle(target, other, true, i);178shapes.add(result);179for (int j=0;j<result.getPointCount();j++) {180float[] kpoint = result.getPoint(j);181Vector2f kpt = new Vector2f(kpoint[0], kpoint[1]);182used.add(kpt);183}184}185}186187return (Shape[]) shapes.toArray(new Shape[0]);188} else {189for (int i=0;i<target.getPointCount();i++) {190if (!other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {191if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {192Shape shape = combineSingle(target, other, false, i);193return new Shape[] {shape};194}195}196}197198return new Shape[] {other};199}200}201202/**203* Combine two shapes204*205* @param target The target shape206* @param missing The second shape to apply207* @param subtract True if we should subtract missing from target, otherwise union208* @param start The point to start at209* @return The newly created shape210*/211private Shape combineSingle(Shape target, Shape missing, boolean subtract, int start) {212Shape current = target;213Shape other = missing;214int point = start;215int dir = 1;216217Polygon poly = new Polygon();218boolean first = true;219220int loop = 0;221222// while we've not reached the same point223float px = current.getPoint(point)[0];224float py = current.getPoint(point)[1];225226while (!poly.hasVertex(px, py) || (first) || (current != target)) {227first = false;228loop++;229if (loop > MAX_POINTS) {230break;231}232233// add the current point to the result shape234poly.addPoint(px,py);235if (listener != null) {236listener.pointUsed(px,py);237}238239// if the line between the current point and the next one intersect the240// other shape work out where on the other shape and start traversing it's241// path instead242Line line = getLine(current, px, py, rationalPoint(current, point+dir));243HitResult hit = intersect(other, line);244245if (hit != null) {246Line hitLine = hit.line;247Vector2f pt = hit.pt;248px = pt.x;249py = pt.y;250251if (listener != null) {252listener.pointIntersected(px,py);253}254255if (other.hasVertex(px, py)) {256point = other.indexOf(pt.x,pt.y);257dir = 1;258px = pt.x;259py = pt.y;260261Shape temp = current;262current = other;263other = temp;264continue;265}266267float dx = hitLine.getDX() / hitLine.length();268float dy = hitLine.getDY() / hitLine.length();269dx *= EDGE_SCALE;270dy *= EDGE_SCALE;271272if (current.contains(pt.x + dx, pt.y + dy)) {273// the point is the next one, we need to take the first and traverse274// the path backwards275if (subtract) {276if (current == missing) {277point = hit.p2;278dir = -1;279} else {280point = hit.p1;281dir = 1;282}283} else {284if (current == target) {285point = hit.p2;286dir = -1;287} else {288point = hit.p2;289dir = -1;290}291}292293// swap the shapes over, we'll traverse the other one294Shape temp = current;295current = other;296other = temp;297} else if (current.contains(pt.x - dx, pt.y - dy)) {298if (subtract) {299if (current == target) {300point = hit.p2;301dir = -1;302} else {303point = hit.p1;304dir = 1;305}306} else {307if (current == missing) {308point = hit.p1;309dir = 1;310} else {311point = hit.p1;312dir = 1;313}314}315316// swap the shapes over, we'll traverse the other one317Shape temp = current;318current = other;319other = temp;320} else {321// give up322if (subtract) {323break;324} else {325point = hit.p1;326dir = 1;327Shape temp = current;328current = other;329other = temp;330331point = rationalPoint(current, point+dir);332px = current.getPoint(point)[0];333py = current.getPoint(point)[1];334}335}336} else {337// otherwise just move to the next point in the current shape338point = rationalPoint(current, point+dir);339px = current.getPoint(point)[0];340py = current.getPoint(point)[1];341}342}343344poly.addPoint(px,py);345if (listener != null) {346listener.pointUsed(px,py);347}348349return poly;350}351352/**353* Intersect a line with a shape354*355* @param shape The shape to compare356* @param line The line to intersect against the shape357* @return The result describing the intersection or null if none358*/359public HitResult intersect(Shape shape, Line line) {360float distance = Float.MAX_VALUE;361HitResult hit = null;362363for (int i=0;i<shape.getPointCount();i++) {364int next = rationalPoint(shape, i+1);365Line local = getLine(shape, i, next);366367Vector2f pt = line.intersect(local, true);368if (pt != null) {369float newDis = pt.distance(line.getStart());370if ((newDis < distance) && (newDis > EPSILON)) {371hit = new HitResult();372hit.pt = pt;373hit.line = local;374hit.p1 = i;375hit.p2 = next;376distance = newDis;377}378}379}380381return hit;382}383384/**385* Rationalise a point in terms of a given shape386*387* @param shape The shape388* @param p The index of the point389* @return The index that is rational for the shape390*/391public static int rationalPoint(Shape shape, int p) {392while (p < 0) {393p += shape.getPointCount();394}395while (p >= shape.getPointCount()) {396p -= shape.getPointCount();397}398399return p;400}401402/**403* Get a line between two points in a shape404*405* @param shape The shape406* @param s The index of the start point407* @param e The index of the end point408* @return The line between the two points409*/410public Line getLine(Shape shape, int s, int e) {411float[] start = shape.getPoint(s);412float[] end = shape.getPoint(e);413414Line line = new Line(start[0],start[1],end[0],end[1]);415return line;416}417418/**419* Get a line between two points in a shape420*421* @param shape The shape422* @param sx The x coordinate of the start point423* @param sy The y coordinate of the start point424* @param e The index of the end point425* @return The line between the two points426*/427public Line getLine(Shape shape, float sx, float sy, int e) {428float[] end = shape.getPoint(e);429430Line line = new Line(sx,sy,end[0],end[1]);431return line;432}433434/**435* A lightweigtht description of a intersection between a shape and436* line.437*/438public class HitResult {439/** The line on the target shape that intersected */440public Line line;441/** The index of the first point on the target shape that forms the line */442public int p1;443/** The index of the second point on the target shape that forms the line */444public int p2;445/** The position of the intersection */446public Vector2f pt;447}448}449450451