Simulations are fun to watch. You code some basic rules, and as time progresses, you start to see the emergent behavior. Some are more deterministic like classical mechanics, and others are hard to encode as is, like flying behavior of a flock of birds.

Let’s try to simulate how a ball would behave under the force of gravity. One of Newton’s laws tells us that an object would continue to move without stopping unless there is an external force operating on it. The force (in our case, gravity) would change the acceleration of an object. It can’t change its speed (aka velocity) or position directly. Without getting into Calculus, acceleration created by force then changes an object’s velocity, which ultimately changes the position of the object.

In physics, these changes are being done continuously. For computer to simulate it, we need to make it discrete. We calculate the ball movement for a short time interval. If we do that movement per 1 second, we would see the ball teleporting on a screen every 1 second. If we do the same calculation 60 times in a second, we would see a smooth movement of the ball on a screen. So we have 1/60 seconds to calculate velocity and position based on “current” acceleration.

Let’s try to encode this knowledge in code,

struct Vec2 {
    float x, y;
};

struct Particle {
    Vec2 position;
    Vec2 velocity;
    Vec2 acceleration;
};

We are using the same type Vec2 to represent a position (stationary) and a velocity/acceleration (movement). To simulate falling of a ball, we don’t need to understand the difference between them. Since it’s a 2D plane on which we are simulating, we need x and y values.

void step(Particle& particle, float dt) {
    particle.velocity += particle.acceleration * dt;
    particle.position += particle.velocity * dt;
}

This is our discretized calculation of continuously changing of properties of an object. Our simulation doesn’t have any other forces other than gravity. It allows us to make our code pretty simple by setting the object’s acceleration to the same acceleration would added by the force of gravity, which is 9.81 m/s². In simple terms, the dt is our time interval. It could be 1 second or 1/60 second. At a given point, we update velocity for a given time interval and based on that we update position. It gives us the final position of an object after dt time interval. Once we have it, we draw it on the screen.

Particle ball =  Particle{
    .position = to_world(Vec2{WIDTH / 2.0f, HEIGHT / 2.0f}), // center of screen
    .velocity = Vec2{0.0f, 20.0f}, // going up
    .acceleration = Vec2{0.0f, -9.81f} // gravity
};

// sim loop
while (...) {
    float dt = // time elapsed since last frame
    step(ball, dt);
    // draw the ball
}

Let’s render a ball at the center of the screen, already moving up and getting affected by gravity. Over time, it will stop moving up and start falling down.

Ball Drop Simulation

The falling of a ball is deterministic thanks to classical mechanics. We also made it simple by constraining ourselves with only one force acting on an object. We can extend our code to calculate an acceleration because of multiple forces acting upon an object. Think of gravity, air drag and wind affecting a trajectory of a bullet or even fascinating simulation like Boids, flying behavior of a flock of birds. Underlying calculation would still be the same, calculate next position of elapsed time, just added by acceleration calculation instead of a single constant gravity acceleration.

Complete code