animation: move curve primitives into layout crate
This commit is contained in:
parent
1558666601
commit
db94c9167f
2 changed files with 162 additions and 159 deletions
|
|
@ -1,5 +1,165 @@
|
||||||
use jay_geometry::Rect;
|
use jay_geometry::Rect;
|
||||||
|
|
||||||
|
const CURVE_MAX_POINTS: usize = 33;
|
||||||
|
const CURVE_FLATNESS_EPSILON: f32 = 0.001;
|
||||||
|
const CURVE_MAX_DEPTH: u8 = 8;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub enum AnimationCurve {
|
||||||
|
Linear,
|
||||||
|
Piecewise(PiecewiseCurve),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum AnimationStyle {
|
||||||
|
Plain,
|
||||||
|
Multiphase,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimationStyle {
|
||||||
|
pub fn from_config(value: u32) -> Option<Self> {
|
||||||
|
match value {
|
||||||
|
0 => Some(Self::Plain),
|
||||||
|
1 => Some(Self::Multiphase),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimationCurve {
|
||||||
|
pub fn from_config(value: u32) -> Self {
|
||||||
|
match value {
|
||||||
|
0 => Self::Linear,
|
||||||
|
1 => Self::from_cubic_bezier(0.25, 0.1, 0.25, 1.0).unwrap(),
|
||||||
|
2 => Self::from_cubic_bezier(0.42, 0.0, 1.0, 1.0).unwrap(),
|
||||||
|
4 => Self::from_cubic_bezier(0.42, 0.0, 0.58, 1.0).unwrap(),
|
||||||
|
_ => Self::from_cubic_bezier(0.0, 0.0, 0.58, 1.0).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> Option<Self> {
|
||||||
|
if !x1.is_finite()
|
||||||
|
|| !y1.is_finite()
|
||||||
|
|| !x2.is_finite()
|
||||||
|
|| !y2.is_finite()
|
||||||
|
|| !(0.0..=1.0).contains(&x1)
|
||||||
|
|| !(0.0..=1.0).contains(&x2)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Self::Piecewise(PiecewiseCurve::from_cubic_bezier(
|
||||||
|
x1, y1, x2, y2,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample(self, t: f64) -> f64 {
|
||||||
|
let t = t.clamp(0.0, 1.0);
|
||||||
|
match self {
|
||||||
|
Self::Linear => t,
|
||||||
|
Self::Piecewise(curve) => curve.sample(t as f32) as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub struct PiecewiseCurve {
|
||||||
|
len: u8,
|
||||||
|
points: [CurvePoint; CURVE_MAX_POINTS],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PiecewiseCurve {
|
||||||
|
fn from_cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
|
||||||
|
let mut points = Vec::with_capacity(CURVE_MAX_POINTS);
|
||||||
|
let p0 = cubic_bezier_point(x1, y1, x2, y2, 0.0);
|
||||||
|
let p1 = cubic_bezier_point(x1, y1, x2, y2, 1.0);
|
||||||
|
points.push(p0);
|
||||||
|
flatten_cubic_bezier(&mut points, (x1, y1, x2, y2), 0.0, p0, 1.0, p1, 0);
|
||||||
|
let mut array = [CurvePoint::default(); CURVE_MAX_POINTS];
|
||||||
|
let len = points.len().min(CURVE_MAX_POINTS);
|
||||||
|
array[..len].copy_from_slice(&points[..len]);
|
||||||
|
Self {
|
||||||
|
len: len as u8,
|
||||||
|
points: array,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample(self, x: f32) -> f32 {
|
||||||
|
let len = self.len as usize;
|
||||||
|
if len <= 1 {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
let points = &self.points[..len];
|
||||||
|
if x <= points[0].x {
|
||||||
|
return points[0].y;
|
||||||
|
}
|
||||||
|
if x >= points[len - 1].x {
|
||||||
|
return points[len - 1].y;
|
||||||
|
}
|
||||||
|
let mut lo = 0;
|
||||||
|
let mut hi = len - 1;
|
||||||
|
while lo + 1 < hi {
|
||||||
|
let mid = (lo + hi) / 2;
|
||||||
|
if points[mid].x <= x {
|
||||||
|
lo = mid;
|
||||||
|
} else {
|
||||||
|
hi = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let from = points[lo];
|
||||||
|
let to = points[hi];
|
||||||
|
if to.x <= from.x {
|
||||||
|
return to.y;
|
||||||
|
}
|
||||||
|
let t = (x - from.x) / (to.x - from.x);
|
||||||
|
from.y + (to.y - from.y) * t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||||
|
struct CurvePoint {
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flatten_cubic_bezier(
|
||||||
|
points: &mut Vec<CurvePoint>,
|
||||||
|
controls: (f32, f32, f32, f32),
|
||||||
|
t0: f32,
|
||||||
|
p0: CurvePoint,
|
||||||
|
t1: f32,
|
||||||
|
p1: CurvePoint,
|
||||||
|
depth: u8,
|
||||||
|
) {
|
||||||
|
let tm = (t0 + t1) * 0.5;
|
||||||
|
let pm = cubic_bezier_point(controls.0, controls.1, controls.2, controls.3, tm);
|
||||||
|
let projected_y = if p1.x <= p0.x {
|
||||||
|
(p0.y + p1.y) * 0.5
|
||||||
|
} else {
|
||||||
|
let tx = (pm.x - p0.x) / (p1.x - p0.x);
|
||||||
|
p0.y + (p1.y - p0.y) * tx
|
||||||
|
};
|
||||||
|
if (pm.y - projected_y).abs() > CURVE_FLATNESS_EPSILON
|
||||||
|
&& depth < CURVE_MAX_DEPTH
|
||||||
|
&& points.len() + 2 < CURVE_MAX_POINTS
|
||||||
|
{
|
||||||
|
flatten_cubic_bezier(points, controls, t0, p0, tm, pm, depth + 1);
|
||||||
|
flatten_cubic_bezier(points, controls, tm, pm, t1, p1, depth + 1);
|
||||||
|
} else {
|
||||||
|
points.push(p1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cubic_bezier_point(x1: f32, y1: f32, x2: f32, y2: f32, t: f32) -> CurvePoint {
|
||||||
|
fn bezier(a: f32, b: f32, t: f32) -> f32 {
|
||||||
|
let inv = 1.0 - t;
|
||||||
|
3.0 * inv * inv * t * a + 3.0 * inv * t * t * b + t * t * t
|
||||||
|
}
|
||||||
|
CurvePoint {
|
||||||
|
x: bezier(x1, x2, t),
|
||||||
|
y: bezier(y1, y2, t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub struct NodeId(pub u32);
|
pub struct NodeId(pub u32);
|
||||||
|
|
||||||
|
|
|
||||||
161
src/animation.rs
161
src/animation.rs
|
|
@ -19,127 +19,9 @@ use {
|
||||||
|
|
||||||
pub mod multiphase;
|
pub mod multiphase;
|
||||||
|
|
||||||
|
pub use jay_layout_animation::{AnimationCurve, AnimationStyle};
|
||||||
|
|
||||||
const DEFAULT_DURATION_MS: u32 = 160;
|
const DEFAULT_DURATION_MS: u32 = 160;
|
||||||
const CURVE_MAX_POINTS: usize = 33;
|
|
||||||
const CURVE_FLATNESS_EPSILON: f32 = 0.001;
|
|
||||||
const CURVE_MAX_DEPTH: u8 = 8;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub enum AnimationCurve {
|
|
||||||
Linear,
|
|
||||||
Piecewise(PiecewiseCurve),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub enum AnimationStyle {
|
|
||||||
Plain,
|
|
||||||
Multiphase,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnimationStyle {
|
|
||||||
pub fn from_config(value: u32) -> Option<Self> {
|
|
||||||
match value {
|
|
||||||
0 => Some(Self::Plain),
|
|
||||||
1 => Some(Self::Multiphase),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnimationCurve {
|
|
||||||
pub fn from_config(value: u32) -> Self {
|
|
||||||
match value {
|
|
||||||
0 => Self::Linear,
|
|
||||||
1 => Self::from_cubic_bezier(0.25, 0.1, 0.25, 1.0).unwrap(),
|
|
||||||
2 => Self::from_cubic_bezier(0.42, 0.0, 1.0, 1.0).unwrap(),
|
|
||||||
4 => Self::from_cubic_bezier(0.42, 0.0, 0.58, 1.0).unwrap(),
|
|
||||||
_ => Self::from_cubic_bezier(0.0, 0.0, 0.58, 1.0).unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> Option<Self> {
|
|
||||||
if !x1.is_finite()
|
|
||||||
|| !y1.is_finite()
|
|
||||||
|| !x2.is_finite()
|
|
||||||
|| !y2.is_finite()
|
|
||||||
|| !(0.0..=1.0).contains(&x1)
|
|
||||||
|| !(0.0..=1.0).contains(&x2)
|
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(Self::Piecewise(PiecewiseCurve::from_cubic_bezier(
|
|
||||||
x1, y1, x2, y2,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sample(self, t: f64) -> f64 {
|
|
||||||
let t = t.clamp(0.0, 1.0);
|
|
||||||
match self {
|
|
||||||
Self::Linear => t,
|
|
||||||
Self::Piecewise(curve) => curve.sample(t as f32) as f64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub struct PiecewiseCurve {
|
|
||||||
len: u8,
|
|
||||||
points: [CurvePoint; CURVE_MAX_POINTS],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PiecewiseCurve {
|
|
||||||
fn from_cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
|
|
||||||
let mut points = Vec::with_capacity(CURVE_MAX_POINTS);
|
|
||||||
let p0 = cubic_bezier_point(x1, y1, x2, y2, 0.0);
|
|
||||||
let p1 = cubic_bezier_point(x1, y1, x2, y2, 1.0);
|
|
||||||
points.push(p0);
|
|
||||||
flatten_cubic_bezier(&mut points, (x1, y1, x2, y2), 0.0, p0, 1.0, p1, 0);
|
|
||||||
let mut array = [CurvePoint::default(); CURVE_MAX_POINTS];
|
|
||||||
let len = points.len().min(CURVE_MAX_POINTS);
|
|
||||||
array[..len].copy_from_slice(&points[..len]);
|
|
||||||
Self {
|
|
||||||
len: len as u8,
|
|
||||||
points: array,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sample(self, x: f32) -> f32 {
|
|
||||||
let len = self.len as usize;
|
|
||||||
if len <= 1 {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
let points = &self.points[..len];
|
|
||||||
if x <= points[0].x {
|
|
||||||
return points[0].y;
|
|
||||||
}
|
|
||||||
if x >= points[len - 1].x {
|
|
||||||
return points[len - 1].y;
|
|
||||||
}
|
|
||||||
let mut lo = 0;
|
|
||||||
let mut hi = len - 1;
|
|
||||||
while lo + 1 < hi {
|
|
||||||
let mid = (lo + hi) / 2;
|
|
||||||
if points[mid].x <= x {
|
|
||||||
lo = mid;
|
|
||||||
} else {
|
|
||||||
hi = mid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let from = points[lo];
|
|
||||||
let to = points[hi];
|
|
||||||
if to.x <= from.x {
|
|
||||||
return to.y;
|
|
||||||
}
|
|
||||||
let t = (x - from.x) / (to.x - from.x);
|
|
||||||
from.y + (to.y - from.y) * t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
|
||||||
struct CurvePoint {
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AnimationState {
|
pub struct AnimationState {
|
||||||
pub enabled: Cell<bool>,
|
pub enabled: Cell<bool>,
|
||||||
|
|
@ -885,45 +767,6 @@ pub(crate) fn expand_damage_rect(rect: Rect, width: i32) -> Rect {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flatten_cubic_bezier(
|
|
||||||
points: &mut Vec<CurvePoint>,
|
|
||||||
controls: (f32, f32, f32, f32),
|
|
||||||
t0: f32,
|
|
||||||
p0: CurvePoint,
|
|
||||||
t1: f32,
|
|
||||||
p1: CurvePoint,
|
|
||||||
depth: u8,
|
|
||||||
) {
|
|
||||||
let tm = (t0 + t1) * 0.5;
|
|
||||||
let pm = cubic_bezier_point(controls.0, controls.1, controls.2, controls.3, tm);
|
|
||||||
let projected_y = if p1.x <= p0.x {
|
|
||||||
(p0.y + p1.y) * 0.5
|
|
||||||
} else {
|
|
||||||
let tx = (pm.x - p0.x) / (p1.x - p0.x);
|
|
||||||
p0.y + (p1.y - p0.y) * tx
|
|
||||||
};
|
|
||||||
if (pm.y - projected_y).abs() > CURVE_FLATNESS_EPSILON
|
|
||||||
&& depth < CURVE_MAX_DEPTH
|
|
||||||
&& points.len() + 2 < CURVE_MAX_POINTS
|
|
||||||
{
|
|
||||||
flatten_cubic_bezier(points, controls, t0, p0, tm, pm, depth + 1);
|
|
||||||
flatten_cubic_bezier(points, controls, tm, pm, t1, p1, depth + 1);
|
|
||||||
} else {
|
|
||||||
points.push(p1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cubic_bezier_point(x1: f32, y1: f32, x2: f32, y2: f32, t: f32) -> CurvePoint {
|
|
||||||
fn bezier(a: f32, b: f32, t: f32) -> f32 {
|
|
||||||
let inv = 1.0 - t;
|
|
||||||
3.0 * inv * inv * t * a + 3.0 * inv * t * t * b + t * t * t
|
|
||||||
}
|
|
||||||
CurvePoint {
|
|
||||||
x: bezier(x1, x2, t),
|
|
||||||
y: bezier(y1, y2, t),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue