packages feed

geomancy-0.4.0.0: src/Geomancy/DTransform.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE TypeFamilies #-}

#ifdef TH_LIFT
{-# LANGUAGE DeriveLift #-}
#endif

module Geomancy.DTransform
  ( DTransform(..)
  , DMat4.inverse

  , apply
  , (!.)

  , translate
  , translateV

  , rotateX
  , rotateY
  , rotateZ
  , rotateQ

  , scale
  , scaleX
  , scaleY
  , scaleZ
  , scaleXY
  , scale3
  , scaleV

  , dirPos
  , node

  , fromTransform
  , toTransform
  ) where

import Foreign (Storable(..))
import Foreign.Ptr.Diff (peekDiffOff, pokeDiffOff)

#ifdef TH_LIFT
import Language.Haskell.TH.Syntax (Lift)
#endif

#ifdef SERIALISE
import Codec.Serialise (Serialise)
#endif

import Geomancy.DMat4 (DMat4, colMajor)
import Geomancy.DQuaternion (DQuaternion, withDQuaternion)
import Geomancy.DVec3 (DVec3, dvec3, withDVec3)
import Geomancy.DVec4 (fromDVec3, withDVec4)
import Geomancy.DMat4 qualified as DMat4
import Geomancy.Transform (Transform(..))

import Graphics.Gl.Block (Block(..))

newtype DTransform = DTransform { unDTransform :: DMat4 }
  deriving newtype (Eq, Show, Semigroup, Monoid, Storable)
#ifdef TH_LIFT
  deriving Lift
#endif
#ifdef SERIALISE
  deriving Serialise
#endif

instance Block DTransform where
  type PackedSize DTransform = 128
  alignment140 _  = 32
  sizeOf140       = sizeOfPacked
  alignment430    = alignment140
  sizeOf430       = sizeOf140
  isStruct _      = False
  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 #-}

{-# INLINE fromTransform #-}
fromTransform :: Transform -> DTransform
fromTransform = DTransform . DMat4.fromMat4

{-# INLINE toTransform #-}
toTransform :: DTransform -> Transform
toTransform = Transform . DMat4.toMat4 . unDTransform

apply :: DVec3 -> DTransform -> DVec3
apply = flip (!.)

(!.) :: DTransform -> DVec3 -> DVec3
(!.) mat vec =
  withDVec4 res \x y z w ->
    dvec3 (x / w) (y / w) (z / w)
  where
    res = mat DMat4.!* fromDVec3 vec 1.0

infixr 5 !.

{-# INLINE translate #-}
translate :: Double -> Double -> Double -> DTransform
translate x y z = colMajor
  1 0 0 x
  0 1 0 y
  0 0 1 z
  0 0 0 1

{-# INLINE translateV #-}
translateV :: DVec3 -> DTransform
translateV vec = withDVec3 vec translate

{-# INLINE scale3 #-}
scale3 :: Double -> Double -> Double -> DTransform
scale3 x y z = colMajor
  x 0 0 0
  0 y 0 0
  0 0 z 0
  0 0 0 1

{-# INLINE scale #-}
scale :: Double -> DTransform
scale s = scale3 s s s

{-# INLINE scaleX #-}
scaleX :: Double -> DTransform
scaleX x = scale3 x 1 1

{-# INLINE scaleY #-}
scaleY :: Double -> DTransform
scaleY y = scale3 1 y 1

{-# INLINE scaleZ #-}
scaleZ :: Double -> DTransform
scaleZ z = scale3 1 1 z

{-# INLINE scaleXY #-}
scaleXY :: Double -> Double -> DTransform
scaleXY x y = scale3 x y 1

{-# INLINE scaleV #-}
scaleV :: DVec3 -> DTransform
scaleV s = withDVec3 s scale3

{-# INLINE rotateX #-}
rotateX :: Double -> DTransform
rotateX rads =
  colMajor
    1 0   0  0
    0 c (-s) 0
    0 s   c  0
    0 0   0  1
  where
    c = cos rads
    s = sin rads

{-# INLINE rotateY #-}
rotateY :: Double -> DTransform
rotateY rads =
  colMajor
    c  0 s 0
    0  1 0 0
  (-s) 0 c 0
    0  0 0 1
  where
    c = cos rads
    s = sin rads

{-# INLINE rotateZ #-}
rotateZ :: Double -> DTransform
rotateZ rads =
  colMajor
    c (-s) 0 0
    s   c  0 0
    0   0  1 0
    0   0  0 1
  where
   c = cos rads
   s = sin rads

{-# INLINE rotateQ #-}
rotateQ :: DQuaternion -> DTransform
rotateQ dir = dirPos dir 0

{-# INLINE dirPos #-}
dirPos :: DQuaternion -> DVec3 -> DTransform
dirPos rs t =
  withDQuaternion rs \w x y z ->
  withDVec3 t \tx ty tz ->
    let
      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
    in
      colMajor
        (1 - 2 * (y2 + z2)) (    2 * (xy - zw)) (    2 * (xz + yw)) tx
        (    2 * (xy + zw)) (1 - 2 * (x2 + z2)) (    2 * (yz - xw)) ty
        (    2 * (xz - yw)) (    2 * (yz + xw)) (1 - 2 * (x2 + y2)) tz
        0                   0                   0                    1

node :: DVec3 -> DQuaternion -> DVec3 -> DTransform
node t r s
 | s == 1.0 = dirPos r t
 | otherwise = dirPos r t <> scaleV s