mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
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
1 changed files with 109 additions and 0 deletions
|
@ -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,
|
||||
/// stretching infinitely far
|
||||
#[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]
|
||||
fn circle_math() {
|
||||
let circle = Circle { radius: 3.0 };
|
||||
|
@ -726,6 +826,15 @@ mod tests {
|
|||
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]
|
||||
fn ellipse_math() {
|
||||
let ellipse = Ellipse::new(3.0, 1.0);
|
||||
|
|
Loading…
Reference in a new issue