packages feed

dyna-gloss-0.1.0.0: src/Dyna/Gloss/Data/Vec.hs

module Dyna.Gloss.Data.Vec(
    Vec(..)
  , magV
  , argV
  , dotV
  , detV
  , mulSV
  , rotateV
  , angleVV
  , normalizeV
  , unitVecAtAngle
  , e
  , VecBasis(..)
  , fromTuple
  , toTuple
  ) where

import Graphics.Gloss.Geometry.Angle
import Dyna (BasisArity(..))
import Data.AdditiveGroup
import Data.AffineSpace
import Data.Basis
import Data.Cross
import Data.VectorSpace

-- | Pair of unboxed floats. All operations on vectors are strict
-- which is more suitable for computation intensive domains such as computer graphics.
data Vec = Vec
  { vec'x :: {-# UNPACK #-} !Float
  , vec'y :: {-# UNPACK #-} !Float
  }
  deriving (Show, Eq, Ord)

-- | Converts vector to tuple of Floats
toTuple :: Vec -> (Float, Float)
toTuple (Vec x y) = (x, y)

-- | Converts tuple of floats to vector
fromTuple :: (Float, Float) -> Vec
fromTuple (x, y) = Vec x y

lift0 :: Float -> Vec
lift0 a = Vec a a

lift1 :: (Float -> Float) -> Vec -> Vec
lift1 f (Vec a b) = Vec (f a) (f b)

lift2 :: (Float -> Float -> Float) -> Vec -> Vec -> Vec
lift2 f (Vec a1 b1) (Vec a2 b2) = Vec (f a1 a2) (f b1 b2)

-- numeric instances

instance Num Vec where
  fromInteger = lift0 . fromInteger
  (+) = lift2 (+)
  (*) = lift2 (*)
  (-) = lift2 (-)
  negate = lift1 negate
  abs = lift1 abs
  signum = lift1 signum

instance Fractional Vec where
  fromRational = lift0 . fromRational
  recip = lift1 recip
  (/) = lift2 (/)

-- vector space instances

instance AdditiveGroup Vec where
  zeroV = lift0 zeroV
  (^+^) = lift2 (^+^)
  (^-^) = lift2 (^-^)
  negateV = lift1 negateV

instance VectorSpace Vec where
  type Scalar Vec = Float
  (*^) k = lift1 (k *^)

instance AffineSpace Vec where
  type Diff Vec = Vec
  (.-.) = lift2 (.-.)
  (.+^) = lift2 (.+^)

instance BasisArity Vec where
  basisArity _ = 2

data VecBasis = VecX | VecY

instance HasBasis Vec where
  type Basis Vec = VecBasis
  basisValue = \case
    VecX -> Vec 1 0
    VecY -> Vec 0 1

  decompose (Vec x y) = [(VecX, x), (VecY, y)]

  decompose' (Vec x y) = \case
    VecX -> x
    VecY -> y

instance HasNormal Vec where
  normalVec = normalizeV

instance HasCross2 Vec where
  cross2 (Vec x y) = Vec (negate y) x -- or @(y,-x)@?

-------------------------------------------------------------------
-- gloss functions on Vectors

-- | Normalise a vector, so it has a magnitude of 1.
normalizeV :: Vec -> Vec
normalizeV v = lift1 ((1 / magV v) * ) v
{-# INLINE normalizeV #-}

-- | The magnitude of a vector.
magV :: Vec -> Float
magV (Vec x y) = sqrt (x * x + y * y)
{-# INLINE magV #-}

-- | The angle of this vector, relative to the +ve x-axis.
argV :: Vec -> Float
argV (Vec x y) = normalizeAngle $ atan2 y x
{-# INLINE argV #-}

-- | The dot product of two vectors.
dotV :: Vec -> Vec -> Float
dotV (Vec x1 x2) (Vec y1 y2) = x1 * y1 + x2 * y2
{-# INLINE dotV #-}

-- | The determinant of two vectors.
detV :: Vec -> Vec -> Float
detV (Vec x1 y1) (Vec x2 y2) = x1 * y2 - y1 * x2
{-# INLINE detV #-}

-- | Multiply a vector by a scalar.
mulSV :: Float -> Vec -> Vec
mulSV s (Vec x y) = Vec (s * x) (s * y)
{-# INLINE mulSV #-}

-- | Rotate a vector by an angle (in radians). +ve angle is counter-clockwise.
rotateV :: Float -> Vec -> Vec
rotateV r (Vec x y)
 = Vec (x * cos r - y * sin r)
       (x * sin r + y * cos r)
{-# INLINE rotateV #-}

-- | Compute the inner angle (in radians) between two vectors.
angleVV :: Vec -> Vec -> Float
angleVV p1 p2
 = let  m1      = magV p1
        m2      = magV p2
        d       = p1 `dotV` p2
        aDiff   = acos $ d / (m1 * m2)

   in   aDiff
{-# INLINE angleVV #-}

-- | Produce a unit vector at a given angle relative to the +ve x-axis.
--      The provided angle is in radians.
unitVecAtAngle :: Float -> Vec
unitVecAtAngle r = Vec (cos r) (sin r)
{-# INLINE unitVecAtAngle #-}

-- | Shortcut for @unitVecAtAngle@
e :: Float -> Vec
e = unitVecAtAngle
{-# INLINE e #-}