Space 9: collision decection on grid

Created: — modified: — tags: space3d

Unlike sector-based one, this uses more complex logic, but simpler maps

Given that walls can be placed only on sides of a square grid, let's have a look at sample collision situation:

Circle ramming a line segment

To make calculations simpler, let's mark areas where center of character is allowed to be located. Also, let's make these areas orthogonal. So we see that due to non-zero character size, around each wall there is an area of "forbidden coordinates", where center of character is forbidden to be located. Adding wall width to that, let's reserve about ¼ of cell size around each wall. Looking at areas where walls might be, we can separates whole map into regions like this:

Grid with each line surrounded by two dashed lines at 25% distance each. Label "A" at a middle of a grid cell, label "B" on a line segment, label "C" at a line cross

Here we mark three different types of regions:

First, how do you easily find out what region you're in? That's pretty easy, just look at this picture:

Grid lines have numbers 1,2,3,etc. Multiplied by 2, grid lines become 2,4,6,etc, and spaces between them - 1,3,5,etc

Given that walls can lay only on grid with integer numbers, it's enough just to multiply coordinates by 2, and round result to nearest integer:

So given that a character wants to move from coordinates (x0,y0) to (x1,y1) coordinates, we first find out type of region for (x1,y1) coordinates according to the simple maths in above paragraph, and then:

For region "c", if there is any of 4 adjacent walls, we need to check where the character was before (coordinates (x0,y0)):

Cross of two black lines can be approached either horizontally or diagonally

When moving from region "a" to "c", then we need to check two nearest walls -

Sample code:

function pmove(old_x, old_y, new_x, new_y, walls_h, walls_v){
	var new_x_2x=Math.round(new_x*2);
	var new_y_2x=Math.round(new_y*2);
	var on_v_wall=(new_x_2x % 2 == 0);
	var on_h_wall=(new_y_2x % 2 == 0);
	if(on_h_wall && on_v_wall){
		// we're moving into area "c" - hard case
		if(walls_h[new_y_2x/2][(new_x_2x)/2] || walls_h[new_y_2x/2][(new_x_2x)/2-1] ||
		   walls_v[(new_y_2x)/2][new_x_2x/2] || walls_v[(new_y_2x)/2-1][new_x_2x/2]){
			// and one of walls is here
			var old_x_2x=Math.round(old_x*2);
			var old_y_2x=Math.round(old_y*2);
			if(old_y_2x % 2 == 0){
				// we're moving from "b" area, horizontally
				// hence, block horizontal movement - only vertical allowed
				new_x=old_x;
			} else if(old_x_2x % 2 == 0){
				// same, but opposite
				new_y=old_y;
			} else {
				// we're moving from "a" area - corner case
				// new_x=old_x;
				// new_y=old_y;
				var changed=false;
				if((walls_h[new_y_2x/2][(new_x_2x)/2] && old_x_2x>new_x_2x) ||
				   (walls_h[new_y_2x/2][(new_x_2x)/2-1] && old_x_2x<new_x_2x)) {
					// nearest horizontal wall is here
					// forbid vertical movement
					new_y=old_y;
					changed=true;
				}
				if((walls_v[(new_y_2x)/2][new_x_2x/2] && old_y_2x>new_y_2x) ||
				   (walls_v[(new_y_2x)/2-1][new_x_2x/2] && old_y_2x<new_y_2x)){
					// same, but opposite
					new_x=old_x;
					changed=true;
				}
				if(!changed){
					// we must change something to prevent walking into the wall
					if(Math.random()<0.5){
						new_x=old_x;
					} else {
						new_y=old_y;
					}
				}
			}
		}
	} else if(on_h_wall){
		// we're moving into area "b"
		if(walls_h[new_y_2x/2][(new_x_2x-1)/2]){
			// there's a  horizontal wall ahead
			// forbid horizontal movement
			new_y=old_y;
		}
	} else if(on_v_wall){
		if(walls_v[(new_y_2x-1)/2][new_x_2x/2]){
			// same, but opposite
			new_x=old_x;
		}
	}
	return [new_x, new_y];
}

Illustrations drawn using drawsvg.