Previous work
I looked around for existing code or libraries to do the fluid simulation for me. There are some, but most are either GPL-licensed or too expensive, so they are out of the question for a small-time independent developer like me. Some publications exist on the topic, most notably Jos Stam’s famous Real-Time Fluid Dynamics for Games from 2003. Mick West describes this algorithm in detail in his article Practical Fluid Dynamics. He provides a great overview of the basics, geared towards programmers. A somewhat more mathematical, but broader overview is in Michael Gourlay’s brilliant Gamasutra article Fluid Simulation for Video Games. He gives explanations of many techniques, their respective advantages and drawbacks. Many other interesting (academic) links are on the CFDtoy blog.
Basically, there are two ways to go about fluid simulation: grid-based methods (so-called Eulerian) and particle-based methods (Lagrangian). Stam’s method is an example of an Eulerian method. Of the Lagrangian method, the SPH method is the most widely used. It simulates particles and dictates how they interact with each other.
The largest advantage of particle methods is that they are easy and intuitive to understand. The largest drawback is that the interactions between particles are, theoretically, quadratic in the number of particles, so some clever scheme has to be used to make it work for large numbers of particles. Another problem is that forces and pressures are difficult to estimate, because the “grid” formed by the particles is so irregular.
Grid methods do not suffer from these particular problems, and have as a major advantage that they can be represented by 2D matrices. That allows us to port them to the GPU more easily, resulting in huge speedups at the same level of detail, or more detail at the same execution speed. Grid methods are also easier to make mathematically and physically sound. On the other hand, they are plagued by numerical instability: without the right set of parameters, the simulation might blow up and leave you with matrices full of NaN values.
Code exists for both compressible and incompressible flows. Compressible simulations are somewhat more complex, because do not assume that the density is constant, as is the case for gases. For water, the density is pretty much constant regardless of the pressure, so I can assume incompressibility. An interesting point is that incompressibility is also a reasonable assumption for gases, provided that the speed does not exceed Mach 0.3, that is, 30% of the speed of sound in the gas.