Fishbowl View source on GitHub

Vector Math - Standard Library

Gallery

Motivation

Physics solvers tend to be very complex software. In the past, I toyed around with physics solvers for different kinds of geometries, but struggled to implement these algorithms in a way that was both convincing and performant. The idea for Fishbowl came from a video by the creator Pezzza. They describe the high-level implementation details of a Verlet Integration solver. This approach caught my interest.
Pezza's work is very impressive, particularly the demonstration at the end which proves the program is deterministic. However, the focus of Pezzza's project was to create a performant real-time application. I was instead interested in pre-rendered images and animations. I decided to expand the one-off demonstration at the end of Pezzza's video into a fully fledged app.

Approach

At the center of any physics solver is the physics 'step', a fixed interval of time where objects move and collide. The physics step of Fishbowl consists of five distinct phases.

// Keep all objects inside the simulation bounds
self.constrain_rect();
// Sort objects from left-most to right-most.
self.sort();
// Apply collision impulses
self.collide();
// Calculate new velocities and accelerations
self.integrate();
// Finally, advance the simulation clock
self.clock += 1;
    
Because the program doesn't run in real time, simulating over one second may take more or less than a second. Calculating a larger number of shorter time steps will make the simulation more accurate, but will take longer to process. Fishbowl uses an number of optimizations to reduce processing time. By keeping the array of objects sorted from left to right, the number of collision checks is significantly reduced. The rendering system is decoupled from the simulation, and the two are pipelined together and run in parallel.

Rendering

The method I used for drawing circles to a frame went through numerous iterations. Rendering performance is a major bottleneck, and so it was important to do so in parallel. After writing and benchmarking several software renderers, I landed on WebGPU. While quite bloated, WebGPU provides a fast, low level, and cross platform rendering API designed for async programs. This allows Fishbowl to easily integrate with both my server, which is entirely async, and the simulation, which is mostly synchronous. While I would have liked to use a bespoke solution, GPU accelerated rendering is far too quick to realistically consider any other option.

Try It Out

You can try out Fishbowl below, or download the CLI tool from my GitHub. It works best on images that are roughly square and don't contain any text. Keep in mind the web version below will be slower and generate lower quality results than the native application, as it is being run on my home server with limited resources.