{- |
  This module provides several small vectors over @Double@ values.
  All fields are strict and unpacked, so using these should be
  fairly efficient. Each size of vector is a seperate type. It also
  provides a few vector constants to save you some typing now and
  then.
-}

module Data.Vector where

{- | The type of @Vector@ fields. -}
type Scalar = Double

{- |
  The @Vector@ class. All vectors are members of this class,
  and it provides ways to apply functions over vectors.
  Typically this methods aren't used directly; rather, the
  other class instances for each vector are implemented
  in terms of these.
-}
class Vector v where
  fromScalar :: Scalar -> v
  vmap       :: (Scalar -> Scalar) -> v -> v
  vzip       :: (Scalar -> Scalar -> Scalar) -> v -> v -> v
  vfold      :: (Scalar -> Scalar -> Scalar) -> v -> Scalar

{- |
  Takes the /dot product/ of two vectors [of the same dimension].
  If you remember your highschool linear algebra, the dot product
  of two vectors V and W is equal to |V| * |W| * cos k, where
  |V| is the length of vector V, and k is the minimum angle
  between the two vectors.
-}
vdot :: Vector v => v -> v -> Scalar
vdot v w = vfold (+) $ vzip (*) v w

{- |
  Returns the /magnitude/ of a vector (that is, it's length).
  Note that this is always positive or zero (never negative).
-}
vmag :: Vector v => v -> Scalar
vmag v = sqrt $ v `vdot` v

{- |
  Multiply a vector by a scalar. This scales the magnitude
  (length) of the vector, but leaves its length unchanged.
  (Except in the case of a negative scalar, in which case
  the vector's direction is reversed.)

  The operators '|*' and '*|' are identical, just with their
  arguments flipped.
-}
(|*) :: Vector v => Scalar -> v -> v
s |* v = vmap (*s) v

{- |
  Multiply a vector by a scalar. This scales the magnitude
  (length) of the vector, but leaves its length unchanged.
  (Except in the case of a negative scalar, in which case
  the vector's direction is reversed.)

  The operators '*|' and '|*' are identical, just with their
  arguments flipped.
-}
(*|) :: Vector v => v -> Scalar -> v
v *| s = vmap (*s) v

{- |
  Adjust a vector so that its length is exactly one. (Or,
  if the vector's length was zero, it stays zero.)
-}
vnormalise :: Vector v => v -> v
vnormalise v =
  let m = vmag v
  in  if m < 1e-10 then v else v *| (1/m)

{- |
  The type of 2-dimensional vectors. It provides various
  class instances such as 'Eq', 'Num', 'Show', etc.
-}
data Vector2 = Vector2 {v2x, v2y :: {-# UNPACK #-} !Scalar}
  deriving (Eq, Ord, Show)

instance Vector Vector2 where
  fromScalar x = Vector2 x x
  vmap  f (Vector2 x1 y1)                 = Vector2 (f x1)    (f y1)
  vzip  f (Vector2 x1 y1) (Vector2 x2 y2) = Vector2 (f x1 x2) (f y1 y2)
  vfold f (Vector2 x1 y1)                 = f x1 y1

instance Num Vector2 where
  (+) = vzip (+)
  (-) = vzip (-)
  (*) = vzip (*)
  negate = vmap negate
  abs    = vmap abs
  signum = vmap signum
  fromInteger n = let s = fromInteger n in fromScalar s

instance Fractional Vector2 where
  (/) = vzip (/)
  recip = vmap recip
  fromRational r = let s = fromRational r in fromScalar s

-- | Constant: The unit-length X vector, (1, 0).
vector2X :: Vector2
vector2X = Vector2 1 0

-- | Constant: The unit-length Y vector, (0, 1).
vector2Y :: Vector2
vector2Y = Vector2 0 1



{- |
  The type of 3-dimensional vectors. Similar to 'Vector2'.
-}
data Vector3 = Vector3 {v3x, v3y, v3z :: {-# UNPACK #-} !Scalar}
  deriving (Eq, Ord, Show)

instance Vector Vector3 where
  fromScalar x = Vector3 x x x
  vmap  f (Vector3 x1 y1 z1)                    = Vector3 (f x1)    (f y1)    (f z1)
  vzip  f (Vector3 x1 y1 z1) (Vector3 x2 y2 z2) = Vector3 (f x1 x2) (f y1 y2) (f z1 z2)
  vfold f (Vector3 x1 y1 z1)                    = f x1 (f y1 z1)

instance Num Vector3 where
  (+) = vzip (+)
  (-) = vzip (-)
  (*) = vzip (*)
  negate = vmap negate
  abs    = vmap abs
  signum = vmap signum
  fromInteger n = let s = fromInteger n in fromScalar s

instance Fractional Vector3 where
  (/) = vzip (/)
  recip = vmap recip
  fromRational r = let s = fromRational r in fromScalar s

{- |
  Takes the /cross product/ of two [3D] vectors. Again, from highschool
  linear algebra, the cross product of vector V and W is a new vector
  P such that |P| = |V| * |W| * sin k (where k is the minimum angle
  between V and W), and the direction of P is perpendicular to both
  V and W. For example, @vcross 'vector3X' 'vector3Y' = 'vector3Z'@.
  Note also that @vcross w v = negate (vcross v w)@.
-}
vcross :: Vector3 -> Vector3 -> Vector3
vcross (Vector3 x1 y1 z1) (Vector3 x2 y2 z2) = Vector3
  {
    v3x = y1 * z2   -   y2 * z1,
    v3y = z1 * x2   -   z2 * x1,
    v3z = x1 * y2   -   x2 * y1
  }

-- | Constant: The unit-length X vector, (1, 0, 0).
vector3X :: Vector3
vector3X = Vector3 1 0 0

-- | Constant: The unit-length Y vector, (0, 1, 0).
vector3Y :: Vector3
vector3Y = Vector3 0 1 0

-- | Constant: The unit-length Z vector, (0, 0, 1).
vector3Z :: Vector3
vector3Z = Vector3 0 0 1