purrmitive 0.2.0

Reproducing images with geometric primitives.
Documentation
use crate::core::PurrShape;
use crate::graphics::point::*;
use crate::graphics::raster::rasterize_polygon;
use crate::graphics::scanline::*;
use crate::graphics::Shape;
use crate::{clamp, degrees};
use crate::{Rgba, RgbaImage};
use rand::{Rng, RngCore, SeedableRng};
use rand_distr::StandardNormal;

#[derive(Debug, Clone, Copy)]
pub struct Triangle {
    pub a: Point,
    pub b: Point,
    pub c: Point,
}

impl Default for Triangle {
    fn default() -> Self {
        Triangle {
            a: Point { x: 0, y: 0 },
            b: Point { x: 0, y: 0 },
            c: Point { x: 0, y: 0 },
        }
    }
}

impl Triangle {
    fn valid(&self) -> bool {
        let min_degree = 15.0;
        let mut x1 = (self.b.x - self.a.x) as f64;
        let mut y1 = (self.b.y - self.a.y) as f64;
        let mut x2 = (self.c.x - self.a.x) as f64;
        let mut y2 = (self.c.y - self.a.y) as f64;
        let mut d1 = (x1 * x1 + y1 * y1).sqrt();
        let mut d2 = (x2 * x2 + y2 * y2).sqrt();
        x1 /= d1;
        y1 /= d1;
        x2 /= d2;
        y2 /= d2;
        let a1 = degrees((x1 * x2 + y1 * y2).acos());

        x1 = (self.a.x - self.b.x) as f64;
        y1 = (self.a.y - self.b.y) as f64;
        x2 = (self.c.x - self.b.x) as f64;
        y2 = (self.c.y - self.b.y) as f64;
        d1 = (x1 * x1 + y1 * y1).sqrt();
        d2 = (x2 * x2 + y2 * y2).sqrt();
        x1 /= d1;
        y1 /= d1;
        x2 /= d2;
        y2 /= d2;
        let a2 = degrees((x1 * x2 + y1 * y2).acos());
        let a3 = 180.0 - a1 - a2;

        a1 > min_degree && a2 > min_degree && a3 > min_degree
    }

    fn clockwise(&self) -> [Point; 3] {
        let val = (self.b.y - self.a.y) * (self.c.x - self.b.x)
            - (self.b.x - self.a.x) * (self.c.y - self.b.y);
        if val >= 0 {
            [self.a, self.c, self.b]
        } else {
            [self.a, self.b, self.c]
        }
    }
}

impl Shape for Triangle {
    fn draw(&self, img: &mut RgbaImage, color: &Rgba<u8>) {
        let (w, h) = img.dimensions();
        let lines = self.rasterize(w, h);
        for line in lines {
            line.draw(img, &color);
        }
    }
    fn rasterize(&self, w: u32, h: u32) -> Vec<Scanline> {
        // clockwise this
        let points = Vec::from(self.clockwise());
        let lines = rasterize_polygon(&points, w, h);
        let mut visible_lines: Vec<Scanline> = lines
            .into_iter()
            .filter(|l| l.x1 <= l.x2 && l.x2 > 0 && l.x1 < w && l.y > 0 && l.y < h)
            .collect();
        for line in &mut visible_lines {
            line.crop(w, h);
        }
        visible_lines
    }
    fn random<T: SeedableRng + RngCore>(w: u32, h: u32, rng: &mut T) -> Self {
        let x1 = rng.gen_range(0, w as i32);
        let y1 = rng.gen_range(0, h as i32);
        let x2 = x1 + rng.gen_range(0, 31) - 15;
        let y2 = y1 + rng.gen_range(0, 31) - 15;
        let x3 = x1 + rng.gen_range(0, 31) - 15;
        let y3 = y1 + rng.gen_range(0, 31) - 15;

        let mut triangle = Triangle {
            a: Point { x: x1, y: y1 },
            b: Point { x: x2, y: y2 },
            c: Point { x: x3, y: y3 },
        };
        triangle.mutate(w, h, rng);
        triangle
    }
    fn mutate<T: SeedableRng + RngCore>(&mut self, w: u32, h: u32, rng: &mut T) {
        let m = 16;
        loop {
            match rng.gen_range(0, 3) {
                0 => {
                    self.a.x = clamp(
                        self.a.x + (m as f64 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        -m,
                        w as i32 - 1 + m,
                    );
                    self.a.y = clamp(
                        self.a.y + (m as f64 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        -m,
                        h as i32 - 1 + m,
                    );
                }
                1 => {
                    self.b.x = clamp(
                        self.b.x + (m as f64 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        -m,
                        w as i32 - 1 + m,
                    );
                    self.b.y = clamp(
                        self.b.y + (m as f64 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        -m,
                        h as i32 - 1 + m,
                    );
                }
                2 => {
                    self.c.x = clamp(
                        self.c.x + (m as f64 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        -m,
                        w as i32 - 1 + m,
                    );
                    self.c.y = clamp(
                        self.c.y + (m as f64 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        -m,
                        h as i32 - 1 + m,
                    );
                }
                _ => unreachable!(),
            }

            if self.valid() {
                break;
            }
        }
    }

    fn to_svg(&self, attr: &str) -> String {
        format!(
            "<polygon {} points=\"{},{} {},{} {},{}\" />",
            attr, self.a.x, self.a.y, self.b.x, self.b.y, self.c.x, self.c.y,
        )
    }
}

impl PurrShape for Triangle {}