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.
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.
Architecture Development Notes
Focused on architecture design, technology trend analysis, and practical development experience sharing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.