How to Make a Physics Engine from Scratch

A physics engine simulates how objects move, collide, and react to forces. At its core, it runs a loop that repeats the same sequence every time step: apply forces, move objects, check for collisions, then resolve those collisions. Building one from scratch is entirely doable if you understand each stage of that pipeline and implement them in order, starting simple and layering complexity as you go.

The Physics Pipeline

Every physics engine follows roughly the same sequence of stages each frame, similar to how a graphics engine has a rendering pipeline. The four main stages are: force accumulation, numerical integration, collision detection, and collision response. You process them in that order, every single tick.

Force accumulation is the simplest stage. You loop through every object and tally up the forces acting on it: gravity, springs, player input, wind, whatever your simulation needs. The result is a net force vector for each object.

Numerical integration comes next. This is where you use those accumulated forces to update each object’s velocity and position. The most basic approach is Euler integration: multiply acceleration (force divided by mass) by your time step to get the new velocity, then multiply velocity by the time step to get the new position. Euler integration is easy to implement but drifts over time, especially with stiff springs or high speeds. A common upgrade is semi-implicit Euler (also called symplectic Euler), which updates velocity first, then uses the new velocity to update position. That single change dramatically improves stability with almost no extra code.

After integration, you run collision detection to find every pair of objects that overlap or are about to overlap. Finally, collision response adjusts the velocities (and sometimes positions) of colliding objects so they bounce apart, slide, or stick together depending on their material properties.

Fixed Time Steps and Determinism

One of the most important architectural decisions you’ll make is how to handle time. If you tie your physics update to the frame rate, the simulation behaves differently at 30 FPS than at 144 FPS. At low frame rates, objects can teleport through walls because the collision check happens too infrequently. At high frame rates, floating-point rounding accumulates differently, producing subtly different outcomes.

The standard solution, established through hard lessons in the 1990s when networked games took off, is to run physics at a fixed interval and graphics at a variable one. You maintain a time accumulator that tracks how much real time has elapsed. Each frame, you step the physics simulation forward in fixed-size chunks until the accumulator is caught up:

The logic looks like this in pseudocode: while the time buffer is greater than or equal to your fixed interval, run one physics update and subtract the interval from the buffer. Your rendering code then runs every frame regardless, interpolating between the last two physics states for smooth visuals.

This approach gives you determinism: the same inputs always produce the same outputs. That property is essential for replays, ghost recordings, unit testing, and multiplayer via lockstep simulation (where players only send each other their inputs, and each machine simulates identically). A fixed interval of 1/60th or 1/120th of a second is typical for games.

Collision Detection in Two Phases

Collision detection is usually the most computationally expensive part of the pipeline, so engines split it into two phases: broad phase and narrow phase.

The broad phase quickly eliminates pairs of objects that are obviously too far apart to collide. The simplest broad-phase technique is to wrap every object in an axis-aligned bounding box (AABB) and check which boxes overlap. You can accelerate this further with spatial data structures. A uniform grid divides the world into cells and only tests objects that share a cell. A bounding volume hierarchy (BVH) organizes objects into a tree so you can prune entire branches. For a first implementation, a simple sort-and-sweep along one axis works well: sort all bounding boxes by their left edge, then walk through the sorted list checking for overlaps.

The narrow phase takes the pairs that survived the broad phase and computes exact contact information: whether they truly overlap, where the contact point is, how deep the penetration is, and what direction the contact normal points. For circles or spheres, this is straightforward vector math. For convex polygons, the Separating Axis Theorem (SAT) checks whether any axis exists that separates the two shapes. If no separating axis exists, the shapes overlap. For 3D convex shapes, the GJK algorithm is the standard approach. It’s more complex to implement but handles arbitrary convex geometry efficiently.

Collision Response and Impulses

Once you know two objects are colliding, you need to push them apart and change their velocities. The basic tool here is the impulse: an instantaneous change in momentum applied along the contact normal.

For two colliding objects, you calculate the relative velocity at the contact point, project it onto the contact normal, and then compute an impulse magnitude that reverses (and scales) that relative velocity. The scaling factor is the coefficient of restitution: 0 means the objects absorb all energy and don’t bounce, 1 means a perfectly elastic bounce. You divide the impulse between the two objects based on their inverse masses, so heavier objects are affected less.

For objects with rotation, the calculation expands to account for angular velocity at the contact point and the moment of inertia. The principle is the same: compute relative velocity (now including rotational contribution), solve for the impulse magnitude, and apply it as both a linear impulse and a torque.

Friction adds a tangential impulse perpendicular to the contact normal. You calculate it similarly, clamping its magnitude so it doesn’t exceed the normal impulse multiplied by a friction coefficient (this is the Coulomb friction model). Without friction, everything slides like it’s on ice.

Position correction is also important. Even with correct impulses, floating-point errors cause objects to sink into each other over time. A simple fix is to nudge overlapping objects apart along the contact normal by a fraction of the penetration depth each frame. This is sometimes called the “Baumgarte stabilization” approach.

Constraints and Joints

Constraints are rules that restrict how two bodies can move relative to each other. A hinge joint forces two bodies to share a pivot point. A distance constraint keeps two points at a fixed separation. A contact between two non-penetrating objects is itself a constraint: “these two surfaces must not overlap.”

The math behind constraints uses a function that equals zero when the constraint is satisfied. For a distance constraint between two points, that function is the current distance minus the target distance. The engine’s job is to apply forces (or impulses) that keep this function at zero.

The practical approach used in most real-time engines is sequential impulses. You loop through all active constraints and contacts, applying a corrective impulse for each one. A single pass won’t perfectly satisfy everything (fixing one constraint can violate another), so you repeat the loop multiple times. Somewhere between 4 and 10 iterations typically produces stable results for games. More iterations mean more accuracy but cost more CPU time. This is the solver used in Box2D and many other well-known engines.

In pseudocode, the solver loop looks like: for each solver iteration, loop through all contact manifolds and apply impulses, then loop through all constraints and apply impulses. The order you process them affects convergence speed, but even a naive ordering works reasonably well with enough iterations.

Sleeping Objects to Save Performance

In any scene with many objects, most of them are sitting still at any given moment. A stack of crates, a pile of debris, objects resting on the floor. Simulating all of them every frame wastes significant CPU time.

The solution is a sleep system. When an object’s velocity drops below a threshold for a sustained period, the engine marks it as “sleeping” and skips it during integration, collision detection, and constraint solving. When a sleeping object receives a collision, a force, or a direct velocity change, the engine wakes it up and resumes simulating it. Unity, for example, exposes this as a configurable sleep threshold in its physics settings.

Implementing sleep is straightforward. Track each object’s kinetic energy (or just its linear and angular speed) over a short window. If it stays below your threshold for, say, half a second, put it to sleep. When anything applies a force or collides with it, flip it back to awake. In scenes with hundreds of objects, this optimization can cut physics CPU time by 80% or more during calm moments.

Choosing 2D or 3D

If this is your first physics engine, start in 2D. The math is identical in principle but dramatically simpler in practice. Rotation in 2D is a single scalar angle. In 3D, you need quaternions to avoid gimbal lock, and the inertia tensor becomes a 3×3 matrix instead of a single number. Collision detection in 2D uses simpler algorithms (SAT on polygons, circle-vs-circle, circle-vs-polygon), while 3D requires GJK, EPA, or similar algorithms for convex shapes, plus additional work for mesh colliders.

A good first project is a 2D engine that handles circles and boxes. Implement gravity, Euler integration, AABB broad phase, SAT narrow phase, and impulse-based response. Once that works, add rotation, friction, and a sequential impulse solver. You’ll have a system capable of simulating stacks, chains, and basic ragdolls.

Practical Starting Points

Build incrementally. Your first version should be a single ball bouncing inside a box. That alone requires integration, boundary collision detection, and a basic velocity reflection for response. Add a second ball and you need pair-wise collision detection and proper impulse math. Add ten balls and you need a broad phase. Add boxes and you need SAT and rotational dynamics. Each step teaches one new concept without overwhelming you.

Erin Catto’s GDC presentations on Box2D are some of the best free resources available. They walk through impulse-based dynamics, sequential impulses, and constraint formulation with clear diagrams and working code. The Box2D Lite source code is specifically designed as a teaching tool: a complete, readable 2D physics engine in about 500 lines of C++.

For languages, C++ gives you the most control and performance, but physics engines have been built successfully in JavaScript, Python, Rust, and C#. The algorithms are language-agnostic. What matters is understanding the math and getting each pipeline stage working correctly before optimizing anything. Profile first, then optimize the bottleneck, which will almost always be collision detection.