GLUT is the simplest way to get started with OpenGL. It's a window system toolkit designed specifically for writing OpenGL programs. The Haskell Platform comes complete with a set of Haskell bindings to GLUT. The documentation doesn't give a whole lot of help regarding how to get started, but thankfully there's a huge bundle of examples within the Cabal package that help you get started.
To draw the fluid dynamics results, I needed to have some representation of the various state.
State encapsulates the various bits necessary to draw the fluid dynamics simulation. This includes the data (density and velocity) together with state about the mouse position and so on. This is stored in an
IORef that provides a mutable reference within the IO monad. IORef seems very simliar to Clojure's references, with similar options for changing the state inside the reference via a supplied function.
OpenGL supports several primitive types, including lines, points, quads and triangles. Each of these primitives is specified using a sequence of vertices. For example, lines are specified as a series of pairs, and a point is drawn between each pair. Similarly quads are specified using groups of four points. For drawing the fluid we use the GL_QUAD to specify a series of squares. The colour of each vertex is the density at that particular point. In order to make it look pretty the color is based on not just the density but the x and y co-ordinates. This gives green in the top-left corner down to purple in the bottom right. It's not based on any clever principles, I just thought it looked nice!
To draw primitive types the Haskell GLUT library exposes a function called
renderPrimitive which takes the PrimitiveMode (e.g. Lines / Quads / Polygon) and an IO action. The IO action represents the drawing of the list of points, so all we need to do is loop through each point and construct an IO action to choose a color and create the vertex.
Similarly, to draw the velocity field we just need to draw a series of lines.
Putting this all together with a few event handlers and timers gives a little OpenGL application (compile with
ghc -O2 --make -lglut Main.hs MFluid.hs - I appear to need to specify -lglut manually on my system at least?).
I was quite suprised about how easy it was to get something together in Haskell to do this. Reading the web, you'd guess that it would require writing a monad tutorial and getting a deep understanding of category theory. In reality, you just download the Haskell platform and get going. There's no need to understand all the details about what's going on under the hood straight away (though no doubt it helps!).
The full code is available here. Any suggestions for improvements greatly appreciated!