use fixed::traits::ToFixed as _;
use crate::primitives::{
Point, Size,
geometry::{PathEl, Rectangle, Shape, ShapePathIter},
transform::{CoordinateSpaceTransform, LinearTransform, ScaleFactor},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Circle {
pub origin: Point,
pub diameter: u32,
}
impl Circle {
#[must_use]
pub const fn new(origin: Point, diameter: u32) -> Self {
Self { origin, diameter }
}
}
impl Shape for Circle {
type PathElementsIter<'iter>
= ShapePathIter<66>
where
Self: 'iter;
#[expect(clippy::cast_precision_loss)]
fn path_elements(&self, _tolerance: u16) -> Self::PathElementsIter<'_> {
let radius = self.diameter as f32 / 2.0;
let center_x = self.origin.x as f32 + radius;
let center_y = self.origin.y as f32 + radius;
let mut elements = [PathEl::ClosePath; 66];
let first_point = Point::new((center_x + radius) as i32, center_y as i32);
elements[0] = PathEl::MoveTo(first_point);
#[cfg(feature = "std")]
(1..=64).for_each(|i| {
let angle = (i as f32 * 2.0 * core::f32::consts::PI) / 64.0;
let x = center_x + radius * angle.cos();
let y = center_y + radius * angle.sin();
elements[i] = PathEl::LineTo(Point::new(x as i32, y as i32));
});
ShapePathIter::new(elements)
}
fn bounding_box(&self) -> Rectangle {
Rectangle::new(self.origin, Size::new(self.diameter, self.diameter))
}
fn as_circle(&self) -> Option<Circle> {
Some(self.clone())
}
}
impl CoordinateSpaceTransform for Circle {
fn applying(&self, transform: &LinearTransform) -> Self {
Self {
origin: self.origin.applying(transform),
diameter: (self.diameter * transform.scale).to_num(),
}
}
fn applying_inverse(&self, transform: &LinearTransform) -> Self {
Self {
origin: self.origin.applying_inverse(transform),
diameter: (self.diameter.to_fixed::<ScaleFactor>() / transform.scale).to_num(),
}
}
}
#[cfg(feature = "embedded-graphics")]
impl From<Circle> for embedded_graphics::primitives::Circle {
fn from(value: Circle) -> Self {
Self::new(value.origin.into(), value.diameter)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::transform::LinearTransform;
#[test]
fn applying_transform() {
let circle = Circle::new(Point::new(10, 20), 40);
let transform = LinearTransform::new(Point::new(5, 10), 2.0);
let transformed = circle.applying(&transform);
assert_eq!(transformed.origin, Point::new(25, 50));
assert_eq!(transformed.diameter, 80);
}
#[test]
fn applying_inverse_transform() {
let circle = Circle::new(Point::new(50, 80), 80);
let transform = LinearTransform::new(Point::new(5, 10), 2.0);
let inverse_transformed = circle.applying_inverse(&transform);
assert_eq!(inverse_transformed.origin, Point::new(22, 35));
assert_eq!(inverse_transformed.diameter, 40);
}
#[test]
fn transform_roundtrip() {
let original = Circle::new(Point::new(16, 24), 32);
let transform = LinearTransform::new(Point::new(4, 8), 2.0);
let transformed = original.applying(&transform).applying_inverse(&transform);
assert_eq!(transformed.origin, original.origin);
assert_eq!(transformed.diameter, original.diameter);
}
#[test]
fn identity_transform() {
let circle = Circle::new(Point::new(100, 200), 50);
let identity = LinearTransform::identity();
let transformed = circle.applying(&identity);
assert_eq!(transformed, circle);
}
}