Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

63
vendor/bevy_math/src/affine3.rs vendored Normal file
View File

@@ -0,0 +1,63 @@
use glam::{Affine3A, Mat3, Vec3, Vec3Swizzles, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// Reduced-size version of `glam::Affine3A` for use when storage has
/// significant performance impact. Convert to `glam::Affine3A` to do
/// non-trivial calculations.
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct Affine3 {
/// Scaling, rotation, shears, and other non-translation affine transforms
pub matrix3: Mat3,
/// Translation
pub translation: Vec3,
}
impl Affine3 {
/// Calculates the transpose of the affine 4x3 matrix to a 3x4 and formats it for packing into GPU buffers
#[inline]
pub fn to_transpose(&self) -> [Vec4; 3] {
let transpose_3x3 = self.matrix3.transpose();
[
transpose_3x3.x_axis.extend(self.translation.x),
transpose_3x3.y_axis.extend(self.translation.y),
transpose_3x3.z_axis.extend(self.translation.z),
]
}
/// Calculates the inverse transpose of the 3x3 matrix and formats it for packing into GPU buffers
#[inline]
pub fn inverse_transpose_3x3(&self) -> ([Vec4; 2], f32) {
let inverse_transpose_3x3 = Affine3A::from(self).inverse().matrix3.transpose();
(
[
(inverse_transpose_3x3.x_axis, inverse_transpose_3x3.y_axis.x).into(),
(
inverse_transpose_3x3.y_axis.yz(),
inverse_transpose_3x3.z_axis.xy(),
)
.into(),
],
inverse_transpose_3x3.z_axis.z,
)
}
}
impl From<&Affine3A> for Affine3 {
fn from(affine: &Affine3A) -> Self {
Self {
matrix3: affine.matrix3.into(),
translation: affine.translation.into(),
}
}
}
impl From<&Affine3> for Affine3A {
fn from(affine3: &Affine3) -> Self {
Self {
matrix3: affine3.matrix3.into(),
translation: affine3.translation.into(),
}
}
}

103
vendor/bevy_math/src/aspect_ratio.rs vendored Normal file
View File

@@ -0,0 +1,103 @@
//! Provides a simple aspect ratio struct to help with calculations.
use crate::Vec2;
use derive_more::derive::Into;
use thiserror::Error;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// An `AspectRatio` is the ratio of width to height.
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Into)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
pub struct AspectRatio(f32);
impl AspectRatio {
/// Standard 16:9 aspect ratio
pub const SIXTEEN_NINE: Self = Self(16.0 / 9.0);
/// Standard 4:3 aspect ratio
pub const FOUR_THREE: Self = Self(4.0 / 3.0);
/// Standard 21:9 ultrawide aspect ratio
pub const ULTRAWIDE: Self = Self(21.0 / 9.0);
/// Attempts to create a new [`AspectRatio`] from a given width and height.
///
/// # Errors
///
/// Returns an `Err` with `AspectRatioError` if:
/// - Either width or height is zero (`AspectRatioError::Zero`)
/// - Either width or height is infinite (`AspectRatioError::Infinite`)
/// - Either width or height is NaN (`AspectRatioError::NaN`)
#[inline]
pub fn try_new(width: f32, height: f32) -> Result<Self, AspectRatioError> {
match (width, height) {
(w, h) if w == 0.0 || h == 0.0 => Err(AspectRatioError::Zero),
(w, h) if w.is_infinite() || h.is_infinite() => Err(AspectRatioError::Infinite),
(w, h) if w.is_nan() || h.is_nan() => Err(AspectRatioError::NaN),
_ => Ok(Self(width / height)),
}
}
/// Attempts to create a new [`AspectRatio`] from a given amount of x pixels and y pixels.
#[inline]
pub fn try_from_pixels(x: u32, y: u32) -> Result<Self, AspectRatioError> {
Self::try_new(x as f32, y as f32)
}
/// Returns the aspect ratio as a f32 value.
#[inline]
pub const fn ratio(&self) -> f32 {
self.0
}
/// Returns the inverse of this aspect ratio (height/width).
#[inline]
pub const fn inverse(&self) -> Self {
Self(1.0 / self.0)
}
/// Returns true if the aspect ratio represents a landscape orientation.
#[inline]
pub const fn is_landscape(&self) -> bool {
self.0 > 1.0
}
/// Returns true if the aspect ratio represents a portrait orientation.
#[inline]
pub const fn is_portrait(&self) -> bool {
self.0 < 1.0
}
/// Returns true if the aspect ratio is exactly square.
#[inline]
pub const fn is_square(&self) -> bool {
self.0 == 1.0
}
}
impl TryFrom<Vec2> for AspectRatio {
type Error = AspectRatioError;
#[inline]
fn try_from(value: Vec2) -> Result<Self, Self::Error> {
Self::try_new(value.x, value.y)
}
}
/// An Error type for when [`super::AspectRatio`] is provided invalid width or height values
#[derive(Error, Debug, PartialEq, Eq, Clone, Copy)]
pub enum AspectRatioError {
/// Error due to width or height having zero as a value.
#[error("AspectRatio error: width or height is zero")]
Zero,
/// Error due towidth or height being infinite.
#[error("AspectRatio error: width or height is infinite")]
Infinite,
/// Error due to width or height being Not a Number (NaN).
#[error("AspectRatio error: width or height is NaN")]
NaN,
}

View File

@@ -0,0 +1,770 @@
mod primitive_impls;
use super::{BoundingVolume, IntersectsVolume};
use crate::{
ops,
prelude::{Mat2, Rot2, Vec2},
FloatPow, Isometry2d,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
/// Computes the geometric center of the given set of points.
#[inline(always)]
fn point_cloud_2d_center(points: &[Vec2]) -> Vec2 {
assert!(
!points.is_empty(),
"cannot compute the center of an empty set of points"
);
let denom = 1.0 / points.len() as f32;
points.iter().fold(Vec2::ZERO, |acc, point| acc + *point) * denom
}
/// A trait with methods that return 2D bounding volumes for a shape.
pub trait Bounded2d {
/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d;
/// Get a bounding circle for the shape translated and rotated by the given isometry.
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle;
}
/// A 2D axis-aligned bounding box, or bounding rectangle
#[doc(alias = "BoundingRectangle")]
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Aabb2d {
/// The minimum, conventionally bottom-left, point of the box
pub min: Vec2,
/// The maximum, conventionally top-right, point of the box
pub max: Vec2,
}
impl Aabb2d {
/// Constructs an AABB from its center and half-size.
#[inline(always)]
pub fn new(center: Vec2, half_size: Vec2) -> Self {
debug_assert!(half_size.x >= 0.0 && half_size.y >= 0.0);
Self {
min: center - half_size,
max: center + half_size,
}
}
/// Computes the smallest [`Aabb2d`] containing the given set of points,
/// transformed by the rotation and translation of the given isometry.
///
/// # Panics
///
/// Panics if the given set of points is empty.
#[inline(always)]
pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> Aabb2d {
let isometry = isometry.into();
// Transform all points by rotation
let mut iter = points.iter().map(|point| isometry.rotation * *point);
let first = iter
.next()
.expect("point cloud must contain at least one point for Aabb2d construction");
let (min, max) = iter.fold((first, first), |(prev_min, prev_max), point| {
(point.min(prev_min), point.max(prev_max))
});
Aabb2d {
min: min + isometry.translation,
max: max + isometry.translation,
}
}
/// Computes the smallest [`BoundingCircle`] containing this [`Aabb2d`].
#[inline(always)]
pub fn bounding_circle(&self) -> BoundingCircle {
let radius = self.min.distance(self.max) / 2.0;
BoundingCircle::new(self.center(), radius)
}
/// Finds the point on the AABB that is closest to the given `point`.
///
/// If the point is outside the AABB, the returned point will be on the perimeter of the AABB.
/// Otherwise, it will be inside the AABB and returned as is.
#[inline(always)]
pub fn closest_point(&self, point: Vec2) -> Vec2 {
// Clamp point coordinates to the AABB
point.clamp(self.min, self.max)
}
}
impl BoundingVolume for Aabb2d {
type Translation = Vec2;
type Rotation = Rot2;
type HalfSize = Vec2;
#[inline(always)]
fn center(&self) -> Self::Translation {
(self.min + self.max) / 2.
}
#[inline(always)]
fn half_size(&self) -> Self::HalfSize {
(self.max - self.min) / 2.
}
#[inline(always)]
fn visible_area(&self) -> f32 {
let b = self.max - self.min;
b.x * b.y
}
#[inline(always)]
fn contains(&self, other: &Self) -> bool {
other.min.x >= self.min.x
&& other.min.y >= self.min.y
&& other.max.x <= self.max.x
&& other.max.y <= self.max.y
}
#[inline(always)]
fn merge(&self, other: &Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
#[inline(always)]
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
let b = Self {
min: self.min - amount,
max: self.max + amount,
};
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
b
}
#[inline(always)]
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
let b = Self {
min: self.min + amount,
max: self.max - amount,
};
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
b
}
#[inline(always)]
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
let scale = scale.into();
let b = Self {
min: self.center() - (self.half_size() * scale),
max: self.center() + (self.half_size() * scale),
};
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
b
}
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn transformed_by(
mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) -> Self {
self.transform_by(translation, rotation);
self
}
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn transform_by(
&mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) {
self.rotate_by(rotation);
self.translate_by(translation);
}
#[inline(always)]
fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
let translation = translation.into();
self.min += translation;
self.max += translation;
}
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
self.rotate_by(rotation);
self
}
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
let rot_mat = Mat2::from(rotation.into());
let half_size = rot_mat.abs() * self.half_size();
*self = Self::new(rot_mat * self.center(), half_size);
}
}
impl IntersectsVolume<Self> for Aabb2d {
#[inline(always)]
fn intersects(&self, other: &Self) -> bool {
let x_overlaps = self.min.x <= other.max.x && self.max.x >= other.min.x;
let y_overlaps = self.min.y <= other.max.y && self.max.y >= other.min.y;
x_overlaps && y_overlaps
}
}
impl IntersectsVolume<BoundingCircle> for Aabb2d {
#[inline(always)]
fn intersects(&self, circle: &BoundingCircle) -> bool {
let closest_point = self.closest_point(circle.center);
let distance_squared = circle.center.distance_squared(closest_point);
let radius_squared = circle.radius().squared();
distance_squared <= radius_squared
}
}
#[cfg(test)]
mod aabb2d_tests {
use approx::assert_relative_eq;
use super::Aabb2d;
use crate::{
bounding::{BoundingCircle, BoundingVolume, IntersectsVolume},
ops, Vec2,
};
#[test]
fn center() {
let aabb = Aabb2d {
min: Vec2::new(-0.5, -1.),
max: Vec2::new(1., 1.),
};
assert!((aabb.center() - Vec2::new(0.25, 0.)).length() < f32::EPSILON);
let aabb = Aabb2d {
min: Vec2::new(5., -10.),
max: Vec2::new(10., -5.),
};
assert!((aabb.center() - Vec2::new(7.5, -7.5)).length() < f32::EPSILON);
}
#[test]
fn half_size() {
let aabb = Aabb2d {
min: Vec2::new(-0.5, -1.),
max: Vec2::new(1., 1.),
};
let half_size = aabb.half_size();
assert!((half_size - Vec2::new(0.75, 1.)).length() < f32::EPSILON);
}
#[test]
fn area() {
let aabb = Aabb2d {
min: Vec2::new(-1., -1.),
max: Vec2::new(1., 1.),
};
assert!(ops::abs(aabb.visible_area() - 4.) < f32::EPSILON);
let aabb = Aabb2d {
min: Vec2::new(0., 0.),
max: Vec2::new(1., 0.5),
};
assert!(ops::abs(aabb.visible_area() - 0.5) < f32::EPSILON);
}
#[test]
fn contains() {
let a = Aabb2d {
min: Vec2::new(-1., -1.),
max: Vec2::new(1., 1.),
};
let b = Aabb2d {
min: Vec2::new(-2., -1.),
max: Vec2::new(1., 1.),
};
assert!(!a.contains(&b));
let b = Aabb2d {
min: Vec2::new(-0.25, -0.8),
max: Vec2::new(1., 1.),
};
assert!(a.contains(&b));
}
#[test]
fn merge() {
let a = Aabb2d {
min: Vec2::new(-1., -1.),
max: Vec2::new(1., 0.5),
};
let b = Aabb2d {
min: Vec2::new(-2., -0.5),
max: Vec2::new(0.75, 1.),
};
let merged = a.merge(&b);
assert!((merged.min - Vec2::new(-2., -1.)).length() < f32::EPSILON);
assert!((merged.max - Vec2::new(1., 1.)).length() < f32::EPSILON);
assert!(merged.contains(&a));
assert!(merged.contains(&b));
assert!(!a.contains(&merged));
assert!(!b.contains(&merged));
}
#[test]
fn grow() {
let a = Aabb2d {
min: Vec2::new(-1., -1.),
max: Vec2::new(1., 1.),
};
let padded = a.grow(Vec2::ONE);
assert!((padded.min - Vec2::new(-2., -2.)).length() < f32::EPSILON);
assert!((padded.max - Vec2::new(2., 2.)).length() < f32::EPSILON);
assert!(padded.contains(&a));
assert!(!a.contains(&padded));
}
#[test]
fn shrink() {
let a = Aabb2d {
min: Vec2::new(-2., -2.),
max: Vec2::new(2., 2.),
};
let shrunk = a.shrink(Vec2::ONE);
assert!((shrunk.min - Vec2::new(-1., -1.)).length() < f32::EPSILON);
assert!((shrunk.max - Vec2::new(1., 1.)).length() < f32::EPSILON);
assert!(a.contains(&shrunk));
assert!(!shrunk.contains(&a));
}
#[test]
fn scale_around_center() {
let a = Aabb2d {
min: Vec2::NEG_ONE,
max: Vec2::ONE,
};
let scaled = a.scale_around_center(Vec2::splat(2.));
assert!((scaled.min - Vec2::splat(-2.)).length() < f32::EPSILON);
assert!((scaled.max - Vec2::splat(2.)).length() < f32::EPSILON);
assert!(!a.contains(&scaled));
assert!(scaled.contains(&a));
}
#[test]
fn rotate() {
let a = Aabb2d {
min: Vec2::new(-2.0, -2.0),
max: Vec2::new(2.0, 2.0),
};
let rotated = a.rotated_by(core::f32::consts::PI);
assert_relative_eq!(rotated.min, a.min);
assert_relative_eq!(rotated.max, a.max);
}
#[test]
fn transform() {
let a = Aabb2d {
min: Vec2::new(-2.0, -2.0),
max: Vec2::new(2.0, 2.0),
};
let transformed = a.transformed_by(Vec2::new(2.0, -2.0), core::f32::consts::FRAC_PI_4);
let half_length = ops::hypot(2.0, 2.0);
assert_eq!(
transformed.min,
Vec2::new(2.0 - half_length, -half_length - 2.0)
);
assert_eq!(
transformed.max,
Vec2::new(2.0 + half_length, half_length - 2.0)
);
}
#[test]
fn closest_point() {
let aabb = Aabb2d {
min: Vec2::NEG_ONE,
max: Vec2::ONE,
};
assert_eq!(aabb.closest_point(Vec2::X * 10.0), Vec2::X);
assert_eq!(aabb.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);
assert_eq!(
aabb.closest_point(Vec2::new(0.25, 0.1)),
Vec2::new(0.25, 0.1)
);
}
#[test]
fn intersect_aabb() {
let aabb = Aabb2d {
min: Vec2::NEG_ONE,
max: Vec2::ONE,
};
assert!(aabb.intersects(&aabb));
assert!(aabb.intersects(&Aabb2d {
min: Vec2::new(0.5, 0.5),
max: Vec2::new(2.0, 2.0),
}));
assert!(aabb.intersects(&Aabb2d {
min: Vec2::new(-2.0, -2.0),
max: Vec2::new(-0.5, -0.5),
}));
assert!(!aabb.intersects(&Aabb2d {
min: Vec2::new(1.1, 0.0),
max: Vec2::new(2.0, 0.5),
}));
}
#[test]
fn intersect_bounding_circle() {
let aabb = Aabb2d {
min: Vec2::NEG_ONE,
max: Vec2::ONE,
};
assert!(aabb.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
assert!(aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
assert!(aabb.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.5, 1.0)));
assert!(!aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.75, 1.0)));
}
}
use crate::primitives::Circle;
/// A bounding circle
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct BoundingCircle {
/// The center of the bounding circle
pub center: Vec2,
/// The circle
pub circle: Circle,
}
impl BoundingCircle {
/// Constructs a bounding circle from its center and radius.
#[inline(always)]
pub fn new(center: Vec2, radius: f32) -> Self {
debug_assert!(radius >= 0.);
Self {
center,
circle: Circle { radius },
}
}
/// Computes a [`BoundingCircle`] containing the given set of points,
/// transformed by the rotation and translation of the given isometry.
///
/// The bounding circle is not guaranteed to be the smallest possible.
#[inline(always)]
pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> BoundingCircle {
let isometry = isometry.into();
let center = point_cloud_2d_center(points);
let mut radius_squared = 0.0;
for point in points {
// Get squared version to avoid unnecessary sqrt calls
let distance_squared = point.distance_squared(center);
if distance_squared > radius_squared {
radius_squared = distance_squared;
}
}
BoundingCircle::new(isometry * center, ops::sqrt(radius_squared))
}
/// Get the radius of the bounding circle
#[inline(always)]
pub fn radius(&self) -> f32 {
self.circle.radius
}
/// Computes the smallest [`Aabb2d`] containing this [`BoundingCircle`].
#[inline(always)]
pub fn aabb_2d(&self) -> Aabb2d {
Aabb2d {
min: self.center - Vec2::splat(self.radius()),
max: self.center + Vec2::splat(self.radius()),
}
}
/// Finds the point on the bounding circle that is closest to the given `point`.
///
/// If the point is outside the circle, the returned point will be on the perimeter of the circle.
/// Otherwise, it will be inside the circle and returned as is.
#[inline(always)]
pub fn closest_point(&self, point: Vec2) -> Vec2 {
self.circle.closest_point(point - self.center) + self.center
}
}
impl BoundingVolume for BoundingCircle {
type Translation = Vec2;
type Rotation = Rot2;
type HalfSize = f32;
#[inline(always)]
fn center(&self) -> Self::Translation {
self.center
}
#[inline(always)]
fn half_size(&self) -> Self::HalfSize {
self.radius()
}
#[inline(always)]
fn visible_area(&self) -> f32 {
core::f32::consts::PI * self.radius() * self.radius()
}
#[inline(always)]
fn contains(&self, other: &Self) -> bool {
let diff = self.radius() - other.radius();
self.center.distance_squared(other.center) <= ops::copysign(diff.squared(), diff)
}
#[inline(always)]
fn merge(&self, other: &Self) -> Self {
let diff = other.center - self.center;
let length = diff.length();
if self.radius() >= length + other.radius() {
return *self;
}
if other.radius() >= length + self.radius() {
return *other;
}
let dir = diff / length;
Self::new(
(self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),
(length + self.radius() + other.radius()) / 2.,
)
}
#[inline(always)]
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
debug_assert!(amount >= 0.);
Self::new(self.center, self.radius() + amount)
}
#[inline(always)]
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
debug_assert!(amount >= 0.);
debug_assert!(self.radius() >= amount);
Self::new(self.center, self.radius() - amount)
}
#[inline(always)]
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
let scale = scale.into();
debug_assert!(scale >= 0.);
Self::new(self.center, self.radius() * scale)
}
#[inline(always)]
fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
self.center += translation.into();
}
#[inline(always)]
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
let rotation: Rot2 = rotation.into();
self.center = rotation * self.center;
}
}
impl IntersectsVolume<Self> for BoundingCircle {
#[inline(always)]
fn intersects(&self, other: &Self) -> bool {
let center_distance_squared = self.center.distance_squared(other.center);
let radius_sum_squared = (self.radius() + other.radius()).squared();
center_distance_squared <= radius_sum_squared
}
}
impl IntersectsVolume<Aabb2d> for BoundingCircle {
#[inline(always)]
fn intersects(&self, aabb: &Aabb2d) -> bool {
aabb.intersects(self)
}
}
#[cfg(test)]
mod bounding_circle_tests {
use super::BoundingCircle;
use crate::{
bounding::{BoundingVolume, IntersectsVolume},
ops, Vec2,
};
#[test]
fn area() {
let circle = BoundingCircle::new(Vec2::ONE, 5.);
// Since this number is messy we check it with a higher threshold
assert!(ops::abs(circle.visible_area() - 78.5398) < 0.001);
}
#[test]
fn contains() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
let b = BoundingCircle::new(Vec2::new(5.5, 1.), 1.);
assert!(!a.contains(&b));
let b = BoundingCircle::new(Vec2::new(1., -3.5), 0.5);
assert!(a.contains(&b));
}
#[test]
fn contains_identical() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
assert!(a.contains(&a));
}
#[test]
fn merge() {
// When merging two circles that don't contain each other, we find a center position that
// contains both
let a = BoundingCircle::new(Vec2::ONE, 5.);
let b = BoundingCircle::new(Vec2::new(1., -4.), 1.);
let merged = a.merge(&b);
assert!((merged.center - Vec2::new(1., 0.5)).length() < f32::EPSILON);
assert!(ops::abs(merged.radius() - 5.5) < f32::EPSILON);
assert!(merged.contains(&a));
assert!(merged.contains(&b));
assert!(!a.contains(&merged));
assert!(!b.contains(&merged));
// When one circle contains the other circle, we use the bigger circle
let b = BoundingCircle::new(Vec2::ZERO, 3.);
assert!(a.contains(&b));
let merged = a.merge(&b);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), a.radius());
// When two circles are at the same point, we use the bigger radius
let b = BoundingCircle::new(Vec2::ONE, 6.);
let merged = a.merge(&b);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), b.radius());
}
#[test]
fn merge_identical() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
let merged = a.merge(&a);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), a.radius());
}
#[test]
fn grow() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
let padded = a.grow(1.25);
assert!(ops::abs(padded.radius() - 6.25) < f32::EPSILON);
assert!(padded.contains(&a));
assert!(!a.contains(&padded));
}
#[test]
fn shrink() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
let shrunk = a.shrink(0.5);
assert!(ops::abs(shrunk.radius() - 4.5) < f32::EPSILON);
assert!(a.contains(&shrunk));
assert!(!shrunk.contains(&a));
}
#[test]
fn scale_around_center() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
let scaled = a.scale_around_center(2.);
assert!(ops::abs(scaled.radius() - 10.) < f32::EPSILON);
assert!(!a.contains(&scaled));
assert!(scaled.contains(&a));
}
#[test]
fn transform() {
let a = BoundingCircle::new(Vec2::ONE, 5.0);
let transformed = a.transformed_by(Vec2::new(2.0, -2.0), core::f32::consts::FRAC_PI_4);
assert_eq!(
transformed.center,
Vec2::new(2.0, core::f32::consts::SQRT_2 - 2.0)
);
assert_eq!(transformed.radius(), 5.0);
}
#[test]
fn closest_point() {
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);
assert_eq!(
circle.closest_point(Vec2::NEG_ONE * 10.0),
Vec2::NEG_ONE.normalize()
);
assert_eq!(
circle.closest_point(Vec2::new(0.25, 0.1)),
Vec2::new(0.25, 0.1)
);
}
#[test]
fn intersect_bounding_circle() {
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
assert!(circle.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
assert!(circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.25, 1.0)));
assert!(circle.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.25, 1.0)));
assert!(!circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,473 @@
use core::f32::consts::FRAC_PI_2;
use glam::{Vec2, Vec3A, Vec3Swizzles};
use crate::{
bounding::{BoundingCircle, BoundingVolume},
ops,
primitives::{
Capsule2d, Cuboid, Cylinder, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Primitive2d,
Rectangle, RegularPolygon, Segment2d, Triangle2d,
},
Isometry2d, Isometry3d, Quat, Rot2,
};
#[cfg(feature = "alloc")]
use crate::primitives::{BoxedPolygon, BoxedPolyline2d};
use crate::{bounding::Bounded2d, primitives::Circle};
use super::{Aabb3d, Bounded3d, BoundingSphere};
impl BoundedExtrusion for Circle {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Z;
let top = (segment_dir * half_depth).abs();
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_size = self.radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
Aabb3d {
min: isometry.translation - half_size - top,
max: isometry.translation + half_size + top,
}
}
}
impl BoundedExtrusion for Ellipse {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let Vec2 { x: a, y: b } = self.half_size;
let normal = isometry.rotation * Vec3A::Z;
let conjugate_rot = isometry.rotation.conjugate();
let [max_x, max_y, max_z] = Vec3A::AXES.map(|axis| {
let Some(axis) = (conjugate_rot * axis.reject_from(normal))
.xy()
.try_normalize()
else {
return Vec3A::ZERO;
};
if axis.element_product() == 0. {
return isometry.rotation * Vec3A::new(a * axis.y, b * axis.x, 0.);
}
let m = -axis.x / axis.y;
let signum = axis.signum();
let y = signum.y * b * b / ops::sqrt(b * b + m * m * a * a);
let x = signum.x * a * ops::sqrt(1. - y * y / b / b);
isometry.rotation * Vec3A::new(x, y, 0.)
});
let half_size = Vec3A::new(max_x.x, max_y.y, max_z.z).abs() + (normal * half_depth).abs();
Aabb3d::new(isometry.translation, half_size)
}
}
impl BoundedExtrusion for Line2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let dir = isometry.rotation * Vec3A::from(self.direction.extend(0.));
let half_depth = (isometry.rotation * Vec3A::new(0., 0., half_depth)).abs();
let max = f32::MAX / 2.;
let half_size = Vec3A::new(
if dir.x == 0. { half_depth.x } else { max },
if dir.y == 0. { half_depth.y } else { max },
if dir.z == 0. { half_depth.z } else { max },
);
Aabb3d::new(isometry.translation, half_size)
}
}
impl BoundedExtrusion for Segment2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let half_size = isometry.rotation * Vec3A::from(self.point1().extend(0.));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
Aabb3d::new(isometry.translation, half_size.abs() + depth.abs())
}
}
impl<const N: usize> BoundedExtrusion for Polyline2d<N> {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb =
Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
#[cfg(feature = "alloc")]
impl BoundedExtrusion for BoxedPolyline2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for Triangle2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for Rectangle {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
Cuboid {
half_size: self.half_size.extend(half_depth),
}
.aabb_3d(isometry)
}
}
impl<const N: usize> BoundedExtrusion for Polygon<N> {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb =
Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
#[cfg(feature = "alloc")]
impl BoundedExtrusion for BoxedPolygon {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for RegularPolygon {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(
isometry,
self.vertices(0.).into_iter().map(|v| v.extend(0.)),
);
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for Capsule2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Cylinder {
half_height: half_depth,
radius: self.radius,
}
.aabb_3d(isometry.rotation * Quat::from_rotation_x(FRAC_PI_2));
let up = isometry.rotation * Vec3A::new(0., self.half_length, 0.);
let half_size = aabb.max + up.abs();
Aabb3d::new(isometry.translation, half_size)
}
}
impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
self.base_shape.extrusion_aabb_3d(self.half_depth, isometry)
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
self.base_shape
.extrusion_bounding_sphere(self.half_depth, isometry)
}
}
/// A trait implemented on 2D shapes which determines the 3D bounding volumes of their extrusions.
///
/// Since default implementations can be inferred from 2D bounding volumes, this allows a `Bounded2d`
/// implementation on some shape `MyShape` to be extrapolated to a `Bounded3d` implementation on
/// `Extrusion<MyShape>` without supplying any additional data; e.g.:
/// `impl BoundedExtrusion for MyShape {}`
pub trait BoundedExtrusion: Primitive2d + Bounded2d {
/// Get an axis-aligned bounding box for an extrusion with this shape as a base and the given `half_depth`, transformed by the given `translation` and `rotation`.
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let cap_normal = isometry.rotation * Vec3A::Z;
let conjugate_rot = isometry.rotation.conjugate();
// The `(halfsize, offset)` for each axis
let axis_values = Vec3A::AXES.map(|ax| {
// This is the direction of the line of intersection of a plane with the `ax` normal and the plane containing the cap of the extrusion.
let intersect_line = ax.cross(cap_normal);
if intersect_line.length_squared() <= f32::EPSILON {
return (0., 0.);
};
// This is the normal vector of the intersection line rotated to be in the XY-plane
let line_normal = (conjugate_rot * intersect_line).yx();
let angle = line_normal.to_angle();
// Since the plane containing the caps of the extrusion is not guaranteed to be orthogonal to the `ax` plane, only a certain "scale" factor
// of the `Aabb2d` will actually go towards the dimensions of the `Aabb3d`
let scale = cap_normal.reject_from(ax).length();
// Calculate the `Aabb2d` of the base shape. The shape is rotated so that the line of intersection is parallel to the Y axis in the `Aabb2d` calculations.
// This guarantees that the X value of the `Aabb2d` is closest to the `ax` plane
let aabb2d = self.aabb_2d(Rot2::radians(angle));
(aabb2d.half_size().x * scale, aabb2d.center().x * scale)
});
let offset = Vec3A::from_array(axis_values.map(|(_, offset)| offset));
let cap_size = Vec3A::from_array(axis_values.map(|(max_val, _)| max_val)).abs();
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
Aabb3d::new(isometry.translation - offset, cap_size + depth.abs())
}
/// Get a bounding sphere for an extrusion of the `base_shape` with the given `half_depth` with the given translation and rotation
fn extrusion_bounding_sphere(
&self,
half_depth: f32,
isometry: impl Into<Isometry3d>,
) -> BoundingSphere {
let isometry = isometry.into();
// We calculate the bounding circle of the base shape.
// Since each of the extrusions bases will have the same distance from its center,
// and they are just shifted along the Z-axis, the minimum bounding sphere will be the bounding sphere
// of the cylinder defined by the two bounding circles of the bases for any base shape
let BoundingCircle {
center,
circle: Circle { radius },
} = self.bounding_circle(Isometry2d::IDENTITY);
let radius = ops::hypot(radius, half_depth);
let center = isometry * Vec3A::from(center.extend(0.));
BoundingSphere::new(center, radius)
}
}
#[cfg(test)]
mod tests {
use core::f32::consts::FRAC_PI_4;
use glam::{EulerRot, Quat, Vec2, Vec3, Vec3A};
use crate::{
bounding::{Bounded3d, BoundingVolume},
ops,
primitives::{
Capsule2d, Circle, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Rectangle,
RegularPolygon, Segment2d, Triangle2d,
},
Dir2, Isometry3d,
};
#[test]
fn circle() {
let cylinder = Extrusion::new(Circle::new(0.5), 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = cylinder.aabb_3d(translation);
assert_eq!(aabb.center(), Vec3A::from(translation));
assert_eq!(aabb.half_size(), Vec3A::new(0.5, 0.5, 1.0));
let bounding_sphere = cylinder.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
}
#[test]
fn ellipse() {
let extrusion = Extrusion::new(Ellipse::new(2.0, 0.5), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_euler(EulerRot::ZYX, FRAC_PI_4, FRAC_PI_4, FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), Vec3A::from(translation));
assert_eq!(aabb.half_size(), Vec3A::new(2.709784, 1.3801551, 2.436141));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::sqrt(8f32));
}
#[test]
fn line() {
let extrusion = Extrusion::new(
Line2d {
direction: Dir2::new_unchecked(Vec2::Y),
},
4.,
);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_y(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.min, Vec3A::new(1.5857864, f32::MIN / 2., 3.5857865));
assert_eq!(aabb.max, Vec3A::new(4.4142136, f32::MAX / 2., 6.414213));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center(), translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.);
}
#[test]
fn rectangle() {
let extrusion = Extrusion::new(Rectangle::new(2.0, 1.0), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_z(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1.0606602, 1.0606602, 2.));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.291288);
}
#[test]
fn segment() {
let extrusion = Extrusion::new(
Segment2d::new(Vec2::new(0.0, -1.5), Vec2::new(0.0, 1.5)),
4.0,
);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(0., 2.4748735, 2.4748735));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.5);
}
#[test]
fn polyline() {
let polyline = Polyline2d::<4>::new([
Vec2::ONE,
Vec2::new(-1.0, 1.0),
Vec2::NEG_ONE,
Vec2::new(1.0, -1.0),
]);
let extrusion = Extrusion::new(polyline, 3.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.0615528);
}
#[test]
fn triangle() {
let triangle = Triangle2d::new(
Vec2::new(0.0, 1.0),
Vec2::new(-10.0, -1.0),
Vec2::new(10.0, -1.0),
);
let extrusion = Extrusion::new(triangle, 3.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(10., 1.7677668, 1.7677668));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(
bounding_sphere.center,
Vec3A::new(3.0, 3.2928934, 4.2928934)
);
assert_eq!(bounding_sphere.radius(), 10.111875);
}
#[test]
fn polygon() {
let polygon = Polygon::<4>::new([
Vec2::ONE,
Vec2::new(-1.0, 1.0),
Vec2::NEG_ONE,
Vec2::new(1.0, -1.0),
]);
let extrusion = Extrusion::new(polygon, 3.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.0615528);
}
#[test]
fn regular_polygon() {
let extrusion = Extrusion::new(RegularPolygon::new(2.0, 7), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(
aabb.center(),
Vec3A::from(translation) + Vec3A::new(0., 0.0700254, 0.0700254)
);
assert_eq!(
aabb.half_size(),
Vec3A::new(1.9498558, 2.7584014, 2.7584019)
);
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::sqrt(8f32));
}
#[test]
fn capsule() {
let extrusion = Extrusion::new(Capsule2d::new(0.5, 2.0), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(0.5, 2.4748735, 2.4748735));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.5);
}
}

View File

@@ -0,0 +1,807 @@
mod extrusion;
mod primitive_impls;
use glam::Mat3;
use super::{BoundingVolume, IntersectsVolume};
use crate::{
ops::{self, FloatPow},
Isometry3d, Quat, Vec3A,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
pub use extrusion::BoundedExtrusion;
/// Computes the geometric center of the given set of points.
#[inline(always)]
fn point_cloud_3d_center(points: impl Iterator<Item = impl Into<Vec3A>>) -> Vec3A {
let (acc, len) = points.fold((Vec3A::ZERO, 0), |(acc, len), point| {
(acc + point.into(), len + 1)
});
assert!(
len > 0,
"cannot compute the center of an empty set of points"
);
acc / len as f32
}
/// A trait with methods that return 3D bounding volumes for a shape.
pub trait Bounded3d {
/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d;
/// Get a bounding sphere for the shape translated and rotated by the given isometry.
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere;
}
/// A 3D axis-aligned bounding box
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Aabb3d {
/// The minimum point of the box
pub min: Vec3A,
/// The maximum point of the box
pub max: Vec3A,
}
impl Aabb3d {
/// Constructs an AABB from its center and half-size.
#[inline(always)]
pub fn new(center: impl Into<Vec3A>, half_size: impl Into<Vec3A>) -> Self {
let (center, half_size) = (center.into(), half_size.into());
debug_assert!(half_size.x >= 0.0 && half_size.y >= 0.0 && half_size.z >= 0.0);
Self {
min: center - half_size,
max: center + half_size,
}
}
/// Computes the smallest [`Aabb3d`] containing the given set of points,
/// transformed by the rotation and translation of the given isometry.
///
/// # Panics
///
/// Panics if the given set of points is empty.
#[inline(always)]
pub fn from_point_cloud(
isometry: impl Into<Isometry3d>,
points: impl Iterator<Item = impl Into<Vec3A>>,
) -> Aabb3d {
let isometry = isometry.into();
// Transform all points by rotation
let mut iter = points.map(|point| isometry.rotation * point.into());
let first = iter
.next()
.expect("point cloud must contain at least one point for Aabb3d construction");
let (min, max) = iter.fold((first, first), |(prev_min, prev_max), point| {
(point.min(prev_min), point.max(prev_max))
});
Aabb3d {
min: min + isometry.translation,
max: max + isometry.translation,
}
}
/// Computes the smallest [`BoundingSphere`] containing this [`Aabb3d`].
#[inline(always)]
pub fn bounding_sphere(&self) -> BoundingSphere {
let radius = self.min.distance(self.max) / 2.0;
BoundingSphere::new(self.center(), radius)
}
/// Finds the point on the AABB that is closest to the given `point`.
///
/// If the point is outside the AABB, the returned point will be on the surface of the AABB.
/// Otherwise, it will be inside the AABB and returned as is.
#[inline(always)]
pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {
// Clamp point coordinates to the AABB
point.into().clamp(self.min, self.max)
}
}
impl BoundingVolume for Aabb3d {
type Translation = Vec3A;
type Rotation = Quat;
type HalfSize = Vec3A;
#[inline(always)]
fn center(&self) -> Self::Translation {
(self.min + self.max) / 2.
}
#[inline(always)]
fn half_size(&self) -> Self::HalfSize {
(self.max - self.min) / 2.
}
#[inline(always)]
fn visible_area(&self) -> f32 {
let b = self.max - self.min;
b.x * (b.y + b.z) + b.y * b.z
}
#[inline(always)]
fn contains(&self, other: &Self) -> bool {
other.min.cmpge(self.min).all() && other.max.cmple(self.max).all()
}
#[inline(always)]
fn merge(&self, other: &Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
#[inline(always)]
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
let b = Self {
min: self.min - amount,
max: self.max + amount,
};
debug_assert!(b.min.cmple(b.max).all());
b
}
#[inline(always)]
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
let b = Self {
min: self.min + amount,
max: self.max - amount,
};
debug_assert!(b.min.cmple(b.max).all());
b
}
#[inline(always)]
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
let scale = scale.into();
let b = Self {
min: self.center() - (self.half_size() * scale),
max: self.center() + (self.half_size() * scale),
};
debug_assert!(b.min.cmple(b.max).all());
b
}
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn transformed_by(
mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) -> Self {
self.transform_by(translation, rotation);
self
}
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn transform_by(
&mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) {
self.rotate_by(rotation);
self.translate_by(translation);
}
#[inline(always)]
fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
let translation = translation.into();
self.min += translation;
self.max += translation;
}
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
self.rotate_by(rotation);
self
}
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
let rot_mat = Mat3::from_quat(rotation.into());
let half_size = rot_mat.abs() * self.half_size();
*self = Self::new(rot_mat * self.center(), half_size);
}
}
impl IntersectsVolume<Self> for Aabb3d {
#[inline(always)]
fn intersects(&self, other: &Self) -> bool {
self.min.cmple(other.max).all() && self.max.cmpge(other.min).all()
}
}
impl IntersectsVolume<BoundingSphere> for Aabb3d {
#[inline(always)]
fn intersects(&self, sphere: &BoundingSphere) -> bool {
let closest_point = self.closest_point(sphere.center);
let distance_squared = sphere.center.distance_squared(closest_point);
let radius_squared = sphere.radius().squared();
distance_squared <= radius_squared
}
}
#[cfg(test)]
mod aabb3d_tests {
use approx::assert_relative_eq;
use super::Aabb3d;
use crate::{
bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},
ops, Quat, Vec3, Vec3A,
};
#[test]
fn center() {
let aabb = Aabb3d {
min: Vec3A::new(-0.5, -1., -0.5),
max: Vec3A::new(1., 1., 2.),
};
assert!((aabb.center() - Vec3A::new(0.25, 0., 0.75)).length() < f32::EPSILON);
let aabb = Aabb3d {
min: Vec3A::new(5., 5., -10.),
max: Vec3A::new(10., 10., -5.),
};
assert!((aabb.center() - Vec3A::new(7.5, 7.5, -7.5)).length() < f32::EPSILON);
}
#[test]
fn half_size() {
let aabb = Aabb3d {
min: Vec3A::new(-0.5, -1., -0.5),
max: Vec3A::new(1., 1., 2.),
};
assert!((aabb.half_size() - Vec3A::new(0.75, 1., 1.25)).length() < f32::EPSILON);
}
#[test]
fn area() {
let aabb = Aabb3d {
min: Vec3A::new(-1., -1., -1.),
max: Vec3A::new(1., 1., 1.),
};
assert!(ops::abs(aabb.visible_area() - 12.) < f32::EPSILON);
let aabb = Aabb3d {
min: Vec3A::new(0., 0., 0.),
max: Vec3A::new(1., 0.5, 0.25),
};
assert!(ops::abs(aabb.visible_area() - 0.875) < f32::EPSILON);
}
#[test]
fn contains() {
let a = Aabb3d {
min: Vec3A::new(-1., -1., -1.),
max: Vec3A::new(1., 1., 1.),
};
let b = Aabb3d {
min: Vec3A::new(-2., -1., -1.),
max: Vec3A::new(1., 1., 1.),
};
assert!(!a.contains(&b));
let b = Aabb3d {
min: Vec3A::new(-0.25, -0.8, -0.9),
max: Vec3A::new(1., 1., 0.9),
};
assert!(a.contains(&b));
}
#[test]
fn merge() {
let a = Aabb3d {
min: Vec3A::new(-1., -1., -1.),
max: Vec3A::new(1., 0.5, 1.),
};
let b = Aabb3d {
min: Vec3A::new(-2., -0.5, -0.),
max: Vec3A::new(0.75, 1., 2.),
};
let merged = a.merge(&b);
assert!((merged.min - Vec3A::new(-2., -1., -1.)).length() < f32::EPSILON);
assert!((merged.max - Vec3A::new(1., 1., 2.)).length() < f32::EPSILON);
assert!(merged.contains(&a));
assert!(merged.contains(&b));
assert!(!a.contains(&merged));
assert!(!b.contains(&merged));
}
#[test]
fn grow() {
let a = Aabb3d {
min: Vec3A::new(-1., -1., -1.),
max: Vec3A::new(1., 1., 1.),
};
let padded = a.grow(Vec3A::ONE);
assert!((padded.min - Vec3A::new(-2., -2., -2.)).length() < f32::EPSILON);
assert!((padded.max - Vec3A::new(2., 2., 2.)).length() < f32::EPSILON);
assert!(padded.contains(&a));
assert!(!a.contains(&padded));
}
#[test]
fn shrink() {
let a = Aabb3d {
min: Vec3A::new(-2., -2., -2.),
max: Vec3A::new(2., 2., 2.),
};
let shrunk = a.shrink(Vec3A::ONE);
assert!((shrunk.min - Vec3A::new(-1., -1., -1.)).length() < f32::EPSILON);
assert!((shrunk.max - Vec3A::new(1., 1., 1.)).length() < f32::EPSILON);
assert!(a.contains(&shrunk));
assert!(!shrunk.contains(&a));
}
#[test]
fn scale_around_center() {
let a = Aabb3d {
min: Vec3A::NEG_ONE,
max: Vec3A::ONE,
};
let scaled = a.scale_around_center(Vec3A::splat(2.));
assert!((scaled.min - Vec3A::splat(-2.)).length() < f32::EPSILON);
assert!((scaled.max - Vec3A::splat(2.)).length() < f32::EPSILON);
assert!(!a.contains(&scaled));
assert!(scaled.contains(&a));
}
#[test]
fn rotate() {
use core::f32::consts::PI;
let a = Aabb3d {
min: Vec3A::new(-2.0, -2.0, -2.0),
max: Vec3A::new(2.0, 2.0, 2.0),
};
let rotation = Quat::from_euler(glam::EulerRot::XYZ, PI, PI, 0.0);
let rotated = a.rotated_by(rotation);
assert_relative_eq!(rotated.min, a.min);
assert_relative_eq!(rotated.max, a.max);
}
#[test]
fn transform() {
let a = Aabb3d {
min: Vec3A::new(-2.0, -2.0, -2.0),
max: Vec3A::new(2.0, 2.0, 2.0),
};
let transformed = a.transformed_by(
Vec3A::new(2.0, -2.0, 4.0),
Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),
);
let half_length = ops::hypot(2.0, 2.0);
assert_eq!(
transformed.min,
Vec3A::new(2.0 - half_length, -half_length - 2.0, 2.0)
);
assert_eq!(
transformed.max,
Vec3A::new(2.0 + half_length, half_length - 2.0, 6.0)
);
}
#[test]
fn closest_point() {
let aabb = Aabb3d {
min: Vec3A::NEG_ONE,
max: Vec3A::ONE,
};
assert_eq!(aabb.closest_point(Vec3A::X * 10.0), Vec3A::X);
assert_eq!(aabb.closest_point(Vec3A::NEG_ONE * 10.0), Vec3A::NEG_ONE);
assert_eq!(
aabb.closest_point(Vec3A::new(0.25, 0.1, 0.3)),
Vec3A::new(0.25, 0.1, 0.3)
);
}
#[test]
fn intersect_aabb() {
let aabb = Aabb3d {
min: Vec3A::NEG_ONE,
max: Vec3A::ONE,
};
assert!(aabb.intersects(&aabb));
assert!(aabb.intersects(&Aabb3d {
min: Vec3A::splat(0.5),
max: Vec3A::splat(2.0),
}));
assert!(aabb.intersects(&Aabb3d {
min: Vec3A::splat(-2.0),
max: Vec3A::splat(-0.5),
}));
assert!(!aabb.intersects(&Aabb3d {
min: Vec3A::new(1.1, 0.0, 0.0),
max: Vec3A::new(2.0, 0.5, 0.25),
}));
}
#[test]
fn intersect_bounding_sphere() {
let aabb = Aabb3d {
min: Vec3A::NEG_ONE,
max: Vec3A::ONE,
};
assert!(aabb.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
assert!(aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.5, 1.0)));
assert!(aabb.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.5, 1.0)));
assert!(!aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.75, 1.0)));
}
}
use crate::primitives::Sphere;
/// A bounding sphere
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct BoundingSphere {
/// The center of the bounding sphere
pub center: Vec3A,
/// The sphere
pub sphere: Sphere,
}
impl BoundingSphere {
/// Constructs a bounding sphere from its center and radius.
pub fn new(center: impl Into<Vec3A>, radius: f32) -> Self {
debug_assert!(radius >= 0.);
Self {
center: center.into(),
sphere: Sphere { radius },
}
}
/// Computes a [`BoundingSphere`] containing the given set of points,
/// transformed by the rotation and translation of the given isometry.
///
/// The bounding sphere is not guaranteed to be the smallest possible.
#[inline(always)]
pub fn from_point_cloud(
isometry: impl Into<Isometry3d>,
points: &[impl Copy + Into<Vec3A>],
) -> BoundingSphere {
let isometry = isometry.into();
let center = point_cloud_3d_center(points.iter().map(|v| Into::<Vec3A>::into(*v)));
let mut radius_squared: f32 = 0.0;
for point in points {
// Get squared version to avoid unnecessary sqrt calls
let distance_squared = Into::<Vec3A>::into(*point).distance_squared(center);
if distance_squared > radius_squared {
radius_squared = distance_squared;
}
}
BoundingSphere::new(isometry * center, ops::sqrt(radius_squared))
}
/// Get the radius of the bounding sphere
#[inline(always)]
pub fn radius(&self) -> f32 {
self.sphere.radius
}
/// Computes the smallest [`Aabb3d`] containing this [`BoundingSphere`].
#[inline(always)]
pub fn aabb_3d(&self) -> Aabb3d {
Aabb3d {
min: self.center - self.radius(),
max: self.center + self.radius(),
}
}
/// Finds the point on the bounding sphere that is closest to the given `point`.
///
/// If the point is outside the sphere, the returned point will be on the surface of the sphere.
/// Otherwise, it will be inside the sphere and returned as is.
#[inline(always)]
pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {
let point = point.into();
let radius = self.radius();
let distance_squared = (point - self.center).length_squared();
if distance_squared <= radius.squared() {
// The point is inside the sphere.
point
} else {
// The point is outside the sphere.
// Find the closest point on the surface of the sphere.
let dir_to_point = point / ops::sqrt(distance_squared);
self.center + radius * dir_to_point
}
}
}
impl BoundingVolume for BoundingSphere {
type Translation = Vec3A;
type Rotation = Quat;
type HalfSize = f32;
#[inline(always)]
fn center(&self) -> Self::Translation {
self.center
}
#[inline(always)]
fn half_size(&self) -> Self::HalfSize {
self.radius()
}
#[inline(always)]
fn visible_area(&self) -> f32 {
2. * core::f32::consts::PI * self.radius() * self.radius()
}
#[inline(always)]
fn contains(&self, other: &Self) -> bool {
let diff = self.radius() - other.radius();
self.center.distance_squared(other.center) <= ops::copysign(diff.squared(), diff)
}
#[inline(always)]
fn merge(&self, other: &Self) -> Self {
let diff = other.center - self.center;
let length = diff.length();
if self.radius() >= length + other.radius() {
return *self;
}
if other.radius() >= length + self.radius() {
return *other;
}
let dir = diff / length;
Self::new(
(self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),
(length + self.radius() + other.radius()) / 2.,
)
}
#[inline(always)]
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
debug_assert!(amount >= 0.);
Self {
center: self.center,
sphere: Sphere {
radius: self.radius() + amount,
},
}
}
#[inline(always)]
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
debug_assert!(amount >= 0.);
debug_assert!(self.radius() >= amount);
Self {
center: self.center,
sphere: Sphere {
radius: self.radius() - amount,
},
}
}
#[inline(always)]
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
let scale = scale.into();
debug_assert!(scale >= 0.);
Self::new(self.center, self.radius() * scale)
}
#[inline(always)]
fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
self.center += translation.into();
}
#[inline(always)]
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
let rotation: Quat = rotation.into();
self.center = rotation * self.center;
}
}
impl IntersectsVolume<Self> for BoundingSphere {
#[inline(always)]
fn intersects(&self, other: &Self) -> bool {
let center_distance_squared = self.center.distance_squared(other.center);
let radius_sum_squared = (self.radius() + other.radius()).squared();
center_distance_squared <= radius_sum_squared
}
}
impl IntersectsVolume<Aabb3d> for BoundingSphere {
#[inline(always)]
fn intersects(&self, aabb: &Aabb3d) -> bool {
aabb.intersects(self)
}
}
#[cfg(test)]
mod bounding_sphere_tests {
use approx::assert_relative_eq;
use super::BoundingSphere;
use crate::{
bounding::{BoundingVolume, IntersectsVolume},
ops, Quat, Vec3, Vec3A,
};
#[test]
fn area() {
let sphere = BoundingSphere::new(Vec3::ONE, 5.);
// Since this number is messy we check it with a higher threshold
assert!(ops::abs(sphere.visible_area() - 157.0796) < 0.001);
}
#[test]
fn contains() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
let b = BoundingSphere::new(Vec3::new(5.5, 1., 1.), 1.);
assert!(!a.contains(&b));
let b = BoundingSphere::new(Vec3::new(1., -3.5, 1.), 0.5);
assert!(a.contains(&b));
}
#[test]
fn contains_identical() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
assert!(a.contains(&a));
}
#[test]
fn merge() {
// When merging two circles that don't contain each other, we find a center position that
// contains both
let a = BoundingSphere::new(Vec3::ONE, 5.);
let b = BoundingSphere::new(Vec3::new(1., 1., -4.), 1.);
let merged = a.merge(&b);
assert!((merged.center - Vec3A::new(1., 1., 0.5)).length() < f32::EPSILON);
assert!(ops::abs(merged.radius() - 5.5) < f32::EPSILON);
assert!(merged.contains(&a));
assert!(merged.contains(&b));
assert!(!a.contains(&merged));
assert!(!b.contains(&merged));
// When one circle contains the other circle, we use the bigger circle
let b = BoundingSphere::new(Vec3::ZERO, 3.);
assert!(a.contains(&b));
let merged = a.merge(&b);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), a.radius());
// When two circles are at the same point, we use the bigger radius
let b = BoundingSphere::new(Vec3::ONE, 6.);
let merged = a.merge(&b);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), b.radius());
}
#[test]
fn merge_identical() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
let merged = a.merge(&a);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), a.radius());
}
#[test]
fn grow() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
let padded = a.grow(1.25);
assert!(ops::abs(padded.radius() - 6.25) < f32::EPSILON);
assert!(padded.contains(&a));
assert!(!a.contains(&padded));
}
#[test]
fn shrink() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
let shrunk = a.shrink(0.5);
assert!(ops::abs(shrunk.radius() - 4.5) < f32::EPSILON);
assert!(a.contains(&shrunk));
assert!(!shrunk.contains(&a));
}
#[test]
fn scale_around_center() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
let scaled = a.scale_around_center(2.);
assert!(ops::abs(scaled.radius() - 10.) < f32::EPSILON);
assert!(!a.contains(&scaled));
assert!(scaled.contains(&a));
}
#[test]
fn transform() {
let a = BoundingSphere::new(Vec3::ONE, 5.0);
let transformed = a.transformed_by(
Vec3::new(2.0, -2.0, 4.0),
Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),
);
assert_relative_eq!(
transformed.center,
Vec3A::new(2.0, core::f32::consts::SQRT_2 - 2.0, 5.0)
);
assert_eq!(transformed.radius(), 5.0);
}
#[test]
fn closest_point() {
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3A::X);
assert_eq!(
sphere.closest_point(Vec3::NEG_ONE * 10.0),
Vec3A::NEG_ONE.normalize()
);
assert_eq!(
sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
Vec3A::new(0.25, 0.1, 0.3)
);
}
#[test]
fn intersect_bounding_sphere() {
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
assert!(sphere.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
assert!(sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.1, 1.0)));
assert!(sphere.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.1, 1.0)));
assert!(!sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.2, 1.0)));
}
}

View File

@@ -0,0 +1,700 @@
//! Contains [`Bounded3d`] implementations for [geometric primitives](crate::primitives).
use crate::{
bounding::{Bounded2d, BoundingCircle, BoundingVolume},
ops,
primitives::{
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d,
Segment3d, Sphere, Torus, Triangle2d, Triangle3d,
},
Isometry2d, Isometry3d, Mat3, Vec2, Vec3, Vec3A,
};
#[cfg(feature = "alloc")]
use crate::primitives::BoxedPolyline3d;
use super::{Aabb3d, Bounded3d, BoundingSphere};
impl Bounded3d for Sphere {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
Aabb3d::new(isometry.translation, Vec3::splat(self.radius))
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.radius)
}
}
impl Bounded3d for InfinitePlane3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let normal = isometry.rotation * *self.normal;
let facing_x = normal == Vec3::X || normal == Vec3::NEG_X;
let facing_y = normal == Vec3::Y || normal == Vec3::NEG_Y;
let facing_z = normal == Vec3::Z || normal == Vec3::NEG_Z;
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
// like growing or shrinking the AABB without breaking things.
let half_width = if facing_x { 0.0 } else { f32::MAX / 2.0 };
let half_height = if facing_y { 0.0 } else { f32::MAX / 2.0 };
let half_depth = if facing_z { 0.0 } else { f32::MAX / 2.0 };
let half_size = Vec3A::new(half_width, half_height, half_depth);
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded3d for Line3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let direction = isometry.rotation * *self.direction;
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
// like growing or shrinking the AABB without breaking things.
let max = f32::MAX / 2.0;
let half_width = if direction.x == 0.0 { 0.0 } else { max };
let half_height = if direction.y == 0.0 { 0.0 } else { max };
let half_depth = if direction.z == 0.0 { 0.0 } else { max };
let half_size = Vec3A::new(half_width, half_height, half_depth);
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded3d for Segment3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
Aabb3d::from_point_cloud(isometry, [self.point1(), self.point2()].iter().copied())
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
let local_sphere = BoundingSphere::new(self.center(), self.length() / 2.);
local_sphere.transformed_by(isometry.translation, isometry.rotation)
}
}
impl<const N: usize> Bounded3d for Polyline3d<N> {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
BoundingSphere::from_point_cloud(isometry, &self.vertices)
}
}
#[cfg(feature = "alloc")]
impl Bounded3d for BoxedPolyline3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
BoundingSphere::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded3d for Cuboid {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
// Compute the AABB of the rotated cuboid by transforming the half-size
// by an absolute rotation matrix.
let rot_mat = Mat3::from_quat(isometry.rotation);
let abs_rot_mat = Mat3::from_cols(
rot_mat.x_axis.abs(),
rot_mat.y_axis.abs(),
rot_mat.z_axis.abs(),
);
let half_size = abs_rot_mat * self.half_size;
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.half_size.length())
}
}
impl Bounded3d for Cylinder {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * self.half_height;
let bottom = -top;
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_size = self.radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
Aabb3d {
min: isometry.translation + (top - half_size).min(bottom - half_size),
max: isometry.translation + (top + half_size).max(bottom + half_size),
}
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
let radius = ops::hypot(self.radius, self.half_height);
BoundingSphere::new(isometry.translation, radius)
}
}
impl Bounded3d for Capsule3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
// Get the line segment between the hemispheres of the rotated capsule
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * self.half_length;
let bottom = -top;
// Expand the line segment by the capsule radius to get the capsule half-extents
let min = bottom.min(top) - Vec3A::splat(self.radius);
let max = bottom.max(top) + Vec3A::splat(self.radius);
Aabb3d {
min: min + isometry.translation,
max: max + isometry.translation,
}
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.radius + self.half_length)
}
}
impl Bounded3d for Cone {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * 0.5 * self.height;
let bottom = -top;
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_extents = Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
Aabb3d {
min: isometry.translation + top.min(bottom - self.radius * half_extents),
max: isometry.translation + top.max(bottom + self.radius * half_extents),
}
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
// Get the triangular cross-section of the cone.
let half_height = 0.5 * self.height;
let triangle = Triangle2d::new(
half_height * Vec2::Y,
Vec2::new(-self.radius, -half_height),
Vec2::new(self.radius, -half_height),
);
// Because of circular symmetry, we can use the bounding circle of the triangle
// for the bounding sphere of the cone.
let BoundingCircle { circle, center } = triangle.bounding_circle(Isometry2d::IDENTITY);
BoundingSphere::new(
isometry.rotation * Vec3A::from(center.extend(0.0)) + isometry.translation,
circle.radius,
)
}
}
impl Bounded3d for ConicalFrustum {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * 0.5 * self.height;
let bottom = -top;
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_extents = Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
Aabb3d {
min: isometry.translation
+ (top - self.radius_top * half_extents)
.min(bottom - self.radius_bottom * half_extents),
max: isometry.translation
+ (top + self.radius_top * half_extents)
.max(bottom + self.radius_bottom * half_extents),
}
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
let half_height = 0.5 * self.height;
// To compute the bounding sphere, we'll get the center and radius of the circumcircle
// passing through all four vertices of the trapezoidal cross-section of the conical frustum.
//
// If the circumcenter is inside the trapezoid, we can use that for the bounding sphere.
// Otherwise, we clamp it to the longer parallel side to get a more tightly fitting bounding sphere.
//
// The circumcenter is at the intersection of the bisectors perpendicular to the sides.
// For the isosceles trapezoid, the X coordinate is zero at the center, so a single bisector is enough.
//
// A
// *-------*
// / | \
// / | \
// AB / \ | / \
// / \ | / \
// / C \
// *-------------------*
// B
let a = Vec2::new(-self.radius_top, half_height);
let b = Vec2::new(-self.radius_bottom, -half_height);
let ab = a - b;
let ab_midpoint = b + 0.5 * ab;
let bisector = ab.perp();
// Compute intersection between bisector and vertical line at x = 0.
//
// x = ab_midpoint.x + t * bisector.x = 0
// y = ab_midpoint.y + t * bisector.y = ?
//
// Because ab_midpoint.y = 0 for our conical frustum, we get:
// y = t * bisector.y
//
// Solve x for t:
// t = -ab_midpoint.x / bisector.x
//
// Substitute t to solve for y:
// y = -ab_midpoint.x / bisector.x * bisector.y
let circumcenter_y = -ab_midpoint.x / bisector.x * bisector.y;
// If the circumcenter is outside the trapezoid, the bounding circle is too large.
// In those cases, we clamp it to the longer parallel side.
let (center, radius) = if circumcenter_y <= -half_height {
(Vec2::new(0.0, -half_height), self.radius_bottom)
} else if circumcenter_y >= half_height {
(Vec2::new(0.0, half_height), self.radius_top)
} else {
let circumcenter = Vec2::new(0.0, circumcenter_y);
// We can use the distance from an arbitrary vertex because they all lie on the circumcircle.
(circumcenter, a.distance(circumcenter))
};
BoundingSphere::new(
isometry.translation + isometry.rotation * Vec3A::from(center.extend(0.0)),
radius,
)
}
}
impl Bounded3d for Torus {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
// Compute the AABB of a flat disc with the major radius of the torus.
// Reference: http://iquilezles.org/articles/diskbbox/
let normal = isometry.rotation * Vec3A::Y;
let e = (Vec3A::ONE - normal * normal).max(Vec3A::ZERO);
let disc_half_size =
self.major_radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
// Expand the disc by the minor radius to get the torus half-size
let half_size = disc_half_size + Vec3A::splat(self.minor_radius);
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.outer_radius())
}
}
impl Bounded3d for Triangle3d {
/// Get the bounding box of the triangle.
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let [a, b, c] = self.vertices;
let a = isometry.rotation * a;
let b = isometry.rotation * b;
let c = isometry.rotation * c;
let min = Vec3A::from(a.min(b).min(c));
let max = Vec3A::from(a.max(b).max(c));
let bounding_center = (max + min) / 2.0 + isometry.translation;
let half_extents = (max - min) / 2.0;
Aabb3d::new(bounding_center, half_extents)
}
/// Get the bounding sphere of the triangle.
///
/// The [`Triangle3d`] implements the minimal bounding sphere calculation. For acute triangles, the circumcenter is used as
/// the center of the sphere. For the others, the bounding sphere is the minimal sphere
/// that contains the largest side of the triangle.
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
if self.is_degenerate() || self.is_obtuse() {
let (p1, p2) = self.largest_side();
let (p1, p2) = (Vec3A::from(p1), Vec3A::from(p2));
let mid_point = (p1 + p2) / 2.0;
let radius = mid_point.distance(p1);
BoundingSphere::new(mid_point + isometry.translation, radius)
} else {
let [a, _, _] = self.vertices;
let circumcenter = self.circumcenter();
let radius = circumcenter.distance(a);
BoundingSphere::new(Vec3A::from(circumcenter) + isometry.translation, radius)
}
}
}
#[cfg(test)]
mod tests {
use crate::{bounding::BoundingVolume, ops, Isometry3d};
use glam::{Quat, Vec3, Vec3A};
use crate::{
bounding::Bounded3d,
primitives::{
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d,
Segment3d, Sphere, Torus, Triangle3d,
},
Dir3,
};
#[test]
fn sphere() {
let sphere = Sphere { radius: 1.0 };
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = sphere.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = sphere.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.0);
}
#[test]
fn plane() {
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb1 = InfinitePlane3d::new(Vec3::X).aabb_3d(translation);
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, -f32::MAX / 2.0));
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, f32::MAX / 2.0));
let aabb2 = InfinitePlane3d::new(Vec3::Y).aabb_3d(translation);
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, -f32::MAX / 2.0));
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, f32::MAX / 2.0));
let aabb3 = InfinitePlane3d::new(Vec3::Z).aabb_3d(translation);
assert_eq!(aabb3.min, Vec3A::new(-f32::MAX / 2.0, -f32::MAX / 2.0, 0.0));
assert_eq!(aabb3.max, Vec3A::new(f32::MAX / 2.0, f32::MAX / 2.0, 0.0));
let aabb4 = InfinitePlane3d::new(Vec3::ONE).aabb_3d(translation);
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
let bounding_sphere = InfinitePlane3d::new(Vec3::Y).bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
}
#[test]
fn line() {
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb1 = Line3d { direction: Dir3::Y }.aabb_3d(translation);
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, 0.0));
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, 0.0));
let aabb2 = Line3d { direction: Dir3::X }.aabb_3d(translation);
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, 0.0));
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, 0.0));
let aabb3 = Line3d { direction: Dir3::Z }.aabb_3d(translation);
assert_eq!(aabb3.min, Vec3A::new(2.0, 1.0, -f32::MAX / 2.0));
assert_eq!(aabb3.max, Vec3A::new(2.0, 1.0, f32::MAX / 2.0));
let aabb4 = Line3d {
direction: Dir3::from_xyz(1.0, 1.0, 1.0).unwrap(),
}
.aabb_3d(translation);
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
let bounding_sphere = Line3d { direction: Dir3::Y }.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
}
#[test]
fn segment() {
let segment = Segment3d::new(Vec3::new(-1.0, -0.5, 0.0), Vec3::new(1.0, 0.5, 0.0));
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = segment.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.5, 0.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 1.5, 0.0));
let bounding_sphere = segment.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
}
#[test]
fn polyline() {
let polyline = Polyline3d::<4>::new([
Vec3::ONE,
Vec3::new(-1.0, 1.0, 1.0),
Vec3::NEG_ONE,
Vec3::new(1.0, -1.0, -1.0),
]);
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = polyline.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = polyline.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(
bounding_sphere.radius(),
ops::hypot(ops::hypot(1.0, 1.0), 1.0)
);
}
#[test]
fn cuboid() {
let cuboid = Cuboid::new(2.0, 1.0, 1.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = cuboid.aabb_3d(Isometry3d::new(
translation,
Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),
));
let expected_half_size = Vec3A::new(1.0606601, 1.0606601, 0.5);
assert_eq!(aabb.min, Vec3A::from(translation) - expected_half_size);
assert_eq!(aabb.max, Vec3A::from(translation) + expected_half_size);
let bounding_sphere = cuboid.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(
bounding_sphere.radius(),
ops::hypot(ops::hypot(1.0, 0.5), 0.5)
);
}
#[test]
fn cylinder() {
let cylinder = Cylinder::new(0.5, 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = cylinder.aabb_3d(translation);
assert_eq!(
aabb.min,
Vec3A::from(translation) - Vec3A::new(0.5, 1.0, 0.5)
);
assert_eq!(
aabb.max,
Vec3A::from(translation) + Vec3A::new(0.5, 1.0, 0.5)
);
let bounding_sphere = cylinder.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
}
#[test]
fn capsule() {
let capsule = Capsule3d::new(0.5, 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = capsule.aabb_3d(translation);
assert_eq!(
aabb.min,
Vec3A::from(translation) - Vec3A::new(0.5, 1.5, 0.5)
);
assert_eq!(
aabb.max,
Vec3A::from(translation) + Vec3A::new(0.5, 1.5, 0.5)
);
let bounding_sphere = capsule.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.5);
}
#[test]
fn cone() {
let cone = Cone {
radius: 1.0,
height: 2.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = cone.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = cone.bounding_sphere(translation);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.25
);
assert_eq!(bounding_sphere.radius(), 1.25);
}
#[test]
fn conical_frustum() {
let conical_frustum = ConicalFrustum {
radius_top: 0.5,
radius_bottom: 1.0,
height: 2.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = conical_frustum.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = conical_frustum.bounding_sphere(translation);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.1875
);
assert_eq!(bounding_sphere.radius(), 1.2884705);
}
#[test]
fn wide_conical_frustum() {
let conical_frustum = ConicalFrustum {
radius_top: 0.5,
radius_bottom: 5.0,
height: 1.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = conical_frustum.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(-3.0, 0.5, -5.0));
assert_eq!(aabb.max, Vec3A::new(7.0, 1.5, 5.0));
// For wide conical frusta like this, the circumcenter can be outside the frustum,
// so the center and radius should be clamped to the longest side.
let bounding_sphere = conical_frustum.bounding_sphere(translation);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.5
);
assert_eq!(bounding_sphere.radius(), 5.0);
}
#[test]
fn torus() {
let torus = Torus {
minor_radius: 0.5,
major_radius: 1.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = torus.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(0.5, 0.5, -1.5));
assert_eq!(aabb.max, Vec3A::new(3.5, 1.5, 1.5));
let bounding_sphere = torus.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.5);
}
#[test]
fn triangle3d() {
let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);
let br = zero_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
assert_eq!(
br.center(),
Vec3::ZERO.into(),
"incorrect bounding box center"
);
assert_eq!(
br.half_size(),
Vec3::ZERO.into(),
"incorrect bounding box half extents"
);
let bs = zero_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
assert_eq!(
bs.center,
Vec3::ZERO.into(),
"incorrect bounding sphere center"
);
assert_eq!(bs.sphere.radius, 0.0, "incorrect bounding sphere radius");
let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
let bs = dup_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
assert_eq!(
bs.center,
Vec3::new(0.5, 0.0, 0.0).into(),
"incorrect bounding sphere center"
);
assert_eq!(bs.sphere.radius, 0.5, "incorrect bounding sphere radius");
let br = dup_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
assert_eq!(
br.center(),
Vec3::new(0.5, 0.0, 0.0).into(),
"incorrect bounding box center"
);
assert_eq!(
br.half_size(),
Vec3::new(0.5, 0.0, 0.0).into(),
"incorrect bounding box half extents"
);
let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
let bs = collinear_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
assert_eq!(
bs.center,
Vec3::ZERO.into(),
"incorrect bounding sphere center"
);
assert_eq!(bs.sphere.radius, 1.0, "incorrect bounding sphere radius");
let br = collinear_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
assert_eq!(
br.center(),
Vec3::ZERO.into(),
"incorrect bounding box center"
);
assert_eq!(
br.half_size(),
Vec3::new(1.0, 0.0, 0.0).into(),
"incorrect bounding box half extents"
);
}
}

117
vendor/bevy_math/src/bounding/mod.rs vendored Normal file
View File

@@ -0,0 +1,117 @@
//! This module contains traits and implements for working with bounding shapes
//!
//! There are four traits used:
//! - [`BoundingVolume`] is a generic abstraction for any bounding volume
//! - [`IntersectsVolume`] abstracts intersection tests against a [`BoundingVolume`]
//! - [`Bounded2d`]/[`Bounded3d`] are abstractions for shapes to generate [`BoundingVolume`]s
/// A trait that generalizes different bounding volumes.
/// Bounding volumes are simplified shapes that are used to get simpler ways to check for
/// overlapping elements or finding intersections.
///
/// This trait supports both 2D and 3D bounding shapes.
pub trait BoundingVolume: Sized {
/// The position type used for the volume. This should be `Vec2` for 2D and `Vec3` for 3D.
type Translation: Clone + Copy + PartialEq;
/// The rotation type used for the volume. This should be `Rot2` for 2D and `Quat` for 3D.
type Rotation: Clone + Copy + PartialEq;
/// The type used for the size of the bounding volume. Usually a half size. For example an
/// `f32` radius for a circle, or a `Vec3` with half sizes for x, y and z for a 3D axis-aligned
/// bounding box
type HalfSize;
/// Returns the center of the bounding volume.
fn center(&self) -> Self::Translation;
/// Returns the half size of the bounding volume.
fn half_size(&self) -> Self::HalfSize;
/// Computes the visible surface area of the bounding volume.
/// This method can be useful to make decisions about merging bounding volumes,
/// using a Surface Area Heuristic.
///
/// For 2D shapes this would simply be the area of the shape.
/// For 3D shapes this would usually be half the area of the shape.
fn visible_area(&self) -> f32;
/// Checks if this bounding volume contains another one.
fn contains(&self, other: &Self) -> bool;
/// Computes the smallest bounding volume that contains both `self` and `other`.
fn merge(&self, other: &Self) -> Self;
/// Increases the size of the bounding volume in each direction by the given amount.
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self;
/// Decreases the size of the bounding volume in each direction by the given amount.
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self;
/// Scale the size of the bounding volume around its center by the given amount
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self;
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
fn transformed_by(
mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) -> Self {
self.transform_by(translation, rotation);
self
}
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
fn transform_by(
&mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) {
self.rotate_by(rotation);
self.translate_by(translation);
}
/// Translates the bounding volume by the given translation.
fn translated_by(mut self, translation: impl Into<Self::Translation>) -> Self {
self.translate_by(translation);
self
}
/// Translates the bounding volume by the given translation.
fn translate_by(&mut self, translation: impl Into<Self::Translation>);
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is a combination of the original volume and the rotated volume,
/// so it is guaranteed to be either the same size or larger than the original.
fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
self.rotate_by(rotation);
self
}
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is a combination of the original volume and the rotated volume,
/// so it is guaranteed to be either the same size or larger than the original.
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>);
}
/// A trait that generalizes intersection tests against a volume.
/// Intersection tests can be used for a variety of tasks, for example:
/// - Raycasting
/// - Testing for overlap
/// - Checking if an object is within the view frustum of a camera
pub trait IntersectsVolume<Volume: BoundingVolume> {
/// Check if a volume intersects with this intersection test
fn intersects(&self, volume: &Volume) -> bool;
}
mod bounded2d;
pub use bounded2d::*;
mod bounded3d;
pub use bounded3d::*;
mod raycast2d;
pub use raycast2d::*;
mod raycast3d;
pub use raycast3d::*;

View File

@@ -0,0 +1,536 @@
use super::{Aabb2d, BoundingCircle, IntersectsVolume};
use crate::{
ops::{self, FloatPow},
Dir2, Ray2d, Vec2,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// A raycast intersection test for 2D bounding volumes
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct RayCast2d {
/// The ray for the test
pub ray: Ray2d,
/// The maximum distance for the ray
pub max: f32,
/// The multiplicative inverse direction of the ray
direction_recip: Vec2,
}
impl RayCast2d {
/// Construct a [`RayCast2d`] from an origin, [`Dir2`], and max distance.
pub fn new(origin: Vec2, direction: Dir2, max: f32) -> Self {
Self::from_ray(Ray2d { origin, direction }, max)
}
/// Construct a [`RayCast2d`] from a [`Ray2d`] and max distance.
pub fn from_ray(ray: Ray2d, max: f32) -> Self {
Self {
ray,
direction_recip: ray.direction.recip(),
max,
}
}
/// Get the cached multiplicative inverse of the direction of the ray.
pub fn direction_recip(&self) -> Vec2 {
self.direction_recip
}
/// Get the distance of an intersection with an [`Aabb2d`], if any.
pub fn aabb_intersection_at(&self, aabb: &Aabb2d) -> Option<f32> {
let (min_x, max_x) = if self.ray.direction.x.is_sign_positive() {
(aabb.min.x, aabb.max.x)
} else {
(aabb.max.x, aabb.min.x)
};
let (min_y, max_y) = if self.ray.direction.y.is_sign_positive() {
(aabb.min.y, aabb.max.y)
} else {
(aabb.max.y, aabb.min.y)
};
// Calculate the minimum/maximum time for each axis based on how much the direction goes that
// way. These values can get arbitrarily large, or even become NaN, which is handled by the
// min/max operations below
let tmin_x = (min_x - self.ray.origin.x) * self.direction_recip.x;
let tmin_y = (min_y - self.ray.origin.y) * self.direction_recip.y;
let tmax_x = (max_x - self.ray.origin.x) * self.direction_recip.x;
let tmax_y = (max_y - self.ray.origin.y) * self.direction_recip.y;
// An axis that is not relevant to the ray direction will be NaN. When one of the arguments
// to min/max is NaN, the other argument is used.
// An axis for which the direction is the wrong way will return an arbitrarily large
// negative value.
let tmin = tmin_x.max(tmin_y).max(0.);
let tmax = tmax_y.min(tmax_x).min(self.max);
if tmin <= tmax {
Some(tmin)
} else {
None
}
}
/// Get the distance of an intersection with a [`BoundingCircle`], if any.
pub fn circle_intersection_at(&self, circle: &BoundingCircle) -> Option<f32> {
let offset = self.ray.origin - circle.center;
let projected = offset.dot(*self.ray.direction);
let closest_point = offset - projected * *self.ray.direction;
let distance_squared = circle.radius().squared() - closest_point.length_squared();
if distance_squared < 0.
|| ops::copysign(projected.squared(), -projected) < -distance_squared
{
None
} else {
let toi = -projected - ops::sqrt(distance_squared);
if toi > self.max {
None
} else {
Some(toi.max(0.))
}
}
}
}
impl IntersectsVolume<Aabb2d> for RayCast2d {
fn intersects(&self, volume: &Aabb2d) -> bool {
self.aabb_intersection_at(volume).is_some()
}
}
impl IntersectsVolume<BoundingCircle> for RayCast2d {
fn intersects(&self, volume: &BoundingCircle) -> bool {
self.circle_intersection_at(volume).is_some()
}
}
/// An intersection test that casts an [`Aabb2d`] along a ray.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct AabbCast2d {
/// The ray along which to cast the bounding volume
pub ray: RayCast2d,
/// The aabb that is being cast
pub aabb: Aabb2d,
}
impl AabbCast2d {
/// Construct an [`AabbCast2d`] from an [`Aabb2d`], origin, [`Dir2`], and max distance.
pub fn new(aabb: Aabb2d, origin: Vec2, direction: Dir2, max: f32) -> Self {
Self::from_ray(aabb, Ray2d { origin, direction }, max)
}
/// Construct an [`AabbCast2d`] from an [`Aabb2d`], [`Ray2d`], and max distance.
pub fn from_ray(aabb: Aabb2d, ray: Ray2d, max: f32) -> Self {
Self {
ray: RayCast2d::from_ray(ray, max),
aabb,
}
}
/// Get the distance at which the [`Aabb2d`]s collide, if at all.
pub fn aabb_collision_at(&self, mut aabb: Aabb2d) -> Option<f32> {
aabb.min -= self.aabb.max;
aabb.max -= self.aabb.min;
self.ray.aabb_intersection_at(&aabb)
}
}
impl IntersectsVolume<Aabb2d> for AabbCast2d {
fn intersects(&self, volume: &Aabb2d) -> bool {
self.aabb_collision_at(*volume).is_some()
}
}
/// An intersection test that casts a [`BoundingCircle`] along a ray.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct BoundingCircleCast {
/// The ray along which to cast the bounding volume
pub ray: RayCast2d,
/// The circle that is being cast
pub circle: BoundingCircle,
}
impl BoundingCircleCast {
/// Construct a [`BoundingCircleCast`] from a [`BoundingCircle`], origin, [`Dir2`], and max distance.
pub fn new(circle: BoundingCircle, origin: Vec2, direction: Dir2, max: f32) -> Self {
Self::from_ray(circle, Ray2d { origin, direction }, max)
}
/// Construct a [`BoundingCircleCast`] from a [`BoundingCircle`], [`Ray2d`], and max distance.
pub fn from_ray(circle: BoundingCircle, ray: Ray2d, max: f32) -> Self {
Self {
ray: RayCast2d::from_ray(ray, max),
circle,
}
}
/// Get the distance at which the [`BoundingCircle`]s collide, if at all.
pub fn circle_collision_at(&self, mut circle: BoundingCircle) -> Option<f32> {
circle.center -= self.circle.center;
circle.circle.radius += self.circle.radius();
self.ray.circle_intersection_at(&circle)
}
}
impl IntersectsVolume<BoundingCircle> for BoundingCircleCast {
fn intersects(&self, volume: &BoundingCircle) -> bool {
self.circle_collision_at(*volume).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f32 = 0.001;
#[test]
fn test_ray_intersection_circle_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of a centered bounding circle
RayCast2d::new(Vec2::Y * -5., Dir2::Y, 90.),
BoundingCircle::new(Vec2::ZERO, 1.),
4.,
),
(
// Hit the center of a centered bounding circle, but from the other side
RayCast2d::new(Vec2::Y * 5., -Dir2::Y, 90.),
BoundingCircle::new(Vec2::ZERO, 1.),
4.,
),
(
// Hit the center of an offset circle
RayCast2d::new(Vec2::ZERO, Dir2::Y, 90.),
BoundingCircle::new(Vec2::Y * 3., 2.),
1.,
),
(
// Just barely hit the circle before the max distance
RayCast2d::new(Vec2::X, Dir2::Y, 1.),
BoundingCircle::new(Vec2::ONE, 0.01),
0.99,
),
(
// Hit a circle off-center
RayCast2d::new(Vec2::X, Dir2::Y, 90.),
BoundingCircle::new(Vec2::Y * 5., 2.),
3.268,
),
(
// Barely hit a circle on the side
RayCast2d::new(Vec2::X * 0.99999, Dir2::Y, 90.),
BoundingCircle::new(Vec2::Y * 5., 1.),
4.996,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.circle_intersection_at(volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast2d::new(test.ray.origin, -test.ray.direction, test.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_ray_intersection_circle_misses() {
for (test, volume) in &[
(
// The ray doesn't go in the right direction
RayCast2d::new(Vec2::ZERO, Dir2::X, 90.),
BoundingCircle::new(Vec2::Y * 2., 1.),
),
(
// Ray's alignment isn't enough to hit the circle
RayCast2d::new(Vec2::ZERO, Dir2::from_xy(1., 1.).unwrap(), 90.),
BoundingCircle::new(Vec2::Y * 2., 1.),
),
(
// The ray's maximum distance isn't high enough
RayCast2d::new(Vec2::ZERO, Dir2::Y, 0.5),
BoundingCircle::new(Vec2::Y * 2., 1.),
),
] {
assert!(
!test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}",
);
}
}
#[test]
fn test_ray_intersection_circle_inside() {
let volume = BoundingCircle::new(Vec2::splat(0.5), 1.);
for origin in &[Vec2::X, Vec2::Y, Vec2::ONE, Vec2::ZERO] {
for direction in &[Dir2::X, Dir2::Y, -Dir2::X, -Dir2::Y] {
for max in &[0., 1., 900.] {
let test = RayCast2d::new(*origin, *direction, *max);
assert!(
test.intersects(&volume),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
let actual_distance = test.circle_intersection_at(&volume);
assert_eq!(
actual_distance,
Some(0.),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
}
}
}
}
#[test]
fn test_ray_intersection_aabb_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of a centered aabb
RayCast2d::new(Vec2::Y * -5., Dir2::Y, 90.),
Aabb2d::new(Vec2::ZERO, Vec2::ONE),
4.,
),
(
// Hit the center of a centered aabb, but from the other side
RayCast2d::new(Vec2::Y * 5., -Dir2::Y, 90.),
Aabb2d::new(Vec2::ZERO, Vec2::ONE),
4.,
),
(
// Hit the center of an offset aabb
RayCast2d::new(Vec2::ZERO, Dir2::Y, 90.),
Aabb2d::new(Vec2::Y * 3., Vec2::splat(2.)),
1.,
),
(
// Just barely hit the aabb before the max distance
RayCast2d::new(Vec2::X, Dir2::Y, 1.),
Aabb2d::new(Vec2::ONE, Vec2::splat(0.01)),
0.99,
),
(
// Hit an aabb off-center
RayCast2d::new(Vec2::X, Dir2::Y, 90.),
Aabb2d::new(Vec2::Y * 5., Vec2::splat(2.)),
3.,
),
(
// Barely hit an aabb on corner
RayCast2d::new(Vec2::X * -0.001, Dir2::from_xy(1., 1.).unwrap(), 90.),
Aabb2d::new(Vec2::Y * 2., Vec2::ONE),
1.414,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.aabb_intersection_at(volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast2d::new(test.ray.origin, -test.ray.direction, test.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_ray_intersection_aabb_misses() {
for (test, volume) in &[
(
// The ray doesn't go in the right direction
RayCast2d::new(Vec2::ZERO, Dir2::X, 90.),
Aabb2d::new(Vec2::Y * 2., Vec2::ONE),
),
(
// Ray's alignment isn't enough to hit the aabb
RayCast2d::new(Vec2::ZERO, Dir2::from_xy(1., 0.99).unwrap(), 90.),
Aabb2d::new(Vec2::Y * 2., Vec2::ONE),
),
(
// The ray's maximum distance isn't high enough
RayCast2d::new(Vec2::ZERO, Dir2::Y, 0.5),
Aabb2d::new(Vec2::Y * 2., Vec2::ONE),
),
] {
assert!(
!test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}",
);
}
}
#[test]
fn test_ray_intersection_aabb_inside() {
let volume = Aabb2d::new(Vec2::splat(0.5), Vec2::ONE);
for origin in &[Vec2::X, Vec2::Y, Vec2::ONE, Vec2::ZERO] {
for direction in &[Dir2::X, Dir2::Y, -Dir2::X, -Dir2::Y] {
for max in &[0., 1., 900.] {
let test = RayCast2d::new(*origin, *direction, *max);
assert!(
test.intersects(&volume),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
let actual_distance = test.aabb_intersection_at(&volume);
assert_eq!(
actual_distance,
Some(0.),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
}
}
}
}
#[test]
fn test_aabb_cast_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of the aabb, that a ray would've also hit
AabbCast2d::new(Aabb2d::new(Vec2::ZERO, Vec2::ONE), Vec2::ZERO, Dir2::Y, 90.),
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
3.,
),
(
// Hit the center of the aabb, but from the other side
AabbCast2d::new(
Aabb2d::new(Vec2::ZERO, Vec2::ONE),
Vec2::Y * 10.,
-Dir2::Y,
90.,
),
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
3.,
),
(
// Hit the edge of the aabb, that a ray would've missed
AabbCast2d::new(
Aabb2d::new(Vec2::ZERO, Vec2::ONE),
Vec2::X * 1.5,
Dir2::Y,
90.,
),
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
3.,
),
(
// Hit the edge of the aabb, by casting an off-center AABB
AabbCast2d::new(
Aabb2d::new(Vec2::X * -2., Vec2::ONE),
Vec2::X * 3.,
Dir2::Y,
90.,
),
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
3.,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.aabb_collision_at(*volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray =
RayCast2d::new(test.ray.ray.origin, -test.ray.ray.direction, test.ray.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_circle_cast_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of the bounding circle, that a ray would've also hit
BoundingCircleCast::new(
BoundingCircle::new(Vec2::ZERO, 1.),
Vec2::ZERO,
Dir2::Y,
90.,
),
BoundingCircle::new(Vec2::Y * 5., 1.),
3.,
),
(
// Hit the center of the bounding circle, but from the other side
BoundingCircleCast::new(
BoundingCircle::new(Vec2::ZERO, 1.),
Vec2::Y * 10.,
-Dir2::Y,
90.,
),
BoundingCircle::new(Vec2::Y * 5., 1.),
3.,
),
(
// Hit the bounding circle off-center, that a ray would've missed
BoundingCircleCast::new(
BoundingCircle::new(Vec2::ZERO, 1.),
Vec2::X * 1.5,
Dir2::Y,
90.,
),
BoundingCircle::new(Vec2::Y * 5., 1.),
3.677,
),
(
// Hit the bounding circle off-center, by casting a circle that is off-center
BoundingCircleCast::new(
BoundingCircle::new(Vec2::X * -1.5, 1.),
Vec2::X * 3.,
Dir2::Y,
90.,
),
BoundingCircle::new(Vec2::Y * 5., 1.),
3.677,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.circle_collision_at(*volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray =
RayCast2d::new(test.ray.ray.origin, -test.ray.ray.direction, test.ray.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
}

View File

@@ -0,0 +1,546 @@
use super::{Aabb3d, BoundingSphere, IntersectsVolume};
use crate::{
ops::{self, FloatPow},
Dir3A, Ray3d, Vec3A,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// A raycast intersection test for 3D bounding volumes
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct RayCast3d {
/// The origin of the ray.
pub origin: Vec3A,
/// The direction of the ray.
pub direction: Dir3A,
/// The maximum distance for the ray
pub max: f32,
/// The multiplicative inverse direction of the ray
direction_recip: Vec3A,
}
impl RayCast3d {
/// Construct a [`RayCast3d`] from an origin, [direction], and max distance.
///
/// [direction]: crate::direction::Dir3
pub fn new(origin: impl Into<Vec3A>, direction: impl Into<Dir3A>, max: f32) -> Self {
let direction = direction.into();
Self {
origin: origin.into(),
direction,
direction_recip: direction.recip(),
max,
}
}
/// Construct a [`RayCast3d`] from a [`Ray3d`] and max distance.
pub fn from_ray(ray: Ray3d, max: f32) -> Self {
Self::new(ray.origin, ray.direction, max)
}
/// Get the cached multiplicative inverse of the direction of the ray.
pub fn direction_recip(&self) -> Vec3A {
self.direction_recip
}
/// Get the distance of an intersection with an [`Aabb3d`], if any.
pub fn aabb_intersection_at(&self, aabb: &Aabb3d) -> Option<f32> {
let positive = self.direction.signum().cmpgt(Vec3A::ZERO);
let min = Vec3A::select(positive, aabb.min, aabb.max);
let max = Vec3A::select(positive, aabb.max, aabb.min);
// Calculate the minimum/maximum time for each axis based on how much the direction goes that
// way. These values can get arbitrarily large, or even become NaN, which is handled by the
// min/max operations below
let tmin = (min - self.origin) * self.direction_recip;
let tmax = (max - self.origin) * self.direction_recip;
// An axis that is not relevant to the ray direction will be NaN. When one of the arguments
// to min/max is NaN, the other argument is used.
// An axis for which the direction is the wrong way will return an arbitrarily large
// negative value.
let tmin = tmin.max_element().max(0.);
let tmax = tmax.min_element().min(self.max);
if tmin <= tmax {
Some(tmin)
} else {
None
}
}
/// Get the distance of an intersection with a [`BoundingSphere`], if any.
pub fn sphere_intersection_at(&self, sphere: &BoundingSphere) -> Option<f32> {
let offset = self.origin - sphere.center;
let projected = offset.dot(*self.direction);
let closest_point = offset - projected * *self.direction;
let distance_squared = sphere.radius().squared() - closest_point.length_squared();
if distance_squared < 0.
|| ops::copysign(projected.squared(), -projected) < -distance_squared
{
None
} else {
let toi = -projected - ops::sqrt(distance_squared);
if toi > self.max {
None
} else {
Some(toi.max(0.))
}
}
}
}
impl IntersectsVolume<Aabb3d> for RayCast3d {
fn intersects(&self, volume: &Aabb3d) -> bool {
self.aabb_intersection_at(volume).is_some()
}
}
impl IntersectsVolume<BoundingSphere> for RayCast3d {
fn intersects(&self, volume: &BoundingSphere) -> bool {
self.sphere_intersection_at(volume).is_some()
}
}
/// An intersection test that casts an [`Aabb3d`] along a ray.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct AabbCast3d {
/// The ray along which to cast the bounding volume
pub ray: RayCast3d,
/// The aabb that is being cast
pub aabb: Aabb3d,
}
impl AabbCast3d {
/// Construct an [`AabbCast3d`] from an [`Aabb3d`], origin, [direction], and max distance.
///
/// [direction]: crate::direction::Dir3
pub fn new(
aabb: Aabb3d,
origin: impl Into<Vec3A>,
direction: impl Into<Dir3A>,
max: f32,
) -> Self {
Self {
ray: RayCast3d::new(origin, direction, max),
aabb,
}
}
/// Construct an [`AabbCast3d`] from an [`Aabb3d`], [`Ray3d`], and max distance.
pub fn from_ray(aabb: Aabb3d, ray: Ray3d, max: f32) -> Self {
Self::new(aabb, ray.origin, ray.direction, max)
}
/// Get the distance at which the [`Aabb3d`]s collide, if at all.
pub fn aabb_collision_at(&self, mut aabb: Aabb3d) -> Option<f32> {
aabb.min -= self.aabb.max;
aabb.max -= self.aabb.min;
self.ray.aabb_intersection_at(&aabb)
}
}
impl IntersectsVolume<Aabb3d> for AabbCast3d {
fn intersects(&self, volume: &Aabb3d) -> bool {
self.aabb_collision_at(*volume).is_some()
}
}
/// An intersection test that casts a [`BoundingSphere`] along a ray.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct BoundingSphereCast {
/// The ray along which to cast the bounding volume
pub ray: RayCast3d,
/// The sphere that is being cast
pub sphere: BoundingSphere,
}
impl BoundingSphereCast {
/// Construct a [`BoundingSphereCast`] from a [`BoundingSphere`], origin, [direction], and max distance.
///
/// [direction]: crate::direction::Dir3
pub fn new(
sphere: BoundingSphere,
origin: impl Into<Vec3A>,
direction: impl Into<Dir3A>,
max: f32,
) -> Self {
Self {
ray: RayCast3d::new(origin, direction, max),
sphere,
}
}
/// Construct a [`BoundingSphereCast`] from a [`BoundingSphere`], [`Ray3d`], and max distance.
pub fn from_ray(sphere: BoundingSphere, ray: Ray3d, max: f32) -> Self {
Self::new(sphere, ray.origin, ray.direction, max)
}
/// Get the distance at which the [`BoundingSphere`]s collide, if at all.
pub fn sphere_collision_at(&self, mut sphere: BoundingSphere) -> Option<f32> {
sphere.center -= self.sphere.center;
sphere.sphere.radius += self.sphere.radius();
self.ray.sphere_intersection_at(&sphere)
}
}
impl IntersectsVolume<BoundingSphere> for BoundingSphereCast {
fn intersects(&self, volume: &BoundingSphere) -> bool {
self.sphere_collision_at(*volume).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Dir3, Vec3};
const EPSILON: f32 = 0.001;
#[test]
fn test_ray_intersection_sphere_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of a centered bounding sphere
RayCast3d::new(Vec3::Y * -5., Dir3::Y, 90.),
BoundingSphere::new(Vec3::ZERO, 1.),
4.,
),
(
// Hit the center of a centered bounding sphere, but from the other side
RayCast3d::new(Vec3::Y * 5., -Dir3::Y, 90.),
BoundingSphere::new(Vec3::ZERO, 1.),
4.,
),
(
// Hit the center of an offset sphere
RayCast3d::new(Vec3::ZERO, Dir3::Y, 90.),
BoundingSphere::new(Vec3::Y * 3., 2.),
1.,
),
(
// Just barely hit the sphere before the max distance
RayCast3d::new(Vec3::X, Dir3::Y, 1.),
BoundingSphere::new(Vec3::new(1., 1., 0.), 0.01),
0.99,
),
(
// Hit a sphere off-center
RayCast3d::new(Vec3::X, Dir3::Y, 90.),
BoundingSphere::new(Vec3::Y * 5., 2.),
3.268,
),
(
// Barely hit a sphere on the side
RayCast3d::new(Vec3::X * 0.99999, Dir3::Y, 90.),
BoundingSphere::new(Vec3::Y * 5., 1.),
4.996,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.sphere_intersection_at(volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast3d::new(test.origin, -test.direction, test.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_ray_intersection_sphere_misses() {
for (test, volume) in &[
(
// The ray doesn't go in the right direction
RayCast3d::new(Vec3::ZERO, Dir3::X, 90.),
BoundingSphere::new(Vec3::Y * 2., 1.),
),
(
// Ray's alignment isn't enough to hit the sphere
RayCast3d::new(Vec3::ZERO, Dir3::from_xyz(1., 1., 1.).unwrap(), 90.),
BoundingSphere::new(Vec3::Y * 2., 1.),
),
(
// The ray's maximum distance isn't high enough
RayCast3d::new(Vec3::ZERO, Dir3::Y, 0.5),
BoundingSphere::new(Vec3::Y * 2., 1.),
),
] {
assert!(
!test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}",
);
}
}
#[test]
fn test_ray_intersection_sphere_inside() {
let volume = BoundingSphere::new(Vec3::splat(0.5), 1.);
for origin in &[Vec3::X, Vec3::Y, Vec3::ONE, Vec3::ZERO] {
for direction in &[Dir3::X, Dir3::Y, Dir3::Z, -Dir3::X, -Dir3::Y, -Dir3::Z] {
for max in &[0., 1., 900.] {
let test = RayCast3d::new(*origin, *direction, *max);
assert!(
test.intersects(&volume),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
let actual_distance = test.sphere_intersection_at(&volume);
assert_eq!(
actual_distance,
Some(0.),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
}
}
}
}
#[test]
fn test_ray_intersection_aabb_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of a centered aabb
RayCast3d::new(Vec3::Y * -5., Dir3::Y, 90.),
Aabb3d::new(Vec3::ZERO, Vec3::ONE),
4.,
),
(
// Hit the center of a centered aabb, but from the other side
RayCast3d::new(Vec3::Y * 5., -Dir3::Y, 90.),
Aabb3d::new(Vec3::ZERO, Vec3::ONE),
4.,
),
(
// Hit the center of an offset aabb
RayCast3d::new(Vec3::ZERO, Dir3::Y, 90.),
Aabb3d::new(Vec3::Y * 3., Vec3::splat(2.)),
1.,
),
(
// Just barely hit the aabb before the max distance
RayCast3d::new(Vec3::X, Dir3::Y, 1.),
Aabb3d::new(Vec3::new(1., 1., 0.), Vec3::splat(0.01)),
0.99,
),
(
// Hit an aabb off-center
RayCast3d::new(Vec3::X, Dir3::Y, 90.),
Aabb3d::new(Vec3::Y * 5., Vec3::splat(2.)),
3.,
),
(
// Barely hit an aabb on corner
RayCast3d::new(Vec3::X * -0.001, Dir3::from_xyz(1., 1., 1.).unwrap(), 90.),
Aabb3d::new(Vec3::Y * 2., Vec3::ONE),
1.732,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.aabb_intersection_at(volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast3d::new(test.origin, -test.direction, test.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_ray_intersection_aabb_misses() {
for (test, volume) in &[
(
// The ray doesn't go in the right direction
RayCast3d::new(Vec3::ZERO, Dir3::X, 90.),
Aabb3d::new(Vec3::Y * 2., Vec3::ONE),
),
(
// Ray's alignment isn't enough to hit the aabb
RayCast3d::new(Vec3::ZERO, Dir3::from_xyz(1., 0.99, 1.).unwrap(), 90.),
Aabb3d::new(Vec3::Y * 2., Vec3::ONE),
),
(
// The ray's maximum distance isn't high enough
RayCast3d::new(Vec3::ZERO, Dir3::Y, 0.5),
Aabb3d::new(Vec3::Y * 2., Vec3::ONE),
),
] {
assert!(
!test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}",
);
}
}
#[test]
fn test_ray_intersection_aabb_inside() {
let volume = Aabb3d::new(Vec3::splat(0.5), Vec3::ONE);
for origin in &[Vec3::X, Vec3::Y, Vec3::ONE, Vec3::ZERO] {
for direction in &[Dir3::X, Dir3::Y, Dir3::Z, -Dir3::X, -Dir3::Y, -Dir3::Z] {
for max in &[0., 1., 900.] {
let test = RayCast3d::new(*origin, *direction, *max);
assert!(
test.intersects(&volume),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
let actual_distance = test.aabb_intersection_at(&volume);
assert_eq!(
actual_distance,
Some(0.),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
}
}
}
}
#[test]
fn test_aabb_cast_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of the aabb, that a ray would've also hit
AabbCast3d::new(Aabb3d::new(Vec3::ZERO, Vec3::ONE), Vec3::ZERO, Dir3::Y, 90.),
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
3.,
),
(
// Hit the center of the aabb, but from the other side
AabbCast3d::new(
Aabb3d::new(Vec3::ZERO, Vec3::ONE),
Vec3::Y * 10.,
-Dir3::Y,
90.,
),
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
3.,
),
(
// Hit the edge of the aabb, that a ray would've missed
AabbCast3d::new(
Aabb3d::new(Vec3::ZERO, Vec3::ONE),
Vec3::X * 1.5,
Dir3::Y,
90.,
),
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
3.,
),
(
// Hit the edge of the aabb, by casting an off-center AABB
AabbCast3d::new(
Aabb3d::new(Vec3::X * -2., Vec3::ONE),
Vec3::X * 3.,
Dir3::Y,
90.,
),
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
3.,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.aabb_collision_at(*volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast3d::new(test.ray.origin, -test.ray.direction, test.ray.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_sphere_cast_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of the bounding sphere, that a ray would've also hit
BoundingSphereCast::new(
BoundingSphere::new(Vec3::ZERO, 1.),
Vec3::ZERO,
Dir3::Y,
90.,
),
BoundingSphere::new(Vec3::Y * 5., 1.),
3.,
),
(
// Hit the center of the bounding sphere, but from the other side
BoundingSphereCast::new(
BoundingSphere::new(Vec3::ZERO, 1.),
Vec3::Y * 10.,
-Dir3::Y,
90.,
),
BoundingSphere::new(Vec3::Y * 5., 1.),
3.,
),
(
// Hit the bounding sphere off-center, that a ray would've missed
BoundingSphereCast::new(
BoundingSphere::new(Vec3::ZERO, 1.),
Vec3::X * 1.5,
Dir3::Y,
90.,
),
BoundingSphere::new(Vec3::Y * 5., 1.),
3.677,
),
(
// Hit the bounding sphere off-center, by casting a sphere that is off-center
BoundingSphereCast::new(
BoundingSphere::new(Vec3::X * -1.5, 1.),
Vec3::X * 3.,
Dir3::Y,
90.,
),
BoundingSphere::new(Vec3::Y * 5., 1.),
3.677,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.sphere_collision_at(*volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast3d::new(test.ray.origin, -test.ray.direction, test.ray.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
}

471
vendor/bevy_math/src/common_traits.rs vendored Normal file
View File

@@ -0,0 +1,471 @@
//! This module contains abstract mathematical traits shared by types used in `bevy_math`.
use crate::{ops, Dir2, Dir3, Dir3A, Quat, Rot2, Vec2, Vec3, Vec3A, Vec4};
use core::{
fmt::Debug,
ops::{Add, Div, Mul, Neg, Sub},
};
use variadics_please::all_tuples_enumerated;
/// A type that supports the mathematical operations of a real vector space, irrespective of dimension.
/// In particular, this means that the implementing type supports:
/// - Scalar multiplication and division on the right by elements of `f32`
/// - Negation
/// - Addition and subtraction
/// - Zero
///
/// Within the limitations of floating point arithmetic, all the following are required to hold:
/// - (Associativity of addition) For all `u, v, w: Self`, `(u + v) + w == u + (v + w)`.
/// - (Commutativity of addition) For all `u, v: Self`, `u + v == v + u`.
/// - (Additive identity) For all `v: Self`, `v + Self::ZERO == v`.
/// - (Additive inverse) For all `v: Self`, `v - v == v + (-v) == Self::ZERO`.
/// - (Compatibility of multiplication) For all `a, b: f32`, `v: Self`, `v * (a * b) == (v * a) * b`.
/// - (Multiplicative identity) For all `v: Self`, `v * 1.0 == v`.
/// - (Distributivity for vector addition) For all `a: f32`, `u, v: Self`, `(u + v) * a == u * a + v * a`.
/// - (Distributivity for scalar addition) For all `a, b: f32`, `v: Self`, `v * (a + b) == v * a + v * b`.
///
/// Note that, because implementing types use floating point arithmetic, they are not required to actually
/// implement `PartialEq` or `Eq`.
pub trait VectorSpace:
Mul<f32, Output = Self>
+ Div<f32, Output = Self>
+ Add<Self, Output = Self>
+ Sub<Self, Output = Self>
+ Neg<Output = Self>
+ Default
+ Debug
+ Clone
+ Copy
{
/// The zero vector, which is the identity of addition for the vector space type.
const ZERO: Self;
/// Perform vector space linear interpolation between this element and another, based
/// on the parameter `t`. When `t` is `0`, `self` is recovered. When `t` is `1`, `rhs`
/// is recovered.
///
/// Note that the value of `t` is not clamped by this function, so extrapolating outside
/// of the interval `[0,1]` is allowed.
#[inline]
fn lerp(self, rhs: Self, t: f32) -> Self {
self * (1. - t) + rhs * t
}
}
impl VectorSpace for Vec4 {
const ZERO: Self = Vec4::ZERO;
}
impl VectorSpace for Vec3 {
const ZERO: Self = Vec3::ZERO;
}
impl VectorSpace for Vec3A {
const ZERO: Self = Vec3A::ZERO;
}
impl VectorSpace for Vec2 {
const ZERO: Self = Vec2::ZERO;
}
impl VectorSpace for f32 {
const ZERO: Self = 0.0;
}
/// A type consisting of formal sums of elements from `V` and `W`. That is,
/// each value `Sum(v, w)` is thought of as `v + w`, with no available
/// simplification. In particular, if `V` and `W` are [vector spaces], then
/// `Sum<V, W>` is a vector space whose dimension is the sum of those of `V`
/// and `W`, and the field accessors `.0` and `.1` are vector space projections.
///
/// [vector spaces]: VectorSpace
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct Sum<V, W>(pub V, pub W);
impl<V, W> Mul<f32> for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Sum(self.0 * rhs, self.1 * rhs)
}
}
impl<V, W> Div<f32> for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
Sum(self.0 / rhs, self.1 / rhs)
}
}
impl<V, W> Add<Self> for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
type Output = Self;
fn add(self, other: Self) -> Self::Output {
Sum(self.0 + other.0, self.1 + other.1)
}
}
impl<V, W> Sub<Self> for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Sum(self.0 - other.0, self.1 - other.1)
}
}
impl<V, W> Neg for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
type Output = Self;
fn neg(self) -> Self::Output {
Sum(-self.0, -self.1)
}
}
impl<V, W> Default for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
fn default() -> Self {
Sum(V::default(), W::default())
}
}
impl<V, W> VectorSpace for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
const ZERO: Self = Sum(V::ZERO, W::ZERO);
}
/// A type that supports the operations of a normed vector space; i.e. a norm operation in addition
/// to those of [`VectorSpace`]. Specifically, the implementor must guarantee that the following
/// relationships hold, within the limitations of floating point arithmetic:
/// - (Nonnegativity) For all `v: Self`, `v.norm() >= 0.0`.
/// - (Positive definiteness) For all `v: Self`, `v.norm() == 0.0` implies `v == Self::ZERO`.
/// - (Absolute homogeneity) For all `c: f32`, `v: Self`, `(v * c).norm() == v.norm() * c.abs()`.
/// - (Triangle inequality) For all `v, w: Self`, `(v + w).norm() <= v.norm() + w.norm()`.
///
/// Note that, because implementing types use floating point arithmetic, they are not required to actually
/// implement `PartialEq` or `Eq`.
pub trait NormedVectorSpace: VectorSpace {
/// The size of this element. The return value should always be nonnegative.
fn norm(self) -> f32;
/// The squared norm of this element. Computing this is often faster than computing
/// [`NormedVectorSpace::norm`].
#[inline]
fn norm_squared(self) -> f32 {
self.norm() * self.norm()
}
/// The distance between this element and another, as determined by the norm.
#[inline]
fn distance(self, rhs: Self) -> f32 {
(rhs - self).norm()
}
/// The squared distance between this element and another, as determined by the norm. Note that
/// this is often faster to compute in practice than [`NormedVectorSpace::distance`].
#[inline]
fn distance_squared(self, rhs: Self) -> f32 {
(rhs - self).norm_squared()
}
}
impl NormedVectorSpace for Vec4 {
#[inline]
fn norm(self) -> f32 {
self.length()
}
#[inline]
fn norm_squared(self) -> f32 {
self.length_squared()
}
}
impl NormedVectorSpace for Vec3 {
#[inline]
fn norm(self) -> f32 {
self.length()
}
#[inline]
fn norm_squared(self) -> f32 {
self.length_squared()
}
}
impl NormedVectorSpace for Vec3A {
#[inline]
fn norm(self) -> f32 {
self.length()
}
#[inline]
fn norm_squared(self) -> f32 {
self.length_squared()
}
}
impl NormedVectorSpace for Vec2 {
#[inline]
fn norm(self) -> f32 {
self.length()
}
#[inline]
fn norm_squared(self) -> f32 {
self.length_squared()
}
}
impl NormedVectorSpace for f32 {
#[inline]
fn norm(self) -> f32 {
ops::abs(self)
}
#[inline]
fn norm_squared(self) -> f32 {
self * self
}
}
/// A type with a natural interpolation that provides strong subdivision guarantees.
///
/// Although the only required method is `interpolate_stable`, many things are expected of it:
///
/// 1. The notion of interpolation should follow naturally from the semantics of the type, so
/// that inferring the interpolation mode from the type alone is sensible.
///
/// 2. The interpolation recovers something equivalent to the starting value at `t = 0.0`
/// and likewise with the ending value at `t = 1.0`. They do not have to be data-identical, but
/// they should be semantically identical. For example, [`Quat::slerp`] doesn't always yield its
/// second rotation input exactly at `t = 1.0`, but it always returns an equivalent rotation.
///
/// 3. Importantly, the interpolation must be *subdivision-stable*: for any interpolation curve
/// between two (unnamed) values and any parameter-value pairs `(t0, p)` and `(t1, q)`, the
/// interpolation curve between `p` and `q` must be the *linear* reparameterization of the original
/// interpolation curve restricted to the interval `[t0, t1]`.
///
/// The last of these conditions is very strong and indicates something like constant speed. It
/// is called "subdivision stability" because it guarantees that breaking up the interpolation
/// into segments and joining them back together has no effect.
///
/// Here is a diagram depicting it:
/// ```text
/// top curve = u.interpolate_stable(v, t)
///
/// t0 => p t1 => q
/// |-------------|---------|-------------|
/// 0 => u / \ 1 => v
/// / \
/// / \
/// / linear \
/// / reparameterization \
/// / t = t0 * (1 - s) + t1 * s \
/// / \
/// |-------------------------------------|
/// 0 => p 1 => q
///
/// bottom curve = p.interpolate_stable(q, s)
/// ```
///
/// Note that some common forms of interpolation do not satisfy this criterion. For example,
/// [`Quat::lerp`] and [`Rot2::nlerp`] are not subdivision-stable.
///
/// Furthermore, this is not to be used as a general trait for abstract interpolation.
/// Consumers rely on the strong guarantees in order for behavior based on this trait to be
/// well-behaved.
///
/// [`Quat::slerp`]: crate::Quat::slerp
/// [`Quat::lerp`]: crate::Quat::lerp
/// [`Rot2::nlerp`]: crate::Rot2::nlerp
pub trait StableInterpolate: Clone {
/// Interpolate between this value and the `other` given value using the parameter `t`. At
/// `t = 0.0`, a value equivalent to `self` is recovered, while `t = 1.0` recovers a value
/// equivalent to `other`, with intermediate values interpolating between the two.
/// See the [trait-level documentation] for details.
///
/// [trait-level documentation]: StableInterpolate
fn interpolate_stable(&self, other: &Self, t: f32) -> Self;
/// A version of [`interpolate_stable`] that assigns the result to `self` for convenience.
///
/// [`interpolate_stable`]: StableInterpolate::interpolate_stable
fn interpolate_stable_assign(&mut self, other: &Self, t: f32) {
*self = self.interpolate_stable(other, t);
}
/// Smoothly nudge this value towards the `target` at a given decay rate. The `decay_rate`
/// parameter controls how fast the distance between `self` and `target` decays relative to
/// the units of `delta`; the intended usage is for `decay_rate` to generally remain fixed,
/// while `delta` is something like `delta_time` from an updating system. This produces a
/// smooth following of the target that is independent of framerate.
///
/// More specifically, when this is called repeatedly, the result is that the distance between
/// `self` and a fixed `target` attenuates exponentially, with the rate of this exponential
/// decay given by `decay_rate`.
///
/// For example, at `decay_rate = 0.0`, this has no effect.
/// At `decay_rate = f32::INFINITY`, `self` immediately snaps to `target`.
/// In general, higher rates mean that `self` moves more quickly towards `target`.
///
/// # Example
/// ```
/// # use bevy_math::{Vec3, StableInterpolate};
/// # let delta_time: f32 = 1.0 / 60.0;
/// let mut object_position: Vec3 = Vec3::ZERO;
/// let target_position: Vec3 = Vec3::new(2.0, 3.0, 5.0);
/// // Decay rate of ln(10) => after 1 second, remaining distance is 1/10th
/// let decay_rate = f32::ln(10.0);
/// // Calling this repeatedly will move `object_position` towards `target_position`:
/// object_position.smooth_nudge(&target_position, decay_rate, delta_time);
/// ```
fn smooth_nudge(&mut self, target: &Self, decay_rate: f32, delta: f32) {
self.interpolate_stable_assign(target, 1.0 - ops::exp(-decay_rate * delta));
}
}
// Conservatively, we presently only apply this for normed vector spaces, where the notion
// of being constant-speed is literally true. The technical axioms are satisfied for any
// VectorSpace type, but the "natural from the semantics" part is less clear in general.
impl<V> StableInterpolate for V
where
V: NormedVectorSpace,
{
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.lerp(*other, t)
}
}
impl StableInterpolate for Rot2 {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Quat {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Dir2 {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Dir3 {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Dir3A {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
macro_rules! impl_stable_interpolate_tuple {
($(#[$meta:meta])* $(($n:tt, $T:ident)),*) => {
$(#[$meta])*
impl<$($T: StableInterpolate),*> StableInterpolate for ($($T,)*) {
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
(
$(
<$T as StableInterpolate>::interpolate_stable(&self.$n, &other.$n, t),
)*
)
}
}
};
}
all_tuples_enumerated!(
#[doc(fake_variadic)]
impl_stable_interpolate_tuple,
1,
11,
T
);
/// A type that has tangents.
pub trait HasTangent {
/// The tangent type.
type Tangent: VectorSpace;
}
/// A value with its derivative.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct WithDerivative<T>
where
T: HasTangent,
{
/// The underlying value.
pub value: T,
/// The derivative at `value`.
pub derivative: T::Tangent,
}
/// A value together with its first and second derivatives.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct WithTwoDerivatives<T>
where
T: HasTangent,
{
/// The underlying value.
pub value: T,
/// The derivative at `value`.
pub derivative: T::Tangent,
/// The second derivative at `value`.
pub second_derivative: <T::Tangent as HasTangent>::Tangent,
}
impl<V: VectorSpace> HasTangent for V {
type Tangent = V;
}
impl<M, N> HasTangent for (M, N)
where
M: HasTangent,
N: HasTangent,
{
type Tangent = Sum<M::Tangent, N::Tangent>;
}

585
vendor/bevy_math/src/compass.rs vendored Normal file
View File

@@ -0,0 +1,585 @@
use core::ops::Neg;
use crate::Dir2;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A compass enum with 4 directions.
/// ```text
/// N (North)
/// ▲
/// │
/// │
/// W (West) ┼─────► E (East)
/// │
/// │
/// ▼
/// S (South)
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Deserialize, Serialize)
)]
pub enum CompassQuadrant {
/// Corresponds to [`Dir2::Y`] and [`Dir2::NORTH`]
North,
/// Corresponds to [`Dir2::X`] and [`Dir2::EAST`]
East,
/// Corresponds to [`Dir2::NEG_X`] and [`Dir2::SOUTH`]
South,
/// Corresponds to [`Dir2::NEG_Y`] and [`Dir2::WEST`]
West,
}
impl CompassQuadrant {
/// Converts a standard index to a [`CompassQuadrant`].
///
/// Starts at 0 for [`CompassQuadrant::North`] and increments clockwise.
pub const fn from_index(index: usize) -> Option<Self> {
match index {
0 => Some(Self::North),
1 => Some(Self::East),
2 => Some(Self::South),
3 => Some(Self::West),
_ => None,
}
}
/// Converts a [`CompassQuadrant`] to a standard index.
///
/// Starts at 0 for [`CompassQuadrant::North`] and increments clockwise.
pub const fn to_index(self) -> usize {
match self {
Self::North => 0,
Self::East => 1,
Self::South => 2,
Self::West => 3,
}
}
/// Returns the opposite [`CompassQuadrant`], located 180 degrees from `self`.
///
/// This can also be accessed via the `-` operator, using the [`Neg`] trait.
pub const fn opposite(&self) -> CompassQuadrant {
match self {
Self::North => Self::South,
Self::East => Self::West,
Self::South => Self::North,
Self::West => Self::East,
}
}
}
/// A compass enum with 8 directions.
/// ```text
/// N (North)
/// ▲
/// NW │ NE
/// ╲ │
/// W (West) ┼─────► E (East)
/// │ ╲
/// SW │ SE
/// ▼
/// S (South)
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Deserialize, Serialize)
)]
pub enum CompassOctant {
/// Corresponds to [`Dir2::Y`] and [`Dir2::NORTH`]
North,
/// Corresponds to [`Dir2::NORTH_EAST`]
NorthEast,
/// Corresponds to [`Dir2::X`] and [`Dir2::EAST`]
East,
/// Corresponds to [`Dir2::SOUTH_EAST`]
SouthEast,
/// Corresponds to [`Dir2::NEG_X`] and [`Dir2::SOUTH`]
South,
/// Corresponds to [`Dir2::SOUTH_WEST`]
SouthWest,
/// Corresponds to [`Dir2::NEG_Y`] and [`Dir2::WEST`]
West,
/// Corresponds to [`Dir2::NORTH_WEST`]
NorthWest,
}
impl CompassOctant {
/// Converts a standard index to a [`CompassOctant`].
///
/// Starts at 0 for [`CompassOctant::North`] and increments clockwise.
pub const fn from_index(index: usize) -> Option<Self> {
match index {
0 => Some(Self::North),
1 => Some(Self::NorthEast),
2 => Some(Self::East),
3 => Some(Self::SouthEast),
4 => Some(Self::South),
5 => Some(Self::SouthWest),
6 => Some(Self::West),
7 => Some(Self::NorthWest),
_ => None,
}
}
/// Converts a [`CompassOctant`] to a standard index.
///
/// Starts at 0 for [`CompassOctant::North`] and increments clockwise.
pub const fn to_index(self) -> usize {
match self {
Self::North => 0,
Self::NorthEast => 1,
Self::East => 2,
Self::SouthEast => 3,
Self::South => 4,
Self::SouthWest => 5,
Self::West => 6,
Self::NorthWest => 7,
}
}
/// Returns the opposite [`CompassOctant`], located 180 degrees from `self`.
///
/// This can also be accessed via the `-` operator, using the [`Neg`] trait.
pub const fn opposite(&self) -> CompassOctant {
match self {
Self::North => Self::South,
Self::NorthEast => Self::SouthWest,
Self::East => Self::West,
Self::SouthEast => Self::NorthWest,
Self::South => Self::North,
Self::SouthWest => Self::NorthEast,
Self::West => Self::East,
Self::NorthWest => Self::SouthEast,
}
}
}
impl From<CompassQuadrant> for Dir2 {
fn from(q: CompassQuadrant) -> Self {
match q {
CompassQuadrant::North => Dir2::NORTH,
CompassQuadrant::East => Dir2::EAST,
CompassQuadrant::South => Dir2::SOUTH,
CompassQuadrant::West => Dir2::WEST,
}
}
}
impl From<Dir2> for CompassQuadrant {
/// Converts a [`Dir2`] to a [`CompassQuadrant`] in a lossy manner.
/// Converting back to a [`Dir2`] is not guaranteed to yield the same value.
fn from(dir: Dir2) -> Self {
let angle = dir.to_angle().to_degrees();
match angle {
-135.0..=-45.0 => Self::South,
-45.0..=45.0 => Self::East,
45.0..=135.0 => Self::North,
135.0..=180.0 | -180.0..=-135.0 => Self::West,
_ => unreachable!(),
}
}
}
impl From<CompassOctant> for Dir2 {
fn from(o: CompassOctant) -> Self {
match o {
CompassOctant::North => Dir2::NORTH,
CompassOctant::NorthEast => Dir2::NORTH_EAST,
CompassOctant::East => Dir2::EAST,
CompassOctant::SouthEast => Dir2::SOUTH_EAST,
CompassOctant::South => Dir2::SOUTH,
CompassOctant::SouthWest => Dir2::SOUTH_WEST,
CompassOctant::West => Dir2::WEST,
CompassOctant::NorthWest => Dir2::NORTH_WEST,
}
}
}
impl From<Dir2> for CompassOctant {
/// Converts a [`Dir2`] to a [`CompassOctant`] in a lossy manner.
/// Converting back to a [`Dir2`] is not guaranteed to yield the same value.
fn from(dir: Dir2) -> Self {
let angle = dir.to_angle().to_degrees();
match angle {
-112.5..=-67.5 => Self::South,
-67.5..=-22.5 => Self::SouthEast,
-22.5..=22.5 => Self::East,
22.5..=67.5 => Self::NorthEast,
67.5..=112.5 => Self::North,
112.5..=157.5 => Self::NorthWest,
157.5..=180.0 | -180.0..=-157.5 => Self::West,
-157.5..=-112.5 => Self::SouthWest,
_ => unreachable!(),
}
}
}
impl Neg for CompassQuadrant {
type Output = CompassQuadrant;
fn neg(self) -> Self::Output {
self.opposite()
}
}
impl Neg for CompassOctant {
type Output = CompassOctant;
fn neg(self) -> Self::Output {
self.opposite()
}
}
#[cfg(test)]
mod test_compass_quadrant {
use crate::{CompassQuadrant, Dir2, Vec2};
#[test]
fn test_cardinal_directions() {
let tests = [
(
Dir2::new(Vec2::new(1.0, 0.0)).unwrap(),
CompassQuadrant::East,
),
(
Dir2::new(Vec2::new(0.0, 1.0)).unwrap(),
CompassQuadrant::North,
),
(
Dir2::new(Vec2::new(-1.0, 0.0)).unwrap(),
CompassQuadrant::West,
),
(
Dir2::new(Vec2::new(0.0, -1.0)).unwrap(),
CompassQuadrant::South,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_north_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(),
CompassQuadrant::North,
),
(
Dir2::new(Vec2::new(0.1, 0.9)).unwrap(),
CompassQuadrant::North,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_east_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(0.9, 0.1)).unwrap(),
CompassQuadrant::East,
),
(
Dir2::new(Vec2::new(0.9, -0.1)).unwrap(),
CompassQuadrant::East,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_south_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(),
CompassQuadrant::South,
),
(
Dir2::new(Vec2::new(0.1, -0.9)).unwrap(),
CompassQuadrant::South,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(),
CompassQuadrant::West,
),
(
Dir2::new(Vec2::new(-0.9, 0.1)).unwrap(),
CompassQuadrant::West,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn out_of_bounds_indexes_return_none() {
assert_eq!(CompassQuadrant::from_index(4), None);
assert_eq!(CompassQuadrant::from_index(5), None);
assert_eq!(CompassQuadrant::from_index(usize::MAX), None);
}
#[test]
fn compass_indexes_are_reversible() {
for i in 0..4 {
let quadrant = CompassQuadrant::from_index(i).unwrap();
assert_eq!(quadrant.to_index(), i);
}
}
#[test]
fn opposite_directions_reverse_themselves() {
for i in 0..4 {
let quadrant = CompassQuadrant::from_index(i).unwrap();
assert_eq!(-(-quadrant), quadrant);
}
}
}
#[cfg(test)]
mod test_compass_octant {
use crate::{CompassOctant, Dir2, Vec2};
#[test]
fn test_cardinal_directions() {
let tests = [
(
Dir2::new(Vec2::new(-0.5, 0.5)).unwrap(),
CompassOctant::NorthWest,
),
(
Dir2::new(Vec2::new(0.0, 1.0)).unwrap(),
CompassOctant::North,
),
(
Dir2::new(Vec2::new(0.5, 0.5)).unwrap(),
CompassOctant::NorthEast,
),
(Dir2::new(Vec2::new(1.0, 0.0)).unwrap(), CompassOctant::East),
(
Dir2::new(Vec2::new(0.5, -0.5)).unwrap(),
CompassOctant::SouthEast,
),
(
Dir2::new(Vec2::new(0.0, -1.0)).unwrap(),
CompassOctant::South,
),
(
Dir2::new(Vec2::new(-0.5, -0.5)).unwrap(),
CompassOctant::SouthWest,
),
(
Dir2::new(Vec2::new(-1.0, 0.0)).unwrap(),
CompassOctant::West,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_north_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(),
CompassOctant::North,
),
(
Dir2::new(Vec2::new(0.1, 0.9)).unwrap(),
CompassOctant::North,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_north_east_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(0.4, 0.6)).unwrap(),
CompassOctant::NorthEast,
),
(
Dir2::new(Vec2::new(0.6, 0.4)).unwrap(),
CompassOctant::NorthEast,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_east_pie_slice() {
let tests = [
(Dir2::new(Vec2::new(0.9, 0.1)).unwrap(), CompassOctant::East),
(
Dir2::new(Vec2::new(0.9, -0.1)).unwrap(),
CompassOctant::East,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_south_east_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(0.4, -0.6)).unwrap(),
CompassOctant::SouthEast,
),
(
Dir2::new(Vec2::new(0.6, -0.4)).unwrap(),
CompassOctant::SouthEast,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_south_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(),
CompassOctant::South,
),
(
Dir2::new(Vec2::new(0.1, -0.9)).unwrap(),
CompassOctant::South,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_south_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.4, -0.6)).unwrap(),
CompassOctant::SouthWest,
),
(
Dir2::new(Vec2::new(-0.6, -0.4)).unwrap(),
CompassOctant::SouthWest,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(),
CompassOctant::West,
),
(
Dir2::new(Vec2::new(-0.9, 0.1)).unwrap(),
CompassOctant::West,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_north_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.4, 0.6)).unwrap(),
CompassOctant::NorthWest,
),
(
Dir2::new(Vec2::new(-0.6, 0.4)).unwrap(),
CompassOctant::NorthWest,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn out_of_bounds_indexes_return_none() {
assert_eq!(CompassOctant::from_index(8), None);
assert_eq!(CompassOctant::from_index(9), None);
assert_eq!(CompassOctant::from_index(usize::MAX), None);
}
#[test]
fn compass_indexes_are_reversible() {
for i in 0..8 {
let octant = CompassOctant::from_index(i).unwrap();
assert_eq!(octant.to_index(), i);
}
}
#[test]
fn opposite_directions_reverse_themselves() {
for i in 0..8 {
let octant = CompassOctant::from_index(i).unwrap();
assert_eq!(-(-octant), octant);
}
}
}

View File

@@ -0,0 +1,159 @@
use super::{CubicSegment, RationalSegment};
use crate::common_traits::{VectorSpace, WithDerivative, WithTwoDerivatives};
use crate::curve::{
derivatives::{SampleDerivative, SampleTwoDerivatives},
Curve, Interval,
};
#[cfg(feature = "alloc")]
use super::{CubicCurve, RationalCurve};
// -- CubicSegment
impl<P: VectorSpace> Curve<P> for CubicSegment<P> {
#[inline]
fn domain(&self) -> Interval {
Interval::UNIT
}
#[inline]
fn sample_unchecked(&self, t: f32) -> P {
self.position(t)
}
}
impl<P: VectorSpace> SampleDerivative<P> for CubicSegment<P> {
#[inline]
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
WithDerivative {
value: self.position(t),
derivative: self.velocity(t),
}
}
}
impl<P: VectorSpace> SampleTwoDerivatives<P> for CubicSegment<P> {
#[inline]
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
WithTwoDerivatives {
value: self.position(t),
derivative: self.velocity(t),
second_derivative: self.acceleration(t),
}
}
}
// -- CubicCurve
#[cfg(feature = "alloc")]
impl<P: VectorSpace> Curve<P> for CubicCurve<P> {
#[inline]
fn domain(&self) -> Interval {
// The non-emptiness invariant guarantees that this succeeds.
Interval::new(0.0, self.segments.len() as f32)
.expect("CubicCurve is invalid because it has no segments")
}
#[inline]
fn sample_unchecked(&self, t: f32) -> P {
self.position(t)
}
}
#[cfg(feature = "alloc")]
impl<P: VectorSpace> SampleDerivative<P> for CubicCurve<P> {
#[inline]
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
WithDerivative {
value: self.position(t),
derivative: self.velocity(t),
}
}
}
#[cfg(feature = "alloc")]
impl<P: VectorSpace> SampleTwoDerivatives<P> for CubicCurve<P> {
#[inline]
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
WithTwoDerivatives {
value: self.position(t),
derivative: self.velocity(t),
second_derivative: self.acceleration(t),
}
}
}
// -- RationalSegment
impl<P: VectorSpace> Curve<P> for RationalSegment<P> {
#[inline]
fn domain(&self) -> Interval {
Interval::UNIT
}
#[inline]
fn sample_unchecked(&self, t: f32) -> P {
self.position(t)
}
}
impl<P: VectorSpace> SampleDerivative<P> for RationalSegment<P> {
#[inline]
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
WithDerivative {
value: self.position(t),
derivative: self.velocity(t),
}
}
}
impl<P: VectorSpace> SampleTwoDerivatives<P> for RationalSegment<P> {
#[inline]
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
WithTwoDerivatives {
value: self.position(t),
derivative: self.velocity(t),
second_derivative: self.acceleration(t),
}
}
}
// -- RationalCurve
#[cfg(feature = "alloc")]
impl<P: VectorSpace> Curve<P> for RationalCurve<P> {
#[inline]
fn domain(&self) -> Interval {
// The non-emptiness invariant guarantees the success of this.
Interval::new(0.0, self.length())
.expect("RationalCurve is invalid because it has zero length")
}
#[inline]
fn sample_unchecked(&self, t: f32) -> P {
self.position(t)
}
}
#[cfg(feature = "alloc")]
impl<P: VectorSpace> SampleDerivative<P> for RationalCurve<P> {
#[inline]
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
WithDerivative {
value: self.position(t),
derivative: self.velocity(t),
}
}
}
#[cfg(feature = "alloc")]
impl<P: VectorSpace> SampleTwoDerivatives<P> for RationalCurve<P> {
#[inline]
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
WithTwoDerivatives {
value: self.position(t),
derivative: self.velocity(t),
second_derivative: self.acceleration(t),
}
}
}

1845
vendor/bevy_math/src/cubic_splines/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

815
vendor/bevy_math/src/curve/adaptors.rs vendored Normal file
View File

@@ -0,0 +1,815 @@
//! Adaptors used by the Curve API for transforming and combining curves together.
use super::interval::*;
use super::Curve;
use crate::ops;
use crate::VectorSpace;
use core::any::type_name;
use core::fmt::{self, Debug};
use core::marker::PhantomData;
#[cfg(feature = "bevy_reflect")]
use {
alloc::format,
bevy_reflect::{utility::GenericTypePathCell, FromReflect, Reflect, TypePath},
};
#[cfg(feature = "bevy_reflect")]
mod paths {
pub(super) const THIS_MODULE: &str = "bevy_math::curve::adaptors";
pub(super) const THIS_CRATE: &str = "bevy_math";
}
#[expect(unused, reason = "imported just for doc links")]
use super::CurveExt;
// NOTE ON REFLECTION:
//
// Function members of structs pose an obstacle for reflection, because they don't implement
// reflection traits themselves. Some of these are more problematic than others; for example,
// `FromReflect` is basically hopeless for function members regardless, so function-containing
// adaptors will just never be `FromReflect` (at least until function item types implement
// Default, if that ever happens). Similarly, they do not implement `TypePath`, and as a result,
// those adaptors also need custom `TypePath` adaptors which use `type_name` instead.
//
// The sum total weirdness of the `Reflect` implementations amounts to this; those adaptors:
// - are currently never `FromReflect`;
// - have custom `TypePath` implementations which are not fully stable;
// - have custom `Debug` implementations which display the function only by type name.
/// A curve with a constant value over its domain.
///
/// This is a curve that holds an inner value and always produces a clone of that value when sampled.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct ConstantCurve<T> {
pub(crate) domain: Interval,
pub(crate) value: T,
}
impl<T> ConstantCurve<T>
where
T: Clone,
{
/// Create a constant curve, which has the given `domain` and always produces the given `value`
/// when sampled.
pub fn new(domain: Interval, value: T) -> Self {
Self { domain, value }
}
}
impl<T> Curve<T> for ConstantCurve<T>
where
T: Clone,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, _t: f32) -> T {
self.value.clone()
}
}
/// A curve defined by a function together with a fixed domain.
///
/// This is a curve that holds an inner function `f` which takes numbers (`f32`) as input and produces
/// output of type `T`. The value of this curve when sampled at time `t` is just `f(t)`.
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct FunctionCurve<T, F> {
pub(crate) domain: Interval,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, F> Debug for FunctionCurve<T, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FunctionCurve")
.field("domain", &self.domain)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, F> TypePath for FunctionCurve<T, F>
where
T: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::FunctionCurve<{},{}>",
paths::THIS_MODULE,
T::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"FunctionCurve<{},{}>",
T::short_type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("FunctionCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, F> FunctionCurve<T, F>
where
F: Fn(f32) -> T,
{
/// Create a new curve with the given `domain` from the given `function`. When sampled, the
/// `function` is evaluated at the sample time to compute the output.
pub fn new(domain: Interval, function: F) -> Self {
FunctionCurve {
domain,
f: function,
_phantom: PhantomData,
}
}
}
impl<T, F> Curve<T> for FunctionCurve<T, F>
where
F: Fn(f32) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
(self.f)(t)
}
}
/// A curve whose samples are defined by mapping samples from another curve through a
/// given function. Curves of this type are produced by [`CurveExt::map`].
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where S: TypePath, T: TypePath, C: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct MapCurve<S, T, C, F> {
pub(crate) preimage: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<(fn() -> S, fn(S) -> T)>,
}
impl<S, T, C, F> Debug for MapCurve<S, T, C, F>
where
C: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapCurve")
.field("preimage", &self.preimage)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<S, T, C, F> TypePath for MapCurve<S, T, C, F>
where
S: TypePath,
T: TypePath,
C: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::MapCurve<{},{},{},{}>",
paths::THIS_MODULE,
S::type_path(),
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"MapCurve<{},{},{},{}>",
S::type_path(),
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("MapCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<S, T, C, F> Curve<T> for MapCurve<S, T, C, F>
where
C: Curve<S>,
F: Fn(S) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.preimage.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
(self.f)(self.preimage.sample_unchecked(t))
}
}
/// A curve whose sample space is mapped onto that of some base curve's before sampling.
/// Curves of this type are produced by [`CurveExt::reparametrize`].
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath, C: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct ReparamCurve<T, C, F> {
pub(crate) domain: Interval,
pub(crate) base: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, F> Debug for ReparamCurve<T, C, F>
where
C: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReparamCurve")
.field("domain", &self.domain)
.field("base", &self.base)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, C, F> TypePath for ReparamCurve<T, C, F>
where
T: TypePath,
C: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::ReparamCurve<{},{},{}>",
paths::THIS_MODULE,
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"ReparamCurve<{},{},{}>",
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("ReparamCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, C, F> Curve<T> for ReparamCurve<T, C, F>
where
C: Curve<T>,
F: Fn(f32) -> f32,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.base.sample_unchecked((self.f)(t))
}
}
/// A curve that has had its domain changed by a linear reparameterization (stretching and scaling).
/// Curves of this type are produced by [`CurveExt::reparametrize_linear`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct LinearReparamCurve<T, C> {
/// Invariants: The domain of this curve must always be bounded.
pub(crate) base: C,
/// Invariants: This interval must always be bounded.
pub(crate) new_domain: Interval,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for LinearReparamCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.new_domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
// The invariants imply this unwrap always succeeds.
let f = self.new_domain.linear_map_to(self.base.domain()).unwrap();
self.base.sample_unchecked(f(t))
}
}
/// A curve that has been reparametrized by another curve, using that curve to transform the
/// sample times before sampling. Curves of this type are produced by [`CurveExt::reparametrize_by_curve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct CurveReparamCurve<T, C, D> {
pub(crate) base: C,
pub(crate) reparam_curve: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for CurveReparamCurve<T, C, D>
where
C: Curve<T>,
D: Curve<f32>,
{
#[inline]
fn domain(&self) -> Interval {
self.reparam_curve.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let sample_time = self.reparam_curve.sample_unchecked(t);
self.base.sample_unchecked(sample_time)
}
}
/// A curve that is the graph of another curve over its parameter space. Curves of this type are
/// produced by [`CurveExt::graph`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct GraphCurve<T, C> {
pub(crate) base: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<(f32, T)> for GraphCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.base.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> (f32, T) {
(t, self.base.sample_unchecked(t))
}
}
/// A curve that combines the output data from two constituent curves into a tuple output. Curves
/// of this type are produced by [`CurveExt::zip`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ZipCurve<S, T, C, D> {
pub(crate) domain: Interval,
pub(crate) first: C,
pub(crate) second: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> (S, T)>,
}
impl<S, T, C, D> Curve<(S, T)> for ZipCurve<S, T, C, D>
where
C: Curve<S>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> (S, T) {
(
self.first.sample_unchecked(t),
self.second.sample_unchecked(t),
)
}
}
/// The curve that results from chaining one curve with another. The second curve is
/// effectively reparametrized so that its start is at the end of the first.
///
/// For this to be well-formed, the first curve's domain must be right-finite and the second's
/// must be left-finite.
///
/// Curves of this type are produced by [`CurveExt::chain`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ChainCurve<T, C, D> {
pub(crate) first: C,
pub(crate) second: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for ChainCurve<T, C, D>
where
C: Curve<T>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `first` has a valid Interval as its domain and the
// length of `second` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.first.domain().start(),
self.first.domain().end() + self.second.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
if t > self.first.domain().end() {
self.second.sample_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
)
} else {
self.first.sample_unchecked(t)
}
}
}
/// The curve that results from reversing another.
///
/// Curves of this type are produced by [`CurveExt::reverse`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`ReverseCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ReverseCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for ReverseCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.curve.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.curve
.sample_unchecked(self.domain().end() - (t - self.domain().start()))
}
}
/// The curve that results from repeating a curve `N` times.
///
/// # Notes
///
/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
/// value at `domain.end()` in the original curve
///
/// Curves of this type are produced by [`CurveExt::repeat`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`RepeatCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct RepeatCurve<T, C> {
pub(crate) domain: Interval,
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for RepeatCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let t = self.base_curve_sample_time(t);
self.curve.sample_unchecked(t)
}
}
impl<T, C> RepeatCurve<T, C>
where
C: Curve<T>,
{
#[inline]
pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
// the domain is bounded by construction
let d = self.curve.domain();
let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
if t != d.start() && cyclic_t == 0.0 {
d.end()
} else {
d.start() + cyclic_t
}
}
}
/// The curve that results from repeating a curve forever.
///
/// # Notes
///
/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
/// value at `domain.end()` in the original curve
///
/// Curves of this type are produced by [`CurveExt::forever`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`ForeverCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ForeverCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for ForeverCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
Interval::EVERYWHERE
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let t = self.base_curve_sample_time(t);
self.curve.sample_unchecked(t)
}
}
impl<T, C> ForeverCurve<T, C>
where
C: Curve<T>,
{
#[inline]
pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
// the domain is bounded by construction
let d = self.curve.domain();
let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
if t != d.start() && cyclic_t == 0.0 {
d.end()
} else {
d.start() + cyclic_t
}
}
}
/// The curve that results from chaining a curve with its reversed version. The transition point
/// is guaranteed to make no jump.
///
/// Curves of this type are produced by [`CurveExt::ping_pong`].
///
/// # Domain
///
/// The original curve's domain must be right-finite to get a valid [`PingPongCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct PingPongCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for PingPongCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `curve` has a valid Interval as its domain and the
// length of `curve` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.curve.domain().start(),
self.curve.domain().end() + self.curve.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
// the domain is bounded by construction
let final_t = if t > self.curve.domain().end() {
self.curve.domain().end() * 2.0 - t
} else {
t
};
self.curve.sample_unchecked(final_t)
}
}
/// The curve that results from chaining two curves.
///
/// Additionally the transition of the samples is guaranteed to not make sudden jumps. This is
/// useful if you really just know about the shapes of your curves and don't want to deal with
/// stitching them together properly when it would just introduce useless complexity. It is
/// realized by translating the second curve so that its start sample point coincides with the
/// first curves' end sample point.
///
/// Curves of this type are produced by [`CurveExt::chain_continue`].
///
/// # Domain
///
/// The first curve's domain must be right-finite and the second's must be left-finite to get a
/// valid [`ContinuationCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ContinuationCurve<T, C, D> {
pub(crate) first: C,
pub(crate) second: D,
// cache the offset in the curve directly to prevent triple sampling for every sample we make
pub(crate) offset: T,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for ContinuationCurve<T, C, D>
where
T: VectorSpace,
C: Curve<T>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `curve` has a valid Interval as its domain and the
// length of `curve` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.first.domain().start(),
self.first.domain().end() + self.second.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
if t > self.first.domain().end() {
self.second.sample_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
) + self.offset
} else {
self.first.sample_unchecked(t)
}
}
}

826
vendor/bevy_math/src/curve/cores.rs vendored Normal file
View File

@@ -0,0 +1,826 @@
//! Core data structures to be used internally in Curve implementations, encapsulating storage
//! and access patterns for reuse.
//!
//! The `Core` types here expose their fields publicly so that it is easier to manipulate and
//! extend them, but in doing so, you must maintain the invariants of those fields yourself. The
//! provided methods all maintain the invariants, so this is only a concern if you manually mutate
//! the fields.
use crate::ops;
use super::interval::Interval;
use core::fmt::Debug;
use thiserror::Error;
#[cfg(feature = "alloc")]
use {alloc::vec::Vec, itertools::Itertools};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// This type expresses the relationship of a value to a fixed collection of values. It is a kind
/// of summary used intermediately by sampling operations.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub enum InterpolationDatum<T> {
/// This value lies exactly on a value in the family.
Exact(T),
/// This value is off the left tail of the family; the inner value is the family's leftmost.
LeftTail(T),
/// This value is off the right tail of the family; the inner value is the family's rightmost.
RightTail(T),
/// This value lies on the interior, in between two points, with a third parameter expressing
/// the interpolation factor between the two.
Between(T, T, f32),
}
impl<T> InterpolationDatum<T> {
/// Map all values using a given function `f`, leaving the interpolation parameters in any
/// [`Between`] variants unchanged.
///
/// [`Between`]: `InterpolationDatum::Between`
#[must_use]
pub fn map<S>(self, f: impl Fn(T) -> S) -> InterpolationDatum<S> {
match self {
InterpolationDatum::Exact(v) => InterpolationDatum::Exact(f(v)),
InterpolationDatum::LeftTail(v) => InterpolationDatum::LeftTail(f(v)),
InterpolationDatum::RightTail(v) => InterpolationDatum::RightTail(f(v)),
InterpolationDatum::Between(u, v, s) => InterpolationDatum::Between(f(u), f(v), s),
}
}
}
/// The data core of a curve derived from evenly-spaced samples. The intention is to use this
/// in addition to explicit or inferred interpolation information in user-space in order to
/// implement curves using [`domain`] and [`sample_with`].
///
/// The internals are made transparent to give curve authors freedom, but [the provided constructor]
/// enforces the required invariants, and the methods maintain those invariants.
///
/// [the provided constructor]: EvenCore::new
/// [`domain`]: EvenCore::domain
/// [`sample_with`]: EvenCore::sample_with
///
/// # Example
/// ```rust
/// # use bevy_math::curve::*;
/// # use bevy_math::curve::cores::*;
/// // Let's make a curve that interpolates evenly spaced samples using either linear interpolation
/// // or step "interpolation" — i.e. just using the most recent sample as the source of truth.
/// enum InterpolationMode {
/// Linear,
/// Step,
/// }
///
/// // Linear interpolation mode is driven by a trait.
/// trait LinearInterpolate {
/// fn lerp(&self, other: &Self, t: f32) -> Self;
/// }
///
/// // Step interpolation just uses an explicit function.
/// fn step<T: Clone>(first: &T, second: &T, t: f32) -> T {
/// if t >= 1.0 {
/// second.clone()
/// } else {
/// first.clone()
/// }
/// }
///
/// // Omitted: Implementing `LinearInterpolate` on relevant types; e.g. `f32`, `Vec3`, and so on.
///
/// // The curve itself uses `EvenCore` to hold the evenly-spaced samples, and the `sample_with`
/// // function will do all the work of interpolating once given a function to do it with.
/// struct MyCurve<T> {
/// core: EvenCore<T>,
/// interpolation_mode: InterpolationMode,
/// }
///
/// impl<T> Curve<T> for MyCurve<T>
/// where
/// T: LinearInterpolate + Clone,
/// {
/// fn domain(&self) -> Interval {
/// self.core.domain()
/// }
///
/// fn sample_unchecked(&self, t: f32) -> T {
/// // To sample this curve, check the interpolation mode and dispatch accordingly.
/// match self.interpolation_mode {
/// InterpolationMode::Linear => self.core.sample_with(t, <T as LinearInterpolate>::lerp),
/// InterpolationMode::Step => self.core.sample_with(t, step),
/// }
/// }
/// }
/// ```
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct EvenCore<T> {
/// The domain over which the samples are taken, which corresponds to the domain of the curve
/// formed by interpolating them.
///
/// # Invariants
/// This must always be a bounded interval; i.e. its endpoints must be finite.
pub domain: Interval,
/// The samples that are interpolated to extract values.
///
/// # Invariants
/// This must always have a length of at least 2.
pub samples: Vec<T>,
}
/// An error indicating that an [`EvenCore`] could not be constructed.
#[derive(Debug, Error)]
#[error("Could not construct an EvenCore")]
pub enum EvenCoreError {
/// Not enough samples were provided.
#[error("Need at least two samples to create an EvenCore, but {samples} were provided")]
NotEnoughSamples {
/// The number of samples that were provided.
samples: usize,
},
/// Unbounded domains are not compatible with `EvenCore`.
#[error("Cannot create an EvenCore over an unbounded domain")]
UnboundedDomain,
}
#[cfg(feature = "alloc")]
impl<T> EvenCore<T> {
/// Create a new [`EvenCore`] from the specified `domain` and `samples`. The samples are
/// regarded to be evenly spaced within the given domain interval, so that the outermost
/// samples form the boundary of that interval. An error is returned if there are not at
/// least 2 samples or if the given domain is unbounded.
#[inline]
pub fn new(
domain: Interval,
samples: impl IntoIterator<Item = T>,
) -> Result<Self, EvenCoreError> {
let samples: Vec<T> = samples.into_iter().collect();
if samples.len() < 2 {
return Err(EvenCoreError::NotEnoughSamples {
samples: samples.len(),
});
}
if !domain.is_bounded() {
return Err(EvenCoreError::UnboundedDomain);
}
Ok(EvenCore { domain, samples })
}
/// The domain of the curve derived from this core.
#[inline]
pub const fn domain(&self) -> Interval {
self.domain
}
/// Obtain a value from the held samples using the given `interpolation` to interpolate
/// between adjacent samples.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
#[inline]
pub fn sample_with<I>(&self, t: f32, interpolation: I) -> T
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
match even_interp(self.domain, self.samples.len(), t) {
InterpolationDatum::Exact(idx)
| InterpolationDatum::LeftTail(idx)
| InterpolationDatum::RightTail(idx) => self.samples[idx].clone(),
InterpolationDatum::Between(lower_idx, upper_idx, s) => {
interpolation(&self.samples[lower_idx], &self.samples[upper_idx], s)
}
}
}
/// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
/// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
/// be used to interpolate between the two contained values with the given parameter. The other
/// variants give additional context about where the value is relative to the family of samples.
///
/// [`Between`]: `InterpolationDatum::Between`
pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&T> {
even_interp(self.domain, self.samples.len(), t).map(|idx| &self.samples[idx])
}
/// Like [`sample_interp`], but the returned values include the sample times. This can be
/// useful when sample interpolation is not scale-invariant.
///
/// [`sample_interp`]: EvenCore::sample_interp
pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &T)> {
let segment_len = self.domain.length() / (self.samples.len() - 1) as f32;
even_interp(self.domain, self.samples.len(), t).map(|idx| {
(
self.domain.start() + segment_len * idx as f32,
&self.samples[idx],
)
})
}
}
/// Given a domain and a number of samples taken over that interval, return an [`InterpolationDatum`]
/// that governs how samples are extracted relative to the stored data.
///
/// `domain` must be a bounded interval (i.e. `domain.is_bounded() == true`).
///
/// `samples` must be at least 2.
///
/// This function will never panic, but it may return invalid indices if its assumptions are violated.
pub fn even_interp(domain: Interval, samples: usize, t: f32) -> InterpolationDatum<usize> {
let subdivs = samples - 1;
let step = domain.length() / subdivs as f32;
let t_shifted = t - domain.start();
let steps_taken = t_shifted / step;
if steps_taken <= 0.0 {
// To the left side of all the samples.
InterpolationDatum::LeftTail(0)
} else if steps_taken >= subdivs as f32 {
// To the right side of all the samples
InterpolationDatum::RightTail(samples - 1)
} else {
let lower_index = ops::floor(steps_taken) as usize;
// This upper index is always valid because `steps_taken` is a finite value
// strictly less than `samples - 1`, so its floor is at most `samples - 2`
let upper_index = lower_index + 1;
let s = ops::fract(steps_taken);
InterpolationDatum::Between(lower_index, upper_index, s)
}
}
/// The data core of a curve defined by unevenly-spaced samples or keyframes. The intention is to
/// use this in concert with implicitly or explicitly-defined interpolation in user-space in
/// order to implement the curve interface using [`domain`] and [`sample_with`].
///
/// The internals are made transparent to give curve authors freedom, but [the provided constructor]
/// enforces the required invariants, and the methods maintain those invariants.
///
/// # Example
/// ```rust
/// # use bevy_math::curve::*;
/// # use bevy_math::curve::cores::*;
/// // Let's make a curve formed by interpolating rotations.
/// // We'll support two common modes of interpolation:
/// // - Normalized linear: First do linear interpolation, then normalize to get a valid rotation.
/// // - Spherical linear: Interpolate through valid rotations with constant angular velocity.
/// enum InterpolationMode {
/// NormalizedLinear,
/// SphericalLinear,
/// }
///
/// // Our interpolation modes will be driven by traits.
/// trait NormalizedLinearInterpolate {
/// fn nlerp(&self, other: &Self, t: f32) -> Self;
/// }
///
/// trait SphericalLinearInterpolate {
/// fn slerp(&self, other: &Self, t: f32) -> Self;
/// }
///
/// // Omitted: These traits would be implemented for `Rot2`, `Quat`, and other rotation representations.
///
/// // The curve itself just needs to use the curve core for keyframes, `UnevenCore`, which handles
/// // everything except for the explicit interpolation used.
/// struct RotationCurve<T> {
/// core: UnevenCore<T>,
/// interpolation_mode: InterpolationMode,
/// }
///
/// impl<T> Curve<T> for RotationCurve<T>
/// where
/// T: NormalizedLinearInterpolate + SphericalLinearInterpolate + Clone,
/// {
/// fn domain(&self) -> Interval {
/// self.core.domain()
/// }
///
/// fn sample_unchecked(&self, t: f32) -> T {
/// // To sample the curve, we just look at the interpolation mode and
/// // dispatch accordingly.
/// match self.interpolation_mode {
/// InterpolationMode::NormalizedLinear =>
/// self.core.sample_with(t, <T as NormalizedLinearInterpolate>::nlerp),
/// InterpolationMode::SphericalLinear =>
/// self.core.sample_with(t, <T as SphericalLinearInterpolate>::slerp),
/// }
/// }
/// }
/// ```
///
/// [`domain`]: UnevenCore::domain
/// [`sample_with`]: UnevenCore::sample_with
/// [the provided constructor]: UnevenCore::new
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct UnevenCore<T> {
/// The times for the samples of this curve.
///
/// # Invariants
/// This must always have a length of at least 2, be sorted, and have no
/// duplicated or non-finite times.
pub times: Vec<f32>,
/// The samples corresponding to the times for this curve.
///
/// # Invariants
/// This must always have the same length as `times`.
pub samples: Vec<T>,
}
/// An error indicating that an [`UnevenCore`] could not be constructed.
#[derive(Debug, Error)]
#[error("Could not construct an UnevenCore")]
pub enum UnevenCoreError {
/// Not enough samples were provided.
#[error(
"Need at least two unique samples to create an UnevenCore, but {samples} were provided"
)]
NotEnoughSamples {
/// The number of samples that were provided.
samples: usize,
},
}
#[cfg(feature = "alloc")]
impl<T> UnevenCore<T> {
/// Create a new [`UnevenCore`]. The given samples are filtered to finite times and
/// sorted internally; if there are not at least 2 valid timed samples, an error will be
/// returned.
pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
// Filter out non-finite sample times first so they don't interfere with sorting/deduplication.
let mut timed_samples = timed_samples
.into_iter()
.filter(|(t, _)| t.is_finite())
.collect_vec();
timed_samples
// Using `total_cmp` is fine because no NANs remain and because deduplication uses
// `PartialEq` anyway (so -0.0 and 0.0 will be considered equal later regardless).
.sort_by(|(t0, _), (t1, _)| t0.total_cmp(t1));
timed_samples.dedup_by_key(|(t, _)| *t);
if timed_samples.len() < 2 {
return Err(UnevenCoreError::NotEnoughSamples {
samples: timed_samples.len(),
});
}
let (times, samples): (Vec<f32>, Vec<T>) = timed_samples.into_iter().unzip();
Ok(UnevenCore { times, samples })
}
/// The domain of the curve derived from this core.
///
/// # Panics
/// This method may panic if the type's invariants aren't satisfied.
#[inline]
pub fn domain(&self) -> Interval {
let start = self.times.first().unwrap();
let end = self.times.last().unwrap();
Interval::new(*start, *end).unwrap()
}
/// Obtain a value from the held samples using the given `interpolation` to interpolate
/// between adjacent samples.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
#[inline]
pub fn sample_with<I>(&self, t: f32, interpolation: I) -> T
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
match uneven_interp(&self.times, t) {
InterpolationDatum::Exact(idx)
| InterpolationDatum::LeftTail(idx)
| InterpolationDatum::RightTail(idx) => self.samples[idx].clone(),
InterpolationDatum::Between(lower_idx, upper_idx, s) => {
interpolation(&self.samples[lower_idx], &self.samples[upper_idx], s)
}
}
}
/// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
/// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
/// be used to interpolate between the two contained values with the given parameter. The other
/// variants give additional context about where the value is relative to the family of samples.
///
/// [`Between`]: `InterpolationDatum::Between`
pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&T> {
uneven_interp(&self.times, t).map(|idx| &self.samples[idx])
}
/// Like [`sample_interp`], but the returned values include the sample times. This can be
/// useful when sample interpolation is not scale-invariant.
///
/// [`sample_interp`]: UnevenCore::sample_interp
pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &T)> {
uneven_interp(&self.times, t).map(|idx| (self.times[idx], &self.samples[idx]))
}
/// This core, but with the sample times moved by the map `f`.
/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
/// but the function inputs to each are inverses of one another.
///
/// The samples are re-sorted by time after mapping and deduplicated by output time, so
/// the function `f` should generally be injective over the set of sample times, otherwise
/// data will be deleted.
///
/// [`CurveExt::reparametrize`]: crate::curve::CurveExt::reparametrize
#[must_use]
pub fn map_sample_times(mut self, f: impl Fn(f32) -> f32) -> UnevenCore<T> {
let mut timed_samples = self
.times
.into_iter()
.map(f)
.zip(self.samples)
.collect_vec();
timed_samples.sort_by(|(t1, _), (t2, _)| t1.total_cmp(t2));
timed_samples.dedup_by_key(|(t, _)| *t);
(self.times, self.samples) = timed_samples.into_iter().unzip();
self
}
}
/// The data core of a curve using uneven samples (i.e. keyframes), where each sample time
/// yields some fixed number of values — the [sampling width]. This may serve as storage for
/// curves that yield vectors or iterators, and in some cases, it may be useful for cache locality
/// if the sample type can effectively be encoded as a fixed-length slice of values.
///
/// [sampling width]: ChunkedUnevenCore::width
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct ChunkedUnevenCore<T> {
/// The times, one for each sample.
///
/// # Invariants
/// This must always have a length of at least 2, be sorted, and have no duplicated or
/// non-finite times.
pub times: Vec<f32>,
/// The values that are used in sampling. Each width-worth of these correspond to a single sample.
///
/// # Invariants
/// The length of this vector must always be some fixed integer multiple of that of `times`.
pub values: Vec<T>,
}
/// An error that indicates that a [`ChunkedUnevenCore`] could not be formed.
#[derive(Debug, Error)]
#[error("Could not create a ChunkedUnevenCore")]
pub enum ChunkedUnevenCoreError {
/// The width of a `ChunkedUnevenCore` cannot be zero.
#[error("Chunk width must be at least 1")]
ZeroWidth,
/// At least two sample times are necessary to interpolate in `ChunkedUnevenCore`.
#[error(
"Need at least two unique samples to create a ChunkedUnevenCore, but {samples} were provided"
)]
NotEnoughSamples {
/// The number of samples that were provided.
samples: usize,
},
/// The length of the value buffer is supposed to be the `width` times the number of samples.
#[error("Expected {expected} total values based on width, but {actual} were provided")]
MismatchedLengths {
/// The expected length of the value buffer.
expected: usize,
/// The actual length of the value buffer.
actual: usize,
},
/// Tried to infer the width, but the ratio of lengths wasn't an integer, so no such length exists.
#[error("The length of the list of values ({values_len}) was not divisible by that of the list of times ({times_len})")]
NonDivisibleLengths {
/// The length of the value buffer.
values_len: usize,
/// The length of the time buffer.
times_len: usize,
},
}
#[cfg(feature = "alloc")]
impl<T> ChunkedUnevenCore<T> {
/// Create a new [`ChunkedUnevenCore`]. The given `times` are sorted, filtered to finite times,
/// and deduplicated. See the [type-level documentation] for more information about this type.
///
/// Produces an error in any of the following circumstances:
/// - `width` is zero.
/// - `times` has less than `2` unique valid entries.
/// - `values` has the incorrect length relative to `times`.
///
/// [type-level documentation]: ChunkedUnevenCore
pub fn new(
times: impl IntoIterator<Item = f32>,
values: impl IntoIterator<Item = T>,
width: usize,
) -> Result<Self, ChunkedUnevenCoreError> {
let times = times.into_iter().collect_vec();
let values = values.into_iter().collect_vec();
if width == 0 {
return Err(ChunkedUnevenCoreError::ZeroWidth);
}
let times = filter_sort_dedup_times(times);
if times.len() < 2 {
return Err(ChunkedUnevenCoreError::NotEnoughSamples {
samples: times.len(),
});
}
if values.len() != times.len() * width {
return Err(ChunkedUnevenCoreError::MismatchedLengths {
expected: times.len() * width,
actual: values.len(),
});
}
Ok(Self { times, values })
}
/// Create a new [`ChunkedUnevenCore`], inferring the width from the sizes of the inputs.
/// The given `times` are sorted, filtered to finite times, and deduplicated. See the
/// [type-level documentation] for more information about this type. Prefer using [`new`]
/// if possible, since that constructor has richer error checking.
///
/// Produces an error in any of the following circumstances:
/// - `values` has length zero.
/// - `times` has less than `2` unique valid entries.
/// - The length of `values` is not divisible by that of `times` (once sorted, filtered,
/// and deduplicated).
///
/// The [width] is implicitly taken to be the length of `values` divided by that of `times`
/// (once sorted, filtered, and deduplicated).
///
/// [type-level documentation]: ChunkedUnevenCore
/// [`new`]: ChunkedUnevenCore::new
/// [width]: ChunkedUnevenCore::width
pub fn new_width_inferred(
times: impl IntoIterator<Item = f32>,
values: impl IntoIterator<Item = T>,
) -> Result<Self, ChunkedUnevenCoreError> {
let times = times.into_iter().collect_vec();
let values = values.into_iter().collect_vec();
let times = filter_sort_dedup_times(times);
if times.len() < 2 {
return Err(ChunkedUnevenCoreError::NotEnoughSamples {
samples: times.len(),
});
}
if values.len() % times.len() != 0 {
return Err(ChunkedUnevenCoreError::NonDivisibleLengths {
values_len: values.len(),
times_len: times.len(),
});
}
if values.is_empty() {
return Err(ChunkedUnevenCoreError::ZeroWidth);
}
Ok(Self { times, values })
}
/// The domain of the curve derived from this core.
///
/// # Panics
/// This may panic if this type's invariants aren't met.
#[inline]
pub fn domain(&self) -> Interval {
let start = self.times.first().unwrap();
let end = self.times.last().unwrap();
Interval::new(*start, *end).unwrap()
}
/// The sample width: the number of values that are contained in each sample.
#[inline]
pub fn width(&self) -> usize {
self.values.len() / self.times.len()
}
/// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
/// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
/// be used to interpolate between the two contained values with the given parameter. The other
/// variants give additional context about where the value is relative to the family of samples.
///
/// [`Between`]: `InterpolationDatum::Between`
#[inline]
pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&[T]> {
uneven_interp(&self.times, t).map(|idx| self.time_index_to_slice(idx))
}
/// Like [`sample_interp`], but the returned values include the sample times. This can be
/// useful when sample interpolation is not scale-invariant.
///
/// [`sample_interp`]: ChunkedUnevenCore::sample_interp
pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &[T])> {
uneven_interp(&self.times, t).map(|idx| (self.times[idx], self.time_index_to_slice(idx)))
}
/// Given an index in [times], returns the slice of [values] that correspond to the sample at
/// that time.
///
/// [times]: ChunkedUnevenCore::times
/// [values]: ChunkedUnevenCore::values
#[inline]
fn time_index_to_slice(&self, idx: usize) -> &[T] {
let width = self.width();
let lower_idx = width * idx;
let upper_idx = lower_idx + width;
&self.values[lower_idx..upper_idx]
}
}
/// Sort the given times, deduplicate them, and filter them to only finite times.
#[cfg(feature = "alloc")]
fn filter_sort_dedup_times(times: impl IntoIterator<Item = f32>) -> Vec<f32> {
// Filter before sorting/deduplication so that NAN doesn't interfere with them.
let mut times = times.into_iter().filter(|t| t.is_finite()).collect_vec();
times.sort_by(f32::total_cmp);
times.dedup();
times
}
/// Given a list of `times` and a target value, get the interpolation relationship for the
/// target value in terms of the indices of the starting list. In a sense, this encapsulates the
/// heart of uneven/keyframe sampling.
///
/// `times` is assumed to be sorted, deduplicated, and consisting only of finite values. It is also
/// assumed to contain at least two values.
///
/// # Panics
/// This function will panic if `times` contains NAN.
pub fn uneven_interp(times: &[f32], t: f32) -> InterpolationDatum<usize> {
match times.binary_search_by(|pt| pt.partial_cmp(&t).unwrap()) {
Ok(index) => InterpolationDatum::Exact(index),
Err(index) => {
if index == 0 {
// This is before the first keyframe.
InterpolationDatum::LeftTail(0)
} else if index >= times.len() {
// This is after the last keyframe.
InterpolationDatum::RightTail(times.len() - 1)
} else {
// This is actually in the middle somewhere.
let t_lower = times[index - 1];
let t_upper = times[index];
let s = (t - t_lower) / (t_upper - t_lower);
InterpolationDatum::Between(index - 1, index, s)
}
}
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::{ChunkedUnevenCore, EvenCore, UnevenCore};
use crate::curve::{cores::InterpolationDatum, interval};
use alloc::vec;
use approx::{assert_abs_diff_eq, AbsDiffEq};
fn approx_between<T>(datum: InterpolationDatum<T>, start: T, end: T, p: f32) -> bool
where
T: PartialEq,
{
if let InterpolationDatum::Between(m_start, m_end, m_p) = datum {
m_start == start && m_end == end && m_p.abs_diff_eq(&p, 1e-6)
} else {
false
}
}
fn is_left_tail<T>(datum: InterpolationDatum<T>) -> bool {
matches!(datum, InterpolationDatum::LeftTail(_))
}
fn is_right_tail<T>(datum: InterpolationDatum<T>) -> bool {
matches!(datum, InterpolationDatum::RightTail(_))
}
fn is_exact<T>(datum: InterpolationDatum<T>, target: T) -> bool
where
T: PartialEq,
{
if let InterpolationDatum::Exact(v) = datum {
v == target
} else {
false
}
}
#[test]
fn even_sample_interp() {
let even_core = EvenCore::<f32>::new(
interval(0.0, 1.0).unwrap(),
// 11 entries -> 10 segments
vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
)
.expect("Failed to construct test core");
let datum = even_core.sample_interp(-1.0);
assert!(is_left_tail(datum));
let datum = even_core.sample_interp(0.0);
assert!(is_left_tail(datum));
let datum = even_core.sample_interp(1.0);
assert!(is_right_tail(datum));
let datum = even_core.sample_interp(2.0);
assert!(is_right_tail(datum));
let datum = even_core.sample_interp(0.05);
let InterpolationDatum::Between(0.0, 1.0, p) = datum else {
panic!("Sample did not lie in the correct subinterval")
};
assert_abs_diff_eq!(p, 0.5);
let datum = even_core.sample_interp(0.05);
assert!(approx_between(datum, &0.0, &1.0, 0.5));
let datum = even_core.sample_interp(0.33);
assert!(approx_between(datum, &3.0, &4.0, 0.3));
let datum = even_core.sample_interp(0.78);
assert!(approx_between(datum, &7.0, &8.0, 0.8));
let datum = even_core.sample_interp(0.5);
assert!(approx_between(datum, &4.0, &5.0, 1.0) || approx_between(datum, &5.0, &6.0, 0.0));
let datum = even_core.sample_interp(0.7);
assert!(approx_between(datum, &6.0, &7.0, 1.0) || approx_between(datum, &7.0, &8.0, 0.0));
}
#[test]
fn uneven_sample_interp() {
let uneven_core = UnevenCore::<f32>::new(vec![
(0.0, 0.0),
(1.0, 3.0),
(2.0, 9.0),
(4.0, 10.0),
(8.0, -5.0),
])
.expect("Failed to construct test core");
let datum = uneven_core.sample_interp(-1.0);
assert!(is_left_tail(datum));
let datum = uneven_core.sample_interp(0.0);
assert!(is_exact(datum, &0.0));
let datum = uneven_core.sample_interp(8.0);
assert!(is_exact(datum, &(-5.0)));
let datum = uneven_core.sample_interp(9.0);
assert!(is_right_tail(datum));
let datum = uneven_core.sample_interp(0.5);
assert!(approx_between(datum, &0.0, &3.0, 0.5));
let datum = uneven_core.sample_interp(2.5);
assert!(approx_between(datum, &9.0, &10.0, 0.25));
let datum = uneven_core.sample_interp(7.0);
assert!(approx_between(datum, &10.0, &(-5.0), 0.75));
let datum = uneven_core.sample_interp(2.0);
assert!(is_exact(datum, &9.0));
let datum = uneven_core.sample_interp(4.0);
assert!(is_exact(datum, &10.0));
}
#[test]
fn chunked_uneven_sample_interp() {
let core =
ChunkedUnevenCore::new(vec![0.0, 2.0, 8.0], vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0], 2)
.expect("Failed to construct test core");
let datum = core.sample_interp(-1.0);
assert!(is_left_tail(datum));
let datum = core.sample_interp(0.0);
assert!(is_exact(datum, &[0.0, 1.0]));
let datum = core.sample_interp(8.0);
assert!(is_exact(datum, &[4.0, 5.0]));
let datum = core.sample_interp(10.0);
assert!(is_right_tail(datum));
let datum = core.sample_interp(1.0);
assert!(approx_between(datum, &[0.0, 1.0], &[2.0, 3.0], 0.5));
let datum = core.sample_interp(3.0);
assert!(approx_between(datum, &[2.0, 3.0], &[4.0, 5.0], 1.0 / 6.0));
let datum = core.sample_interp(2.0);
assert!(is_exact(datum, &[2.0, 3.0]));
}
}

View File

@@ -0,0 +1,648 @@
//! Implementations of derivatives on curve adaptors. These allow
//! compositionality for derivatives.
use super::{SampleDerivative, SampleTwoDerivatives};
use crate::common_traits::{HasTangent, Sum, VectorSpace, WithDerivative, WithTwoDerivatives};
use crate::curve::{
adaptors::{
ChainCurve, ConstantCurve, ContinuationCurve, CurveReparamCurve, ForeverCurve, GraphCurve,
LinearReparamCurve, PingPongCurve, RepeatCurve, ReverseCurve, ZipCurve,
},
Curve,
};
// -- ConstantCurve
impl<T> SampleDerivative<T> for ConstantCurve<T>
where
T: HasTangent + Clone,
{
fn sample_with_derivative_unchecked(&self, _t: f32) -> WithDerivative<T> {
WithDerivative {
value: self.value.clone(),
derivative: VectorSpace::ZERO,
}
}
}
impl<T> SampleTwoDerivatives<T> for ConstantCurve<T>
where
T: HasTangent + Clone,
{
fn sample_with_two_derivatives_unchecked(&self, _t: f32) -> WithTwoDerivatives<T> {
WithTwoDerivatives {
value: self.value.clone(),
derivative: VectorSpace::ZERO,
second_derivative: VectorSpace::ZERO,
}
}
}
// -- ChainCurve
impl<T, C, D> SampleDerivative<T> for ChainCurve<T, C, D>
where
T: HasTangent,
C: SampleDerivative<T>,
D: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
if t > self.first.domain().end() {
self.second.sample_with_derivative_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
)
} else {
self.first.sample_with_derivative_unchecked(t)
}
}
}
impl<T, C, D> SampleTwoDerivatives<T> for ChainCurve<T, C, D>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
D: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
if t > self.first.domain().end() {
self.second.sample_with_two_derivatives_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
)
} else {
self.first.sample_with_two_derivatives_unchecked(t)
}
}
}
// -- ContinuationCurve
impl<T, C, D> SampleDerivative<T> for ContinuationCurve<T, C, D>
where
T: VectorSpace,
C: SampleDerivative<T>,
D: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
if t > self.first.domain().end() {
let mut output = self.second.sample_with_derivative_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
);
output.value = output.value + self.offset;
output
} else {
self.first.sample_with_derivative_unchecked(t)
}
}
}
impl<T, C, D> SampleTwoDerivatives<T> for ContinuationCurve<T, C, D>
where
T: VectorSpace,
C: SampleTwoDerivatives<T>,
D: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
if t > self.first.domain().end() {
let mut output = self.second.sample_with_two_derivatives_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
);
output.value = output.value + self.offset;
output
} else {
self.first.sample_with_two_derivatives_unchecked(t)
}
}
}
// -- RepeatCurve
impl<T, C> SampleDerivative<T> for RepeatCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
let t = self.base_curve_sample_time(t);
self.curve.sample_with_derivative_unchecked(t)
}
}
impl<T, C> SampleTwoDerivatives<T> for RepeatCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
let t = self.base_curve_sample_time(t);
self.curve.sample_with_two_derivatives_unchecked(t)
}
}
// -- ForeverCurve
impl<T, C> SampleDerivative<T> for ForeverCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
let t = self.base_curve_sample_time(t);
self.curve.sample_with_derivative_unchecked(t)
}
}
impl<T, C> SampleTwoDerivatives<T> for ForeverCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
let t = self.base_curve_sample_time(t);
self.curve.sample_with_two_derivatives_unchecked(t)
}
}
// -- PingPongCurve
impl<T, C> SampleDerivative<T> for PingPongCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
if t > self.curve.domain().end() {
let t = self.curve.domain().end() * 2.0 - t;
// The derivative of the preceding expression is -1, so the chain
// rule implies the derivative should be negated.
let mut output = self.curve.sample_with_derivative_unchecked(t);
output.derivative = -output.derivative;
output
} else {
self.curve.sample_with_derivative_unchecked(t)
}
}
}
impl<T, C> SampleTwoDerivatives<T> for PingPongCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
if t > self.curve.domain().end() {
let t = self.curve.domain().end() * 2.0 - t;
// See the implementation on `ReverseCurve` for an explanation of
// why this is correct.
let mut output = self.curve.sample_with_two_derivatives_unchecked(t);
output.derivative = -output.derivative;
output
} else {
self.curve.sample_with_two_derivatives_unchecked(t)
}
}
}
// -- ZipCurve
impl<S, T, C, D> SampleDerivative<(S, T)> for ZipCurve<S, T, C, D>
where
S: HasTangent,
T: HasTangent,
C: SampleDerivative<S>,
D: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<(S, T)> {
let first_output = self.first.sample_with_derivative_unchecked(t);
let second_output = self.second.sample_with_derivative_unchecked(t);
WithDerivative {
value: (first_output.value, second_output.value),
derivative: Sum(first_output.derivative, second_output.derivative),
}
}
}
impl<S, T, C, D> SampleTwoDerivatives<(S, T)> for ZipCurve<S, T, C, D>
where
S: HasTangent,
T: HasTangent,
C: SampleTwoDerivatives<S>,
D: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<(S, T)> {
let first_output = self.first.sample_with_two_derivatives_unchecked(t);
let second_output = self.second.sample_with_two_derivatives_unchecked(t);
WithTwoDerivatives {
value: (first_output.value, second_output.value),
derivative: Sum(first_output.derivative, second_output.derivative),
second_derivative: Sum(
first_output.second_derivative,
second_output.second_derivative,
),
}
}
}
// -- GraphCurve
impl<T, C> SampleDerivative<(f32, T)> for GraphCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<(f32, T)> {
let output = self.base.sample_with_derivative_unchecked(t);
WithDerivative {
value: (t, output.value),
derivative: Sum(1.0, output.derivative),
}
}
}
impl<T, C> SampleTwoDerivatives<(f32, T)> for GraphCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<(f32, T)> {
let output = self.base.sample_with_two_derivatives_unchecked(t);
WithTwoDerivatives {
value: (t, output.value),
derivative: Sum(1.0, output.derivative),
second_derivative: Sum(0.0, output.second_derivative),
}
}
}
// -- ReverseCurve
impl<T, C> SampleDerivative<T> for ReverseCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
// This gets almost the correct value, but we haven't accounted for the
// reversal of orientation yet.
let mut output = self
.curve
.sample_with_derivative_unchecked(self.domain().end() - (t - self.domain().start()));
output.derivative = -output.derivative;
output
}
}
impl<T, C> SampleTwoDerivatives<T> for ReverseCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
// This gets almost the correct value, but we haven't accounted for the
// reversal of orientation yet.
let mut output = self.curve.sample_with_two_derivatives_unchecked(
self.domain().end() - (t - self.domain().start()),
);
output.derivative = -output.derivative;
// (Note that the reparametrization that reverses the curve satisfies
// g'(t)^2 = 1 and g''(t) = 0, so the second derivative is already
// correct.)
output
}
}
// -- CurveReparamCurve
impl<T, C, D> SampleDerivative<T> for CurveReparamCurve<T, C, D>
where
T: HasTangent,
C: SampleDerivative<T>,
D: SampleDerivative<f32>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t)
// is `self.reparam_curve`.
// Start by computing g(t) and g'(t).
let reparam_output = self.reparam_curve.sample_with_derivative_unchecked(t);
// Compute:
// - value: f(g(t))
// - derivative: f'(g(t))
let mut output = self
.base
.sample_with_derivative_unchecked(reparam_output.value);
// Do the multiplication part of the chain rule.
output.derivative = output.derivative * reparam_output.derivative;
// value: f(g(t)), derivative: f'(g(t)) g'(t)
output
}
}
impl<T, C, D> SampleTwoDerivatives<T> for CurveReparamCurve<T, C, D>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
D: SampleTwoDerivatives<f32>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t)
// is `self.reparam_curve`.
// Start by computing g(t), g'(t), g''(t).
let reparam_output = self.reparam_curve.sample_with_two_derivatives_unchecked(t);
// Compute:
// - value: f(g(t))
// - derivative: f'(g(t))
// - second derivative: f''(g(t))
let mut output = self
.base
.sample_with_two_derivatives_unchecked(reparam_output.value);
// Set the second derivative according to the chain and product rules
// r''(t) = f''(g(t)) g'(t)^2 + f'(g(t)) g''(t)
output.second_derivative = (output.second_derivative
* (reparam_output.derivative * reparam_output.derivative))
+ (output.derivative * reparam_output.second_derivative);
// Set the first derivative according to the chain rule
// r'(t) = f'(g(t)) g'(t)
output.derivative = output.derivative * reparam_output.derivative;
output
}
}
// -- LinearReparamCurve
impl<T, C> SampleDerivative<T> for LinearReparamCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t) is
// the linear map bijecting `self.new_domain` onto `self.base.domain()`.
// The invariants imply this unwrap always succeeds.
let g = self.new_domain.linear_map_to(self.base.domain()).unwrap();
// Compute g'(t) from the domain lengths.
let g_derivative = self.base.domain().length() / self.new_domain.length();
// Compute:
// - value: f(g(t))
// - derivative: f'(g(t))
let mut output = self.base.sample_with_derivative_unchecked(g(t));
// Adjust the derivative according to the chain rule.
output.derivative = output.derivative * g_derivative;
output
}
}
impl<T, C> SampleTwoDerivatives<T> for LinearReparamCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t) is
// the linear map bijecting `self.new_domain` onto `self.base.domain()`.
// The invariants imply this unwrap always succeeds.
let g = self.new_domain.linear_map_to(self.base.domain()).unwrap();
// Compute g'(t) from the domain lengths.
let g_derivative = self.base.domain().length() / self.new_domain.length();
// Compute:
// - value: f(g(t))
// - derivative: f'(g(t))
// - second derivative: f''(g(t))
let mut output = self.base.sample_with_two_derivatives_unchecked(g(t));
// Set the second derivative according to the chain and product rules
// r''(t) = f''(g(t)) g'(t)^2 (g''(t) = 0)
output.second_derivative = output.second_derivative * (g_derivative * g_derivative);
// Set the first derivative according to the chain rule
// r'(t) = f'(g(t)) g'(t)
output.derivative = output.derivative * g_derivative;
output
}
}
#[cfg(test)]
mod tests {
use approx::assert_abs_diff_eq;
use super::*;
use crate::cubic_splines::{CubicBezier, CubicCardinalSpline, CubicCurve, CubicGenerator};
use crate::curve::{Curve, CurveExt, Interval};
use crate::{vec2, Vec2, Vec3};
fn test_curve() -> CubicCurve<Vec2> {
let control_pts = [[
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(0.0, 1.0),
vec2(1.0, 1.0),
]];
CubicBezier::new(control_pts).to_curve().unwrap()
}
fn other_test_curve() -> CubicCurve<Vec2> {
let control_pts = [
vec2(1.0, 1.0),
vec2(2.0, 1.0),
vec2(2.0, 0.0),
vec2(1.0, 0.0),
];
CubicCardinalSpline::new(0.5, control_pts)
.to_curve()
.unwrap()
}
fn reparam_curve() -> CubicCurve<f32> {
let control_pts = [[0.0, 0.25, 0.75, 1.0]];
CubicBezier::new(control_pts).to_curve().unwrap()
}
#[test]
fn constant_curve() {
let curve = ConstantCurve::new(Interval::UNIT, Vec3::new(0.2, 1.5, -2.6));
let jet = curve.sample_with_derivative(0.5).unwrap();
assert_abs_diff_eq!(jet.derivative, Vec3::ZERO);
}
#[test]
fn chain_curve() {
let curve1 = test_curve();
let curve2 = other_test_curve();
let curve = curve1.by_ref().chain(&curve2).unwrap();
let jet = curve.sample_with_derivative(0.65).unwrap();
let true_jet = curve1.sample_with_derivative(0.65).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
let jet = curve.sample_with_derivative(1.1).unwrap();
let true_jet = curve2.sample_with_derivative(0.1).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
}
#[test]
fn continuation_curve() {
let curve1 = test_curve();
let curve2 = other_test_curve();
let curve = curve1.by_ref().chain_continue(&curve2).unwrap();
let jet = curve.sample_with_derivative(0.99).unwrap();
let true_jet = curve1.sample_with_derivative(0.99).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
let jet = curve.sample_with_derivative(1.3).unwrap();
let true_jet = curve2.sample_with_derivative(0.3).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
}
#[test]
fn repeat_curve() {
let curve1 = test_curve();
let curve = curve1.by_ref().repeat(3).unwrap();
let jet = curve.sample_with_derivative(0.73).unwrap();
let true_jet = curve1.sample_with_derivative(0.73).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
let jet = curve.sample_with_derivative(3.5).unwrap();
let true_jet = curve1.sample_with_derivative(0.5).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
}
#[test]
fn forever_curve() {
let curve1 = test_curve();
let curve = curve1.by_ref().forever().unwrap();
let jet = curve.sample_with_derivative(0.12).unwrap();
let true_jet = curve1.sample_with_derivative(0.12).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
let jet = curve.sample_with_derivative(500.5).unwrap();
let true_jet = curve1.sample_with_derivative(0.5).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
}
#[test]
fn ping_pong_curve() {
let curve1 = test_curve();
let curve = curve1.by_ref().ping_pong().unwrap();
let jet = curve.sample_with_derivative(0.99).unwrap();
let comparison_jet = curve1.sample_with_derivative(0.99).unwrap();
assert_abs_diff_eq!(jet.value, comparison_jet.value);
assert_abs_diff_eq!(jet.derivative, comparison_jet.derivative);
let jet = curve.sample_with_derivative(1.3).unwrap();
let comparison_jet = curve1.sample_with_derivative(0.7).unwrap();
assert_abs_diff_eq!(jet.value, comparison_jet.value);
assert_abs_diff_eq!(jet.derivative, -comparison_jet.derivative, epsilon = 1.0e-5);
}
#[test]
fn zip_curve() {
let curve1 = test_curve();
let curve2 = other_test_curve();
let curve = curve1.by_ref().zip(&curve2).unwrap();
let jet = curve.sample_with_derivative(0.7).unwrap();
let comparison_jet1 = curve1.sample_with_derivative(0.7).unwrap();
let comparison_jet2 = curve2.sample_with_derivative(0.7).unwrap();
assert_abs_diff_eq!(jet.value.0, comparison_jet1.value);
assert_abs_diff_eq!(jet.value.1, comparison_jet2.value);
let Sum(derivative1, derivative2) = jet.derivative;
assert_abs_diff_eq!(derivative1, comparison_jet1.derivative);
assert_abs_diff_eq!(derivative2, comparison_jet2.derivative);
}
#[test]
fn graph_curve() {
let curve1 = test_curve();
let curve = curve1.by_ref().graph();
let jet = curve.sample_with_derivative(0.25).unwrap();
let comparison_jet = curve1.sample_with_derivative(0.25).unwrap();
assert_abs_diff_eq!(jet.value.0, 0.25);
assert_abs_diff_eq!(jet.value.1, comparison_jet.value);
let Sum(one, derivative) = jet.derivative;
assert_abs_diff_eq!(one, 1.0);
assert_abs_diff_eq!(derivative, comparison_jet.derivative);
}
#[test]
fn reverse_curve() {
let curve1 = test_curve();
let curve = curve1.by_ref().reverse().unwrap();
let jet = curve.sample_with_derivative(0.23).unwrap();
let comparison_jet = curve1.sample_with_derivative(0.77).unwrap();
assert_abs_diff_eq!(jet.value, comparison_jet.value);
assert_abs_diff_eq!(jet.derivative, -comparison_jet.derivative);
}
#[test]
fn curve_reparam_curve() {
let reparam_curve = reparam_curve();
let reparam_jet = reparam_curve.sample_with_derivative(0.6).unwrap();
let curve1 = test_curve();
let curve = curve1.by_ref().reparametrize_by_curve(&reparam_curve);
let jet = curve.sample_with_derivative(0.6).unwrap();
let base_jet = curve1
.sample_with_derivative(reparam_curve.sample(0.6).unwrap())
.unwrap();
assert_abs_diff_eq!(jet.value, base_jet.value);
assert_abs_diff_eq!(jet.derivative, base_jet.derivative * reparam_jet.derivative);
}
#[test]
fn linear_reparam_curve() {
let curve1 = test_curve();
let curve = curve1
.by_ref()
.reparametrize_linear(Interval::new(0.0, 0.5).unwrap())
.unwrap();
let jet = curve.sample_with_derivative(0.23).unwrap();
let comparison_jet = curve1.sample_with_derivative(0.46).unwrap();
assert_abs_diff_eq!(jet.value, comparison_jet.value);
assert_abs_diff_eq!(jet.derivative, comparison_jet.derivative * 2.0);
}
}

View File

@@ -0,0 +1,230 @@
//! This module holds traits related to extracting derivatives from curves. In
//! applications, the derivatives of interest are chiefly the first and second;
//! in this module, these are provided by the traits [`CurveWithDerivative`]
//! and [`CurveWithTwoDerivatives`].
//!
//! These take ownership of the curve they are used on by default, so that
//! the resulting output may be used in more durable contexts. For example,
//! `CurveWithDerivative<T>` is not dyn-compatible, but `Curve<WithDerivative<T>>`
//! is, so if such a curve needs to be stored in a dynamic context, calling
//! [`with_derivative`] and then placing the result in a
//! `Box<Curve<WithDerivative<T>>>` is sensible.
//!
//! On the other hand, in more transient contexts, consuming a value merely to
//! sample derivatives is inconvenient, and in these cases, it is recommended
//! to use [`by_ref`] when possible to create a referential curve first, retaining
//! liveness of the original.
//!
//! This module also holds the [`SampleDerivative`] and [`SampleTwoDerivatives`]
//! traits, which can be used to easily implement `CurveWithDerivative` and its
//! counterpart.
//!
//! [`with_derivative`]: CurveWithDerivative::with_derivative
//! [`by_ref`]: crate::curve::CurveExt::by_ref
pub mod adaptor_impls;
use crate::{
common_traits::{HasTangent, WithDerivative, WithTwoDerivatives},
curve::{Curve, Interval},
};
use core::ops::Deref;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{FromReflect, Reflect};
/// Trait for curves that have a well-defined notion of derivative, allowing for
/// derivatives to be extracted along with values.
///
/// This is implemented by implementing [`SampleDerivative`].
pub trait CurveWithDerivative<T>: SampleDerivative<T> + Sized
where
T: HasTangent,
{
/// This curve, but with its first derivative included in sampling.
///
/// Notably, the output type is a `Curve<WithDerivative<T>>`.
fn with_derivative(self) -> SampleDerivativeWrapper<Self>;
}
/// Trait for curves that have a well-defined notion of second derivative,
/// allowing for two derivatives to be extracted along with values.
///
/// This is implemented by implementing [`SampleTwoDerivatives`].
pub trait CurveWithTwoDerivatives<T>: SampleTwoDerivatives<T> + Sized
where
T: HasTangent,
{
/// This curve, but with its first two derivatives included in sampling.
///
/// Notably, the output type is a `Curve<WithTwoDerivatives<T>>`.
fn with_two_derivatives(self) -> SampleTwoDerivativesWrapper<Self>;
}
/// A trait for curves that can sample derivatives in addition to values.
///
/// Types that implement this trait automatically implement [`CurveWithDerivative`];
/// the curve produced by [`with_derivative`] uses the sampling defined in the trait
/// implementation.
///
/// [`with_derivative`]: CurveWithDerivative::with_derivative
pub trait SampleDerivative<T>: Curve<T>
where
T: HasTangent,
{
/// Sample this curve at the parameter value `t`, extracting the associated value
/// in addition to its derivative. This is the unchecked version of sampling, which
/// should only be used if the sample time `t` is already known to lie within the
/// curve's domain.
///
/// See [`Curve::sample_unchecked`] for more information.
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T>;
/// Sample this curve's value and derivative at the parameter value `t`, returning
/// `None` if the point is outside of the curve's domain.
fn sample_with_derivative(&self, t: f32) -> Option<WithDerivative<T>> {
match self.domain().contains(t) {
true => Some(self.sample_with_derivative_unchecked(t)),
false => None,
}
}
/// Sample this curve's value and derivative at the parameter value `t`, clamping `t`
/// to lie inside the domain of the curve.
fn sample_with_derivative_clamped(&self, t: f32) -> WithDerivative<T> {
let t = self.domain().clamp(t);
self.sample_with_derivative_unchecked(t)
}
}
impl<T, C, D> SampleDerivative<T> for D
where
T: HasTangent,
C: SampleDerivative<T> + ?Sized,
D: Deref<Target = C>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
<C as SampleDerivative<T>>::sample_with_derivative_unchecked(self, t)
}
}
/// A trait for curves that can sample two derivatives in addition to values.
///
/// Types that implement this trait automatically implement [`CurveWithTwoDerivatives`];
/// the curve produced by [`with_two_derivatives`] uses the sampling defined in the trait
/// implementation.
///
/// [`with_two_derivatives`]: CurveWithTwoDerivatives::with_two_derivatives
pub trait SampleTwoDerivatives<T>: Curve<T>
where
T: HasTangent,
{
/// Sample this curve at the parameter value `t`, extracting the associated value
/// in addition to two derivatives. This is the unchecked version of sampling, which
/// should only be used if the sample time `t` is already known to lie within the
/// curve's domain.
///
/// See [`Curve::sample_unchecked`] for more information.
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T>;
/// Sample this curve's value and two derivatives at the parameter value `t`, returning
/// `None` if the point is outside of the curve's domain.
fn sample_with_two_derivatives(&self, t: f32) -> Option<WithTwoDerivatives<T>> {
match self.domain().contains(t) {
true => Some(self.sample_with_two_derivatives_unchecked(t)),
false => None,
}
}
/// Sample this curve's value and two derivatives at the parameter value `t`, clamping `t`
/// to lie inside the domain of the curve.
fn sample_with_two_derivatives_clamped(&self, t: f32) -> WithTwoDerivatives<T> {
let t = self.domain().clamp(t);
self.sample_with_two_derivatives_unchecked(t)
}
}
/// A wrapper that uses a [`SampleDerivative<T>`] curve to produce a `Curve<WithDerivative<T>>`.
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct SampleDerivativeWrapper<C>(C);
impl<T, C> Curve<WithDerivative<T>> for SampleDerivativeWrapper<C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn domain(&self) -> Interval {
self.0.domain()
}
fn sample_unchecked(&self, t: f32) -> WithDerivative<T> {
self.0.sample_with_derivative_unchecked(t)
}
fn sample(&self, t: f32) -> Option<WithDerivative<T>> {
self.0.sample_with_derivative(t)
}
fn sample_clamped(&self, t: f32) -> WithDerivative<T> {
self.0.sample_with_derivative_clamped(t)
}
}
/// A wrapper that uses a [`SampleTwoDerivatives<T>`] curve to produce a
/// `Curve<WithTwoDerivatives<T>>`.
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct SampleTwoDerivativesWrapper<C>(C);
impl<T, C> Curve<WithTwoDerivatives<T>> for SampleTwoDerivativesWrapper<C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn domain(&self) -> Interval {
self.0.domain()
}
fn sample_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
self.0.sample_with_two_derivatives_unchecked(t)
}
fn sample(&self, t: f32) -> Option<WithTwoDerivatives<T>> {
self.0.sample_with_two_derivatives(t)
}
fn sample_clamped(&self, t: f32) -> WithTwoDerivatives<T> {
self.0.sample_with_two_derivatives_clamped(t)
}
}
impl<T, C> CurveWithDerivative<T> for C
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn with_derivative(self) -> SampleDerivativeWrapper<Self> {
SampleDerivativeWrapper(self)
}
}
impl<T, C> CurveWithTwoDerivatives<T> for C
where
T: HasTangent,
C: SampleTwoDerivatives<T> + CurveWithDerivative<T>,
{
fn with_two_derivatives(self) -> SampleTwoDerivativesWrapper<Self> {
SampleTwoDerivativesWrapper(self)
}
}

1202
vendor/bevy_math/src/curve/easing.rs vendored Normal file

File diff suppressed because it is too large Load Diff

382
vendor/bevy_math/src/curve/interval.rs vendored Normal file
View File

@@ -0,0 +1,382 @@
//! The [`Interval`] type for nonempty intervals used by the [`Curve`](super::Curve) trait.
use core::{
cmp::{max_by, min_by},
ops::RangeInclusive,
};
use itertools::Either;
use thiserror::Error;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A nonempty closed interval, possibly unbounded in either direction.
///
/// In other words, the interval may stretch all the way to positive or negative infinity, but it
/// will always have some nonempty interior.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Interval {
start: f32,
end: f32,
}
/// An error that indicates that an operation would have returned an invalid [`Interval`].
#[derive(Debug, Error)]
#[error("The resulting interval would be invalid (empty or with a NaN endpoint)")]
pub struct InvalidIntervalError;
/// An error indicating that spaced points could not be extracted from an unbounded interval.
#[derive(Debug, Error)]
#[error("Cannot extract spaced points from an unbounded interval")]
pub struct SpacedPointsError;
/// An error indicating that a linear map between intervals could not be constructed because of
/// unboundedness.
#[derive(Debug, Error)]
#[error("Could not construct linear function to map between intervals")]
pub(super) enum LinearMapError {
/// The source interval being mapped out of was unbounded.
#[error("The source interval is unbounded")]
SourceUnbounded,
/// The target interval being mapped into was unbounded.
#[error("The target interval is unbounded")]
TargetUnbounded,
}
impl Interval {
/// Create a new [`Interval`] with the specified `start` and `end`. The interval can be unbounded
/// but cannot be empty (so `start` must be less than `end`) and neither endpoint can be NaN; invalid
/// parameters will result in an error.
#[inline]
pub fn new(start: f32, end: f32) -> Result<Self, InvalidIntervalError> {
if start >= end || start.is_nan() || end.is_nan() {
Err(InvalidIntervalError)
} else {
Ok(Self { start, end })
}
}
/// An interval of length 1.0, starting at 0.0 and ending at 1.0.
pub const UNIT: Self = Self {
start: 0.0,
end: 1.0,
};
/// An interval which stretches across the entire real line from negative infinity to infinity.
pub const EVERYWHERE: Self = Self {
start: f32::NEG_INFINITY,
end: f32::INFINITY,
};
/// Get the start of this interval.
#[inline]
pub const fn start(self) -> f32 {
self.start
}
/// Get the end of this interval.
#[inline]
pub const fn end(self) -> f32 {
self.end
}
/// Create an [`Interval`] by intersecting this interval with another. Returns an error if the
/// intersection would be empty (hence an invalid interval).
pub fn intersect(self, other: Interval) -> Result<Interval, InvalidIntervalError> {
let lower = max_by(self.start, other.start, f32::total_cmp);
let upper = min_by(self.end, other.end, f32::total_cmp);
Self::new(lower, upper)
}
/// Get the length of this interval. Note that the result may be infinite (`f32::INFINITY`).
#[inline]
pub fn length(self) -> f32 {
self.end - self.start
}
/// Returns `true` if this interval is bounded — that is, if both its start and end are finite.
///
/// Equivalently, an interval is bounded if its length is finite.
#[inline]
pub fn is_bounded(self) -> bool {
self.length().is_finite()
}
/// Returns `true` if this interval has a finite start.
#[inline]
pub fn has_finite_start(self) -> bool {
self.start.is_finite()
}
/// Returns `true` if this interval has a finite end.
#[inline]
pub fn has_finite_end(self) -> bool {
self.end.is_finite()
}
/// Returns `true` if `item` is contained in this interval.
#[inline]
pub fn contains(self, item: f32) -> bool {
(self.start..=self.end).contains(&item)
}
/// Returns `true` if the other interval is contained in this interval.
///
/// This is non-strict: each interval will contain itself.
#[inline]
pub fn contains_interval(self, other: Self) -> bool {
self.start <= other.start && self.end >= other.end
}
/// Clamp the given `value` to lie within this interval.
#[inline]
pub fn clamp(self, value: f32) -> f32 {
value.clamp(self.start, self.end)
}
/// Get an iterator over equally-spaced points from this interval in increasing order.
/// If `points` is 1, the start of this interval is returned. If `points` is 0, an empty
/// iterator is returned. An error is returned if the interval is unbounded.
#[inline]
pub fn spaced_points(
self,
points: usize,
) -> Result<impl Iterator<Item = f32>, SpacedPointsError> {
if !self.is_bounded() {
return Err(SpacedPointsError);
}
if points < 2 {
// If `points` is 1, this is `Some(self.start)` as an iterator, and if `points` is 0,
// then this is `None` as an iterator. This is written this way to avoid having to
// introduce a ternary disjunction of iterators.
let iter = (points == 1).then_some(self.start).into_iter();
return Ok(Either::Left(iter));
}
let step = self.length() / (points - 1) as f32;
let iter = (0..points).map(move |x| self.start + x as f32 * step);
Ok(Either::Right(iter))
}
/// Get the linear function which maps this interval onto the `other` one. Returns an error if either
/// interval is unbounded.
#[inline]
pub(super) fn linear_map_to(self, other: Self) -> Result<impl Fn(f32) -> f32, LinearMapError> {
if !self.is_bounded() {
return Err(LinearMapError::SourceUnbounded);
}
if !other.is_bounded() {
return Err(LinearMapError::TargetUnbounded);
}
let scale = other.length() / self.length();
Ok(move |x| (x - self.start) * scale + other.start)
}
}
impl TryFrom<RangeInclusive<f32>> for Interval {
type Error = InvalidIntervalError;
fn try_from(range: RangeInclusive<f32>) -> Result<Self, Self::Error> {
Interval::new(*range.start(), *range.end())
}
}
/// Create an [`Interval`] with a given `start` and `end`. Alias of [`Interval::new`].
#[inline]
pub fn interval(start: f32, end: f32) -> Result<Interval, InvalidIntervalError> {
Interval::new(start, end)
}
#[cfg(test)]
mod tests {
use crate::ops;
use super::*;
use alloc::vec::Vec;
use approx::{assert_abs_diff_eq, AbsDiffEq};
#[test]
fn make_intervals() {
let ivl = Interval::new(2.0, -1.0);
assert!(ivl.is_err());
let ivl = Interval::new(-0.0, 0.0);
assert!(ivl.is_err());
let ivl = Interval::new(f32::NEG_INFINITY, 15.5);
assert!(ivl.is_ok());
let ivl = Interval::new(-2.0, f32::INFINITY);
assert!(ivl.is_ok());
let ivl = Interval::new(f32::NEG_INFINITY, f32::INFINITY);
assert!(ivl.is_ok());
let ivl = Interval::new(f32::INFINITY, f32::NEG_INFINITY);
assert!(ivl.is_err());
let ivl = Interval::new(-1.0, f32::NAN);
assert!(ivl.is_err());
let ivl = Interval::new(f32::NAN, -42.0);
assert!(ivl.is_err());
let ivl = Interval::new(f32::NAN, f32::NAN);
assert!(ivl.is_err());
let ivl = Interval::new(0.0, 1.0);
assert!(ivl.is_ok());
}
#[test]
fn lengths() {
let ivl = interval(-5.0, 10.0).unwrap();
assert!(ops::abs(ivl.length() - 15.0) <= f32::EPSILON);
let ivl = interval(5.0, 100.0).unwrap();
assert!(ops::abs(ivl.length() - 95.0) <= f32::EPSILON);
let ivl = interval(0.0, f32::INFINITY).unwrap();
assert_eq!(ivl.length(), f32::INFINITY);
let ivl = interval(f32::NEG_INFINITY, 0.0).unwrap();
assert_eq!(ivl.length(), f32::INFINITY);
let ivl = Interval::EVERYWHERE;
assert_eq!(ivl.length(), f32::INFINITY);
}
#[test]
fn intersections() {
let ivl1 = interval(-1.0, 1.0).unwrap();
let ivl2 = interval(0.0, 2.0).unwrap();
let ivl3 = interval(-3.0, 0.0).unwrap();
let ivl4 = interval(0.0, f32::INFINITY).unwrap();
let ivl5 = interval(f32::NEG_INFINITY, 0.0).unwrap();
let ivl6 = Interval::EVERYWHERE;
assert!(ivl1.intersect(ivl2).is_ok_and(|ivl| ivl == Interval::UNIT));
assert!(ivl1
.intersect(ivl3)
.is_ok_and(|ivl| ivl == interval(-1.0, 0.0).unwrap()));
assert!(ivl2.intersect(ivl3).is_err());
assert!(ivl1.intersect(ivl4).is_ok_and(|ivl| ivl == Interval::UNIT));
assert!(ivl1
.intersect(ivl5)
.is_ok_and(|ivl| ivl == interval(-1.0, 0.0).unwrap()));
assert!(ivl4.intersect(ivl5).is_err());
assert_eq!(ivl1.intersect(ivl6).unwrap(), ivl1);
assert_eq!(ivl4.intersect(ivl6).unwrap(), ivl4);
assert_eq!(ivl5.intersect(ivl6).unwrap(), ivl5);
}
#[test]
fn containment() {
let ivl = Interval::UNIT;
assert!(ivl.contains(0.0));
assert!(ivl.contains(1.0));
assert!(ivl.contains(0.5));
assert!(!ivl.contains(-0.1));
assert!(!ivl.contains(1.1));
assert!(!ivl.contains(f32::NAN));
let ivl = interval(3.0, f32::INFINITY).unwrap();
assert!(ivl.contains(3.0));
assert!(ivl.contains(2.0e5));
assert!(ivl.contains(3.5e6));
assert!(!ivl.contains(2.5));
assert!(!ivl.contains(-1e5));
assert!(!ivl.contains(f32::NAN));
}
#[test]
fn interval_containment() {
let ivl = Interval::UNIT;
assert!(ivl.contains_interval(interval(-0.0, 0.5).unwrap()));
assert!(ivl.contains_interval(interval(0.5, 1.0).unwrap()));
assert!(ivl.contains_interval(interval(0.25, 0.75).unwrap()));
assert!(!ivl.contains_interval(interval(-0.25, 0.5).unwrap()));
assert!(!ivl.contains_interval(interval(0.5, 1.25).unwrap()));
assert!(!ivl.contains_interval(interval(0.25, f32::INFINITY).unwrap()));
assert!(!ivl.contains_interval(interval(f32::NEG_INFINITY, 0.75).unwrap()));
let big_ivl = interval(0.0, f32::INFINITY).unwrap();
assert!(big_ivl.contains_interval(interval(0.0, 5.0).unwrap()));
assert!(big_ivl.contains_interval(interval(0.0, f32::INFINITY).unwrap()));
assert!(big_ivl.contains_interval(interval(1.0, 5.0).unwrap()));
assert!(!big_ivl.contains_interval(interval(-1.0, f32::INFINITY).unwrap()));
assert!(!big_ivl.contains_interval(interval(-2.0, 5.0).unwrap()));
}
#[test]
fn boundedness() {
assert!(!Interval::EVERYWHERE.is_bounded());
assert!(interval(0.0, 3.5e5).unwrap().is_bounded());
assert!(!interval(-2.0, f32::INFINITY).unwrap().is_bounded());
assert!(!interval(f32::NEG_INFINITY, 5.0).unwrap().is_bounded());
}
#[test]
fn linear_maps() {
let ivl1 = interval(-3.0, 5.0).unwrap();
let ivl2 = Interval::UNIT;
let map = ivl1.linear_map_to(ivl2);
assert!(map.is_ok_and(|f| f(-3.0).abs_diff_eq(&0.0, f32::EPSILON)
&& f(5.0).abs_diff_eq(&1.0, f32::EPSILON)
&& f(1.0).abs_diff_eq(&0.5, f32::EPSILON)));
let ivl1 = Interval::UNIT;
let ivl2 = Interval::EVERYWHERE;
assert!(ivl1.linear_map_to(ivl2).is_err());
let ivl1 = interval(f32::NEG_INFINITY, -4.0).unwrap();
let ivl2 = Interval::UNIT;
assert!(ivl1.linear_map_to(ivl2).is_err());
}
#[test]
fn spaced_points() {
let ivl = interval(0.0, 50.0).unwrap();
let points_iter: Vec<f32> = ivl.spaced_points(1).unwrap().collect();
assert_abs_diff_eq!(points_iter[0], 0.0);
assert_eq!(points_iter.len(), 1);
let points_iter: Vec<f32> = ivl.spaced_points(2).unwrap().collect();
assert_abs_diff_eq!(points_iter[0], 0.0);
assert_abs_diff_eq!(points_iter[1], 50.0);
let points_iter = ivl.spaced_points(21).unwrap();
let step = ivl.length() / 20.0;
for (index, point) in points_iter.enumerate() {
let expected = ivl.start() + step * index as f32;
assert_abs_diff_eq!(point, expected);
}
let ivl = interval(-21.0, 79.0).unwrap();
let points_iter = ivl.spaced_points(10000).unwrap();
let step = ivl.length() / 9999.0;
for (index, point) in points_iter.enumerate() {
let expected = ivl.start() + step * index as f32;
assert_abs_diff_eq!(point, expected);
}
let ivl = interval(-1.0, f32::INFINITY).unwrap();
let points_iter = ivl.spaced_points(25);
assert!(points_iter.is_err());
let ivl = interval(f32::NEG_INFINITY, -25.0).unwrap();
let points_iter = ivl.spaced_points(9);
assert!(points_iter.is_err());
}
}

57
vendor/bevy_math/src/curve/iterable.rs vendored Normal file
View File

@@ -0,0 +1,57 @@
//! Iterable curves, which sample in the form of an iterator in order to support `Vec`-like
//! output whose length cannot be known statically.
use super::Interval;
#[cfg(feature = "alloc")]
use {super::ConstantCurve, alloc::vec::Vec};
/// A curve which provides samples in the form of [`Iterator`]s.
///
/// This is an abstraction that provides an interface for curves which look like `Curve<Vec<T>>`
/// but side-stepping issues with allocation on sampling. This happens when the size of an output
/// array cannot be known statically.
pub trait IterableCurve<T> {
/// The interval over which this curve is parametrized.
fn domain(&self) -> Interval;
/// Sample a point on this curve at the parameter value `t`, producing an iterator over values.
/// This is the unchecked version of sampling, which should only be used if the sample time `t`
/// is already known to lie within the curve's domain.
///
/// Values sampled from outside of a curve's domain are generally considered invalid; data which
/// is nonsensical or otherwise useless may be returned in such a circumstance, and extrapolation
/// beyond a curve's domain should not be relied upon.
fn sample_iter_unchecked(&self, t: f32) -> impl Iterator<Item = T>;
/// Sample this curve at a specified time `t`, producing an iterator over sampled values.
/// The parameter `t` is clamped to the domain of the curve.
fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {
let t_clamped = self.domain().clamp(t);
self.sample_iter_unchecked(t_clamped)
}
/// Sample this curve at a specified time `t`, producing an iterator over sampled values.
/// If the parameter `t` does not lie in the curve's domain, `None` is returned.
fn sample_iter(&self, t: f32) -> Option<impl Iterator<Item = T>> {
if self.domain().contains(t) {
Some(self.sample_iter_unchecked(t))
} else {
None
}
}
}
#[cfg(feature = "alloc")]
impl<T> IterableCurve<T> for ConstantCurve<Vec<T>>
where
T: Clone,
{
fn domain(&self) -> Interval {
self.domain
}
fn sample_iter_unchecked(&self, _t: f32) -> impl Iterator<Item = T> {
self.value.iter().cloned()
}
}

1343
vendor/bevy_math/src/curve/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,406 @@
//! Sample-interpolated curves constructed using the [`Curve`] API.
use super::cores::{EvenCore, EvenCoreError, UnevenCore, UnevenCoreError};
use super::{Curve, Interval};
use crate::StableInterpolate;
#[cfg(feature = "bevy_reflect")]
use alloc::format;
use core::any::type_name;
use core::fmt::{self, Debug};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{utility::GenericTypePathCell, Reflect, TypePath};
#[cfg(feature = "bevy_reflect")]
mod paths {
pub(super) const THIS_MODULE: &str = "bevy_math::curve::sample_curves";
pub(super) const THIS_CRATE: &str = "bevy_math";
}
/// A curve that is defined by explicit neighbor interpolation over a set of evenly-spaced samples.
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct SampleCurve<T, I> {
pub(crate) core: EvenCore<T>,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) interpolation: I,
}
impl<T, I> Debug for SampleCurve<T, I>
where
EvenCore<T>: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SampleCurve")
.field("core", &self.core)
.field("interpolation", &type_name::<I>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, I> TypePath for SampleCurve<T, I>
where
T: TypePath,
I: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::SampleCurve<{},{}>",
paths::THIS_MODULE,
T::type_path(),
type_name::<I>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!("SampleCurve<{},{}>", T::type_path(), type_name::<I>())
})
}
fn type_ident() -> Option<&'static str> {
Some("SampleCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, I> Curve<T> for SampleCurve<T, I>
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `EvenCore::sample_with` is implicitly clamped.
self.core.sample_with(t, &self.interpolation)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T, I> SampleCurve<T, I> {
/// Create a new [`SampleCurve`] using the specified `interpolation` to interpolate between
/// the given `samples`. An error is returned if there are not at least 2 samples or if the
/// given `domain` is unbounded.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
pub fn new(
domain: Interval,
samples: impl IntoIterator<Item = T>,
interpolation: I,
) -> Result<Self, EvenCoreError>
where
I: Fn(&T, &T, f32) -> T,
{
Ok(Self {
core: EvenCore::new(domain, samples)?,
interpolation,
})
}
}
/// A curve that is defined by neighbor interpolation over a set of evenly-spaced samples,
/// interpolated automatically using [a particularly well-behaved interpolation].
///
/// [a particularly well-behaved interpolation]: StableInterpolate
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct SampleAutoCurve<T> {
pub(crate) core: EvenCore<T>,
}
impl<T> Curve<T> for SampleAutoCurve<T>
where
T: StableInterpolate,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `EvenCore::sample_with` is implicitly clamped.
self.core
.sample_with(t, <T as StableInterpolate>::interpolate_stable)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T> SampleAutoCurve<T> {
/// Create a new [`SampleCurve`] using type-inferred interpolation to interpolate between
/// the given `samples`. An error is returned if there are not at least 2 samples or if the
/// given `domain` is unbounded.
pub fn new(
domain: Interval,
samples: impl IntoIterator<Item = T>,
) -> Result<Self, EvenCoreError> {
Ok(Self {
core: EvenCore::new(domain, samples)?,
})
}
}
/// A curve that is defined by interpolation over unevenly spaced samples with explicit
/// interpolation.
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct UnevenSampleCurve<T, I> {
pub(crate) core: UnevenCore<T>,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) interpolation: I,
}
impl<T, I> Debug for UnevenSampleCurve<T, I>
where
UnevenCore<T>: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SampleCurve")
.field("core", &self.core)
.field("interpolation", &type_name::<I>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, I> TypePath for UnevenSampleCurve<T, I>
where
T: TypePath,
I: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::UnevenSampleCurve<{},{}>",
paths::THIS_MODULE,
T::type_path(),
type_name::<I>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!("UnevenSampleCurve<{},{}>", T::type_path(), type_name::<I>())
})
}
fn type_ident() -> Option<&'static str> {
Some("UnevenSampleCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, I> Curve<T> for UnevenSampleCurve<T, I>
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `UnevenCore::sample_with` is implicitly clamped.
self.core.sample_with(t, &self.interpolation)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T, I> UnevenSampleCurve<T, I> {
/// Create a new [`UnevenSampleCurve`] using the provided `interpolation` to interpolate
/// between adjacent `timed_samples`. The given samples are filtered to finite times and
/// sorted internally; if there are not at least 2 valid timed samples, an error will be
/// returned.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
pub fn new(
timed_samples: impl IntoIterator<Item = (f32, T)>,
interpolation: I,
) -> Result<Self, UnevenCoreError> {
Ok(Self {
core: UnevenCore::new(timed_samples)?,
interpolation,
})
}
/// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
/// but the function inputs to each are inverses of one another.
///
/// The samples are re-sorted by time after mapping and deduplicated by output time, so
/// the function `f` should generally be injective over the sample times of the curve.
///
/// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize
pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleCurve<T, I> {
Self {
core: self.core.map_sample_times(f),
interpolation: self.interpolation,
}
}
}
/// A curve that is defined by interpolation over unevenly spaced samples,
/// interpolated automatically using [a particularly well-behaved interpolation].
///
/// [a particularly well-behaved interpolation]: StableInterpolate
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct UnevenSampleAutoCurve<T> {
pub(crate) core: UnevenCore<T>,
}
impl<T> Curve<T> for UnevenSampleAutoCurve<T>
where
T: StableInterpolate,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `UnevenCore::sample_with` is implicitly clamped.
self.core
.sample_with(t, <T as StableInterpolate>::interpolate_stable)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T> UnevenSampleAutoCurve<T> {
/// Create a new [`UnevenSampleAutoCurve`] from a given set of timed samples.
///
/// The samples are filtered to finite times and sorted internally; if there are not
/// at least 2 valid timed samples, an error will be returned.
pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
Ok(Self {
core: UnevenCore::new(timed_samples)?,
})
}
/// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
/// but the function inputs to each are inverses of one another.
///
/// The samples are re-sorted by time after mapping and deduplicated by output time, so
/// the function `f` should generally be injective over the sample times of the curve.
///
/// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize
pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleAutoCurve<T> {
Self {
core: self.core.map_sample_times(f),
}
}
}
#[cfg(test)]
#[cfg(feature = "bevy_reflect")]
mod tests {
//! These tests should guarantee (by even compiling) that `SampleCurve` and `UnevenSampleCurve`
//! can be `Reflect` under reasonable circumstances where their interpolation is defined by:
//! - function items
//! - 'static closures
//! - function pointers
use super::{SampleCurve, UnevenSampleCurve};
use crate::{curve::Interval, VectorSpace};
use alloc::boxed::Box;
use bevy_reflect::Reflect;
#[test]
fn reflect_sample_curve() {
fn foo(x: &f32, y: &f32, t: f32) -> f32 {
x.lerp(*y, t)
}
let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
let baz: fn(&f32, &f32, f32) -> f32 = bar;
let samples = [0.0, 1.0, 2.0];
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, foo).unwrap());
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, bar).unwrap());
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, baz).unwrap());
}
#[test]
fn reflect_uneven_sample_curve() {
fn foo(x: &f32, y: &f32, t: f32) -> f32 {
x.lerp(*y, t)
}
let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
let baz: fn(&f32, &f32, f32) -> f32 = bar;
let keyframes = [(0.0, 1.0), (1.0, 0.0), (2.0, -1.0)];
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, foo).unwrap());
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, bar).unwrap());
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, baz).unwrap());
}
}

1093
vendor/bevy_math/src/direction.rs vendored Normal file

File diff suppressed because it is too large Load Diff

183
vendor/bevy_math/src/float_ord.rs vendored Normal file
View File

@@ -0,0 +1,183 @@
use core::{
cmp::Ordering,
hash::{Hash, Hasher},
ops::Neg,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// A wrapper for floats that implements [`Ord`], [`Eq`], and [`Hash`] traits.
///
/// This is a work around for the fact that the IEEE 754-2008 standard,
/// implemented by Rust's [`f32`] type,
/// doesn't define an ordering for [`NaN`](f32::NAN),
/// and `NaN` is not considered equal to any other `NaN`.
///
/// Wrapping a float with `FloatOrd` breaks conformance with the standard
/// by sorting `NaN` as less than all other numbers and equal to any other `NaN`.
#[derive(Debug, Copy, Clone)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
pub struct FloatOrd(pub f32);
impl PartialOrd for FloatOrd {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
fn lt(&self, other: &Self) -> bool {
!other.le(self)
}
// If `self` is NaN, it is equal to another NaN and less than all other floats, so return true.
// If `self` isn't NaN and `other` is, the float comparison returns false, which match the `FloatOrd` ordering.
// Otherwise, a standard float comparison happens.
fn le(&self, other: &Self) -> bool {
self.0.is_nan() || self.0 <= other.0
}
fn gt(&self, other: &Self) -> bool {
!self.le(other)
}
fn ge(&self, other: &Self) -> bool {
other.le(self)
}
}
impl Ord for FloatOrd {
#[expect(
clippy::comparison_chain,
reason = "This can't be rewritten with `match` and `cmp`, as this is `cmp` itself."
)]
fn cmp(&self, other: &Self) -> Ordering {
if self > other {
Ordering::Greater
} else if self < other {
Ordering::Less
} else {
Ordering::Equal
}
}
}
impl PartialEq for FloatOrd {
fn eq(&self, other: &Self) -> bool {
if self.0.is_nan() {
other.0.is_nan()
} else {
self.0 == other.0
}
}
}
impl Eq for FloatOrd {}
impl Hash for FloatOrd {
fn hash<H: Hasher>(&self, state: &mut H) {
if self.0.is_nan() {
// Ensure all NaN representations hash to the same value
state.write(&f32::to_ne_bytes(f32::NAN));
} else if self.0 == 0.0 {
// Ensure both zeroes hash to the same value
state.write(&f32::to_ne_bytes(0.0f32));
} else {
state.write(&f32::to_ne_bytes(self.0));
}
}
}
impl Neg for FloatOrd {
type Output = FloatOrd;
fn neg(self) -> Self::Output {
FloatOrd(-self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
const NAN: FloatOrd = FloatOrd(f32::NAN);
const ZERO: FloatOrd = FloatOrd(0.0);
const ONE: FloatOrd = FloatOrd(1.0);
#[test]
fn float_ord_eq() {
assert_eq!(NAN, NAN);
assert_ne!(NAN, ZERO);
assert_ne!(ZERO, NAN);
assert_eq!(ZERO, ZERO);
}
#[test]
fn float_ord_cmp() {
assert_eq!(NAN.cmp(&NAN), Ordering::Equal);
assert_eq!(NAN.cmp(&ZERO), Ordering::Less);
assert_eq!(ZERO.cmp(&NAN), Ordering::Greater);
assert_eq!(ZERO.cmp(&ZERO), Ordering::Equal);
assert_eq!(ONE.cmp(&ZERO), Ordering::Greater);
assert_eq!(ZERO.cmp(&ONE), Ordering::Less);
}
#[test]
#[expect(
clippy::nonminimal_bool,
reason = "This tests that all operators work as they should, and in the process requires some non-simplified boolean expressions."
)]
fn float_ord_cmp_operators() {
assert!(!(NAN < NAN));
assert!(NAN < ZERO);
assert!(!(ZERO < NAN));
assert!(!(ZERO < ZERO));
assert!(ZERO < ONE);
assert!(!(ONE < ZERO));
assert!(!(NAN > NAN));
assert!(!(NAN > ZERO));
assert!(ZERO > NAN);
assert!(!(ZERO > ZERO));
assert!(!(ZERO > ONE));
assert!(ONE > ZERO);
assert!(NAN <= NAN);
assert!(NAN <= ZERO);
assert!(!(ZERO <= NAN));
assert!(ZERO <= ZERO);
assert!(ZERO <= ONE);
assert!(!(ONE <= ZERO));
assert!(NAN >= NAN);
assert!(!(NAN >= ZERO));
assert!(ZERO >= NAN);
assert!(ZERO >= ZERO);
assert!(!(ZERO >= ONE));
assert!(ONE >= ZERO);
}
#[cfg(feature = "std")]
#[test]
fn float_ord_hash() {
let hash = |num| {
let mut h = std::hash::DefaultHasher::new();
FloatOrd(num).hash(&mut h);
h.finish()
};
assert_ne!((-0.0f32).to_bits(), 0.0f32.to_bits());
assert_eq!(hash(-0.0), hash(0.0));
let nan_1 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0001);
assert!(nan_1.is_nan());
let nan_2 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0010);
assert!(nan_2.is_nan());
assert_ne!(nan_1.to_bits(), nan_2.to_bits());
assert_eq!(hash(nan_1), hash(nan_2));
}
}

688
vendor/bevy_math/src/isometry.rs vendored Normal file
View File

@@ -0,0 +1,688 @@
//! Isometry types for expressing rigid motions in two and three dimensions.
use crate::{Affine2, Affine3, Affine3A, Dir2, Dir3, Mat3, Mat3A, Quat, Rot2, Vec2, Vec3, Vec3A};
use core::ops::Mul;
#[cfg(feature = "approx")]
use approx::{AbsDiffEq, RelativeEq, UlpsEq};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// An isometry in two dimensions, representing a rotation followed by a translation.
/// This can often be useful for expressing relative positions and transformations from one position to another.
///
/// In particular, this type represents a distance-preserving transformation known as a *rigid motion* or a *direct motion*,
/// and belongs to the special [Euclidean group] SE(2). This includes translation and rotation, but excludes reflection.
///
/// For the three-dimensional version, see [`Isometry3d`].
///
/// [Euclidean group]: https://en.wikipedia.org/wiki/Euclidean_group
///
/// # Example
///
/// Isometries can be created from a given translation and rotation:
///
/// ```
/// # use bevy_math::{Isometry2d, Rot2, Vec2};
/// #
/// let iso = Isometry2d::new(Vec2::new(2.0, 1.0), Rot2::degrees(90.0));
/// ```
///
/// Or from separate parts:
///
/// ```
/// # use bevy_math::{Isometry2d, Rot2, Vec2};
/// #
/// let iso1 = Isometry2d::from_translation(Vec2::new(2.0, 1.0));
/// let iso2 = Isometry2d::from_rotation(Rot2::degrees(90.0));
/// ```
///
/// The isometries can be used to transform points:
///
/// ```
/// # use approx::assert_abs_diff_eq;
/// # use bevy_math::{Isometry2d, Rot2, Vec2};
/// #
/// let iso = Isometry2d::new(Vec2::new(2.0, 1.0), Rot2::degrees(90.0));
/// let point = Vec2::new(4.0, 4.0);
///
/// // These are equivalent
/// let result = iso.transform_point(point);
/// let result = iso * point;
///
/// assert_eq!(result, Vec2::new(-2.0, 5.0));
/// ```
///
/// Isometries can also be composed together:
///
/// ```
/// # use bevy_math::{Isometry2d, Rot2, Vec2};
/// #
/// # let iso = Isometry2d::new(Vec2::new(2.0, 1.0), Rot2::degrees(90.0));
/// # let iso1 = Isometry2d::from_translation(Vec2::new(2.0, 1.0));
/// # let iso2 = Isometry2d::from_rotation(Rot2::degrees(90.0));
/// #
/// assert_eq!(iso1 * iso2, iso);
/// ```
///
/// One common operation is to compute an isometry representing the relative positions of two objects
/// for things like intersection tests. This can be done with an inverse transformation:
///
/// ```
/// # use bevy_math::{Isometry2d, Rot2, Vec2};
/// #
/// let circle_iso = Isometry2d::from_translation(Vec2::new(2.0, 1.0));
/// let rectangle_iso = Isometry2d::from_rotation(Rot2::degrees(90.0));
///
/// // Compute the relative position and orientation between the two shapes
/// let relative_iso = circle_iso.inverse() * rectangle_iso;
///
/// // Or alternatively, to skip an extra rotation operation:
/// let relative_iso = circle_iso.inverse_mul(rectangle_iso);
/// ```
#[derive(Copy, Clone, Default, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Isometry2d {
/// The rotational part of a two-dimensional isometry.
pub rotation: Rot2,
/// The translational part of a two-dimensional isometry.
pub translation: Vec2,
}
impl Isometry2d {
/// The identity isometry which represents the rigid motion of not doing anything.
pub const IDENTITY: Self = Isometry2d {
rotation: Rot2::IDENTITY,
translation: Vec2::ZERO,
};
/// Create a two-dimensional isometry from a rotation and a translation.
#[inline]
pub fn new(translation: Vec2, rotation: Rot2) -> Self {
Isometry2d {
rotation,
translation,
}
}
/// Create a two-dimensional isometry from a rotation.
#[inline]
pub fn from_rotation(rotation: Rot2) -> Self {
Isometry2d {
rotation,
translation: Vec2::ZERO,
}
}
/// Create a two-dimensional isometry from a translation.
#[inline]
pub fn from_translation(translation: Vec2) -> Self {
Isometry2d {
rotation: Rot2::IDENTITY,
translation,
}
}
/// Create a two-dimensional isometry from a translation with the given `x` and `y` components.
#[inline]
pub fn from_xy(x: f32, y: f32) -> Self {
Isometry2d {
rotation: Rot2::IDENTITY,
translation: Vec2::new(x, y),
}
}
/// The inverse isometry that undoes this one.
#[inline]
pub fn inverse(&self) -> Self {
let inv_rot = self.rotation.inverse();
Isometry2d {
rotation: inv_rot,
translation: inv_rot * -self.translation,
}
}
/// Compute `iso1.inverse() * iso2` in a more efficient way for one-shot cases.
///
/// If the same isometry is used multiple times, it is more efficient to instead compute
/// the inverse once and use that for each transformation.
#[inline]
pub fn inverse_mul(&self, rhs: Self) -> Self {
let inv_rot = self.rotation.inverse();
let delta_translation = rhs.translation - self.translation;
Self::new(inv_rot * delta_translation, inv_rot * rhs.rotation)
}
/// Transform a point by rotating and translating it using this isometry.
#[inline]
pub fn transform_point(&self, point: Vec2) -> Vec2 {
self.rotation * point + self.translation
}
/// Transform a point by rotating and translating it using the inverse of this isometry.
///
/// This is more efficient than `iso.inverse().transform_point(point)` for one-shot cases.
/// If the same isometry is used multiple times, it is more efficient to instead compute
/// the inverse once and use that for each transformation.
#[inline]
pub fn inverse_transform_point(&self, point: Vec2) -> Vec2 {
self.rotation.inverse() * (point - self.translation)
}
}
impl From<Isometry2d> for Affine2 {
#[inline]
fn from(iso: Isometry2d) -> Self {
Affine2 {
matrix2: iso.rotation.into(),
translation: iso.translation,
}
}
}
impl From<Vec2> for Isometry2d {
#[inline]
fn from(translation: Vec2) -> Self {
Isometry2d::from_translation(translation)
}
}
impl From<Rot2> for Isometry2d {
#[inline]
fn from(rotation: Rot2) -> Self {
Isometry2d::from_rotation(rotation)
}
}
impl Mul for Isometry2d {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self::Output {
Isometry2d {
rotation: self.rotation * rhs.rotation,
translation: self.rotation * rhs.translation + self.translation,
}
}
}
impl Mul<Vec2> for Isometry2d {
type Output = Vec2;
#[inline]
fn mul(self, rhs: Vec2) -> Self::Output {
self.transform_point(rhs)
}
}
impl Mul<Dir2> for Isometry2d {
type Output = Dir2;
#[inline]
fn mul(self, rhs: Dir2) -> Self::Output {
self.rotation * rhs
}
}
#[cfg(feature = "approx")]
impl AbsDiffEq for Isometry2d {
type Epsilon = <f32 as AbsDiffEq>::Epsilon;
fn default_epsilon() -> Self::Epsilon {
f32::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.rotation.abs_diff_eq(&other.rotation, epsilon)
&& self.translation.abs_diff_eq(other.translation, epsilon)
}
}
#[cfg(feature = "approx")]
impl RelativeEq for Isometry2d {
fn default_max_relative() -> Self::Epsilon {
Self::default_epsilon()
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.rotation
.relative_eq(&other.rotation, epsilon, max_relative)
&& self
.translation
.relative_eq(&other.translation, epsilon, max_relative)
}
}
#[cfg(feature = "approx")]
impl UlpsEq for Isometry2d {
fn default_max_ulps() -> u32 {
4
}
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.rotation.ulps_eq(&other.rotation, epsilon, max_ulps)
&& self
.translation
.ulps_eq(&other.translation, epsilon, max_ulps)
}
}
/// An isometry in three dimensions, representing a rotation followed by a translation.
/// This can often be useful for expressing relative positions and transformations from one position to another.
///
/// In particular, this type represents a distance-preserving transformation known as a *rigid motion* or a *direct motion*,
/// and belongs to the special [Euclidean group] SE(3). This includes translation and rotation, but excludes reflection.
///
/// For the two-dimensional version, see [`Isometry2d`].
///
/// [Euclidean group]: https://en.wikipedia.org/wiki/Euclidean_group
///
/// # Example
///
/// Isometries can be created from a given translation and rotation:
///
/// ```
/// # use bevy_math::{Isometry3d, Quat, Vec3};
/// # use std::f32::consts::FRAC_PI_2;
/// #
/// let iso = Isometry3d::new(Vec3::new(2.0, 1.0, 3.0), Quat::from_rotation_z(FRAC_PI_2));
/// ```
///
/// Or from separate parts:
///
/// ```
/// # use bevy_math::{Isometry3d, Quat, Vec3};
/// # use std::f32::consts::FRAC_PI_2;
/// #
/// let iso1 = Isometry3d::from_translation(Vec3::new(2.0, 1.0, 3.0));
/// let iso2 = Isometry3d::from_rotation(Quat::from_rotation_z(FRAC_PI_2));
/// ```
///
/// The isometries can be used to transform points:
///
/// ```
/// # use approx::assert_relative_eq;
/// # use bevy_math::{Isometry3d, Quat, Vec3};
/// # use std::f32::consts::FRAC_PI_2;
/// #
/// let iso = Isometry3d::new(Vec3::new(2.0, 1.0, 3.0), Quat::from_rotation_z(FRAC_PI_2));
/// let point = Vec3::new(4.0, 4.0, 4.0);
///
/// // These are equivalent
/// let result = iso.transform_point(point);
/// let result = iso * point;
///
/// assert_relative_eq!(result, Vec3::new(-2.0, 5.0, 7.0));
/// ```
///
/// Isometries can also be composed together:
///
/// ```
/// # use bevy_math::{Isometry3d, Quat, Vec3};
/// # use std::f32::consts::FRAC_PI_2;
/// #
/// # let iso = Isometry3d::new(Vec3::new(2.0, 1.0, 3.0), Quat::from_rotation_z(FRAC_PI_2));
/// # let iso1 = Isometry3d::from_translation(Vec3::new(2.0, 1.0, 3.0));
/// # let iso2 = Isometry3d::from_rotation(Quat::from_rotation_z(FRAC_PI_2));
/// #
/// assert_eq!(iso1 * iso2, iso);
/// ```
///
/// One common operation is to compute an isometry representing the relative positions of two objects
/// for things like intersection tests. This can be done with an inverse transformation:
///
/// ```
/// # use bevy_math::{Isometry3d, Quat, Vec3};
/// # use std::f32::consts::FRAC_PI_2;
/// #
/// let sphere_iso = Isometry3d::from_translation(Vec3::new(2.0, 1.0, 3.0));
/// let cuboid_iso = Isometry3d::from_rotation(Quat::from_rotation_z(FRAC_PI_2));
///
/// // Compute the relative position and orientation between the two shapes
/// let relative_iso = sphere_iso.inverse() * cuboid_iso;
///
/// // Or alternatively, to skip an extra rotation operation:
/// let relative_iso = sphere_iso.inverse_mul(cuboid_iso);
/// ```
#[derive(Copy, Clone, Default, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Isometry3d {
/// The rotational part of a three-dimensional isometry.
pub rotation: Quat,
/// The translational part of a three-dimensional isometry.
pub translation: Vec3A,
}
impl Isometry3d {
/// The identity isometry which represents the rigid motion of not doing anything.
pub const IDENTITY: Self = Isometry3d {
rotation: Quat::IDENTITY,
translation: Vec3A::ZERO,
};
/// Create a three-dimensional isometry from a rotation and a translation.
#[inline]
pub fn new(translation: impl Into<Vec3A>, rotation: Quat) -> Self {
Isometry3d {
rotation,
translation: translation.into(),
}
}
/// Create a three-dimensional isometry from a rotation.
#[inline]
pub fn from_rotation(rotation: Quat) -> Self {
Isometry3d {
rotation,
translation: Vec3A::ZERO,
}
}
/// Create a three-dimensional isometry from a translation.
#[inline]
pub fn from_translation(translation: impl Into<Vec3A>) -> Self {
Isometry3d {
rotation: Quat::IDENTITY,
translation: translation.into(),
}
}
/// Create a three-dimensional isometry from a translation with the given `x`, `y`, and `z` components.
#[inline]
pub fn from_xyz(x: f32, y: f32, z: f32) -> Self {
Isometry3d {
rotation: Quat::IDENTITY,
translation: Vec3A::new(x, y, z),
}
}
/// The inverse isometry that undoes this one.
#[inline]
pub fn inverse(&self) -> Self {
let inv_rot = self.rotation.inverse();
Isometry3d {
rotation: inv_rot,
translation: inv_rot * -self.translation,
}
}
/// Compute `iso1.inverse() * iso2` in a more efficient way for one-shot cases.
///
/// If the same isometry is used multiple times, it is more efficient to instead compute
/// the inverse once and use that for each transformation.
#[inline]
pub fn inverse_mul(&self, rhs: Self) -> Self {
let inv_rot = self.rotation.inverse();
let delta_translation = rhs.translation - self.translation;
Self::new(inv_rot * delta_translation, inv_rot * rhs.rotation)
}
/// Transform a point by rotating and translating it using this isometry.
#[inline]
pub fn transform_point(&self, point: impl Into<Vec3A>) -> Vec3A {
self.rotation * point.into() + self.translation
}
/// Transform a point by rotating and translating it using the inverse of this isometry.
///
/// This is more efficient than `iso.inverse().transform_point(point)` for one-shot cases.
/// If the same isometry is used multiple times, it is more efficient to instead compute
/// the inverse once and use that for each transformation.
#[inline]
pub fn inverse_transform_point(&self, point: impl Into<Vec3A>) -> Vec3A {
self.rotation.inverse() * (point.into() - self.translation)
}
}
impl From<Isometry3d> for Affine3 {
#[inline]
fn from(iso: Isometry3d) -> Self {
Affine3 {
matrix3: Mat3::from_quat(iso.rotation),
translation: iso.translation.into(),
}
}
}
impl From<Isometry3d> for Affine3A {
#[inline]
fn from(iso: Isometry3d) -> Self {
Affine3A {
matrix3: Mat3A::from_quat(iso.rotation),
translation: iso.translation,
}
}
}
impl From<Vec3> for Isometry3d {
#[inline]
fn from(translation: Vec3) -> Self {
Isometry3d::from_translation(translation)
}
}
impl From<Vec3A> for Isometry3d {
#[inline]
fn from(translation: Vec3A) -> Self {
Isometry3d::from_translation(translation)
}
}
impl From<Quat> for Isometry3d {
#[inline]
fn from(rotation: Quat) -> Self {
Isometry3d::from_rotation(rotation)
}
}
impl Mul for Isometry3d {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self::Output {
Isometry3d {
rotation: self.rotation * rhs.rotation,
translation: self.rotation * rhs.translation + self.translation,
}
}
}
impl Mul<Vec3A> for Isometry3d {
type Output = Vec3A;
#[inline]
fn mul(self, rhs: Vec3A) -> Self::Output {
self.transform_point(rhs)
}
}
impl Mul<Vec3> for Isometry3d {
type Output = Vec3;
#[inline]
fn mul(self, rhs: Vec3) -> Self::Output {
self.transform_point(rhs).into()
}
}
impl Mul<Dir3> for Isometry3d {
type Output = Dir3;
#[inline]
fn mul(self, rhs: Dir3) -> Self::Output {
self.rotation * rhs
}
}
#[cfg(feature = "approx")]
impl AbsDiffEq for Isometry3d {
type Epsilon = <f32 as AbsDiffEq>::Epsilon;
fn default_epsilon() -> Self::Epsilon {
f32::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.rotation.abs_diff_eq(other.rotation, epsilon)
&& self.translation.abs_diff_eq(other.translation, epsilon)
}
}
#[cfg(feature = "approx")]
impl RelativeEq for Isometry3d {
fn default_max_relative() -> Self::Epsilon {
Self::default_epsilon()
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.rotation
.relative_eq(&other.rotation, epsilon, max_relative)
&& self
.translation
.relative_eq(&other.translation, epsilon, max_relative)
}
}
#[cfg(feature = "approx")]
impl UlpsEq for Isometry3d {
fn default_max_ulps() -> u32 {
4
}
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.rotation.ulps_eq(&other.rotation, epsilon, max_ulps)
&& self
.translation
.ulps_eq(&other.translation, epsilon, max_ulps)
}
}
#[cfg(test)]
#[cfg(feature = "approx")]
mod tests {
use super::*;
use crate::{vec2, vec3, vec3a};
use approx::assert_abs_diff_eq;
use core::f32::consts::{FRAC_PI_2, FRAC_PI_3};
#[test]
fn mul_2d() {
let iso1 = Isometry2d::new(vec2(1.0, 0.0), Rot2::FRAC_PI_2);
let iso2 = Isometry2d::new(vec2(0.0, 1.0), Rot2::FRAC_PI_2);
let expected = Isometry2d::new(vec2(0.0, 0.0), Rot2::PI);
assert_abs_diff_eq!(iso1 * iso2, expected);
}
#[test]
fn inverse_mul_2d() {
let iso1 = Isometry2d::new(vec2(1.0, 0.0), Rot2::FRAC_PI_2);
let iso2 = Isometry2d::new(vec2(0.0, 0.0), Rot2::PI);
let expected = Isometry2d::new(vec2(0.0, 1.0), Rot2::FRAC_PI_2);
assert_abs_diff_eq!(iso1.inverse_mul(iso2), expected);
}
#[test]
fn mul_3d() {
let iso1 = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_x(FRAC_PI_2));
let iso2 = Isometry3d::new(vec3(0.0, 1.0, 0.0), Quat::IDENTITY);
let expected = Isometry3d::new(vec3(1.0, 0.0, 1.0), Quat::from_rotation_x(FRAC_PI_2));
assert_abs_diff_eq!(iso1 * iso2, expected);
}
#[test]
fn inverse_mul_3d() {
let iso1 = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_x(FRAC_PI_2));
let iso2 = Isometry3d::new(vec3(1.0, 0.0, 1.0), Quat::from_rotation_x(FRAC_PI_2));
let expected = Isometry3d::new(vec3(0.0, 1.0, 0.0), Quat::IDENTITY);
assert_abs_diff_eq!(iso1.inverse_mul(iso2), expected);
}
#[test]
fn identity_2d() {
let iso = Isometry2d::new(vec2(-1.0, -0.5), Rot2::degrees(75.0));
assert_abs_diff_eq!(Isometry2d::IDENTITY * iso, iso);
assert_abs_diff_eq!(iso * Isometry2d::IDENTITY, iso);
}
#[test]
fn identity_3d() {
let iso = Isometry3d::new(vec3(-1.0, 2.5, 3.3), Quat::from_rotation_z(FRAC_PI_3));
assert_abs_diff_eq!(Isometry3d::IDENTITY * iso, iso);
assert_abs_diff_eq!(iso * Isometry3d::IDENTITY, iso);
}
#[test]
fn inverse_2d() {
let iso = Isometry2d::new(vec2(-1.0, -0.5), Rot2::degrees(75.0));
let inv = iso.inverse();
assert_abs_diff_eq!(iso * inv, Isometry2d::IDENTITY);
assert_abs_diff_eq!(inv * iso, Isometry2d::IDENTITY);
}
#[test]
fn inverse_3d() {
let iso = Isometry3d::new(vec3(-1.0, 2.5, 3.3), Quat::from_rotation_z(FRAC_PI_3));
let inv = iso.inverse();
assert_abs_diff_eq!(iso * inv, Isometry3d::IDENTITY);
assert_abs_diff_eq!(inv * iso, Isometry3d::IDENTITY);
}
#[test]
fn transform_2d() {
let iso = Isometry2d::new(vec2(0.5, -0.5), Rot2::FRAC_PI_2);
let point = vec2(1.0, 1.0);
assert_abs_diff_eq!(vec2(-0.5, 0.5), iso * point);
}
#[test]
fn inverse_transform_2d() {
let iso = Isometry2d::new(vec2(0.5, -0.5), Rot2::FRAC_PI_2);
let point = vec2(-0.5, 0.5);
assert_abs_diff_eq!(vec2(1.0, 1.0), iso.inverse_transform_point(point));
}
#[test]
fn transform_3d() {
let iso = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_y(FRAC_PI_2));
let point = vec3(1.0, 1.0, 1.0);
assert_abs_diff_eq!(vec3(2.0, 1.0, -1.0), iso * point);
}
#[test]
fn inverse_transform_3d() {
let iso = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_y(FRAC_PI_2));
let point = vec3(2.0, 1.0, -1.0);
assert_abs_diff_eq!(vec3a(1.0, 1.0, 1.0), iso.inverse_transform_point(point));
}
}

101
vendor/bevy_math/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,101 @@
#![forbid(unsafe_code)]
#![cfg_attr(
any(docsrs, docsrs_dep),
expect(
internal_features,
reason = "rustdoc_internals is needed for fake_variadic"
)
)]
#![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
)]
#![no_std]
//! Provides math types and functionality for the Bevy game engine.
//!
//! The commonly used types are vectors like [`Vec2`] and [`Vec3`],
//! matrices like [`Mat2`], [`Mat3`] and [`Mat4`] and orientation representations
//! like [`Quat`].
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "alloc")]
extern crate alloc;
mod affine3;
mod aspect_ratio;
pub mod bounding;
pub mod common_traits;
mod compass;
pub mod cubic_splines;
mod direction;
mod float_ord;
mod isometry;
pub mod ops;
pub mod primitives;
mod ray;
mod rects;
mod rotation2d;
#[cfg(feature = "curve")]
pub mod curve;
#[cfg(feature = "rand")]
pub mod sampling;
pub use affine3::*;
pub use aspect_ratio::AspectRatio;
pub use common_traits::*;
pub use compass::{CompassOctant, CompassQuadrant};
pub use direction::*;
pub use float_ord::*;
pub use isometry::{Isometry2d, Isometry3d};
pub use ops::FloatPow;
pub use ray::{Ray2d, Ray3d};
pub use rects::*;
pub use rotation2d::Rot2;
#[cfg(feature = "curve")]
pub use curve::Curve;
#[cfg(feature = "rand")]
pub use sampling::{FromRng, ShapeSample};
/// The math prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[doc(hidden)]
pub use crate::{
bvec2, bvec3, bvec3a, bvec4, bvec4a,
cubic_splines::{CubicNurbsError, CubicSegment, RationalSegment},
direction::{Dir2, Dir3, Dir3A},
ivec2, ivec3, ivec4, mat2, mat3, mat3a, mat4, ops,
primitives::*,
quat, uvec2, uvec3, uvec4, vec2, vec3, vec3a, vec4, BVec2, BVec3, BVec3A, BVec4, BVec4A,
EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d, Isometry3d, Mat2, Mat3, Mat3A,
Mat4, Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect, UVec2, UVec3, UVec4, Vec2,
Vec2Swizzles, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles,
};
#[doc(hidden)]
#[cfg(feature = "curve")]
pub use crate::curve::*;
#[doc(hidden)]
#[cfg(feature = "rand")]
pub use crate::sampling::{FromRng, ShapeSample};
#[cfg(feature = "alloc")]
#[doc(hidden)]
pub use crate::cubic_splines::{
CubicBSpline, CubicBezier, CubicCardinalSpline, CubicCurve, CubicGenerator, CubicHermite,
CubicNurbs, CyclicCubicGenerator, RationalCurve, RationalGenerator,
};
}
pub use glam::*;

646
vendor/bevy_math/src/ops.rs vendored Normal file
View File

@@ -0,0 +1,646 @@
//! This mod re-exports the correct versions of floating-point operations with
//! unspecified precision in the standard library depending on whether the `libm`
//! crate feature is enabled.
//!
//! All the functions here are named according to their versions in the standard
//! library.
//!
//! It also provides `no_std` compatible alternatives to certain floating-point
//! operations which are not provided in the [`core`] library.
// Note: There are some Rust methods with unspecified precision without a `libm`
// equivalent:
// - `f32::powi` (integer powers)
// - `f32::log` (logarithm with specified base)
// - `f32::abs_sub` (actually unsure if `libm` has this, but don't use it regardless)
//
// Additionally, the following nightly API functions are not presently integrated
// into this, but they would be candidates once standardized:
// - `f32::gamma`
// - `f32::ln_gamma`
#[cfg(all(not(feature = "libm"), feature = "std"))]
#[expect(
clippy::disallowed_methods,
reason = "Many of the disallowed methods are disallowed to force code to use the feature-conditional re-exports from this module, but this module itself is exempt from that rule."
)]
mod std_ops {
/// Raises a number to a floating point power.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn powf(x: f32, y: f32) -> f32 {
f32::powf(x, y)
}
/// Returns `e^(self)`, (the exponential function).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp(x: f32) -> f32 {
f32::exp(x)
}
/// Returns `2^(self)`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp2(x: f32) -> f32 {
f32::exp2(x)
}
/// Returns the natural logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn ln(x: f32) -> f32 {
f32::ln(x)
}
/// Returns the base 2 logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn log2(x: f32) -> f32 {
f32::log2(x)
}
/// Returns the base 10 logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn log10(x: f32) -> f32 {
f32::log10(x)
}
/// Returns the cube root of a number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cbrt(x: f32) -> f32 {
f32::cbrt(x)
}
/// Compute the distance between the origin and a point `(x, y)` on the Euclidean plane.
/// Equivalently, compute the length of the hypotenuse of a right-angle triangle with other sides having length `x.abs()` and `y.abs()`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn hypot(x: f32, y: f32) -> f32 {
f32::hypot(x, y)
}
/// Computes the sine of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sin(x: f32) -> f32 {
f32::sin(x)
}
/// Computes the cosine of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cos(x: f32) -> f32 {
f32::cos(x)
}
/// Computes the tangent of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn tan(x: f32) -> f32 {
f32::tan(x)
}
/// Computes the arcsine of a number. Return value is in radians in
/// the range [-pi/2, pi/2] or NaN if the number is outside the range
/// [-1, 1].
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn asin(x: f32) -> f32 {
f32::asin(x)
}
/// Computes the arccosine of a number. Return value is in radians in
/// the range [0, pi] or NaN if the number is outside the range
/// [-1, 1].
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn acos(x: f32) -> f32 {
f32::acos(x)
}
/// Computes the arctangent of a number. Return value is in radians in the
/// range [-pi/2, pi/2];
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atan(x: f32) -> f32 {
f32::atan(x)
}
/// Computes the four-quadrant arctangent of `y` and `x` in radians.
///
/// * `x = 0`, `y = 0`: `0`
/// * `x >= 0`: `arctan(y/x)` -> `[-pi/2, pi/2]`
/// * `y >= 0`: `arctan(y/x) + pi` -> `(pi/2, pi]`
/// * `y < 0`: `arctan(y/x) - pi` -> `(-pi, -pi/2)`
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atan2(y: f32, x: f32) -> f32 {
f32::atan2(y, x)
}
/// Simultaneously computes the sine and cosine of the number, `x`. Returns
/// `(sin(x), cos(x))`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sin_cos(x: f32) -> (f32, f32) {
f32::sin_cos(x)
}
/// Returns `e^(self) - 1` in a way that is accurate even if the
/// number is close to zero.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp_m1(x: f32) -> f32 {
f32::exp_m1(x)
}
/// Returns `ln(1+n)` (natural logarithm) more accurately than if
/// the operations were performed separately.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn ln_1p(x: f32) -> f32 {
f32::ln_1p(x)
}
/// Hyperbolic sine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sinh(x: f32) -> f32 {
f32::sinh(x)
}
/// Hyperbolic cosine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cosh(x: f32) -> f32 {
f32::cosh(x)
}
/// Hyperbolic tangent function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn tanh(x: f32) -> f32 {
f32::tanh(x)
}
/// Inverse hyperbolic sine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn asinh(x: f32) -> f32 {
f32::asinh(x)
}
/// Inverse hyperbolic cosine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn acosh(x: f32) -> f32 {
f32::acosh(x)
}
/// Inverse hyperbolic tangent function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atanh(x: f32) -> f32 {
f32::atanh(x)
}
}
#[cfg(any(feature = "libm", all(feature = "nostd-libm", not(feature = "std"))))]
mod libm_ops {
/// Raises a number to a floating point power.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn powf(x: f32, y: f32) -> f32 {
libm::powf(x, y)
}
/// Returns `e^(self)`, (the exponential function).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp(x: f32) -> f32 {
libm::expf(x)
}
/// Returns `2^(self)`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp2(x: f32) -> f32 {
libm::exp2f(x)
}
/// Returns the natural logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn ln(x: f32) -> f32 {
// This isn't documented in `libm` but this is actually the base e logarithm.
libm::logf(x)
}
/// Returns the base 2 logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn log2(x: f32) -> f32 {
libm::log2f(x)
}
/// Returns the base 10 logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn log10(x: f32) -> f32 {
libm::log10f(x)
}
/// Returns the cube root of a number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cbrt(x: f32) -> f32 {
libm::cbrtf(x)
}
/// Compute the distance between the origin and a point `(x, y)` on the Euclidean plane.
///
/// Equivalently, compute the length of the hypotenuse of a right-angle triangle with other sides having length `x.abs()` and `y.abs()`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn hypot(x: f32, y: f32) -> f32 {
libm::hypotf(x, y)
}
/// Computes the sine of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sin(x: f32) -> f32 {
libm::sinf(x)
}
/// Computes the cosine of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cos(x: f32) -> f32 {
libm::cosf(x)
}
/// Computes the tangent of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn tan(x: f32) -> f32 {
libm::tanf(x)
}
/// Computes the arcsine of a number. Return value is in radians in
/// the range [-pi/2, pi/2] or NaN if the number is outside the range
/// [-1, 1].
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn asin(x: f32) -> f32 {
libm::asinf(x)
}
/// Computes the arccosine of a number. Return value is in radians in
/// Hyperbolic tangent function.
///
/// Precision is specified when the `libm` feature is enabled.
/// the range [0, pi] or NaN if the number is outside the range
/// [-1, 1].
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn acos(x: f32) -> f32 {
libm::acosf(x)
}
/// Computes the arctangent of a number. Return value is in radians in the
/// range [-pi/2, pi/2];
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atan(x: f32) -> f32 {
libm::atanf(x)
}
/// Computes the four-quadrant arctangent of `y` and `x` in radians.
///
/// * `x = 0`, `y = 0`: `0`
/// * `x >= 0`: `arctan(y/x)` -> `[-pi/2, pi/2]`
/// * `y >= 0`: `arctan(y/x) + pi` -> `(pi/2, pi]`
/// * `y < 0`: `arctan(y/x) - pi` -> `(-pi, -pi/2)`
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atan2(y: f32, x: f32) -> f32 {
libm::atan2f(y, x)
}
/// Simultaneously computes the sine and cosine of the number, `x`. Returns
/// `(sin(x), cos(x))`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sin_cos(x: f32) -> (f32, f32) {
libm::sincosf(x)
}
/// Returns `e^(self) - 1` in a way that is accurate even if the
/// number is close to zero.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp_m1(x: f32) -> f32 {
libm::expm1f(x)
}
/// Returns `ln(1+n)` (natural logarithm) more accurately than if
/// the operations were performed separately.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn ln_1p(x: f32) -> f32 {
libm::log1pf(x)
}
/// Hyperbolic sine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sinh(x: f32) -> f32 {
libm::sinhf(x)
}
/// Hyperbolic cosine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cosh(x: f32) -> f32 {
libm::coshf(x)
}
/// Hyperbolic tangent function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn tanh(x: f32) -> f32 {
libm::tanhf(x)
}
/// Inverse hyperbolic sine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn asinh(x: f32) -> f32 {
libm::asinhf(x)
}
/// Inverse hyperbolic cosine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn acosh(x: f32) -> f32 {
libm::acoshf(x)
}
/// Inverse hyperbolic tangent function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atanh(x: f32) -> f32 {
libm::atanhf(x)
}
}
#[cfg(all(any(feature = "libm", feature = "nostd-libm"), not(feature = "std")))]
mod libm_ops_for_no_std {
//! Provides standardized names for [`f32`] operations which may not be
//! supported on `no_std` platforms.
//! On `no_std` platforms, this forwards to the implementations provided
//! by [`libm`].
/// Calculates the least nonnegative remainder of `self (mod rhs)`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn rem_euclid(x: f32, y: f32) -> f32 {
let result = libm::remainderf(x, y);
// libm::remainderf has a range of -y/2 to +y/2
if result < 0. {
result + y
} else {
result
}
}
/// Computes the absolute value of x.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn abs(x: f32) -> f32 {
libm::fabsf(x)
}
/// Returns the square root of a number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sqrt(x: f32) -> f32 {
libm::sqrtf(x)
}
/// Returns a number composed of the magnitude of `x` and the sign of `y`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn copysign(x: f32, y: f32) -> f32 {
libm::copysignf(x, y)
}
/// Returns the nearest integer to `x`. If a value is half-way between two integers, round away from `0.0`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn round(x: f32) -> f32 {
libm::roundf(x)
}
/// Returns the largest integer less than or equal to `x`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn floor(x: f32) -> f32 {
libm::floorf(x)
}
/// Returns the smallest integer greater than or equal to `x`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn ceil(x: f32) -> f32 {
libm::ceilf(x)
}
/// Returns the fractional part of `x`.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn fract(x: f32) -> f32 {
libm::modff(x).0
}
}
#[cfg(feature = "std")]
#[expect(
clippy::disallowed_methods,
reason = "Many of the disallowed methods are disallowed to force code to use the feature-conditional re-exports from this module, but this module itself is exempt from that rule."
)]
mod std_ops_for_no_std {
//! Provides standardized names for [`f32`] operations which may not be
//! supported on `no_std` platforms.
//! On `std` platforms, this forwards directly to the implementations provided
//! by [`std`].
/// Calculates the least nonnegative remainder of `x (mod y)`.
///
/// The result of this operation is guaranteed to be the rounded infinite-precision result.
#[inline(always)]
pub fn rem_euclid(x: f32, y: f32) -> f32 {
f32::rem_euclid(x, y)
}
/// Computes the absolute value of x.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn abs(x: f32) -> f32 {
f32::abs(x)
}
/// Returns the square root of a number.
///
/// The result of this operation is guaranteed to be the rounded infinite-precision result.
/// It is specified by IEEE 754 as `squareRoot` and guaranteed not to change.
#[inline(always)]
pub fn sqrt(x: f32) -> f32 {
f32::sqrt(x)
}
/// Returns a number composed of the magnitude of `x` and the sign of `y`.
///
/// Equal to `x` if the sign of `x` and `y` are the same, otherwise equal to `-x`. If `x` is a
/// `NaN`, then a `NaN` with the sign bit of `y` is returned. Note, however, that conserving the
/// sign bit on `NaN` across arithmetical operations is not generally guaranteed.
#[inline(always)]
pub fn copysign(x: f32, y: f32) -> f32 {
f32::copysign(x, y)
}
/// Returns the nearest integer to `x`. If a value is half-way between two integers, round away from `0.0`.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn round(x: f32) -> f32 {
f32::round(x)
}
/// Returns the largest integer less than or equal to `x`.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn floor(x: f32) -> f32 {
f32::floor(x)
}
/// Returns the smallest integer greater than or equal to `x`.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn ceil(x: f32) -> f32 {
f32::ceil(x)
}
/// Returns the fractional part of `x`.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn fract(x: f32) -> f32 {
f32::fract(x)
}
}
#[cfg(any(feature = "libm", all(feature = "nostd-libm", not(feature = "std"))))]
pub use libm_ops::*;
#[cfg(all(not(feature = "libm"), feature = "std"))]
pub use std_ops::*;
#[cfg(feature = "std")]
pub use std_ops_for_no_std::*;
#[cfg(all(any(feature = "libm", feature = "nostd-libm"), not(feature = "std")))]
pub use libm_ops_for_no_std::*;
#[cfg(all(
not(feature = "libm"),
not(feature = "std"),
not(feature = "nostd-libm")
))]
compile_error!("Either the `libm`, `std`, or `nostd-libm` feature must be enabled.");
/// This extension trait covers shortfall in determinacy from the lack of a `libm` counterpart
/// to `f32::powi`. Use this for the common small exponents.
pub trait FloatPow {
/// Squares the f32
fn squared(self) -> Self;
/// Cubes the f32
fn cubed(self) -> Self;
}
impl FloatPow for f32 {
#[inline]
fn squared(self) -> Self {
self * self
}
#[inline]
fn cubed(self) -> Self {
self * self * self
}
}

2486
vendor/bevy_math/src/primitives/dim2.rs vendored Normal file

File diff suppressed because it is too large Load Diff

1875
vendor/bevy_math/src/primitives/dim3.rs vendored Normal file

File diff suppressed because it is too large Load Diff

50
vendor/bevy_math/src/primitives/mod.rs vendored Normal file
View File

@@ -0,0 +1,50 @@
//! This module defines primitive shapes.
//! The origin is (0, 0) for 2D primitives and (0, 0, 0) for 3D primitives,
//! unless stated otherwise.
mod dim2;
pub use dim2::*;
mod dim3;
pub use dim3::*;
mod polygon;
#[cfg(feature = "serialize")]
mod serde;
/// A marker trait for 2D primitives
pub trait Primitive2d {}
/// A marker trait for 3D primitives
pub trait Primitive3d {}
/// The winding order for a set of points
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[doc(alias = "Orientation")]
pub enum WindingOrder {
/// A clockwise winding order
Clockwise,
/// A counterclockwise winding order
#[doc(alias = "AntiClockwise")]
CounterClockwise,
/// An invalid winding order indicating that it could not be computed reliably.
/// This often happens in *degenerate cases* where the points lie on the same line
#[doc(alias("Degenerate", "Collinear"))]
Invalid,
}
/// A trait for getting measurements of 2D shapes
pub trait Measured2d {
/// Get the perimeter of the shape
fn perimeter(&self) -> f32;
/// Get the area of the shape
fn area(&self) -> f32;
}
/// A trait for getting measurements of 3D shapes
pub trait Measured3d {
/// Get the surface area of the shape
fn area(&self) -> f32;
/// Get the volume of the shape
fn volume(&self) -> f32;
}

View File

@@ -0,0 +1,374 @@
#[cfg(feature = "alloc")]
use {
super::{Measured2d, Triangle2d},
alloc::{collections::BTreeMap, vec::Vec},
};
use core::cmp::Ordering;
use crate::Vec2;
#[cfg_attr(
not(feature = "alloc"),
expect(dead_code, reason = "this type is only used with the alloc feature")
)]
#[derive(Debug, Clone, Copy)]
enum Endpoint {
Left,
Right,
}
/// An event in the [`EventQueue`] is either the left or right vertex of an edge of the polygon.
///
/// Events are ordered so that any event `e1` which is to the left of another event `e2` is less than that event.
/// If `e1.position().x == e2.position().x` the events are ordered from bottom to top.
///
/// This is the order expected by the [`SweepLine`].
#[derive(Debug, Clone, Copy)]
#[cfg_attr(
not(feature = "alloc"),
allow(dead_code, reason = "this type is only used with the alloc feature")
)]
struct SweepLineEvent {
segment: Segment,
/// Type of the vertex (left or right)
endpoint: Endpoint,
}
impl SweepLineEvent {
#[cfg_attr(
not(feature = "alloc"),
allow(dead_code, reason = "this type is only used with the alloc feature")
)]
fn position(&self) -> Vec2 {
match self.endpoint {
Endpoint::Left => self.segment.left,
Endpoint::Right => self.segment.right,
}
}
}
impl PartialEq for SweepLineEvent {
fn eq(&self, other: &Self) -> bool {
self.position() == other.position()
}
}
impl Eq for SweepLineEvent {}
impl PartialOrd for SweepLineEvent {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SweepLineEvent {
fn cmp(&self, other: &Self) -> Ordering {
xy_order(self.position(), other.position())
}
}
/// Orders 2D points according to the order expected by the sweep line and event queue from -X to +X and then -Y to Y.
#[cfg_attr(
not(feature = "alloc"),
allow(dead_code, reason = "this type is only used with the alloc feature")
)]
fn xy_order(a: Vec2, b: Vec2) -> Ordering {
a.x.total_cmp(&b.x).then_with(|| a.y.total_cmp(&b.y))
}
/// The event queue holds an ordered list of all events the [`SweepLine`] will encounter when checking the current polygon.
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
struct EventQueue {
events: Vec<SweepLineEvent>,
}
#[cfg(feature = "alloc")]
impl EventQueue {
/// Initialize a new `EventQueue` with all events from the polygon represented by `vertices`.
///
/// The events in the event queue will be ordered.
fn new(vertices: &[Vec2]) -> Self {
if vertices.is_empty() {
return Self { events: Vec::new() };
}
let mut events = Vec::with_capacity(vertices.len() * 2);
for i in 0..vertices.len() {
let v1 = vertices[i];
let v2 = *vertices.get(i + 1).unwrap_or(&vertices[0]);
let (left, right) = if xy_order(v1, v2) == Ordering::Less {
(v1, v2)
} else {
(v2, v1)
};
let segment = Segment {
edge_index: i,
left,
right,
};
events.push(SweepLineEvent {
segment,
endpoint: Endpoint::Left,
});
events.push(SweepLineEvent {
segment,
endpoint: Endpoint::Right,
});
}
events.sort();
Self { events }
}
}
/// Represents a segment or rather an edge of the polygon in the [`SweepLine`].
///
/// Segments are ordered from bottom to top based on their left vertices if possible.
/// If their y values are identical, the segments are ordered based on the y values of their right vertices.
#[derive(Debug, Clone, Copy)]
struct Segment {
edge_index: usize,
left: Vec2,
right: Vec2,
}
impl PartialEq for Segment {
fn eq(&self, other: &Self) -> bool {
self.edge_index == other.edge_index
}
}
impl Eq for Segment {}
impl PartialOrd for Segment {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Segment {
fn cmp(&self, other: &Self) -> Ordering {
self.left
.y
.total_cmp(&other.left.y)
.then_with(|| self.right.y.total_cmp(&other.right.y))
}
}
/// Holds information about which segment is above and which is below a given [`Segment`]
#[cfg_attr(
not(feature = "alloc"),
expect(dead_code, reason = "this type is only used with the alloc feature")
)]
#[derive(Debug, Clone, Copy)]
struct SegmentOrder {
above: Option<usize>,
below: Option<usize>,
}
/// A sweep line allows for an efficient search for intersections between [segments](`Segment`).
///
/// It can be thought of as a vertical line sweeping from -X to +X across the polygon that keeps track of the order of the segments
/// the sweep line is intersecting at any given moment.
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
struct SweepLine<'a> {
vertices: &'a [Vec2],
tree: BTreeMap<Segment, SegmentOrder>,
}
#[cfg(feature = "alloc")]
impl<'a> SweepLine<'a> {
fn new(vertices: &'a [Vec2]) -> Self {
Self {
vertices,
tree: BTreeMap::new(),
}
}
/// Determine whether the given edges of the polygon intersect.
fn intersects(&self, edge1: Option<usize>, edge2: Option<usize>) -> bool {
let Some(edge1) = edge1 else {
return false;
};
let Some(edge2) = edge2 else {
return false;
};
// All adjacent edges intersect at their shared vertex
// but these intersections do not count so we ignore them here.
// Likewise a segment will always intersect itself / an identical edge.
if edge1 == edge2
|| (edge1 + 1) % self.vertices.len() == edge2
|| (edge2 + 1) % self.vertices.len() == edge1
{
return false;
}
let s11 = self.vertices[edge1];
let s12 = *self.vertices.get(edge1 + 1).unwrap_or(&self.vertices[0]);
let s21 = self.vertices[edge2];
let s22 = *self.vertices.get(edge2 + 1).unwrap_or(&self.vertices[0]);
// When both points of the second edge are on the same side of the first edge, no intersection is possible.
if point_side(s11, s12, s21) * point_side(s11, s12, s22) > 0.0 {
return false;
}
if point_side(s21, s22, s11) * point_side(s21, s22, s12) > 0.0 {
return false;
}
true
}
/// Add a new segment to the sweep line
fn add(&mut self, s: Segment) -> SegmentOrder {
let above = if let Some((next_s, next_ord)) = self.tree.range_mut(s..).next() {
next_ord.below.replace(s.edge_index);
Some(next_s.edge_index)
} else {
None
};
let below = if let Some((prev_s, prev_ord)) = self.tree.range_mut(..s).next_back() {
prev_ord.above.replace(s.edge_index);
Some(prev_s.edge_index)
} else {
None
};
let s_ord = SegmentOrder { above, below };
self.tree.insert(s, s_ord);
s_ord
}
/// Get the segment order for the given segment.
///
/// If `s` has not been added to the [`SweepLine`] `None` will be returned.
fn find(&self, s: &Segment) -> Option<&SegmentOrder> {
self.tree.get(s)
}
/// Remove `s` from the [`SweepLine`].
fn remove(&mut self, s: &Segment) {
let Some(s_ord) = self.tree.get(s).copied() else {
return;
};
if let Some((_, above_ord)) = self.tree.range_mut(s..).next() {
above_ord.below = s_ord.below;
}
if let Some((_, below_ord)) = self.tree.range_mut(..s).next_back() {
below_ord.above = s_ord.above;
}
self.tree.remove(s);
}
}
/// Test what side of the line through `p1` and `p2` `q` is.
///
/// The result will be `0` if the `q` is on the segment, negative for one side and positive for the other.
#[cfg_attr(
not(feature = "alloc"),
expect(
dead_code,
reason = "this function is only used with the alloc feature"
)
)]
#[inline(always)]
fn point_side(p1: Vec2, p2: Vec2, q: Vec2) -> f32 {
(p2.x - p1.x) * (q.y - p1.y) - (q.x - p1.x) * (p2.y - p1.y)
}
/// Tests whether the `vertices` describe a simple polygon.
/// The last vertex must not be equal to the first vertex.
///
/// A polygon is simple if it is not self intersecting and not self tangent.
/// As such, no two edges of the polygon may cross each other and each vertex must not lie on another edge.
///
/// Any 'polygon' with less than three vertices is simple.
///
/// The algorithm used is the Shamos-Hoey algorithm, a version of the Bentley-Ottman algorithm adapted to only detect whether any intersections exist.
/// This function will run in O(n * log n)
#[cfg(feature = "alloc")]
pub fn is_polygon_simple(vertices: &[Vec2]) -> bool {
if vertices.len() < 3 {
return true;
}
if vertices.len() == 3 {
return Triangle2d::new(vertices[0], vertices[1], vertices[2]).area() > 0.0;
}
let event_queue = EventQueue::new(vertices);
let mut sweep_line = SweepLine::new(vertices);
for e in event_queue.events {
match e.endpoint {
Endpoint::Left => {
let s = sweep_line.add(e.segment);
if sweep_line.intersects(Some(e.segment.edge_index), s.above)
|| sweep_line.intersects(Some(e.segment.edge_index), s.below)
{
return false;
}
}
Endpoint::Right => {
if let Some(s) = sweep_line.find(&e.segment) {
if sweep_line.intersects(s.above, s.below) {
return false;
}
sweep_line.remove(&e.segment);
}
}
}
}
true
}
#[cfg(test)]
mod tests {
use crate::{primitives::polygon::is_polygon_simple, Vec2};
#[test]
fn complex_polygon() {
// A square with one side punching through the opposite side.
let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y, Vec2::new(2.0, 0.5)];
assert!(!is_polygon_simple(&verts));
// A square with a vertex from one side touching the opposite side.
let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y, Vec2::new(1.0, 0.5)];
assert!(!is_polygon_simple(&verts));
// A square with one side touching the opposite side.
let verts = [
Vec2::ZERO,
Vec2::X,
Vec2::ONE,
Vec2::Y,
Vec2::new(1.0, 0.6),
Vec2::new(1.0, 0.4),
];
assert!(!is_polygon_simple(&verts));
// Four points lying on a line
let verts = [Vec2::ONE, Vec2::new(3., 2.), Vec2::new(5., 3.), Vec2::NEG_X];
assert!(!is_polygon_simple(&verts));
// Three points lying on a line
let verts = [Vec2::ONE, Vec2::new(3., 2.), Vec2::NEG_X];
assert!(!is_polygon_simple(&verts));
// Two identical points and one other point
let verts = [Vec2::ONE, Vec2::ONE, Vec2::NEG_X];
assert!(!is_polygon_simple(&verts));
// Two triangles with one shared side
let verts = [Vec2::ZERO, Vec2::X, Vec2::Y, Vec2::ONE, Vec2::X, Vec2::Y];
assert!(!is_polygon_simple(&verts));
}
#[test]
fn simple_polygon() {
// A square
let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y];
assert!(is_polygon_simple(&verts));
let verts = [];
assert!(is_polygon_simple(&verts));
}
}

View File

@@ -0,0 +1,67 @@
//! This module defines serialization/deserialization for const generic arrays.
//! Unlike serde's default behavior, it supports arbitrarily large arrays.
//! The code is based on this github comment:
//! <https://github.com/serde-rs/serde/issues/1937#issuecomment-812137971>
pub(crate) mod array {
use core::marker::PhantomData;
use serde::{
de::{SeqAccess, Visitor},
ser::SerializeTuple,
Deserialize, Deserializer, Serialize, Serializer,
};
pub fn serialize<S: Serializer, T: Serialize, const N: usize>(
data: &[T; N],
ser: S,
) -> Result<S::Ok, S::Error> {
let mut s = ser.serialize_tuple(N)?;
for item in data {
s.serialize_element(item)?;
}
s.end()
}
struct GenericArrayVisitor<T, const N: usize>(PhantomData<T>);
impl<'de, T, const N: usize> Visitor<'de> for GenericArrayVisitor<T, N>
where
T: Deserialize<'de>,
{
type Value = [T; N];
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_fmt(format_args!("an array of length {}", N))
}
#[inline]
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut data = [const { Option::<T>::None }; N];
for element in data.iter_mut() {
match (seq.next_element())? {
Some(val) => *element = Some(val),
None => return Err(serde::de::Error::invalid_length(N, &self)),
}
}
let data = data.map(|value| match value {
Some(value) => value,
None => unreachable!(),
});
Ok(data)
}
}
pub fn deserialize<'de, D, T, const N: usize>(deserializer: D) -> Result<[T; N], D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
deserializer.deserialize_tuple(N, GenericArrayVisitor::<T, N>(PhantomData))
}
}

190
vendor/bevy_math/src/ray.rs vendored Normal file
View File

@@ -0,0 +1,190 @@
use crate::{
ops,
primitives::{InfinitePlane3d, Plane2d},
Dir2, Dir3, Vec2, Vec3,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// An infinite half-line starting at `origin` and going in `direction` in 2D space.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Deserialize, Serialize)
)]
pub struct Ray2d {
/// The origin of the ray.
pub origin: Vec2,
/// The direction of the ray.
pub direction: Dir2,
}
impl Ray2d {
/// Create a new `Ray2d` from a given origin and direction
#[inline]
pub const fn new(origin: Vec2, direction: Dir2) -> Self {
Self { origin, direction }
}
/// Get a point at a given distance along the ray
#[inline]
pub fn get_point(&self, distance: f32) -> Vec2 {
self.origin + *self.direction * distance
}
/// Get the distance to a plane if the ray intersects it
#[inline]
pub fn intersect_plane(&self, plane_origin: Vec2, plane: Plane2d) -> Option<f32> {
let denominator = plane.normal.dot(*self.direction);
if ops::abs(denominator) > f32::EPSILON {
let distance = (plane_origin - self.origin).dot(*plane.normal) / denominator;
if distance > f32::EPSILON {
return Some(distance);
}
}
None
}
}
/// An infinite half-line starting at `origin` and going in `direction` in 3D space.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Deserialize, Serialize)
)]
pub struct Ray3d {
/// The origin of the ray.
pub origin: Vec3,
/// The direction of the ray.
pub direction: Dir3,
}
impl Ray3d {
/// Create a new `Ray3d` from a given origin and direction
#[inline]
pub const fn new(origin: Vec3, direction: Dir3) -> Self {
Self { origin, direction }
}
/// Get a point at a given distance along the ray
#[inline]
pub fn get_point(&self, distance: f32) -> Vec3 {
self.origin + *self.direction * distance
}
/// Get the distance to a plane if the ray intersects it
#[inline]
pub fn intersect_plane(&self, plane_origin: Vec3, plane: InfinitePlane3d) -> Option<f32> {
let denominator = plane.normal.dot(*self.direction);
if ops::abs(denominator) > f32::EPSILON {
let distance = (plane_origin - self.origin).dot(*plane.normal) / denominator;
if distance > f32::EPSILON {
return Some(distance);
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intersect_plane_2d() {
let ray = Ray2d::new(Vec2::ZERO, Dir2::Y);
// Orthogonal, and test that an inverse plane_normal has the same result
assert_eq!(
ray.intersect_plane(Vec2::Y, Plane2d::new(Vec2::Y)),
Some(1.0)
);
assert_eq!(
ray.intersect_plane(Vec2::Y, Plane2d::new(Vec2::NEG_Y)),
Some(1.0)
);
assert!(ray
.intersect_plane(Vec2::NEG_Y, Plane2d::new(Vec2::Y))
.is_none());
assert!(ray
.intersect_plane(Vec2::NEG_Y, Plane2d::new(Vec2::NEG_Y))
.is_none());
// Diagonal
assert_eq!(
ray.intersect_plane(Vec2::Y, Plane2d::new(Vec2::ONE)),
Some(1.0)
);
assert!(ray
.intersect_plane(Vec2::NEG_Y, Plane2d::new(Vec2::ONE))
.is_none());
// Parallel
assert!(ray
.intersect_plane(Vec2::X, Plane2d::new(Vec2::X))
.is_none());
// Parallel with simulated rounding error
assert!(ray
.intersect_plane(Vec2::X, Plane2d::new(Vec2::X + Vec2::Y * f32::EPSILON))
.is_none());
}
#[test]
fn intersect_plane_3d() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::Z);
// Orthogonal, and test that an inverse plane_normal has the same result
assert_eq!(
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::Z)),
Some(1.0)
);
assert_eq!(
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::NEG_Z)),
Some(1.0)
);
assert!(ray
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::Z))
.is_none());
assert!(ray
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::NEG_Z))
.is_none());
// Diagonal
assert_eq!(
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::ONE)),
Some(1.0)
);
assert!(ray
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::ONE))
.is_none());
// Parallel
assert!(ray
.intersect_plane(Vec3::X, InfinitePlane3d::new(Vec3::X))
.is_none());
// Parallel with simulated rounding error
assert!(ray
.intersect_plane(
Vec3::X,
InfinitePlane3d::new(Vec3::X + Vec3::Z * f32::EPSILON)
)
.is_none());
}
}

481
vendor/bevy_math/src/rects/irect.rs vendored Normal file
View File

@@ -0,0 +1,481 @@
use crate::{IVec2, Rect, URect};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A rectangle defined by two opposite corners.
///
/// The rectangle is axis aligned, and defined by its minimum and maximum coordinates,
/// stored in `IRect::min` and `IRect::max`, respectively. The minimum/maximum invariant
/// must be upheld by the user when directly assigning the fields, otherwise some methods
/// produce invalid results. It is generally recommended to use one of the constructor
/// methods instead, which will ensure this invariant is met, unless you already have
/// the minimum and maximum corners.
#[repr(C)]
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct IRect {
/// The minimum corner point of the rect.
pub min: IVec2,
/// The maximum corner point of the rect.
pub max: IVec2,
}
impl IRect {
/// An empty `IRect`, represented by maximum and minimum corner points
/// with `max == IVec2::MIN` and `min == IVec2::MAX`, so the
/// rect has an extremely large negative size.
/// This is useful, because when taking a union B of a non-empty `IRect` A and
/// this empty `IRect`, B will simply equal A.
pub const EMPTY: Self = Self {
max: IVec2::MIN,
min: IVec2::MAX,
};
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::IRect;
/// let r = IRect::new(0, 4, 10, 6); // w=10 h=2
/// let r = IRect::new(2, 3, 5, -1); // w=3 h=4
/// ```
#[inline]
pub fn new(x0: i32, y0: i32, x1: i32, y1: i32) -> Self {
Self::from_corners(IVec2::new(x0, y0), IVec2::new(x1, y1))
}
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// // Unit rect from [0,0] to [1,1]
/// let r = IRect::from_corners(IVec2::ZERO, IVec2::ONE); // w=1 h=1
/// // Same; the points do not need to be ordered
/// let r = IRect::from_corners(IVec2::ONE, IVec2::ZERO); // w=1 h=1
/// ```
#[inline]
pub fn from_corners(p0: IVec2, p1: IVec2) -> Self {
Self {
min: p0.min(p1),
max: p0.max(p1),
}
}
/// Create a new rectangle from its center and size.
///
/// # Rounding Behavior
///
/// If the size contains odd numbers they will be rounded down to the nearest whole number.
///
/// # Panics
///
/// This method panics if any of the components of the size is negative.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::from_center_size(IVec2::ZERO, IVec2::new(3, 2)); // w=2 h=2
/// assert_eq!(r.min, IVec2::splat(-1));
/// assert_eq!(r.max, IVec2::splat(1));
/// ```
#[inline]
pub fn from_center_size(origin: IVec2, size: IVec2) -> Self {
debug_assert!(size.cmpge(IVec2::ZERO).all(), "IRect size must be positive");
let half_size = size / 2;
Self::from_center_half_size(origin, half_size)
}
/// Create a new rectangle from its center and half-size.
///
/// # Panics
///
/// This method panics if any of the components of the half-size is negative.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::from_center_half_size(IVec2::ZERO, IVec2::ONE); // w=2 h=2
/// assert_eq!(r.min, IVec2::splat(-1));
/// assert_eq!(r.max, IVec2::splat(1));
/// ```
#[inline]
pub fn from_center_half_size(origin: IVec2, half_size: IVec2) -> Self {
assert!(
half_size.cmpge(IVec2::ZERO).all(),
"IRect half_size must be positive"
);
Self {
min: origin - half_size,
max: origin + half_size,
}
}
/// Check if the rectangle is empty.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::from_corners(IVec2::ZERO, IVec2::new(0, 1)); // w=0 h=1
/// assert!(r.is_empty());
/// ```
#[inline]
pub fn is_empty(&self) -> bool {
self.min.cmpge(self.max).any()
}
/// Rectangle width (max.x - min.x).
///
/// # Examples
///
/// ```
/// # use bevy_math::IRect;
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.width(), 5);
/// ```
#[inline]
pub fn width(&self) -> i32 {
self.max.x - self.min.x
}
/// Rectangle height (max.y - min.y).
///
/// # Examples
///
/// ```
/// # use bevy_math::IRect;
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.height(), 1);
/// ```
#[inline]
pub fn height(&self) -> i32 {
self.max.y - self.min.y
}
/// Rectangle size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.size(), IVec2::new(5, 1));
/// ```
#[inline]
pub fn size(&self) -> IVec2 {
self.max - self.min
}
/// Rectangle half-size.
///
/// # Rounding Behavior
///
/// If the full size contains odd numbers they will be rounded down to the nearest whole number when calculating the half size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::new(0, 0, 4, 3); // w=4 h=3
/// assert_eq!(r.half_size(), IVec2::new(2, 1));
/// ```
#[inline]
pub fn half_size(&self) -> IVec2 {
self.size() / 2
}
/// The center point of the rectangle.
///
/// # Rounding Behavior
///
/// If the (min + max) contains odd numbers they will be rounded down to the nearest whole number when calculating the center.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::new(0, 0, 5, 2); // w=5 h=2
/// assert_eq!(r.center(), IVec2::new(2, 1));
/// ```
#[inline]
pub fn center(&self) -> IVec2 {
(self.min + self.max) / 2
}
/// Check if a point lies within this rectangle, inclusive of its edges.
///
/// # Examples
///
/// ```
/// # use bevy_math::IRect;
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// assert!(r.contains(r.center()));
/// assert!(r.contains(r.min));
/// assert!(r.contains(r.max));
/// ```
#[inline]
pub fn contains(&self, point: IVec2) -> bool {
(point.cmpge(self.min) & point.cmple(self.max)).all()
}
/// Build a new rectangle formed of the union of this rectangle and another rectangle.
///
/// The union is the smallest rectangle enclosing both rectangles.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r1 = IRect::new(0, 0, 5, 1); // w=5 h=1
/// let r2 = IRect::new(1, -1, 3, 3); // w=2 h=4
/// let r = r1.union(r2);
/// assert_eq!(r.min, IVec2::new(0, -1));
/// assert_eq!(r.max, IVec2::new(5, 3));
/// ```
#[inline]
pub fn union(&self, other: Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
/// Build a new rectangle formed of the union of this rectangle and a point.
///
/// The union is the smallest rectangle enclosing both the rectangle and the point. If the
/// point is already inside the rectangle, this method returns a copy of the rectangle.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// let u = r.union_point(IVec2::new(3, 6));
/// assert_eq!(u.min, IVec2::ZERO);
/// assert_eq!(u.max, IVec2::new(5, 6));
/// ```
#[inline]
pub fn union_point(&self, other: IVec2) -> Self {
Self {
min: self.min.min(other),
max: self.max.max(other),
}
}
/// Build a new rectangle formed of the intersection of this rectangle and another rectangle.
///
/// The intersection is the largest rectangle enclosed in both rectangles. If the intersection
/// is empty, this method returns an empty rectangle ([`IRect::is_empty()`] returns `true`), but
/// the actual values of [`IRect::min`] and [`IRect::max`] are implementation-dependent.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r1 = IRect::new(0, 0, 5, 1); // w=5 h=1
/// let r2 = IRect::new(1, -1, 3, 3); // w=2 h=4
/// let r = r1.intersect(r2);
/// assert_eq!(r.min, IVec2::new(1, 0));
/// assert_eq!(r.max, IVec2::new(3, 1));
/// ```
#[inline]
pub fn intersect(&self, other: Self) -> Self {
let mut r = Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Create a new rectangle by expanding it evenly on all sides.
///
/// A positive expansion value produces a larger rectangle,
/// while a negative expansion value produces a smaller rectangle.
/// If this would result in zero or negative width or height, [`IRect::EMPTY`] is returned instead.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// let r2 = r.inflate(3); // w=11 h=7
/// assert_eq!(r2.min, IVec2::splat(-3));
/// assert_eq!(r2.max, IVec2::new(8, 4));
///
/// let r = IRect::new(0, -1, 4, 3); // w=4 h=4
/// let r2 = r.inflate(-1); // w=2 h=2
/// assert_eq!(r2.min, IVec2::new(1, 0));
/// assert_eq!(r2.max, IVec2::new(3, 2));
/// ```
#[inline]
pub fn inflate(&self, expansion: i32) -> Self {
let mut r = Self {
min: self.min - expansion,
max: self.max + expansion,
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Returns self as [`Rect`] (f32)
#[inline]
pub fn as_rect(&self) -> Rect {
Rect::from_corners(self.min.as_vec2(), self.max.as_vec2())
}
/// Returns self as [`URect`] (u32)
#[inline]
pub fn as_urect(&self) -> URect {
URect::from_corners(self.min.as_uvec2(), self.max.as_uvec2())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn well_formed() {
let r = IRect::from_center_size(IVec2::new(3, -5), IVec2::new(8, 12));
assert_eq!(r.min, IVec2::new(-1, -11));
assert_eq!(r.max, IVec2::new(7, 1));
assert_eq!(r.center(), IVec2::new(3, -5));
assert_eq!(r.width().abs(), 8);
assert_eq!(r.height().abs(), 12);
assert_eq!(r.size(), IVec2::new(8, 12));
assert_eq!(r.half_size(), IVec2::new(4, 6));
assert!(r.contains(IVec2::new(3, -5)));
assert!(r.contains(IVec2::new(-1, -10)));
assert!(r.contains(IVec2::new(-1, 0)));
assert!(r.contains(IVec2::new(7, -10)));
assert!(r.contains(IVec2::new(7, 0)));
assert!(!r.contains(IVec2::new(50, -5)));
}
#[test]
fn rect_union() {
let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2, -2] - [2, 2]
// overlapping
let r2 = IRect {
min: IVec2::new(1, 1),
max: IVec2::new(3, 3),
};
let u = r.union(r2);
assert_eq!(u.min, IVec2::new(-2, -2));
assert_eq!(u.max, IVec2::new(3, 3));
// disjoint
let r2 = IRect {
min: IVec2::new(1, 4),
max: IVec2::new(4, 6),
};
let u = r.union(r2);
assert_eq!(u.min, IVec2::new(-2, -2));
assert_eq!(u.max, IVec2::new(4, 6));
// included
let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(2));
let u = r.union(r2);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
// including
let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(6));
let u = r.union(r2);
assert_eq!(u.min, r2.min);
assert_eq!(u.min, r2.min);
}
#[test]
fn rect_union_pt() {
let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2,-2] - [2,2]
// inside
let v = IVec2::new(1, -1);
let u = r.union_point(v);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
// outside
let v = IVec2::new(10, -3);
let u = r.union_point(v);
assert_eq!(u.min, IVec2::new(-2, -3));
assert_eq!(u.max, IVec2::new(10, 2));
}
#[test]
fn rect_intersect() {
let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(8)); // [-4,-4] - [4,4]
// overlapping
let r2 = IRect {
min: IVec2::new(2, 2),
max: IVec2::new(6, 6),
};
let u = r.intersect(r2);
assert_eq!(u.min, IVec2::new(2, 2));
assert_eq!(u.max, IVec2::new(4, 4));
// disjoint
let r2 = IRect {
min: IVec2::new(-8, -2),
max: IVec2::new(-6, 2),
};
let u = r.intersect(r2);
assert!(u.is_empty());
assert_eq!(u.width(), 0);
// included
let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(2));
let u = r.intersect(r2);
assert_eq!(u.min, r2.min);
assert_eq!(u.max, r2.max);
// including
let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(10));
let u = r.intersect(r2);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
}
#[test]
fn rect_inflate() {
let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2,-2] - [2,2]
let r2 = r.inflate(2);
assert_eq!(r2.min, IVec2::new(-4, -4));
assert_eq!(r2.max, IVec2::new(4, 4));
}
}

7
vendor/bevy_math/src/rects/mod.rs vendored Normal file
View File

@@ -0,0 +1,7 @@
mod irect;
mod rect;
mod urect;
pub use irect::IRect;
pub use rect::Rect;
pub use urect::URect;

495
vendor/bevy_math/src/rects/rect.rs vendored Normal file
View File

@@ -0,0 +1,495 @@
use crate::{IRect, URect, Vec2};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A rectangle defined by two opposite corners.
///
/// The rectangle is axis aligned, and defined by its minimum and maximum coordinates,
/// stored in `Rect::min` and `Rect::max`, respectively. The minimum/maximum invariant
/// must be upheld by the user when directly assigning the fields, otherwise some methods
/// produce invalid results. It is generally recommended to use one of the constructor
/// methods instead, which will ensure this invariant is met, unless you already have
/// the minimum and maximum corners.
#[repr(C)]
#[derive(Default, Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Rect {
/// The minimum corner point of the rect.
pub min: Vec2,
/// The maximum corner point of the rect.
pub max: Vec2,
}
impl Rect {
/// An empty `Rect`, represented by maximum and minimum corner points
/// at `Vec2::NEG_INFINITY` and `Vec2::INFINITY`, respectively.
/// This is so the `Rect` has a infinitely negative size.
/// This is useful, because when taking a union B of a non-empty `Rect` A and
/// this empty `Rect`, B will simply equal A.
pub const EMPTY: Self = Self {
max: Vec2::NEG_INFINITY,
min: Vec2::INFINITY,
};
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::Rect;
/// let r = Rect::new(0., 4., 10., 6.); // w=10 h=2
/// let r = Rect::new(2., 3., 5., -1.); // w=3 h=4
/// ```
#[inline]
pub fn new(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
Self::from_corners(Vec2::new(x0, y0), Vec2::new(x1, y1))
}
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// // Unit rect from [0,0] to [1,1]
/// let r = Rect::from_corners(Vec2::ZERO, Vec2::ONE); // w=1 h=1
/// // Same; the points do not need to be ordered
/// let r = Rect::from_corners(Vec2::ONE, Vec2::ZERO); // w=1 h=1
/// ```
#[inline]
pub fn from_corners(p0: Vec2, p1: Vec2) -> Self {
Self {
min: p0.min(p1),
max: p0.max(p1),
}
}
/// Create a new rectangle from its center and size.
///
/// # Panics
///
/// This method panics if any of the components of the size is negative.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // w=1 h=1
/// assert!(r.min.abs_diff_eq(Vec2::splat(-0.5), 1e-5));
/// assert!(r.max.abs_diff_eq(Vec2::splat(0.5), 1e-5));
/// ```
#[inline]
pub fn from_center_size(origin: Vec2, size: Vec2) -> Self {
assert!(size.cmpge(Vec2::ZERO).all(), "Rect size must be positive");
let half_size = size / 2.;
Self::from_center_half_size(origin, half_size)
}
/// Create a new rectangle from its center and half-size.
///
/// # Panics
///
/// This method panics if any of the components of the half-size is negative.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::from_center_half_size(Vec2::ZERO, Vec2::ONE); // w=2 h=2
/// assert!(r.min.abs_diff_eq(Vec2::splat(-1.), 1e-5));
/// assert!(r.max.abs_diff_eq(Vec2::splat(1.), 1e-5));
/// ```
#[inline]
pub fn from_center_half_size(origin: Vec2, half_size: Vec2) -> Self {
assert!(
half_size.cmpge(Vec2::ZERO).all(),
"Rect half_size must be positive"
);
Self {
min: origin - half_size,
max: origin + half_size,
}
}
/// Check if the rectangle is empty.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::from_corners(Vec2::ZERO, Vec2::new(0., 1.)); // w=0 h=1
/// assert!(r.is_empty());
/// ```
#[inline]
pub fn is_empty(&self) -> bool {
self.min.cmpge(self.max).any()
}
/// Rectangle width (max.x - min.x).
///
/// # Examples
///
/// ```
/// # use bevy_math::Rect;
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!((r.width() - 5.).abs() <= 1e-5);
/// ```
#[inline]
pub fn width(&self) -> f32 {
self.max.x - self.min.x
}
/// Rectangle height (max.y - min.y).
///
/// # Examples
///
/// ```
/// # use bevy_math::Rect;
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!((r.height() - 1.).abs() <= 1e-5);
/// ```
#[inline]
pub fn height(&self) -> f32 {
self.max.y - self.min.y
}
/// Rectangle size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!(r.size().abs_diff_eq(Vec2::new(5., 1.), 1e-5));
/// ```
#[inline]
pub fn size(&self) -> Vec2 {
self.max - self.min
}
/// Rectangle half-size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!(r.half_size().abs_diff_eq(Vec2::new(2.5, 0.5), 1e-5));
/// ```
#[inline]
pub fn half_size(&self) -> Vec2 {
self.size() * 0.5
}
/// The center point of the rectangle.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!(r.center().abs_diff_eq(Vec2::new(2.5, 0.5), 1e-5));
/// ```
#[inline]
pub fn center(&self) -> Vec2 {
(self.min + self.max) * 0.5
}
/// Check if a point lies within this rectangle, inclusive of its edges.
///
/// # Examples
///
/// ```
/// # use bevy_math::Rect;
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!(r.contains(r.center()));
/// assert!(r.contains(r.min));
/// assert!(r.contains(r.max));
/// ```
#[inline]
pub fn contains(&self, point: Vec2) -> bool {
(point.cmpge(self.min) & point.cmple(self.max)).all()
}
/// Build a new rectangle formed of the union of this rectangle and another rectangle.
///
/// The union is the smallest rectangle enclosing both rectangles.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r1 = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// let r2 = Rect::new(1., -1., 3., 3.); // w=2 h=4
/// let r = r1.union(r2);
/// assert!(r.min.abs_diff_eq(Vec2::new(0., -1.), 1e-5));
/// assert!(r.max.abs_diff_eq(Vec2::new(5., 3.), 1e-5));
/// ```
#[inline]
pub fn union(&self, other: Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
/// Build a new rectangle formed of the union of this rectangle and a point.
///
/// The union is the smallest rectangle enclosing both the rectangle and the point. If the
/// point is already inside the rectangle, this method returns a copy of the rectangle.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// let u = r.union_point(Vec2::new(3., 6.));
/// assert!(u.min.abs_diff_eq(Vec2::ZERO, 1e-5));
/// assert!(u.max.abs_diff_eq(Vec2::new(5., 6.), 1e-5));
/// ```
#[inline]
pub fn union_point(&self, other: Vec2) -> Self {
Self {
min: self.min.min(other),
max: self.max.max(other),
}
}
/// Build a new rectangle formed of the intersection of this rectangle and another rectangle.
///
/// The intersection is the largest rectangle enclosed in both rectangles. If the intersection
/// is empty, this method returns an empty rectangle ([`Rect::is_empty()`] returns `true`), but
/// the actual values of [`Rect::min`] and [`Rect::max`] are implementation-dependent.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r1 = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// let r2 = Rect::new(1., -1., 3., 3.); // w=2 h=4
/// let r = r1.intersect(r2);
/// assert!(r.min.abs_diff_eq(Vec2::new(1., 0.), 1e-5));
/// assert!(r.max.abs_diff_eq(Vec2::new(3., 1.), 1e-5));
/// ```
#[inline]
pub fn intersect(&self, other: Self) -> Self {
let mut r = Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Create a new rectangle by expanding it evenly on all sides.
///
/// A positive expansion value produces a larger rectangle,
/// while a negative expansion value produces a smaller rectangle.
/// If this would result in zero or negative width or height, [`Rect::EMPTY`] is returned instead.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// let r2 = r.inflate(3.); // w=11 h=7
/// assert!(r2.min.abs_diff_eq(Vec2::splat(-3.), 1e-5));
/// assert!(r2.max.abs_diff_eq(Vec2::new(8., 4.), 1e-5));
///
/// let r = Rect::new(0., -1., 6., 7.); // w=6 h=8
/// let r2 = r.inflate(-2.); // w=11 h=7
/// assert!(r2.min.abs_diff_eq(Vec2::new(2., 1.), 1e-5));
/// assert!(r2.max.abs_diff_eq(Vec2::new(4., 5.), 1e-5));
/// ```
#[inline]
pub fn inflate(&self, expansion: f32) -> Self {
let mut r = Self {
min: self.min - expansion,
max: self.max + expansion,
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Build a new rectangle from this one with its coordinates expressed
/// relative to `other` in a normalized ([0..1] x [0..1]) coordinate system.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(2., 3., 4., 6.);
/// let s = Rect::new(0., 0., 10., 10.);
/// let n = r.normalize(s);
///
/// assert_eq!(n.min.x, 0.2);
/// assert_eq!(n.min.y, 0.3);
/// assert_eq!(n.max.x, 0.4);
/// assert_eq!(n.max.y, 0.6);
/// ```
pub fn normalize(&self, other: Self) -> Self {
let outer_size = other.size();
Self {
min: (self.min - other.min) / outer_size,
max: (self.max - other.min) / outer_size,
}
}
/// Returns self as [`IRect`] (i32)
#[inline]
pub fn as_irect(&self) -> IRect {
IRect::from_corners(self.min.as_ivec2(), self.max.as_ivec2())
}
/// Returns self as [`URect`] (u32)
#[inline]
pub fn as_urect(&self) -> URect {
URect::from_corners(self.min.as_uvec2(), self.max.as_uvec2())
}
}
#[cfg(test)]
mod tests {
use crate::ops;
use super::*;
#[test]
fn well_formed() {
let r = Rect::from_center_size(Vec2::new(3., -5.), Vec2::new(8., 11.));
assert!(r.min.abs_diff_eq(Vec2::new(-1., -10.5), 1e-5));
assert!(r.max.abs_diff_eq(Vec2::new(7., 0.5), 1e-5));
assert!(r.center().abs_diff_eq(Vec2::new(3., -5.), 1e-5));
assert!(ops::abs(r.width() - 8.) <= 1e-5);
assert!(ops::abs(r.height() - 11.) <= 1e-5);
assert!(r.size().abs_diff_eq(Vec2::new(8., 11.), 1e-5));
assert!(r.half_size().abs_diff_eq(Vec2::new(4., 5.5), 1e-5));
assert!(r.contains(Vec2::new(3., -5.)));
assert!(r.contains(Vec2::new(-1., -10.5)));
assert!(r.contains(Vec2::new(-1., 0.5)));
assert!(r.contains(Vec2::new(7., -10.5)));
assert!(r.contains(Vec2::new(7., 0.5)));
assert!(!r.contains(Vec2::new(50., -5.)));
}
#[test]
fn rect_union() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
// overlapping
let r2 = Rect {
min: Vec2::new(-0.8, 0.3),
max: Vec2::new(0.1, 0.7),
};
let u = r.union(r2);
assert!(u.min.abs_diff_eq(Vec2::new(-0.8, -0.5), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(0.5, 0.7), 1e-5));
// disjoint
let r2 = Rect {
min: Vec2::new(-1.8, -0.5),
max: Vec2::new(-1.5, 0.3),
};
let u = r.union(r2);
assert!(u.min.abs_diff_eq(Vec2::new(-1.8, -0.5), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(0.5, 0.5), 1e-5));
// included
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(0.5));
let u = r.union(r2);
assert!(u.min.abs_diff_eq(r.min, 1e-5));
assert!(u.max.abs_diff_eq(r.max, 1e-5));
// including
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(1.5));
let u = r.union(r2);
assert!(u.min.abs_diff_eq(r2.min, 1e-5));
assert!(u.max.abs_diff_eq(r2.max, 1e-5));
}
#[test]
fn rect_union_pt() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
// inside
let v = Vec2::new(0.3, -0.2);
let u = r.union_point(v);
assert!(u.min.abs_diff_eq(r.min, 1e-5));
assert!(u.max.abs_diff_eq(r.max, 1e-5));
// outside
let v = Vec2::new(10., -3.);
let u = r.union_point(v);
assert!(u.min.abs_diff_eq(Vec2::new(-0.5, -3.), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(10., 0.5), 1e-5));
}
#[test]
fn rect_intersect() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
// overlapping
let r2 = Rect {
min: Vec2::new(-0.8, 0.3),
max: Vec2::new(0.1, 0.7),
};
let u = r.intersect(r2);
assert!(u.min.abs_diff_eq(Vec2::new(-0.5, 0.3), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(0.1, 0.5), 1e-5));
// disjoint
let r2 = Rect {
min: Vec2::new(-1.8, -0.5),
max: Vec2::new(-1.5, 0.3),
};
let u = r.intersect(r2);
assert!(u.is_empty());
assert!(u.width() <= 1e-5);
// included
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(0.5));
let u = r.intersect(r2);
assert!(u.min.abs_diff_eq(r2.min, 1e-5));
assert!(u.max.abs_diff_eq(r2.max, 1e-5));
// including
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(1.5));
let u = r.intersect(r2);
assert!(u.min.abs_diff_eq(r.min, 1e-5));
assert!(u.max.abs_diff_eq(r.max, 1e-5));
}
#[test]
fn rect_inflate() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
let r2 = r.inflate(0.3);
assert!(r2.min.abs_diff_eq(Vec2::new(-0.8, -0.8), 1e-5));
assert!(r2.max.abs_diff_eq(Vec2::new(0.8, 0.8), 1e-5));
}
}

484
vendor/bevy_math/src/rects/urect.rs vendored Normal file
View File

@@ -0,0 +1,484 @@
use crate::{IRect, Rect, UVec2};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A rectangle defined by two opposite corners.
///
/// The rectangle is axis aligned, and defined by its minimum and maximum coordinates,
/// stored in `URect::min` and `URect::max`, respectively. The minimum/maximum invariant
/// must be upheld by the user when directly assigning the fields, otherwise some methods
/// produce invalid results. It is generally recommended to use one of the constructor
/// methods instead, which will ensure this invariant is met, unless you already have
/// the minimum and maximum corners.
#[repr(C)]
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct URect {
/// The minimum corner point of the rect.
pub min: UVec2,
/// The maximum corner point of the rect.
pub max: UVec2,
}
impl URect {
/// An empty `URect`, represented by maximum and minimum corner points
/// with `max == UVec2::MIN` and `min == UVec2::MAX`, so the
/// rect has an extremely large negative size.
/// This is useful, because when taking a union B of a non-empty `URect` A and
/// this empty `URect`, B will simply equal A.
pub const EMPTY: Self = Self {
max: UVec2::MIN,
min: UVec2::MAX,
};
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::URect;
/// let r = URect::new(0, 4, 10, 6); // w=10 h=2
/// let r = URect::new(2, 4, 5, 0); // w=3 h=4
/// ```
#[inline]
pub fn new(x0: u32, y0: u32, x1: u32, y1: u32) -> Self {
Self::from_corners(UVec2::new(x0, y0), UVec2::new(x1, y1))
}
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// // Unit rect from [0,0] to [1,1]
/// let r = URect::from_corners(UVec2::ZERO, UVec2::ONE); // w=1 h=1
/// // Same; the points do not need to be ordered
/// let r = URect::from_corners(UVec2::ONE, UVec2::ZERO); // w=1 h=1
/// ```
#[inline]
pub fn from_corners(p0: UVec2, p1: UVec2) -> Self {
Self {
min: p0.min(p1),
max: p0.max(p1),
}
}
/// Create a new rectangle from its center and size.
///
/// # Rounding Behavior
///
/// If the size contains odd numbers they will be rounded down to the nearest whole number.
///
/// # Panics
///
/// This method panics if any of the components of the size is negative or if `origin - (size / 2)` results in any negatives.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::from_center_size(UVec2::ONE, UVec2::splat(2)); // w=2 h=2
/// assert_eq!(r.min, UVec2::splat(0));
/// assert_eq!(r.max, UVec2::splat(2));
/// ```
#[inline]
pub fn from_center_size(origin: UVec2, size: UVec2) -> Self {
assert!(origin.cmpge(size / 2).all(), "Origin must always be greater than or equal to (size / 2) otherwise the rectangle is undefined! Origin was {origin} and size was {size}");
let half_size = size / 2;
Self::from_center_half_size(origin, half_size)
}
/// Create a new rectangle from its center and half-size.
///
/// # Panics
///
/// This method panics if any of the components of the half-size is negative or if `origin - half_size` results in any negatives.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::from_center_half_size(UVec2::ONE, UVec2::ONE); // w=2 h=2
/// assert_eq!(r.min, UVec2::splat(0));
/// assert_eq!(r.max, UVec2::splat(2));
/// ```
#[inline]
pub fn from_center_half_size(origin: UVec2, half_size: UVec2) -> Self {
assert!(origin.cmpge(half_size).all(), "Origin must always be greater than or equal to half_size otherwise the rectangle is undefined! Origin was {origin} and half_size was {half_size}");
Self {
min: origin - half_size,
max: origin + half_size,
}
}
/// Check if the rectangle is empty.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::from_corners(UVec2::ZERO, UVec2::new(0, 1)); // w=0 h=1
/// assert!(r.is_empty());
/// ```
#[inline]
pub fn is_empty(&self) -> bool {
self.min.cmpge(self.max).any()
}
/// Rectangle width (max.x - min.x).
///
/// # Examples
///
/// ```
/// # use bevy_math::URect;
/// let r = URect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.width(), 5);
/// ```
#[inline]
pub const fn width(&self) -> u32 {
self.max.x - self.min.x
}
/// Rectangle height (max.y - min.y).
///
/// # Examples
///
/// ```
/// # use bevy_math::URect;
/// let r = URect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.height(), 1);
/// ```
#[inline]
pub const fn height(&self) -> u32 {
self.max.y - self.min.y
}
/// Rectangle size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.size(), UVec2::new(5, 1));
/// ```
#[inline]
pub fn size(&self) -> UVec2 {
self.max - self.min
}
/// Rectangle half-size.
///
/// # Rounding Behavior
///
/// If the full size contains odd numbers they will be rounded down to the nearest whole number when calculating the half size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::new(0, 0, 4, 2); // w=4 h=2
/// assert_eq!(r.half_size(), UVec2::new(2, 1));
/// ```
#[inline]
pub fn half_size(&self) -> UVec2 {
self.size() / 2
}
/// The center point of the rectangle.
///
/// # Rounding Behavior
///
/// If the (min + max) contains odd numbers they will be rounded down to the nearest whole number when calculating the center.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::new(0, 0, 4, 2); // w=4 h=2
/// assert_eq!(r.center(), UVec2::new(2, 1));
/// ```
#[inline]
pub fn center(&self) -> UVec2 {
(self.min + self.max) / 2
}
/// Check if a point lies within this rectangle, inclusive of its edges.
///
/// # Examples
///
/// ```
/// # use bevy_math::URect;
/// let r = URect::new(0, 0, 5, 1); // w=5 h=1
/// assert!(r.contains(r.center()));
/// assert!(r.contains(r.min));
/// assert!(r.contains(r.max));
/// ```
#[inline]
pub fn contains(&self, point: UVec2) -> bool {
(point.cmpge(self.min) & point.cmple(self.max)).all()
}
/// Build a new rectangle formed of the union of this rectangle and another rectangle.
///
/// The union is the smallest rectangle enclosing both rectangles.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r1 = URect::new(0, 0, 5, 1); // w=5 h=1
/// let r2 = URect::new(1, 0, 3, 8); // w=2 h=4
/// let r = r1.union(r2);
/// assert_eq!(r.min, UVec2::new(0, 0));
/// assert_eq!(r.max, UVec2::new(5, 8));
/// ```
#[inline]
pub fn union(&self, other: Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
/// Build a new rectangle formed of the union of this rectangle and a point.
///
/// The union is the smallest rectangle enclosing both the rectangle and the point. If the
/// point is already inside the rectangle, this method returns a copy of the rectangle.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::new(0, 0, 5, 1); // w=5 h=1
/// let u = r.union_point(UVec2::new(3, 6));
/// assert_eq!(u.min, UVec2::ZERO);
/// assert_eq!(u.max, UVec2::new(5, 6));
/// ```
#[inline]
pub fn union_point(&self, other: UVec2) -> Self {
Self {
min: self.min.min(other),
max: self.max.max(other),
}
}
/// Build a new rectangle formed of the intersection of this rectangle and another rectangle.
///
/// The intersection is the largest rectangle enclosed in both rectangles. If the intersection
/// is empty, this method returns an empty rectangle ([`URect::is_empty()`] returns `true`), but
/// the actual values of [`URect::min`] and [`URect::max`] are implementation-dependent.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r1 = URect::new(0, 0, 2, 2); // w=2 h=2
/// let r2 = URect::new(1, 1, 3, 3); // w=2 h=2
/// let r = r1.intersect(r2);
/// assert_eq!(r.min, UVec2::new(1, 1));
/// assert_eq!(r.max, UVec2::new(2, 2));
/// ```
#[inline]
pub fn intersect(&self, other: Self) -> Self {
let mut r = Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Create a new rectangle by expanding it evenly on all sides.
///
/// A positive expansion value produces a larger rectangle,
/// while a negative expansion value produces a smaller rectangle.
/// If this would result in zero width or height, [`URect::EMPTY`] is returned instead.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::new(4, 4, 6, 6); // w=2 h=2
/// let r2 = r.inflate(1); // w=4 h=4
/// assert_eq!(r2.min, UVec2::splat(3));
/// assert_eq!(r2.max, UVec2::splat(7));
///
/// let r = URect::new(4, 4, 8, 8); // w=4 h=4
/// let r2 = r.inflate(-1); // w=2 h=2
/// assert_eq!(r2.min, UVec2::splat(5));
/// assert_eq!(r2.max, UVec2::splat(7));
/// ```
#[inline]
pub fn inflate(&self, expansion: i32) -> Self {
let mut r = Self {
min: UVec2::new(
self.min.x.saturating_add_signed(-expansion),
self.min.y.saturating_add_signed(-expansion),
),
max: UVec2::new(
self.max.x.saturating_add_signed(expansion),
self.max.y.saturating_add_signed(expansion),
),
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Returns self as [`Rect`] (f32)
#[inline]
pub fn as_rect(&self) -> Rect {
Rect::from_corners(self.min.as_vec2(), self.max.as_vec2())
}
/// Returns self as [`IRect`] (i32)
#[inline]
pub fn as_irect(&self) -> IRect {
IRect::from_corners(self.min.as_ivec2(), self.max.as_ivec2())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn well_formed() {
let r = URect::from_center_size(UVec2::new(10, 16), UVec2::new(8, 12));
assert_eq!(r.min, UVec2::new(6, 10));
assert_eq!(r.max, UVec2::new(14, 22));
assert_eq!(r.center(), UVec2::new(10, 16));
assert_eq!(r.width(), 8);
assert_eq!(r.height(), 12);
assert_eq!(r.size(), UVec2::new(8, 12));
assert_eq!(r.half_size(), UVec2::new(4, 6));
assert!(r.contains(UVec2::new(7, 10)));
assert!(r.contains(UVec2::new(14, 10)));
assert!(r.contains(UVec2::new(10, 22)));
assert!(r.contains(UVec2::new(6, 22)));
assert!(r.contains(UVec2::new(14, 22)));
assert!(!r.contains(UVec2::new(50, 5)));
}
#[test]
fn rect_union() {
let r = URect::from_center_size(UVec2::splat(4), UVec2::splat(4)); // [2, 2] - [6, 6]
// overlapping
let r2 = URect {
min: UVec2::new(0, 0),
max: UVec2::new(3, 3),
};
let u = r.union(r2);
assert_eq!(u.min, UVec2::new(0, 0));
assert_eq!(u.max, UVec2::new(6, 6));
// disjoint
let r2 = URect {
min: UVec2::new(4, 7),
max: UVec2::new(8, 8),
};
let u = r.union(r2);
assert_eq!(u.min, UVec2::new(2, 2));
assert_eq!(u.max, UVec2::new(8, 8));
// included
let r2 = URect::from_center_size(UVec2::splat(4), UVec2::splat(2));
let u = r.union(r2);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
// including
let r2 = URect::from_center_size(UVec2::splat(4), UVec2::splat(6));
let u = r.union(r2);
assert_eq!(u.min, r2.min);
assert_eq!(u.min, r2.min);
}
#[test]
fn rect_union_pt() {
let r = URect::from_center_size(UVec2::splat(4), UVec2::splat(4)); // [2, 2] - [6, 6]
// inside
let v = UVec2::new(2, 5);
let u = r.union_point(v);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
// outside
let v = UVec2::new(10, 5);
let u = r.union_point(v);
assert_eq!(u.min, UVec2::new(2, 2));
assert_eq!(u.max, UVec2::new(10, 6));
}
#[test]
fn rect_intersect() {
let r = URect::from_center_size(UVec2::splat(6), UVec2::splat(8)); // [2, 2] - [10, 10]
// overlapping
let r2 = URect {
min: UVec2::new(8, 8),
max: UVec2::new(12, 12),
};
let u = r.intersect(r2);
assert_eq!(u.min, UVec2::new(8, 8));
assert_eq!(u.max, UVec2::new(10, 10));
// disjoint
let r2 = URect {
min: UVec2::new(12, 12),
max: UVec2::new(14, 18),
};
let u = r.intersect(r2);
assert!(u.is_empty());
assert_eq!(u.width(), 0);
// included
let r2 = URect::from_center_size(UVec2::splat(6), UVec2::splat(2));
let u = r.intersect(r2);
assert_eq!(u.min, r2.min);
assert_eq!(u.max, r2.max);
// including
let r2 = URect::from_center_size(UVec2::splat(6), UVec2::splat(10));
let u = r.intersect(r2);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
}
#[test]
fn rect_inflate() {
let r = URect::from_center_size(UVec2::splat(6), UVec2::splat(6)); // [3, 3] - [9, 9]
let r2 = r.inflate(2);
assert_eq!(r2.min, UVec2::new(1, 1));
assert_eq!(r2.max, UVec2::new(11, 11));
}
}

724
vendor/bevy_math/src/rotation2d.rs vendored Normal file
View File

@@ -0,0 +1,724 @@
use core::f32::consts::TAU;
use glam::FloatExt;
use crate::{
ops,
prelude::{Mat2, Vec2},
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A counterclockwise 2D rotation.
///
/// # Example
///
/// ```
/// # use approx::assert_relative_eq;
/// # use bevy_math::{Rot2, Vec2};
/// use std::f32::consts::PI;
///
/// // Create rotations from radians or degrees
/// let rotation1 = Rot2::radians(PI / 2.0);
/// let rotation2 = Rot2::degrees(45.0);
///
/// // Get the angle back as radians or degrees
/// assert_eq!(rotation1.as_degrees(), 90.0);
/// assert_eq!(rotation2.as_radians(), PI / 4.0);
///
/// // "Add" rotations together using `*`
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rotation1 * rotation2, Rot2::degrees(135.0));
///
/// // Rotate vectors
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rotation1 * Vec2::X, Vec2::Y);
/// ```
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "rotation", alias = "rotation2d", alias = "rotation_2d")]
pub struct Rot2 {
/// The cosine of the rotation angle in radians.
///
/// This is the real part of the unit complex number representing the rotation.
pub cos: f32,
/// The sine of the rotation angle in radians.
///
/// This is the imaginary part of the unit complex number representing the rotation.
pub sin: f32,
}
impl Default for Rot2 {
fn default() -> Self {
Self::IDENTITY
}
}
impl Rot2 {
/// No rotation.
pub const IDENTITY: Self = Self { cos: 1.0, sin: 0.0 };
/// A rotation of π radians.
pub const PI: Self = Self {
cos: -1.0,
sin: 0.0,
};
/// A counterclockwise rotation of π/2 radians.
pub const FRAC_PI_2: Self = Self { cos: 0.0, sin: 1.0 };
/// A counterclockwise rotation of π/3 radians.
pub const FRAC_PI_3: Self = Self {
cos: 0.5,
sin: 0.866_025_4,
};
/// A counterclockwise rotation of π/4 radians.
pub const FRAC_PI_4: Self = Self {
cos: core::f32::consts::FRAC_1_SQRT_2,
sin: core::f32::consts::FRAC_1_SQRT_2,
};
/// A counterclockwise rotation of π/6 radians.
pub const FRAC_PI_6: Self = Self {
cos: 0.866_025_4,
sin: 0.5,
};
/// A counterclockwise rotation of π/8 radians.
pub const FRAC_PI_8: Self = Self {
cos: 0.923_879_5,
sin: 0.382_683_43,
};
/// Creates a [`Rot2`] from a counterclockwise angle in radians.
///
/// # Note
///
/// The input rotation will always be clamped to the range `(-π, π]` by design.
///
/// # Example
///
/// ```
/// # use bevy_math::Rot2;
/// # use approx::assert_relative_eq;
/// # use std::f32::consts::{FRAC_PI_2, PI};
///
/// let rot1 = Rot2::radians(3.0 * FRAC_PI_2);
/// let rot2 = Rot2::radians(-FRAC_PI_2);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1, rot2);
///
/// let rot3 = Rot2::radians(PI);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1 * rot1, rot3);
/// ```
#[inline]
pub fn radians(radians: f32) -> Self {
let (sin, cos) = ops::sin_cos(radians);
Self::from_sin_cos(sin, cos)
}
/// Creates a [`Rot2`] from a counterclockwise angle in degrees.
///
/// # Note
///
/// The input rotation will always be clamped to the range `(-180°, 180°]` by design.
///
/// # Example
///
/// ```
/// # use bevy_math::Rot2;
/// # use approx::assert_relative_eq;
///
/// let rot1 = Rot2::degrees(270.0);
/// let rot2 = Rot2::degrees(-90.0);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1, rot2);
///
/// let rot3 = Rot2::degrees(180.0);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1 * rot1, rot3);
/// ```
#[inline]
pub fn degrees(degrees: f32) -> Self {
Self::radians(degrees.to_radians())
}
/// Creates a [`Rot2`] from a counterclockwise fraction of a full turn of 360 degrees.
///
/// # Note
///
/// The input rotation will always be clamped to the range `(-50%, 50%]` by design.
///
/// # Example
///
/// ```
/// # use bevy_math::Rot2;
/// # use approx::assert_relative_eq;
///
/// let rot1 = Rot2::turn_fraction(0.75);
/// let rot2 = Rot2::turn_fraction(-0.25);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1, rot2);
///
/// let rot3 = Rot2::turn_fraction(0.5);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1 * rot1, rot3);
/// ```
#[inline]
pub fn turn_fraction(fraction: f32) -> Self {
Self::radians(TAU * fraction)
}
/// Creates a [`Rot2`] from the sine and cosine of an angle in radians.
///
/// The rotation is only valid if `sin * sin + cos * cos == 1.0`.
///
/// # Panics
///
/// Panics if `sin * sin + cos * cos != 1.0` when the `glam_assert` feature is enabled.
#[inline]
pub fn from_sin_cos(sin: f32, cos: f32) -> Self {
let rotation = Self { sin, cos };
debug_assert!(
rotation.is_normalized(),
"the given sine and cosine produce an invalid rotation"
);
rotation
}
/// Returns the rotation in radians in the `(-pi, pi]` range.
#[inline]
pub fn as_radians(self) -> f32 {
ops::atan2(self.sin, self.cos)
}
/// Returns the rotation in degrees in the `(-180, 180]` range.
#[inline]
pub fn as_degrees(self) -> f32 {
self.as_radians().to_degrees()
}
/// Returns the rotation as a fraction of a full 360 degree turn.
#[inline]
pub fn as_turn_fraction(self) -> f32 {
self.as_radians() / TAU
}
/// Returns the sine and cosine of the rotation angle in radians.
#[inline]
pub const fn sin_cos(self) -> (f32, f32) {
(self.sin, self.cos)
}
/// Computes the length or norm of the complex number used to represent the rotation.
///
/// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations
/// can be a result of incorrect construction or floating point error caused by
/// successive operations.
#[inline]
#[doc(alias = "norm")]
pub fn length(self) -> f32 {
Vec2::new(self.sin, self.cos).length()
}
/// Computes the squared length or norm of the complex number used to represent the rotation.
///
/// This is generally faster than [`Rot2::length()`], as it avoids a square
/// root operation.
///
/// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations
/// can be a result of incorrect construction or floating point error caused by
/// successive operations.
#[inline]
#[doc(alias = "norm2")]
pub fn length_squared(self) -> f32 {
Vec2::new(self.sin, self.cos).length_squared()
}
/// Computes `1.0 / self.length()`.
///
/// For valid results, `self` must _not_ have a length of zero.
#[inline]
pub fn length_recip(self) -> f32 {
Vec2::new(self.sin, self.cos).length_recip()
}
/// Returns `self` with a length of `1.0` if possible, and `None` otherwise.
///
/// `None` will be returned if the sine and cosine of `self` are both zero (or very close to zero),
/// or if either of them is NaN or infinite.
///
/// Note that [`Rot2`] should typically already be normalized by design.
/// Manual normalization is only needed when successive operations result in
/// accumulated floating point error, or if the rotation was constructed
/// with invalid values.
#[inline]
pub fn try_normalize(self) -> Option<Self> {
let recip = self.length_recip();
if recip.is_finite() && recip > 0.0 {
Some(Self::from_sin_cos(self.sin * recip, self.cos * recip))
} else {
None
}
}
/// Returns `self` with a length of `1.0`.
///
/// Note that [`Rot2`] should typically already be normalized by design.
/// Manual normalization is only needed when successive operations result in
/// accumulated floating point error, or if the rotation was constructed
/// with invalid values.
///
/// # Panics
///
/// Panics if `self` has a length of zero, NaN, or infinity when debug assertions are enabled.
#[inline]
pub fn normalize(self) -> Self {
let length_recip = self.length_recip();
Self::from_sin_cos(self.sin * length_recip, self.cos * length_recip)
}
/// Returns `self` after an approximate normalization, assuming the value is already nearly normalized.
/// Useful for preventing numerical error accumulation.
/// See [`Dir3::fast_renormalize`](crate::Dir3::fast_renormalize) for an example of when such error accumulation might occur.
#[inline]
pub fn fast_renormalize(self) -> Self {
let length_squared = self.length_squared();
// Based on a Taylor approximation of the inverse square root, see [`Dir3::fast_renormalize`](crate::Dir3::fast_renormalize) for more details.
let length_recip_approx = 0.5 * (3.0 - length_squared);
Rot2 {
sin: self.sin * length_recip_approx,
cos: self.cos * length_recip_approx,
}
}
/// Returns `true` if the rotation is neither infinite nor NaN.
#[inline]
pub fn is_finite(self) -> bool {
self.sin.is_finite() && self.cos.is_finite()
}
/// Returns `true` if the rotation is NaN.
#[inline]
pub fn is_nan(self) -> bool {
self.sin.is_nan() || self.cos.is_nan()
}
/// Returns whether `self` has a length of `1.0` or not.
///
/// Uses a precision threshold of approximately `1e-4`.
#[inline]
pub fn is_normalized(self) -> bool {
// The allowed length is 1 +/- 1e-4, so the largest allowed
// squared length is (1 + 1e-4)^2 = 1.00020001, which makes
// the threshold for the squared length approximately 2e-4.
ops::abs(self.length_squared() - 1.0) <= 2e-4
}
/// Returns `true` if the rotation is near [`Rot2::IDENTITY`].
#[inline]
pub fn is_near_identity(self) -> bool {
// Same as `Quat::is_near_identity`, but using sine and cosine
let threshold_angle_sin = 0.000_049_692_047; // let threshold_angle = 0.002_847_144_6;
self.cos > 0.0 && ops::abs(self.sin) < threshold_angle_sin
}
/// Returns the angle in radians needed to make `self` and `other` coincide.
#[inline]
pub fn angle_to(self, other: Self) -> f32 {
(other * self.inverse()).as_radians()
}
/// Returns the inverse of the rotation. This is also the conjugate
/// of the unit complex number representing the rotation.
#[inline]
#[must_use]
#[doc(alias = "conjugate")]
pub const fn inverse(self) -> Self {
Self {
cos: self.cos,
sin: -self.sin,
}
}
/// Performs a linear interpolation between `self` and `rhs` based on
/// the value `s`, and normalizes the rotation afterwards.
///
/// When `s == 0.0`, the result will be equal to `self`.
/// When `s == 1.0`, the result will be equal to `rhs`.
///
/// This is slightly more efficient than [`slerp`](Self::slerp), and produces a similar result
/// when the difference between the two rotations is small. At larger differences,
/// the result resembles a kind of ease-in-out effect.
///
/// If you would like the angular velocity to remain constant, consider using [`slerp`](Self::slerp) instead.
///
/// # Details
///
/// `nlerp` corresponds to computing an angle for a point at position `s` on a line drawn
/// between the endpoints of the arc formed by `self` and `rhs` on a unit circle,
/// and normalizing the result afterwards.
///
/// Note that if the angles are opposite like 0 and π, the line will pass through the origin,
/// and the resulting angle will always be either `self` or `rhs` depending on `s`.
/// If `s` happens to be `0.5` in this case, a valid rotation cannot be computed, and `self`
/// will be returned as a fallback.
///
/// # Example
///
/// ```
/// # use bevy_math::Rot2;
/// #
/// let rot1 = Rot2::IDENTITY;
/// let rot2 = Rot2::degrees(135.0);
///
/// let result1 = rot1.nlerp(rot2, 1.0 / 3.0);
/// assert_eq!(result1.as_degrees(), 28.675055);
///
/// let result2 = rot1.nlerp(rot2, 0.5);
/// assert_eq!(result2.as_degrees(), 67.5);
/// ```
#[inline]
pub fn nlerp(self, end: Self, s: f32) -> Self {
Self {
sin: self.sin.lerp(end.sin, s),
cos: self.cos.lerp(end.cos, s),
}
.try_normalize()
// Fall back to the start rotation.
// This can happen when `self` and `end` are opposite angles and `s == 0.5`,
// because the resulting rotation would be zero, which cannot be normalized.
.unwrap_or(self)
}
/// Performs a spherical linear interpolation between `self` and `end`
/// based on the value `s`.
///
/// This corresponds to interpolating between the two angles at a constant angular velocity.
///
/// When `s == 0.0`, the result will be equal to `self`.
/// When `s == 1.0`, the result will be equal to `rhs`.
///
/// If you would like the rotation to have a kind of ease-in-out effect, consider
/// using the slightly more efficient [`nlerp`](Self::nlerp) instead.
///
/// # Example
///
/// ```
/// # use bevy_math::Rot2;
/// #
/// let rot1 = Rot2::IDENTITY;
/// let rot2 = Rot2::degrees(135.0);
///
/// let result1 = rot1.slerp(rot2, 1.0 / 3.0);
/// assert_eq!(result1.as_degrees(), 45.0);
///
/// let result2 = rot1.slerp(rot2, 0.5);
/// assert_eq!(result2.as_degrees(), 67.5);
/// ```
#[inline]
pub fn slerp(self, end: Self, s: f32) -> Self {
self * Self::radians(self.angle_to(end) * s)
}
}
impl From<f32> for Rot2 {
/// Creates a [`Rot2`] from a counterclockwise angle in radians.
fn from(rotation: f32) -> Self {
Self::radians(rotation)
}
}
impl From<Rot2> for Mat2 {
/// Creates a [`Mat2`] rotation matrix from a [`Rot2`].
fn from(rot: Rot2) -> Self {
Mat2::from_cols_array(&[rot.cos, -rot.sin, rot.sin, rot.cos])
}
}
impl core::ops::Mul for Rot2 {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
cos: self.cos * rhs.cos - self.sin * rhs.sin,
sin: self.sin * rhs.cos + self.cos * rhs.sin,
}
}
}
impl core::ops::MulAssign for Rot2 {
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl core::ops::Mul<Vec2> for Rot2 {
type Output = Vec2;
/// Rotates a [`Vec2`] by a [`Rot2`].
fn mul(self, rhs: Vec2) -> Self::Output {
Vec2::new(
rhs.x * self.cos - rhs.y * self.sin,
rhs.x * self.sin + rhs.y * self.cos,
)
}
}
#[cfg(any(feature = "approx", test))]
impl approx::AbsDiffEq for Rot2 {
type Epsilon = f32;
fn default_epsilon() -> f32 {
f32::EPSILON
}
fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
self.cos.abs_diff_eq(&other.cos, epsilon) && self.sin.abs_diff_eq(&other.sin, epsilon)
}
}
#[cfg(any(feature = "approx", test))]
impl approx::RelativeEq for Rot2 {
fn default_max_relative() -> f32 {
f32::EPSILON
}
fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
self.cos.relative_eq(&other.cos, epsilon, max_relative)
&& self.sin.relative_eq(&other.sin, epsilon, max_relative)
}
}
#[cfg(any(feature = "approx", test))]
impl approx::UlpsEq for Rot2 {
fn default_max_ulps() -> u32 {
4
}
fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {
self.cos.ulps_eq(&other.cos, epsilon, max_ulps)
&& self.sin.ulps_eq(&other.sin, epsilon, max_ulps)
}
}
#[cfg(test)]
mod tests {
use core::f32::consts::FRAC_PI_2;
use approx::assert_relative_eq;
use crate::{ops, Dir2, Rot2, Vec2};
#[test]
fn creation() {
let rotation1 = Rot2::radians(FRAC_PI_2);
let rotation2 = Rot2::degrees(90.0);
let rotation3 = Rot2::from_sin_cos(1.0, 0.0);
let rotation4 = Rot2::turn_fraction(0.25);
// All three rotations should be equal
assert_relative_eq!(rotation1.sin, rotation2.sin);
assert_relative_eq!(rotation1.cos, rotation2.cos);
assert_relative_eq!(rotation1.sin, rotation3.sin);
assert_relative_eq!(rotation1.cos, rotation3.cos);
assert_relative_eq!(rotation1.sin, rotation4.sin);
assert_relative_eq!(rotation1.cos, rotation4.cos);
// The rotation should be 90 degrees
assert_relative_eq!(rotation1.as_radians(), FRAC_PI_2);
assert_relative_eq!(rotation1.as_degrees(), 90.0);
assert_relative_eq!(rotation1.as_turn_fraction(), 0.25);
}
#[test]
fn rotate() {
let rotation = Rot2::degrees(90.0);
assert_relative_eq!(rotation * Vec2::X, Vec2::Y);
assert_relative_eq!(rotation * Dir2::Y, Dir2::NEG_X);
}
#[test]
fn rotation_range() {
// the rotation range is `(-180, 180]` and the constructors
// normalize the rotations to that range
assert_relative_eq!(Rot2::radians(3.0 * FRAC_PI_2), Rot2::radians(-FRAC_PI_2));
assert_relative_eq!(Rot2::degrees(270.0), Rot2::degrees(-90.0));
assert_relative_eq!(Rot2::turn_fraction(0.75), Rot2::turn_fraction(-0.25));
}
#[test]
fn add() {
let rotation1 = Rot2::degrees(90.0);
let rotation2 = Rot2::degrees(180.0);
// 90 deg + 180 deg becomes -90 deg after it wraps around to be within the `(-180, 180]` range
assert_eq!((rotation1 * rotation2).as_degrees(), -90.0);
}
#[test]
fn subtract() {
let rotation1 = Rot2::degrees(90.0);
let rotation2 = Rot2::degrees(45.0);
assert_relative_eq!((rotation1 * rotation2.inverse()).as_degrees(), 45.0);
// This should be equivalent to the above
assert_relative_eq!(rotation2.angle_to(rotation1), core::f32::consts::FRAC_PI_4);
}
#[test]
fn length() {
let rotation = Rot2 {
sin: 10.0,
cos: 5.0,
};
assert_eq!(rotation.length_squared(), 125.0);
assert_eq!(rotation.length(), 11.18034);
assert!(ops::abs(rotation.normalize().length() - 1.0) < 10e-7);
}
#[test]
fn is_near_identity() {
assert!(!Rot2::radians(0.1).is_near_identity());
assert!(!Rot2::radians(-0.1).is_near_identity());
assert!(Rot2::radians(0.00001).is_near_identity());
assert!(Rot2::radians(-0.00001).is_near_identity());
assert!(Rot2::radians(0.0).is_near_identity());
}
#[test]
fn normalize() {
let rotation = Rot2 {
sin: 10.0,
cos: 5.0,
};
let normalized_rotation = rotation.normalize();
assert_eq!(normalized_rotation.sin, 0.89442724);
assert_eq!(normalized_rotation.cos, 0.44721362);
assert!(!rotation.is_normalized());
assert!(normalized_rotation.is_normalized());
}
#[test]
fn fast_renormalize() {
let rotation = Rot2 { sin: 1.0, cos: 0.5 };
let normalized_rotation = rotation.normalize();
let mut unnormalized_rot = rotation;
let mut renormalized_rot = rotation;
let mut initially_normalized_rot = normalized_rotation;
let mut fully_normalized_rot = normalized_rotation;
// Compute a 64x (=2⁶) multiple of the rotation.
for _ in 0..6 {
unnormalized_rot = unnormalized_rot * unnormalized_rot;
renormalized_rot = renormalized_rot * renormalized_rot;
initially_normalized_rot = initially_normalized_rot * initially_normalized_rot;
fully_normalized_rot = fully_normalized_rot * fully_normalized_rot;
renormalized_rot = renormalized_rot.fast_renormalize();
fully_normalized_rot = fully_normalized_rot.normalize();
}
assert!(!unnormalized_rot.is_normalized());
assert!(renormalized_rot.is_normalized());
assert!(fully_normalized_rot.is_normalized());
assert_relative_eq!(fully_normalized_rot, renormalized_rot, epsilon = 0.000001);
assert_relative_eq!(
fully_normalized_rot,
unnormalized_rot.normalize(),
epsilon = 0.000001
);
assert_relative_eq!(
fully_normalized_rot,
initially_normalized_rot.normalize(),
epsilon = 0.000001
);
}
#[test]
fn try_normalize() {
// Valid
assert!(Rot2 {
sin: 10.0,
cos: 5.0,
}
.try_normalize()
.is_some());
// NaN
assert!(Rot2 {
sin: f32::NAN,
cos: 5.0,
}
.try_normalize()
.is_none());
// Zero
assert!(Rot2 { sin: 0.0, cos: 0.0 }.try_normalize().is_none());
// Non-finite
assert!(Rot2 {
sin: f32::INFINITY,
cos: 5.0,
}
.try_normalize()
.is_none());
}
#[test]
fn nlerp() {
let rot1 = Rot2::IDENTITY;
let rot2 = Rot2::degrees(135.0);
assert_eq!(rot1.nlerp(rot2, 1.0 / 3.0).as_degrees(), 28.675055);
assert!(rot1.nlerp(rot2, 0.0).is_near_identity());
assert_eq!(rot1.nlerp(rot2, 0.5).as_degrees(), 67.5);
assert_eq!(rot1.nlerp(rot2, 1.0).as_degrees(), 135.0);
let rot1 = Rot2::IDENTITY;
let rot2 = Rot2::from_sin_cos(0.0, -1.0);
assert!(rot1.nlerp(rot2, 1.0 / 3.0).is_near_identity());
assert!(rot1.nlerp(rot2, 0.0).is_near_identity());
// At 0.5, there is no valid rotation, so the fallback is the original angle.
assert_eq!(rot1.nlerp(rot2, 0.5).as_degrees(), 0.0);
assert_eq!(ops::abs(rot1.nlerp(rot2, 1.0).as_degrees()), 180.0);
}
#[test]
fn slerp() {
let rot1 = Rot2::IDENTITY;
let rot2 = Rot2::degrees(135.0);
assert_eq!(rot1.slerp(rot2, 1.0 / 3.0).as_degrees(), 45.0);
assert!(rot1.slerp(rot2, 0.0).is_near_identity());
assert_eq!(rot1.slerp(rot2, 0.5).as_degrees(), 67.5);
assert_eq!(rot1.slerp(rot2, 1.0).as_degrees(), 135.0);
let rot1 = Rot2::IDENTITY;
let rot2 = Rot2::from_sin_cos(0.0, -1.0);
assert!(ops::abs(rot1.slerp(rot2, 1.0 / 3.0).as_degrees() - 60.0) < 10e-6);
assert!(rot1.slerp(rot2, 0.0).is_near_identity());
assert_eq!(rot1.slerp(rot2, 0.5).as_degrees(), 90.0);
assert_eq!(ops::abs(rot1.slerp(rot2, 1.0).as_degrees()), 180.0);
}
}

View File

@@ -0,0 +1,59 @@
//! Functionality related to random sampling from triangle meshes.
use crate::{
primitives::{Measured2d, Triangle3d},
ShapeSample, Vec3,
};
use alloc::vec::Vec;
use rand::Rng;
use rand_distr::{Distribution, WeightedAliasIndex, WeightedError};
/// A [distribution] that caches data to allow fast sampling from a collection of triangles.
/// Generally used through [`sample`] or [`sample_iter`].
///
/// [distribution]: Distribution
/// [`sample`]: Distribution::sample
/// [`sample_iter`]: Distribution::sample_iter
///
/// Example
/// ```
/// # use bevy_math::{Vec3, primitives::*};
/// # use bevy_math::sampling::mesh_sampling::UniformMeshSampler;
/// # use rand::{SeedableRng, rngs::StdRng, distributions::Distribution};
/// let faces = Tetrahedron::default().faces();
/// let sampler = UniformMeshSampler::try_new(faces).unwrap();
/// let rng = StdRng::seed_from_u64(8765309);
/// // 50 random points on the tetrahedron:
/// let samples: Vec<Vec3> = sampler.sample_iter(rng).take(50).collect();
/// ```
pub struct UniformMeshSampler {
triangles: Vec<Triangle3d>,
face_distribution: WeightedAliasIndex<f32>,
}
impl Distribution<Vec3> for UniformMeshSampler {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let face_index = self.face_distribution.sample(rng);
self.triangles[face_index].sample_interior(rng)
}
}
impl UniformMeshSampler {
/// Construct a new [`UniformMeshSampler`] from a list of [triangles].
///
/// Returns an error if the distribution of areas for the collection of triangles could not be formed
/// (most notably if the collection has zero surface area).
///
/// [triangles]: Triangle3d
pub fn try_new<T: IntoIterator<Item = Triangle3d>>(
triangles: T,
) -> Result<Self, WeightedError> {
let triangles: Vec<Triangle3d> = triangles.into_iter().collect();
let areas = triangles.iter().map(Measured2d::area).collect();
WeightedAliasIndex::new(areas).map(|face_distribution| Self {
triangles,
face_distribution,
})
}
}

13
vendor/bevy_math/src/sampling/mod.rs vendored Normal file
View File

@@ -0,0 +1,13 @@
//! This module contains tools related to random sampling.
//!
//! To use this, the "rand" feature must be enabled.
#[cfg(feature = "alloc")]
pub mod mesh_sampling;
pub mod shape_sampling;
pub mod standard;
#[cfg(feature = "alloc")]
pub use mesh_sampling::*;
pub use shape_sampling::*;
pub use standard::*;

View File

@@ -0,0 +1,640 @@
//! The [`ShapeSample`] trait, allowing random sampling from geometric shapes.
//!
//! At the most basic level, this allows sampling random points from the interior and boundary of
//! geometric primitives. For example:
//! ```
//! # use bevy_math::primitives::*;
//! # use bevy_math::ShapeSample;
//! # use rand::SeedableRng;
//! # use rand::rngs::StdRng;
//! // Get some `Rng`:
//! let rng = &mut StdRng::from_entropy();
//! // Make a circle of radius 2:
//! let circle = Circle::new(2.0);
//! // Get a point inside this circle uniformly at random:
//! let interior_pt = circle.sample_interior(rng);
//! // Get a point on the circle's boundary uniformly at random:
//! let boundary_pt = circle.sample_boundary(rng);
//! ```
//!
//! For repeated sampling, `ShapeSample` also includes methods for accessing a [`Distribution`]:
//! ```
//! # use bevy_math::primitives::*;
//! # use bevy_math::{Vec2, ShapeSample};
//! # use rand::SeedableRng;
//! # use rand::rngs::StdRng;
//! # use rand::distributions::Distribution;
//! # let rng1 = StdRng::from_entropy();
//! # let rng2 = StdRng::from_entropy();
//! // Use a rectangle this time:
//! let rectangle = Rectangle::new(1.0, 2.0);
//! // Get an iterator that spits out random interior points:
//! let interior_iter = rectangle.interior_dist().sample_iter(rng1);
//! // Collect random interior points from the iterator:
//! let interior_pts: Vec<Vec2> = interior_iter.take(1000).collect();
//! // Similarly, get an iterator over many random boundary points and collect them:
//! let boundary_pts: Vec<Vec2> = rectangle.boundary_dist().sample_iter(rng2).take(1000).collect();
//! ```
//!
//! In any case, the [`Rng`] used as the source of randomness must be provided explicitly.
use core::f32::consts::{PI, TAU};
use crate::{ops, primitives::*, NormedVectorSpace, Vec2, Vec3};
use rand::{
distributions::{Distribution, WeightedIndex},
Rng,
};
/// Exposes methods to uniformly sample a variety of primitive shapes.
pub trait ShapeSample {
/// The type of vector returned by the sample methods, [`Vec2`] for 2D shapes and [`Vec3`] for 3D shapes.
type Output;
/// Uniformly sample a point from inside the area/volume of this shape, centered on 0.
///
/// Shapes like [`Cylinder`], [`Capsule2d`] and [`Capsule3d`] are oriented along the y-axis.
///
/// # Example
/// ```
/// # use bevy_math::prelude::*;
/// let square = Rectangle::new(2.0, 2.0);
///
/// // Returns a Vec2 with both x and y between -1 and 1.
/// println!("{}", square.sample_interior(&mut rand::thread_rng()));
/// ```
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output;
/// Uniformly sample a point from the surface of this shape, centered on 0.
///
/// Shapes like [`Cylinder`], [`Capsule2d`] and [`Capsule3d`] are oriented along the y-axis.
///
/// # Example
/// ```
/// # use bevy_math::prelude::*;
/// let square = Rectangle::new(2.0, 2.0);
///
/// // Returns a Vec2 where one of the coordinates is at ±1,
/// // and the other is somewhere between -1 and 1.
/// println!("{}", square.sample_boundary(&mut rand::thread_rng()));
/// ```
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output;
/// Extract a [`Distribution`] whose samples are points of this shape's interior, taken uniformly.
///
/// # Example
///
/// ```
/// # use bevy_math::prelude::*;
/// # use rand::distributions::Distribution;
/// let square = Rectangle::new(2.0, 2.0);
/// let rng = rand::thread_rng();
///
/// // Iterate over points randomly drawn from `square`'s interior:
/// for random_val in square.interior_dist().sample_iter(rng).take(5) {
/// println!("{}", random_val);
/// }
/// ```
fn interior_dist(self) -> impl Distribution<Self::Output>
where
Self: Sized,
{
InteriorOf(self)
}
/// Extract a [`Distribution`] whose samples are points of this shape's boundary, taken uniformly.
///
/// # Example
///
/// ```
/// # use bevy_math::prelude::*;
/// # use rand::distributions::Distribution;
/// let square = Rectangle::new(2.0, 2.0);
/// let rng = rand::thread_rng();
///
/// // Iterate over points randomly drawn from `square`'s boundary:
/// for random_val in square.boundary_dist().sample_iter(rng).take(5) {
/// println!("{}", random_val);
/// }
/// ```
fn boundary_dist(self) -> impl Distribution<Self::Output>
where
Self: Sized,
{
BoundaryOf(self)
}
}
#[derive(Clone, Copy)]
/// A wrapper struct that allows interior sampling from a [`ShapeSample`] type directly as
/// a [`Distribution`].
pub struct InteriorOf<T: ShapeSample>(pub T);
#[derive(Clone, Copy)]
/// A wrapper struct that allows boundary sampling from a [`ShapeSample`] type directly as
/// a [`Distribution`].
pub struct BoundaryOf<T: ShapeSample>(pub T);
impl<T: ShapeSample> Distribution<<T as ShapeSample>::Output> for InteriorOf<T> {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> <T as ShapeSample>::Output {
self.0.sample_interior(rng)
}
}
impl<T: ShapeSample> Distribution<<T as ShapeSample>::Output> for BoundaryOf<T> {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> <T as ShapeSample>::Output {
self.0.sample_boundary(rng)
}
}
impl ShapeSample for Circle {
type Output = Vec2;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
// https://mathworld.wolfram.com/DiskPointPicking.html
let theta = rng.gen_range(0.0..TAU);
let r_squared = rng.gen_range(0.0..=(self.radius * self.radius));
let r = ops::sqrt(r_squared);
let (sin, cos) = ops::sin_cos(theta);
Vec2::new(r * cos, r * sin)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
let theta = rng.gen_range(0.0..TAU);
let (sin, cos) = ops::sin_cos(theta);
Vec2::new(self.radius * cos, self.radius * sin)
}
}
/// Boundary sampling for unit-spheres
#[inline]
fn sample_unit_sphere_boundary<R: Rng + ?Sized>(rng: &mut R) -> Vec3 {
let z = rng.gen_range(-1f32..=1f32);
let (a_sin, a_cos) = ops::sin_cos(rng.gen_range(-PI..=PI));
let c = ops::sqrt(1f32 - z * z);
let x = a_sin * c;
let y = a_cos * c;
Vec3::new(x, y, z)
}
impl ShapeSample for Sphere {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let r_cubed = rng.gen_range(0.0..=(self.radius * self.radius * self.radius));
let r = ops::cbrt(r_cubed);
r * sample_unit_sphere_boundary(rng)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
self.radius * sample_unit_sphere_boundary(rng)
}
}
impl ShapeSample for Annulus {
type Output = Vec2;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let inner_radius = self.inner_circle.radius;
let outer_radius = self.outer_circle.radius;
// Like random sampling for a circle, radius is weighted by the square.
let r_squared = rng.gen_range((inner_radius * inner_radius)..(outer_radius * outer_radius));
let r = ops::sqrt(r_squared);
let theta = rng.gen_range(0.0..TAU);
let (sin, cos) = ops::sin_cos(theta);
Vec2::new(r * cos, r * sin)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let total_perimeter = self.inner_circle.perimeter() + self.outer_circle.perimeter();
let inner_prob = (self.inner_circle.perimeter() / total_perimeter) as f64;
// Sample from boundary circles, choosing which one by weighting by perimeter:
let inner = rng.gen_bool(inner_prob);
if inner {
self.inner_circle.sample_boundary(rng)
} else {
self.outer_circle.sample_boundary(rng)
}
}
}
impl ShapeSample for Rectangle {
type Output = Vec2;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
let x = rng.gen_range(-self.half_size.x..=self.half_size.x);
let y = rng.gen_range(-self.half_size.y..=self.half_size.y);
Vec2::new(x, y)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
let primary_side = rng.gen_range(-1.0..1.0);
let other_side = if rng.r#gen() { -1.0 } else { 1.0 };
if self.half_size.x + self.half_size.y > 0.0 {
if rng.gen_bool((self.half_size.x / (self.half_size.x + self.half_size.y)) as f64) {
Vec2::new(primary_side, other_side) * self.half_size
} else {
Vec2::new(other_side, primary_side) * self.half_size
}
} else {
Vec2::ZERO
}
}
}
impl ShapeSample for Cuboid {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let x = rng.gen_range(-self.half_size.x..=self.half_size.x);
let y = rng.gen_range(-self.half_size.y..=self.half_size.y);
let z = rng.gen_range(-self.half_size.z..=self.half_size.z);
Vec3::new(x, y, z)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let primary_side1 = rng.gen_range(-1.0..1.0);
let primary_side2 = rng.gen_range(-1.0..1.0);
let other_side = if rng.r#gen() { -1.0 } else { 1.0 };
if let Ok(dist) = WeightedIndex::new([
self.half_size.y * self.half_size.z,
self.half_size.x * self.half_size.z,
self.half_size.x * self.half_size.y,
]) {
match dist.sample(rng) {
0 => Vec3::new(other_side, primary_side1, primary_side2) * self.half_size,
1 => Vec3::new(primary_side1, other_side, primary_side2) * self.half_size,
2 => Vec3::new(primary_side1, primary_side2, other_side) * self.half_size,
_ => unreachable!(),
}
} else {
Vec3::ZERO
}
}
}
/// Interior sampling for triangles which doesn't depend on the ambient dimension.
fn sample_triangle_interior<P: NormedVectorSpace, R: Rng + ?Sized>(
vertices: [P; 3],
rng: &mut R,
) -> P {
let [a, b, c] = vertices;
let ab = b - a;
let ac = c - a;
// Generate random points on a parallelepiped and reflect so that
// we can use the points that lie outside the triangle
let u = rng.gen_range(0.0..=1.0);
let v = rng.gen_range(0.0..=1.0);
if u + v > 1. {
let u1 = 1. - v;
let v1 = 1. - u;
a + (ab * u1 + ac * v1)
} else {
a + (ab * u + ac * v)
}
}
/// Boundary sampling for triangles which doesn't depend on the ambient dimension.
fn sample_triangle_boundary<P: NormedVectorSpace, R: Rng + ?Sized>(
vertices: [P; 3],
rng: &mut R,
) -> P {
let [a, b, c] = vertices;
let ab = b - a;
let ac = c - a;
let bc = c - b;
let t = rng.gen_range(0.0..=1.0);
if let Ok(dist) = WeightedIndex::new([ab.norm(), ac.norm(), bc.norm()]) {
match dist.sample(rng) {
0 => a.lerp(b, t),
1 => a.lerp(c, t),
2 => b.lerp(c, t),
_ => unreachable!(),
}
} else {
// This should only occur when the triangle is 0-dimensional degenerate
// so this is actually the correct result.
a
}
}
impl ShapeSample for Triangle2d {
type Output = Vec2;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
sample_triangle_interior(self.vertices, rng)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
sample_triangle_boundary(self.vertices, rng)
}
}
impl ShapeSample for Triangle3d {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
sample_triangle_interior(self.vertices, rng)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
sample_triangle_boundary(self.vertices, rng)
}
}
impl ShapeSample for Tetrahedron {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let [v0, v1, v2, v3] = self.vertices;
// Generate a random point in a cube:
let mut coords: [f32; 3] = [
rng.gen_range(0.0..1.0),
rng.gen_range(0.0..1.0),
rng.gen_range(0.0..1.0),
];
// The cube is broken into six tetrahedra of the form 0 <= c_0 <= c_1 <= c_2 <= 1,
// where c_i are the three euclidean coordinates in some permutation. (Since 3! = 6,
// there are six of them). Sorting the coordinates folds these six tetrahedra into the
// tetrahedron 0 <= x <= y <= z <= 1 (i.e. a fundamental domain of the permutation action).
coords.sort_by(|x, y| x.partial_cmp(y).unwrap());
// Now, convert a point from the fundamental tetrahedron into barycentric coordinates by
// taking the four successive differences of coordinates; note that these telescope to sum
// to 1, and this transformation is linear, hence preserves the probability density, since
// the latter comes from the Lebesgue measure.
//
// (See https://en.wikipedia.org/wiki/Lebesgue_measure#Properties — specifically, that
// Lebesgue measure of a linearly transformed set is its original measure times the
// determinant.)
let (a, b, c, d) = (
coords[0],
coords[1] - coords[0],
coords[2] - coords[1],
1. - coords[2],
);
// This is also a linear mapping, so probability density is still preserved.
v0 * a + v1 * b + v2 * c + v3 * d
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let triangles = self.faces();
let areas = triangles.iter().map(Measured2d::area);
if areas.clone().sum::<f32>() > 0.0 {
// There is at least one triangle with nonzero area, so this unwrap succeeds.
let dist = WeightedIndex::new(areas).unwrap();
// Get a random index, then sample the interior of the associated triangle.
let idx = dist.sample(rng);
triangles[idx].sample_interior(rng)
} else {
// In this branch the tetrahedron has zero surface area; just return a point that's on
// the tetrahedron.
self.vertices[0]
}
}
}
impl ShapeSample for Cylinder {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let Vec2 { x, y: z } = self.base().sample_interior(rng);
let y = rng.gen_range(-self.half_height..=self.half_height);
Vec3::new(x, y, z)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
// This uses the area of the ends divided by the overall surface area (optimized)
// [2 (\pi r^2)]/[2 (\pi r^2) + 2 \pi r h] = r/(r + h)
if self.radius + 2.0 * self.half_height > 0.0 {
if rng.gen_bool((self.radius / (self.radius + 2.0 * self.half_height)) as f64) {
let Vec2 { x, y: z } = self.base().sample_interior(rng);
if rng.r#gen() {
Vec3::new(x, self.half_height, z)
} else {
Vec3::new(x, -self.half_height, z)
}
} else {
let Vec2 { x, y: z } = self.base().sample_boundary(rng);
let y = rng.gen_range(-self.half_height..=self.half_height);
Vec3::new(x, y, z)
}
} else {
Vec3::ZERO
}
}
}
impl ShapeSample for Capsule2d {
type Output = Vec2;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
let rectangle_area = self.half_length * self.radius * 4.0;
let capsule_area = rectangle_area + PI * self.radius * self.radius;
if capsule_area > 0.0 {
// Check if the random point should be inside the rectangle
if rng.gen_bool((rectangle_area / capsule_area) as f64) {
self.to_inner_rectangle().sample_interior(rng)
} else {
let circle = Circle::new(self.radius);
let point = circle.sample_interior(rng);
// Add half length if it is the top semi-circle, otherwise subtract half
if point.y > 0.0 {
point + Vec2::Y * self.half_length
} else {
point - Vec2::Y * self.half_length
}
}
} else {
Vec2::ZERO
}
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
let rectangle_surface = 4.0 * self.half_length;
let capsule_surface = rectangle_surface + TAU * self.radius;
if capsule_surface > 0.0 {
if rng.gen_bool((rectangle_surface / capsule_surface) as f64) {
let side_distance =
rng.gen_range((-2.0 * self.half_length)..=(2.0 * self.half_length));
if side_distance < 0.0 {
Vec2::new(self.radius, side_distance + self.half_length)
} else {
Vec2::new(-self.radius, side_distance - self.half_length)
}
} else {
let circle = Circle::new(self.radius);
let point = circle.sample_boundary(rng);
// Add half length if it is the top semi-circle, otherwise subtract half
if point.y > 0.0 {
point + Vec2::Y * self.half_length
} else {
point - Vec2::Y * self.half_length
}
}
} else {
Vec2::ZERO
}
}
}
impl ShapeSample for Capsule3d {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let cylinder_vol = PI * self.radius * self.radius * 2.0 * self.half_length;
// Add 4/3 pi r^3
let capsule_vol = cylinder_vol + 4.0 / 3.0 * PI * self.radius * self.radius * self.radius;
if capsule_vol > 0.0 {
// Check if the random point should be inside the cylinder
if rng.gen_bool((cylinder_vol / capsule_vol) as f64) {
self.to_cylinder().sample_interior(rng)
} else {
let sphere = Sphere::new(self.radius);
let point = sphere.sample_interior(rng);
// Add half length if it is the top semi-sphere, otherwise subtract half
if point.y > 0.0 {
point + Vec3::Y * self.half_length
} else {
point - Vec3::Y * self.half_length
}
}
} else {
Vec3::ZERO
}
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let cylinder_surface = TAU * self.radius * 2.0 * self.half_length;
let capsule_surface = cylinder_surface + 4.0 * PI * self.radius * self.radius;
if capsule_surface > 0.0 {
if rng.gen_bool((cylinder_surface / capsule_surface) as f64) {
let Vec2 { x, y: z } = Circle::new(self.radius).sample_boundary(rng);
let y = rng.gen_range(-self.half_length..=self.half_length);
Vec3::new(x, y, z)
} else {
let sphere = Sphere::new(self.radius);
let point = sphere.sample_boundary(rng);
// Add half length if it is the top semi-sphere, otherwise subtract half
if point.y > 0.0 {
point + Vec3::Y * self.half_length
} else {
point - Vec3::Y * self.half_length
}
}
} else {
Vec3::ZERO
}
}
}
impl<P: Primitive2d + Measured2d + ShapeSample<Output = Vec2>> ShapeSample for Extrusion<P> {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let base_point = self.base_shape.sample_interior(rng);
let depth = rng.gen_range(-self.half_depth..self.half_depth);
base_point.extend(depth)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let base_area = self.base_shape.area();
let total_area = self.area();
let random = rng.gen_range(0.0..total_area);
match random {
x if x < base_area => self.base_shape.sample_interior(rng).extend(self.half_depth),
x if x < 2. * base_area => self
.base_shape
.sample_interior(rng)
.extend(-self.half_depth),
_ => self
.base_shape
.sample_boundary(rng)
.extend(rng.gen_range(-self.half_depth..self.half_depth)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
#[test]
fn circle_interior_sampling() {
let mut rng = ChaCha8Rng::from_seed(Default::default());
let circle = Circle::new(8.0);
let boxes = [
(-3.0, 3.0),
(1.0, 2.0),
(-1.0, -2.0),
(3.0, -2.0),
(1.0, -6.0),
(-3.0, -7.0),
(-7.0, -3.0),
(-6.0, 1.0),
];
let mut box_hits = [0; 8];
// Checks which boxes (if any) the sampled points are in
for _ in 0..5000 {
let point = circle.sample_interior(&mut rng);
for (i, box_) in boxes.iter().enumerate() {
if (point.x > box_.0 && point.x < box_.0 + 4.0)
&& (point.y > box_.1 && point.y < box_.1 + 4.0)
{
box_hits[i] += 1;
}
}
}
assert_eq!(
box_hits,
[396, 377, 415, 404, 366, 408, 408, 430],
"samples will occur across all array items at statistically equal chance"
);
}
#[test]
fn circle_boundary_sampling() {
let mut rng = ChaCha8Rng::from_seed(Default::default());
let circle = Circle::new(1.0);
let mut wedge_hits = [0; 8];
// Checks in which eighth of the circle each sampled point is in
for _ in 0..5000 {
let point = circle.sample_boundary(&mut rng);
let angle = ops::atan(point.y / point.x) + PI / 2.0;
let wedge = ops::floor(angle * 8.0 / PI) as usize;
wedge_hits[wedge] += 1;
}
assert_eq!(
wedge_hits,
[636, 608, 639, 603, 614, 650, 640, 610],
"samples will occur across all array items at statistically equal chance"
);
}
}

View File

@@ -0,0 +1,99 @@
//! This module holds local implementations of the [`Distribution`] trait for [`Standard`], which
//! allow certain Bevy math types (those whose values can be randomly generated without additional
//! input other than an [`Rng`]) to be produced using [`rand`]'s APIs. It also holds [`FromRng`],
//! an ergonomic extension to that functionality which permits the omission of type annotations.
//!
//! For instance:
//! ```
//! # use rand::{random, Rng, SeedableRng, rngs::StdRng, distributions::Standard};
//! # use bevy_math::{Dir3, sampling::FromRng};
//! let mut rng = StdRng::seed_from_u64(7313429298);
//! // Random direction using thread-local rng
//! let random_direction1: Dir3 = random();
//!
//! // Random direction using the rng constructed above
//! let random_direction2: Dir3 = rng.r#gen();
//!
//! // The same as the previous but with different syntax
//! let random_direction3 = Dir3::from_rng(&mut rng);
//!
//! // Five random directions, using Standard explicitly
//! let many_random_directions: Vec<Dir3> = rng.sample_iter(Standard).take(5).collect();
//! ```
use core::f32::consts::TAU;
use crate::{
primitives::{Circle, Sphere},
Dir2, Dir3, Dir3A, Quat, Rot2, ShapeSample, Vec3A,
};
use rand::{
distributions::{Distribution, Standard},
Rng,
};
/// Ergonomics trait for a type with a [`Standard`] distribution, allowing values to be generated
/// uniformly from an [`Rng`] by a method in its own namespace.
///
/// Example
/// ```
/// # use rand::{Rng, SeedableRng, rngs::StdRng};
/// # use bevy_math::{Dir3, sampling::FromRng};
/// let mut rng = StdRng::seed_from_u64(451);
/// let random_dir = Dir3::from_rng(&mut rng);
/// ```
pub trait FromRng
where
Self: Sized,
Standard: Distribution<Self>,
{
/// Construct a value of this type uniformly at random using `rng` as the source of randomness.
fn from_rng<R: Rng + ?Sized>(rng: &mut R) -> Self {
rng.r#gen()
}
}
impl Distribution<Dir2> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Dir2 {
let circle = Circle::new(1.0);
let vector = circle.sample_boundary(rng);
Dir2::new_unchecked(vector)
}
}
impl FromRng for Dir2 {}
impl Distribution<Dir3> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Dir3 {
let sphere = Sphere::new(1.0);
let vector = sphere.sample_boundary(rng);
Dir3::new_unchecked(vector)
}
}
impl FromRng for Dir3 {}
impl Distribution<Dir3A> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Dir3A {
let sphere = Sphere::new(1.0);
let vector: Vec3A = sphere.sample_boundary(rng).into();
Dir3A::new_unchecked(vector)
}
}
impl FromRng for Dir3A {}
impl Distribution<Rot2> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Rot2 {
let angle = rng.gen_range(0.0..TAU);
Rot2::radians(angle)
}
}
impl FromRng for Rot2 {}
impl FromRng for Quat {}