packages feed

nehe-tuts-0.2.4: lesson11.hs

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

module Main where
import qualified Graphics.UI.GLFW as GLFW
-- everything from here starts with gl or GL
import Graphics.Rendering.OpenGL.Raw
import Graphics.Rendering.GLU.Raw ( gluPerspective )
import Data.Bits ( (.|.) )
import System.Exit ( exitWith, ExitCode(..) )
import Control.Monad ( forever, when, forM_, join )
import Data.IORef ( IORef, newIORef, readIORef, writeIORef )
import Foreign ( withForeignPtr, plusPtr, alloca, peek )
import qualified Data.ByteString.Internal as BSI
import Util ( Image(..), bitmapLoad )
import Data.Array.IO ( readArray, IOArray, newListArray )
import Control.Applicative ( (<$>), (<*>) )
import Paths_nehe_tuts

type Points = IOArray (Int, Int, Int) GLfloat

initGL :: GLFW.Window -> IO GLuint
initGL win = do
  glEnable gl_TEXTURE_2D
  glShadeModel gl_SMOOTH
  glClearColor 0 0 0 0.5
  glClearDepth 1
  glEnable gl_DEPTH_TEST
  glDepthFunc gl_LEQUAL
  glHint gl_PERSPECTIVE_CORRECTION_HINT gl_NICEST
  -- On some video cards/drivers this looks terrible
  -- So if you get an ugly image, try commenting out
  -- these two glPolygonMode lines
  glPolygonMode gl_BACK  gl_FILL
  glPolygonMode gl_FRONT gl_LINE
  (w,h) <- GLFW.getFramebufferSize win
  resizeScene win w h
  loadGLTextures

loadGLTextures :: IO GLuint
loadGLTextures = do
  fp <- getDataFileName "tim.bmp"
  Just (Image w h pd) <- bitmapLoad fp
  putStrLn $ "Image w = " ++ show w
  putStrLn $ "Image h = " ++ show h
  tex <- alloca $ \p -> do
            glGenTextures 1 p
            peek p
  let (ptr, off, _) = BSI.toForeignPtr pd
  _ <- withForeignPtr ptr $ \p -> do
    let p' = p `plusPtr` off
        glNearest  = fromIntegral gl_NEAREST
    -- create linear filtered texture
    glBindTexture gl_TEXTURE_2D tex
    glTexImage2D gl_TEXTURE_2D 0 3
      (fromIntegral w) (fromIntegral h)
      0 gl_RGB gl_UNSIGNED_BYTE p'
    glTexParameteri gl_TEXTURE_2D gl_TEXTURE_MAG_FILTER glNearest
    glTexParameteri gl_TEXTURE_2D gl_TEXTURE_MIN_FILTER glNearest
  return tex

shutdown :: GLFW.WindowCloseCallback
shutdown win = do
  GLFW.destroyWindow win
  GLFW.terminate
  _ <- exitWith ExitSuccess
  return ()

resizeScene :: GLFW.WindowSizeCallback
resizeScene win w     0      = resizeScene win w 1 -- prevent divide by zero
resizeScene _   width height = do
  glViewport 0 0 (fromIntegral width) (fromIntegral height)
  glMatrixMode gl_PROJECTION
  glLoadIdentity
  gluPerspective 45 (fromIntegral width/fromIntegral height) 0.1 100
  glMatrixMode gl_MODELVIEW
  glLoadIdentity
  glFlush

drawScene :: GLuint -> IORef GLfloat -> IORef GLfloat -> IORef GLfloat
          -> Points -> IORef Int -> IORef Int -> GLFW.Window -> IO () 
drawScene tex xrot yrot zrot points wiggleRef offsetRef _ = do
  glClear $ fromIntegral  $  gl_COLOR_BUFFER_BIT
                         .|. gl_DEPTH_BUFFER_BIT

  glLoadIdentity

  glTranslatef 0 0 (-12)

  xr <- readIORef xrot
  yr <- readIORef yrot
  zr <- readIORef zrot
  offset <- readIORef offsetRef
  wiggle <- readIORef wiggleRef
  glRotatef xr 1 0 0
  glRotatef yr 0 1 0
  glRotatef zr 0 0 1
  glBindTexture gl_TEXTURE_2D tex
  glBegin gl_QUADS
  forM_ [(x,y) | x <- [0..43], y<-[0..43]] $ \(x,y) -> do
    let x'  = (x+offset) `mod` 45
        fx  = fromIntegral x/44 :: GLfloat
        fy  = fromIntegral y/44 :: GLfloat
        fxb = fromIntegral (x+1)/44 :: GLfloat
        fyb = fromIntegral (y+1)/44 :: GLfloat
    glTexCoord2f fx fy
    join $ glVertex3f <$> (readArray points (x,y,0))
                      <*> (readArray points (x,y,1))
                      <*> (readArray points (x',y,2))
    glTexCoord2f fx fyb
    join $ glVertex3f <$> (readArray points (x,y+1,0))
                      <*> (readArray points (x,y+1,1))
                      <*> (readArray points (x',y+1,2))
    glTexCoord2f fxb fyb
    join $ glVertex3f <$> (readArray points (x+1,y+1,0))
                      <*> (readArray points (x+1,y+1,1))
                      <*> (readArray points ((x'+1)`mod`45,y+1,2))
    glTexCoord2f fxb fy
    join $ glVertex3f <$> (readArray points (x+1,y,0))
                      <*> (readArray points (x+1,y,1))
                      <*> (readArray points ((x'+1)`mod`45,y,2))
  glEnd

  writeIORef xrot $! xr + 0.3
  writeIORef yrot $! yr + 0.2
  writeIORef zrot $! zr + 0.4

  when (wiggle == 2) $ do
    writeIORef offsetRef $! offset + 1
    writeIORef wiggleRef $! 0

  w <- readIORef wiggleRef
  writeIORef wiggleRef $! w + 1

  glFlush

keyPressed :: GLFW.KeyCallback
keyPressed win GLFW.Key'Escape _ GLFW.KeyState'Pressed _ = shutdown win
keyPressed _   _               _ _                     _ = return ()

main :: IO ()
main = do
     True <- GLFW.init
     -- select type of display mode:
     -- Double buffer
     -- RGBA color
     -- Alpha components supported
     -- Depth buffer
     GLFW.defaultWindowHints
     -- open a window
     Just win <- GLFW.createWindow 800 600 "Lesson 11" Nothing Nothing
     GLFW.makeContextCurrent (Just win)
     -- window starts at upper left corner of the screen
     xrot <- newIORef 0
     yrot <- newIORef 0
     zrot <- newIORef 0
     wiggle <- newIORef 0
     offset <- newIORef 0
     let elems = concat [[((x/5)-4.5),
                          ((y/5)-4.5),
                          sin (((x/5)*40/360)*pi*2)]
                        | x <- [0..44]::[GLfloat], y <- [0..44]::[GLfloat] ]
     points <- newListArray ((0,0,0), (44,44,2)) elems :: IO Points
     -- initialize our window.
     tex <- initGL win
     GLFW.setWindowRefreshCallback win $
       Just (drawScene tex xrot yrot zrot points wiggle offset)
     -- register the funciton called when our window is resized
     GLFW.setFramebufferSizeCallback win (Just resizeScene)
     -- register the function called when the keyboard is pressed.
     GLFW.setKeyCallback win $
       Just keyPressed
     GLFW.setWindowCloseCallback win (Just shutdown)
     forever $ do
       GLFW.pollEvents
       drawScene tex xrot yrot zrot points wiggle offset win
       GLFW.swapBuffers win