use crate::{geometry::Point, primitives::Line};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct MajorMinor<T> {
pub major: T,
pub minor: T,
}
impl<T> MajorMinor<T> {
pub const fn new(major: T, minor: T) -> Self {
Self { major, minor }
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct BresenhamParameters {
pub error_threshold: i32,
pub error_step: MajorMinor<i32>,
pub position_step: MajorMinor<Point>,
}
impl BresenhamParameters {
pub fn new(line: &Line) -> Self {
let delta = line.end - line.start;
let direction = Point::new(
if delta.x >= 0 { 1 } else { -1 },
if delta.y >= 0 { 1 } else { -1 },
);
let delta = delta.abs();
let (delta, position_step) = if delta.y >= delta.x {
(
MajorMinor::new(delta.y, delta.x),
MajorMinor::new(direction.y_axis(), direction.x_axis()),
)
} else {
(
MajorMinor::new(delta.x, delta.y),
MajorMinor::new(direction.x_axis(), direction.y_axis()),
)
};
Self {
error_threshold: delta.major,
error_step: MajorMinor::new(2 * delta.minor, 2 * delta.major),
position_step,
}
}
pub fn increase_error(&self, error: &mut i32) -> bool {
*error += self.error_step.major;
if *error > self.error_threshold {
*error -= self.error_step.minor;
true
} else {
false
}
}
pub fn decrease_error(&self, error: &mut i32) -> bool {
*error -= self.error_step.major;
if *error <= -self.error_threshold {
*error += self.error_step.minor;
true
} else {
false
}
}
const fn mirror_extra_points(&self) -> bool {
if self.position_step.major.x != 0 {
self.position_step.major.x == self.position_step.minor.y
} else {
self.position_step.major.y == -self.position_step.minor.x
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct Bresenham {
pub point: Point,
error: i32,
}
impl Bresenham {
pub const fn new(start_point: Point) -> Self {
Self::with_initial_error(start_point, 0)
}
pub const fn with_initial_error(start_point: Point, initial_error: i32) -> Self {
Self {
point: start_point,
error: initial_error,
}
}
pub fn next(&mut self, parameters: &BresenhamParameters) -> Point {
if self.error > parameters.error_threshold {
self.point += parameters.position_step.minor;
self.error -= parameters.error_step.minor;
}
let ret = self.point;
self.point += parameters.position_step.major;
self.error += parameters.error_step.major;
ret
}
pub fn next_all(&mut self, parameters: &BresenhamParameters) -> BresenhamPoint {
let mut point = self.point;
if self.error > parameters.error_threshold {
self.point += parameters.position_step.minor;
self.error -= parameters.error_step.minor;
if parameters.mirror_extra_points() {
point += parameters.position_step.minor;
point -= parameters.position_step.major;
}
BresenhamPoint::Extra(point)
} else {
self.point += parameters.position_step.major;
self.error += parameters.error_step.major;
BresenhamPoint::Normal(point)
}
}
pub fn previous_all(&mut self, parameters: &BresenhamParameters) -> BresenhamPoint {
let mut point = self.point;
if self.error <= -parameters.error_threshold {
self.point -= parameters.position_step.minor;
self.error += parameters.error_step.minor;
if !parameters.mirror_extra_points() {
point -= parameters.position_step.minor;
point += parameters.position_step.major;
}
BresenhamPoint::Extra(point)
} else {
self.point -= parameters.position_step.major;
self.error -= parameters.error_step.major;
BresenhamPoint::Normal(point)
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub enum BresenhamPoint {
Normal(Point),
Extra(Point),
}
pub fn major_length(line: &Line) -> u32 {
let delta = (line.end - line.start).abs();
delta.x.max(delta.y) as u32 + 1
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{mock_display::MockDisplay, pixelcolor::BinaryColor, Drawable, Pixel};
#[test]
fn bresenham() {
let line = Line::new(Point::new(1, 2), Point::new(5, 4));
let mut bresenham = Bresenham::new(line.start);
let parameters = BresenhamParameters::new(&line);
let expected = &[(1, 2), (2, 2), (3, 3), (4, 3), (5, 4)];
for point in expected.iter().copied().map(Point::from) {
assert_eq!(bresenham.next(¶meters), point);
}
}
fn draw_with_extras<'a>(lines: impl Iterator<Item = &'a Line>) -> MockDisplay<BinaryColor> {
let mut display_next = MockDisplay::new();
let mut display_previous = MockDisplay::new();
for line in lines {
let mut bresenham = Bresenham::new(line.start);
let parameters = BresenhamParameters::new(&line);
for point in core::iter::from_fn(|| Some(bresenham.next_all(¶meters))).take(7) {
match point {
BresenhamPoint::Normal(point) => Pixel(point, BinaryColor::On),
BresenhamPoint::Extra(point) => Pixel(point, BinaryColor::Off),
}
.draw(&mut display_next)
.unwrap();
}
let mut bresenham = Bresenham::new(line.end);
for point in core::iter::from_fn(|| Some(bresenham.previous_all(¶meters))).take(7) {
match point {
BresenhamPoint::Normal(point) => Pixel(point, BinaryColor::On),
BresenhamPoint::Extra(point) => Pixel(point, BinaryColor::Off),
}
.draw(&mut display_previous)
.unwrap();
}
}
display_next.assert_eq(&display_previous);
display_next
}
#[test]
fn lines_with_extra_points_1() {
let lines = &[
Line::new(Point::new(4, 2), Point::new(0, 0)),
Line::new(Point::new(6, 2), Point::new(10, 0)),
Line::new(Point::new(4, 4), Point::new(0, 6)),
Line::new(Point::new(6, 4), Point::new(10, 6)),
];
let display = draw_with_extras(lines.iter());
display.assert_pattern(&[
"#. #", " ##. ##.", " ## ##. ", " ", " .## ## ", ".## .## ", "# .#", ]);
}
#[test]
fn lines_with_extra_points_2() {
let lines = &[
Line::new(Point::new(2, 4), Point::new(0, 0)),
Line::new(Point::new(4, 4), Point::new(6, 0)),
Line::new(Point::new(2, 6), Point::new(0, 10)),
Line::new(Point::new(4, 6), Point::new(6, 10)),
];
let display = draw_with_extras(lines.iter());
display.assert_pattern(&[
"#. #", " # #.", " #. # ", " # #. ", " # # ", " ", " # # ", " .# # ", " # .# ", ".# # ", "# .#", ]);
}
#[test]
fn lines_with_extra_points_3() {
let lines = &[
Line::new(Point::new(3, 3), Point::new(0, 0)),
Line::new(Point::new(5, 3), Point::new(8, 0)),
Line::new(Point::new(3, 5), Point::new(0, 8)),
Line::new(Point::new(5, 5), Point::new(8, 8)),
];
let display = draw_with_extras(lines.iter());
display.assert_pattern(&[
"#. #", " #. #.", " #. #. ", " # #. ", " ", " .# # ", " .# .# ", ".# .# ", "# .#", ]);
}
}