Introduction
Hello! In our last lesson, you built a decoupled collision event system. This was a crucial architectural step, allowing our engine to cleanly separate the detection of a collision from the reaction to it. We now have a system that can publish "a collision happened" events, and various other systems can subscribe to these events to perform actions like playing sounds, updating scores, or, most importantly, triggering a physics response.
This lesson marks the beginning of our "Simple Physics Engine" module. We're going to build the system that makes objects move, accelerate, and feel like they have momentum. To do that, we'll focus on this lesson's main goal: to integrate velocity and acceleration into a fixed update loop using Euler integration.
You'll learn about two core concepts:
- Numerical Integration: The mathematical technique used to simulate the equations of motion over discrete time steps. We'll focus on a simple and effective method called Euler integration.
- Fixed Timestep: The architectural pattern that decouples our physics simulation from the rendering frame rate, ensuring stable and predictable behavior on any computer.
Your background in engineering has prepared you for the underlying principles here, as we'll be dealing with numerical solutions to differential equationsโa concept you've likely encountered in your studies.
The Physics of Motion
At the heart of physics simulation are the fundamental equations of motion, which you'll recognize from your physics courses. Given an object with mass , and a net force acting on it, its acceleration is given by Newton's second law:
Acceleration is the rate of change of velocity () over time, and velocity is the rate of change of position () over time. This gives us a system of first-order ordinary differential equations:
To simulate this on a computer, we can't solve these equations continuously. Instead, we approximate the solution by taking small, discrete steps forward in time. This process is called numerical integration.

Euler Integration: Stepping Through Time
The simplest method for numerical integration is called Euler integration. It works by approximating the next state of a system (position and velocity) using its current state and a small time step, which we'll call delta time or dt.
Let's start with a formal derivation, which will likely feel familiar from your calculus or numerical methods courses.
Deriving Forward Euler and Backward/Implicit Euler Integration Schemes for Differential Equations
The video 'Deriving Forward Euler...' from Steve Brunton provides a great mathematical foundation for what we're about to do. It explains how to get the Euler update rule by approximating a derivative using a finite difference.
Watch from the beginning to 9:43. Pay attention to how the continuous differential equation x_dot = f(x) is discretized into an update rule x_k+1 = x_k + dt * f(x_k). The visual explanation at the end makes the concept very intuitive.
As the video showed, we can approximate the change in position and velocity over a small time step dt as:
change in position โ velocity * dtchange in velocity โ acceleration * dt
This leads to a straightforward implementation. However, the order in which we calculate these updates matters a great deal.
Now, let's read a classic article for game developers, 'Integration Basics' by Glenn Fiedler. It explains the practical differences between two variants of Euler integration.
Read the article from the beginning up to the 'Conclusion'. Focus on the distinction between 'Explicit Euler' and 'Semi-implicit Euler', especially the simple code change and the significant impact it has on stability. The conclusion summarizes the key recommendation.
Key Takeaways from "Integration Basics":
-
Explicit Euler Integration:
// Update position using the OLD velocity position = position + velocity * dt; // Update velocity velocity = velocity + acceleration * dt;This method is simple but often unstable. In many physical systems (like springs), it tends to add energy over time, causing simulations to "explode".
-
Semi-Implicit Euler Integration:
// Update velocity FIRST velocity = velocity + acceleration * dt; // Update position using the NEW velocity position = position + velocity * dt;By simply swapping the order of operations, we get a much more stable integrator. It tends to conserve energy better and is the standard choice for physics in most commercial game engines due to its simplicity and stability. We will use this method.
The Problem with Frame Rate: Why We Need a Fixed Timestep
Now that we know how to step our simulation forward by dt, we need to decide what value dt should have. A naive approach is to measure the time taken by the last frame and use that as dt. This is called a variable timestep, and it's a recipe for disaster in physics simulations.
Let's explore why this is a problem and what the correct solution is.
The video 'Getting The Game Loop Right' by Vittorio Romeo provides an excellent visual demonstration of the problems with a variable timestep and introduces the concept of a fixed-step loop.
Watch from the beginning to 07:30. This will guide you through three stages: the basic (broken) loop, the delta-time-scaled (better, but still flawed) loop, and finally the fixed-step (correct) loop.
The video makes it clear: to have a stable, deterministic, and predictable physics simulation, we must separate the physics update rate from the rendering frame rate. The physics simulation should always advance in constant, fixed time steps.

Now, let's dive into the implementation details of this fixed-step loop using a technique called an accumulator.
The article 'Fix Your Timestep!' by Glenn Fiedler is the definitive guide on this topic. It provides the exact code structure we need to implement a robust, frame-rate-independent game loop.
Please read the sections 'Variable delta time' to understand the pitfalls, and then 'Free the physics' for the final, correct implementation using an accumulator. This second section contains the exact logic we will implement.
The core idea is:
- Measure the real time that has passed since the last frame (
frameTime). - Add this
frameTimeto anaccumulatorvariable. - Run the physics update in a
whileloop, with a fixeddt, as long as theaccumulatorhas enough time stored in it. - Subtract
dtfrom theaccumulatorfor each physics step taken. - Render the game state once per frame, after the physics loop is done.
Implementation: Building the Physics System
Let's integrate these concepts into our C engine.
1. The PhysicsBody Component
First, we need a component to store the physical properties of an entity. In your components.h (or a new physics_component.h), define the PhysicsBody struct.
// In your components header file
// ... other component structs
typedef struct {
vec3 velocity;
vec3 acceleration;
float mass;
} PhysicsBody;
// Remember to add it to your World struct and entity manager functions!
You'll need to update your entity/world management code to handle this new component, just as you did for Transform and Collider. For now, we'll only use velocity and acceleration. We'll use mass in the next lesson.
2. The physics_system
Create a new pair of files: physics_system.h and physics_system.c.
physics_system.h
#ifndef PHYSICS_SYSTEM_H
#define PHYSICS_SYSTEM_H
#include "ecs.h" // Your entity-component-system header
// Updates the position of all entities with a physics body
// using semi-implicit Euler integration.
void physics_system_update(World* world, float dt);
#endif // PHYSICS_SYSTEM_H
physics_system.c
#include "physics_system.h"
#include <cglm/cglm.h> // Or your math library
void physics_system_update(World* world, float dt) {
// Iterate over all entities that have both a Transform and a PhysicsBody
for (int i = 0; i < world->entity_count; ++i) {
// Check if entity has the required components
if ((world->entity_masks[i] & (COMPONENT_TRANSFORM | COMPONENT_PHYSICS_BODY))
== (COMPONENT_TRANSFORM | COMPONENT_PHYSICS_BODY)) {
Transform* transform = &world->transforms[i];
PhysicsBody* physics = &world->physics_bodies[i];
// --- Semi-implicit Euler Integration ---
// 1. Update velocity
vec3 scaled_accel;
glm_vec3_scale(physics->acceleration, dt, scaled_accel);
glm_vec3_add(physics->velocity, scaled_accel, physics->velocity);
// 2. Update position
vec3 scaled_vel;
glm_vec3_scale(physics->velocity, dt, scaled_vel);
glm_vec3_add(transform->position, scaled_vel, transform->position);
}
}
}
(Note: You might need to adjust the code to match your specific ECS implementation, such as how you access components.)
3. The New Main Loop in main.c
Finally, let's restructure your main loop in main.c to use the fixed timestep with an accumulator.
// In main.c
#include "physics_system.h"
// ... other includes
// You will also need a function to get high-resolution time.
// On Windows, you can use QueryPerformanceCounter.
// On Linux/macOS, you can use clock_gettime.
// For now, let's assume you have a function `get_time_in_seconds()`.
// GLFW provides one: glfwGetTime().
int main(void) {
// ... Initialization code (window, ECS, entities etc.) ...
// --- Create a test entity ---
int cube_entity = world_create_entity(&world);
entity_add_transform(&world, cube_entity, (Transform){{0.0f, 0.0f, -5.0f}});
entity_add_physics_body(&world, cube_entity, (PhysicsBody){
.velocity = {1.0f, 1.0f, 0.0f}, // Give it some initial velocity
.acceleration = {0.0f, -0.5f, 0.0f}, // Give it some downward acceleration
.mass = 1.0f
});
// Add a mesh/model component so you can see it!
// --- Fixed Timestep Game Loop ---
double t = 0.0;
const double dt = 1.0 / 60.0; // Physics timestep (e.g., 60 Hz)
double currentTime = glfwGetTime();
double accumulator = 0.0;
while (!glfwWindowShouldClose(window)) {
double newTime = glfwGetTime();
double frameTime = newTime - currentTime;
currentTime = newTime;
accumulator += frameTime;
while (accumulator >= dt) {
// Update physics with a fixed delta time
physics_system_update(&world, (float)dt);
accumulator -= dt;
t += dt;
}
// --- Rendering ---
// Your existing rendering code goes here.
// It runs once per frame, independent of physics updates.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// render_system_draw_entities(&world, ...);
glfwSwapBuffers(window);
glfwPollEvents();
}
// ... Cleanup ...
return 0;
}
When you run this code, you should see your cube move diagonally up and to the right, while slowly accelerating downwards, tracing a parabolic arc. This movement will be smooth and consistent, regardless of your computer's speed or the rendering frame rate.
Conclusion
Congratulations! You have just implemented two of the most fundamental concepts in game physics. Your engine now has a proper pulse, with a physics heart beating at a steady, fixed rate.
Key Takeaways:
- Physics simulation is an approximation of continuous motion, solved with numerical integration.
- Semi-implicit Euler is a stable, simple, and effective integration method for games. It's crucial to update velocity before updating position.
- A fixed timestep loop with an accumulator is essential for creating a stable and deterministic physics simulation that is independent of the rendering frame rate.
Preview of the Next Lesson:
Our physics simulation is running, but it's not very dynamic. The acceleration is hard-coded. To make our world interactive, we need to be able to change acceleration on the fly. In the next lesson, you will learn how to apply forces, like gravity, to entities. This will involve using the mass property of the PhysicsBody and the formula a = F/m to calculate acceleration each frame, paving the way for jumping, gravity, and other dynamic behaviors.