From a568ab23f68d5a03efcd6dcfeb3096652b767c74 Mon Sep 17 00:00:00 2001 From: Nils <80390054+nils-mathieu@users.noreply.github.com> Date: Sat, 3 Aug 2024 02:30:51 +0200 Subject: [PATCH] Add the `Rect::overlaps` and `Rect::contains_rect` methods (#347) I stumbled on this while toying with the `vello` rendering engine (which is great btw, you guys are doing god's work). I'm trying to create a simple audio sequencer with it and while trying to determine whether shapes were visible on screen, I found that there was no way of checking for the intersection of two `Rect`s. Right now, the only way to do that is to compute their intersection, then verify whether it's empty or not. ```Rust let rect1 = Rect::new(0.0, 0.0, 10.0, 10.0); let rect2 = Rect::new(5.0, 5.0, 15.0, 15.0); assert!(!rect1.intersect(rect2).is_empty()); ``` This is not ideal for two reasons: 1. It's not very ergonomic. Checking whether two rectangles overlap should probably not involve that intermediary step. 2. This is doing a bunch of wasted work to find out what the intersection is when we only care about whether it's empty or not. This pull request adds `Rect::overlaps` and `Rect::contains_rect`, which do exactly that. ```Rust let rect1 = Rect::new(0.0, 0.0, 10.0, 10.0); let rect2 = Rect::new(5.0, 5.0, 15.0, 15.0); assert!(rect1.overlaps(rect2)); assert!(!rect1.contains_rect(rect2)); ``` --- CHANGELOG.md | 3 ++ src/rect.rs | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2e1e929..b344df0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ This release has an [MSRV][] of 1.65. ### Added - Add `From (f32, f32)` for `Point`. ([#339] by [@rsheeter]) +- Add `Rect::overlaps` and `Rect::contains_rect`. ([#347] by [@nils-mathieu]) ### Changed @@ -35,6 +36,7 @@ This release has an [MSRV][] of 1.65. Note: A changelog was not kept for or before this release +[@nils-mathieu]: https://github.com/nils-mathieu [@platlas]: https://github.com/platlas [@raphlinus]: https://github.com/raphlinus [@rsheeter]: https://github.com/rsheeter @@ -45,6 +47,7 @@ Note: A changelog was not kept for or before this release [#339]: https://github.com/linebender/kurbo/pull/339 [#340]: https://github.com/linebender/kurbo/pull/340 [#343]: https://github.com/linebender/kurbo/pull/343 +[#347]: https://github.com/linebender/kurbo/pull/347 [#354]: https://github.com/linebender/kurbo/pull/354 [Unreleased]: https://github.com/linebender/kurbo/compare/v0.11.0...HEAD diff --git a/src/rect.rs b/src/rect.rs index 397b9785..eeeb4aa3 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -222,6 +222,11 @@ impl Rect { /// /// The result is zero-area if either input has negative width or /// height. The result always has non-negative width and height. + /// + /// If you want to determine whether two rectangles intersect, use the + /// [`overlaps`] method instead. + /// + /// [`overlaps`]: Rect::overlaps #[inline] pub fn intersect(&self, other: Rect) -> Rect { let x0 = self.x0.max(other.x0); @@ -231,6 +236,64 @@ impl Rect { Rect::new(x0, y0, x1.max(x0), y1.max(y0)) } + /// Determines whether this rectangle overlaps with another in any way. + /// + /// Note that the edge of the rectangle is considered to be part of itself, meaning + /// that two rectangles that share an edge are considered to overlap. + /// + /// Returns `true` if the rectangles overlap, `false` otherwise. + /// + /// If you want to compute the *intersection* of two rectangles, use the + /// [`intersect`] method instead. + /// + /// [`intersect`]: Rect::intersect + /// + /// # Examples + /// + /// ``` + /// use kurbo::Rect; + /// + /// let rect1 = Rect::new(0.0, 0.0, 10.0, 10.0); + /// let rect2 = Rect::new(5.0, 5.0, 15.0, 15.0); + /// assert!(rect1.overlaps(rect2)); + /// + /// let rect1 = Rect::new(0.0, 0.0, 10.0, 10.0); + /// let rect2 = Rect::new(10.0, 0.0, 20.0, 10.0); + /// assert!(rect1.overlaps(rect2)); + /// ``` + #[inline] + pub fn overlaps(&self, other: Rect) -> bool { + self.x0 <= other.x1 && self.x1 >= other.x0 && self.y0 <= other.y1 && self.y1 >= other.y0 + } + + /// Returns whether this rectangle contains another rectangle. + /// + /// A rectangle is considered to contain another rectangle if the other + /// rectangle is fully enclosed within the bounds of this rectangle. + /// + /// # Examples + /// + /// ``` + /// use kurbo::Rect; + /// + /// let rect1 = Rect::new(0.0, 0.0, 10.0, 10.0); + /// let rect2 = Rect::new(2.0, 2.0, 4.0, 4.0); + /// assert!(rect1.contains_rect(rect2)); + /// ``` + /// + /// Two equal rectangles are considered to contain each other. + /// + /// ``` + /// use kurbo::Rect; + /// + /// let rect = Rect::new(0.0, 0.0, 10.0, 10.0); + /// assert!(rect.contains_rect(rect)); + /// ``` + #[inline] + pub fn contains_rect(&self, other: Rect) -> bool { + self.x0 <= other.x0 && self.y0 <= other.y0 && self.x1 >= other.x1 && self.y1 >= other.y1 + } + /// Expand a rectangle by a constant amount in both directions. /// /// The logic simply applies the amount in each direction. If rectangle @@ -768,4 +831,61 @@ mod tests { let test = Rect::new(0.0, 0.0, 1.0, 1.0); assert!((test.aspect_ratio() - 1.0).abs() < 1e-6); } + + #[test] + fn contained_rect_overlaps() { + let outer = Rect::new(0.0, 0.0, 10.0, 10.0); + let inner = Rect::new(2.0, 2.0, 4.0, 4.0); + assert!(outer.overlaps(inner)); + } + + #[test] + fn overlapping_rect_overlaps() { + let a = Rect::new(0.0, 0.0, 10.0, 10.0); + let b = Rect::new(5.0, 5.0, 15.0, 15.0); + assert!(a.overlaps(b)); + } + + #[test] + fn disjoint_rect_overlaps() { + let a = Rect::new(0.0, 0.0, 10.0, 10.0); + let b = Rect::new(11.0, 11.0, 15.0, 15.0); + assert!(!a.overlaps(b)); + } + + #[test] + fn sharing_edge_overlaps() { + let a = Rect::new(0.0, 0.0, 10.0, 10.0); + let b = Rect::new(10.0, 0.0, 20.0, 10.0); + assert!(a.overlaps(b)); + } + + // Test the two other directions in case there is a bug that only appears in one direction. + #[test] + fn disjoint_rect_overlaps_negative() { + let a = Rect::new(0.0, 0.0, 10.0, 10.0); + let b = Rect::new(-10.0, -10.0, -5.0, -5.0); + assert!(!a.overlaps(b)); + } + + #[test] + fn contained_rectangle_contains() { + let outer = Rect::new(0.0, 0.0, 10.0, 10.0); + let inner = Rect::new(2.0, 2.0, 4.0, 4.0); + assert!(outer.contains_rect(inner)); + } + + #[test] + fn overlapping_rectangle_contains() { + let outer = Rect::new(0.0, 0.0, 10.0, 10.0); + let inner = Rect::new(5.0, 5.0, 15.0, 15.0); + assert!(!outer.contains_rect(inner)); + } + + #[test] + fn disjoint_rectangle_contains() { + let outer = Rect::new(0.0, 0.0, 10.0, 10.0); + let inner = Rect::new(11.0, 11.0, 15.0, 15.0); + assert!(!outer.contains_rect(inner)); + } }