Package ponkOut.logic

Source Code of ponkOut.logic.CollisionDetector

/* Copyright 2010 Christian Matt
*
* This file is part of PonkOut.
*
* PonkOut is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PonkOut 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PonkOut.  If not, see <http://www.gnu.org/licenses/>.
*/

package ponkOut.logic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.lwjgl.util.vector.Vector2f;

import ponkOut.logic.Block.CornerPlace;
import ponkOut.logic.Block.FacePlace;

/**
* methods for collision detection
*/
public class CollisionDetector {
  /** positive epsilon to deal with inaccuracies */
  private final float EPS = 1e-8f;

  /** contains all blocks currently in the game */
  private LinkedList<Block> blocks;

  // maintain four lists of block faces, i.e. combine blocks which share a
  // common face
  // to speed up collision detection and to prevent balls from being reflected
  // at a corner between two adjacent blocks
  private LinkedList<BlockFace> leftBlockFaces;
  private LinkedList<BlockFace> rightBlockFaces;
  private LinkedList<BlockFace> topBlockFaces;
  private LinkedList<BlockFace> bottomBlockFaces;

  /**
   * contains the four lists of block faces in the order of the values of
   * FacePlace
   */
  private ArrayList<LinkedList<BlockFace>> blockFaces;

  public CollisionDetector() {
    blocks = new LinkedList<Block>();

    leftBlockFaces = new LinkedList<BlockFace>();
    rightBlockFaces = new LinkedList<BlockFace>();
    topBlockFaces = new LinkedList<BlockFace>();
    bottomBlockFaces = new LinkedList<BlockFace>();

    blockFaces = new ArrayList<LinkedList<BlockFace>>(Collections.nCopies(4, (LinkedList<BlockFace>) null));
    blockFaces.set(FacePlace.LEFT.ordinal(), leftBlockFaces);
    blockFaces.set(FacePlace.RIGHT.ordinal(), rightBlockFaces);
    blockFaces.set(FacePlace.TOP.ordinal(), topBlockFaces);
    blockFaces.set(FacePlace.BOTTOM.ordinal(), bottomBlockFaces);
  }

  public void addBlock(Block block) {
    ArrayList<LinkedList<BlockFace>> horizontalNeighbours = new ArrayList<LinkedList<BlockFace>>();
    ArrayList<LinkedList<BlockFace>> verticalNeighbours = new ArrayList<LinkedList<BlockFace>>();
    horizontalNeighbours.add(new LinkedList<BlockFace>());
    horizontalNeighbours.add(new LinkedList<BlockFace>());
    verticalNeighbours.add(new LinkedList<BlockFace>());
    verticalNeighbours.add(new LinkedList<BlockFace>());

    Vector2f[] blockLeft = { block.getCornerPos(CornerPlace.TOP_LEFT), block.getCornerPos(CornerPlace.BOTTOM_LEFT) };
    Vector2f[] blockRight = { block.getCornerPos(CornerPlace.TOP_RIGHT),
        block.getCornerPos(CornerPlace.BOTTOM_RIGHT) };
    Vector2f[] blockTop = { block.getCornerPos(CornerPlace.TOP_LEFT), block.getCornerPos(CornerPlace.TOP_RIGHT) };
    Vector2f[] blockBottom = { block.getCornerPos(CornerPlace.BOTTOM_LEFT),
        block.getCornerPos(CornerPlace.BOTTOM_RIGHT) };

    // update the vertical block faces
    ArrayList<LinkedList<BlockFace>> verticalFaces = new ArrayList<LinkedList<BlockFace>>();
    verticalFaces.add(leftBlockFaces);
    verticalFaces.add(rightBlockFaces);

    BlockFace[] topExtension = { null, null };
    BlockFace[] bottomExtension = { null, null };

    for (int i = 0; i < 2; ++i) {
      for (BlockFace face : verticalFaces.get(i)) {
        Vector2f faceTop = face.getFirstCorner();
        Vector2f faceBottom = face.getSecondCorner();

        // find right/left neighbour
        if (Math.abs(faceTop.x - blockTop[(i + 1) % 2].x) < EPS && faceBottom.y < blockTop[i].y
            && faceTop.y > blockBottom[i].y)
          horizontalNeighbours.get((i + 1) % 2).add(face);

        // find top/bottom extension
        if (Math.abs(faceTop.x - blockTop[i].x) < EPS) {
          if (Math.abs(faceBottom.y - blockTop[i].y) < EPS)
            topExtension[i] = face;
          else if (Math.abs(faceTop.y - blockBottom[i].y) < EPS)
            bottomExtension[i] = face;
        }
      }
    }

    for (int i = 0; i < 2; ++i) {
      BlockFace face = null;

      // create a new face if no extension was found
      // add it to existing face if there is exactly one extension
      // merge to faces if two extensions were found
      if (topExtension[i] == null && bottomExtension[i] == null) {
        // only if block is not smaller than neighbour
        // if it has more than one neighbours it cannot be completely
        // hidden by them
        BlockFace neighbour = null;
        if (horizontalNeighbours.get(i).size() == 1)
          neighbour = horizontalNeighbours.get(i).getFirst();
        if (neighbour == null || neighbour.getFirstCorner().y < blockTop[i].y
            || neighbour.getSecondCorner().y > blockBottom[i].y) {
          face = new BlockFace(block, i == 0 ? FacePlace.LEFT : FacePlace.RIGHT);
          verticalFaces.get(i).add(face);
        }
      } else if (topExtension[i] != null && bottomExtension[i] == null) {
        face = topExtension[i];
        face.addBack(block);
      } else if (topExtension[i] == null && bottomExtension[i] != null) {
        face = bottomExtension[i];
        face.addFront(block);
      } else {
        face = topExtension[i];
        face.addBack(block);
        face.addBack(bottomExtension[i]);
        verticalFaces.get(i).remove(bottomExtension[i]);
      }

      for (BlockFace neighbour : horizontalNeighbours.get(i)) {
        float neighbourTop = neighbour.getFirstCorner().y;
        float neighbourBottom = neighbour.getSecondCorner().y;

        // adjust corners if exactly one overlaps neighbour
        // both cases cannot happen for more than one neighbour
        // it is not necessary to split face if there is a neighbour in
        // the middle
        if (face != null) {
          if (neighbourTop < face.getFirstCorner().y && neighbourBottom <= face.getSecondCorner().y)
            face.setSecondCorner(neighbour.getFirstCorner());
          else if (neighbourBottom > face.getSecondCorner().y && neighbourTop >= face.getFirstCorner().y)
            face.setFirstCorner(neighbour.getSecondCorner());
        }

        // delete neighbour if it is smaller
        // adjust corners if exactly one overlaps
        if (neighbourTop <= blockTop[i].y && neighbourBottom >= blockBottom[i].y)
          removeFace(neighbour);
        else if (neighbourTop > blockTop[i].y && neighbourBottom >= blockBottom[i].y) {
          // follow the block's face upwards
          Block lastBlock = block;
          boolean found;
          do {
            found = false;
            for (Block nextBlock : blocks) {
              Vector2f dv = new Vector2f();
              if (i == 0) // left face
                Vector2f.sub(lastBlock.getCornerPos(CornerPlace.TOP_LEFT),
                    nextBlock.getCornerPos(CornerPlace.BOTTOM_LEFT), dv);
              else
                // right face
                Vector2f.sub(lastBlock.getCornerPos(CornerPlace.TOP_RIGHT),
                    nextBlock.getCornerPos(CornerPlace.BOTTOM_RIGHT), dv);

              if (Math.abs(dv.x) < EPS && Math.abs(dv.y) < EPS) {
                lastBlock = nextBlock;
                found = true;
                break;
              }
            }
          } while (found);

          neighbour.setSecondCorner(lastBlock.getCornerPos((i == 0) ? CornerPlace.TOP_LEFT
              : CornerPlace.TOP_RIGHT));
        } else if (neighbourBottom < blockBottom[i].y && neighbourTop <= blockTop[i].y) {
          // follow the block's face downwards
          Block lastBlock = block;
          boolean found;
          do {
            found = false;
            for (Block nextBlock : blocks) {
              Vector2f dv = new Vector2f();
              if (i == 0) // left face
                Vector2f.sub(lastBlock.getCornerPos(CornerPlace.BOTTOM_LEFT),
                    nextBlock.getCornerPos(CornerPlace.TOP_LEFT), dv);
              else
                // right face
                Vector2f.sub(lastBlock.getCornerPos(CornerPlace.BOTTOM_RIGHT),
                    nextBlock.getCornerPos(CornerPlace.TOP_RIGHT), dv);

              if (Math.abs(dv.x) < EPS && Math.abs(dv.y) < EPS) {
                lastBlock = nextBlock;
                found = true;
                break;
              }
            }
          } while (found);

          neighbour.setFirstCorner(lastBlock.getCornerPos((i == 0) ? CornerPlace.BOTTOM_LEFT
              : CornerPlace.BOTTOM_RIGHT));
        }
      }
    }

    // update the horizontal block faces
    ArrayList<LinkedList<BlockFace>> horizontalFaces = new ArrayList<LinkedList<BlockFace>>();
    horizontalFaces.add(topBlockFaces);
    horizontalFaces.add(bottomBlockFaces);

    BlockFace[] leftExtension = { null, null };
    BlockFace[] rightExtension = { null, null };

    for (int i = 0; i < 2; ++i) {
      for (BlockFace face : horizontalFaces.get(i)) {
        Vector2f faceLeft = face.getFirstCorner();
        Vector2f faceRight = face.getSecondCorner();

        // find bottom/top neighbour
        if (Math.abs(faceLeft.y - blockLeft[(i + 1) % 2].y) < EPS && faceRight.x > blockLeft[i].x
            && faceLeft.x < blockRight[i].x)
          verticalNeighbours.get((i + 1) % 2).add(face);

        // find left/right extension
        if (Math.abs(faceLeft.y - blockLeft[i].y) < EPS) {
          if (Math.abs(faceRight.x - blockLeft[i].x) < EPS)
            leftExtension[i] = face;
          else if (Math.abs(faceLeft.x - blockRight[i].x) < EPS)
            rightExtension[i] = face;
        }
      }
    }

    for (int i = 0; i < 2; ++i) {
      BlockFace face = null;

      // create a new face if no extension was found
      // add it to existing face if there is exactly one extension
      // merge to faces if two extensions were found
      if (leftExtension[i] == null && rightExtension[i] == null) {
        // only if block is not smaller than neighbour
        // if it has more than one neighbours it cannot be completely
        // hidden by them
        BlockFace neighbour = null;
        if (verticalNeighbours.get(i).size() == 1)
          neighbour = verticalNeighbours.get(i).getFirst();
        if (neighbour == null || neighbour.getFirstCorner().x > blockLeft[i].x
            || neighbour.getSecondCorner().x < blockRight[i].x) {
          face = new BlockFace(block, i == 0 ? FacePlace.TOP : FacePlace.BOTTOM);
          horizontalFaces.get(i).add(face);
        }
      } else if (leftExtension[i] != null && rightExtension[i] == null) {
        face = leftExtension[i];
        face.addBack(block);
      } else if (leftExtension[i] == null && rightExtension[i] != null) {
        face = rightExtension[i];
        face.addFront(block);
      } else {
        face = leftExtension[i];
        face.addBack(block);
        face.addBack(rightExtension[i]);
        horizontalFaces.get(i).remove(rightExtension[i]);
      }

      for (BlockFace neighbour : verticalNeighbours.get(i)) {
        float neighbourLeft = neighbour.getFirstCorner().x;
        float neighbourRight = neighbour.getSecondCorner().x;

        // adjust corners if exactly one overlaps neighbour
        // both cases cannot happen for more than one neighbour
        // it is not necessary to split face if there is a neighbour in
        // the middle
        if (face != null) {
          if (neighbourLeft > face.getFirstCorner().x && neighbourRight >= face.getSecondCorner().x)
            face.setSecondCorner(neighbour.getFirstCorner());
          else if (neighbourRight < face.getSecondCorner().x && neighbourLeft <= face.getFirstCorner().x)
            face.setFirstCorner(neighbour.getSecondCorner());
        }

        // delete neighbour if it is smaller
        // adjust corners if exactly one overlaps
        if (neighbourLeft >= blockLeft[i].x && neighbourRight <= blockRight[i].x)
          removeFace(neighbour);
        else if (neighbourLeft < blockLeft[i].x && neighbourRight <= blockRight[i].x) {
          // follow the block's face leftwards
          Block lastBlock = block;
          boolean found;
          do {
            found = false;
            for (Block nextBlock : blocks) {
              Vector2f dv = new Vector2f();
              if (i == 0) // top face
                Vector2f.sub(lastBlock.getCornerPos(CornerPlace.TOP_LEFT),
                    nextBlock.getCornerPos(CornerPlace.TOP_RIGHT), dv);
              else
                // bottom face
                Vector2f.sub(lastBlock.getCornerPos(CornerPlace.BOTTOM_LEFT),
                    nextBlock.getCornerPos(CornerPlace.BOTTOM_RIGHT), dv);

              if (Math.abs(dv.x) < EPS && Math.abs(dv.y) < EPS) {
                lastBlock = nextBlock;
                found = true;
                break;
              }
            }
          } while (found);

          neighbour.setSecondCorner(lastBlock.getCornerPos((i == 0) ? CornerPlace.TOP_LEFT
              : CornerPlace.BOTTOM_LEFT));
        } else if (neighbourRight > blockRight[i].x && neighbourLeft >= blockLeft[i].x) {
          // follow the block's face rightwards
          Block lastBlock = block;
          boolean found;
          do {
            found = false;
            for (Block nextBlock : blocks) {
              Vector2f dv = new Vector2f();
              if (i == 0) // top face
                Vector2f.sub(lastBlock.getCornerPos(CornerPlace.TOP_RIGHT),
                    nextBlock.getCornerPos(CornerPlace.TOP_LEFT), dv);
              else
                // bottom face
                Vector2f.sub(lastBlock.getCornerPos(CornerPlace.BOTTOM_RIGHT),
                    nextBlock.getCornerPos(CornerPlace.BOTTOM_LEFT), dv);

              if (Math.abs(dv.x) < EPS && Math.abs(dv.y) < EPS) {
                lastBlock = nextBlock;
                found = true;
                break;
              }
            }
          } while (found);

          neighbour.setFirstCorner(lastBlock.getCornerPos((i == 0) ? CornerPlace.TOP_RIGHT
              : CornerPlace.BOTTOM_RIGHT));
        }
      }
    }

    blocks.add(block);
  }

  /**
   * Removes this face from the list and removes corresponding face-reference
   * from each block
   *
   * @param face
   *            face to remove
   */
  private void removeFace(BlockFace face) {
    for (Block block : face.getBlocks())
      block.removeFace(face.getFacePlace());
    blockFaces.get(face.getFacePlace().ordinal()).remove(face);
  }

  public void removeBlock(Block block) {
    // remove block from faces
    for (FacePlace facePlace : Block.FacePlace.values()) {
      BlockFace face = block.getFace(facePlace);
      if (face != null)
        face.remove(block, blockFaces.get(facePlace.ordinal()));
    }

    // find all adjacent blocks
    LinkedList<Block> leftNeighbours = new LinkedList<Block>();
    LinkedList<Block> rightNeighbours = new LinkedList<Block>();
    LinkedList<Block> topNeighbours = new LinkedList<Block>();
    LinkedList<Block> bottomNeighbours = new LinkedList<Block>();

    Vector2f topLeft = block.getCornerPos(CornerPlace.TOP_LEFT);
    Vector2f bottomRight = block.getCornerPos(CornerPlace.BOTTOM_RIGHT);

    for (Block block2 : blocks) {
      Vector2f b2TopLeft = block2.getCornerPos(CornerPlace.TOP_LEFT);
      Vector2f b2BottomRight = block2.getCornerPos(CornerPlace.BOTTOM_RIGHT);

      if (Math.abs(topLeft.x - b2BottomRight.x) < EPS && b2BottomRight.y < topLeft.y
          && b2TopLeft.y > bottomRight.y)
        leftNeighbours.add(block2);
      else if (Math.abs(bottomRight.x - b2TopLeft.x) < EPS && b2BottomRight.y < topLeft.y
          && b2TopLeft.y > bottomRight.y)
        rightNeighbours.add(block2);
      else if (Math.abs(topLeft.y - b2BottomRight.y) < EPS && b2BottomRight.x > topLeft.x
          && b2TopLeft.x < bottomRight.x)
        topNeighbours.add(block2);
      else if (Math.abs(bottomRight.y - b2TopLeft.y) < EPS && b2BottomRight.x > topLeft.x
          && b2TopLeft.x < bottomRight.x)
        bottomNeighbours.add(block2);
    }

    // sort lists for easy creation of new faces
    Collections.sort(leftNeighbours, Block.verticalComparator);
    Collections.sort(rightNeighbours, Block.verticalComparator);
    Collections.sort(topNeighbours, Block.horizontalComperator);
    Collections.sort(bottomNeighbours, Block.horizontalComperator);

    // create new faces for the adjacent blocks if they do not already exist
    // create shortcuts here: left neighbour will get a right face etc.
    ArrayList<LinkedList<Block>> neighbours = new ArrayList<LinkedList<Block>>(Collections.nCopies(4,
        (LinkedList<Block>) null));
    neighbours.set(FacePlace.LEFT.ordinal(), rightNeighbours);
    neighbours.set(FacePlace.RIGHT.ordinal(), leftNeighbours);
    neighbours.set(FacePlace.TOP.ordinal(), bottomNeighbours);
    neighbours.set(FacePlace.BOTTOM.ordinal(), topNeighbours);

    for (FacePlace facePlace : FacePlace.values()) {
      BlockFace face = null;
      for (Block neighbour : neighbours.get(facePlace.ordinal())) {
        if (neighbour.getFace(facePlace) == null) {
          if (face == null) // first hit
            face = new BlockFace(neighbour, facePlace);
          else {
            // check if block extends old face
            Vector2f dv = new Vector2f();
            switch (facePlace) {
            case LEFT:
              Vector2f.sub(face.getSecondCorner(), neighbour.getCornerPos(CornerPlace.TOP_LEFT), dv);
              break;

            case RIGHT:
              Vector2f.sub(face.getSecondCorner(), neighbour.getCornerPos(CornerPlace.TOP_RIGHT), dv);
              break;

            case TOP:
              Vector2f.sub(face.getSecondCorner(), neighbour.getCornerPos(CornerPlace.TOP_LEFT), dv);
              break;

            case BOTTOM:
              Vector2f.sub(face.getSecondCorner(), neighbour.getCornerPos(CornerPlace.BOTTOM_LEFT), dv);
              break;
            }
            // add block to face if it extends it
            if (Math.abs(dv.x) < EPS && Math.abs(dv.y) < EPS)
              face.addBack(neighbour);
            else {
              // otherwise create new face
              // first adjust old face and add it to list
              limitFaceByBlock(face, block);
              addFace(face);

              face = new BlockFace(neighbour, facePlace);
            }
          }
        }
      }
      if (face != null) {
        limitFaceByBlock(face, block);
        addFace(face);
      }
    }

    blocks.remove(block);
  }

  /**
   * ensures the face does not overlap block
   *
   * @param face
   *            the face to adjust
   * @param block
   *            a neighbour of the face (i.e. e.g. if face is a left face then
   *            the right face of the block touches the face)
   */
  private void limitFaceByBlock(BlockFace face, Block block) {
    switch (face.getFacePlace()) {
    case LEFT:
      if (face.getFirstCorner().y > block.getCornerPos(CornerPlace.TOP_RIGHT).y)
        face.setFirstCorner(block.getCornerPos(CornerPlace.TOP_RIGHT));
      if (face.getSecondCorner().y < block.getCornerPos(CornerPlace.BOTTOM_RIGHT).y)
        face.setSecondCorner(block.getCornerPos(CornerPlace.BOTTOM_RIGHT));
      break;

    case RIGHT:
      if (face.getFirstCorner().y > block.getCornerPos(CornerPlace.TOP_LEFT).y)
        face.setFirstCorner(block.getCornerPos(CornerPlace.TOP_LEFT));
      if (face.getSecondCorner().y < block.getCornerPos(CornerPlace.BOTTOM_LEFT).y)
        face.setSecondCorner(block.getCornerPos(CornerPlace.BOTTOM_LEFT));
      break;

    case TOP:
      if (face.getFirstCorner().x < block.getCornerPos(CornerPlace.BOTTOM_LEFT).x)
        face.setFirstCorner(block.getCornerPos(CornerPlace.BOTTOM_LEFT));
      if (face.getSecondCorner().x > block.getCornerPos(CornerPlace.BOTTOM_RIGHT).x)
        face.setSecondCorner(block.getCornerPos(CornerPlace.BOTTOM_RIGHT));
      break;

    case BOTTOM:
      if (face.getFirstCorner().x < block.getCornerPos(CornerPlace.TOP_LEFT).x)
        face.setFirstCorner(block.getCornerPos(CornerPlace.TOP_LEFT));
      if (face.getSecondCorner().x > block.getCornerPos(CornerPlace.TOP_RIGHT).x)
        face.setSecondCorner(block.getCornerPos(CornerPlace.TOP_RIGHT));
      break;
    }
  }

  /**
   * tries to find extensions to this face and adds it to them or to the list
   * of block faces if no extension is found
   *
   * @param face
   *            a face to add
   */
  private void addFace(BlockFace face) {
    BlockFace[] extension = { null, null };

    Vector2f firstCorner = face.getFirstCorner();
    Vector2f secondCorner = face.getSecondCorner();

    switch (face.getFacePlace()) {
    case LEFT:
    case RIGHT:
      for (BlockFace face2 : blockFaces.get(face.getFacePlace().ordinal())) {
        if (Math.abs(face2.getFirstCorner().x - firstCorner.x) < EPS) {
          if (Math.abs(face2.getSecondCorner().y - firstCorner.y) < EPS)
            extension[0] = face2;
          else if (Math.abs(face2.getFirstCorner().y - secondCorner.y) < EPS)
            extension[1] = face2;
        }
      }
      break;

    case TOP:
    case BOTTOM:
      for (BlockFace face2 : blockFaces.get(face.getFacePlace().ordinal())) {
        if (Math.abs(face2.getFirstCorner().y - firstCorner.y) < EPS) {
          if (Math.abs(face2.getSecondCorner().x - firstCorner.x) < EPS)
            extension[0] = face2;
          else if (Math.abs(face2.getFirstCorner().x - secondCorner.x) < EPS)
            extension[1] = face2;
        }
      }
      break;
    }

    if (extension[0] == null && extension[1] == null)
      blockFaces.get(face.getFacePlace().ordinal()).add(face);
    else if (extension[0] != null && extension[1] == null)
      extension[0].addBack(face);
    else if (extension[0] == null && extension[1] != null)
      extension[1].addFront(face);
    else {
      extension[0].addBack(face);
      extension[0].addBack(extension[1]);
      blockFaces.get(face.getFacePlace().ordinal()).remove(extension[1]);
    }
  }

  /**
   * @param balls
   *            list of all balls in the game
   * @param paddles
   *            list of all paddles in the game
   * @param blocks
   *            list of all blocks in the game
   * @return first occurring collision or null if no collision will occur
   */
  public Collision getFirstCollision(List<Ball> balls, List<Paddle> paddles, List<Block> blocks) {
    Collision first = null;

    // test every Ball with every other Entity for collisions
    for (int i = 0; i < balls.size(); ++i) {
      Ball ball = balls.get(i);

      // test Ball - Ball
      for (int j = i + 1; j < balls.size(); ++j) {
        Ball ball2 = balls.get(j);

        float time = getCollisionTimeBall(ball.getPosition(), ball.getVelocity(), ball.getRadius(),
            ball2.getPosition(), ball2.getVelocity(), ball2.getRadius());

        if (first == null || time < first.time) {
          first = new BallCollision(time, ball, ball2);
        }
      }

      // test Ball - Paddle
      for (Paddle paddle : paddles) {
        float cTime = getCollisionTimePaddleCylinder(ball, paddle);
        float h1Time = getCollisionTimePaddleSphere(ball, paddle, Paddle.HemispherePlace.TOP_LEFT);
        float h2Time = getCollisionTimePaddleSphere(ball, paddle, Paddle.HemispherePlace.BOTTOM_RIGHT);

        // find minimum of those 3 values
        if (cTime < h1Time && cTime < h2Time) {
          if (first == null || cTime < first.time) {
            first = new PaddleCylinderCollision(cTime, ball, paddle);
          }
        } else if (h1Time < h2Time) {
          if (first == null || h1Time < first.time) {
            first = new PaddleHemisphereCollision(h1Time, ball, paddle, Paddle.HemispherePlace.TOP_LEFT);
          }
        } else if (first == null || h2Time < first.time) {
          first = new PaddleHemisphereCollision(h2Time, ball, paddle, Paddle.HemispherePlace.BOTTOM_RIGHT);
        }
      }

      // test Ball - Block
      if (ball.getVelocity().length() > EPS) {
        Collision faceCollision = getCollisionBlockFace(ball);
        Collision cornerCollision = getCollisionBlockCorner(ball);
        if (faceCollision != null && (cornerCollision == null || faceCollision.time <= cornerCollision.time)) {
          if (first == null || faceCollision.time < first.time)
            first = faceCollision;
        } else if (cornerCollision != null) {
          if (first == null || cornerCollision.time < first.time)
            first = cornerCollision;
        }
      }
    }

    return first;
  }

  /**
   * calculates smallest t solving ||p2-p1 + t*(v2-v1)|| = r1+r2
   *
   * @param p1
   *            position of first ball
   * @param v1
   *            velocity of first ball
   * @param r1
   *            radius of first ball
   * @param p2
   *            position of second ball
   * @param v2
   *            velocity of second ball
   * @param r2
   *            radius of second ball
   * @return first time in the future the balls will collide or
   *         Float.POSITIVE_INFINITY if they won't
   */
  private float getCollisionTimeBall(Vector2f p1, Vector2f v1, float r1, Vector2f p2, Vector2f v2, float r2) {
    // change to system where p1 = 0 and v1 = 0
    Vector2f p = new Vector2f();
    Vector2f v = new Vector2f();
    Vector2f.sub(p2, p1, p);
    Vector2f.sub(v2, v1, v);

    // first check if potential collision is in the future
    if (Vector2f.angle(p, v) < Math.PI / 2.0f)
      return Float.POSITIVE_INFINITY;

    // now do the real calculation
    float rsqr = (r1 + r2) * (r1 + r2);

    float dv = (v.x * v.x + v.y * v.y);
    if (dv < EPS)
      return Float.POSITIVE_INFINITY;

    float r = 2 * p.x * v.x * p.y * v.y + v.x * v.x * rsqr - v.x * v.x * p.y * p.y - v.y * v.y * p.x * p.x + v.y
        * v.y * rsqr;
    if (r < EPS)
      return Float.POSITIVE_INFINITY;

    return (float) ((-p.x * v.x - p.y * v.y - Math.sqrt(r)) / dv);

  }

  /**
   * @return first time in the future the ball will collide with the cylinder
   *         of the paddle or Float.POSITIVE_INFINITY if it won't
   */
  private float getCollisionTimePaddleCylinder(Ball b, Paddle p) {
    Vector2f ballPos = b.getPosition();
    Vector2f ballVel = b.getVelocity();
    Vector2f paddlePos = p.getPosition();
    float pHalfLength = p.getLength() / 2.0f;
    float time = Float.POSITIVE_INFINITY;

    switch (p.getPlace()) {
    case LEFT:
      if (ballPos.x < paddlePos.x || ballVel.x > -EPS) {
        time = Float.POSITIVE_INFINITY;
      } else {
        time = (paddlePos.x - ballPos.x + (b.getRadius() + p.getRadius())) / ballVel.x;
        float newY = ballPos.y + time * ballVel.y;
        if (newY < paddlePos.y - pHalfLength || newY > paddlePos.y + pHalfLength) {
          time = Float.POSITIVE_INFINITY;
        }
      }
      break;

    case RIGHT:
      if (ballPos.x > paddlePos.x || ballVel.x < EPS) {
        time = Float.POSITIVE_INFINITY;
      } else {
        time = (paddlePos.x - ballPos.x - (b.getRadius() + p.getRadius())) / ballVel.x;
        float newY = ballPos.y + time * ballVel.y;
        if (newY < paddlePos.y - pHalfLength || newY > paddlePos.y + pHalfLength) {
          time = Float.POSITIVE_INFINITY;
        }
      }
      break;

    case TOP:
      if (ballPos.y > paddlePos.y || ballVel.y < EPS) {
        time = Float.POSITIVE_INFINITY;
      } else {
        time = (paddlePos.y - b.getPosition().y - (b.getRadius() + p.getRadius())) / ballVel.y;
        float newX = ballPos.x + time * ballVel.x;
        if (newX < paddlePos.x - pHalfLength || newX > paddlePos.x + pHalfLength) {
          time = Float.POSITIVE_INFINITY;
        }
      }
      break;

    case BOTTOM:
      if (ballPos.y < paddlePos.y || ballVel.y > -EPS) {
        time = Float.POSITIVE_INFINITY;
      } else {
        time = (paddlePos.y - b.getPosition().y + (b.getRadius() + p.getRadius())) / ballVel.y;
        float newX = ballPos.x + time * ballVel.x;
        if (newX < paddlePos.x - pHalfLength || newX > paddlePos.x + pHalfLength) {
          time = Float.POSITIVE_INFINITY;
        }
      }
      break;
    }

    return time;
  }

  /**
   * Calculates collision with ball and the hemisphere completed to whole
   * spheres.
   *
   * This is no problem, because if a collision with the nonexistent part of
   * the hemisphere is detected, there will be a collision with the cylinder
   * beforehand.
   *
   * @return first time in the future the ball will collide with one of the
   *         hemispheres of the paddle or Float.POSITIVE_INFINITY if they
   *         won't
   */
  private float getCollisionTimePaddleSphere(Ball b, Paddle p, Paddle.HemispherePlace hemisphere) {
    return getCollisionTimeBall(b.getPosition(), b.getVelocity(), b.getRadius(),
        p.getHemispherePosition(hemisphere), p.getVelocity(), p.getRadius());
  }

  /**
   * @return first collision between ball and a block face in the future or
   *         null if there will not be such a collision
   */
  private BlockFaceCollision getCollisionBlockFace(Ball ball) {
    float firstTime = Float.POSITIVE_INFINITY;
    BlockFace firstFace = null;

    Vector2f ballPos = ball.getPosition();
    Vector2f ballVel = ball.getVelocity();

    // left faces
    if (ballVel.x > EPS) {
      for (BlockFace face : leftBlockFaces) {
        Vector2f tlCornerPos = face.getFirstCorner();
        Vector2f blCornerPos = face.getSecondCorner();
        if (ballPos.x < tlCornerPos.x) {
          float time = (tlCornerPos.x - ballPos.x - ball.getRadius()) / ballVel.x;
          if (time < firstTime) {
            float newY = ballPos.y + time * ballVel.y;
            if (newY >= blCornerPos.y && newY <= tlCornerPos.y) {
              firstTime = time;
              firstFace = face;
            }
          }
        }
      }
    }

    // right faces
    if (ballVel.x < -EPS) {
      for (BlockFace face : rightBlockFaces) {
        Vector2f trCornerPos = face.getFirstCorner();
        Vector2f brCornerPos = face.getSecondCorner();
        if (ballPos.x > trCornerPos.x) {
          float time = (trCornerPos.x - ballPos.x + ball.getRadius()) / ballVel.x;
          if (time < firstTime) {
            float newY = ballPos.y + time * ballVel.y;
            if (newY >= brCornerPos.y && newY <= trCornerPos.y) {
              firstTime = time;
              firstFace = face;
            }
          }
        }
      }
    }

    // top faces
    if (ballVel.y < -EPS) {
      for (BlockFace face : topBlockFaces) {
        Vector2f tlCornerPos = face.getFirstCorner();
        Vector2f trCornerPos = face.getSecondCorner();
        if (ballPos.y > tlCornerPos.y) {
          float time = (tlCornerPos.y - ballPos.y + ball.getRadius()) / ballVel.y;
          if (time < firstTime) {
            float newX = ballPos.x + time * ballVel.x;
            if (newX >= tlCornerPos.x && newX <= trCornerPos.x) {
              firstTime = time;
              firstFace = face;
            }
          }
        }
      }
    }

    // bottom faces
    if (ballVel.y > EPS) {
      for (BlockFace face : bottomBlockFaces) {
        Vector2f blCornerPos = face.getFirstCorner();
        Vector2f brCornerPos = face.getSecondCorner();
        if (ballPos.y < blCornerPos.y) {
          float time = (blCornerPos.y - ballPos.y - ball.getRadius()) / ballVel.y;
          if (time < firstTime) {
            float newX = ballPos.x + time * ballVel.x;
            if (newX >= blCornerPos.x && newX <= brCornerPos.x) {
              firstTime = time;
              firstFace = face;
            }
          }
        }
      }
    }

    if (firstTime < Float.POSITIVE_INFINITY) {
      Vector2f pos = new Vector2f(ballPos.x + firstTime * ballVel.x, ballPos.y + firstTime * ballVel.y);
      Block block = firstFace.getBlockAt(pos);
      return new BlockFaceCollision(firstTime, ball, block, firstFace.getFacePlace());
    } else
      return null;
  }

  /**
   * @return first collision between ball and a block corner in the future or
   *         null if there will not be such a collision
   */
  private BlockCornerCollision getCollisionBlockCorner(Ball ball) {
    float firstTime = Float.POSITIVE_INFINITY;
    BlockFace firstFace = null;

    Vector2f ballPos = ball.getPosition();
    Vector2f ballVel = ball.getVelocity();
    float ballRadius = ball.getRadius();
    Vector2f blockVel = new Vector2f(0.0f, 0.0f);

    // test top corner of left face, bottom corner of right face, right
    // corner of top face and left corner of bottom face
    for (BlockFace face : leftBlockFaces) {
      float time = getCollisionTimeBall(ballPos, ballVel, ballRadius, face.getFirstCorner(), blockVel, 0.0f);
      if (time < firstTime) {
        firstTime = time;
        firstFace = face;
      }
    }
    for (BlockFace face : rightBlockFaces) {
      float time = getCollisionTimeBall(ballPos, ballVel, ballRadius, face.getSecondCorner(), blockVel, 0.0f);
      if (time < firstTime) {
        firstTime = time;
        firstFace = face;
      }
    }
    for (BlockFace face : topBlockFaces) {
      float time = getCollisionTimeBall(ballPos, ballVel, ballRadius, face.getSecondCorner(), blockVel, 0.0f);
      if (time < firstTime) {
        firstTime = time;
        firstFace = face;
      }
    }
    for (BlockFace face : bottomBlockFaces) {
      float time = getCollisionTimeBall(ballPos, ballVel, ballRadius, face.getFirstCorner(), blockVel, 0.0f);
      if (time < firstTime) {
        firstTime = time;
        firstFace = face;
      }
    }

    BlockCornerCollision firstCollision = null;
    if (firstTime < Float.POSITIVE_INFINITY) {
      Vector2f pos = new Vector2f(ballPos.x + firstTime * ballVel.x, ballPos.y + firstTime * ballVel.y);
      Block block = firstFace.getBlockAt(pos);
      switch (firstFace.getFacePlace()) {
      case LEFT:
        firstCollision = new BlockCornerCollision(firstTime, ball, block, firstFace.getFirstCorner());
        break;

      case RIGHT:
        firstCollision = new BlockCornerCollision(firstTime, ball, block, firstFace.getSecondCorner());
        break;

      case TOP:
        firstCollision = new BlockCornerCollision(firstTime, ball, block, firstFace.getSecondCorner());
        break;

      case BOTTOM:
        firstCollision = new BlockCornerCollision(firstTime, ball, block, firstFace.getFirstCorner());
        break;
      }
    }

    return firstCollision;
  }
}
TOP

Related Classes of ponkOut.logic.CollisionDetector

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.