1use crate::impl_option;
4
5#[inline]
9#[allow(clippy::cast_precision_loss)]
10const fn idx_to_f64(v: usize) -> f64 {
11 v as f64
12}
13
14#[inline]
17#[allow(clippy::cast_possible_truncation)]
18const fn f64_to_f32(v: f64) -> f32 {
19 v as f32
20}
21
22#[derive(Debug, Copy, Clone, PartialEq)]
24#[repr(C)]
25pub struct InterpolateResolver {
26 pub interpolate_func: AnimationInterpolationFunction,
27 pub parent_rect_width: f32,
28 pub parent_rect_height: f32,
29 pub current_rect_width: f32,
30 pub current_rect_height: f32,
31}
32
33#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
35#[repr(C)]
36pub struct SvgPoint {
37 pub x: f32,
38 pub y: f32,
39}
40
41#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
43#[repr(C)]
44pub struct SvgCubicCurve {
45 pub start: SvgPoint,
46 pub ctrl_1: SvgPoint,
47 pub ctrl_2: SvgPoint,
48 pub end: SvgPoint,
49}
50#[allow(variant_size_differences)] #[derive(Debug, Copy, Clone, PartialEq)]
53#[repr(C, u8)]
54pub enum AnimationInterpolationFunction {
55 Ease,
56 Linear,
57 EaseIn,
58 EaseOut,
59 EaseInOut,
60 CubicBezier(SvgCubicCurve),
61}
62
63#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
65#[repr(C)]
66pub struct SvgRect {
67 pub width: f32,
68 pub height: f32,
69 pub x: f32,
70 pub y: f32,
71 pub radius_top_left: f32,
72 pub radius_top_right: f32,
73 pub radius_bottom_left: f32,
74 pub radius_bottom_right: f32,
75}
76
77#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
79#[repr(C)]
80pub struct SvgVector {
81 pub x: f64,
82 pub y: f64,
83}
84
85#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
87#[repr(C)]
88pub struct SvgQuadraticCurve {
89 pub start: SvgPoint,
90 pub ctrl: SvgPoint,
91 pub end: SvgPoint,
92}
93
94impl_option!(
95 SvgPoint,
96 OptionSvgPoint,
97 [Debug, Clone, PartialEq, PartialOrd]
98);
99
100impl SvgPoint {
101 #[inline]
103 #[must_use] pub const fn new(x: f32, y: f32) -> Self {
104 Self { x, y }
105 }
106
107 #[inline]
109 #[must_use] pub fn distance(&self, other: Self) -> f64 {
110 let dx = other.x - self.x;
111 let dy = other.y - self.y;
112 f64::from(libm::hypotf(dx, dy))
113 }
114}
115
116impl SvgRect {
117 pub fn union_with(&mut self, other: &Self) {
119 let self_max_x = self.x + self.width;
120 let self_max_y = self.y + self.height;
121 let self_min_x = self.x;
122 let self_min_y = self.y;
123
124 let other_max_x = other.x + other.width;
125 let other_max_y = other.y + other.height;
126 let other_min_x = other.x;
127 let other_min_y = other.y;
128
129 let max_x = self_max_x.max(other_max_x);
130 let max_y = self_max_y.max(other_max_y);
131 let min_x = self_min_x.min(other_min_x);
132 let min_y = self_min_y.min(other_min_y);
133
134 self.x = min_x;
135 self.y = min_y;
136 self.width = max_x - min_x;
137 self.height = max_y - min_y;
138 }
139
140 #[must_use] pub fn contains_point(&self, point: SvgPoint) -> bool {
143 point.x > self.x
144 && point.x < self.x + self.width
145 && point.y > self.y
146 && point.y < self.y + self.height
147 }
148
149 #[must_use] pub fn expand(
151 &self,
152 padding_top: f32,
153 padding_bottom: f32,
154 padding_left: f32,
155 padding_right: f32,
156 ) -> Self {
157 Self {
158 width: self.width + padding_left + padding_right,
159 height: self.height + padding_top + padding_bottom,
160 x: self.x - padding_left,
161 y: self.y - padding_top,
162 ..*self
163 }
164 }
165
166 #[must_use] pub fn get_center(&self) -> SvgPoint {
168 SvgPoint {
169 x: self.x + (self.width / 2.0),
170 y: self.y + (self.height / 2.0),
171 }
172 }
173}
174
175const STEP_SIZE: usize = 20;
176const STEP_SIZE_F64: f64 = 0.05;
177
178#[allow(clippy::suboptimal_flops)]
182impl SvgCubicCurve {
183 #[inline]
185 #[must_use] pub const fn new(start: SvgPoint, ctrl_1: SvgPoint, ctrl_2: SvgPoint, end: SvgPoint) -> Self {
186 Self { start, ctrl_1, ctrl_2, end }
187 }
188
189 pub const fn reverse(&mut self) {
191 core::mem::swap(&mut self.start, &mut self.end);
192 core::mem::swap(&mut self.ctrl_1, &mut self.ctrl_2);
193 }
194
195 #[must_use] pub const fn get_start(&self) -> SvgPoint {
197 self.start
198 }
199 #[must_use] pub const fn get_end(&self) -> SvgPoint {
201 self.end
202 }
203
204 #[must_use] pub fn get_x_at_t(&self, t: f64) -> f64 {
206 let c_x = 3.0 * (f64::from(self.ctrl_1.x) - f64::from(self.start.x));
207 let b_x = 3.0 * (f64::from(self.ctrl_2.x) - f64::from(self.ctrl_1.x)) - c_x;
208 let a_x = f64::from(self.end.x) - f64::from(self.start.x) - c_x - b_x;
209
210 (a_x * t * t * t) + (b_x * t * t) + (c_x * t) + f64::from(self.start.x)
211 }
212
213 #[must_use] pub fn get_y_at_t(&self, t: f64) -> f64 {
215 let c_y = 3.0 * (f64::from(self.ctrl_1.y) - f64::from(self.start.y));
216 let b_y = 3.0 * (f64::from(self.ctrl_2.y) - f64::from(self.ctrl_1.y)) - c_y;
217 let a_y = f64::from(self.end.y) - f64::from(self.start.y) - c_y - b_y;
218
219 (a_y * t * t * t) + (b_y * t * t) + (c_y * t) + f64::from(self.start.y)
220 }
221
222 #[must_use] pub fn get_length(&self) -> f64 {
224 let mut arc_length = 0.0;
226 let mut prev_point = self.get_start();
227
228 for i in 0..STEP_SIZE {
229 let t_next = idx_to_f64(i + 1) * STEP_SIZE_F64;
230 let next_point = SvgPoint {
231 x: f64_to_f32(self.get_x_at_t(t_next)),
232 y: f64_to_f32(self.get_y_at_t(t_next)),
233 };
234 arc_length += prev_point.distance(next_point);
235 prev_point = next_point;
236 }
237
238 arc_length
239 }
240
241 #[must_use] pub fn get_t_at_offset(&self, offset: f64) -> f64 {
243 let mut arc_length = 0.0;
247 let mut t_current = 0.0;
248 let mut prev_point = self.get_start();
249
250 for i in 0..STEP_SIZE {
251 let t_next = idx_to_f64(i + 1) * STEP_SIZE_F64;
252 let next_point = SvgPoint {
253 x: f64_to_f32(self.get_x_at_t(t_next)),
254 y: f64_to_f32(self.get_y_at_t(t_next)),
255 };
256
257 let distance = prev_point.distance(next_point);
258
259 arc_length += distance;
260
261 if arc_length > offset {
263 let remaining = arc_length - offset;
264 return t_current + ((distance - remaining) / distance) * STEP_SIZE_F64;
265 }
266
267 prev_point = next_point;
268 t_current = t_next;
269 }
270
271 t_current
272 }
273
274 #[must_use] pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
276 let w0 = SvgPoint {
286 x: self.ctrl_1.x - self.start.x,
287 y: self.ctrl_1.y - self.start.y,
288 };
289
290 let w1 = SvgPoint {
291 x: self.ctrl_2.x - self.ctrl_1.x,
292 y: self.ctrl_2.y - self.ctrl_1.y,
293 };
294
295 let w2 = SvgPoint {
296 x: self.end.x - self.ctrl_2.x,
297 y: self.end.y - self.ctrl_2.y,
298 };
299
300 let quadratic_curve = SvgQuadraticCurve {
301 start: w0,
302 ctrl: w1,
303 end: w2,
304 };
305
306 let tangent_vector = SvgVector {
311 x: quadratic_curve.get_x_at_t(t),
312 y: quadratic_curve.get_y_at_t(t),
313 };
314
315 tangent_vector.normalize()
316 }
317
318 #[must_use] pub fn get_bounds(&self) -> SvgRect {
320 let min_x = self
321 .start
322 .x
323 .min(self.end.x)
324 .min(self.ctrl_1.x)
325 .min(self.ctrl_2.x);
326 let max_x = self
327 .start
328 .x
329 .max(self.end.x)
330 .max(self.ctrl_1.x)
331 .max(self.ctrl_2.x);
332
333 let min_y = self
334 .start
335 .y
336 .min(self.end.y)
337 .min(self.ctrl_1.y)
338 .min(self.ctrl_2.y);
339 let max_y = self
340 .start
341 .y
342 .max(self.end.y)
343 .max(self.ctrl_1.y)
344 .max(self.ctrl_2.y);
345
346 let width = (max_x - min_x).abs();
347 let height = (max_y - min_y).abs();
348
349 SvgRect {
350 width,
351 height,
352 x: min_x,
353 y: min_y,
354 ..SvgRect::default()
355 }
356 }
357}
358
359impl SvgVector {
360 #[inline]
362 #[must_use] pub fn angle_degrees(&self) -> f64 {
363 (-self.y).atan2(self.x).to_degrees()
364 }
365
366 #[inline]
368 #[must_use = "returns a new vector"]
369 pub fn normalize(&self) -> Self {
370 let tangent_length = libm::hypot(self.x, self.y);
371 if tangent_length == 0.0 {
372 return Self { x: 0.0, y: 0.0 };
373 }
374 Self {
375 x: self.x / tangent_length,
376 y: self.y / tangent_length,
377 }
378 }
379
380 #[must_use = "returns a new vector"]
382 #[inline]
383 pub fn rotate_90deg_ccw(&self) -> Self {
384 Self {
385 x: -self.y,
386 y: self.x,
387 }
388 }
389}
390
391#[allow(clippy::suboptimal_flops)]
393impl SvgQuadraticCurve {
394 #[inline]
396 #[must_use] pub const fn new(start: SvgPoint, ctrl: SvgPoint, end: SvgPoint) -> Self {
397 Self { start, ctrl, end }
398 }
399
400 pub const fn reverse(&mut self) {
402 core::mem::swap(&mut self.start, &mut self.end);
403 }
404 #[must_use] pub const fn get_start(&self) -> SvgPoint {
406 self.start
407 }
408 #[must_use] pub const fn get_end(&self) -> SvgPoint {
410 self.end
411 }
412 #[must_use] pub fn get_bounds(&self) -> SvgRect {
414 let min_x = self.start.x.min(self.end.x).min(self.ctrl.x);
415 let max_x = self.start.x.max(self.end.x).max(self.ctrl.x);
416
417 let min_y = self.start.y.min(self.end.y).min(self.ctrl.y);
418 let max_y = self.start.y.max(self.end.y).max(self.ctrl.y);
419
420 let width = (max_x - min_x).abs();
421 let height = (max_y - min_y).abs();
422
423 SvgRect {
424 width,
425 height,
426 x: min_x,
427 y: min_y,
428 ..SvgRect::default()
429 }
430 }
431
432 #[must_use] pub fn get_x_at_t(&self, t: f64) -> f64 {
434 let one_minus = 1.0 - t;
435 one_minus * one_minus * f64::from(self.start.x)
436 + 2.0 * one_minus * t * f64::from(self.ctrl.x)
437 + t * t * f64::from(self.end.x)
438 }
439
440 #[must_use] pub fn get_y_at_t(&self, t: f64) -> f64 {
442 let one_minus = 1.0 - t;
443 one_minus * one_minus * f64::from(self.start.y)
444 + 2.0 * one_minus * t * f64::from(self.ctrl.y)
445 + t * t * f64::from(self.end.y)
446 }
447
448 #[must_use] pub fn get_length(&self) -> f64 {
450 self.to_cubic().get_length()
451 }
452
453 #[must_use] pub fn get_t_at_offset(&self, offset: f64) -> f64 {
455 self.to_cubic().get_t_at_offset(offset)
456 }
457
458 #[must_use] pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
460 self.to_cubic().get_tangent_vector_at_t(t)
461 }
462
463 fn to_cubic(self) -> SvgCubicCurve {
465 SvgCubicCurve {
466 start: self.start,
467 ctrl_1: SvgPoint {
468 x: self.start.x + (2.0 / 3.0) * (self.ctrl.x - self.start.x),
469 y: self.start.y + (2.0 / 3.0) * (self.ctrl.y - self.start.y),
470 },
471 ctrl_2: SvgPoint {
472 x: self.end.x + (2.0 / 3.0) * (self.ctrl.x - self.end.x),
473 y: self.end.y + (2.0 / 3.0) * (self.ctrl.y - self.end.y),
474 },
475 end: self.end,
476 }
477 }
478}
479
480impl AnimationInterpolationFunction {
481 #[must_use]
483 pub const fn get_curve(self) -> SvgCubicCurve {
484 match self {
485 Self::Ease => SvgCubicCurve {
486 start: SvgPoint { x: 0.0, y: 0.0 },
487 ctrl_1: SvgPoint { x: 0.25, y: 0.1 },
488 ctrl_2: SvgPoint { x: 0.25, y: 1.0 },
489 end: SvgPoint { x: 1.0, y: 1.0 },
490 },
491 Self::Linear => SvgCubicCurve {
492 start: SvgPoint { x: 0.0, y: 0.0 },
493 ctrl_1: SvgPoint { x: 0.0, y: 0.0 },
494 ctrl_2: SvgPoint { x: 1.0, y: 1.0 },
495 end: SvgPoint { x: 1.0, y: 1.0 },
496 },
497 Self::EaseIn => SvgCubicCurve {
498 start: SvgPoint { x: 0.0, y: 0.0 },
499 ctrl_1: SvgPoint { x: 0.42, y: 0.0 },
500 ctrl_2: SvgPoint { x: 1.0, y: 1.0 },
501 end: SvgPoint { x: 1.0, y: 1.0 },
502 },
503 Self::EaseOut => SvgCubicCurve {
504 start: SvgPoint { x: 0.0, y: 0.0 },
505 ctrl_1: SvgPoint { x: 0.0, y: 0.0 },
506 ctrl_2: SvgPoint { x: 0.58, y: 1.0 },
507 end: SvgPoint { x: 1.0, y: 1.0 },
508 },
509 Self::EaseInOut => SvgCubicCurve {
510 start: SvgPoint { x: 0.0, y: 0.0 },
511 ctrl_1: SvgPoint { x: 0.42, y: 0.0 },
512 ctrl_2: SvgPoint { x: 0.58, y: 1.0 },
513 end: SvgPoint { x: 1.0, y: 1.0 },
514 },
515 Self::CubicBezier(c) => c,
516 }
517 }
518
519 #[must_use] pub fn evaluate(self, t: f64) -> f32 {
521 f64_to_f32(self.get_curve().get_y_at_t(t))
522 }
523}