geomancy-0.4.0.0: test/Spec/Quaternion.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
module Spec.Quaternion (tests) where
import Geomancy
import Hedgehog
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog (testPropertyNamed)
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Linear.Quaternion qualified
import Geomancy.Quaternion qualified
import Geomancy.Transform ((!.))
import Geomancy.Transform qualified as Transform
import Support.Ball (balls, consistent, enclosed, exactly)
import Support.Fixtures
(intrinsicComposed, intrinsicComposedRef, quarterTurn, rotationsMatch, xNeg, xPos, yNeg, yPos, zNeg, zPos)
import Support.Gen (genAngle, genSmallVec3, genVec3)
import Support.Linear (toQ, toV3)
import Support.Reference.Linear qualified as Ref.Linear
import Support.Reference.Quaternion qualified as Ref.Quaternion
import Support.Reference.Transform qualified as Ref.Transform
tests :: TestTree
tests = testGroup "Quaternion"
[ testPropertyNamed "ref" "prop_quaternion_ref" prop_quaternion_ref
, testPropertyNamed "rotate_matrix" "prop_quaternion_rotate_matrix" prop_quaternion_rotate_matrix
, testPropertyNamed "associativity" "prop_quaternion_associativity" prop_quaternion_associativity
, testPropertyNamed "intrinsic" "prop_quaternion_intrinsic" prop_quaternion_intrinsic
, testPropertyNamed "extrinsic" "prop_quaternion_extrinsic" prop_quaternion_extrinsic
, testPropertyNamed "intrinsic_ref" "prop_quaternion_intrinsic_ref" prop_quaternion_intrinsic_ref
]
prop_quaternion_ref :: Property
prop_quaternion_ref = property do
annotate "Construction"
(axis, angle) <- forAll $ (,) <$> genVec3 <*> genAngle
let qg = Geomancy.Quaternion.axisAngle axis angle
let ql = Linear.Quaternion.axisAngle (toV3 axis) angle
show (toQ qg) === show ql
annotate "Rotation"
p <- forAll genSmallVec3
let
rpg = Geomancy.Quaternion.rotate qg p
rpl = Linear.Quaternion.rotate ql (toV3 p)
rpgBall =
Ref.Quaternion.rotate
(Ref.Quaternion.axisAngle (balls (toV3 axis)) (exactly angle))
(balls (toV3 p))
rplBall =
Ref.Linear.rotate
(Linear.Quaternion.axisAngle (balls (toV3 axis)) (exactly angle))
(balls (toV3 p))
replicate 3 Nothing === enclosed rpgBall (toV3 rpg)
replicate 3 Nothing === enclosed rplBall rpl
replicate 3 Nothing === consistent rpgBall rplBall
annotate "Multiplication"
(axisB, angleB) <- forAll $ (,) <$> genVec3 <*> genAngle
let qgB = Geomancy.Quaternion.axisAngle axisB angleB
let qlB = Linear.Quaternion.axisAngle (toV3 axisB) angleB
show (toQ (qg * qgB)) === show (ql * qlB)
prop_quaternion_rotate_matrix :: Property
prop_quaternion_rotate_matrix = property do
q <- forAll $ quaternion <$> genUnitish <*> genUnitish <*> genUnitish <*> genUnitish
p <- forAll genSmallVec3
let
viaRotate = Geomancy.Quaternion.rotate q p
viaMatrix = Transform.rotateQ q !. p
viaRotateBall = Ref.Quaternion.rotate (balls (toQ q)) (balls (toV3 p))
viaMatrixBall = Ref.Transform.rotateQ (balls (toQ q)) Ref.Transform.!. balls (toV3 p)
replicate 3 Nothing === enclosed viaRotateBall (toV3 viaRotate)
replicate 3 Nothing === enclosed viaMatrixBall (toV3 viaMatrix)
replicate 3 Nothing === consistent viaRotateBall viaMatrixBall
where
genUnitish = Gen.float (Range.linearFracFrom 0.0 (-1) 1)
prop_quaternion_associativity :: Property
prop_quaternion_associativity = property do
(a, b, c) <- forAll $ (,,)
<$> (Geomancy.Quaternion.axisAngle <$> genVec3 <*> genAngle)
<*> (Geomancy.Quaternion.axisAngle <$> genVec3 <*> genAngle)
<*> (Geomancy.Quaternion.axisAngle <$> genVec3 <*> genAngle)
let ab'c = (a * b) * c
annotateShow ab'c
let a'bc = a * (b * c)
annotateShow a'bc
let
aBall = balls (toQ a)
bBall = balls (toQ b)
cBall = balls (toQ c)
ab'cBall = (aBall `Ref.Quaternion.multiply` bBall) `Ref.Quaternion.multiply` cBall
a'bcBall = aBall `Ref.Quaternion.multiply` (bBall `Ref.Quaternion.multiply` cBall)
replicate 4 Nothing === enclosed ab'cBall (toQ ab'c)
replicate 4 Nothing === enclosed a'bcBall (toQ a'bc)
replicate 4 Nothing === consistent ab'cBall a'bcBall
prop_quaternion_intrinsic :: Property
prop_quaternion_intrinsic = withTests 1 $ property do
let
q = Geomancy.Quaternion.intrinsic 0 quarterTurn quarterTurn
qBall = Ref.Quaternion.intrinsic 0 quarterTurn quarterTurn
rotationsMatch toV3
(Geomancy.Quaternion.rotate q)
(Ref.Quaternion.rotate qBall)
[(zPos, yNeg), (xPos, zNeg), (yNeg, xNeg)]
prop_quaternion_extrinsic :: Property
prop_quaternion_extrinsic = withTests 1 $ property do
let
q = Geomancy.Quaternion.extrinsic quarterTurn quarterTurn 0
qBall = Ref.Quaternion.extrinsic quarterTurn quarterTurn 0
rotationsMatch toV3
(Geomancy.Quaternion.rotate q)
(Ref.Quaternion.rotate qBall)
[(zPos, xPos), (xPos, yPos), (yNeg, zNeg)]
prop_quaternion_intrinsic_ref :: Property
prop_quaternion_intrinsic_ref = property do
(roll, pitch, yaw) <- forAll $ (,,) <$> genAngle <*> genAngle <*> genAngle
let fused = Geomancy.Quaternion.intrinsic roll pitch yaw
annotateShow fused
let composed = intrinsicComposed roll pitch yaw
annotateShow composed
let viaFused = Geomancy.Quaternion.rotate fused zPos
annotateShow viaFused
let viaComposed = Geomancy.Quaternion.rotate composed zPos
let
zPosBall = balls (toV3 zPos)
viaFusedBall =
Ref.Quaternion.rotate (Ref.Quaternion.intrinsic (exactly roll) (exactly pitch) (exactly yaw)) zPosBall
viaComposedBall =
Ref.Quaternion.rotate (intrinsicComposedRef (exactly roll) (exactly pitch) (exactly yaw)) zPosBall
replicate 3 Nothing === enclosed viaFusedBall (toV3 viaFused)
replicate 3 Nothing === enclosed viaComposedBall (toV3 viaComposed)
replicate 3 Nothing === consistent viaFusedBall viaComposedBall