implicit-0.4.0.0: tests/Graphics/Implicit/Test/Instances.hs
{- ORMOLU_DISABLE -}
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright (C) 2014 2015 2016, Julia Longtin (julia.longtin@gmail.com)
-- Released under the GNU AGPLV3+, see LICENSE
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE GADTs #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Graphics.Implicit.Test.Instances (Observe, observe, (=~=)) where
import Prelude (abs, fmap, Bounded, Double, Enum, Show, Ord, Eq, (==), pure, Int, Double, (.), ($), (<), div, (<*>), (<$>), (+), (<>), (<=))
import Graphics.Implicit
( square,
emptySpace,
fullSpace,
sphere,
cube,
cylinder2,
cylinder,
circle,
polygon,
extrude,
rotate3,
rotate3V,
transform3,
rotate,
transform )
import Graphics.Implicit.Definitions
( ExtrudeMScale(C1,C2,Fn),
SymbolicObj2(Shared2),
SymbolicObj3(Shared3),
ℝ,
ℝ2,
ℝ3,
SharedObj(Outset, Translate, Scale, UnionR, IntersectR,
DifferenceR, Shell, WithRounding) )
import Graphics.Implicit.Primitives ( getImplicit )
import qualified Test.QuickCheck
import Test.QuickCheck
(CoArbitrary(coarbitrary), discard, Arbitrary(arbitrary, shrink),
genericShrink,
choose,
oneof,
scale,
sized,
vectorOf,
Gen,
Positive(getPositive),
Property)
import Linear (V2(V2), V3(V3), V4(V4), Quaternion, axisAngle)
data Insidedness = Inside | Outside | Surface
deriving (Eq, Ord, Show, Enum, Bounded)
insidedness :: Double -> Insidedness
insidedness 0 = Surface
insidedness x = if x < 0 then Inside else Outside
------------------------------------------------------------------------------
instance Arbitrary SymbolicObj2 where
shrink = genericShrink
arbitrary = sized $ \n ->
if n <= 1
then oneof small
else oneof $
[ rotate <$> arbitrary <*> decayArbitrary 2
, transform <$> arbitrary <*> decayArbitrary 2
, Shared2 <$> arbitrary
] <> small
where
small =
[ circle <$> arbitrary
, square <$> arbitrary <*> arbitrary
, polygon <$> do
n <- choose (3, 10)
vectorOf n arbitrary
, pure fullSpace
, pure emptySpace
]
-- TODO(sandy): Also generate all of the extrusion variants.
instance Arbitrary SymbolicObj3 where
shrink = genericShrink
arbitrary = sized $ \n ->
if n <= 1
then oneof small
else oneof $
[ rotate3 <$> arbitrary <*> decayArbitrary 2
, rotate3V <$> arbitrary <*> arbitrary <*> decayArbitrary 2
, transform3 <$> arbitrary <*> decayArbitrary 2
, extrude <$> decayArbitrary 2 <*> arbitraryPos
, Shared3 <$> arbitrary
] <> small
where
small =
[ sphere <$> arbitraryPos
, cylinder <$> arbitraryPos <*> arbitraryPos
, cylinder2 <$> arbitraryPos <*> arbitraryPos <*> arbitraryPos
, cube <$> arbitrary <*> arbitraryV3
, pure fullSpace
, pure emptySpace
]
instance (Arbitrary obj, Arbitrary a, Arbitrary (f a), CoArbitrary (f a)) => Arbitrary (SharedObj obj f a) where
shrink = genericShrink
arbitrary = oneof
[ Translate <$> arbitrary <*> decayArbitrary 2
, Scale <$> arbitrary <*> decayArbitrary 2
, UnionR <$> arbitraryPos <*> decayedList
, IntersectR <$> arbitraryPos <*> decayedList
, DifferenceR <$> arbitraryPos <*> decayArbitrary 2 <*> decayedList
, Shell <$> arbitraryPos <*> decayArbitrary 2
, Outset <$> arbitraryPos <*> decayArbitrary 2
, WithRounding <$> arbitraryPos <*> decayArbitrary 2
]
instance Arbitrary a => Arbitrary (V2 a) where
shrink = genericShrink
arbitrary = V2 <$> arbitrary <*> arbitrary
instance Arbitrary a => Arbitrary (V3 a) where
shrink = genericShrink
arbitrary = V3 <$> arbitrary <*> arbitrary <*> arbitrary
instance Arbitrary a => Arbitrary (V4 a) where
shrink = genericShrink
arbitrary = V4 <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
instance CoArbitrary a => CoArbitrary (V2 a) where
coarbitrary (V2 a b) = coarbitrary (a, b)
instance CoArbitrary a => CoArbitrary (V3 a) where
coarbitrary (V3 a b c) = coarbitrary (a, b, c)
instance CoArbitrary a => CoArbitrary (V4 a) where
coarbitrary (V4 a b c d) = coarbitrary (a, b, c, d)
instance Arbitrary ExtrudeMScale where
shrink = genericShrink
arbitrary = oneof
[ C1 <$> arbitrary
, C2 <$> arbitrary
, Fn <$> arbitrary
]
instance Arbitrary (Quaternion ℝ) where
arbitrary = do
q <- arbitrary
v <- arbitraryV3
if v == 0.0
then discard
else pure $ axisAngle v q
------------------------------------------------------------------------------
-- Minimum of quickspec(s) Observe class and instances required for implicit testsuite
-- BSD3 Copyright: 2009-2019 Nick Smallbone
class (Arbitrary test, Ord outcome) => Observe test outcome a | a -> test outcome where
-- | Make an observation on a value. Should satisfy the following law: if
-- @x /= y@, then there exists a value of @test@ such that @observe test x /= observe test y@.
observe :: test -> a -> outcome
default observe :: (outcome ~ a) => test -> a -> outcome
observe _ x = x
instance (Arbitrary a, Observe test outcome b) => Observe (a, test) outcome (a -> b) where
observe (x, obs) f = observe obs (f x)
instance Observe () Double Double
(=~=) :: (Show test, Show outcome, Observe test outcome a) => a -> a -> Property
a =~= b = Test.QuickCheck.property $ \test -> observe test a Test.QuickCheck.=== observe test b
infix 4 =~=
------------------------------------------------------------------------------
-- | Two 'SymbolicObj2's are the same if their 'getImplicit' functions agree at
-- all points (up to an error term of 'epsilon')
instance Observe (ℝ2, ()) Insidedness SymbolicObj2 where
observe p = insidedness . observe p . getImplicit
------------------------------------------------------------------------------
-- | Two 'SymbolicObj3's are the same if their 'getImplicit' functions agree at
-- all points (up to an error term of 'epsilon')
instance Observe (ℝ3, ()) Insidedness SymbolicObj3 where
observe p = insidedness . observe p . getImplicit
-- | Generate a small list of 'Arbitrary' elements, splitting the current
-- complexity budget between all of them.
decayedList :: Arbitrary a => Gen [a]
decayedList = do
n <- choose (1, 10)
vectorOf n $ decayArbitrary $ n + 1
-- | Generate an arbitrary positive 'Double'. Useful for sizes.
arbitraryPos :: Gen Double
arbitraryPos = getPositive <$> arbitrary
-- | Generate an arbitrary positive 'ℝ3'. Useful for sizes.
arbitraryV3 :: Gen ℝ3
arbitraryV3 = fmap abs <$> arbitrary
-- | Split the complexity budget by a factor of @n@.
decayArbitrary :: Arbitrary a => Int -> Gen a
decayArbitrary n = scale (`div` n) arbitrary