// 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};
/// An axis aligned 3D box represented by its minimum and maximum coordinates. #[repr(C)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(
feature = "serde",
serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
)] pubstruct Box3D<T, U> { pub min: Point3D<T, U>, pub max: Point3D<T, U>,
}
/// Creates a `Box3D` of the given size, at offset zero. #[inline] pubfn from_size(size: Size3D<T, U>) -> Self where
T: Zero,
{
Box3D {
min: Point3D::zero(),
max: point3(size.width, size.height, size.depth),
}
}
}
impl<T, U> Box3D<T, U> where
T: PartialOrd,
{ /// Returns `true` if the box has a negative volume. /// /// 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 || self.max.z < self.min.z
}
/// 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 && self.max.z > self.min.z)
}
/// Returns `true` if this [`Box3D`] contains the point `p`. /// /// Points on the front, left, and top faces are inside the box, whereas /// points on the back, right, and bottom faces are outside the box. /// See [`Box3D::contains_inclusive`] for a variant that also includes those /// latter points. /// /// # Examples /// /// ``` /// use euclid::default::{Box3D, Point3D}; /// /// let cube = Box3D::new(Point3D::origin(), Point3D::new(2, 2, 2)); /// /// assert!(cube.contains(Point3D::new(1, 1, 1))); /// /// assert!(cube.contains(Point3D::new(0, 1, 1))); // front face /// assert!(cube.contains(Point3D::new(1, 0, 1))); // left face /// assert!(cube.contains(Point3D::new(1, 1, 0))); // top face /// assert!(cube.contains(Point3D::new(0, 0, 0))); /// /// assert!(!cube.contains(Point3D::new(2, 1, 1))); // back face /// assert!(!cube.contains(Point3D::new(1, 2, 1))); // right face /// assert!(!cube.contains(Point3D::new(1, 1, 2))); // bottom face /// assert!(!cube.contains(Point3D::new(2, 2, 2))); /// ``` #[inline] pubfn contains(&self, other: Point3D<T, U>) -> bool {
(self.min.x <= other.x)
& (other.x < self.max.x)
& (self.min.y <= other.y)
& (other.y < self.max.y)
& (self.min.z <= other.z)
& (other.z < self.max.z)
}
/// Returns `true` if this [`Box3D`] contains the point `p`. /// /// This is like [`Box3D::contains`], but points on the back, right, /// and bottom faces are also inside the box. /// /// # Examples /// /// ``` /// use euclid::default::{Box3D, Point3D}; /// /// let cube = Box3D::new(Point3D::origin(), Point3D::new(2, 2, 2)); /// /// assert!(cube.contains_inclusive(Point3D::new(1, 1, 1))); /// /// assert!(cube.contains_inclusive(Point3D::new(0, 1, 1))); // front face /// assert!(cube.contains_inclusive(Point3D::new(1, 0, 1))); // left face /// assert!(cube.contains_inclusive(Point3D::new(1, 1, 0))); // top face /// assert!(cube.contains_inclusive(Point3D::new(0, 0, 0))); // front-left-top corner /// /// assert!(cube.contains_inclusive(Point3D::new(2, 1, 1))); // back face /// assert!(cube.contains_inclusive(Point3D::new(1, 2, 1))); // right face /// assert!(cube.contains_inclusive(Point3D::new(1, 1, 2))); // bottom face /// assert!(cube.contains_inclusive(Point3D::new(2, 2, 2))); // back-right-bottom corner /// ``` #[inline] pubfn contains_inclusive(&self, other: Point3D<T, U>) -> bool {
(self.min.x <= other.x)
& (other.x <= self.max.x)
& (self.min.y <= other.y)
& (other.y <= self.max.y)
& (self.min.z <= other.z)
& (other.z <= self.max.z)
}
/// Returns `true` if this box3d contains the interior of the other box3d. Always /// returns `true` if other is empty, and always returns `false` if other is /// nonempty but this box3d 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)
& (self.min.z <= other.min.z)
& (other.max.z <= self.max.z))
}
}
/// 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;
}
impl<T, U> Box3D<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, depth: T) -> Self {
Box3D::new(
Point3D::new(self.min.x - width, self.min.y - height, self.min.z - depth),
Point3D::new(self.max.x + width, self.max.y + height, self.max.z + depth),
)
}
}
impl<T, U> Box3D<T, U> where
T: Copy + Zero + PartialOrd,
{ /// Returns the smallest box enclosing all of the provided points. /// /// The top/bottom/left/right/front/back-most points are exactly on the box's edges. /// Since [`Box3D::contains`] excludes points that are on the right/bottom/back-most /// faces, not all points passed to [`Box3D::from_points`] are /// contained in the returned [`Box3D`] when probed with [`Box3D::contains`], but /// are when probed with [`Box3D::contains_inclusive`]. /// /// For example: /// /// ``` /// use euclid::default::{Point3D, Box3D}; /// /// let a = Point3D::origin(); /// let b = Point3D::new(1, 2, 3); /// let box3 = Box3D::from_points([a, b]); /// /// assert_eq!(box3.width(), 1); /// assert_eq!(box3.height(), 2); /// assert_eq!(box3.depth(), 3); /// /// assert!(box3.contains(a)); /// assert!(!box3.contains(b)); /// assert!(box3.contains_inclusive(b)); /// ``` /// /// In particular, calling [`Box3D::from_points`] with a single point /// results in an empty [`Box3D`]: /// /// ``` /// use euclid::default::{Point3D, Box3D}; /// /// let a = Point3D::new(1, 0, 1); /// let box3 = Box3D::from_points([a]); /// /// assert!(box3.is_empty()); /// assert!(!box3.contains(a)); /// assert!(box3.contains_inclusive(a)); /// ``` /// /// The [`Box3D`] enclosing no points is also empty: /// /// ``` /// use euclid::default::{Box3D, Point3D}; /// /// let box3 = Box3D::from_points(std::iter::empty::<Point3D<i32>>()); /// assert!(box3.is_empty()); /// ``` pubfn from_points<I>(points: I) -> Self where
I: IntoIterator,
I::Item: Borrow<Point3D<T, U>>,
{ letmut points = points.into_iter();
let (mut min_x, mut min_y, mut min_z) = match points.next() {
Some(first) => first.borrow().to_tuple(),
None => return Box3D::zero(),
}; let (mut max_x, mut max_y, mut max_z) = (min_x, min_y, min_z);
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;
} if p.z < min_z {
min_z = p.z;
} if p.z > max_z {
max_z = p.z;
}
}
impl<T: NumCast + Copy, U> Box3D<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) -> Box3D<NewT, U> {
Box3D::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<Box3D<NewT, U>> { match (self.min.try_cast(), self.max.try_cast()) {
(Some(a), Some(b)) => Some(Box3D::new(a, b)),
_ => None,
}
}
// Convenience functions for common casts
/// Cast into an `f32` box3d. #[inline] pubfn to_f32(&self) -> Box3D<f32, U> { self.cast()
}
/// Cast into an `f64` box3d. #[inline] pubfn to_f64(&self) -> Box3D<f64, U> { self.cast()
}
/// Cast into an `usize` box3d, truncating decimals if any. /// /// When casting from floating point cuboids, 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) -> Box3D<usize, U> { self.cast()
}
/// Cast into an `u32` box3d, truncating decimals if any. /// /// When casting from floating point cuboids, 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) -> Box3D<u32, U> { self.cast()
}
/// Cast into an `i32` box3d, truncating decimals if any. /// /// When casting from floating point cuboids, 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) -> Box3D<i32, U> { self.cast()
}
/// Cast into an `i64` box3d, truncating decimals if any. /// /// When casting from floating point cuboids, 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) -> Box3D<i64, U> { self.cast()
}
}
impl<T: Float, U> Box3D<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> Box3D<T, U> where
T: Round,
{ /// Return a box3d with edges rounded to integer coordinates, such that /// the returned box3d 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 {
Box3D::new(self.min.round(), self.max.round())
}
}
impl<T, U> Box3D<T, U> where
T: Floor + Ceil,
{ /// Return a box3d with faces/edges rounded to integer coordinates, such that /// the original box3d contains the resulting box3d. #[must_use] pubfn round_in(&self) -> Self {
Box3D {
min: self.min.ceil(),
max: self.max.floor(),
}
}
/// Return a box3d with faces/edges rounded to integer coordinates, such that /// the original box3d is contained in the resulting box3d. #[must_use] pubfn round_out(&self) -> Self {
Box3D {
min: self.min.floor(),
max: self.max.ceil(),
}
}
}
impl<T, U> From<Size3D<T, U>> for Box3D<T, U> where
T: Copy + Zero + PartialOrd,
{ fn from(b: Size3D<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.