spatial-math 0.1.7 → 0.2.0
raw patch · 7 files changed
+208/−264 lines, 7 filesdep +doctestdep +lineardep −hmatrixdep −random
Dependencies added: doctest, linear
Dependencies removed: hmatrix, random
Files
- Quat.hs +0/−64
- README.md +10/−0
- SpatialMath.hs +167/−62
- Xyz.hs +0/−128
- changelog.txt +4/−0
- spatial-math.cabal +19/−10
- tests/doctests.hs +8/−0
− Quat.hs
@@ -1,64 +0,0 @@-{-# OPTIONS_GHC -Wall #-}-{-# Language StandaloneDeriving #-}-{-# Language DeriveDataTypeable #-}-{-# Language DeriveFunctor #-}--module Quat ( Quat(..)- , zipWithQuat- , inv- , norm- , normalize- , qmult- , qmult'- ) where--import Data.Data ( Data )-import Data.Typeable ( Typeable1 )--data Quat a = Quat a a a a deriving (Show, Eq)--deriving instance Typeable1 Quat-deriving instance Data a => Data (Quat a)-deriving instance Functor Quat--zipWithQuat :: (a -> b -> c) -> Quat a -> Quat b -> Quat c-zipWithQuat f (Quat p0 p1 p2 p3) (Quat q0 q1 q2 q3) = Quat (f p0 q0) (f p1 q1) (f p2 q2) (f p3 q3)--instance (Num a, Ord a) => Num (Quat a) where- (+) = zipWithQuat (+)- (-) = zipWithQuat (-)- negate = fmap negate- (*) = qmult- abs = fmap abs- signum = fmap signum- fromInteger = error "fromInteger undefined for Quat"---- | q_out = q_in^-1-inv :: Num a => Quat a -> Quat a-inv (Quat q0 q1 q2 q3) = Quat q0 (-q1) (-q2) (-q3)---- | return ||q||-norm :: Floating a => Quat a -> a-norm (Quat q0 q1 q2 q3) = sqrt $ q0*q0 + q1*q1 + q2*q2 + q3*q3---- | q /= ||q||-normalize :: Floating a => Quat a -> Quat a-normalize q = fmap (* normInv) q- where- normInv = 1/(norm q)---- | quaternion multiply: qa * qb-qmult :: (Num a, Ord a) => Quat a -> Quat a -> Quat a-qmult (Quat p0 p1 p2 p3) (Quat q0 q1 q2 q3)- | r0 < 0 = negate qOut- | otherwise = qOut- where- qOut = Quat r0 r1 r2 r3- r0 = p0*q0 - p1*q1 - p2*q2 - p3*q3- r1 = p0*q1 + p1*q0 + p2*q3 - p3*q2- r2 = p0*q2 - p1*q3 + p2*q0 + p3*q1- r3 = p0*q3 + p1*q2 - p2*q1 + p3*q0---- | quaternion multiply then normalize-qmult' :: (Floating a, Ord a) => Quat a -> Quat a -> Quat a-qmult' p q = normalize (qmult q p)
+ README.md view
@@ -0,0 +1,10 @@+spatial-math+===++[](http://travis-ci.org/ghorn/spatial-math)++3d math including quaternions/euler angles/dcms and utility functions.++This is a port of my 'mathlib' C library: https://github.com/ghorn/mathlib and may someday be merged with it.++
SpatialMath.hs view
@@ -1,13 +1,12 @@ {-# OPTIONS_GHC -Wall #-} {-# Language StandaloneDeriving #-} {-# Language DeriveDataTypeable #-}+{-# Language DeriveGeneric #-} {-# Language DeriveFunctor #-}+{-# Language DeriveFoldable #-}+{-# Language DeriveTraversable #-} -module SpatialMath ( module Xyz- , module Quat- , Xyz(..)- , Quat(..)- , Euler(..)+module SpatialMath ( Euler(..) , rotateXyzAboutX , rotateXyzAboutY , rotateXyzAboutZ@@ -25,56 +24,105 @@ , rotVecByQuatB2A , rotVecByEuler , rotVecByEulerB2A+ -- * re-exported from linear+ , M33+ , V3(..)+ , Quaternion(..) ) where -import qualified Xyz-import qualified Quat-import Xyz ( Xyz(..) )-import Quat ( Quat(..) )--import Numeric.LinearAlgebra-import Foreign.Storable ( Storable ) import Data.Data ( Data )+import Data.Foldable ( Foldable )+import Data.Traversable ( Traversable ) import Data.Typeable ( Typeable1 )+import GHC.Generics ( Generic, Generic1 )+import Linear -data Euler a = Euler a a a deriving (Eq, Show) -- {yaw, pitch, roll}+normalize' :: Floating a => Quaternion a -> Quaternion a+normalize' q = fmap (* normInv) q+ where+ normInv = 1/(norm q) +--normalize' :: (Floating a, Epsilon a) => Quaternion a -> Quaternion a+--normalize' = normalize++-- | 3-2-1 Euler angle rotation sequence+data Euler a = Euler { eYaw :: a+ , ePitch :: a+ , eRoll :: a+ } deriving (Eq, Show, Generic, Generic1, Functor, Foldable, Traversable, Ord)+ deriving instance Typeable1 Euler deriving instance Data a => Data (Euler a)-deriving instance Functor Euler -rotateXyzAboutX :: Floating a => Xyz a -> a -> Xyz a-rotateXyzAboutX (Xyz ax ay az) rotAngle = Xyz bx by bz+-- | Rotate a vector about the X axis+--+-- >>> rotateXyzAboutX (V3 0 1 0) (pi/2)+-- V3 0.0 6.123233995736766e-17 1.0+--+-- >>> rotateXyzAboutX (V3 0 0 1) (pi/2)+-- V3 0.0 (-1.0) 6.123233995736766e-17+rotateXyzAboutX :: Floating a => V3 a -> a -> V3 a+rotateXyzAboutX (V3 ax ay az) rotAngle = V3 bx by bz where cosTheta = cos rotAngle sinTheta = sin rotAngle bx = ax- by = ay*cosTheta + az*sinTheta- bz = -ay*sinTheta + az*cosTheta+ by = ay*cosTheta - az*sinTheta+ bz = ay*sinTheta + az*cosTheta -rotateXyzAboutY :: Floating a => Xyz a -> a -> Xyz a-rotateXyzAboutY (Xyz ax ay az) rotAngle = Xyz bx by bz+-- | Rotate a vector about the Y axis+--+-- >>> rotateXyzAboutY (V3 0 0 1) (pi/2)+-- V3 1.0 0.0 6.123233995736766e-17+--+-- >>> rotateXyzAboutY (V3 1 0 0) (pi/2)+-- V3 6.123233995736766e-17 0.0 (-1.0)+rotateXyzAboutY :: Floating a => V3 a -> a -> V3 a+rotateXyzAboutY (V3 ax ay az) rotAngle = V3 bx by bz where cosTheta = cos rotAngle sinTheta = sin rotAngle - bx = ax*cosTheta - az*sinTheta+ bx = ax*cosTheta + az*sinTheta by = ay- bz = ax*sinTheta + az*cosTheta+ bz = -ax*sinTheta + az*cosTheta -rotateXyzAboutZ :: Floating a => Xyz a -> a -> Xyz a-rotateXyzAboutZ (Xyz ax ay az) rotAngle = Xyz bx by bz+-- | Rotate a vector about the Z axis+--+-- >>> rotateXyzAboutZ (V3 1 0 0) (pi/2)+-- V3 6.123233995736766e-17 1.0 0.0+--+-- >>> rotateXyzAboutZ (V3 0 1 0) (pi/2)+-- V3 (-1.0) 6.123233995736766e-17 0.0+--+rotateXyzAboutZ :: Floating a => V3 a -> a -> V3 a+rotateXyzAboutZ (V3 ax ay az) rotAngle = V3 bx by bz where cosTheta = cos rotAngle sinTheta = sin rotAngle - bx = ax*cosTheta + ay*sinTheta- by = -ax*sinTheta + ay*cosTheta+ bx = ax*cosTheta - ay*sinTheta+ by = ax*sinTheta + ay*cosTheta bz = az -euler321OfQuat :: RealFloat a => Quat a -> Euler a-euler321OfQuat (Quat q0 q1 q2 q3) = Euler yaw pitch roll++-- | Convert quaternion to Euler angles+--+-- >>> euler321OfQuat (Quaternion 1.0 (V3 0.0 0.0 0.0))+-- Euler {eYaw = 0.0, ePitch = -0.0, eRoll = 0.0}+--+-- >>> euler321OfQuat (Quaternion (sqrt(2)/2) (V3 (sqrt(2)/2) 0.0 0.0))+-- Euler {eYaw = 0.0, ePitch = -0.0, eRoll = 1.5707963267948966}+--+-- >>> euler321OfQuat (Quaternion (sqrt(2)/2) (V3 0.0 (sqrt(2)/2) 0.0))+-- Euler {eYaw = 0.0, ePitch = 1.5707963267948966, eRoll = 0.0}+--+-- >>> euler321OfQuat (Quaternion (sqrt(2)/2) (V3 0.0 0.0 (sqrt(2)/2)))+-- Euler {eYaw = 1.5707963267948966, ePitch = -0.0, eRoll = 0.0}+--+euler321OfQuat :: RealFloat a => Quaternion a -> Euler a+euler321OfQuat (Quaternion q0 (V3 q1 q2 q3)) = Euler yaw pitch roll where r11 = q0*q0 + q1*q1 - q2*q2 - q3*q3 r12 = 2.0*(q1*q2 + q0*q3)@@ -90,31 +138,67 @@ pitch = asin mr13 roll = atan2 r23 r33 -quatOfDcm :: (Storable a, RealFloat a) => Matrix a -> Quat a+-- | convert a DCM to a quaternion+--+-- >>> quatOfDcm $ V3 (V3 1 0 0) (V3 0 1 0) (V3 0 0 1)+-- Quaternion 1.0 (V3 0.0 0.0 0.0)+--+-- >>> quatOfDcm $ V3 (V3 0 1 0) (V3 (-1) 0 0) (V3 0 0 1)+-- Quaternion 0.7071067811865476 (V3 0.0 0.0 0.7071067811865475)+--+-- >>> let s = sqrt(2)/2 in quatOfDcm $ V3 (V3 s s 0) (V3 (-s) s 0) (V3 0 0 1)+-- Quaternion 0.9238795325112867 (V3 0.0 0.0 0.3826834323650898)+--+quatOfDcm :: RealFloat a => M33 a -> Quaternion a quatOfDcm = quatOfEuler321 . euler321OfDcm -quatOfDcmB2A :: (Storable a, RealFloat a) => Matrix a -> Quat a-quatOfDcmB2A = Quat.inv . quatOfDcm+quatOfDcmB2A :: (Conjugate a, RealFloat a) => M33 a -> Quaternion a+quatOfDcmB2A = conjugate . quatOfDcm -euler321OfDcm :: (RealFloat a, Storable a) => Matrix a -> Euler a-euler321OfDcm r = Euler yaw pitch roll+-- | Convert DCM to euler angles+--+-- >>> euler321OfDcm $ V3 (V3 1 0 0) (V3 0 1 0) (V3 0 0 1)+-- Euler {eYaw = 0.0, ePitch = -0.0, eRoll = 0.0}+--+-- >>> euler321OfDcm $ V3 (V3 0 1 0) (V3 (-1) 0 0) (V3 0 0 1)+-- Euler {eYaw = 1.5707963267948966, ePitch = -0.0, eRoll = 0.0}+--+-- >>> let s = sqrt(2)/2 in euler321OfDcm $ V3 (V3 s s 0) (V3 (-s) s 0) (V3 0 0 1)+-- Euler {eYaw = 0.7853981633974483, ePitch = -0.0, eRoll = 0.0}+--+euler321OfDcm :: RealFloat a => M33 a -> Euler a+euler321OfDcm+ (V3+ (V3 r11 r12 r13)+ (V3 _ _ r23)+ (V3 _ _ r33)) = Euler yaw pitch roll where- r11 = r @@> (0,0)- r12 = r @@> (0,1)- mr13' = -(r @@> (0,2))+ mr13' = -r13 mr13 -- nan protect | mr13' > 1 = 1 | mr13' < -1 = -1 | otherwise = mr13'- r23 = r @@> (1,2)- r33 = r @@> (2,2)- + yaw = atan2 r12 r11 pitch = asin mr13 roll = atan2 r23 r33 -quatOfEuler321 :: (Floating a, Ord a) => Euler a -> Quat a-quatOfEuler321 (Euler yaw pitch roll) = Quat.normalize q+-- | Convert Euler angles to quaternion+--+-- >>> quatOfEuler321 (Euler 0 0 0)+-- Quaternion 1.0 (V3 0.0 0.0 0.0)+--+-- >>> quatOfEuler321 (Euler (pi/2) 0 0)+-- Quaternion 0.7071067811865476 (V3 0.0 0.0 0.7071067811865475)+--+-- >>> quatOfEuler321 (Euler 0 (pi/2) 0)+-- Quaternion 0.7071067811865476 (V3 0.0 0.7071067811865475 0.0)+--+-- >>> quatOfEuler321 (Euler 0 0 (pi/2))+-- Quaternion 0.7071067811865476 (V3 0.7071067811865475 0.0 0.0)+--+quatOfEuler321 :: (Floating a, Ord a) => Euler a -> Quaternion a+quatOfEuler321 (Euler yaw pitch roll) = normalize' q where sr2 = sin $ 0.5*roll cr2 = cos $ 0.5*roll@@ -127,57 +211,78 @@ q2 = cr2*sp2*cy2 + sr2*cp2*sy2 q3 = cr2*cp2*sy2 - sr2*sp2*cy2 - q' = Quat q0 q1 q2 q3- + q' = Quaternion q0 (V3 q1 q2 q3)+ q- | q0 < 0 = negate q'+ | q0 < 0 = Quaternion (-q0) (V3 (-q1) (-q2) (-q3)) | otherwise = q' -dcmOfQuat :: (Num a, Element a) => Quat a -> Matrix a-dcmOfQuat (Quat q0 q1 q2 q3) = fromLists [ [r0, r1, r2]- , [r3, r4, r5]- , [r6, r7, r8]- ]+-- | convert a quaternion to a DCM+--+-- >>> dcmOfQuat $ Quaternion 1.0 (V3 0.0 0.0 0.0)+-- V3 (V3 1.0 0.0 0.0) (V3 0.0 1.0 0.0) (V3 0.0 0.0 1.0)+--+-- >>> let s = sqrt(2)/2 in dcmOfQuat $ Quaternion s (V3 0.0 0.0 s)+-- V3 (V3 0.0 1.0000000000000002 0.0) (V3 (-1.0000000000000002) 0.0 0.0) (V3 0.0 0.0 1.0000000000000002)+--+-- >>> dcmOfQuat $ Quaternion 0.9238795325112867 (V3 0.0 0.0 0.3826834323650898)+-- V3 (V3 0.7071067811865475 0.7071067811865476 0.0) (V3 (-0.7071067811865476) 0.7071067811865475 0.0) (V3 0.0 0.0 1.0)+--+dcmOfQuat :: Num a => Quaternion a -> M33 a+dcmOfQuat (Quaternion q0 (V3 q1 q2 q3)) = V3 (V3 r0 r1 r2)+ (V3 r3 r4 r5)+ (V3 r6 r7 r8) where -- 1st column r0 = q0*q0 + q1*q1 - q2*q2 - q3*q3 r3 = 2*(q1*q2 - q0*q3) r6 = 2*(q1*q3 + q0*q2)- + -- 2nd column r1 = 2*(q1*q2 + q0*q3) r4 = q0*q0 - q1*q1 + q2*q2 - q3*q3 r7 = 2*(q2*q3 - q0*q1)- + -- 3rd column r2 = 2*(q1*q3 - q0*q2) r5 = 2*(q2*q3 + q0*q1) r8 = q0*q0 - q1*q1 - q2*q2 + q3*q3 -dcmOfEuler321 :: (Floating a, Element a, Ord a) => Euler a -> Matrix a+-- | Convert DCM to euler angles+--+-- >>> dcmOfEuler321 $ Euler {eYaw = 0.0, ePitch = 0, eRoll = 0}+-- V3 (V3 1.0 0.0 0.0) (V3 0.0 1.0 0.0) (V3 0.0 0.0 1.0)+--+-- >>> dcmOfEuler321 $ Euler {eYaw = pi/2, ePitch = 0, eRoll = 0}+-- V3 (V3 2.220446049250313e-16 1.0 0.0) (V3 (-1.0) 2.220446049250313e-16 0.0) (V3 0.0 0.0 1.0)+--+-- >>> dcmOfEuler321 $ Euler {eYaw = pi/4, ePitch = 0, eRoll = 0}+-- V3 (V3 0.7071067811865475 0.7071067811865476 0.0) (V3 (-0.7071067811865476) 0.7071067811865475 0.0) (V3 0.0 0.0 1.0)+--+dcmOfEuler321 :: (Floating a, Ord a) => Euler a -> M33 a dcmOfEuler321 = dcmOfQuat . quatOfEuler321 -dcmOfQuatB2A :: (Num a, Element a) => Quat a -> Matrix a-dcmOfQuatB2A = dcmOfQuat . Quat.inv+dcmOfQuatB2A :: (Conjugate a, RealFloat a) => Quaternion a -> M33 a+dcmOfQuatB2A = dcmOfQuat . conjugate -- | vec_b = R_a2b * vec_a-rotVecByDcm :: (Num a, Storable a) => Matrix a -> Xyz a -> Xyz a-rotVecByDcm dcm vec = Xyz.mult3x3ByXyz dcm vec+rotVecByDcm :: Num a => M33 a -> V3 a -> V3 a+rotVecByDcm dcm vec = dcm !* vec -- | vec_a = R_a2b^T * vec_b-rotVecByDcmB2A :: (Num a, Storable a) => Matrix a -> Xyz a -> Xyz a-rotVecByDcmB2A dcm vec = Xyz.mult3x3TransposeByXyz dcm vec+rotVecByDcmB2A :: Num a => M33 a -> V3 a -> V3 a+rotVecByDcmB2A dcm vec = vec *! dcm -- | vec_b = q_a2b * vec_a * q_a2b^(-1) -- vec_b = R(q_a2b) * vec_a-rotVecByQuat :: (Num a, Element a) => Quat a -> Xyz a -> Xyz a+rotVecByQuat :: Num a => Quaternion a -> V3 a -> V3 a rotVecByQuat q = rotVecByDcm (dcmOfQuat q) -rotVecByQuatB2A :: (Num a, Element a) => Quat a -> Xyz a -> Xyz a+rotVecByQuatB2A :: Num a => Quaternion a -> V3 a -> V3 a rotVecByQuatB2A q = rotVecByDcmB2A (dcmOfQuat q) -rotVecByEuler :: (Floating a, Element a, Ord a) => Euler a -> Xyz a -> Xyz a+rotVecByEuler :: (Floating a, Ord a) => Euler a -> V3 a -> V3 a rotVecByEuler = rotVecByDcm . dcmOfEuler321 -rotVecByEulerB2A :: (Floating a, Element a, Ord a) => Euler a -> Xyz a -> Xyz a+rotVecByEulerB2A :: (Floating a, Ord a) => Euler a -> V3 a -> V3 a rotVecByEulerB2A = rotVecByDcmB2A . dcmOfEuler321
− Xyz.hs
@@ -1,128 +0,0 @@-{-# OPTIONS_GHC -Wall #-}-{-# Language StandaloneDeriving #-}-{-# Language DeriveDataTypeable #-}--module Xyz ( Xyz(..)- , zipWithXyz- , cross- , dot- , normSquared- , norm- , distance- , scale- , normalizeTo- , normalize- , mult3x3ByXyz- , mult3x3TransposeByXyz- ) where--import Numeric.LinearAlgebra ( (@@>), Matrix )-import Foreign.Storable ( Storable )-import Data.Data ( Data )-import Data.Typeable ( Typeable1 )-import System.Random ( Random(..) )--data Xyz a = Xyz a a a deriving (Show, Eq)--deriving instance Typeable1 Xyz-deriving instance Data a => Data (Xyz a)--instance Functor Xyz where- fmap f (Xyz x y z) = Xyz (f x) (f y) (f z)--instance Random a => Random (Xyz a) where- random g0 = (Xyz x y z, gz)- where- (x,gx) = random g0- (y,gy) = random gx- (z,gz) = random gy- randomR (Xyz x0 y0 z0, Xyz x1 y1 z1) g0 = (Xyz x y z, gz)- where- (x,gx) = randomR (x0,x1) g0- (y,gy) = randomR (y0,y1) gx- (z,gz) = randomR (z0,z1) gy--zipWithXyz :: (a -> b -> c) -> Xyz a -> Xyz b -> Xyz c-zipWithXyz f (Xyz x0 y0 z0) (Xyz x1 y1 z1) = Xyz (f x0 x1) (f y0 y1) (f z0 z1)--instance (Num a) => Num (Xyz a) where- (+) = zipWithXyz (+)- (-) = zipWithXyz (-)- negate = fmap negate- (*) = zipWithXyz (*)- abs = fmap abs- signum = fmap signum- fromInteger k = fmap fromInteger (Xyz k k k)--instance (Fractional a) => Fractional (Xyz a) where- fromRational r = fmap fromRational (Xyz r r r)- (/) = zipWithXyz (/)--instance (Floating a) => Floating (Xyz a) where- pi = Xyz pi pi pi- exp = fmap exp- log = fmap log- sin = fmap sin- cos = fmap cos- tan = fmap tan- asin = fmap asin- acos = fmap acos- atan = fmap atan- sinh = fmap sinh- cosh = fmap cosh- tanh = fmap tanh- asinh = fmap asinh- acosh = fmap acosh- atanh = fmap atanh---- | c = a (cross) b-cross :: Num a => Xyz a -> Xyz a -> Xyz a-cross (Xyz ax ay az) (Xyz bx by bz) = Xyz cx cy cz- where- cx = ay*bz - az*by- cy = - ax*bz + az*bx- cz = ax*by - ay*bx---- | c = a (dot) b-dot :: Num a => Xyz a -> Xyz a -> a-dot (Xyz ax ay az) (Xyz bx by bz) = ax*bx + ay*by + az*bz;---- | c = vec (dot) vec-normSquared :: Num a => Xyz a -> a-normSquared x = dot x x---- | norm(x)-norm :: Floating a => Xyz a -> a-norm x = sqrt $ dot x x---- | norm(a - b)-distance :: Floating a => Xyz a -> Xyz a -> a-distance a b = norm $ a - b---- | vec_out = vec_in*scale_factor-scale :: Num a => a -> Xyz a -> Xyz a-scale k = fmap (k *)---- | vec_out = scale (new_norm/norm(vec_in)) vec_in-normalizeTo :: Floating a => a -> Xyz a -> Xyz a -> Xyz a-normalizeTo newNorm vec = scale (newNorm/(norm(vec) + 1e-12))---- | vec_out = vec_in/norm(vec_in)-normalize :: Floating a => Xyz a -> Xyz a -> Xyz a-normalize = normalizeTo 1---- | v_out = M*v-mult3x3ByXyz :: (Num a, Storable a) => Matrix a -> Xyz a -> Xyz a-mult3x3ByXyz mat (Xyz x y z) = Xyz x' y' z'- where- x' = (mat @@> (0,0))*x + (mat @@> (0,1))*y + (mat @@> (0,2))*z- y' = (mat @@> (1,0))*x + (mat @@> (1,1))*y + (mat @@> (1,2))*z- z' = (mat @@> (2,0))*x + (mat @@> (2,1))*y + (mat @@> (2,2))*z---- // v_out = M^T*v-mult3x3TransposeByXyz :: (Num a, Storable a) => Matrix a -> Xyz a -> Xyz a-mult3x3TransposeByXyz mat (Xyz x y z) = Xyz x' y' z'- where- x' = (mat @@> (0,0))*x + (mat @@> (1,0))*y + (mat @@> (2,0))*z- y' = (mat @@> (0,1))*x + (mat @@> (1,1))*y + (mat @@> (2,1))*z- z' = (mat @@> (0,2))*x + (mat @@> (1,2))*y + (mat @@> (2,2))*z
+ changelog.txt view
@@ -0,0 +1,4 @@+0.2.0+- convert to using `linear` V3, M33, Quaternion types+- doctests+- fix long unknown bug: rotateXyzAbout{X,Y,Z} was rotating opposite direction from intended
spatial-math.cabal view
@@ -1,25 +1,34 @@ name: spatial-math-version: 0.1.7+version: 0.2.0 synopsis: 3d math including quaternions/euler angles/dcms and utility functions-description: This is a port of my 'mathlib' C library: https://github.com/ghorn/mathlib+description: This is a port of my 'mathlib' C library: `https://github.com/ghorn/mathlib` license: BSD3 license-file: LICENSE author: Greg Horn maintainer: gregmainland@gmail.com--- copyright: +copyright: Copyright (c) 2012, Greg Horn category: Math build-type: Simple-cabal-version: >=1.8+cabal-version: >=1.10 +extra-source-files: README.md+ changelog.txt+ library- exposed-modules: SpatialMath,- Quat,- Xyz- -- other-modules: + exposed-modules: SpatialMath build-depends: base >= 4 && < 5,- hmatrix >= 0.14 && < 0.15,- random+ linear >= 1.3.1+ default-language: Haskell2010 source-repository head type: git location: git://github.com/ghorn/spatial-math.git++test-suite doctests+ type: exitcode-stdio-1.0+ main-is: doctests.hs+ build-depends: base >= 4 && < 5,+ doctest >= 0.8+ default-language: Haskell2010+ ghc-options: -threaded+ hs-source-dirs: tests
+ tests/doctests.hs view
@@ -0,0 +1,8 @@+{-# OPTIONS_GHC -Wall #-}++module Main where++import Test.DocTest++main :: IO ()+main = doctest ["SpatialMath.hs"]