packages feed

nehe-tuts-0.1: lesson10.hs

--
-- This code was created by Jeff Molofee '99 (ported to Haskell GHC 2005)
--

module Main where

import Graphics.UI.GLUT 
import System.Exit ( exitWith, ExitCode(..) )
import System.IO ( openFile, IOMode(..), hGetContents )
import Data.IORef ( IORef, newIORef, writeIORef, readIORef, modifyIORef )
import Util ( Image(..), bitmapLoad )
import Monad ( when )

type Sector = [Tri]

data Tri = Tri Vert Vert Vert
           deriving (Eq, Ord, Show)

data Vert = Vert { vertPoint :: Vertex3 GLfloat, 
                   vertTx, vertTy :: GLfloat }
            deriving (Eq, Ord, Show)

piover180 :: GLfloat
piover180 = 0.0174532925

data Global = Global { xrot, yrot, xspeed, yspeed, walkbias, walkbiasangle,
                       lookupdown, xpos, zpos, camx, camy, camz,
                       therotate, heading, zdepth :: IORef GLfloat,
                       filterSelector :: IORef Int }
mkGlobal :: IO Global
mkGlobal = do xr <- newIORef 0  -- please someone find a nicer way...
              yr <- newIORef 0
              xs <- newIORef 0
              ys <- newIORef 0
              wb <- newIORef 0
              wba <- newIORef 0
              lud <- newIORef 0
              xp <- newIORef 0
              zp <- newIORef 0
              cx <- newIORef 0
              cy <- newIORef 0
              cz <- newIORef 0
              tr <- newIORef 0
              h <- newIORef 0
              zd <- newIORef 0
              fs <- newIORef 0
              return Global { xrot = xr, yrot = yr, xspeed = xs, yspeed = ys,
                              walkbias = wb, walkbiasangle = wba, lookupdown =
                              lud, xpos = xp, zpos = zp, camx = cx, camy = cy,
                              camz = cz, therotate = tr, heading = h,
                              zdepth = zd, filterSelector = fs }

lightAmbient, lightDiffuse :: Color4 GLfloat
lightAmbient = Color4 0.5 0.5 0.5 1
lightDiffuse = Color4 1 1 1 1

lightPosition :: Vertex4 GLfloat
lightPosition = Vertex4 0 0 2 1

readRef :: IO (IORef a) -> IO a
readRef r = r >>= readIORef

setupWorld :: IO Sector
setupWorld = do h <- openFile "Data/world.txt" ReadMode
                ls <- (fmap (filter ignorable) ((fmap lines . hGetContents) h))
                let numtris = read ((head . tail . words . head) ls)
                let tris = readTris (tail ls)
                when (length tris /= numtris) $ putStrLn "error reading world.txt" >> exitWith (ExitFailure 1)
                return tris
  where
  readTris (l1:l2:l3:ls) = Tri (readVert (words l1)) (readVert (words l2))
                               (readVert (words l3)) : readTris ls
  readTris [] = []
  readTris _  = undefined
  readVert (x:y:z:u:v:[]) = Vert (readVertex (x,y,z)) (read u) (read v)
  readVert _              = undefined
  readVertex (x,y,z) = Vertex3 (read x) (read y) (read z)
  ignorable ('/':'/':_) = False
  ignorable []          = False
  ignorable _           = True
                
initGL :: IO [TextureObject]
initGL = do
  tex <- loadGLTextures
  texture Texture2D $= Enabled
  clearColor $= Color4 0 0 0 0.5 -- Clear the background color to black
  clearDepth $= 1 -- enables clearing of the depth buffer
  depthFunc  $= Just Less -- type of depth test
  shadeModel $= Smooth -- enables smooth color shading
  matrixMode $= Projection
  hint PerspectiveCorrection $= Nicest
  ambient (Light 1) $= lightAmbient
  diffuse (Light 1) $= lightDiffuse
  position (Light 1) $= lightPosition
  light (Light 1) $= Enabled
  lighting $= Enabled
  loadIdentity  -- reset projection matrix
  Size width height <- get windowSize
  perspective 45 (fromIntegral width/fromIntegral height) 0.1 100 -- calculate the aspect ratio of the window
  matrixMode $= Modelview 0
  color (Color4 1 1 1 (0.5::GLfloat))
  blendFunc $= (SrcAlpha, One)

  flush -- finally, we tell opengl to do it.
  return tex

loadGLTextures :: IO [TextureObject]
loadGLTextures = do
  (Image (Size w h) pd) <- bitmapLoad "Data/mud.bmp"
  texNames <- (genObjectNames 3)
  -- create nearest filtered texture
  textureBinding Texture2D $= Just (texNames !! 0)
  textureFilter  Texture2D $= ((Nearest, Nothing), Nearest)
  texImage2D Nothing NoProxy 0 RGB' (TextureSize2D w h) 0 pd
  -- create linear filtered texture
  textureBinding Texture2D $= Just (texNames !! 1)
  textureFilter  Texture2D $= ((Linear', Nothing), Linear')
  texImage2D Nothing NoProxy 0 RGB' (TextureSize2D w h) 0 pd
  -- create mipmap filtered texture
  textureBinding Texture2D $= Just (texNames !! 2)
  textureFilter  Texture2D $= ((Linear', Just Nearest), Linear')
  texImage2D Nothing NoProxy 0 RGB' (TextureSize2D w h) 0 pd
  build2DMipmaps Texture2D RGB' w h pd
  return texNames

resizeScene :: Size -> IO ()
resizeScene (Size w 0) = resizeScene (Size w 1) -- prevent divide by zero
resizeScene s@(Size width height) = do
  viewport   $= (Position 0 0, s)
  matrixMode $= Projection
  loadIdentity
  perspective 45 (fromIntegral width/fromIntegral height) 0.1 100
  matrixMode $= Modelview 0
  flush

drawScene :: [TextureObject] -> Sector -> Global -> IO ()
drawScene texs sector globals = do
  clear [ColorBuffer, DepthBuffer] -- clear the screen and the depth bufer
  loadIdentity  -- reset view
  xtrans <- fmap negate (readIORef (xpos globals))
  ytrans <- fmap (\y -> -y-0.25) (readIORef (walkbias globals))
  ztrans <- fmap negate (readIORef (zpos globals))
  sceneroty <- fmap (\y -> 360 - y) (readIORef (yrot globals))
  --print sceneroty
  look <- readIORef (lookupdown globals)
  filt <- readIORef (filterSelector globals)
  rotate look (Vector3 1 0 0)
  rotate sceneroty (Vector3 0 1 0)
  
  translate (Vector3 xtrans ytrans ztrans)
  textureBinding Texture2D $= Just (texs!!filt)
  mapM_ (\t -> renderPrimitive Triangles $
    do normal (Normal3 0 0 (1::GLfloat))
       case t of Tri (Vert (Vertex3 x1 y1 z1) u1 v1) 
                     (Vert (Vertex3 x2 y2 z2) u2 v2) 
                     (Vert (Vertex3 x3 y3 z3) u3 v3) -> do texCoord (TexCoord2 u1 v1)
                                                           vertex (Vertex3 x1 y1 z1)
                                                           texCoord (TexCoord2 u2 v2)
                                                           vertex (Vertex3 x2 y2 z2)
                                                           texCoord (TexCoord2 u3 v3)
                                                           vertex (Vertex3 x3 y3 z3)) sector
  
  -- since this is double buffered, swap the buffers to display what was just
  -- drawn
  flush
  swapBuffers

updateIORef :: Show a => IO (IORef a) -> (a -> a) -> IO ()
updateIORef ioref f = do ref <- ioref
                         r <- readIORef ref
                         print r
                         writeIORef ref (f r)

keyPressed :: Global -> KeyboardMouseCallback
-- 27 is ESCAPE
keyPressed _ (Char '\27') Down _ _ = exitWith ExitSuccess
keyPressed g (Char 'B') Down x y = keyPressed g (Char 'b') Down x y
keyPressed _ (Char 'b') Down _ _  = do
  r <- get blend
  if r == Enabled
     then blend $= Disabled >> depthFunc $= (Just Less)
     else blend $= Enabled >> depthFunc $= Nothing
keyPressed g (Char 'f') Down _ _ = modifyIORef (filterSelector g) (\x -> x+1 `mod` 3)
keyPressed g (Char 'F') Down x y = keyPressed g (Char 'f') Down x y
keyPressed g (Char 'L') Down x y = keyPressed g (Char 'l') Down x y
keyPressed _ (Char 'l') Down _ _ = do
  l <- get lighting
  if l == Enabled
     then lighting $= Disabled
     else lighting $= Enabled  
keyPressed g (SpecialKey KeyRight) Down _ _ = modifyIORef (yrot g) (subtract 1.5)
keyPressed g (SpecialKey KeyLeft) Down _ _ = modifyIORef (yrot g) (+ 1.5)
keyPressed g (SpecialKey KeyUp) Down _ _ = do
  h <- readIORef (yrot g)
  modifyIORef (xpos g) (subtract ((sin (h * piover180))*0.05))
  modifyIORef (zpos g) (subtract ((cos (h * piover180))*0.05))
  modifyIORef (walkbiasangle g) (\w -> (w+10) `fmod` 360)
  wba <- readIORef (walkbiasangle g)
  modifyIORef (walkbias g) (\_ -> ((sin (wba * piover180))/20))
keyPressed g (SpecialKey KeyDown) Down _  _ = do
  h <- readIORef (yrot g)
  modifyIORef (xpos g) (+ (sin (h * piover180))*0.05)
  modifyIORef (zpos g) (+ (cos (h * piover180))*0.05)
  modifyIORef (walkbiasangle g) (\w -> (w-10) `fmod` 360)
  wba <- readIORef (walkbiasangle g)
  modifyIORef (walkbias g) (\_ -> ((sin (wba * piover180))/20))
keyPressed g (SpecialKey KeyPageUp) Down _ _ = do
  modifyIORef (zdepth g) (subtract 0.2)
  modifyIORef (lookupdown g) (subtract 0.2)
keyPressed g (SpecialKey KeyPageDown) Down _ _ = do
  modifyIORef (zdepth g) (+ 0.2)
  modifyIORef (lookupdown g) (+ 0.2)

keyPressed _ _           _    _ _ = return ()

fmod :: RealFrac a => a -> Int -> a
fmod x m = (fromIntegral ((floor x :: Int) `mod` m)) + 
           (x - (fromIntegral (floor x :: Int)))

main :: IO ()
main = do
     -- Initialize GLUT state - glut will take any command line arguments
     -- that pertain to it or X windows -- look at its documentation at
     -- http://reality.sgi.com/mjk/spec3/spec3.html
     sector <- setupWorld

     getArgsAndInitialize 
     -- select type of display mode:
     -- Double buffer
     -- RGBA color
     -- Alpha components supported
     -- Depth buffer
     initialDisplayMode $= [ DoubleBuffered, RGBAMode, WithDepthBuffer, 
                             WithAlphaComponent ]
     -- get an 800 x 600 window
     initialWindowSize $= Size 800 600
     -- window starts at upper left corner of the screen
     initialWindowPosition $= Position 0 0
     -- open a window
     createWindow "Jeff Molofee's GL Code Tutorial ... NeHe '99"
     -- register the function to do all our OpenGL drawing
     -- initialize our window.
     texs <- initGL
     global <- mkGlobal
     displayCallback $= (drawScene texs sector global)
     -- go fullscreen. This is as soon as possible.
     fullScreen
     -- even if there are no events, redraw our gl scene
     idleCallback $= Just (drawScene texs sector global)
     -- register the funciton called when our window is resized
     reshapeCallback $= Just resizeScene
     -- register the function called when the keyboard is pressed.
     keyboardMouseCallback $= Just (keyPressed global)
     -- start event processing engine
     mainLoop