// 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}; 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. /// /// [`Rect`]: struct.Rect.html /// [`intersection`]: #method.intersection /// [`is_empty`]: #method.is_empty /// [`union`]: #method.union /// [`size`]: #method.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>,
}
/// Creates a Box2D of the given size, at offset zero. #[inline] pubfn from_size(size: Size2D<T, U>) -> Self where
T: Zero,
{
Box2D {
min: Point2D::zero(),
max: point2(size.width, size.height),
}
}
}
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`. A point is considered /// in the box2d if it lies on the left or top edges, but outside if it lies /// on the right or bottom edges. #[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`. A point is considered /// in the box2d if it lies on any edge of the box2d. #[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 containing all of the provided points. 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. #[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. 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.