// Copyright 2013 The Servo Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms.
#[cfg(feature = "bytemuck")] use bytemuck::{Pod, Zeroable}; #[cfg(feature = "malloc_size_of")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use num_traits::{Float, NumCast}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize};
use core::borrow::Borrow; use core::cmp::PartialOrd; use core::fmt; use core::hash::{Hash, Hasher}; use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Range, Sub};
/// A 2d axis aligned rectangle represented by its minimum and maximum coordinates. /// /// # Representation /// /// This struct is similar to [`Rect`], but stores rectangle as two endpoints /// instead of origin point and size. Such representation has several advantages over /// [`Rect`] representation: /// - Several operations are more efficient with `Box2D`, including [`intersection`], /// [`union`], and point-in-rect. /// - The representation is less susceptible to overflow. With [`Rect`], computation /// of second point can overflow for a large range of values of origin and size. /// However, with `Box2D`, computation of [`size`] cannot overflow if the coordinates /// are signed and the resulting size is unsigned. /// /// A known disadvantage of `Box2D` is that translating the rectangle requires translating /// both points, whereas translating [`Rect`] only requires translating one point. /// /// # Empty box /// /// A box is considered empty (see [`is_empty`]) if any of the following is true: /// - it's area is empty, /// - it's area is negative (`min.x > max.x` or `min.y > max.y`), /// - it contains NaNs. /// /// [`intersection`]: Self::intersection /// [`is_empty`]: Self::is_empty /// [`union`]: Self::union /// [`size`]: Self::size #[repr(C)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(
feature = "serde",
serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
)] pubstruct Box2D<T, U> { pub min: Point2D<T, U>, pub max: Point2D<T, U>,
}
impl<T, U> Box2D<T, U> where
T: PartialOrd,
{ /// Returns `true` if the box has a negative area. /// /// The common interpretation for a negative box is to consider it empty. It can be obtained /// by calculating the intersection of two boxes that do not intersect. #[inline] pubfn is_negative(&self) -> bool { self.max.x < self.min.x || self.max.y < self.min.y
}
/// Returns `true` if the size is zero, negative or NaN. #[inline] pubfn is_empty(&self) -> bool {
!(self.max.x > self.min.x && self.max.y > self.min.y)
}
/// Returns `true` if the two boxes intersect. #[inline] pubfn intersects(&self, other: &Self) -> bool { // Use bitwise and instead of && to avoid emitting branches.
(self.min.x < other.max.x)
& (self.max.x > other.min.x)
& (self.min.y < other.max.y)
& (self.max.y > other.min.y)
}
/// Returns `true` if this [`Box2D`] contains the point `p`. /// /// Points on the top and left edges are inside the box, whereas /// points on the bottom and right edges are outside the box. /// See [`Box2D::contains_inclusive`] for a variant that also includes those /// latter points. /// /// # Examples /// /// ``` /// use euclid::default::{Box2D, Point2D}; /// /// let rect = Box2D::new(Point2D::origin(), Point2D::new(2, 2)); /// /// assert!(rect.contains(Point2D::new(1, 1))); /// /// assert!(rect.contains(Point2D::new(0, 1))); // left edge /// assert!(rect.contains(Point2D::new(1, 0))); // top edge /// assert!(rect.contains(Point2D::origin())); /// /// assert!(!rect.contains(Point2D::new(2, 1))); // right edge /// assert!(!rect.contains(Point2D::new(1, 2))); // bottom edge /// assert!(!rect.contains(Point2D::new(2, 2))); /// ``` #[inline] pubfn contains(&self, p: Point2D<T, U>) -> bool { // Use bitwise and instead of && to avoid emitting branches.
(self.min.x <= p.x) & (p.x < self.max.x) & (self.min.y <= p.y) & (p.y < self.max.y)
}
/// Returns `true` if this box contains the point `p`. /// /// This is like [`Box2D::contains`], but points on the bottom and right /// edges are also inside the box. /// /// # Examples /// ``` /// use euclid::default::{Box2D, Point2D}; /// /// let rect = Box2D::new(Point2D::origin(), Point2D::new(2, 2)); /// /// assert!(rect.contains_inclusive(Point2D::new(1, 1))); /// /// assert!(rect.contains_inclusive(Point2D::new(0, 1))); // left edge /// assert!(rect.contains_inclusive(Point2D::new(1, 0))); // top edge /// assert!(rect.contains_inclusive(Point2D::origin())); /// /// assert!(rect.contains_inclusive(Point2D::new(2, 1))); // right edge /// assert!(rect.contains_inclusive(Point2D::new(1, 2))); // bottom edge /// assert!(rect.contains_inclusive(Point2D::new(2, 2))); /// ``` #[inline] pubfn contains_inclusive(&self, p: Point2D<T, U>) -> bool { // Use bitwise and instead of && to avoid emitting branches.
(self.min.x <= p.x) & (p.x <= self.max.x) & (self.min.y <= p.y) & (p.y <= self.max.y)
}
/// Returns `true` if this box contains the interior of the other box. Always /// returns `true` if other is empty, and always returns `false` if other is /// nonempty but this box is empty. #[inline] pubfn contains_box(&self, other: &Self) -> bool {
other.is_empty()
|| ((self.min.x <= other.min.x)
& (other.max.x <= self.max.x)
& (self.min.y <= other.min.y)
& (other.max.y <= self.max.y))
}
}
/// Computes the intersection of two boxes, returning `None` if the boxes do not intersect. #[inline] pubfn intersection(&self, other: &Self) -> Option<Self> { let b = self.intersection_unchecked(other);
if b.is_empty() { return None;
}
Some(b)
}
/// Computes the intersection of two boxes without check whether they do intersect. /// /// The result is a negative box if the boxes do not intersect. /// This can be useful for computing the intersection of more than two boxes, as /// it is possible to chain multiple `intersection_unchecked` calls and check for /// empty/negative result at the end. #[inline] pubfn intersection_unchecked(&self, other: &Self) -> Self {
Box2D {
min: point2(max(self.min.x, other.min.x), max(self.min.y, other.min.y)),
max: point2(min(self.max.x, other.max.x), min(self.max.y, other.max.y)),
}
}
/// Computes the union of two boxes. /// /// If either of the boxes is empty, the other one is returned. #[inline] pubfn union(&self, other: &Self) -> Self { if other.is_empty() { return *self;
} ifself.is_empty() { return *other;
}
/// Change the size of the box by adjusting the max endpoint /// without modifying the min endpoint. #[inline] pubfn set_size(&mutself, size: Size2D<T, U>) { let diff = (self.size() - size).to_vector(); self.max -= diff;
}
#[inline] pubfn width(&self) -> T { self.max.x - self.min.x
}
#[inline] pubfn height(&self) -> T { self.max.y - self.min.y
}
impl<T, U> Box2D<T, U> where
T: Copy + Add<T, Output = T> + Sub<T, Output = T>,
{ /// Inflates the box by the specified sizes on each side respectively. #[inline] #[must_use] pubfn inflate(&self, width: T, height: T) -> Self {
Box2D {
min: point2(self.min.x - width, self.min.y - height),
max: point2(self.max.x + width, self.max.y + height),
}
}
/// Calculate the size and position of an inner box. /// /// Subtracts the side offsets from all sides. The horizontal, vertical /// and applicate offsets must not be larger than the original side length. pubfn inner_box(&self, offsets: SideOffsets2D<T, U>) -> Self {
Box2D {
min: self.min + vec2(offsets.left, offsets.top),
max: self.max - vec2(offsets.right, offsets.bottom),
}
}
/// Calculate the b and position of an outer box. /// /// Add the offsets to all sides. The expanded box is returned. pubfn outer_box(&self, offsets: SideOffsets2D<T, U>) -> Self {
Box2D {
min: self.min - vec2(offsets.left, offsets.top),
max: self.max + vec2(offsets.right, offsets.bottom),
}
}
}
impl<T, U> Box2D<T, U> where
T: Copy + Zero + PartialOrd,
{ /// Returns the smallest box enclosing all of the provided points. /// /// The top/bottom/left/right-most points are exactly on the box's edges. /// Since [`Box2D::contains`] excludes points that are on the right-most and /// bottom-most edges, not all points passed to [`Box2D::from_points`] are /// contained in the returned [`Box2D`] when probed with [`Box2D::contains`], but /// are when probed with [`Box2D::contains_inclusive`]. /// /// For example: /// /// ``` /// use euclid::default::{Point2D, Box2D}; /// /// let a = Point2D::origin(); /// let b = Point2D::new(1, 2); /// let rect = Box2D::from_points([a, b]); /// /// assert_eq!(rect.width(), 1); /// assert_eq!(rect.height(), 2); /// /// assert!(rect.contains(a)); /// assert!(!rect.contains(b)); /// assert!(rect.contains_inclusive(b)); /// ``` /// /// In particular, calling [`Box2D::from_points`] with a single point /// results in an empty [`Box2D`]: /// /// ``` /// use euclid::default::{Point2D, Box2D}; /// /// let a = Point2D::new(1, 0); /// let rect = Box2D::from_points([a]); /// /// assert!(rect.is_empty()); /// assert!(!rect.contains(a)); /// assert!(rect.contains_inclusive(a)); /// ``` /// /// The [`Box2D`] enclosing no points is also empty: /// /// ``` /// use euclid::default::{Box2D, Point2D}; /// /// let rect = Box2D::from_points(std::iter::empty::<Point2D<i32>>()); /// assert!(rect.is_empty()); /// ``` pubfn from_points<I>(points: I) -> Self where
I: IntoIterator,
I::Item: Borrow<Point2D<T, U>>,
{ letmut points = points.into_iter();
let (mut min_x, mut min_y) = match points.next() {
Some(first) => first.borrow().to_tuple(),
None => return Box2D::zero(),
};
let (mut max_x, mut max_y) = (min_x, min_y); for point in points { let p = point.borrow(); if p.x < min_x {
min_x = p.x;
} if p.x > max_x {
max_x = p.x;
} if p.y < min_y {
min_y = p.y;
} if p.y > max_y {
max_y = p.y;
}
}
/// Drop the units, preserving only the numeric value. #[inline] pubfn to_untyped(&self) -> Box2D<T, UnknownUnit> {
Box2D::new(self.min.to_untyped(), self.max.to_untyped())
}
/// Tag a unitless value with units. #[inline] pubfn from_untyped(c: &Box2D<T, UnknownUnit>) -> Box2D<T, U> {
Box2D::new(Point2D::from_untyped(c.min), Point2D::from_untyped(c.max))
}
/// Cast the unit #[inline] pubfn cast_unit<V>(&self) -> Box2D<T, V> {
Box2D::new(self.min.cast_unit(), self.max.cast_unit())
}
impl<T: NumCast + Copy, U> Box2D<T, U> { /// Cast from one numeric representation to another, preserving the units. /// /// When casting from floating point to integer coordinates, the decimals are truncated /// as one would expect from a simple cast, but this behavior does not always make sense /// geometrically. Consider using [`round`], [`round_in`] or [`round_out`] before casting. /// /// [`round`]: Self::round /// [`round_in`]: Self::round_in /// [`round_out`]: Self::round_out #[inline] pubfn cast<NewT: NumCast>(&self) -> Box2D<NewT, U> {
Box2D::new(self.min.cast(), self.max.cast())
}
/// Fallible cast from one numeric representation to another, preserving the units. /// /// When casting from floating point to integer coordinates, the decimals are truncated /// as one would expect from a simple cast, but this behavior does not always make sense /// geometrically. Consider using [`round`], [`round_in`] or [`round_out`] before casting. /// /// [`round`]: Self::round /// [`round_in`]: Self::round_in /// [`round_out`]: Self::round_out pubfn try_cast<NewT: NumCast>(&self) -> Option<Box2D<NewT, U>> { match (self.min.try_cast(), self.max.try_cast()) {
(Some(a), Some(b)) => Some(Box2D::new(a, b)),
_ => None,
}
}
// Convenience functions for common casts
/// Cast into an `f32` box. #[inline] pubfn to_f32(&self) -> Box2D<f32, U> { self.cast()
}
/// Cast into an `f64` box. #[inline] pubfn to_f64(&self) -> Box2D<f64, U> { self.cast()
}
/// Cast into an `usize` box, truncating decimals if any. /// /// When casting from floating point boxes, it is worth considering whether /// to `round()`, `round_in()` or `round_out()` before the cast in order to /// obtain the desired conversion behavior. #[inline] pubfn to_usize(&self) -> Box2D<usize, U> { self.cast()
}
/// Cast into an `u32` box, truncating decimals if any. /// /// When casting from floating point boxes, it is worth considering whether /// to `round()`, `round_in()` or `round_out()` before the cast in order to /// obtain the desired conversion behavior. #[inline] pubfn to_u32(&self) -> Box2D<u32, U> { self.cast()
}
/// Cast into an `i32` box, truncating decimals if any. /// /// When casting from floating point boxes, it is worth considering whether /// to `round()`, `round_in()` or `round_out()` before the cast in order to /// obtain the desired conversion behavior. #[inline] pubfn to_i32(&self) -> Box2D<i32, U> { self.cast()
}
/// Cast into an `i64` box, truncating decimals if any. /// /// When casting from floating point boxes, it is worth considering whether /// to `round()`, `round_in()` or `round_out()` before the cast in order to /// obtain the desired conversion behavior. #[inline] pubfn to_i64(&self) -> Box2D<i64, U> { self.cast()
}
}
impl<T: Float, U> Box2D<T, U> { /// Returns `true` if all members are finite. #[inline] pubfn is_finite(self) -> bool { self.min.is_finite() && self.max.is_finite()
}
}
impl<T, U> Box2D<T, U> where
T: Round,
{ /// Return a box with edges rounded to integer coordinates, such that /// the returned box has the same set of pixel centers as the original /// one. /// Values equal to 0.5 round up. /// Suitable for most places where integral device coordinates /// are needed, but note that any translation should be applied first to /// avoid pixel rounding errors. /// Note that this is *not* rounding to nearest integer if the values are negative. /// They are always rounding as floor(n + 0.5). #[must_use] pubfn round(&self) -> Self {
Box2D::new(self.min.round(), self.max.round())
}
}
impl<T, U> Box2D<T, U> where
T: Floor + Ceil,
{ /// Return a box with faces/edges rounded to integer coordinates, such that /// the original box contains the resulting box. #[must_use] pubfn round_in(&self) -> Self { let min = self.min.ceil(); let max = self.max.floor();
Box2D { min, max }
}
/// Return a box with faces/edges rounded to integer coordinates, such that /// the original box is contained in the resulting box. #[must_use] pubfn round_out(&self) -> Self { let min = self.min.floor(); let max = self.max.ceil();
Box2D { min, max }
}
}
impl<T, U> From<Size2D<T, U>> for Box2D<T, U> where
T: Copy + Zero + PartialOrd,
{ fn from(b: Size2D<T, U>) -> Self { Self::from_size(b)
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.