iron_shapes/point_string.rs
1// Copyright (c) 2018-2020 Thomas Kramer.
2// SPDX-FileCopyrightText: 2018-2022 Thomas Kramer
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6//! A point string is a finite sequence of points.
7
8use crate::edge::Edge;
9use crate::point::Point;
10use crate::rect::Rect;
11
12use crate::CoordinateType;
13
14pub use crate::traits::TryBoundingBox;
15use crate::traits::{MapPointwise, TryCastCoord};
16
17use std::iter::FromIterator;
18use std::slice::Iter;
19
20use num_traits::{Float, NumCast};
21use std::ops::Sub;
22
23/// A point string is a finite sequence of points.
24/// TODO: Implement `Deref` for accessing the list of points.
25#[derive(Clone, Debug, PartialEq, Eq, Hash)]
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27pub struct PointString<T> {
28 /// The points defining this point string.
29 pub points: Vec<Point<T>>,
30}
31
32impl<T> PointString<T> {
33 /// Get the number of vertices.
34 pub fn len(&self) -> usize {
35 self.points.len()
36 }
37
38 /// Check if string has zero length.
39 pub fn is_empty(&self) -> bool {
40 self.points.is_empty()
41 }
42}
43
44impl<T: Copy> PointString<T> {
45 /// Create new point string by taking vertices from a type that implements `Into<PointString<T>>`.
46 pub fn new<I>(i: I) -> Self
47 where
48 I: Into<Self>,
49 {
50 i.into()
51 }
52
53 /// Shortcut for `self.points.iter()`.
54 pub fn iter(&self) -> Iter<Point<T>> {
55 self.points.iter()
56 }
57
58 /// Get the sequence of edges of the point string starting from the first point to the last.
59 /// # Examples
60 ///
61 /// ```
62 /// use iron_shapes::point_string::PointString;
63 /// use iron_shapes::edge::Edge;
64 /// let coords = vec![(0, 0), (1, 0), (2, 0)];
65 ///
66 /// let point_string = PointString::new(coords);
67 ///
68 /// let edges: Vec<_> = point_string.edges().collect();
69 ///
70 /// assert_eq!(edges, vec![Edge::new((0, 0), (1, 0)), Edge::new((1, 0), (2, 0))]);
71 /// ```
72 pub fn edges(&self) -> impl Iterator<Item = Edge<T>> + '_ {
73 self.iter()
74 .zip(self.iter().skip(1))
75 .map(|(a, b)| Edge::new(a, b))
76 }
77
78 /// Same as `edges` but in reverse order.
79 /// Get the sequence of edges of the point string starting from the last point to the first.
80 /// # Examples
81 ///
82 /// ```
83 /// use iron_shapes::point_string::PointString;
84 /// use iron_shapes::edge::Edge;
85 /// let coords = vec![(0, 0), (1, 0), (2, 0)];
86 ///
87 /// let point_string = PointString::new(coords);
88 ///
89 /// let edges: Vec<_> = point_string.edges_reversed().collect();
90 ///
91 /// assert_eq!(edges, vec![Edge::new((2, 0), (1, 0)), Edge::new((1, 0), (0, 0))]);
92 /// ```
93 pub fn edges_reversed(&self) -> impl Iterator<Item = Edge<T>> + '_ {
94 self.iter()
95 .rev()
96 .zip(self.iter().rev().skip(1))
97 .map(|(a, b)| Edge::new(a, b))
98 }
99}
100
101impl<T: Copy + Sub<Output = T> + NumCast> PointString<T> {
102 /// Compute geometrical length of the path defined by the point string.
103 /// # Examples
104 ///
105 /// ```
106 /// use iron_shapes::point_string::PointString;
107 /// let coords = vec![(0, 0), (1, 0), (2, 0)];
108 ///
109 /// let point_string = PointString::new(coords);
110 ///
111 /// assert_eq!(point_string.path_length::<f64>(), 2.0);
112 /// ```
113 pub fn path_length<F: Float>(&self) -> F {
114 self.iter()
115 .zip(self.iter().skip(1))
116 .map(|(a, b)| a.distance(b))
117 .fold(F::zero(), |a, b| a + b)
118 }
119}
120
121impl<T: Copy + NumCast, Dst: CoordinateType + NumCast> TryCastCoord<T, Dst> for PointString<T> {
122 type Output = PointString<Dst>;
123
124 fn try_cast(&self) -> Option<Self::Output> {
125 let new_points: Option<Vec<_>> = self.points.iter().map(|p| p.try_cast()).collect();
126
127 new_points.map(|p| PointString::new(p))
128 }
129}
130
131/// Create a point string from something that can be turned into an iterator of values convertible to [`Point`]s.
132impl<I, T, P> From<I> for PointString<T>
133where
134 T: Copy,
135 I: IntoIterator<Item = P>,
136 Point<T>: From<P>,
137{
138 fn from(iter: I) -> Self {
139 let points: Vec<Point<T>> = iter.into_iter().map(|x| x.into()).collect();
140
141 PointString { points }
142 }
143}
144//
145// /// Create a point string from a [`Vec`] of values convertible to [`Point`]s.
146// impl<'a, T, P> From<&'a Vec<P>> for PointString<T>
147// where T: CoordinateType,
148// Point<T>: From<&'a P>
149// {
150// fn from(vec: &'a Vec<P>) -> Self {
151// let points: Vec<Point<T>> = vec.into_iter().map(
152// |x| x.into()
153// ).collect();
154//
155// PointString { points }
156// }
157// }
158//
159// /// Create a point string from a [`Vec`] of values convertible to [`Point`]s.
160// impl<T, P> From<Vec<P>> for PointString<T>
161// where T: CoordinateType,
162// Point<T>: From<P>
163// {
164// fn from(vec: Vec<P>) -> Self {
165// let points: Vec<Point<T>> = vec.into_iter().map(
166// |x| x.into()
167// ).collect();
168//
169// PointString { points }
170// }
171// }
172//
173
174/// Create a point string from a iterator of values convertible to [`Point`]s.
175impl<T, P> FromIterator<P> for PointString<T>
176where
177 T: Copy,
178 P: Into<Point<T>>,
179{
180 fn from_iter<I>(iter: I) -> Self
181 where
182 I: IntoIterator<Item = P>,
183 {
184 let points: Vec<Point<T>> = iter.into_iter().map(|x| x.into()).collect();
185
186 PointString { points }
187 }
188}
189
190impl<T> MapPointwise<T> for PointString<T>
191where
192 T: Copy,
193{
194 fn transform<F: Fn(Point<T>) -> Point<T>>(&self, tf: F) -> Self {
195 let points = self.points.iter().map(|&p| tf(p)).collect();
196
197 PointString { points }
198 }
199}
200
201impl<T> TryBoundingBox<T> for PointString<T>
202where
203 T: Copy + PartialOrd,
204{
205 /// Compute the bounding box of all the points in this string.
206 /// Returns `None` if the string is empty.
207 /// # Examples
208 ///
209 /// ```
210 /// use iron_shapes::point_string::PointString;
211 /// use iron_shapes::traits::TryBoundingBox;
212 /// use iron_shapes::rect::Rect;
213 /// let coords = vec![(0, 0), (1, 0), (2, 1), (-1, -3)];
214 ///
215 /// let point_string = PointString::new(coords);
216 ///
217 /// assert_eq!(point_string.try_bounding_box(), Some(Rect::new((2, 1), (-1, -3))));
218 /// ```
219 fn try_bounding_box(&self) -> Option<Rect<T>> {
220 if self.points.is_empty() {
221 None
222 } else {
223 let mut x_min = self.points[0].x;
224 let mut x_max = x_min;
225 let mut y_min = self.points[0].y;
226 let mut y_max = y_min;
227
228 for p in self.iter().skip(1) {
229 let (x, y) = p.into();
230 if x < x_min {
231 x_min = x;
232 }
233 if x > x_max {
234 x_max = x;
235 }
236 if y < y_min {
237 y_min = y;
238 }
239 if y > y_max {
240 y_max = y;
241 }
242 }
243
244 Some(Rect::new((x_min, y_min), (x_max, y_max)))
245 }
246 }
247}