rive_rs/animation/
cubic_interpolator.rs
1use std::any::TypeId;
6use std::cell::RefCell;
7
8use crate::core::{Core, CoreContext, Object, ObjectRef, OnAdded, Property};
9use crate::importers::{ArtboardImporter, ImportStack};
10use crate::status_code::StatusCode;
11use crate::Artboard;
12
13const SPLINE_TABLE_SIZE: usize = 11;
14const SAMPLE_STEP_SIZE: f32 = 1.0 / (SPLINE_TABLE_SIZE as f32 - 1.0);
15
16const NEWTON_ITERATIONS: usize = 4;
17const NEWTON_MIN_SLOPE: f32 = 0.001;
18const SUBDIVISION_PRECISION: f32 = 0.0000001;
19const SUBDIVISION_MAX_ITERATIONS: usize = 10;
20
21#[derive(Debug)]
22pub struct CubicInterpolator {
23 x1: Property<f32>,
24 y1: Property<f32>,
25 x2: Property<f32>,
26 y2: Property<f32>,
27 values: RefCell<[f32; SPLINE_TABLE_SIZE]>,
28}
29
30impl ObjectRef<'_, CubicInterpolator> {
31 pub fn x1(&self) -> f32 {
32 self.x1.get()
33 }
34
35 pub fn set_x1(&self, x1: f32) {
36 self.x1.set(x1);
37 }
38
39 pub fn y1(&self) -> f32 {
40 self.y1.get()
41 }
42
43 pub fn set_y1(&self, y1: f32) {
44 self.y1.set(y1);
45 }
46
47 pub fn x2(&self) -> f32 {
48 self.x2.get()
49 }
50
51 pub fn set_x2(&self, x2: f32) {
52 self.x2.set(x2);
53 }
54
55 pub fn y2(&self) -> f32 {
56 self.y2.get()
57 }
58
59 pub fn set_y2(&self, y2: f32) {
60 self.y2.set(y2);
61 }
62}
63
64fn calc_bezier(t: f32, c1: f32, c2: f32) -> f32 {
65 (((1.0 - 3.0 * c2 + 3.0 * c1) * t + (3.0 * c2 - 6.0 * c1)) * t + (3.0 * c1)) * t
66}
67
68fn get_slope(t: f32, c1: f32, c2: f32) -> f32 {
69 3.0 * (1.0 - 3.0 * c2 + 3.0 * c1) * t * t + 2.0 * (3.0 * c2 - 6.0 * c1) * t + (3.0 * c1)
70}
71
72impl ObjectRef<'_, CubicInterpolator> {
73 pub fn t(&self, x: f32) -> f32 {
74 let values = self.values.borrow();
75 let mut interval_start = 0.0;
76 let mut current_sample = 1;
77 let last_sample = SPLINE_TABLE_SIZE - 1;
78
79 while current_sample != last_sample && values[current_sample] <= x {
80 interval_start += SAMPLE_STEP_SIZE;
81
82 current_sample += 1;
83 }
84
85 current_sample -= 1;
86
87 let dist =
88 (x - values[current_sample]) / (values[current_sample + 1] - values[current_sample]);
89 let mut guess_for_t = interval_start + dist * SAMPLE_STEP_SIZE;
90
91 let x1 = self.x1();
92 let x2 = self.x2();
93
94 let initial_slope = get_slope(guess_for_t, x1, x2);
95 if initial_slope >= NEWTON_MIN_SLOPE {
96 for _ in 0..NEWTON_ITERATIONS {
97 let current_slope = get_slope(guess_for_t, x1, x2);
98 if current_slope == 0.0 {
99 return guess_for_t;
100 }
101
102 let current_x = calc_bezier(guess_for_t, x1, x2) - x;
103 guess_for_t -= current_x / current_slope;
104 }
105 } else if initial_slope != 0.0 {
106 let mut ab = interval_start + SAMPLE_STEP_SIZE;
107 let mut i = 0;
108 let mut current_t;
109 let mut current_x;
110
111 loop {
112 current_t = interval_start + (ab - interval_start) / 2.0;
113 current_x = calc_bezier(current_t, x1, x2) - x;
114
115 if current_x > 0.0 {
116 ab = current_t;
117 } else {
118 interval_start = current_t;
119 }
120
121 i += 1;
122
123 if current_x.abs() > SUBDIVISION_PRECISION && i < SUBDIVISION_MAX_ITERATIONS {
124 return current_t;
125 }
126 }
127 }
128
129 guess_for_t
130 }
131
132 pub fn transform(&self, mix: f32) -> f32 {
133 calc_bezier(self.t(mix), self.y1(), self.y2())
134 }
135}
136
137impl Core for CubicInterpolator {
138 properties![(63, x1, set_x1), (64, y1, set_y1), (65, x2, set_x2), (66, y2, set_y2)];
139}
140
141impl OnAdded for ObjectRef<'_, CubicInterpolator> {
142 on_added!([on_added_clean]);
143
144 fn on_added_dirty(&self, _context: &dyn CoreContext) -> StatusCode {
145 for (i, value) in self.values.borrow_mut().iter_mut().enumerate() {
146 *value = calc_bezier(i as f32 * SAMPLE_STEP_SIZE, self.x1(), self.x2());
147 }
148
149 StatusCode::Ok
150 }
151
152 fn import(&self, object: Object, import_stack: &ImportStack) -> StatusCode {
153 if let Some(importer) = import_stack.latest::<ArtboardImporter>(TypeId::of::<Artboard>()) {
154 importer.push_object(object);
155 StatusCode::Ok
156 } else {
157 StatusCode::MissingObject
158 }
159 }
160}
161
162impl Default for CubicInterpolator {
163 fn default() -> Self {
164 Self {
165 x1: Property::new(0.42),
166 y1: Property::new(0.0),
167 x2: Property::new(0.58),
168 y2: Property::new(1.0),
169 values: RefCell::default(),
170 }
171 }
172}