package dwarf; import java.awt.Polygon; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; import dwarf.gfx.Circle; import dwarf.gfx.Colour; import dwarf.util.Point2D; import dwarf.engine.core.Window; import static dwarf.mouse.MOUSE_LEFT; /** * A wrapper around the values needed for a malleable 2D polygon collision * class. * * <p> * will detect - but not resolve - collisions. It uses an efficient data search * structure to quickly find intersecting <code>Collidable</code> as well as * offering general utilities to the <code>Collidable</code>. Cant not have more * than 32767 vertices. * </p> * * @author Matthew 'siD' Van der Bijl * * @see java.lang.Object * @see java.lang.Cloneable * @see java.io.Serializable * * @see dwarf.Child */ @SuppressWarnings("serial") public strictfp class Collidable extends java.lang.Object implements Cloneable, Serializable, Child { private Parent parent; private Point2D position; private ArrayList<Point2D> vertices; /** * Default constructor. */ public Collidable() { super(); } /** * @param position the position of the <code>Collidable</code> */ public Collidable(Point2D position) { super(); this.position = position; this.vertices = new ArrayList<>(); } /** * Construct a new <code>Collidable</code> 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 vertices An array of <code>Points2D</code> in x, y order. */ public Collidable(Point2D[] vertices) throws IllegalArgumentException { super(); this.setVertices(vertices); } /** * Construct a new <code>Collidable</code> 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 position the position of the <code>Collidable</code> * @param vertices An array of <code>Points2D</code> in x, y order. */ public Collidable(Point2D position, Point2D[] vertices) { super(); this.position = position; this.vertices = new ArrayList<>(); this.vertices.addAll(Arrays.asList(vertices)); } /** * Construct a new <code>Collidable</code> 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 xPos the X position of the <code>Collidable</code> * @param yPos the Y position of the <code>Collidable</code> * @param vertices An array of <code>Points2D</code> in x, y order. */ public Collidable(int xPos, int yPos, Point2D[] vertices) { this(new Point2D(xPos, yPos), vertices); } public Collidable(double[] xPoints, double[] yPoints) throws IllegalArgumentException { super(); this.position = new Point2D(0, 0); this.setVertices(xPoints, yPoints); } public Collidable(int[] xPoints, int[] yPoints) { super(); this.position = new Point2D(0, 0); this.setVertices(xPoints, yPoints); } public Collidable(Point2D position, double[] xPoints, double[] yPoints) { super(); this.position = new Point2D(0, 0); this.setVertices(xPoints, yPoints); } /** * @see java.awt.Polygon */ public Collidable(Polygon p) { this(p.xpoints, p.ypoints); } /** * creates a new <code>Collidable</code> from a inputed * <code>Collidable</code>. * * @param Collidable the inputed <code>Collidable</code>. */ public Collidable(Collidable Collidable) { this(Collidable.getPosition(), Collidable.getVertices()); } /** * @return all vertices as a <code>Point2D</code> array of the * <code>Collidable</code>. */ public Point2D[] getVertices() { Point2D[] points = new Point2D[vertices.size()]; for (short i = 0; i < vertices.size(); i++) { points[i] = new Point2D(vertices.get(i)); } return points; } public void addPoint(Point2D point) { this.vertices.add(point); } public void addPoint(double xPos, double yPos) { this.addPoint(new Point2D(xPos, yPos)); } public void addPoints(Point2D[] points) { for (Point2D point : points) { this.addPoint(point.getX(), point.getY()); } } public Collidable get() { return this; } public void set(Collidable coll) { this.setVertices(coll.getVertices()); this.setPosition(coll.getPosition()); } /** * creates a new <code>Collidable</code> with the arrays given. This method * is called from within the constructor to initialize the * <code>Collidable</code>. <b>WARNING: Do NOT modify this code.</b> * * @param vertices an array of the <code>Point2D</code> coordinates of the * <code>Collidable</code> */ public void setVertices(Point2D[] vertices) { double[] xPoints = new double[vertices.length]; double[] yPoints = new double[vertices.length]; for (short i = 0; i < vertices.length; i++) { xPoints[i] = vertices[i].getX(); yPoints[i] = vertices[i].getY(); } this.setVertices(xPoints, yPoints); } public void setVertices(Polygon p) { double[] xpoints = new double[p.npoints]; double[] ypoints = new double[p.npoints]; for (int i = 0; i < p.npoints; i++) { xpoints[i] = p.xpoints[i]; ypoints[i] = p.ypoints[i]; } this.setVertices(xpoints, ypoints); } /** * creates a new <code>Collidable</code> with the arrays given. This method * is called from within the constructor to initialize the * <code>Collidable</code>. <b>WARNING: Do NOT modify this code.</b> * * @throws IllegalArgumentException if the the number of X points does not * equal the number of Y points or if they contain less than 3 points. * * @param xPoints an array of the x coordinates of the * <code>Collidable</code> * @param yPoints an array of the y coordinates of the * <code>Collidable</code> */ public void setVertices(int[] xPoints, int[] yPoints) throws IllegalArgumentException { double[] xpoints = new double[xPoints.length]; double[] ypoints = new double[yPoints.length]; for (int i = 0; i < xPoints.length; i++) { xpoints[i] = xPoints[i]; } for (int i = 0; i < yPoints.length; i++) { ypoints[i] = yPoints[i]; } this.setVertices(xpoints, ypoints); } /** * Returns the total number of points. The value of * <code>vertices.size()</code> represents the number of valid points in * this <code>Collidable</code> and might be less than the number of * elements in vertices. This value can be NULL. * * @return the total number of points in the vertices * <code>ArrayList.</code> */ public int getNumVertices() { return this.vertices.size(); } /** * creates a new <code>Collidable</code> with the arrays given.This method * is called from within the constructor to initialize the * <code>Collidable</code>. <b>WARNING: Do NOT modify this code.</b> * * @throws IllegalArgumentException if the the number of X points does not * equal the number of Y points or if they contain less than 3 points * * @param xPoints an array of the X coordinates of the polygon * @param yPoints an array of the Y coordinates of the polygon */ public void setVertices(double[] xPoints, double[] yPoints) throws IllegalArgumentException { // if (x == null || y == null) { // throw new NullPointerException( // "Polygon requires non-null x and y coordinates"); // } else if (xPoints.length < 3) { throw new IllegalArgumentException( "Polygon requires at least 3 x values. Found " + xPoints.length); } else if (yPoints.length < 3) { throw new IllegalArgumentException( "Polygon requires at least 3 y values. Found " + yPoints.length); } else if (xPoints.length != yPoints.length) { throw new IllegalArgumentException( "Polygon requires the same amount of x and y values. Found " + xPoints.length + "," + yPoints.length); } else { ArrayList<Point2D> temp = new ArrayList<>(xPoints.length); for (int i = 0; i < xPoints.length; i++) { temp.add(new Point2D(xPoints[i], yPoints[i])); } this.vertices = temp; } } /** * Determines whether the specified coordinates are inside this * <code>Collidable</code>. * <p> * * @param point the <code>Point2D</code> to be tested * @return <code>true</code> if this <code>Collidable</code> contains the * specified coordinates (x;y) <code>false</code> otherwise. */ public boolean contains(Point2D point) { return this.contains(point.getX(), point.getY()); } /** * Determines whether the specified coordinates are inside this * <code>Collidable</code>. * * @param xPos the specified X coordinate to be tested (double) * @param yPos the specified Y coordinate to be tested (double) * * @return <code>true</code> if this <code>Collidable</code> contains the * specified coordinates <code>(xPos; yPos)</code> <code>false</code> * otherwise. */ public boolean contains(double xPos, double yPos) { short hits = 0; double lastPosX = getVertices()[getNumVertices() - 1].getX() + getPosition().getX() + 1; double lastPosY = getVertices()[getNumVertices() - 1].getY() + getPosition().getY() + 1; double curPosX, curPosY; for (short i = 0; i < getNumVertices(); lastPosX = curPosX, lastPosY = curPosY, i++) { curPosX = getVertices()[i].getX() + getPosition().getX() + 1; curPosY = getVertices()[i].getY() + getPosition().getY() + 1; if (curPosY == lastPosY) { continue; } double leftPosX; if (curPosX < lastPosX) { if (xPos >= lastPosX) { continue; } leftPosX = curPosX; } else { if (xPos >= curPosX) { continue; } leftPosX = lastPosX; } double testA, testB; if (curPosY < lastPosY) { if (yPos < curPosY || yPos >= lastPosY) { continue; } if (xPos < leftPosX) { hits++; continue; } testA = xPos - curPosX; testB = yPos - curPosY; } else { if (yPos < lastPosY || yPos >= curPosY) { continue; } if (xPos < leftPosX) { hits++; continue; } testA = xPos - lastPosX; testB = yPos - lastPosY; } if (testA < (testB / (lastPosY - curPosY) * (lastPosX - curPosX))) { hits++; } } return ((hits & 1) != 0); } /** * Translates the vertices of the <code>Collidable</code> by * <code>deltaX</code> along the X axis and by <code>deltaY</code> along the * Y axis. * * @param deltaX the amount to translate along the X axis. (double) * @param deltaY the amount to translate along the Y axis. (double) */ public void translate(double deltaX, double deltaY) { this.getPosition().translate(new Point2D(deltaX, deltaY)); for (Point2D point : getVertices()) { point.translate(new Point2D(deltaX, deltaY)); } } /** * Translates the vertices of the <code>Collidable</code> by the inputed * <code>Point2D</code> (<code>delta</code>). * * @param delta the amount to translate along the axis */ public void translate(Point2D delta) { this.getPosition().translate(new Point2D(delta.getX(), delta.getY())); for (Point2D point : getVertices()) { point.translate(new Point2D(delta.getX(), delta.getY())); } } /** * Translates the vertices of the <code>Collidable</code> by * <code>deltaX</code> along the X axis. * * @param deltaX the amount to translate along the X axis (double) */ public void translateX(double deltaX) { this.getPosition().translateX(deltaX); for (Point2D point : getVertices()) { point.translateX(deltaX); } } /** * Translates the vertices of the <code>Collidable</code> by * <code>deltaX</code> along the Y axis. * * @param deltaY the amount to translate along the Y axis. (double) */ public void translateY(double deltaY) { this.getPosition().translateY(deltaY); for (Point2D point : getVertices()) { point.translateY(deltaY); } } /** * Resets this <code>Collidable</code> object to an empty * <code>Collidable</code> by setting the vertices <code>ArrayList</code> * equal to a new <code>ArrayList</code> of <code>Point2D</code>. */ @SuppressWarnings("Convert2Diamond") public void reset() { this.vertices = new ArrayList<Point2D>(); } /** * Returns the <code>Collidable</code> as a new Abstract Window Toolkit * (AWT) <code>Polygon</code>. The <code>Polygon</code> class encapsulates a * description of a closed, two-dimensional region within a coordinate * space. * * @see java.awt.Polygon * @return a new AWT Polygon created by the points in the vertices * <code>ArrayList</code> */ public java.awt.Polygon toPolygon() { int[] x = new int[getNumVertices()]; int[] y = new int[getNumVertices()]; for (short i = 0; i < getNumVertices(); i++) { x[i] = (int) this.getVertices()[i].getX(); y[i] = (int) this.getVertices()[i].getY(); } return new java.awt.Polygon(x, y, getNumVertices()); } /** * Returns Average of the height of the <code>Collidable</code>, in pixels. * * @return Average of the height, in pixels. */ public float getAverageHeight() { float result = 0; for (Point2D point : getVertices()) { result += point.getX(); } result /= getNumVertices(); return result; } /** * Returns average of the width of the <code>Collidable</code>, in pixels. * * @return average of the width, in pixels */ public float getAverageWidth() { float result = 0; for (Point2D point : getVertices()) { result += point.getX(); } return result / getNumVertices(); } public Point2D getPosition() { return this.position; } public void setPosition(Point2D position) { this.position = position; } /** * Class Object is the root of the class hierarchy. * <p> * Every class has Object as a superclass. All objects, including arrays, * implement the methods of this class.</p> * * @return a hash code value for this object. * @see java.lang.Object#equals(java.lang.Object) */ @Override public int hashCode() { int hash = 7; hash = 79 * hash + Objects.hashCode(getVertices()); hash = 79 * hash + Objects.hashCode(getPosition()); return hash; } /** * Returns true if the <code>this</code> is equal to the argument and false * otherwise. * <p> * Consequently, if both argument are null, true is returned, false is * returned. Otherwise, equality is determined by using the equals method of * the first argument.</p> * * @param obj the <code>Object</code> to be tested * @see java.lang.Object#equals(java.lang.Object) * * @return true if the argument is equal to <code>this</code> other and * false otherwise */ @Override public boolean equals(Object obj) { if (obj == null) { return false; } else if (super.getClass() != obj.getClass()) { return false; } final Collidable coll = (Collidable) obj; if (!Objects.equals(this.getVertices(), coll.getVertices())) { return false; } else if (!Objects.equals(this.getPosition(), coll.getPosition())) { return false; } return true; } /** * Returns a string representation of the object. * <p> * In general, the toString method returns a string that "textually * represents" this object. The result should be a concise but informative * representation that is easy for a person to read. It is recommended that * all subclasses override this method.</p> * * @return a textually representation of this object */ @Override public String toString() { return "Collidable[" + "points: " + Arrays.toString(getVertices()) + ", " + "position: " + getPosition() + "]"; } /** * Returns the position of the center of the <code>Collidable</code>. * * @return the position of the center of the <code>Collidable</code>. */ public Point2D getCenter() { return new Point2D( this.getCenterX().getX(), this.getCenterY().getY() ); } /** * Returns the coordinate of the center of the <code>Collidable</code> in * the X axis. * * @return the coordinate of the center of the <code>Collidable</code> in * the X axis */ public Point2D getCenterX() { return new Point2D( this.getPosition().getX() - this.getAverageWidth(), this.getPosition().getY() ); } /** * Returns the coordinate of the center of the <code>Collidable</code> in * the Y axis. * * @return the coordinate of the center of the <code>Collidable</code> in * the Y axis */ public Point2D getCenterY() { return new Point2D( this.getPosition().getX(), this.getPosition().getY() - this.getAverageWidth() ); } /** * returns true if the <code>Collidable</code> have intersected with this * <code>Collidable</code>. * * @see dwarf.Collidable#contains(dwarf.util.Point2D) * * @param coll the <code>Collidable</code> to be tested * @return true if the <code>Collidable</code> has intersected/collided with * this */ public boolean intersects(Collidable coll) { if (coll.getNumVertices() != 0) { for (short i = 0; i < coll.getNumVertices();) { if (this.contains( coll.getVertices()[i].getX() + coll.getPosition().getX(), coll.getVertices()[i].getY() + coll.getPosition().getY() )) { return true; } if (coll instanceof Circle) { i += (35 * coll.getNumVertices()) / 100; } else { i++; } } } if (this.getNumVertices() != 0) { for (short i = 0; i < this.getNumVertices();) { if (coll.contains( this.getVertices()[i].getX() + this.getPosition().getX(), this.getVertices()[i].getY() + this.getPosition().getY() )) { return true; } if (this instanceof Circle) { i += (35 * this.getNumVertices()) / 100; } else { i++; } } } return false; } public void setPosition(double xPos, double yPos) { this.setPosition(new Point2D(xPos, yPos)); } /** * tests if the mouse is hovering over the <code>Collidable</code>. * * @return true if the mouse is hovering over the <code>Collidable</code> * otherwise false. */ public boolean isMouseHover() { return intersects(new Circle(1, dwarf.mouse.getMousePosition(), 1, Colour.white)); } /** * tests if the <code>Collidable</code> is clicked of by a mouse button. * * @see dwarf.mouse#isMouseClicked(int) * @see dwarf.Collidable#isMouseHover() * * @param button the that needs to be clicked. * @return true if the <code>Collidable</code> is clicked on. */ public boolean isClickedOn(int button) { return dwarf.mouse.isMouseClicked(button) && this.isMouseHover(); } /** * tests if the <code>Collidable</code> is clicked of by the left mouse * button. * * @see dwarf.Collidable#isClickedOn(int) * * @return true is the <code>Collidable</code> is clicked on by the left * mouse button. */ public boolean isClickedOn() { return isClickedOn(MOUSE_LEFT); } /** * tests if the <code>Collidable</code> is clicked of by a mouse button. * * @see dwarf.mouse#isMouseClicked(String) * @see dwarf.Collidable#isMouseHover() * * @param button the that needs to be clicked. * @return true if the <code>Collidable</code> is clicked on. */ public boolean isClickedOn(String button) { return dwarf.mouse.isMouseClicked(button) && this.isMouseHover(); } /** * tests if the <code>Collidable</code> in the current view screen. * * @return true if the <code>Collidable</code> position bigger than or equal * to the Camera's position at all four sides. */ public boolean atEdge() { if (this.getPosition().getX() > (Window.getWidth() + Window.getActiveCamera().getPosition().getX())) { return true; } if (this.getPosition().getX() < (0 + Window.getActiveCamera().getPosition().getX())) { return true; } if (this.getPosition().getY() > (Window.getHeight() + Window.getActiveCamera().getPosition().getY())) { return true; } if (this.getPosition().getY() < (0 + Window.getActiveCamera().getPosition().getY())) { return true; } return false; } public void gotoPos(Point2D destination, float speed) { if (this.getPosition().getX() > destination.getX()) { this.getPosition().translateX(-speed); } if (this.getPosition().getX() < destination.getX()) { this.getPosition().translateX(speed); } if (this.getPosition().getY() < destination.getY()) { this.getPosition().translateY(speed); } if (this.getPosition().getY() > destination.getY()) { this.getPosition().translateY(-speed); } } public void gotoPos(Point2D destination) { this.gotoPos(destination, 1); } public void gotoPos(double xPos, double yPos) { this.gotoPos(new Point2D(xPos, yPos), 1); } public void gotoPos(double xPos, double yPos, float speed) { this.gotoPos(new Point2D(xPos, yPos), speed); } public final Collidable getCollidable() { return this; } @Override public Parent getParent() { return this.parent; } @Override public void setParent(Parent parent) { this.parent = parent; } }