Skip to main content

rustsim_geometry/
vec2.rs

1//! 2-D vector helpers.
2//!
3//! [`Vec2`] is a type alias over `[f64; 2]` so it is ABI-compatible with
4//! any `[f64; 2]` used elsewhere in the workspace.
5
6/// 2-D vector in metres (or any consistent unit).
7pub type Vec2 = [f64; 2];
8
9/// `a + b`.
10#[inline]
11pub fn add(a: Vec2, b: Vec2) -> Vec2 {
12    [a[0] + b[0], a[1] + b[1]]
13}
14
15/// `a - b`.
16#[inline]
17pub fn sub(a: Vec2, b: Vec2) -> Vec2 {
18    [a[0] - b[0], a[1] - b[1]]
19}
20
21/// `a * s`.
22#[inline]
23pub fn scale(a: Vec2, s: f64) -> Vec2 {
24    [a[0] * s, a[1] * s]
25}
26
27/// `a · b`.
28#[inline]
29pub fn dot(a: Vec2, b: Vec2) -> f64 {
30    a[0] * b[0] + a[1] * b[1]
31}
32
33/// 2-D cross product (scalar, = `a × b` z-component).
34#[inline]
35pub fn cross(a: Vec2, b: Vec2) -> f64 {
36    a[0] * b[1] - a[1] * b[0]
37}
38
39/// `|a|`.
40#[inline]
41pub fn norm(a: Vec2) -> f64 {
42    (a[0] * a[0] + a[1] * a[1]).sqrt()
43}
44
45/// `|a|²`.
46#[inline]
47pub fn norm_squared(a: Vec2) -> f64 {
48    a[0] * a[0] + a[1] * a[1]
49}
50
51/// Unit vector or `[0, 0]` if `|a|` is below machine epsilon.
52#[inline]
53pub fn normalize(a: Vec2) -> Vec2 {
54    let n = norm(a);
55    if n < 1e-12 {
56        [0.0, 0.0]
57    } else {
58        [a[0] / n, a[1] / n]
59    }
60}
61
62/// Euclidean distance `|a - b|`.
63#[inline]
64pub fn distance(a: Vec2, b: Vec2) -> f64 {
65    norm(sub(a, b))
66}
67
68/// Linear interpolation `a + t * (b - a)`.
69#[inline]
70pub fn lerp(a: Vec2, b: Vec2, t: f64) -> Vec2 {
71    [a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1])]
72}
73
74/// Rotate `v` by `angle` radians (counter-clockwise).
75#[inline]
76pub fn rotate(v: Vec2, angle: f64) -> Vec2 {
77    let (s, c) = angle.sin_cos();
78    [c * v[0] - s * v[1], s * v[0] + c * v[1]]
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn basic_ops() {
87        assert_eq!(add([1.0, 2.0], [3.0, 4.0]), [4.0, 6.0]);
88        assert_eq!(sub([3.0, 4.0], [1.0, 2.0]), [2.0, 2.0]);
89        assert_eq!(scale([1.0, 2.0], 3.0), [3.0, 6.0]);
90        assert_eq!(dot([1.0, 2.0], [3.0, 4.0]), 11.0);
91    }
92
93    #[test]
94    fn normalize_zero_is_zero() {
95        assert_eq!(normalize([0.0, 0.0]), [0.0, 0.0]);
96    }
97
98    #[test]
99    fn rotate_90_degrees() {
100        let r = rotate([1.0, 0.0], std::f64::consts::FRAC_PI_2);
101        assert!((r[0]).abs() < 1e-9);
102        assert!((r[1] - 1.0).abs() < 1e-9);
103    }
104}