use crate::{color::Color, image::Image, vec::Vec2d};
use super::{Line, Shape};
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Polygon {
points: Vec<Vec2d>,
filled: bool,
color: Color,
}
#[derive(Debug, Clone, Copy)]
struct Edge {
pub y2: f64,
pub x: f64,
pub delta: f64,
}
impl Edge {
fn from(first: &Vec2d, next: &Vec2d) -> Self {
Self {
y2: next.y,
x: first.x,
delta: (next.x - first.x) / (next.y - first.y),
}
}
}
impl Polygon {
pub fn from_points(points: Vec<Vec2d>) -> Self {
Self {
points,
..Default::default()
}
}
pub fn set_filled(&mut self, filled: bool) {
self.filled = filled;
}
pub fn set_color(&mut self, color: Color) {
self.color = color;
}
fn fill(&self, img: &mut Image) {
let mut edge_table: Vec<Vec<Edge>> = vec![vec![]; img.rows()];
for (i, current) in self.points.iter().enumerate() {
let prev = self.points[if i > 0 { i - 1 } else { self.points.len() - 1 }];
let next = self.points[if i < self.points.len() - 1 { i + 1 } else { 0 }];
if current.y < prev.y {
edge_table[current.y as usize].push(Edge::from(current, &prev));
}
if current.y < next.y {
edge_table[current.y as usize].push(Edge::from(current, &next));
}
}
let mut active_edges: Vec<Edge> = vec![];
for (y, starting_edges) in edge_table.iter().enumerate().take(img.rows()) {
active_edges = active_edges
.into_iter()
.map(|mut edge| {
edge.x += edge.delta;
edge
})
.filter(|edge| edge.y2 > y as f64)
.collect();
starting_edges
.iter()
.for_each(|elem| active_edges.push(*elem));
active_edges.sort_by(|first, second| first.x.partial_cmp(&second.x).unwrap());
let mut in_shape = false;
let mut index = 0;
for x in 0..img.cols() {
while index < active_edges.len() {
let next_edge = active_edges[index];
if x == next_edge.x.ceil() as usize {
in_shape = !in_shape;
} else {
break;
}
index += 1;
}
if in_shape {
img.set(x, y, &self.color);
}
}
}
}
}
impl Shape for Polygon {
fn draw(&self, img: &mut Image) {
if self.filled {
self.fill(img);
}
for (i, point) in self.points.iter().enumerate() {
let line =
Line::new(*point, self.points[(i + 1) % self.points.len()]).with_color(self.color);
img.draw(&line);
}
}
}
#[cfg(test)]
mod tests {
use crate::{color::Color, rgb, vec2};
use super::*;
#[test]
fn test_polygon_from() {
let vecs = vec![vec2![2.0, 10.0], vec2![10.0, 15.0], vec2![16.0, 20.0]];
assert_eq!(
Polygon::from_points(vecs.clone()),
Polygon {
points: vecs,
filled: false,
color: Color::default()
}
)
}
#[test]
fn test_set_filled() {
let mut polygon = Polygon::default();
polygon.set_filled(true);
assert!(polygon.filled);
polygon.set_filled(false);
assert!(!polygon.filled);
}
#[test]
fn test_set_color() {
let mut polygon = Polygon::default();
let color = rgb!(17, 42, 137);
polygon.set_color(color);
assert_eq!(polygon.color, color);
}
}