Recently I had the need to determine whether or not two rotated rectangles were colliding. These rectangles were rotated around their centers and were of the form x, y, w, h, a, where a is the rotation angle in radians.

I searched online for a bit and found out that one way is to convert the rectangles into a series of line segments and then use the Separating Axis Theorem to determine if there was an intersection.

This is a general technique to find out if there is an intersection between two convex polygons. I was wondering if there was possibly something more specific that I could use in this case because they are just rotated rectangles, and not complex polygons.

I thought of the case of how to find the collision between a rotated rectangle and a circle. In that situation you can simply rotate both of them by the negative amount of how much the rectangle was rotated by. This moves the circle and makes the rectangle no longer rotated. Then you simply determine if there is a collision between a rectangle and a circle, which is trivial:

function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}

function circleRectangleCollision(cX, cY, cR, rX, rY, rW, rH) {
  let x = clamp(cX, rX, rX + rW);
  let y = clamp(cY, rY, rY + rH);

  let dX = x - cX;
  let dY = y - cY;

  return dX * dX + dY * dY < cR * cR;
}

I was wondering if this technique could also work for finding whether or not there was a collision between two rotated rectangles.

However, it didn’t seem likely. If you try to rotate both rectangles by the negative amount of one of them, then sure one of them becomes a trivial AABB (axis-aligned bounding box) rectangle, but the other one is still rotated (but now by a different amount). So, now what? Checking for collisions between two AABB rectangles is simple, so I was hoping I could leverage that function:

function rectanglesCollide(r1X, r1Y, r1W, r1H, r2X, r2Y, r2W, r2H) {
  return r1X < r2X + r2W && r1X + r1W > r2X && r1Y < r2Y + r2H && r1Y + r1H > r2Y;
}

Maybe you could calculate the bounding box for the second, still rotated rectangle and use the doRectanglesCollide function to test for collisions that way? Unfortunately, that fails in cases like this, where the rectangles are close, but still not colliding:

Wrong collision

Then I had the idea that maybe you could do a two-pass check. I had no intuition behind why this would work, as math is not my strong suit. It was just a hunch.

So, first we would rotate both rectangles by the negative amount of the first rectangle’s rotation angle. This would make the first rectangle a trivial axis-aligned rectangle. Now we calculate the bounding box for the second still-rotated rectangle and then check whether or not they collide. Okay, nothing different so far.

However, now we rotate both of the rectangles by the negative amount of the second rectangle’s rotation angle. Now this time the second rectangle is a trivial axis-aligned rectangle. We then calculate the bounding box for the first rectangle and check whether or not there is a collision between those two.

And apparently, for mathematical reasons I do not understand, if both of these checks results in a collision, then the two rotated rectangles are colliding. I spent a couple days coding this up and optimizing it, and ended up coming up with this function, which seems to work:

function rotatedRectanglesCollide(r1X, r1Y, r1W, r1H, r1A, r2X, r2Y, r2W, r2H, r2A) {
  let r1HW = r1W / 2;
  let r1HH = r1H / 2;
  let r2HW = r2W / 2;
  let r2HH = r2H / 2;

  let r1CX = r1X + r1HW;
  let r1CY = r1Y + r1HH;
  let r2CX = r2X + r2HW;
  let r2CY = r2Y + r2HH;

  let cosR1A = Math.cos(r1A);
  let sinR1A = Math.sin(r1A);
  let cosR2A = Math.cos(r2A);
  let sinR2A = Math.sin(r2A);

  let r1RX =  cosR2A * (r1CX - r2CX) + sinR2A * (r1CY - r2CY) + r2CX - r1HW;
  let r1RY = -sinR2A * (r1CX - r2CX) + cosR2A * (r1CY - r2CY) + r2CY - r1HH;
  let r2RX =  cosR1A * (r2CX - r1CX) + sinR1A * (r2CY - r1CY) + r1CX - r2HW;
  let r2RY = -sinR1A * (r2CX - r1CX) + cosR1A * (r2CY - r1CY) + r1CY - r2HH;

  let cosR1AR2A = Math.abs(cosR1A * cosR2A + sinR1A * sinR2A);
  let sinR1AR2A = Math.abs(sinR1A * cosR2A - cosR1A * sinR2A);
  let cosR2AR1A = Math.abs(cosR2A * cosR1A + sinR2A * sinR1A);
  let sinR2AR1A = Math.abs(sinR2A * cosR1A - cosR2A * sinR1A);

  let r1BBH = r1W * sinR1AR2A + r1H * cosR1AR2A;
  let r1BBW = r1W * cosR1AR2A + r1H * sinR1AR2A;
  let r1BBX = r1RX + r1HW - r1BBW / 2;
  let r1BBY = r1RY + r1HH - r1BBH / 2;

  let r2BBH = r2W * sinR2AR1A + r2H * cosR2AR1A;
  let r2BBW = r2W * cosR2AR1A + r2H * sinR2AR1A;
  let r2BBX = r2RX + r2HW - r2BBW / 2;
  let r2BBY = r2RY + r2HH - r2BBH / 2;

  return r1X < r2BBX + r2BBW && r1X + r1W > r2BBX && r1Y < r2BBY + r2BBH && r1Y + r1H > r2BBY &&
         r2X < r1BBX + r1BBW && r2X + r2W > r1BBX && r2Y < r1BBY + r1BBH && r2Y + r2H > r1BBY;
}

It’s a bit lengthy unfortunately but I also think it’s pretty pretty with its symmetry. I’m not sure if this is a novel technique or if it’s known, but I’m pretty proud of it regardless. Feel free to let me know if you can think of any further optimizations to speed this up some more, or if you’re aware of any other techniques for calculating collisions between rotated rectangles.

I’ll try my best to answer questions, but I must admit my mathematical skill is not the greatest, so no promises! Anyways, thanks for reading, and enjoy! Here’s a demo below (use WASD to control the one rectangle)