oxigdal_noalloc/
linestring.rs1use crate::{BBox2D, NoAllocError, Point2D, f64_max, f64_min};
7
8pub struct FixedLineString<const N: usize> {
12 points: [Point2D; N],
13 len: usize,
14}
15
16impl<const N: usize> FixedLineString<N> {
17 #[must_use]
19 #[inline]
20 pub fn new() -> Self {
21 Self {
22 points: [Point2D::new(0.0, 0.0); N],
23 len: 0,
24 }
25 }
26
27 #[inline]
30 pub fn push(&mut self, p: Point2D) -> Result<(), NoAllocError> {
31 if self.len >= N {
32 return Err(NoAllocError::CapacityExceeded);
33 }
34 self.points[self.len] = p;
35 self.len += 1;
36 Ok(())
37 }
38
39 #[must_use]
41 #[inline]
42 pub fn len(&self) -> usize {
43 self.len
44 }
45
46 #[must_use]
48 #[inline]
49 pub fn is_empty(&self) -> bool {
50 self.len == 0
51 }
52
53 #[must_use]
55 #[inline]
56 pub fn get(&self, index: usize) -> Option<&Point2D> {
57 if index < self.len {
58 Some(&self.points[index])
59 } else {
60 None
61 }
62 }
63
64 #[must_use]
66 #[inline]
67 pub fn as_slice(&self) -> &[Point2D] {
68 &self.points[..self.len]
69 }
70
71 #[must_use]
75 pub fn total_length(&self) -> f64 {
76 if self.len < 2 {
77 return 0.0;
78 }
79 let pts = self.as_slice();
80 let mut total = 0.0_f64;
81 let mut i = 0;
82 while i + 1 < self.len {
83 total += pts[i].distance_to(&pts[i + 1]);
84 i += 1;
85 }
86 total
87 }
88
89 #[must_use]
91 pub fn bbox(&self) -> Option<BBox2D> {
92 if self.is_empty() {
93 return None;
94 }
95 let pts = self.as_slice();
96 let mut min_x = pts[0].x;
97 let mut min_y = pts[0].y;
98 let mut max_x = pts[0].x;
99 let mut max_y = pts[0].y;
100 let mut i = 1;
101 while i < self.len {
102 min_x = f64_min(min_x, pts[i].x);
103 min_y = f64_min(min_y, pts[i].y);
104 max_x = f64_max(max_x, pts[i].x);
105 max_y = f64_max(max_y, pts[i].y);
106 i += 1;
107 }
108 Some(BBox2D::new(min_x, min_y, max_x, max_y))
109 }
110
111 #[inline]
113 pub fn iter(&self) -> core::slice::Iter<'_, Point2D> {
114 self.as_slice().iter()
115 }
116
117 #[must_use]
119 #[inline]
120 pub const fn capacity(&self) -> usize {
121 N
122 }
123
124 #[inline]
126 pub fn clear(&mut self) {
127 self.len = 0;
128 }
129
130 #[must_use]
132 #[inline]
133 pub fn first(&self) -> Option<&Point2D> {
134 if self.is_empty() {
135 None
136 } else {
137 Some(&self.points[0])
138 }
139 }
140
141 #[must_use]
143 #[inline]
144 pub fn last(&self) -> Option<&Point2D> {
145 if self.is_empty() {
146 None
147 } else {
148 Some(&self.points[self.len - 1])
149 }
150 }
151}
152
153impl<const N: usize> Default for FixedLineString<N> {
154 fn default() -> Self {
155 Self::new()
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn test_new_empty() {
165 let ls: FixedLineString<10> = FixedLineString::new();
166 assert!(ls.is_empty());
167 assert_eq!(ls.len(), 0);
168 assert_eq!(ls.capacity(), 10);
169 }
170
171 #[test]
172 fn test_push_and_get() {
173 let mut ls: FixedLineString<4> = FixedLineString::new();
174 assert!(ls.push(Point2D::new(1.0, 2.0)).is_ok());
175 assert!(ls.push(Point2D::new(3.0, 4.0)).is_ok());
176 assert_eq!(ls.len(), 2);
177 assert!(!ls.is_empty());
178
179 let p = ls.get(0);
180 assert!(p.is_some());
181 let p = p.expect("tested above");
182 assert!((p.x - 1.0).abs() < f64::EPSILON);
183 assert!((p.y - 2.0).abs() < f64::EPSILON);
184
185 assert!(ls.get(2).is_none());
186 }
187
188 #[test]
189 fn test_capacity_exceeded() {
190 let mut ls: FixedLineString<2> = FixedLineString::new();
191 assert!(ls.push(Point2D::new(0.0, 0.0)).is_ok());
192 assert!(ls.push(Point2D::new(1.0, 1.0)).is_ok());
193 let result = ls.push(Point2D::new(2.0, 2.0));
194 assert_eq!(result, Err(NoAllocError::CapacityExceeded));
195 }
196
197 #[test]
198 fn test_total_length_empty() {
199 let ls: FixedLineString<10> = FixedLineString::new();
200 assert!((ls.total_length() - 0.0).abs() < f64::EPSILON);
201 }
202
203 #[test]
204 fn test_total_length_single_point() {
205 let mut ls: FixedLineString<10> = FixedLineString::new();
206 let _ = ls.push(Point2D::new(5.0, 5.0));
207 assert!((ls.total_length() - 0.0).abs() < f64::EPSILON);
208 }
209
210 #[test]
211 fn test_total_length_two_points() {
212 let mut ls: FixedLineString<10> = FixedLineString::new();
213 let _ = ls.push(Point2D::new(0.0, 0.0));
214 let _ = ls.push(Point2D::new(3.0, 4.0));
215 assert!((ls.total_length() - 5.0).abs() < 1e-10);
216 }
217
218 #[test]
219 fn test_total_length_three_points() {
220 let mut ls: FixedLineString<10> = FixedLineString::new();
221 let _ = ls.push(Point2D::new(0.0, 0.0));
222 let _ = ls.push(Point2D::new(3.0, 0.0));
223 let _ = ls.push(Point2D::new(3.0, 4.0));
224 assert!((ls.total_length() - 7.0).abs() < 1e-10);
225 }
226
227 #[test]
228 fn test_bbox_empty() {
229 let ls: FixedLineString<10> = FixedLineString::new();
230 assert!(ls.bbox().is_none());
231 }
232
233 #[test]
234 fn test_bbox_single_point() {
235 let mut ls: FixedLineString<10> = FixedLineString::new();
236 let _ = ls.push(Point2D::new(5.0, 10.0));
237 let bb = ls.bbox();
238 assert!(bb.is_some());
239 let bb = bb.expect("tested above");
240 assert!((bb.min_x - 5.0).abs() < f64::EPSILON);
241 assert!((bb.max_x - 5.0).abs() < f64::EPSILON);
242 }
243
244 #[test]
245 fn test_bbox_multiple_points() {
246 let mut ls: FixedLineString<10> = FixedLineString::new();
247 let _ = ls.push(Point2D::new(1.0, 5.0));
248 let _ = ls.push(Point2D::new(3.0, 2.0));
249 let _ = ls.push(Point2D::new(-1.0, 8.0));
250 let bb = ls.bbox().expect("non-empty line string");
251 assert!((bb.min_x - (-1.0)).abs() < f64::EPSILON);
252 assert!((bb.min_y - 2.0).abs() < f64::EPSILON);
253 assert!((bb.max_x - 3.0).abs() < f64::EPSILON);
254 assert!((bb.max_y - 8.0).abs() < f64::EPSILON);
255 }
256
257 #[test]
258 fn test_iter() {
259 let mut ls: FixedLineString<10> = FixedLineString::new();
260 let _ = ls.push(Point2D::new(1.0, 2.0));
261 let _ = ls.push(Point2D::new(3.0, 4.0));
262 let collected: &[Point2D] = ls.as_slice();
263 assert_eq!(collected.len(), 2);
264 }
265
266 #[test]
267 fn test_first_last() {
268 let mut ls: FixedLineString<10> = FixedLineString::new();
269 assert!(ls.first().is_none());
270 assert!(ls.last().is_none());
271
272 let _ = ls.push(Point2D::new(1.0, 2.0));
273 let _ = ls.push(Point2D::new(5.0, 6.0));
274 let first = ls.first().expect("non-empty");
275 let last = ls.last().expect("non-empty");
276 assert!((first.x - 1.0).abs() < f64::EPSILON);
277 assert!((last.x - 5.0).abs() < f64::EPSILON);
278 }
279
280 #[test]
281 fn test_clear() {
282 let mut ls: FixedLineString<10> = FixedLineString::new();
283 let _ = ls.push(Point2D::new(1.0, 2.0));
284 ls.clear();
285 assert!(ls.is_empty());
286 assert_eq!(ls.len(), 0);
287 }
288
289 #[test]
290 fn test_default() {
291 let ls: FixedLineString<5> = FixedLineString::default();
292 assert!(ls.is_empty());
293 }
294}