Rotation Based Collision Detection

Hey all, I am using Slick2D atm just to explore it and so far, its pretty awesome. After a couple of tutorials… I am making a 2D TopDown Zombie Shooter and whilst atm, all is okay. I really dislike how I have done collision Detection. I create a 2D Boolean array and set it to true in the following way:

  1. Grab the Tiled (.tmx) map properties and check the blocked property of the tile. If its a blocked, tile. Return true else false

After this, I then do my collision detection between the player and the map, my player can rotate 360 degrees and move forward. Here is where I am having odd glitches, my player collides with the tiles correctly however if the user tries to rotate and keep moving forward, eventually the player will move through the tile.
A simple example:
o = tile, x = player

o
x //player moves upwards and collides with the tile

player then continues to rotate, upon 180 degree rotation the player glitches and moves through the block.

Any ideas on how to fix my collision detection? I am rather stuck with this.
Here is my code:
SimpleGame.java


import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.tiled.TiledMap;

public class SimpleGame extends BasicGame {
	Image land = null;
	Image player = null;

	float playerX = 400f;
	float playerY = 300f;
	float scale = 1.0f;
	private TiledMap map;
	private Camera cam;
	private String blockedStr = "free";

	int currentBullet = 0;
	/**
	 * The collision map indicating which tiles block movement - generated based
	 * on tile properties
	 */
	private boolean[][] blocked;
	private static final int SIZE = 32;

	public SimpleGame() {
		super("Window");
	}

	// overriden methods are the 3 methods that we will add code later to, they
	// make up our game loop
	@Override
	public void init(GameContainer gc) throws SlickException {

		map = new TiledMap("res/jungle.tmx");
		player = new Image("res/player.png"); // Note that this is Slick2D's
												// Image class not AWT's Image
												// class.

		cam = new Camera(gc, map);
		// build a collision map based on tile properties in the TileD map
		blocked = new boolean[map.getWidth()][map.getHeight()];

		for (int xAxis = 0; xAxis < map.getWidth(); xAxis++) {
			for (int yAxis = 0; yAxis < map.getHeight(); yAxis++) {
				int tileID = map.getTileId(xAxis, yAxis, 0);
				String value = map.getTileProperty(tileID, "blocked", "false");
				if (value.equals("true")) {
					blocked[xAxis][yAxis] = true;
				}
			}
		}
	}

	@Override
	public void update(GameContainer container, int delta)
			throws SlickException {
		boolean collision = false;
		Input input = container.getInput();
		float rotation = player.getRotation();
		float speed = 0.1f * delta;
		float newX = 0;
		float newY = 0;

		newX += speed * Math.sin(Math.toRadians(rotation));

		newY -= speed * Math.cos(Math.toRadians(rotation));

		if (input.isKeyDown(Input.KEY_UP)) {
			// Check all directions
			if (!(isBlocked(playerX, playerY - newY)
					|| isBlocked(playerX + SIZE - 1, playerY - newY) //1 is a buffer space
					|| isBlocked(playerX, playerY + SIZE + newY)
					|| isBlocked(playerX + SIZE - 1, playerY + SIZE + newY)
					|| isBlocked(playerX - newX, playerY)
					|| isBlocked(playerX - newX, playerY + SIZE - 1)
					|| isBlocked(playerX + SIZE + newX, playerY) || isBlocked(
						playerX + SIZE + newX, playerY + SIZE - 1))) {

				// The lower the delta the slowest the sprite will animate.

				blockedStr = "Free";

				playerX += newX;
				playerY += newY;

			} else {
				collision = true;
			
			}
		}
if (collision == false) {
		if (input.isKeyDown(Input.KEY_LEFT)) {

			player.rotate(-0.2f * delta);

		}
		if (input.isKeyDown(Input.KEY_RIGHT)) {

			player.rotate(0.2f * delta);
		}
}
if (collision == true) {
	//reverse the player
	newX = 0;
	newX -= speed * Math.sin(Math.toRadians(rotation));

	newY = 0;
	newY += speed * Math.cos(Math.toRadians(rotation));
	playerY += newY;
	playerX += newX;	
}
	}

	private boolean isBlocked(float x, float y) {
		int xBlock = (int) x / SIZE;
		int yBlock = (int) y / SIZE;
		return blocked[xBlock][yBlock];
	}

	@Override
	public void render(GameContainer gc, Graphics g) throws SlickException {

		// after calculating the positions of all entities
		cam.centerOn(playerX, playerY);

		// in the render()-method
		cam.drawMap();
		cam.translateGraphics();

		// no depth in slick, images are rendered as the code executes so if we
		// render the player before the land, it will be underneath (no
		// transparency)
		player.draw(playerX, playerY);
		g.drawString("blocked:" + blockedStr, playerX - 10, playerY - 10);
	}

	public static void main(String[] args) throws SlickException {
		AppGameContainer app = new AppGameContainer(new SimpleGame());

		app.setDisplayMode(800, 600, false);
		app.start();

	}

}

Camera.java


import org.newdawn.slick.GameContainer;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.tiled.TiledMap;
//Example Camera Class
public class Camera {

   protected TiledMap map;
   protected int numTilesX;
   protected int numTilesY;
   protected int mapHeight;
   protected int mapWidth;
   protected int tileWidth;
   protected int tileHeight;
   protected GameContainer gc;
   protected float cameraX;
   protected float cameraY;
   
   public Camera(GameContainer gc, TiledMap map) {
      this.map = map;
      
      this.numTilesX = map.getWidth();
      this.numTilesY = map.getHeight();
      
      this.tileWidth = map.getTileWidth();
      this.tileHeight = map.getTileHeight();
      
      this.mapHeight = this.numTilesX * this.tileWidth;
      this.mapWidth = this.numTilesY * this.tileHeight;
      
      this.gc = gc;
   }
   
   public void centerOn(float x, float y) {
      //try to set the given position as center of the camera by default
      cameraX = x - gc.getWidth()  / 2;
      cameraY = y - gc.getHeight() / 2;
      
      //if the camera is at the right or left edge lock it to prevent a black bar
      if(cameraX < 0) cameraX = 0;
      if(cameraX + gc.getWidth() > mapWidth) cameraX = mapWidth - gc.getWidth();
      
      //if the camera is at the top or bottom edge lock it to prevent a black bar
      if(cameraY < 0) cameraY = 0;
      if(cameraY + gc.getHeight() > mapHeight) cameraY = mapHeight - gc.getHeight();
   }

   public void centerOn(float x, float y, float height, float width) {
      this.centerOn(x + width / 2, y + height / 2);
   }


   public void centerOn(Shape shape) {
      this.centerOn(shape.getCenterX(), shape.getCenterY());
   }
   
  
   public void drawMap() {
      this.drawMap(0, 0);
   }
   
 
   
   public void drawMap(int offsetX, int offsetY) {
       //calculate the offset to the next tile (needed by TiledMap.render())
       int tileOffsetX = (int) - (cameraX % tileWidth);
       int tileOffsetY = (int) - (cameraY % tileHeight);
       
       //calculate the index of the leftmost tile that is being displayed
       int tileIndexX = (int) (cameraX / tileWidth);
       int tileIndexY = (int) (cameraY / tileHeight);
       
       //finally draw the section of the map on the screen
       map.render(   
             tileOffsetX + offsetX, 
             tileOffsetY + offsetY, 
             tileIndexX,  
             tileIndexY,
                (gc.getWidth()  - tileOffsetX) / tileWidth  + 1,
                (gc.getHeight() - tileOffsetY) / tileHeight + 1);
   }
   
   /**
    * Translates the Graphics-context to the coordinates of the map - now everything
    * can be drawn with it's NATURAL coordinates.
    */
   public void translateGraphics() {
      gc.getGraphics().translate(-cameraX, -cameraY);
   }
   /**
    * Reverses the Graphics-translation of Camera.translatesGraphics().
    * Call this before drawing HUD-elements or the like
    */
   public void untranslateGraphics() {
      gc.getGraphics().translate(cameraX, cameraY);
   }
   
}

Any help is appreciated,
v0rtex

You could always rotate the 4 corners of each tile in the opposite rotation of the player and see if the rotated points intersect with the non rotated player rectangle, then also check if any of the corners of the rotated player rectangle intersect with the non-rotated tile rectangle. It’s expensive, and not very clever, but I did something like that when messing with rotational based collisions and it worked for me.

I’ve never used Slick, but would assume it has a rectangle/polygon object you can probably use to wrap up your coordinates for the collision checks.

If not, here’s an inelegant one I wrote when I was playing around with doing rotated collisions in a non-clever way.


        
    //Wrap coords in rectangles
    CollRect tile_rect = new CollRect(tile_x, tile_y, tile_width, tile_height);
    CollRect player_rect = new CollRect(player_x, player_y, player_width, player_height);

     //Check for rotation
     player_rect.setDegrees(player_rotation_degrees);
     boolean collision = player_rect.dumbRotateIntersection(tile_rect);


	public class CollRect {
	public double x, y;
	public double x2, y2;
	public double width;
	public double height;
	public double mid_x;
	public double mid_y;
	public double degrees=0;

	
	public CollPoint2D[] rect_points;
	private CollRect() {}
	
	public CollRect(double x, double y, double width, double height) {
		this.width = width;
		this.height = height;
		this.x = x;
		this.y = y;
		x2 = x+width-1;
		y2 = y+height-1;
		mid_x = x+(width/2);
		mid_y = y+(height/2);
		
		//Make the rect_points 
		rect_points = new CollPoint2D[4];
		resetPoints();

	}
	
	public void resetPoints() {
		rect_points[0] = new CollPoint2D(x,y);
		rect_points[1] = new CollPoint2D(x2,y);
		rect_points[2] = new CollPoint2D(x2,y2);
		rect_points[3] = new CollPoint2D(x,y2);
	}
	
	public void setDegrees(double degrees) {
		this.degrees = degrees;
	}
	
	
	public boolean dumbRotateIntersection(CollRect cr2) {
		if (rotatedIntersect(cr2,this)) {
			return true;
		}
		if (rotatedIntersect(this,cr2)) {
			return true;
		}

		return false;
	}
	
	
	private static boolean rotatedIntersect(CollRect cr1, CollRect cr2) {
                //Reset points so there is no rotation
		cr1.resetPoints();
		cr2.resetPoints();

                //keep rectangle 1 unrotated, but rotate rectangle 2
		cr2.rotatePoints(cr2.degrees);

                //Since rectangle 1 is unrotated, we need to adjust rectangle to by the opposite of the rotation that didn't happen
		cr2.rotatePoints(-cr1.degrees,cr1.mid_x,cr1.mid_y);

		//Check if any of the points in cr2 intersect cr1
		double cx = cr1.rect_points[0].x;
		double cy = cr1.rect_points[0].y;
		double cx2 = cr1.rect_points[2].x;
		double cy2 = cr1.rect_points[2].y;

		
		for (int i=0;i<4;i++) {
			CollPoint2D p = cr2.rect_points[i];
			if (p.x>=cx&&p.x<=cx2&&
				p.y>=cy&&p.y <=cy2) {
				return true;
			} 
		}
		return false;
	}
	
	
	
	public void rotatePoints(double angle) {
		rotatePoints(angle,mid_x,mid_y);
	}
	
	
	public void rotatePoints(double angle, double mid_x, double mid_y) {
	
		double radians = Math.toRadians(angle);
        double sin = Math.sin(radians);
        double cos = Math.cos(radians);
        double new_x;
        double new_y;
        
        for (int i=0;i<4;i++) {
            new_x = (rect_points[i].x-mid_x)*cos-(rect_points[i].y-mid_y)*sin+mid_x;
            new_y = (rect_points[i].x-mid_x)*sin+(rect_points[i].y-mid_y)*cos+mid_y;
            rect_points[i].x = (int) Math.round(new_x);
            rect_points[i].y = (int) Math.round(new_y);
        }
	}
	
	public boolean intersects(CollRect cr2) {
		CollRect cr = this;
		if (((cr.x>=cr2.x&&cr.x<=cr2.x2)||(cr2.x>=cr.x&&cr2.x<=cr.x2))&&
		((cr.y>=cr2.y&&cr.y<=cr2.y2)||(cr2.y>=cr.y&&cr2.y<=cr.y2))) {
			return true;
		}
		return false;
	}
	
	
	public CollRect intersection(CollRect cr) {
		CollRect ir = new CollRect();
		if (!intersects(cr)) {return ir;}
		
		//Always use the greatest left edge
		if (cr.x>=x) {
			ir.x = cr.x;
		} else {
			ir.x = x;
		}
		
		//Always use the least right edge
		if (cr.x2<=x2) {
			ir.x2 = cr.x2;
		} else {
			ir.x2 = x2;
		}
		
		//Always use the greatest top edge
		if (cr.y>=y) {
			ir.y = cr.y;
		} else {
			ir.y = y;
		}
		
		//Always use the least bottom edge
		if (cr.y2<=y2) {
			ir.y2 = cr.y2;
		} else {
			ir.y2 = y2;
		}
		
		//Set the size
		ir.width = ir.x2-ir.x;
		ir.height = ir.y2-ir.y;
		
		return ir;
	}
	
}

public class CollPoint2D {
	public double x;
	public double y;
		
	public CollPoint2D(double x, double y) {
		this.x=x;
		this.y=y;
	}
	
	public double magnitude() {
		return Math.sqrt((x*x)+(y*y));
	}
	
	public void normalize() {
		double mag = magnitude();
		x = x / mag;
		y = y / mag;
	}
	
	public double dotProduct(CollPoint2D c2) {
		return ((x*c2.x)+(y*c2.y));
	}
	
}

Thanks antiharpist. I am a little confused by your code, do you have a demo application for it? Unsure of what you mean by rotating the map tile’s :confused:
Any help is appreciated,
v0rtex