{-# LANGUAGE NoImplicitPrelude #-} module Imj.Graphics.Math.Ease ( -- * 4th order /inverse/ easing, continuous {- | Easing is traditionally seen as a function from /time/ to value. Here, it is a function from /value/ to time, hence the use of the term /Inverse/ in the title. -} invQuartEaseInOut -- * From continuous to discrete {- | Easing in a continuous world is /easy/ (no pun intended), but easing in a discrete world is harder : we have to make sure the discretization will not break the visual easing effect. The 'discreteAdaptor' function does just that, making a continuous easing function usable in a discrete context. -} , discreteAdaptor -- * 4th order inverse easing, discrete -- | Using 'discreteAdaptor' on 'invQuartEaseInOut' we can make -- 'discreteInvQuartEaseInOut' : , discreteInvQuartEaseInOut ) where import Imj.Prelude -- cf. this for formatting : https://math.meta.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference {- | Returns the time \( t \in [\,0,1]\, \) at which a value \( y \in [\,0,1]\,\) is reached given a \( quartEaseInOut \): \[ y = quartEaseInOut(t) = \begin{cases} {1 \over 2} * (2*t)^4, & \;\;\;\; \text{if $t < {1 \over 2}$} \\[2ex] -{1 \over 2} * \left( [ 2*(t-1) ]^4 - 2 \right), & \;\;\;\; \text{if $t > {1 \over 2}$} \end{cases} \] To find the formulas of 'invQuartEaseInOut', we need to invert \( quartEaseInOut \), i.e. we need to express \(t\) in terms of \(y\): \[ \text{$quartEaseInOut$ is strictly increasing} \implies \begin{cases} t<{1 \over 2} \iff y<{1 \over 2} \\ t>{1 \over 2} \iff y>{1 \over 2} \end{cases} \] \[ \begin{alignedat}{3} \text{if $y < {1 \over 2} $, given the $quartEaseInOut$ equation for $t < {1 \over 2} $ :} && y &= {1 \over 2} * (2*t)^4 && \\ \implies && \quad t &= \left({y \over 2^3}\right)^{1/4} && \quad \forall y < {1 \over 2} \\ \text{if $y > {1 \over 2} $, given the $quartEaseInOut$ equation for $t > {1 \over 2} $ :} && y &= - {1 \over 2} * \left( [2*(t-1)]^4 - 2 \right) && \\ \implies && \quad t &= 1-\left[{1-y \over 2^3}\right]^{1/4} && \quad \forall y > {1 \over 2} \end{alignedat} \] /Note that there are multiple solutions, we chose the ones that produce results in the \( [\,0,1]\, \) range./ Hence, the formulas for 'invQuartEaseInOut' are : \[ t = invQuartEaseInOut(y) = \begin{cases} \left({y \over 2^3}\right)^{1/4}, & \text{if $y < {1 \over 2}$} \\[2ex] 1-\left[{1-y \over 2^3}\right]^{1/4}, & \text{if $y > {1 \over 2}$} \end{cases} \] -} invQuartEaseInOut :: Float -- ^ Value : \( y \) -> Float -- ^ Time : \( t \) invQuartEaseInOut y = if y < 0.5 then (y / 8.0) ** (1.0/4.0) else 1.0 - ((1.0 - y) / 8.0) ** (1.0/4.0) -- | Adapts continuous inout ease functions to the discrete case. discreteAdaptor :: (Float -> Float) -- ^ Continuous (optionally inverse) ease in/out function -> Int -- ^ The number of discrete steps -> Float -- ^ Input value -> Float -- ^ (optionnaly inverse) Eased value discreteAdaptor f n v = -- We use the center of the intervals instead of the extremities. let nIntervals = n intervalSize = recip $ fromIntegral nIntervals firstValue = intervalSize / 2 lastValue = 1 - firstValue scaledValue = firstValue + v * (lastValue - firstValue) in f scaledValue -- | Returns the time (in range [0 1]) at which a value (in range [0 1]) is reached -- given a 4th order ease in-out function, and a total number of discrete steps. discreteInvQuartEaseInOut :: Int -- ^ The number of discrete steps -> Float -- ^ Value -> Float -- ^ Time discreteInvQuartEaseInOut = discreteAdaptor invQuartEaseInOut