graphics/
math.rs

1//! Various methods for computing with vectors.
2
3use std::ops::{Add, Rem};
4
5use vecmath::{self, traits::Float};
6pub use vecmath::{
7    mat2x3_inv as invert, row_mat2x3_mul as multiply, row_mat2x3_transform_pos2 as transform_pos,
8    row_mat2x3_transform_vec2 as transform_vec, vec2_add as add, vec2_cast as cast,
9    vec2_cross as cross, vec2_dot as dot, vec2_mul as mul, vec2_scale as mul_scalar,
10    vec2_square_len as square_len, vec2_sub as sub,
11};
12
13use crate::{
14    modular_index::previous,
15    types::{Area, Color, Line, Polygon, Ray, Rectangle, SourceRectangle, Triangle},
16};
17
18/// The type used for scalars.
19pub type Scalar = f64;
20
21/// The type used for matrices.
22pub type Matrix2d<T = Scalar> = vecmath::Matrix2x3<T>;
23
24/// The type used for 2D vectors.
25pub type Vec2d<T = Scalar> = vecmath::Vector2<T>;
26
27/// The type used for 3D vectors.
28pub type Vec3d<T = Scalar> = vecmath::Vector3<T>;
29
30/// Creates a perpendicular vector.
31#[inline(always)]
32pub fn perp<T>(v: [T; 2]) -> [T; 2]
33where
34    T: Float,
35{
36    [-v[1], v[0]]
37}
38
39/// Transforms from normalized to absolute coordinates.
40///
41/// Computes absolute transform from width and height of viewport.
42/// In absolute coordinates, the x axis points to the right,
43/// and the y axis points down on the screen.
44#[allow(clippy::just_underscores_and_digits)] // Naming convention.
45#[inline(always)]
46pub fn abs_transform<T>(w: T, h: T) -> Matrix2d<T>
47where
48    T: Float,
49{
50    use vecmath::traits::{FromPrimitive, One, Zero};
51
52    let _0: T = Zero::zero();
53    let _1: T = One::one();
54    let _2: T = FromPrimitive::from_f64(2.0);
55    let sx = _2 / w;
56    let sy = -_2 / h;
57    [[sx, _0, -_1], [_0, sy, _1]]
58}
59
60/// Creates a translation matrix.
61#[allow(clippy::just_underscores_and_digits)] // Naming convention.
62#[inline(always)]
63pub fn translate<T>(v: Vec2d<T>) -> Matrix2d<T>
64where
65    T: Float,
66{
67    use vecmath::traits::{One, Zero};
68
69    let _0: T = Zero::zero();
70    let _1: T = One::one();
71    [[_1, _0, v[0]], [_0, _1, v[1]]]
72}
73
74/// Creates a rotation matrix.
75#[allow(clippy::just_underscores_and_digits)] // Naming convention.
76#[inline(always)]
77pub fn rotate_radians<T>(angle: T) -> Matrix2d<T>
78where
79    T: Float,
80{
81    use vecmath::traits::Zero;
82
83    let _0 = Zero::zero();
84    let c = angle.cos();
85    let s = angle.sin();
86    [[c, -s, _0], [s, c, _0]]
87}
88
89/// Orients x axis to look at point.
90///
91/// Leaves x axis unchanged if the
92/// point to look at is the origin.
93#[allow(clippy::just_underscores_and_digits)] // Naming convention.
94#[inline(always)]
95pub fn orient<T>(x: T, y: T) -> Matrix2d<T>
96where
97    T: Float,
98{
99    use vecmath::traits::Zero;
100
101    let _0: T = Zero::zero();
102    let len = x * x + y * y;
103    if len == _0 {
104        return identity();
105    }
106
107    let len = len.sqrt();
108    let c = x / len;
109    let s = y / len;
110    [[c, -s, _0], [s, c, _0]]
111}
112
113/// Create a scale matrix.
114#[allow(clippy::just_underscores_and_digits)] // Naming convention.
115#[inline(always)]
116pub fn scale<T>(sx: T, sy: T) -> Matrix2d<T>
117where
118    T: Float,
119{
120    use vecmath::traits::Zero;
121
122    let _0: T = Zero::zero();
123    [[sx, _0, _0], [_0, sy, _0]]
124}
125
126/// Create a shear matrix.
127#[allow(clippy::just_underscores_and_digits)] // Naming convention.
128#[inline(always)]
129pub fn shear<T>(v: Vec2d<T>) -> Matrix2d<T>
130where
131    T: Float,
132{
133    use vecmath::traits::{One, Zero};
134
135    let _0 = Zero::zero();
136    let _1 = One::one();
137    [[_1, v[0], _0], [v[1], _1, _0]]
138}
139
140/// Create an identity matrix.
141#[allow(clippy::just_underscores_and_digits)] // Naming convention.
142#[inline(always)]
143pub fn identity<T>() -> Matrix2d<T>
144where
145    T: Float,
146{
147    use vecmath::traits::{One, Zero};
148
149    let _0: T = Zero::zero();
150    let _1: T = One::one();
151    [[_1, _0, _0], [_0, _1, _0]]
152}
153
154/// Extract scale information from matrix.
155#[inline(always)]
156pub fn get_scale<T>(m: Matrix2d<T>) -> Vec2d<T>
157where
158    T: Float,
159{
160    [
161        (m[0][0] * m[0][0] + m[1][0] * m[1][0]).sqrt(),
162        (m[0][1] * m[0][1] + m[1][1] * m[1][1]).sqrt(),
163    ]
164}
165
166/// Compute the shortest vector from point to ray.
167/// A ray stores starting point and directional vector.
168#[inline(always)]
169pub fn separation<T>(ray: Ray<T>, v: Vec2d<T>) -> Vec2d<T>
170where
171    T: Float,
172{
173    // Get the directional vector.
174    let (dir_x, dir_y) = (ray[2], ray[3]);
175    // Get displacement vector from point.
176    let (dx, dy) = (ray[0] - v[0], ray[1] - v[1]);
177    // Compute the component of position in ray direction.
178    let dot = dir_x * v[0] + dir_y * v[1];
179    // The directional vector multiplied with
180    // the dot gives us a parallel vector.
181    // When we subtract this from the displacement
182    // we get a vector normal to the ray.
183    // This is the shortest vector from the point to the ray.
184    [dx - dot * dir_x, dy - dot * dir_y]
185}
186
187/// Returns the least separation out of four.
188/// Each seperation can be computed using `separation` function.
189/// The separation returned can be used
190/// to solve collision of rectangles.
191#[inline(always)]
192pub fn least_separation_4<T>(
193    sep1: Vec2d<T>,
194    sep2: Vec2d<T>,
195    sep3: Vec2d<T>,
196    sep4: Vec2d<T>,
197) -> Vec2d<T>
198where
199    T: Float,
200{
201    let dot1 = sep1[0] * sep1[0] + sep1[1] * sep1[1];
202    let dot2 = sep2[0] * sep2[0] + sep2[1] * sep2[1];
203    let dot3 = sep3[0] * sep3[0] + sep3[1] * sep3[1];
204    let dot4 = sep4[0] * sep4[0] + sep4[1] * sep4[1];
205    // Search for the smallest dot product.
206    if dot1 < dot2 {
207        if dot3 < dot4 {
208            if dot1 < dot3 {
209                sep1
210            } else {
211                sep3
212            }
213        } else if dot1 < dot4 {
214            sep1
215        } else {
216            sep4
217        }
218    } else if dot3 < dot4 {
219        if dot2 < dot3 {
220            sep2
221        } else {
222            sep3
223        }
224    } else if dot2 < dot4 {
225        sep2
226    } else {
227        sep4
228    }
229}
230
231/// Shrinks a rectangle by a factor on all sides.
232#[allow(clippy::just_underscores_and_digits)] // Naming convention.
233#[inline(always)]
234pub fn margin_rectangle<T>(rect: Rectangle<T>, m: T) -> Rectangle<T>
235where
236    T: Float,
237{
238    use vecmath::traits::{FromPrimitive, Zero};
239
240    let _0: T = Zero::zero();
241    let _05: T = FromPrimitive::from_f64(0.5);
242    let _2: T = FromPrimitive::from_f64(2.0);
243    let w = rect[2] - _2 * m;
244    let h = rect[3] - _2 * m;
245    let (x, w) = if w < _0 {
246        (rect[0] + _05 * rect[2], _0)
247    } else {
248        (rect[0] + m, w)
249    };
250    let (y, h) = if h < _0 {
251        (rect[1] + _05 * rect[3], _0)
252    } else {
253        (rect[1] + m, h)
254    };
255    [x, y, w, h]
256}
257
258/// Computes a relative rectangle using the rectangle as a tile.
259#[inline(always)]
260pub fn relative_rectangle<T>(rect: Rectangle<T>, v: Vec2d<T>) -> Rectangle<T>
261where
262    T: Float,
263{
264    [
265        rect[0] + v[0] * rect[2],
266        rect[1] + v[1] * rect[3],
267        rect[2],
268        rect[3],
269    ]
270}
271
272/// Computes overlap between two rectangles.
273/// The area of the overlapping rectangle is positive.
274/// A shared edge or corner is not considered overlap.
275#[inline(always)]
276pub fn overlap_rectangle<T>(a: Rectangle<T>, b: Rectangle<T>) -> Option<Rectangle<T>>
277where
278    T: Float,
279{
280    #[inline(always)]
281    fn min<T: Float>(a: T, b: T) -> T {
282        if a < b {
283            a
284        } else {
285            b
286        }
287    }
288
289    #[inline(always)]
290    fn max<T: Float>(a: T, b: T) -> T {
291        if a > b {
292            a
293        } else {
294            b
295        }
296    }
297
298    if a[0] < b[0] + b[2] && a[1] < b[1] + b[3] && b[0] < a[0] + a[2] && b[1] < a[1] + a[3] {
299        let x = max(a[0], b[0]);
300        let y = max(a[1], b[1]);
301        let w = min(a[0] + a[2], b[0] + b[2]) - x;
302        let h = min(a[1] + a[3], b[1] + b[3]) - y;
303        Some([x, y, w, h])
304    } else {
305        None
306    }
307}
308
309#[cfg(test)]
310mod test_overlap {
311    use super::overlap_rectangle;
312
313    #[test]
314    fn overlap() {
315        let a = [0.0, 1.0, 100.0, 101.0];
316        let b = [51.0, 52.0, 102.0, 103.0];
317        let c = overlap_rectangle(a, b).unwrap();
318        assert_eq!(c, [51.0, 52.0, 49.0, 50.0]);
319        let d = overlap_rectangle(a, c).unwrap();
320        assert_eq!(d, c);
321        let e = overlap_rectangle(b, c).unwrap();
322        assert_eq!(e, c);
323    }
324
325    #[test]
326    fn edge() {
327        let a = [0.0, 0.0, 100.0, 100.0];
328        let b = [100.0, 0.0, 100.0, 100.0];
329        let c = overlap_rectangle(a, b);
330        assert_eq!(c, None);
331    }
332}
333
334/// Computes a relative source rectangle using
335/// the source rectangle as a tile.
336#[inline(always)]
337pub fn relative_source_rectangle<T>(rect: SourceRectangle<T>, x: T, y: T) -> SourceRectangle<T>
338where
339    T: Float,
340{
341    let (rx, ry, rw, rh) = (rect[0], rect[1], rect[2], rect[3]);
342    let (x, y) = (rx + x * rw, ry + y * rh);
343    [x, y, rw, rh]
344}
345
346/// Computes modular offset safely for numbers.
347#[inline(always)]
348pub fn modular_offset<T: Add<Output = T> + Rem<Output = T> + Copy>(n: &T, i: &T, off: &T) -> T {
349    (*i + (*off % *n + *n)) % *n
350}
351
352#[cfg(test)]
353mod test_modular_offset {
354    use super::*;
355
356    #[test]
357    fn test_modular_offset() {
358        assert_eq!(modular_offset(&3.0_f64, &0.0_f64, &-1.0_f64), 2.0_f64);
359        assert_eq!(modular_offset(&3.0_f64, &1.0_f64, &-1.0_f64), 0.0_f64);
360        assert_eq!(modular_offset(&3.0_f64, &2.0_f64, &-1.0_f64), 1.0_f64);
361        assert_eq!(modular_offset(&3.0_f64, &3.0_f64, &-1.0_f64), 2.0_f64);
362
363        assert_eq!(modular_offset(&3.0_f64, &0.0_f64, &1.0_f64), 1.0_f64);
364        assert_eq!(modular_offset(&3.0_f64, &1.0_f64, &1.0_f64), 2.0_f64);
365        assert_eq!(modular_offset(&3.0_f64, &2.0_f64, &1.0_f64), 0.0_f64);
366        assert_eq!(modular_offset(&3.0_f64, &3.0_f64, &1.0_f64), 1.0_f64);
367    }
368}
369
370/// Computes the area and centroid of a simple polygon.
371///
372/// A simple polygon is one that does not intersect itself.
373/// Source: http://en.wikipedia.org/wiki/Polygon_area#Simple_polygons
374#[allow(clippy::just_underscores_and_digits)] // Naming convention.
375pub fn area_centroid<T>(polygon: Polygon<'_, T>) -> (Area<T>, Vec2d<T>)
376where
377    T: Float,
378{
379    use vecmath::traits::{FromPrimitive, Zero};
380
381    let _0: T = Zero::zero();
382    let _05: T = FromPrimitive::from_f64(0.5);
383    let _3: T = FromPrimitive::from_f64(3.0);
384    let n = polygon.len();
385    let mut sum = _0;
386    let (mut cx, mut cy) = (_0, _0);
387    for i in 0..n {
388        let qx = polygon[i][0];
389        let qy = polygon[i][1];
390        let p_i = previous(n, i);
391        let px = polygon[p_i][0];
392        let py = polygon[p_i][1];
393        let cross = px * qy - qx * py;
394        cx += (px + qx) * cross;
395        cy += (py + qy) * cross;
396        sum += cross;
397    }
398
399    let area = _05 * sum;
400    // 'cx / (6.0 * area)' = 'cx / (3.0 * sum)'
401    let centroid = [cx / (_3 * sum), cy / (_3 * sum)];
402    (area, centroid)
403}
404
405/// Computes area of a simple polygon.
406///
407/// A simple polygon is one that does not intersect itself.
408#[inline(always)]
409pub fn area<T>(polygon: Polygon<'_, T>) -> T
410where
411    T: Float,
412{
413    let (res, _) = area_centroid(polygon);
414    res
415}
416
417/// Computes centroid of a simple polygon.
418///
419/// A simple polygon is one that does not intersect itself.
420#[inline(always)]
421pub fn centroid<T>(polygon: Polygon<'_, T>) -> Vec2d<T>
422where
423    T: Float,
424{
425    let (_, res) = area_centroid(polygon);
426    res
427}
428
429/// Returns a number that tells which side it is relative to a line.
430///
431/// Computes the cross product of the vector that gives the line
432/// with the vector between point and starting point of line.
433/// One side of the line has opposite sign of the other.
434#[inline(always)]
435pub fn line_side<T>(line: Line<T>, v: Vec2d<T>) -> T
436where
437    T: Float,
438{
439    let (ax, ay) = (line[0], line[1]);
440    let (bx, by) = (line[2], line[3]);
441    (bx - ax) * (v[1] - ay) - (by - ay) * (v[0] - ax)
442}
443
444/// Returns true if point is inside triangle.
445///
446/// This is done by computing a `side` number for each edge.
447/// If the number is inside if it is on the same side for all edges.
448/// Might break for very small triangles.
449#[allow(clippy::just_underscores_and_digits)] // Naming convention.
450pub fn inside_triangle<T>(triangle: Triangle<T>, v: Vec2d<T>) -> bool
451where
452    T: Float,
453{
454    use vecmath::traits::Zero;
455
456    let _0: T = Zero::zero();
457
458    let ax = triangle[0][0];
459    let ay = triangle[0][1];
460    let bx = triangle[1][0];
461    let by = triangle[1][1];
462    let cx = triangle[2][0];
463    let cy = triangle[2][1];
464
465    let ab_side = line_side([ax, ay, bx, by], v);
466    let bc_side = line_side([bx, by, cx, cy], v);
467    let ca_side = line_side([cx, cy, ax, ay], v);
468
469    let ab_positive = ab_side >= _0;
470    let bc_positive = bc_side >= _0;
471    let ca_positive = ca_side >= _0;
472
473    ab_positive == bc_positive && bc_positive == ca_positive
474}
475
476/// Returns true if triangle is clockwise.
477///
478/// This is done by computing which side the third vertex is relative to
479/// the line starting from the first vertex to second vertex.
480///
481/// The triangle is considered clockwise if the third vertex is on the line
482/// between the two first vertices.
483#[allow(clippy::just_underscores_and_digits)] // Naming convention.
484#[inline(always)]
485pub fn triangle_face<T>(triangle: Triangle<T>) -> bool
486where
487    T: Float,
488{
489    use vecmath::traits::Zero;
490
491    let _0 = Zero::zero();
492
493    let ax = triangle[0][0];
494    let ay = triangle[0][1];
495    let bx = triangle[1][0];
496    let by = triangle[1][1];
497    let cx = triangle[2][0];
498    let cy = triangle[2][1];
499
500    let ab_side = line_side([ax, ay, bx, by], [cx, cy]);
501
502    ab_side <= _0
503}
504
505#[cfg(test)]
506mod test_triangle {
507    use super::*;
508
509    #[test]
510    fn test_triangle() {
511        // Triangle counter clock-wise.
512        let tri_1 = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]];
513        // Triangle clock-wise.
514        let tri_2 = [[0.0, 0.0], [1.0, 1.0], [1.0, 0.0]];
515        let (x, y) = (0.5, 0.25);
516        assert!(inside_triangle(tri_1, [x, y]));
517        assert!(inside_triangle(tri_2, [x, y]));
518        assert_eq!(triangle_face(tri_1), false);
519        assert!(triangle_face(tri_2));
520    }
521}
522
523/// Transforms from cartesian coordinates to barycentric.
524#[allow(clippy::just_underscores_and_digits)] // Naming convention.
525#[inline(always)]
526pub fn to_barycentric<T>(triangle: Triangle<T>, pos: Vec2d<T>) -> Vec3d<T>
527where
528    T: Float,
529{
530    use vecmath::traits::One;
531
532    let _1: T = One::one();
533    let x = pos[0];
534    let y = pos[1];
535    let x1 = triangle[0][0];
536    let y1 = triangle[0][1];
537    let x2 = triangle[1][0];
538    let y2 = triangle[1][1];
539    let x3 = triangle[2][0];
540    let y3 = triangle[2][1];
541    let lambda1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3))
542        / ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3));
543    let lambda2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3))
544        / ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3));
545    let lambda3 = _1 - lambda1 - lambda2;
546    [lambda1, lambda2, lambda3]
547}
548
549/// Transforms from barycentric coordinates to cartesian.
550#[inline(always)]
551pub fn from_barycentric<T>(triangle: Triangle<T>, lambda: Vec3d<T>) -> Vec2d<T>
552where
553    T: Float,
554{
555    let x1 = triangle[0][0];
556    let y1 = triangle[0][1];
557    let x2 = triangle[1][0];
558    let y2 = triangle[1][1];
559    let x3 = triangle[2][0];
560    let y3 = triangle[2][1];
561    [
562        lambda[0] * x1 + lambda[1] * x2 + lambda[2] * x3,
563        lambda[0] * y1 + lambda[1] * y2 + lambda[2] * y3,
564    ]
565}
566
567#[cfg(test)]
568mod test_barycentric {
569    use super::*;
570
571    #[test]
572    fn test_barycentric() {
573        let triangle = [[0.0, 0.0], [100.0, 0.0], [0.0, 50.0]];
574        let old_pos = [10.0, 20.0];
575        let b = to_barycentric(triangle, old_pos);
576        let new_pos: Vec2d = from_barycentric(triangle, b);
577        let eps = 0.00001;
578        assert!((new_pos[0] - old_pos[0]).abs() < eps);
579        assert!((new_pos[1] - old_pos[1]).abs() < eps);
580    }
581}
582
583/// Transform color with hue, saturation and value.
584///
585/// Source: http://beesbuzz.biz/code/hsv_color_transforms.php
586#[inline(always)]
587pub fn hsv(color: Color, h_rad: f32, s: f32, v: f32) -> Color {
588    let vsu = v * s * h_rad.cos();
589    let vsw = v * s * h_rad.sin();
590    [
591        (0.299 * v + 0.701 * vsu + 0.168 * vsw) * color[0]
592            + (0.587 * v - 0.587 * vsu + 0.330 * vsw) * color[1]
593            + (0.114 * v - 0.114 * vsu - 0.497 * vsw) * color[2],
594        (0.299 * v - 0.299 * vsu - 0.328 * vsw) * color[0]
595            + (0.587 * v + 0.413 * vsu + 0.035 * vsw) * color[1]
596            + (0.114 * v - 0.114 * vsu + 0.292 * vsw) * color[2],
597        (0.299 * v - 0.3 * vsu + 1.25 * vsw) * color[0]
598            + (0.587 * v - 0.588 * vsu - 1.05 * vsw) * color[1]
599            + (0.114 * v + 0.886 * vsu - 0.203 * vsw) * color[2],
600        color[3],
601    ]
602}