packages feed

geomancy-0.4.0.0: test/Spec/Transform.hs

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

module Spec.Transform (tests) where

import Geomancy
import Hedgehog
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog (testPropertyNamed)

import Linear (M44, V3(..))

import Geomancy.Mat4 qualified
import Geomancy.Quaternion qualified
import Geomancy.Transform ((!.))
import Geomancy.Transform qualified as Transform

import Support.Ball (Ball, balls, consistent, enclosed)
import Support.Fixtures
  (intrinsicComposed, intrinsicComposedRef, quarterTurn, rotationsMatch, trs, trsRef, xPos, yNeg, yPos, zPos)
import Support.Linear (toV3)
import Support.Reference.Mat4 qualified as Ref.Mat4
import Support.Reference.Quaternion qualified as Ref.Quaternion
import Support.Reference.Transform qualified as Ref.Transform

tests :: TestTree
tests = testGroup "Transform"
  [ testPropertyNamed "det" "prop_transform_det" prop_transform_det
  , testPropertyNamed "rotateX" "prop_transform_rotateX" prop_transform_rotateX
  , testPropertyNamed "rotateY" "prop_transform_rotateY" prop_transform_rotateY
  , testPropertyNamed "rotateZ" "prop_transform_rotateZ" prop_transform_rotateZ
  , testPropertyNamed "node" "prop_transform_node" prop_transform_node
  , testPropertyNamed "order" "prop_transform_order" prop_transform_order
  ]

prop_transform_det :: Property
prop_transform_det = withTests 1 $ property do
  1     === Geomancy.Mat4.det Geomancy.Mat4.identity
  1     === Geomancy.Mat4.det (Transform.rotateY (pi/6))  -- area-preserving
  1     === Geomancy.Mat4.det (Transform.translate 1 2 3) -- area-preserving too
  0     === Geomancy.Mat4.det (Transform.scaleX 0)        -- space collapse, no inverse
  -1    === Geomancy.Mat4.det (Transform.scaleZ (-1))     -- flips the space
  8     === Geomancy.Mat4.det (Transform.scale 2)         -- stretches a little
  (1/8) === Geomancy.Mat4.det (Transform.scale (1/2))     -- stretches backwards

prop_transform_rotateX :: Property
prop_transform_rotateX = withTests 1 $ property do
  rotationMatches xPos Transform.rotateX Ref.Transform.rotateX zPos yNeg

prop_transform_rotateY :: Property
prop_transform_rotateY = withTests 1 $ property do
  rotationMatches yPos Transform.rotateY Ref.Transform.rotateY zPos xPos

prop_transform_rotateZ :: Property
prop_transform_rotateZ = withTests 1 $ property do
  rotationMatches zPos Transform.rotateZ Ref.Transform.rotateZ xPos yPos

rotationMatches
  :: Vec3
  -> (Float -> Transform)
  -> (Ball Float -> M44 (Ball Float))
  -> Vec3
  -> Vec3
  -> PropertyT IO ()
rotationMatches axis rotate rotateRef input expected = do
  rotationsMatch toV3
    (rotate quarterTurn !.)
    (rotateRef quarterTurn Ref.Transform.!.)
    [(input, expected)]
  rotationsMatch toV3
    (Transform.rotateQ (Geomancy.Quaternion.axisAngle axis quarterTurn) !.)
    (Ref.Transform.rotateQ (Ref.Quaternion.axisAngle (balls (toV3 axis)) quarterTurn) Ref.Transform.!.)
    [(input, expected)]

prop_transform_node :: Property
prop_transform_node = withTests 1 $ property do
  let
    r = intrinsicComposed 0 0 (pi/7)
    dps = Transform.node (vec3 2 3 5) r 11
    node = trs (vec3 2 3 5) r 11
    v = vec3 0 0 1
    rBall = intrinsicComposedRef 0 0 (pi/7)
    dpsBall = Ref.Transform.node (V3 2 3 5) rBall 11
    nodeBall = trsRef (V3 2 3 5) rBall 11
    viaDpsBall = dpsBall Ref.Transform.!. V3 0 0 1
    viaNodeBall = nodeBall Ref.Transform.!. V3 0 0 1
  replicate 3 Nothing === enclosed viaDpsBall (toV3 (dps !. v))
  replicate 3 Nothing === enclosed viaNodeBall (toV3 (node !. v))
  replicate 3 Nothing === consistent viaDpsBall viaNodeBall

prop_transform_order :: Property
prop_transform_order = withTests 1 $ property do
  let
    parent = Transform.node (vec3 4 5 6) (intrinsicComposed 0 0 (pi/6)) 2
    local = Transform.node (vec3 7 8 9) (intrinsicComposed 0 (pi/6) 0) (1/3)
    precomp = parent <> local !. vec3 0 0 1
    nocomp = parent !. local !. vec3 0 0 1
    parentBall = Ref.Transform.node (V3 4 5 6) (intrinsicComposedRef 0 0 (pi/6)) 2
    localBall = Ref.Transform.node (V3 7 8 9) (intrinsicComposedRef 0 (pi/6) 0) (1/3)
    precompBall = Ref.Mat4.compose parentBall localBall Ref.Transform.!. V3 0 0 1
    nocompBall = parentBall Ref.Transform.!. localBall Ref.Transform.!. V3 0 0 1
  replicate 3 Nothing === enclosed precompBall (toV3 precomp)
  replicate 3 Nothing === enclosed nocompBall (toV3 nocomp)
  replicate 3 Nothing === consistent precompBall nocompBall