1
//! SVG geometry primitives (points, curves, rects, vectors) and animation interpolation functions.
2

            
3
use crate::impl_option;
4

            
5
/// Holds context needed to resolve animation interpolation relative to parent and current rects.
6
#[derive(Debug, Clone, PartialEq)]
7
#[repr(C)]
8
pub 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
/// A 2D point with f32 coordinates, used in SVG paths and bezier curves.
17
#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
18
#[repr(C)]
19
pub struct SvgPoint {
20
    pub x: f32,
21
    pub y: f32,
22
}
23

            
24
/// A cubic bezier curve defined by start, two control points, and end point.
25
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
26
#[repr(C)]
27
pub struct SvgCubicCurve {
28
    pub start: SvgPoint,
29
    pub ctrl_1: SvgPoint,
30
    pub ctrl_2: SvgPoint,
31
    pub end: SvgPoint,
32
}
33

            
34
/// Represents an animation timing function.
35
#[derive(Debug, Copy, Clone, PartialEq)]
36
#[repr(C, u8)]
37
pub enum AnimationInterpolationFunction {
38
    Ease,
39
    Linear,
40
    EaseIn,
41
    EaseOut,
42
    EaseInOut,
43
    CubicBezier(SvgCubicCurve),
44
}
45

            
46
/// An axis-aligned rectangle with optional rounded corners.
47
#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
48
#[repr(C)]
49
pub 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
/// A 2D vector with f64 coordinates, used for tangent and direction calculations.
61
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
62
#[repr(C)]
63
pub struct SvgVector {
64
    pub x: f64,
65
    pub y: f64,
66
}
67

            
68
/// A quadratic bezier curve defined by start, one control point, and end point.
69
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
70
#[repr(C)]
71
pub struct SvgQuadraticCurve {
72
    pub start: SvgPoint,
73
    pub ctrl: SvgPoint,
74
    pub end: SvgPoint,
75
}
76

            
77
impl_option!(
78
    SvgPoint,
79
    OptionSvgPoint,
80
    [Debug, Clone, PartialEq, PartialOrd]
81
);
82

            
83
impl SvgPoint {
84
    /// Creates a new SvgPoint from x and y coordinates
85
    #[inline]
86
    pub const fn new(x: f32, y: f32) -> Self {
87
        Self { x, y }
88
    }
89

            
90
    /// Returns the Euclidean distance between this point and `other`.
91
    #[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

            
99
impl SvgRect {
100
    /// Expands this rect to also contain `other`.
101
    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
    /// Note: does not incorporate rounded edges!
124
    /// Origin of x and y is assumed to be the top left corner
125
    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
    /// Expands the rect with a certain amount of padding
133
    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
    /// Returns the center point of the rect.
150
    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

            
158
const STEP_SIZE: usize = 20;
159
const STEP_SIZE_F64: f64 = 0.05;
160

            
161
impl SvgCubicCurve {
162
    /// Creates a new SvgCubicCurve from start, two control points, and end point
163
    #[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
    /// Reverses the curve direction in place, swapping start/end and ctrl_1/ctrl_2.
169
    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
    /// Returns the start point of the curve.
175
42
    pub fn get_start(&self) -> SvgPoint {
176
42
        self.start
177
42
    }
178
    /// Returns the end point of the curve.
179
42
    pub fn get_end(&self) -> SvgPoint {
180
42
        self.end
181
42
    }
182

            
183
    /// Evaluates the x coordinate of the curve at parameter `t` in [0, 1].
184
    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
    /// Evaluates the y coordinate of the curve at parameter `t` in [0, 1].
193
    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
    /// Returns the approximate arc length of the curve using linear sampling.
202
    pub fn get_length(&self) -> f64 {
203
        // NOTE: this arc length parametrization is not very precise, but fast
204
        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
    /// Returns the parameter `t` corresponding to a given arc-length `offset`.
221
    pub fn get_t_at_offset(&self, offset: f64) -> f64 {
222
        // step through the line until the offset is reached,
223
        // then interpolate linearly between the
224
        // current at the last sampled point
225
        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
            // linearly interpolate between last t and current t
241
            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
    /// Returns the normalized tangent vector at parameter `t`.
254
    pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
255
        // 1. Calculate the derivative of the bezier curve.
256
        //
257
        // This means that we go from 4 points to 3 points and redistribute
258
        // the weights of the control points according to the formula:
259
        //
260
        // w'0 = 3 * (w1-w0)
261
        // w'1 = 3 * (w2-w1)
262
        // w'2 = 3 * (w3-w2)
263

            
264
        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
        // The first derivative of a cubic bezier curve is a quadratic
286
        // bezier curve. Luckily, the first derivative is also the tangent
287
        // vector (slope) of the curve. So all we need to do is to sample the
288
        // quadratic curve at t
289
        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
    /// Returns the axis-aligned bounding box of the curve's control points.
298
    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

            
338
impl SvgVector {
339
    /// Returns the angle of the vector in degrees
340
    #[inline]
341
    pub fn angle_degrees(&self) -> f64 {
342
        (-self.y).atan2(self.x).to_degrees()
343
    }
344

            
345
    /// Returns a unit-length vector in the same direction, or zero if the length is zero.
346
    #[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
    /// Rotate the vector 90 degrees counter-clockwise
360
    #[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

            
370
impl SvgQuadraticCurve {
371
    /// Creates a new SvgQuadraticCurve from start, control, and end points
372
    #[inline]
373
    pub const fn new(start: SvgPoint, ctrl: SvgPoint, end: SvgPoint) -> Self {
374
        Self { start, ctrl, end }
375
    }
376

            
377
    /// Reverses the curve direction in place.
378
    pub fn reverse(&mut self) {
379
        core::mem::swap(&mut self.start, &mut self.end);
380
    }
381
    /// Returns the start point of the curve.
382
    pub fn get_start(&self) -> SvgPoint {
383
        self.start
384
    }
385
    /// Returns the end point of the curve.
386
    pub fn get_end(&self) -> SvgPoint {
387
        self.end
388
    }
389
    /// Returns the axis-aligned bounding box of the curve's control points.
390
    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
    /// Evaluates the x coordinate of the curve at parameter `t` in [0, 1].
410
    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
    /// Evaluates the y coordinate of the curve at parameter `t` in [0, 1].
418
    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
    /// Returns the approximate arc length by converting to a cubic curve.
426
    pub fn get_length(&self) -> f64 {
427
        self.to_cubic().get_length()
428
    }
429

            
430
    /// Returns the parameter `t` corresponding to a given arc-length `offset`.
431
    pub fn get_t_at_offset(&self, offset: f64) -> f64 {
432
        self.to_cubic().get_t_at_offset(offset)
433
    }
434

            
435
    /// Returns the normalized tangent vector at parameter `t`.
436
    pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
437
        self.to_cubic().get_tangent_vector_at_t(t)
438
    }
439

            
440
    /// Converts this quadratic curve to an equivalent cubic bezier curve.
441
    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

            
457
impl AnimationInterpolationFunction {
458
    /// Returns the cubic bezier curve corresponding to this timing function.
459
    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
    /// Evaluates the interpolation function at time `t`, returning the eased value.
496
    pub fn evaluate(self, t: f64) -> f32 {
497
        self.get_curve().get_y_at_t(t) as f32
498
    }
499
}