packages feed

geomancy-0.2.4.2: src/Geomancy/Mat4.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE UnliftedFFITypes #-}
{-# LANGUAGE ViewPatterns #-}

-- | General matrix storage and operations.

module Geomancy.Mat4
  ( Mat4

  , rowMajor
  , withRowMajor
  , toListRowMajor
  , toListRowMajor2d
  , fromRowMajor2d

  , colMajor
  , withColMajor
  , toListColMajor
  , toListColMajor2d

  , identity
  , transpose
  , inverse
  , pointwise
  , zipWith
  , matrixProduct
  , scalarMultiply
  , (!*)
  ) where

import Prelude hiding (zipWith)
import GHC.Exts hiding (VecCount(..), toList)

import Control.DeepSeq (NFData(rnf))
import Foreign (Storable(..))
import GHC.IO (IO(..))
import System.IO.Unsafe (unsafePerformIO)
import Text.Printf (printf)

import qualified Data.Foldable as Foldable
import qualified Data.List as List

import Geomancy.Vec4 (Vec4(..), unsafeNewVec4)

data Mat4 = Mat4 ByteArray#

{- | Construct 'Mat4' from @row@ notation.
-}
rowMajor
  :: Coercible Mat4 a
  => Float -> Float -> Float -> Float
  -> Float -> Float -> Float -> Float
  -> Float -> Float -> Float -> Float
  -> Float -> Float -> Float -> Float
  -> a
rowMajor = coerce mat4

{- | Reduce 'Mat4' with a function with @row@ notation of arguments.
-}
withRowMajor
  :: Coercible a Mat4
  => a
  ->
    ( Float -> Float -> Float -> Float ->
      Float -> Float -> Float -> Float ->
      Float -> Float -> Float -> Float ->
      Float -> Float -> Float -> Float ->
      r
    )
  -> r
withRowMajor m = withMat4 (coerce m)

toListRowMajor :: Coercible a Mat4 => a -> [Float]
toListRowMajor = toList . coerce

toListRowMajor2d :: Coercible a Mat4 => a -> [[Float]]
toListRowMajor2d = toList2d . coerce

{- |
  Build a Mat4 from a list-of-lists kind of container
  with row-major ordering of elements.

@
  fromRowMajor2d (Linear.mkTransformation dir pos) :: Transform
@
-}
fromRowMajor2d
  :: forall t a
  .  ( Foldable t
     , Coercible Mat4 a
     )
  => t (t Float)
  -> Maybe a
fromRowMajor2d rows =
  case Foldable.toList rows of
    [r0, r1, r2, r3] ->
      withRow r0 \m00 m01 m02 m03 ->
      withRow r1 \m10 m11 m12 m13 ->
      withRow r2 \m20 m21 m22 m23 ->
      withRow r3 \m30 m31 m32 m33 ->
        Just . coerce $ mat4
          m00 m01 m02 m03
          m10 m11 m12 m13
          m20 m21 m22 m23
          m30 m31 m32 m33
    _ ->
      Nothing
  where
    withRow row f =
      case Foldable.toList row of
        [c0, c1, c2, c3] ->
          f c0 c1 c2 c3
        _ ->
          Nothing

{- | Construct a 'Mat4' from @column@ notation.
-}
{-# INLINE colMajor #-}
colMajor
  :: Coercible Mat4 a
  => Float -> Float -> Float -> Float
  -> Float -> Float -> Float -> Float
  -> Float -> Float -> Float -> Float
  -> Float -> Float -> Float -> Float
  -> a
colMajor
  m00 m01 m02 m03
  m10 m11 m12 m13
  m20 m21 m22 m23
  m30 m31 m32 m33 =
    coerce $ mat4
      m00 m10 m20 m30
      m01 m11 m21 m31
      m02 m12 m22 m32
      m03 m13 m23 m33

{- | Reduce 'Mat4' with a function with @column@ notation for arguments.
-}
{-# INLINE withColMajor #-}
withColMajor
  :: Coercible a Mat4
  => a
  ->
    ( Float -> Float -> Float -> Float ->
      Float -> Float -> Float -> Float ->
      Float -> Float -> Float -> Float ->
      Float -> Float -> Float -> Float ->
      r
    )
  -> r
withColMajor m f = withMat4 (coerce m)
  \ m00 m01 m02 m03
    m10 m11 m12 m13
    m20 m21 m22 m23
    m30 m31 m32 m33 ->
  f
    m00 m10 m20 m30
    m01 m11 m21 m31
    m02 m12 m22 m32
    m03 m13 m23 m33

toListColMajor :: Coercible a Mat4 => a -> [Float]
toListColMajor = toListTrans . coerce

toListColMajor2d :: Coercible a Mat4 => a -> [[Float]]
toListColMajor2d = toList2dTrans . coerce

{- | Construct 'Mat4' from elements in memory order.
-}
{-# INLINE mat4 #-}
mat4
  :: Float -> Float -> Float -> Float
  -> Float -> Float -> Float -> Float
  -> Float -> Float -> Float -> Float
  -> Float -> Float -> Float -> Float
  -> Mat4
mat4
  (F# m00) (F# m01) (F# m02) (F# m03)
  (F# m10) (F# m11) (F# m12) (F# m13)
  (F# m20) (F# m21) (F# m22) (F# m23)
  (F# m30) (F# m31) (F# m32) (F# m33) =
  runRW# \world ->
    let
      !(# world_, arr #) = newAlignedPinnedByteArray# 64# 16# world

      world00 = writeFloatArray# arr 0x0# m00 world_
      world01 = writeFloatArray# arr 0x1# m01 world00
      world02 = writeFloatArray# arr 0x2# m02 world01
      world03 = writeFloatArray# arr 0x3# m03 world02

      world10 = writeFloatArray# arr 0x4# m10 world03
      world11 = writeFloatArray# arr 0x5# m11 world10
      world12 = writeFloatArray# arr 0x6# m12 world11
      world13 = writeFloatArray# arr 0x7# m13 world12

      world20 = writeFloatArray# arr 0x8# m20 world13
      world21 = writeFloatArray# arr 0x9# m21 world20
      world22 = writeFloatArray# arr 0xA# m22 world21
      world23 = writeFloatArray# arr 0xB# m23 world22

      world30 = writeFloatArray# arr 0xC# m30 world23
      world31 = writeFloatArray# arr 0xD# m31 world30
      world32 = writeFloatArray# arr 0xE# m32 world31
      world33 = writeFloatArray# arr 0xF# m33 world32

      !(# _world', arr' #) = unsafeFreezeByteArray# arr world33
    in
      Mat4 arr'

{- | Reduce 'Mat4' with a function with @memory@ notation for arguments.
-}
{-# INLINE withMat4 #-}
withMat4
  :: Mat4
  ->
    ( Float -> Float -> Float -> Float ->
      Float -> Float -> Float -> Float ->
      Float -> Float -> Float -> Float ->
      Float -> Float -> Float -> Float ->
      r
    )
  -> r
withMat4 (Mat4 arr) f =
  f
    (F# (indexFloatArray# arr 0x0#))
    (F# (indexFloatArray# arr 0x1#))
    (F# (indexFloatArray# arr 0x2#))
    (F# (indexFloatArray# arr 0x3#))

    (F# (indexFloatArray# arr 0x4#))
    (F# (indexFloatArray# arr 0x5#))
    (F# (indexFloatArray# arr 0x6#))
    (F# (indexFloatArray# arr 0x7#))

    (F# (indexFloatArray# arr 0x8#))
    (F# (indexFloatArray# arr 0x9#))
    (F# (indexFloatArray# arr 0xA#))
    (F# (indexFloatArray# arr 0xB#))

    (F# (indexFloatArray# arr 0xC#))
    (F# (indexFloatArray# arr 0xD#))
    (F# (indexFloatArray# arr 0xE#))
    (F# (indexFloatArray# arr 0xF#))

{- | @I@, the identity matrix.

Neutral element of its monoid, so you can use 'mempty'.
-}
{-# INLINE identity #-}
identity :: Mat4
identity = mat4
  1 0 0 0
  0 1 0 0
  0 0 1 0
  0 0 0 1

{-# INLINE transpose #-}
transpose :: Mat4 -> Mat4
transpose =
  flip withMat4
    \ m00 m01 m02 m03
      m10 m11 m12 m13
      m20 m21 m22 m23
      m30 m31 m32 m33 ->
    mat4
      m00 m10 m20 m30
      m01 m11 m21 m31
      m02 m12 m22 m32
      m03 m13 m23 m33

{- | Compute an inverse matrix, slowly.
-}
inverse :: (Coercible Mat4 a, Coercible Mat4 a) => a -> a
inverse m =
  coerce $ withMat4 (coerce m)
    \ m00 m01 m02 m03
      m10 m11 m12 m13
      m20 m21 m22 m23
      m30 m31 m32 m33 ->
        let
          invDet = recip det

          det
            = s0 * c5
            - s1 * c4
            + s2 * c3
            + s3 * c2
            - s4 * c1
            + s5 * c0

          s0 = m00 * m11 - m10 * m01
          s1 = m00 * m12 - m10 * m02
          s2 = m00 * m13 - m10 * m03
          s3 = m01 * m12 - m11 * m02
          s4 = m01 * m13 - m11 * m03
          s5 = m02 * m13 - m12 * m03

          c5 = m22 * m33 - m32 * m23
          c4 = m21 * m33 - m31 * m23
          c3 = m21 * m32 - m31 * m22
          c2 = m20 * m33 - m30 * m23
          c1 = m20 * m32 - m30 * m22
          c0 = m20 * m31 - m30 * m21

          i00 = ( m11 * c5 - m12 * c4 + m13 * c3) * invDet
          i01 = (-m01 * c5 + m02 * c4 - m03 * c3) * invDet
          i02 = ( m31 * s5 - m32 * s4 + m33 * s3) * invDet
          i03 = (-m21 * s5 + m22 * s4 - m23 * s3) * invDet

          i10 = (-m10 * c5 + m12 * c2 - m13 * c1) * invDet
          i11 = ( m00 * c5 - m02 * c2 + m03 * c1) * invDet
          i12 = (-m30 * s5 + m32 * s2 - m33 * s1) * invDet
          i13 = ( m20 * s5 - m22 * s2 + m23 * s1) * invDet

          i20 = ( m10 * c4 - m11 * c2 + m13 * c0) * invDet
          i21 = (-m00 * c4 + m01 * c2 - m03 * c0) * invDet
          i22 = ( m30 * s4 - m31 * s2 + m33 * s0) * invDet
          i23 = (-m20 * s4 + m21 * s2 - m23 * s0) * invDet

          i30 = (-m10 * c3 + m11 * c1 - m12 * c0) * invDet
          i31 = ( m00 * c3 - m01 * c1 + m02 * c0) * invDet
          i32 = (-m30 * s3 + m31 * s1 - m32 * s0) * invDet
          i33 = ( m20 * s3 - m21 * s1 + m22 * s0) * invDet
        in
          mat4
            i00 i01 i02 i03
            i10 i11 i12 i13
            i20 i21 i22 i23
            i30 i31 i32 i33

pointwise :: Mat4 -> Mat4 -> (Float -> Float -> Float) -> Mat4
pointwise a b f =
  withMat4 a
    \ a00 a01 a02 a03
      a10 a11 a12 a13
      a20 a21 a22 a23
      a30 a31 a32 a33 ->
  withMat4 b
    \ b00 b01 b02 b03
      b10 b11 b12 b13
      b20 b21 b22 b23
      b30 b31 b32 b33 ->
  mat4
    (f a00 b00) (f a01 b01) (f a02 b02) (f a03 b03)
    (f a10 b10) (f a11 b11) (f a12 b12) (f a13 b13)
    (f a20 b20) (f a21 b21) (f a22 b22) (f a23 b23)
    (f a30 b30) (f a31 b31) (f a32 b32) (f a33 b33)

toList :: Mat4 -> [Float]
toList = flip withMat4
    \ a00 a01 a02 a03
      a10 a11 a12 a13
      a20 a21 a22 a23
      a30 a31 a32 a33 ->
    [ a00, a01, a02, a03
    , a10, a11, a12, a13
    , a20, a21, a22, a23
    , a30, a31, a32, a33
    ]

toList2d :: Mat4 -> [[Float]]
toList2d = flip withMat4
    \ a00 a01 a02 a03
      a10 a11 a12 a13
      a20 a21 a22 a23
      a30 a31 a32 a33 ->
    [ [a00, a01, a02, a03]
    , [a10, a11, a12, a13]
    , [a20, a21, a22, a23]
    , [a30, a31, a32, a33]
    ]

toListTrans :: Mat4 -> [Float]
toListTrans = flip withMat4
    \ a00 a01 a02 a03
      a10 a11 a12 a13
      a20 a21 a22 a23
      a30 a31 a32 a33 ->
    [ a00, a10, a20, a30
    , a01, a11, a21, a31
    , a02, a12, a22, a32
    , a03, a13, a23, a33
    ]

toList2dTrans :: Mat4 -> [[Float]]
toList2dTrans = flip withMat4
    \ a00 a01 a02 a03
      a10 a11 a12 a13
      a20 a21 a22 a23
      a30 a31 a32 a33 ->
    [ [a00, a10, a20, a30]
    , [a01, a11, a21, a31]
    , [a02, a12, a22, a32]
    , [a03, a13, a23, a33]
    ]

zipWith :: (Float -> Float -> c) -> Mat4 -> Mat4 -> [c]
zipWith f a b = List.zipWith f (toList a) (toList b)

foreign import ccall unsafe "Mat4xMat4_SIMD" m4m4simd :: Addr# -> Addr# -> Addr# -> IO ()

{-# INLINE matrixProduct #-}
matrixProduct :: Mat4 -> Mat4 -> Mat4
matrixProduct (Mat4 l) (Mat4 r) = unsafePerformIO do
  result@(Mat4 m) <- unsafeNewMat4
  m4m4simd
    (byteArrayContents# l)
    (byteArrayContents# r)
    (byteArrayContents# m)
  pure result

{-# INLINE unsafeNewMat4 #-}
unsafeNewMat4 :: IO Mat4
unsafeNewMat4 =
  IO \world ->
    let
      !(# world_, arr_ #) = newAlignedPinnedByteArray# 64# 16# world
      !(# _world', arr #) = unsafeFreezeByteArray# arr_ world_
    in
      (# world, Mat4 arr #)

{-# INLINE scalarMultiply #-}
scalarMultiply :: Float -> Mat4 -> Mat4
scalarMultiply x m =
  withMat4 m
    \ m00 m01 m02 m03
      m10 m11 m12 m13
      m20 m21 m22 m23
      m30 m31 m32 m33 ->
      mat4
        (m00 * x) (m10 * x) (m20 * x) (m30 * x)
        (m01 * x) (m11 * x) (m21 * x) (m31 * x)
        (m02 * x) (m12 * x) (m22 * x) (m32 * x)
        (m03 * x) (m13 * x) (m23 * x) (m33 * x)

foreign import ccall unsafe "Mat4xVec4_SIMD" m4v4simd :: Addr# -> Addr# -> Addr# -> IO ()

-- | Matrix - column vector multiplication
(!*) :: Coercible a Mat4 => a -> Vec4 -> Vec4
(!*) (coerce -> Mat4 m) (Vec4 v) = unsafePerformIO do
  result@(Vec4 o) <- unsafeNewVec4
  m4v4simd
    (byteArrayContents# m)
    (byteArrayContents# v)
    (byteArrayContents# o)
  pure result

  -- withVec4 vec \v1 v2 v3 v4 ->
  --   withColMajor mat
  --     \ m11 m12 m13 m14
  --       m21 m22 m23 m24
  --       m31 m32 m33 m34
  --       m41 m42 m43 m44 ->
  --         vec4
  --           (m11 * v1 + m12 * v2 + m13 * v3 + m14 * v4)
  --           (m21 * v1 + m22 * v2 + m23 * v3 + m24 * v4)
  --           (m31 * v1 + m32 * v2 + m33 * v3 + m34 * v4)
  --           (m41 * v1 + m42 * v2 + m43 * v3 + m44 * v4)

instance NFData Mat4 where
  rnf Mat4{} = ()

instance Semigroup Mat4 where
  {-# INLINE (<>) #-}
  (<>) = matrixProduct

instance Monoid Mat4 where
  {-# INLINE mempty #-}
  mempty = identity

instance Show Mat4 where
  show = flip withMat4
    \ m00 m01 m02 m03
      m10 m11 m12 m13
      m20 m21 m22 m23
      m30 m31 m32 m33 ->
    unlines
      [ printf "| %.4f %.4f %.4f %.4f |" m00 m01 m02 m03
      , printf "| %.4f %.4f %.4f %.4f |" m10 m11 m12 m13
      , printf "| %.4f %.4f %.4f %.4f |" m20 m21 m22 m23
      , printf "| %.4f %.4f %.4f %.4f |" m30 m31 m32 m33
      ]

instance Storable Mat4 where
  sizeOf _mat4 = 64

  alignment _mat4 = 16

  {-# INLINE poke #-}
  poke (Ptr addr) (Mat4 arr) = IO \world ->
    let
      world' = copyByteArrayToAddr# arr 0# addr 64# world
    in
      (# world', () #)

  {-# INLINE peek #-}
  peek (Ptr addr) = IO \world ->
    let
      !(# world0, arr #)  = newAlignedPinnedByteArray# 64# 16# world
      world1              = copyAddrToByteArray# addr arr 0# 64# world0
      !(# world', arr' #) = unsafeFreezeByteArray# arr world1
    in
      (# world', Mat4 arr' #)