Path: blob/main/crates/bevy_math/src/primitives/dim2.rs
6596 views
use core::f32::consts::{FRAC_1_SQRT_2, FRAC_PI_2, FRAC_PI_3, PI};1use derive_more::derive::From;2#[cfg(feature = "alloc")]3use thiserror::Error;45use super::{Measured2d, Primitive2d, WindingOrder};6use crate::{7ops::{self, FloatPow},8Dir2, InvalidDirectionError, Isometry2d, Ray2d, Rot2, Vec2,9};1011#[cfg(feature = "alloc")]12use super::polygon::is_polygon_simple;1314#[cfg(feature = "bevy_reflect")]15use bevy_reflect::{std_traits::ReflectDefault, Reflect};16#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]17use bevy_reflect::{ReflectDeserialize, ReflectSerialize};1819#[cfg(feature = "alloc")]20use alloc::vec::Vec;2122/// A circle primitive, representing the set of points some distance from the origin23#[derive(Clone, Copy, Debug, PartialEq)]24#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]25#[cfg_attr(26feature = "bevy_reflect",27derive(Reflect),28reflect(Debug, PartialEq, Default, Clone)29)]30#[cfg_attr(31all(feature = "serialize", feature = "bevy_reflect"),32reflect(Serialize, Deserialize)33)]34pub struct Circle {35/// The radius of the circle36pub radius: f32,37}3839impl Primitive2d for Circle {}4041impl Default for Circle {42/// Returns the default [`Circle`] with a radius of `0.5`.43fn default() -> Self {44Self { radius: 0.5 }45}46}4748impl Circle {49/// Create a new [`Circle`] from a `radius`50#[inline(always)]51pub const fn new(radius: f32) -> Self {52Self { radius }53}5455/// Get the diameter of the circle56#[inline(always)]57pub const fn diameter(&self) -> f32 {582.0 * self.radius59}6061/// Finds the point on the circle that is closest to the given `point`.62///63/// If the point is outside the circle, the returned point will be on the perimeter of the circle.64/// Otherwise, it will be inside the circle and returned as is.65#[inline(always)]66pub fn closest_point(&self, point: Vec2) -> Vec2 {67let distance_squared = point.length_squared();6869if distance_squared <= self.radius.squared() {70// The point is inside the circle.71point72} else {73// The point is outside the circle.74// Find the closest point on the perimeter of the circle.75let dir_to_point = point / ops::sqrt(distance_squared);76self.radius * dir_to_point77}78}79}8081impl Measured2d for Circle {82/// Get the area of the circle83#[inline(always)]84fn area(&self) -> f32 {85PI * self.radius.squared()86}8788/// Get the perimeter or circumference of the circle89#[inline(always)]90#[doc(alias = "circumference")]91fn perimeter(&self) -> f32 {922.0 * PI * self.radius93}94}9596/// A primitive representing an arc between two points on a circle.97///98/// An arc has no area.99/// If you want to include the portion of a circle's area swept out by the arc,100/// use the pie-shaped [`CircularSector`].101/// If you want to include only the space inside the convex hull of the arc,102/// use the bowl-shaped [`CircularSegment`].103///104/// The arc is drawn starting from [`Vec2::Y`], extending by `half_angle` radians on105/// either side. The center of the circle is the origin [`Vec2::ZERO`]. Note that this106/// means that the origin may not be within the `Arc2d`'s convex hull.107///108/// **Warning:** Arcs with negative angle or radius, or with angle greater than an entire circle, are not officially supported.109/// It is recommended to normalize arcs to have an angle in [0, 2Ï€].110#[derive(Clone, Copy, Debug, PartialEq)]111#[doc(alias("CircularArc", "CircleArc"))]112#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]113#[cfg_attr(114feature = "bevy_reflect",115derive(Reflect),116reflect(Debug, PartialEq, Default, Clone)117)]118#[cfg_attr(119all(feature = "serialize", feature = "bevy_reflect"),120reflect(Serialize, Deserialize)121)]122pub struct Arc2d {123/// The radius of the circle124pub radius: f32,125/// Half the angle defining the arc126pub half_angle: f32,127}128129impl Primitive2d for Arc2d {}130131impl Default for Arc2d {132/// Returns the default [`Arc2d`] with radius `0.5`, covering one third of a circle133fn default() -> Self {134Self {135radius: 0.5,136half_angle: 2.0 * FRAC_PI_3,137}138}139}140141impl Arc2d {142/// Create a new [`Arc2d`] from a `radius` and a `half_angle`143#[inline(always)]144pub const fn new(radius: f32, half_angle: f32) -> Self {145Self { radius, half_angle }146}147148/// Create a new [`Arc2d`] from a `radius` and an `angle` in radians149#[inline(always)]150pub const fn from_radians(radius: f32, angle: f32) -> Self {151Self {152radius,153half_angle: angle / 2.0,154}155}156157/// Create a new [`Arc2d`] from a `radius` and an `angle` in degrees.158#[inline(always)]159pub const fn from_degrees(radius: f32, angle: f32) -> Self {160Self {161radius,162half_angle: angle.to_radians() / 2.0,163}164}165166/// Create a new [`Arc2d`] from a `radius` and a `fraction` of a single turn.167///168/// For instance, `0.5` turns is a semicircle.169#[inline(always)]170pub const fn from_turns(radius: f32, fraction: f32) -> Self {171Self {172radius,173half_angle: fraction * PI,174}175}176177/// Get the angle of the arc178#[inline(always)]179pub const fn angle(&self) -> f32 {180self.half_angle * 2.0181}182183/// Get the length of the arc184#[inline(always)]185pub const fn length(&self) -> f32 {186self.angle() * self.radius187}188189/// Get the right-hand end point of the arc190#[inline(always)]191pub fn right_endpoint(&self) -> Vec2 {192self.radius * Vec2::from_angle(FRAC_PI_2 - self.half_angle)193}194195/// Get the left-hand end point of the arc196#[inline(always)]197pub fn left_endpoint(&self) -> Vec2 {198self.radius * Vec2::from_angle(FRAC_PI_2 + self.half_angle)199}200201/// Get the endpoints of the arc202#[inline(always)]203pub fn endpoints(&self) -> [Vec2; 2] {204[self.left_endpoint(), self.right_endpoint()]205}206207/// Get the midpoint of the arc208#[inline]209pub fn midpoint(&self) -> Vec2 {210self.radius * Vec2::Y211}212213/// Get half the distance between the endpoints (half the length of the chord)214#[inline(always)]215pub fn half_chord_length(&self) -> f32 {216self.radius * ops::sin(self.half_angle)217}218219/// Get the distance between the endpoints (the length of the chord)220#[inline(always)]221pub fn chord_length(&self) -> f32 {2222.0 * self.half_chord_length()223}224225/// Get the midpoint of the two endpoints (the midpoint of the chord)226#[inline(always)]227pub fn chord_midpoint(&self) -> Vec2 {228self.apothem() * Vec2::Y229}230231/// Get the length of the apothem of this arc, that is,232/// the distance from the center of the circle to the midpoint of the chord, in the direction of the midpoint of the arc.233/// Equivalently, the [`radius`](Self::radius) minus the [`sagitta`](Self::sagitta).234///235/// Note that for a [`major`](Self::is_major) arc, the apothem will be negative.236#[inline(always)]237// Naming note: Various sources are inconsistent as to whether the apothem is the segment between the center and the238// midpoint of a chord, or the length of that segment. Given this confusion, we've opted for the definition239// used by Wolfram MathWorld, which is the distance rather than the segment.240pub fn apothem(&self) -> f32 {241let sign = if self.is_minor() { 1.0 } else { -1.0 };242sign * ops::sqrt(self.radius.squared() - self.half_chord_length().squared())243}244245/// Get the length of the sagitta of this arc, that is,246/// the length of the line between the midpoints of the arc and its chord.247/// Equivalently, the height of the triangle whose base is the chord and whose apex is the midpoint of the arc.248///249/// The sagitta is also the sum of the [`radius`](Self::radius) and the [`apothem`](Self::apothem).250pub fn sagitta(&self) -> f32 {251self.radius - self.apothem()252}253254/// Produces true if the arc is at most half a circle.255///256/// **Note:** This is not the negation of [`is_major`](Self::is_major): an exact semicircle is both major and minor.257#[inline(always)]258pub const fn is_minor(&self) -> bool {259self.half_angle <= FRAC_PI_2260}261262/// Produces true if the arc is at least half a circle.263///264/// **Note:** This is not the negation of [`is_minor`](Self::is_minor): an exact semicircle is both major and minor.265#[inline(always)]266pub const fn is_major(&self) -> bool {267self.half_angle >= FRAC_PI_2268}269}270271/// A primitive representing a circular sector: a pie slice of a circle.272///273/// The segment is positioned so that it always includes [`Vec2::Y`] and is vertically symmetrical.274/// To orient the sector differently, apply a rotation.275/// The sector is drawn with the center of its circle at the origin [`Vec2::ZERO`].276///277/// **Warning:** Circular sectors with negative angle or radius, or with angle greater than an entire circle, are not officially supported.278/// We recommend normalizing circular sectors to have an angle in [0, 2Ï€].279#[derive(Clone, Copy, Debug, PartialEq, From)]280#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]281#[cfg_attr(282feature = "bevy_reflect",283derive(Reflect),284reflect(Debug, PartialEq, Default, Clone)285)]286#[cfg_attr(287all(feature = "serialize", feature = "bevy_reflect"),288reflect(Serialize, Deserialize)289)]290pub struct CircularSector {291/// The arc defining the sector292#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]293pub arc: Arc2d,294}295296impl Primitive2d for CircularSector {}297298impl Default for CircularSector {299/// Returns the default [`CircularSector`] with radius `0.5` and covering a third of a circle300fn default() -> Self {301Self::from(Arc2d::default())302}303}304305impl Measured2d for CircularSector {306#[inline(always)]307fn area(&self) -> f32 {308self.arc.radius.squared() * self.arc.half_angle309}310311#[inline(always)]312fn perimeter(&self) -> f32 {313if self.half_angle() >= PI {314self.arc.radius * 2.0 * PI315} else {3162.0 * self.radius() + self.arc_length()317}318}319}320321impl CircularSector {322/// Create a new [`CircularSector`] from a `radius` and an `angle`323#[inline(always)]324pub const fn new(radius: f32, angle: f32) -> Self {325Self {326arc: Arc2d::new(radius, angle),327}328}329330/// Create a new [`CircularSector`] from a `radius` and an `angle` in radians.331#[inline(always)]332pub const fn from_radians(radius: f32, angle: f32) -> Self {333Self {334arc: Arc2d::from_radians(radius, angle),335}336}337338/// Create a new [`CircularSector`] from a `radius` and an `angle` in degrees.339#[inline(always)]340pub const fn from_degrees(radius: f32, angle: f32) -> Self {341Self {342arc: Arc2d::from_degrees(radius, angle),343}344}345346/// Create a new [`CircularSector`] from a `radius` and a number of `turns` of a circle.347///348/// For instance, `0.5` turns is a semicircle.349#[inline(always)]350pub const fn from_turns(radius: f32, fraction: f32) -> Self {351Self {352arc: Arc2d::from_turns(radius, fraction),353}354}355356/// Get half the angle of the sector357#[inline(always)]358pub const fn half_angle(&self) -> f32 {359self.arc.half_angle360}361362/// Get the angle of the sector363#[inline(always)]364pub const fn angle(&self) -> f32 {365self.arc.angle()366}367368/// Get the radius of the sector369#[inline(always)]370pub const fn radius(&self) -> f32 {371self.arc.radius372}373374/// Get the length of the arc defining the sector375#[inline(always)]376pub const fn arc_length(&self) -> f32 {377self.arc.length()378}379380/// Get half the length of the chord defined by the sector381///382/// See [`Arc2d::half_chord_length`]383#[inline(always)]384pub fn half_chord_length(&self) -> f32 {385self.arc.half_chord_length()386}387388/// Get the length of the chord defined by the sector389///390/// See [`Arc2d::chord_length`]391#[inline(always)]392pub fn chord_length(&self) -> f32 {393self.arc.chord_length()394}395396/// Get the midpoint of the chord defined by the sector397///398/// See [`Arc2d::chord_midpoint`]399#[inline(always)]400pub fn chord_midpoint(&self) -> Vec2 {401self.arc.chord_midpoint()402}403404/// Get the length of the apothem of this sector405///406/// See [`Arc2d::apothem`]407#[inline(always)]408pub fn apothem(&self) -> f32 {409self.arc.apothem()410}411412/// Get the length of the sagitta of this sector413///414/// See [`Arc2d::sagitta`]415#[inline(always)]416pub fn sagitta(&self) -> f32 {417self.arc.sagitta()418}419}420421/// A primitive representing a circular segment:422/// the area enclosed by the arc of a circle and its chord (the line between its endpoints).423///424/// The segment is drawn starting from [`Vec2::Y`], extending equally on either side.425/// To orient the segment differently, apply a rotation.426/// The segment is drawn with the center of its circle at the origin [`Vec2::ZERO`].427/// When positioning a segment, the [`apothem`](Self::apothem) function may be particularly useful.428///429/// **Warning:** Circular segments with negative angle or radius, or with angle greater than an entire circle, are not officially supported.430/// We recommend normalizing circular segments to have an angle in [0, 2Ï€].431#[derive(Clone, Copy, Debug, PartialEq, From)]432#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]433#[cfg_attr(434feature = "bevy_reflect",435derive(Reflect),436reflect(Debug, PartialEq, Default, Clone)437)]438#[cfg_attr(439all(feature = "serialize", feature = "bevy_reflect"),440reflect(Serialize, Deserialize)441)]442pub struct CircularSegment {443/// The arc defining the segment444#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]445pub arc: Arc2d,446}447448impl Primitive2d for CircularSegment {}449450impl Default for CircularSegment {451/// Returns the default [`CircularSegment`] with radius `0.5` and covering a third of a circle452fn default() -> Self {453Self::from(Arc2d::default())454}455}456457impl Measured2d for CircularSegment {458#[inline(always)]459fn area(&self) -> f32 {4600.5 * self.arc.radius.squared() * (self.arc.angle() - ops::sin(self.arc.angle()))461}462463#[inline(always)]464fn perimeter(&self) -> f32 {465self.chord_length() + self.arc_length()466}467}468469impl CircularSegment {470/// Create a new [`CircularSegment`] from a `radius`, and an `angle`471#[inline(always)]472pub const fn new(radius: f32, angle: f32) -> Self {473Self {474arc: Arc2d::new(radius, angle),475}476}477478/// Create a new [`CircularSegment`] from a `radius` and an `angle` in radians.479#[inline(always)]480pub const fn from_radians(radius: f32, angle: f32) -> Self {481Self {482arc: Arc2d::from_radians(radius, angle),483}484}485486/// Create a new [`CircularSegment`] from a `radius` and an `angle` in degrees.487#[inline(always)]488pub const fn from_degrees(radius: f32, angle: f32) -> Self {489Self {490arc: Arc2d::from_degrees(radius, angle),491}492}493494/// Create a new [`CircularSegment`] from a `radius` and a number of `turns` of a circle.495///496/// For instance, `0.5` turns is a semicircle.497#[inline(always)]498pub const fn from_turns(radius: f32, fraction: f32) -> Self {499Self {500arc: Arc2d::from_turns(radius, fraction),501}502}503504/// Get the half-angle of the segment505#[inline(always)]506pub const fn half_angle(&self) -> f32 {507self.arc.half_angle508}509510/// Get the angle of the segment511#[inline(always)]512pub const fn angle(&self) -> f32 {513self.arc.angle()514}515516/// Get the radius of the segment517#[inline(always)]518pub const fn radius(&self) -> f32 {519self.arc.radius520}521522/// Get the length of the arc defining the segment523#[inline(always)]524pub const fn arc_length(&self) -> f32 {525self.arc.length()526}527528/// Get half the length of the segment's base, also known as its chord529#[inline(always)]530#[doc(alias = "half_base_length")]531pub fn half_chord_length(&self) -> f32 {532self.arc.half_chord_length()533}534535/// Get the length of the segment's base, also known as its chord536#[inline(always)]537#[doc(alias = "base_length")]538#[doc(alias = "base")]539pub fn chord_length(&self) -> f32 {540self.arc.chord_length()541}542543/// Get the midpoint of the segment's base, also known as its chord544#[inline(always)]545#[doc(alias = "base_midpoint")]546pub fn chord_midpoint(&self) -> Vec2 {547self.arc.chord_midpoint()548}549550/// Get the length of the apothem of this segment,551/// which is the signed distance between the segment and the center of its circle552///553/// See [`Arc2d::apothem`]554#[inline(always)]555pub fn apothem(&self) -> f32 {556self.arc.apothem()557}558559/// Get the length of the sagitta of this segment, also known as its height560///561/// See [`Arc2d::sagitta`]562#[inline(always)]563#[doc(alias = "height")]564pub fn sagitta(&self) -> f32 {565self.arc.sagitta()566}567}568569#[cfg(test)]570mod arc_tests {571use core::f32::consts::FRAC_PI_4;572use core::f32::consts::SQRT_2;573574use approx::assert_abs_diff_eq;575576use super::*;577578struct ArcTestCase {579radius: f32,580half_angle: f32,581angle: f32,582length: f32,583right_endpoint: Vec2,584left_endpoint: Vec2,585endpoints: [Vec2; 2],586midpoint: Vec2,587half_chord_length: f32,588chord_length: f32,589chord_midpoint: Vec2,590apothem: f32,591sagitta: f32,592is_minor: bool,593is_major: bool,594sector_area: f32,595sector_perimeter: f32,596segment_area: f32,597segment_perimeter: f32,598}599600impl ArcTestCase {601fn check_arc(&self, arc: Arc2d) {602assert_abs_diff_eq!(self.radius, arc.radius);603assert_abs_diff_eq!(self.half_angle, arc.half_angle);604assert_abs_diff_eq!(self.angle, arc.angle());605assert_abs_diff_eq!(self.length, arc.length());606assert_abs_diff_eq!(self.right_endpoint, arc.right_endpoint());607assert_abs_diff_eq!(self.left_endpoint, arc.left_endpoint());608assert_abs_diff_eq!(self.endpoints[0], arc.endpoints()[0]);609assert_abs_diff_eq!(self.endpoints[1], arc.endpoints()[1]);610assert_abs_diff_eq!(self.midpoint, arc.midpoint());611assert_abs_diff_eq!(self.half_chord_length, arc.half_chord_length());612assert_abs_diff_eq!(self.chord_length, arc.chord_length(), epsilon = 0.00001);613assert_abs_diff_eq!(self.chord_midpoint, arc.chord_midpoint());614assert_abs_diff_eq!(self.apothem, arc.apothem());615assert_abs_diff_eq!(self.sagitta, arc.sagitta());616assert_eq!(self.is_minor, arc.is_minor());617assert_eq!(self.is_major, arc.is_major());618}619620fn check_sector(&self, sector: CircularSector) {621assert_abs_diff_eq!(self.radius, sector.radius());622assert_abs_diff_eq!(self.half_angle, sector.half_angle());623assert_abs_diff_eq!(self.angle, sector.angle());624assert_abs_diff_eq!(self.half_chord_length, sector.half_chord_length());625assert_abs_diff_eq!(self.chord_length, sector.chord_length(), epsilon = 0.00001);626assert_abs_diff_eq!(self.chord_midpoint, sector.chord_midpoint());627assert_abs_diff_eq!(self.apothem, sector.apothem());628assert_abs_diff_eq!(self.sagitta, sector.sagitta());629assert_abs_diff_eq!(self.sector_area, sector.area());630assert_abs_diff_eq!(self.sector_perimeter, sector.perimeter());631}632633fn check_segment(&self, segment: CircularSegment) {634assert_abs_diff_eq!(self.radius, segment.radius());635assert_abs_diff_eq!(self.half_angle, segment.half_angle());636assert_abs_diff_eq!(self.angle, segment.angle());637assert_abs_diff_eq!(self.half_chord_length, segment.half_chord_length());638assert_abs_diff_eq!(self.chord_length, segment.chord_length(), epsilon = 0.00001);639assert_abs_diff_eq!(self.chord_midpoint, segment.chord_midpoint());640assert_abs_diff_eq!(self.apothem, segment.apothem());641assert_abs_diff_eq!(self.sagitta, segment.sagitta());642assert_abs_diff_eq!(self.segment_area, segment.area());643assert_abs_diff_eq!(self.segment_perimeter, segment.perimeter());644}645}646647#[test]648fn zero_angle() {649let tests = ArcTestCase {650radius: 1.0,651half_angle: 0.0,652angle: 0.0,653length: 0.0,654left_endpoint: Vec2::Y,655right_endpoint: Vec2::Y,656endpoints: [Vec2::Y, Vec2::Y],657midpoint: Vec2::Y,658half_chord_length: 0.0,659chord_length: 0.0,660chord_midpoint: Vec2::Y,661apothem: 1.0,662sagitta: 0.0,663is_minor: true,664is_major: false,665sector_area: 0.0,666sector_perimeter: 2.0,667segment_area: 0.0,668segment_perimeter: 0.0,669};670671tests.check_arc(Arc2d::new(1.0, 0.0));672tests.check_sector(CircularSector::new(1.0, 0.0));673tests.check_segment(CircularSegment::new(1.0, 0.0));674}675676#[test]677fn zero_radius() {678let tests = ArcTestCase {679radius: 0.0,680half_angle: FRAC_PI_4,681angle: FRAC_PI_2,682length: 0.0,683left_endpoint: Vec2::ZERO,684right_endpoint: Vec2::ZERO,685endpoints: [Vec2::ZERO, Vec2::ZERO],686midpoint: Vec2::ZERO,687half_chord_length: 0.0,688chord_length: 0.0,689chord_midpoint: Vec2::ZERO,690apothem: 0.0,691sagitta: 0.0,692is_minor: true,693is_major: false,694sector_area: 0.0,695sector_perimeter: 0.0,696segment_area: 0.0,697segment_perimeter: 0.0,698};699700tests.check_arc(Arc2d::new(0.0, FRAC_PI_4));701tests.check_sector(CircularSector::new(0.0, FRAC_PI_4));702tests.check_segment(CircularSegment::new(0.0, FRAC_PI_4));703}704705#[test]706fn quarter_circle() {707let sqrt_half: f32 = ops::sqrt(0.5);708let tests = ArcTestCase {709radius: 1.0,710half_angle: FRAC_PI_4,711angle: FRAC_PI_2,712length: FRAC_PI_2,713left_endpoint: Vec2::new(-sqrt_half, sqrt_half),714right_endpoint: Vec2::splat(sqrt_half),715endpoints: [Vec2::new(-sqrt_half, sqrt_half), Vec2::splat(sqrt_half)],716midpoint: Vec2::Y,717half_chord_length: sqrt_half,718chord_length: ops::sqrt(2.0),719chord_midpoint: Vec2::new(0.0, sqrt_half),720apothem: sqrt_half,721sagitta: 1.0 - sqrt_half,722is_minor: true,723is_major: false,724sector_area: FRAC_PI_4,725sector_perimeter: FRAC_PI_2 + 2.0,726segment_area: FRAC_PI_4 - 0.5,727segment_perimeter: FRAC_PI_2 + SQRT_2,728};729730tests.check_arc(Arc2d::from_turns(1.0, 0.25));731tests.check_sector(CircularSector::from_turns(1.0, 0.25));732tests.check_segment(CircularSegment::from_turns(1.0, 0.25));733}734735#[test]736fn half_circle() {737let tests = ArcTestCase {738radius: 1.0,739half_angle: FRAC_PI_2,740angle: PI,741length: PI,742left_endpoint: Vec2::NEG_X,743right_endpoint: Vec2::X,744endpoints: [Vec2::NEG_X, Vec2::X],745midpoint: Vec2::Y,746half_chord_length: 1.0,747chord_length: 2.0,748chord_midpoint: Vec2::ZERO,749apothem: 0.0,750sagitta: 1.0,751is_minor: true,752is_major: true,753sector_area: FRAC_PI_2,754sector_perimeter: PI + 2.0,755segment_area: FRAC_PI_2,756segment_perimeter: PI + 2.0,757};758759tests.check_arc(Arc2d::from_radians(1.0, PI));760tests.check_sector(CircularSector::from_radians(1.0, PI));761tests.check_segment(CircularSegment::from_radians(1.0, PI));762}763764#[test]765fn full_circle() {766let tests = ArcTestCase {767radius: 1.0,768half_angle: PI,769angle: 2.0 * PI,770length: 2.0 * PI,771left_endpoint: Vec2::NEG_Y,772right_endpoint: Vec2::NEG_Y,773endpoints: [Vec2::NEG_Y, Vec2::NEG_Y],774midpoint: Vec2::Y,775half_chord_length: 0.0,776chord_length: 0.0,777chord_midpoint: Vec2::NEG_Y,778apothem: -1.0,779sagitta: 2.0,780is_minor: false,781is_major: true,782sector_area: PI,783sector_perimeter: 2.0 * PI,784segment_area: PI,785segment_perimeter: 2.0 * PI,786};787788tests.check_arc(Arc2d::from_degrees(1.0, 360.0));789tests.check_sector(CircularSector::from_degrees(1.0, 360.0));790tests.check_segment(CircularSegment::from_degrees(1.0, 360.0));791}792}793794/// An ellipse primitive, which is like a circle, but the width and height can be different795#[derive(Clone, Copy, Debug, PartialEq)]796#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]797#[cfg_attr(798feature = "bevy_reflect",799derive(Reflect),800reflect(Debug, PartialEq, Default, Clone)801)]802#[cfg_attr(803all(feature = "serialize", feature = "bevy_reflect"),804reflect(Serialize, Deserialize)805)]806pub struct Ellipse {807/// Half of the width and height of the ellipse.808///809/// This corresponds to the two perpendicular radii defining the ellipse.810pub half_size: Vec2,811}812813impl Primitive2d for Ellipse {}814815impl Default for Ellipse {816/// Returns the default [`Ellipse`] with a half-width of `1.0` and a half-height of `0.5`.817fn default() -> Self {818Self {819half_size: Vec2::new(1.0, 0.5),820}821}822}823824impl Ellipse {825/// Create a new `Ellipse` from half of its width and height.826///827/// This corresponds to the two perpendicular radii defining the ellipse.828#[inline(always)]829pub const fn new(half_width: f32, half_height: f32) -> Self {830Self {831half_size: Vec2::new(half_width, half_height),832}833}834835/// Create a new `Ellipse` from a given full size.836///837/// `size.x` is the diameter along the X axis, and `size.y` is the diameter along the Y axis.838#[inline(always)]839pub const fn from_size(size: Vec2) -> Self {840Self {841half_size: Vec2::new(size.x / 2.0, size.y / 2.0),842}843}844845#[inline(always)]846/// Returns the [eccentricity](https://en.wikipedia.org/wiki/Eccentricity_(mathematics)) of the ellipse.847/// It can be thought of as a measure of how "stretched" or elongated the ellipse is.848///849/// The value should be in the range [0, 1), where 0 represents a circle, and 1 represents a parabola.850pub fn eccentricity(&self) -> f32 {851let a = self.semi_major();852let b = self.semi_minor();853854ops::sqrt(a * a - b * b) / a855}856857#[inline(always)]858/// Get the focal length of the ellipse. This corresponds to the distance between one of the foci and the center of the ellipse.859///860/// The focal length of an ellipse is related to its eccentricity by `eccentricity = focal_length / semi_major`861pub fn focal_length(&self) -> f32 {862let a = self.semi_major();863let b = self.semi_minor();864865ops::sqrt(a * a - b * b)866}867868/// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse.869#[inline(always)]870pub fn semi_major(&self) -> f32 {871self.half_size.max_element()872}873874/// Returns the length of the semi-minor axis. This corresponds to the shortest radius of the ellipse.875#[inline(always)]876pub fn semi_minor(&self) -> f32 {877self.half_size.min_element()878}879}880881impl Measured2d for Ellipse {882/// Get the area of the ellipse883#[inline(always)]884fn area(&self) -> f32 {885PI * self.half_size.x * self.half_size.y886}887888#[inline(always)]889/// Get an approximation for the perimeter or circumference of the ellipse.890///891/// The approximation is reasonably precise with a relative error less than 0.007%, getting more precise as the eccentricity of the ellipse decreases.892fn perimeter(&self) -> f32 {893let a = self.semi_major();894let b = self.semi_minor();895896// In the case that `a == b`, the ellipse is a circle897if a / b - 1. < 1e-5 {898return PI * (a + b);899};900901// In the case that `a` is much larger than `b`, the ellipse is a line902if a / b > 1e4 {903return 4. * a;904};905906// These values are the result of (0.5 choose n)^2 where n is the index in the array907// They could be calculated on the fly but hardcoding them yields more accurate and faster results908// because the actual calculation for these values involves factorials and numbers > 10^23909const BINOMIAL_COEFFICIENTS: [f32; 21] = [9101.,9110.25,9120.015625,9130.00390625,9140.0015258789,9150.00074768066,9160.00042057037,9170.00025963783,9180.00017140154,9190.000119028846,9200.00008599834,9210.00006414339,9220.000049109784,9230.000038430585,9240.000030636627,9250.000024815668,9260.000020380836,9270.000016942893,9280.000014236736,9290.000012077564,9300.000010333865,931];932933// The algorithm used here is the Gauss-Kummer infinite series expansion of the elliptic integral expression for the perimeter of ellipses934// For more information see https://www.wolframalpha.com/input/?i=gauss-kummer+series935// We only use the terms up to `i == 20` for this approximation936let h = ((a - b) / (a + b)).squared();937938PI * (a + b)939* (0..=20)940.map(|i| BINOMIAL_COEFFICIENTS[i] * ops::powf(h, i as f32))941.sum::<f32>()942}943}944945/// A primitive shape formed by the region between two circles, also known as a ring.946#[derive(Clone, Copy, Debug, PartialEq)]947#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]948#[cfg_attr(949feature = "bevy_reflect",950derive(Reflect),951reflect(Debug, PartialEq, Default, Clone)952)]953#[cfg_attr(954all(feature = "serialize", feature = "bevy_reflect"),955reflect(Serialize, Deserialize)956)]957#[doc(alias = "Ring")]958pub struct Annulus {959/// The inner circle of the annulus960pub inner_circle: Circle,961/// The outer circle of the annulus962pub outer_circle: Circle,963}964965impl Primitive2d for Annulus {}966967impl Default for Annulus {968/// Returns the default [`Annulus`] with radii of `0.5` and `1.0`.969fn default() -> Self {970Self {971inner_circle: Circle::new(0.5),972outer_circle: Circle::new(1.0),973}974}975}976977impl Annulus {978/// Create a new [`Annulus`] from the radii of the inner and outer circle979#[inline(always)]980pub const fn new(inner_radius: f32, outer_radius: f32) -> Self {981Self {982inner_circle: Circle::new(inner_radius),983outer_circle: Circle::new(outer_radius),984}985}986987/// Get the diameter of the annulus988#[inline(always)]989pub const fn diameter(&self) -> f32 {990self.outer_circle.diameter()991}992993/// Get the thickness of the annulus994#[inline(always)]995pub const fn thickness(&self) -> f32 {996self.outer_circle.radius - self.inner_circle.radius997}998999/// Finds the point on the annulus that is closest to the given `point`:1000///1001/// - If the point is outside of the annulus completely, the returned point will be on the outer perimeter.1002/// - If the point is inside of the inner circle (hole) of the annulus, the returned point will be on the inner perimeter.1003/// - Otherwise, the returned point is overlapping the annulus and returned as is.1004#[inline(always)]1005pub fn closest_point(&self, point: Vec2) -> Vec2 {1006let distance_squared = point.length_squared();10071008if self.inner_circle.radius.squared() <= distance_squared {1009if distance_squared <= self.outer_circle.radius.squared() {1010// The point is inside the annulus.1011point1012} else {1013// The point is outside the annulus and closer to the outer perimeter.1014// Find the closest point on the perimeter of the annulus.1015let dir_to_point = point / ops::sqrt(distance_squared);1016self.outer_circle.radius * dir_to_point1017}1018} else {1019// The point is outside the annulus and closer to the inner perimeter.1020// Find the closest point on the perimeter of the annulus.1021let dir_to_point = point / ops::sqrt(distance_squared);1022self.inner_circle.radius * dir_to_point1023}1024}1025}10261027impl Measured2d for Annulus {1028/// Get the area of the annulus1029#[inline(always)]1030fn area(&self) -> f32 {1031PI * (self.outer_circle.radius.squared() - self.inner_circle.radius.squared())1032}10331034/// Get the perimeter or circumference of the annulus,1035/// which is the sum of the perimeters of the inner and outer circles.1036#[inline(always)]1037#[doc(alias = "circumference")]1038fn perimeter(&self) -> f32 {10392.0 * PI * (self.outer_circle.radius + self.inner_circle.radius)1040}1041}10421043/// A rhombus primitive, also known as a diamond shape.1044/// A four sided polygon, centered on the origin, where opposite sides are parallel but without1045/// requiring right angles.1046#[derive(Clone, Copy, Debug, PartialEq)]1047#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1048#[cfg_attr(1049feature = "bevy_reflect",1050derive(Reflect),1051reflect(Debug, PartialEq, Default, Clone)1052)]1053#[cfg_attr(1054all(feature = "serialize", feature = "bevy_reflect"),1055reflect(Serialize, Deserialize)1056)]1057#[doc(alias = "Diamond")]1058pub struct Rhombus {1059/// Size of the horizontal and vertical diagonals of the rhombus1060pub half_diagonals: Vec2,1061}10621063impl Primitive2d for Rhombus {}10641065impl Default for Rhombus {1066/// Returns the default [`Rhombus`] with a half-horizontal and half-vertical diagonal of `0.5`.1067fn default() -> Self {1068Self {1069half_diagonals: Vec2::splat(0.5),1070}1071}1072}10731074impl Rhombus {1075/// Create a new `Rhombus` from a vertical and horizontal diagonal sizes.1076#[inline(always)]1077pub const fn new(horizontal_diagonal: f32, vertical_diagonal: f32) -> Self {1078Self {1079half_diagonals: Vec2::new(horizontal_diagonal / 2.0, vertical_diagonal / 2.0),1080}1081}10821083/// Create a new `Rhombus` from a side length with all inner angles equal.1084#[inline(always)]1085pub const fn from_side(side: f32) -> Self {1086Self {1087half_diagonals: Vec2::splat(side * FRAC_1_SQRT_2),1088}1089}10901091/// Create a new `Rhombus` from a given inradius with all inner angles equal.1092#[inline(always)]1093pub const fn from_inradius(inradius: f32) -> Self {1094let half_diagonal = inradius * 2.0 / core::f32::consts::SQRT_2;1095Self {1096half_diagonals: Vec2::new(half_diagonal, half_diagonal),1097}1098}10991100/// Get the length of each side of the rhombus1101#[inline(always)]1102pub fn side(&self) -> f32 {1103self.half_diagonals.length()1104}11051106/// Get the radius of the circumcircle on which all vertices1107/// of the rhombus lie1108#[inline(always)]1109pub const fn circumradius(&self) -> f32 {1110self.half_diagonals.x.max(self.half_diagonals.y)1111}11121113/// Get the radius of the largest circle that can1114/// be drawn within the rhombus1115#[inline(always)]1116#[doc(alias = "apothem")]1117pub fn inradius(&self) -> f32 {1118let side = self.side();1119if side == 0.0 {11200.01121} else {1122(self.half_diagonals.x * self.half_diagonals.y) / side1123}1124}11251126/// Finds the point on the rhombus that is closest to the given `point`.1127///1128/// If the point is outside the rhombus, the returned point will be on the perimeter of the rhombus.1129/// Otherwise, it will be inside the rhombus and returned as is.1130#[inline(always)]1131pub fn closest_point(&self, point: Vec2) -> Vec2 {1132// Fold the problem into the positive quadrant1133let point_abs = point.abs();1134let half_diagonals = self.half_diagonals.abs(); // to ensure correct sign11351136// The unnormalised normal vector perpendicular to the side of the rhombus1137let normal = Vec2::new(half_diagonals.y, half_diagonals.x);1138let normal_magnitude_squared = normal.length_squared();1139if normal_magnitude_squared == 0.0 {1140return Vec2::ZERO; // A null Rhombus has only one point anyway.1141}11421143// The last term corresponds to normal.dot(rhombus_vertex)1144let distance_unnormalised = normal.dot(point_abs) - half_diagonals.x * half_diagonals.y;11451146// The point is already inside so we simply return it.1147if distance_unnormalised <= 0.0 {1148return point;1149}11501151// Clamp the point to the edge1152let mut result = point_abs - normal * distance_unnormalised / normal_magnitude_squared;11531154// Clamp the point back to the positive quadrant1155// if it's outside, it needs to be clamped to either vertex1156if result.x <= 0.0 {1157result = Vec2::new(0.0, half_diagonals.y);1158} else if result.y <= 0.0 {1159result = Vec2::new(half_diagonals.x, 0.0);1160}11611162// Finally, we restore the signs of the original vector1163result.copysign(point)1164}1165}11661167impl Measured2d for Rhombus {1168/// Get the area of the rhombus1169#[inline(always)]1170fn area(&self) -> f32 {11712.0 * self.half_diagonals.x * self.half_diagonals.y1172}11731174/// Get the perimeter of the rhombus1175#[inline(always)]1176fn perimeter(&self) -> f32 {11774.0 * self.side()1178}1179}11801181/// An unbounded plane in 2D space. It forms a separating surface through the origin,1182/// stretching infinitely far1183#[derive(Clone, Copy, Debug, PartialEq)]1184#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1185#[cfg_attr(1186feature = "bevy_reflect",1187derive(Reflect),1188reflect(Debug, PartialEq, Default, Clone)1189)]1190#[cfg_attr(1191all(feature = "serialize", feature = "bevy_reflect"),1192reflect(Serialize, Deserialize)1193)]1194pub struct Plane2d {1195/// The normal of the plane. The plane will be placed perpendicular to this direction1196pub normal: Dir2,1197}11981199impl Primitive2d for Plane2d {}12001201impl Default for Plane2d {1202/// Returns the default [`Plane2d`] with a normal pointing in the `+Y` direction.1203fn default() -> Self {1204Self { normal: Dir2::Y }1205}1206}12071208impl Plane2d {1209/// Create a new `Plane2d` from a normal1210///1211/// # Panics1212///1213/// Panics if the given `normal` is zero (or very close to zero), or non-finite.1214#[inline(always)]1215pub fn new(normal: Vec2) -> Self {1216Self {1217normal: Dir2::new(normal).expect("normal must be nonzero and finite"),1218}1219}1220}12211222/// An infinite line going through the origin along a direction in 2D space.1223///1224/// For a finite line: [`Segment2d`]1225#[derive(Clone, Copy, Debug, PartialEq)]1226#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1227#[cfg_attr(1228feature = "bevy_reflect",1229derive(Reflect),1230reflect(Debug, PartialEq, Clone)1231)]1232#[cfg_attr(1233all(feature = "serialize", feature = "bevy_reflect"),1234reflect(Serialize, Deserialize)1235)]1236pub struct Line2d {1237/// The direction of the line. The line extends infinitely in both the given direction1238/// and its opposite direction1239pub direction: Dir2,1240}12411242impl Primitive2d for Line2d {}12431244/// A line segment defined by two endpoints in 2D space.1245#[derive(Clone, Copy, Debug, PartialEq)]1246#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1247#[cfg_attr(1248feature = "bevy_reflect",1249derive(Reflect),1250reflect(Debug, PartialEq, Clone)1251)]1252#[cfg_attr(1253all(feature = "serialize", feature = "bevy_reflect"),1254reflect(Serialize, Deserialize)1255)]1256#[doc(alias = "LineSegment2d")]1257pub struct Segment2d {1258/// The endpoints of the line segment.1259pub vertices: [Vec2; 2],1260}12611262impl Primitive2d for Segment2d {}12631264impl Default for Segment2d {1265fn default() -> Self {1266Self {1267vertices: [Vec2::new(-0.5, 0.0), Vec2::new(0.5, 0.0)],1268}1269}1270}12711272impl Segment2d {1273/// Create a new `Segment2d` from its endpoints.1274#[inline(always)]1275pub const fn new(point1: Vec2, point2: Vec2) -> Self {1276Self {1277vertices: [point1, point2],1278}1279}12801281/// Create a new `Segment2d` centered at the origin with the given direction and length.1282///1283/// The endpoints will be at `-direction * length / 2.0` and `direction * length / 2.0`.1284#[inline(always)]1285pub fn from_direction_and_length(direction: Dir2, length: f32) -> Self {1286let endpoint = 0.5 * length * direction;1287Self {1288vertices: [-endpoint, endpoint],1289}1290}12911292/// Create a new `Segment2d` centered at the origin from a vector representing1293/// the direction and length of the line segment.1294///1295/// The endpoints will be at `-scaled_direction / 2.0` and `scaled_direction / 2.0`.1296#[inline(always)]1297pub fn from_scaled_direction(scaled_direction: Vec2) -> Self {1298let endpoint = 0.5 * scaled_direction;1299Self {1300vertices: [-endpoint, endpoint],1301}1302}13031304/// Create a new `Segment2d` starting from the origin of the given `ray`,1305/// going in the direction of the ray for the given `length`.1306///1307/// The endpoints will be at `ray.origin` and `ray.origin + length * ray.direction`.1308#[inline(always)]1309pub fn from_ray_and_length(ray: Ray2d, length: f32) -> Self {1310Self {1311vertices: [ray.origin, ray.get_point(length)],1312}1313}13141315/// Get the position of the first endpoint of the line segment.1316#[inline(always)]1317pub const fn point1(&self) -> Vec2 {1318self.vertices[0]1319}13201321/// Get the position of the second endpoint of the line segment.1322#[inline(always)]1323pub const fn point2(&self) -> Vec2 {1324self.vertices[1]1325}13261327/// Compute the midpoint between the two endpoints of the line segment.1328#[inline(always)]1329#[doc(alias = "midpoint")]1330pub fn center(&self) -> Vec2 {1331self.point1().midpoint(self.point2())1332}13331334/// Compute the length of the line segment.1335#[inline(always)]1336pub fn length(&self) -> f32 {1337self.point1().distance(self.point2())1338}13391340/// Compute the squared length of the line segment.1341#[inline(always)]1342pub fn length_squared(&self) -> f32 {1343self.point1().distance_squared(self.point2())1344}13451346/// Compute the normalized direction pointing from the first endpoint to the second endpoint.1347///1348/// For the non-panicking version, see [`Segment2d::try_direction`].1349///1350/// # Panics1351///1352/// Panics if a valid direction could not be computed, for example when the endpoints are coincident, NaN, or infinite.1353#[inline(always)]1354pub fn direction(&self) -> Dir2 {1355self.try_direction().unwrap_or_else(|err| {1356panic!("Failed to compute the direction of a line segment: {err}")1357})1358}13591360/// Try to compute the normalized direction pointing from the first endpoint to the second endpoint.1361///1362/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid direction could not be computed,1363/// for example when the endpoints are coincident, NaN, or infinite.1364#[inline(always)]1365pub fn try_direction(&self) -> Result<Dir2, InvalidDirectionError> {1366Dir2::new(self.scaled_direction())1367}13681369/// Compute the vector from the first endpoint to the second endpoint.1370#[inline(always)]1371pub fn scaled_direction(&self) -> Vec2 {1372self.point2() - self.point1()1373}13741375/// Compute the normalized counterclockwise normal on the left-hand side of the line segment.1376///1377/// For the non-panicking version, see [`Segment2d::try_left_normal`].1378///1379/// # Panics1380///1381/// Panics if a valid normal could not be computed, for example when the endpoints are coincident, NaN, or infinite.1382#[inline(always)]1383pub fn left_normal(&self) -> Dir2 {1384self.try_left_normal().unwrap_or_else(|err| {1385panic!("Failed to compute the left-hand side normal of a line segment: {err}")1386})1387}13881389/// Try to compute the normalized counterclockwise normal on the left-hand side of the line segment.1390///1391/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid normal could not be computed,1392/// for example when the endpoints are coincident, NaN, or infinite.1393#[inline(always)]1394pub fn try_left_normal(&self) -> Result<Dir2, InvalidDirectionError> {1395Dir2::new(self.scaled_left_normal())1396}13971398/// Compute the non-normalized counterclockwise normal on the left-hand side of the line segment.1399///1400/// The length of the normal is the distance between the endpoints.1401#[inline(always)]1402pub fn scaled_left_normal(&self) -> Vec2 {1403let scaled_direction = self.scaled_direction();1404Vec2::new(-scaled_direction.y, scaled_direction.x)1405}14061407/// Compute the normalized clockwise normal on the right-hand side of the line segment.1408///1409/// For the non-panicking version, see [`Segment2d::try_right_normal`].1410///1411/// # Panics1412///1413/// Panics if a valid normal could not be computed, for example when the endpoints are coincident, NaN, or infinite.1414#[inline(always)]1415pub fn right_normal(&self) -> Dir2 {1416self.try_right_normal().unwrap_or_else(|err| {1417panic!("Failed to compute the right-hand side normal of a line segment: {err}")1418})1419}14201421/// Try to compute the normalized clockwise normal on the right-hand side of the line segment.1422///1423/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid normal could not be computed,1424/// for example when the endpoints are coincident, NaN, or infinite.1425#[inline(always)]1426pub fn try_right_normal(&self) -> Result<Dir2, InvalidDirectionError> {1427Dir2::new(self.scaled_right_normal())1428}14291430/// Compute the non-normalized clockwise normal on the right-hand side of the line segment.1431///1432/// The length of the normal is the distance between the endpoints.1433#[inline(always)]1434pub fn scaled_right_normal(&self) -> Vec2 {1435let scaled_direction = self.scaled_direction();1436Vec2::new(scaled_direction.y, -scaled_direction.x)1437}14381439/// Compute the segment transformed by the given [`Isometry2d`].1440#[inline(always)]1441pub fn transformed(&self, isometry: impl Into<Isometry2d>) -> Self {1442let isometry: Isometry2d = isometry.into();1443Self::new(1444isometry.transform_point(self.point1()),1445isometry.transform_point(self.point2()),1446)1447}14481449/// Compute the segment translated by the given vector.1450#[inline(always)]1451pub fn translated(&self, translation: Vec2) -> Segment2d {1452Self::new(self.point1() + translation, self.point2() + translation)1453}14541455/// Compute the segment rotated around the origin by the given rotation.1456#[inline(always)]1457pub fn rotated(&self, rotation: Rot2) -> Segment2d {1458Segment2d::new(rotation * self.point1(), rotation * self.point2())1459}14601461/// Compute the segment rotated around the given point by the given rotation.1462#[inline(always)]1463pub fn rotated_around(&self, rotation: Rot2, point: Vec2) -> Segment2d {1464// We offset our segment so that our segment is rotated as if from the origin, then we can apply the offset back1465let offset = self.translated(-point);1466let rotated = offset.rotated(rotation);1467rotated.translated(point)1468}14691470/// Compute the segment rotated around its own center.1471#[inline(always)]1472pub fn rotated_around_center(&self, rotation: Rot2) -> Segment2d {1473self.rotated_around(rotation, self.center())1474}14751476/// Compute the segment with its center at the origin, keeping the same direction and length.1477#[inline(always)]1478pub fn centered(&self) -> Segment2d {1479let center = self.center();1480self.translated(-center)1481}14821483/// Compute the segment with a new length, keeping the same direction and center.1484#[inline(always)]1485pub fn resized(&self, length: f32) -> Segment2d {1486let offset_from_origin = self.center();1487let centered = self.translated(-offset_from_origin);1488let ratio = length / self.length();1489let segment = Segment2d::new(centered.point1() * ratio, centered.point2() * ratio);1490segment.translated(offset_from_origin)1491}14921493/// Reverses the direction of the line segment by swapping the endpoints.1494#[inline(always)]1495pub fn reverse(&mut self) {1496let [point1, point2] = &mut self.vertices;1497core::mem::swap(point1, point2);1498}14991500/// Returns the line segment with its direction reversed by swapping the endpoints.1501#[inline(always)]1502#[must_use]1503pub fn reversed(mut self) -> Self {1504self.reverse();1505self1506}15071508/// Returns the point on the [`Segment2d`] that is closest to the specified `point`.1509#[inline(always)]1510pub fn closest_point(&self, point: Vec2) -> Vec2 {1511// `point`1512// x1513// ^|1514// / |1515//`offset`/ |1516// / | `segment_vector`1517// x----.-------------->x1518// 0 t 11519let segment_vector = self.vertices[1] - self.vertices[0];1520let offset = point - self.vertices[0];1521// The signed projection of `offset` onto `segment_vector`, scaled by the length of the segment.1522let projection_scaled = segment_vector.dot(offset);15231524// `point` is too far "left" in the picture1525if projection_scaled <= 0.0 {1526return self.vertices[0];1527}15281529let length_squared = segment_vector.length_squared();1530// `point` is too far "right" in the picture1531if projection_scaled >= length_squared {1532return self.vertices[1];1533}15341535// Point lies somewhere in the middle, we compute the closest point by finding the parameter along the line.1536let t = projection_scaled / length_squared;1537self.vertices[0] + t * segment_vector1538}1539}15401541impl From<[Vec2; 2]> for Segment2d {1542#[inline(always)]1543fn from(vertices: [Vec2; 2]) -> Self {1544Self { vertices }1545}1546}15471548impl From<(Vec2, Vec2)> for Segment2d {1549#[inline(always)]1550fn from((point1, point2): (Vec2, Vec2)) -> Self {1551Self::new(point1, point2)1552}1553}15541555/// A series of connected line segments in 2D space.1556#[cfg(feature = "alloc")]1557#[derive(Clone, Debug, PartialEq)]1558#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1559#[cfg_attr(1560feature = "bevy_reflect",1561derive(Reflect),1562reflect(Debug, PartialEq, Clone)1563)]1564#[cfg_attr(1565all(feature = "serialize", feature = "bevy_reflect"),1566reflect(Serialize, Deserialize)1567)]1568pub struct Polyline2d {1569/// The vertices of the polyline1570pub vertices: Vec<Vec2>,1571}15721573#[cfg(feature = "alloc")]1574impl Primitive2d for Polyline2d {}15751576#[cfg(feature = "alloc")]1577impl FromIterator<Vec2> for Polyline2d {1578fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {1579Self {1580vertices: iter.into_iter().collect(),1581}1582}1583}15841585#[cfg(feature = "alloc")]1586impl Default for Polyline2d {1587fn default() -> Self {1588Self {1589vertices: Vec::from([Vec2::new(-0.5, 0.0), Vec2::new(0.5, 0.0)]),1590}1591}1592}15931594#[cfg(feature = "alloc")]1595impl Polyline2d {1596/// Create a new `Polyline2d` from its vertices1597pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {1598Self::from_iter(vertices)1599}16001601/// Create a new `Polyline2d` from two endpoints with subdivision points.1602/// `subdivisions = 0` creates a simple line with just start and end points.1603/// `subdivisions = 1` adds one point in the middle, creating 2 segments, etc.1604pub fn with_subdivisions(start: Vec2, end: Vec2, subdivisions: usize) -> Self {1605let total_vertices = subdivisions + 2;1606let mut vertices = Vec::with_capacity(total_vertices);16071608let step = (end - start) / (subdivisions + 1) as f32;1609for i in 0..total_vertices {1610vertices.push(start + step * i as f32);1611}16121613Self { vertices }1614}1615}16161617/// A triangle in 2D space1618#[derive(Clone, Copy, Debug, PartialEq)]1619#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1620#[cfg_attr(1621feature = "bevy_reflect",1622derive(Reflect),1623reflect(Debug, PartialEq, Default, Clone)1624)]1625#[cfg_attr(1626all(feature = "serialize", feature = "bevy_reflect"),1627reflect(Serialize, Deserialize)1628)]1629pub struct Triangle2d {1630/// The vertices of the triangle1631pub vertices: [Vec2; 3],1632}16331634impl Primitive2d for Triangle2d {}16351636impl Default for Triangle2d {1637/// Returns the default [`Triangle2d`] with the vertices `[0.0, 0.5]`, `[-0.5, -0.5]`, and `[0.5, -0.5]`.1638fn default() -> Self {1639Self {1640vertices: [Vec2::Y * 0.5, Vec2::new(-0.5, -0.5), Vec2::new(0.5, -0.5)],1641}1642}1643}16441645impl Triangle2d {1646/// Create a new `Triangle2d` from points `a`, `b`, and `c`1647#[inline(always)]1648pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {1649Self {1650vertices: [a, b, c],1651}1652}16531654/// Get the [`WindingOrder`] of the triangle1655#[inline(always)]1656#[doc(alias = "orientation")]1657pub fn winding_order(&self) -> WindingOrder {1658let [a, b, c] = self.vertices;1659let area = (b - a).perp_dot(c - a);1660if area > f32::EPSILON {1661WindingOrder::CounterClockwise1662} else if area < -f32::EPSILON {1663WindingOrder::Clockwise1664} else {1665WindingOrder::Invalid1666}1667}16681669/// Compute the circle passing through all three vertices of the triangle.1670/// The vector in the returned tuple is the circumcenter.1671pub fn circumcircle(&self) -> (Circle, Vec2) {1672// We treat the triangle as translated so that vertex A is at the origin. This simplifies calculations.1673//1674// A = (0, 0)1675// *1676// / \1677// / \1678// / \1679// / \1680// / U \1681// / \1682// *-------------*1683// B C16841685let a = self.vertices[0];1686let (b, c) = (self.vertices[1] - a, self.vertices[2] - a);1687let b_length_sq = b.length_squared();1688let c_length_sq = c.length_squared();16891690// Reference: https://en.wikipedia.org/wiki/Circumcircle#Cartesian_coordinates_21691let inv_d = (2.0 * (b.x * c.y - b.y * c.x)).recip();1692let ux = inv_d * (c.y * b_length_sq - b.y * c_length_sq);1693let uy = inv_d * (b.x * c_length_sq - c.x * b_length_sq);1694let u = Vec2::new(ux, uy);16951696// Compute true circumcenter and circumradius, adding the tip coordinate so that1697// A is translated back to its actual coordinate.1698let center = u + a;1699let radius = u.length();17001701(Circle { radius }, center)1702}17031704/// Checks if the triangle is degenerate, meaning it has zero area.1705///1706/// A triangle is degenerate if the cross product of the vectors `ab` and `ac` has a length less than `10e-7`.1707/// This indicates that the three vertices are collinear or nearly collinear.1708#[inline(always)]1709pub fn is_degenerate(&self) -> bool {1710let [a, b, c] = self.vertices;1711let ab = (b - a).extend(0.);1712let ac = (c - a).extend(0.);1713ab.cross(ac).length() < 10e-71714}17151716/// Checks if the triangle is acute, meaning all angles are less than 90 degrees1717#[inline(always)]1718pub fn is_acute(&self) -> bool {1719let [a, b, c] = self.vertices;1720let ab = b - a;1721let bc = c - b;1722let ca = a - c;17231724// a^2 + b^2 < c^2 for an acute triangle1725let mut side_lengths = [1726ab.length_squared(),1727bc.length_squared(),1728ca.length_squared(),1729];1730side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());1731side_lengths[0] + side_lengths[1] > side_lengths[2]1732}17331734/// Checks if the triangle is obtuse, meaning one angle is greater than 90 degrees1735#[inline(always)]1736pub fn is_obtuse(&self) -> bool {1737let [a, b, c] = self.vertices;1738let ab = b - a;1739let bc = c - b;1740let ca = a - c;17411742// a^2 + b^2 > c^2 for an obtuse triangle1743let mut side_lengths = [1744ab.length_squared(),1745bc.length_squared(),1746ca.length_squared(),1747];1748side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());1749side_lengths[0] + side_lengths[1] < side_lengths[2]1750}17511752/// Reverse the [`WindingOrder`] of the triangle1753/// by swapping the first and last vertices.1754#[inline(always)]1755pub fn reverse(&mut self) {1756self.vertices.swap(0, 2);1757}17581759/// This triangle but reversed.1760#[inline(always)]1761#[must_use]1762pub fn reversed(mut self) -> Self {1763self.reverse();1764self1765}1766}17671768impl Measured2d for Triangle2d {1769/// Get the area of the triangle1770#[inline(always)]1771fn area(&self) -> f32 {1772let [a, b, c] = self.vertices;1773ops::abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2.01774}17751776/// Get the perimeter of the triangle1777#[inline(always)]1778fn perimeter(&self) -> f32 {1779let [a, b, c] = self.vertices;17801781let ab = a.distance(b);1782let bc = b.distance(c);1783let ca = c.distance(a);17841785ab + bc + ca1786}1787}17881789/// A rectangle primitive, which is like a square, except that the width and height can be different1790#[derive(Clone, Copy, Debug, PartialEq)]1791#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1792#[cfg_attr(1793feature = "bevy_reflect",1794derive(Reflect),1795reflect(Debug, PartialEq, Default, Clone)1796)]1797#[cfg_attr(1798all(feature = "serialize", feature = "bevy_reflect"),1799reflect(Serialize, Deserialize)1800)]1801#[doc(alias = "Quad")]1802pub struct Rectangle {1803/// Half of the width and height of the rectangle1804pub half_size: Vec2,1805}18061807impl Primitive2d for Rectangle {}18081809impl Default for Rectangle {1810/// Returns the default [`Rectangle`] with a half-width and half-height of `0.5`.1811fn default() -> Self {1812Self {1813half_size: Vec2::splat(0.5),1814}1815}1816}18171818impl Rectangle {1819/// Create a new `Rectangle` from a full width and height1820#[inline(always)]1821pub const fn new(width: f32, height: f32) -> Self {1822Self::from_size(Vec2::new(width, height))1823}18241825/// Create a new `Rectangle` from a given full size1826#[inline(always)]1827pub const fn from_size(size: Vec2) -> Self {1828Self {1829half_size: Vec2::new(size.x / 2.0, size.y / 2.0),1830}1831}18321833/// Create a new `Rectangle` from two corner points1834#[inline(always)]1835pub fn from_corners(point1: Vec2, point2: Vec2) -> Self {1836Self {1837half_size: (point2 - point1).abs() / 2.0,1838}1839}18401841/// Create a `Rectangle` from a single length.1842/// The resulting `Rectangle` will be the same size in every direction.1843#[inline(always)]1844pub const fn from_length(length: f32) -> Self {1845Self {1846half_size: Vec2::splat(length / 2.0),1847}1848}18491850/// Get the size of the rectangle1851#[inline(always)]1852pub fn size(&self) -> Vec2 {18532.0 * self.half_size1854}18551856/// Finds the point on the rectangle that is closest to the given `point`.1857///1858/// If the point is outside the rectangle, the returned point will be on the perimeter of the rectangle.1859/// Otherwise, it will be inside the rectangle and returned as is.1860#[inline(always)]1861pub fn closest_point(&self, point: Vec2) -> Vec2 {1862// Clamp point coordinates to the rectangle1863point.clamp(-self.half_size, self.half_size)1864}1865}18661867impl Measured2d for Rectangle {1868/// Get the area of the rectangle1869#[inline(always)]1870fn area(&self) -> f32 {18714.0 * self.half_size.x * self.half_size.y1872}18731874/// Get the perimeter of the rectangle1875#[inline(always)]1876fn perimeter(&self) -> f32 {18774.0 * (self.half_size.x + self.half_size.y)1878}1879}18801881/// A polygon with N vertices.1882#[cfg(feature = "alloc")]1883#[derive(Clone, Debug, PartialEq)]1884#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1885#[cfg_attr(1886feature = "bevy_reflect",1887derive(Reflect),1888reflect(Debug, PartialEq, Clone)1889)]1890#[cfg_attr(1891all(feature = "serialize", feature = "bevy_reflect"),1892reflect(Serialize, Deserialize)1893)]1894pub struct Polygon {1895/// The vertices of the `Polygon`1896pub vertices: Vec<Vec2>,1897}18981899#[cfg(feature = "alloc")]1900impl Primitive2d for Polygon {}19011902#[cfg(feature = "alloc")]1903impl FromIterator<Vec2> for Polygon {1904fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {1905Self {1906vertices: iter.into_iter().collect(),1907}1908}1909}19101911#[cfg(feature = "alloc")]1912impl Polygon {1913/// Create a new `Polygon` from its vertices1914pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {1915Self::from_iter(vertices)1916}19171918/// Tests if the polygon is simple.1919///1920/// A polygon is simple if it is not self intersecting and not self tangent.1921/// As such, no two edges of the polygon may cross each other and each vertex must not lie on another edge.1922#[cfg(feature = "alloc")]1923pub fn is_simple(&self) -> bool {1924is_polygon_simple(&self.vertices)1925}1926}19271928#[cfg(feature = "alloc")]1929impl From<ConvexPolygon> for Polygon {1930fn from(val: ConvexPolygon) -> Self {1931Polygon {1932vertices: val.vertices,1933}1934}1935}19361937/// A convex polygon with `N` vertices.1938#[cfg(feature = "alloc")]1939#[derive(Clone, Debug, PartialEq)]1940#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1941#[cfg_attr(1942feature = "bevy_reflect",1943derive(Reflect),1944reflect(Debug, PartialEq, Clone)1945)]1946#[cfg_attr(1947all(feature = "serialize", feature = "bevy_reflect"),1948reflect(Serialize, Deserialize)1949)]1950pub struct ConvexPolygon {1951/// The vertices of the [`ConvexPolygon`].1952vertices: Vec<Vec2>,1953}19541955#[cfg(feature = "alloc")]1956impl Primitive2d for ConvexPolygon {}19571958/// An error that happens when creating a [`ConvexPolygon`].1959#[cfg(feature = "alloc")]1960#[derive(Error, Debug, Clone)]1961pub enum ConvexPolygonError {1962/// The created polygon is not convex.1963#[error("The created polygon is not convex")]1964Concave,1965}19661967#[cfg(feature = "alloc")]1968impl ConvexPolygon {1969fn triangle_winding_order(1970&self,1971a_index: usize,1972b_index: usize,1973c_index: usize,1974) -> WindingOrder {1975let a = self.vertices[a_index];1976let b = self.vertices[b_index];1977let c = self.vertices[c_index];1978Triangle2d::new(a, b, c).winding_order()1979}19801981/// Create a [`ConvexPolygon`] from its `vertices`.1982///1983/// # Errors1984///1985/// Returns [`ConvexPolygonError::Concave`] if the `vertices` do not form a convex polygon.1986pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Result<Self, ConvexPolygonError> {1987let polygon = Self::new_unchecked(vertices);1988let len = polygon.vertices.len();1989let ref_winding_order = polygon.triangle_winding_order(len - 1, 0, 1);1990for i in 1..len {1991let winding_order = polygon.triangle_winding_order(i - 1, i, (i + 1) % len);1992if winding_order != ref_winding_order {1993return Err(ConvexPolygonError::Concave);1994}1995}1996Ok(polygon)1997}19981999/// Create a [`ConvexPolygon`] from its `vertices`, without checks.2000/// Use this version only if you know that the `vertices` make up a convex polygon.2001#[inline(always)]2002pub fn new_unchecked(vertices: impl IntoIterator<Item = Vec2>) -> Self {2003Self {2004vertices: vertices.into_iter().collect(),2005}2006}20072008/// Get the vertices of this polygon2009#[inline(always)]2010pub fn vertices(&self) -> &[Vec2] {2011&self.vertices2012}2013}20142015#[cfg(feature = "alloc")]2016impl TryFrom<Polygon> for ConvexPolygon {2017type Error = ConvexPolygonError;20182019fn try_from(val: Polygon) -> Result<Self, Self::Error> {2020ConvexPolygon::new(val.vertices)2021}2022}20232024/// A polygon centered on the origin where all vertices lie on a circle, equally far apart.2025#[derive(Clone, Copy, Debug, PartialEq)]2026#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]2027#[cfg_attr(2028feature = "bevy_reflect",2029derive(Reflect),2030reflect(Debug, PartialEq, Default, Clone)2031)]2032#[cfg_attr(2033all(feature = "serialize", feature = "bevy_reflect"),2034reflect(Serialize, Deserialize)2035)]2036pub struct RegularPolygon {2037/// The circumcircle on which all vertices lie2038pub circumcircle: Circle,2039/// The number of sides2040pub sides: u32,2041}20422043impl Primitive2d for RegularPolygon {}20442045impl Default for RegularPolygon {2046/// Returns the default [`RegularPolygon`] with six sides (a hexagon) and a circumradius of `0.5`.2047fn default() -> Self {2048Self {2049circumcircle: Circle { radius: 0.5 },2050sides: 6,2051}2052}2053}20542055impl RegularPolygon {2056/// Create a new `RegularPolygon`2057/// from the radius of the circumcircle and a number of sides2058///2059/// # Panics2060///2061/// Panics if `circumradius` is negative2062#[inline(always)]2063pub const fn new(circumradius: f32, sides: u32) -> Self {2064assert!(2065circumradius.is_sign_positive(),2066"polygon has a negative radius"2067);2068assert!(sides > 2, "polygon has less than 3 sides");20692070Self {2071circumcircle: Circle {2072radius: circumradius,2073},2074sides,2075}2076}20772078/// Get the radius of the circumcircle on which all vertices2079/// of the regular polygon lie2080#[inline(always)]2081pub const fn circumradius(&self) -> f32 {2082self.circumcircle.radius2083}20842085/// Get the inradius or apothem of the regular polygon.2086/// This is the radius of the largest circle that can2087/// be drawn within the polygon2088#[inline(always)]2089#[doc(alias = "apothem")]2090pub fn inradius(&self) -> f32 {2091self.circumradius() * ops::cos(PI / self.sides as f32)2092}20932094/// Get the length of one side of the regular polygon2095#[inline(always)]2096pub fn side_length(&self) -> f32 {20972.0 * self.circumradius() * ops::sin(PI / self.sides as f32)2098}20992100/// Get the internal angle of the regular polygon in degrees.2101///2102/// This is the angle formed by two adjacent sides with points2103/// within the angle being in the interior of the polygon2104#[inline(always)]2105pub const fn internal_angle_degrees(&self) -> f32 {2106(self.sides - 2) as f32 / self.sides as f32 * 180.02107}21082109/// Get the internal angle of the regular polygon in radians.2110///2111/// This is the angle formed by two adjacent sides with points2112/// within the angle being in the interior of the polygon2113#[inline(always)]2114pub const fn internal_angle_radians(&self) -> f32 {2115(self.sides - 2) as f32 * PI / self.sides as f322116}21172118/// Get the external angle of the regular polygon in degrees.2119///2120/// This is the angle formed by two adjacent sides with points2121/// within the angle being in the exterior of the polygon2122#[inline(always)]2123pub const fn external_angle_degrees(&self) -> f32 {2124360.0 / self.sides as f322125}21262127/// Get the external angle of the regular polygon in radians.2128///2129/// This is the angle formed by two adjacent sides with points2130/// within the angle being in the exterior of the polygon2131#[inline(always)]2132pub const fn external_angle_radians(&self) -> f32 {21332.0 * PI / self.sides as f322134}21352136/// Returns an iterator over the vertices of the regular polygon,2137/// rotated counterclockwise by the given angle in radians.2138///2139/// With a rotation of 0, a vertex will be placed at the top `(0.0, circumradius)`.2140pub fn vertices(self, rotation: f32) -> impl IntoIterator<Item = Vec2> {2141// Add pi/2 so that the polygon has a vertex at the top (sin is 1.0 and cos is 0.0)2142let start_angle = rotation + FRAC_PI_2;2143let step = core::f32::consts::TAU / self.sides as f32;21442145(0..self.sides).map(move |i| {2146let theta = start_angle + i as f32 * step;2147let (sin, cos) = ops::sin_cos(theta);2148Vec2::new(cos, sin) * self.circumcircle.radius2149})2150}2151}21522153impl Measured2d for RegularPolygon {2154/// Get the area of the regular polygon2155#[inline(always)]2156fn area(&self) -> f32 {2157let angle: f32 = 2.0 * PI / (self.sides as f32);2158(self.sides as f32) * self.circumradius().squared() * ops::sin(angle) / 2.02159}21602161/// Get the perimeter of the regular polygon.2162/// This is the sum of its sides2163#[inline(always)]2164fn perimeter(&self) -> f32 {2165self.sides as f32 * self.side_length()2166}2167}21682169/// A 2D capsule primitive, also known as a stadium or pill shape.2170///2171/// A two-dimensional capsule is defined as a neighborhood of points at a distance (radius) from a line2172#[derive(Clone, Copy, Debug, PartialEq)]2173#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]2174#[cfg_attr(2175feature = "bevy_reflect",2176derive(Reflect),2177reflect(Debug, PartialEq, Default, Clone)2178)]2179#[cfg_attr(2180all(feature = "serialize", feature = "bevy_reflect"),2181reflect(Serialize, Deserialize)2182)]2183#[doc(alias = "stadium", alias = "pill")]2184pub struct Capsule2d {2185/// The radius of the capsule2186pub radius: f32,2187/// Half the height of the capsule, excluding the semicircles2188pub half_length: f32,2189}21902191impl Primitive2d for Capsule2d {}21922193impl Default for Capsule2d {2194/// Returns the default [`Capsule2d`] with a radius of `0.5` and a half-height of `0.5`,2195/// excluding the semicircles.2196fn default() -> Self {2197Self {2198radius: 0.5,2199half_length: 0.5,2200}2201}2202}22032204impl Capsule2d {2205/// Create a new `Capsule2d` from a radius and length2206pub const fn new(radius: f32, length: f32) -> Self {2207Self {2208radius,2209half_length: length / 2.0,2210}2211}22122213/// Get the part connecting the semicircular ends of the capsule as a [`Rectangle`]2214#[inline]2215pub const fn to_inner_rectangle(&self) -> Rectangle {2216Rectangle::new(self.radius * 2.0, self.half_length * 2.0)2217}2218}22192220impl Measured2d for Capsule2d {2221/// Get the area of the capsule2222#[inline]2223fn area(&self) -> f32 {2224// pi*r^2 + (2r)*l2225PI * self.radius.squared() + self.to_inner_rectangle().area()2226}22272228/// Get the perimeter of the capsule2229#[inline]2230fn perimeter(&self) -> f32 {2231// 2pi*r + 2l22322.0 * PI * self.radius + 4.0 * self.half_length2233}2234}22352236#[cfg(test)]2237mod tests {2238// Reference values were computed by hand and/or with external tools22392240use super::*;2241use approx::{assert_abs_diff_eq, assert_relative_eq};22422243#[test]2244fn rectangle_closest_point() {2245let rectangle = Rectangle::new(2.0, 2.0);2246assert_eq!(rectangle.closest_point(Vec2::X * 10.0), Vec2::X);2247assert_eq!(rectangle.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);2248assert_eq!(2249rectangle.closest_point(Vec2::new(0.25, 0.1)),2250Vec2::new(0.25, 0.1)2251);2252}22532254#[test]2255fn circle_closest_point() {2256let circle = Circle { radius: 1.0 };2257assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);2258assert_eq!(2259circle.closest_point(Vec2::NEG_ONE * 10.0),2260Vec2::NEG_ONE.normalize()2261);2262assert_eq!(2263circle.closest_point(Vec2::new(0.25, 0.1)),2264Vec2::new(0.25, 0.1)2265);2266}22672268#[test]2269fn annulus_closest_point() {2270let annulus = Annulus::new(1.5, 2.0);2271assert_eq!(annulus.closest_point(Vec2::X * 10.0), Vec2::X * 2.0);2272assert_eq!(2273annulus.closest_point(Vec2::NEG_ONE),2274Vec2::NEG_ONE.normalize() * 1.52275);2276assert_eq!(2277annulus.closest_point(Vec2::new(1.55, 0.85)),2278Vec2::new(1.55, 0.85)2279);2280}22812282#[test]2283fn rhombus_closest_point() {2284let rhombus = Rhombus::new(2.0, 1.0);2285assert_eq!(rhombus.closest_point(Vec2::X * 10.0), Vec2::X);2286assert_eq!(2287rhombus.closest_point(Vec2::NEG_ONE * 0.2),2288Vec2::NEG_ONE * 0.22289);2290assert_eq!(2291rhombus.closest_point(Vec2::new(-0.55, 0.35)),2292Vec2::new(-0.5, 0.25)2293);22942295let rhombus = Rhombus::new(0.0, 0.0);2296assert_eq!(rhombus.closest_point(Vec2::X * 10.0), Vec2::ZERO);2297assert_eq!(rhombus.closest_point(Vec2::NEG_ONE * 0.2), Vec2::ZERO);2298assert_eq!(rhombus.closest_point(Vec2::new(-0.55, 0.35)), Vec2::ZERO);2299}23002301#[test]2302fn segment_closest_point() {2303assert_eq!(2304Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(3.0, 0.0))2305.closest_point(Vec2::new(1.0, 6.0)),2306Vec2::new(1.0, 0.0)2307);23082309let segments = [2310Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(0.0, 0.0)),2311Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0)),2312Segment2d::new(Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0)),2313Segment2d::new(Vec2::new(1.0, 0.0), Vec2::new(1.0, 5.0 * f32::EPSILON)),2314];2315let points = [2316Vec2::new(0.0, 0.0),2317Vec2::new(1.0, 0.0),2318Vec2::new(-1.0, 1.0),2319Vec2::new(1.0, 1.0),2320Vec2::new(-1.0, 0.0),2321Vec2::new(5.0, -1.0),2322Vec2::new(1.0, f32::EPSILON),2323];23242325for point in points.iter() {2326for segment in segments.iter() {2327let closest = segment.closest_point(*point);2328assert!(2329point.distance_squared(closest) <= point.distance_squared(segment.point1()),2330"Closest point must always be at least as close as either vertex."2331);2332assert!(2333point.distance_squared(closest) <= point.distance_squared(segment.point2()),2334"Closest point must always be at least as close as either vertex."2335);2336assert!(2337point.distance_squared(closest) <= point.distance_squared(segment.center()),2338"Closest point must always be at least as close as the center."2339);2340let closest_to_closest = segment.closest_point(closest);2341// Closest point must already be on the segment2342assert_relative_eq!(closest_to_closest, closest);2343}2344}2345}23462347#[test]2348fn circle_math() {2349let circle = Circle { radius: 3.0 };2350assert_eq!(circle.diameter(), 6.0, "incorrect diameter");2351assert_eq!(circle.area(), 28.274334, "incorrect area");2352assert_eq!(circle.perimeter(), 18.849556, "incorrect perimeter");2353}23542355#[test]2356fn capsule_math() {2357let capsule = Capsule2d::new(2.0, 9.0);2358assert_eq!(2359capsule.to_inner_rectangle(),2360Rectangle::new(4.0, 9.0),2361"rectangle wasn't created correctly from a capsule"2362);2363assert_eq!(capsule.area(), 48.566371, "incorrect area");2364assert_eq!(capsule.perimeter(), 30.566371, "incorrect perimeter");2365}23662367#[test]2368fn annulus_math() {2369let annulus = Annulus::new(2.5, 3.5);2370assert_eq!(annulus.diameter(), 7.0, "incorrect diameter");2371assert_eq!(annulus.thickness(), 1.0, "incorrect thickness");2372assert_eq!(annulus.area(), 18.849556, "incorrect area");2373assert_eq!(annulus.perimeter(), 37.699112, "incorrect perimeter");2374}23752376#[test]2377fn rhombus_math() {2378let rhombus = Rhombus::new(3.0, 4.0);2379assert_eq!(rhombus.area(), 6.0, "incorrect area");2380assert_eq!(rhombus.perimeter(), 10.0, "incorrect perimeter");2381assert_eq!(rhombus.side(), 2.5, "incorrect side");2382assert_eq!(rhombus.inradius(), 1.2, "incorrect inradius");2383assert_eq!(rhombus.circumradius(), 2.0, "incorrect circumradius");2384let rhombus = Rhombus::new(0.0, 0.0);2385assert_eq!(rhombus.area(), 0.0, "incorrect area");2386assert_eq!(rhombus.perimeter(), 0.0, "incorrect perimeter");2387assert_eq!(rhombus.side(), 0.0, "incorrect side");2388assert_eq!(rhombus.inradius(), 0.0, "incorrect inradius");2389assert_eq!(rhombus.circumradius(), 0.0, "incorrect circumradius");2390let rhombus = Rhombus::from_side(core::f32::consts::SQRT_2);2391assert_abs_diff_eq!(rhombus.half_diagonals, Vec2::new(1.0, 1.0));2392assert_abs_diff_eq!(2393rhombus.half_diagonals,2394Rhombus::from_inradius(FRAC_1_SQRT_2).half_diagonals2395);2396}23972398#[test]2399fn ellipse_math() {2400let ellipse = Ellipse::new(3.0, 1.0);2401assert_eq!(ellipse.area(), 9.424778, "incorrect area");24022403assert_eq!(ellipse.eccentricity(), 0.94280905, "incorrect eccentricity");24042405let line = Ellipse::new(1., 0.);2406assert_eq!(line.eccentricity(), 1., "incorrect line eccentricity");24072408let circle = Ellipse::new(2., 2.);2409assert_eq!(circle.eccentricity(), 0., "incorrect circle eccentricity");2410}24112412#[test]2413fn ellipse_perimeter() {2414let circle = Ellipse::new(1., 1.);2415assert_relative_eq!(circle.perimeter(), 6.2831855);24162417let line = Ellipse::new(75_000., 0.5);2418assert_relative_eq!(line.perimeter(), 300_000.);24192420let ellipse = Ellipse::new(0.5, 2.);2421assert_relative_eq!(ellipse.perimeter(), 8.578423);24222423let ellipse = Ellipse::new(5., 3.);2424assert_relative_eq!(ellipse.perimeter(), 25.526999);2425}24262427#[test]2428fn triangle_math() {2429let triangle = Triangle2d::new(2430Vec2::new(-2.0, -1.0),2431Vec2::new(1.0, 4.0),2432Vec2::new(7.0, 0.0),2433);2434assert_eq!(triangle.area(), 21.0, "incorrect area");2435assert_eq!(triangle.perimeter(), 22.097439, "incorrect perimeter");24362437let degenerate_triangle =2438Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(0., 0.), Vec2::new(1., 0.));2439assert!(degenerate_triangle.is_degenerate());24402441let acute_triangle =2442Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 5.));2443let obtuse_triangle =2444Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 0.5));24452446assert!(acute_triangle.is_acute());2447assert!(!acute_triangle.is_obtuse());2448assert!(!obtuse_triangle.is_acute());2449assert!(obtuse_triangle.is_obtuse());2450}24512452#[test]2453fn triangle_winding_order() {2454let mut cw_triangle = Triangle2d::new(2455Vec2::new(0.0, 2.0),2456Vec2::new(-0.5, -1.2),2457Vec2::new(-1.0, -1.0),2458);2459assert_eq!(cw_triangle.winding_order(), WindingOrder::Clockwise);24602461let ccw_triangle = Triangle2d::new(2462Vec2::new(-1.0, -1.0),2463Vec2::new(-0.5, -1.2),2464Vec2::new(0.0, 2.0),2465);2466assert_eq!(ccw_triangle.winding_order(), WindingOrder::CounterClockwise);24672468// The clockwise triangle should be the same as the counterclockwise2469// triangle when reversed2470cw_triangle.reverse();2471assert_eq!(cw_triangle, ccw_triangle);24722473let invalid_triangle = Triangle2d::new(2474Vec2::new(0.0, 2.0),2475Vec2::new(0.0, -1.0),2476Vec2::new(0.0, -1.2),2477);2478assert_eq!(invalid_triangle.winding_order(), WindingOrder::Invalid);2479}24802481#[test]2482fn rectangle_math() {2483let rectangle = Rectangle::new(3.0, 7.0);2484assert_eq!(2485rectangle,2486Rectangle::from_corners(Vec2::new(-1.5, -3.5), Vec2::new(1.5, 3.5))2487);2488assert_eq!(rectangle.area(), 21.0, "incorrect area");2489assert_eq!(rectangle.perimeter(), 20.0, "incorrect perimeter");2490}24912492#[test]2493fn regular_polygon_math() {2494let polygon = RegularPolygon::new(3.0, 6);2495assert_eq!(polygon.inradius(), 2.598076, "incorrect inradius");2496assert_eq!(polygon.side_length(), 3.0, "incorrect side length");2497assert_relative_eq!(polygon.area(), 23.38268, epsilon = 0.00001);2498assert_eq!(polygon.perimeter(), 18.0, "incorrect perimeter");2499assert_eq!(2500polygon.internal_angle_degrees(),2501120.0,2502"incorrect internal angle"2503);2504assert_eq!(2505polygon.internal_angle_radians(),2506120_f32.to_radians(),2507"incorrect internal angle"2508);2509assert_eq!(2510polygon.external_angle_degrees(),251160.0,2512"incorrect external angle"2513);2514assert_eq!(2515polygon.external_angle_radians(),251660_f32.to_radians(),2517"incorrect external angle"2518);2519}25202521#[test]2522fn triangle_circumcenter() {2523let triangle = Triangle2d::new(2524Vec2::new(10.0, 2.0),2525Vec2::new(-5.0, -3.0),2526Vec2::new(2.0, -1.0),2527);2528let (Circle { radius }, circumcenter) = triangle.circumcircle();25292530// Calculated with external calculator2531assert_eq!(radius, 98.34887);2532assert_eq!(circumcenter, Vec2::new(-28.5, 92.5));2533}25342535#[test]2536fn regular_polygon_vertices() {2537let polygon = RegularPolygon::new(1.0, 4);25382539// Regular polygons have a vertex at the top by default2540let mut vertices = polygon.vertices(0.0).into_iter();2541assert!((vertices.next().unwrap() - Vec2::Y).length() < 1e-7);25422543// Rotate by 45 degrees, forming an axis-aligned square2544let mut rotated_vertices = polygon.vertices(core::f32::consts::FRAC_PI_4).into_iter();25452546// Distance from the origin to the middle of a side, derived using Pythagorean theorem2547let side_distance = FRAC_1_SQRT_2;2548assert!(2549(rotated_vertices.next().unwrap() - Vec2::new(-side_distance, side_distance)).length()2550< 1e-7,2551);2552}2553}255425552556