added sources for Slick

Former-commit-id: 1647fa32ef6894bd7db44f741f07c2f4dcdf9054
Former-commit-id: 0e5810dcfbe1fd59b13e7cabe9f1e93c5542da2d
This commit is contained in:
Song Minjae
2016-12-30 23:29:12 +09:00
parent d24b31e15d
commit 059abff814
329 changed files with 58400 additions and 7 deletions

View File

@@ -0,0 +1,437 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
/**
* Triangulates a polygon into triangles - duh. Doesn't handle
* holes in polys
*
* @author Based on Public Source from FlipCode
*/
public class BasicTriangulator implements Triangulator {
/** The accepted error value */
private static final float EPSILON = 0.0000000001f;
/** The list of points to be triangulated */
private PointList poly = new PointList();
/** The list of points describing the triangles */
private PointList tris = new PointList();
/** True if we've tried to triangulate */
private boolean tried;
/**
* Create a new triangulator
*/
public BasicTriangulator() {
}
/**
* Add a point describing the polygon to be triangulated
*
* @param x The x coordinate of the point
* @param y the y coordinate of the point
*/
public void addPolyPoint(float x, float y) {
Point p = new Point(x,y);
if (!poly.contains(p)) {
poly.add(p);
}
}
/**
* Get the number of points in the polygon
*
* @return The number of points in the polygon
*/
public int getPolyPointCount() {
return poly.size();
}
/**
* Get the coordinates of the point at the specified index
*
* @param index The index of the point to retrieve
* @return The oordinates of the point at the specified index
*/
public float[] getPolyPoint(int index) {
return new float[] {poly.get(index).x,poly.get(index).y};
}
/**
* Cause the triangulator to split the polygon
*
* @return True if we managed the task
*/
public boolean triangulate() {
tried = true;
boolean worked = process(poly,tris);
return worked;
}
/**
* Get a count of the number of triangles produced
*
* @return The number of triangles produced
*/
public int getTriangleCount() {
if (!tried) {
throw new RuntimeException("Call triangulate() before accessing triangles");
}
return tris.size() / 3;
}
/**
* Get a point on a specified generated triangle
*
* @param tri The index of the triangle to interegate
* @param i The index of the point within the triangle to retrieve
* (0 - 2)
* @return The x,y coordinate pair for the point
*/
public float[] getTrianglePoint(int tri, int i) {
if (!tried) {
throw new RuntimeException("Call triangulate() before accessing triangles");
}
return tris.get((tri*3)+i).toArray();
}
/**
* Find the area of a polygon defined by the series of points
* in the list
*
* @param contour The list of points defined the contour of the polygon
* (Vector2f)
* @return The area of the polygon defined
*/
private float area(PointList contour) {
int n = contour.size();
float A = 0.0f;
for (int p = n - 1, q = 0; q < n; p = q++) {
Point contourP = contour.get(p);
Point contourQ = contour.get(q);
A += contourP.getX() * contourQ.getY() - contourQ.getX()
* contourP.getY();
}
return A * 0.5f;
}
/**
* Check if the point P is inside the triangle defined by
* the points A,B,C
*
* @param Ax Point A x-coordinate
* @param Ay Point A y-coordinate
* @param Bx Point B x-coordinate
* @param By Point B y-coordinate
* @param Cx Point C x-coordinate
* @param Cy Point C y-coordinate
* @param Px Point P x-coordinate
* @param Py Point P y-coordinate
* @return True if the point specified is within the triangle
*/
private boolean insideTriangle(float Ax, float Ay, float Bx,
float By, float Cx, float Cy, float Px, float Py) {
float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
float cCROSSap, bCROSScp, aCROSSbp;
ax = Cx - Bx;
ay = Cy - By;
bx = Ax - Cx;
by = Ay - Cy;
cx = Bx - Ax;
cy = By - Ay;
apx = Px - Ax;
apy = Py - Ay;
bpx = Px - Bx;
bpy = Py - By;
cpx = Px - Cx;
cpy = Py - Cy;
aCROSSbp = ax * bpy - ay * bpx;
cCROSSap = cx * apy - cy * apx;
bCROSScp = bx * cpy - by * cpx;
return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
}
/**
* Cut a the contour and add a triangle into V to describe the
* location of the cut
*
* @param contour The list of points defining the polygon
* @param u The index of the first point
* @param v The index of the second point
* @param w The index of the third point
* @param n ?
* @param V The array to populate with indicies of triangles
* @return True if a triangle was found
*/
private boolean snip(PointList contour, int u, int v, int w, int n,
int[] V) {
int p;
float Ax, Ay, Bx, By, Cx, Cy, Px, Py;
Ax = contour.get(V[u]).getX();
Ay = contour.get(V[u]).getY();
Bx = contour.get(V[v]).getX();
By = contour.get(V[v]).getY();
Cx = contour.get(V[w]).getX();
Cy = contour.get(V[w]).getY();
if (EPSILON > (((Bx - Ax) * (Cy - Ay)) - ((By - Ay) * (Cx - Ax)))) {
return false;
}
for (p = 0; p < n; p++) {
if ((p == u) || (p == v) || (p == w)) {
continue;
}
Px = contour.get(V[p]).getX();
Py = contour.get(V[p]).getY();
if (insideTriangle(Ax, Ay, Bx, By, Cx, Cy, Px, Py)) {
return false;
}
}
return true;
}
/**
* Process a list of points defining a polygon
* @param contour The list of points describing the polygon
* @param result The list of points describing the triangles. Groups
* of 3 describe each triangle
*
* @return True if we succeeded in completing triangulation
*/
private boolean process(PointList contour, PointList result) {
result.clear();
/* allocate and initialize list of Vertices in polygon */
int n = contour.size();
if (n < 3)
return false;
int[] V = new int[n];
/* we want a counter-clockwise polygon in V */
if (0.0f < area(contour)) {
for (int v = 0; v < n; v++)
V[v] = v;
} else {
for (int v = 0; v < n; v++)
V[v] = (n - 1) - v;
}
int nv = n;
/* remove nv-2 Vertices, creating 1 triangle every time */
int count = 2 * nv; /* error detection */
for (int m = 0, v = nv - 1; nv > 2;) {
/* if we loop, it is probably a non-simple polygon */
if (0 >= (count--)) {
//** Triangulator4: ERROR - probable bad polygon!
return false;
}
/* three consecutive vertices in current polygon, <u,v,w> */
int u = v;
if (nv <= u)
u = 0; /* previous */
v = u + 1;
if (nv <= v)
v = 0; /* new v */
int w = v + 1;
if (nv <= w)
w = 0; /* next */
if (snip(contour, u, v, w, nv, V)) {
int a, b, c, s, t;
/* true names of the vertices */
a = V[u];
b = V[v];
c = V[w];
/* output Triangle */
result.add(contour.get(a));
result.add(contour.get(b));
result.add(contour.get(c));
m++;
/* remove v from remaining polygon */
for (s = v, t = v + 1; t < nv; s++, t++) {
V[s] = V[t];
}
nv--;
/* resest error detection counter */
count = 2 * nv;
}
}
return true;
}
/**
* A single point handled by the triangulator
*
* @author Kevin Glass
*/
private class Point {
/** The x coorindate of this point */
private float x;
/** The y coorindate of this point */
private float y;
/** The points in an array */
private float[] array;
/**
* Create a new point
*
* @param x The x coordindate of the point
* @param y The y coordindate of the point
*/
public Point(float x, float y) {
this.x = x;
this.y = y;
array = new float[] {x,y};
}
/**
* Get the x coordinate of the point
*
* @return The x coordinate of the point
*/
public float getX() {
return x;
}
/**
* Get the y coordinate of the point
*
* @return The y coordinate of the point
*/
public float getY() {
return y;
}
/**
* Convert this point into a float array
*
* @return The contents of this point as a float array
*/
public float[] toArray() {
return array;
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return (int) (x * y * 31);
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object other) {
if (other instanceof Point) {
Point p = (Point) other;
return (p.x == x) && (p.y == y);
}
return false;
}
}
/**
* A list of type <code>Point</code>
*
* @author Kevin Glass
*/
private class PointList {
/** The list of points */
private ArrayList points = new ArrayList();
/**
* Create a new empty list
*/
public PointList() {
}
/**
* Check if the list contains a point
*
* @param p The point to look for
* @return True if the point is in the list
*/
public boolean contains(Point p) {
return points.contains(p);
}
/**
* Add a point to the list
*
* @param point The point to add
*/
public void add(Point point) {
points.add(point);
}
/**
* Remove a point from the list
*
* @param point The point to remove
*/
public void remove(Point point) {
points.remove(point);
}
/**
* Get the size of the list
*
* @return The size of the list
*/
public int size() {
return points.size();
}
/**
* Get a point a specific index in the list
*
* @param i The index of the point to retrieve
* @return The point
*/
public Point get(int i) {
return (Point) points.get(i);
}
/**
* Clear the list
*/
public void clear() {
points.clear();
}
}
/**
* @see org.newdawn.slick.geom.Triangulator#startHole()
*/
public void startHole() {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,234 @@
package org.newdawn.slick.geom;
/**
* A simple Circle geometry
*
* @author Kevin Glass
*/
public strictfp class Circle extends Ellipse {
/** The radius of the circle */
public float radius;
/**
* Create a new circle based on its radius
*
* @param centerPointX The x location of the center of the circle
* @param centerPointY The y location of the center of the circle
* @param radius The radius of the circle
*/
public Circle(float centerPointX, float centerPointY, float radius) {
this(centerPointX, centerPointY, radius, DEFAULT_SEGMENT_COUNT);
}
/**
* Create a new circle based on its radius
*
* @param centerPointX The x location of the center of the circle
* @param centerPointY The y location of the center of the circle
* @param radius The radius of the circle
* @param segmentCount The number of segments to build the circle out of
*/
public Circle(float centerPointX, float centerPointY, float radius, int segmentCount) {
super(centerPointX, centerPointY, radius, radius, segmentCount);
this.x = centerPointX - radius;
this.y = centerPointY - radius;
this.radius = radius;
boundingCircleRadius = radius;
}
/**
* Get the x coordinate of the centre of the circle
*
* @return The x coordinate of the centre of the circle
*/
public float getCenterX() {
return getX() + radius;
}
/**
* Get the y coordinate of the centre of the circle
*
* @return The y coordinate of the centre of the circle
*/
public float getCenterY() {
return getY() + radius;
}
/**
* Get the coordinates of the center of the circle
*
* @return 2-element array with the center of the circle.
*/
@Override
public float[] getCenter() {
return new float[] { getCenterX(), getCenterY() };
}
/**
* Set the radius of this circle
*
* @param radius The radius of this circle
*/
public void setRadius(float radius) {
if (radius != this.radius) {
pointsDirty = true;
this.radius = radius;
setRadii(radius, radius);
}
}
/**
* Get the radius of the circle
*
* @return The radius of the circle
*/
public float getRadius() {
return radius;
}
/**
* Check if this circle touches another
*
* @param shape The other circle
* @return True if they touch
*/
public boolean intersects(Shape shape) {
if(shape instanceof Circle) {
Circle other = (Circle)shape;
float totalRad2 = getRadius() + other.getRadius();
if (Math.abs(other.getCenterX() - getCenterX()) > totalRad2) {
return false;
}
if (Math.abs(other.getCenterY() - getCenterY()) > totalRad2) {
return false;
}
totalRad2 *= totalRad2;
float dx = Math.abs(other.getCenterX() - getCenterX());
float dy = Math.abs(other.getCenterY() - getCenterY());
return totalRad2 >= ((dx*dx) + (dy*dy));
}
else if(shape instanceof Rectangle) {
return intersects((Rectangle)shape);
}
else {
return super.intersects(shape);
}
}
/**
* Check if a point is contained by this circle
*
* @param x The x coordinate of the point to check
* @param y The y coorindate of the point to check
* @return True if the point is contained by this circle
*/
public boolean contains(float x, float y)
{
float xDelta = x - getCenterX(), yDelta = y - getCenterY();
return xDelta * xDelta + yDelta * yDelta < getRadius() * getRadius();
}
/**
* Check if circle contains the line
* @param line Line to check against
* @return True if line inside circle
*/
private boolean contains(Line line) {
return contains(line.getX1(), line.getY1()) && contains(line.getX2(), line.getY2());
}
/**
* @see org.newdawn.slick.geom.Ellipse#findCenter()
*/
protected void findCenter() {
center = new float[2];
center[0] = x + radius;
center[1] = y + radius;
}
/**
* @see org.newdawn.slick.geom.Ellipse#calculateRadius()
*/
protected void calculateRadius() {
boundingCircleRadius = radius;
}
/**
* Check if this circle touches a rectangle
*
* @param other The rectangle to check against
* @return True if they touch
*/
private boolean intersects(Rectangle other) {
Rectangle box = other;
Circle circle = this;
if (box.contains(x+radius,y+radius)) {
return true;
}
float x1 = box.getX();
float y1 = box.getY();
float x2 = box.getX() + box.getWidth();
float y2 = box.getY() + box.getHeight();
Line[] lines = new Line[4];
lines[0] = new Line(x1,y1,x2,y1);
lines[1] = new Line(x2,y1,x2,y2);
lines[2] = new Line(x2,y2,x1,y2);
lines[3] = new Line(x1,y2,x1,y1);
float r2 = circle.getRadius() * circle.getRadius();
Vector2f pos = new Vector2f(circle.getCenterX(), circle.getCenterY());
for (int i=0;i<4;i++) {
float dis = lines[i].distanceSquared(pos);
if (dis < r2) {
return true;
}
}
return false;
}
/**
* Check if circle touches a line.
* @param other The line to check against
* @return True if they touch
*/
private boolean intersects(Line other) {
// put it nicely into vectors
Vector2f lineSegmentStart = new Vector2f(other.getX1(), other.getY1());
Vector2f lineSegmentEnd = new Vector2f(other.getX2(), other.getY2());
Vector2f circleCenter = new Vector2f(getCenterX(), getCenterY());
// calculate point on line closest to the circle center and then
// compare radius to distance to the point for intersection result
Vector2f closest;
Vector2f segv = lineSegmentEnd.copy().sub(lineSegmentStart);
Vector2f ptv = circleCenter.copy().sub(lineSegmentStart);
float segvLength = segv.length();
float projvl = ptv.dot(segv) / segvLength;
if (projvl < 0)
{
closest = lineSegmentStart;
}
else if (projvl > segvLength)
{
closest = lineSegmentEnd;
}
else
{
Vector2f projv = segv.copy().scale(projvl / segvLength);
closest = lineSegmentStart.copy().add(projv);
}
boolean intersects = circleCenter.copy().sub(closest).lengthSquared() <= getRadius()*getRadius();
return intersects;
}
}

View File

@@ -0,0 +1,113 @@
package org.newdawn.slick.geom;
/**
* A beizer curve implementation. The curve is defined by a start point, an end point
* and two control points that it will tend towards. This is implementation is fixed
* segmenting meaning it doesn't scale too well.
*
* @author kevin
*/
public class Curve extends Shape {
/** The start point of the curve */
private Vector2f p1;
/** The first control point */
private Vector2f c1;
/** The second control point */
private Vector2f c2;
/** The end point of the curve */
private Vector2f p2;
/** The number of lines segments the curve is built out of */
private int segments;
/**
* Create a new curve with the default segments (20)
*
* @param p1 The start of the curve
* @param c1 The first control point
* @param c2 The second control point
* @param p2 The end of the curve
*/
public Curve(Vector2f p1, Vector2f c1, Vector2f c2, Vector2f p2) {
this(p1,c1,c2,p2,20);
}
/**
* Create a new curve
*
* @param p1 The start of the curve
* @param c1 The first control point
* @param c2 The second control point
* @param p2 The end of the curve
* @param segments The number of segments to use
*/
public Curve(Vector2f p1, Vector2f c1, Vector2f c2, Vector2f p2, int segments) {
this.p1 = new Vector2f(p1);
this.c1 = new Vector2f(c1);
this.c2 = new Vector2f(c2);
this.p2 = new Vector2f(p2);
this.segments = segments;
pointsDirty = true;
}
/**
* Get the point at a particular location on the curve
*
* @param t A value between 0 and 1 defining the location of the curve the point is at
* @return The point on the curve
*/
public Vector2f pointAt(float t) {
float a = 1 - t;
float b = t;
float f1 = a * a * a;
float f2 = 3 * a * a * b;
float f3 = 3 * a * b * b;
float f4 = b * b * b;
float nx = (p1.x * f1) + (c1.x * f2) + (c2.x * f3) + (p2.x * f4);
float ny = (p1.y * f1) + (c1.y * f2) + (c2.y * f3) + (p2.y * f4);
return new Vector2f(nx,ny);
}
/**
* @see org.newdawn.slick.geom.Shape#createPoints()
*/
protected void createPoints() {
float step = 1.0f / segments;
points = new float[(segments+1) * 2];
for (int i=0;i<segments+1;i++) {
float t = i * step;
Vector2f p = pointAt(t);
points[i*2] = p.x;
points[(i*2)+1] = p.y;
}
}
/**
* @see org.newdawn.slick.geom.Shape#transform(org.newdawn.slick.geom.Transform)
*/
public Shape transform(Transform transform) {
float[] pts = new float[8];
float[] dest = new float[8];
pts[0] = p1.x; pts[1] = p1.y;
pts[2] = c1.x; pts[3] = c1.y;
pts[4] = c2.x; pts[5] = c2.y;
pts[6] = p2.x; pts[7] = p2.y;
transform.transform(pts, 0, dest, 0, 4);
return new Curve(new Vector2f(dest[0],dest[1]), new Vector2f(dest[2],dest[3]),
new Vector2f(dest[4],dest[5]), new Vector2f(dest[6],dest[7]));
}
/**
* True if this is a closed shape
*
* @return True if this is a closed shape
*/
public boolean closed() {
return false;
}
}

View File

@@ -0,0 +1,196 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
import org.newdawn.slick.util.FastTrig;
/**
* An ellipse meeting the <code>Shape</code> contract. The ellipse is actually an approximation using
* a series of points generated around the contour of the ellipse.
*
* @author Mark
*/
public class Ellipse extends Shape {
/**
* Default number of segments to draw this ellipse with
*/
protected static final int DEFAULT_SEGMENT_COUNT = 50;
/**
* The number of segments for graphical representation.
*/
private int segmentCount;
/**
* horizontal radius
*/
private float radius1;
/**
* vertical radius
*/
private float radius2;
/**
* Creates a new Ellipse object.
*
* @param centerPointX x coordinate of the center of the ellipse
* @param centerPointY y coordinate of the center of the ellipse
* @param radius1 horizontal radius
* @param radius2 vertical radius
*/
public Ellipse(float centerPointX, float centerPointY, float radius1, float radius2) {
this(centerPointX, centerPointY, radius1, radius2, DEFAULT_SEGMENT_COUNT);
}
/**
* Creates a new Ellipse object.
*
* @param centerPointX x coordinate of the center of the ellipse
* @param centerPointY y coordinate of the center of the ellipse
* @param radius1 horizontal radius
* @param radius2 vertical radius
* @param segmentCount how fine to make the ellipse.
*/
public Ellipse(float centerPointX, float centerPointY, float radius1, float radius2, int segmentCount) {
this.x = centerPointX - radius1;
this.y = centerPointY - radius2;
this.radius1 = radius1;
this.radius2 = radius2;
this.segmentCount = segmentCount;
checkPoints();
}
/**
* Change the shape of this Ellipse
*
* @param radius1 horizontal radius
* @param radius2 vertical radius
*/
public void setRadii(float radius1, float radius2) {
setRadius1(radius1);
setRadius2(radius2);
}
/**
* Get the horizontal radius of the ellipse
*
* @return The horizontal radius of the ellipse
*/
public float getRadius1() {
return radius1;
}
/**
* Set the horizontal radius of the ellipse
*
* @param radius1 The horizontal radius to set
*/
public void setRadius1(float radius1) {
if (radius1 != this.radius1) {
this.radius1 = radius1;
pointsDirty = true;
}
}
/**
* Get the vertical radius of the ellipse
*
* @return The vertical radius of the ellipse
*/
public float getRadius2() {
return radius2;
}
/**
* Set the vertical radius of the ellipse
*
* @param radius2 The vertical radius to set
*/
public void setRadius2(float radius2) {
if (radius2 != this.radius2) {
this.radius2 = radius2;
pointsDirty = true;
}
}
/**
* Generate the points to outline this ellipse.
*
*/
protected void createPoints() {
ArrayList tempPoints = new ArrayList();
maxX = -Float.MIN_VALUE;
maxY = -Float.MIN_VALUE;
minX = Float.MAX_VALUE;
minY = Float.MAX_VALUE;
float start = 0;
float end = 359;
float cx = x + radius1;
float cy = y + radius2;
int step = 360 / segmentCount;
for (float a=start;a<=end+step;a+=step) {
float ang = a;
if (ang > end) {
ang = end;
}
float newX = (float) (cx + (FastTrig.cos(Math.toRadians(ang)) * radius1));
float newY = (float) (cy + (FastTrig.sin(Math.toRadians(ang)) * radius2));
if(newX > maxX) {
maxX = newX;
}
if(newY > maxY) {
maxY = newY;
}
if(newX < minX) {
minX = newX;
}
if(newY < minY) {
minY = newY;
}
tempPoints.add(new Float(newX));
tempPoints.add(new Float(newY));
}
points = new float[tempPoints.size()];
for(int i=0;i<points.length;i++) {
points[i] = ((Float)tempPoints.get(i)).floatValue();
}
}
/**
* @see org.newdawn.slick.geom.Shape#transform(org.newdawn.slick.geom.Transform)
*/
public Shape transform(Transform transform) {
checkPoints();
Polygon resultPolygon = new Polygon();
float result[] = new float[points.length];
transform.transform(points, 0, result, 0, points.length / 2);
resultPolygon.points = result;
resultPolygon.checkPoints();
return resultPolygon;
}
/**
* @see org.newdawn.slick.geom.Shape#findCenter()
*/
protected void findCenter() {
center = new float[2];
center[0] = x + radius1;
center[1] = y + radius2;
}
/**
* @see org.newdawn.slick.geom.Shape#calculateRadius()
*/
protected void calculateRadius() {
boundingCircleRadius = (radius1 > radius2) ? radius1 : radius2;
}
}

View File

@@ -0,0 +1,449 @@
package org.newdawn.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 float EPSILON = 0.0001f;
/** The tolerance for determining direction change */
public float EDGE_SCALE = 1f;
/** The maximum number of points returned by an operation - prevents full lockups */
public int MAX_POINTS = 10000;
/** The listener to notify of operations */
public GeomUtilListener listener;
/**
* Subtract one shape from another - note this is experimental and doesn't
* currently handle islands
*
* @param target The target to be subtracted from
* @param missing The shape to subtract
* @return The newly created shapes
*/
public Shape[] subtract(Shape target, Shape missing) {
target = target.transform(new Transform());
missing = missing.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 target The target shape to union with
* @param other The additional shape to union
* @return The newly created shapes
*/
public Shape[] union(Shape target, Shape other) {
target = target.transform(new Transform());
other = other.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 (Shape[]) 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.x;
py = pt.y;
if (listener != null) {
listener.pointIntersected(px,py);
}
if (other.hasVertex(px, py)) {
point = other.indexOf(pt.x,pt.y);
dir = 1;
px = pt.x;
py = pt.y;
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.x + dx, pt.y + dy)) {
// the point is the next one, we need to take the first and traverse
// the path backwards
if (subtract) {
if (current == missing) {
point = hit.p2;
dir = -1;
} else {
point = hit.p1;
dir = 1;
}
} else {
if (current == target) {
point = hit.p2;
dir = -1;
} else {
point = hit.p2;
dir = -1;
}
}
// swap the shapes over, we'll traverse the other one
Shape temp = current;
current = other;
other = temp;
} else if (current.contains(pt.x - dx, pt.y - dy)) {
if (subtract) {
if (current == target) {
point = hit.p2;
dir = -1;
} else {
point = hit.p1;
dir = 1;
}
} else {
if (current == missing) {
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) {
while (p < 0) {
p += shape.getPointCount();
}
while (p >= shape.getPointCount()) {
p -= shape.getPointCount();
}
return p;
}
/**
* 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);
Line line = new Line(start[0],start[1],end[0],end[1]);
return line;
}
/**
* 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);
Line line = new Line(sx,sy,end[0],end[1]);
return line;
}
/**
* A lightweigtht description of a intersection between a shape and
* line.
*/
public class HitResult {
/** The line on the target shape that intersected */
public Line line;
/** The index of the first point on the target shape that forms the line */
public int p1;
/** The index of the second point on the target shape that forms the line */
public int p2;
/** The position of the intersection */
public Vector2f pt;
}
}

View File

@@ -0,0 +1,32 @@
package org.newdawn.slick.geom;
/**
* Debug listener for notifications assocaited with geometry utilities
*
* @author kevin
*/
public interface GeomUtilListener {
/**
* Notification that a point was excluded from geometry
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
*/
public void pointExcluded(float x, float y);
/**
* Notification that a point was intersected between two geometries
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
*/
public void pointIntersected(float x, float y);
/**
* Notification that a point was used to build a new geometry
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
*/
public void pointUsed(float x, float y);
}

View File

@@ -0,0 +1,482 @@
package org.newdawn.slick.geom;
/**
* Implemenation of a bunch of maths functions to do with lines. Note that lines
* can't be used as dynamic shapes right now - also collision with the end of a
* line is undefined.
*
* @author Kevin Glass
*/
public class Line extends Shape {
/** The start point of the line */
private Vector2f start;
/** The end point of the line */
private Vector2f end;
/** The vector between the two points */
private Vector2f vec;
/** The length of the line squared */
private float lenSquared;
/** Temporary storage - declared globally to reduce GC */
private Vector2f loc = new Vector2f(0, 0);
/** Temporary storage - declared globally to reduce GC */
private Vector2f v = new Vector2f(0, 0);
/** Temporary storage - declared globally to reduce GC */
private Vector2f v2 = new Vector2f(0, 0);
/** Temporary storage - declared globally to reduce GC */
private Vector2f proj = new Vector2f(0, 0);
/** Temporary storage - declared globally to reduce GC */
private Vector2f closest = new Vector2f(0, 0);
/** Temporary storage - declared globally to reduce GC */
private Vector2f other = new Vector2f(0, 0);
/** True if this line blocks on the outer edge */
private boolean outerEdge = true;
/** True if this line blocks on the inner edge */
private boolean innerEdge = true;
/**
* Create a new line based on the origin and a single point
*
* @param x
* The end point of the line
* @param y
* The end point of the line
* @param inner
* True if this line blocks on it's inner edge
* @param outer
* True if this line blocks on it's outer edge
*/
public Line(float x, float y, boolean inner, boolean outer) {
this(0, 0, x, y);
}
/**
* Create a new line based on the origin and a single point
*
* @param x
* The end point of the line
* @param y
* The end point of the line
*/
public Line(float x, float y) {
this(x, y, true, true);
}
/**
* Create a new line based on two points
*
* @param x1
* The x coordinate of the start point
* @param y1
* The y coordinate of the start point
* @param x2
* The x coordinate of the end point
* @param y2
* The y coordinate of the end point
*/
public Line(float x1, float y1, float x2, float y2) {
this(new Vector2f(x1, y1), new Vector2f(x2, y2));
}
/**
* Create a line with relative second point
*
* @param x1
* The x coordinate of the start point
* @param y1
* The y coordinate of the start point
* @param dx
* The x change to get to the second point
* @param dy
* The y change to get to the second point
* @param dummy
* A dummy value
*/
public Line(float x1, float y1, float dx, float dy, boolean dummy) {
this(new Vector2f(x1, y1), new Vector2f(x1 + dx, y1 + dy));
}
/**
* Create a new line based on two points
*
* @param start
* The start point
* @param end
* The end point
*/
public Line(float[] start, float[] end) {
super();
set(start, end);
}
/**
* Create a new line based on two points
*
* @param start
* The start point
* @param end
* The end point
*/
public Line(Vector2f start, Vector2f end) {
super();
set(start, end);
}
/**
* Configure the line
*
* @param start
* The start point of the line
* @param end
* The end point of the line
*/
public void set(float[] start, float[] end) {
set(start[0], start[1], end[0], end[1]);
}
/**
* Get the start point of the line
*
* @return The start point of the line
*/
public Vector2f getStart() {
return start;
}
/**
* Get the end point of the line
*
* @return The end point of the line
*/
public Vector2f getEnd() {
return end;
}
/**
* Find the length of the line
*
* @return The the length of the line
*/
public float length() {
return vec.length();
}
/**
* Find the length of the line squared (cheaper and good for comparisons)
*
* @return The length of the line squared
*/
public float lengthSquared() {
return vec.lengthSquared();
}
/**
* Configure the line
*
* @param start
* The start point of the line
* @param end
* The end point of the line
*/
public void set(Vector2f start, Vector2f end) {
super.pointsDirty = true;
if (this.start == null) {
this.start = new Vector2f();
}
this.start.set(start);
if (this.end == null) {
this.end = new Vector2f();
}
this.end.set(end);
vec = new Vector2f(end);
vec.sub(start);
lenSquared = vec.lengthSquared();
}
/**
* Configure the line without garbage
*
* @param sx
* The x coordinate of the start
* @param sy
* The y coordinate of the start
* @param ex
* The x coordiante of the end
* @param ey
* The y coordinate of the end
*/
public void set(float sx, float sy, float ex, float ey) {
super.pointsDirty = true;
start.set(sx, sy);
end.set(ex, ey);
float dx = (ex - sx);
float dy = (ey - sy);
vec.set(dx,dy);
lenSquared = (dx * dx) + (dy * dy);
}
/**
* Get the x direction of this line
*
* @return The x direction of this line
*/
public float getDX() {
return end.getX() - start.getX();
}
/**
* Get the y direction of this line
*
* @return The y direction of this line
*/
public float getDY() {
return end.getY() - start.getY();
}
/**
* @see org.newdawn.slick.geom.Shape#getX()
*/
public float getX() {
return getX1();
}
/**
* @see org.newdawn.slick.geom.Shape#getY()
*/
public float getY() {
return getY1();
}
/**
* Get the x coordinate of the start point
*
* @return The x coordinate of the start point
*/
public float getX1() {
return start.getX();
}
/**
* Get the y coordinate of the start point
*
* @return The y coordinate of the start point
*/
public float getY1() {
return start.getY();
}
/**
* Get the x coordinate of the end point
*
* @return The x coordinate of the end point
*/
public float getX2() {
return end.getX();
}
/**
* Get the y coordinate of the end point
*
* @return The y coordinate of the end point
*/
public float getY2() {
return end.getY();
}
/**
* Get the shortest distance from a point to this line
*
* @param point
* The point from which we want the distance
* @return The distance from the line to the point
*/
public float distance(Vector2f point) {
return (float) Math.sqrt(distanceSquared(point));
}
/**
* Check if the given point is on the line
*
* @param point
* The point to check
* @return True if the point is on this line
*/
public boolean on(Vector2f point) {
getClosestPoint(point, closest);
return point.equals(closest);
}
/**
* Get the shortest distance squared from a point to this line
*
* @param point
* The point from which we want the distance
* @return The distance squared from the line to the point
*/
public float distanceSquared(Vector2f point) {
getClosestPoint(point, closest);
closest.sub(point);
float result = closest.lengthSquared();
return result;
}
/**
* Get the closest point on the line to a given point
*
* @param point
* The point which we want to project
* @param result
* The point on the line closest to the given point
*/
public void getClosestPoint(Vector2f point, Vector2f result) {
loc.set(point);
loc.sub(start);
float projDistance = vec.dot(loc);
projDistance /= vec.lengthSquared();
if (projDistance < 0) {
result.set(start);
return;
}
if (projDistance > 1) {
result.set(end);
return;
}
result.x = start.getX() + projDistance * vec.getX();
result.y = start.getY() + projDistance * vec.getY();
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "[Line " + start + "," + end + "]";
}
/**
* Intersect this line with another
*
* @param other
* The other line we should intersect with
* @return The intersection point or null if the lines are parallel
*/
public Vector2f intersect(Line other) {
return intersect(other, false);
}
/**
* Intersect this line with another
*
* @param other
* The other line we should intersect with
* @param limit
* True if the collision is limited to the extent of the lines
* @return The intersection point or null if the lines don't intersect
*/
public Vector2f intersect(Line other, boolean limit) {
Vector2f temp = new Vector2f();
if (!intersect(other, limit, temp)) {
return null;
}
return temp;
}
/**
* Intersect this line with another
*
* @param other
* The other line we should intersect with
* @param limit
* True if the collision is limited to the extent of the lines
* @param result
* The resulting intersection point if any
* @return True if the lines intersect
*/
public boolean intersect(Line other, boolean limit, Vector2f result) {
float dx1 = end.getX() - start.getX();
float dx2 = other.end.getX() - other.start.getX();
float dy1 = end.getY() - start.getY();
float dy2 = other.end.getY() - other.start.getY();
float denom = (dy2 * dx1) - (dx2 * dy1);
if (denom == 0) {
return false;
}
float ua = (dx2 * (start.getY() - other.start.getY()))
- (dy2 * (start.getX() - other.start.getX()));
ua /= denom;
float ub = (dx1 * (start.getY() - other.start.getY()))
- (dy1 * (start.getX() - other.start.getX()));
ub /= denom;
if ((limit) && ((ua < 0) || (ua > 1) || (ub < 0) || (ub > 1))) {
return false;
}
float u = ua;
float ix = start.getX() + (u * (end.getX() - start.getX()));
float iy = start.getY() + (u * (end.getY() - start.getY()));
result.set(ix, iy);
return true;
}
/**
* @see org.newdawn.slick.geom.Shape#createPoints()
*/
protected void createPoints() {
points = new float[4];
points[0] = getX1();
points[1] = getY1();
points[2] = getX2();
points[3] = getY2();
}
/**
* @see org.newdawn.slick.geom.Shape#transform(org.newdawn.slick.geom.Transform)
*/
public Shape transform(Transform transform) {
float[] temp = new float[4];
createPoints();
transform.transform(points, 0, temp, 0, 2);
return new Line(temp[0], temp[1], temp[2], temp[3]);
}
/**
* @see org.newdawn.slick.geom.Shape#closed()
*/
public boolean closed() {
return false;
}
/**
* @see org.newdawn.slick.geom.Shape#intersects(org.newdawn.slick.geom.Shape)
*/
public boolean intersects(Shape shape)
{
if (shape instanceof Circle)
{
return shape.intersects(this);
}
return super.intersects(shape);
}
}

View File

@@ -0,0 +1,617 @@
/*
* Triangulator0.java
*
* (BSD license)
*
* Copyright (c) 2005, Matthias Mann (www.matthiasmann.de)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* * Neither the name of the Matthias Mann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Created on 17. March 2005, 22:19
*/
package org.newdawn.slick.geom;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* A 2D Triangulator. Graciously made available by the man(n) himself.
*
* @author Matthias Mann
*/
public class MannTriangulator implements Triangulator {
/** The allowed error value */
private static final double EPSILON = 1e-5;
/** The outer countour of the shape */
protected PointBag contour;
/** The holes defined in the polygon */
protected PointBag holes;
/** The next available point bag */
private PointBag nextFreePointBag;
/** The next available point */
private Point nextFreePoint;
/** The list of triangles created (or rather points in triangles, 3xn) */
private List triangles = new ArrayList();
/** Creates a new instance of Triangulator0 */
public MannTriangulator() {
contour = getPointBag();
}
/**
* @see org.newdawn.slick.geom.Triangulator#addPolyPoint(float, float)
*/
public void addPolyPoint(float x, float y) {
addPoint(new Vector2f(x,y));
}
/**
* Reset the internal state of the triangulator
*/
public void reset() {
while (holes != null) {
holes = freePointBag(holes);
}
contour.clear();
holes = null;
}
/**
* Begin adding a hole to the polygon
*/
public void startHole() {
PointBag newHole = getPointBag();
newHole.next = holes;
holes = newHole;
}
/**
* Add a defined point to the current contour
*
* @param pt The point to add
*/
private void addPoint(Vector2f pt) {
if (holes == null) {
Point p = getPoint(pt);
contour.add(p);
} else {
Point p = getPoint(pt);
holes.add(p);
}
}
/**
* Triangulate the points given
*
* @param result The array to fill with the result or use to determine type
* @return The resultng triangles
*/
private Vector2f[] triangulate(Vector2f[] result) {
// Step 1: Compute all angles
contour.computeAngles();
for (PointBag hole = holes; hole != null; hole = hole.next) {
hole.computeAngles();
}
// Step 2: Connect the holes with the contour (build bridges)
while (holes != null) {
Point pHole = holes.first;
outer: do {
if (pHole.angle <= 0) {
Point pContour = contour.first;
do {
inner: if (pHole.isInfront(pContour)
&& pContour.isInfront(pHole)) {
if (!contour.doesIntersectSegment(pHole.pt,
pContour.pt)) {
PointBag hole = holes;
do {
if (hole.doesIntersectSegment(pHole.pt,
pContour.pt)) {
break inner;
}
} while ((hole = hole.next) != null);
Point newPtContour = getPoint(pContour.pt);
pContour.insertAfter(newPtContour);
Point newPtHole = getPoint(pHole.pt);
pHole.insertBefore(newPtHole);
pContour.next = pHole;
pHole.prev = pContour;
newPtHole.next = newPtContour;
newPtContour.prev = newPtHole;
pContour.computeAngle();
pHole.computeAngle();
newPtContour.computeAngle();
newPtHole.computeAngle();
// detach the points from the hole
holes.first = null;
break outer;
}
}
} while ((pContour = pContour.next) != contour.first);
}
} while ((pHole = pHole.next) != holes.first);
// free the hole
holes = freePointBag(holes);
}
// Step 3: Make sure we have enough space for the result
int numTriangles = contour.countPoints() - 2;
int neededSpace = numTriangles * 3 + 1; // for the null
if (result.length < neededSpace) {
result = (Vector2f[]) Array.newInstance(result.getClass()
.getComponentType(), neededSpace);
}
// Step 4: Extract the triangles
int idx = 0;
for (;;) {
Point pContour = contour.first;
if (pContour == null) {
break;
}
// Are there 2 or less points left ?
if (pContour.next == pContour.prev) {
break;
}
outer: do {
if (pContour.angle > 0) {
Point prev = pContour.prev;
Point next = pContour.next;
if (next.next == prev || prev.isInfront(next) && next.isInfront(prev)) {
if (!contour.doesIntersectSegment(prev.pt, next.pt)) {
result[idx++] = pContour.pt;
result[idx++] = next.pt;
result[idx++] = prev.pt;
break outer;
}
}
}
} while ((pContour = pContour.next) != contour.first);
// remove the point - we do it in every case to prevent endless loop
Point prev = pContour.prev;
Point next = pContour.next;
contour.first = prev;
pContour.unlink();
freePoint(pContour);
next.computeAngle();
prev.computeAngle();
}
// Step 5: Append a null (see Collection.toArray)
result[idx] = null;
// Step 6: Free memory
contour.clear();
// Finished !
return result;
}
/**
* Create a new point bag (or recycle an old one)
*
* @return The new point bag
*/
private PointBag getPointBag() {
PointBag pb = nextFreePointBag;
if (pb != null) {
nextFreePointBag = pb.next;
pb.next = null;
return pb;
}
return new PointBag();
}
/**
* Release a pooled bag
*
* @param pb The bag to release
* @return The next available bag
*/
private PointBag freePointBag(PointBag pb) {
PointBag next = pb.next;
pb.clear();
pb.next = nextFreePointBag;
nextFreePointBag = pb;
return next;
}
/**
* Create or reuse a point
*
* @param pt The point data to set
* @return The new point
*/
private Point getPoint(Vector2f pt) {
Point p = nextFreePoint;
if (p != null) {
nextFreePoint = p.next;
// initialize new point to safe values
p.next = null;
p.prev = null;
p.pt = pt;
return p;
}
return new Point(pt);
}
/**
* Release a point into the pool
*
* @param p The point to release
*/
private void freePoint(Point p) {
p.next = nextFreePoint;
nextFreePoint = p;
}
/**
* Release all points
*
* @param head The head of the points bag
*/
private void freePoints(Point head) {
head.prev.next = nextFreePoint;
head.prev = null;
nextFreePoint = head;
}
/**
* A single point being considered during triangulation
*
* @author Matthias Mann
*/
private static class Point implements Serializable {
/** The location of the point */
protected Vector2f pt;
/** The previous point in the contour */
protected Point prev;
/** The next point in the contour */
protected Point next;
/** The x component of the of the normal */
protected double nx;
/** The y component of the of the normal */
protected double ny;
/** The angle at this point in the path */
protected double angle;
/** The distance of this point from */
protected double dist;
/**
* Create a new point
*
* @param pt The points location
*/
public Point(Vector2f pt) {
this.pt = pt;
}
/**
* Remove this point from it's contour
*/
public void unlink() {
prev.next = next;
next.prev = prev;
next = null;
prev = null;
}
/**
* Insert a point before this one (see LinkedList)
*
* @param p The point to insert
*/
public void insertBefore(Point p) {
prev.next = p;
p.prev = prev;
p.next = this;
prev = p;
}
/**
* Insert a point after this one (see LinkedList)
*
* @param p The point to insert
*/
public void insertAfter(Point p) {
next.prev = p;
p.prev = this;
p.next = next;
next = p;
}
/**
* Java 5 hypot method
*
* @param x The x component
* @param y The y component
* @return The hypotenuse
*/
private double hypot(double x, double y) {
return Math.sqrt(x*x + y*y);
}
/**
* Compute the angle at this point
*/
public void computeAngle() {
if (prev.pt.equals(pt)) {
pt.x += 0.01f;
}
double dx1 = pt.x - prev.pt.x;
double dy1 = pt.y - prev.pt.y;
double len1 = hypot(dx1, dy1);
dx1 /= len1;
dy1 /= len1;
if (next.pt.equals(pt)) {
pt.y += 0.01f;
}
double dx2 = next.pt.x - pt.x;
double dy2 = next.pt.y - pt.y;
double len2 = hypot(dx2, dy2);
dx2 /= len2;
dy2 /= len2;
double nx1 = -dy1;
double ny1 = dx1;
nx = (nx1 - dy2) * 0.5;
ny = (ny1 + dx2) * 0.5;
if (nx * nx + ny * ny < EPSILON) {
nx = dx1;
ny = dy2;
angle = 1; // TODO: nx1,ny1 and nx2,ny2 facing ?
if (dx1 * dx2 + dy1 * dy2 > 0) {
nx = -dx1;
ny = -dy1;
}
} else {
angle = nx * dx2 + ny * dy2;
}
}
/**
* Get the angle of this point to another
*
* @param p The other point
* @return The angle between this point and another
*/
public double getAngle(Point p) {
double dx = p.pt.x - pt.x;
double dy = p.pt.y - pt.y;
double dlen = hypot(dx, dy);
return (nx * dx + ny * dy) / dlen;
}
/**
* Check if this point is convave
*
* @return True if this point remains concave
*/
public boolean isConcave() {
return angle < 0;
}
/**
* Check if this point is infront of another
*
* @param dx The other x
* @param dy The other y
* @return True if this point is infront (in the contour)
*/
public boolean isInfront(double dx, double dy) {
// no nead to normalize, amplitude does not metter for side
// detection
boolean sidePrev = ((prev.pt.y - pt.y) * dx + (pt.x - prev.pt.x)
* dy) >= 0;
boolean sideNext = ((pt.y - next.pt.y) * dx + (next.pt.x - pt.x)
* dy) >= 0;
return (angle < 0) ? (sidePrev | sideNext) : (sidePrev & sideNext);
}
/**
* Check if this point is infront of another
*
* @param p The other point
* @return True if this point is infront (in the contour)
*/
public boolean isInfront(Point p) {
return isInfront(p.pt.x - pt.x, p.pt.y - pt.y);
}
}
/**
* A bag/pool of point objects
*
* @author kevin
*/
protected class PointBag implements Serializable {
/** The first point in the bag - head of the list */
protected Point first;
/** The next bag in the list of bags */
protected PointBag next;
/**
* Clear all the points from this bag
*/
public void clear() {
if (first != null) {
freePoints(first);
first = null;
}
}
/**
* Add a point to the bag
*
* @param p The point to add
*/
public void add(Point p) {
if (first != null) {
first.insertBefore(p);
} else {
first = p;
p.next = p;
p.prev = p;
}
}
/**
* Compute the angles for the points in this bag
*/
public void computeAngles() {
if (first == null) {
return;
}
Point p = first;
do {
p.computeAngle();
} while ((p = p.next) != first);
}
/**
* Check if the points in this bag form a path intersecting
* with the specified path
*
* @param v1 The start point of the segment
* @param v2 The end point of the segment
* @return True if points in this contour intersect with the segment
*/
public boolean doesIntersectSegment(Vector2f v1, Vector2f v2) {
double dxA = v2.x - v1.x;
double dyA = v2.y - v1.y;
for (Point p = first;;) {
Point n = p.next;
if (p.pt != v1 && n.pt != v1 && p.pt != v2 && n.pt != v2) {
double dxB = n.pt.x - p.pt.x;
double dyB = n.pt.y - p.pt.y;
double d = (dxA * dyB) - (dyA * dxB);
if (Math.abs(d) > EPSILON) {
double tmp1 = p.pt.x - v1.x;
double tmp2 = p.pt.y - v1.y;
double tA = (dyB * tmp1 - dxB * tmp2) / d;
double tB = (dyA * tmp1 - dxA * tmp2) / d;
if (tA >= 0 && tA <= 1 && tB >= 0 && tB <= 1) {
return true;
}
}
}
if (n == first) {
return false;
}
p = n;
}
}
/**
* Get the number of points in the bag
*
* @return The number of points in the bag
*/
public int countPoints() {
if (first == null) {
return 0;
}
int count = 0;
Point p = first;
do {
++count;
} while ((p = p.next) != first);
return count;
}
/**
* Check if the point provided was contained
*
* @param point The point provided
* @return True if it's in the bag
*/
public boolean contains(Vector2f point) {
if (first == null) {
return false;
}
if (first.prev.pt.equals(point)) {
return true;
}
if (first.pt.equals(point)) {
return true;
}
return false;
}
}
public boolean triangulate() {
Vector2f[] temp = triangulate(new Vector2f[0]);
for (int i = 0; i < temp.length; i++) {
if (temp[i] == null) {
break;
} else {
triangles.add(temp[i]);
}
}
return true;
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTriangleCount()
*/
public int getTriangleCount() {
return triangles.size() / 3;
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTrianglePoint(int, int)
*/
public float[] getTrianglePoint(int tri, int i) {
Vector2f pt = (Vector2f) triangles.get((tri * 3) + i);
return new float[] { pt.x, pt.y };
}
}

View File

@@ -0,0 +1,193 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
/**
* A shape that morphs between a set of other shapes
*
* @author kevin
*/
public class MorphShape extends Shape {
/** The shapes to morph between */
private ArrayList shapes = new ArrayList();
/** The offset between the shapes */
private float offset;
/** The current shape */
private Shape current;
/** The next shape */
private Shape next;
/**
* Create a new mighty morphin shape
*
* @param base The base shape we're starting the morph from
*/
public MorphShape(Shape base) {
shapes.add(base);
float[] copy = base.points;
this.points = new float[copy.length];
current = base;
next = base;
}
/**
* Add a subsequent shape that we should morph too in order
*
* @param shape The new shape that forms part of the morphing shape
*/
public void addShape(Shape shape) {
if (shape.points.length != points.length) {
throw new RuntimeException("Attempt to morph between two shapes with different vertex counts");
}
Shape prev = (Shape) shapes.get(shapes.size()-1);
if (equalShapes(prev, shape)) {
shapes.add(prev);
} else {
shapes.add(shape);
}
if (shapes.size() == 2) {
next = (Shape) shapes.get(1);
}
}
/**
* Check if the shape's points are all equal
*
* @param a The first shape to compare
* @param b The second shape to compare
* @return True if the shapes are equal
*/
private boolean equalShapes(Shape a, Shape b) {
a.checkPoints();
b.checkPoints();
for (int i=0;i<a.points.length;i++) {
if (a.points[i] != b.points[i]) {
return false;
}
}
return true;
}
/**
* Set the "time" index for this morph. This is given in terms of shapes, so
* 0.5f would give you the position half way between the first and second shapes.
*
* @param time The time index to represent on this shape
*/
public void setMorphTime(float time) {
int p = (int) time;
int n = p + 1;
float offset = time - p;
p = rational(p);
n = rational(n);
setFrame(p, n, offset);
}
/**
* Update the morph time and hence the curent frame
*
* @param delta The amount to change the morph time by
*/
public void updateMorphTime(float delta) {
offset += delta;
if (offset < 0) {
int index = shapes.indexOf(current);
if (index < 0) {
index = shapes.size() - 1;
}
int nframe = rational(index+1);
setFrame(index, nframe, offset);
offset += 1;
} else if (offset > 1) {
int index = shapes.indexOf(next);
if (index < 1) {
index = 0;
}
int nframe = rational(index+1);
setFrame(index, nframe, offset);
offset -= 1;
} else {
pointsDirty = true;
}
}
/**
* Set the current frame
*
* @param current The current frame
*/
public void setExternalFrame(Shape current) {
this.current = current;
next = (Shape) shapes.get(0);
offset = 0;
}
/**
* Get an index that is rational, i.e. fits inside this set of shapes
*
* @param n The index to rationalize
* @return The index rationalized
*/
private int rational(int n) {
while (n >= shapes.size()) {
n -= shapes.size();
}
while (n < 0) {
n += shapes.size();
}
return n;
}
/**
* Set the frame to be represented
*
* @param a The index of the first shape
* @param b The index of the second shape
* @param offset The offset between the two shapes to represent
*/
private void setFrame(int a, int b, float offset) {
current = (Shape) shapes.get(a);
next = (Shape) shapes.get(b);
this.offset = offset;
pointsDirty = true;
}
/**
* @see MorphShape#createPoints()
*/
protected void createPoints() {
if (current == next) {
System.arraycopy(current.points,0,points,0,points.length);
return;
}
float[] apoints = current.points;
float[] bpoints = next.points;
for (int i=0;i<points.length;i++) {
points[i] = apoints[i] * (1 - offset);
points[i] += bpoints[i] * offset;
}
}
/**
* @see MorphShape#transform(Transform)
*/
public Shape transform(Transform transform) {
createPoints();
Polygon poly = new Polygon(points);
return poly;
}
}

View File

@@ -0,0 +1,617 @@
package org.newdawn.slick.geom;
/**
* A second triangulator that seems slightly more robust
*
* @author Online examples
*/
public class NeatTriangulator implements Triangulator
{
/** The error factor */
static final float EPSILON = 1E-006F;
/** The x coordinates */
private float pointsX[];
/** The y coordiantes */
private float pointsY[];
/** The number of points that have been added */
private int numPoints;
/** The edges defines by triangulation */
private Edge edges[];
/** Voroni */
private int V[];
/** The number of edges found */
private int numEdges;
/** The triangles that have been found */
private Triangle triangles[];
/** The number of triangles found */
private int numTriangles;
/** The current offset */
private float offset = EPSILON;
/**
* Create a new triangulator
*/
public NeatTriangulator()
{
pointsX = new float[100];
pointsY = new float[100];
numPoints = 0;
edges = new Edge[100];
numEdges = 0;
triangles = new Triangle[100];
numTriangles = 0;
}
/**
* Clear the triangulator status
*/
public void clear()
{
numPoints = 0;
numEdges = 0;
numTriangles = 0;
}
/**
* Find an edge between two verts
*
* @param i The index of the first vert
* @param j The index of the second vert
* @return The index of the dge
*/
private int findEdge(int i, int j)
{
int k;
int l;
if(i < j)
{
k = i;
l = j;
} else
{
k = j;
l = i;
}
for(int i1 = 0; i1 < numEdges; i1++)
if(edges[i1].v0 == k && edges[i1].v1 == l)
return i1;
return -1;
}
/**
* Add a discovered edge
*
* @param i The index of the first vert
* @param j The index of the second vert
* @param k The index of the spread vert
*/
private void addEdge(int i, int j, int k)
{
int l1 = findEdge(i, j);
int j1;
int k1;
Edge edge;
if(l1 < 0)
{
if(numEdges == edges.length)
{
Edge aedge[] = new Edge[edges.length * 2];
System.arraycopy(edges, 0, aedge, 0, numEdges);
edges = aedge;
}
j1 = -1;
k1 = -1;
l1 = numEdges++;
edge = edges[l1] = new Edge();
} else
{
edge = edges[l1];
j1 = edge.t0;
k1 = edge.t1;
}
int l;
int i1;
if(i < j)
{
l = i;
i1 = j;
j1 = k;
} else
{
l = j;
i1 = i;
k1 = k;
}
edge.v0 = l;
edge.v1 = i1;
edge.t0 = j1;
edge.t1 = k1;
edge.suspect = true;
}
/**
* Remove and edge identified by it's verts
*
* @param i The index of the first vert
* @param j The index of the second vert
* @throws InternalException Indicates the edge didn't exist
*/
private void deleteEdge(int i, int j) throws InternalException
{
int k;
if(0 > (k = findEdge(i, j)))
{
throw new InternalException("Attempt to delete unknown edge");
}
else
{
edges[k] = edges[--numEdges];
return;
}
}
/**
* Mark an edge as either a suspect or not
*
* @param i The index of the first vert
* @param j The index of the second vert
* @param flag True if the edge is a suspect
* @throws InternalException Indicates the edge didn't exist
*/
void markSuspect(int i, int j, boolean flag) throws InternalException
{
int k;
if(0 > (k = findEdge(i, j)))
{
throw new InternalException("Attempt to mark unknown edge");
} else
{
edges[k].suspect = flag;
return;
}
}
/**
* Choose the suspect to become part of the triangle
*
* @return The edge selected
*/
private Edge chooseSuspect()
{
for(int i = 0; i < numEdges; i++)
{
Edge edge = edges[i];
if(edge.suspect)
{
edge.suspect = false;
if(edge.t0 >= 0 && edge.t1 >= 0)
return edge;
}
}
return null;
}
/**
* Factor rho.
*
* @param f Factor 1
* @param f1 Factor 2
* @param f2 Factor 3
* @param f3 Factor 4
* @param f4 Factor 5
* @param f5 Factor 6
* @return The computation of rho
*/
private static float rho(float f, float f1, float f2, float f3, float f4, float f5)
{
float f6 = f4 - f2;
float f7 = f5 - f3;
float f8 = f - f4;
float f9 = f1 - f5;
float f18 = f6 * f9 - f7 * f8;
if(f18 > 0.0F)
{
if(f18 < 1E-006F)
f18 = 1E-006F;
float f12 = f6 * f6;
float f13 = f7 * f7;
float f14 = f8 * f8;
float f15 = f9 * f9;
float f10 = f2 - f;
float f11 = f3 - f1;
float f16 = f10 * f10;
float f17 = f11 * f11;
return ((f12 + f13) * (f14 + f15) * (f16 + f17)) / (f18 * f18);
} else
{
return -1F;
}
}
/**
* Check if the point P is inside the triangle defined by
* the points A,B,C
*
* @param f Point A x-coordinate
* @param f1 Point A y-coordinate
* @param f2 Point B x-coordinate
* @param f3 Point B y-coordinate
* @param f4 Point C x-coordinate
* @param f5 Point C y-coordinate
* @param f6 Point P x-coordinate
* @param f7 Point P y-coordinate
* @return True if the point specified is within the triangle
*/
private static boolean insideTriangle(float f, float f1, float f2, float f3, float f4, float f5, float f6, float f7)
{
float f8 = f4 - f2;
float f9 = f5 - f3;
float f10 = f - f4;
float f11 = f1 - f5;
float f12 = f2 - f;
float f13 = f3 - f1;
float f14 = f6 - f;
float f15 = f7 - f1;
float f16 = f6 - f2;
float f17 = f7 - f3;
float f18 = f6 - f4;
float f19 = f7 - f5;
float f22 = f8 * f17 - f9 * f16;
float f20 = f12 * f15 - f13 * f14;
float f21 = f10 * f19 - f11 * f18;
return f22 >= 0.0D && f21 >= 0.0D && f20 >= 0.0D;
}
/**
* Cut a the contour and add a triangle into V to describe the
* location of the cut
*
* @param i The index of the first point
* @param j The index of the second point
* @param k The index of the third point
* @param l ?
* @return True if a triangle was found
*/
private boolean snip(int i, int j, int k, int l)
{
float f = pointsX[V[i]];
float f1 = pointsY[V[i]];
float f2 = pointsX[V[j]];
float f3 = pointsY[V[j]];
float f4 = pointsX[V[k]];
float f5 = pointsY[V[k]];
if(1E-006F > (f2 - f) * (f5 - f1) - (f3 - f1) * (f4 - f))
return false;
for(int i1 = 0; i1 < l; i1++)
if(i1 != i && i1 != j && i1 != k)
{
float f6 = pointsX[V[i1]];
float f7 = pointsY[V[i1]];
if(insideTriangle(f, f1, f2, f3, f4, f5, f6, f7))
return false;
}
return true;
}
/**
* Get the area defined by the points
*
* @return The area defined by the points
*/
private float area()
{
float f = 0.0F;
int i = numPoints - 1;
for(int j = 0; j < numPoints;)
{
f += pointsX[i] * pointsY[j] - pointsY[i] * pointsX[j];
i = j++;
}
return f * 0.5F;
}
/**
* Perform simple triangulation
*
* @throws InternalException Indicates a polygon that can't be triangulated
*/
public void basicTriangulation() throws InternalException
{
int i = numPoints;
if(i < 3)
return;
numEdges = 0;
numTriangles = 0;
V = new int[i];
if(0.0D < area())
{
for(int k = 0; k < i; k++)
V[k] = k;
} else
{
for(int l = 0; l < i; l++)
V[l] = numPoints - 1 - l;
}
int k1 = 2 * i;
int i1 = i - 1;
while(i > 2)
{
if(0 >= k1--) {
throw new InternalException("Bad polygon");
}
int j = i1;
if(i <= j)
j = 0;
i1 = j + 1;
if(i <= i1)
i1 = 0;
int j1 = i1 + 1;
if(i <= j1)
j1 = 0;
if(snip(j, i1, j1, i))
{
int l1 = V[j];
int i2 = V[i1];
int j2 = V[j1];
if(numTriangles == triangles.length)
{
Triangle atriangle[] = new Triangle[triangles.length * 2];
System.arraycopy(triangles, 0, atriangle, 0, numTriangles);
triangles = atriangle;
}
triangles[numTriangles] = new Triangle(l1, i2, j2);
addEdge(l1, i2, numTriangles);
addEdge(i2, j2, numTriangles);
addEdge(j2, l1, numTriangles);
numTriangles++;
int k2 = i1;
for(int l2 = i1 + 1; l2 < i; l2++)
{
V[k2] = V[l2];
k2++;
}
i--;
k1 = 2 * i;
}
}
V = null;
}
/**
* Optimize the triangulation by applying delauney rules
*
* @throws InternalException Indicates an invalid polygon
*/
private void optimize() throws InternalException
{
do
{
Edge edge;
if ((edge = chooseSuspect()) == null) {
break;
}
int i1 = edge.v0;
int k1 = edge.v1;
int i = edge.t0;
int j = edge.t1;
int j1 = -1;
int l1 = -1;
for (int k = 0; k < 3; k++)
{
int i2 = triangles[i].v[k];
if(i1 == i2 || k1 == i2) {
continue;
}
l1 = i2;
break;
}
for (int l = 0; l < 3; l++)
{
int j2 = triangles[j].v[l];
if(i1 == j2 || k1 == j2) {
continue;
}
j1 = j2;
break;
}
if(-1 == j1 || -1 == l1) {
throw new InternalException("can't find quad");
}
float f = pointsX[i1];
float f1 = pointsY[i1];
float f2 = pointsX[j1];
float f3 = pointsY[j1];
float f4 = pointsX[k1];
float f5 = pointsY[k1];
float f6 = pointsX[l1];
float f7 = pointsY[l1];
float f8 = rho(f, f1, f2, f3, f4, f5);
float f9 = rho(f, f1, f4, f5, f6, f7);
float f10 = rho(f2, f3, f4, f5, f6, f7);
float f11 = rho(f2, f3, f6, f7, f, f1);
if(0.0F > f8 || 0.0F > f9) {
throw new InternalException("original triangles backwards");
}
if(0.0F <= f10 && 0.0F <= f11)
{
if(f8 > f9) {
f8 = f9;
}
if(f10 > f11) {
f10 = f11;
}
if(f8 > f10) {
deleteEdge(i1, k1);
triangles[i].v[0] = j1;
triangles[i].v[1] = k1;
triangles[i].v[2] = l1;
triangles[j].v[0] = j1;
triangles[j].v[1] = l1;
triangles[j].v[2] = i1;
addEdge(j1, k1, i);
addEdge(k1, l1, i);
addEdge(l1, j1, i);
addEdge(l1, i1, j);
addEdge(i1, j1, j);
addEdge(j1, l1, j);
markSuspect(j1, l1, false);
}
}
} while(true);
}
/**
* Upate the triangles
*/
public boolean triangulate()
{
try
{
basicTriangulation();
//optimize();
return true;
}
catch (InternalException e)
{
numEdges = 0;
}
return false;
}
/**
* Add a point to the polygon
*/
public void addPolyPoint(float x, float y)
{
for (int i=0;i<numPoints;i++) {
if ((pointsX[i] == x) && (pointsY[i] == y)) {
//return;
y += offset;
offset += EPSILON;
}
}
if(numPoints == pointsX.length)
{
float af[] = new float[numPoints * 2];
System.arraycopy(pointsX, 0, af, 0, numPoints);
pointsX = af;
af = new float[numPoints * 2];
System.arraycopy(pointsY, 0, af, 0, numPoints);
pointsY = af;
}
pointsX[numPoints] = x;
pointsY[numPoints] = y;
numPoints++;
}
/**
* A single triangle
*
* @author Online Source
*/
class Triangle
{
/** The verticies index */
int v[];
/**
* Create a new triangle
*
* @param i The index of vert 1
* @param j The index of vert 2
* @param k The index of vert 3
*/
Triangle(int i, int j, int k)
{
v = new int[3];
v[0] = i;
v[1] = j;
v[2] = k;
}
}
/**
* A single edge between two points
*
* @author Online Source
*/
class Edge
{
/** The start vert */
int v0;
/** The end vert */
int v1;
/** The start tangent vert */
int t0;
/** The end tangent vert */
int t1;
/** True if the edge is marked as a suspect */
boolean suspect;
/**
* Create a new empty edge
*/
Edge()
{
v0 = -1;
v1 = -1;
t0 = -1;
t1 = -1;
}
}
/**
* A failure to triangulate, hidden from outside and handled
*
* @author Online Source
*/
class InternalException extends Exception {
/**
* Create an internal exception
*
* @param msg The message describing the exception
*/
public InternalException(String msg) {
super(msg);
}
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTriangleCount()
*/
public int getTriangleCount() {
return numTriangles;
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTrianglePoint(int, int)
*/
public float[] getTrianglePoint(int tri, int i) {
float xp = pointsX[triangles[tri].v[i]];
float yp = pointsY[triangles[tri].v[i]];
return new float[] {xp,yp};
}
/**
* @see org.newdawn.slick.geom.Triangulator#startHole()
*/
public void startHole() {
}
}

View File

@@ -0,0 +1,113 @@
package org.newdawn.slick.geom;
/**
* A triangulator implementation that splits the triangules of another, subdividing
* to give a higher tesselation - and hence smoother transitions.
*
* @author kevin
*/
public class OverTriangulator implements Triangulator {
/** The triangles data */
private float[][] triangles;
/**
* Create a new triangulator
*
* @param tris The original set of triangles to be sub-dividied
*/
public OverTriangulator(Triangulator tris) {
triangles = new float[tris.getTriangleCount()*6*3][2];
int tcount = 0;
for (int i=0;i<tris.getTriangleCount();i++) {
float cx = 0;
float cy = 0;
for (int p = 0;p < 3;p++) {
float[] pt = tris.getTrianglePoint(i, p);
cx += pt[0];
cy += pt[1];
}
cx /= 3;
cy /= 3;
for (int p = 0;p < 3;p++) {
int n = p +1;
if (n > 2) {
n = 0;
}
float[] pt1 = tris.getTrianglePoint(i, p);
float[] pt2 = tris.getTrianglePoint(i, n);
pt1[0] = (pt1[0] + pt2[0]) / 2;
pt1[1] = (pt1[1] + pt2[1]) / 2;
triangles[(tcount *3) + 0][0] = cx;
triangles[(tcount *3) + 0][1] = cy;
triangles[(tcount *3) + 1][0] = pt1[0];
triangles[(tcount *3) + 1][1] = pt1[1];
triangles[(tcount *3) + 2][0] = pt2[0];
triangles[(tcount *3) + 2][1] = pt2[1];
tcount++;
}
for (int p = 0;p < 3;p++) {
int n = p +1;
if (n > 2) {
n = 0;
}
float[] pt1 = tris.getTrianglePoint(i, p);
float[] pt2 = tris.getTrianglePoint(i, n);
pt2[0] = (pt1[0] + pt2[0]) / 2;
pt2[1] = (pt1[1] + pt2[1]) / 2;
triangles[(tcount *3) + 0][0] = cx;
triangles[(tcount *3) + 0][1] = cy;
triangles[(tcount *3) + 1][0] = pt1[0];
triangles[(tcount *3) + 1][1] = pt1[1];
triangles[(tcount *3) + 2][0] = pt2[0];
triangles[(tcount *3) + 2][1] = pt2[1];
tcount++;
}
}
}
/**
* @see org.newdawn.slick.geom.Triangulator#addPolyPoint(float, float)
*/
public void addPolyPoint(float x, float y) {
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTriangleCount()
*/
public int getTriangleCount() {
return triangles.length / 3;
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTrianglePoint(int, int)
*/
public float[] getTrianglePoint(int tri, int i) {
float[] pt = triangles[(tri * 3)+i];
return new float[] {pt[0],pt[1]};
}
/**
* @see org.newdawn.slick.geom.Triangulator#startHole()
*/
public void startHole() {
}
/**
* @see org.newdawn.slick.geom.Triangulator#triangulate()
*/
public boolean triangulate() {
return true;
}
}

View File

@@ -0,0 +1,241 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
/**
* A shape built from lines and curves. Hole support is present but
* restricted.
*
* @author kevin
*/
public class Path extends Shape {
/** The local list of points */
private ArrayList localPoints = new ArrayList();
/** The current x coordinate */
private float cx;
/** The current y coordiante */
private float cy;
/** True if the path has been closed */
private boolean closed;
/** The list of holes placed */
private ArrayList holes = new ArrayList();
/** The current hole being built */
private ArrayList hole;
/**
* Create a new path
*
* @param sx The start x coordinate of the path
* @param sy The start y coordiante of the path
*/
public Path(float sx, float sy) {
localPoints.add(new float[] {sx,sy});
cx = sx;
cy = sy;
pointsDirty = true;
}
/**
* Start building a hole in the previously defined contour
*
* @param sx The start point of the hole
* @param sy The start point of the hole
*/
public void startHole(float sx, float sy) {
hole = new ArrayList();
holes.add(hole);
}
/**
* Add a line to the contour or hole which ends at the specified
* location.
*
* @param x The x coordinate to draw the line to
* @param y The y coordiante to draw the line to
*/
public void lineTo(float x, float y) {
if (hole != null) {
hole.add(new float[] {x,y});
} else {
localPoints.add(new float[] {x,y});
}
cx = x;
cy = y;
pointsDirty = true;
}
/**
* Close the path to form a polygon
*/
public void close() {
closed = true;
}
/**
* Add a curve to the specified location (using the default segments 10)
*
* @param x The destination x coordinate
* @param y The destination y coordiante
* @param cx1 The x coordiante of the first control point
* @param cy1 The y coordiante of the first control point
* @param cx2 The x coordinate of the second control point
* @param cy2 The y coordinate of the second control point
*/
public void curveTo(float x, float y, float cx1, float cy1, float cx2, float cy2) {
curveTo(x,y,cx1,cy1,cx2,cy2,10);
}
/**
* Add a curve to the specified location (specifing the number of segments)
*
* @param x The destination x coordinate
* @param y The destination y coordiante
* @param cx1 The x coordiante of the first control point
* @param cy1 The y coordiante of the first control point
* @param cx2 The x coordinate of the second control point
* @param cy2 The y coordinate of the second control point
* @param segments The number of segments to use for the new curve
*/
public void curveTo(float x, float y, float cx1, float cy1, float cx2, float cy2, int segments) {
// special case for zero movement
if ((cx == x) && (cy == y)) {
return;
}
Curve curve = new Curve(new Vector2f(cx,cy),new Vector2f(cx1,cy1),new Vector2f(cx2,cy2),new Vector2f(x,y));
float step = 1.0f / segments;
for (int i=1;i<segments+1;i++) {
float t = i * step;
Vector2f p = curve.pointAt(t);
if (hole != null) {
hole.add(new float[] {p.x,p.y});
} else {
localPoints.add(new float[] {p.x,p.y});
}
cx = p.x;
cy = p.y;
}
pointsDirty = true;
}
/**
* @see org.newdawn.slick.geom.Shape#createPoints()
*/
protected void createPoints() {
points = new float[localPoints.size() * 2];
for (int i=0;i<localPoints.size();i++) {
float[] p = (float[]) localPoints.get(i);
points[(i*2)] = p[0];
points[(i*2)+1] = p[1];
}
}
/**
* @see org.newdawn.slick.geom.Shape#transform(org.newdawn.slick.geom.Transform)
*/
public Shape transform(Transform transform) {
Path p = new Path(cx,cy);
p.localPoints = transform(localPoints, transform);
for (int i=0;i<holes.size();i++) {
p.holes.add(transform((ArrayList) holes.get(i), transform));
}
p.closed = this.closed;
return p;
}
/**
* Transform a list of points
*
* @param pts The pts to transform
* @param t The transform to apply
* @return The transformed points
*/
private ArrayList transform(ArrayList pts, Transform t) {
float[] in = new float[pts.size()*2];
float[] out = new float[pts.size()*2];
for (int i=0;i<pts.size();i++) {
in[i*2] = ((float[]) pts.get(i))[0];
in[(i*2)+1] = ((float[]) pts.get(i))[1];
}
t.transform(in, 0, out, 0, pts.size());
ArrayList outList = new ArrayList();
for (int i=0;i<pts.size();i++) {
outList.add(new float[] {out[(i*2)],out[(i*2)+1]});
}
return outList;
}
// /**
// * Calculate the triangles that can fill this shape
// */
// protected void calculateTriangles() {
// if (!trianglesDirty) {
// return;
// }
// if (points.length >= 6) {
// boolean clockwise = true;
// float area = 0;
// for (int i=0;i<(points.length/2)-1;i++) {
// float x1 = points[(i*2)];
// float y1 = points[(i*2)+1];
// float x2 = points[(i*2)+2];
// float y2 = points[(i*2)+3];
//
// area += (x1 * y2) - (y1 * x2);
// }
// area /= 2;
// clockwise = area > 0;
//
// if (clockwise) {
// tris = new MannTriangulator();
// for (int i=0;i<points.length;i+=2) {
// tris.addPolyPoint(points[i], points[i+1]);
// }
//
// for (int h=0;h<holes.size();h++) {
// ArrayList hole = (ArrayList) holes.get(h);
// tris.startHole();
// for (int i=0;i<hole.size();i++) {
// float[] pt = (float[]) hole.get(i);
// tris.addPolyPoint(pt[0],pt[1]);
// }
// }
// tris.triangulate();
// } else {
// tris = new MannTriangulator();
// for (int i=points.length-2;i>=0;i-=2) {
// tris.addPolyPoint(points[i], points[i+1]);
// }
//
// for (int h=0;h<holes.size();h++) {
// ArrayList hole = (ArrayList) holes.get(h);
// tris.startHole();
// for (int i=hole.size()-1;i>=0;i--) {
// float[] pt = (float[]) hole.get(i);
// tris.addPolyPoint(pt[0],pt[1]);
// }
// }
// tris.triangulate();
// }
//
// } else {
// tris.triangulate();
// }
//
// trianglesDirty = false;
// }
/**
* True if this is a closed shape
*
* @return True if this is a closed shape
*/
public boolean closed() {
return closed;
}
}

View File

@@ -0,0 +1,72 @@
package org.newdawn.slick.geom;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Transform;
/**
* A single point shape
*
* @author Kova
*/
public class Point extends Shape
{
/**
* Create a new point
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
*/
public Point(float x, float y)
{
this.x = x;
this.y = y;
checkPoints();
}
/**
* @see org.newdawn.slick.geom.Shape#transform(org.newdawn.slick.geom.Transform)
*/
public Shape transform(Transform transform)
{
float result[] = new float[points.length];
transform.transform(points, 0, result, 0, points.length / 2);
return new Point(points[0], points[1]);
}
/**
* @see org.newdawn.slick.geom.Shape#createPoints()
*/
protected void createPoints()
{
points = new float[2];
points[0] = getX();
points[1] = getY();
maxX = x;
maxY = y;
minX = x;
minY = y;
findCenter();
calculateRadius();
}
/**
* @see org.newdawn.slick.geom.Shape#findCenter()
*/
protected void findCenter()
{
center = new float[2];
center[0] = points[0];
center[1] = points[1];
}
/**
* @see org.newdawn.slick.geom.Shape#calculateRadius()
*/
protected void calculateRadius()
{
boundingCircleRadius = 0;
}
}

View File

@@ -0,0 +1,200 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
import java.util.Arrays;
/**
* A polygon implementation meeting the <code>Shape</code> contract.
*
* @author Mark
*/
public class Polygon extends Shape {
/** Allow duplicated points */
private boolean allowDups = false;
/** True if the polygon is closed */
private boolean closed = true;
/**
* Construct a new polygon with 3 or more points.
* This constructor will take the first set of points and copy them after
* the last set of points to create a closed shape.
*
* @param points An array of points in x, y order.
*/
public Polygon(float points[]) {
int length = points.length;
this.points = new float[length];
maxX = -Float.MIN_VALUE;
maxY = -Float.MIN_VALUE;
minX = Float.MAX_VALUE;
minY = Float.MAX_VALUE;
x = Float.MAX_VALUE;
y = Float.MAX_VALUE;
for(int i=0;i<length;i++) {
this.points[i] = points[i];
if(i % 2 == 0) {
if(points[i] > maxX) {
maxX = points[i];
}
if(points[i] < minX) {
minX = points[i];
}
if(points[i] < x) {
x = points[i];
}
}
else {
if(points[i] > maxY) {
maxY = points[i];
}
if(points[i] < minY) {
minY = points[i];
}
if(points[i] < y) {
y = points[i];
}
}
}
findCenter();
calculateRadius();
pointsDirty = true;
}
/**
* Create an empty polygon
*
*/
public Polygon(){
points = new float[0];
maxX = -Float.MIN_VALUE;
maxY = -Float.MIN_VALUE;
minX = Float.MAX_VALUE;
minY = Float.MAX_VALUE;
}
/**
* Indicate if duplicate points are allow
*
* @param allowDups True if duplicate points are allowed
*/
public void setAllowDuplicatePoints(boolean allowDups) {
this.allowDups = allowDups;
}
/**
* Add a point to the polygon
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
*/
public void addPoint(float x, float y) {
if (hasVertex(x,y) && (!allowDups)) {
return;
}
ArrayList tempPoints = new ArrayList();
for(int i=0;i<points.length;i++) {
tempPoints.add(new Float(points[i]));
}
tempPoints.add(new Float(x));
tempPoints.add(new Float(y));
int length = tempPoints.size();
points = new float[length];
for(int i=0;i<length;i++) {
points[i] = ((Float)tempPoints.get(i)).floatValue();
}
if(x > maxX) {
maxX = x;
}
if(y > maxY) {
maxY = y;
}
if(x < minX) {
minX = x;
}
if(y < minY) {
minY = y;
}
findCenter();
calculateRadius();
pointsDirty = true;
}
/**
* 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 Shape transform(Transform transform) {
checkPoints();
Polygon resultPolygon = new Polygon();
float result[] = new float[points.length];
transform.transform(points, 0, result, 0, points.length / 2);
resultPolygon.points = result;
resultPolygon.findCenter();
resultPolygon.closed = closed;
return resultPolygon;
}
/**
* @see org.newdawn.slick.geom.Shape#setX(float)
*/
public void setX(float x) {
super.setX(x);
pointsDirty = false;
}
/**
* @see org.newdawn.slick.geom.Shape#setY(float)
*/
public void setY(float y) {
super.setY(y);
pointsDirty = false;
}
/**
* @see org.newdawn.slick.geom.Shape#createPoints()
*/
protected void createPoints() {
// This is empty since a polygon must have it's points all the time.
}
/**
* @see org.newdawn.slick.geom.Shape#closed()
*/
public boolean closed() {
return closed;
}
/**
* Indicate if the polygon should be closed
*
* @param closed True if the polygon should be closed
*/
public void setClosed(boolean closed) {
this.closed = closed;
}
/**
* Provide a copy of this polygon
*
* @return A copy of this polygon
*/
public Polygon copy() {
float[] copyPoints = new float[points.length];
System.arraycopy(points, 0, copyPoints, 0, copyPoints.length);
return new Polygon(copyPoints);
}
}

View File

@@ -0,0 +1,269 @@
package org.newdawn.slick.geom;
/**
* An axis oriented used for shape bounds
*
* @author Kevin Glass
*/
public class Rectangle extends Shape {
/** The width of the box */
protected float width;
/** The height of the box */
protected float height;
/**
* Create a new bounding box
*
* @param x The x position of the box
* @param y The y position of the box
* @param width The width of the box
* @param height The hieght of the box
*/
public Rectangle(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
maxX = x+width;
maxY = y+height;
checkPoints();
}
/**
* Check if this rectangle contains a point
*
* @param xp The x coordinate of the point to check
* @param yp The y coordinate of the point to check
* @return True if the point is within the rectangle
*/
public boolean contains(float xp, float yp) {
if (xp <= getX()) {
return false;
}
if (yp <= getY()) {
return false;
}
if (xp >= maxX) {
return false;
}
if (yp >= maxY) {
return false;
}
return true;
}
/**
* Set the bounds of this rectangle based on the given rectangle
*
* @param other The other rectangle whose bounds should be applied
*/
public void setBounds(Rectangle other) {
setBounds(other.getX(), other.getY(), other.getWidth(), other.getHeight());
}
/**
* Set the bounds of this rectangle
*
* @param x The x coordinate of this rectangle
* @param y The y coordinate of this rectangle
* @param width The width to set in this rectangle
* @param height The height to set in this rectangle
*/
public void setBounds(float x, float y, float width, float height) {
setX(x);
setY(y);
setSize(width, height);
}
/**
* Set the size (widtha and height) of this rectangle
*
* @param width The width to set in this rectangle
* @param height The height to set in this rectangle
*/
public void setSize(float width, float height) {
setWidth(width);
setHeight(height);
}
/**
* Get the width of the box
*
* @return The width of the box
*/
public float getWidth() {
return width;
}
/**
* Get the height of the box
*
* @return The height of the box
*/
public float getHeight() {
return height;
}
/**
* Grow the rectangle at all edges by the given amounts. This will result in the
* rectangle getting larger around it's centre.
*
* @param h The amount to adjust horizontally
* @param v The amount to ajust vertically
*/
public void grow(float h, float v) {
setX(getX() - h);
setY(getY() - v);
setWidth(getWidth() + (h*2));
setHeight(getHeight() + (v*2));
}
/**
* Grow the rectangle based on scaling it's size
*
* @param h The scale to apply to the horizontal
* @param v The scale to appy to the vertical
*/
public void scaleGrow(float h, float v) {
grow(getWidth() * (h-1), getHeight() * (v-1));
}
/**
* Set the width of this box
*
* @param width The new width of this box
*/
public void setWidth(float width) {
if (width != this.width) {
pointsDirty = true;
this.width = width;
maxX = x+width;
}
}
/**
* Set the heightof this box
*
* @param height The height of this box
*/
public void setHeight(float height) {
if (height != this.height) {
pointsDirty = true;
this.height = height;
maxY = y+height;
}
}
/**
* Check if this box touches another
*
* @param shape The other shape to check against
* @return True if the rectangles touch
*/
public boolean intersects(Shape shape) {
if(shape instanceof Rectangle) {
Rectangle other = (Rectangle)shape;
if ((x > (other.x + other.width)) || ((x + width) < other.x)) {
return false;
}
if ((y > (other.y + other.height)) || ((y + height) < other.y)) {
return false;
}
return true;
}
else if(shape instanceof Circle) {
return intersects((Circle)shape);
}
else {
return super.intersects(shape);
}
}
protected void createPoints() {
float useWidth = width ;
float useHeight = height;
points = new float[8];
points[0] = x;
points[1] = y;
points[2] = x + useWidth;
points[3] = y;
points[4] = x + useWidth;
points[5] = y + useHeight;
points[6] = x;
points[7] = y + useHeight;
maxX = points[2];
maxY = points[5];
minX = points[0];
minY = points[1];
findCenter();
calculateRadius();
}
/**
* Check if a circle touches this rectangle
*
* @param other The circle to check against
* @return True if they touch
*/
private boolean intersects(Circle other) {
return other.intersects(this);
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "[Rectangle "+width+"x"+height+"]";
}
/**
* Check if a rectangle contains a point (static to use it everywhere)
*
* @param xp
* The x coordinate of the point to check
* @param yp
* The y coordinate of the point to check
* @param xr
* The x coordinate of the rectangle
* @param yr
* The y coordinate of the rectangle
* @param widthr
* The width of the rectangle
* @param heightr The height of the rectangle
* @return True if the point is within the rectangle
*/
public static boolean contains(float xp, float yp, float xr, float yr,
float widthr, float heightr) {
return (xp >= xr) && (yp >= yr) && (xp <= xr + widthr)
&& (yp <= yr + heightr);
}
/**
* 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 Shape transform(Transform transform) {
checkPoints();
Polygon resultPolygon = new Polygon();
float result[] = new float[points.length];
transform.transform(points, 0, result, 0, points.length / 2);
resultPolygon.points = result;
resultPolygon.findCenter();
resultPolygon.checkPoints();
return resultPolygon;
}
}

View File

@@ -0,0 +1,284 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
import java.util.List;
import org.newdawn.slick.util.FastTrig;
/**
* Class to create rounded rectangles with.
*
* @author Mark Bernard
*/
public class RoundedRectangle extends Rectangle {
/** Indicates the top left corner should be rounded */
public static final int TOP_LEFT = 1;
/** Indicates the top right corner should be rounded */
public static final int TOP_RIGHT = 2;
/** Indicates the bottom right corner should be rounded */
public static final int BOTTOM_RIGHT = 4;
/** Indicates the bottom left corner should be rounded */
public static final int BOTTOM_LEFT = 8;
/** Indicates the all cornders should be rounded */
public static final int ALL = TOP_LEFT | TOP_RIGHT | BOTTOM_RIGHT | BOTTOM_LEFT;
/** Default number of segments to draw the rounded corners with */
private static final int DEFAULT_SEGMENT_COUNT = 25;
/** radius of each corner */
private float cornerRadius;
/** number of segments for each corner */
private int segmentCount;
/** The flags indicating which corners should be rounded */
private int cornerFlags;
/**
* Construct a rectangle with rounded corners.
*
* @param x The x position of the rectangle.
* @param y The y position of the rectangle.
* @param width The width of the rectangle.
* @param height The hieght of the rectangle.
* @param cornerRadius The radius to use for the arc in each corner.
*/
public RoundedRectangle(float x, float y, float width, float height, float cornerRadius) {
this(x, y, width, height, cornerRadius, DEFAULT_SEGMENT_COUNT);
}
/**
* Construct a rectangle with rounded corners.
*
* @param x The x position of the rectangle.
* @param y The y position of the rectangle.
* @param width The width of the rectangle.
* @param height The hieght of the rectangle.
* @param cornerRadius The radius to use for the arc in each corner.
* @param segmentCount The number of segments to use to draw each corner arc.
*/
public RoundedRectangle(float x, float y, float width, float height, float cornerRadius, int segmentCount) {
this(x,y,width,height,cornerRadius,segmentCount,ALL);
}
/**
* Construct a rectangle with rounded corners.
*
* @param x The x position of the rectangle.
* @param y The y position of the rectangle.
* @param width The width of the rectangle.
* @param height The hieght of the rectangle.
* @param cornerRadius The radius to use for the arc in each corner.
* @param segmentCount The number of segments to use to draw each corner arc.
* @param cornerFlags Indicates which corners should be rounded
*/
public RoundedRectangle(float x, float y, float width, float height,
float cornerRadius, int segmentCount, int cornerFlags) {
super(x,y,width,height);
if(cornerRadius < 0) {
throw new IllegalArgumentException("corner radius must be >= 0");
}
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.cornerRadius = cornerRadius;
this.segmentCount = segmentCount;
this.pointsDirty = true;
this.cornerFlags = cornerFlags;
}
/**
* Get the radius for each corner.
*
* @return The radius for each corner.
*/
public float getCornerRadius() {
return cornerRadius;
}
/**
* Set the radius for each corner.
*
* @param cornerRadius The radius for each corner to set.
*/
public void setCornerRadius(float cornerRadius) {
if (cornerRadius >= 0) {
if (cornerRadius != this.cornerRadius) {
this.cornerRadius = cornerRadius;
pointsDirty = true;
}
}
}
/**
* Get the height of this rectangle.
*
* @return The height of this rectangle.
*/
public float getHeight() {
return height;
}
/**
* Set the height of this rectangle.
*
* @param height The height to set.
*/
public void setHeight(float height) {
if (this.height != height) {
this.height = height;
pointsDirty = true;
}
}
/**
* Get the width of this rectangle.
*
* @return The width of this rectangle.
*/
public float getWidth() {
return width;
}
/**
* Set the width of this rectangle.
*
* @param width The width to set.
*/
public void setWidth(float width) {
if (width != this.width) {
this.width = width;
pointsDirty = true;
}
}
protected void createPoints() {
maxX = x + width;
maxY = y + height;
minX = x;
minY = y;
float useWidth = width - 1;
float useHeight = height - 1;
if(cornerRadius == 0) {
points = new float[8];
points[0] = x;
points[1] = y;
points[2] = x + useWidth;
points[3] = y;
points[4] = x + useWidth;
points[5] = y + useHeight;
points[6] = x;
points[7] = y + useHeight;
}
else {
float doubleRadius = cornerRadius * 2;
if(doubleRadius > useWidth) {
doubleRadius = useWidth;
cornerRadius = doubleRadius / 2;
}
if(doubleRadius > useHeight) {
doubleRadius = useHeight;
cornerRadius = doubleRadius / 2;
}
ArrayList tempPoints = new ArrayList();
//the outer most set of points for each arc will also ac as the points that start the
//straight sides, so the straight sides do not have to be added.
//top left corner arc
if ((cornerFlags & TOP_LEFT) != 0) {
tempPoints.addAll(createPoints(segmentCount, cornerRadius, x + cornerRadius, y + cornerRadius, 180, 270));
} else {
tempPoints.add(new Float(x));
tempPoints.add(new Float(y));
}
//top right corner arc
if ((cornerFlags & TOP_RIGHT) != 0) {
tempPoints.addAll(createPoints(segmentCount, cornerRadius, x + useWidth - cornerRadius, y + cornerRadius, 270, 360));
} else {
tempPoints.add(new Float(x+useWidth));
tempPoints.add(new Float(y));
}
//bottom right corner arc
if ((cornerFlags & BOTTOM_RIGHT) != 0) {
tempPoints.addAll(createPoints(segmentCount, cornerRadius, x + useWidth - cornerRadius, y + useHeight - cornerRadius, 0, 90));
} else {
tempPoints.add(new Float(x+useWidth));
tempPoints.add(new Float(y+useHeight));
}
//bottom left corner arc
if ((cornerFlags & BOTTOM_LEFT) != 0) {
tempPoints.addAll(createPoints(segmentCount, cornerRadius, x + cornerRadius, y + useHeight - cornerRadius, 90, 180));
} else {
tempPoints.add(new Float(x));
tempPoints.add(new Float(y+useHeight));
}
points = new float[tempPoints.size()];
for(int i=0;i<tempPoints.size();i++) {
points[i] = ((Float)tempPoints.get(i)).floatValue();
}
}
findCenter();
calculateRadius();
}
/**
* Generate the points to fill a corner arc.
*
* @param numberOfSegments How fine to make the ellipse.
* @param radius The radius of the arc.
* @param cx The x center of the arc.
* @param cy The y center of the arc.
* @param start The start angle of the arc.
* @param end The end angle of the arc.
* @return The points created.
*/
private List createPoints(int numberOfSegments, float radius, float cx, float cy, float start, float end) {
ArrayList tempPoints = new ArrayList();
int step = 360 / numberOfSegments;
for (float a=start;a<=end+step;a+=step) {
float ang = a;
if (ang > end) {
ang = end;
}
float x = (float) (cx + (FastTrig.cos(Math.toRadians(ang)) * radius));
float y = (float) (cy + (FastTrig.sin(Math.toRadians(ang)) * radius));
tempPoints.add(new Float(x));
tempPoints.add(new Float(y));
}
return tempPoints;
}
/**
* 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 Shape transform(Transform transform) {
checkPoints();
Polygon resultPolygon = new Polygon();
float result[] = new float[points.length];
transform.transform(points, 0, result, 0, points.length / 2);
resultPolygon.points = result;
resultPolygon.findCenter();
return resultPolygon;
}
}

View File

@@ -0,0 +1,767 @@
package org.newdawn.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() {
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;
x += dx;
maxX += dx;
minX += dx;
trianglesDirty = true;
}
}
/**
* 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;
y += dy;
maxY += dy;
minY += dy;
trianglesDirty = true;
}
}
/**
* Get the y position of the top of this shape.
*
* @return The y position of the top of this shape.
*/
public float getY() {
return y;
}
/**
* 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.x);
setY(loc.y);
}
/**
* 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;
float xnew,ynew;
float xold,yold;
float x1,y1;
float x2,y2;
int npoints = points.length;
xold=points[npoints - 2];
yold=points[npoints - 1];
for (int i=0;i < npoints;i+=2) {
xnew = points[i];
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();
boolean result = false;
float points[] = getPoints(); // (x3, y3) and (x4, y4)
float thatPoints[] = shape.getPoints(); // (x1, y1) and (x2, y2)
int length = points.length;
int thatLength = thatPoints.length;
double unknownA;
double unknownB;
if (!closed()) {
length -= 2;
}
if (!shape.closed()) {
thatLength -= 2;
}
// x1 = thatPoints[j]
// x2 = thatPoints[j + 2]
// y1 = thatPoints[j + 1]
// y2 = thatPoints[j + 3]
// x3 = points[i]
// x4 = points[i + 2]
// y3 = points[i + 1]
// y4 = points[i + 3]
for(int i=0;i<length;i+=2) {
int iNext = i+2;
if (iNext >= points.length) {
iNext = 0;
}
for(int j=0;j<thatLength;j+=2) {
int jNext = j+2;
if (jNext >= thatPoints.length) {
jNext = 0;
}
unknownA = (((points[iNext] - points[i]) * (double) (thatPoints[j + 1] - points[i + 1])) -
((points[iNext+1] - points[i + 1]) * (thatPoints[j] - points[i]))) /
(((points[iNext+1] - points[i + 1]) * (thatPoints[jNext] - thatPoints[j])) -
((points[iNext] - points[i]) * (thatPoints[jNext+1] - thatPoints[j + 1])));
unknownB = (((thatPoints[jNext] - thatPoints[j]) * (double) (thatPoints[j + 1] - points[i + 1])) -
((thatPoints[jNext+1] - thatPoints[j + 1]) * (thatPoints[j] - points[i]))) /
(((points[iNext+1] - points[i + 1]) * (thatPoints[jNext] - thatPoints[j])) -
((points[iNext] - points[i]) * (thatPoints[jNext+1] - thatPoints[j + 1])));
if(unknownA >= 0 && unknownA <= 1 && unknownB >= 0 && unknownB <= 1) {
result = true;
break;
}
}
if(result) {
break;
}
}
return result;
}
/**
* 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 / 2);
center[1] /= (length / 2);
}
/**
* 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) {
boolean clockwise = true;
float area = 0;
for (int i=0;i<(points.length/2)-1;i++) {
float x1 = points[(i*2)];
float y1 = points[(i*2)+1];
float x2 = points[(i*2)+2];
float y2 = points[(i*2)+3];
area += (x1 * y2) - (y1 * x2);
}
area /= 2;
clockwise = area > 0;
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);
}
}
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;
}
}

View File

@@ -0,0 +1,391 @@
package org.newdawn.slick.geom;
import org.newdawn.slick.Image;
import org.newdawn.slick.ShapeFill;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureImpl;
import org.newdawn.slick.opengl.renderer.LineStripRenderer;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
/**
* @author Mark Bernard
*
* Use this class to render shpaes directly to OpenGL. Allows you to bypass the Graphics class.
*/
public final class ShapeRenderer {
/** The renderer to use for all GL operations */
private static SGL GL = Renderer.get();
/** The renderer to use line strips */
private static LineStripRenderer LSR = Renderer.getLineStripRenderer();
/**
* Draw the outline of the given shape. Only the vertices are set.
* The colour has to be set independently of this method.
*
* @param shape The shape to draw.
*/
public static final void draw(Shape shape) {
Texture t = TextureImpl.getLastBind();
TextureImpl.bindNone();
float points[] = shape.getPoints();
LSR.start();
for(int i=0;i<points.length;i+=2) {
LSR.vertex(points[i], points[i + 1]);
}
if (shape.closed()) {
LSR.vertex(points[0], points[1]);
}
LSR.end();
if (t == null) {
TextureImpl.bindNone();
} else {
t.bind();
}
}
/**
* Draw the outline of the given shape. Only the vertices are set.
* The colour has to be set independently of this method.
*
* @param shape The shape to draw.
* @param fill The fill to apply
*/
public static final void draw(Shape shape, ShapeFill fill) {
float points[] = shape.getPoints();
Texture t = TextureImpl.getLastBind();
TextureImpl.bindNone();
float center[] = shape.getCenter();
GL.glBegin(SGL.GL_LINE_STRIP);
for(int i=0;i<points.length;i+=2) {
fill.colorAt(shape, points[i], points[i + 1]).bind();
Vector2f offset = fill.getOffsetAt(shape, points[i], points[i + 1]);
GL.glVertex2f(points[i] + offset.x, points[i + 1] + offset.y);
}
if (shape.closed()) {
fill.colorAt(shape, points[0], points[1]).bind();
Vector2f offset = fill.getOffsetAt(shape, points[0], points[1]);
GL.glVertex2f(points[0] + offset.x, points[1] + offset.y);
}
GL.glEnd();
if (t == null) {
TextureImpl.bindNone();
} else {
t.bind();
}
}
/**
* Check there are enough points to fill
*
* @param shape THe shape we're drawing
* @return True if the fill is valid
*/
public static boolean validFill(Shape shape) {
if (shape.getTriangles() == null) {
return false;
}
return shape.getTriangles().getTriangleCount() != 0;
}
/**
* Draw the the given shape filled in. Only the vertices are set.
* The colour has to be set independently of this method.
*
* @param shape The shape to fill.
*/
public static final void fill(Shape shape) {
if (!validFill(shape)) {
return;
}
Texture t = TextureImpl.getLastBind();
TextureImpl.bindNone();
fill(shape, new PointCallback() {
public float[] preRenderPoint(Shape shape, float x, float y) {
// do nothing, we're just filling the shape this time
return null;
}
});
if (t == null) {
TextureImpl.bindNone();
} else {
t.bind();
}
}
/**
* Draw the the given shape filled in. Only the vertices are set.
* The colour has to be set independently of this method.
*
* @param shape The shape to fill.
* @param callback The callback that will be invoked for each shape point
*/
private static final void fill(Shape shape, PointCallback callback) {
Triangulator tris = shape.getTriangles();
GL.glBegin(SGL.GL_TRIANGLES);
for (int i=0;i<tris.getTriangleCount();i++) {
for (int p=0;p<3;p++) {
float[] pt = tris.getTrianglePoint(i, p);
float[] np = callback.preRenderPoint(shape, pt[0],pt[1]);
if (np == null) {
GL.glVertex2f(pt[0],pt[1]);
} else {
GL.glVertex2f(np[0],np[1]);
}
}
}
GL.glEnd();
}
/**
* Draw the the given shape filled in with a texture. Only the vertices are set.
* The colour has to be set independently of this method.
*
* @param shape The shape to texture.
* @param image The image to tile across the shape
*/
public static final void texture(Shape shape, Image image) {
texture(shape, image, 0.01f, 0.01f);
}
/**
* Draw the the given shape filled in with a texture. Only the vertices are set.
* The colour has to be set independently of this method. This method is required to
* fit the texture once across the shape.
*
* @param shape The shape to texture.
* @param image The image to tile across the shape
*/
public static final void textureFit(Shape shape, Image image) {
textureFit(shape, image,1f,1f);
}
/**
* Draw the the given shape filled in with a texture. Only the vertices are set.
* The colour has to be set independently of this method.
*
* @param shape The shape to texture.
* @param image The image to tile across the shape
* @param scaleX The scale to apply on the x axis for texturing
* @param scaleY The scale to apply on the y axis for texturing
*/
public static final void texture(Shape shape, final Image image, final float scaleX, final float scaleY) {
if (!validFill(shape)) {
return;
}
final Texture t = TextureImpl.getLastBind();
image.getTexture().bind();
fill(shape, new PointCallback() {
public float[] preRenderPoint(Shape shape, float x, float y) {
float tx = x * scaleX;
float ty = y * scaleY;
tx = image.getTextureOffsetX() + (image.getTextureWidth() * tx);
ty = image.getTextureOffsetY() + (image.getTextureHeight() * ty);
GL.glTexCoord2f(tx, ty);
return null;
}
});
float points[] = shape.getPoints();
if (t == null) {
TextureImpl.bindNone();
} else {
t.bind();
}
}
/**
* Draw the the given shape filled in with a texture. Only the vertices are set.
* The colour has to be set independently of this method. This method is required to
* fit the texture scaleX times across the shape and scaleY times down the shape.
*
* @param shape The shape to texture.
* @param image The image to tile across the shape
* @param scaleX The scale to apply on the x axis for texturing
* @param scaleY The scale to apply on the y axis for texturing
*/
public static final void textureFit(Shape shape, final Image image, final float scaleX, final float scaleY) {
if (!validFill(shape)) {
return;
}
float points[] = shape.getPoints();
Texture t = TextureImpl.getLastBind();
image.getTexture().bind();
final float minX = shape.getX();
final float minY = shape.getY();
final float maxX = shape.getMaxX() - minX;
final float maxY = shape.getMaxY() - minY;
fill(shape, new PointCallback() {
public float[] preRenderPoint(Shape shape, float x, float y) {
x -= shape.getMinX();
y -= shape.getMinY();
x /= (shape.getMaxX() - shape.getMinX());
y /= (shape.getMaxY() - shape.getMinY());
float tx = x * scaleX;
float ty = y * scaleY;
tx = image.getTextureOffsetX() + (image.getTextureWidth() * tx);
ty = image.getTextureOffsetY() + (image.getTextureHeight() * ty);
GL.glTexCoord2f(tx, ty);
return null;
}
});
if (t == null) {
TextureImpl.bindNone();
} else {
t.bind();
}
}
/**
* Draw the the given shape filled in. Only the vertices are set.
* The colour has to be set independently of this method.
*
* @param shape The shape to fill.
* @param fill The fill to apply
*/
public static final void fill(final Shape shape, final ShapeFill fill) {
if (!validFill(shape)) {
return;
}
Texture t = TextureImpl.getLastBind();
TextureImpl.bindNone();
final float center[] = shape.getCenter();
fill(shape, new PointCallback() {
public float[] preRenderPoint(Shape shape, float x, float y) {
fill.colorAt(shape, x, y).bind();
Vector2f offset = fill.getOffsetAt(shape, x, y);
return new float[] {offset.x + x,offset.y + y};
}
});
if (t == null) {
TextureImpl.bindNone();
} else {
t.bind();
}
}
/**
* Draw the the given shape filled in with a texture. Only the vertices are set.
* The colour has to be set independently of this method.
*
* @param shape The shape to texture.
* @param image The image to tile across the shape
* @param scaleX The scale to apply on the x axis for texturing
* @param scaleY The scale to apply on the y axis for texturing
* @param fill The fill to apply
*/
public static final void texture(final Shape shape, final Image image, final float scaleX, final float scaleY, final ShapeFill fill) {
if (!validFill(shape)) {
return;
}
Texture t = TextureImpl.getLastBind();
image.getTexture().bind();
final float center[] = shape.getCenter();
fill(shape, new PointCallback() {
public float[] preRenderPoint(Shape shape, float x, float y) {
fill.colorAt(shape, x - center[0], y - center[1]).bind();
Vector2f offset = fill.getOffsetAt(shape, x, y);
x += offset.x;
y += offset.y;
float tx = x * scaleX;
float ty = y * scaleY;
tx = image.getTextureOffsetX() + (image.getTextureWidth() * tx);
ty = image.getTextureOffsetY() + (image.getTextureHeight() * ty);
GL.glTexCoord2f(tx, ty);
return new float[] {offset.x + x,offset.y + y};
}
});
if (t == null) {
TextureImpl.bindNone();
} else {
t.bind();
}
}
/**
* Draw the the given shape filled in with a texture. Only the vertices are set.
* The colour has to be set independently of this method.
*
* @param shape The shape to texture.
* @param image The image to tile across the shape
* @param gen The texture coordinate generator to create coordiantes for the shape
*/
public static final void texture(final Shape shape, Image image, final TexCoordGenerator gen) {
Texture t = TextureImpl.getLastBind();
image.getTexture().bind();
final float center[] = shape.getCenter();
fill(shape, new PointCallback() {
public float[] preRenderPoint(Shape shape, float x, float y) {
Vector2f tex = gen.getCoordFor(x, y);
GL.glTexCoord2f(tex.x, tex.y);
return new float[] {x,y};
}
});
if (t == null) {
TextureImpl.bindNone();
} else {
t.bind();
}
}
/**
* Description of some feature that will be applied to each point render
*
* @author kevin
*/
private static interface PointCallback {
/**
* Apply feature before the call to glVertex
*
* @param shape The shape the point belongs to
* @param x The x poisiton the vertex will be at
* @param y The y position the vertex will be at
* @return The new coordinates of null
*/
float[] preRenderPoint(Shape shape, float x, float y);
}
}

View File

@@ -0,0 +1,19 @@
package org.newdawn.slick.geom;
/**
* A class capable of generating texture coordiantes based on
* rendering positions of verticies. This allows custom texturing
* of geometric shapes
*
* @author kevin
*/
public interface TexCoordGenerator {
/**
* Get the texture coordinate for a given render position
*
* @param x The x coordinate of the vertex being rendered
* @param y The y coordinate of the vertex being rendered
* @return The texture coordinate to apply
*/
public Vector2f getCoordFor(float x, float y);
}

View File

@@ -0,0 +1,230 @@
package org.newdawn.slick.geom;
import org.newdawn.slick.util.FastTrig;
/**
* A 2 dimensional transformation that can be applied to <code>Shape</code> implemenations.
*
* @author Mark
*/
public class Transform {
/**
* Value for each position in the matrix
*
* |0 1 2|
* |3 4 5|
* |6 7 8|
*/
private float matrixPosition[];
/**
* Create and identity transform
*
*/
public Transform() {
matrixPosition = new float[]{1, 0, 0, 0, 1, 0, 0, 0, 1};
}
/**
* Copy a transform
*
* @param other The other transform to copy
*/
public Transform(Transform other) {
matrixPosition = new float[9];
for (int i=0;i<9;i++) {
matrixPosition[i] = other.matrixPosition[i];
}
}
/**
* Concatanate to transform into one
*
* @param t1 The first transform to join
* @param t2 The second transform to join
*/
public Transform(Transform t1, Transform t2) {
this(t1);
concatenate(t2);
}
/**
* Create a transform for the given positions
*
* @param matrixPosition An array of float[6] to set up a transform
* @throws RuntimeException if the array is not of length 6
*/
public Transform(float matrixPosition[]) {
if(matrixPosition.length != 6) {
throw new RuntimeException("The parameter must be a float array of length 6.");
}
this.matrixPosition = new float[]{matrixPosition[0], matrixPosition[1], matrixPosition[2],
matrixPosition[3], matrixPosition[4], matrixPosition[5],
0, 0, 1};
}
/**
* Create a transform for the given positions
*
* @param point00 float for the first position
* @param point01 float for the second position
* @param point02 float for the third position
* @param point10 float for the fourth position
* @param point11 float for the fifth position
* @param point12 float for the sixth position
*/
public Transform(float point00, float point01, float point02, float point10, float point11, float point12) {
matrixPosition = new float[]{point00, point01, point02, point10, point11, point12, 0, 0, 1};
}
/**
* Transform the point pairs in the source array and store them in the destination array.
* All operations will be done before storing the results in the destination. This way the source
* and destination array can be the same without worry of overwriting information before it is transformed.
*
* @param source Array of floats containing the points to be transformed
* @param sourceOffset Where in the array to start processing
* @param destination Array of floats to store the results.
* @param destOffset Where in the array to start storing
* @param numberOfPoints Number of points to be transformed
* @throws ArrayIndexOutOfBoundsException if sourceOffset + numberOfPoints * 2 > source.length or the same operation on the destination array
*/
public void transform(float source[], int sourceOffset, float destination[], int destOffset, int numberOfPoints) {
//TODO performance can be improved by removing the safety to the destination array
float result[] = source == destination ? new float[numberOfPoints * 2] : destination;
for(int i=0;i<numberOfPoints * 2;i+=2) {
for(int j=0;j<6;j+=3) {
result[i + (j / 3)] = source[i + sourceOffset] * matrixPosition[j] + source[i + sourceOffset + 1] * matrixPosition[j + 1] + 1 * matrixPosition[j + 2];
}
}
if (source == destination) {
//for safety of the destination, the results are copied after the entire operation.
for(int i=0;i<numberOfPoints * 2;i+=2) {
destination[i + destOffset] = result[i];
destination[i + destOffset + 1] = result[i + 1];
}
}
}
/**
* Update this Transform by concatenating the given Transform to this one.
*
* @param tx The Transfrom to concatenate to this one.
* @return The resulting Transform
*/
public Transform concatenate(Transform tx) {
float[] mp = new float[9];
float n00 = matrixPosition[0] * tx.matrixPosition[0] + matrixPosition[1] * tx.matrixPosition[3];
float n01 = matrixPosition[0] * tx.matrixPosition[1] + matrixPosition[1] * tx.matrixPosition[4];
float n02 = matrixPosition[0] * tx.matrixPosition[2] + matrixPosition[1] * tx.matrixPosition[5] + matrixPosition[2];
float n10 = matrixPosition[3] * tx.matrixPosition[0] + matrixPosition[4] * tx.matrixPosition[3];
float n11 = matrixPosition[3] * tx.matrixPosition[1] + matrixPosition[4] * tx.matrixPosition[4];
float n12 = matrixPosition[3] * tx.matrixPosition[2] + matrixPosition[4] * tx.matrixPosition[5] + matrixPosition[5];
mp[0] = n00;
mp[1] = n01;
mp[2] = n02;
mp[3] = n10;
mp[4] = n11;
mp[5] = n12;
//
// mp[0] = matrixPosition[0] * transform.matrixPosition[0] + matrixPosition[0] * transform.matrixPosition[3] + matrixPosition[0] * transform.matrixPosition[6];
// mp[1] = matrixPosition[1] * transform.matrixPosition[1] + matrixPosition[1] * transform.matrixPosition[4] + matrixPosition[1] * transform.matrixPosition[7];
// mp[2] = matrixPosition[2] * transform.matrixPosition[2] + matrixPosition[2] * transform.matrixPosition[5] + matrixPosition[2] * transform.matrixPosition[8];
// mp[3] = matrixPosition[3] * transform.matrixPosition[0] + matrixPosition[3] * transform.matrixPosition[3] + matrixPosition[3] * transform.matrixPosition[6];
// mp[4] = matrixPosition[4] * transform.matrixPosition[1] + matrixPosition[4] * transform.matrixPosition[4] + matrixPosition[4] * transform.matrixPosition[7];
// mp[5] = matrixPosition[5] * transform.matrixPosition[2] + matrixPosition[5] * transform.matrixPosition[5] + matrixPosition[5] * transform.matrixPosition[8];
//
matrixPosition = mp;
return this;
}
/**
* Convert this Transform to a String.
*
* @return This Transform in human readable format.
*/
public String toString() {
String result = "Transform[[" + matrixPosition[0] + "," + matrixPosition[1] + "," + matrixPosition[2] +
"][" + matrixPosition[3] + "," + matrixPosition[4] + "," + matrixPosition[5] +
"][" + matrixPosition[6] + "," + matrixPosition[7] + "," + matrixPosition[8] + "]]";
return result.toString();
}
/**
* Get an array representing this Transform.
*
* @return an array representing this Transform.
*/
public float[] getMatrixPosition() {
return matrixPosition;
}
/**
* Create a new rotation Transform
*
* @param angle The angle in radians to set the transform.
* @return The resulting Transform
*/
public static Transform createRotateTransform(float angle) {
return new Transform((float)FastTrig.cos(angle), -(float)FastTrig.sin(angle), 0, (float)FastTrig.sin(angle), (float)FastTrig.cos(angle), 0);
}
/**
* Create a new rotation Transform around the specified point
*
* @param angle The angle in radians to set the transform.
* @param x The x coordinate around which to rotate.
* @param y The y coordinate around which to rotate.
* @return The resulting Transform
*/
public static Transform createRotateTransform(float angle, float x, float y) {
Transform temp = Transform.createRotateTransform(angle);
float sinAngle = temp.matrixPosition[3];
float oneMinusCosAngle = 1.0f - temp.matrixPosition[4];
temp.matrixPosition[2] = x * oneMinusCosAngle + y * sinAngle;
temp.matrixPosition[5] = y * oneMinusCosAngle - x * sinAngle;
return temp;
}
/**
* Create a new translation Transform
*
* @param xOffset The amount to move in the x direction
* @param yOffset The amount to move in the y direction
* @return The resulting Transform
*/
public static Transform createTranslateTransform(float xOffset, float yOffset) {
return new Transform(1, 0, xOffset, 0, 1, yOffset);
}
/**
* Create an new scaling Transform
*
* @param xScale The amount to scale in the x coordinate
* @param yScale The amount to scale in the x coordinate
* @return The resulting Transform
*/
public static Transform createScaleTransform(float xScale, float yScale) {
return new Transform(xScale, 0, 0, 0, yScale, 0);
}
/**
* Transform the vector2f based on the matrix defined in this transform
*
* @param pt The point to be transformed
* @return The resulting point transformed by this matrix
*/
public Vector2f transform(Vector2f pt) {
float[] in = new float[] {pt.x, pt.y};
float[] out = new float[2];
transform(in, 0, out, 0, 1);
return new Vector2f(out[0], out[1]);
}
}

View File

@@ -0,0 +1,48 @@
package org.newdawn.slick.geom;
import java.io.Serializable;
/**
* A collection of triangles
*
* @author kevin
*/
public interface Triangulator extends Serializable {
/**
* Get a count of the number of triangles produced
*
* @return The number of triangles produced
*/
public int getTriangleCount();
/**
* Get a point on a specified generated triangle
*
* @param tri The index of the triangle to interegate
* @param i The index of the point within the triangle to retrieve
* (0 - 2)
* @return The x,y coordinate pair for the point
*/
public float[] getTrianglePoint(int tri, int i);
/**
* Add a point that forms part of the outer polygon
*
* @param x The x coordinate of the point
* @param y The y coordiante of the point
*/
public void addPolyPoint(float x, float y);
/**
* Start a hole in the polygon
*/
public void startHole();
/**
* Run the triangulation
*
* @return True if successful
*/
public boolean triangulate();
}

View File

@@ -0,0 +1,396 @@
package org.newdawn.slick.geom;
import java.io.Serializable;
import org.newdawn.slick.util.FastTrig;
/**
* A two dimensional vector
*
* @author Kevin Glass
*/
public strictfp class Vector2f implements Serializable {
/** The version ID for this class */
private static final long serialVersionUID = 1339934L;
/** The x component of this vector */
public float x;
/** The y component of this vector */
public float y;
/**
* Create an empty vector
*/
public Vector2f() {
}
/**
* Create a vector based on the contents of a coordinate array
*
* @param coords The coordinates array, index 0 = x, index 1 = y
*/
public Vector2f(float[] coords) {
x = coords[0];
y = coords[1];
}
/**
* Create a new vector based on an angle
*
* @param theta The angle of the vector in degrees
*/
public Vector2f(double theta) {
x = 1;
y = 0;
setTheta(theta);
}
/**
* Calculate the components of the vectors based on a angle
*
* @param theta The angle to calculate the components from (in degrees)
*/
public void setTheta(double theta) {
// Next lines are to prevent numbers like -1.8369701E-16
// when working with negative numbers
if ((theta < -360) || (theta > 360)) {
theta = theta % 360;
}
if (theta < 0) {
theta = 360 + theta;
}
double oldTheta = getTheta();
if ((theta < -360) || (theta > 360)) {
oldTheta = oldTheta % 360;
}
if (theta < 0) {
oldTheta = 360 + oldTheta;
}
float len = length();
x = len * (float) FastTrig.cos(StrictMath.toRadians(theta));
y = len * (float) FastTrig.sin(StrictMath.toRadians(theta));
// x = x / (float) FastTrig.cos(StrictMath.toRadians(oldTheta))
// * (float) FastTrig.cos(StrictMath.toRadians(theta));
// y = x / (float) FastTrig.sin(StrictMath.toRadians(oldTheta))
// * (float) FastTrig.sin(StrictMath.toRadians(theta));
}
/**
* Adjust this vector by a given angle
*
* @param theta
* The angle to adjust the angle by (in degrees)
* @return This vector - useful for chaining operations
*
*/
public Vector2f add(double theta) {
setTheta(getTheta() + theta);
return this;
}
/**
* Adjust this vector by a given angle
*
* @param theta The angle to adjust the angle by (in degrees)
* @return This vector - useful for chaining operations
*/
public Vector2f sub(double theta) {
setTheta(getTheta() - theta);
return this;
}
/**
* Get the angle this vector is at
*
* @return The angle this vector is at (in degrees)
*/
public double getTheta() {
double theta = StrictMath.toDegrees(StrictMath.atan2(y, x));
if ((theta < -360) || (theta > 360)) {
theta = theta % 360;
}
if (theta < 0) {
theta = 360 + theta;
}
return theta;
}
/**
* Get the x component
*
* @return The x component
*/
public float getX() {
return x;
}
/**
* Get the y component
*
* @return The y component
*/
public float getY() {
return y;
}
/**
* Create a new vector based on another
*
* @param other The other vector to copy into this one
*/
public Vector2f(Vector2f other) {
this(other.getX(),other.getY());
}
/**
* Create a new vector
*
* @param x The x component to assign
* @param y The y component to assign
*/
public Vector2f(float x, float y) {
this.x = x;
this.y = y;
}
/**
* Set the value of this vector
*
* @param other The values to set into the vector
*/
public void set(Vector2f other) {
set(other.getX(),other.getY());
}
/**
* Dot this vector against another
*
* @param other The other vector dot agianst
* @return The dot product of the two vectors
*/
public float dot(Vector2f other) {
return (x * other.getX()) + (y * other.getY());
}
/**
* Set the values in this vector
*
* @param x The x component to set
* @param y The y component to set
* @return This vector - useful for chaining operations
*/
public Vector2f set(float x, float y) {
this.x = x;
this.y = y;
return this;
}
/**
* A vector perpendicular to this vector.
*
* @return a vector perpendicular to this vector
*/
public Vector2f getPerpendicular() {
return new Vector2f(-y, x);
}
/**
* Set the values in this vector
*
* @param pt The pair of values to set into the vector
* @return This vector - useful for chaining operations
*/
public Vector2f set(float[] pt) {
return set(pt[0], pt[1]);
}
/**
* Negate this vector
*
* @return A copy of this vector negated
*/
public Vector2f negate() {
return new Vector2f(-x, -y);
}
/**
* Negate this vector without creating a new copy
*
* @return This vector - useful for chaning operations
*/
public Vector2f negateLocal() {
x = -x;
y = -y;
return this;
}
/**
* Add a vector to this vector
*
* @param v The vector to add
* @return This vector - useful for chaning operations
*/
public Vector2f add(Vector2f v)
{
x += v.getX();
y += v.getY();
return this;
}
/**
* Subtract a vector from this vector
*
* @param v The vector subtract
* @return This vector - useful for chaining operations
*/
public Vector2f sub(Vector2f v)
{
x -= v.getX();
y -= v.getY();
return this;
}
/**
* Scale this vector by a value
*
* @param a The value to scale this vector by
* @return This vector - useful for chaining operations
*/
public Vector2f scale(float a)
{
x *= a;
y *= a;
return this;
}
/**
* Normalise the vector
*
* @return This vector - useful for chaning operations
*/
public Vector2f normalise() {
float l = length();
if (l == 0) {
return this;
}
x /= l;
y /= l;
return this;
}
/**
* The normal of the vector
*
* @return A unit vector with the same direction as the vector
*/
public Vector2f getNormal() {
Vector2f cp = copy();
cp.normalise();
return cp;
}
/**
* The length of the vector squared
*
* @return The length of the vector squared
*/
public float lengthSquared() {
return (x * x) + (y * y);
}
/**
* Get the length of this vector
*
* @return The length of this vector
*/
public float length()
{
return (float) Math.sqrt(lengthSquared());
}
/**
* Project this vector onto another
*
* @param b The vector to project onto
* @param result The projected vector
*/
public void projectOntoUnit(Vector2f b, Vector2f result) {
float dp = b.dot(this);
result.x = dp * b.getX();
result.y = dp * b.getY();
}
/**
* Return a copy of this vector
*
* @return The new instance that copies this vector
*/
public Vector2f copy() {
return new Vector2f(x,y);
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "[Vector2f "+x+","+y+" ("+length()+")]";
}
/**
* Get the distance from this point to another
*
* @param other The other point we're measuring to
* @return The distance to the other point
*/
public float distance(Vector2f other) {
return (float) Math.sqrt(distanceSquared(other));
}
/**
* Get the distance from this point to another, squared. This
* can sometimes be used in place of distance and avoids the
* additional sqrt.
*
* @param other The other point we're measuring to
* @return The distance to the other point squared
*/
public float distanceSquared(Vector2f other) {
float dx = other.getX() - getX();
float dy = other.getY() - getY();
return (float) (dx*dx)+(dy*dy);
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return 997 * ((int)x) ^ 991 * ((int)y); //large primes!
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object other) {
if (other instanceof Vector2f) {
Vector2f o = ((Vector2f) other);
return (o.x == x) && (o.y == y);
}
return false;
}
}

View File

@@ -0,0 +1,3 @@
<BODY>
Simple geometric wrappers that can be used for rendering and collision.
</BODY>