Skip to content

Commit

Permalink
Add the Rect::overlaps and Rect::contains_rect methods (#347)
Browse files Browse the repository at this point in the history
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));
```
  • Loading branch information
nils-mathieu committed Aug 3, 2024
1 parent 106317e commit a568ab2
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down
120 changes: 120 additions & 0 deletions src/rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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));
}
}

0 comments on commit a568ab2

Please sign in to comment.