As I may (or may not) have mentioned, I have been piecing together my own Haskell tutorial that I hope to make available soon (what time is soon? With respect to deadlines, all times are soon). I decided to write the tour de force example program with HOpenGL as I've used OpenGL in the past and, I admit, I was a little ticked when the Haskell School of Expression used the Haskell X11/Win32 overlay. The reason being that because I play with sundry operating systems and programming languages I appreciate lessons that are transferable. If this is coding, I appreciate being able to pick up the bindings in another language and make use of it there, rather than using some languages jerry-rigged utility that I can't use anywhere else.
The thing is, on the other project for which I've used OpenGL, I did it in pure 3D, but this example is a simple 2D game. This makes most of the rendering easier (or rather, less verbose), however this combined with the conditions of the game means that I have to redo the mouse picking. I also noticed that there isn't a lot available on this rather arcane condition: 2D mouse picking in HOpenGL with GLUT. So, post-odyssey, here is the mad computer scientist's lab report.
OpenGL, being a forward thinking spec, was written with 3D specifically in mind. This is well and good, we like 3D, and, for the most part, it is easy to ignore depth when rendering and get something that is 2D-ish out of the system.
GLUT offers a simple event handler for passive motion (i.e. motion when there is no mouse button pressed). In Haskell, the signature is of the form:
type MotionCallback = Position -> IO ()
Position has the form Position x y, where x and y are expressed in pixels. We can set the new "target" equal to these coordinates, with one problem: OpenGL does not measure its coordinate system in pixels (come to think of it, I haven't the foggiest notion what it IS based on; I just had to develop something of a feel for it with practice). So we need to convert from pixels to the OpenGL coordinate system. Here is the finished code that accomplishes this:
viewp <- get viewport
pm <- get (matrix $ Just Projection) :: IO (GLmatrix GLdouble)
mvm <- get (matrix $ Just $ Modelview 0) :: IO (GLmatrix GLdouble)
coords@(Vertex3 x1 y1 z1) <- unProject (Vertex3 (fromIntegral x) (fromIntegral y) 0)
Providing the Z value in Vertex3 as 0 is important for the 2D aspect of this. If we were doing 3D picking, we would either render the scene to the back-buffer with color coding and read the color of the pixel where the mouse was located (the method used in Latrunculi) or we would use unProject twice: once with Z = 0 and the other time with Z = far plane and use these values to create a pick ray. x1 and y1 are the OpenGL coordinates we will need in 2D--with one caveat. The OpenGL axis is inverted relative to X11/Win32 windowing systems.
The OpenGL FAQ gives the way to do this as taking the WindowHeight - y1. This assumes the integer form of the OpenGL commands which is not present in the current HOpenGL bindings. What I found works is to invert the y1 coordinate in the following manner:
(x1, 0.0 - y1)
Which is, admittedly, the same as multiplying y1 by -1. So we literally invert the Y-Coordinate and presto! we have the coordinates we need.
As usual, the culprits in figuring things like this are the stupid little things: getting the type signatures correct in the first two lines and finding out (or rather, remembering) that the axis needs to be inverted.