1
0
Fork 0
forked from wry/wry

animation: move curve primitives into layout crate

This commit is contained in:
kossLAN 2026-05-29 11:56:39 -04:00
parent 1558666601
commit db94c9167f
No known key found for this signature in database
2 changed files with 162 additions and 159 deletions

View file

@ -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<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)]
pub struct NodeId(pub u32);

View file

@ -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<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 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)]
mod tests {
use super::*;