Raise levels of abstraction to implement draw_thick_line() avoiding code duplication
authorunc0rr
Wed, 17 Oct 2018 22:00:58 +0200
changeset 13955 9c112f2ae02d
parent 13954 429945a9b8db
child 13956 75eaf7c71789
Raise levels of abstraction to implement draw_thick_line() avoiding code duplication
rust/land2d/src/lib.rs
--- a/rust/land2d/src/lib.rs	Wed Oct 17 05:33:02 2018 +0200
+++ b/rust/land2d/src/lib.rs	Wed Oct 17 22:00:58 2018 +0200
@@ -1,6 +1,7 @@
 extern crate vec2d;
 
 use std::cmp;
+use std::ops;
 
 pub struct Land2D<T> {
     pixels: vec2d::Vec2D<T>,
@@ -46,15 +47,25 @@
     }
 
     #[inline]
-    pub fn map<U, F: FnOnce(&mut T) -> U>(&mut self, y: i32, x: i32, f: F) {
+    pub fn map<U: Default, F: FnOnce(&mut T) -> U>(&mut self, y: i32, x: i32, f: F) -> U {
         if self.is_valid_coordinate(x, y) {
-            unsafe { // hey, I just checked that coordinates are valid!
-                f(self.pixels.get_unchecked_mut(y as usize, x as usize));
+            unsafe {
+                // hey, I just checked that coordinates are valid!
+                f(self.pixels.get_unchecked_mut(y as usize, x as usize))
             }
+        } else {
+            U::default()
         }
     }
 
-    pub fn draw_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, value: T) {
+    fn apply_along_line<U: Default + ops::AddAssign, F: FnMut(i32, i32) -> U>(
+        x1: i32,
+        y1: i32,
+        x2: i32,
+        y2: i32,
+        f: &mut F,
+    ) -> U {
+        let mut result = U::default();
         let mut e_x: i32 = 0;
         let mut e_y: i32 = 0;
         let mut d_x: i32 = x2 - x1;
@@ -99,8 +110,46 @@
                 y += s_y;
             }
 
+            result += f(x, y);
+        }
+
+        result
+    }
+
+    fn apply_around_circle<U: Default + ops::AddAssign, F: FnMut(i32, i32) -> U>(
+        radius: i32,
+        f: &mut F,
+    ) -> U {
+        let mut dx: i32 = 0;
+        let mut dy: i32 = radius;
+        let mut d = 3 - 2 * radius;
+        let mut result = U::default();
+
+        while dx < dy {
+            result += f(dx, dy);
+
+            if d < 0 {
+                d += 4 * dx + 6;
+            } else {
+                d += 4 * (dx - dy) + 10;
+                dy -= 1;
+            }
+
+            dx += 1;
+        }
+
+        if dx == dy {
+            result += f(dx, dy);
+        }
+
+        result
+    }
+
+    pub fn draw_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, value: T) -> usize {
+        <Land2D<T>>::apply_along_line(x1, y1, x2, y2, &mut |x, y| {
             self.map(y, x, |p| *p = value);
-        }
+            1
+        })
     }
 
     pub fn fill(&mut self, start_x: i32, start_y: i32, border_value: T, fill_value: T) {
@@ -192,7 +241,8 @@
 
         if self.is_valid_y(y) {
             for i in cmp::min(x_from, 0) as usize..cmp::max(x_to as usize, self.width() - 1) {
-                unsafe { // coordinates are valid at this point
+                unsafe {
+                    // coordinates are valid at this point
                     result += f(self.pixels.get_unchecked_mut(y as usize, i));
                 }
             }
@@ -223,30 +273,56 @@
         radius: i32,
         f: F,
     ) -> usize {
-        let mut dx: i32 = 0;
-        let mut dy: i32 = radius;
-        let mut d = 3 - 2 * radius;
-        let mut result = 0;
-
-        while dx < dy {
-            result += self.fill_circle_lines(x, y, dx, dy, &f);
+        <Land2D<T>>::apply_around_circle(radius, &mut |dx, dy| {
+            self.fill_circle_lines(x, y, dx, dy, &f)
+        })
+    }
 
-            if d < 0 {
-                d += 4 * dx + 6;
-            } else {
-                d += 4 * (dx - dy) + 10;
-                dy -= 1;
-            }
-
-            dx += 1;
-        }
-
-        if dx == dy {
-            result += self.fill_circle_lines(x, y, dx, dy, &f);
-        }
+    #[inline]
+    fn change_dots_around<U: Default + ops::AddAssign, F: FnMut(i32, i32) -> U>(
+        x: i32,
+        y: i32,
+        xx: i32,
+        yy: i32,
+        f: &mut F,
+    ) -> U {
+        let mut result = U::default();
+        result += f(y + yy, x + xx);
+        result += f(y - yy, x + xx);
+        result += f(y + yy, x - xx);
+        result += f(y - yy, x - xx);
+        result += f(y + xx, x + yy);
+        result += f(y - xx, x + yy);
+        result += f(y + xx, x - yy);
+        result += f(y - xx, x - yy);
 
         result
     }
+
+    pub fn draw_thick_line(
+        &mut self,
+        x1: i32,
+        y1: i32,
+        x2: i32,
+        y2: i32,
+        radius: i32,
+        value: T,
+    ) -> usize {
+        <Land2D<T>>::apply_around_circle(radius, &mut |dx, dy| {
+            <Land2D<T>>::apply_along_line(x1, y1, x2, y2, &mut |x, y| {
+                <Land2D<T>>::change_dots_around(x, y, dx, dy, &mut |x, y| {
+                    self.map(x, y, |p| {
+                        if *p != value {
+                            *p = value;
+                            1
+                        } else {
+                            0
+                        }
+                    })
+                })
+            })
+        })
+    }
 }
 
 #[cfg(test)]