Defining what is a ray


I’m going to define a ray as a point in space with a direction vector, or if your prefer, an angle to know in which direction is looking (although i’ll be using the direction vector in this procedure).

  • A position like {x, y} for our ray origin.
  • A direction vector (normalized position between {-1, -1} to {1, 1}).
    OR
  • An angle [0.0, 360.0] to calculate our ray direction vector.

We will be using the parametric equation of the ray with it’s direction; Like this:
x = originX + t * directionX
y = originY + t * directionY

Here, t is referred to as ‘parameter’, which can also be interpreted as ‘distance’ or ‘time’ in this context.

Ray collision with AABB by the slab method

If you are not familiar with ray collisions, your first take would probably be checking points of the ray sequentially until one of the points ends up inside a figure.
But we don’t need to do this for rectangles that are aligned to the axis.


During this example i’m going to use numbers so you can follow me with pen and paper.
Let’s start with a rectangle with origin [3, 2] and width and height of [7, 4].


The first step we have to make is to calculate the parameter (distance or time of collision) with each side. Because it’s axis aligned, this is really easy to do.
For each side that we want to calculate t there is always a constant value that we can use. Ex: For the left side we know that all the points need to be in X = X origin of rectangle. And for the right side X = X origin + width of rectangle.

This means we just need to resolve the parametric equation for the X in our rectangle, like this
rectangleX = xRayOrigin + t * dirRayX
Solving for t will get us:
t = (rectangleX-xRayOrigin) / dirRayX
In our case tXMin = 2.44.


The values of the for each side are the following:
tXMin = 2.44
tXMax = 10.99
tYMin = 8.72
tYMax = 1.74

As you can see some t points do not collide with our rectangle, because we are calculating the distance needed to an imaginary infinite side of our rectangle.
You can better see it in the next example:


The procedure

There are two rules that every ray intersecting an AABB follows.

  1. The segment intersecting an AABB is always between the second and third points of collision with it’s sides (assuming that the sides are infinite).
  2. If we call 'left' and 'right' sides X and up and down sides Y. The order in which the ray intersect them can’t be two X sides or two Y sides. Specifically, the ray must intersect an X side first, followed by a Y side, or vice versa.

We can see the segment of the ray that intersects a rectangle in the following image.


If we paint the points in which our rays intersect the rectangle, we can see that the segment that intersect the rectangle is always between the second and third points counting from it’s origin. This concept is illustrated in the following image:


At this point, we could solve our problem just by checking if there are two points that overlap with the rectangle; And if it’s colliding we will get two points out of four potential collision points. Then, we can select the point that has the shortest distance between the origin of our ray.
But there is a better less instruction intensive way.
The slab method

Okay, but how do we find the second point with math?. Well, if we keep drawing and label the potential collision points as X1 and X2 for the left and right sides and Y1 and Y2 for bottom and top sides respectively. We conclude that a ray that intersect a rectangle always change it’s axis of collision between the first and second, and the third and fourth points.


Now we need to classify this four points in two groups (the nearest and farthest points by axis). The “near” group formed by the smallest X and Y. And the “far” group formed by the biggest X and Y.


And for the final step, we must get the two points that form the intersecting segment. Pick the biggest value of the Near set. And the smallest value of the Far set.


That’s it, as you can see, the ray only collides if the ‘near’ point is smaller that the ‘far’ one.
So now that we know why and how the slab algorithm works we can go to our example.

Finishing the example

Now that we have listed the four potential collision points with each side. We need to classify the nearest group of t collision by axis and the farthest t. Basically we are swapping the parameter for an axis if tMin > tMax.
This will be something like:
tXNear = Min(tXMinSide, tXMaxSide)
tXFar = Max(tXMinSide, tXMaxSide)
The same goes for YNear and YFar.
In the example for the YNear, it will be our YMax since is lower than YMax.


The values of the for each near and far parameters are the following:
tXNear = 2.44
tYNear = 1.74
tXFar = 8.72
tYFar = 10.99


We are going to calculate a TNEAR and TFAR with the four points that we have. For this we are going to take the greater time from both tXNear and tYnear. And the lowest time from tXFar and tYfar.
tNear = Max(tXNear, tYNear)
tFar = Min(tXFar, tYFar)


The values of this two t are:
tNear = 2.44
tFar = 8.72

Now The only step that is left is to compare tNear and tFar.
If tNear is smaller than tFar it means we have collision.
And so, our tNear is our collision parameter.

Now we can calculate our point of collision by resolving the parametric equation of the ray for our tNear. This get us:
X = 3
Y = 5.597


Code

// Calculate collision time/distance with sides of rectangle
let tXMin = (rectangle.x - segmentRay.origin.x) / segmentRay.dirX;
let tXMax = ((rectangle.x+rectangle.width) - segmentRay.origin.x) / segmentRay.dirX;
let tYMin = (rectangle.y - segmentRay.origin.y) / segmentRay.dirY;
let tYMax = ((rectangle.y+rectangle.height) - segmentRay.origin.y) / segmentRay.dirY;

// Organize collisions in two groups, nearest and farthest
let tXNear = Math.min(tXMin, tXMax);
let tXFar = Math.max(tXMin, tXMax);

let tYNear = Math.min(tYMin, tYMax);
let tYFar = Math.max(tYMin, tYMax);

// Calculate Nearest and farthest collision of all components
let tNear = Math.max(tXNear, tYNear);
let tFar = Math.min(tXFar, tYFar);

// If tNear < tFar we have collision
// Discard negatives since it can't be a collision
if(tNear < tFar && tNear >= 0.0 && tNear <= segmentRay.magnitude) {
	return true;
}else{
	return false;
}

Since my code treat the ray as a segment i have another condition in the case the segment does not reach collision time.
Another thing that this code does NOT account for is the possibility that our ray is parallel to an axis. Therefore, one of the component of it’s direction vector will be 0.
In JS a division by 0 returns Infinity by the float IEEE 754 standard. But you should control this in other languages.

Here it’s a interactive version. You can click and drag on the circles to move the ray origin/end.

  X Y
Near
Far
tNear  
tFar  

Resources used

https://www.realtimerendering.com/intersections.html All types of intersection algorithms

https://gdbooks.gitbooks.io/3dcollisions/content/Chapter3/raycast_aabb.html Collision ray vs AABB

https://tavianator.com/2011/ray_box.html Fast, Branchless Ray/Bounding Box Intersections