From db94c9167f2da901cfe04640e8142e1c7de2ceec Mon Sep 17 00:00:00 2001 From: kossLAN Date: Fri, 29 May 2026 11:56:39 -0400 Subject: [PATCH] animation: move curve primitives into layout crate --- layout-animation/src/lib.rs | 160 +++++++++++++++++++++++++++++++++++ src/animation.rs | 161 +----------------------------------- 2 files changed, 162 insertions(+), 159 deletions(-) diff --git a/layout-animation/src/lib.rs b/layout-animation/src/lib.rs index 03360a8e..c9ad258e 100644 --- a/layout-animation/src/lib.rs +++ b/layout-animation/src/lib.rs @@ -1,5 +1,165 @@ 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 { + 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 { + 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, + 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)] pub struct NodeId(pub u32); diff --git a/src/animation.rs b/src/animation.rs index e76e030b..3d6136fa 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -19,127 +19,9 @@ use { pub mod multiphase; +pub use jay_layout_animation::{AnimationCurve, AnimationStyle}; + 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 { - 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 { - 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 enabled: Cell, @@ -885,45 +767,6 @@ pub(crate) fn expand_damage_rect(rect: Rect, width: i32) -> Rect { ) } -fn flatten_cubic_bezier( - points: &mut Vec, - 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)] mod tests { use super::*;