roshask-0.2: Examples/Turtle/src/AngleNum.hs
{-# LANGUAGE TypeFamilies, GeneralizedNewtypeDeriving #-}
-- |Define a lightweight wrapper, 'Angle', for floating point numeric
-- types that comes with a 'Num' instance that wraps around at
-- 0 and 2π, and properly handles angle differencing.
module AngleNum (Angle, angle, fromAngle, toDegrees) where
import Data.Function
import Data.VectorSpace
wrapAngle :: (Floating a, Ord a) => a -> a
wrapAngle theta
| theta < 0 = theta + 2 * pi
| theta > 2*pi = theta - 2 * pi
| otherwise = theta
angleDiff :: (Floating a, Ord a) => a -> a -> a
angleDiff x y = wrapAngle (x' + pi - y') - pi
where x' = if x < 0 then x + 2*pi else x
y' = if y < 0 then y + 2*pi else y
-- |Representation of an angle in radians.
newtype Angle a = Angle { fromAngle :: a }
deriving (Eq, Ord, Show, Fractional, Floating)
-- |Produce an 'Angle' value within the range [0,2pi].
angle :: (Floating a, Ord a) => a -> Angle a
angle = Angle . wrapAngle
-- |Convert an 'Angle' into a floating point number of degrees.
toDegrees :: Floating a => Angle a -> a
toDegrees = (* (180 / pi)) . fromAngle
instance (Floating a, Ord a) => Num (Angle a) where
Angle x + Angle y = Angle . wrapAngle $ x + y
(*) = ((Angle . wrapAngle) .) . (*) `on` fromAngle
Angle x - Angle y = Angle $ angleDiff x y
negate (Angle x) = Angle (negate x)
abs (Angle x) = Angle (abs x)
signum (Angle x) = Angle (signum x)
fromInteger = Angle . wrapAngle . fromInteger
instance (Floating a, Ord a, AdditiveGroup a) => AdditiveGroup (Angle a) where
zeroV = Angle 0
(^+^) = ((Angle . wrapAngle) . ) . (^+^) `on` fromAngle
negateV = negate
instance (Floating a, Ord a, AdditiveGroup a) => VectorSpace (Angle a) where
type Scalar (Angle a) = a
s *^ Angle x = Angle . wrapAngle $ s * x