veloren_common/spiral.rs
1use vek::*;
2
3/// An iterator of coordinates that create a rectangular spiral out from the
4/// origin
5#[derive(Clone)]
6pub struct Spiral2d {
7 layer: i32,
8 i: i32,
9}
10
11impl Spiral2d {
12 #[expect(clippy::new_without_default)]
13 /// Creates a new spiral starting at the origin
14 pub fn new() -> Self { Self { layer: 0, i: 0 } }
15
16 /// Creates an iterator over points in a spiral starting at the origin and
17 /// going out to some radius
18 pub fn with_radius(radius: i32) -> impl Iterator<Item = Vec2<i32>> {
19 Self::new()
20 .take((radius * 2 + 1).pow(2) as usize)
21 .filter(move |pos| pos.magnitude_squared() < (radius + 1).pow(2))
22 }
23
24 /// Creates an iterator over points in the edge of a circle of some radius
25 pub fn with_edge_radius(radius: i32) -> impl Iterator<Item = Vec2<i32>> {
26 Self::new()
27 .take((radius * 2 + 1).pow(2) as usize)
28 .filter(move |pos| pos.magnitude_squared() < (radius + 1).pow(2))
29 .filter(move |pos| pos.magnitude_squared() >= radius.pow(2))
30 }
31
32 /// Creates an iterator over points in the margin between two squares,
33 /// inclusive of the inner_radius and exclusive of the outer_radius
34 /// where outer_radius = inner_radius + margin
35 /**
36 Spiral2d iterates over the points in a square spiral pattern starting at the bottom left.
37 In the ring spiral, the iteration starts at the bottom left of the inner square and
38 does not include the outer square (if you think of the outer square as inner_radius + margin).
39 +-----------------------+
40 | Margin |
41 | +-----------+ |
42 | | | |
43 | | Not | |
44 | | Included | |
45 | | | |
46 | +-----------+ |
47 | |
48 +-----------------------+
49 For example, Spiral2d::with_ring(1, 2) yields the following output:
50 Vec2 { x: -1, y: -1 }
51 Vec2 { x: 0, y: -1 }
52 Vec2 { x: 1, y: -1 }
53 Vec2 { x: 1, y: 0 }
54 Vec2 { x: 1, y: 1 }
55 Vec2 { x: 0, y: 1 }
56 Vec2 { x: -1, y: 1 }
57 Vec2 { x: -1, y: 0 }
58 Vec2 { x: -2, y: -2 }
59 Vec2 { x: -1, y: -2 }
60 Vec2 { x: 0, y: -2 }
61 Vec2 { x: 1, y: -2 }
62 Vec2 { x: 2, y: -2 }
63 Vec2 { x: 2, y: -1 }
64 Vec2 { x: 2, y: 0 }
65 Vec2 { x: 2, y: 1 }
66 Vec2 { x: 2, y: 2 }
67 Vec2 { x: 1, y: 2 }
68 Vec2 { x: 0, y: 2 }
69 Vec2 { x: -1, y: 2 }
70 Vec2 { x: -2, y: 2 }
71 Vec2 { x: -2, y: 1 }
72 Vec2 { x: -2, y: 0 }
73 Vec2 { x: -2, y: -1 }
74 Run the first test below to see this output.
75 **/
76 pub fn with_ring(inner_radius: u32, margin: u32) -> impl Iterator<Item = Vec2<i32>> {
77 let outer_radius: u32 = inner_radius + margin - 1;
78 let adjusted_inner_radius = if inner_radius > 0 {
79 inner_radius - 1
80 } else {
81 0
82 };
83 Spiral2d {
84 layer: inner_radius as i32,
85 i: 0,
86 }
87 .take(
88 (outer_radius * 2 + 1).pow(2) as usize
89 - (adjusted_inner_radius * 2 + 1).pow(2) as usize,
90 )
91 }
92}
93
94impl Iterator for Spiral2d {
95 type Item = Vec2<i32>;
96
97 #[expect(clippy::erasing_op, clippy::identity_op)]
98 fn next(&mut self) -> Option<Self::Item> {
99 let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1);
100 if self.i >= layer_size {
101 self.layer += 1;
102 self.i = 0;
103 }
104 let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1);
105
106 let pos = Vec2::new(
107 -self.layer + (self.i - (layer_size / 4) * 0).clamp(0, self.layer * 2)
108 - (self.i - (layer_size / 4) * 2).clamp(0, self.layer * 2),
109 -self.layer + (self.i - (layer_size / 4) * 1).clamp(0, self.layer * 2)
110 - (self.i - (layer_size / 4) * 3).clamp(0, self.layer * 2),
111 );
112
113 self.i += 1;
114
115 Some(pos)
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn print_spiral_ring() {
125 let spiral = Spiral2d::with_ring(1, 2);
126 for pos in spiral {
127 println!("{:?}", pos);
128 }
129 }
130
131 #[test]
132 fn empty_spiral_ring() {
133 assert_eq!(Spiral2d::with_ring(0, 1).count(), 0);
134 assert_eq!(Spiral2d::with_ring(0, 2).count(), 8);
135 }
136
137 #[test]
138 fn minimum_spiral_ring() {
139 let min_spiral_ring: Vec<Vec2<i32>> = vec![
140 Vec2::new(-1, -1),
141 Vec2::new(0, -1),
142 Vec2::new(1, -1),
143 Vec2::new(1, 0),
144 Vec2::new(1, 1),
145 Vec2::new(0, 1),
146 Vec2::new(-1, 1),
147 Vec2::new(-1, 0),
148 ];
149 let result: Vec<Vec2<i32>> = Spiral2d::with_ring(1, 1).collect();
150 assert_eq!(result, min_spiral_ring);
151 }
152}