// -*- coding: utf-8 -*- // // Copyright 2021-2023 Michael Büsch // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // use crate::vec2d::Vec2D; use std::cmp::Ordering; trait XY { fn to_xy(&self) -> (i32, i32); } impl XY for Vec2D { #[inline] fn to_xy(&self) -> (i32, i32) { (self.x().round() as i32, self.y().round() as i32) } } /// Line pixel iterator. struct LineIter { bx: i32, by: i32, dx: i32, dy: i32, xinc: i32, yinc: i32, x: i32, y: i32, err: i32, done: bool, } impl LineIter { /// Line iterator, from a to b. #[inline] fn new(a: (i32, i32), b: (i32, i32)) -> LineIter { let dx = b.0 - a.0; let dy = b.1 - a.1; let (dx, xinc) = if dx < 0 { (-dx, -1) } else { (dx, 1) }; let (dy, yinc) = if dy < 0 { (-dy, -1) } else { (dy, 1) }; LineIter { bx: b.0, by: b.1, dx, dy, xinc, yinc, x: a.0, y: a.1, err: 0, done: false, } } } impl Iterator for LineIter { /// (x, y) type Item = (i32, i32); /// Next pixel. #[allow(clippy::collapsible_else_if)] fn next(&mut self) -> Option { if !self.done { if self.dx >= self.dy { if self.x != self.bx { let (x, y) = (self.x, self.y); self.x += self.xinc; self.err += self.dy * 2; if self.err > self.dx { self.y += self.yinc; self.err -= self.dx * 2; } Some((x, y)) } else { self.done = true; Some((self.x, self.y)) } } else { if self.y != self.by { let (x, y) = (self.x, self.y); self.y += self.yinc; self.err += self.dx * 2; if self.err > self.dy { self.x += self.xinc; self.err -= self.dy * 2; } Some((x, y)) } else { self.done = true; Some((self.x, self.y)) } } } else { None } } } struct PathIter { points: [(i32, i32); SIZE], size: usize, index: usize, line: Option, } impl PathIter { #[inline] fn new() -> PathIter { PathIter { points: [(0, 0); SIZE], size: 0, index: 0, line: None, } } /// Add a new point to the path. #[inline] fn add(&mut self, p: (i32, i32)) { if self.size < SIZE { self.points[self.size] = p; self.size += 1; } } } impl Iterator for PathIter { /// (x, y) type Item = (i32, i32); fn next(&mut self) -> Option { if self.size >= 2 { 'a: loop { if self.index < self.size - 1 { if let Some(ref mut line) = self.line { if let Some((x, y)) = line.next() { break 'a Some((x, y)); } else { // Go to next section. self.index += 1; self.line = None; } } else { // Next section. self.line = Some(LineIter::new( self.points[self.index], self.points[self.index + 1], )); } } else { // All sections done. break 'a None; } } } else { // Not enough points to form a section. None } } } pub struct Draw<'a> { image: &'a mut [u32], width: usize, height: usize, } impl<'a> Draw<'a> { pub fn new(image: &'a mut [u32], width: usize, height: usize) -> Draw { assert!(width <= i32::MAX as usize); assert!(height <= i32::MAX as usize); Draw { image, width, height, } } #[inline] pub fn set_pixel(&mut self, x: i32, y: i32, color: u32) { if x >= 0 && (x as usize) < self.width && y >= 0 && (y as usize) < self.height { self.image[y as usize * self.width + x as usize] = color; } } pub fn draw_line(&mut self, coords: (&Vec2D, &Vec2D), color: u32) { for (x, y) in LineIter::new(coords.0.to_xy(), coords.1.to_xy()) { self.set_pixel(x, y, color); } } pub fn draw_horizontal_line(&mut self, point: (i32, i32), length: i32, color: u32) { let (mut x, y) = point; let mut length = length; if y < self.height as i32 && y >= 0 { if length < 0 { x += length; debug_assert!(length != i32::MIN); length = -length; } if x < 0 { length = (length + x).max(0); x = 0; } if x < self.width as i32 { length = length.min(self.width as i32 - x); let (x, y, length) = (x as usize, y as usize, length as usize); let offs = y * self.width + x; self.image[offs..offs + length].fill(color); } } } #[allow(dead_code)] pub fn draw_tetragon(&mut self, corners: (&Vec2D, &Vec2D, &Vec2D, &Vec2D), color: u32) { self.draw_line((corners.0, corners.1), color); self.draw_line((corners.1, corners.2), color); self.draw_line((corners.2, corners.3), color); self.draw_line((corners.3, corners.0), color); } /// Fill a convex tetragon. #[allow(dead_code)] pub fn fill_tetragon(&mut self, corners: (&Vec2D, &Vec2D, &Vec2D, &Vec2D), color: u32) { // Sort all points top to bottom (= along y axis). let mut ttb = [ corners.0.to_xy(), corners.1.to_xy(), corners.2.to_xy(), corners.3.to_xy(), ]; ttb.sort_unstable_by(|a, b| { if a.1 < b.1 || (a.1 == b.1 && a.0 < b.0) { Ordering::Less } else if a.1 > b.1 || (a.1 == b.1 && a.0 > b.0) { Ordering::Greater } else { Ordering::Equal } }); // Construct the left hand side path. let mut a_path: PathIter<4> = PathIter::new(); a_path.add(ttb[0]); if ttb[1].0 < ttb[3].0 { a_path.add(ttb[1]); } if ttb[2].0 < ttb[3].0 { a_path.add(ttb[2]); } a_path.add(ttb[3]); // Construct the right hand side path. let mut b_path: PathIter<4> = PathIter::new(); b_path.add(ttb[0]); if ttb[1].0 >= ttb[3].0 { b_path.add(ttb[1]); } if ttb[2].0 >= ttb[3].0 { b_path.add(ttb[2]); } b_path.add(ttb[3]); // Draw all horizontal lines in the convex tetragon // confined by the vertical paths a_path and b_path. let mut prev_ay = i32::MIN; let (mut bx, mut by) = (i32::MIN, i32::MIN); 'outer: loop { if let Some((ax, ay)) = a_path.next() { if ay != prev_ay { prev_ay = ay; 'inner: loop { if ay == by { self.draw_horizontal_line((ax, ay), bx - ax, color); break 'inner; } if let Some((x, y)) = b_path.next() { bx = x; by = y; } else { break 'outer; } } } } else { break 'outer; } } } } // vim: ts=4 sw=4 expandtab