packages feed

mini-2.0.0.0: src/Mini/Linear/Quaternion.hs

-- | Quaternion transforms for three-dimensional Euclidean space
module Mini.Linear.Quaternion (
  -- * Type
  Quaternion (Quaternion),

  -- * Construction
  axisAngle,
  identity,
  zero,

  -- * Operations
  (%*%),
  (%+%),
  (%-%),
  (~*%),
  conjugate,
  inverse,
  norm,

  -- * Rotation
  rotate,

  -- * Interpolation
  slerp,

  -- * Conversion
  toMatrix,
) where

import Control.Applicative (
  liftA2,
 )
import Control.Monad (
  ap,
  liftM,
 )
import Mini.Hash.Class (
  Hashable,
  toBytes,
 )
import Mini.Linear.Approx (
  Approx,
  (~=),
 )
import qualified Mini.Linear.Matrix as M (
  identity,
 )
import Mini.Linear.Space (
  R1,
  R2,
  R3,
  R4,
  V3,
  V4 (V4),
  w,
  x,
  xy,
  xyz,
  xyzw,
  y,
  z,
 )
import Mini.Linear.Vector (
  cross,
  dot,
  (^+^),
  (~*^),
 )
import qualified Mini.Linear.Vector as V (
  norm,
  zero,
 )
import Mini.Optics.Lens (
  over,
  set,
  view,
 )
import Mini.Random.Class (
  Random,
  random,
 )
import Prelude (
  Applicative,
  Eq,
  Floating,
  Foldable,
  Functor,
  Monad,
  Monoid,
  Num,
  Ord,
  Semigroup,
  Show,
  Traversable,
  acos,
  and,
  concatMap,
  cos,
  fmap,
  foldr,
  mempty,
  negate,
  pure,
  recip,
  sin,
  sum,
  traverse,
  ($),
  (*),
  (+),
  (-),
  (.),
  (/),
  (<$>),
  (<*>),
  (<>),
  (>>=),
 )

-- Type

-- | Wrapper whose 'xyz'-components are imaginary and 'w'-component is real
newtype Quaternion a = Quaternion (V4 a)
  deriving (Show, Eq, Ord)

instance R1 Quaternion where
  x f (Quaternion v) =
    (\x' -> Quaternion $ set x x' v) <$> f (view x v)

instance R2 Quaternion where
  xy f (Quaternion v) =
    (\xy' -> Quaternion $ set xy xy' v) <$> f (view xy v)

instance R3 Quaternion where
  xyz f (Quaternion v) =
    (\xyz' -> Quaternion $ set xyz xyz' v) <$> f (view xyz v)

instance R4 Quaternion where
  xyzw f (Quaternion v) = Quaternion <$> f v

instance Functor Quaternion where
  fmap = liftM

instance Applicative Quaternion where
  pure = Quaternion . pure
  (<*>) = ap

instance Monad Quaternion where
  m >>= k =
    Quaternion
      ( V4
          (view x . k $ view x m)
          (view y . k $ view y m)
          (view z . k $ view z m)
          (view w . k $ view w m)
      )

instance Foldable Quaternion where
  foldr f b (Quaternion v) = foldr f b v

instance Traversable Quaternion where
  traverse f (Quaternion v) = Quaternion <$> traverse f v

instance (Semigroup a) => Semigroup (Quaternion a) where
  (<>) = liftA2 (<>)

instance (Monoid a) => Monoid (Quaternion a) where
  mempty = pure mempty

instance (Approx a) => Approx (Quaternion a) where
  p ~= q = and $ liftA2 (~=) p q

instance (Hashable a) => Hashable (Quaternion a) where
  toBytes = concatMap toBytes

instance (Random a) => Random (Quaternion a) where
  random g = let (v, g') = random g in (Quaternion v, g')

-- Construction

-- | Rotation around an axis /v/ a number of radians (assumes @v ~= hat v@)
axisAngle :: (Floating a) => V3 a -> a -> Quaternion a
axisAngle v a =
  let v' = sin (a / 2) ~*^ v
   in Quaternion $ V4 (view x v') (view y v') (view z v') (cos (a / 2))

-- | Multiplicative identity quaternion
identity :: (Num a) => Quaternion a
identity = set w 1 zero

-- | Additive identity quaternion
zero :: (Num a) => Quaternion a
zero = Quaternion V.zero

-- Operations

infixl 7 %*%

-- | Quaternion-quaternion multiplication
(%*%) :: (Num a) => Quaternion a -> Quaternion a -> Quaternion a
q %*% r =
  let qv = view xyz q
      qw = view w q
      rv = view xyz r
      rw = view w r
      v' = (qv `cross` rv) ^+^ (rw ~*^ qv) ^+^ (qw ~*^ rv)
      w' = (qw * rw - qv `dot` rv)
   in Quaternion $
        V4
          (view x v')
          (view y v')
          (view z v')
          w'

infixl 6 %+%

-- | Quaternion-quaternion addition
(%+%) :: (Num a) => Quaternion a -> Quaternion a -> Quaternion a
(%+%) = liftA2 (+)

infixl 6 %-%

-- | Quaternion-quaternion subtraction
(%-%) :: (Num a) => Quaternion a -> Quaternion a -> Quaternion a
(%-%) = liftA2 (-)

infixl 7 ~*%

-- | Scalar-quaternion multiplication
(~*%) :: (Num a) => a -> Quaternion a -> Quaternion a
s ~*% q = over w (s *) $ over xyz (s ~*^) q

-- | Conjugate of a quaternion
conjugate :: (Num a) => Quaternion a -> Quaternion a
conjugate = over xyz (fmap negate)

-- | Multiplicative inverse of a quaternion /q/ (assumes @not $ q ~= zero@)
inverse :: (Floating a) => Quaternion a -> Quaternion a
inverse q = recip (norm q * norm q) ~*% conjugate q

-- | Norm of a quaternion
norm :: (Floating a) => Quaternion a -> a
norm = V.norm . view xyzw

-- Rotation

-- | Apply a rotation /r/ to a vector (assumes /r/ is a unit quaternion)
rotate :: (Floating a) => Quaternion a -> V3 a -> V3 a
rotate r v =
  view xyz $
    r
      %*% Quaternion (V4 (view x v) (view y v) (view z v) 0)
      %*% conjugate r

-- Interpolation

-- | Spherical linear interpolation s.t. @slerp q r 0 == q && slerp q r 1 == r@
slerp :: (Floating a) => Quaternion a -> Quaternion a -> a -> Quaternion a
slerp q r t =
  let phi = acos . sum $ liftA2 (*) q r
      q' = (sin (phi * (1 - t)) / sin phi) ~*% q
      r' = (sin (phi * t) / sin phi) ~*% r
   in q' %+% r'

-- Conversion

-- | Convert a rotation /r/ to matrix form (assumes /r/ is a unit quaternion)
toMatrix :: (Num a) => Quaternion a -> V4 (V4 a)
toMatrix (Quaternion (V4 qx qy qz qw)) =
  set (x . x) (1 - 2 * (qy * qy + qz * qz))
    . set (x . y) (2 * (qx * qy + qw * qz))
    . set (x . z) (2 * (qx * qz - qw * qy))
    . set (y . x) (2 * (qx * qy - qw * qz))
    . set (y . y) (1 - 2 * (qx * qx + qz * qz))
    . set (y . z) (2 * (qy * qz + qw * qx))
    . set (z . x) (2 * (qx * qz + qw * qy))
    . set (z . y) (2 * (qy * qz - qw * qx))
    . set (z . z) (1 - 2 * (qx * qx + qy * qy))
    $ M.identity