Haskell GL Raw Binding Texture Trouble - haskell

I'm using two textures on two different objects that each have their own shader program. However, the second object renders with both textures applied and with some strange distortion.
I will focus on the second object's code as the first object and texture renders correctly.
The first texture is loaded like this:
-- Icosahedron texture
dice_textureP <- malloc
glGenTextures 1 dice_textureP
diceText <- peek dice_textureP
glBindTexture GL_TEXTURE_2D diceText
glEnable GL_BLEND
glBlendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA
glGenerateMipmap GL_TEXTURE_2D
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_LINEAR
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_LINEAR
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_REPEAT
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_REPEAT
eErrDI0 <- readImage "app/numbers.png"
dyImage0 <- case eErrDI0 of
Left e -> do
putStrLn e
return $ ImageRGBA8 $ generateImage (\x y ->
let x' = fromIntegral x in (PixelRGBA8 x' x' x' x')) 500 400
Right di -> return di
let ipixelrgba80 = convertRGBA8 dyImage0
iWidth0 = fromIntegral $ imageWidth ipixelrgba80
iHeight0 = fromIntegral $ imageHeight ipixelrgba80
iData0 = imageData ipixelrgba80
VS.unsafeWith iData0 $ \dataP ->
glTexImage2D GL_TEXTURE_2D 0 GL_RGBA iWidth0 iHeight0 0 GL_RGBA GL_UNSIGNED_BYTE (castPtr dataP)
glGenerateMipmap GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D 0
The second texture is loaded like this:
card_oneP <- malloc
glGenTextures 1 card_oneP
card_oneTexture <- peek card_oneP
glBindTexture GL_TEXTURE_2D card_oneTexture
glDisable GL_BLEND
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_NEAREST_MIPMAP_NEAREST
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_NEAREST_MIPMAP_NEAREST
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_CLAMP_TO_EDGE
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_CLAMP_TO_EDGE
eErrDI1 <- readImage "app/card_one.jpg"
dyImage1 <- case eErrDI1 of
Left e -> do
putStrLn e
return $ ImageRGBA8 $ generateImage (\x y ->
let x' = fromIntegral x in (PixelRGBA8 x' x' x' x')) 312 445
Right di -> return di
let ipixelrgb81 = convertRGBA8 dyImage1
iWidth1 = fromIntegral $ imageWidth ipixelrgb81
iHeight1 = fromIntegral $ imageHeight ipixelrgb81
iData1 = imageData ipixelrgb81
VS.unsafeWith iData1 $ \dataP ->
glTexImage2D GL_TEXTURE_2D 0 GL_RGBA iWidth0 iHeight0 0 GL_RGBA GL_UNSIGNED_BYTE (castPtr dataP)
glGenerateMipmap GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D 0
I bind the first texture before drawing the first object like this:
-- first vao and shader program
glUseProgram ico_shaderProgram
glBindVertexArray ico_vao
-- bind first texture
glActiveTexture GL_TEXTURE0
glBindTexture GL_TEXTURE_2D diceText
diceTextureLocation <- glGetUniformLocation ico_shaderProgram diceTexture
glUniform1i diceTextureLocation 0
After calling glDrawArrays on the first object, I unbind the first texture using glBindTexture_2D 0.
The second texture is bound in a similar fashion:
-- second vao and shader
glUseProgram card_shaderProgram
glBindVertexArray card_vao
-- bind second texture
glActiveTexture GL_TEXTURE0
glBindTexture GL_TEXTURE_2D card_oneTexture
cardTextureLocation <- glGetUniformLocation card_shaderProgram cardTexture
glUniform1i cardTextureLocation 0
I call glBindTexture_2D 0 after the draw before re-entering the render loop.
The result looks like this:
As you can see, the object on the right has a few artifacts... The second texture is rendering on top of the first texture (which should only be on the first object). Moreover, the second texture is all distorted.
The verticies and uv coords for the second object can be found here (gist).
The shader used for this object is here (gist).
The attributes for the second object are set up like so:
-- second object attribs
glVertexAttribPointer 0 3 GL_FLOAT GL_FALSE (5*floatSize) nullPtr
glEnableVertexAttribArray 0
let threeFloatOffset = castPtr $ plusPtr nullPtr (fromIntegral $ 3*floatSize)
glVertexAttribPointer 1 2 GL_FLOAT GL_FALSE (5*floatSize) threeFloatOffset
glEnableVertexAttribArray 1
glBindVertexArray 0
I've tried to include the most relevant code. I would be very grateful if someone can spot my error. I am 100% willing to post more code if needed.
UPDATE:
Someone in the comments kindly pointed out that my code was using incorrect width and height values for the second texture. I am using this function to load in images now:
loadImageTexture :: [Char] -> Int -> Int -> IO (Image PixelRGBA8)
loadImageTexture filePath w h = do
errorOrDyImage <- readImage filePath
dyImage <- case errorOrDyImage of
Left e -> do
putStrLn e
return $ ImageRGBA8 $ generateImage (\x y ->
let x' = fromIntegral x in (PixelRGBA8 x' x' x' x')) w h
Right di -> return di
return $ convertRGBA8 dyImage

As someone pointed out in the comments, iWidth0 and iHeight0 (width and height for the first texture) are incorrectly called for the second texture:
glTexImage2D GL_TEXTURE_2D 0 GL_RGBA iWidth0 iHeight0 0 GL_RGBA GL_UNSIGNED_BYTE (castPtr dataP)
It should be:
glTexImage2D GL_TEXTURE_2D 0 GL_RGBA iWidth1 iHeight1 0 GL_RGBA GL_UNSIGNED_BYTE (castPtr dataP)
Creating a function to handle loading textures would have avoided this problem from the start.

Related

Quaternions rotation has a weird behaviour (Haskell OpenGL)

I've been following the Haskell OpenGL tutorial.
Rotations in a 3D space intrigued me so I started learning about Euler angles and finally, quaternions.
I wanted to implement my own function using quaternions to perform a rotation (on a cube), I've based myself on those two papers: mostly this one and this one.
My function works fine when I'm performing a rotation on only one axis, but when I do it on X and Y for example, the cube start to randomly go forward and being "blocked" when it rotates.
Video of the cube performing rotation on XY.
When I set the three axis (X, Y, Z), it zooms even more (but doesn't have that weird blocking thing): video.
Here is the code of my program:
Here is the main file that creates a window, set idle function and outputs result of rotation by angle A on the screen where A is increment by 0.05 at each frames.
module Main (main) where
import Core
import Utils
import Data.IORef
import Graphics.UI.GLUT
import Graphics.Rendering.OpenGL
main :: IO ()
main = do
createAWindow "177013"
mainLoop
createAWindow :: [Char] -> IO ()
createAWindow windowName = do
(procName, _args) <- getArgsAndInitialize
createWindow windowName
initialDisplayMode $= [DoubleBuffered]
angle <- newIORef 0.0
delta <- newIORef 0.05
displayCallback $= (start angle)
reshapeCallback $= Just reshape
keyboardMouseCallback $= Just keyboardMouse
idleCallback $= Just (idle angle delta)
reshape :: ReshapeCallback
reshape size = do
viewport $= (Position 0 0, size)
postRedisplay Nothing
keyboardMouse :: KeyboardMouseCallback
keyboardMouse _ _ _ _ = return ()
idle :: IORef GLfloat -> IORef GLfloat -> IdleCallback
idle angle delta = do
d <- get delta
a <- get angle
angle $~! (+d)
postRedisplay Nothing
start :: IORef GLfloat -> DisplayCallback
start angle = do
clear [ColorBuffer]
loadIdentity
a <- get angle
let c = rotate3f (0, 0, 0) [X,Y,Z] a $ cube3f 0.2 -- here I'm rotating on X, Y and Z axis
draw3f Quads c CCyan
flush
swapBuffers
where
Here is the core file where the rotation function is defined (with a few other ones). I added some comments as it's probably some low quality haskell code.
module Core (draw3f, vertex3f, rotate3f, translate3f, rotate3d, Colors(..), Axes(..)) where
import Control.Lens
import Graphics.Rendering.OpenGL
data Axes = X | Y | Z
deriving Eq
data Colors = CRed | CGreen | CBlue | CYellow | CWhite | CMagenta | CCyan | CBlack | CNone | CPreset
deriving Eq
rotate3f :: (GLfloat, GLfloat, GLfloat) -> [Axes] -> GLfloat -> [(GLfloat, GLfloat, GLfloat)] -> [(GLfloat, GLfloat, GLfloat)]
rotate3f _ _ _ [] = []
rotate3f _ [] _ _ = []
rotate3f o axes a p = let p' = translate3f p u -- translation if I don't want to rotate it by the origin
q = cos a' : ((\x -> if x `elem` axes then sin a' else 0) <$> [X,Y,Z]) -- if the axe is set then its related component is equal to sin theta/2, otherwise it will be 0
q' = q !! 0 : (negate <$> (tail q)) -- quaternion inversion
in translate3f ((rotate q q') <$> p') [(0,0,0),o] -- rotate and translate again to put the object where it belongs
where
a' = (a * (pi / 180)) / 2 -- convert to radians and divide by 2 as all q components takes theta/2
u :: [(GLfloat, GLfloat, GLfloat)]
u = [o,(0,0,0)]
rotate :: [GLfloat] -> [GLfloat] -> (GLfloat, GLfloat, GLfloat) -> (GLfloat, GLfloat, GLfloat)
rotate q q' (x,y,z) = let p = [0,x,y,z]
qmul q1 q2 = [(q1 !! 0) * (q2 !! 0) - (q1 !! 1) * (q2 !! 1) - (q1 !! 2) * (q2 !! 2) - (q1 !! 3) * (q2 !! 3),
(q1 !! 0) * (q2 !! 1) + (q1 !! 1) * (q2 !! 0) + (q1 !! 2) * (q2 !! 3) - (q1 !! 3) * (q2 !! 2),
(q1 !! 0) * (q2 !! 2) - (q1 !! 1) * (q2 !! 3) + (q1 !! 2) * (q2 !! 0) + (q1 !! 3) * (q2 !! 1),
(q1 !! 0) * (q2 !! 3) + (q1 !! 1) * (q2 !! 2) - (q1 !! 2) * (q2 !! 1) + (q1 !! 3) * (q2 !! 0)]
p' = qmul (qmul q p) q'
in (p' !! 1, p' !! 2, p' !! 3)
translate3f :: [(GLfloat, GLfloat, GLfloat)] -> [(GLfloat, GLfloat, GLfloat)] -> [(GLfloat, GLfloat, GLfloat)]
translate3f p [(ax,ay,az),(bx,by,bz)] = map (\(x,y,z) -> (x + (bx - ax), y + (by - ay), z + (bz - az))) p
draw3f :: PrimitiveMode -> [(GLfloat, GLfloat, GLfloat)] -> Colors -> IO()
draw3f shape points color = renderPrimitive shape $ mapM_ (\(x,y,z) -> vertex3f x y z color) points
vertex3f :: GLfloat -> GLfloat -> GLfloat -> Colors -> IO()
vertex3f x y z c = do
if c /= CPreset
then color $ Color3 (c' ^. _1) (c' ^. _2) ((c' ^. _3) :: GLfloat)
else return ()
vertex $ Vertex3 x y z
where
c' :: (GLfloat, GLfloat, GLfloat)
c' = case c of CRed -> (1,0,0)
CGreen -> (0,1,0)
CBlue -> (0,0,1)
CYellow -> (1,1,0)
CMagenta -> (1,0,1)
CCyan -> (0,1,1)
CBlack -> (0,0,0)
_ -> (1,1,1)
And here is the utils file where there's just the definition of the cube, from the Haskell OpenGL tutorial
module Utils (cube3f) where
import Core
import Graphics.UI.GLUT
import Graphics.Rendering.OpenGL
cube3f :: GLfloat -> [(GLfloat, GLfloat, GLfloat)]
cube3f w = [( w, w, w), ( w, w,-w), ( w,-w,-w), ( w,-w, w),
( w, w, w), ( w, w,-w), (-w, w,-w), (-w, w, w),
( w, w, w), ( w,-w, w), (-w,-w, w), (-w, w, w),
(-w, w, w), (-w, w,-w), (-w,-w,-w), (-w,-w, w),
( w,-w, w), ( w,-w,-w), (-w,-w,-w), (-w,-w, w),
( w, w,-w), ( w,-w,-w), (-w,-w,-w), (-w, w,-w)]
Finally, if it can helps people to see if there's a problem in my algorithms, here are some rotation samples using my function:
Rotation at 90°, of point (1, 2, 3) on X axis around point (0, 0, 0) (origin) gives: (0.99999994,-3.0,2.0)
Same rotation but on X & Y axis gives: (5.4999995,-0.99999994,-0.49999988)
Same rotation again but on X, Y and Z axis gives: (5.9999995,1.9999999,3.9999995)
The second paper about rotations by quaternions that you point to has this sentence:
“(x̂, ŷ, ẑ) is a unit vector that defines the axis of rotation.”.
So the quaternion has to be normalized, the sum of components squared being equal to 1.
So for example if you have all 3 axis involved, it has to be (cos θ/2, r3sin θ/2, r3sin θ/2, r3*sin θ/2) where r3 is the reciprocal of the square root of 3. This is how I would explain that the rotation results you mention at the end of your post fail to conserve the length of the vector when several axis are involved.
The critical piece is thus this line in function rotate3f:
q = cos a' : ((\x -> if x `elem` axes then sin a' else 0) <$> [X,Y,Z])
where a normalization factor is missing.
Your code offers a number of opportunities for readability improvement. You might consider using CodeReview for further details.
A major concern is the fact that the source code lines are too wide. If the reader has to use an horizontal slider, it is much more difficult to understand the code and find the bugs. Below, I will try to avoid going beyond 80 characters width.
First, we need some quaternion infrastructure:
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ExplicitForAll #-}
type GLfloat = Float
type GLfloatV3 = (GLfloat, GLfloat, GLfloat)
type QuatFloat = [GLfloat]
data Axes = X | Y | Z deriving Eq
qmul :: QuatFloat -> QuatFloat -> QuatFloat
qmul [qa0, qa1, qa2, qa3] [qb0, qb1, qb2, qb3] =
[
qa0*qb0 - qa1*qb1 - qa2*qb2 - qa3*qb3 ,
qa0*qb1 + qa1*qb0 + qa2*qb3 - qa3*qb2 ,
qa0*qb2 - qa1*qb3 + qa2*qb0 + qa3*qb1 ,
qa0*qb3 + qa1*qb2 - qa2*qb1 + qa3*qb0
]
qmul _ _ = error "Quaternion length differs from 4"
qconj :: QuatFloat -> QuatFloat
qconj q = (head q) : (map negate (tail q)) -- q-conjugation
rotate :: [GLfloat] -> [GLfloat] -> GLfloatV3 -> GLfloatV3
rotate q q' (x,y,z) = let p = [0, x,y,z]
[q0,q1,q2,q3] = qmul (qmul q p) q'
in (q1, q2, q3)
Note that the idea of defining ad hoc types not only allows for reduced code width, but that also gives extra flexibility. If some day you decide to represent quaternions by some other data structure which is more efficient than a plain list, it can be done while leaving the client code unchanged.
Next, the rotation code proper. Function rotQuat0 is your initial algorithm, which reproduces the numerical results mentioned at the end of your question. Function rotQuat1 is the modified version giving a 1-normalized quaternion.
-- original code:
rotQuat0 :: [Axes] -> GLfloat -> QuatFloat
rotQuat0 axes angle = let fn x = if (x `elem` axes) then (sin angle) else 0
in (cos angle) : (map fn [X,Y,Z])
-- modified code:
rotQuat1 :: [Axes] -> GLfloat -> QuatFloat
rotQuat1 axes angle = let corr = 1.0 / sqrt (fromIntegral (length axes))
fn x = if (x `elem` axes) then corr*(sin angle) else 0
in (cos angle) : (map fn [X,Y,Z])
Code using rotQuat1:
rotate3f :: GLfloatV3 -> [Axes] -> GLfloat -> [GLfloatV3] -> [GLfloatV3]
rotate3f _ _ _ [] = []
rotate3f _ [] _ _ = []
rotate3f org axes degθ pts =
let -- convert to radians and divide by 2, as all q components take θ/2
a' = (degθ * (pi / 180)) / 2
u :: [GLfloatV3]
u = [org, (0,0,0)]
-- translation if I don't want to rotate it by the origin
p' = translate3f pts u
-- if the axis is set, then its related component is
-- equal to sin θ/2, otherwise it will be zero
---- q = cos a' : ((\x -> if x `elem` axes then sin a' else 0) <$> [X,Y,Z])
q = rotQuat1 axes a' -- modified version
q' = qconj q
-- rotate and translate again to put the object where it belongs
in translate3f ((rotate q q') <$> p') [(0,0,0), org]
translate3f :: [GLfloatV3] -> [GLfloatV3] -> [GLfloatV3]
translate3f pts [(ax,ay,az), (bx,by,bz)] =
let dx = bx - ax
dy = by - ay
dz = bz - az
in map (\(x,y,z) -> (x + dx, y + dy, z + dz)) pts
Testing code:
sqNorm3 :: GLfloatV3 -> GLfloat
sqNorm3 (x,y,z) = x*x + y*y +z*z
printAsLines :: Show α => [α] -> IO ()
printAsLines xs = mapM_ (putStrLn . show) xs
main = do
let pt = (1,2,3) :: GLfloatV3
pt1 = rotate3f (0,0,0) [X] 90 [pt]
pt2 = rotate3f (0,0,0) [X,Y] 90 [pt]
pt3 = rotate3f (0,0,0) [X,Y,Z] 90 [pt]
pts = map head [pt1, pt2, pt3]
ptN = map sqNorm3 pts
printAsLines pts
putStrLn " "
printAsLines ptN
Let's check that with function rotQuat1, the squared norm of your initial (1,2,3) input vector (that is 1+4+9=13) remains unchanged, as befits a proper rotation:
$ ghc opengl00.hs -o ./opengl00.x && ./opengl00.x
[1 of 1] Compiling Main ( opengl00.hs, opengl00.o )
Linking ./opengl00.x ...
(0.99999994,-3.0,2.0)
(3.6213198,-0.62132025,0.70710695)
(2.5773501,0.84529924,2.5773501)
14.0
13.999995
13.999998
$
Unfortunately I don't have enough time to install the OpenGL infrastructure and reproduce the animation. Please let us know whether this fixes the whole thing.

Shininess with Haskell Opengl

I have done numerous graphics with Haskell OpenGL. They are in my repo here: opengl-examples (the gallery is not exhaustive). However I have a problem: when I use materialShininess nothing happens. It there something to enable in order to have the shininess ?
Here is an example of one of my prog. It it not complete but I hope it's enough to identify the issue.
module CompoundFiveTetrahedra2
where
import CompoundFiveTetrahedra.Data
import Control.Monad (when)
import qualified Data.ByteString as B
import Data.IORef
import Graphics.Rendering.OpenGL.Capture (capturePPM)
import Graphics.Rendering.OpenGL.GL
import Graphics.UI.GLUT
import Text.Printf
import Utils.ConvertPPM
import Utils.OpenGL (negateNormal)
import Utils.Prism
blue,red,green,yellow,purple,white,black :: Color4 GLfloat
blue = Color4 0 0 1 1
red = Color4 1 0 0 1
green = Color4 0 1 0 1
yellow = Color4 1 1 0 1
white = Color4 1 1 1 1
black = Color4 0 0 0 1
purple = Color4 0.5 0 0.5 1
display :: IORef GLfloat -> IORef GLfloat -> IORef GLfloat -> IORef GLdouble
-> IORef GLint -> IORef GLfloat -> DisplayCallback
display rot1 rot2 rot3 zoom capture angle = do
clear [ColorBuffer, DepthBuffer]
r1 <- get rot1
r2 <- get rot2
r3 <- get rot3
z <- get zoom
a <- get angle
i <- get capture
loadIdentity
(_, size) <- get viewport
resize z size
rotate a $ Vector3 1 1 1
rotate r1 $ Vector3 1 0 0
rotate r2 $ Vector3 0 1 0
rotate r3 $ Vector3 0 0 1
mapM_ (drawEdge blue) (edges!!0)
mapM_ (drawEdge red) (edges!!1)
mapM_ (drawEdge green) (edges!!2)
mapM_ (drawEdge yellow) (edges!!3)
mapM_ (drawEdge purple) (edges!!4)
mapM_ (drawVertex blue) vertices1
mapM_ (drawVertex red) vertices2
mapM_ (drawVertex green) vertices3
mapM_ (drawVertex yellow) vertices4
mapM_ (drawVertex purple) vertices5
when (i > 0) $ do
let ppm = printf "tetrahedra%04d.ppm" i
png = printf "tetrahedra%04d.png" i
(>>=) capturePPM (B.writeFile ppm)
convert ppm png True
capture $~! (+1)
swapBuffers
drawVertex :: Color4 GLfloat -> Vertex3 GLfloat -> IO ()
drawVertex col v =
preservingMatrix $ do
translate $ toVector v
materialDiffuse Front $= col
renderObject Solid $ Sphere' 0.03 30 30
where
toVector (Vertex3 x y z) = Vector3 x y z
drawEdge :: Color4 GLfloat -> (Vertex3 GLfloat, Vertex3 GLfloat) -> IO ()
drawEdge col (v1,v2) = do
let cylinder = prism v1 v2 30 0.03
renderPrimitive Quads $ do
materialDiffuse Front $= col
mapM_ drawQuad cylinder
where
drawQuad ((w1,w2,w3,w4),n) = do
normal $ negateNormal n
vertex w1
vertex w2
vertex w3
vertex w4
resize :: Double -> Size -> IO ()
resize zoom s#(Size w h) = do
viewport $= (Position 0 0, s)
matrixMode $= Projection
loadIdentity
perspective 45.0 (w'/h') 1.0 100.0
lookAt (Vertex3 0 0 (-3 + zoom)) (Vertex3 0 0 0) (Vector3 0 1 0)
matrixMode $= Modelview 0
where
w' = realToFrac w
h' = realToFrac h
keyboard :: IORef GLfloat -> IORef GLfloat -> IORef GLfloat -> IORef GLint
-> KeyboardCallback
keyboard rot1 rot2 rot3 capture c _ =
case c of
'r' -> rot1 $~! subtract 1
't' -> rot1 $~! (+1)
'f' -> rot2 $~! subtract 1
'g' -> rot2 $~! (+1)
'v' -> rot3 $~! subtract 1
'b' -> rot3 $~! (+1)
'c' -> capture $~! (+1)
'q' -> leaveMainLoop
_ -> return ()
mouse :: IORef GLdouble -> MouseCallback
mouse zoom button keyState _ =
case (button, keyState) of
(LeftButton, Down) -> zoom $~! (+0.1)
(RightButton, Down) -> zoom $~! subtract 0.1
_ -> return ()
idle :: IORef GLfloat -> IdleCallback
idle angle = do
angle $~! (+ 2)
postRedisplay Nothing
main :: IO ()
main = do
_ <- getArgsAndInitialize
_ <- createWindow "Five tetrahedra"
initialDisplayMode $= [RGBAMode, DoubleBuffered, WithDepthBuffer]
clearColor $= black
materialAmbient Front $= black
materialShininess Front $= 80 -- THIS DOES NOT WORK
lighting $= Enabled
light (Light 0) $= Enabled
position (Light 0) $= Vertex4 0 0 (-100) 1
ambient (Light 0) $= white
diffuse (Light 0) $= white
specular (Light 0) $= white
depthFunc $= Just Lequal
depthMask $= Enabled
shadeModel $= Smooth
rot1 <- newIORef 0.0
rot2 <- newIORef 0.0
rot3 <- newIORef 0.0
zoom <- newIORef 0.0
capture <- newIORef 0
angle <- newIORef 0.0
displayCallback $= display rot1 rot2 rot3 zoom capture angle
reshapeCallback $= Just (resize 0)
keyboardCallback $= Just (keyboard rot1 rot2 rot3 capture)
mouseCallback $= Just (mouse zoom)
idleCallback $= Just (idle angle)
mainLoop
Do I miss something to enable the shininess ?
EDIT
Here is an example with the R package rgl, which is also a wrapper to OpenGL. Look at the white part on the spheres. I cannot achieve that with Haskell.
Update: Try shininess of 1.0 to see the difference more clearly at low resolutions.
The shininess parameter affects the sharpness of specular lighting, so you need to turn this type of lighting on for your materials by giving them a specular color. (By default, the specular color is black, so the effect of the shininess parameter will not be visible.) You'll also want to reduce the shininess value for this scene, because it's too high to be very visible.
Try:
materialSpecular Front $= white
materialShininess Front $= 1.0
and you'll start to see white highlights, particularly along the curved edges of your shape. The flat faces will also reflect some white light, but only when they are nearly perpendicular to a line that's mid-angle between the viewer and the light source -- it's a little complicated.
Note that the specular color of most materials is taken to be some "multiple" of white (i.e., somewhere between black for a perfectly dull material to white for the shiniest materials in the scene). The only materials with tinted specular color would be colored metals, like gold or bronze.
Some additional notes:
You're using old-style OpenGL 2.1 shading, not "modern OpenGL", so you don't have to worry so much about the "shaders" that #user2297560 is talking about. OpenGL 2.1 comes with built-in shaders to do basic shading; with modern OpenGL, you have to build everything from scratch.
As #luqui mentioned, if you're looking for materials that actually reflect other parts of the scenes, this kind of shininess won't help you.
Here is the difference. Your original code on the left, the settings above on the right, on your "compoundfivetetrahedra" example. It'll look better if you increase the size of the window.
Note that it works better on curved surfaces. Here's your cylinder example, using:
materialShininess Front $= 5
materialSpecular Front $= white
You can see the shininess on the closer sphere.

Why doesn't this OpenGL example draw the triangle?

I'm trying to get a basic "draw a triangle on the screen" example working, but in Haskell with the gl and GLFW-b packages.
My code is here:
-- https://learnopengl.com/#!Getting-started/Hello-Triangle
-- stuff from base
import Control.Monad (when, mapM)
import Foreign -- includes Ptr and Marshal, among other things.
-- we qualify these names so we can tell what's from GLFW
import qualified Graphics.UI.GLFW as GLFW
-- gl funcs all already have "gl" in their name
import Graphics.GL
width = 800 :: Int
height = 600 :: Int
vertexShaderSource = [
"#version 330 core\n",
"layout (location = 0) in vec3 position;\n",
"void main()\n",
"{\n",
"gl_Position = vec4(position.x, position.y, position.z, 1.0);\n",
"}\n"]
fragmentShaderSource = [
"#version 330 core\n",
"out vec4 color;\n",
"void main()\n",
"{\n",
"color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n",
"}\n"]
getSrcPointer :: [String] -> IO (Ptr (Ptr GLchar), Ptr GLint)
getSrcPointer sourceLines = do
let glcharLines = map (map (fromIntegral.fromEnum)) sourceLines
let linesLengths = map (fromIntegral.length) glcharLines
linesPtrs <- mapM newArray glcharLines
linesPtrsPtr <- newArray linesPtrs
lengthsPtr <- newArray linesLengths
return (linesPtrsPtr,lengthsPtr)
-- type KeyCallback = Window -> Key -> Int -> KeyState -> ModifierKeys -> IO ()
callback :: GLFW.KeyCallback
callback window key scanCode keyState modKeys = do
putStrLn (show key)
when (key == GLFW.Key'Escape && keyState == GLFW.KeyState'Pressed)
(GLFW.setWindowShouldClose window True)
main :: IO ()
main = do
-- init GLFW and set the appropriate options
_ <- GLFW.init
GLFW.windowHint (GLFW.WindowHint'ContextVersionMajor 3)
GLFW.windowHint (GLFW.WindowHint'ContextVersionMinor 3)
GLFW.windowHint (GLFW.WindowHint'OpenGLProfile GLFW.OpenGLProfile'Core)
GLFW.windowHint (GLFW.WindowHint'Resizable False)
-- create our window
maybeWindow <- GLFW.createWindow width height "Lesson 02" Nothing Nothing
case maybeWindow of
Nothing -> do
-- somehow we failed. Nothing to do but report that and quit.
putStrLn "Failed to create a GLFW window!"
GLFW.terminate
Just window -> do
-- set our context and callback
GLFW.makeContextCurrent (Just window)
GLFW.setKeyCallback window (Just callback)
-- define the viewport dimensions
(frameWidth,frameHeight) <- GLFW.getFramebufferSize window
glViewport 0 0 (fromIntegral frameWidth) (fromIntegral frameHeight)
-- build and compile our shader program.
successP <- malloc
-- vertex shader
vertexShader <- glCreateShader GL_VERTEX_SHADER
(linesPtrsPtr,lengthsPtr) <- getSrcPointer vertexShaderSource
glShaderSource vertexShader 1 linesPtrsPtr lengthsPtr
glCompileShader vertexShader
-- check for compilation errors
glGetShaderiv vertexShader GL_COMPILE_STATUS successP
success <- peek successP
when (success == 0) $ do
putStrLn "Vertex Shader Compile Error:"
let infoLength = 512
resultP <- malloc
infoLog <- mallocArray (fromIntegral infoLength)
glGetShaderInfoLog vertexShader (fromIntegral infoLength) resultP infoLog
result <- fromIntegral <$> peek resultP
logBytes <- peekArray result infoLog
putStrLn (map (toEnum.fromEnum) logBytes)
-- fragment shader
fragmentShader <- glCreateShader GL_FRAGMENT_SHADER
(linesPtrsPtr,lengthsPtr) <- getSrcPointer fragmentShaderSource
glShaderSource fragmentShader 1 linesPtrsPtr lengthsPtr
glCompileShader fragmentShader
-- check for compilation errors
glGetShaderiv fragmentShader GL_COMPILE_STATUS successP
success <- peek successP
when (success == 0) $ do
putStrLn "Fragment Shader Compile Error:"
let infoLength = 512
resultP <- malloc
infoLog <- mallocArray (fromIntegral infoLength)
glGetShaderInfoLog fragmentShader (fromIntegral infoLength) resultP infoLog
result <- fromIntegral <$> peek resultP
logBytes <- peekArray result infoLog
putStrLn (map (toEnum.fromEnum) logBytes)
-- link up the shaders
shaderProgram <- glCreateProgram
glAttachShader shaderProgram vertexShader
glAttachShader shaderProgram fragmentShader
glLinkProgram shaderProgram
-- check for linking errors
glGetProgramiv shaderProgram GL_LINK_STATUS successP
success <- peek successP
when (success == 0) $ do
putStrLn "Program Linking Error:"
let infoLength = 512
resultP <- malloc
infoLog <- mallocArray (fromIntegral infoLength)
glGetProgramInfoLog shaderProgram (fromIntegral infoLength) resultP infoLog
result <- fromIntegral <$> peek resultP
logBytes <- peekArray result infoLog
putStrLn (map (toEnum.fromEnum) logBytes)
-- cleanup the sub-programs now that our complete shader program is ready
glDeleteShader vertexShader
glDeleteShader fragmentShader
-- setup vertex data and attribute pointers
verticesP <- newArray ([
-0.5, -0.5, 0.0, -- Left
0.5, -0.5, 0.0, -- Right
0.0, 0.5, 0.0 -- Top
] :: [GLfloat])
let verticesSize = fromIntegral $ sizeOf (0.0 :: GLfloat) * 9
vboP <- malloc :: IO (Ptr GLuint)
vaoP <- malloc :: IO (Ptr GLuint)
glGenVertexArrays 1 vaoP
glGenBuffers 1 vboP
-- Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointer(s).
vao <- peek vaoP
glBindVertexArray vao
vbo <- peek vboP
glBindBuffer GL_ARRAY_BUFFER vbo
glBufferData GL_ARRAY_BUFFER verticesSize (castPtr verticesP) GL_STATIC_DRAW
let threeFloats = fromIntegral $ sizeOf (0.0::GLfloat) * 3
glVertexAttribPointer 0 3 GL_FLOAT GL_FALSE threeFloats nullPtr
glEnableVertexAttribArray 0
-- Note that this is allowed, the call to glVertexAttribPointer
-- registered VBO as the currently bound vertex buffer object so
-- afterwards we can safely unbind
glBindBuffer GL_ARRAY_BUFFER 0
-- Unbind VAO (it's always a good thing to unbind any buffer/array
-- to prevent strange bugs)
glBindVertexArray 0
-- "game loop"
let loop :: IO ()
loop = do
shouldClose <- GLFW.windowShouldClose window
if shouldClose
then return ()
else do
-- event poll
GLFW.pollEvents
-- clear the screen
glClearColor 0.2 0.3 0.3 1.0
glClear GL_COLOR_BUFFER_BIT
-- draw a triangle
glUseProgram shaderProgram
glBindVertexArray vao
glDrawArrays GL_TRIANGLES 0 3
glBindVertexArray 0
-- swap buffers and go again
GLFW.swapBuffers window
loop
loop
-- clean up the gl resources
glDeleteVertexArrays 1 vaoP
glDeleteBuffers 1 vboP
-- clean up the GLFW resources
GLFW.terminate
It compiles and runs without any reported errors, but it only shows the clear color; the triangle isn't drawn at all.
Note that when I compile and run your program, I get shader compile and program link errors. Specifically, I get errors:
Vertex Shader Compile Error:
0:1(18): error: syntax error, unexpected $end
Fragment Shader Compile Error:
0:1(18): error: syntax error, unexpected $end
and there are link errors too, of course.
On closer inspection, your glShaderSource calls are flawed. Though they are passing arrays of line pointers and line lengths for six lines per shader, the line count argument is 1. If I modify the last line of getSrcPointer to return the line count:
return (linesPtrsPtr,lengthsPtr, (fromIntegral.length) linesLengths)
and then pass this count to the glShaderSource calls in each case:
(linesPtrsPtr,lengthsPtr,count) <- getSrcPointer vertexShaderSource
glShaderSource vertexShader count linesPtrsPtr lengthsPtr
...
(linesPtrsPtr,lengthsPtr,count) <- getSrcPointer fragmentShaderSource
glShaderSource fragmentShader count linesPtrsPtr lengthsPtr
then your program works fine.
Happy triangling!

Rendering a texture on screen

I'm new to both Haskell and OpenGL and I'm trying to render a texture on the screen.
Here's what I've got so far:
makeTexture :: FilePath -> IO GL.TextureObject
makeTexture f = do
t <- either error id <$> GLU.readTexture f
GL.textureFilter GL.Texture2D GL.$= ((GL.Linear', Nothing), GL.Linear')
GLU.texture2DWrap GL.$= (GL.Mirrored, GL.ClampToEdge)
return t
renderEntity :: Entity -> IO ()
renderEntity e = do
GL.activeTexture GL.$= GL.TextureUnit 0
GL.textureBinding GL.Texture2D GL.$= Just (texture $ model e)
-- I see a white triangle on the screen.
renderTriangle $ fmap (vadd $ position e) (points $ model e :: [Vector2])
-- I do not see this. Nor is the triangle textured either.
GL.renderPrimitive GL.Quads $ do
n 0 1 0
t 0 1 >> v 10 (-10) 10
t 1 1 >> v 10 (-10) (-10)
t 1 0 >> v (-10) (-10) (-10)
t 0 0 >> v (-10) (-10) 10
where v x y z = GL.vertex (GL.Vertex3 x y z :: GL.Vertex3 GL.GLfloat)
n x y z = GL.normal (GL.Normal3 x y z :: GL.Normal3 GL.GLfloat)
t u v = GL.texCoord (GL.TexCoord2 u v :: GL.TexCoord2 GL.GLfloat)
Where Entity looks like:
texMetal <- makeTexture "texture/metal.jpg"
let
entity = Entity
{ angle = 0
, position = (0, 0) :: Vector2
, velocity = (5, 5) :: Vector2
, model = Model
{ points = [(-60, -40), (60, -40), (0, 60)] :: [Vector2]
, texture = texMetal
}
}
And when I'm initializing I have these:
GL.viewport GL.$= (pos, size)
GL.matrixMode GL.$= GL.Projection
GL.texture GL.Texture2D GL.$= GL.Enabled
GL.normalize GL.$= GL.Enabled
GL.loadIdentity
GL.ortho2D (-fromIntegral width / 2)
(fromIntegral width / 2)
(-fromIntegral height / 2)
(fromIntegral height / 2)
GL.matrixMode GL.$= GL.Modelview 0
GL.loadIdentity
And the resulting picture:
It has two triangles because I have two 'entities' set in my code. But I don't see my quad nor do I see any sign of texturing.
It seems I had incorrect coordinates, because this works well:
renderEntity :: Entity -> IO ()
renderEntity e = do
GL.activeTexture GL.$= GL.TextureUnit 0
GL.textureBinding GL.Texture2D GL.$= Just (texture $ model e)
GL.renderPrimitive GL.Quads $ do
v 100 100
t 0 1
v 100 (-100)
t 1 1
v (-100) (-100)
t 1 0
v (-100) 100
t 0 0
where v x y = GL.vertex (GL.Vertex2 x y :: GL.Vertex2 GL.GLfloat)
t u v = GL.texCoord (GL.TexCoord2 u v :: GL.TexCoord2 GL.GLfloat)

Raycaster displays phantom perpendicular wall faces

The output looks like this:
You should just see a flat, continuous red wall on one side, blue wall on another, green on another, yellow on another (see the definition of the map, testMapTiles, it's just a map with four walls). Yet there are these phantom wall faces of varying height, which are perpendicular to the real walls. Why?
Note that the white "gaps" aren't actually gaps: it's trying to draw a wall of height Infinity (distance 0). If you specifically account for it (this version of the code doesn't) and just cap it at screen height, then you just see a very high wall there.
The source code is below. It's plain Haskell, using Haste to compile to JavaScript and render to canvas. It is based on the C++ code from this tutorial, though note that I replaced mapX and mapY with tileX and tileY, and I don't have the ray prefix for pos and dir within the main loop. Any discrepancies from the C++ code are probably what's breaking everything, but I can't seem to find any after having pored over this code many times.
Any help?
import Data.Array.IArray
import Control.Arrow (first, second)
import Control.Monad (forM_)
import Haste
import Haste.Graphics.Canvas
data MapTile = Empty | RedWall | BlueWall | GreenWall | YellowWall deriving (Eq)
type TilemapArray = Array (Int, Int) MapTile
emptyTilemapArray :: (Int, Int) -> TilemapArray
emptyTilemapArray dim#(w, h) = listArray ((1, 1), dim) $ replicate (w * h) Empty
testMapTiles :: TilemapArray
testMapTiles =
let arr = emptyTilemapArray (16, 16)
myBounds#((xB, yB), (w, h)) = bounds arr
in listArray myBounds $ flip map (indices arr) (\(x, y) ->
if x == xB then RedWall
else if y == yB then BlueWall
else if x == w then GreenWall
else if y == h then YellowWall
else Empty)
type Vec2 a = (a, a)
type DblVec2 = Vec2 Double
type IntVec2 = Vec2 Int
add :: (Num a) => Vec2 a -> Vec2 a -> Vec2 a
add (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
mul :: (Num a) => Vec2 a -> a -> Vec2 a
mul (x, y) factor = (x * factor, y * factor)
rot :: (Floating a) => Vec2 a -> a -> Vec2 a
rot (x, y) angle =
(x * (cos angle) - y * (sin angle), x * (sin angle) + y * (cos angle))
dbl :: Int -> Double
dbl = fromIntegral
-- fractional part of a float
-- `truncate` matches behaviour of C++'s int()
frac :: Double -> Double
frac d = d - dbl (truncate d)
-- get whole and fractional parts of a float
split :: Double -> (Int, Double)
split d = (truncate d, frac d)
-- stops 'Warning: Defaulting the following constraint(s) to type ‘Integer’'
square :: Double -> Double
square = (^ (2 :: Int))
-- raycasting algorithm based on code here:
-- http://lodev.org/cgtutor/raycasting.html#Untextured_Raycaster_
data HitSide = NorthSouth | EastWest deriving (Show)
-- direction, tile, distance
type HitInfo = (HitSide, IntVec2, Double)
-- pos: start position
-- dir: initial direction
-- plane: camera "plane" (a line, really, perpendicular to the direction)
traceRays :: TilemapArray -> Int -> DblVec2 -> DblVec2 -> DblVec2 -> [HitInfo]
traceRays arr numRays pos dir plane =
flip map [0..numRays] $ \x ->
let cameraX = 2 * ((dbl x) / (dbl numRays)) - 1
in traceRay arr pos $ dir `add` (plane `mul` cameraX)
traceRay :: TilemapArray -> DblVec2 -> DblVec2 -> HitInfo
traceRay arr pos#(posX, posY) dir#(dirX, dirY) =
-- map tile we're in (whole part of position)
-- position within map tile (fractional part of position)
let ((tileX, fracX), (tileY, fracY)) = (split posX, split posY)
tile = (tileX, tileY)
-- length of ray from one x or y-side to next x or y-side
deltaDistX = sqrt $ 1 + (square dirY / square dirX)
deltaDistY = sqrt $ 1 + (square dirX / square dirY)
deltaDist = (deltaDistX, deltaDistY)
-- direction of step
stepX = if dirX < 0 then -1 else 1
stepY = if dirY < 0 then -1 else 1
step = (stepX, stepY)
-- length of ray from current position to next x or y-side
sideDistX = deltaDistX * if dirX < 0 then fracX else 1 - fracX
sideDistY = deltaDistY * if dirY < 0 then fracY else 1 - fracY
sideDist = (sideDistX, sideDistY)
(hitSide, wallTile) = traceRayInner arr step deltaDist tile sideDist
in (hitSide, wallTile, calculateDistance hitSide pos dir wallTile step)
traceRayInner :: TilemapArray -> IntVec2 -> DblVec2 -> IntVec2 -> DblVec2 -> (HitSide, IntVec2)
traceRayInner arr step#(stepX, stepY) deltaDist#(deltaDistX, deltaDistY) tile sideDist#(sideDistX, sideDistY)
-- a wall has been hit, report hit direction and coördinates
| arr ! tile /= Empty = (hitSide, tile)
-- advance until a wall is hit
| otherwise = case hitSide of
EastWest ->
let newSideDist = first (deltaDistX+) sideDist
newTile = first (stepX+) tile
in
traceRayInner arr step deltaDist newTile newSideDist
NorthSouth ->
let newSideDist = second (deltaDistY+) sideDist
newTile = second (stepY+) tile
in
traceRayInner arr step deltaDist newTile newSideDist
where
hitSide = if sideDistX < sideDistY then EastWest else NorthSouth
-- calculate distance projected on camera direction
-- (an oblique distance would give a fisheye effect)
calculateDistance :: HitSide -> DblVec2 -> DblVec2 -> IntVec2 -> IntVec2 -> Double
calculateDistance EastWest (startX, _) (dirX, _) (tileX, _) (stepX, _) =
((dbl tileX) - startX + (1 - dbl stepX) / 2) / dirX
calculateDistance NorthSouth (_, startY) (_, dirY) (_, tileY) (_, stepY) =
((dbl tileY) - startY + (1 - dbl stepY) / 2) / dirY
-- calculate the height of the vertical line on-screen based on the distance
calculateHeight :: Double -> Double -> Double
calculateHeight screenHeight 0 = screenHeight
calculateHeight screenHeight perpWallDist = screenHeight / perpWallDist
width :: Double
height :: Double
(width, height) = (640, 480)
main :: IO ()
main = do
cvElem <- newElem "canvas" `with` [
attr "width" =: show width,
attr "height" =: show height
]
addChild cvElem documentBody
Just canvas <- getCanvas cvElem
let pos = (8, 8)
dir = (-1, 0)
plane = (0, 0.66)
renderGame canvas pos dir plane
renderGame :: Canvas -> DblVec2 -> DblVec2 -> DblVec2 -> IO ()
renderGame canvas pos dir plane = do
let rays = traceRays testMapTiles (floor width) pos dir plane
render canvas $ forM_ (zip [0..width - 1] rays) (\(x, (side, tile, dist)) ->
let lineHeight = calculateHeight height dist
wallColor = case testMapTiles ! tile of
RedWall -> RGB 255 0 0
BlueWall -> RGB 0 255 0
GreenWall -> RGB 0 0 255
YellowWall -> RGB 255 255 0
_ -> RGB 255 255 255
shadedWallColor = case side of
EastWest ->
let (RGB r g b) = wallColor
in RGB (r `div` 2) (g `div` 2) (b `div` 2)
NorthSouth -> wallColor
in color shadedWallColor $ do
translate (x, height / 2) $ stroke $ do
line (0, -lineHeight / 2) (0, lineHeight / 2))
-- 25fps
let fps = 25
timeout = (1000 `div` fps) :: Int
rots_per_min = 1
rots_per_sec = dbl rots_per_min / 60
rots_per_frame = rots_per_sec / dbl fps
tau = 2 * pi
increment = tau * rots_per_frame
setTimeout timeout $ do
renderGame canvas pos (rot dir $ -increment) (rot plane $ -increment)
HTML page:
<!doctype html>
<meta charset=utf-8>
<title>Raycaster</title>
<noscript>If you're seeing this message, either your browser doesn't support JavaScript, or it is disabled for some reason. This game requires JavaScript to play, so you'll need to make sure you're using a browser which supports it, and enable it, to play.</noscript>
<script src=raycast.js></script>
The "phantom faces" are occurring because an incorrect HitSide is being reported: you're saying the face was hit on a horizontal move (EastWest), but was actually hit on a vertical move (NorthSouth), or vice-versa.
Why is it reporting an incorrect value, then? if sideDistX < sideDistY then EastWest else NorthSouth seems pretty foolproof, right? And it is.
The problem isn't how we calculated that value. It's when we calculated that value. The distance calculation function needs to know the direction we moved in to get to the wall. However, what we've actually given is the direction we would move in if we were to keep going (that is, if that tile wasn't a wall, or we were to ignore it for some reason).
Look at the Haskell code:
traceRayInner arr step#(stepX, stepY) deltaDist#(deltaDistX, deltaDistY) tile sideDist#(sideDistX, sideDistY)
-- a wall has been hit, report hit direction and coördinates
| arr ! tile /= Empty = (hitSide, tile)
-- advance until a wall is hit
| otherwise = case hitSide of
EastWest ->
let newSideDist = first (deltaDistX+) sideDist
newTile = first (stepX+) tile
in
traceRayInner arr step deltaDist newTile newSideDist
NorthSouth ->
let newSideDist = second (deltaDistY+) sideDist
newTile = second (stepY+) tile
in
traceRayInner arr step deltaDist newTile newSideDist
where
hitSide = if sideDistX < sideDistY then EastWest else NorthSouth
Notice that we do things in this order:
calculate hitSide
check if a wall has been hit, and if so, report hitSide
move
Compare this to the original C++ code:
//perform DDA
while (hit == 0)
{
//jump to next map square, OR in x-direction, OR in y-direction
if (sideDistX < sideDistY)
{
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
}
else
{
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
//Check if ray has hit a wall
if (worldMap[mapX][mapY] > 0) hit = 1;
}
It does things in a different order:
check if a wall has been hit, and if so, report side (equivalent to hitSide)
move and calculate side
The C++ code only calculates side when it moves, and then it reports that value if it hits a wall. So, it reports the way it moved in order to hit the wall.
The Haskell code calculates side whether or not it moves: so it's correct for each move, but when it hits a wall, it reports the way it would have moved were it to keep going.
So, the Haskell code can be fixed by re-ordering it so that it checks for a hit after moving, and if so, reports the hitSide value from that move. This isn't pretty code, but it works:
traceRayInner arr step#(stepX, stepY) deltaDist#(deltaDistX, deltaDistY) tile sideDist#(sideDistX, sideDistY) =
let hitSide = if sideDistX < sideDistY then EastWest else NorthSouth
in case hitSide of
EastWest ->
let newSideDist = first (deltaDistX+) sideDist
newTile = first (stepX+) tile
in case arr ! newTile of
-- advance until a wall is hit
Empty -> traceRayInner arr step deltaDist newTile newSideDist
-- a wall has been hit, report hit direction and coördinates
_ -> (hitSide, newTile)
NorthSouth ->
let newSideDist = second (deltaDistY+) sideDist
newTile = second (stepY+) tile
in case arr ! newTile of
-- advance until a wall is hit
Empty -> traceRayInner arr step deltaDist newTile newSideDist
-- a wall has been hit, report hit direction and coördinates
_ -> (hitSide, newTile)
Problem solved!
Side note: I figured out what was wrong after carrying out the algorithm on paper. While in that particular case it just so happened the last two HitSide values matched, it became obvious that that they might not in every case. So, a big thanks to Madsy on Freenode's #algorithms for suggesting trying it out on paper. :)

Resources