We don't have a set of GLM bindings in Haskell, as far as I know, instead we'll use a library called linear, which does similar sorts of vector math.
Let's see if we can translate a vector of (1,0,0) by (1,1,0). We should get (2,1,0) as our result. I've never used the
linear package before, but it seems to have a Linear.Matrix module, which is probably what we want. Ctrl+F shows no results for "translate", but "translat" matches several of the "translation" operations. mkTransformationMat seems like what we want.
mkTransformationMat :: Num a => M33 a -> V3 a -> M44 a "Build a transformation matrix from a rotation matrix and a translation vector."
Okay, so clicking on M33 and such it seems those are all specific-dimension matrix and vector inputs. Seems good. Looking around a bit for "id", you can find identity, which can stand in for our rotation matrix for now (since we don't want rotation). Then we just give the
V3 value and we get our translation matrix. To mix that with the vector we want to move, there's a lot of funny looking operators at the top of the module for all sorts of matrix and vector mixing. The one we want is (!*), "Matrix * column vector". So let's try all that in
>stack ghci -- startup messages skipped learnopengl> import Linear learnopengl> let vec = V4 1.0 0.0 0.0 1.0 learnopengl> let trans = mkTransformationMat identity (V3 1 1 0) learnopengl> trans V4 (V4 1 0 0 1) (V4 0 1 0 1) (V4 0 0 1 0) (V4 0 0 0 1) learnopengl> trans !* vec V4 2.0 1.0 0.0 1.0
Cool, so we can kinda use the library already. One thing to keep in mind is that the
linear package uses row-major representation for all of its matrix data.
Next we're going to make a matrix that rotates our pictures by 90 degrees counter-clockwise (widdershins) and then scales it down to 0.5 of normal. For this we'll need to use the Linear.Quaternion module as well. The axisAngle function seems like exactly what we want. It takes an axis as a V3 and an angle in radians and gives us the Quaternion to make such a rotation back. Our axis is (0,0,1), and our angle is 90 degrees, which is pi/2 radians. Remember that there's 2*pi radians in a full circle, so the basic angles are usually just a fractional portion of that. There's all sorts of tables on any number of websites if you want it in chart form. We can use the (*!!) operator to scale a matrix.
Putting this all together, and applying a little pretty printing to the output ourselves, we get something like this:
learnopengl> let rotQ = axisAngle (V3 0 0 1) (pi/2) learnopengl> let rotM33 = fromQuaternion rotQ learnopengl> let rotM33' = rotM33 !!* 0.5 learnopengl> mkTransformationMat rotM33' (V3 0 0 0) V4 (V4 1.1102230246251565e-16 (-0.5) 0.0 0.0) (V4 0.5 1.1102230246251565e-16 0.0 0.0) (V4 0.0 0.0 0.5 0.0) (V4 0.0 0.0 0.0 1.0)
That looks... pretty close? Remember that the
e-16 bits are like "*10^(-16)", so those are very small numbers.
Rotating A Face (Static)
Let's try this in our program.
-- setup our transformation let rotQ = axisAngle (V3 (0::GLfloat) 0 1) (pi/2) let rotM33 = fromQuaternion rotQ let rotM33' = rotM33 !!* 0.5 let transformMatrix = mkTransformationMat rotM33' (V3 0 0 0) transP <- malloc -- remember that we need to switch from row-major to column-major when we give it to OGL. poke transP (transpose transformMatrix)
And then in the main loop we also set our uniform value
-- the transform uniform transformLoc <- glGetUniformLocation shaderProgram transform glUniformMatrix4fv transformLoc 1 GL_FALSE (castPtr transP)
And now we've got a tiny rotated picture! Code
Rotating A Face (Dynamic)
Making the stuff rotate over time is as easy as getting the time value like we did for the pulsing green triangle example. We'll also apply a translation into the bottom right corner like the C++ version does.
-- Before Main Loop -- setup our transformation pointer. It will be written to during -- the Main Loop transP <- malloc -- During Main Loop timeValue <- maybe 0 realToFrac <$> GLFW.getTime let rotQ = axisAngle (V3 (0::GLfloat) 0 1) timeValue let rotM33 = fromQuaternion rotQ let rotM33' = rotM33 !!* 0.5 let transformMatrix = mkTransformationMat rotM33' (V3 0.5 (-0.5) 0) poke transP (transpose transformMatrix) transformLoc <- glGetUniformLocation shaderProgram transform glUniformMatrix4fv transformLoc 1 GL_FALSE (castPtr transP)
And now it rotates in place.