varying-0.8.0.0: src/Control/Varying/Tween.hs
-- |
-- Module: Control.Varying.Tween
-- Copyright: (c) 2016 Schell Scivally
-- License: MIT
-- Maintainer: Schell Scivally <schell@takt.com>
--
-- 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