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).

<code>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],
  ]
}
</code>

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:

<code>fn subtract(p1: [f64; 2], p2: [f64; 2]) -> [f64; 2] {
  [p1[0] - p2[0], p1[1] - p2[1]]
}
</code>
<code>fn perpendicular(edge: [f64; 2]) -> [f64; 2] {
  [-edge[1], edge[0]]
}
</code>
<code>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
  }
}
</code>

Dot product calculation

The dot product multiplies corresponding components of two vectors.

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

Projection and overlap detection

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

<code>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)
}
</code>
<code>fn overlap(proj1: (f64, f64), proj2: (f64, f64)) -> bool {
  const EPSILON: f64 = 1e-9;
  proj1.1 + EPSILON >= proj2.0 && proj2.1 + EPSILON >= proj1.0
}
</code>

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.

<code>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
</code>

Circle‑square collision detection

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

<code>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
}
</code>

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

RustGame developmentcollision 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

login 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.