#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![forbid(clippy::float_arithmetic)]
#![cfg(target_pointer_width = "64")]
#![deny(clippy::all)]
use core::fmt::Debug;
use core::iter::FusedIterator;
use core::marker::PhantomData;
use core::ops::{Add, AddAssign, RangeInclusive, Shr, Sub, SubAssign};
use iterextd::{Scaler, Scaling};
use num_convert::FromAs;
use num_traits::{CheckedAdd, Signed, Unsigned, Zero};
mod concentric_circles_adapters;
mod point_with_color;
mod radii;
mod scaling;
pub use concentric_circles_adapters::{
AddColor, AddColorRadial, CircleSectorSkip, CircleSectorTake, ConcentricCirclesAdapters, Crown,
PointWithRadius, StepRings, ToImageCoordinates,
};
pub use point_with_color::{
CrownWithColor, MapCircleSector, MapColorRadial, PointWithColor, ToImageCoordinatesWithColor,
};
pub use radii::Radii;
pub use scaling::ScalingU32;
#[derive(Clone, Debug, PartialEq)]
pub struct ConcentricCircles<T, U> {
x: T,
y: T,
d: T,
outer_radius: U,
inner_radius: U,
current_quadrant: u8,
radius_pending: bool,
}
impl<T, U> ConcentricCircles<T, U>
where
T: Signed + FromAs<U> + TryFrom<U> + Shr<Output = T> + CheckedAdd + Copy,
U: Unsigned + PartialOrd + Copy,
{
#[inline]
pub fn new(inner_radius: U, outer_radius: U) -> Self {
let current_quadrant = if (inner_radius == U::zero()) || (inner_radius > outer_radius) {
5
} else if let Ok(value) = T::try_from(outer_radius) {
if value.checked_add(&T::one()).is_some() {
0
} else {
5
}
} else {
5
};
Self {
x: T::one(),
y: T::from_as(inner_radius),
d: -(T::from_as(inner_radius) >> T::one()),
outer_radius,
inner_radius,
current_quadrant,
radius_pending: false,
}
}
}
impl<T, U> Iterator for ConcentricCircles<T, U>
where
T: Signed + AddAssign + FromAs<U> + PartialOrd + SubAssign + Shr<Output = T> + Copy,
U: Unsigned + AddAssign + PartialOrd + SubAssign + Copy,
usize: FromAs<U>,
{
type Item = (T, T);
#[allow(clippy::precedence)]
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.current_quadrant == 0 {
let xy = (self.x - T::one(), -self.y);
if self.radius_pending {
self.inner_radius += U::one();
self.radius_pending = false;
}
if self.d < T::zero() {
self.x += T::one();
self.d += self.x;
} else {
self.y -= T::one();
self.d -= self.y;
if self.y <= T::zero() {
self.x = T::one();
self.y = T::from_as(self.inner_radius);
self.d = -(T::from_as(self.inner_radius) >> T::one());
self.current_quadrant = 1;
}
}
Some((xy.0, xy.1))
} else if self.current_quadrant == 1 {
let xy = (self.y - T::one(), self.x - T::one());
if self.d < T::zero() {
self.x += T::one();
self.d += self.x;
} else {
self.y -= T::one();
self.d -= self.y;
if self.y <= T::zero() {
self.x = T::one();
self.y = T::from_as(self.inner_radius);
self.d = -(T::from_as(self.inner_radius) >> T::one());
self.current_quadrant = 2;
}
}
Some((xy.0, xy.1))
} else if self.current_quadrant == 2 {
let xy = (-self.x, self.y - T::one());
if self.d < T::zero() {
self.x += T::one();
self.d += self.x;
} else {
self.y -= T::one();
self.d -= self.y;
if self.y <= T::zero() {
self.x = T::one();
self.y = T::from_as(self.inner_radius);
self.d = -(T::from_as(self.inner_radius) >> T::one());
self.current_quadrant = 3;
}
}
Some((xy.0, xy.1))
} else if self.current_quadrant == 3 {
let xy = (-self.y, -self.x);
if self.d < T::zero() {
self.x += T::one();
self.d += self.x;
} else {
self.y -= T::one();
self.d -= self.y;
if self.y <= T::zero() {
if self.inner_radius == self.outer_radius {
self.current_quadrant = 4;
} else {
self.current_quadrant = 0;
self.radius_pending = true;
}
self.y = T::from_as(self.inner_radius) + T::one();
self.d = -(T::from_as(self.inner_radius) + T::one() >> T::one());
self.x = T::one();
return Some(xy);
}
}
Some(xy)
} else {
None
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(
0,
sum_circles_points(
usize::from_as(self.inner_radius),
usize::from_as(self.outer_radius),
),
)
}
}
impl<T, U> FusedIterator for ConcentricCircles<T, U>
where
T: Signed + FromAs<U> + PartialOrd + AddAssign + SubAssign + Shr<Output = T> + Copy,
U: Unsigned + PartialOrd + SubAssign + AddAssign + Copy,
usize: FromAs<U>,
{
}
#[inline]
pub fn points_in_circle(radius: usize) -> Option<usize> {
if radius == 0 {
None
} else {
Some(8_usize.checked_mul(radius)? - 4)
}
}
#[inline]
pub(crate) fn points_in_circle_unchecked(radius: usize) -> usize {
if radius == 0 {
0
} else {
8 * radius - 4
}
}
#[inline]
pub fn radius_from_circle_points(points: usize) -> Option<usize> {
let r = points.checked_add(4)?;
if r % 8 == 0 {
Some(r >> 3)
} else {
None
}
}
#[inline]
pub fn sum_circles_points(from: usize, to: usize) -> Option<usize> {
if from == 0 || from > to {
None
} else {
let n = to - from + 1;
let sum_r = from.checked_add(to)?.checked_mul(n)? / 2;
Some(8_usize.checked_mul(sum_r)? - 4_usize.checked_mul(n)?)
}
}
#[inline]
pub(crate) fn sum_circles_points_unchecked(from: usize, to: usize) -> usize {
let n = to - from + 1;
let sum_r = (from + to) * n / 2;
8 * sum_r - 4 * n
}
#[cfg(test)]
mod tests {
use super::sum_circles_points_unchecked;
#[test]
fn test_sum_points_in_circle_uncheckeds() {
assert_eq!(sum_circles_points_unchecked(1, 1), 4);
assert_eq!(sum_circles_points_unchecked(1, 2), 4 + 12);
assert_eq!(sum_circles_points_unchecked(2, 2), 12);
assert_eq!(sum_circles_points_unchecked(1, 3), 4 + 12 + 20);
assert_eq!(sum_circles_points_unchecked(2, 3), 12 + 20);
assert_eq!(sum_circles_points_unchecked(3, 3), 20);
assert_eq!(sum_circles_points_unchecked(1, 4), 4 + 12 + 20 + 28);
assert_eq!(sum_circles_points_unchecked(2, 4), 12 + 20 + 28);
assert_eq!(sum_circles_points_unchecked(3, 4), 20 + 28);
assert_eq!(sum_circles_points_unchecked(4, 4), 28);
assert_eq!(sum_circles_points_unchecked(1, 5), 4 + 12 + 20 + 28 + 36);
assert_eq!(sum_circles_points_unchecked(2, 5), 12 + 20 + 28 + 36);
assert_eq!(sum_circles_points_unchecked(3, 5), 20 + 28 + 36);
assert_eq!(sum_circles_points_unchecked(4, 5), 28 + 36);
assert_eq!(sum_circles_points_unchecked(5, 5), 36);
}
#[should_panic(expected = "attempt to add with overflow")]
#[test]
fn test_sum_points_in_circle_uncheckeds_max_values() {
let _ = sum_circles_points_unchecked(usize::MAX, usize::MAX);
}
#[should_panic(expected = "attempt to add with overflow")]
#[test]
fn test_sum_points_in_circle_uncheckeds_max_end_value() {
let _ = sum_circles_points_unchecked(1, usize::MAX);
}
}