← Back

1. What is the Separating Axis Theorem?

SAT states: if two convex shapes are not colliding, you can always find at least one axis where their projected "shadows" don't overlap. Equivalently — if every possible separating axis shows overlapping projections, the shapes must be intersecting.

The key properties: SAT only works with convex shapes. A separating axis is just a direction (a unit vector). You project both shapes onto that direction as 1D intervals, and check if those intervals are disjoint.

Drag either shape below to separate them. When a gap exists, the algorithm finds the separating axis instantly.

Drag a shape to separate them

2. Projecting Shapes onto an Axis

To test a given axis, take every vertex of a shape, compute its dot product with the axis direction vector, and record the minimum and maximum values. This gives a 1D interval — the shape's "shadow" on that axis.

min = dot(axis, vertices[0])
max = min
for each vertex v:
    p = dot(axis, v)
    if p < min: min = p
    if p > max: max = p
return Projection(min, max)

Drag the handle at the end of the axis line below to rotate it. Dotted lines show each vertex being projected, and the thick colored segment shows the resulting [min, max] interval.

Axis angle: 0°

3. Finding the Axes to Test

You don't need to test every possible direction — only axes that are perpendicular to each shape's edges. For two polygons with M and N sides, you test at most M+N axes. This is the insight that makes SAT practical.

axes = []
for i in range(len(vertices)):
    edge = vertices[(i+1) % len] - vertices[i]
    normal = perpendicular(edge)  // (-edge.y, edge.x)
    axes.append(normalize(normal))

Note: parallel edges produce the same axis — so a rectangle only needs 2 unique test axes, not 4.

Below: blue arrows are Shape A's edge normals, amber arrows are Shape B's. Drag either shape.

Drag a shape
■ Shape A edge normals  |  ■ Shape B edge normals

4. Overlap Detection & the MTV

For each axis, project both shapes and measure the overlap of their intervals. If any axis gives a negative overlap — the shapes are separated on that axis, so no collision. If all axes overlap, they're colliding.

overlap = min(projA.max, projB.max) - max(projA.min, projB.min)
if overlap < 0: no collision on this axis (shapes separated)

Track the axis with the smallest positive overlap — that's the Minimum Translation Vector (MTV): the shortest push needed to separate the shapes. The MTV is that axis direction scaled by that overlap amount, directed from A toward B.

Start the shapes overlapping, then hit "Resolve Collision" to push them apart.

Drag shapes to overlap, then resolve

5. Collision Response — Bounce & Friction

Once we have the MTV (collision normal + depth), we apply a physical response. First, project the object out of the collision along the MTV. Then decompose its velocity into a normal component (perpendicular to the contact surface) and a tangential component (along the surface). Scale each separately.

v_normal  = dot(velocity, normal) * normal   // perpendicular to surface
v_tangent = velocity - v_normal              // along surface

new_velocity = -bounce * v_normal + (1 - friction) * v_tangent

The bounce coefficient (0–1) controls elasticity: 1.0 = perfectly elastic, 0.0 = no bounce. Friction reduces the tangential (sliding) velocity on each contact.

Animated — ball bounces with gravity
Bounce (elasticity) 0.70
Friction 0.20

6. Circle vs Polygon

Circles are special — they have infinitely many potential separating axes. The trick: the only extra axis you need to test (beyond all polygon edge normals) is the axis from the closest polygon vertex to the circle center.

Steps:

1. Test all polygon edge normals (same as polygon-polygon SAT)
2. Find the closest polygon vertex to the circle center
3. Add the vertex→center direction as one extra axis
4. Project the circle: its interval on any axis is [dot(center,axis) - r, dot(center,axis) + r]

When colliding, the closest vertex is highlighted and the extra axis is drawn. Drag either shape.

Drag the circle or polygon