{-# LANGUAGE BangPatterns #-}

{- BSP.hs; Mun Hon Cheong (mhch295@cse.unsw.edu.au) 2005

A module for loading Quake 3 BSP files

source code in C++ can be found at

http://www.paulsprojects.net/opengl/q3bsp/q3bsp.html

credits also go to Ben Humphrey for his excellent BSP tutorial

I might split this module up. Perhaps rendering performance
could be increased if i used Vertex Buffer Objects instead of
vertex arrays?

-}

module BSP (
   BSPMap(..),
   readBSP,
   renderBSP,
   Tree(..),
   BSPNode(..),
   BSPLeaf(..),
   BSPBrush(..),
   BSPBrushSide(..),
   isObjectVisible
  ) where

import Data.IORef
import Control.Exception ( bracket )
import Control.Monad ( liftM, when )
import System.IO hiding (withBinaryFile)
import System.IO.Error ( mkIOError, eofErrorType )
import Foreign
import Foreign.C.Types
import Foreign.C.String
import Data.List
import Data.Typeable
import Graphics.UI.GLUT
import BitSet
import Textures
import Data.Array
import qualified Data.Array.MArray  as Arr (readArray, newListArray)
import qualified Data.Array.IO as IOArr hiding (readArray, newListArray)
import Frustum
import Matrix
import Curves
import Data.Maybe

-------------------------------------------------------------------------------
-- lump directory indices

-- Stores texture information
kTextures :: Int
kTextures    = 1

-- Stores the splitting planes
kPlanes     :: Int
kPlanes      = 2

-- Stores the BSP nodes
kNodes      :: Int
kNodes       = 3

-- Stores the leafs of the nodes
kLeafs :: Int
kLeafs = 4

-- Stores the leaf's indices into the faces
kLeafFaces :: Int
kLeafFaces = 5

-- Stores the leaf's indices into the brushes
kLeafBrushes :: Int
kLeafBrushes = 6

-- Stores the brushes info (for collision)
kBrushes :: Int
kBrushes = 8

-- Stores the brush surfaces
kBrushSides :: Int
kBrushSides = 9

-- Stores the level vertices
kVertices :: Int
kVertices = 10

-- Stores the level indices
kIndices :: Int
kIndices = 11

-- Stores the faces for the level
kFaces :: Int
kFaces = 13

-- Stores the lightmaps for the level
kLightmaps :: Int
kLightmaps = 14

-- Stores PVS and cluster info (visibility)
kVisData      :: Int
kVisData    = 16

-- A constant to store the number of lumps
kMaxLumps     :: Int
kMaxLumps   = 17

-------------------------------------------------------------------------------
-- types used in this module

data BSPMap = BSPMap {
     vertexData :: !VertexArrays,
     vindices   :: !(Ptr GLint),
     leaves     :: ![BSPLeaf],
     tree     :: !Tree,
     visData    :: !(Maybe BSPVisData),
     bitset     :: !BitSet
}

type VertexArrays = (Ptr Float,Ptr Float,Ptr Float,Ptr Float,Ptr Word8)

data BSPLeaf = BSPLeaf {
    cluster    :: !Int,
    area       :: Int,
    leafMin    :: (GLdouble,GLdouble,GLdouble),
    leafMax    :: (GLdouble,GLdouble,GLdouble),
    leafface     :: Int,
    numOfLeafFaces   :: Int,
    leafBrush    :: Int,
    numOfLeafBrushes :: Int,
    leafFaces    :: [BSPFace],
    leafBrushes  :: [BSPBrush]
} deriving Show

data BSPFace = BSPFace {
    -- The index into the texture array
    textureObj     :: Maybe TextureObject,
    -- The index for the effects (or -1 = n/a)
    effect         :: Int,
    -- 1=polygon, 2=patch, 3=mesh, 4=billboard
    faceType       :: Int,
    -- The starting index into this face's first vertex
    startVertIndex :: Int,
    -- The number of vertices for this face
    numOfVerts     :: Int,
    -- The starting index into the indices array for this face
    startIndex     :: Int,
    -- The number of indices for this face
    numOfIndices   :: GLint,
    -- The texture index for the lightmap
    lightmapObj    :: Maybe TextureObject,
    -- The face's lightmap corner in the image
    lMapCorner     :: (Int,Int),
    -- The size of the lightmap section
    lMapSize       :: (Int,Int),
    -- The 3D origin of lightmap.
    lMapPos        :: (Float,Float,Float),
    -- The 3D space for s and t unit vectors.
    lMapVecs       :: [(Float,Float,Float)],
    -- The face normal.
    vNormal        :: (Float,Float,Float),
    -- The bezier patch dimensions.
    size           :: (Int,Int),
    faceNo         :: Int,
    patch          :: [BSPPatch],
    arrayPtrs      :: VertexPointers
} deriving Show

data BSPBrush = BSPBrush {
    brushSide      :: Int,
    numOfBrushSides    :: Int,
    brushSides     :: [BSPBrushSide],
    bTextureID     :: Int,
    textureType    :: Int
} deriving Show

data BSPBrushSide = BSPBrushSide {
    bsPlane     :: Int,
    bsPlaneNorm    :: (GLdouble,GLdouble,GLdouble),
    bsPlaneDist    :: GLdouble,
    bsTextureID    :: Int
} deriving Show

data Tree  = Leaf BSPLeaf | Branch BSPNode Tree Tree

data BSPNode = BSPNode {
    planeNormal :: (GLdouble,GLdouble,GLdouble),
    dist     :: GLdouble,
    front    :: Int,
    back     :: Int,
    nodeMin  :: (Int,Int,Int),
    nodeMax  :: (Int,Int,Int)
} deriving Show

data BSPVisData = BSPVisData {
    numOfClusters :: Int,
    bytesPerCluster :: Int,
    bitSets   :: IOArr.IOUArray Int Bool
}

data BSPLump = BSPLump {
    offset :: Int,
    len  :: Int
}  deriving Show

data BSPHeader = BSPHeader {
    strID   :: String,
    version :: Int
}  deriving Show

data BSPTexInfo = BSPTexInfo {
    strName  :: String,
    flags    :: Int,
    contents :: Int
} deriving Show

type VertexData = ([Float],[Float],[Float],[Float],[Word8])

type VertexPointers = (Ptr GLfloat, Ptr GLfloat, Ptr GLfloat, Ptr GLint)

type BSPLeafFace = Int

data BSPPlane = BSPPlane {
    pNormal  :: (GLdouble,GLdouble,GLdouble),
    distance :: GLdouble
} deriving Show

-------------------------------------------------------------------------------
--BSP rendering

renderBSP :: IORef(BSPMap) -> (GLdouble,GLdouble,GLdouble)-> IO()
renderBSP mapRef (x,y,z) = do
   activeTexture $= TextureUnit 0
   clientActiveTexture $= TextureUnit 0
   clientState TextureCoordArray $= Enabled
   texture Texture2D $= Enabled

   activeTexture $= TextureUnit 1
   clientActiveTexture $= TextureUnit 1
   clientState TextureCoordArray $= Enabled
   texture Texture2D $= Enabled

   mp <- readIORef mapRef
   leaf <- findLeaf (x,y,z) (tree mp)
   renderBSP' leaf mp
   return ()


-- given a position finds a in the tree where the position lies in
findLeaf :: (GLdouble, GLdouble,GLdouble) -> Tree -> IO BSPLeaf
findLeaf (x,y,z) (Branch node left right) = do
   let (px,py,pz) = (planeNormal node)
   let d = (dist node)
   let dstnc = (px*x)+(py*y)+(pz*z)-d
   case (dstnc >= 0) of
      True  -> do
                 leaf <- findLeaf (x,y,z) left
                 return leaf
      False -> do
                 leaf <- findLeaf (x,y,z) right
                 return leaf
findLeaf (_,_,_) (Leaf leaf) = return leaf


-- we are actually going across all the leaves in the tree
-- instead of walking the tree and pushing the leaves that
-- we want to render into a stack
renderBSP' :: BSPLeaf -> BSPMap -> IO()
renderBSP' leaf mp = do
   sze <- sizeBS $ bitset mp
   newbs <- emptyBS sze
   frstm <- getFrustum
   mapM_ (renderLeaves frstm newbs visFunc mp) (leaves mp)
   renderBSPCleanUp
       where visFunc = (isClusterVisible (visData mp) (cluster leaf))


-- we have to reset the openGL state after rendering
renderBSPCleanUp :: IO()
renderBSPCleanUp = do
    activeTexture $= TextureUnit 1
    clientState TextureCoordArray $= Disabled
    texture Texture2D $= Disabled
    activeTexture $= TextureUnit 0
    clientActiveTexture $= TextureUnit 0
    clientState TextureCoordArray $= Disabled
    texture Texture2D $= Disabled


-- renders a BSP leaf if it is visible
renderLeaves ::
   Frustum -> BitSet -> (Int -> IO Bool) -> BSPMap -> BSPLeaf -> IO()
renderLeaves  frstm bitSet func mp leaf  = do
   clusterVisible <- func (cluster leaf)
   case (clusterVisible) of
      True -> case ((boxInFrustum frstm (leafMin leaf) (leafMax leaf))) of
         True -> renderFaces bitSet mp (leafFaces leaf)
         _ -> return ()
      _   -> return()


-- is an object visible
isObjectVisible :: BSPMap -> Vec3 -> Vec3 -> IO Bool
isObjectVisible bsp (x,y,z) (ox,oy,oz) = do
   currentLeaf <- findLeaf (x,y,z) (tree bsp)
   objectLeaf  <- findLeaf (ox,oy,oz) (tree bsp)
   isVis   <- isClusterVisible
     (visData bsp) (cluster currentLeaf) (cluster objectLeaf)
   return (isVis)

isClusterVisible ::Maybe BSPVisData -> Int -> Int -> IO Bool
isClusterVisible (Just visdata) current target
    | current < 0 = do
     return True
    | target  < 0 = do
     return False
    | otherwise   = do
     Arr.readArray
       (bitSets visdata)
          (((bytesPerCluster visdata)*current*8) + target)
isClusterVisible _ _ _ = return False

renderFaces :: BitSet -> BSPMap -> [BSPFace] -> IO()
renderFaces _ _ [] = return ()
renderFaces bitSet mp (face:faces) = do
    isSet <- (isSetBS bitSet (faceNo face))
    case (isSet, (faceType face)) of
      (False, 1) -> do
          setBS bitSet (faceNo face)
          renderPolygonFace face (vertexData mp) (vindices mp)
          renderFaces bitSet mp faces
      (False, 2) -> do
          setBS bitSet (faceNo face)
          renderPatches face
          renderFaces bitSet mp faces
      (False, 3) -> do
          setBS bitSet (faceNo face)
          renderMeshFace face (vertexData mp) (vindices mp)
          renderFaces bitSet mp faces
      (_  , _) -> do
          renderFaces bitSet mp faces




-------------------------------------------------------------------------------
-- surface rendering

-- renders a polygon surface
renderPolygonFace :: BSPFace -> VertexArrays -> Ptr GLint -> IO ()
renderPolygonFace face (_,_,_,_,_) _ =  do
   let (a,b,c,d) = arrayPtrs face
   arrayPointer VertexArray $=
      VertexArrayDescriptor 3 Float 0 a
   clientState VertexArray $= Enabled

   activeTexture $= TextureUnit 0
   clientActiveTexture $= TextureUnit 0
   arrayPointer TextureCoordArray $=
      VertexArrayDescriptor 2 Float 0 b
   textureBinding Texture2D $= (textureObj face)

   activeTexture $= TextureUnit 1
   clientActiveTexture $= TextureUnit 1
   arrayPointer TextureCoordArray $=
      VertexArrayDescriptor 2 Float 0 c
   textureBinding Texture2D $= (lightmapObj face)

   drawRangeElements Triangles (0,(numOfIndices face))
      (numOfIndices face) UnsignedInt d
   --drawElements Triangles (numOfIndices face) UnsignedInt d


-- renders a mesh face
renderMeshFace :: BSPFace -> VertexArrays -> Ptr GLint -> IO ()
renderMeshFace face (vertexPtr,texturePtr,c,_,_)  vIndex =  do
   startVIndex <- return (startVertIndex face)
   arrayPointer VertexArray $=
      VertexArrayDescriptor 3 Float 0
         (plusPtr vertexPtr (12*(startVIndex)))
   clientState VertexArray  $= Enabled

   activeTexture $= TextureUnit 0
   clientActiveTexture $= TextureUnit 0
   arrayPointer TextureCoordArray $=
      VertexArrayDescriptor 2 Float 0
         (advancePtr texturePtr (2*(startVertIndex face)))
   clientState TextureCoordArray $= Enabled
   texture Texture2D $= Enabled
   textureBinding Texture2D $= (textureObj face)

   activeTexture $= TextureUnit 1
   clientActiveTexture $= TextureUnit 1
   arrayPointer TextureCoordArray $=
      VertexArrayDescriptor 2 Float 0 (plusPtr c (8*(startVIndex)))
   clientState TextureCoordArray $= Enabled
   texture Texture2D $= Enabled
   textureBinding Texture2D $= (lightmapObj face)

   drawRangeElements Triangles (0,fromIntegral (numOfVerts face))
      (numOfIndices face ) UnsignedInt (plusPtr vIndex (4*(startIndex face)))


-- renders patch surfaces
renderPatches :: BSPFace -> IO()
renderPatches face = do
   mapM_ (renderPatch face) (patch face)


renderPatch :: BSPFace -> BSPPatch -> IO()
renderPatch face bsppatch=  do
   arrayPointer VertexArray $=
      VertexArrayDescriptor 3 Float 28 (patchPtr bsppatch)
   clientState VertexArray $= Enabled

   activeTexture $= TextureUnit 0
   clientActiveTexture $= TextureUnit 0
   arrayPointer TextureCoordArray $=
      VertexArrayDescriptor 2 Float 28 (plusPtr (patchPtr bsppatch) 12)
   clientState TextureCoordArray $= Enabled
   texture Texture2D $= Enabled
   textureBinding Texture2D $= (textureObj face)

   activeTexture $= TextureUnit 1
   clientActiveTexture $= TextureUnit 1
   arrayPointer TextureCoordArray $=
      VertexArrayDescriptor 2 Float 28 (plusPtr (patchPtr bsppatch) 20)
   clientState TextureCoordArray $= Enabled
   texture Texture2D $= Enabled
   textureBinding Texture2D $= (lightmapObj face)

   multiDrawElements TriangleStrip (numIndexPtr bsppatch)
      UnsignedInt (indexPtrPtr bsppatch)
         (fromIntegral (patchLOD bsppatch))



-------------------------------------------------------------------------------
-- reading functions

-- reads a BSP file
readBSP :: FilePath -> IO(IORef(BSPMap))
readBSP filePath = withBinaryFile filePath $ \handle -> do
   readHeader handle
   lumps           <- mapM (readLump handle)
                        [ 0 .. (kMaxLumps -1)] :: IO [BSPLump]
   (a,b,c,d,e)     <- readVertices handle lumps
   indcs           <- readIndices  handle lumps
   newbitset       <- createBitset lumps
   newVertexArrays <- dataToPointers (a,b,c,d,e)
   indexPtr        <- newArray indcs
   newNodes        <- readNodes handle lumps
   newLeaves       <- readLeaves handle lumps newVertexArrays indexPtr
   newVisData      <- readVisData handle lumps
   let leafArray = listArray (0,((length newLeaves)-1)) newLeaves
   let nodeArray = listArray (0,((length newNodes)-1)) newNodes
   ntree <- constructTree nodeArray leafArray 0
   bsp <-  (newIORef ( BSPMap  {
                          vertexData = newVertexArrays,
                          vindices   = indexPtr,
                          leaves     = (reverse newLeaves),
                          tree       = ntree,
                          visData    = newVisData,
                          bitset     = newbitset
   }))
   return bsp

constructTree :: Array Int BSPNode -> Array Int BSPLeaf -> Int -> IO(Tree)
constructTree nodes lvs ind = do
   case (ind >= 0) of
      True  -> do
         let currentNode = (nodes ! ind)
         leftNode  <- constructTree nodes lvs (front currentNode)
         rightNode <- constructTree nodes lvs (back currentNode)
         return (Branch currentNode leftNode rightNode)
      False -> do
         let currentLeaf = (lvs ! ((-1)*(ind+1)))
         return (Leaf currentLeaf)


createBitset :: [BSPLump] -> IO BitSet
createBitset lumps = do
   (_,lngth) <- (getLumpData (lumps !! kFaces))
   newbitset  <- emptyBS (lngth `div` 104)
   return newbitset




-- - - - - - - - - - - - - - - - - - -
-- reads the BSP files header information


readHeader :: Handle -> IO BSPHeader
readHeader handle = do
   buf <- mallocBytes 4
   hGetBuf handle buf 4
   iD <- mapM (peekByteOff buf) [ 0 ..  3] :: IO [CChar]
   hGetBuf handle buf cIntSize
   ver <- (peek (castPtr buf :: Ptr CInt)) :: IO CInt
   free buf
   return (BSPHeader {
              strID   = map castCCharToChar iD,
              version = (fromIntegral ver)})




-- - - - - - - - - - - - - - - - - - -
-- reads the lumps in our bsp

readLump :: Handle -> Int -> IO BSPLump
readLump handle _ = do
   buf  <- mallocBytes cIntSize
   hGetBuf handle buf cIntSize
   offs <- (peek (castPtr buf :: Ptr CInt)) :: IO CInt
   hGetBuf handle buf cIntSize
   l    <- (peek (castPtr buf :: Ptr CInt)) :: IO CInt
   free buf
   return (BSPLump {offset = (fromIntegral offs),
                    len    = (fromIntegral l)})

getLumpData :: BSPLump -> IO (Int, Int)
getLumpData lump = return (offset lump,len lump)

-- - - - - - - - - - - - - - - - - - -
-- reads the nodes

readNodes :: Handle -> [BSPLump] -> IO [BSPNode]
readNodes handle lumps = do
   planes <- readPlanes handle lumps
   let planeArray = listArray (0,((length planes)-1)) planes
   (offst,lngth) <- (getLumpData (lumps !! kNodes))
   offs <- getOffsets lngth offst 36
   nodes <- mapM (readNode handle planeArray) offs
   return  nodes

readNode :: Handle -> Array Int BSPPlane -> Int -> IO (BSPNode)
readNode handle planeArray offst = do
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf <- mallocBytes 4
   let getCInt  =
          getAndPeek handle (castPtr buf :: Ptr CInt) (undefined :: CInt)
   let getCInts =
          getAndPeeks handle (castPtr buf :: Ptr CInt) (undefined :: CInt)
   let ints    = liftM toInts (getCInts 3)
   let get3Ints   = liftM get3t ints
   plnIndex  <- getCInt
   frt  <- getCInt
   bck  <- getCInt
   nMin <- get3Ints
   nMax <- get3Ints
   let pln = planeArray ! (fromIntegral plnIndex)
   return $ BSPNode {
               planeNormal = (pNormal pln),
               dist        = (distance pln),
               front       = fromIntegral frt,
               back        = fromIntegral bck,
               nodeMin     = nMin,
               nodeMax     = nMax
            }




-- - - - - - - - - - - - - - - - - - -
-- reads the planes in the nodes

readPlanes :: Handle -> [BSPLump] -> IO [BSPPlane]
readPlanes handle lumps = do
   (offst,lngth) <- (getLumpData (lumps !! kPlanes))
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf <- mallocBytes lngth
   hGetBuf handle buf lngth
   let ptrs = getPtrs buf lngth 16
   planes <- mapM readPlane ptrs
   free buf
   return planes

readPlane :: Ptr a -> IO (BSPPlane)
readPlane ptr = do
   [e1,e2,e3,e4] <- getFloats ptr 4
   return $ BSPPlane {
        pNormal  = (fromRational (toRational e1),
           fromRational (toRational e3),
           fromRational (toRational ((-1)*e2))),
        distance = (fromRational (toRational e4))
   }




-- - - - - - - - - - - - - - - - - - -
-- reads the leaves

readLeaves :: Handle -> [BSPLump] -> VertexArrays -> Ptr GLint -> IO [BSPLeaf]
readLeaves handle lumps vertArrays indcs = do
   faces              <- readFaces handle lumps vertArrays  indcs
   let faceArray      =  listArray (0,((length faces)-1))  faces
   leaffaces          <- readLeafFaces handle lumps
   let leafFaceArray  =  listArray (0,((length leaffaces)-1)) leaffaces
   brushes            <- readBrushes handle lumps
   let brushArray     =  listArray (0,((length brushes)-1)) brushes
   leafbrushes        <- readLeafBrushes handle lumps
   let leafBrushArray =  listArray (0,((length leafbrushes)-1)) leafbrushes
   (offst,lngth)    <- getLumpData (lumps !! kLeafs)
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf <- mallocBytes lngth
   hGetBuf handle buf lngth
   let ptrs = getPtrs buf lngth 48
   nodes <-
      mapM (readLeaf leafFaceArray faceArray leafBrushArray brushArray) ptrs
   free buf
   return nodes


readLeaf ::
   Array Int Int -> Array Int BSPFace ->
      Array Int Int -> Array Int BSPBrush ->Ptr a ->IO (BSPLeaf)
readLeaf leafFaceArray faceArray leafBrushArray brushArray ptr = do
   [e1,e2,e3,e4,e5,e6,e7,e8,e9,e10,e11,e12] <- getInts ptr 12
   let leafIndices  = map (leafFaceArray !) [((e9+e10)-1),((e9+e10)-2)..e9]
   let faceList     = map (faceArray !) leafIndices
   let brushIndices = map (leafBrushArray !) [e11..(e11+e12-1)]
   let brushList    = map (brushArray !) brushIndices
   return $ BSPLeaf {
        cluster          = e1,
        area             = e2,
        leafMin          = (realToFrac e3,
                            realToFrac e5,
                            realToFrac ((-1)*e4)),
        leafMax          = (realToFrac e6,
                            realToFrac e8,
                            realToFrac ((-1)*e7)),
        leafface         = e9,
        numOfLeafFaces   = e10,
        leafBrush        = e11,
        numOfLeafBrushes = e12,
        leafFaces        = faceList,
        leafBrushes      = brushList
   }




-- - - - - - - - - - - - - - - - - - -
-- huge function for reading the faces in our leaves


readFaces :: Handle -> [BSPLump] -> VertexArrays -> Ptr GLint -> IO [BSPFace]
readFaces handle lumps vertArrays indcs = do
   lightMaps         <- readLightMaps handle lumps
   let lightMapArray =  listArray (0,((length lightMaps)-1)) lightMaps
   texInfos          <- readTexInfos handle lumps
   texFileNames      <- return (map strName texInfos)
   texObjs           <- getAndCreateTextures texFileNames
   let texObjArray   =  listArray (0,((length texObjs)-1)) texObjs
   (offst,lngth)   <- (getLumpData (lumps !! kFaces))
   offs              <- getOffsets lngth offst 104
   faces             <- mapM
     (readFace handle offst lightMapArray texObjArray vertArrays indcs) offs
   return  faces


readFace ::
  Handle -> Int -> Array Int TextureObject -> Array Int (Maybe TextureObject)->
     VertexArrays -> Ptr GLint -> Int ->  IO (BSPFace)
readFace handle origin  lightmaps textures
  vertArrays@(a1,b1,c1,_,_) indcs offst = do
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf <- mallocBytes 4
   let getCInts   =
          getAndPeeks handle (castPtr buf :: Ptr CInt) (undefined :: CInt)
   let getCFloats =
          getAndPeeks handle (castPtr buf :: Ptr CFloat) (undefined :: CFloat)
   let ints       = liftM toInts (getCInts 4)
   let get4Ints   = liftM get4t ints
   let floats     = liftM toFloats (getCFloats 3)
   let get3Floats = liftM get3t floats
   let twoInts    = liftM toInts (getCInts 2)
   let get2Ints   = liftM get2t twoInts
   (a,b,c,d) <- get4Ints
   (e,f,g,h) <- get4Ints
   (i,j,k,l) <- get4Ints
   lMPos     <- get3Floats
   lMVec1    <- get3Floats
   lMVec2    <- get3Floats
   norms     <- get3Floats
   sz        <- get2Ints
   free buf
   bspPatch <- checkForPatch c d sz vertArrays
   return BSPFace {
    textureObj     = textures ! a,
    effect         = b,
    faceType       = c,
    startVertIndex = d,
    numOfVerts     = e,
    startIndex     = f,
    numOfIndices   = fromIntegral g,
    lightmapObj    = fixLightmap h lightmaps,
    lMapCorner     = (i, j),
    lMapSize       = (k, l),
    lMapPos        = lMPos,
    lMapVecs       = [lMVec1,lMVec2],
    vNormal        = norms,
    size           = sz,
    faceNo         = (offst - origin)`div` 104,
    patch          = bspPatch,
    arrayPtrs      = (plusPtr a1 (12*d),
                      plusPtr b1 (8*d),
                      plusPtr c1 (8*d),
                      plusPtr indcs (4*f))
    }


-- - - - - - - - - - - - - - - - - - -
-- reads the leafaces that refer to the faces

readLeafFaces :: Handle -> [BSPLump] -> IO [BSPLeafFace]
readLeafFaces handle lumps = do
   (offst,lngth) <- (getLumpData (lumps !! kLeafFaces))
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf <- mallocBytes lngth
   hGetBuf handle buf lngth
   leaffaces <- getInts buf (lngth `div` 4)
   free buf
   return leaffaces

-- - - - - - - - - - - - - - - - - - -
-- reads the brushes

readBrushes :: Handle -> [BSPLump] -> IO [BSPBrush]
readBrushes handle lumps = do
   brushsides         <- readBrushSides handle lumps
   let brushSideArray =  listArray (0,((length brushsides)-1)) brushsides
   texInfos           <- readTexInfos handle lumps
   let texInfoArray   =  listArray (0,((length texInfos)-1)) texInfos
   (offst,lngth)    <- (getLumpData (lumps !! kBrushes))
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf                <- mallocBytes (lngth)
   hGetBuf handle buf (lngth)
   let ptrs           = getPtrs buf (lngth) 12
   brushes            <- mapM (readBrush brushSideArray texInfoArray) ptrs
   free buf
   return brushes

readBrush ::  Array Int BSPBrushSide ->
   Array Int BSPTexInfo -> Ptr a ->IO (BSPBrush)
readBrush brushSideArray texInfos ptr = do
   [e1,e2,e3] <- getInts ptr 3
   let bSides = map (brushSideArray !) [e1..(e1+e2-1)]
   return $ BSPBrush {
      brushSide        = e1,
      numOfBrushSides  = e2,
      brushSides       = bSides,
      bTextureID       = e3,
      textureType      = (contents (texInfos ! e3))
   }




-- - - - - - - - - - - - - - - - - - -
-- reads the brush sides in our brushes

readBrushSides :: Handle -> [BSPLump] -> IO [BSPBrushSide]
readBrushSides handle lumps = do
   planes          <- readPlanes handle lumps
   let planeArray  =  listArray (0,((length planes)-1)) planes
   (offst,lngth) <- (getLumpData (lumps !! kBrushSides))
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf             <- mallocBytes (lngth)
   hGetBuf handle buf (lngth)
   let ptrs        =  getPtrs buf (lngth) 8
   brushsides      <- mapM (readBrushSide planeArray) ptrs
   free buf
   return brushsides

readBrushSide ::  Array Int BSPPlane -> Ptr a ->IO (BSPBrushSide)
readBrushSide planeArray ptr = do
   [e1,e2] <- getInts ptr 2
   let pln = planeArray ! (fromIntegral e1)
   return $ BSPBrushSide {
      bsPlane     = e1,
      bsPlaneNorm = (pNormal pln),
      bsPlaneDist = (distance pln),
      bsTextureID = e2
   }

-- - - - - - - - - - - - - - - - - - -
-- reads the leaf brushes that refer to the brushes

readLeafBrushes :: Handle -> [BSPLump] -> IO [BSPLeafFace]
readLeafBrushes handle lumps = do
   (offst,lngth) <- (getLumpData (lumps !! kLeafBrushes))
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf <- mallocBytes lngth
   hGetBuf handle buf lngth
   leafbrushes <- getInts buf (lngth `div` 4)
   free buf
   return leafbrushes

-- - - - - - - - - - - - - - - - - - -
-- read the PVS visibility information

readVisData :: Handle -> [BSPLump] -> IO (Maybe BSPVisData)
readVisData handle lumps = do
   (offst,lngth) <- (getLumpData (lumps !! kVisData))
   case lngth of
     0  -> return Nothing
     _  -> do
        hSeek handle AbsoluteSeek (fromIntegral offst)
        buf <- mallocBytes lngth
        hGetBuf handle buf lngth
        cInts <- peekArray 2 (castPtr buf :: Ptr CInt)
        let [numC, bytesPerC] = toInts cInts
        bitst <- peekArray (numC*bytesPerC) $ plusPtr (castPtr buf :: Ptr Word8) 8
        bs <-
           Arr.newListArray (0 ,(numC*bytesPerC*8-1)) (toBools bitst)
        return (Just BSPVisData {
                        numOfClusters   = numC,
                        bytesPerCluster = bytesPerC,
                        bitSets         = bs
               })




-- - - - - - - - - - - - - - - - - - -
-- reads vertex information


readVertices :: Handle -> [BSPLump] -> IO VertexData
readVertices handle lumps = do
   (offst,lngth) <- getLumpData (lumps !! kVertices)
   offs            <- getOffsets lngth offst 44
   verts           <- mapM (readVertex handle) offs
   (v,t,l,n,r)     <- seperateArrays verts
   return $ toVertexData (concat v, concat t, concat l, concat n, concat r)


readVertex :: Handle -> Int -> IO ([CFloat],[CFloat],[CFloat],[CFloat],[Word8])
readVertex handle offst = do
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf <- mallocBytes 4
   let getCFloats =
          getAndPeeks handle (castPtr buf :: Ptr CFloat) (undefined :: CFloat)
   let getWord8s  =
          getAndPeeks handle (castPtr buf :: Ptr Word8) (undefined :: Word8)
   let floats = (getCFloats 3)
   let get3Floats = liftM get3t floats
   (x,y,z)        <- get3Floats
   texCoords      <- getCFloats 2
   lightMapCoords <- getCFloats 2
   normals        <- getCFloats 3
   rgbaVal           <- getWord8s  4
   free buf
   return ([x,z,(-1)*y],texCoords,lightMapCoords,normals,rgbaVal)

dataToPointers :: VertexData -> IO VertexArrays
dataToPointers (a,b,c,d,e) = do
   a1 <- (newArray a)
   b1 <- (newArray b)
   c1 <- (newArray c)
   d1 <- (newArray d)
   e1 <- (newArray e)
   return (a1,b1,c1,d1,e1)

seperateArrays :: [([CFloat],[CFloat],[CFloat],[CFloat],[Word8])] ->
   IO ([[CFloat]],[[CFloat]],[[CFloat]],[[CFloat]],[[Word8]])
seperateArrays verts = return (unzip5 verts)

toVertexData :: ([CFloat],[CFloat],[CFloat],[CFloat],[Word8]) -> VertexData
toVertexData (a,b,c,d,e) = (toFloats a,toFloats b,toFloats c,toFloats d,e)




-- - - - - - - - - - - - - - - - - - -
-- reads lightmaps


readLightMaps :: Handle -> [BSPLump] -> IO [TextureObject]
readLightMaps handle lumps = do
   (offst,lngth) <- (getLumpData (lumps !! kLightmaps))
   offs <- getOffsets lngth offst 49152
   mapM (readLightMap handle) offs

readLightMap :: Handle -> Int -> IO TextureObject
readLightMap handle offst = do
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf <- mallocBytes 49152 :: IO (Ptr Word8)
   hGetBuf handle buf 49152
   mapM (adjustRGB buf 5.0) [0..((16384)-1)]
   texObj <-createLightmapTexture buf
   return texObj

createLightmapTexture :: Ptr Word8 -> IO TextureObject
createLightmapTexture ptr = do
   [texName] <- genObjectNames 1
   rowAlignment  Unpack $= 1
   textureBinding Texture2D $= Just texName
   build2DMipmaps
     Texture2D RGB'
        (fromIntegral (128 :: Int)) (fromIntegral (128 :: Int))
            (PixelData RGB UnsignedByte ptr)
   textureFilter  Texture2D $= ((Linear', Just Nearest), Linear')
   textureFunction $= Modulate
   free ptr
   return texName


-- adjusts the brightness of the lightmap
adjustRGB :: Ptr Word8 -> Float -> Int -> IO ()
adjustRGB lightMap factor offst = do
   ptr <- return (advancePtr lightMap (3*offst))
   [r,g,b] <- (peekArray 3 ptr)
   (r2,tempr) <- scaleRGB (((realToFrac r)*factor)/255) 1
   (g2,tempg) <- scaleRGB (((realToFrac g)*factor)/255) tempr
   (b2,tempb) <- scaleRGB (((realToFrac b)*factor)/255) tempg
   byter2 <- return $ fromIntegral $ (truncate (r2 *  tempb * 255.0) :: Int)
   byteg2 <- return $ fromIntegral $ (truncate (g2 * tempb * 255.0) :: Int)
   byteb2 <- return $ fromIntegral $ (truncate (b2 * tempb * 255.0) :: Int)
   pokeArray (advancePtr lightMap (3*offst)) [byter2,byteg2,byteb2]


scaleRGB :: Float -> Float -> IO (Float,Float)
scaleRGB clr scl = do
                     if ((clr > 1.0) && ((1.0/clr) < scl))
                        then return (clr, 1.0/clr)
                      else
                         return (clr, scl)

fixLightmap ::
    Int -> Array Int TextureObject -> Maybe TextureObject
fixLightmap ind arr
    | ind < 0 = Nothing
    | otherwise = Just (arr ! ind)




-- - - - - - - - - - - - - - - - - - -
-- reads the texture information


readTexInfos :: Handle -> [BSPLump] -> IO [BSPTexInfo]
readTexInfos handle lumps = do
   (offst,lngth) <- (getLumpData (lumps !! kTextures))
   offs <- getOffsets lngth offst 72
   mapM (readTexInfo handle) offs

readTexInfo :: Handle -> Int -> IO (BSPTexInfo)
readTexInfo handle offst = do
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf <- mallocBytes 64 :: IO (Ptr CChar)
   hGetBuf handle buf 64
   str <- peekCAString buf
   hSeek handle AbsoluteSeek ((fromIntegral offst) + 64)
   let getCInt =
         getAndPeek handle (castPtr buf :: Ptr CInt) (undefined :: CInt)
   flgs <- getCInt
   cons <- getCInt
   free buf
   return BSPTexInfo {
      strName  = str,
      flags  = (fromIntegral flgs),
      contents = (fromIntegral cons)
    }




-- - - - - - - - - - - - - - - - - - -
-- reads the indices to the vertex array


readIndices :: Handle -> [BSPLump] -> IO [GLint]
readIndices handle lumps = do
   (offst,lngth) <- (getLumpData (lumps !! kIndices))
   hSeek handle AbsoluteSeek (fromIntegral offst)
   buf   <- mallocBytes lngth
   hGetBuf handle buf lngth
   indces <- mapM
     (peekElemOff (castPtr buf :: Ptr CInt))
         [ 0 .. ((lngth `div` 4)-1)] :: IO [CInt]
   free buf
   return $ map fromIntegral indces




-- - - - - - - - - - - - - - - - - - -

getAndPeek :: (Storable a, Typeable a) => Handle -> Ptr a -> a -> IO a
getAndPeek handle buf be = do
   bytesRead <- hGetBuf handle buf (sizeOf be)
   when (bytesRead /= (sizeOf be)) $
      ioError $ mkIOError eofErrorType "hGetBufFully" (Just handle) Nothing
   val <- (peek buf)
   return val

getAndPeeks :: (Storable a, Typeable a) =>
   Handle -> Ptr a -> a -> Int -> IO [a]
getAndPeeks handle buf be i =
   mapM (\_ -> getAndPeek handle buf be) [1..i]

withBinaryFile :: FilePath -> (Handle -> IO a) -> IO a
withBinaryFile filePath = bracket (openBinaryFile filePath ReadMode) hClose

getOffsets :: Int -> Int -> Int -> IO [Int]
getOffsets lngth off sze = return $ map ((off+) . (sze*)) [0.. ((lngth `div` sze)-1)]

toInts :: (Integral a)=>[a] -> [Int]
toInts a = map fromIntegral a

toFloats :: (Real a) => [a] -> [Float]
toFloats a = map realToFrac a

get2t :: [a] -> (a, a)
get2t list = (list !! 0, list !! 1)

get3t :: [a] -> (a, a, a)
get3t list = (list !! 0, list !! 1, list !! 2)

get4t :: [a] -> (a, a, a, a)
get4t list = (list !! 0, list !! 1, list !! 2, list !! 3)

toBools :: [Word8] -> [Bool]
toBools list =
   [ y | x<-list, y <- map (testBit x) [0..7]]

getInts :: Ptr a -> Int -> IO [Int]
getInts ptr n = do
   ints <- peekArray n (castPtr ptr:: Ptr CInt)
   return $ toInts ints

getFloats :: Ptr a -> Int -> IO [Float]
getFloats ptr n = do
   floats <- peekArray n (castPtr ptr :: Ptr CFloat)
   return $ toFloats floats

cIntSize :: Int
cIntSize = (sizeOf (undefined :: CInt))

getPtrs :: Ptr a -> Int -> Int -> [Ptr a]
getPtrs ptr lngth sze = map ((plusPtr ptr) . (sze *)) [0.. ((lngth `div` sze) - 1)]


