← Collision detection for beginners

[Chapter 6 · Part I · 2D]

Lines

The general equation of a line, which side of a line a point is on, the distance from a point to a line, and circle vs line.

The equation of a line

The line you probably know from school is y=kx+qy = kx + q, where kk is the slope and qq is where the line crosses the yy-axis. It has one flaw for games: a vertical line has no slope, so it can’t be written that way. The general equation of a line covers every line:

ax+by+c=0ax + by + c = 0

For a line through two points P1(x1,y1)P_{1}(x_{1}, y_{1}) and P2(x2,y2)P_{2}(x_{2}, y_{2}), one choice of coefficients is

a=y2y1,b=x1x2,c=x2y1x1y2a = y_{2} - y_{1}, \quad b = x_{1} - x_{2}, \quad c = x_{2}y_{1} - x_{1}y_{2}

The vector (a,b)(a, b) is the line’s normal vector. It points straight out of the line, perpendicular to it.

Drag the two points below and watch the coefficients follow. Whenever the line isn’t vertical, the slope form can be read straight off them, k=abk = -\frac{a}{b} and q=cbq = -\frac{c}{b}. Now line the points up vertically: bb becomes 00, the slope form has nothing to divide by, and ax+by+c=0ax + by + c = 0 carries on as if nothing happened.

(ab)P1(3, 7)P2(15, 3)
Drag P₁ or P₂; they snap to the grid, and one square is one unit. Line them up vertically to see the slope form fail. a = y₂ − y₁ = 3 − 7 = -4 · b = x₁ − x₂ = 3 − 15 = -12 · c = x₂y₁ − x₁y₂ = 96 → −4x − 12y + 96 = 0 · y = kx + q with k = −a/b = −0.33, q = −c/b = 8
TypeScript
type Line = { a: number; b: number; c: number };

function lineThrough(p1: Point, p2: Point): Line {
	return { a: p2.y - p1.y, b: p1.x - p2.x, c: p2.x * p1.y - p1.x * p2.y };
}
JavaScript
// A line is an object like { a: 1, b: -2, c: 5 }, for x - 2y + 5 = 0.
function lineThrough(p1, p2) {
	return { a: p2.y - p1.y, b: p1.x - p2.x, c: p2.x * p1.y - p1.x * p2.y };
}
Python
@dataclass
class Line:
    a: float
    b: float
    c: float


def line_through(p1: Point, p2: Point) -> Line:
    return Line(p2.y - p1.y, p1.x - p2.x, p2.x * p1.y - p1.x * p2.y)
lineThrough
time O(1) space O(1)

Which side of a line a point is on

Put any point (x0,y0)(x_{0}, y_{0}) into the left-hand side of the equation. For a point on the line, you get 00, which is what the equation says. For a point off the line, you get a number whose sign tells you which side of the line the point is on: positive on the side the normal vector (a,b)(a, b) points to, negative on the other. Every chapter from here on leans on this one number.

TypeScript
// ax + by + c for the point: 0 on the line, and its sign says which side.
function side(p: Point, line: Line): number {
	return line.a * p.x + line.b * p.y + line.c;
}
JavaScript
// ax + by + c for the point: 0 on the line, and its sign says which side.
function side(p, line) {
	return line.a * p.x + line.b * p.y + line.c;
}
Python
def side(p: Point, line: Line) -> float:
    """ax + by + c for the point: 0 on the line, and its sign says which side."""
    return line.a * p.x + line.b * p.y + line.c
side
time O(1) space O(1)

The sign is useful on its own, too: it tells you which side of a wall the player is on.

Distance from a point to a line

The size of that number grows with the distance from the line, multiplied by the length of the normal vector, a2+b2\sqrt{a^2 + b^2}. Divide that length out and you have the distance:

d=ax0+by0+ca2+b2d = \frac{|ax_{0} + by_{0} + c|}{\sqrt{a^2 + b^2}}

Drag PP across the line and watch the sign flip, and the distance shrink to 00 on the line itself.

+dP₁P₂P(13, 7)
Drag P, P₁ or P₂; one square is one unit. The + side is where the normal (a, b) points. ax₀ + by₀ + c = −6·13 − 17·7 + 148 = -49 → − side · d = |-49| / √((−6)² + (−17)²) = 2.72
TypeScript
function distanceToLine(p: Point, line: Line): number {
	return Math.abs(side(p, line)) / Math.hypot(line.a, line.b);
}
JavaScript
function distanceToLine(p, line) {
	return Math.abs(side(p, line)) / Math.hypot(line.a, line.b);
}
Python
def distance_to_line(p: Point, line: Line) -> float:
    return abs(side(p, line)) / math.hypot(line.a, line.b)
distanceToLine
time O(1) space O(1)

Circle vs line

A circle touches a line when its centre is at most rr away from it. With the squaring trick, that’s (ax0+by0+c)2r2(a2+b2)(ax_{0} + by_{0} + c)^2 \le r^2(a^2 + b^2): no root, and no division.

dP1P2C
Drag P₁, P₂ or the circle. −180x − 500y + 137600 = 0 · d = |ax + by + c| / √(a² + b²) = 116 > r = 50 → apart
TypeScript
function circleLineCollide(c: Circle, line: Line): boolean {
	return side(c, line) ** 2 <= c.r ** 2 * (line.a ** 2 + line.b ** 2);
}
JavaScript
function circleLineCollide(c, line) {
	return side(c, line) ** 2 <= c.r ** 2 * (line.a ** 2 + line.b ** 2);
}
Python
def circle_line_collide(c: Circle, line: Line) -> bool:
    return side(c, line) ** 2 <= c.r ** 2 * (line.a ** 2 + line.b ** 2)
circleLineCollide
time O(1) space O(1)

This line never ends. Walls and platforms do, which is what the next chapter is about.

Comments

No comments yet. Questions and corrections are welcome.

Plain text, line breaks kept. Your IP address is stored only as a one-way hash, to limit spam.