[index] [pictures] [about] [how]

Flowfields in Rust and WebAssembly

Table of Contents

Introduction

Inspired by this Low Byte Productions video I wanted to create a visualization of particles moving along a 2D vector field.

Here is the result:

Time per frame: - ms | Frames per second: -
Reset form

Some explanations on the parameters will be given below. To start and stop the animation click on the canvas; to apply changes to the paramters press enter.

In Firefox the image can be exported by pausing it first and then selecting "Save Image As…" from the context menu that shows up if you right-click the canvas. For other browsers it should work similarly.

Some explanations

The dots above are particles that move along a (constant in time) vector field

\begin{align} \vec{v}: \vec{x} \mapsto \vec{v}(\vec{x}), \quad \vec{x} \in [0, 1] \times [0, 1]. \end{align}

In the beginning, each particle is initialized at a time \(t_0\) with a random position \(\vec{x}(t_0) \in [0, 1] \times [0, 1]\) and a random lifetime \(\tau \in \left[0, \tau_{\mathrm{max}}\right]\). In one rendering step we update the position according to

\begin{align} \label{eq:update-step} \vec{x}(t_0) \to \vec{x}(t_0 + \delta t) = \vec{x}(t_0) + \vec{v}(\vec{x}(t_0)) \, \delta t. \end{align}

After \(\tau\) rendering steps the particle is destroyed and a new particle is created at a new random position. Moreover after some time, the entire canvas is cleared and everything starts from the beginning.

In summary, we have the following parameters:

  • The vector field function \(\vec{v}\)
  • The total number of particles
  • The maximum lifetime of a particle \(\tau_{\mathrm{max}}\)
  • The number of samples \(N\) of the vector field \(\vec{v}\)
  • The lifetime of the grid

Instead of evaluating the vector field \(\vec{v}\) at the position \(\vec{x}\) of a particle, we uniformly sample \(\vec{v}\) at \(N \times N\) grid points of the unit square. In the update step \label{eq:update-step} we use the value of \(\vec{v}\) at the grid point closest to the position \(\vec{x}\). In each square of size \(1/N \times 1/N\) the trajectories of the particles are therefore of approximated by a straight line.

If the number of samples is high enough, the sampling procedure gives a good approximation to \(\vec{v}\) and the particle trajectories look smooth. For lower number of samples, the \(1/N \times 1/N\) squares of straight lines become visible and the image exihibts a block structure.

Here are four example images next to each other:

flowfield-samples.png

In the future it would be nice to have control over more parameters (e.g. color) and to react to more user input (e.g. clicks on the canvas). It would also be nice to allow the user to type a vector field or even a scalar function and compute its gradient automatically (possibly numerically).

Implementation details

The main part of the code is written in Rust, in particular the part that updates the position of the particles. This Rust program is compiled to WebAssembly with wasm-pack build --target web. The wasm-pack command not only creates the WebAssembly binary, but also a JavaScript file that exports classes and functions for the parts of the Rust program annotated with #[wasm_bindgen].

The Rust program renders the particles to a pixel buffer that is shared with the JavaScript program. This buffer is painted to the canvas using the putImageData() method. Another (possibly simpler) method would be to render the particles from JavaScript.

The Rust code is mirrored in this repository. This was the first time I played around with Rust and WebAssembly and this is reflected in the quality of the code.

References

RSS: [index] [pictures] Last build: 2025-01-05 16:27:20