Background

This is the first of several physics-focused projects I have worked on while entering the job market this past May. After taking a break for mental reset, I came back with a new-found motivation to reinforce coding fundamentals and experiment with fresh ideas.

There are several choices for JS modules which allow you to visually render shapes and programmed animations, but for my foray into simple physics simulations, I went with p5.js for its ease of use and it's ability to be layered on top of other HTML elements.

I spent a couple hours getting the hang of the canvas, drawing simple shapes, and the animation loop. This was fun and all, but I wanted to do something more. When I had a simple loop of a cube bouncing from edge to edge, it reminded me of a video I saw a long time ago, 3Blue1Brown's "Why colliding blocks compute pi", which explores elastic collisions on a frictionless plane.

With this, I had the idea to implement the experiment in a browser. First, I wanted to test my ability to realize a visual solution to the problem, and second, physics experiments have implementations that involve a lot of math, which sounded like a lot of fun to me.

Implementation

Using what I had learned in the first several hours of exploring p5.js, I quickly had two cubes moving together and through each other. Next was to begin implementing the collision logic.

One of first issues I ran into was how to understand how a cube - a square() in p5.js, but for the sake of the experiment, I'm using the word 'cube' - is drawn given an x and y pair of coordinates. In p5.js and most other development animation canvases, the y-axis is drawn from the top of the canvas, and increases on the positive side visually going down, rather than up. This means that when I draw a cube with a side-length of 50 pixels, the bottom corners of the cube will be 50 pixels further from the top edge. The x-axis behaves as one would assume, but this means that rectangular shapes are drawn from the top left corner based on an (x, y) coordinate.

For my implementation, I particularly wanted to utilize a radius factor for drawing the cubes, where the (x,y) position of the cube is at the center, rather than the left corner.

After reviewing the references, I found p5.js's rectMode() function. This will draw rectangles in a variety of formats, but for my purposes, I used the CENTER parameter to achieve my goal.

Next, I created cube.tsx to create a cube class and to define parameters such as mass and velocity and local helper functions which return the left and right edge values for collision checking. Collisions are then checked by computing the distance between the right cube's left edge x-coordinate and the left cube's right edge x-coordinate. If that distance is less than or equal to 0, we have a collision. If a collision is recorded, then a global collisions counter is incremented by 1. Now cubes could collide, but how do we tell it to transfer it's momentum?

This is where the Law of Conservation of Energy comes into focus. Within a system, energy cannot be created or destroyed, so in order to simulate physics, we need to adhere to this law. Thankfully, since this experiment is only considering elastic collisions on a frictionless plane, we look at the formula for the conservation of momentum, and calculate a new velocity for each cube.

With this, I could successfully take two cubes of the same mass, set one with a velocity, and watch as the energy is perfeclty conserved after a collision. However, without wall collision, the left cube cannot bounce back. To add wall collision, I checked if the position of the left cube's left edge x-coordinate was less than zero (the left edge of the canvas). If so, we invert the cube's velocity which turns it in the opposite direction. One first glance, this created a bouncing effect if the cube was impacting the wall too quickly, so I also manually reset the cubes x-coordinate to the zero plus the cube's radius. Once again, a collision with the wall will increment the global collisions counter.

Using p5's text() function, I could write the number of collisions to the canvas and begin testing with different masses.

I then ran the simulation and found that for two cubes with a mass value of 1, they would collide three times; this is aligned with the findings of 3Blue1Brown. Then, I increased the mass of the right cube to a power of 100. For the second test, a mass value of '100'. This test resulted in 31 collisions. For test three, a mass value of '10000' with a result of 314 collisions.

314 collisions, which if you imagine a decimal after the 3, you get 3.14, an approximation of Pi.

Fourth test and a mass value of 1000000 results in 3141 collisions, continuing with the trend.

Reflection

While I was astounded by the results, one thing stood out to me: For each time a user increase the mass by a power of 100, the execution time gets significantly longer. I found this to be due to that p5 runs its animation loop based on the frame rate of the display it's being used on. One idea to continue to explore is called deltaTime, which is a well-documented concept in the game development sphere that is described as "the elapsed time (in seconds) between completion of the previous frame and the current frame." This allows the developer to ensure physics simulations are consistent between devices.

With a successful implementation of deltaTime, I could increase the timescale so users can see the result of the test faster, and would give an opportunity to add more masses to test.

Result