Game Development 8 min read

Building a Rust Physics Engine with SAT Collision Detection

This article walks through creating a Rust physics engine by implementing collision detection using the Separating Axis Theorem, covering point extraction, vector math, projection, overlap checks, and handling both rectangle‑rectangle and circle‑square collisions with complete code examples.

Architecture Development Notes
Architecture Development Notes
Architecture Development Notes
Building a Rust Physics Engine with SAT Collision Detection

In this article we explore how to create a physics engine in Rust, focusing on implementing collision detection using the Separating Axis Theorem (SAT).

pub fn get_all_points(&self) -> [[f64; 2]; 4] {
  [
    [((*self).rect.x as f64) + (*self).rect.w as f64, (*self).rect.y as f64],
    [((*self).rect.x as f64) + (*self).rect.w as f64, ((*self).rect.y as f64) + (*self).rect.h as f64],
    [((*self).rect.x as f64), ((*self).rect.y as f64) + (*self).rect.h as f64],
    [(*self).rect.x as f64, (*self).rect.y as f64],
  ]
}

This function returns the four corner points of a rectangle in the order: top‑right, bottom‑right, bottom‑left, top‑left.

Collision detection functions

Vector operations needed for SAT:

fn subtract(p1: [f64; 2], p2: [f64; 2]) -> [f64; 2] {
  [p1[0] - p2[0], p1[1] - p2[1]]
}
fn perpendicular(edge: [f64; 2]) -> [f64; 2] {
  [-edge[1], edge[0]]
}
fn normalize(v: [f64; 2]) -> [f64; 2] {
  let length = (v[0].powi(2) + v[1].powi(2)).sqrt();
  if length > 0.0 {
    [v[0] / length, v[1] / length]
  } else {
    [0.0, 0.0] // handle zero‑length vector
  }
}

Dot product calculation

The dot product multiplies corresponding components of two vectors.

fn dot(v1: [f64; 2], v2: [f64; 2]) -> f64 {
  v1[0] * v2[0] + v1[1] * v2[1]
}

Projection and overlap detection

Project a shape onto an axis and obtain the minimum and maximum scalar values.

fn project(shape: &[[f64; 2]; 4], axis: [f64; 2]) -> (f64, f64) {
  let mut min = dot(shape[0], axis);
  let mut max = min;
  for &point in shape.iter().skip(1) {
    let projection = dot(point, axis);
    if projection < min { min = projection; }
    if projection > max { max = projection; }
  }
  (min, max)
}
fn overlap(proj1: (f64, f64), proj2: (f64, f64)) -> bool {
  const EPSILON: f64 = 1e-9;
  proj1.1 + EPSILON >= proj2.0 && proj2.1 + EPSILON >= proj1.0
}

Putting it all together

Gather points for both rectangles, compute edge vectors, generate axes from the perpendicular normalized edges, and test each axis for a separating axis.

let points1: [[f64; 2]; 4] = self.get_all_points();
let points2: [[f64; 2]; 4] = rect.get_all_points();

let edges1: Vec<[f64; 2]> = points1
  .iter()
  .zip(points1.iter().cycle().skip(1))
  .map(|(p1, p2)| subtract(*p2, *p1))
  .collect();
let edges2: Vec<[f64; 2]> = points2
  .iter()
  .zip(points2.iter().cycle().skip(1))
  .map(|(p1, p2)| subtract(*p2, *p1))
  .collect();

let axes: Vec<[f64; 2]> = edges1
  .iter()
  .chain(edges2.iter())
  .map(|edge| normalize(perpendicular(*edge)))
  .collect();

for axis in axes {
  let proj1 = project(&points1, axis);
  let proj2 = project(&points2, axis);
  if !overlap(proj1, proj2) {
    // Found a separating axis – no collision
    return false;
  }
}
// All projections overlap – collision detected
true

Circle‑square collision detection

Approximate a circle with its bounding box and test overlap with a rectangle.

pub fn detect_collision(&self, rect: &Rect) -> bool {
  // Use diameter to build the circle's bounding rectangle
  let circle_diameter = (self.r * 2) as u32;
  let circle_rect = sdl2::rect::Rect::new(
    self.x as i32,
    self.y as i32,
    circle_diameter,
    circle_diameter,
  );
  // Check overlap between the two axis‑aligned rectangles
  let r = rect.get_rect();
  let overlap_x = circle_rect.x < r.x + r.w && circle_rect.x + circle_rect.w > r.x;
  let overlap_y = circle_rect.y < r.y + r.h && circle_rect.y + circle_rect.h > r.y;
  overlap_x && overlap_y
}

This completes a basic SAT‑based collision system for rectangles and an approximate method for circle‑square collisions in Rust.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

collision detectionphysics enginevector mathSAT
Architecture Development Notes
Written by

Architecture Development Notes

Focused on architecture design, technology trend analysis, and practical development experience sharing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.