1use core::fmt;
4
5use crate::impl_option;
6
7#[derive(Debug, Clone, PartialEq)]
8#[repr(C)]
9pub struct InterpolateResolver {
10 pub interpolate_func: AnimationInterpolationFunction,
11 pub parent_rect_width: f32,
12 pub parent_rect_height: f32,
13 pub current_rect_width: f32,
14 pub current_rect_height: f32,
15}
16
17#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
18#[repr(C)]
19pub struct SvgPoint {
20 pub x: f32,
21 pub y: f32,
22}
23
24#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
25#[repr(C)]
26pub struct SvgCubicCurve {
27 pub start: SvgPoint,
28 pub ctrl_1: SvgPoint,
29 pub ctrl_2: SvgPoint,
30 pub end: SvgPoint,
31}
32
33#[derive(Debug, Copy, Clone, PartialEq)]
35#[repr(C, u8)]
36pub enum AnimationInterpolationFunction {
37 Ease,
38 Linear,
39 EaseIn,
40 EaseOut,
41 EaseInOut,
42 CubicBezier(SvgCubicCurve),
43}
44
45#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
46#[repr(C)]
47pub struct SvgRect {
48 pub width: f32,
49 pub height: f32,
50 pub x: f32,
51 pub y: f32,
52 pub radius_top_left: f32,
53 pub radius_top_right: f32,
54 pub radius_bottom_left: f32,
55 pub radius_bottom_right: f32,
56}
57
58#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
59#[repr(C)]
60pub struct SvgVector {
61 pub x: f64,
62 pub y: f64,
63}
64
65#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
66#[repr(C)]
67pub struct SvgQuadraticCurve {
68 pub start: SvgPoint,
69 pub ctrl: SvgPoint,
70 pub end: SvgPoint,
71}
72
73impl_option!(
76 SvgPoint,
77 OptionSvgPoint,
78 [Debug, Clone, PartialEq, PartialOrd]
79);
80
81impl SvgPoint {
82 #[inline]
83 pub fn distance(&self, other: Self) -> f64 {
84 let dx = other.x - self.x;
85 let dy = other.y - self.y;
86 libm::hypotf(dx, dy) as f64
87 }
88}
89
90impl SvgRect {
91 pub fn union_with(&mut self, other: &Self) {
92 let self_max_x = self.x + self.width;
93 let self_max_y = self.y + self.height;
94 let self_min_x = self.x;
95 let self_min_y = self.y;
96
97 let other_max_x = other.x + other.width;
98 let other_max_y = other.y + other.height;
99 let other_min_x = other.x;
100 let other_min_y = other.y;
101
102 let max_x = self_max_x.max(other_max_x);
103 let max_y = self_max_y.max(other_max_y);
104 let min_x = self_min_x.min(other_min_x);
105 let min_y = self_min_y.min(other_min_y);
106
107 self.x = min_x;
108 self.y = min_y;
109 self.width = max_x - min_x;
110 self.height = max_y - min_y;
111 }
112
113 pub fn contains_point(&self, point: SvgPoint) -> bool {
116 point.x > self.x
117 && point.x < self.x + self.width
118 && point.y > self.y
119 && point.y < self.y + self.height
120 }
121
122 pub fn expand(
124 &self,
125 padding_top: f32,
126 padding_bottom: f32,
127 padding_left: f32,
128 padding_right: f32,
129 ) -> SvgRect {
130 SvgRect {
131 width: self.width + padding_left + padding_right,
132 height: self.height + padding_top + padding_bottom,
133 x: self.x - padding_left,
134 y: self.y - padding_top,
135 ..*self
136 }
137 }
138
139 pub fn get_center(&self) -> SvgPoint {
140 SvgPoint {
141 x: self.x + (self.width / 2.0),
142 y: self.y + (self.height / 2.0),
143 }
144 }
145}
146
147const STEP_SIZE: usize = 20;
148const STEP_SIZE_F64: f64 = 0.05;
149
150impl SvgCubicCurve {
151 pub fn reverse(&mut self) {
152 let temp = self.start;
153 self.start = self.end;
154 self.end = temp;
155 let temp = self.ctrl_1;
156 self.ctrl_1 = self.ctrl_2;
157 self.ctrl_2 = temp;
158 }
159
160 pub fn get_start(&self) -> SvgPoint {
161 self.start
162 }
163 pub fn get_end(&self) -> SvgPoint {
164 self.end
165 }
166
167 pub fn get_x_at_t(&self, t: f64) -> f64 {
169 let c_x = 3.0 * (self.ctrl_1.x as f64 - self.start.x as f64);
170 let b_x = 3.0 * (self.ctrl_2.x as f64 - self.ctrl_1.x as f64) - c_x;
171 let a_x = self.end.x as f64 - self.start.x as f64 - c_x - b_x;
172
173 (a_x * t * t * t) + (b_x * t * t) + (c_x * t) + self.start.x as f64
174 }
175
176 pub fn get_y_at_t(&self, t: f64) -> f64 {
177 let c_y = 3.0 * (self.ctrl_1.y as f64 - self.start.y as f64);
178 let b_y = 3.0 * (self.ctrl_2.y as f64 - self.ctrl_1.y as f64) - c_y;
179 let a_y = self.end.y as f64 - self.start.y as f64 - c_y - b_y;
180
181 (a_y * t * t * t) + (b_y * t * t) + (c_y * t) + self.start.y as f64
182 }
183
184 pub fn get_length(&self) -> f64 {
185 let mut arc_length = 0.0;
187 let mut prev_point = self.get_start();
188
189 for i in 0..STEP_SIZE {
190 let t_next = (i + 1) as f64 * STEP_SIZE_F64;
191 let next_point = SvgPoint {
192 x: self.get_x_at_t(t_next) as f32,
193 y: self.get_y_at_t(t_next) as f32,
194 };
195 arc_length += prev_point.distance(next_point);
196 prev_point = next_point;
197 }
198
199 arc_length
200 }
201
202 pub fn get_t_at_offset(&self, offset: f64) -> f64 {
203 let mut arc_length = 0.0;
207 let mut t_current = 0.0;
208 let mut prev_point = self.get_start();
209
210 for i in 0..STEP_SIZE {
211 let t_next = (i + 1) as f64 * STEP_SIZE_F64;
212 let next_point = SvgPoint {
213 x: self.get_x_at_t(t_next) as f32,
214 y: self.get_y_at_t(t_next) as f32,
215 };
216
217 let distance = prev_point.distance(next_point);
218
219 arc_length += distance;
220
221 if arc_length > offset {
223 let remaining = arc_length - offset;
224 return t_current + (remaining / distance) * STEP_SIZE_F64;
225 }
226
227 prev_point = next_point;
228 t_current = t_next;
229 }
230
231 t_current
232 }
233
234 pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
235 let w0 = SvgPoint {
245 x: self.ctrl_1.x - self.start.x,
246 y: self.ctrl_1.y - self.start.y,
247 };
248
249 let w1 = SvgPoint {
250 x: self.ctrl_2.x - self.ctrl_1.x,
251 y: self.ctrl_2.y - self.ctrl_1.y,
252 };
253
254 let w2 = SvgPoint {
255 x: self.end.x - self.ctrl_2.x,
256 y: self.end.y - self.ctrl_2.y,
257 };
258
259 let quadratic_curve = SvgQuadraticCurve {
260 start: w0,
261 ctrl: w1,
262 end: w2,
263 };
264
265 let tangent_vector = SvgVector {
270 x: quadratic_curve.get_x_at_t(t),
271 y: quadratic_curve.get_y_at_t(t),
272 };
273
274 tangent_vector.normalize()
275 }
276
277 pub fn get_bounds(&self) -> SvgRect {
278 let min_x = self
279 .start
280 .x
281 .min(self.end.x)
282 .min(self.ctrl_1.x)
283 .min(self.ctrl_2.x);
284 let max_x = self
285 .start
286 .x
287 .max(self.end.x)
288 .max(self.ctrl_1.x)
289 .max(self.ctrl_2.x);
290
291 let min_y = self
292 .start
293 .y
294 .min(self.end.y)
295 .min(self.ctrl_1.y)
296 .min(self.ctrl_2.y);
297 let max_y = self
298 .start
299 .y
300 .max(self.end.y)
301 .max(self.ctrl_1.y)
302 .max(self.ctrl_2.y);
303
304 let width = (max_x - min_x).abs();
305 let height = (max_y - min_y).abs();
306
307 SvgRect {
308 width,
309 height,
310 x: min_x,
311 y: min_y,
312 ..SvgRect::default()
313 }
314 }
315}
316
317impl SvgVector {
318 #[inline]
320 pub fn angle_degrees(&self) -> f64 {
321 (-self.y).atan2(self.x).to_degrees()
322 }
323
324 #[inline]
325 #[must_use = "returns a new vector"]
326 pub fn normalize(&self) -> Self {
327 let tangent_length = libm::hypotf(self.x as f32, self.y as f32) as f64;
328 if tangent_length == 0.0 {
329 return Self { x: 0.0, y: 0.0 };
330 }
331 Self {
332 x: self.x / tangent_length,
333 y: self.y / tangent_length,
334 }
335 }
336
337 #[must_use = "returns a new vector"]
339 #[inline]
340 pub fn rotate_90deg_ccw(&self) -> Self {
341 Self {
342 x: -self.y,
343 y: self.x,
344 }
345 }
346}
347
348impl SvgQuadraticCurve {
349 pub fn reverse(&mut self) {
350 let temp = self.start;
351 self.start = self.end;
352 self.end = temp;
353 }
354 pub fn get_start(&self) -> SvgPoint {
355 self.start
356 }
357 pub fn get_end(&self) -> SvgPoint {
358 self.end
359 }
360 pub fn get_bounds(&self) -> SvgRect {
361 let min_x = self.start.x.min(self.end.x).min(self.ctrl.x);
362 let max_x = self.start.x.max(self.end.x).max(self.ctrl.x);
363
364 let min_y = self.start.y.min(self.end.y).min(self.ctrl.y);
365 let max_y = self.start.y.max(self.end.y).max(self.ctrl.y);
366
367 let width = (max_x - min_x).abs();
368 let height = (max_y - min_y).abs();
369
370 SvgRect {
371 width,
372 height,
373 x: min_x,
374 y: min_y,
375 ..SvgRect::default()
376 }
377 }
378
379 pub fn get_x_at_t(&self, t: f64) -> f64 {
380 let one_minus = 1.0 - t;
381 one_minus * one_minus * self.start.x as f64
382 + 2.0 * one_minus * t * self.ctrl.x as f64
383 + t * t * self.end.x as f64
384 }
385
386 pub fn get_y_at_t(&self, t: f64) -> f64 {
387 let one_minus = 1.0 - t;
388 one_minus * one_minus * self.start.y as f64
389 + 2.0 * one_minus * t * self.ctrl.y as f64
390 + t * t * self.end.y as f64
391 }
392
393 pub fn get_length(&self) -> f64 {
394 self.to_cubic().get_length()
395 }
396
397 pub fn get_t_at_offset(&self, offset: f64) -> f64 {
398 self.to_cubic().get_t_at_offset(offset)
399 }
400
401 pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
402 self.to_cubic().get_tangent_vector_at_t(t)
403 }
404
405 fn to_cubic(&self) -> SvgCubicCurve {
406 SvgCubicCurve {
407 start: self.start,
408 ctrl_1: SvgPoint {
409 x: self.start.x + (2.0 / 3.0) * (self.ctrl.x - self.start.x),
410 y: self.start.y + (2.0 / 3.0) * (self.ctrl.y - self.start.y),
411 },
412 ctrl_2: SvgPoint {
413 x: self.end.x + (2.0 / 3.0) * (self.ctrl.x - self.end.x),
414 y: self.end.y + (2.0 / 3.0) * (self.ctrl.y - self.end.y),
415 },
416 end: self.end,
417 }
418 }
419}
420
421impl AnimationInterpolationFunction {
422 pub const fn get_curve(self) -> SvgCubicCurve {
423 match self {
424 AnimationInterpolationFunction::Ease => SvgCubicCurve {
425 start: SvgPoint { x: 0.0, y: 0.0 },
426 ctrl_1: SvgPoint { x: 0.25, y: 0.1 },
427 ctrl_2: SvgPoint { x: 0.25, y: 1.0 },
428 end: SvgPoint { x: 1.0, y: 1.0 },
429 },
430 AnimationInterpolationFunction::Linear => SvgCubicCurve {
431 start: SvgPoint { x: 0.0, y: 0.0 },
432 ctrl_1: SvgPoint { x: 0.0, y: 0.0 },
433 ctrl_2: SvgPoint { x: 1.0, y: 1.0 },
434 end: SvgPoint { x: 1.0, y: 1.0 },
435 },
436 AnimationInterpolationFunction::EaseIn => SvgCubicCurve {
437 start: SvgPoint { x: 0.0, y: 0.0 },
438 ctrl_1: SvgPoint { x: 0.42, y: 0.0 },
439 ctrl_2: SvgPoint { x: 1.0, y: 1.0 },
440 end: SvgPoint { x: 1.0, y: 1.0 },
441 },
442 AnimationInterpolationFunction::EaseOut => SvgCubicCurve {
443 start: SvgPoint { x: 0.0, y: 0.0 },
444 ctrl_1: SvgPoint { x: 0.0, y: 0.0 },
445 ctrl_2: SvgPoint { x: 0.58, y: 1.0 },
446 end: SvgPoint { x: 1.0, y: 1.0 },
447 },
448 AnimationInterpolationFunction::EaseInOut => SvgCubicCurve {
449 start: SvgPoint { x: 0.0, y: 0.0 },
450 ctrl_1: SvgPoint { x: 0.42, y: 0.0 },
451 ctrl_2: SvgPoint { x: 0.58, y: 1.0 },
452 end: SvgPoint { x: 1.0, y: 1.0 },
453 },
454 AnimationInterpolationFunction::CubicBezier(c) => c,
455 }
456 }
457
458 pub fn evaluate(self, t: f64) -> f32 {
459 self.get_curve().get_y_at_t(t) as f32
460 }
461}