geomancy-0.4.0.0: src/Geomancy/DQuaternion.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
#ifdef TH_LIFT
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE DerivingStrategies #-}
#endif
module Geomancy.DQuaternion
( DQuaternion
, dquaternion
, withDQuaternion
, axisAngle
, intrinsic
, extrinsic
, rotate
, rotatePoint
, rotationBetween
, lookAtUp
, (^*)
, (^/)
, slerp
, conjugate
, norm
, quadrance
, dot
, normalize
, qNaN
, fromQuaternion
, toQuaternion
) where
import Control.DeepSeq (NFData(rnf))
import Foreign (Storable(..), castPtr)
import Foreign.Ptr.Diff (peekDiffOff, pokeDiffOff)
import GHC.Float (double2Float, float2Double)
#ifdef TH_LIFT
import Language.Haskell.TH.Syntax (Lift)
#endif
#ifdef SERIALISE
import Codec.Serialise (Serialise(..))
import qualified Codec.Serialise.Encoding as Serialise
import qualified Codec.Serialise.Decoding as Serialise
#endif
import Graphics.Gl.Block (Block(..))
import Geomancy.DVec3 (DVec3, dvec3, withDVec3)
import Geomancy.Quaternion (Quaternion, quaternion, withQuaternion)
import qualified Geomancy.DVec3 as DVec3
data DQuaternion = DQuaternion
{-# UNPACK #-} !Double
{-# UNPACK #-} !Double
{-# UNPACK #-} !Double
{-# UNPACK #-} !Double
deriving (Eq, Ord, Show)
#ifdef TH_LIFT
deriving Lift
#endif
{-# INLINE dquaternion #-}
dquaternion :: Double -> Double -> Double -> Double -> DQuaternion
dquaternion = DQuaternion
{-# INLINE withDQuaternion #-}
withDQuaternion
:: DQuaternion
-> (Double -> Double -> Double -> Double -> r)
-> r
withDQuaternion (DQuaternion a b c d) f = f a b c d
{-# INLINE fromQuaternion #-}
fromQuaternion :: Quaternion -> DQuaternion
fromQuaternion q =
withQuaternion q \a b c d ->
dquaternion (float2Double a) (float2Double b) (float2Double c) (float2Double d)
{-# INLINE toQuaternion #-}
toQuaternion :: DQuaternion -> Quaternion
toQuaternion q =
withDQuaternion q \a b c d ->
quaternion (double2Float a) (double2Float b) (double2Float c) (double2Float d)
{-# INLINE (^*) #-}
(^*) :: DQuaternion -> Double -> DQuaternion
DQuaternion a b c d ^* x =
DQuaternion
(a * x)
(b * x)
(c * x)
(d * x)
{-# INLINE (^/) #-}
(^/) :: DQuaternion -> Double -> DQuaternion
DQuaternion a b c d ^/ x =
DQuaternion
(a / x)
(b / x)
(c / x)
(d / x)
slerp :: DQuaternion -> DQuaternion -> Double -> DQuaternion
slerp q p t
| 1.0 - cosphi < 1e-12 =
q
| otherwise =
( (q ^* sin ((1 - t) * phi)) +
f p ^* sin (t * phi)
) ^/ sin phi
where
phi = acos cosphi
(cosphi, f) =
if dqp < 0 then
(-dqp, negate)
else
(dqp, id)
dqp = dot q p
{-# INLINE conjugate #-}
conjugate :: DQuaternion -> DQuaternion
conjugate (DQuaternion e x y z) = DQuaternion e (-x) (-y) (-z)
{-# INLINE norm #-}
norm :: DQuaternion -> Double
norm = sqrt . quadrance
{-# INLINE quadrance #-}
quadrance :: DQuaternion -> Double
quadrance q = dot q q
{-# INLINE dot #-}
dot :: DQuaternion -> DQuaternion -> Double
dot (DQuaternion a b c d) (DQuaternion e f g h) =
a * e +
b * f +
c * g +
d * h
{-# INLINE normalize #-}
normalize :: DQuaternion -> DQuaternion
normalize v =
if nearZero q || nearZero (1-q) then
v
else
let
DQuaternion e i j k = v
in
DQuaternion (e / l) (i / l) (j / l) (k / l)
where
q = dot v v
l = sqrt q
nearZero a = abs a <= 1e-12
instance NFData DQuaternion where
rnf DQuaternion{} = ()
instance Num DQuaternion where
{-# INLINE (+) #-}
DQuaternion a b c d + DQuaternion e f g h =
DQuaternion
(a + e)
(b + f)
(c + g)
(d + h)
{-# INLINE (-) #-}
DQuaternion a b c d - DQuaternion e f g h =
DQuaternion
(a - e)
(b - f)
(c - g)
(d - h)
{-# INLINE (*) #-}
DQuaternion as ax ay az * DQuaternion bs bx by bz =
withDVec3 v \x y z ->
DQuaternion s x y z
where
s = as * bs - DVec3.dot v1 v2
v = DVec3.cross v1 v2 + v2 DVec3.^* as + v1 DVec3.^* bs
v1 = dvec3 ax ay az
v2 = dvec3 bx by bz
{-# INLINE fromInteger #-}
fromInteger x = DQuaternion (fromInteger x) 0 0 0
{-# INLINE abs #-}
abs z = DQuaternion (norm z) 0 0 0
{-# INLINE signum #-}
signum q@(DQuaternion e i j k)
| m == 0 =
q
| not (isInfinite m || isNaN m) =
DQuaternion (e * misqrt) (i * misqrt) (j * misqrt) (k * misqrt)
| any isNaN [e, i, j, k] = qNaN
| not (ii || ij || ik) = DQuaternion 1 0 0 0
| not (ie || ij || ik) = DQuaternion 0 1 0 0
| not (ie || ii || ik) = DQuaternion 0 0 1 0
| not (ie || ii || ij) = DQuaternion 0 0 0 1
| otherwise = qNaN
where
m = quadrance q
misqrt = recip (sqrt m)
ie = isInfinite e
ii = isInfinite i
ij = isInfinite j
ik = isInfinite k
{-# INLINE qNaN #-}
qNaN :: DQuaternion
qNaN = DQuaternion dNaN dNaN dNaN dNaN
where
dNaN = 0/0
instance Storable DQuaternion where
{-# INLINE sizeOf #-}
sizeOf _ = 32
{-# INLINE alignment #-}
alignment _ = 32
{-# INLINE poke #-}
poke ptr (DQuaternion a b c d) = do
poke ptr' a
pokeElemOff ptr' 1 b
pokeElemOff ptr' 2 c
pokeElemOff ptr' 3 d
where
ptr' = castPtr ptr
{-# INLINE peek #-}
peek ptr = DQuaternion
<$> peek ptr'
<*> peekElemOff ptr' 1
<*> peekElemOff ptr' 2
<*> peekElemOff ptr' 3
where
ptr' = castPtr ptr
{-# INLINE axisAngle #-}
axisAngle :: DVec3 -> Double -> DQuaternion
axisAngle axis rads =
withDVec3 (DVec3.normalize axis DVec3.^* sin half) $
dquaternion (cos half)
where
half = rads / 2
intrinsic :: Double -> Double -> Double -> DQuaternion
intrinsic roll pitch yaw =
dquaternion
(cx * cy * cz + sx * sy * sz)
(sx * cy * cz + cx * sy * sz)
(cx * sy * cz - sx * cy * sz)
(cx * cy * sz - sx * sy * cz)
where
cx = cos (pitch * 0.5)
sx = sin (pitch * 0.5)
cy = cos (yaw * 0.5)
sy = sin (yaw * 0.5)
cz = cos (roll * 0.5)
sz = sin (roll * 0.5)
extrinsic :: Double -> Double -> Double -> DQuaternion
extrinsic heading elevation tilt =
axisAngle (dvec3 0 0 1) tilt *
axisAngle (dvec3 1 0 0) elevation *
axisAngle (dvec3 0 1 0) heading
{-# INLINE rotate #-}
rotate :: DQuaternion -> DVec3 -> DVec3
rotate (DQuaternion w x y z) v =
withDVec3 v \vx vy vz ->
let
x2 = x + x
y2 = y + y
z2 = z + z
tx = y2 * vz - z2 * vy
ty = z2 * vx - x2 * vz
tz = x2 * vy - y2 * vx
in
dvec3
(vx + w * tx + y * tz - z * ty)
(vy + w * ty + z * tx - x * tz)
(vz + w * tz + x * ty - y * tx)
{-# INLINE rotatePoint #-}
rotatePoint :: DQuaternion -> DVec3 -> DVec3 -> DVec3
rotatePoint q origin point =
origin + rotate q (point - origin)
rotationBetween :: DVec3 -> DVec3 -> DQuaternion
rotationBetween v1 v2 = axisAngle axis angle
where
axis = DVec3.cross v1 v2
angle = acos cosAngle
cosAngle =
max (-1) . min 1 $
DVec3.dot (DVec3.normalize v1) (DVec3.normalize v2)
lookAtUp :: DVec3 -> DVec3 -> DVec3 -> DQuaternion
lookAtUp src dst up = rot2 * rot1
where
dir3 = dst - src
rot1 = rotationBetween (dvec3 0 0 1) dir3
rot2 = rotationBetween newUp fixedUp
newUp = rotate rot1 up
fixedUp = DVec3.cross (DVec3.cross dir3 up) dir3
instance Block DQuaternion where
type PackedSize DQuaternion = 32
alignment140 _ = 32
sizeOf140 = sizeOfPacked
alignment430 = alignment140
sizeOf430 = sizeOf140
isStruct _ = True
read140 = peekDiffOff
write140 = pokeDiffOff
read430 = read140
write430 = write140
readPacked = read140
writePacked = write140
{-# INLINE alignment140 #-}
{-# INLINE sizeOf140 #-}
{-# INLINE alignment430 #-}
{-# INLINE sizeOf430 #-}
{-# INLINE isStruct #-}
{-# INLINE read140 #-}
{-# INLINE write140 #-}
{-# INLINE read430 #-}
{-# INLINE write430 #-}
{-# INLINE readPacked #-}
{-# INLINE writePacked #-}
#ifdef SERIALISE
instance Serialise DQuaternion where
encode v = withDQuaternion v \x y z w ->
Serialise.encodeListLen 4 <> encode x <> encode y <> encode z <> encode w
decode =
Serialise.decodeListLenOf 4 *> (dquaternion <$> decode <*> decode <*> decode <*> decode)
#endif