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

This modules is used to generate a biquadratic patch used in the
Q3Map2 format.

The code for this was translated from

http://graphics.cs.brown.edu/games/quake/quake3.html

it can also be found in the source code for Paul's Quake 3 BSP loader

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

-}


module Curves (checkForPatch, BSPPatch(..)) where

import Foreign hiding (newArray)
import Data.Array.IArray
-- import Data.Array.MArray (newArray)
import Data.Array.IO
import Graphics.UI.GLUT (GLint, GLsizei)
import Foreign.Storable


data BSPPatch = BSPPatch {
           patchLOD    :: Int,               -- the level of tesselation
           patchPtr    :: Ptr Float,         -- points to patch vertices
           indexPtrPtr :: Ptr (Ptr GLint),  -- points to indices
           numIndexPtr :: Ptr GLsizei       -- the number of indices
           } deriving Show


-- given a face type return a list of patches if the facetype is 2.
-- Otherwise return an empty list.
checkForPatch :: Int -> Int -> (Int, Int) ->
   (Ptr Float, Ptr Float, Ptr Float,Ptr Float, Ptr Word8)
          -> IO [BSPPatch]
checkForPatch faceType startVIndex (width,height) vertData
   |faceType == 2 = do
          patches <- createPatches vertData startVIndex width height 4
          return patches
   |otherwise = return []


-- Create control points for each patch.
-- Each patch has 9 control points.
getControlPointIndices :: Int -> Int -> Int -> [Int]
getControlPointIndices  i width height =
   concat [(create3x3ControlPoints x y)|
                    y <-[0..(((height-1) `div` 2)-1)],
                    x <-[0..(((width-1)  `div` 2)-1)]]
   where
         create3x3ControlPoints x y =
            [(i+((y*2*width)+(x*2))+(row*width)+point) |
                   row   <- [0..2],
                   point <- [0..2]]


-- Take a list of control points and split them into lists of 9
splitControlPoints :: [VertTup] -> [[VertTup]]
splitControlPoints [] = []
splitControlPoints tups = (take 9 tups):(splitControlPoints $ drop 9 tups)


-- gets the control points
getControlPoints  :: (Ptr Float, Ptr Float, Ptr Float, Ptr Float, Ptr Word8) ->
   Int -> Int -> Int -> IO [Array Int VertTup]
getControlPoints vertexData startIndex width height = do
   -- get the indices for the control points
   let indcs = getControlPointIndices  startIndex width  height
   -- get the vertices at those indices
   controlPoints <- mapM (readControlPoints vertexData) indcs
   -- divide the lists into arrays of 9 control points
   return $ map (listArray (0,8)) (splitControlPoints controlPoints)

-- reads the control point information from the vertex arrays
readControlPoints :: (Ptr Float, Ptr Float, Ptr Float,Ptr Float, Ptr Word8) ->
   Int -> IO VertTup
readControlPoints  (vert, uv, lmuv, _, _) i = do
   x   <- peekElemOff vert  vertIndex     -- vertex coord
   y   <- peekElemOff vert (vertIndex+1)
   z   <- peekElemOff vert (vertIndex+2)
   u   <- peekElemOff uv    uvIndex       -- tex coord
   v   <- peekElemOff uv   (uvIndex+1)
   lmu <- peekElemOff lmuv  lmIndex       -- lightmap coord
   lmv <- peekElemOff lmuv (lmIndex+1)
   return (x,y,z,u,v,lmu,lmv)
   where
         vertIndex = i*3
         uvIndex         = i*2
         lmIndex         = i*2


-- write the coordinate, texture coordinate and lightmap coordinates
-- for the cntrol points
writeControlPointData :: [VertTup] -> Int -> Ptr Float -> IO ()
writeControlPointData  [] _ _ = return()
writeControlPointData  ((a,b,c,d,e,f,g):rest) indx ptr = do
   let i = (indx*7)
   pokeElemOff ptr i       a
   pokeElemOff ptr (i+1) b
   pokeElemOff ptr (i+2) c
   pokeElemOff ptr (i+3) d
   pokeElemOff ptr (i+4) e
   pokeElemOff ptr (i+5) f
   pokeElemOff ptr (i+6) g
   writeControlPointData rest (indx+1) ptr

type VertTup = (Float,Float,Float,Float,Float,Float,Float)

-- multiplies a set of floats by n
mul7 :: VertTup -> Float -> VertTup
mul7 (a,b,c,d,e,f,g) n = ((n*a),(n*b),(n*c),(n*d),(n*e),(n*f),(n*g))


-- adds to sets of floats together
add7 :: VertTup -> VertTup -> VertTup
add7 (u1,u2,u3,u4,u5,u6,u7) (v1,v2,v3,v4,v5,v6,v7) =
            (u1+v1,u2+v2,u3+v3,u4+v4,u5+v5,u6+v6,u7+v7)


-- create a set of patches
createPatches :: (Ptr Float, Ptr Float, Ptr Float, Ptr Float, Ptr Word8) ->
   Int -> Int -> Int -> Int -> IO [BSPPatch]
createPatches vertData startVert width height tesselation = do
   controlPoints <- getControlPoints vertData startVert width height
   patches <- mapM (createPatch tesselation) controlPoints
   return patches


createPatch :: Int -> Array Int VertTup -> IO BSPPatch
createPatch tesselation controlPoints  = do
   ptr <- mallocBytes (((tesselation+1)*(tesselation+1))*28)
   createPatch' tesselation ptr controlPoints
   createPatch'' tesselation ptr controlPoints
   (numiptr,iptrptr)<- generateIndices tesselation
   return (BSPPatch {
                    patchLOD = tesselation,
                    patchPtr = ptr,
                    indexPtrPtr = iptrptr,
                    numIndexPtr = numiptr
                 })


createPatch' ::Int ->  Ptr Float -> Array Int VertTup -> IO()
createPatch' tess ptr arr = do
   let patchVerts = map (bezier tess (arr!0) (arr!3) (arr!6)) [0..tess]
   writeControlPointData patchVerts 0 ptr


createPatch'' ::Int ->  Ptr Float -> Array Int VertTup -> IO()
createPatch'' tess ptr arr = do
   mapM_        (createPatch''' tess ptr arr) [1..tess]


createPatch''' ::Int -> Ptr Float -> Array Int VertTup -> Int -> IO()
createPatch''' tess ptr arr u = do
   let tup1 = bezier tess (arr!0) (arr!1) (arr!2) u
   let tup2 = bezier tess (arr!3) (arr!4) (arr!5) u
   let tup3 = bezier tess (arr!6) (arr!7) (arr!8) u
   let patchVerts = map (bezier tess tup1 tup2 tup3) [0..tess]
   writeControlPointData patchVerts 0 (plusPtr ptr (((tess+1)*u)*28))


bezier :: Int -> VertTup -> VertTup -> VertTup -> Int -> VertTup
bezier tes cp1 cp2 cp3 i = add7 (add7 d1 d2) d3
   where
         d1 = mul7 cp1 ((1-p)*(1-p))
         d2 = mul7 cp2 ((1-p)*p*2)
         d3 = mul7 cp3 (p*p)
         p  = (realToFrac i)/(realToFrac tes)


-- generate indices
generateIndices :: Int -> IO (Ptr GLsizei, Ptr (Ptr GLint))
generateIndices tess = do
   indexArray <- newArray (0,((tess*(tess+1)*2)-1)) 0
   let pt1 = [ ((((row*(tess+1))+point)*2)+1, fromIntegral ((row*(tess+1))+point)) |
                         row<-[0..(tess-1)],
                         point<-[0..tess]]
   let pt2 = [ ((((row*(tess+1))+point)*2), fromIntegral (((row+1)*(tess+1))+point)) |
                         row<-[0..(tess-1)],
                         point<-[0..tess]]
   mapM_ (writeIndices indexArray) pt1
   mapM_ (writeIndices indexArray) pt2
   indexList <- (getElems indexArray)
   indexPtr <- mallocBytes ((tess * (tess+1)*2) * (sizeOf (undefined :: GLint)))
   pokeArray indexPtr indexList
   numArrayIndicesPtr <- mallocBytes (tess * (sizeOf (undefined :: GLsizei)))
   pokeArray numArrayIndicesPtr (map (\_->(fromIntegral (2*(tess+1)))) [0..(tess-1)])
   indexptrptr <- mallocBytes (tess * (sizeOf (undefined :: Ptr GLint)))
   let ptrPtr = map (plusPtr indexPtr) [((sizeOf (undefined :: GLint)) * (row*2*(tess+1))) | row <-[0..(tess-1)]]
   pokeArray indexptrptr ptrPtr
   return (numArrayIndicesPtr, indexptrptr)


-- writes the indices to memory
writeIndices :: IOArray Int GLint -> (Int,GLint) -> IO ()
writeIndices indcs (pos,content) = writeArray indcs pos content
