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

Provides a function to extract the frustum and test
whether an AABB intersects it

-}

module Frustum where

import Graphics.UI.GLUT
import Graphics.Rendering.OpenGL.GL.CoordTrans


type FPlane =  (Double, Double, Double, Double)
type Frustum = (FPlane,FPlane,FPlane,FPlane,FPlane,FPlane)


normalisePlane :: FPlane -> IO FPlane
normalisePlane (x,y,z,d) = do
   let reciMag = (1/(sqrt(x*x+y*y+z*z)))
   return (x*reciMag, y*reciMag, z*reciMag, d*reciMag)


-- gets the frustum from the current view
getFrustum :: IO Frustum
getFrustum = do

   mvMatrix <- get (matrix (Just (Modelview 0))) :: IO (GLmatrix GLdouble)
   [m00,m01,m02,m03,
    m10,m11,m12,m13,
    m20,m21,m22,m23,
    m30,m31,m32,m33] <- getMatrixComponents ColumnMajor mvMatrix
   pjMatrix <- get (matrix (Just (Projection))) :: IO (GLmatrix GLdouble)
   [p00,p01,p02,p03,
    p10,p11,p12,p13,
    p20,p21,p22,p23,
    p30,p31,p32,p33] <- getMatrixComponents ColumnMajor pjMatrix

   let clip00 = m00*p00 + m01*p10 + m02*p20 + m03*p30
   let clip01 = m00*p01 + m01*p11 + m02*p21 + m03*p31
   let clip02 = m00*p02 + m01*p12 + m02*p22 + m03*p32
   let clip03 = m00*p03 + m01*p13 + m02*p23 + m03*p33

   let clip10 = m10*p00 + m11*p10 + m12*p20 + m13*p30
   let clip11 = m10*p01 + m11*p11 + m12*p21 + m13*p31
   let clip12 = m10*p02 + m11*p12 + m12*p22 + m13*p32
   let clip13 = m10*p03 + m11*p13 + m12*p23 + m13*p33

   let clip20 = m20*p00 + m21*p10 + m22*p20 + m23*p30
   let clip21 = m20*p01 + m21*p11 + m22*p21 + m23*p31
   let clip22 = m20*p02 + m21*p12 + m22*p22 + m23*p32
   let clip23 = m20*p03 + m21*p13 + m22*p23 + m23*p33

   let clip30 = m30*p00 + m31*p10 + m32*p20 + m33*p30
   let clip31 = m30*p01 + m31*p11 + m32*p21 + m33*p31
   let clip32 = m30*p02 + m31*p12 + m32*p22 + m33*p32
   let clip33 = m30*p03 + m31*p13 + m32*p23 + m33*p33

   let rightX = clip03 - clip00
   let rightY = clip13 - clip10
   let rightZ = clip23 - clip20
   let rightD = clip33 - clip30

   let leftX  = clip03 + clip00
   let leftY  = clip13 + clip10
   let leftZ  = clip23 + clip20
   let leftD  = clip33 + clip30

   let bottomX  = clip03 + clip01
   let bottomY  = clip13 + clip11
   let bottomZ  = clip23 + clip21
   let bottomD  = clip33 + clip31

   let topX  = clip03 - clip01
   let topY  = clip13 - clip11
   let topZ  = clip23 - clip21
   let topD  = clip33 - clip31

   let backX  = clip03 - clip02
   let backY  = clip13 - clip12
   let backZ  = clip23 - clip22
   let backD  = clip33 - clip32

   let frontX  = clip03 + clip02
   let frontY  = clip13 + clip12
   let frontZ  = clip23 + clip22
   let frontD  = clip33 + clip32

   let toFrac (a,b,c,d) = (realToFrac a, realToFrac b, realToFrac c, realToFrac d)

   rightPlane  <- normalisePlane $ toFrac $ (rightX ,rightY ,rightZ ,rightD)
   leftPlane   <- normalisePlane $ toFrac $ (leftX  ,leftY  ,leftZ  ,leftD)
   bottomPlane <- normalisePlane $ toFrac $ (bottomX,bottomY,bottomZ,bottomD)
   topPlane    <- normalisePlane $ toFrac $ (topX   ,topY   ,topZ   ,topD)
   backPlane   <- normalisePlane $ toFrac $ (backX  ,backY  ,backZ  ,backD)
   frontPlane  <- normalisePlane $ toFrac $ (frontX ,frontY ,frontZ,frontD)

   return (rightPlane,leftPlane,bottomPlane,topPlane,backPlane,frontPlane)


-- tests if a box intersects a plane
testBox :: (Double,Double,Double)->(Double,Double,Double) -> FPlane ->Bool
testBox (x,y,z) (x2,y2,z2) (a,b,c,d)
   | (a * x  + b * y  + c * z  + d > 0) = True
   | (a * x2 + b * y  + c * z  + d > 0) = True
   | (a * x  + b * y2 + c * z  + d > 0) = True
   | (a * x2 + b * y2 + c * z  + d > 0) = True
   | (a * x  + b * y  + c * z2 + d > 0) = True
   | (a * x2 + b * y  + c * z2 + d > 0) = True
   | (a * x  + b * y2 + c * z2 + d > 0) = True
   | (a * x2 + b * y2 + c * z2 + d > 0) = True
   | otherwise = False


-- tests if an AABB lies within a frustum
boxInFrustum :: Frustum -> (Double,Double,Double) -> (Double,Double,Double) -> Bool
boxInFrustum (a,b,c,d,e,f) mn mx
   | not(test a) || not(test b) || not(test c)
     || not(test d) || not(test e) || not(test f) = False
   | otherwise = True
   where test = testBox mn mx

