Subchapter 178.2
references/algorithms.mdMarkdown24 KBView on GitHub
A comprehensive reference covering essential algorithms for game development, including line drawing, raycasting, collision detection, physics simulation, and vector mathematics.
Bresenham’s line algorithm is an efficient method for determining which cells in a grid lie along a straight line between two points. Originally developed for plotting pixels on raster displays, it has become a foundational tool in game development for raycasting, line-of-sight checks, and grid-based pathfinding. The algorithm uses only integer arithmetic (additions, subtractions, and bit shifts), making it extremely fast.
The core idea is to walk along the major axis (the axis with the greater distance) one cell at a time, accumulating an error term that tracks how far the true line deviates from the current minor-axis position. When the error exceeds a threshold, the minor-axis coordinate is incremented.
Key properties:
Given two grid points (x0, y0) and (x1, y1):
dx = abs(x1 - x0)
dy = abs(y1 - y0)The error term is initialized and updated each step. When it crosses zero, the secondary axis is stepped.
function bresenham(x0, y0, x1, y1):
dx = abs(x1 - x0)
dy = abs(y1 - y0)
sx = sign(x1 - x0) // -1 or +1
sy = sign(y1 - y0) // -1 or +1
err = dx - dy
while true:
visit(x0, y0) // process or record this cell
if x0 == x1 AND y0 == y1:
break
e2 = 2 * err
if e2 > -dy:
err = err - dy
x0 = x0 + sx
if e2 < dx:
err = err + dx
y0 = y0 + sypublic function hasLineOfSight(x0:Int, y0:Int, x1:Int, y1:Int):Bool {
var dx = hxd.Math.iabs(x1 - x0);
var dy = hxd.Math.iabs(y1 - y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx - dy;
while (true) {
if (isBlocking(x0, y0))
return false;
if (x0 == x1 && y0 == y1)
return true;
var e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}A collision system is responsible for detecting when game objects overlap or intersect and then resolving those overlaps so that objects respond physically (bouncing, stopping, sliding). Building a custom collision system involves choosing appropriate bounding shapes, implementing overlap tests, and designing a resolution strategy.
Two axis-aligned bounding boxes overlap if and only if they overlap on every axis:
overlapX = (a.x - a.halfW < b.x + b.halfW) AND (a.x + a.halfW > b.x - b.halfW)
overlapY = (a.y - a.halfH < b.y + b.halfH) AND (a.y + a.halfH > b.y - b.halfH)
collision = overlapX AND overlapYdx = a.x - b.x
dy = a.y - b.y
distSquared = dx * dx + dy * dy
collision = distSquared < (a.radius + b.radius) ^ 2Comparing squared distances avoids a costly square root operation.
Two convex shapes do NOT collide if there exists at least one axis along which their projections do not overlap. For rectangles, test the edge normals of both rectangles. If all projections overlap, the shapes are colliding.
Rather than testing every pair of objects (O(n^2)), sort objects along one axis by their minimum extent. Objects that do not overlap on that axis cannot collide and are pruned from detailed checks.
// Broad phase: spatial hash or sweep-and-prune
candidates = broadPhase(allObjects)
for each pair (a, b) in candidates:
overlap = narrowPhaseTest(a, b)
if overlap:
// Compute penetration vector
penetration = computePenetration(a, b)
// Resolve: push objects apart along the minimum penetration axis
if a.isStatic:
b.position += penetration
else if b.isStatic:
a.position -= penetration
else:
a.position -= penetration * 0.5
b.position += penetration * 0.5
// Optional: apply impulse for velocity response
relativeVelocity = a.velocity - b.velocity
impulse = computeImpulse(relativeVelocity, penetration.normal, a.mass, b.mass)
a.velocity -= impulse / a.mass
b.velocity += impulse / b.massfunction computePenetration(a, b):
overlapX_left = (a.x + a.halfW) - (b.x - b.halfW)
overlapX_right = (b.x + b.halfW) - (a.x - a.halfW)
overlapY_top = (a.y + a.halfH) - (b.y - b.halfH)
overlapY_bot = (b.y + b.halfH) - (a.y - a.halfH)
minOverlapX = min(overlapX_left, overlapX_right)
minOverlapY = min(overlapY_top, overlapY_bot)
if minOverlapX < minOverlapY:
return Vector(sign * minOverlapX, 0)
else:
return Vector(0, sign * minOverlapY)| Strategy | Best For | Description |
|---|---|---|
| Uniform Grid | Evenly distributed objects | Divide world into fixed cells; objects register in their cell(s). |
| Quadtree | Non-uniform distribution | Recursively subdivide space into 4 quadrants. Efficient for sparse scenes. |
| Spatial Hash | Dynamic scenes | Hash object positions to buckets. O(1) lookup for neighbors. |
| Sweep and Prune | Many moving objects | Sort by axis; only test overlapping intervals. |
Velocity and speed are fundamental concepts for moving objects in games. Speed is a scalar (magnitude only), while velocity is a vector (magnitude and direction). Understanding the distinction is critical for implementing correct movement, physics, and AI steering behaviors.
Speed: A scalar quantity representing how fast an object moves, regardless of direction.
speed = |velocity| = sqrt(vx^2 + vy^2)Velocity: A vector quantity representing both speed and direction.
velocity = (vx, vy)Acceleration: The rate of change of velocity over time.
acceleration = (ax, ay)
velocity += acceleration * deltaTimeEach frame, an object’s position is updated by its velocity, scaled by the time step:
position.x += velocity.x * deltaTime
position.y += velocity.y * deltaTimeThis is Euler integration, the simplest (first-order) integration method.
To move at a fixed speed in a given direction, normalize the direction vector and multiply by the desired speed:
direction = target - current
length = sqrt(direction.x^2 + direction.y^2)
if length > 0:
direction.x /= length
direction.y /= length
velocity = direction * speedThis prevents the “diagonal movement problem” where moving diagonally at full speed on both axes results in ~1.414x the intended speed.
Without deltaTime, movement speed depends on the frame rate:
// WRONG: frame-rate dependent
position += velocity
// CORRECT: frame-rate independent
position += velocity * deltaTimedeltaTime is the elapsed time (in seconds) since the last frame update.
function update(entity, deltaTime):
// Apply acceleration (gravity, thrust, friction, etc.)
entity.velocity.x += entity.acceleration.x * deltaTime
entity.velocity.y += entity.acceleration.y * deltaTime
// Clamp speed to a maximum
currentSpeed = magnitude(entity.velocity)
if currentSpeed > entity.maxSpeed:
entity.velocity = normalize(entity.velocity) * entity.maxSpeed
// Apply friction / drag
entity.velocity.x *= (1 - entity.friction * deltaTime)
entity.velocity.y *= (1 - entity.friction * deltaTime)
// Update position
entity.position.x += entity.velocity.x * deltaTime
entity.position.y += entity.velocity.y * deltaTimeA physics engine simulates real-world physical behaviors – gravity, collisions, rigid body dynamics – so that game objects move and interact realistically. The core loop of a physics engine consists of: applying forces, integrating motion, detecting collisions, and resolving collisions.
A physics engine runs a fixed-timestep update loop:
accumulator = 0
fixedDeltaTime = 1 / 60 // 60 Hz physics
function physicsUpdate(frameDeltaTime):
accumulator += frameDeltaTime
while accumulator >= fixedDeltaTime:
step(fixedDeltaTime)
accumulator -= fixedDeltaTimeUsing a fixed timestep ensures deterministic, stable simulation regardless of rendering frame rate.
Semi-Implicit Euler (symplectic Euler) – the standard for game physics:
velocity += acceleration * dt
position += velocity * dtThis is more stable than explicit Euler (which updates position first) because velocity is updated before being used to update position.
Verlet Integration – an alternative that does not store velocity explicitly:
newPosition = 2 * position - oldPosition + acceleration * dt * dt
oldPosition = position
position = newPositionVerlet is particularly useful for constraints (cloth, ragdoll) because positions can be directly manipulated while preserving momentum.
Each rigid body has:
| Property | Description |
|---|---|
position | Center of mass in world space |
velocity | Linear velocity vector |
acceleration | Sum of all forces / mass |
mass | Resistance to linear acceleration |
inverseMass | 1 / mass (0 for static objects) |
angle | Rotation angle |
angularVelocity | Rate of rotation |
inertia | Resistance to angular acceleration |
restitution | Bounciness (0 = no bounce, 1 = perfectly elastic) |
friction | Surface friction coefficient |
Forces are accumulated each frame, then converted to acceleration:
function applyForce(body, force):
body.forceAccumulator += force
function integrate(body, dt):
body.acceleration = body.forceAccumulator * body.inverseMass
body.velocity += body.acceleration * dt
body.position += body.velocity * dt
body.forceAccumulator = (0, 0) // resetThe detection phase is split into two stages:
Broad Phase: Quickly eliminate pairs that cannot possibly collide using bounding volumes (AABBs) and spatial structures (grids, BVH trees, sweep-and-prune).
Narrow Phase: For candidate pairs, perform precise shape-vs-shape tests to determine if they actually overlap and compute contact information (collision normal, penetration depth, contact points).
When two bodies collide, an impulse is applied along the collision normal to separate them and adjust their velocities:
function resolveCollision(a, b, normal, penetration):
// Relative velocity at the contact point
relVel = b.velocity - a.velocity
velAlongNormal = dot(relVel, normal)
// Do not resolve if objects are separating
if velAlongNormal > 0:
return
// Coefficient of restitution (take minimum)
e = min(a.restitution, b.restitution)
// Impulse magnitude
j = -(1 + e) * velAlongNormal
j /= a.inverseMass + b.inverseMass
// Apply impulse
impulse = j * normal
a.velocity -= impulse * a.inverseMass
b.velocity += impulse * b.inverseMass
// Positional correction (prevent sinking)
correction = max(penetration - slop, 0) / (a.inverseMass + b.inverseMass) * percent
a.position -= correction * a.inverseMass * normal
b.position += correction * b.inverseMass * normalKey constants:
slop: A small tolerance (e.g., 0.01) to prevent jitter from micro-penetrations.percent: Typically 0.2 to 0.8; controls how aggressively positional correction is
applied.For 2D rotation, torque is the rotational equivalent of force:
torque = cross(contactPoint - centerOfMass, impulse)
angularAcceleration = torque * inverseInertia
angularVelocity += angularAcceleration * dt
angle += angularVelocity * dtThe moment of inertia depends on the shape:
I = 0.5 * m * r^2I = (1/12) * m * (w^2 + h^2)function step(dt):
// 1. Apply external forces (gravity, player input, etc.)
for each body in world.bodies:
if not body.isStatic:
body.applyForce(gravity * body.mass)
// 2. Integrate velocities and positions
for each body in world.bodies:
if not body.isStatic:
body.velocity += (body.forceAccumulator * body.inverseMass) * dt
body.position += body.velocity * dt
body.angularVelocity += body.torque * body.inverseInertia * dt
body.angle += body.angularVelocity * dt
body.forceAccumulator = (0, 0)
body.torque = 0
// 3. Broad-phase collision detection
pairs = broadPhase(world.bodies)
// 4. Narrow-phase collision detection
contacts = []
for each (a, b) in pairs:
contact = narrowPhase(a, b)
if contact:
contacts.append(contact)
// 5. Resolve collisions (iterative solver)
for i in range(solverIterations): // typically 4-10 iterations
for each contact in contacts:
resolveCollision(contact.a, contact.b,
contact.normal, contact.penetration)Vectors are the mathematical building blocks of game development. A vector represents
a quantity with both magnitude and direction. In 2D games, vectors are pairs (x, y);
in 3D, triples (x, y, z). Nearly every game system – movement, physics, rendering,
AI – relies on vector operations.
A 2D vector:
v = (x, y)A 3D vector:
v = (x, y, z)Vectors can represent positions, directions, velocities, forces, or any quantity with magnitude and direction.
Component-wise addition. Used to apply velocity to position, combine forces, etc.
a + b = (a.x + b.x, a.y + b.y)Example: Moving a character by its velocity:
position = position + velocity * deltaTimeComponent-wise subtraction. Used to find the direction and distance from one point to another.
a - b = (a.x - b.x, a.y - b.y)Example: Direction from enemy to player:
directionToPlayer = player.position - enemy.positionScales a vector’s magnitude without changing its direction:
s * v = (s * v.x, s * v.y)Example: Setting movement speed:
velocity = normalizedDirection * speedThe length of a vector, computed via the Pythagorean theorem:
|v| = sqrt(v.x^2 + v.y^2)In 3D:
|v| = sqrt(v.x^2 + v.y^2 + v.z^2)Optimization: When only comparing distances (not needing the actual value), use squared magnitude to avoid the expensive square root:
|v|^2 = v.x^2 + v.y^2Produces a unit vector (length 1) pointing in the same direction:
normalize(v) = v / |v| = (v.x / |v|, v.y / |v|)A normalized vector represents pure direction. Always check that |v| > 0 before
dividing to avoid division by zero.
Example: Get the direction an entity is facing:
facing = normalize(target - self.position)A scalar result that encodes the angular relationship between two vectors:
a . b = a.x * b.x + a.y * b.yIn 3D:
a . b = a.x * b.x + a.y * b.y + a.z * b.zGeometric interpretation:
a . b = |a| * |b| * cos(theta)Where theta is the angle between the vectors. For unit vectors:
a . b = cos(theta)Key properties:
a . b > 0: Vectors point in roughly the same direction (angle < 90 degrees).a . b == 0: Vectors are perpendicular (angle = 90 degrees).a . b < 0: Vectors point in roughly opposite directions (angle > 90 degrees).Game dev uses:
max(0, dot(normal, lightDir))).Produces a vector perpendicular to both input vectors:
a x b = (
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
)The magnitude of the cross product equals:
|a x b| = |a| * |b| * sin(theta)In 2D, the “cross product” is a scalar (the z-component of the 3D cross product):
a x b = a.x * b.y - a.y * b.xGame dev uses:
To get a vector perpendicular to (x, y):
perp = (-y, x) // 90 degrees counter-clockwise
perp = (y, -x) // 90 degrees clockwiseUseful for computing normals of 2D edges and walls.
Project vector a onto vector b:
proj_b(a) = (a . b / b . b) * bIf b is already a unit vector:
proj_b(a) = (a . b) * bGame dev uses:
Reflect vector v across a surface with normal n (where n is a unit vector):
reflected = v - 2 * (v . n) * nGame dev uses:
class Vector2D:
x, y
function add(other):
return Vector2D(x + other.x, y + other.y)
function subtract(other):
return Vector2D(x - other.x, y - other.y)
function scale(scalar):
return Vector2D(x * scalar, y * scalar)
function magnitude():
return sqrt(x * x + y * y)
function magnitudeSquared():
return x * x + y * y
function normalize():
mag = magnitude()
if mag > 0:
return Vector2D(x / mag, y / mag)
return Vector2D(0, 0)
function dot(other):
return x * other.x + y * other.y
function cross(other):
return x * other.y - y * other.x
function perpendicular():
return Vector2D(-y, x)
function reflect(normal):
d = dot(normal)
return Vector2D(x - 2 * d * normal.x, y - 2 * d * normal.y)
function angleTo(other):
return acos(normalize().dot(other.normalize()))
function distanceTo(other):
return subtract(other).magnitude()
function lerp(other, t):
return Vector2D(
x + (other.x - x) * t,
y + (other.y - y) * t
)lerp (linear interpolation) between two vectors for smooth
movement, camera tracking, and animations.rotated.x = v.x * cos(angle) - v.y * sin(angle)
rotated.y = v.x * sin(angle) + v.y * cos(angle)| Algorithm / Concept | Primary Use Case | Complexity |
|---|---|---|
| Bresenham’s Line | Grid raycasting, line of sight | O(max(dx, dy)) per ray |
| AABB Overlap | Fast collision detection | O(1) per pair |
| Circle Overlap | Round collider detection | O(1) per pair |
| Separating Axis Theorem | Convex polygon collision | O(n) per pair (n = edges) |
| Spatial Hashing | Broad-phase collision culling | O(1) average lookup |
| Euler Integration | Simple physics stepping | O(1) per body per step |
| Verlet Integration | Constraint-based physics | O(1) per body per step |
| Impulse Resolution | Collision response | O(iterations * contacts) |
| Vector Normalization | Direction extraction | O(1) |
| Dot Product | Angle/projection queries | O(1) |
| Cross Product | Perpendicularity / winding | O(1) |
| Reflection | Bounce / ricochet | O(1) |
This file