Skip to content

Add Annulus primitive to bevy_math::primitives #12706

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 25, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions crates/bevy_math/src/primitives/dim2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,90 @@ impl Ellipse {
}
}

/// An annulus primitive
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct Annulus {
/// The inner radius of the annulus
pub inner_radius: f32,
/// The outer radius of the annulus
pub outer_radius: f32,
}
impl Primitive2d for Annulus {}

impl Default for Annulus {
/// Returns the default [`Annulus`] with radii of `0.5` and `1.0`.
fn default() -> Self {
Self {
inner_radius: 0.5,
outer_radius: 1.0,
}
}
}

impl Annulus {
/// Create a new [`Annulus`] from an inner and outer radius
#[inline(always)]
pub const fn new(inner_radius: f32, outer_radius: f32) -> Self {
Self {
inner_radius,
outer_radius,
}
}

/// Get the diameter of the annulus
#[inline(always)]
pub fn diameter(&self) -> f32 {
2.0 * self.outer_radius
}

/// Get the width of the annulus
#[inline(always)]
pub fn width(&self) -> f32 {
self.outer_radius - self.inner_radius
}

/// Get the area of the annulus
#[inline(always)]
pub fn area(&self) -> f32 {
PI * (self.outer_radius.powi(2) - self.inner_radius.powi(2))
}

/// Get the perimeter or circumference of the annulus
#[inline(always)]
#[doc(alias = "circumference")]
pub fn perimeter(&self) -> f32 {
2.0 * PI * (self.outer_radius + self.inner_radius)
}

/// Finds the point on the annulus that is closest to the given `point`.
///
/// If the point is outside the annulus, the returned point will be on
/// the perimeter of the annulus. Otherwise, it will be inside the annulus
/// and returned as is.
#[inline(always)]
pub fn closest_point(&self, point: Vec2) -> Vec2 {
let distance_squared = point.length_squared();

if self.inner_radius.powi(2) <= distance_squared {
if distance_squared <= self.outer_radius.powi(2) {
// The point is inside the annulus.
point
} else {
// The point is outside the annulus and closer to the outer perimeter.
// Find the closest point on the perimeter of the annulus.
let dir_to_point = point / distance_squared.sqrt();
self.outer_radius * dir_to_point
}
} else {
// The point is outside the annulus and closer to the inner perimeter.
// Find the closest point on the perimeter of the annulus.
let dir_to_point = point / distance_squared.sqrt();
self.inner_radius * dir_to_point
}
}
}

/// An unbounded plane in 2D space. It forms a separating surface through the origin,
/// stretching infinitely far
#[derive(Clone, Copy, Debug, PartialEq)]
Expand Down Expand Up @@ -718,6 +802,23 @@ mod tests {
);
}

#[test]
fn annulus_closest_point() {
let annulus = Annulus {
inner_radius: 1.5,
outer_radius: 2.0,
};
assert_eq!(annulus.closest_point(Vec2::X * 10.0), Vec2::X * 2.0);
assert_eq!(
annulus.closest_point(Vec2::NEG_ONE * 10.0),
Vec2::NEG_ONE.normalize() * 2.0
);
assert_eq!(
annulus.closest_point(Vec2::new(1.55, 0.85)),
Vec2::new(1.55, 0.85)
);
}

#[test]
fn circle_math() {
let circle = Circle { radius: 3.0 };
Expand All @@ -726,6 +827,18 @@ mod tests {
assert_eq!(circle.perimeter(), 18.849556, "incorrect perimeter");
}

#[test]
fn annulus_math() {
let annulus = Annulus {
inner_radius: 2.5,
outer_radius: 3.5,
};
assert_eq!(annulus.diameter(), 7.0, "incorrect diameter");
assert_eq!(annulus.width(), 1.0, "incorrect width");
assert_eq!(annulus.area(), 18.849556, "incorrect area");
assert_eq!(annulus.perimeter(), 37.699112, "incorrect perimeter");
}

#[test]
fn ellipse_math() {
let ellipse = Ellipse::new(3.0, 1.0);
Expand Down