fir-0.1.0.0: src/Math/Quaternion.hs
{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-|
Module: Math.Quaternion
Bare-bones implementation of quaternions,
together with a few simple operations
such as the conjugation action of quaternions on 3-d vectors.
-}
module Math.Quaternion where
-- base
import Prelude
( Eq(..), Show(..), Functor(..), Applicative(..)
, Foldable(..), Traversable(..) )
import Control.Applicative
( liftA2 )
-- fir
import FIR.Prelude
import Math.Algebra.Class
( AdditiveMonoid(..), CancellativeAdditiveMonoid(..), AdditiveGroup(..)
, Semiring(..), Ring, DivisionRing(..)
, Signed(..), Floating(..)
)
import Math.Linear
( V(..), M(M)
, pattern V3
, pattern V4
, Semimodule(..), LinearModule(..), Inner(..), Cross(..)
, norm, normalise
)
----------------------------------------------------------------------------
newtype Quaternion a = Quaternion { quaternion :: V 4 a }
deriving stock ( Eq, Show, Foldable, Traversable )
deriving newtype ( Functor, Applicative )
instance Semiring a => Semimodule () (Quaternion a) where
type Scalar (Quaternion a) = a
type OfDim (Quaternion a) () '() = Quaternion a
type ValidDim (Quaternion a) () unit = ( unit ~ '() )
(^+^) = coerce ( (^+^) :: V 4 a -> V 4 a -> V 4 a )
(*^) = coerce ( (*^) :: a -> V 4 a -> V 4 a )
instance Ring a => LinearModule () (Quaternion a) where
(^-^) = coerce ( (^-^) :: V 4 a -> V 4 a -> V 4 a )
(-^) = coerce ( ( -^) :: V 4 a -> V 4 a )
instance (Semiring a, Floating a) => Inner () (Quaternion a) where
dot = coerce ( dot :: V 4 a -> V 4 a -> a )
normalise = coerce ( normalise :: V 4 a -> V 4 a )
instance AdditiveMonoid a => AdditiveMonoid (Quaternion a) where
(+) = liftA2 (+)
zero = Quaternion (pure zero)
fromInteger = Quaternion . ( :. pure zero ) . fromInteger
instance Ring a => Semiring (Quaternion a) where
Quaternion { quaternion = V4 xr xi xj xk } * Quaternion { quaternion = V4 yr yi yj yk }
= Quaternion $
V4 (xr*yr - xi*yi - xj*yj - xk*yk)
(xr*yi + xi*yr + xj*yk - xk*yj)
(xr*yj + xj*yr + xk*yi - xi*yk)
(xr*yk + xk*yr + xi*yj - xj*yi)
instance CancellativeAdditiveMonoid a => CancellativeAdditiveMonoid (Quaternion a) where
(-) = liftA2 (-)
instance AdditiveGroup a => AdditiveGroup (Quaternion a) where
negate = fmap negate
instance Floating a => Signed (Quaternion a) where
abs q = norm q *^ Quaternion (V4 1 0 0 0)
signum q = recip (norm q) *^ q
instance ( DivisionRing a, Floating a ) => DivisionRing (Quaternion a) where
recip a = recip (norm a) *^ conjugate a
fromRational = Quaternion . ( :. pure zero ) . fromRational
conjugate :: Ring a => Quaternion a -> Quaternion a
conjugate (Quaternion (a :. v)) = Quaternion (a :. (-^) @_ @_ @3 v)
-- 'rotate' expects the quaternion to be of unit norm
rotate :: Ring a => Quaternion a -> V 3 a -> V 3 a
rotate q v = ijk where
Quaternion (_ :. ijk) = q * Quaternion (0 :. v) * conjugate q
axisAngle :: ( Semiring a, Floating a ) => V 3 a -> a -> Quaternion a
axisAngle axis theta = Quaternion ( cos half :. sin half *^ normalise axis )
where half = 0.5 * theta
-- | The shortest rotation from one vector to another.
rotationFromTo :: ( Floating a, Eq a ) => V 3 a -> V 3 a -> Quaternion a
rotationFromTo u v
| w == 0
= error "rotationFromTo: opposite directions"
| otherwise
= normalise ( Quaternion ( w :. cross u' v' ) )
where
u' = normalise u
v' = normalise v
w = 1 + dot u' v'
fromQuaternion :: Ring a => Quaternion a -> M 3 3 a
fromQuaternion (Quaternion (V4 w x y z))
= M $ V3 (V3 (1-2*(y2+z2)) (2*(xy+zw)) (2*(xz-yw)))
(V3 (2*(xy-zw)) (1-2*(x2+z2)) (2*(yz+xw)))
(V3 (2*(xz+yw)) (2*(yz-xw)) (1-2*(x2+y2)))
where x2 = x*x
y2 = y*y
z2 = z*z
xy = x*y
xz = x*z
xw = x*w
yz = y*z
yw = y*w
zw = z*w