Skip to main content

ndslive_math/
vec2.rs

1// SPDX-License-Identifier: BSD-3-Clause
2//! A plain 2D vector that is *not* WGS84-normalized.
3//!
4//! The geometry layer ([`crate::Wgs84Aabb`] in particular) needs a raw
5//! `(dx, dy)` extent that can legitimately exceed `360` / `180` degrees or be
6//! negative — unlike [`crate::Wgs84`], whose constructor wraps longitude and
7//! clamps latitude. Reusing `Wgs84` for a size/extent would silently corrupt
8//! those values via normalization, so this small struct is used instead.
9//!
10//! Faithful port of `python/src/ndslive/math/vec2.py`.
11
12use std::ops::{Add, Mul, Sub};
13
14/// A raw, un-normalized 2D vector `(x, y)`.
15///
16/// `x` is the longitude/horizontal component, `y` the latitude/vertical
17/// component. Supports component-wise `+` / `-` (with another [`Vec2`]) and
18/// scalar multiplication.
19#[derive(Debug, Clone, Copy, PartialEq)]
20pub struct Vec2 {
21    /// Horizontal (longitude) component.
22    pub x: f64,
23    /// Vertical (latitude) component.
24    pub y: f64,
25}
26
27impl Vec2 {
28    /// Construct a [`Vec2`] from its two components.
29    pub fn new(x: f64, y: f64) -> Self {
30        Vec2 { x, y }
31    }
32
33    /// Return the component-wise absolute value.
34    pub fn abs(self) -> Vec2 {
35        Vec2 {
36            x: self.x.abs(),
37            y: self.y.abs(),
38        }
39    }
40}
41
42impl Default for Vec2 {
43    fn default() -> Self {
44        Vec2 { x: 0.0, y: 0.0 }
45    }
46}
47
48impl Add for Vec2 {
49    type Output = Vec2;
50    fn add(self, other: Vec2) -> Vec2 {
51        Vec2 {
52            x: self.x + other.x,
53            y: self.y + other.y,
54        }
55    }
56}
57
58impl Sub for Vec2 {
59    type Output = Vec2;
60    fn sub(self, other: Vec2) -> Vec2 {
61        Vec2 {
62            x: self.x - other.x,
63            y: self.y - other.y,
64        }
65    }
66}
67
68impl Mul<f64> for Vec2 {
69    type Output = Vec2;
70    /// Component-wise scalar multiplication.
71    fn mul(self, scalar: f64) -> Vec2 {
72        Vec2 {
73            x: self.x * scalar,
74            y: self.y * scalar,
75        }
76    }
77}
78
79impl std::fmt::Display for Vec2 {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        write!(f, "Vec2(x={}, y={})", self.x, self.y)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn arithmetic_and_abs() {
91        let a = Vec2::new(3.0, -4.0);
92        let b = Vec2::new(1.0, 2.0);
93        assert_eq!(a + b, Vec2::new(4.0, -2.0));
94        assert_eq!(a - b, Vec2::new(2.0, -6.0));
95        assert_eq!(a * 2.0, Vec2::new(6.0, -8.0));
96        assert_eq!(a.abs(), Vec2::new(3.0, 4.0));
97        assert_eq!(Vec2::default(), Vec2::new(0.0, 0.0));
98        assert!(format!("{}", a).contains("Vec2(x="));
99    }
100}