----------------------------------------------------------------------------- -- -- Module : Data.Vec.AABB -- Copyright : Tobias Bexelius -- License : BSD3 -- -- Maintainer : Tobias Bexelius -- Stability : Experimental -- Portability : Portable -- -- | -- This module provides an axis aligned bounding box based on 'Data.Vec's. ----------------------------------------------------------------------------- module Data.Vec.AABB ( AABB(..), aabbTransform, testAABBprojection, Intersection(..) ) where import Data.Monoid import qualified Data.Vec.Base as Vec import Data.Vec.Base ((:.)(..), Mat44, Mat33, Vec3) import Data.Vec.Nat import Data.Vec.LinAlg import Data.Vec.LinAlg.Transform3D -- | An axis aligned bounding box. data AABB = AABB { aabbMin :: Vec3 Float, aabbMax :: Vec3 Float } deriving (Show, Eq) instance Monoid AABB where mempty = let inf = read "Infinity" :: Float in AABB (Vec.vec inf) (Vec.vec (-inf)) mappend (AABB minA maxA) (AABB minB maxB) = AABB (Vec.zipWith min minA minB) (Vec.zipWith max maxA maxB) data Intersection = Inside | Intersecting | Outside deriving (Eq, Show, Ord, Enum, Bounded) -- | Try if an 'AABB' is inside a projection frustum. The AABB must be defined in the same vector space as the matrix, e.g. use the model-view-projection matrix for model-local aabb's. testAABBprojection :: Mat44 Float -> AABB -> Intersection testAABBprojection m = let planes = [-(row n3 m + row n0 m), -(row n3 m - row n0 m), -(row n3 m + row n1 m), -(row n3 m - row n1 m), -(row n3 m + row n2 m), -(row n3 m - row n2 m)] getMin min max = min getMax min max = max vMinF = Vec.map (\ni -> if ni >= 0 then getMin else getMax) vMaxF = Vec.map (\ni -> if ni >= 0 then getMax else getMin) checkPlane (AABB bmin bmax) Outside _ = Outside checkPlane (AABB bmin bmax) state plane = let n = Vec.take n3 plane d = Vec.last plane vMin = Vec.zipWith ($) (Vec.zipWith ($) (vMinF n) bmin) bmax vMax = Vec.zipWith ($) (Vec.zipWith ($) (vMaxF n) bmin) bmax in if (n `dot` vMin) + d > 0 then Outside else if (n `dot` vMax) + d >= 0 then Intersecting else Inside in \aabb -> foldl (checkPlane aabb) Inside planes -- | Transforms an 'AABB* using a 4x4 matrix. Note that this may grow the AABB and is not associative with matrix multiplication, i.e. -- -- > (m2 `multmm` m1) `aabbTransform` aabb -- -- is usually not the same as -- -- > m2 `aabbTransform` (m1 `aabbTransform` aabb) -- -- (The former is preferred as it minimizes the growing of the AABB). aabbTransform :: Mat44 Float -> AABB -> AABB aabbTransform m (AABB bmin bmax) = let center = (bmax + bmin) / 2 extent = bmax - center mAbs33 = Vec.map (Vec.map abs . Vec.take n3) $ Vec.take n3 m tcenter = project $ m `multmv` homPoint center textent = mAbs33 `multmv` extent in AABB (tcenter - textent) (tcenter + textent)