Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tolerance parmeters to Shape methods #293

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
6 changes: 3 additions & 3 deletions src/arc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ impl Shape for Arc {

/// Note: shape isn't closed so area is not well defined.
#[inline]
fn area(&self) -> f64 {
fn area(&self, _tolerance: f64) -> f64 {
let Vec2 { x, y } = self.radii;
PI * x * y
}
Expand All @@ -183,12 +183,12 @@ impl Shape for Arc {

/// Note: shape isn't closed, so a point's winding number is not well defined.
#[inline]
fn winding(&self, pt: Point) -> i32 {
fn winding(&self, pt: Point, _tolerance: f64) -> i32 {
self.path_segments(0.1).winding(pt)
}

#[inline]
fn bounding_box(&self) -> Rect {
fn bounding_box(&self, _tolerance: f64) -> Rect {
self.path_segments(0.1).bounding_box()
}
}
Expand Down
50 changes: 25 additions & 25 deletions src/bezpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -705,23 +705,23 @@ impl<I: Iterator<Item = PathEl>> Segments<I> {
/// Here, `accuracy` specifies the accuracy for each Bézier segment. At worst,
/// the total error is `accuracy` times the number of Bézier segments.

// TODO: pub? Or is this subsumed by method of &[PathEl]?
pub(crate) fn perimeter(self, accuracy: f64) -> f64 {
/// Version of [`Shape::perimeter`] which consumes the iterator.
pub fn perimeter(self, accuracy: f64) -> f64 {
self.map(|seg| seg.arclen(accuracy)).sum()
}

// Same
pub(crate) fn area(self) -> f64 {
/// Version of [`Shape::area] which consumes the iterator and needs no tolerance parameter.
george-steel marked this conversation as resolved.
Show resolved Hide resolved
pub fn area(self) -> f64 {
self.map(|seg| seg.signed_area()).sum()
}

// Same
pub(crate) fn winding(self, p: Point) -> i32 {
/// Version of [`Shape::winding`] which consumes the iterator and needs no tolerance parameter.
pub fn winding(self, p: Point) -> i32 {
self.map(|seg| seg.winding(p)).sum()
}

// Same
pub(crate) fn bounding_box(self) -> Rect {
/// Version of [`Shape::bounding_box`] which consumes the iterator and needs no tolerance parameter.
pub fn bounding_box(self) -> Rect {
let mut bbox: Option<Rect> = None;
for seg in self {
let seg_bb = ParamCurveExtrema::bounding_box(&seg);
Expand Down Expand Up @@ -1204,21 +1204,21 @@ impl Shape for BezPath {
}

/// Signed area.
fn area(&self) -> f64 {
self.elements().area()
fn area(&self, tolerance: f64) -> f64 {
self.elements().area(tolerance)
}

fn perimeter(&self, accuracy: f64) -> f64 {
self.elements().perimeter(accuracy)
}

/// Winding number of point.
fn winding(&self, pt: Point) -> i32 {
self.elements().winding(pt)
fn winding(&self, pt: Point, tolerance: f64) -> i32 {
self.elements().winding(pt, tolerance)
}

fn bounding_box(&self) -> Rect {
self.elements().bounding_box()
fn bounding_box(&self, tolerance: f64) -> Rect {
self.elements().bounding_box(tolerance)
}

fn as_path_slice(&self) -> Option<&[PathEl]> {
Expand Down Expand Up @@ -1282,7 +1282,7 @@ impl<'a> Shape for &'a [PathEl] {
}

/// Signed area.
fn area(&self) -> f64 {
fn area(&self, _tolerance: f64) -> f64 {
segments(self.iter().copied()).area()
}

Expand All @@ -1291,11 +1291,11 @@ impl<'a> Shape for &'a [PathEl] {
}

/// Winding number of point.
fn winding(&self, pt: Point) -> i32 {
fn winding(&self, pt: Point, _tolerance: f64) -> i32 {
segments(self.iter().copied()).winding(pt)
}

fn bounding_box(&self) -> Rect {
fn bounding_box(&self, _tolerance: f64) -> Rect {
segments(self.iter().copied()).bounding_box()
}

Expand All @@ -1322,7 +1322,7 @@ impl<const N: usize> Shape for [PathEl; N] {
}

/// Signed area.
fn area(&self) -> f64 {
fn area(&self, _tolerance: f64) -> f64 {
segments(self.iter().copied()).area()
}

Expand All @@ -1331,11 +1331,11 @@ impl<const N: usize> Shape for [PathEl; N] {
}

/// Winding number of point.
fn winding(&self, pt: Point) -> i32 {
fn winding(&self, pt: Point, _tolerance: f64) -> i32 {
segments(self.iter().copied()).winding(pt)
}

fn bounding_box(&self) -> Rect {
fn bounding_box(&self, _tolerance: f64) -> Rect {
segments(self.iter().copied()).bounding_box()
}

Expand All @@ -1362,7 +1362,7 @@ impl Shape for PathSeg {
/// The area under the curve.
///
/// We could just return 0, but this seems more useful.
fn area(&self) -> f64 {
fn area(&self, _tolerance: f64) -> f64 {
self.signed_area()
}

Expand All @@ -1371,12 +1371,12 @@ impl Shape for PathSeg {
self.arclen(accuracy)
}

fn winding(&self, _pt: Point) -> i32 {
fn winding(&self, _pt: Point, _tolerance: f64) -> i32 {
0
}

#[inline]
fn bounding_box(&self) -> Rect {
fn bounding_box(&self, _tolerance: f64) -> Rect {
ParamCurveExtrema::bounding_box(self)
}

Expand Down Expand Up @@ -1496,8 +1496,8 @@ mod tests {
path.line_to((1.0, 1.0));
path.line_to((2.0, 0.0));
path.close_path();
assert_eq!(path.winding(Point::new(1.0, 0.5)), -1);
assert!(path.contains(Point::new(1.0, 0.5)));
assert_eq!(path.winding(Point::new(1.0, 0.5), 1.0), -1);
assert!(path.contains(Point::new(1.0, 0.5), 1.0));
}

// get_seg(i) should produce the same results as path_segments().nth(i - 1).
Expand Down
31 changes: 17 additions & 14 deletions src/circle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl Shape for Circle {
}

#[inline]
fn area(&self) -> f64 {
fn area(&self, _tolerance: f64) -> f64 {
PI * self.radius.powi(2)
}

Expand All @@ -134,7 +134,7 @@ impl Shape for Circle {
(2.0 * PI * self.radius).abs()
}

fn winding(&self, pt: Point) -> i32 {
fn winding(&self, pt: Point, _tolerance: f64) -> i32 {
if (pt - self.center).hypot2() < self.radius.powi(2) {
1
} else {
Expand All @@ -143,7 +143,7 @@ impl Shape for Circle {
}

#[inline]
fn bounding_box(&self) -> Rect {
fn bounding_box(&self, _tolerance: f64) -> Rect {
let r = self.radius.abs();
let (x, y) = self.center.into();
Rect::new(x - r, y - r, x + r, y + r)
Expand Down Expand Up @@ -320,7 +320,7 @@ impl Shape for CircleSegment {
}

#[inline]
fn area(&self) -> f64 {
fn area(&self, _tolerance: f64) -> f64 {
0.5 * (self.outer_radius.powi(2) - self.inner_radius.powi(2)).abs() * self.sweep_angle
}

Expand All @@ -330,7 +330,7 @@ impl Shape for CircleSegment {
+ self.sweep_angle * (self.inner_radius + self.outer_radius)
}

fn winding(&self, pt: Point) -> i32 {
fn winding(&self, pt: Point, _tolerance: f64) -> i32 {
let angle = (pt - self.center).atan2();
if angle < self.start_angle || angle > self.start_angle + self.sweep_angle {
return 0;
Expand All @@ -347,7 +347,7 @@ impl Shape for CircleSegment {
}

#[inline]
fn bounding_box(&self) -> Rect {
fn bounding_box(&self, _tolerance: f64) -> Rect {
// todo this is currently not tight
let r = self.inner_radius.max(self.outer_radius);
let (x, y) = self.center.into();
Expand All @@ -370,22 +370,25 @@ mod tests {
fn area_sign() {
let center = Point::new(5.0, 5.0);
let c = Circle::new(center, 5.0);
assert_approx_eq(c.area(), 25.0 * PI);
assert_approx_eq(c.area(1.0), 25.0 * PI);
george-steel marked this conversation as resolved.
Show resolved Hide resolved

assert_eq!(c.winding(center), 1);
assert_eq!(c.winding(center, 1.0), 1);

let p = c.to_path(1e-9);
assert_approx_eq(c.area(), p.area());
assert_eq!(c.winding(center), p.winding(center));
assert_approx_eq(c.area(1.0), p.area(1.0));
assert_eq!(c.winding(center, 1.0), p.winding(center, 1.0));

let c_neg_radius = Circle::new(center, -5.0);
assert_approx_eq(c_neg_radius.area(), 25.0 * PI);
assert_approx_eq(c_neg_radius.area(1.0), 25.0 * PI);

assert_eq!(c_neg_radius.winding(center), 1);
assert_eq!(c_neg_radius.winding(center, 1.0), 1);

let p_neg_radius = c_neg_radius.to_path(1e-9);
assert_approx_eq(c_neg_radius.area(), p_neg_radius.area());
assert_eq!(c_neg_radius.winding(center), p_neg_radius.winding(center));
assert_approx_eq(c_neg_radius.area(1.0), p_neg_radius.area(1.0));
assert_eq!(
c_neg_radius.winding(center, 1.0),
p_neg_radius.winding(center, 1.0)
);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/cubicbez.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ impl Shape for CubicBez {
}
}

fn area(&self) -> f64 {
fn area(&self, _tolerance: f64) -> f64 {
0.0
}

Expand All @@ -479,12 +479,12 @@ impl Shape for CubicBez {
self.arclen(accuracy)
}

fn winding(&self, _pt: Point) -> i32 {
fn winding(&self, _pt: Point, _tolerance: f64) -> i32 {
0
}

#[inline]
fn bounding_box(&self) -> Rect {
fn bounding_box(&self, _tolerance: f64) -> Rect {
ParamCurveExtrema::bounding_box(self)
}
}
Expand Down
27 changes: 15 additions & 12 deletions src/ellipse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ impl Shape for Ellipse {
}

#[inline]
fn area(&self) -> f64 {
fn area(&self, _tolerance: f64) -> f64 {
let Vec2 { x, y } = self.radii();
PI * x * y
}
Expand All @@ -236,7 +236,7 @@ impl Shape for Ellipse {
self.path_segments(0.1).perimeter(accuracy)
}

fn winding(&self, pt: Point) -> i32 {
fn winding(&self, pt: Point, _tolerance: f64) -> i32 {
// Strategy here is to apply the inverse map to the point and see if it is in the unit
// circle.
let inv = self.inner.inverse();
Expand All @@ -259,7 +259,7 @@ impl Shape for Ellipse {
//
// We can then use the method in the link with the translation to get the bounding box.
#[inline]
fn bounding_box(&self) -> Rect {
fn bounding_box(&self, _tolerance: f64) -> Rect {
let aff = self.inner.as_coeffs();
let a2 = aff[0] * aff[0];
let b2 = aff[1] * aff[1];
Expand Down Expand Up @@ -293,23 +293,26 @@ mod tests {
fn area_sign() {
let center = Point::new(5.0, 5.0);
let e = Ellipse::new(center, (5.0, 5.0), 1.0);
assert_approx_eq(e.area(), 25.0 * PI);
assert_approx_eq(e.area(1.0), 25.0 * PI);
let e = Ellipse::new(center, (5.0, 10.0), 1.0);
assert_approx_eq(e.area(), 50.0 * PI);
assert_approx_eq(e.area(1.0), 50.0 * PI);

assert_eq!(e.winding(center), 1);
assert_eq!(e.winding(center, 1.0), 1);

let p = e.to_path(1e-9);
assert_approx_eq(e.area(), p.area());
assert_eq!(e.winding(center), p.winding(center));
assert_approx_eq(e.area(1.0), p.area(1.0));
assert_eq!(e.winding(center, 1.0), p.winding(center, 1.0));

let e_neg_radius = Ellipse::new(center, (-5.0, 10.0), 1.0);
assert_approx_eq(e_neg_radius.area(), 50.0 * PI);
assert_approx_eq(e_neg_radius.area(1.0), 50.0 * PI);

assert_eq!(e_neg_radius.winding(center), 1);
assert_eq!(e_neg_radius.winding(center, 1.0), 1);

let p_neg_radius = e_neg_radius.to_path(1e-9);
assert_approx_eq(e_neg_radius.area(), p_neg_radius.area());
assert_eq!(e_neg_radius.winding(center), p_neg_radius.winding(center));
assert_approx_eq(e_neg_radius.area(1.0), p_neg_radius.area(1.0));
assert_eq!(
e_neg_radius.winding(center, 1.0),
p_neg_radius.winding(center, 1.0)
);
}
}
6 changes: 3 additions & 3 deletions src/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ impl Shape for Line {
/// only meaningful for closed shapes), but an argument can be made
/// that the contract should be tightened to include the Green's
/// theorem contribution.
fn area(&self) -> f64 {
fn area(&self, _tolerance: f64) -> f64 {
0.0
}

Expand All @@ -262,12 +262,12 @@ impl Shape for Line {
}

/// Same consideration as `area`.
fn winding(&self, _pt: Point) -> i32 {
fn winding(&self, _pt: Point, _tolerance: f64) -> i32 {
0
}

#[inline]
fn bounding_box(&self) -> Rect {
fn bounding_box(&self, _tolerance: f64) -> Rect {
Rect::from_points(self.p0, self.p1)
}

Expand Down
Loading