12. Smooth Collisions
Right now, have no collision response aside from disallowing movement if the next state would be invalid. This creates an abrupt stop, feeling, ironically, as if the collision system is broken somehow.
Players expect smooth movement that feels "natural", like sliding along walls rather than hitting invisible force fields.
Our current collision detection works, but the response is terrible - let's fix that.
What Is Collision Response?
Collision detection tells us if objects overlap, response decides what happens when they do.
How do we smoothly position a moving circle exactly against a rectangle's edge?
We need to find the minimum "push-back" vector that moves the circle out of the box and make it rest on the contact point.
The Algorithm
We are going to use a very simple algorithm here:
Given the Circle is intersecting with Rectangle,
find the Minimum Vector (V) that would push the Circle out of the Rectangle
![[Pasted image 20250727113508.png]]
Q := clamp(P, bmin, bmax)
W := Q - P
D := normalize(W)
T := P + D * R
V := Q - T
Pnext = P + V
![[{DDF2E8DD-C330-422D-96D0-B7C49EA8E864}.png]]
Vector Maths in Action
What is a vector? It's a magnitude (length) and a direction.
You can also describe a vector as two points with one being the base (start) and the other being the head (end).
It's stored as two numbers (for 2D, three for 3D), usually x, y.
If you imagine starting from [0, 0] and have a vector of [3, 2], you can think of this as first traveling 3 units in the positive x direction, then 2 units in the positive y direction.
Vector Operations
Addition: Adding two vectors combines them into a new vector. For example:
v1 = [1, 2]
v2 = [4, 5]
v3 = v1 + v2
// v3 == [1 + 4, 2 + 5] == [5, 7]
In this example, you can imagine the new vector is traveling 5 units in the positive x direction, then 7 units in the positive y direction.
Subtraction: Subtracting two vectors will give you a new vector that is the vector between the two heads of the input vectors. For example:
v1 = [1, 2]
v2 = [4, 5]
v3 = v2 - v1
// v3 == [4 - 1, 5 - 2] == [3, 2]
The head of the resultant vector will be the first input (v2 in this case).
Normalisation: Normalising any vector will give the direction of the vector by reducing its magnitude to 1.0. For example:
v = [1, 1] // magnitude = ~1.41
nv = normalize(v)
// nv = [0.7071, 0.7071] // magnitude = 1.0
Step-by-Step
- Find the Closest Point
- Use
rl.Vector2Clamp - Result:
Qon the box closest toP
- Use
- Calculate Direction Vector
- Direction from
PtoQ:dirction = normalize(Q - P) - Why normalise: We need direction, not magnitude
- This gives us the unit vector (vector with length 1) pointing toward the contact point
- Direction from
- Find the Contact Point on Circle
T = P + (direction * radius)- The exact point where the circle edge would have touched the rectangle if you dragged it along its movement vector
- Calculate Penetration Vector
V = Q - T- This is our answer. The exact distance/direction to move the player for perfect contact
- Final position is
P + V
Key Implementation Details:
- Critical assumption: Player never starts inside box (prevents divide-by-zero)
- Movement constraint:
movement_speed <= radius * delta_time * 0.95prevents tunneling (for example) - Raylib functions used:
Vector2Clamp,Vector2Normalize,Vector2Length
Recap
Using straightforward mathematics lets us create smooth, natural feeling movement.
Breaking down this spatial problem into simple vector operations keeps it approachable.
Good collision response is about finding the minimal correction needed, and applying it, not just detecting problems.