1use crate::utilities::{FormatError, SpanStringTokenizer};
5use crate::{ferro_class, ferro_impl_classes, instantiate, FerroObject, FerroObjectImpl, Ref};
6use std::cell::Cell;
7
8const ACCURACY: f64 = 0.001;
10const FUZZ: f64 = 0.000001;
12
13#[repr(C)]
16pub struct KeySpline {
17 base: FerroObject,
18
19 control_point_x1: Cell<f64>,
21 control_point_y1: Cell<f64>,
22 control_point_x2: Cell<f64>,
23 control_point_y2: Cell<f64>,
24 is_specified: Cell<bool>,
25 is_dirty: Cell<bool>,
26
27 parameter: Cell<f64>,
29
30 bx: Cell<f64>, cx: Cell<f64>, cx_bx: Cell<f64>, three_cx: Cell<f64>, by: Cell<f64>, cy: Cell<f64>, }
39
40ferro_class!(KeySpline: FerroObject);
41crate::ferro_class_info!(KeySpline { new: KeySpline::new });
42ferro_impl_classes!(KeySpline: FerroObjectImpl);
43
44impl KeySpline {
45 pub fn construct(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
47 Self {
48 base: FerroObject::construct(),
49 control_point_x1: Cell::new(x1),
50 control_point_y1: Cell::new(y1),
51 control_point_x2: Cell::new(x2),
52 control_point_y2: Cell::new(y2),
53 is_specified: Cell::new(false),
54 is_dirty: Cell::new(true),
55 parameter: Cell::new(0.0),
56 bx: Cell::new(0.0),
57 cx: Cell::new(0.0),
58 cx_bx: Cell::new(0.0),
59 three_cx: Cell::new(0.0),
60 by: Cell::new(0.0),
61 cy: Cell::new(0.0),
62 }
63 }
64
65 pub fn new() -> Ref<Self> {
68 instantiate(Self::construct(0.0, 0.0, 1.0, 1.0))
69 }
70
71 pub fn with_points(x1: f64, y1: f64, x2: f64, y2: f64) -> Ref<Self> {
74 instantiate(Self::construct(x1, y1, x2, y2))
75 }
76
77 pub fn parse(value: &str) -> Result<Ref<KeySpline>, FormatError> {
79 let error = |_| FormatError::from_string(format!("Invalid KeySpline string: \"{value}\"."));
80 let mut tokenizer = SpanStringTokenizer::new(value);
81 let x1 = tokenizer.read_double().map_err(error)?;
82 let y1 = tokenizer.read_double().map_err(error)?;
83 let x2 = tokenizer.read_double().map_err(error)?;
84 let y2 = tokenizer.read_double().map_err(error)?;
85 tokenizer.finish().map_err(error)?;
86 Ok(KeySpline::with_points(x1, y1, x2, y2))
87 }
88
89 pub fn control_point_x1(&self) -> f64 {
91 self.control_point_x1.get()
92 }
93
94 pub fn set_control_point_x1(&self, value: f64) {
97 if Self::is_valid_x_value(value) {
98 self.control_point_x1.set(value);
99 self.is_dirty.set(true);
100 } else {
101 panic!("Invalid KeySpline X1 value. Must be >= 0.0 and <= 1.0.");
102 }
103 }
104
105 pub fn control_point_y1(&self) -> f64 {
107 self.control_point_y1.get()
108 }
109
110 pub fn set_control_point_y1(&self, value: f64) {
111 self.control_point_y1.set(value);
112 self.is_dirty.set(true);
113 }
114
115 pub fn control_point_x2(&self) -> f64 {
117 self.control_point_x2.get()
118 }
119
120 pub fn set_control_point_x2(&self, value: f64) {
123 if Self::is_valid_x_value(value) {
124 self.control_point_x2.set(value);
125 self.is_dirty.set(true);
126 } else {
127 panic!("Invalid KeySpline X2 value. Must be >= 0.0 and <= 1.0.");
128 }
129 }
130
131 pub fn control_point_y2(&self) -> f64 {
133 self.control_point_y2.get()
134 }
135
136 pub fn set_control_point_y2(&self, value: f64) {
137 self.control_point_y2.set(value);
138 self.is_dirty.set(true);
139 }
140
141 pub fn get_spline_progress(&self, linear_progress: f64) -> f64 {
143 if self.is_dirty.get() {
144 self.build();
145 }
146
147 if !self.is_specified.get() {
148 linear_progress
149 } else {
150 self.set_parameter_from_x(linear_progress);
151
152 Self::get_bezier_value(self.by.get(), self.cy.get(), self.parameter.get())
153 }
154 }
155
156 pub fn is_valid(&self) -> bool {
159 Self::is_valid_x_value(self.control_point_x1.get()) && Self::is_valid_x_value(self.control_point_x2.get())
160 }
161
162 fn is_valid_x_value(value: f64) -> bool {
163 (0.0..=1.0).contains(&value)
164 }
165
166 fn build(&self) {
168 if self.control_point_x1.get() == 0.0
169 && self.control_point_y1.get() == 0.0
170 && self.control_point_x2.get() == 1.0
171 && self.control_point_y2.get() == 1.0
172 {
173 self.is_specified.set(false);
175 } else {
176 self.is_specified.set(true);
177
178 self.parameter.set(0.0);
179
180 self.bx.set(3.0 * self.control_point_x1.get());
182 self.cx.set(3.0 * self.control_point_x2.get());
183 self.cx_bx.set(2.0 * (self.cx.get() - self.bx.get()));
184 self.three_cx.set(3.0 - self.cx.get());
185
186 self.by.set(3.0 * self.control_point_y1.get());
188 self.cy.set(3.0 * self.control_point_y2.get());
189 }
190
191 self.is_dirty.set(false);
192 }
193
194 fn get_bezier_value(b: f64, c: f64, t: f64) -> f64 {
196 let s = 1.0 - t;
197 let t2 = t * t;
198
199 b * t * s * s + c * t2 * s + t2 * t
200 }
201
202 fn get_x_and_dx(&self, t: f64) -> (f64, f64) {
204 let s = 1.0 - t;
205 let t2 = t * t;
206 let s2 = s * s;
207
208 let x = self.bx.get() * t * s2 + self.cx.get() * t2 * s + t2 * t;
209 let dx = self.bx.get() * s2 + self.cx_bx.get() * s * t + self.three_cx.get() * t2;
210 (x, dx)
211 }
212
213 fn set_parameter_from_x(&self, time: f64) {
215 let mut bottom = 0.0;
217 let mut top = 1.0;
218
219 if time == 0.0 {
220 self.parameter.set(0.0);
221 } else if time == 1.0 {
222 self.parameter.set(1.0);
223 } else {
224 let mut parameter = self.parameter.get();
225
226 while top - bottom > FUZZ {
228 let (x, dx) = self.get_x_and_dx(parameter);
230 let absdx = dx.abs();
231
232 if x > time {
234 top = parameter; } else {
236 bottom = parameter; }
238
239 if (x - time).abs() < ACCURACY * absdx {
243 break; }
245
246 if absdx > FUZZ {
247 let next = parameter - (x - time) / dx;
249
250 if next >= top {
252 parameter = (parameter + top) / 2.0;
253 } else if next <= bottom {
254 parameter = (parameter + bottom) / 2.0;
255 } else {
256 parameter = next;
258 }
259 } else {
260 parameter = (bottom + top) / 2.0;
262 }
263 }
264
265 self.parameter.set(parameter);
266 }
267 }
268}
269
270#[derive(Clone, Copy, Debug)]
277pub struct KeySplineSolver {
278 is_specified: bool,
279 parameter: f64,
280 bx: f64,
281 cx: f64,
282 cx_bx: f64,
283 three_cx: f64,
284 by: f64,
285 cy: f64,
286}
287
288impl KeySplineSolver {
289 pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
291 let bx = 3.0 * x1;
292 let cx = 3.0 * x2;
293 Self {
294 is_specified: !(x1 == 0.0 && y1 == 0.0 && x2 == 1.0 && y2 == 1.0),
296 parameter: 0.0,
297 bx,
298 cx,
299 cx_bx: 2.0 * (cx - bx),
300 three_cx: 3.0 - cx,
301 by: 3.0 * y1,
302 cy: 3.0 * y2,
303 }
304 }
305
306 pub fn get_spline_progress(&mut self, linear_progress: f64) -> f64 {
308 if !self.is_specified {
309 linear_progress
310 } else {
311 self.set_parameter_from_x(linear_progress);
312
313 KeySpline::get_bezier_value(self.by, self.cy, self.parameter)
314 }
315 }
316
317 fn get_x_and_dx(&self, t: f64) -> (f64, f64) {
318 let s = 1.0 - t;
319 let t2 = t * t;
320 let s2 = s * s;
321
322 let x = self.bx * t * s2 + self.cx * t2 * s + t2 * t;
323 let dx = self.bx * s2 + self.cx_bx * s * t + self.three_cx * t2;
324 (x, dx)
325 }
326
327 fn set_parameter_from_x(&mut self, time: f64) {
328 let mut bottom = 0.0;
329 let mut top = 1.0;
330
331 if time == 0.0 {
332 self.parameter = 0.0;
333 } else if time == 1.0 {
334 self.parameter = 1.0;
335 } else {
336 let mut parameter = self.parameter;
337
338 while top - bottom > FUZZ {
339 let (x, dx) = self.get_x_and_dx(parameter);
340 let absdx = dx.abs();
341
342 if x > time {
343 top = parameter;
344 } else {
345 bottom = parameter;
346 }
347
348 if (x - time).abs() < ACCURACY * absdx {
349 break;
350 }
351
352 if absdx > FUZZ {
353 let next = parameter - (x - time) / dx;
354
355 if next >= top {
356 parameter = (parameter + top) / 2.0;
357 } else if next <= bottom {
358 parameter = (parameter + bottom) / 2.0;
359 } else {
360 parameter = next;
361 }
362 } else {
363 parameter = (bottom + top) / 2.0;
364 }
365 }
366
367 self.parameter = parameter;
368 }
369 }
370}
371
372#[cfg(test)]
373mod solver_tests {
374 use super::*;
376
377 #[test]
378 fn the_solver_follows_the_key_spline() {
379 for (x1, y1, x2, y2) in [(0.25, 0.1, 0.25, 1.0), (0.42, 0.0, 0.58, 1.0), (0.0, 0.0, 1.0, 1.0), (0.1, 0.9, 0.9, 0.1)]
380 {
381 let spline = KeySpline::with_points(x1, y1, x2, y2);
382 let mut solver = KeySplineSolver::new(x1, y1, x2, y2);
383
384 for step in 0..=100 {
385 let progress = f64::from(step) / 100.0;
386 assert_eq!(spline.get_spline_progress(progress), solver.get_spline_progress(progress));
387 }
388 for step in (0..=100).rev() {
390 let progress = f64::from(step) / 100.0;
391 assert_eq!(spline.get_spline_progress(progress), solver.get_spline_progress(progress));
392 }
393 }
394 }
395}