3b1b-manim/manimlib/utils/rate_functions.py

110 lines
2.6 KiB
Python
Raw Normal View History

2022-04-12 20:03:48 +08:00
from __future__ import annotations
import numpy as np
from manimlib.utils.bezier import bezier
2022-04-12 20:03:48 +08:00
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable
def linear(t: float) -> float:
2018-05-08 16:19:42 +02:00
return t
2018-05-30 12:02:35 -07:00
def smooth(t: float) -> float:
2020-07-22 18:19:07 -07:00
# Zero first and second derivatives at t=0 and t=1.
# Equivalent to bezier([0, 0, 0, 1, 1, 1])
s = 1 - t
return (t**3) * (10 * s * s + 5 * s * t + t * t)
def rush_into(t: float) -> float:
2020-07-22 18:19:07 -07:00
return 2 * smooth(0.5 * t)
def rush_from(t: float) -> float:
2020-07-22 18:19:07 -07:00
return 2 * smooth(0.5 * (t + 1)) - 1
def slow_into(t: float) -> float:
return np.sqrt(1 - (1 - t) * (1 - t))
def double_smooth(t: float) -> float:
if t < 0.5:
return 0.5 * smooth(2 * t)
else:
return 0.5 * (1 + smooth(2 * t - 1))
def there_and_back(t: float) -> float:
new_t = 2 * t if t < 0.5 else 2 * (1 - t)
2020-07-22 18:19:07 -07:00
return smooth(new_t)
def there_and_back_with_pause(t: float, pause_ratio: float = 1. / 3) -> float:
2018-05-02 08:17:34 -07:00
a = 1. / pause_ratio
if t < 0.5 - pause_ratio / 2:
return smooth(a * t)
elif t < 0.5 + pause_ratio / 2:
return 1
else:
2018-05-02 08:17:34 -07:00
return smooth(a - a * t)
def running_start(t: float, pull_factor: float = -0.5) -> float:
return bezier([0, 0, pull_factor, pull_factor, 1, 1, 1])(t)
2022-11-18 09:12:52 -08:00
def overshoot(t: float, pull_factor: float = 1.5) -> float:
return bezier([0, 0, pull_factor, pull_factor, 1, 1])(t)
def not_quite_there(
func: Callable[[float], float] = smooth,
proportion: float = 0.7
) -> Callable[[float], float]:
def result(t):
return proportion * func(t)
return result
def wiggle(t: float, wiggles: float = 2) -> float:
return there_and_back(t) * np.sin(wiggles * np.pi * t)
def squish_rate_func(
func: Callable[[float], float],
a: float = 0.4,
b: float = 0.6
) -> Callable[[float], float]:
def result(t):
if a == b:
return a
2020-07-22 18:19:07 -07:00
elif t < a:
return func(0)
elif t > b:
return func(1)
else:
return func((t - a) / (b - a))
return result
# Stylistically, should this take parameters (with default values)?
# Ultimately, the functionality is entirely subsumed by squish_rate_func,
# but it may be useful to have a nice name for with nice default params for
# "lingering", different from squish_rate_func's default params
def lingering(t: float) -> float:
return squish_rate_func(lambda t: t, 0, 0.8)(t)
2018-05-10 00:00:15 +02:00
def exponential_decay(t: float, half_life: float = 0.1) -> float:
2018-07-14 10:31:56 -07:00
# The half-life should be rather small to minimize
# the cut-off error at the end
return 1 - np.exp(-t / half_life)