Tutorial 2 of 4

Collision Response: When Objects Collide

25 min read Intermediate JavaScript + Canvas

What You'll Learn

In the previous tutorial, we made balls bounce off walls. But what happens when two balls hit each other? That's where collision response becomes crucial.

In this tutorial, you'll learn:

Circle vs Circle Detection

Detecting if two circles collide is surprisingly simple: just check if the distance between their centers is less than the sum of their radii.

r1 = 50 r2 = 40 distance collision if: distance < r1 + r2
Two circles collide when the distance between centers is less than the sum of their radii
circle-collision.js
function circlesCollide(a, b) {
    const dx = b.x - a.x;
    const dy = b.y - a.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    const minDistance = a.radius + b.radius;
    
    return distance < minDistance;
}

// Usage
if (circlesCollide(ball1, ball2)) {
    // Handle collision!
}
Performance Tip

You can skip the expensive Math.sqrt() by comparing squared distances: dx*dx + dy*dy < minDist*minDist

Separating Overlapping Objects

When objects overlap, we need to push them apart. The key is to move them along the collision normal (the line connecting their centers).

separate-circles.js
function separateCircles(a, b) {
    const dx = b.x - a.x;
    const dy = b.y - a.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    const minDistance = a.radius + b.radius;
    
    if (distance < minDistance) {
        // Calculate overlap
        const overlap = minDistance - distance;
        
        // Normalize the collision vector
        const nx = dx / distance;
        const ny = dy / distance;
        
        // Push each circle by half the overlap
        a.x -= nx * overlap / 2;
        a.y -= ny * overlap / 2;
        b.x += nx * overlap / 2;
        b.y += ny * overlap / 2;
    }
}
Important

Always separate objects before applying velocity changes. Otherwise, they might get stuck inside each other!

Elastic Collision Response

In an elastic collision, kinetic energy is conserved. The objects bounce off each other like billiard balls.

v1′ = v1 - (2m2/(m1+m2)) × ((v1-v2)·n) × n
Where n is the normalized collision vector, and m is mass

Don't worry if the formula looks complex! Here's the practical implementation:

elastic-collision.js
function resolveCollision(a, b) {
    const dx = b.x - a.x;
    const dy = b.y - a.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    
    // Collision normal (unit vector)
    const nx = dx / distance;
    const ny = dy / distance;
    
    // Relative velocity
    const dvx = a.vx - b.vx;
    const dvy = a.vy - b.vy;
    
    // Relative velocity along collision normal
    const dvn = dvx * nx + dvy * ny;
    
    // Don't resolve if velocities are separating
    if (dvn > 0) return;
    
    // Restitution (bounciness): 1 = perfectly elastic
    const restitution = 0.9;
    
    // Calculate impulse scalar (assuming equal mass)
    const impulse = -(1 + restitution) * dvn / 2;
    
    // Apply impulse to both balls
    a.vx += impulse * nx;
    a.vy += impulse * ny;
    b.vx -= impulse * nx;
    b.vy -= impulse * ny;
}

Understanding the Code

  1. Collision Normal: The direction from circle A to circle B
  2. Relative Velocity: How fast A is approaching B
  3. Dot Product: How much of that velocity is along the collision normal
  4. Impulse: The "push" we need to apply to both objects

Interactive Demo

Click anywhere to add a new ball. Watch them collide and bounce off each other!

Multi-Ball Collision Simulation
Balls: 3 Collisions: 0 Click canvas to add balls

Adding Mass

In real physics, heavier objects transfer more momentum. Here's how to handle different masses:

mass-collision.js
function resolveCollisionWithMass(a, b) {
    const dx = b.x - a.x;
    const dy = b.y - a.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    
    const nx = dx / distance;
    const ny = dy / distance;
    
    const dvx = a.vx - b.vx;
    const dvy = a.vy - b.vy;
    const dvn = dvx * nx + dvy * ny;
    
    if (dvn > 0) return;
    
    const restitution = 0.9;
    
    // Mass-weighted impulse
    const impulse = -(1 + restitution) * dvn / (1/a.mass + 1/b.mass);
    
    // Apply impulse proportional to inverse mass
    a.vx += (impulse / a.mass) * nx;
    a.vy += (impulse / a.mass) * ny;
    b.vx -= (impulse / b.mass) * nx;
    b.vy -= (impulse / b.mass) * ny;
}

// Example: bigger ball is heavier
const ball = {
    x: 100, y: 100,
    vx: 2, vy: 0,
    radius: 30,
    mass: 30  // Mass proportional to radius
};
Physics Insight

A heavy object hitting a light one will barely slow down, while the light object flies away. A light object hitting a heavy one bounces back with almost the same speed!

Complete Implementation

Here's the full multi-ball collision system you can use in your games:

complete-collision-system.js
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

const GRAVITY = 0.2;
const BOUNCE = 0.9;
const FRICTION = 0.999;

const balls = [];

function createBall(x, y) {
    const radius = 15 + Math.random() * 20;
    return {
        x, y,
        vx: (Math.random() - 0.5) * 8,
        vy: (Math.random() - 0.5) * 8,
        radius,
        mass: radius,
        color: `hsl(${Math.random() * 360}, 80%, 60%)`
    };
}

function update() {
    for (const ball of balls) {
        // Apply gravity
        ball.vy += GRAVITY;
        
        // Apply friction
        ball.vx *= FRICTION;
        ball.vy *= FRICTION;
        
        // Update position
        ball.x += ball.vx;
        ball.y += ball.vy;
        
        // Wall collisions
        if (ball.x - ball.radius < 0) {
            ball.x = ball.radius;
            ball.vx *= -BOUNCE;
        }
        if (ball.x + ball.radius > canvas.width) {
            ball.x = canvas.width - ball.radius;
            ball.vx *= -BOUNCE;
        }
        if (ball.y + ball.radius > canvas.height) {
            ball.y = canvas.height - ball.radius;
            ball.vy *= -BOUNCE;
        }
    }
    
    // Check all pairs for collisions
    for (let i = 0; i < balls.length; i++) {
        for (let j = i + 1; j < balls.length; j++) {
            handleCollision(balls[i], balls[j]);
        }
    }
}

function handleCollision(a, b) {
    const dx = b.x - a.x;
    const dy = b.y - a.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    const minDist = a.radius + b.radius;
    
    if (distance < minDist) {
        // Separate
        const overlap = minDist - distance;
        const nx = dx / distance;
        const ny = dy / distance;
        
        const totalMass = a.mass + b.mass;
        a.x -= nx * overlap * (b.mass / totalMass);
        a.y -= ny * overlap * (b.mass / totalMass);
        b.x += nx * overlap * (a.mass / totalMass);
        b.y += ny * overlap * (a.mass / totalMass);
        
        // Resolve collision
        const dvx = a.vx - b.vx;
        const dvy = a.vy - b.vy;
        const dvn = dvx * nx + dvy * ny;
        
        if (dvn > 0) return;
        
        const impulse = -(1 + BOUNCE) * dvn / (1/a.mass + 1/b.mass);
        
        a.vx += (impulse / a.mass) * nx;
        a.vy += (impulse / a.mass) * ny;
        b.vx -= (impulse / b.mass) * nx;
        b.vy -= (impulse / b.mass) * ny;
    }
}

function render() {
    ctx.fillStyle = '#0A0A12';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    for (const ball of balls) {
        ctx.beginPath();
        ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
        ctx.fillStyle = ball.color;
        ctx.fill();
    }
}

function gameLoop() {
    update();
    render();
    requestAnimationFrame(gameLoop);
}

// Initialize with some balls
for (let i = 0; i < 5; i++) {
    balls.push(createBall(
        100 + Math.random() * 300,
        100 + Math.random() * 200
    ));
}

gameLoop();

Challenges

  1. Pool Game: Create a simple pool game with a cue ball you can shoot
  2. Color Transfer: When balls collide, blend their colors together
  3. Size Change: When balls collide, the bigger one absorbs the smaller one
  4. Chain Reaction: Make balls explode into smaller balls on hard impacts
Next Steps

In the next tutorial, we'll use these collision techniques to build Platformer Physics – jump curves, coyote time, and wall sliding!