Skip to content

Commit

Permalink
Implement more interpolation types (#402)
Browse files Browse the repository at this point in the history
  • Loading branch information
noituri authored Mar 6, 2024
1 parent 568f14e commit b5a8567
Show file tree
Hide file tree
Showing 16 changed files with 426 additions and 26 deletions.
5 changes: 4 additions & 1 deletion compositor_render/src/scene/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use std::{fmt::Display, sync::Arc, time::Duration};

use crate::{InputId, RendererId};

use super::{AbsolutePosition, Component, HorizontalAlign, RGBAColor, Size, VerticalAlign};
use super::{
AbsolutePosition, Component, HorizontalAlign, InterpolationKind, RGBAColor, Size, VerticalAlign,
};

mod interpolation;

Expand Down Expand Up @@ -150,6 +152,7 @@ pub enum Overflow {
#[derive(Debug, Clone, Copy)]
pub struct Transition {
pub duration: Duration,
pub interpolation_kind: InterpolationKind,
}

#[derive(Debug, Clone, Copy)]
Expand Down
2 changes: 1 addition & 1 deletion compositor_render/src/scene/rescaler_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl RescalerComponent {
let transition = TransitionState::new(
self.transition.map(|transition| TransitionOptions {
duration: transition.duration,
interpolation_kind: super::InterpolationKind::Linear,
interpolation_kind: transition.interpolation_kind,
}),
previous_state.and_then(|s| s.transition.clone()),
ctx.last_render_pts,
Expand Down
2 changes: 1 addition & 1 deletion compositor_render/src/scene/tiles_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ impl TilesComponent {
let transition = TransitionState::new(
self.transition.map(|transition| TransitionOptions {
duration: transition.duration,
interpolation_kind: super::InterpolationKind::Linear,
interpolation_kind: transition.interpolation_kind,
}),
previous_state.and_then(|s| s.transition.clone()),
ctx.last_render_pts,
Expand Down
9 changes: 9 additions & 0 deletions compositor_render/src/scene/transition.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use std::time::Duration;

use self::{bounce::bounce_easing, cubic_bezier::cubic_bezier_easing};

use super::{types::interpolation::InterpolationState, InterpolationKind};

mod bounce;
mod cubic_bezier;

/// Similar concept to InterpolationState, but it represents a time instead.
/// Values between 0 and 1 represent transition and larger than 1 post transition.
///
Expand Down Expand Up @@ -91,6 +96,10 @@ impl InterpolationKind {
fn state(&self, t: f64) -> InterpolationState {
match self {
InterpolationKind::Linear => InterpolationState(t),
InterpolationKind::Bounce => InterpolationState(bounce_easing(t)),
InterpolationKind::CubicBezier { x1, y1, x2, y2 } => {
InterpolationState(cubic_bezier_easing(t, *x1, *y1, *x2, *y2))
}
}
}
}
14 changes: 14 additions & 0 deletions compositor_render/src/scene/transition/bounce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub fn bounce_easing(t: f64) -> f64 {
let n1 = 7.5625;
let d1 = 2.75;

if t < (1.0 / d1) {
n1 * t * t
} else if t < (2.0 / d1) {
n1 * (t - 1.5 / d1) * (t - 1.5 / d1) + 0.75
} else if t < (2.5 / d1) {
n1 * (t - 2.25 / d1) * (t - 2.25 / d1) + 0.9375
} else {
n1 * (t - 2.625 / d1) * (t - 2.625 / d1) + 0.984375
}
}
148 changes: 148 additions & 0 deletions compositor_render/src/scene/transition/cubic_bezier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use std::f64::consts::PI;

const ALLOWED_FLOATING_ERROR: f64 = 1e-7;

pub fn cubic_bezier_easing(progress: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
if progress.is_close_to(0.0) {
return 0.0;
}
if progress.is_close_to(1.0) {
return 1.0;
}

let t = find_first_cubic_root(-progress, x1 - progress, x2 - progress, 1.0 - progress);
if t.is_nan() {
// TODO(noituri): IDK how to handle this case
return 1.0;
}

cubic_bezier(t, y1, y2).clamp(0.0, 1.0)
}

fn cubic_bezier(t: f64, p1: f64, p2: f64) -> f64 {
let a = 1.0 / 3.0 + (p1 - p2);
let b = p2 - 2.0 * p1;
let c = p1;

3.0 * ((a * t + b) * t + c) * t
}

// Based on https://pomax.github.io/bezierinfo/#yforx
// https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt;l=274;drc=9b09524f2b2cbfc397269fbbe1267dd46b14077a
fn find_first_cubic_root(p0: f64, p1: f64, p2: f64, p3: f64) -> f64 {
// Coefficients calculated from control points
let a = 3.0 * (p0 - 2.0 * p1 + p2);
let b = 3.0 * (p1 - p0);
let c = p0;
let d = -p0 + 3.0 * (p1 - p2) + p3;

// If `d` is 0 then the function is not cubic
if d.is_close_to(0.0) {
// If `a` is 0 then the function is not quadratic
if a.is_close_to(0.0) {
// If `b` is 0 then the function is not linear
if b.is_close_to(0.0) {
return f64::NAN;
}

return (-c / b).clamp_valid_root_in_unit_range();
}
let q = f64::sqrt(b * b - 4.0 * a * c);
let a2 = 2.0 * a;
let root = ((q - b) / a2).clamp_valid_root_in_unit_range();
if !root.is_nan() {
return root;
}

return ((-b - q) / a2).clamp_valid_root_in_unit_range();
}

let a = a / d;
let b = b / d;
let c = c / d;

let o3 = (3.0 * b - a.powi(2)) / 9.0;
let q2 = (2.0 * a.powi(3) - 9.0 * a * b + 27.0 * c) / 54.0;
let a3 = a / 3.0;
let discriminant = q2.powi(2) + o3.powi(3);

if discriminant < 0.0 {
let mp33 = -f64::powi(o3, 3);
let r = mp33.sqrt();
let cos_phi = (-q2 / r).clamp(-1.0, 1.0);
let phi = cos_phi.acos();
let t1 = 2.0 * r.cbrt();

let root = (t1 * f64::cos(phi / 3.0) - a3).clamp_valid_root_in_unit_range();
if !root.is_nan() {
return root;
}

let root = (t1 * f64::cos((phi + 2.0 * PI) / 3.0) - a3).clamp_valid_root_in_unit_range();
if !root.is_nan() {
return root;
}

return (t1 * f64::cos((phi + 4.0 * PI) / 3.0) - a3).clamp_valid_root_in_unit_range();
}

if discriminant == 0.0 {
let u1 = -f64::cbrt(q2);
let root = (2.0 * u1 - a3).clamp_valid_root_in_unit_range();
if !root.is_nan() {
return root;
}

return (-u1 - a3).clamp_valid_root_in_unit_range();
}

let sd = discriminant.sqrt();
let u1 = (-q2 + sd).cbrt();
let v1 = (q2 + sd).cbrt();

(u1 - v1 - a3).clamp_valid_root_in_unit_range()
}

trait F64Ext {
fn is_close_to(&self, value: Self) -> bool;

fn clamp_valid_root_in_unit_range(self) -> Self;
}

impl F64Ext for f64 {
fn is_close_to(&self, value: Self) -> bool {
(*self - value).abs() < ALLOWED_FLOATING_ERROR
}

fn clamp_valid_root_in_unit_range(self) -> Self {
if self < 0.0 {
if self >= -ALLOWED_FLOATING_ERROR {
0.0
} else {
f64::NAN
}
} else if self > 1.0 {
if self <= 1.0 + ALLOWED_FLOATING_ERROR {
1.0
} else {
f64::NAN
}
} else {
self
}
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_cubic_bezier_easing() {
assert!(cubic_bezier_easing(0.0, 0.0, 0.0, 1.0, 1.0).is_close_to(0.0));
assert!(cubic_bezier_easing(1.0, 0.0, 0.0, 1.0, 1.0).is_close_to(1.0));
assert!(cubic_bezier_easing(0.5, 0.0, 0.0, 1.0, 1.0).is_close_to(0.5));
assert!(cubic_bezier_easing(0.294, 0.25, 0.1, 0.25, 1.0).is_close_to(0.5014012915764126));
assert!(cubic_bezier_easing(0.5, 0.85, 0.0, 0.15, 1.0).is_close_to(0.5));
}
}
2 changes: 2 additions & 0 deletions compositor_render/src/scene/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,6 @@ pub enum HorizontalPosition {
#[derive(Debug, Clone, Copy)]
pub enum InterpolationKind {
Linear,
Bounce,
CubicBezier { x1: f64, y1: f64, x2: f64, y2: f64 },
}
2 changes: 1 addition & 1 deletion compositor_render/src/scene/view_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl ViewComponent {
let transition = TransitionState::new(
self.transition.map(|transition| TransitionOptions {
duration: transition.duration,
interpolation_kind: super::InterpolationKind::Linear,
interpolation_kind: transition.interpolation_kind,
}),
previous_state.and_then(|s| s.transition.clone()),
ctx.last_render_pts,
Expand Down
6 changes: 5 additions & 1 deletion examples/tiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ fn start_example_client_code() -> Result<()> {
"background_color_rgba": "#444444FF",
"children": children,
"transition": {
"duration_ms": 500,
"duration_ms": 700,
"easing_function": {
"function_name": "cubic_bezier",
"points": [0.35, 0.22, 0.1, 0.8]
}
},
})
};
Expand Down
67 changes: 67 additions & 0 deletions schemas/register.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b5a8567

Please sign in to comment.