{- Copyright (c) 2008, Scott E. Dillard. All rights reserved. -} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TypeFamilies #-} {-# OPTIONS_HADDOCK ignore-exports,prune #-} module Data.Vec.LinAlg (dot ,normSq ,norm ,normalize ,cross ,homPoint ,homVec ,project ,multvm ,multmv ,multmm ,translate ,column ,row ,Transpose(transpose) ,SetDiagonal(setDiagonal) ,GetDiagonal(getDiagonal) ,scale ,diagonal ,identity ,det ,cramer'sRule ,NearZero(nearZero) ,GaussElim(gaussElim) ,BackSubstitute(backSubstitute) ,BackSubstitute'(backSubstitute') ,invert ,invertAndDet ,solve ,translation ,rotationX ,rotationY ,rotationZ ,rotationVec ,rotationEuler ,rotationQuat ,rotationLookAt ,scaling ,perspective ,orthogonal ) where import Prelude hiding (map,zipWith,foldl,foldr,reverse,take,drop, head,tail,sum,length,last) import qualified Prelude as P import Data.Vec.Base import Data.Vec.Nat import Control.Monad import Data.Maybe -- | dot \/ inner \/ scalar product dot :: (Num a, Fold v a, ZipWith a a a v v v) => v -> v -> a dot u v = fold (+) (zipWith (*) u v) {-# INLINE dot #-} -- | vector norm, squared normSq :: (Num a, Num v, Fold v a, ZipWith a a a v v v) => v -> a normSq v = dot v v {-# INLINE normSq #-} -- | vector \/ L2 \/ Euclidean norm norm :: (Num v, Floating a, Fold v a, ZipWith a a a v v v) => v -> a norm v = sqrt (dot v v) {-# INLINE norm #-} -- | @normalize v@ is a unit vector in the direction of @v@. @v@ is assumed -- non-null. normalize :: (Floating a, Num v, Fold v a, Map a a v v, ZipWith a a a v v v) => v -> v normalize v = map (/ norm v) v {-# INLINE normalize #-} -- | 3d cross product. cross :: Num a => Vec3 a -> Vec3 a -> Vec3 a cross (ux:.uy:.uz:._) (vx:.vy:.vz:._) = (uy*vz-uz*vy):.(uz*vx-ux*vz):.(ux*vy-uy*vx):.() {-# INLINE cross #-} -- | lift a point into homogenous coordinates homPoint :: (Snoc v a v', Num a) => v -> v' homPoint v = snoc v 1 {-# INLINE homPoint #-} -- | point-at-infinity in homogenous coordinates homVec :: (Snoc v a v', Num a) => v -> v' homVec v = snoc v 0 {-# INLINE homVec #-} -- | project a vector from homogenous coordinates. Last vector element is -- assumed non-zero. project :: ( Reverse' () t1 v' , Fractional t1 , Vec a t t1 , Reverse' () v (t :. t1) ) => v -> v' project v = case reverse v of (w:.u) -> reverse (u/vec w) {-# INLINE project #-} -- | row vector * matrix multvm :: ( Transpose m mt , Map v a mt v' , Fold v a , ZipWith a a a v v v , Num a , Num v ) => v -> m -> v' multvm v m = map (dot v) (transpose m) {-# INLINE multvm #-} -- | matrix * column vector multmv :: ( Map v a m v' , Num v , Fold v a , ZipWith a a a v v v , Num a ) => m -> v -> v' multmv m v = map (dot v) m {-# INLINE multmv #-} -- | matrix * matrix multmm :: (Map v v' m1 m3 ,Map v a b v' ,Transpose m2 b ,Fold v a ,ZipWith a a a v v v ,Num v ,Num a ) => m1 -> m2 -> m3 multmm a b = map (\v -> map (dot v) (transpose b)) a {-# INLINE multmm #-} -- | apply a translation to a projective transformation matrix translate :: (Transpose m mt ,Reverse' () mt (v' :. t) ,Reverse' (v' :. ()) t v'1 ,Transpose v'1 m ,Num v' ,Num a ,Snoc v a v' ) => v -> m -> m translate v m = case reverse (transpose m) of (h:.t) -> transpose (reverse ((homVec v + h) :. t)) {-# INLINE translate #-} -- | get the @n@-th column as a vector. @n@ is a type-level natural. column :: (Transpose m mt, Access n v mt) => n -> m -> v column n = get n . transpose {-# INLINE row #-} -- | get the @n@-th row as a vector. @n@ is a type-level natural. row :: (Access n a v) => n -> v -> a row = get {-# INLINE column #-} -- Matrix transpose wrapper class: infers type of one argument from the other, -- because Transpose` can't do it, the fundeps there can't be bijective -- | matrix transposition class Transpose a b | a -> b, b -> a where transpose :: a -> b instance Transpose () () where transpose = id instance (Vec (Succ n) s (s:.ra) --(s:ra) is an n-vector of s'es (row of a) ,Vec (Succ m) (s:.ra) ((s:.ra):.a) --a is an m-vector of ra's ,Vec (Succ m) s (s:.rb) --rb is an m-vector of s'es (row of b) ,Vec (Succ n) (s:.rb) ((s:.rb):.b) --b is an n-vector of rb's ,Transpose' ((s:.ra):.a) ((s:.rb):.b) ) => Transpose ((s:.ra):.a) ((s:.rb):.b) where transpose = transpose' {-# INLINE transpose #-} class Transpose' a b | a->b where transpose' :: a -> b instance Transpose' () () where transpose' = id {-# INLINE transpose' #-} instance (Transpose' vs vs') => Transpose' ( () :. vs ) vs' where transpose' (():.vs) = transpose' vs {-# INLINE transpose' #-} instance Transpose' ((x:.()):.()) ((x:.()):.()) where transpose' = id instance (Head xss_h xss_hh ,Map xss_h xss_hh (xss_h:.xss_t) xs' ,Tail xss_h xss_ht ,Map xss_h xss_ht (xss_h:.xss_t) xss_ ,Transpose' (xs :. xss_) xss' ) => Transpose' ((x:.xs):.(xss_h:.xss_t)) ((x:.xs'):.xss') where transpose' ((x:.xs):.xss) = (x :. map head xss) :. transpose' (xs :. map tail xss :: (xs :. xss_)) {-# INLINE transpose' #-} class SetDiagonal v m where -- |set the diagonal of an n-by-n matrix to a given n-vector setDiagonal :: v -> m -> m instance (Vec n a v, Vec n r m, SetDiagonal' N0 v m) => SetDiagonal v m where setDiagonal = setDiagonal' (undefined :: N0) {-# INLINE setDiagonal #-} class SetDiagonal' n v m where setDiagonal' :: n -> v -> m -> m instance SetDiagonal' n () m where setDiagonal' _ _ m = m {-# INLINE setDiagonal' #-} instance ( SetDiagonal' (Succ n) v m , Access n a r ) => SetDiagonal' n (a:.v) (r:.m) where setDiagonal' _ (a:.v) (r:.m) = set (undefined :: n) a r :. setDiagonal' (undefined :: Succ n) v m {-# INLINE setDiagonal' #-} class GetDiagonal m v | m -> v, v -> m where -- |get the diagonal of an n-by-n matrix as a vector getDiagonal :: m -> v instance (Vec n a v, Vec n v m, GetDiagonal' N0 () m v) => GetDiagonal m v where getDiagonal = getDiagonal' (undefined :: N0) () {-# INLINE getDiagonal #-} class GetDiagonal' n p m v where getDiagonal' :: n -> p -> m -> v instance (Access n a r ,Append p (a:.()) (a:.p) ) => GetDiagonal' n p (r:.()) (a:.p) where getDiagonal' _ p (r:.()) = append p (get (undefined :: n) r :. ()) {-# INLINE getDiagonal' #-} instance (Access n a r ,Append p (a:.()) p' ,GetDiagonal' (Succ n) p' (r:.m) v ) => GetDiagonal' n p (r:.r:.m) v where getDiagonal' _ p (r:.m) = getDiagonal' (undefined::Succ n) (append p (get (undefined :: n) r :. ())) m {-# INLINE getDiagonal' #-} -- | @scale v m@ multiplies the diagonal of matrix @m@ by the vector @s@, component-wise. So -- @scale 5 m@ multiplies the diagonal by 5, whereas @scale 2:.1 m@ -- only scales the x component. scale :: ( GetDiagonal' N0 () m r , Num r , Vec n a r , Vec n r m , SetDiagonal' N0 r m ) => r -> m -> m scale s m = setDiagonal (s * getDiagonal m) m {-# INLINE scale #-} -- | @diagonal v@ is a square matrix with the vector v as the diagonal, and 0 -- elsewhere. diagonal :: (Vec n a v, Vec n v m, SetDiagonal v m, Num m) => v -> m diagonal v = setDiagonal v 0 {-# INLINE diagonal #-} -- | identity matrix (square) identity :: (Vec n a v, Vec n v m, Num v, Num m, SetDiagonal v m) => m identity = diagonal 1 {-# INLINE identity #-} -- Det' needs help inferring that all of the matrix elements are the same type. -- | Determinant by minor expansion, i.e. Laplace's formula. Unfolds into a -- closed form expression. This should be the fastest way for 4x4 and smaller, -- but @snd . gaussElim@ works too. det :: forall n a r m. (Vec n a r, Vec n r m, Det' m a) => m -> a det = det' {-# INLINE det #-} -- The Determinant of a square matrix, by minor expansion. class Det' m a | m -> a where det' :: m -> a instance Det' ((a:.()):.()) a where det' ((a:._):._) = a instance ( (a:.a:.v) ~ r -- a row of the matrix, an n-vector , ((a:.a:.v):.(a:.a:.v):.vs) ~ m -- an n*n matrix, n >= 2 , ((a:.v):.(a:.v):.vs_) ~ m_ -- an n*(n-1) matrix , (((a:.v):.vs_):.(x:.y)) ~ mm -- an n-vector of (n-1)*(n-1) matrices to recurse upon , Map (a:.a:.v) (a:.v) m m_ -- drop the first column of m to get m_ , DropConsec m_ mm -- an n-vector of (n-1)*(n-1) matrices , Det' ((a:.v):.vs_) a -- determinant of (n-1)*(n-1) matrix , Map ((a:.v):.vs_) a mm r -- dets of all n of the (n-1)*(n-1) matrices, the result is same type as a row , Map r a m r -- grab the first column using "map head" the result is same type as a row , NegateOdds r -- flip sign of odd elements of first column , Fold r a -- add evertyhing up... , Num r , Num a ) => Det' ((a:.a:.v):.(a:.a:.v):.vs) a -- et voila where det' m = sum $ negateOdds (map head m) * map det' (dropConsec $ map tail m) -- DropConsec: Drop consecutive elements, collecting the results. Given an -- n-vector v, drop each element from v, one at a time in sequence, and collect -- the resulting (n-1)-vectors into an n-vector (ie an n-by-(n-1) matrix). -- This is used for determinants. -- -- dropConsec [1,2,3,4] = [[2,3,4],[1,3,4],[1,2,4],[1,2,3]] -- class DropConsec v vv | v -> vv where dropConsec :: v -> vv instance (Vec n a v ,Pred n n_ ,Vec n_ a v_ ,Vec n v_ vv ,DropConsec' () v vv ) => DropConsec v vv where dropConsec = dropConsec' () {-# INLINE dropConsec #-} class DropConsec' p v vv where dropConsec' :: p -> v -> vv instance DropConsec' p (a:.()) (p:.()) where dropConsec' p (a:.()) = p :. () {-# INLINE dropConsec' #-} instance (Append p (a:.v) x ,Append p (a:.()) y ,DropConsec' y (a:.v) z ) => DropConsec' p (a:.a:.v) (x:.z) where dropConsec' p (a:.v) = append p v :. dropConsec' (append p (a :. ())) v {-# INLINE dropConsec' #-} -- Negate the odd or even elements of a vector. -- Used for determinants. class NegateOdds v where negateOdds :: v -> v class NegateEvens v where negateEvens :: v -> v instance NegateOdds () where negateOdds () = () {-# INLINE negateOdds #-} instance NegateEvens () where negateEvens () = () {-# INLINE negateEvens #-} instance (Num a, NegateEvens v) => NegateOdds (a:.v) where negateOdds (a:.v) = a :. negateEvens v {-# INLINE negateOdds #-} instance (Num a, NegateOdds v) => NegateEvens (a:.v) where negateEvens (a:.v) = negate a :. negateOdds v {-# INLINE negateEvens #-} --ReplConsec : this is a helper for implementing Cramer's rule. Given an --n-vector v and a value r, replace each consecutive element from v with r, --and collect the resulting n-vectors into an n-vector (ie an n-by-n matrix) class ReplConsec a v vv | v->a, v->vv, vv->v, vv->a where replConsec :: a -> v -> vv instance (Vec n a v ,Vec n v vv ,ReplConsec' a () v vv ) => ReplConsec a v vv where replConsec a v = replConsec' a () v :: vv {-# INLINE replConsec #-} class ReplConsec' a p v vv where replConsec' :: a -> p -> v -> vv instance ReplConsec' a p () () where replConsec' _ _ () = () {-# INLINE replConsec' #-} instance (Append p (a:.v) x ,Append p (a:.()) y ,ReplConsec' a y v z ) => ReplConsec' a p (a:.v) (x:.z) where replConsec' r p (a:.v) = append p (r :. v) :. replConsec' r (append p (a :. ())) v {-# INLINE replConsec' #-} -- | @cramer'sRule m v@ computes the solution to @m\`multmv\`x=v@ using the -- eponymous method. For larger than 3x3 you will want to use 'solve', which -- uses 'gaussElim'. Cramer's rule, however, unfolds into a closed-form -- expression, with no branches or allocations (other than the result). You may -- need to increase the unfolding threshold to see this. cramer'sRule :: (Map a a1 b1 v ,Transpose w b1 ,ZipWith a2 b vv v m w ,ReplConsec' a2 () b vv ,Vec n b vv ,Vec n a2 b ,Fractional a1 ,Det' m a1 ,Det' a a1 ) => m -> v -> v cramer'sRule m b = case map (\m' -> det' m' / det' m) (transpose (zipWith replConsec b m)) of b' -> b' `asTypeOf` b {-# INLINE cramer'sRule #-} mapFst f (a,b) = (f a,b) {-# INLINE mapFst #-} class Num a => NearZero a where -- | @nearZero x@ should be true when x is close enough to 0 to cause -- significant error in division. nearZero :: a -> Bool instance NearZero Float where nearZero x = abs x < 1e-6 {-# INLINE nearZero #-} instance NearZero Double where nearZero x = abs x < 1e-14 {-# INLINE nearZero #-} instance NearZero Rational where nearZero 0 = True nearZero _ = False {-# INLINE nearZero #-} -- Pivot1 : find a non-zero pivot column and put a 1 there. Second return -- argument tracks value of determinant. Returns nothing if no pivot in the -- first row. Does not try to find the 'best' pivot, only an acceptable one: -- matrices are assumed small, roundoff error should be negligible. class Pivot1 a m where pivot1 :: m -> Maybe (m,a) instance Pivot1 a () where pivot1 _ = Nothing instance ( Fractional a, NearZero a ) => Pivot1 a ((a:.()):.()) where pivot1 ((p:._):._) | nearZero p = Nothing | otherwise = Just (1,p) {-# INLINE pivot1 #-} instance ( Fractional a, NearZero a , Map a a (a:.r) (a:.r) ) => Pivot1 a ((a:.(a:.r)):.()) where pivot1 ((p:.r):._) | nearZero p = Nothing | otherwise = Just ((1 :. map (/ p) r):.(), p) {-# INLINE pivot1 #-} instance ( Fractional a, NearZero a , Map a a (a:.r) (a:.r) , ZipWith a a a (a:.r) (a:.r) (a:.r) , Map (a:.r) (a:.r) ((a:.r):.rs) ((a:.r):.rs) , Pivot1 a ((a:.r):.rs) ) => Pivot1 a ((a:.r):.(a:.r):.rs) where pivot1 (row@(p:._):.rows) | nearZero p = pivot1 rows >>= \(r:.rs,p)-> Just(r:.row:.rs,p) | otherwise = Just (first :. map add rows , p) where first = map (/p) row add r@(x:._) = zipWith (-) r . map (*x) $ first {-# INLINE pivot1 #-} -- Pivot : find a pivot. Second return argument tracks determinant. -- Returns Nothing if no pivot anywhere. class Pivot a m where pivot :: m -> Maybe (m,a) instance Pivot a (():.v) where pivot m = Nothing {-# INLINE pivot #-} instance ( Fractional a , NearZero a , Pivot1 a rs , Tail (a:.r) r , Map (a:.r) r ((a:.r):.rs) (r:.rs') , Map r (a:.r) (r:.rs') ((a:.r):.rs) , Pivot1 a ((a:.r):.rs) , Pivot a (r:.rs') ) => Pivot a ((a:.r):.rs) where pivot m = mplus (pivot1 m) (liftM (mapFst (map (0 :.))) (pivot (map tail m)) ) {-# INLINE pivot #-} -- | Gaussian elimination, adapted from Mirko Rahn: -- -- -- This is more of a proof of concept. Using a foreign C function will run -- slightly faster, and compile much faster. But where is the fun in that? -- Set your unfolding threshold as high as possible. class GaussElim a m | m -> a where -- | @gaussElim m@ returns a pair @(m',d)@ where @m'@ is @m@ in row echelon -- form and @d@ is the determinant of @m@. The determinant of @m'@ is 1 or 0, -- i.e., the leading coefficient of each non-zero row is 1. gaussElim :: m -> (m,a) instance (Num a, Pivot a ((a :. r):.())) => GaussElim a ((a :. r):.()) where gaussElim m = fromMaybe (m,1) (pivot m) {-# INLINE gaussElim #-} instance ( Fractional a , Map (a:.r) r ((a:.r):.rs) rs_ , Map r (a:.r) rs_ ((a:.r):.rs) , Pivot a ((a:.r):.(a:.r):.rs) , GaussElim a rs_ ) => GaussElim a ((a:.r):.(a:.r):.rs) where gaussElim m = flip (maybe (m,1)) (pivot m) $ \(row:.rows,p) -> case gaussElim (map tail rows) of (rows',p') -> ( row :. map (0 :.) rows' , p*p') {-# INLINE gaussElim #-} class BackSubstitute m where -- | backSubstitute takes a full rank matrix from row echelon form to reduced -- row echelon form. Returns @Nothing@ if the matrix is rank deficient. backSubstitute :: m -> Maybe m instance NearZero a => BackSubstitute ((a:.r):.()) where backSubstitute r@((a:._):._) | nearZero (1-a) = Just r | otherwise = Nothing {-# INLINE backSubstitute #-} instance ( Map (a:.r) r ((a:.r):.rs) rs_ --map tail , Map r (a:.r) rs_ ((a:.r):.rs) --map cons , Fold aas (a,a:.r) , ZipWith a a a (a:.r) (a:.r) (a:.r) , Map a a (a:.r) (a:.r) , ZipWith a (a:.r) (a,a:.r) r ((a:.r):.rs) aas , Num a, NearZero a , BackSubstitute rs_ ) => BackSubstitute ((a:.r):.(a:.r):.rs) where backSubstitute m@(r@(rh:.rt):.rs) | nearZero (1-rh) = liftM (map (0:.)) (backSubstitute . map tail $ rs) >>= \rs' -> return . (:. rs') . foldl (\ v (a, w) -> sub v a w) r $ zipWith (,) rt rs' | otherwise = Nothing -- rank deficient where sub v a = zipWith (-) v . map (*a) {-# INLINE backSubstitute #-} class BackSubstitute' m where -- | backSubstitute' takes a full rank matrix from row echelon form to reduced -- row echelon form. Returns garbage is matrix is rank deficient. backSubstitute' :: m -> m instance BackSubstitute' ((a:.r):.()) where backSubstitute' = id {-# INLINE backSubstitute' #-} instance ( Map (a:.r) r ((a:.r):.rs) rs_ --map tail , Map r (a:.r) rs_ ((a:.r):.rs) --map cons , Fold aas (a,a:.r) , ZipWith a a a (a:.r) (a:.r) (a:.r) , Map a a (a:.r) (a:.r) , ZipWith a (a:.r) (a,a:.r) r ((a:.r):.rs) aas , Num a , BackSubstitute' rs_ ) => BackSubstitute' ((a:.r):.(a:.r):.rs) where backSubstitute' (r@(_:.rt):.rs) = case map (0:.) (backSubstitute' . map tail $ rs) of rs' -> (:.rs') $ foldl (\ v (a,w) -> sub v a w) r (zipWith (,) rt rs') where sub v a = zipWith (-) v . map (*a) {-# INLINE backSubstitute' #-} -- | @invert m@ returns @Just@ the inverse of @m@ or @Nothing@ if @m@ is singular. invert :: forall n a r m r' m'. ( Num r, Num m , Vec n a r -- r is row type , Vec n r m -- m is matrix type , Append r r r' -- r' is a row of augmented matrix , ZipWith r r r' m m m' -- m' is the augmented matrix , Drop n r' r -- get the right half of an augmented matrix row , Map r' r m' m -- get the right half of the augmented matrix , SetDiagonal r m -- needed to make identity matrix , GaussElim a m' , BackSubstitute m' ) => m -> Maybe m invert m = liftM (map dropn) ((backSubstitute . fst . gaussElim . zipWith append m) i) where dropn = drop (undefined::n) i = identity :: m {-# INLINE invert #-} -- | inverse and determinant. If det = 0, inverted matrix is garbage. invertAndDet :: forall n a r m r' m'. ( Num a, Num r, Num m , Vec n a r -- r is row type , Vec n r m -- m is matrix type , Append r r r' -- r' is a row of augmented matrix , ZipWith r r r' m m m' -- m' is the augmented matrix , Drop n r' r -- get the right half of an augmented matrix row , Map r' r m' m -- get the right half of the augmented matrix , SetDiagonal r m -- needed to make identity matrix , GaussElim a m' , BackSubstitute m' ) => m -> (m,a) invertAndDet m = case backSubstitute rref of Nothing -> (m,0) Just m' -> ( map dropn m' , d ) where (rref,d) = gaussElim . zipWith append m $ i dropn = drop (undefined::n) i = identity :: m {-# INLINE invertAndDet #-} -- | Solution of linear system by Gaussian elimination. Returns @Nothing@ -- if no solution. solve :: forall n a v r m r' m'. ( Num r, Num m , Vec n a r -- r is row type , Vec n r m -- m is matrix type , Snoc r a r' -- a row of the extended matrix is one longer , ZipWith r a r' m r m' -- m' is the augmented matrix , Drop n r' (a:.()) -- get the right part of an augmented matrix row , Map r' a m' r -- get the right part of the augmented matrix , GaussElim a m' , BackSubstitute m' ) => m -> r -> Maybe r solve m v = liftM (map (head . drop (undefined :: n))) ((backSubstitute . fst . gaussElim . zipWith snoc m) v) {-# INLINE solve #-} -- | A 4x4 translation matrix translation :: Num a => Vec3 a -> Mat44 a translation = flip translate identity -- | A 4x4 rotation matrix for a rotation around the X axis rotationX :: Floating a => a -- ^ The angle in radians -> Mat44 a rotationX a = matFromList [1, 0, 0, 0, 0, cos a, -sin a, 0, 0, sin a, cos a, 0, 0, 0, 0, 1] -- | A 4x4 rotation matrix for a rotation around the Y axis rotationY :: Floating a => a -- ^ The angle in radians -> Mat44 a rotationY a = matFromList [cos a, 0, sin a, 0, 0, 1, 0, 0, -sin a, 0, cos a, 0, 0, 0, 0, 1] -- | A 4x4 rotation matrix for a rotation around the Z axis rotationZ :: Floating a => a -- ^ The angle in radians -> Mat44 a rotationZ a = matFromList [cos a, -sin a, 0, 0, sin a, cos a, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] -- | A 4x4 rotation matrix for a rotation around an arbitrary normalized vector rotationVec :: Floating a => Vec3 a -- ^ The normalized vector around which the rotation goes -> a -- ^ The angle in radians -> Mat44 a rotationVec (x:.y:.z:.()) a = matFromList [x^2+(1-x^2)*c, x*y*(1-c)-z*s, x*z*(1-c)+y*s, 0, x*y*(1-c)+z*s, y^2+(1-y^2)*c, y*z*(1-c)-x*s, 0, x*z*(1-c)-y*s, y*z*(1-c)+x*s, z^2+(1-z^2)*c, 0, 0, 0, 0, 1] where c = cos a s = sin a -- | A 4x4 rotation matrix from the euler angles yaw pitch and roll. Could be useful in e.g. -- first person shooter games, rotationEuler :: Floating a => Vec3 a -- rotation around x, y and z respectively -> Mat44 a rotationEuler (x:.y:.z:.()) = rotationZ z `multmm` rotationY y `multmm` rotationX x -- | A 4x4 rotation matrix from a normalized quaternion. Useful for most free flying rotations, such as airplanes. rotationQuat :: Num a => Vec4 a -- ^ The quaternion with the real part (w) last -> Mat44 a rotationQuat (x:.y:.z:.w:.()) = matFromList [1-2*y^2-2*z^2, 2*(x*y-z*w), 2*(x*z+y*w), 0, 2*(x*y+z*w), 1-2*x^2-2*z^2, 2*(y*z-x*w), 0, 2*(x*z-y*w), 2*(x*w+y*z), 1-2*x^2-2*y^2, 0, 0, 0, 0, 1] -- | A 4x4 rotation matrix for turning toward a point. Useful for targeting a camera to a specific point. rotationLookAt :: Floating a => Vec3 a -- ^ The up direction, not necessary unit length or perpendicular to the view vector -> Vec3 a -- ^ The viewers position -> Vec3 a -- ^ The point to look at -> Mat44 a rotationLookAt up' pos target = transpose $ homVec left :. homVec up :. homVec forward :. homPoint 0 :. () where forward = normalize $ pos - target left = normalize $ up' `cross` forward up = forward `cross`left -- | A 4x4 scaling matrix scaling :: Num a => Vec3 a -> Mat44 a scaling = diagonal . homPoint -- | A perspective projection matrix for a right handed coordinate system looking down negative z. This will project far plane to @z = +1@ and near plane to @z = -1@, i.e. into a left handed system. perspective :: Floating a => a -- ^ Near plane clipping distance (always positive) -> a -- ^ Far plane clipping distance (always positive) -> a -- ^ Field of view of the y axis, in radians -> a -- ^ Aspect ratio, i.e. screen's width\/height -> Mat44 a perspective n f fovy aspect = matFromList [2*n/(r-l), 0, -(r+l)/(r-l), 0, 0, 2*n/(t-b), (t+b)/(t-b), 0, 0, 0, -(f+n)/(f-n), -2*f*n/(f-n), 0,0,-1,0] where t = n*tan(fovy/2) b = -t r = aspect*t l = -r -- | An orthogonal projection matrix for a right handed coordinate system looking down negative z. This will project far plane to @z = +1@ and near plane to @z = -1@, i.e. into a left handed system. orthogonal :: Fractional a => a -- ^ Near plane clipping distance -> a -- ^ Far plane clipping distance -> Vec2 a -- ^ The size of the view (center aligned around origo) -> Mat44 a orthogonal n f (w:.h:.()) = matFromList [2/w, 0, 0, 0, 0, 2/h, 0, 0, 0, 0, 2/(f-n), -(f+n)/(f-n), 0, 0, 0, 1]