rive_rs/animation/
cubic_interpolator.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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}