defmodule Easing do @moduledoc """ Easing function calculations Cribbed from: https://easings.net/ """ alias Easing.Range @type easing_tuple :: {atom(), atom()} @type range :: %Easing.Range{first: number(), last: number(), step: number()} @type easing_function :: function() @type easing_function_or_tuple :: easing_function() | easing_tuple() @type easings :: [float()] @spec to_list(range(), easing_function_or_tuple()) :: easings() @doc """ Generates a list of animation frame values. A `Range` is used for the `range` argument. See the example ## Examples: iex> Easing.to_list(%Easing.Range{first: 0, last: 1, step: 0.1}, &Easing.sine_in(&1)) [0.0, 0.01231165940486223, 0.04894348370484647, 0.10899347581163221, 0.19098300562505255, 0.2928932188134524, 0.41221474770752675, 0.5460095002604533, 0.6909830056250525, 0.8435655349597688, 1.0] iex> Easing.to_list(%Easing.Range{first: 0, last: 0.5, step: 0.1}, {:bounce, :in_out}) [0.0, 0.030000000000000027, 0.11375000000000002, 0.04499999999999993, 0.3487500000000001, 0.5] """ def to_list(%Range{} = range, easing) do range |> stream(easing) |> Enum.to_list() end @spec stream(range(), easing_function_or_tuple()) :: Enumerable.t() @doc """ Generates a stream of animation frame values. A `Range` is used for the `range` argument. See the example ## Examples: iex> Easing.stream(%Easing.Range{first: 0, last: 1, step: 0.1}, &Easing.sine_in(&1)) |> Enum.to_list() [0.0, 0.01231165940486223, 0.04894348370484647, 0.10899347581163221, 0.19098300562505255, 0.2928932188134524, 0.41221474770752675, 0.5460095002604533, 0.6909830056250525, 0.8435655349597688, 1.0] iex> Easing.stream(%Easing.Range{first: 0, last: 0.5, step: 0.1}, {:bounce, :in_out}) |> Enum.to_list() [0.0, 0.030000000000000027, 0.11375000000000002, 0.04499999999999993, 0.3487500000000001, 0.5] """ def stream(%Range{} = range, easing_function) when is_function(easing_function) do Stream.map(range, &easing_function.(&1)) end def stream(%Range{} = range, easing_tuple) when is_tuple(easing_tuple) do Stream.map(range, &run(easing_tuple, &1)) end @spec linear_in(float()) :: float() @doc """ Linear in easing function ## Example iex> Easing.linear_in(0.1) 0.1 """ def linear_in(progress), do: run({:linear, :in}, progress) @spec linear_out(float()) ::float() @doc """ Linear out easing function ## Example iex> Easing.linear_out(0.1) 0.1 """ def linear_out(progress), do: run({:linear, :out}, progress) @spec linear_in_out(float()) :: float() @doc """ Linear in-out easing function ## Example iex> Easing.linear_in_out(0.1) 0.1 """ def linear_in_out(progress), do: run({:linear, :in_out}, progress) @spec sine_in(float()) :: float() @doc """ Sine in easing function ![Sine in easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/sine/in.png "Sine in easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.sine_in(0.1) 0.01231165940486223 """ def sine_in(progress), do: run({:sine, :in}, progress) @spec sine_out(float()) :: float() @doc """ Sine out easing function ![Sine out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/sine/out.png "Sine out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.sine_out(0.1) 0.15643446504023087 """ def sine_out(progress), do: run({:sine, :out}, progress) @spec sine_in_out(float()) :: float() @doc """ Sine in-out easing function ![Sine in-out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/sine/in_out.png "Sine in-out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.sine_in_out(0.1) 0.024471741852423234 """ def sine_in_out(progress), do: run({:sine, :in_out}, progress) @spec quadratic_in(float()) :: float() @doc """ Quadratic in easing function ![Quadratic in easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/quadratic/in.png "Quadratic in easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.quadratic_in(0.1) 0.010000000000000002 """ def quadratic_in(progress), do: run({:quadratic, :in}, progress) @spec quadratic_out(float()) :: float() @doc """ Quadratic out easing function ![Quadratic out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/quadratic/out.png "Quadratic out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.quadratic_out(0.1) 0.18999999999999995 """ def quadratic_out(progress), do: run({:quadratic, :out}, progress) @spec quadratic_in_out(float()) :: float() @doc """ Quadratic in-out easing function ![Quadratic in-out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/quadratic/in_out.png "Quadratic in-out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.quadratic_in_out(0.1) 0.020000000000000004 """ def quadratic_in_out(progress), do: run({:quadratic, :in_out}, progress) @spec cubic_in(float()) :: float() @doc """ Cubic in easing function ![Cubic in easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/cubic/in.png "Cubic in easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.cubic_in(0.1) 0.0010000000000000002 """ def cubic_in(progress), do: run({:cubic, :in}, progress) @spec cubic_out(float()) :: float() @doc """ Cubic out easing function ![Cubic out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/cubic/out.png "Cubic out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.cubic_out(0.1) 0.2709999999999999 """ def cubic_out(progress), do: run({:cubic, :out}, progress) @spec cubic_in_out(float()) :: float() @doc """ Cubic in-out easing function ![Cubic in-out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/cubic/in_out.png "Cubic in-out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.cubic_in_out(0.1) 0.004000000000000001 """ def cubic_in_out(progress), do: run({:cubic, :in_out}, progress) @spec quartic_in(float()) :: float() @doc """ Quartic in easing function ![Quartic in easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/quartic/in.png "Quartic in easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.quartic_in(0.1) 1.0000000000000002e-4 """ def quartic_in(progress), do: run({:quartic, :in}, progress) @spec quartic_out(float()) :: float() @doc """ Quartic out easing function ![Quartic out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/quartic/out.png "Quartic out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.quartic_out(0.1) 0.3439 """ def quartic_out(progress), do: run({:quartic, :out}, progress) @spec quartic_in_out(float()) :: float() @doc """ Quartic in-out easing function ![Quartic in-out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/quartic/in_out.png "Quartic in-out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.quartic_in_out(0.1) 8.000000000000001e-4 """ def quartic_in_out(progress), do: run({:quartic, :in_out}, progress) @spec quintic_in(float()) :: float() @doc """ Quintic in easing function ![Quintic in easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/quintic/in.png "Quintic in easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.quintic_in(0.1) 1.0000000000000003e-5 """ def quintic_in(progress), do: run({:quintic, :in}, progress) @spec quintic_out(float()) :: float() @doc """ Quintic out easing function ![Quintic out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/quintic/out.png "Quintic out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.quintic_out(0.1) 0.40950999999999993 """ def quintic_out(progress), do: run({:quintic, :out}, progress) @spec quintic_in_out(float()) :: float() @doc """ Quintic in-out easing function ![Quintic in-out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/quintic/in_out.png "Quintic in-out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.quintic_in_out(0.1) 1.6000000000000004e-4 """ def quintic_in_out(progress), do: run({:quintic, :in_out}, progress) @spec exponential_in(float()) :: float() @doc """ Exponential in easing function ![Exponential in easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/exponential/in.png "Expoenential in easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.exponential_in(0.1) 0.001953125 """ def exponential_in(progress), do: run({:exponential, :in}, progress) @spec exponential_out(float()) :: float() @doc """ Exponential out easing function ![Exponential out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/exponential/out.png "Expoenential out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.exponential_out(0.1) 0.5 """ def exponential_out(progress), do: run({:exponential, :out}, progress) @spec exponential_in_out(float()) :: float() @doc """ Exponential in-out easing function ![Exponential in-out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/exponential/in_out.png "Expoenential in-out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.exponential_in_out(0.1) 0.001953125 """ def exponential_in_out(progress), do: run({:exponential, :in_out}, progress) @spec circular_in(float()) :: float() @doc """ Circular in easing function ![Circular in easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/circular/in.png "Circular in easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.circular_in(0.1) 0.005012562893380035 """ def circular_in(progress), do: run({:circular, :in}, progress) @spec circular_out(float()) :: float() @doc """ Circular out easing function ![Circular out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/circular/out.png "Circular out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.circular_out(0.1) 0.4358898943540673 """ def circular_out(progress), do: run({:circular, :out}, progress) @spec circular_in_out(float()) :: float() @doc """ Circular in-out easing function ![Circular in-out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/circular/in_out.png "Circular in-out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.circular_in_out(0.1) 0.010102051443364402 """ def circular_in_out(progress), do: run({:circular, :in_out}, progress) @spec back_in(float()) :: float() @doc """ Back in easing function ![Back in easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/back/in.png "Back in easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.back_in(0.1) -0.014314220000000004 """ def back_in(progress), do: run({:back, :in}, progress) @spec back_out(float()) :: float() @doc """ Back out easing function ![Back out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/back/out.png "Back out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.back_out(0.1) 0.40882797999999987 """ def back_out(progress), do: run({:back, :out}, progress) @spec back_in_out(float()) :: float() @doc """ Back in-out easing function ![Back in-out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/back/in_out.png "Back in-out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.back_in_out(0.1) -0.037518552000000004 """ def back_in_out(progress), do: run({:back, :in_out}, progress) @spec elastic_in(float()) :: float() @doc """ Elastic in easing function ![Elastic in easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/elastic/in.png "Elastic in easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.elastic_in(0.1) 0.001953125 """ def elastic_in(progress), do: run({:elastic, :in}, progress) @spec elastic_out(float()) :: float() @doc """ Elastic out easing function ![Elastic out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/elastic/out.png "Elastic out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.elastic_out(0.1) 1.25 """ def elastic_out(progress), do: run({:elastic, :out}, progress) @spec elastic_in_out(float()) :: float() @doc """ Elastic in-out easing function ![Elastic in-out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/elastic/in_out.png "Elastic in-out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.elastic_in_out(0.1) 3.39156597005722e-4 """ def elastic_in_out(progress), do: run({:elastic, :in_out}, progress) @spec bounce_in(float()) :: float() @doc """ Bounce in easing function ![Bounce in easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/bounce/in.png "Bounce in easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.bounce_in(0.1) 0.01187500000000008 """ def bounce_in(progress), do: run({:bounce, :in}, progress) @spec bounce_out(float()) :: float() @doc """ Bounce out easing function ![Bounce out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/bounce/out.png "Bounce out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.bounce_out(0.1) 0.07562500000000001 """ def bounce_out(progress), do: run({:bounce, :out}, progress) @spec bounce_in_out(float()) :: float() @doc """ Bounce in-out easing function ![Bounce in-out easing visualizations created by Andrey Sitnik and Ivan Solovev](assets/images/bounce/in_out.png "Bounce in-out easing visulizations created by Andrey Sitnik and Ivan Solovev") ## Example iex> Easing.bounce_in_out(0.1) 0.030000000000000027 """ def bounce_in_out(progress), do: run({:bounce, :in_out}, progress) @spec run(easing_function_or_tuple(), float()) :: float() @doc """ Easing calculation. Take a tupal of atoms `{direction, type}` and the progress is a value between 0 - 1 that represents the animation progress. (0 = beginning, 1 = end) * directions: `:in`, `:out`, and `:in_out` * types: `:sine`, `:quadratic`, `:cubic`, `:quartic`, `:quintic`, :`exponential`, `:circular`, `:back`, `:elastic`, `:bounce` * progress: value between `0` and `1` that represents the % of the animation state. * options: keyword list - round: `true` - will round the result up with a precision of 2 """ def run(easing_function, progress) do cond do progress == 0 -> 0.0 progress == 1 -> 1.0 true -> do_run(easing_function, progress) end end # Linear defp do_run({:linear, _direction}, progress), do: progress # Sine defp do_run({:sine, :in}, progress) do 1 - :math.cos((progress * :math.pi()) / 2) end defp do_run({:sine, :out}, progress) do :math.sin((progress * :math.pi()) / 2) end defp do_run({:sine, :in_out}, progress) do -1 * (:math.cos(:math.pi() * progress) - 1) / 2 end # Quadratic defp do_run({:quadratic, :in}, progress) do :math.pow(progress, 2) end defp do_run({:quadratic, :out}, progress) do 1 - (1 - progress) * (1 - progress) end defp do_run({:quadratic, :in_out}, progress) do if progress < 0.5 do 2 * :math.pow(progress, 2) else 1 - :math.pow(-2 * progress + 2, 2) / 2 end end # Cubic defp do_run({:cubic, :in}, progress) do :math.pow(progress, 3) end defp do_run({:cubic, :out}, progress) do 1 - :math.pow(1 - progress, 3) end defp do_run({:cubic, :in_out}, progress) do if progress < 0.5 do 4 * :math.pow(progress, 3) else 1 - :math.pow(-2 * progress + 2, 3) / 2 end end # Quartic defp do_run({:quartic, :in}, progress) do :math.pow(progress, 4) end defp do_run({:quartic, :out}, progress) do 1 - :math.pow(1 - progress, 4) end defp do_run({:quartic, :in_out}, progress) do if progress < 0.5 do 8 * :math.pow(progress, 4) else 1 - :math.pow(-2 * progress + 2, 4) / 2 end end # Quintic defp do_run({:quintic, :in}, progress) do :math.pow(progress, 5) end defp do_run({:quintic, :out}, progress) do 1 - :math.pow(1 - progress, 5) end defp do_run({:quintic, :in_out}, progress) do if progress < 0.5 do 16 * :math.pow(progress, 5) else 1 - :math.pow(-2 * progress + 2, 5) / 2 end end # Exponential defp do_run({:exponential, :in}, progress) do :math.pow(2, 10 * progress - 10) end defp do_run({:exponential, :out}, progress) do 1 - :math.pow(2, -10 * progress) end defp do_run({:exponential, :in_out}, progress) do cond do progress < 0.5 -> :math.pow(2, 20 * progress - 10) / 2 true -> (2 - :math.pow(2, -20 * progress + 10)) / 2 end end # Circular defp do_run({:circular, :in}, progress) do 1 - :math.sqrt(1 - :math.pow(progress, 2)) end defp do_run({:circular, :out}, progress) do :math.sqrt(1 - :math.pow(progress - 1, 2)) end defp do_run({:circular, :in_out}, progress) do if progress < 0.5 do (1 - :math.sqrt(1 - :math.pow(2 * progress, 2))) / 2 else (:math.sqrt(1 - :math.pow(-2 * progress + 2, 2)) + 1) / 2 end end # Back defp do_run({:back, :in}, progress) do c1 = 1.70158 c3 = c1 + 1 c3 * :math.pow(progress, 3) - c1 * :math.pow(progress, 2) end defp do_run({:back, :out}, progress) do c1 = 1.70158 c3 = c1 + 1 1 + c3 * :math.pow(progress - 1, 3) + c1 * :math.pow(progress - 1, 2) end defp do_run({:back, :in_out}, progress) do c1 = 1.70158 c2 = c1 * 1.525 if progress < 0.5 do (:math.pow(2 * progress, 2) * ((c2 + 1) * 2 * progress - c2)) /2 else (:math.pow(2 * progress - 2, 2) * ((c2 + 1) * (progress * 2 - 2) + c2) + 2) / 2 end end # Elastic defp do_run({:elastic, :in}, progress) do c4 = (2 * :math.pi()) / 3 -1 * :math.pow(2, 10 * progress - 10) * :math.sin((progress * 10 - 10.75) * c4) end defp do_run({:elastic, :out}, progress) do c4 = (2 * :math.pi()) / 3; :math.pow(2, -10 * progress) * :math.sin((progress * 10 - 0.75) * c4) + 1 end defp do_run({:elastic, :in_out}, progress) do c5 = (2 * :math.pi()) / 4.5 cond do progress < 0.5 -> -1 * (:math.pow(2, 20 * progress - 10) * :math.sin((20 * progress - 11.125) * c5)) / 2 true -> (:math.pow(2, -20 * progress + 10) * :math.sin((20 * progress - 11.125) * c5)) / 2 + 1 end end # Bounce defp do_run({:bounce, :in}, progress) do 1.0 - run({:bounce, :out}, 1.0 - progress) end defp do_run({:bounce, :out}, progress) do n1 = 7.5625 d1 = 2.75 cond do progress < 1 / d1 -> n1 * :math.pow(progress, 2) progress < 2 / d1 -> p1 = progress - 1.5 / d1 n1 * :math.pow(p1, 2) + 0.75 progress < 2.5 / d1 -> p2 = progress - 2.25 / d1 n1 * :math.pow(p2, 2) + 0.9375 true -> p3 = progress - 2.625 / d1 n1 * :math.pow(p3, 2) + 0.984375 end end defp do_run({:bounce, :in_out}, progress) do if progress < 0.5 do (1 - run({:bounce, :out}, 1 - 2 * progress)) / 2 else (1 + run({:bounce, :out}, 2 * progress - 1)) / 2 end end end