packages feed

geomancy-0.4.0.0: test/Support/Reference/Quaternion.hs

{-# LANGUAGE ImportQualifiedPost #-}

module Support.Reference.Quaternion
  ( multiply
  , axisAngle
  , intrinsic
  , extrinsic
  , rotate
  ) where

import Linear (Quaternion(..), V3(..))
import Linear.Epsilon (Epsilon)

import Support.Reference.Vec3 qualified as Vec3

multiply :: Num a => Quaternion a -> Quaternion a -> Quaternion a
multiply (Quaternion as v1) (Quaternion bs v2) = Quaternion s v
  where
    s = as * bs - Vec3.dot v1 v2
    v = Vec3.cross v1 v2 + fmap (* as) v2 + fmap (* bs) v1

axisAngle :: (Floating a, Epsilon a) => V3 a -> a -> Quaternion a
axisAngle axis rads =
  Quaternion (cos half) (fmap (* sin half) (Vec3.normalize axis))
  where
    half = rads / 2

intrinsic :: Floating a => a -> a -> a -> Quaternion a
intrinsic roll pitch yaw =
  Quaternion
    (cx * cy * cz + sx * sy * sz)
    (V3
      (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 :: (Floating a, Epsilon a) => a -> a -> a -> Quaternion a
extrinsic heading elevation tilt =
  axisAngle (V3 0 0 1) tilt `multiply`
  axisAngle (V3 1 0 0) elevation `multiply`
  axisAngle (V3 0 1 0) heading

rotate :: Num a => Quaternion a -> V3 a -> V3 a
rotate (Quaternion w (V3 x y z)) (V3 vx vy vz) =
  V3
    (vx + w * tx + y * tz - z * ty)
    (vy + w * ty + z * tx - x * tz)
    (vz + w * tz + x * ty - y * tx)
  where
    x2 = x + x
    y2 = y + y
    z2 = z + z
    tx = y2 * vz - z2 * vy
    ty = z2 * vx - x2 * vz
    tz = x2 * vy - y2 * vx