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