package games.fighter.davidalan.util; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.Point2D; import java.util.List; import util.Location; import util.Vector; import vooga.fighter.util.ShapeMeasurements; /** * This class is dedicated to collision detection for * shapes and provides useful methods to: * -confirm detections for shapes, either quickly or precisely * -check the side of a shape that another shape/point has with * (Treating all shapes as Rectangles in this case), choice * in parameters as velocity can be taken into account for * extra precision. * * Please read through the methods, but this class can be both * extended to include specific functionality for specific shapes (i.e ellipses) * or one can simply use the precise methods and insert their specific shape * (note this will not work for checking the sides, as it will always treat the * shape as a rectangle!) * * @author Jack Matteucci * */ public class CollisionDetector { static final int INTERSECT_LINE_PRECISION = 1000; ShapeMeasurements myMeasurements; public CollisionDetector(){ myMeasurements = new ShapeMeasurements(); } /** * Uses rectangle bounds of each shape to check if any point of shape2 * is within the rectangle bounds of point 1 */ public boolean quickDetectCollision(Shape shape1, Shape shape2){ return shape1.getBounds().intersects(shape2.getBounds()); } /** * Uses rectangle bounds of each shape to check if point2 * is within the rectangle bounds of point 1 */ public boolean quickDetectCollision(Shape shape1, Point2D point2){ return shape1.contains(point2); } /** * Precisely tracks the bounds of a shape, checking if for any * point on the bounds, whether point2 is closer than the * center than that bound point. * @param precision is the allowed angle between the vector created * from the center to bound point and the vector created from the * center to point2 * @param rotationAngle is the angle at which shape1 is rotated- degrees */ public boolean preciseDetectCollision(Shape shape1, Location point2, double rotationAngle, double precision){ List<Location> boundary = myMeasurements.makeBoundary(shape1, Vector.SanitizeAngle(rotationAngle), ShapeMeasurements.BOUNDARY_PRECISION); Location center1 = myMeasurements.getArbitraryShapeCenter(shape1,Vector.SanitizeAngle(rotationAngle)); return pointWithinBoundary(boundary,center1, point2,precision); } /** * Precisely tracks the bounds of a shape, checking if for any * point on the bounds, whether point2 is closer than the * center than that bound point. Precision taken to be a degree of 1; * No rotation considered. */ public boolean preciseDetectCollision(Shape shape1, Location point2){ return preciseDetectCollision(shape1,point2, ShapeMeasurements.NO_ROTATION, ShapeMeasurements.ANGLE_PRECISION); } /** * Precisely tracks the bounds of a shape1, checking if for any * point on the bounds, whether a point on shape2 is closer than shape1's * center than that shape1 bound point. * @param precision is the allowed angle between the vector created * from the center to bound point and the vector created from the * center to the current point of shape2 being looked at * @param rotationAngle1 is the angle at which shape1 is rotated about top left corner- degrees * @param rotationAngle1 is the angle at which shape2 is rotated about top left corner- degrees */ public boolean preciseDetectCollision(Shape shape1, Shape shape2, double rotationAngle1, double rotationAngle2, double precision){ List<Location> boundary2 = myMeasurements.makeBoundary(shape2, rotationAngle2, ShapeMeasurements.BOUNDARY_PRECISION); for(Location boundaryLoc : boundary2){ if(preciseDetectCollision(shape1, boundaryLoc, rotationAngle1, precision)) return true; } return false; } /** * Precisely tracks the bounds of a shape1, checking if for any * point on the bounds, whether a point on shape2 is closer than shape1's * center than that shape1 bound point. * @param precision is the allowed angle between the vector created * from the center to bound point and the vector created from the * center to the current point of shape2 being looked at. Precision * set automatically to 1 degree. No rotation considered. */ public boolean preciseDetectCollsion(Shape shape1, Shape shape2){ return preciseDetectCollision(shape1,shape2,ShapeMeasurements.NO_ROTATION, ShapeMeasurements.NO_ROTATION, ShapeMeasurements.ANGLE_PRECISION); } /** * Convenience method: Treating the Shape as a Rectangle * returns whether shape1's top has been collided with by * point2 */ public boolean hitTop(Shape shape1, Point2D point2){ return checkSide(shape1,point2,myMeasurements.getTopLeftCorner(shape1), myMeasurements.getTopRightCorner(shape1)); } /** * Convenience method: Treating the Shapes as a Rectangles * returns whether shape1's top side has been collided with by * shape2 */ public boolean hitTop(Shape shape1, Shape shape2){ return hitTop(shape1, myMeasurements.getBottomRightCorner(shape2))|| hitTop(shape1, myMeasurements.getBottomLeftCorner(shape2))|| bothCornersIntersecting(shape1,shape2,shape2.getBounds2D().getMaxY() ,shape1.getBounds2D().getMinY()); } /** * Convenience method: Treating the Shape as a Rectangle * returns whether shape1's Top side has been collided with by * the specified point, taking Velocity into account. */ public boolean hitTop(Shape shape1, Location point2, Vector speed1, Vector speed2){ double vNormalization = getVelocityNormalization(speed1, speed2); if(vNormalization == 0) return hitTop(shape1, point2); // Not quite sure how to get rid of this check from directional checks return intersectsLine(shape1, myMeasurements.getTopLeftCorner(shape1), myMeasurements.getTopRightCorner(shape1), point2, speed1, speed2, vNormalization, INTERSECT_LINE_PRECISION); } /** * Convenience method: Treating the Shapes as Rectangles * returns whether shape1's Top side has been collided with by * shape2, taking velocity of each shape into account. */ public boolean hitTop(Shape shape1, Shape shape2, Vector speed1, Vector speed2){ return hitTop(shape1, myMeasurements.getBottomLeftCorner(shape2), speed1, speed2)|| hitTop(shape1, myMeasurements.getBottomRightCorner(shape2), speed1, speed2); } /** * Convenience method: Treating the Shape as a Rectangle * returns whether shape1's bottom side has been collided with by * point2 */ public boolean hitBottom(Shape shape1, Point2D point2){ return checkSide(shape1,point2,myMeasurements.getBottomRightCorner(shape1), myMeasurements.getBottomLeftCorner(shape1)); } /** * Convenience method: Treating the Shapes as a Rectangles * returns whether shape1's bottom side has been collided with by * shape2 */ public boolean hitBottom(Shape shape1, Shape shape2){ return(hitBottom(shape1, myMeasurements.getTopRightCorner(shape2))|| hitBottom(shape1, myMeasurements.getTopLeftCorner(shape2))|| bothCornersIntersecting(shape1,shape2,shape1.getBounds2D().getMaxY() ,shape2.getBounds2D().getMinY())); } /** * Convenience method: Treating the Shape as a Rectangle * returns whether shape1's Bottom side has been collided with by * the specified point, taking Velocity into account. */ public boolean hitBottom(Shape shape1, Location point2, Vector speed1, Vector speed2){ double vNormalization = getVelocityNormalization(speed1, speed2); if(vNormalization == 0) return hitBottom(shape1, point2); return intersectsLine(shape1, myMeasurements.getBottomLeftCorner(shape1), myMeasurements.getBottomRightCorner(shape1), point2, speed1, speed2, vNormalization, INTERSECT_LINE_PRECISION); } /** * Convenience method: Treating the Shapes as Rectangles * returns whether shape1's Bottom side has been collided with by * shape2, taking velocity of each shape into account. */ public boolean hitBottom(Shape shape1, Shape shape2, Vector speed1, Vector speed2){ return (hitBottom(shape1, myMeasurements.getTopLeftCorner(shape2), speed1, speed2)|| hitBottom(shape1, myMeasurements.getTopRightCorner(shape2), speed1, speed2)); } /** * Convenience method: Treating the Shape as a Rectangle * returns whether shape1's right side has been collided with by * point2 */ public boolean hitRight(Shape shape1, Point2D point2){ return checkRightSide(shape1,point2, myMeasurements.getTopRightCorner(shape1), myMeasurements.getBottomRightCorner(shape1)); } /** * Convenience method: Treating the Shapes as a Rectangles * returns whether shape1's right side has been collided with by * shape2 */ public boolean hitRight(Shape shape1, Shape shape2){ return hitRight(shape1, myMeasurements.getBottomLeftCorner(shape2))|| hitRight(shape1, myMeasurements.getTopLeftCorner(shape2))|| bothCornersIntersecting(shape1,shape2,shape2.getBounds2D().getMaxX() ,shape1.getBounds2D().getMinX()); } /** * Convenience method: Treating the Shape as a Rectangle * returns whether shape1's Right side has been collided with by * the specified point, taking Velocity into account. */ public boolean hitRight(Shape shape1, Location point2, Vector speed1, Vector speed2){ double vNormalization = getVelocityNormalization(speed1, speed2); if(vNormalization == 0) return hitRight(shape1, point2); return intersectsLine(shape1, myMeasurements.getBottomRightCorner(shape1), myMeasurements.getTopRightCorner(shape1), point2, speed1, speed2, vNormalization, INTERSECT_LINE_PRECISION); } /** * Convenience method: Treating the Shapes as Rectangles * returns whether shape1's Right side has been collided with by * shape2, taking velocity of each shape into account. */ public boolean hitRight(Shape shape1, Shape shape2, Vector speed1, Vector speed2){ return hitRight(shape1, myMeasurements.getBottomLeftCorner(shape2), speed1, speed2)|| hitRight(shape1, myMeasurements.getTopLeftCorner(shape2), speed1, speed2); } /** * Convenience method: Treating the Shape as a Rectangle * returns whether shape1's left side has been collided with by * point2 */ public boolean hitLeft(Shape shape1, Point2D point2){ return checkSide(shape1,point2,myMeasurements.getBottomLeftCorner(shape1), myMeasurements.getTopLeftCorner(shape1)); } /** * Convenience method: Treating the Shapes as a Rectangles * returns whether shape1's left side has been collided with by * shape2 */ public boolean hitLeft(Shape shape1, Shape shape2){ return(hitLeft(shape1, myMeasurements.getBottomRightCorner(shape2))|| hitLeft(shape1, myMeasurements.getTopRightCorner(shape2))|| bothCornersIntersecting(shape1,shape2,shape1.getBounds2D().getMaxX() ,shape2.getBounds2D().getMinX())); } /** * Convenience method: Treating the Shape as a Rectangle * returns whether shape1's Left side has been collided with by * the specified point, taking Velocity into account. */ public boolean hitLeft(Shape shape1, Location point2, Vector speed1, Vector speed2){ double vNormalization = getVelocityNormalization(speed1, speed2); if(vNormalization == 0) return hitLeft(shape1, point2); return intersectsLine(shape1, myMeasurements.getTopLeftCorner(shape1), myMeasurements.getBottomLeftCorner(shape1), point2, speed1, speed2, vNormalization, INTERSECT_LINE_PRECISION); } /** * Convenience method: Treating the Shapes as Rectangles * returns whether shape1's Left side has been collided with by * shape2, taking velocity of each shape into account. */ public boolean hitLeft(Shape shape1, Shape shape2, Vector speed1, Vector speed2){ return hitLeft(shape1, myMeasurements.getTopRightCorner(shape2), speed1, speed2)|| hitLeft(shape1, myMeasurements.getBottomRightCorner(shape2), speed1, speed2); } /** * Convenience method: * Returns direction from center of Shape to a Point. */ protected double getQuickDirection (Shape shape1, Point2D point2){ Location center = (Location) myMeasurements.getRectangleCenter(shape1); return Vector.SanitizeAngle(Vector.angleBetween(point2, center)); } /** * Convenience method to find if a point is within a path (closer to center than * the outlining path is) */ protected boolean pointWithinBoundary(List<Location> boundary, Location center1, Location point2, double precision){ Vector vector2 = center1.difference(point2); for(Location boundaryLoc : boundary){ Vector vector1 = center1.difference(boundaryLoc); if(Math.abs(Vector.SanitizeAngle(vector1.getAngleBetween(vector2)))<precision&& vector1.getMagnitude()>= vector2.getMagnitude()){ return true; } } return false; } /** * Convenience method to get a non-infinity Noramlization constant for * velocities (used so that backtracking's precision does not depend on velocity * magnitude */ protected double getVelocityNormalization(Vector velocity1, Vector velocity2){ if(velocity1.getMagnitude() == 0 && velocity2.getMagnitude() == 0) return 0; if(velocity1.getMagnitude() >=velocity2.getMagnitude()) return 1/velocity1.getMagnitude(); else return 1/velocity2.getMagnitude(); } /** * Translates Shape back one fram time depending on given velocity and normalization constant * @param vNormalization = 1 uses velocity as is. */ protected Shape backTranslate(Shape shape1, Vector velocity, double vNormalization){ Location newTopLeft = backTranslate(myMeasurements.getTopLeftCorner(shape1), velocity, vNormalization); return new Rectangle((int) newTopLeft.getX(), (int) newTopLeft.getY() , (int) shape1.getBounds2D().getWidth(), (int) shape1.getBounds2D().getHeight()); } /** * Translates point back one frame time depending on given velocity and normalization constant * @param vNormalization = 1 uses velocity as is. */ protected Location backTranslate(Location loc, Vector velocity, double vNormalization){ return new Location(loc.getX()-(velocity.getXChange()*vNormalization), loc.getY()-(velocity.getYChange()*vNormalization)); } /** * Convenience Method * Determines whether a particular point has at some point in the past * crossed the boundary line of a Rectangle, given it is currently within * the rectangle. * @param corner 1 and corner 2 are the two corners one would like to connect a line between * @param speed1 and speed2 are the speeds of Shape1 and point2 respectively * @param numOfPoint is the number of points on desires the boundary line to be described by */ protected boolean intersectsLine(Shape shape1, Location corner1, Location corner2, Location point2, Vector speed1, Vector speed2, double vNormalization, int numOfPoints){ while(quickDetectCollision(shape1,point2)){ if(myMeasurements.pointOnBoundary(myMeasurements.makeBoundaryLine (corner1,corner2,numOfPoints),point2, 2)) return true; else{ shape1 = backTranslate(shape1, speed1, vNormalization); point2 = backTranslate(point2, speed2, vNormalization); } } return false; } /** * Convenience Method * Checks a side of a Rectangle, and depending on inputs, will return a boolean * as to whether the given point hit that side NOTE: only works for RIGHT SIDE */ private boolean checkRightSide(Shape shape1, Point2D point2, Location corner1, Location corner2){ if(!quickDetectCollision(shape1,point2)) return false; //a line of repeated code :( don't really know how to refactor this out double hitdirection = getQuickDirection(shape1,point2); return (hitdirection-getQuickDirection(shape1, corner1 ) > 0 || (hitdirection-getQuickDirection(shape1,corner2)) <= 0); } /** * Convenience Method * Checks a side of a Rectangle, and depending on inputs, will return a boolean * as to whether the given point hit that side NOTE: DOES NOT WORK FOR RIGHT SIDE */ private boolean checkSide(Shape shape1, Point2D point2, Location corner1, Location corner2){ if(!quickDetectCollision(shape1,point2)) return false; return withinCorners(shape1, getQuickDirection(shape1,point2), corner1, corner2); } /** * Convenience Method to get rid of duplicated code: * checks if the direction is within the direction of the two corners */ private boolean withinCorners(Shape shape1, double hitdirection, Location corner1,Location corner2){ return (hitdirection-getQuickDirection(shape1, corner1 ) > 0 && (hitdirection-getQuickDirection(shape1,corner2)) <= 0); } /** * Convenience Method to get rid of duplicated code: * checks if both corners are within the shape */ private boolean bothCornersIntersecting(Shape shape1, Shape shape2, double lowbound, double highbound){ return (quickDetectCollision(shape1,shape2)&& (lowbound >=highbound)); } }