use crate::{
geometry::{Dimensions, Point, PointExt, Size},
primitives::{
common::DistanceIterator, ContainsPoint, OffsetOutline, PointsIter, Primitive, Rectangle,
},
transform::Transform,
};
mod points;
mod styled;
pub use points::Points;
pub use styled::StyledPixelsIterator;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct Circle {
pub top_left: Point,
pub diameter: u32,
}
impl Circle {
pub const fn new(top_left: Point, diameter: u32) -> Self {
Circle { top_left, diameter }
}
pub const fn with_center(center: Point, diameter: u32) -> Self {
let top_left = Rectangle::with_center(center, Size::new_equal(diameter)).top_left;
Circle { top_left, diameter }
}
pub fn center(&self) -> Point {
self.bounding_box().center()
}
pub(in crate::primitives) fn center_2x(&self) -> Point {
let radius = self.diameter.saturating_sub(1);
self.top_left * 2 + Size::new(radius, radius)
}
pub(in crate::primitives) const fn threshold(&self) -> u32 {
diameter_to_threshold(self.diameter)
}
pub(in crate::primitives) fn distances(&self) -> DistanceIterator {
DistanceIterator::new(self.center_2x(), &self.bounding_box())
}
}
impl OffsetOutline for Circle {
fn offset(&self, offset: i32) -> Self {
let diameter = if offset >= 0 {
self.diameter.saturating_add(2 * offset as u32)
} else {
self.diameter.saturating_sub(2 * (-offset) as u32)
};
Self::with_center(self.center(), diameter)
}
}
impl Primitive for Circle {}
impl PointsIter for Circle {
type Iter = Points;
fn points(&self) -> Self::Iter {
Points::new(self)
}
}
impl ContainsPoint for Circle {
fn contains(&self, point: Point) -> bool {
let delta = self.center_2x() - point * 2;
let distance = delta.length_squared() as u32;
distance < self.threshold()
}
}
impl Dimensions for Circle {
fn bounding_box(&self) -> Rectangle {
Rectangle::new(self.top_left, Size::new_equal(self.diameter))
}
}
impl Transform for Circle {
fn translate(&self, by: Point) -> Self {
Self {
top_left: self.top_left + by,
..*self
}
}
fn translate_mut(&mut self, by: Point) -> &mut Self {
self.top_left += by;
self
}
}
pub(in crate::primitives) const fn diameter_to_threshold(diameter: u32) -> u32 {
if diameter <= 4 {
diameter.pow(2) - diameter / 2
} else {
diameter.pow(2)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
geometry::{Dimensions, Point, Size},
primitives::ContainsPoint,
};
#[test]
fn negative_dimensions() {
let circle = Circle::new(Point::new(-15, -15), 20);
assert_eq!(
circle.bounding_box(),
Rectangle::new(Point::new(-15, -15), Size::new(20, 20))
);
}
#[test]
fn dimensions() {
let circle = Circle::new(Point::new(5, 15), 10);
assert_eq!(
circle.bounding_box(),
Rectangle::new(Point::new(5, 15), Size::new(10, 10))
);
}
#[test]
fn center_is_correct() {
let circle = Circle::new(Point::new(10, 10), 5);
assert_eq!(circle.center(), Point::new(12, 12));
let circle = Circle::new(Point::new(10, 10), 6);
assert_eq!(circle.center(), Point::new(12, 12));
let circle = Circle::with_center(Point::new(10, 10), 5);
assert_eq!(circle.center(), Point::new(10, 10));
let circle = Circle::with_center(Point::new(10, 10), 6);
assert_eq!(circle.center(), Point::new(10, 10));
}
#[test]
fn contains() {
let circle = Circle::new(Point::zero(), 5);
let contained_points = Rectangle::new(Point::new(-10, -10), Size::new(20, 20))
.points()
.filter(|p| circle.contains(*p));
assert!(contained_points.eq(circle.points()));
}
#[test]
fn offset() {
let center = Point::new(1, 2);
let circle = Circle::with_center(center, 3);
assert_eq!(circle.offset(0), circle);
assert_eq!(circle.offset(1), Circle::with_center(center, 5));
assert_eq!(circle.offset(2), Circle::with_center(center, 7));
assert_eq!(circle.offset(-1), Circle::with_center(center, 1));
assert_eq!(circle.offset(-2), Circle::with_center(center, 0));
assert_eq!(circle.offset(-3), Circle::with_center(center, 0));
}
}