use libm;
use crate::{arc::ArcAppendIter, Arc, PathEl, Point, Rect, Shape, Vec2};
use core::f64::consts::{FRAC_PI_2, PI};
#[derive(Clone, Copy, Default, Debug)]
pub struct RoundedRect {
rect: Rect,
radius: f64,
}
impl RoundedRect {
#[inline]
pub fn new(x0: f64, y0: f64, x1: f64, y1: f64, radius: f64) -> RoundedRect {
RoundedRect::from_rect(Rect::new(x0, y0, x1, y1), radius)
}
#[inline]
pub fn from_rect(rect: Rect, radius: f64) -> RoundedRect {
let rect = rect.abs();
let radius =
libm::fabs(radius)
.min(rect.width() / 2.0)
.min(rect.height() / 2.0);
RoundedRect { rect, radius }
}
#[inline]
pub fn from_points(p0: Point, p1: Point, radius: f64) -> RoundedRect {
RoundedRect::new(p0.x, p0.y, p1.x, p1.y, radius)
}
#[inline]
pub fn from_origin_size(origin: Point, size: Vec2, radius: f64) -> RoundedRect {
RoundedRect::from_points(origin, origin + size, radius)
}
#[inline]
pub fn width(&self) -> f64 {
self.rect.width()
}
#[inline]
pub fn height(&self) -> f64 {
self.rect.height()
}
pub fn radius(&self) -> f64 {
self.radius
}
pub fn rect(&self) -> Rect {
self.rect
}
#[inline]
pub fn origin(&self) -> Point {
self.rect.origin()
}
#[inline]
pub fn center(&self) -> Point {
self.rect.center()
}
}
#[doc(hidden)]
pub struct RoundedRectPathIter {
idx: usize,
rect: RectPathIter,
arcs: [ArcAppendIter; 4],
}
impl Shape for RoundedRect {
type BezPathIter = RoundedRectPathIter;
fn to_bez_path(&self, tolerance: f64) -> RoundedRectPathIter {
let radius = self.radius();
let radii = Vec2 {
x: self.radius,
y: self.radius,
};
let build_arc_iter = |i, center| {
let arc = Arc {
center,
radii,
start_angle: FRAC_PI_2 * i as f64,
sweep_angle: FRAC_PI_2,
x_rotation: 0.0,
};
arc.append_iter(tolerance)
};
let arcs = [
build_arc_iter(
2,
Point {
x: self.rect.x0 + radius,
y: self.rect.y0 + radius,
},
),
build_arc_iter(
3,
Point {
x: self.rect.x1 - radius,
y: self.rect.y0 + radius,
},
),
build_arc_iter(
0,
Point {
x: self.rect.x1 - radius,
y: self.rect.y1 - radius,
},
),
build_arc_iter(
1,
Point {
x: self.rect.x0 + radius,
y: self.rect.y1 - radius,
},
),
];
let rect = RectPathIter {
rect: self.rect,
ix: 0,
radius: self.radius,
};
RoundedRectPathIter { idx: 0, rect, arcs }
}
#[inline]
fn area(&self) -> f64 {
let radius = self.radius();
self.rect.area() - (4.0 - PI) * radius * radius
}
#[inline]
fn perimeter(&self, _accuracy: f64) -> f64 {
let radius = self.radius();
2.0 * (self.width() + self.height()) - 8.0 * radius + 2.0 * PI * radius
}
#[inline]
fn winding(&self, mut pt: Point) -> i32 {
let center = self.center();
let radius = self.radius();
let inside_half_width = (self.width() / 2.0 - radius).max(0.0);
let inside_half_height = (self.height() / 2.0 - radius).max(0.0);
pt.x -= center.x;
pt.y -= center.y;
let px = (libm::fabs(pt.x) - inside_half_width).max(0.0);
let py = (libm::fabs(pt.y) - inside_half_height).max(0.0);
let inside = px * px + py * py <= radius * radius;
if inside {
1
} else {
0
}
}
#[inline]
fn bounding_box(&self) -> Rect {
self.rect.bounding_box()
}
#[inline]
fn as_rounded_rect(&self) -> Option<RoundedRect> {
Some(*self)
}
}
struct RectPathIter {
rect: Rect,
radius: f64,
ix: usize,
}
impl Iterator for RectPathIter {
type Item = PathEl;
fn next(&mut self) -> Option<PathEl> {
self.ix += 1;
match self.ix {
1 => Some(PathEl::MoveTo(Point::new(
self.rect.x0,
self.rect.y0 + self.radius,
))),
2 => Some(PathEl::LineTo(Point::new(
self.rect.x1 - self.radius,
self.rect.y0,
))),
3 => Some(PathEl::LineTo(Point::new(
self.rect.x1,
self.rect.y1 - self.radius,
))),
4 => Some(PathEl::LineTo(Point::new(
self.rect.x0 + self.radius,
self.rect.y1,
))),
5 => Some(PathEl::ClosePath),
_ => None,
}
}
}
impl Iterator for RoundedRectPathIter {
type Item = PathEl;
fn next(&mut self) -> Option<PathEl> {
if self.idx > 4 {
return None;
}
if self.idx == 0 {
self.idx += 1;
return self.rect.next();
}
match self.arcs[self.idx - 1].next() {
Some(elem) => Some(elem),
None => {
self.idx += 1;
self.rect.next()
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{Circle, Point, Rect, RoundedRect, Shape};
#[test]
fn area() {
let epsilon = 1e-9;
let rect = Rect::new(0.0, 0.0, 100.0, 100.0);
let rounded_rect = RoundedRect::new(0.0, 0.0, 100.0, 100.0, 0.0);
assert!((rect.area() - rounded_rect.area()).abs() < epsilon);
let circle = Circle::new((0.0, 0.0), 50.0);
let rounded_rect = RoundedRect::new(0.0, 0.0, 100.0, 100.0, 50.0);
assert!((circle.area() - rounded_rect.area()).abs() < epsilon);
}
#[test]
fn winding() {
let rect = RoundedRect::new(-5.0, -5.0, 10.0, 20.0, 5.0);
assert_eq!(rect.winding(Point::new(0.0, 0.0)), 1);
assert_eq!(rect.winding(Point::new(-5.0, 0.0)), 1);
assert_eq!(rect.winding(Point::new(0.0, 20.0)), 1);
assert_eq!(rect.winding(Point::new(10.0, 20.0)), 0);
assert_eq!(rect.winding(Point::new(-10.0, 0.0)), 0);
let rect = RoundedRect::new(-10.0, -20.0, 10.0, 20.0, 0.0);
assert_eq!(rect.winding(Point::new(10.0, 20.0)), 1);
}
#[test]
fn bez_conversion() {
let rect = RoundedRect::new(-5.0, -5.0, 10.0, 20.0, 5.0);
let p = rect.into_bez_path(1e-9);
let epsilon = 1e-7;
assert!((rect.area() - p.area()).abs() < epsilon);
assert_eq!(p.winding(Point::new(0.0, 0.0)), 1);
}
}