flaw-math/Flaw/Math/Transform.hs
{-|
Module: Flaw.Math.Transform
Description: Geometric functions.
License: MIT
-}
{-# LANGUAGE DeriveGeneric, ScopedTypeVariables, Trustworthy #-}
module Flaw.Math.Transform
(
-- * Transform class
Transform(..)
-- * Quaternion plus offset
, QuatOffset(..)
, FloatQO, DoubleQO
-- * Dual quaternion
, DualQuat(..)
, FloatDQ, DoubleDQ
-- * Helper functions
, quatAxisRotation
) where
import qualified Data.Serialize as S
import Foreign.Ptr
import Foreign.Storable
import GHC.Generics(Generic)
import Flaw.Math
class Transform t where
identityTransform :: Quaternionized a => t a
applyTransform :: Quaternionized a => t a -> Vec3 a -> Vec3 a
combineTransform :: Quaternionized a => t a -> t a -> t a
transformToMatrix :: Quaternionized a => t a -> Mat4x4 a
transformFromMatrix :: Quaternionized a => Mat4x4 a -> t a
transformTranslation :: Quaternionized a => Vec3 a -> t a
transformAxisRotation :: Quaternionized a => Vec3 a -> a -> t a
instance Transform Mat4x4 where
{-# INLINE identityTransform #-}
identityTransform = Mat4x4
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
{-# INLINE applyTransform #-}
applyTransform m (Vec3 x y z) = Vec3 rx ry rz where
Vec4 rx ry rz _rw = mul m (Vec4 x y z 1)
{-# INLINE combineTransform #-}
combineTransform = mul
{-# INLINE transformToMatrix #-}
transformToMatrix = id
{-# INLINE transformFromMatrix #-}
transformFromMatrix = id
{-# INLINE transformTranslation #-}
transformTranslation (Vec3 x y z) = Mat4x4
1 0 0 x
0 1 0 y
0 0 1 z
0 0 0 1
transformAxisRotation axis angle = transformToMatrix $ QuatOffset (quatAxisRotation axis angle) (Vec3 0 0 0)
-- | Quaternion representing rotation around specified axis.
-- Axis should be normalized.
{-# INLINE quatAxisRotation #-}
quatAxisRotation :: Quaternionized a => Vec3 a -> a -> Quat a
quatAxisRotation (Vec3 x y z) angle = r where
ha = angle * 0.5
sa = sin ha
ca = cos ha
r = Quat $ Vec4 (x * sa) (y * sa) (z * sa) ca
-- | 3D transformation represented by normalized quaternion and offset.
data QuatOffset a = QuatOffset (Quat a) (Vec3 a) deriving (Eq, Ord, Show, Generic)
instance (Quaternionized a, S.Serialize a) => S.Serialize (QuatOffset a)
instance (Quaternionized a, Storable a) => Storable (QuatOffset a) where
sizeOf _ = sizeOf (undefined :: Quat a) + sizeOf (undefined :: Vec3 a)
alignment _ = alignment (undefined :: Quat a)
peek ptr = do
q <- peek $ castPtr ptr
p <- peek $ castPtr $ plusPtr ptr $ sizeOf q
return $ QuatOffset q p
poke ptr (QuatOffset q p) = do
poke (castPtr ptr) q
poke (castPtr $ plusPtr ptr $ sizeOf q) p
type FloatQO = QuatOffset Float
type DoubleQO = QuatOffset Double
instance Transform QuatOffset where
{-# INLINE identityTransform #-}
identityTransform = QuatOffset (Quat (Vec4 0 0 0 1)) (Vec3 0 0 0)
{-# INLINE applyTransform #-}
applyTransform (QuatOffset q p) (Vec3 x y z) = Vec3 rx ry rz + p where
Quat (Vec4 rx ry rz _rw) = q * Quat (Vec4 x y z 0) * conjugate q
{-# INLINE combineTransform #-}
combineTransform t2@(QuatOffset q2 _p2) (QuatOffset q1 p1) = QuatOffset (q2 * q1) (applyTransform t2 p1)
{-# INLINE transformToMatrix #-}
transformToMatrix (QuatOffset (Quat (Vec4 x y z w)) (Vec3 px py pz)) = r where
ww = w * w
xx = x * x
yy = y * y
zz = z * z
wx2 = w * x * 2
wy2 = w * y * 2
wz2 = w * z * 2
xy2 = x * y * 2
xz2 = x * z * 2
yz2 = y * z * 2
r = Mat4x4
(ww + xx - yy - zz) (xy2 - wz2) (xz2 + wy2) px
(xy2 + wz2) (ww - xx + yy - zz) (yz2 - wx2) py
(xz2 - wy2) (yz2 + wx2) (ww - xx - yy + zz) pz
0 0 0 1
{-# INLINE transformFromMatrix #-}
transformFromMatrix (Mat4x4
m11 m12 m13 m14
m21 m22 m23 m24
m31 m32 m33 m34
_41 _42 _43 _44) = QuatOffset (normalize $ Quat (Vec4 x y z w)) (Vec3 m14 m24 m34) where
k = sqrt (1 + m11 + m22 + m33)
kk = 0.5 / k
x = (m32 - m23) * kk
y = (m13 - m31) * kk
z = (m21 - m12) * kk
w = k * 0.5
{-# INLINE transformTranslation #-}
transformTranslation = QuatOffset (Quat (Vec4 0 0 0 1))
{-# INLINE transformAxisRotation #-}
transformAxisRotation axis angle = QuatOffset (quatAxisRotation axis angle) (Vec3 0 0 0)
-- | Dual quaternion representing transforms in 3D space.
data DualQuat a = DualQuat !(Quat a) !(Quat a) deriving (Eq, Ord, Show, Generic)
instance (Quaternionized a, S.Serialize a) => S.Serialize (DualQuat a)
instance (Quaternionized a, Storable a) => Storable (DualQuat a) where
sizeOf _ = sizeOf (undefined :: Quat a) * 2
alignment _ = alignment (undefined :: Quat a)
peek ptr = do
q <- peek $ castPtr ptr
p <- peek $ castPtr $ plusPtr ptr $ sizeOf q
return $ DualQuat q p
poke ptr (DualQuat q p) = do
poke (castPtr ptr) q
poke (castPtr $ plusPtr ptr $ sizeOf q) p
type FloatDQ = DualQuat Float
type DoubleDQ = DualQuat Double
instance Quaternionized a => Num (DualQuat a) where
{-# INLINE (+) #-}
(DualQuat q1 p1) + (DualQuat q2 p2) = DualQuat (q1 + q2) (p1 + p2)
{-# INLINE (-) #-}
(DualQuat q1 p1) - (DualQuat q2 p2) = DualQuat (q1 - q2) (p1 - p2)
{-# INLINE (*) #-}
(DualQuat q1 p1) * (DualQuat q2 p2) = DualQuat (q1 * q2) (q1 * p2 + p1 * q2)
{-# INLINE negate #-}
negate (DualQuat q p) = DualQuat (negate q) (negate p)
{-# INLINE abs #-}
abs (DualQuat q p) = DualQuat (abs q) (abs p)
{-# INLINE signum #-}
signum = error "signum for DualQuat is not implemented"
{-# INLINE fromInteger #-}
fromInteger = error "fromInteger for DualQuat is not implemented"
instance Quaternionized a => Conjugate (DualQuat a) where
{-# INLINE conjugate #-}
conjugate (DualQuat q p) = DualQuat (conjugate q) (conjugate p)
{-# INLINE dualConjugate #-}
dualConjugate :: Quaternionized a => DualQuat a -> DualQuat a
dualConjugate (DualQuat q p) = DualQuat q (negate p)
-- | Equivalent of (dualConjugate . conjugate)
{-# INLINE dualConjugateConjugate #-}
dualConjugateConjugate :: Quaternionized a => DualQuat a -> DualQuat a
dualConjugateConjugate (DualQuat (Quat (Vec4 qx qy qz qw)) (Quat (Vec4 px py pz pw))) =
DualQuat (Quat (Vec4 (-qx) (-qy) (-qz) qw)) (Quat (Vec4 px py pz (-pw)))
instance Transform DualQuat where
{-# INLINE identityTransform #-}
identityTransform = DualQuat (Quat (Vec4 0 0 0 1)) (Quat (Vec4 0 0 0 0))
{-# INLINE applyTransform #-}
applyTransform q (Vec3 x y z) = Vec3 rx ry rz where
DualQuat _rq (Quat (Vec4 rx ry rz _rw)) = q * a * dualConjugateConjugate q
a = DualQuat (Quat (Vec4 0 0 0 1)) (Quat (Vec4 x y z 0))
{-# INLINE combineTransform #-}
combineTransform = (*)
{-# INLINE transformToMatrix #-}
transformToMatrix (DualQuat q p) = transformToMatrix $ QuatOffset q (Vec3 (x * 2) (y * 2) (z * 2)) where
Quat (Vec4 x y z _w) = p * conjugate q
{-# INLINE transformFromMatrix #-}
transformFromMatrix (Mat4x4
m11 m12 m13 m14
m21 m22 m23 m24
m31 m32 m33 m34
_41 _42 _43 _44) = DualQuat q (Quat (Vec4 (m14 * 0.5) (m24 * 0.5) (m34 * 0.5) 0) * q) where
k = sqrt (1 + m11 + m22 + m33)
kk = 0.5 / k
x = (m32 - m23) * kk
y = (m13 - m31) * kk
z = (m21 - m12) * kk
w = k * 0.5
q = normalize $ Quat $ Vec4 x y z w
{-# INLINE transformTranslation #-}
transformTranslation (Vec3 x y z) = DualQuat (Quat (Vec4 0 0 0 1)) (Quat (Vec4 (x * 0.5) (y * 0.5) (z * 0.5) 0))
{-# INLINE transformAxisRotation #-}
transformAxisRotation axis angle = DualQuat (quatAxisRotation axis angle) (Quat (Vec4 0 0 0 0))