packages feed

geomancy-0.4.0.0: test/Spec/DMat4.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TypeApplications #-}

module Spec.DMat4 (tests) where

import Hedgehog
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog (testPropertyNamed)

import Linear qualified
import Linear ((!*!))

import Geomancy.DMat4 qualified
import Geomancy.DVec4 qualified
import Geomancy.Mat4 ((!*))
import Geomancy.Transform qualified as Transform

import Support.Ball (balls, balls44, consistent, enclosed, narrow, widen)
import Support.Gen (forAllDTransform, forAllTransform, genDVec4, genEdgeScalar, genMostlySame, genVec4)
import Support.Linear (entries, toM44, toM44D, toV4, toV4D)
import Support.Property (pattern PROP_TESTS)
import Support.Reference.Mat4 qualified as Ref.Mat4

tests :: TestTree
tests = testGroup "DMat4"
  [ testPropertyNamed "assoc" "prop_dmat4_assoc" prop_dmat4_assoc
  , testPropertyNamed "order" "prop_dmat4_order" prop_dmat4_order
  , testPropertyNamed "eq" "prop_dmat4_eq" prop_dmat4_eq
  , testPropertyNamed "det" "prop_dmat4_det" prop_dmat4_det
  , testPropertyNamed "inverse" "prop_dmat4_inverse" prop_dmat4_inverse
  , testPropertyNamed "apply" "prop_dmat4_apply" prop_dmat4_apply
  , testPropertyNamed "float_parity" "prop_dmat4_float_parity" prop_dmat4_float_parity
  ]

prop_dmat4_assoc :: Property
prop_dmat4_assoc = withDiscards 1000 . withTests PROP_TESTS $ property do
  (p, p_) <- forAllDTransform
  (v, v_) <- forAllDTransform
  (m, m_) <- forAllDTransform

  let
    pv'm = (p <> v) <> m
    p'vm = p <> (v <> m)
    pv'mBall = Ref.Mat4.compose (Ref.Mat4.compose (balls44 p_) (balls44 v_)) (balls44 m_)
    p'vmBall = Ref.Mat4.compose (balls44 p_) (Ref.Mat4.compose (balls44 v_) (balls44 m_))
  replicate 16 Nothing === enclosed (entries pv'mBall) (entries (toM44D pv'm))
  replicate 16 Nothing === enclosed (entries p'vmBall) (entries (toM44D p'vm))

  let
    mv_p = (m_ !*! v_) !*! p_
    m_vp = m_ !*! (v_ !*! p_)
    mv_pBall = (balls44 m_ !*! balls44 v_) !*! balls44 p_
    m_vpBall = balls44 m_ !*! (balls44 v_ !*! balls44 p_)
  replicate 16 Nothing === enclosed (entries mv_pBall) (entries mv_p)
  replicate 16 Nothing === enclosed (entries m_vpBall) (entries m_vp)

  replicate 16 Nothing === consistent (entries pv'mBall) (entries p'vmBall)
  replicate 16 Nothing === consistent (entries mv_pBall) (entries m_vpBall)
  replicate 16 Nothing === consistent (entries pv'mBall) (entries mv_pBall)

prop_dmat4_order :: Property
prop_dmat4_order = withTests 1 $ property do
  let
    cm = Geomancy.DMat4.colMajor @Geomancy.DMat4.DMat4
      0 1 2 3
      4 5 6 7
      8 9 0 1
      2 3 4 5
    rm = Geomancy.DMat4.rowMajor @Geomancy.DMat4.DMat4
      0 4 8 2
      1 5 9 3
      2 6 0 4
      3 7 1 5
  Geomancy.DMat4.toListColMajor2d cm === Geomancy.DMat4.toListColMajor2d rm

  let
    cmT = Geomancy.DMat4.transpose cm
    rmT = Geomancy.DMat4.transpose rm
  Geomancy.DMat4.toListColMajor2d cmT === Geomancy.DMat4.toListColMajor2d rmT

prop_dmat4_det :: Property
prop_dmat4_det = withTests 1 $ property do
  1     === Geomancy.DMat4.det Geomancy.DMat4.identity
  1     === Geomancy.DMat4.det (Geomancy.DMat4.fromMat4 $ Transform.translate 1 2 3)
  0     === Geomancy.DMat4.det (Geomancy.DMat4.fromMat4 $ Transform.scaleX 0)
  -1    === Geomancy.DMat4.det (Geomancy.DMat4.fromMat4 $ Transform.scaleZ (-1))
  8     === Geomancy.DMat4.det (Geomancy.DMat4.fromMat4 $ Transform.scale 2)
  (1/8) === Geomancy.DMat4.det (Geomancy.DMat4.fromMat4 $ Transform.scale (1/2))

prop_dmat4_inverse :: Property
prop_dmat4_inverse = withDiscards 1000 . withTests PROP_TESTS $ property do
  (m, m_) <- forAllDTransform
  let
    mm' = m <> Geomancy.DMat4.inverse m
    mBall = balls44 m_
    mm'Ball = Ref.Mat4.compose mBall (Ref.Mat4.inverse mBall)
  annotateShow mm'
  replicate 16 Nothing === enclosed (entries mm'Ball) (entries (toM44D mm'))
  replicate 16 Nothing === enclosed (entries mm'Ball) (entries (Linear.identity :: Linear.M44 Double))

prop_dmat4_apply :: Property
prop_dmat4_apply = withDiscards 1000 . withTests PROP_TESTS $ property do
  (m, m_) <- forAllDTransform
  v <- forAll genDVec4
  let
    out = m Geomancy.DMat4.!* v
    out_ = toV4D v Linear.*! m_
    outBall = Ref.Mat4.apply (balls44 m_) (balls (toV4D v))
    out_Ball = balls (toV4D v) Linear.*! balls44 m_
  annotateShow out
  annotateShow out_
  replicate 4 Nothing === enclosed outBall (toV4D out)
  replicate 4 Nothing === enclosed out_Ball out_
  replicate 4 Nothing === consistent outBall out_Ball

prop_dmat4_float_parity :: Property
prop_dmat4_float_parity = withDiscards 1000 . withTests PROP_TESTS $ property do
  (a, a_) <- forAllTransform
  (b, b_) <- forAllTransform
  let
    viaDouble =
      Geomancy.DMat4.toMat4 $
        Geomancy.DMat4.fromMat4 a <> Geomancy.DMat4.fromMat4 b
    widened = fmap (fmap widen) . balls44
    viaDoubleBall = fmap (fmap narrow) $ Ref.Mat4.compose (widened a_) (widened b_)
    viaFloatBall = Ref.Mat4.compose (balls44 a_) (balls44 b_)
  replicate 16 Nothing === enclosed (entries viaDoubleBall) (entries (toM44 viaDouble))
  replicate 16 Nothing === enclosed (entries viaFloatBall) (entries (toM44 (a <> b)))
  replicate 16 Nothing === consistent (entries viaDoubleBall) (entries viaFloatBall)

  v <- forAll genVec4
  let
    viaDoubleV =
      Geomancy.DVec4.toVec4 $
        Geomancy.DMat4.fromMat4 a Geomancy.DMat4.!* Geomancy.DVec4.fromVec4 v
    viaDoubleVBall =
      fmap narrow $ Ref.Mat4.apply (widened a_) (fmap widen (balls (toV4 v)))
    viaFloatVBall = Ref.Mat4.apply (balls44 a_) (balls (toV4 v))
  replicate 4 Nothing === enclosed viaDoubleVBall (toV4 viaDoubleV)
  replicate 4 Nothing === enclosed viaFloatVBall (toV4 (a !* v))
  replicate 4 Nothing === consistent viaDoubleVBall viaFloatVBall

prop_dmat4_eq :: Property
prop_dmat4_eq = withTests PROP_TESTS $ property do
  let
    four = Gen.list (Range.singleton 4)
    fromRows = evalMaybe . Geomancy.DMat4.fromRowMajor2d @[] @Geomancy.DMat4.DMat4
  rowsA <- forAll $ four (four genEdgeScalar)
  rowsB <- forAll $ traverse (traverse (genMostlySame 8)) rowsA
  a <- fromRows rowsA
  b <- fromRows rowsB
  label $ if rowsA == rowsB then "equal" else "different"
  (a == b) === (rowsA == rowsB)
  (a /= b) === (rowsA /= rowsB)