Detecting a collision is half the job — now make objects bounce, slide, and settle. Learn impulse-based resolution, restitution (bounciness), friction, and correcting the overlap so objects do not sink.
When two objects collide you apply an impulse — an instantaneous change in velocity along the collision normal — to push them apart so they stop interpenetrating. The impulse magnitude depends on their relative velocity, masses, and bounciness. Impulse-based resolution is the standard because it is stable and handles stacking and resting contact well.
def dot(a, b): return a[0]*b[0] + a[1]*b[1]
def resolve(bodyA, bodyB, normal, restitution=0.5):
# relative velocity along the collision normal
rv = (bodyB.vel[0]-bodyA.vel[0], bodyB.vel[1]-bodyA.vel[1])
vel_along_normal = dot(rv, normal)
if vel_along_normal > 0: return # already separating -> skip
invMassA, invMassB = 1/bodyA.mass, 1/bodyB.mass
# impulse scalar: how much velocity change to apply
j = -(1 + restitution) * vel_along_normal / (invMassA + invMassB)
imp = (j*normal[0], j*normal[1])
bodyA.vel[0] -= imp[0]*invMassA; bodyA.vel[1] -= imp[1]*invMassA
bodyB.vel[0] += imp[0]*invMassB; bodyB.vel[1] += imp[1]*invMassB
# apply an impulse along the normal so they stop overlapping. Heavier mass moves less.The restitution coefficient controls how bouncy a collision is: 0 means the objects stop dead (a beanbag), 1 means a perfect elastic bounce that loses no speed (a superball). It scales how much of the incoming speed is returned along the normal. Tuning restitution per material is how you make a ball bounce, a crate thud, and a puck slide.
Restitution (e) = "bounciness", 0 to 1:
e = 0 perfectly inelastic — objects stop on contact (beanbag, mud)
e = 1 perfectly elastic — no energy lost, full bounce (superball)
e = 0.5 half the speed returned (a typical ball)
It scales the outgoing velocity along the collision normal:
v_out = -e * v_in (along the normal)
Games usually combine the two materials' restitutions (e.g. their min or
average). Tune per material: bouncy ball vs. heavy crate vs. sliding puck.Friction resists motion along the surface (tangent to the collision normal), letting objects slide to a stop and rest on slopes instead of sliding forever. It is applied as a second impulse perpendicular to the normal, bounded by the normal impulse (the Coulomb model). Friction is what makes a stack of boxes stay put and a character stop when they land.
def resolve_friction(bodyA, bodyB, normal, normal_impulse, mu=0.3):
rv = (bodyB.vel[0]-bodyA.vel[0], bodyB.vel[1]-bodyA.vel[1])
# tangent = relative velocity with the normal component removed
vn = dot(rv, normal)
tangent = (rv[0]-vn*normal[0], rv[1]-vn*normal[1])
tlen = (tangent[0]**2 + tangent[1]**2) ** 0.5
if tlen == 0: return
tangent = (tangent[0]/tlen, tangent[1]/tlen)
invA, invB = 1/bodyA.mass, 1/bodyB.mass
jt = -dot(rv, tangent) / (invA + invB)
jt = max(-mu*normal_impulse, min(jt, mu*normal_impulse)) # Coulomb clamp
# apply the friction impulse along the tangent (opposes sliding)
# friction makes objects slide to a stop, rest on slopes, and stack.Impulses fix velocity but objects can still be left slightly overlapping, and that sinking accumulates into visible penetration or jitter. A positional correction nudges the bodies apart along the normal by a fraction of the penetration depth each step (Baumgarte / slop), gently pushing them to a non-overlapping rest without adding energy. It is the finishing touch that makes stacks look solid.
def positional_correction(bodyA, bodyB, normal, penetration):
# push bodies apart to fix leftover overlap (avoids sinking + jitter)
percent = 0.2 # correct 20% of the error per step (Baumgarte)
slop = 0.01 # ignore tiny penetrations to avoid jitter
mag = max(penetration - slop, 0) / (1/bodyA.mass + 1/bodyB.mass) * percent
corr = (mag*normal[0], mag*normal[1])
bodyA.pos[0] -= corr[0]/bodyA.mass; bodyA.pos[1] -= corr[1]/bodyA.mass
bodyB.pos[0] += corr[0]/bodyB.mass; bodyB.pos[1] += corr[1]/bodyB.mass
# impulses fix VELOCITY; positional correction fixes leftover OVERLAP so stacks
# rest cleanly instead of slowly sinking into each other.