is an interactive software for solving level-set evolutions (of Hamilton-Jacobi partial differential equations). More simply, this code ''flows'' a given shape.
Margo was built on Field Play, during which things were added (and broken). Many thanks to previous developers for the flow visualization – @anvaka, @mourner, @skeeto – and HJ-solver – @ian-mitchell, @schmrlng – without whom this wouldnt exist.
NOTE (6/2025): there is a browser bug --> refresh once to workaround.
The level set is a shape that matches the zero-level of a function, called the value or energy. This value evolves based on a momentum, called the Hamiltonian.
The flow, shape and momentum are your choice; define them.
We often choose the momentum to match a flow (aka vector-field) which models a dynamic system, e.g. jet flight. Thus, the shape will move with the flow by default (but can be made to do whatever).
Click the screen before applying any of the following:
f– displays flowdelete + f– deletes the existing flow
w– displays coded BCi&o– switches defining in/out BC (usecto see)
c– fills BC & value interiorl– displays BC & value levelsenter– begins the value evolutiondelete + v– deletes the existing value
The momentum may be defined optimally with respect to control of the flow. The value evolution then reveals the best path, e.g. least time & most reward, to/from the shape and the corresponding optimal control actions. In fact, solving or learning this evolution is how a modern robot guides itself (so maybe I am not the only one dreaming in shape).
I built this because I wanted a faster way to test and discuss new ideas, but also because I wanted to share the beauty of these evolutions. I would love to toy with this day and night but would need to clone myself, which is to say, contributions are more than welcome.
The HJB value evolution is built on-top of the clever particle bank texture idea (described by @mourner here, improved by @anvaka). However, to evolve the value within this framework, we must resort to the traditional usage of textures as interpolators of structured data.
Hence, we define additional textures for (1) the user-defined boundary condition and (2) the corresponding evolved value. To evolve the value over the GPU parallelized framework, the numerical integration is (akin to the particles) is performed in an ''atomic'' fashion for each point in the texture. I have only had the time thus far to implement simple first-order integrators so you may notice some evolutions explode (in such a case, decrease timestep).
To compute the value integration, the shaders query the texture for neighboring values to compute the finite differences (upwind), and pull in the user defined vector field, and then solve the HJB PDE's local value for dV/dt with these objects.
All in all, its relatively simple since no extra vectorization is needed (beyond GLSLs native ability), and to my surprise, pretty fast.
As of now, this is a limited proof-of-concept art exhibit. One will find many user-defined evolutions are unstable and explode, requiring more sophisticated algos (WENO5 implemented but needs testing). For now, use a smaller dt. Additionally, the observed speed suggests that this might be a feasible method for higher dimensions but that requires N-dimensional textures...
As I said before, contributions are welcomed.
I added to @anvaka's query-state the shape and momentum definitions so all initial conditions may be shared with the link. For some inspiration, see below.
