Add Annulus primitive to bevy_math::primitives (#12706)
# Objective - #10572 There is no 2D primitive available for the common shape of an annulus (ring). ## Solution This PR introduces a new type to the existing math primitives: - `Annulus`: the region between two concentric circles --- ## Changelog ### Added - `Annulus` primitive to the `bevy_math` crate - `Annulus` tests (`diameter`, `thickness`, `area`, `perimeter` and `closest_point` methods) --------- Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
This commit is contained in:
parent
cb9789bc35
commit
31d91466b4
@ -125,6 +125,92 @@ impl Ellipse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A primitive shape formed by the region between two circles, also known as a ring.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[doc(alias = "Ring")]
|
||||||
|
pub struct Annulus {
|
||||||
|
/// The inner circle of the annulus
|
||||||
|
pub inner_circle: Circle,
|
||||||
|
/// The outer circle of the annulus
|
||||||
|
pub outer_circle: Circle,
|
||||||
|
}
|
||||||
|
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_circle: Circle::new(0.5),
|
||||||
|
outer_circle: Circle::new(1.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Annulus {
|
||||||
|
/// Create a new [`Annulus`] from the radii of the inner and outer circle
|
||||||
|
#[inline(always)]
|
||||||
|
pub const fn new(inner_radius: f32, outer_radius: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
inner_circle: Circle::new(inner_radius),
|
||||||
|
outer_circle: Circle::new(outer_radius),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the diameter of the annulus
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn diameter(&self) -> f32 {
|
||||||
|
self.outer_circle.diameter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the thickness of the annulus
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn thickness(&self) -> f32 {
|
||||||
|
self.outer_circle.radius - self.inner_circle.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the area of the annulus
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn area(&self) -> f32 {
|
||||||
|
PI * (self.outer_circle.radius.powi(2) - self.inner_circle.radius.powi(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the perimeter or circumference of the annulus,
|
||||||
|
/// which is the sum of the perimeters of the inner and outer circles.
|
||||||
|
#[inline(always)]
|
||||||
|
#[doc(alias = "circumference")]
|
||||||
|
pub fn perimeter(&self) -> f32 {
|
||||||
|
2.0 * PI * (self.outer_circle.radius + self.inner_circle.radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the point on the annulus that is closest to the given `point`:
|
||||||
|
///
|
||||||
|
/// - If the point is outside of the annulus completely, the returned point will be on the outer perimeter.
|
||||||
|
/// - If the point is inside of the inner circle (hole) of the annulus, the returned point will be on the inner perimeter.
|
||||||
|
/// - Otherwise, the returned point is overlapping 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_circle.radius.powi(2) <= distance_squared {
|
||||||
|
if distance_squared <= self.outer_circle.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_circle.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_circle.radius * dir_to_point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An unbounded plane in 2D space. It forms a separating surface through the origin,
|
/// An unbounded plane in 2D space. It forms a separating surface through the origin,
|
||||||
/// stretching infinitely far
|
/// stretching infinitely far
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
@ -718,6 +804,20 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn annulus_closest_point() {
|
||||||
|
let annulus = Annulus::new(1.5, 2.0);
|
||||||
|
assert_eq!(annulus.closest_point(Vec2::X * 10.0), Vec2::X * 2.0);
|
||||||
|
assert_eq!(
|
||||||
|
annulus.closest_point(Vec2::NEG_ONE),
|
||||||
|
Vec2::NEG_ONE.normalize() * 1.5
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
annulus.closest_point(Vec2::new(1.55, 0.85)),
|
||||||
|
Vec2::new(1.55, 0.85)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn circle_math() {
|
fn circle_math() {
|
||||||
let circle = Circle { radius: 3.0 };
|
let circle = Circle { radius: 3.0 };
|
||||||
@ -726,6 +826,15 @@ mod tests {
|
|||||||
assert_eq!(circle.perimeter(), 18.849556, "incorrect perimeter");
|
assert_eq!(circle.perimeter(), 18.849556, "incorrect perimeter");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn annulus_math() {
|
||||||
|
let annulus = Annulus::new(2.5, 3.5);
|
||||||
|
assert_eq!(annulus.diameter(), 7.0, "incorrect diameter");
|
||||||
|
assert_eq!(annulus.thickness(), 1.0, "incorrect thickness");
|
||||||
|
assert_eq!(annulus.area(), 18.849556, "incorrect area");
|
||||||
|
assert_eq!(annulus.perimeter(), 37.699112, "incorrect perimeter");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ellipse_math() {
|
fn ellipse_math() {
|
||||||
let ellipse = Ellipse::new(3.0, 1.0);
|
let ellipse = Ellipse::new(3.0, 1.0);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user