Powered by Starship v1.3
The beginnings of a 2d physics engine
Jul 7 2024
1:40 PM

In this blog post, we'll be covering the basics of how iteration-based physics works,and then implementing a very simple proto-engine in C.


Let's start with some background on iterative simulations as a whole.

Essentially all computer animations are done iteratively. Computer displays have a finite refresh speed, and so continuous motion can't be rendered conventionally, but instead as a sequence of fixed frames. The same is true for the vast majority of numerical simulations. Infinite precision is essentially impossible to achieve, and so physics engines keep track of the positions, velocities, etc. of objects and then simulate their behavior over a discrete timestep.

We can start by simulating the simplest shape we can: a point. It has only two quantities we need to store: a position and a velocity, each which we can store with a pair of floats.

struct point {
  // px & py store position; vx & vy velocity
  float px, py, vx, vy;
};

Iterating such a simple point is very easy. In each timestep, the engine just has to add the velocity of the point to its position - we're ignoring external forces for now.

You might be wondering why we don't store acceleration. Newton's laws state that an object in motion will stay in motion along its trajectory. In other words, if we don't apply any forces to our point, it will mantain its velocity. Because of this we need to store velocity, but not necessarily acceleration. Instead, we apply acceleration by changing the velocity of the particle.

void iterate (struct point *p) {
  p -> px += p -> vx;
  p -> py += p -> vy;
}

Let's animate! I'm using blib, a system I created to easily create braille-based diagrams and animations.

struct point p;
// the point starts halfway down the left edge p.px = 0.0; p.py = height/2;
// moving directly right p.vx = 1.0; p.vy = 0.0;
while (true) { // simulate movement iterate(&p);
// to render, erase the whole screen and then draw the new position of p clear(); point((int) p.px, (int) p.py, 1); draw();
// approximate 50fps refresh with delay sleepms(20); }

Wow, we have a moving point!

What if we want the point to elastically bounce off of the walls?

Conceptually, when a point mass hits an immovable wall, it applies some force F to the wall, proportionally to its mass. By Newton's third law, the same force F is applied by the wall to the point mass. (If the wall wasn't immovable, there wouldn't be total reflection, and the mass ratio would have to be taken into account.)

To simulate this, we invert the velocity vector of our mass along the vector perpendicular to the wall. Because our walls are vertical and horizontal only, we can simply invert the x or y component of the point's velocity anytime it hits a vertical or horizontal wall.

// left border
if (p.px < 0.0) {
  p.px = 0.0;
  p.vx *= -1.0;
}
// top border if (p.py < 0.0) { p.py = 0.0; p.vy *= -1.0; }
// right border (coords range from 0 to width-1) if (p.px > width-1.0) { p.px = width-1.0; p.vx *= -1.0; }
// bottom border if (p.py > height-1.0) { p.py = height-1.0; p.vy *= -1.0; }

It's just like the DVD bouce effect. :)

What if we want inelastic collisions?

To do so, we simply need to reduce the restoring force F that the wall applies back to the point. We can do this by multiplying some factor like 0.8 to the inverted component of the velocity.

// left border
if (p.px < 0.0) {
  p.px = 0.0;
  p.vx *= -0.8;
}
...

Notice an interesting behavior - because of the dimensions of my terminal, the point bounces more often off of the top and bottom surfaces, so its y velocity decays faster than its x.

What other forces can we apply to the point? Let's add gravity!

We can approximate gravity as a constant force that is applied to our point. In the real world, most objects won't be moving fast enough that they experience noticeable change in the magnitude and direction of gravity, so this is fine.

Because the y axis goes downward (the top of the screen is y = 0 and the bottom of the screen is y = height-1) we increase velocity's y component to accelerate the point downward. It's a little unintuitive.

p.vy += 0.1;

I also added a bit of "friction" to the bottom wall by multiplying 0.8 to the x velocity upon contact.

This is great, but it's very limited. It's simulating a point mass with no size or rotation. More complex colliders will require our engine to keep track of angular velocity and apply it properly.


Part 2 here!

tags: programming