A real-time fluid simulator built in C++ and OpenGL, implementing Jos Stam's Real-Time Fluid Dynamics for Games. The solver operates on a 2D grid and runs two coupled steps each tick: a velocity step (advection, diffusion, and pressure projection) and a density step (advection and diffusion of a scalar dye field). The density field is rendered as a texture on a full-screen quad. An ImGui panel exposes simulation parameters, and mouse input lets users inject density and velocity in real time.
Advection moves quantities along the current velocity field. The implementation uses a semi-Lagrangian (backward-trace) scheme: for each grid cell, trace a particle backward in time by dt along the velocity field, then bilinearly interpolate the quantity at that upstream location. This approach is unconditionally stable for any time step size, which is the key property of the Stam stable fluids method. The same advection routine handles both the velocity components and the scalar density field.
After advection the velocity field is generally not divergence-free, meaning the fluid would gain or lose mass. The pressure projection step corrects this. It first computes the divergence of the velocity field, then solves a Poisson equation for pressure using Gauss-Seidel (Jacobi) iteration. Each iteration updates every cell's pressure based on the average of its four neighbors minus the divergence contribution. After convergence the pressure gradient is subtracted from velocity, yielding a curl-only (divergence-free) field that conserves mass.
Diffusion spreads quantities outward from concentrated regions, modeling viscosity for velocity and dye spread for density. Rather than an explicit finite-difference update (which requires tiny time steps for stability), the solver uses an implicit formulation solved again via Gauss-Seidel iteration. This keeps the simulation stable at interactive frame rates regardless of viscosity or diffusion rate. The number of solver iterations is configurable through the ImGui interface.
Users can interact with the simulation in two ways. Holding the left mouse button injects density (dye) at the cursor position, creating plumes that advect through the field. Holding the right mouse button and dragging injects velocity: the drag vector sets both the direction and magnitude of the impulse, so fast swipes create strong vortices. The velocity vector field can also be visualized by holding V; arrows are instance-rendered and oriented in a vertex shader based on per-cell velocity magnitude and direction.
The grid edges enforce no-slip or reflective boundary conditions to prevent fluid from escaping the simulation domain. Users can also place solid obstacle cells anywhere on the grid by holding Shift and clicking. Obstacles reflect velocity at their surfaces and block density transport. Holding Shift and right- clicking removes obstacles. Boundary cells can be given custom colors through an ImGui color picker, which creates some artistic possibilities when building scenes. Three preset scenes (crosswind, whirlwind, water fountain) ship with the project to demonstrate different configurations.