Package com.golden.gamedev.object.collision

Source Code of com.golden.gamedev.object.collision.PreciseCollisionGroup

/*
* Copyright (c) 2008 Golden T Studios.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.golden.gamedev.object.collision;

// GTGE
import com.golden.gamedev.object.CollisionManager;
import com.golden.gamedev.object.PlayField;
import com.golden.gamedev.object.Sprite;
import com.golden.gamedev.object.SpriteGroup;

/**
* <p>
* Subclass of <code>CollisionGroup</code> that calculates the precise
* positions of the <code>Sprite</code>s at the moment of collision. It is
* suitable for collisions that need the colliding objects to stop rather than
* vanish.
* </p>
*
* <p>
* For example:
* <ul>
* <li>Collisions between balls that need to stop or bounce precisely off each
* other.</li>
* <li>An object falling to the ground and stopping.</li>
* <li>Objects that are replaced by something else (such as an explosion
* effect).</li> &
* </ul>
* </p>
*
* <p>
* This class may not work as expected with concave sprites- such as L-shapes.
* The position of the collision will be found accurately, but the direction may
* not be as anticipated as it is based on the <code>CollisionRect</code>s
* rather than pixel collisions or custom <code>CollisionShape</code>s
* defined in subclasses of <code>PreciseCollisionGroup</code> If concave
* sprites are necessary, it might be advisable to break them into groups of
* smaller convex <code>Sprite</code>s.
* </p>
*
* @see PlayField#addCollisionGroup(SpriteGroup, SpriteGroup, CollisionManager)
*
*/
public abstract class PreciseCollisionGroup extends CollisionGroup {
 
  /***************************************************************************
   * This is used to test for non-convergence in pixel perfect collision, or
   * when unusual <code>CollisionShape</code>s are used. The default value
   * is 0.000001.
   **************************************************************************/
  protected static double ITERATIVE_BAILOUT = 0.000001;
 
  /***************************************************************************
   * This is the distance that two objects must be within to be considered
   * adjacent. When a collision occurs, the <Sprite>s at their reverted
   * positions are guaranteed to be at least this close. This should be larger
   * than <code>ITERATIVE_BAILOUT</code>. The default value is 0.0001.
   **************************************************************************/
  protected static double ADJACENCY_TOLERANCE = 0.0001;
 
  // These are used to test if there was a collision before movement
  // with the iterative method
  private CollisionShape shape3;
  private CollisionShape shape4;
 
  /***************************************************************************
   * When set true, this <code>PreciseCollisionGroup</code> will send
   * debugging information to the console.
   **************************************************************************/
  protected boolean log = false;
 
  /***************************************************************************
   * Default constructor.
   **************************************************************************/
  public PreciseCollisionGroup() {
    // sets log to false;
    // set true for debugging
    // log=true;
  }
 
  /**
   * Performs collision check between Sprite <code>s1</code> and Sprite
   * <code>s2</code>, and returns true if the sprites (<code>shape1</code>,
   * <code>shape2</code>) collided.
   * <p>
   *
   * The revert positions are set precisely by this method.
   *
   * @param s1 sprite from group 1
   * @param s2 sprite from group 2
   * @param shape1 bounding box of sprite 1
   * @param shape2 bounding box of sprite 2
   * @return <code>true</code> if the sprites is collided one another.
   */
  public boolean isCollide(Sprite s1, Sprite s2, CollisionShape shape1, CollisionShape shape2) {
   
    // if (shape1.intersects(shape2)) {
    if ((this.pixelPerfectCollision && CollisionManager.isPixelCollide(s1
            .getX(), s1.getY(), s1.getImage(), s2.getX(), s2.getY(), s2
            .getImage()))
            || (!this.pixelPerfectCollision && shape1.intersects(shape2))) {
      // basic check to see if collision occurred
      this.sprite1 = s1;
      this.sprite2 = s2;
      this.collisionSide = 0;
     
      // set up collision variables
     
      // this gets the speed
      double speedX1 = s1.getX() - s1.getOldX(), speedY1 = s1.getY()
              - s1.getOldY(), speedX2 = s2.getX() - s2.getOldX(), speedY2 = s2
              .getY()
              - s2.getOldY();
      // now get the bounds for the CollisionShapes
      double x1 = shape1.getX() - speedX1, y1 = shape1.getY() - speedY1, x2 = shape2
              .getX()
              - speedX2, y2 = shape2.getY() - speedY2;
      double w1 = shape1.getWidth(), h1 = shape1.getHeight(), w2 = shape2
              .getWidth(), h2 = shape2.getHeight();
     
      if (this.log) {
        System.out.print("Collision (" + s1.getX() + "," + s1.getY()
                + "),(" + x1 + "," + y1 + ")-->");
      }
     
      // check for collision at old location
     
      if (this.checkCollisionHelper(s1, s2, x1, y1, x2, y2, true)) {// collision
        // at old
        // location
       
        // if(!log) {log=true;System.out.println("Collision
        // ("+s1.getX()+","+s1.getY()+"),("+x1+","+y1+")-->");}
       
        if (this.log) {
          System.out.print("Overlap->");
        }
       
        this.collisionSide = 0;
       
        Sprite spriteToMove, otherSprite;
       
        if (speedX1 == 0 && speedY1 == 0 && speedX2 == 0
                && speedY2 == 0) {// both
          // stationary
          if (this.log) {
            System.out.println("Both stationary");
          }
          // this should only occur when they are placed directly
          // overtop with no movement
          // behaviour here: leave them alone.
          return false;
        }
        else {// find fastest moving
          // find centres
          double s1cx = shape1.getX() + shape1.getWidth() / 2;
          double s1cy = shape1.getY() + shape1.getHeight() / 2;
          double s2cx = shape2.getX() + shape2.getWidth() / 2;
          double s2cy = shape2.getY() + shape2.getHeight() / 2;
         
          if (Math.pow(speedX1, 2) + Math.pow(speedY1, 2) > Math.pow(
                  speedX2, 2)
                  + Math.pow(speedY2, 2)) {// sprite
            // 1
            // faster
            spriteToMove = s1;
            otherSprite = s2;
          }
          else {
            spriteToMove = s2;
            otherSprite = s1;
          }
          if (this.log) {
            System.out.print(spriteToMove + "-->");
          }
         
          // find distances to move (based on default collision
          // shapes)
          // this might need to be changed to use the iterative method
          // if this behaviour should respect pixel perfection
          double distXLeft = s1cx - s2cx + w1 / 2 + w2 / 2;
          double distXRight = s2cx - s1cx + w1 / 2 + w2 / 2;
          double distYUp = s1cy - s2cy + h1 / 2 + h2 / 2;
          double distYDown = s2cy - s1cy + h1 / 2 + h2 / 2;
         
          // find minimum distance
          double minDist = Math.min(Math.min(distXLeft, distXRight),
                  Math.min(distYUp, distYDown));
         
          if (spriteToMove == s1) {// move sprite1
            this.collisionX2 = s2.getX();
            this.collisionY2 = s2.getY();
            if (minDist == distXLeft) {
              this.collisionX1 = s1.getX() - distXLeft;
              this.collisionY1 = s1.getY();
              this.collisionSide = CollisionGroup.RIGHT_LEFT_COLLISION;
            }
            else if (minDist == distXRight) {
              this.collisionX1 = s1.getX() + distXRight;
              this.collisionY1 = s1.getY();
              this.collisionSide = CollisionGroup.LEFT_RIGHT_COLLISION;
            }
            else if (minDist == distYUp) {
              this.collisionX1 = s1.getX();
              this.collisionY1 = s1.getY() - distYUp;
              this.collisionSide = CollisionGroup.BOTTOM_TOP_COLLISION;
            }
            else {
              this.collisionX1 = s1.getX();
              this.collisionY1 = s1.getY() + distYDown;
              this.collisionSide = CollisionGroup.TOP_BOTTOM_COLLISION;
            }
            if (this.log) {
              System.out.println("Corrected");
            }
            return true;
          }
          else {// move sprite 2
            this.collisionX1 = s1.getX();
            this.collisionY1 = s1.getY();
            if (minDist == distXLeft) {
              this.collisionX2 = s2.getX() - distXLeft;
              this.collisionY2 = s2.getY();
              this.collisionSide = CollisionGroup.LEFT_RIGHT_COLLISION;
            }
            else if (minDist == distXRight) {
              this.collisionX2 = s2.getX() + distXRight;
              this.collisionY2 = s2.getY();
              this.collisionSide = CollisionGroup.RIGHT_LEFT_COLLISION;
            }
            else if (minDist == distYUp) {
              this.collisionX2 = s2.getX();
              this.collisionY2 = s2.getY() - distYUp;
              this.collisionSide = CollisionGroup.TOP_BOTTOM_COLLISION;
            }
            else {
              this.collisionX2 = s2.getX();
              this.collisionY2 = s2.getY() + distYDown;
              this.collisionSide = CollisionGroup.BOTTOM_TOP_COLLISION;
            }
            if (this.log) {
              System.out.println("Corrected");
            }
            return true;
          }
         
        }
       
      } // if overlap
     
      else { // no collision at old location
     
        double tHoriz = 999999.0, tVert = 999999.0; // garbage values
                              // that
        // should not be
        // achieved
        int xCollision = -1, yCollision = -1;
       
        if (speedX1 > speedX2) {// left-to-right on X
          if (this.log) {
            System.out.print("dx1>dx2-->");
          }
          tHoriz = (x2 - x1 - w1) / (speedX1 - speedX2);
          xCollision = CollisionGroup.RIGHT_LEFT_COLLISION;
        }
        else if (speedX2 > speedX1) { // right-to-left on X
          if (this.log) {
            System.out.print("dx1<dx2-->");
          }
          tHoriz = (x1 - x2 - w2) / (speedX2 - speedX1);
          xCollision = CollisionGroup.LEFT_RIGHT_COLLISION;
        }
       
        if (speedY1 > speedY2) {// bottom-to-top on Y
          if (this.log) {
            System.out.print("dy1>dy2-->");
          }
          tVert = (y2 - y1 - h1) / (speedY1 - speedY2);
          yCollision = CollisionGroup.BOTTOM_TOP_COLLISION;
        }
        else if (speedY2 > speedY1) { // top-to-bottom on Y
          if (this.log) {
            System.out.print("dy1<dy2-->");
          }
          tVert = (y1 - y2 - h2) / (speedY2 - speedY1);
          yCollision = CollisionGroup.TOP_BOTTOM_COLLISION;
        }
       
        // completely stationary case should have been dealt with above
       
        double finalT;
       
        if (tHoriz <= tVert) {// X collision happens first
          if (this.log) {
            System.out.print("X " + tHoriz + "-->");
          }
         
          this.collisionSide = xCollision;
          // check to see if this is actual collision or too early.
          // may happen when (for example):
          // ***
          // ^
          // |
          // sss
          // sss
          // sss
          // or similar. If not adjacent at this earliest position,
          // assume other is correct.
         
          if (this.checkAdjacencyHelper(s1, s2,
                  x1 + tHoriz * speedX1, y1 + tHoriz * speedY1, x2
                          + tHoriz * speedX2, y2 + tHoriz * speedY2,
                  speedX1, speedY1, speedX2, speedY2, false)) {
            // Yes- X collision is the first real collision
            if (this.log) {
              System.out.print("X " + tHoriz + "-->");
            }
            finalT = tHoriz;
          }
          else {
            // No- X collision is not an actual collision, so use Y
            // collision.
            if (this.log) {
              System.out.print("Y " + tVert + "-->");
            }
            this.collisionSide = yCollision;
            finalT = tVert;
          }
        }
        else {// Y collision happens first
          if (this.log) {
            System.out.print("Y " + tVert + "-->");
          }
         
          this.collisionSide = yCollision;
          // similar check here
          if (this.checkAdjacencyHelper(s1, s2, x1 + tVert * speedX1,
                  y1 + tVert * speedY1, x2 + tVert * speedX2, y2
                          + tVert * speedY2, speedX1, speedY1,
                  speedX2, speedY2, false)) {
            // Yes- Y collision is the first real collision
            if (this.log) {
              System.out.print("Y " + tVert + "-->");
            }
            finalT = tVert;
          }
          else {
            // No- Y collision is not an actual collision, so use X
            // collision.
            if (this.log) {
              System.out.print("X " + tHoriz + "-->");
            }
            this.collisionSide = xCollision;
            finalT = tHoriz;
          }
        }
       
        // set revert positions
        // these may be changed later but are
        // correct for simple cases
       
        // these are for the CollisionRect
        this.collisionX1 = x1 + finalT * speedX1;
        this.collisionY1 = y1 + finalT * speedY1;
        this.collisionX2 = x2 + finalT * speedX2;
        this.collisionY2 = y2 + finalT * speedY2;
       
        // this is sufficient for non-pixel perfect collisions with
        // bounding rectangles
       
        if (this.checkCollisionHelper(s1, s2, this.collisionX1,
                this.collisionY1, this.collisionX2, this.collisionY2,
                true)) {
          // still a collision- this occurs if a non-rectangular
          // CollisionShape exists
          // larger than its height and width would suggest.
         
          if (this.log) {
            System.out.print("Iterate (1)-->");
          }
         
          if (this.iterativeMethod(s1, s2, 0.0, finalT, x1, y1, x2,
                  y2, speedX1, speedY1, speedX2, speedY2)) {
            // collision occurred- collision positions set in
            // iterativeMethod()
            // correct them because these are for the rect, not the
            // sprite
            this.collisionX1 = this.collisionX1 - x1 + s1.getOldX();
            this.collisionY1 = this.collisionY1 - y1 + s1.getOldY();
            this.collisionX2 = this.collisionX2 - x2 + s2.getOldX();
            this.collisionY2 = this.collisionY2 - y2 + s2.getOldY();
           
            if (this.log) {
              System.out.println("true: " + this.collisionSide
                      + " (" + this.collisionX1 + ","
                      + this.collisionY1 + ")");
            }
            return true;
          }
          else {
            // no actual collision (due to custom collisionShapes)
            if (this.log) {
              System.out.println("false");
            }
            return false;
          }
        }
       
        else if (this.checkAdjacencyHelper(s1, s2, this.collisionX1,
                this.collisionY1, this.collisionX2, this.collisionY2,
                speedX1, speedY1, speedX2, speedY2, true)) {
          // this occurs when regular bounding boxes are used. Nothing
          // more needs to be done.
          // need to correct collision?? positions because these are
          // for the rect, not the sprite
          this.collisionX1 = this.collisionX1 - x1 + s1.getOldX();
          this.collisionY1 = this.collisionY1 - y1 + s1.getOldY();
          this.collisionX2 = this.collisionX2 - x2 + s2.getOldX();
          this.collisionY2 = this.collisionY2 - y2 + s2.getOldY();
         
          if (this.log) {
            System.out.println("true: " + this.collisionSide + " ("
                    + this.collisionX1 + "," + this.collisionY1
                    + ")");
          }
         
          return true;
         
        }
       
        else {
          // this occurs when the bouding shape is smaller than its
          // width and height
          // would suggest,
          // or with pixel perfect collision.
         
          if (this.log) {
            System.out.print("Iterate (2)-->");
          }
         
          if (this.iterativeMethod(s1, s2, finalT, 1.0, x1, y1, x2,
                  y2, speedX1, speedY1, speedX2, speedY2)) {
            // collision occurred- collision positions set in
            // iterativeMethod()
            // correct them because these are for the rect, not the
            // sprite
            this.collisionX1 = this.collisionX1 - x1 + s1.getOldX();
            this.collisionY1 = this.collisionY1 - y1 + s1.getOldY();
            this.collisionX2 = this.collisionX2 - x2 + s2.getOldX();
            this.collisionY2 = this.collisionY2 - y2 + s2.getOldY();
           
            if (this.log) {
              System.out.println("true: " + this.collisionSide
                      + " (" + this.collisionX1 + ","
                      + this.collisionY1 + ")");
            }
            return true;
          }
          else {
            // no actual collision due to small collisionShape or
            // transparent pixels
            if (this.log) {
              System.out.println("false");
            }
            return false;
          }
        }
       
      }// no overlap
    } // no collision
    return false;
  } // end of method
 
  // This checks for an overlap
  protected boolean checkCollisionHelper(Sprite s1, Sprite s2, double x1, double y1, double x2, double y2, boolean includePixelPerfect) {
   
    if (includePixelPerfect && this.pixelPerfectCollision) {
      return CollisionManager.isPixelCollide(x1, y1, s1.getImage(), x2,
              y2, s2.getImage());
    }
    else {// check using normal method
      this.shape3 = this.getCollisionShape1(s1);
      this.shape3.setLocation(x1, y1);
      this.shape4 = this.getCollisionShape2(s2);
      this.shape4.setLocation(x2, y2);
      return this.shape3.intersects(this.shape4);
    }
  }
 
  // This checks for adjacency
  protected boolean checkAdjacencyHelper(Sprite s1, Sprite s2, double x1, double y1, double x2, double y2, double dx1, double dy1, double dx2, double dy2, boolean includePixelPerfect) {
   
    // set up offsets for adjacency
    double dx = 0, dy = 0;
    if (dx1 - dx2 < 0) {
      dx = -PreciseCollisionGroup.ADJACENCY_TOLERANCE;
    }
    else if (dx1 - dx2 > 0) {
      dx = PreciseCollisionGroup.ADJACENCY_TOLERANCE;
    }
    if (dy1 - dy2 < 0) {
      dy = -PreciseCollisionGroup.ADJACENCY_TOLERANCE;
    }
    else if (dy1 - dy2 > 0) {
      dy = PreciseCollisionGroup.ADJACENCY_TOLERANCE;
    }
   
    if (includePixelPerfect && this.pixelPerfectCollision) {
      return CollisionManager.isPixelCollide(x1 + dx, y1 + dy, s1
              .getImage(), x2, y2, s2.getImage());
    }
    else {// check using default collision shapes
      this.shape3 = this.getCollisionShape1(s1);
      this.shape3.setLocation(x1 + dx, y1 + dy);
      this.shape4 = this.getCollisionShape2(s2);
      this.shape4.setLocation(x2, y2);
      // if(log)
      // System.out.print(shape3.getX()+","+shape3.getWidth()+","+shape3.getY()+","+shape3.getHeight()+"-"+shape4.getX()+","+shape4.getWidth()+","+shape4.getY()+","+shape4.getHeight()+"-->");
      // if(log) System.out.print(shape3.intersects(shape4)+"-->");
      return this.shape3.intersects(this.shape4);
    }
  }
 
  // iterates to find the just pre-collision position.
 
  protected boolean iterativeMethod(Sprite s1, Sprite s2, double lowerT, double higherT, double oldX1, double oldY1, double oldX2, double oldY2, double speedX1, double speedY1, double speedX2, double speedY2) {
    // set up working t
    double workingT = (lowerT + higherT) / 2;
   
    // set up candidate positions
    double curX1, curY1, curX2, curY2;
   
    // set up fastest speed- this is used for the bailout condition.
    double maxSpeed = Math.max(Math.max(Math.abs(speedX1), Math
            .abs(speedY1)), Math.max(Math.abs(speedX2), Math.abs(speedY2)));
   
    while (true) {
     
      // find current candidate position
      curX1 = oldX1 + workingT * speedX1;
      curY1 = oldY1 + workingT * speedY1;
      curX2 = oldX2 + workingT * speedX2;
      curY2 = oldY2 + workingT * speedY2;
     
      if (this.checkCollisionHelper(s1, s2, curX1, curY1, curX2, curY2,
              true)) {
        // collided- need a lower t.
        higherT = workingT;
        workingT = (workingT + lowerT) / 2;
       
        if ((higherT - lowerT) * maxSpeed < PreciseCollisionGroup.ITERATIVE_BAILOUT) {
          // got too small without avoiding collision
          // this should not happen- should be caught
          // in overlapping code
          System.err.println("Iterative failure-- too close");
          break;
        }
      }
      else if (this.checkAdjacencyHelper(s1, s2, curX1, curY1, curX2,
              curY2, speedX1, speedY1, speedX2, speedY2, true)) {
        // not collided but adjacent- good enough.
        // extra check here to counter the fact that the iterative
        // method
        // may find a solution the wrong side of its starting point
        // when stationary
        this.collisionX1 = Math.abs(curX1 - oldX1) > 2 * PreciseCollisionGroup.ADJACENCY_TOLERANCE ? curX1
                : oldX1;
        this.collisionY1 = Math.abs(curY1 - oldY1) > 2 * PreciseCollisionGroup.ADJACENCY_TOLERANCE ? curY1
                : oldY1;
        this.collisionX2 = Math.abs(curX2 - oldX2) > 2 * PreciseCollisionGroup.ADJACENCY_TOLERANCE ? curX2
                : oldX2;
        this.collisionY2 = Math.abs(curY2 - oldY2) > 2 * PreciseCollisionGroup.ADJACENCY_TOLERANCE ? curY2
                : oldY2;
       
        return true;
      }
      else {
        // not adjacent- need a higher t.
       
        lowerT = workingT;
        workingT = (workingT + higherT) / 2;
       
        if ((higherT - lowerT) * maxSpeed < PreciseCollisionGroup.ITERATIVE_BAILOUT) {
          // got too large without achieving adjacency
          // this occurs when no collision actually
          // took place.
          break;
        }
      }
    }
   
    return false; // default return- no collision
   
  }
 
}
TOP

Related Classes of com.golden.gamedev.object.collision.PreciseCollisionGroup

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.