PVG
13. Rotated Box Collisions — Program Video Games

13. Rotated Box Collisions

Our current collision system works perfectly for walls that run perfectly horizontal or vertical. But what about slanted walls, rotated doors, or diagonal barriers? Real game worlds need more flexibility than axis-aligned rectangles can provide.

This brings us to Oriented Bounding Boxes (OBBs) - rectangles that can be rotated to any angle whilst maintaining efficient collision detection.

The Fractal Code Cycle Applied to Rotated Collisions

Recall our fundamental pattern:

Input → Processing → Output

For rotated collision detection:

  • Input: Entity positions, movement vectors, oriented bounding box data (position, size, rotation)
  • Processing: Transform coordinates, perform collision detection in local space, transform results back
  • Output: Valid positions accounting for arbitrary rectangle orientations

What Are Rotated Collisions Good For?

Natural Level Design

  • Slanted walls and barriers
  • Rotated doorways and passages
  • Diagonal platforms and ramps

Realistic Environments

  • Buildings at angles
  • Natural rock formations
  • Organic-feeling level geometry

Gameplay Mechanics

  • Rotating platforms
  • Angled conveyor belts
  • Dynamic obstacle orientation

The Coordinate Transformation Approach

Rather than developing complex new collision algorithms, we can use a clever mathematical trick: transform the collision into a space where our existing algorithms work perfectly.

The Big Idea

Instead of checking if a circle collides with a rotated rectangle, we:

  1. Transform both objects into a coordinate system where the rectangle becomes axis-aligned
  2. Use our existing circle-vs-rectangle collision detection
  3. Transform the result back to world coordinates

This approach leverages coordinate system transformations - moving between different mathematical "viewpoints" of the same geometry.

Oriented Bounding Boxes (OBB)

An OBB extends our previous rectangle concept:

OBB :: struct {
    position: Vec2,    // Centre point
    size: Vec2,        // Width and height
    rotation: f32,     // Rotation in radians
    debug_color: rl.Color, // For visual debugging
}

This gives us all the data needed to represent and work with rotated rectangles.

Matrix Transformations

We use 3x3 matrices to perform the coordinate transformations:

Translation Matrix (Moving to Origin)

translate_to_origin := Mat3{
    1, 0, -obb.position.x,
    0, 1, -obb.position.y,
    0, 0, 1,
}

This moves the OBB so its centre is at the origin (0, 0).

Rotation Matrix (Undoing Rotation)

undo_rotation := Mat3{
    math.cos(-obb.rotation), -math.sin(-obb.rotation), 0,
    math.sin(-obb.rotation), math.cos(-obb.rotation), 0,
    0, 0, 1,
}

This rotates everything by the negative of the OBB's rotation, making the OBB axis-aligned.

Combined Transformation

world_to_local := undo_rotation * translate_to_origin

Matrix multiplication combines these transformations into a single operation that converts world coordinates to the OBB's local coordinate system.

The Algorithm Step-by-Step

  1. Transform Player to Local Space

    player_world := Vec3{player.position.x, player.position.y, 1}
    player_local := world_to_local * player_world
    
  2. Create Local AABB

    local_aabb := Rect{-obb.size.x / 2, -obb.size.y / 2, obb.size.x, obb.size.y}
    
  3. Perform Standard Collision Detection

    if rl.CheckCollisionCircleRec(player_local.xy, player.collider_radius, local_aabb) {
        collision_response_local := circle_vs_rect_response(player_local.xy, player.collider_radius, local_aabb)
    }
    
  4. Transform Result Back to World Space

    restore_rotation := Mat3{
        math.cos(obb.rotation), -math.sin(obb.rotation), obb.position.x,
        math.sin(obb.rotation), math.cos(obb.rotation), obb.position.y,
        0, 0, 1,
    }
    response_world := restore_rotation * response_local
    

Why Use Homogeneous Coordinates?

You might notice we're using Vec3 with matrices instead of Vec2. This is because we're using homogeneous coordinates, where 2D points are represented as 3D vectors with the third component set to 1.

This mathematical convention allows us to represent both translation and rotation in a single matrix operation, simplifying our coordinate transformations.

Performance Considerations

Matrix operations might seem expensive, but for our scale (dozens of entities), the computational cost is negligible. The benefits of code reuse and correctness far outweigh the minimal performance overhead.

For games with thousands of collision checks per frame, optimised OBB-vs-circle algorithms exist, but they're significantly more complex to implement and debug.

Visual Debugging

Our implementation includes visual debugging that shows:

  • The rotated rectangles in their world positions
  • The collision boundaries
  • The collision response positions

This makes it much easier to verify that our transformations are working correctly and to tune collision behaviour.

Rotated Collisions in Our System Stack

This enhancement builds upon our existing collision system in the World Layer:

  • Dependencies: Still uses entities, input, and time from the base layer
  • Enhancement: Extends collision detection to handle arbitrary orientations
  • Integration: Maintains the same interface with other systems

The beauty of this approach is that it extends our existing system without breaking compatibility.

The Big Idea

Coordinate system transformations are a powerful mathematical tool that allows us to solve complex problems by converting them into simpler, well-understood problems.

By transforming rotated collision detection into axis-aligned collision detection, we leverage existing, working code whilst supporting much more flexible level design.

This principle - "transform the problem into one you already know how to solve" - applies far beyond collision detection and is a valuable problem-solving strategy in game development and programming in general.