Path: blob/master/SLICK_HOME/src/org/newdawn/slick/geom/Shape.java
1461 views
package org.newdawn.slick.geom;12import java.io.Serializable;34/**5* The description of any 2D shape that can be transformed. The points provided approximate the intent6* of the shape.7*8* @author Mark9*/10public abstract class Shape implements Serializable {11/** The points representing this polygon. */12protected float points[];13/** Center point of the polygon. */14protected float center[];15/** The left most point of this shape. */16protected float x;17/** The top most point of this shape. */18protected float y;19/** The right most point of this shape */20protected float maxX;21/** The bottom most point of this shape */22protected float maxY;23/** The left most point of this shape. */24protected float minX;25/** The top most point of this shape. */26protected float minY;27/** Radius of a circle that can completely enclose this shape. */28protected float boundingCircleRadius;29/** Flag to tell whether points need to be generated */30protected boolean pointsDirty;31/** The triangles that define the shape */32protected Triangulator tris;33/** True if the triangles need updating */34protected boolean trianglesDirty;3536/**37* Shape constructor.38*39*/40public Shape() {41pointsDirty = true;42}4344/**45* Set the location of this shape46*47* @param x The x coordinate of the new location of the shape48* @param y The y coordinate of the new location of the shape49*/50public void setLocation(float x, float y) {51setX(x);52setY(y);53}5455/**56* Apply a transformation and return a new shape. This will not alter the current shape but will57* return the transformed shape.58*59* @param transform The transform to be applied60* @return The transformed shape.61*/62public abstract Shape transform(Transform transform);6364/**65* Subclasses implement this to create the points of the shape.66*67*/68protected abstract void createPoints();6970/**71* Get the x location of the left side of this shape.72*73* @return The x location of the left side of this shape.74*/75public float getX() {76return x;77}7879/**80* Set the x position of the left side this shape.81*82* @param x The new x position of the left side this shape.83*/84public void setX(float x) {85if (x != this.x) {86float dx = x - this.x;87this.x = x;8889if ((points == null) || (center == null)) {90checkPoints();91}92// update the points in the special case93for (int i=0;i<points.length/2;i++) {94points[i*2] += dx;95}96center[0] += dx;97x += dx;98maxX += dx;99minX += dx;100trianglesDirty = true;101}102}103104/**105* Set the y position of the top of this shape.106*107* @param y The new y position of the top of this shape.108*/109public void setY(float y) {110if (y != this.y) {111float dy = y - this.y;112this.y = y;113114if ((points == null) || (center == null)) {115checkPoints();116}117// update the points in the special case118for (int i=0;i<points.length/2;i++) {119points[(i*2)+1] += dy;120}121center[1] += dy;122y += dy;123maxY += dy;124minY += dy;125trianglesDirty = true;126}127}128129/**130* Get the y position of the top of this shape.131*132* @return The y position of the top of this shape.133*/134public float getY() {135return y;136}137138/**139* Set the location of this shape140*141* @param loc The new location of the shape142*/143public void setLocation(Vector2f loc) {144setX(loc.x);145setY(loc.y);146}147148/**149* Get the x center of this shape.150*151* @return The x center of this shape.152*/153public float getCenterX() {154checkPoints();155156return center[0];157}158159/**160* Set the x center of this shape.161*162* @param centerX The center point to set.163*/164public void setCenterX(float centerX) {165if ((points == null) || (center == null)) {166checkPoints();167}168169float xDiff = centerX - getCenterX();170setX(x + xDiff);171}172173/**174* Get the y center of this shape.175*176* @return The y center of this shape.177*/178public float getCenterY() {179checkPoints();180181return center[1];182}183184/**185* Set the y center of this shape.186*187* @param centerY The center point to set.188*/189public void setCenterY(float centerY) {190if ((points == null) || (center == null)) {191checkPoints();192}193194float yDiff = centerY - getCenterY();195setY(y + yDiff);196}197198/**199* Get the right most point of this shape.200*201* @return The right most point of this shape.202*/203public float getMaxX() {204checkPoints();205return maxX;206}207/**208* Get the bottom most point of this shape.209*210* @return The bottom most point of this shape.211*/212public float getMaxY() {213checkPoints();214return maxY;215}216217/**218* Get the left most point of this shape.219*220* @return The left most point of this shape.221*/222public float getMinX() {223checkPoints();224return minX;225}226227/**228* Get the top most point of this shape.229*230* @return The top most point of this shape.231*/232public float getMinY() {233checkPoints();234return minY;235}236237/**238* Get the radius of a circle that can completely enclose this shape.239*240* @return The radius of the circle.241*/242public float getBoundingCircleRadius() {243checkPoints();244return boundingCircleRadius;245}246247/**248* Get the point closet to the center of all the points in this Shape249*250* @return The x,y coordinates of the center.251*/252public float[] getCenter() {253checkPoints();254return center;255}256257/**258* Get the points that outline this shape. Use CW winding rule259*260* @return an array of x,y points261*/262public float[] getPoints() {263checkPoints();264return points;265}266267/**268* Get the number of points in this polygon269*270* @return The number of points in this polygon271*/272public int getPointCount() {273checkPoints();274return points.length / 2;275}276277/**278* Get a single point in this polygon279*280* @param index The index of the point to retrieve281* @return The point's coordinates282*/283public float[] getPoint(int index) {284checkPoints();285286float result[] = new float[2];287288result[0] = points[index * 2];289result[1] = points[index * 2 + 1];290291return result;292}293294/**295* Get the combine normal of a given point296*297* @param index The index of the point whose normal should be retrieved298* @return The combined normal of a given point299*/300public float[] getNormal(int index) {301float[] current = getPoint(index);302float[] prev = getPoint(index - 1 < 0 ? getPointCount() - 1 : index - 1);303float[] next = getPoint(index + 1 >= getPointCount() ? 0 : index + 1);304305float[] t1 = getNormal(prev, current);306float[] t2 = getNormal(current, next);307308if ((index == 0) && (!closed())) {309return t2;310}311if ((index == getPointCount()-1) && (!closed())) {312return t1;313}314315float tx = (t1[0]+t2[0])/2;316float ty = (t1[1]+t2[1])/2;317float len = (float) Math.sqrt((tx*tx)+(ty*ty));318return new float[] {tx/len,ty/len};319}320321/**322* Check if the shape passed is entirely contained within323* this shape.324*325* @param other The other shape to test against this one326* @return True if the other shape supplied is entirely contained327* within this one.328*/329public boolean contains(Shape other) {330if (other.intersects(this)) {331return false;332}333334for (int i=0;i<other.getPointCount();i++) {335float[] pt = other.getPoint(i);336if (!contains(pt[0], pt[1])) {337return false;338}339}340341return true;342}343/**344* Get the normal of the line between two points345*346* @param start The start point347* @param end The end point348* @return The normal of the line between the two points349*/350private float[] getNormal(float[] start, float[] end) {351float dx = start[0] - end[0];352float dy = start[1] - end[1];353float len = (float) Math.sqrt((dx*dx)+(dy*dy));354dx /= len;355dy /= len;356return new float[] {-dy,dx};357}358359/**360* Check if the given point is part of the path that361* forms this shape362*363* @param x The x position of the point to check364* @param y The y position of the point to check365* @return True if the point is includes in the path of the polygon366*/367public boolean includes(float x, float y) {368if (points.length == 0) {369return false;370}371372checkPoints();373374Line testLine = new Line(0,0,0,0);375Vector2f pt = new Vector2f(x,y);376377for (int i=0;i<points.length;i+=2) {378int n = i+2;379if (n >= points.length) {380n = 0;381}382testLine.set(points[i], points[i+1], points[n], points[n+1]);383384if (testLine.on(pt)) {385return true;386}387}388389return false;390}391392/**393* Get the index of a given point394*395* @param x The x coordinate of the point396* @param y The y coordinate of the point397* @return The index of the point or -1 if the point is not part of this shape path398*/399public int indexOf(float x, float y) {400for (int i=0;i<points.length;i+=2) {401if ((points[i] == x) && (points[i+1] == y)) {402return i / 2;403}404}405406return -1;407}408409/**410* Check if this polygon contains the given point411*412* @param x The x position of the point to check413* @param y The y position of the point to check414* @return True if the point is contained in the polygon415*/416public boolean contains(float x, float y) {417checkPoints();418if (points.length == 0) {419return false;420}421422boolean result = false;423float xnew,ynew;424float xold,yold;425float x1,y1;426float x2,y2;427int npoints = points.length;428429xold=points[npoints - 2];430yold=points[npoints - 1];431for (int i=0;i < npoints;i+=2) {432xnew = points[i];433ynew = points[i + 1];434if (xnew > xold) {435x1 = xold;436x2 = xnew;437y1 = yold;438y2 = ynew;439}440else {441x1 = xnew;442x2 = xold;443y1 = ynew;444y2 = yold;445}446if ((xnew < x) == (x <= xold) /* edge "open" at one end */447&& ((double)y - (double)y1) * (x2 - x1)448< ((double)y2 - (double)y1) * (x - x1)) {449result = !result;450}451xold = xnew;452yold = ynew;453}454455return result;456}457458/**459* Check if this shape intersects with the shape provided.460*461* @param shape The shape to check if it intersects with this one.462* @return True if the shapes do intersect, false otherwise.463*/464public boolean intersects(Shape shape) {465/*466* Intersection formula used:467* (x4 - x3)(y1 - y3) - (y4 - y3)(x1 - x3)468* UA = ---------------------------------------469* (y4 - y3)(x2 - x1) - (x4 - x3)(y2 - y1)470*471* (x2 - x1)(y1 - y3) - (y2 - y1)(x1 - x3)472* UB = ---------------------------------------473* (y4 - y3)(x2 - x1) - (x4 - x3)(y2 - y1)474*475* if UA and UB are both between 0 and 1 then the lines intersect.476*477* Source: http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/478*/479checkPoints();480481boolean result = false;482float points[] = getPoints(); // (x3, y3) and (x4, y4)483float thatPoints[] = shape.getPoints(); // (x1, y1) and (x2, y2)484int length = points.length;485int thatLength = thatPoints.length;486double unknownA;487double unknownB;488489if (!closed()) {490length -= 2;491}492if (!shape.closed()) {493thatLength -= 2;494}495496// x1 = thatPoints[j]497// x2 = thatPoints[j + 2]498// y1 = thatPoints[j + 1]499// y2 = thatPoints[j + 3]500// x3 = points[i]501// x4 = points[i + 2]502// y3 = points[i + 1]503// y4 = points[i + 3]504for(int i=0;i<length;i+=2) {505int iNext = i+2;506if (iNext >= points.length) {507iNext = 0;508}509510for(int j=0;j<thatLength;j+=2) {511int jNext = j+2;512if (jNext >= thatPoints.length) {513jNext = 0;514}515516unknownA = (((points[iNext] - points[i]) * (double) (thatPoints[j + 1] - points[i + 1])) -517((points[iNext+1] - points[i + 1]) * (thatPoints[j] - points[i]))) /518(((points[iNext+1] - points[i + 1]) * (thatPoints[jNext] - thatPoints[j])) -519((points[iNext] - points[i]) * (thatPoints[jNext+1] - thatPoints[j + 1])));520unknownB = (((thatPoints[jNext] - thatPoints[j]) * (double) (thatPoints[j + 1] - points[i + 1])) -521((thatPoints[jNext+1] - thatPoints[j + 1]) * (thatPoints[j] - points[i]))) /522(((points[iNext+1] - points[i + 1]) * (thatPoints[jNext] - thatPoints[j])) -523((points[iNext] - points[i]) * (thatPoints[jNext+1] - thatPoints[j + 1])));524525if(unknownA >= 0 && unknownA <= 1 && unknownB >= 0 && unknownB <= 1) {526result = true;527break;528}529}530if(result) {531break;532}533}534535return result;536}537538/**539* Check if a particular location is a vertex of this polygon540*541* @param x The x coordinate to check542* @param y The y coordinate to check543* @return True if the cordinates supplied are a vertex of this polygon544*/545public boolean hasVertex(float x, float y) {546if (points.length == 0) {547return false;548}549550checkPoints();551552for (int i=0;i<points.length;i+=2) {553if ((points[i] == x) && (points[i+1] == y)) {554return true;555}556}557558return false;559}560561/**562* Get the center of this polygon.563*564*/565protected void findCenter() {566center = new float[]{0, 0};567int length = points.length;568for(int i=0;i<length;i+=2) {569center[0] += points[i];570center[1] += points[i + 1];571}572center[0] /= (length / 2);573center[1] /= (length / 2);574}575576/**577* Calculate the radius of a circle that can completely enclose this shape.578*579*/580protected void calculateRadius() {581boundingCircleRadius = 0;582583for(int i=0;i<points.length;i+=2) {584float temp = ((points[i] - center[0]) * (points[i] - center[0])) +585((points[i + 1] - center[1]) * (points[i + 1] - center[1]));586boundingCircleRadius = (boundingCircleRadius > temp) ? boundingCircleRadius : temp;587}588boundingCircleRadius = (float)Math.sqrt(boundingCircleRadius);589}590591/**592* Calculate the triangles that can fill this shape593*/594protected void calculateTriangles() {595if (!trianglesDirty) {596return;597}598if (points.length >= 6) {599boolean clockwise = true;600float area = 0;601for (int i=0;i<(points.length/2)-1;i++) {602float x1 = points[(i*2)];603float y1 = points[(i*2)+1];604float x2 = points[(i*2)+2];605float y2 = points[(i*2)+3];606607area += (x1 * y2) - (y1 * x2);608}609area /= 2;610clockwise = area > 0;611612tris = new NeatTriangulator();613for (int i=0;i<points.length;i+=2) {614tris.addPolyPoint(points[i], points[i+1]);615}616tris.triangulate();617}618619trianglesDirty = false;620}621622/**623* Increase triangulation624*/625public void increaseTriangulation() {626checkPoints();627calculateTriangles();628629tris = new OverTriangulator(tris);630}631632/**633* The triangles that define the filled version of this shape634*635* @return The triangles that define the636*/637public Triangulator getTriangles() {638checkPoints();639calculateTriangles();640return tris;641}642643/**644* Check the dirty flag and create points as necessary.645*/646protected final void checkPoints() {647if (pointsDirty) {648createPoints();649findCenter();650calculateRadius();651652maxX = points[0];653maxY = points[1];654minX = points[0];655minY = points[1];656for (int i=0;i<points.length/2;i++) {657maxX = Math.max(points[i*2],maxX);658maxY = Math.max(points[(i*2)+1],maxY);659minX = Math.min(points[i*2],minX);660minY = Math.min(points[(i*2)+1],minY);661}662663pointsDirty = false;664trianglesDirty = true;665}666}667668/**669* Cause all internal state to be generated and cached670*/671public void preCache() {672checkPoints();673getTriangles();674}675676/**677* True if this is a closed shape678*679* @return True if this is a closed shape680*/681public boolean closed() {682return true;683}684685/**686* Prune any required points in this shape687*688* @return The new shape with points pruned689*/690public Shape prune() {691Polygon result = new Polygon();692693for (int i=0;i<getPointCount();i++) {694int next = i+1 >= getPointCount() ? 0 : i+1;695int prev = i-1 < 0 ? getPointCount() - 1 : i-1;696697float dx1 = getPoint(i)[0] - getPoint(prev)[0];698float dy1 = getPoint(i)[1] - getPoint(prev)[1];699float dx2 = getPoint(next)[0] - getPoint(i)[0];700float dy2 = getPoint(next)[1] - getPoint(i)[1];701702float len1 = (float) Math.sqrt((dx1*dx1) + (dy1*dy1));703float len2 = (float) Math.sqrt((dx2*dx2) + (dy2*dy2));704dx1 /= len1;705dy1 /= len1;706dx2 /= len2;707dy2 /= len2;708709if ((dx1 != dx2) || (dy1 != dy2)) {710result.addPoint(getPoint(i)[0],getPoint(i)[1]);711}712}713714return result;715}716717/**718* Subtract the given shape from this one. Note that this method only deals719* with edges, it will not create holes in polygons.720*721* @param other The other shape to subtract from this one722* @return The newly created set of shapes resulting from the operation723*/724public Shape[] subtract(Shape other) {725return new GeomUtil().subtract(this, other);726}727728/**729* Join this shape with another.730*731* @param other The other shape to join with this one732* @return The newly created set of shapes resulting from the operation733*/734public Shape[] union(Shape other) {735return new GeomUtil().union(this, other);736}737738/**739* Get the width of the shape740*741* @return The width of the shape742*/743public float getWidth() {744return maxX - minX;745}746747748/**749* Get the height of the shape750*751* @return The height of the shape752*/753public float getHeight() {754return maxY - minY;755}756}757758759