Skip to main content

geng_utils/torus/
position.rs

1use geng::prelude::*;
2
3/// A position on a torus.
4#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
5pub struct PositionTorus<T> {
6    pos: vec2<T>,
7    world_size: vec2<T>,
8}
9
10impl<T: Num> PositionTorus<T> {
11    /// Normalize a position in the world to a torus.
12    pub fn from_world(mut pos: vec2<T>, world_size: vec2<T>) -> Self {
13        while pos.y < T::ZERO {
14            pos.y += world_size.y;
15        }
16        while pos.y > world_size.y {
17            pos.y -= world_size.y;
18        }
19        while pos.x < T::ZERO {
20            pos.x += world_size.x;
21            // pos.y = world_size.y - pos.y;
22        }
23        while pos.x > world_size.x {
24            pos.x -= world_size.x;
25            // pos.y = world_size.y - pos.y;
26        }
27
28        Self { pos, world_size }
29    }
30
31    /// The zero position.
32    pub fn zero(world_size: vec2<T>) -> Self {
33        Self::from_world(vec2::ZERO, world_size)
34    }
35
36    /// Get a random position on a torus of a given size.
37    pub fn random(rng: &mut impl Rng, world_size: vec2<T>) -> Self {
38        Self::from_world(
39            vec2(
40                rng.gen_range(T::ZERO..=world_size.x),
41                rng.gen_range(T::ZERO..=world_size.y),
42            ),
43            world_size,
44        )
45    }
46
47    pub fn to_world(self) -> vec2<T> {
48        self.pos
49    }
50
51    pub fn world_size(self) -> vec2<T> {
52        self.world_size
53    }
54
55    /// Returns a delta from zero to `self`.
56    pub fn as_dir(self) -> vec2<T> {
57        Self::zero(self.world_size).delta_to(self)
58    }
59
60    /// Shift a position in-place by the given delta.
61    pub fn shift(&mut self, delta: vec2<T>) {
62        *self = self.shifted(delta);
63    }
64
65    /// Return a position shifted by the given delta
66    pub fn shifted(self, delta: vec2<T>) -> Self {
67        Self::from_world(self.to_world() + delta, self.world_size)
68    }
69
70    /// Calculate the delta from `self` to `towards`.
71    /// Practically a subtraction operator.
72    ///
73    /// Panics if two positions don't have the same `world_size`.
74    pub fn delta_to(self, towards: Self) -> vec2<T> {
75        assert_eq!(
76            self.world_size, towards.world_size,
77            "two positions are not from the same world"
78        );
79
80        let mut delta = towards.to_world() - self.to_world();
81
82        // Normalize delta
83        let two = T::ONE + T::ONE;
84        if delta.x.abs() * two > self.world_size.x {
85            let signum = delta.x.signum();
86            delta.x -= self.world_size.x * signum;
87        }
88        if delta.y.abs() * two > self.world_size.y {
89            let signum = delta.y.signum();
90            delta.y -= self.world_size.y * signum;
91        }
92
93        delta
94    }
95}
96
97impl<T: Float> PositionTorus<T> {
98    pub fn to_world_f32(self) -> vec2<f32> {
99        self.pos.map(T::as_f32)
100    }
101
102    /// Calculate the distance between two points.
103    ///
104    /// Panics if two positions don't have the same `world_size`.
105    pub fn distance(self, other: Self) -> T {
106        self.delta_to(other).len()
107    }
108}
109
110#[test]
111fn test_delta() {
112    let world_size = vec2(20.0, 10.0);
113    let a = PositionTorus::from_world(vec2(15.0, 1.0), world_size);
114    let b = PositionTorus::from_world(vec2(10.0, 5.0), world_size);
115    let c = PositionTorus::from_world(vec2(2.0, 7.0), world_size);
116    assert_eq!(a.delta_to(b), vec2(-5.0, 4.0));
117    assert_eq!(a.delta_to(c), vec2(7.0, -4.0));
118    assert_eq!(b.delta_to(c), vec2(-8.0, 2.0));
119}