-- | -- Module: Control.Varying.Tween -- Copyright: (c) 2016 Schell Scivally -- License: MIT -- Maintainer: Schell Scivally -- -- Tweening is a technique of generating intermediate samples of a type -- __between__ a start and end value. By sampling a running tween -- each frame we get a smooth animation of a value over time. -- -- At first release `varying` is only capable of tweening numerical -- values of type @(Fractional t, Ord t) => t@ that match the type of -- time you use. At some point it would be great to be able to tween -- arbitrary types, and possibly tween one type into another (pipe -- dreams). {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE ScopedTypeVariables #-} module Control.Varying.Tween ( -- * Tweening types Easing , TweenT , Tween -- * Creating tweens -- $creation , tween , tween_ , constant , withTween , withTween_ -- * Combining tweens -- $combining -- * Interpolation functions -- $lerping , linear , easeInCirc , easeOutCirc , easeInExpo , easeOutExpo , easeInSine , easeOutSine , easeInOutSine , easeInPow , easeOutPow , easeInCubic , easeOutCubic , easeInQuad , easeOutQuad -- * Running tweens , tweenStream , runTweenT , scanTween ) where import Control.Monad (void) import Control.Monad.Trans.State (StateT, evalStateT, get, put, runStateT) import Control.Monad.Trans.Class (MonadTrans (..)) import Control.Varying.Core (VarT (..), done) import Control.Varying.Event (after) import Control.Varying.Spline (SplineT (..), mapOutput, scanSpline, untilEvent_) import Data.Bifunctor (first, second) import Data.Functor.Identity (Identity) import GHC.Generics (Generic) -- $setup -- >>> import Control.Varying.Core -------------------------------------------------------------------------------- -- | An easing function. The parameters are often named `c`, `t` and `b`, -- where `c` is the total change in value over the complete duration -- (endValue - startValue), `t` is the current percentage (0 to 1) of the -- duration that has elapsed and `b` is the start value. -- -- To make things simple only numerical values can be tweened and the type -- of time deltas must match the tween's value type. This may change in the -- future :) type Easing t f = t -> f -> t -> t -------------------------------------------------------------------------------- -- $lerping -- These pure functions take a `c` (total change in value, ie end - start), -- `t` (percent of duration completion) and `b` (start value) and result in -- an interpolation of a value. To see what these look like please check -- out http://www.gizma.com/easing/. -------------------------------------------------------------------------------- -- | Ease in quadratic. easeInQuad :: (Fractional t, Real f) => Easing t f easeInQuad c t b = c * realToFrac (t*t) + b -- | Ease out quadratic. easeOutQuad :: (Fractional t, Real f) => Easing t f easeOutQuad c t b = (-c) * realToFrac (t * (t - 2)) + b -- | Ease in cubic. easeInCubic :: (Fractional t, Real f) => Easing t f easeInCubic c t b = c * realToFrac (t*t*t) + b -- | Ease out cubic. easeOutCubic :: (Fractional t, Real f) => Easing t f easeOutCubic c t b = let t' = realToFrac t - 1 in c * (t'*t'*t' + 1) + b -- | Ease in by some power. easeInPow :: (Fractional t, Real f) => Int -> Easing t f easeInPow power c t b = c * (realToFrac t^power) + b -- | Ease out by some power. easeOutPow :: (Fractional t, Real f) => Int -> Easing t f easeOutPow power c t b = let t' = realToFrac t - 1 c' = if power `mod` 2 == 1 then c else -c i = if power `mod` 2 == 1 then 1 else -1 in c' * ((t'^power) + i) + b -- | Ease in sinusoidal. easeInSine :: (Floating t, Real f) => Easing t f easeInSine c t b = let cos' = cos (realToFrac t * (pi / 2)) in -c * cos' + c + b -- | Ease out sinusoidal. easeOutSine :: (Floating t, Real f) => Easing t f easeOutSine c t b = let cos' = cos (realToFrac t * (pi / 2)) in c * cos' + b -- | Ease in and out sinusoidal. easeInOutSine :: (Floating t, Real f) => Easing t f easeInOutSine c t b = let cos' = cos (pi * realToFrac t) in (-c / 2) * (cos' - 1) + b -- | Ease in exponential. easeInExpo :: (Floating t, Real f) => Easing t f easeInExpo c t b = let e = 10 * (realToFrac t - 1) in c * (2**e) + b -- | Ease out exponential. easeOutExpo :: (Floating t, Real f) => Easing t f easeOutExpo c t b = let e = -10 * realToFrac t in c * (-(2**e) + 1) + b -- | Ease in circular. easeInCirc :: (Floating t, Real f, Floating f) => Easing t f easeInCirc c t b = let s = realToFrac $ sqrt (1 - t*t) in -c * (s - 1) + b -- | Ease out circular. easeOutCirc :: (Floating t, Real f) => Easing t f easeOutCirc c t b = let t' = (realToFrac t - 1) s = sqrt (1 - t'*t') in c * s + b -- | Ease linear. linear :: (Floating t, Real f) => Easing t f linear c t b = c * realToFrac t + b -- | A 'TweenT' is a 'SplineT' that holds a duration in local state. This allows -- 'TweenT's to be sequenced monadically. -- -- * 'f' is the input time delta type (the input type) -- * 't' is the start and end value type (the output type) -- * 'a' is the result value type -- -- You can sequence 'TweenT's with monadic notation to produce more complex ones. -- This is especially useful for animation: -- -- >>> :{ -- let -- tweenInOutExpo -- :: ( Monad m, Floating t, Real t, Real f, Fractional f ) -- => t -- -> t -- -> f -- -> TweenT f t m t -- tweenInOutExpo start end dur = do -- x <- tween easeInExpo start (end/2) (dur/2) -- tween easeOutExpo x end $ dur/2 -- >>> :} newtype TweenT f t m a = TweenT { unTweenT :: SplineT f t (StateT f m) a } deriving (Generic, Functor, Applicative, Monad) instance MonadTrans (TweenT f t) where lift = TweenT . lift . lift type Tween f t a = TweenT f t Identity a runTweenT :: Functor m => TweenT f t m a -> f -- ^ The input time delta this frame -> f -- ^ The leftover time delta from last frame -> m (Either a (t, TweenT f t m a), f) -- ^ Returns -- @ -- a tuple of -- either -- the result -- or a tuple of -- this step's output value -- and the tween for the next step -- and the leftover time delta for the next step -- @ runTweenT (TweenT s) dt leftover = first (second $ second TweenT) <$> runStateT (runSplineT s dt) leftover scanTween :: (Monad m, Num f) => TweenT f t m a -> t -> [f] -> m [t] scanTween (TweenT s) t dts = evalStateT (scanSpline s t dts) 0 -- | Converts a tween into a continuous value stream. This is the tween version -- of 'Control.Varying.Spline.outputStream'. This is the preferred way to run -- your tweens. -- -- >>> :{ -- let -- x :: TweenT Float Float IO Float -- x = tween linear 0 1 1 -- y :: TweenT Float Float IO Float -- y = tween linear 0 1 2 -- v :: VarT IO Float (Float, Float) -- v = (,) -- <$> tweenStream x 0 -- <*> tweenStream y 0 -- in -- testVarOver v [0.5, 0.5, 0.5, 0.5] -- >>> :} -- (0.5,0.25) -- (1.0,0.5) -- (1.0,0.75) -- (1.0,1.0) tweenStream :: forall m f t x . (Functor m, Monad m, Num f) => TweenT f t m x -- ^ The tween to convert into a stream -> t -- ^ An initial output value -> VarT m f t tweenStream s0 t0 = VarT $ go s0 t0 0 where go :: TweenT f t m x -- The Tween -> t -- the last output value -> f -- the leftover time delta from last fram -> f -- the input time delta -> m (t, VarT m f t) go s t l i = do (e, l1) <- runTweenT s i l case e of Left _ -> return (t, done t) Right (b, s1) -> return (b, VarT $ go s1 b l1) -------------------------------------------------------------------------------- -- $creation -- The most direct route toward tweening values is to use 'tween' -- along with an interpolation function such as 'easeInExpo'. For example, -- @tween easeInExpo 0 100 10@, this will create a spline that produces a -- number interpolated from 0 to 100 over 10 seconds. At the end of the -- tween the spline will return the result value. -------------------------------------------------------------------------------- -- | Creates a spline that produces a value interpolated between a start and -- end value using an easing equation ('Easing') over a duration. The -- resulting spline will take a time delta as input. -- Keep in mind that `tween` must be fed time deltas, not absolute time or -- duration. This is mentioned because the author has made that mistake -- more than once ;) -- -- `tween` concludes returning the latest output value. tween :: (Monad m, Real t, Real f, Fractional f) => Easing t f -> t -> t -> f -> TweenT f t m t tween f start end dur = TweenT $ SplineT g where c = end - start b = start g dt = do leftover <- get let t = dt + leftover if t == dur then put 0 >> return (Right (end, return end)) else if t > dur then put (t - dur - dt) >> return (Left end) else put t >> return (Right (f c (t/dur) b, SplineT g)) -- | A version of 'tween' that discards the result. It is simply -- -- @ -- tween f a b c >> return () -- @ -- tween_ :: (Monad m, Real t, Real f, Fractional f) => Easing t f -> t -> t -> f -> TweenT f t m () tween_ f a b c = Control.Monad.void (tween f a b c) -- | A version of 'tween' that maps its output using the given constant -- function. -- -- @ -- withTween ease from to dur f = mapOutput (pure f) $ tween ease from to dur -- @ withTween :: (Monad m, Real t, Real a, Fractional a) => Easing t a -> t -> t -> a -> (t -> x) -> TweenT a x m t withTween ease from to dur f = TweenT $ mapOutput (pure f) $ unTweenT $ tween ease from to dur -- | A version of 'withTween' that discards its result. withTween_ :: (Monad m, Real t, Real a, Fractional a) => Easing t a -> t -> t -> a -> (t -> x) -> TweenT a x m () withTween_ ease from to dur f = Control.Monad.void (withTween ease from to dur f) -- | Creates a tween that performs no interpolation over the duration. constant :: (Monad m, Num t, Ord t) => a -> t -> TweenT t a m a constant value duration = TweenT $ pure value `untilEvent_` after duration