Home

Paint

Instructions

Drag your pointer across the canvas to paint the canvas.

Description

This visualization simulates fluid dynamics. See the Smoke visualization for a similar effect, which uses the same code, but a different viscosity.

How it's made...

Fluid Dynamics

This is an implementation of Jos Stam's fluid dynamics method presented at GDC in 2003, which you can find here. As opposed to other methods, this one focuses on making real-time fluid dynamics, sacrificing accuracy for speed. Essentially, it doesn't need to be perfect, it just needs to be "good enough."

There is a bunch of math involved in implementing fluid dynamics, to be sure. However, at the highest level this can broken down into two things:

  1. Calcuating the velocity for each pixel.
  2. Using the velocity to solve for fluid density at each pixel.

Averages

Most of the steps involved actually come down to something fairly straightforward: Finding the average value amongst a pixel's neighbors. The actual algorithm is more complex than this, but that's the underlying concept.

For example, if the velocity at the center-most pixel is (10, 200), but all the pixels around it have a velocity of (0, 0), then the next frame's velocity on that pixel will be significantly dampened. The same thing occurs for density.

Rainbows

The output of this method is essentially just fluid density at each pixel. Often you will see this represented in black and white, where black represents 0 density, and 255 is full density.

In order to portray colors, here the density has been split into 3 separate density values: Red, Green, and Blue. (When the mouse button is pressed, I actually choose an HSV value converted to RGB because it's easier to ensure the color is bright.) Dealing with the 3 color densities essentially means I must triplicate the amount of density calculations that must be performed. These calculations are not cheap, so I apologize that your computer fan may be running a bit higher right now.

The code essentially looks like this:

void step() {
    velocityStep(velocities, initial_velocities, viscocity, delta_time );
    dampen(reds, greens, blues, dampening, delta_time);
    densityStep(reds,   initial_reds,   velocities, diffusion, dampening, delta_time);
    densityStep(greens, initial_greens, velocities, diffusion, dampening, delta_time);
    densityStep(blues,  initial_blues,  velocities, diffusion, dampening, delta_time);
}

The shortest distance between two points

... is a line. But lines are are also handy for filling in the gaps between two points. In this case, the amount of time that passes between one frame and the next can leave a gap between the mouse position last frame and this frame. This means that there would be less smoke/paint dispensed to the canvas than there should be.

The solution is the good ol' dependable Bresenham's line algorithm. This handy algorithm will draw all the pixels between two points, thus filling in the gaps.

Webassembly

Like all visual simulations on this site, this project was programmed using C++, compiled with clang targeting Webassembly. Why C++ you ask? Because it allows me to manage and access memory in a more optimized way. It's challenging, as of this writing I am not aware of any proper debugging tools available... but I get by with console logging.