use crate::animation::easings::{Easing, IEasing, SharedEasing};
use crate::animation::KeySplineSolver;
use crate::numerics::Vector2;
use crate::rendering::composition::Compositor;
use std::cell::Cell;
use std::f64::consts::PI;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum CompositionEasingFunctionMode {
#[default]
In,
Out,
InOut,
}
fn ease_with_mode(mode: CompositionEasingFunctionMode, t: f64, ease_in: impl Fn(f64) -> f64) -> f64 {
match mode {
CompositionEasingFunctionMode::In => ease_in(t),
CompositionEasingFunctionMode::Out => 1.0 - ease_in(1.0 - t),
CompositionEasingFunctionMode::InOut => {
if t < 0.5 {
ease_in(t * 2.0) * 0.5
} else {
(1.0 - ease_in((1.0 - t) * 2.0)) * 0.5 + 0.5
}
}
}
}
macro_rules! value_easing {
($ty:ty) => {
impl IEasing for $ty {
fn to_shared(&self) -> Arc<SharedEasing> {
Arc::new(*self)
}
fn ease(&self, progress: f64) -> f64 {
self.ease_core(progress)
}
}
easing_conversions!($ty);
};
}
macro_rules! easing_conversions {
($ty:ty) => {
impl From<$ty> for Easing {
fn from(value: $ty) -> Easing {
Easing::new(value)
}
}
impl From<Rc<$ty>> for Easing {
fn from(value: Rc<$ty>) -> Easing {
Easing::from_rc(value)
}
}
};
}
pub struct CubicBezierEasingFunction {
control_point1: Vector2,
control_point2: Vector2,
solver: Mutex<KeySplineSolver>,
}
impl CubicBezierEasingFunction {
pub fn new(control_point1: Vector2, control_point2: Vector2) -> CubicBezierEasingFunction {
for x in [control_point1.x, control_point2.x] {
if !(0.0..=1.0).contains(&x) {
panic!("The X coordinate of a control point must be between 0 and 1");
}
}
CubicBezierEasingFunction {
control_point1,
control_point2,
solver: Mutex::new(KeySplineSolver::new(
control_point1.x as f64,
control_point1.y as f64,
control_point2.x as f64,
control_point2.y as f64,
)),
}
}
pub fn control_point1(&self) -> Vector2 {
self.control_point1
}
pub fn control_point2(&self) -> Vector2 {
self.control_point2
}
}
impl IEasing for CubicBezierEasingFunction {
fn to_shared(&self) -> Arc<SharedEasing> {
Arc::new(CubicBezierEasingFunction::new(self.control_point1, self.control_point2))
}
fn ease(&self, progress: f64) -> f64 {
self.solver.lock().unwrap_or_else(|e| e.into_inner()).get_spline_progress(progress)
}
}
easing_conversions!(CubicBezierEasingFunction);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct LinearEasingFunction;
impl LinearEasingFunction {
pub fn new() -> LinearEasingFunction {
LinearEasingFunction
}
fn ease_core(&self, progress: f64) -> f64 {
progress
}
}
value_easing!(LinearEasingFunction);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct StepParameters {
step_count: i32,
initial_step: i32,
final_step: i32,
is_initial_step_single_frame: bool,
is_final_step_single_frame: bool,
}
impl StepParameters {
fn ease_core(&self, t: f64) -> f64 {
let count = self.step_count.max(1);
let initial = self.initial_step.clamp(0, count);
let final_step = self.final_step.clamp(initial, count);
let step = if t <= 0.0 {
initial
} else if t >= 1.0 {
final_step
} else {
let first = initial + i32::from(self.is_initial_step_single_frame);
let last = final_step - i32::from(self.is_final_step_single_frame);
if last < first {
first.min(final_step)
} else {
let steps = (last - first + 1) as f64;
(first + (t * steps).floor() as i32).min(last)
}
};
step as f64 / count as f64
}
}
impl IEasing for StepParameters {
fn to_shared(&self) -> Arc<SharedEasing> {
Arc::new(*self)
}
fn ease(&self, progress: f64) -> f64 {
self.ease_core(progress)
}
}
pub struct StepEasingFunction {
step_count: Cell<i32>,
initial_step: Cell<i32>,
final_step: Cell<Option<i32>>,
is_initial_step_single_frame: Cell<bool>,
is_final_step_single_frame: Cell<bool>,
}
impl StepEasingFunction {
pub fn new() -> StepEasingFunction {
StepEasingFunction::with_step_count(1)
}
pub fn with_step_count(step_count: i32) -> StepEasingFunction {
StepEasingFunction {
step_count: Cell::new(step_count),
initial_step: Cell::new(0),
final_step: Cell::new(None),
is_initial_step_single_frame: Cell::new(false),
is_final_step_single_frame: Cell::new(false),
}
}
pub fn step_count(&self) -> i32 {
self.step_count.get()
}
pub fn set_step_count(&self, value: i32) {
self.step_count.set(value)
}
pub fn initial_step(&self) -> i32 {
self.initial_step.get()
}
pub fn set_initial_step(&self, value: i32) {
self.initial_step.set(value)
}
pub fn final_step(&self) -> i32 {
self.final_step.get().unwrap_or(self.step_count.get())
}
pub fn set_final_step(&self, value: i32) {
self.final_step.set(Some(value))
}
pub fn is_initial_step_single_frame(&self) -> bool {
self.is_initial_step_single_frame.get()
}
pub fn set_is_initial_step_single_frame(&self, value: bool) {
self.is_initial_step_single_frame.set(value)
}
pub fn is_final_step_single_frame(&self) -> bool {
self.is_final_step_single_frame.get()
}
pub fn set_is_final_step_single_frame(&self, value: bool) {
self.is_final_step_single_frame.set(value)
}
fn parameters(&self) -> StepParameters {
StepParameters {
step_count: self.step_count(),
initial_step: self.initial_step(),
final_step: self.final_step(),
is_initial_step_single_frame: self.is_initial_step_single_frame(),
is_final_step_single_frame: self.is_final_step_single_frame(),
}
}
}
impl Default for StepEasingFunction {
fn default() -> Self {
StepEasingFunction::new()
}
}
impl IEasing for StepEasingFunction {
fn to_shared(&self) -> Arc<SharedEasing> {
Arc::new(self.parameters())
}
fn ease(&self, progress: f64) -> f64 {
self.parameters().ease_core(progress)
}
}
easing_conversions!(StepEasingFunction);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BackEasingFunction {
mode: CompositionEasingFunctionMode,
amplitude: f32,
}
impl BackEasingFunction {
pub fn new(mode: CompositionEasingFunctionMode, amplitude: f32) -> BackEasingFunction {
BackEasingFunction { mode, amplitude }
}
pub fn mode(&self) -> CompositionEasingFunctionMode {
self.mode
}
pub fn amplitude(&self) -> f32 {
self.amplitude
}
fn ease_core(&self, t: f64) -> f64 {
let amplitude = (self.amplitude as f64).max(0.0);
ease_with_mode(self.mode, t, |t| t * t * t - t * amplitude * (PI * t).sin())
}
}
value_easing!(BackEasingFunction);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BounceEasingFunction {
mode: CompositionEasingFunctionMode,
bounces: i32,
bounciness: f32,
}
impl BounceEasingFunction {
pub fn new(mode: CompositionEasingFunctionMode, bounces: i32, bounciness: f32) -> BounceEasingFunction {
BounceEasingFunction { mode, bounces, bounciness }
}
pub fn mode(&self) -> CompositionEasingFunctionMode {
self.mode
}
pub fn bounces(&self) -> i32 {
self.bounces
}
pub fn bounciness(&self) -> f32 {
self.bounciness
}
fn ease_in(t: f64, bounces: i32, bounciness: f32) -> f64 {
let bounces = (bounces as f64).max(0.0);
let mut bounciness = bounciness as f64;
if bounciness < 1.0 || (bounciness - 1.0).abs() < 1e-9 {
bounciness = 1.001;
}
let pow = bounciness.powf(bounces);
let one_minus_bounciness = 1.0 - bounciness;
let sum_of_units = (1.0 - pow) / one_minus_bounciness + pow * 0.5;
let unit_at_t = t * sum_of_units;
let bounce_at_t = (-unit_at_t * (1.0 - bounciness) + 1.0).log(bounciness);
let start = bounce_at_t.floor();
let end = start + 1.0;
let start_time = (1.0 - bounciness.powf(start)) / (one_minus_bounciness * sum_of_units);
let end_time = (1.0 - bounciness.powf(end)) / (one_minus_bounciness * sum_of_units);
let mid_time = (start_time + end_time) * 0.5;
let time_relative_to_peak = t - mid_time;
let radius = mid_time - start_time;
let amplitude = (1.0 / bounciness).powf(bounces - start);
(-amplitude / (radius * radius)) * (time_relative_to_peak - radius) * (time_relative_to_peak + radius)
}
fn ease_core(&self, t: f64) -> f64 {
ease_with_mode(self.mode, t, |t| Self::ease_in(t, self.bounces, self.bounciness))
}
}
value_easing!(BounceEasingFunction);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CircleEasingFunction {
mode: CompositionEasingFunctionMode,
}
impl CircleEasingFunction {
pub fn new(mode: CompositionEasingFunctionMode) -> CircleEasingFunction {
CircleEasingFunction { mode }
}
pub fn mode(&self) -> CompositionEasingFunctionMode {
self.mode
}
fn ease_core(&self, t: f64) -> f64 {
ease_with_mode(self.mode, t, |t| {
let t = t.clamp(0.0, 1.0);
1.0 - (1.0 - t * t).sqrt()
})
}
}
value_easing!(CircleEasingFunction);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ElasticEasingFunction {
mode: CompositionEasingFunctionMode,
oscillations: i32,
springiness: f32,
}
impl ElasticEasingFunction {
pub fn new(mode: CompositionEasingFunctionMode, oscillations: i32, springiness: f32) -> ElasticEasingFunction {
ElasticEasingFunction { mode, oscillations, springiness }
}
pub fn mode(&self) -> CompositionEasingFunctionMode {
self.mode
}
pub fn oscillations(&self) -> i32 {
self.oscillations
}
pub fn springiness(&self) -> f32 {
self.springiness
}
fn ease_core(&self, t: f64) -> f64 {
let oscillations = (self.oscillations as f64).max(0.0);
let springiness = (self.springiness as f64).max(0.0);
ease_with_mode(self.mode, t, |t| {
let expo = if springiness == 0.0 { t } else { ((springiness * t).exp() - 1.0) / (springiness.exp() - 1.0) };
expo * ((PI * 2.0 * oscillations + PI * 0.5) * t).sin()
})
}
}
value_easing!(ElasticEasingFunction);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ExponentialEasingFunction {
mode: CompositionEasingFunctionMode,
exponent: f32,
}
impl ExponentialEasingFunction {
pub fn new(mode: CompositionEasingFunctionMode, exponent: f32) -> ExponentialEasingFunction {
ExponentialEasingFunction { mode, exponent }
}
pub fn mode(&self) -> CompositionEasingFunctionMode {
self.mode
}
pub fn exponent(&self) -> f32 {
self.exponent
}
fn ease_core(&self, t: f64) -> f64 {
let factor = self.exponent as f64;
ease_with_mode(self.mode, t, |t| if factor == 0.0 { t } else { ((factor * t).exp() - 1.0) / (factor.exp() - 1.0) })
}
}
value_easing!(ExponentialEasingFunction);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PowerEasingFunction {
mode: CompositionEasingFunctionMode,
power: f32,
}
impl PowerEasingFunction {
pub fn new(mode: CompositionEasingFunctionMode, power: f32) -> PowerEasingFunction {
PowerEasingFunction { mode, power }
}
pub fn mode(&self) -> CompositionEasingFunctionMode {
self.mode
}
pub fn power(&self) -> f32 {
self.power
}
fn ease_core(&self, t: f64) -> f64 {
let power = (self.power as f64).max(0.0);
ease_with_mode(self.mode, t, |t| t.powf(power))
}
}
value_easing!(PowerEasingFunction);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SineEasingFunction {
mode: CompositionEasingFunctionMode,
}
impl SineEasingFunction {
pub fn new(mode: CompositionEasingFunctionMode) -> SineEasingFunction {
SineEasingFunction { mode }
}
pub fn mode(&self) -> CompositionEasingFunctionMode {
self.mode
}
fn ease_core(&self, t: f64) -> f64 {
ease_with_mode(self.mode, t, |t| 1.0 - (PI * 0.5 * (1.0 - t)).sin())
}
}
value_easing!(SineEasingFunction);
pub struct CompositionEasingFunction;
impl CompositionEasingFunction {
pub fn create_cubic_bezier_easing_function(
_owner: &Compositor,
control_point1: Vector2,
control_point2: Vector2,
) -> Easing {
CubicBezierEasingFunction::new(control_point1, control_point2).into()
}
pub fn create_linear_easing_function(_owner: &Compositor) -> Easing {
LinearEasingFunction::new().into()
}
pub fn create_step_easing_function(_owner: &Compositor) -> Easing {
StepEasingFunction::new().into()
}
pub fn create_step_easing_function_with_step_count(_owner: &Compositor, step_count: i32) -> Easing {
StepEasingFunction::with_step_count(step_count).into()
}
pub fn create_back_easing_function(
_owner: &Compositor,
mode: CompositionEasingFunctionMode,
amplitude: f32,
) -> Easing {
BackEasingFunction::new(mode, amplitude).into()
}
pub fn create_bounce_easing_function(
_owner: &Compositor,
mode: CompositionEasingFunctionMode,
bounces: i32,
bounciness: f32,
) -> Easing {
BounceEasingFunction::new(mode, bounces, bounciness).into()
}
pub fn create_circle_easing_function(_owner: &Compositor, mode: CompositionEasingFunctionMode) -> Easing {
CircleEasingFunction::new(mode).into()
}
pub fn create_elastic_easing_function(
_owner: &Compositor,
mode: CompositionEasingFunctionMode,
oscillations: i32,
springiness: f32,
) -> Easing {
ElasticEasingFunction::new(mode, oscillations, springiness).into()
}
pub fn create_exponential_easing_function(
_owner: &Compositor,
mode: CompositionEasingFunctionMode,
exponent: f32,
) -> Easing {
ExponentialEasingFunction::new(mode, exponent).into()
}
pub fn create_power_easing_function(_owner: &Compositor, mode: CompositionEasingFunctionMode, power: f32) -> Easing {
PowerEasingFunction::new(mode, power).into()
}
pub fn create_sine_easing_function(_owner: &Compositor, mode: CompositionEasingFunctionMode) -> Easing {
SineEasingFunction::new(mode).into()
}
}
impl Compositor {
pub fn create_cubic_bezier_easing_function(
&self,
control_point1: Vector2,
control_point2: Vector2,
) -> Rc<CubicBezierEasingFunction> {
Rc::new(CubicBezierEasingFunction::new(control_point1, control_point2))
}
pub fn create_linear_easing_function(&self) -> Rc<LinearEasingFunction> {
Rc::new(LinearEasingFunction::new())
}
pub fn create_step_easing_function(&self) -> Rc<StepEasingFunction> {
Rc::new(StepEasingFunction::new())
}
pub fn create_step_easing_function_with_step_count(&self, step_count: i32) -> Rc<StepEasingFunction> {
Rc::new(StepEasingFunction::with_step_count(step_count))
}
}