strandify/
peg.rs

1use std::collections::HashSet;
2use std::sync::atomic::{AtomicUsize, Ordering};
3
4use rand::{thread_rng, Rng};
5use serde::{Deserialize, Serialize};
6
7use crate::line::Line;
8use crate::utils;
9
10static COUNTER: AtomicUsize = AtomicUsize::new(0);
11
12#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
13/// The [`Peg`] around which the [`Yarn`] is weaved.
14pub struct Peg {
15    /// Horizontal coordinate of the [`Peg`], (0, 0) is the top left corner of the image.
16    pub x: u32,
17    /// Vertical coordinate of the [`Peg`], (0, 0) is the top left corner of the image.
18    pub y: u32,
19    /// [`Peg`] id, should be unique among [`Peg`] instances.
20    pub id: usize,
21}
22
23impl Peg {
24    /// Creates a new [`Peg`].
25    pub fn new(x: u32, y: u32) -> Self {
26        let id = COUNTER.fetch_add(1, Ordering::SeqCst);
27        Self { x, y, id }
28    }
29
30    /// Get the pixel coords connecting 2 [`Pegs`](Peg) using the Bresenham line algorithm and contruct a [`Line`].
31    ///
32    /// # Arguments:
33    ///
34    /// * `other`: the other [`Peg`] to draw the line to.
35    /// * `width`: the width of the line. The line resulting line width can only be odd, which
36    ///     leads to unintuitive behaviours:
37    ///     * `width=0` -> 1 pixel wide
38    ///     * `width=1` -> 1 pixel wide
39    ///     * `width=2` -> 3 pixels wide
40    ///     * `width=3` -> 3 pixels wide
41    ///     * `width=4` -> 5 pixels wide
42    ///     * and so on
43    /// * `min_max`: min and max values of the line (x_min, x_max, y_min, y_max), used to crop the line to the image bounds.
44    pub fn line_to(&self, other: &Peg, width: u32, min_max: Option<(u32, u32, u32, u32)>) -> Line {
45        let mut pixels = HashSet::new();
46        let half_width = width as i32 / 2;
47        let (x_min, x_max, y_min, y_max): (i32, i32, i32, i32) = match min_max {
48            Some((x_min, x_max, y_min, y_max)) => {
49                (x_min as i32, x_max as i32, y_min as i32, y_max as i32)
50            }
51            None => (0, i32::MAX, 0, i32::MAX),
52        };
53
54        // Bresenham's line algorithm
55        let dx = self.x.abs_diff(other.x) as i32;
56        let dy = self.y.abs_diff(other.y) as i32;
57        let sx = if self.x < other.x { 1 } else { -1 };
58        let sy = if self.y < other.y { 1 } else { -1 };
59        let mut err = dx - dy;
60
61        let mut x = self.x as i32;
62        let mut y = self.y as i32;
63
64        // Determine the number of steps (the maximum of dx or dy)
65        let steps = dx.max(dy);
66
67        // Iterate through the number of steps to draw the line
68        for _ in 0..=steps {
69            // Add pixels for the current position and its surrounding area based on width
70            for ox in -(half_width)..=(half_width) {
71                for oy in -(half_width)..=(half_width) {
72                    pixels.insert(((x + ox).clamp(x_min, x_max), (y + oy).clamp(y_min, y_max)));
73                }
74            }
75            if x == other.x as i32 && y == other.y as i32 {
76                break;
77            }
78            let e2 = 2 * err;
79            // Move in the x-direction
80            if e2 > -dy {
81                err -= dy;
82                x += sx;
83            }
84            // Move in the y-direction
85            if e2 < dx {
86                err += dx;
87                y += sy;
88            }
89        }
90
91        // Convert HashSet of pixels to vectors of x and y coordinates
92        let (x_vec, y_vec): (Vec<u32>, Vec<u32>) = pixels
93            .into_iter()
94            .map(|(x, y)| (x as u32, y as u32))
95            .unzip();
96        Line::new(x_vec, y_vec, self.dist_to(other))
97    }
98
99    /// Get the pixels around a [`Peg`] within radius.
100    ///
101    /// # Arguments
102    ///
103    /// * `radius`: Pixel radius around [`Peg`].
104    pub fn around(&self, radius: u32) -> (Vec<u32>, Vec<u32>) {
105        utils::pixels_around((self.x, self.y), radius)
106    }
107
108    /// Compute the distance between 2 [`Pegs`](Peg) in pixels.
109    pub fn dist_to(&self, other: &Peg) -> u32 {
110        let delta_x = utils::abs_diff(self.x, other.x);
111        let delta_y = utils::abs_diff(self.y, other.y);
112        ((delta_x * delta_x + delta_y * delta_y) as f64)
113            .sqrt()
114            .round() as u32
115    }
116
117    /// Add 2d jitter to the [`Peg`] returns a new one with added jitter.
118    ///
119    /// # Arguments
120    ///
121    /// * `jitter`: Amount of jitter to add, in pixels.
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// use strandify::peg::Peg;
127    /// let peg = Peg::new(10, 10);
128    /// let peg_jitter = peg.with_jitter(2);
129    /// assert_eq!(peg_jitter.id, peg.id);
130    /// ```
131    pub fn with_jitter(&self, jitter: i64) -> Self {
132        let mut rng = thread_rng();
133        Self {
134            x: (self.x as i64 + rng.gen_range(-jitter..jitter)) as u32,
135            y: (self.y as i64 + rng.gen_range(-jitter..jitter)) as u32,
136            id: self.id,
137        }
138    }
139}
140
141/// Helper functions to generate [`Pegs`](Peg) based on different shapes.
142pub mod shape {
143    use super::*;
144
145    fn coords_to_pegs(coords: (Vec<u32>, Vec<u32>)) -> Vec<Peg> {
146        coords
147            .0
148            .into_iter()
149            .zip(coords.1)
150            .map(|(x, y)| Peg::new(x, y))
151            .collect()
152    }
153
154    /// Generate [`Pegs`](Peg) in a square.
155    ///
156    /// # Arguments
157    ///
158    /// * `top_left`: Top left corner of the square.
159    /// * `length`: Length of the side of the square.
160    /// * `n_pegs`: Number of pegs.
161    pub fn square(top_left: (u32, u32), length: u32, n_pegs: usize) -> Vec<Peg> {
162        coords_to_pegs(utils::square_coords(top_left, length, n_pegs))
163    }
164
165    /// Generate [`Pegs`](Peg) in a rectangle.
166    ///
167    /// # Arguments
168    ///
169    /// * `top_left`: Top left corner of the square.
170    /// * `width`: Width of the rectangle.
171    /// * `height`: height of the rectangle.
172    /// * `n_pegs`: Number of pegs.
173    pub fn rectangle(top_left: (u32, u32), width: u32, height: u32, n_pegs: usize) -> Vec<Peg> {
174        coords_to_pegs(utils::rectangle_coords(top_left, width, height, n_pegs))
175    }
176
177    /// Generate [`Pegs`](Peg) in a circle.
178    ///
179    /// # Arguments
180    ///
181    /// * `center`: The center of the circle.
182    /// * `radius`: Radius of the circle.
183    /// * `n_pegs`: Number of pegs.
184    pub fn circle(center: (u32, u32), radius: u32, n_pegs: usize) -> Vec<Peg> {
185        coords_to_pegs(utils::circle_coords(center, radius, n_pegs))
186    }
187
188    /// Generate [`Pegs`](Peg) on a line.
189    ///
190    /// # Arguments
191    ///
192    /// * `start`: Start point of the line.
193    /// * `end`: End point of the line.
194    /// * `n_pegs`: Number of pegs.
195    pub fn line(start: (u32, u32), end: (u32, u32), n_pegs: usize) -> Vec<Peg> {
196        coords_to_pegs(utils::line_coords(start, end, n_pegs))
197    }
198}
199
200#[derive(Debug, Clone)]
201/// A [`Yarn`], used to control how to render a [`Blueprint`](crate::blueprint::Blueprint) and to
202/// influence the [`Pather`](crate::pather::Pather)'s pathing algorithm.
203pub struct Yarn {
204    /// Width of the [`Yarn`], in pixels.
205    pub width: f32,
206    /// [`Yarn`] opacity.
207    pub opacity: f64,
208    /// [`Yarn`] color.
209    pub color: (u8, u8, u8),
210}
211
212impl Default for Yarn {
213    fn default() -> Self {
214        Self {
215            width: 1.,
216            opacity: 0.2,
217            color: (0, 0, 0),
218        }
219    }
220}
221
222impl Yarn {
223    /// Creates a new [`Yarn`].
224    pub fn new(width: f32, opacity: f64, color: (u8, u8, u8)) -> Self {
225        Self {
226            width,
227            opacity,
228            color,
229        }
230    }
231
232    pub fn set_color(&mut self, color: (u8, u8, u8)) {
233        self.color = color
234    }
235}
236
237#[cfg(test)]
238mod test {
239    use super::*;
240
241    #[test]
242    fn peg_line_to() {
243        let peg_a = Peg::new(0, 0);
244        let peg_b = Peg::new(1, 1);
245        let mut line = peg_a.line_to(&peg_b, 1, None);
246        line.x.sort();
247        line.y.sort();
248
249        assert_eq!(line.x, vec![0, 1]);
250        assert_eq!(line.y, vec![0, 1]);
251        assert_eq!(line.dist, f32::sqrt(2.0) as u32);
252
253        let peg_a = Peg::new(1, 1);
254        let peg_b = Peg::new(0, 0);
255        let mut line = peg_a.line_to(&peg_b, 1, None);
256        line.x.sort();
257        line.y.sort();
258        assert_eq!(line.x, vec![0, 1]);
259        assert_eq!(line.y, vec![0, 1]);
260        assert_eq!(line.dist, f32::sqrt(2.0) as u32);
261
262        // horizontal line
263        let peg_a = Peg::new(0, 1);
264        let peg_b = Peg::new(3, 1);
265        let mut line = peg_a.line_to(&peg_b, 1, None);
266        line.x.sort();
267        line.y.sort();
268        assert_eq!(line.x, vec![0, 1, 2, 3]);
269        assert_eq!(line.y, vec![1, 1, 1, 1]);
270        assert_eq!(line.dist, 3);
271
272        // vertical line
273        let peg_a = Peg::new(0, 0);
274        let peg_b = Peg::new(0, 1);
275        let mut line = peg_a.line_to(&peg_b, 1, None);
276        line.x.sort();
277        line.y.sort();
278        assert_eq!(line.x, vec![0, 0]);
279        assert_eq!(line.y, vec![0, 1]);
280        assert_eq!(line.dist, 1);
281        assert_eq!(line.zip().len(), 2);
282    }
283
284    #[test]
285    fn peg_line_to_width() {
286        let peg_a = Peg::new(5, 5);
287        let peg_b = Peg::new(5, 5);
288        let line = peg_a.line_to(&peg_b, 0, None);
289        assert_eq!(line.x, vec![5]);
290        assert_eq!(line.y, vec![5]);
291        let line = peg_a.line_to(&peg_b, 1, None);
292        assert_eq!(line.x, vec![5]);
293        assert_eq!(line.y, vec![5]);
294        assert_eq!(line.dist, 0);
295        let line = peg_a.line_to(&peg_b, 2, None);
296        // we go +1 in all directions
297        assert_eq!(*line.x.iter().max().unwrap(), 6);
298        assert_eq!(*line.x.iter().min().unwrap(), 4);
299        assert_eq!(*line.y.iter().max().unwrap(), 6);
300        assert_eq!(*line.y.iter().min().unwrap(), 4);
301        assert_eq!(line.dist, 0);
302        let line = peg_a.line_to(&peg_b, 3, None);
303        // we go +1 in all directions
304        assert_eq!(*line.x.iter().max().unwrap(), 6);
305        assert_eq!(*line.x.iter().min().unwrap(), 4);
306        assert_eq!(*line.y.iter().max().unwrap(), 6);
307        assert_eq!(*line.y.iter().min().unwrap(), 4);
308        assert_eq!(line.dist, 0);
309        let line = peg_a.line_to(&peg_b, 4, None);
310        // we go +2 in all directions
311        assert_eq!(*line.x.iter().max().unwrap(), 7);
312        assert_eq!(*line.x.iter().min().unwrap(), 3);
313        assert_eq!(*line.y.iter().max().unwrap(), 7);
314        assert_eq!(*line.y.iter().min().unwrap(), 3);
315        assert_eq!(line.dist, 0);
316
317        let peg_a = Peg::new(5, 5);
318        let peg_b = Peg::new(6, 6);
319        let line = peg_a.line_to(&peg_b, 2, None);
320        assert_eq!(*line.x.iter().min().unwrap(), 4);
321        assert_eq!(*line.x.iter().max().unwrap(), 7);
322        assert_eq!(*line.y.iter().min().unwrap(), 4);
323        assert_eq!(*line.y.iter().max().unwrap(), 7);
324        assert_eq!(line.dist, 1);
325    }
326
327    #[test]
328    fn peg_line_to_width_min_max() {
329        let peg_a = Peg::new(0, 5);
330        let peg_b = Peg::new(10, 5);
331        let line = peg_a.line_to(&peg_b, 2, Some((0, 10, 3, 7)));
332        assert_eq!(*line.x.iter().max().unwrap(), 10);
333        assert_eq!(*line.x.iter().min().unwrap(), 0);
334
335        assert_eq!(*line.y.iter().max().unwrap(), 6);
336        assert_eq!(*line.y.iter().min().unwrap(), 4);
337    }
338
339    #[test]
340    fn peg_around() {
341        let peg = Peg::new(10, 10);
342        let (x_coords, y_coords) = peg.around(1);
343        assert_eq!(x_coords, vec![9, 10, 10, 10, 11]);
344        assert_eq!(y_coords, vec![10, 9, 10, 11, 10]);
345    }
346
347    #[test]
348    fn peg_jitter() {
349        let peg = Peg::new(10, 10);
350        let jitter = 2;
351        let peg_jitter = peg.with_jitter(jitter);
352        assert!(peg_jitter.x <= (peg.x as i64 + jitter) as u32);
353        assert!(peg_jitter.x >= (peg.x as i64 - jitter) as u32);
354        assert_eq!(peg_jitter.id, peg.id);
355    }
356}