Path: blob/main/crates/bevy_math/src/primitives/dim2.rs
9299 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},8primitives::Inset,9Dir2, InvalidDirectionError, Isometry2d, Ray2d, Rot2, Vec2,10};1112#[cfg(feature = "alloc")]13use super::polygon::is_polygon_simple;1415#[cfg(feature = "bevy_reflect")]16use bevy_reflect::{std_traits::ReflectDefault, Reflect};17#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]18use bevy_reflect::{ReflectDeserialize, ReflectSerialize};1920#[cfg(feature = "alloc")]21use alloc::vec::Vec;2223/// A circle primitive, representing the set of points some distance from the origin24#[derive(Clone, Copy, Debug, PartialEq)]25#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]26#[cfg_attr(27feature = "bevy_reflect",28derive(Reflect),29reflect(Debug, PartialEq, Default, Clone)30)]31#[cfg_attr(32all(feature = "serialize", feature = "bevy_reflect"),33reflect(Serialize, Deserialize)34)]35pub struct Circle {36/// The radius of the circle37pub radius: f32,38}3940impl Primitive2d for Circle {}4142impl Default for Circle {43/// Returns the default [`Circle`] with a radius of `0.5`.44fn default() -> Self {45Self { radius: 0.5 }46}47}4849impl Circle {50/// Create a new [`Circle`] from a `radius`51#[inline]52pub const fn new(radius: f32) -> Self {53Self { radius }54}5556/// Get the diameter of the circle57#[inline]58pub const fn diameter(&self) -> f32 {592.0 * self.radius60}6162/// Finds the point on the circle that is closest to the given `point`.63///64/// If the point is outside the circle, the returned point will be on the perimeter of the circle.65/// Otherwise, it will be inside the circle and returned as is.66#[inline]67pub fn closest_point(&self, point: Vec2) -> Vec2 {68let distance_squared = point.length_squared();6970if distance_squared <= self.radius.squared() {71// The point is inside the circle.72point73} else {74// The point is outside the circle.75// Find the closest point on the perimeter of the circle.76let dir_to_point = point / ops::sqrt(distance_squared);77self.radius * dir_to_point78}79}80}8182impl Measured2d for Circle {83/// Get the area of the circle84#[inline]85fn area(&self) -> f32 {86PI * self.radius.squared()87}8889/// Get the perimeter or circumference of the circle90#[inline]91#[doc(alias = "circumference")]92fn perimeter(&self) -> f32 {932.0 * PI * self.radius94}95}9697/// A primitive representing an arc between two points on a circle.98///99/// An arc has no area.100/// If you want to include the portion of a circle's area swept out by the arc,101/// use the pie-shaped [`CircularSector`].102/// If you want to include only the space inside the convex hull of the arc,103/// use the bowl-shaped [`CircularSegment`].104///105/// The arc is drawn starting from [`Vec2::Y`], extending by `half_angle` radians on106/// either side. The center of the circle is the origin [`Vec2::ZERO`]. Note that this107/// means that the origin may not be within the `Arc2d`'s convex hull.108///109/// **Warning:** Arcs with negative angle or radius, or with angle greater than an entire circle, are not officially supported.110/// It is recommended to normalize arcs to have an angle in [0, 2Ï€].111#[derive(Clone, Copy, Debug, PartialEq)]112#[doc(alias("CircularArc", "CircleArc"))]113#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]114#[cfg_attr(115feature = "bevy_reflect",116derive(Reflect),117reflect(Debug, PartialEq, Default, Clone)118)]119#[cfg_attr(120all(feature = "serialize", feature = "bevy_reflect"),121reflect(Serialize, Deserialize)122)]123pub struct Arc2d {124/// The radius of the circle125pub radius: f32,126/// Half the angle defining the arc127pub half_angle: f32,128}129130impl Primitive2d for Arc2d {}131132impl Default for Arc2d {133/// Returns the default [`Arc2d`] with radius `0.5`, covering one third of a circle134fn default() -> Self {135Self {136radius: 0.5,137half_angle: 2.0 * FRAC_PI_3,138}139}140}141142impl Arc2d {143/// Create a new [`Arc2d`] from a `radius` and a `half_angle`144#[inline]145pub const fn new(radius: f32, half_angle: f32) -> Self {146Self { radius, half_angle }147}148149/// Create a new [`Arc2d`] from a `radius` and an `angle` in radians150#[inline]151pub const fn from_radians(radius: f32, angle: f32) -> Self {152Self {153radius,154half_angle: angle / 2.0,155}156}157158/// Create a new [`Arc2d`] from a `radius` and an `angle` in degrees.159#[inline]160pub const fn from_degrees(radius: f32, angle: f32) -> Self {161Self {162radius,163half_angle: angle.to_radians() / 2.0,164}165}166167/// Create a new [`Arc2d`] from a `radius` and a `fraction` of a single turn.168///169/// For instance, `0.5` turns is a semicircle.170#[inline]171pub const fn from_turns(radius: f32, fraction: f32) -> Self {172Self {173radius,174half_angle: fraction * PI,175}176}177178/// Get the angle of the arc179#[inline]180pub const fn angle(&self) -> f32 {181self.half_angle * 2.0182}183184/// Get the length of the arc185#[inline]186pub const fn length(&self) -> f32 {187self.angle() * self.radius188}189190/// Get the right-hand end point of the arc191#[inline]192pub fn right_endpoint(&self) -> Vec2 {193self.radius * Vec2::from_angle(FRAC_PI_2 - self.half_angle)194}195196/// Get the left-hand end point of the arc197#[inline]198pub fn left_endpoint(&self) -> Vec2 {199self.radius * Vec2::from_angle(FRAC_PI_2 + self.half_angle)200}201202/// Get the endpoints of the arc203#[inline]204pub fn endpoints(&self) -> [Vec2; 2] {205[self.left_endpoint(), self.right_endpoint()]206}207208/// Get the midpoint of the arc209#[inline]210pub fn midpoint(&self) -> Vec2 {211self.radius * Vec2::Y212}213214/// Get half the distance between the endpoints (half the length of the chord)215#[inline]216pub fn half_chord_length(&self) -> f32 {217self.radius * ops::sin(self.half_angle)218}219220/// Get the distance between the endpoints (the length of the chord)221#[inline]222pub fn chord_length(&self) -> f32 {2232.0 * self.half_chord_length()224}225226/// Get the midpoint of the two endpoints (the midpoint of the chord)227#[inline]228pub fn chord_midpoint(&self) -> Vec2 {229self.apothem() * Vec2::Y230}231232/// Get the length of the apothem of this arc, that is,233/// the distance from the center of the circle to the midpoint of the chord, in the direction of the midpoint of the arc.234/// Equivalently, the [`radius`](Self::radius) minus the [`sagitta`](Self::sagitta).235///236/// Note that for a [`major`](Self::is_major) arc, the apothem will be negative.237#[inline]238// Naming note: Various sources are inconsistent as to whether the apothem is the segment between the center and the239// midpoint of a chord, or the length of that segment. Given this confusion, we've opted for the definition240// used by Wolfram MathWorld, which is the distance rather than the segment.241pub fn apothem(&self) -> f32 {242let sign = if self.is_minor() { 1.0 } else { -1.0 };243sign * ops::sqrt(self.radius.squared() - self.half_chord_length().squared())244}245246/// Get the length of the sagitta of this arc, that is,247/// the length of the line between the midpoints of the arc and its chord.248/// Equivalently, the height of the triangle whose base is the chord and whose apex is the midpoint of the arc.249///250/// The sagitta is also the sum of the [`radius`](Self::radius) and the [`apothem`](Self::apothem).251pub fn sagitta(&self) -> f32 {252self.radius - self.apothem()253}254255/// Produces true if the arc is at most half a circle.256///257/// **Note:** This is not the negation of [`is_major`](Self::is_major): an exact semicircle is both major and minor.258#[inline]259pub const fn is_minor(&self) -> bool {260self.half_angle <= FRAC_PI_2261}262263/// Produces true if the arc is at least half a circle.264///265/// **Note:** This is not the negation of [`is_minor`](Self::is_minor): an exact semicircle is both major and minor.266#[inline]267pub const fn is_major(&self) -> bool {268self.half_angle >= FRAC_PI_2269}270}271272/// A primitive representing a circular sector: a pie slice of a circle.273///274/// The segment is positioned so that it always includes [`Vec2::Y`] and is vertically symmetrical.275/// To orient the sector differently, apply a rotation.276/// The sector is drawn with the center of its circle at the origin [`Vec2::ZERO`].277///278/// **Warning:** Circular sectors with negative angle or radius, or with angle greater than an entire circle, are not officially supported.279/// We recommend normalizing circular sectors to have an angle in [0, 2Ï€].280#[derive(Clone, Copy, Debug, PartialEq, From)]281#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]282#[cfg_attr(283feature = "bevy_reflect",284derive(Reflect),285reflect(Debug, PartialEq, Default, Clone)286)]287#[cfg_attr(288all(feature = "serialize", feature = "bevy_reflect"),289reflect(Serialize, Deserialize)290)]291pub struct CircularSector {292/// The arc defining the sector293#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]294pub arc: Arc2d,295}296297impl Primitive2d for CircularSector {}298299impl Default for CircularSector {300/// Returns the default [`CircularSector`] with radius `0.5` and covering a third of a circle301fn default() -> Self {302Self::from(Arc2d::default())303}304}305306impl Measured2d for CircularSector {307#[inline]308fn area(&self) -> f32 {309self.arc.radius.squared() * self.arc.half_angle310}311312#[inline]313fn perimeter(&self) -> f32 {314if self.half_angle() >= PI {315self.arc.radius * 2.0 * PI316} else {3172.0 * self.radius() + self.arc_length()318}319}320}321322impl CircularSector {323/// Create a new [`CircularSector`] from a `radius` and an `angle`324#[inline]325pub const fn new(radius: f32, angle: f32) -> Self {326Self {327arc: Arc2d::new(radius, angle),328}329}330331/// Create a new [`CircularSector`] from a `radius` and an `angle` in radians.332#[inline]333pub const fn from_radians(radius: f32, angle: f32) -> Self {334Self {335arc: Arc2d::from_radians(radius, angle),336}337}338339/// Create a new [`CircularSector`] from a `radius` and an `angle` in degrees.340#[inline]341pub const fn from_degrees(radius: f32, angle: f32) -> Self {342Self {343arc: Arc2d::from_degrees(radius, angle),344}345}346347/// Create a new [`CircularSector`] from a `radius` and a number of `turns` of a circle.348///349/// For instance, `0.5` turns is a semicircle.350#[inline]351pub const fn from_turns(radius: f32, fraction: f32) -> Self {352Self {353arc: Arc2d::from_turns(radius, fraction),354}355}356357/// Get half the angle of the sector358#[inline]359pub const fn half_angle(&self) -> f32 {360self.arc.half_angle361}362363/// Get the angle of the sector364#[inline]365pub const fn angle(&self) -> f32 {366self.arc.angle()367}368369/// Get the radius of the sector370#[inline]371pub const fn radius(&self) -> f32 {372self.arc.radius373}374375/// Get the length of the arc defining the sector376#[inline]377pub const fn arc_length(&self) -> f32 {378self.arc.length()379}380381/// Get half the length of the chord defined by the sector382///383/// See [`Arc2d::half_chord_length`]384#[inline]385pub fn half_chord_length(&self) -> f32 {386self.arc.half_chord_length()387}388389/// Get the length of the chord defined by the sector390///391/// See [`Arc2d::chord_length`]392#[inline]393pub fn chord_length(&self) -> f32 {394self.arc.chord_length()395}396397/// Get the midpoint of the chord defined by the sector398///399/// See [`Arc2d::chord_midpoint`]400#[inline]401pub fn chord_midpoint(&self) -> Vec2 {402self.arc.chord_midpoint()403}404405/// Get the length of the apothem of this sector406///407/// See [`Arc2d::apothem`]408#[inline]409pub fn apothem(&self) -> f32 {410self.arc.apothem()411}412413/// Get the length of the sagitta of this sector414///415/// See [`Arc2d::sagitta`]416#[inline]417pub fn sagitta(&self) -> f32 {418self.arc.sagitta()419}420}421422/// A primitive representing a circular segment:423/// the area enclosed by the arc of a circle and its chord (the line between its endpoints).424///425/// The segment is drawn starting from [`Vec2::Y`], extending equally on either side.426/// To orient the segment differently, apply a rotation.427/// The segment is drawn with the center of its circle at the origin [`Vec2::ZERO`].428/// When positioning a segment, the [`apothem`](Self::apothem) function may be particularly useful.429///430/// **Warning:** Circular segments with negative angle or radius, or with angle greater than an entire circle, are not officially supported.431/// We recommend normalizing circular segments to have an angle in [0, 2Ï€].432#[derive(Clone, Copy, Debug, PartialEq, From)]433#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]434#[cfg_attr(435feature = "bevy_reflect",436derive(Reflect),437reflect(Debug, PartialEq, Default, Clone)438)]439#[cfg_attr(440all(feature = "serialize", feature = "bevy_reflect"),441reflect(Serialize, Deserialize)442)]443pub struct CircularSegment {444/// The arc defining the segment445#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]446pub arc: Arc2d,447}448449impl Primitive2d for CircularSegment {}450451impl Default for CircularSegment {452/// Returns the default [`CircularSegment`] with radius `0.5` and covering a third of a circle453fn default() -> Self {454Self::from(Arc2d::default())455}456}457458impl Measured2d for CircularSegment {459#[inline]460fn area(&self) -> f32 {4610.5 * self.arc.radius.squared() * (self.arc.angle() - ops::sin(self.arc.angle()))462}463464#[inline]465fn perimeter(&self) -> f32 {466self.chord_length() + self.arc_length()467}468}469470impl CircularSegment {471/// Create a new [`CircularSegment`] from a `radius`, and a `half_angle` in radians.472#[inline]473pub const fn new(radius: f32, half_angle: f32) -> Self {474Self {475arc: Arc2d::new(radius, half_angle),476}477}478479/// Create a new [`CircularSegment`] from a `radius` and an `angle` in radians.480#[inline]481pub const fn from_radians(radius: f32, angle: f32) -> Self {482Self {483arc: Arc2d::from_radians(radius, angle),484}485}486487/// Create a new [`CircularSegment`] from a `radius` and an `angle` in degrees.488#[inline]489pub const fn from_degrees(radius: f32, angle: f32) -> Self {490Self {491arc: Arc2d::from_degrees(radius, angle),492}493}494495/// Create a new [`CircularSegment`] from a `radius` and a number of `turns` of a circle.496///497/// For instance, `0.5` turns is a semicircle.498#[inline]499pub const fn from_turns(radius: f32, fraction: f32) -> Self {500Self {501arc: Arc2d::from_turns(radius, fraction),502}503}504505/// Get the half-angle of the segment506#[inline]507pub const fn half_angle(&self) -> f32 {508self.arc.half_angle509}510511/// Get the angle of the segment512#[inline]513pub const fn angle(&self) -> f32 {514self.arc.angle()515}516517/// Get the radius of the segment518#[inline]519pub const fn radius(&self) -> f32 {520self.arc.radius521}522523/// Get the length of the arc defining the segment524#[inline]525pub const fn arc_length(&self) -> f32 {526self.arc.length()527}528529/// Get half the length of the segment's base, also known as its chord530#[inline]531#[doc(alias = "half_base_length")]532pub fn half_chord_length(&self) -> f32 {533self.arc.half_chord_length()534}535536/// Get the length of the segment's base, also known as its chord537#[inline]538#[doc(alias = "base_length")]539#[doc(alias = "base")]540pub fn chord_length(&self) -> f32 {541self.arc.chord_length()542}543544/// Get the midpoint of the segment's base, also known as its chord545#[inline]546#[doc(alias = "base_midpoint")]547pub fn chord_midpoint(&self) -> Vec2 {548self.arc.chord_midpoint()549}550551/// Get the length of the apothem of this segment,552/// which is the signed distance between the segment and the center of its circle553///554/// See [`Arc2d::apothem`]555#[inline]556pub fn apothem(&self) -> f32 {557self.arc.apothem()558}559560/// Get the length of the sagitta of this segment, also known as its height561///562/// See [`Arc2d::sagitta`]563#[inline]564#[doc(alias = "height")]565pub fn sagitta(&self) -> f32 {566self.arc.sagitta()567}568}569570#[cfg(test)]571mod arc_tests {572use core::f32::consts::FRAC_PI_4;573use core::f32::consts::SQRT_2;574575use approx::assert_abs_diff_eq;576577use super::*;578579struct ArcTestCase {580radius: f32,581half_angle: f32,582angle: f32,583length: f32,584right_endpoint: Vec2,585left_endpoint: Vec2,586endpoints: [Vec2; 2],587midpoint: Vec2,588half_chord_length: f32,589chord_length: f32,590chord_midpoint: Vec2,591apothem: f32,592sagitta: f32,593is_minor: bool,594is_major: bool,595sector_area: f32,596sector_perimeter: f32,597segment_area: f32,598segment_perimeter: f32,599}600601impl ArcTestCase {602fn check_arc(&self, arc: Arc2d) {603assert_abs_diff_eq!(self.radius, arc.radius);604assert_abs_diff_eq!(self.half_angle, arc.half_angle);605assert_abs_diff_eq!(self.angle, arc.angle());606assert_abs_diff_eq!(self.length, arc.length());607assert_abs_diff_eq!(self.right_endpoint, arc.right_endpoint());608assert_abs_diff_eq!(self.left_endpoint, arc.left_endpoint());609assert_abs_diff_eq!(self.endpoints[0], arc.endpoints()[0]);610assert_abs_diff_eq!(self.endpoints[1], arc.endpoints()[1]);611assert_abs_diff_eq!(self.midpoint, arc.midpoint());612assert_abs_diff_eq!(self.half_chord_length, arc.half_chord_length());613assert_abs_diff_eq!(self.chord_length, arc.chord_length(), epsilon = 0.00001);614assert_abs_diff_eq!(self.chord_midpoint, arc.chord_midpoint());615assert_abs_diff_eq!(self.apothem, arc.apothem());616assert_abs_diff_eq!(self.sagitta, arc.sagitta());617assert_eq!(self.is_minor, arc.is_minor());618assert_eq!(self.is_major, arc.is_major());619}620621fn check_sector(&self, sector: CircularSector) {622assert_abs_diff_eq!(self.radius, sector.radius());623assert_abs_diff_eq!(self.half_angle, sector.half_angle());624assert_abs_diff_eq!(self.angle, sector.angle());625assert_abs_diff_eq!(self.half_chord_length, sector.half_chord_length());626assert_abs_diff_eq!(self.chord_length, sector.chord_length(), epsilon = 0.00001);627assert_abs_diff_eq!(self.chord_midpoint, sector.chord_midpoint());628assert_abs_diff_eq!(self.apothem, sector.apothem());629assert_abs_diff_eq!(self.sagitta, sector.sagitta());630assert_abs_diff_eq!(self.sector_area, sector.area());631assert_abs_diff_eq!(self.sector_perimeter, sector.perimeter());632}633634fn check_segment(&self, segment: CircularSegment) {635assert_abs_diff_eq!(self.radius, segment.radius());636assert_abs_diff_eq!(self.half_angle, segment.half_angle());637assert_abs_diff_eq!(self.angle, segment.angle());638assert_abs_diff_eq!(self.half_chord_length, segment.half_chord_length());639assert_abs_diff_eq!(self.chord_length, segment.chord_length(), epsilon = 0.00001);640assert_abs_diff_eq!(self.chord_midpoint, segment.chord_midpoint());641assert_abs_diff_eq!(self.apothem, segment.apothem());642assert_abs_diff_eq!(self.sagitta, segment.sagitta());643assert_abs_diff_eq!(self.segment_area, segment.area());644assert_abs_diff_eq!(self.segment_perimeter, segment.perimeter());645}646}647648#[test]649fn zero_angle() {650let tests = ArcTestCase {651radius: 1.0,652half_angle: 0.0,653angle: 0.0,654length: 0.0,655left_endpoint: Vec2::Y,656right_endpoint: Vec2::Y,657endpoints: [Vec2::Y, Vec2::Y],658midpoint: Vec2::Y,659half_chord_length: 0.0,660chord_length: 0.0,661chord_midpoint: Vec2::Y,662apothem: 1.0,663sagitta: 0.0,664is_minor: true,665is_major: false,666sector_area: 0.0,667sector_perimeter: 2.0,668segment_area: 0.0,669segment_perimeter: 0.0,670};671672tests.check_arc(Arc2d::new(1.0, 0.0));673tests.check_sector(CircularSector::new(1.0, 0.0));674tests.check_segment(CircularSegment::new(1.0, 0.0));675}676677#[test]678fn zero_radius() {679let tests = ArcTestCase {680radius: 0.0,681half_angle: FRAC_PI_4,682angle: FRAC_PI_2,683length: 0.0,684left_endpoint: Vec2::ZERO,685right_endpoint: Vec2::ZERO,686endpoints: [Vec2::ZERO, Vec2::ZERO],687midpoint: Vec2::ZERO,688half_chord_length: 0.0,689chord_length: 0.0,690chord_midpoint: Vec2::ZERO,691apothem: 0.0,692sagitta: 0.0,693is_minor: true,694is_major: false,695sector_area: 0.0,696sector_perimeter: 0.0,697segment_area: 0.0,698segment_perimeter: 0.0,699};700701tests.check_arc(Arc2d::new(0.0, FRAC_PI_4));702tests.check_sector(CircularSector::new(0.0, FRAC_PI_4));703tests.check_segment(CircularSegment::new(0.0, FRAC_PI_4));704}705706#[test]707fn quarter_circle() {708let sqrt_half: f32 = ops::sqrt(0.5);709let tests = ArcTestCase {710radius: 1.0,711half_angle: FRAC_PI_4,712angle: FRAC_PI_2,713length: FRAC_PI_2,714left_endpoint: Vec2::new(-sqrt_half, sqrt_half),715right_endpoint: Vec2::splat(sqrt_half),716endpoints: [Vec2::new(-sqrt_half, sqrt_half), Vec2::splat(sqrt_half)],717midpoint: Vec2::Y,718half_chord_length: sqrt_half,719chord_length: ops::sqrt(2.0),720chord_midpoint: Vec2::new(0.0, sqrt_half),721apothem: sqrt_half,722sagitta: 1.0 - sqrt_half,723is_minor: true,724is_major: false,725sector_area: FRAC_PI_4,726sector_perimeter: FRAC_PI_2 + 2.0,727segment_area: FRAC_PI_4 - 0.5,728segment_perimeter: FRAC_PI_2 + SQRT_2,729};730731tests.check_arc(Arc2d::from_turns(1.0, 0.25));732tests.check_sector(CircularSector::from_turns(1.0, 0.25));733tests.check_segment(CircularSegment::from_turns(1.0, 0.25));734}735736#[test]737fn half_circle() {738let tests = ArcTestCase {739radius: 1.0,740half_angle: FRAC_PI_2,741angle: PI,742length: PI,743left_endpoint: Vec2::NEG_X,744right_endpoint: Vec2::X,745endpoints: [Vec2::NEG_X, Vec2::X],746midpoint: Vec2::Y,747half_chord_length: 1.0,748chord_length: 2.0,749chord_midpoint: Vec2::ZERO,750apothem: 0.0,751sagitta: 1.0,752is_minor: true,753is_major: true,754sector_area: FRAC_PI_2,755sector_perimeter: PI + 2.0,756segment_area: FRAC_PI_2,757segment_perimeter: PI + 2.0,758};759760tests.check_arc(Arc2d::from_radians(1.0, PI));761tests.check_sector(CircularSector::from_radians(1.0, PI));762tests.check_segment(CircularSegment::from_radians(1.0, PI));763}764765#[test]766fn full_circle() {767let tests = ArcTestCase {768radius: 1.0,769half_angle: PI,770angle: 2.0 * PI,771length: 2.0 * PI,772left_endpoint: Vec2::NEG_Y,773right_endpoint: Vec2::NEG_Y,774endpoints: [Vec2::NEG_Y, Vec2::NEG_Y],775midpoint: Vec2::Y,776half_chord_length: 0.0,777chord_length: 0.0,778chord_midpoint: Vec2::NEG_Y,779apothem: -1.0,780sagitta: 2.0,781is_minor: false,782is_major: true,783sector_area: PI,784sector_perimeter: 2.0 * PI,785segment_area: PI,786segment_perimeter: 2.0 * PI,787};788789tests.check_arc(Arc2d::from_degrees(1.0, 360.0));790tests.check_sector(CircularSector::from_degrees(1.0, 360.0));791tests.check_segment(CircularSegment::from_degrees(1.0, 360.0));792}793}794795/// An ellipse primitive, which is like a circle, but the width and height can be different796///797/// Ellipse does not implement [`Inset`] as concentric ellipses do not have parallel curves:798/// if the ellipse is not a circle, the inset shape is not actually an ellipse (although it may look like one) but can also be a lens-like shape.799#[derive(Clone, Copy, Debug, PartialEq)]800#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]801#[cfg_attr(802feature = "bevy_reflect",803derive(Reflect),804reflect(Debug, PartialEq, Default, Clone)805)]806#[cfg_attr(807all(feature = "serialize", feature = "bevy_reflect"),808reflect(Serialize, Deserialize)809)]810pub struct Ellipse {811/// Half of the width and height of the ellipse.812///813/// This corresponds to the two perpendicular radii defining the ellipse.814pub half_size: Vec2,815}816817impl Primitive2d for Ellipse {}818819impl Default for Ellipse {820/// Returns the default [`Ellipse`] with a half-width of `1.0` and a half-height of `0.5`.821fn default() -> Self {822Self {823half_size: Vec2::new(1.0, 0.5),824}825}826}827828impl Ellipse {829/// Create a new `Ellipse` from half of its width and height.830///831/// This corresponds to the two perpendicular radii defining the ellipse.832#[inline]833pub const fn new(half_width: f32, half_height: f32) -> Self {834Self {835half_size: Vec2::new(half_width, half_height),836}837}838839/// Create a new `Ellipse` from a given full size.840///841/// `size.x` is the diameter along the X axis, and `size.y` is the diameter along the Y axis.842#[inline]843pub const fn from_size(size: Vec2) -> Self {844Self {845half_size: Vec2::new(size.x / 2.0, size.y / 2.0),846}847}848849#[inline]850/// Returns the [eccentricity](https://en.wikipedia.org/wiki/Eccentricity_(mathematics)) of the ellipse.851/// It can be thought of as a measure of how "stretched" or elongated the ellipse is.852///853/// The value should be in the range [0, 1), where 0 represents a circle, and 1 represents a parabola.854pub fn eccentricity(&self) -> f32 {855let a = self.semi_major();856let b = self.semi_minor();857858ops::sqrt(a * a - b * b) / a859}860861#[inline]862/// Get the focal length of the ellipse. This corresponds to the distance between one of the foci and the center of the ellipse.863///864/// The focal length of an ellipse is related to its eccentricity by `eccentricity = focal_length / semi_major`865pub fn focal_length(&self) -> f32 {866let a = self.semi_major();867let b = self.semi_minor();868869ops::sqrt(a * a - b * b)870}871872/// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse.873#[inline]874pub fn semi_major(&self) -> f32 {875self.half_size.max_element()876}877878/// Returns the length of the semi-minor axis. This corresponds to the shortest radius of the ellipse.879#[inline]880pub fn semi_minor(&self) -> f32 {881self.half_size.min_element()882}883}884885impl Measured2d for Ellipse {886/// Get the area of the ellipse887#[inline]888fn area(&self) -> f32 {889PI * self.half_size.x * self.half_size.y890}891892#[inline]893/// Get an approximation for the perimeter or circumference of the ellipse.894///895/// The approximation is reasonably precise with a relative error less than 0.007%, getting more precise as the eccentricity of the ellipse decreases.896fn perimeter(&self) -> f32 {897let a = self.semi_major();898let b = self.semi_minor();899900// In the case that `a == b`, the ellipse is a circle901if a / b - 1. < 1e-5 {902return PI * (a + b);903};904905// In the case that `a` is much larger than `b`, the ellipse is a line906if a / b > 1e4 {907return 4. * a;908};909910// These values are the result of (0.5 choose n)^2 where n is the index in the array911// They could be calculated on the fly but hardcoding them yields more accurate and faster results912// because the actual calculation for these values involves factorials and numbers > 10^23913const BINOMIAL_COEFFICIENTS: [f32; 21] = [9141.,9150.25,9160.015625,9170.00390625,9180.0015258789,9190.00074768066,9200.00042057037,9210.00025963783,9220.00017140154,9230.000119028846,9240.00008599834,9250.00006414339,9260.000049109784,9270.000038430585,9280.000030636627,9290.000024815668,9300.000020380836,9310.000016942893,9320.000014236736,9330.000012077564,9340.000010333865,935];936937// The algorithm used here is the Gauss-Kummer infinite series expansion of the elliptic integral expression for the perimeter of ellipses938// For more information see https://www.wolframalpha.com/input/?i=gauss-kummer+series939// We only use the terms up to `i == 20` for this approximation940let h = ((a - b) / (a + b)).squared();941942PI * (a + b)943* (0..=20)944.map(|i| BINOMIAL_COEFFICIENTS[i] * ops::powf(h, i as f32))945.sum::<f32>()946}947}948949/// A primitive shape formed by the region between two circles, also known as a ring.950#[derive(Clone, Copy, Debug, PartialEq)]951#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]952#[cfg_attr(953feature = "bevy_reflect",954derive(Reflect),955reflect(Debug, PartialEq, Default, Clone)956)]957#[cfg_attr(958all(feature = "serialize", feature = "bevy_reflect"),959reflect(Serialize, Deserialize)960)]961#[doc(alias = "Ring")]962pub struct Annulus {963/// The inner circle of the annulus964pub inner_circle: Circle,965/// The outer circle of the annulus966pub outer_circle: Circle,967}968969impl Primitive2d for Annulus {}970971impl Default for Annulus {972/// Returns the default [`Annulus`] with radii of `0.5` and `1.0`.973fn default() -> Self {974Self {975inner_circle: Circle::new(0.5),976outer_circle: Circle::new(1.0),977}978}979}980981impl Annulus {982/// Create a new [`Annulus`] from the radii of the inner and outer circle983#[inline]984pub const fn new(inner_radius: f32, outer_radius: f32) -> Self {985Self {986inner_circle: Circle::new(inner_radius),987outer_circle: Circle::new(outer_radius),988}989}990991/// Get the diameter of the annulus992#[inline]993pub const fn diameter(&self) -> f32 {994self.outer_circle.diameter()995}996997/// Get the thickness of the annulus998#[inline]999pub const fn thickness(&self) -> f32 {1000self.outer_circle.radius - self.inner_circle.radius1001}10021003/// Finds the point on the annulus that is closest to the given `point`:1004///1005/// - If the point is outside of the annulus completely, the returned point will be on the outer perimeter.1006/// - If the point is inside of the inner circle (hole) of the annulus, the returned point will be on the inner perimeter.1007/// - Otherwise, the returned point is overlapping the annulus and returned as is.1008#[inline]1009pub fn closest_point(&self, point: Vec2) -> Vec2 {1010let distance_squared = point.length_squared();10111012if self.inner_circle.radius.squared() <= distance_squared {1013if distance_squared <= self.outer_circle.radius.squared() {1014// The point is inside the annulus.1015point1016} else {1017// The point is outside the annulus and closer to the outer perimeter.1018// Find the closest point on the perimeter of the annulus.1019let dir_to_point = point / ops::sqrt(distance_squared);1020self.outer_circle.radius * dir_to_point1021}1022} else {1023// The point is outside the annulus and closer to the inner perimeter.1024// Find the closest point on the perimeter of the annulus.1025let dir_to_point = point / ops::sqrt(distance_squared);1026self.inner_circle.radius * dir_to_point1027}1028}1029}10301031impl Measured2d for Annulus {1032/// Get the area of the annulus1033#[inline]1034fn area(&self) -> f32 {1035PI * (self.outer_circle.radius.squared() - self.inner_circle.radius.squared())1036}10371038/// Get the perimeter or circumference of the annulus,1039/// which is the sum of the perimeters of the inner and outer circles.1040#[inline]1041#[doc(alias = "circumference")]1042fn perimeter(&self) -> f32 {10432.0 * PI * (self.outer_circle.radius + self.inner_circle.radius)1044}1045}10461047/// A rhombus primitive, also known as a diamond shape.1048/// A four sided polygon, centered on the origin, where opposite sides are parallel but without1049/// requiring right angles.1050#[derive(Clone, Copy, Debug, PartialEq)]1051#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1052#[cfg_attr(1053feature = "bevy_reflect",1054derive(Reflect),1055reflect(Debug, PartialEq, Default, Clone)1056)]1057#[cfg_attr(1058all(feature = "serialize", feature = "bevy_reflect"),1059reflect(Serialize, Deserialize)1060)]1061#[doc(alias = "Diamond")]1062pub struct Rhombus {1063/// Size of the horizontal and vertical diagonals of the rhombus1064pub half_diagonals: Vec2,1065}10661067impl Primitive2d for Rhombus {}10681069impl Default for Rhombus {1070/// Returns the default [`Rhombus`] with a half-horizontal and half-vertical diagonal of `0.5`.1071fn default() -> Self {1072Self {1073half_diagonals: Vec2::splat(0.5),1074}1075}1076}10771078impl Rhombus {1079/// Create a new `Rhombus` from a vertical and horizontal diagonal sizes.1080#[inline]1081pub const fn new(horizontal_diagonal: f32, vertical_diagonal: f32) -> Self {1082Self {1083half_diagonals: Vec2::new(horizontal_diagonal / 2.0, vertical_diagonal / 2.0),1084}1085}10861087/// Create a new `Rhombus` from a side length with all inner angles equal.1088#[inline]1089pub const fn from_side(side: f32) -> Self {1090Self {1091half_diagonals: Vec2::splat(side * FRAC_1_SQRT_2),1092}1093}10941095/// Create a new `Rhombus` from a given inradius with all inner angles equal.1096#[inline]1097pub const fn from_inradius(inradius: f32) -> Self {1098let half_diagonal = inradius * 2.0 / core::f32::consts::SQRT_2;1099Self {1100half_diagonals: Vec2::new(half_diagonal, half_diagonal),1101}1102}11031104/// Get the length of each side of the rhombus1105#[inline]1106pub fn side(&self) -> f32 {1107self.half_diagonals.length()1108}11091110/// Get the radius of the circumcircle on which all vertices1111/// of the rhombus lie1112#[inline]1113pub const fn circumradius(&self) -> f32 {1114self.half_diagonals.x.max(self.half_diagonals.y)1115}11161117/// Get the radius of the largest circle that can1118/// be drawn within the rhombus1119#[inline]1120#[doc(alias = "apothem")]1121pub fn inradius(&self) -> f32 {1122let side = self.side();1123if side == 0.0 {11240.01125} else {1126(self.half_diagonals.x * self.half_diagonals.y) / side1127}1128}11291130/// Finds the point on the rhombus that is closest to the given `point`.1131///1132/// If the point is outside the rhombus, the returned point will be on the perimeter of the rhombus.1133/// Otherwise, it will be inside the rhombus and returned as is.1134#[inline]1135pub fn closest_point(&self, point: Vec2) -> Vec2 {1136// Fold the problem into the positive quadrant1137let point_abs = point.abs();1138let half_diagonals = self.half_diagonals.abs(); // to ensure correct sign11391140// The unnormalised normal vector perpendicular to the side of the rhombus1141let normal = Vec2::new(half_diagonals.y, half_diagonals.x);1142let normal_magnitude_squared = normal.length_squared();1143if normal_magnitude_squared == 0.0 {1144return Vec2::ZERO; // A null Rhombus has only one point anyway.1145}11461147// The last term corresponds to normal.dot(rhombus_vertex)1148let distance_unnormalised = normal.dot(point_abs) - half_diagonals.x * half_diagonals.y;11491150// The point is already inside so we simply return it.1151if distance_unnormalised <= 0.0 {1152return point;1153}11541155// Clamp the point to the edge1156let mut result = point_abs - normal * distance_unnormalised / normal_magnitude_squared;11571158// Clamp the point back to the positive quadrant1159// if it's outside, it needs to be clamped to either vertex1160if result.x <= 0.0 {1161result = Vec2::new(0.0, half_diagonals.y);1162} else if result.y <= 0.0 {1163result = Vec2::new(half_diagonals.x, 0.0);1164}11651166// Finally, we restore the signs of the original vector1167result.copysign(point)1168}1169}11701171impl Measured2d for Rhombus {1172/// Get the area of the rhombus1173#[inline]1174fn area(&self) -> f32 {11752.0 * self.half_diagonals.x * self.half_diagonals.y1176}11771178/// Get the perimeter of the rhombus1179#[inline]1180fn perimeter(&self) -> f32 {11814.0 * self.side()1182}1183}11841185/// An unbounded plane in 2D space. It forms a separating surface through the origin,1186/// stretching infinitely far1187#[derive(Clone, Copy, Debug, PartialEq)]1188#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1189#[cfg_attr(1190feature = "bevy_reflect",1191derive(Reflect),1192reflect(Debug, PartialEq, Default, Clone)1193)]1194#[cfg_attr(1195all(feature = "serialize", feature = "bevy_reflect"),1196reflect(Serialize, Deserialize)1197)]1198pub struct Plane2d {1199/// The normal of the plane. The plane will be placed perpendicular to this direction1200pub normal: Dir2,1201}12021203impl Primitive2d for Plane2d {}12041205impl Default for Plane2d {1206/// Returns the default [`Plane2d`] with a normal pointing in the `+Y` direction.1207fn default() -> Self {1208Self { normal: Dir2::Y }1209}1210}12111212impl Plane2d {1213/// Create a new `Plane2d` from a normal1214///1215/// # Panics1216///1217/// Panics if the given `normal` is zero (or very close to zero), or non-finite.1218#[inline]1219pub fn new(normal: Vec2) -> Self {1220Self {1221normal: Dir2::new(normal).expect("normal must be nonzero and finite"),1222}1223}1224}12251226/// An infinite line going through the origin along a direction in 2D space.1227///1228/// For a finite line: [`Segment2d`]1229#[derive(Clone, Copy, Debug, PartialEq)]1230#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1231#[cfg_attr(1232feature = "bevy_reflect",1233derive(Reflect),1234reflect(Debug, PartialEq, Clone)1235)]1236#[cfg_attr(1237all(feature = "serialize", feature = "bevy_reflect"),1238reflect(Serialize, Deserialize)1239)]1240pub struct Line2d {1241/// The direction of the line. The line extends infinitely in both the given direction1242/// and its opposite direction1243pub direction: Dir2,1244}12451246impl Primitive2d for Line2d {}12471248/// A line segment defined by two endpoints in 2D space.1249#[derive(Clone, Copy, Debug, PartialEq)]1250#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1251#[cfg_attr(1252feature = "bevy_reflect",1253derive(Reflect),1254reflect(Debug, PartialEq, Clone)1255)]1256#[cfg_attr(1257all(feature = "serialize", feature = "bevy_reflect"),1258reflect(Serialize, Deserialize)1259)]1260#[doc(alias = "LineSegment2d")]1261pub struct Segment2d {1262/// The endpoints of the line segment.1263pub vertices: [Vec2; 2],1264}12651266impl Primitive2d for Segment2d {}12671268impl Default for Segment2d {1269fn default() -> Self {1270Self {1271vertices: [Vec2::new(-0.5, 0.0), Vec2::new(0.5, 0.0)],1272}1273}1274}12751276impl Segment2d {1277/// Create a new `Segment2d` from its endpoints.1278#[inline]1279pub const fn new(point1: Vec2, point2: Vec2) -> Self {1280Self {1281vertices: [point1, point2],1282}1283}12841285/// Create a new `Segment2d` centered at the origin with the given direction and length.1286///1287/// The endpoints will be at `-direction * length / 2.0` and `direction * length / 2.0`.1288#[inline]1289pub fn from_direction_and_length(direction: Dir2, length: f32) -> Self {1290let endpoint = 0.5 * length * direction;1291Self {1292vertices: [-endpoint, endpoint],1293}1294}12951296/// Create a new `Segment2d` centered at the origin from a vector representing1297/// the direction and length of the line segment.1298///1299/// The endpoints will be at `-scaled_direction / 2.0` and `scaled_direction / 2.0`.1300#[inline]1301pub fn from_scaled_direction(scaled_direction: Vec2) -> Self {1302let endpoint = 0.5 * scaled_direction;1303Self {1304vertices: [-endpoint, endpoint],1305}1306}13071308/// Create a new `Segment2d` starting from the origin of the given `ray`,1309/// going in the direction of the ray for the given `length`.1310///1311/// The endpoints will be at `ray.origin` and `ray.origin + length * ray.direction`.1312#[inline]1313pub fn from_ray_and_length(ray: Ray2d, length: f32) -> Self {1314Self {1315vertices: [ray.origin, ray.get_point(length)],1316}1317}13181319/// Get the position of the first endpoint of the line segment.1320#[inline]1321pub const fn point1(&self) -> Vec2 {1322self.vertices[0]1323}13241325/// Get the position of the second endpoint of the line segment.1326#[inline]1327pub const fn point2(&self) -> Vec2 {1328self.vertices[1]1329}13301331/// Compute the midpoint between the two endpoints of the line segment.1332#[inline]1333#[doc(alias = "midpoint")]1334pub fn center(&self) -> Vec2 {1335self.point1().midpoint(self.point2())1336}13371338/// Compute the length of the line segment.1339#[inline]1340pub fn length(&self) -> f32 {1341self.point1().distance(self.point2())1342}13431344/// Compute the squared length of the line segment.1345#[inline]1346pub fn length_squared(&self) -> f32 {1347self.point1().distance_squared(self.point2())1348}13491350/// Compute the normalized direction pointing from the first endpoint to the second endpoint.1351///1352/// For the non-panicking version, see [`Segment2d::try_direction`].1353///1354/// # Panics1355///1356/// Panics if a valid direction could not be computed, for example when the endpoints are coincident, NaN, or infinite.1357#[inline]1358pub fn direction(&self) -> Dir2 {1359self.try_direction().unwrap_or_else(|err| {1360panic!("Failed to compute the direction of a line segment: {err}")1361})1362}13631364/// Try to compute the normalized direction pointing from the first endpoint to the second endpoint.1365///1366/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid direction could not be computed,1367/// for example when the endpoints are coincident, NaN, or infinite.1368#[inline]1369pub fn try_direction(&self) -> Result<Dir2, InvalidDirectionError> {1370Dir2::new(self.scaled_direction())1371}13721373/// Compute the vector from the first endpoint to the second endpoint.1374#[inline]1375pub fn scaled_direction(&self) -> Vec2 {1376self.point2() - self.point1()1377}13781379/// Compute the normalized counterclockwise normal on the left-hand side of the line segment.1380///1381/// For the non-panicking version, see [`Segment2d::try_left_normal`].1382///1383/// # Panics1384///1385/// Panics if a valid normal could not be computed, for example when the endpoints are coincident, NaN, or infinite.1386#[inline]1387pub fn left_normal(&self) -> Dir2 {1388self.try_left_normal().unwrap_or_else(|err| {1389panic!("Failed to compute the left-hand side normal of a line segment: {err}")1390})1391}13921393/// Try to compute the normalized counterclockwise normal on the left-hand side of the line segment.1394///1395/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid normal could not be computed,1396/// for example when the endpoints are coincident, NaN, or infinite.1397#[inline]1398pub fn try_left_normal(&self) -> Result<Dir2, InvalidDirectionError> {1399Dir2::new(self.scaled_left_normal())1400}14011402/// Compute the non-normalized counterclockwise normal on the left-hand side of the line segment.1403///1404/// The length of the normal is the distance between the endpoints.1405#[inline]1406pub fn scaled_left_normal(&self) -> Vec2 {1407let scaled_direction = self.scaled_direction();1408Vec2::new(-scaled_direction.y, scaled_direction.x)1409}14101411/// Compute the normalized clockwise normal on the right-hand side of the line segment.1412///1413/// For the non-panicking version, see [`Segment2d::try_right_normal`].1414///1415/// # Panics1416///1417/// Panics if a valid normal could not be computed, for example when the endpoints are coincident, NaN, or infinite.1418#[inline]1419pub fn right_normal(&self) -> Dir2 {1420self.try_right_normal().unwrap_or_else(|err| {1421panic!("Failed to compute the right-hand side normal of a line segment: {err}")1422})1423}14241425/// Try to compute the normalized clockwise normal on the right-hand side of the line segment.1426///1427/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid normal could not be computed,1428/// for example when the endpoints are coincident, NaN, or infinite.1429#[inline]1430pub fn try_right_normal(&self) -> Result<Dir2, InvalidDirectionError> {1431Dir2::new(self.scaled_right_normal())1432}14331434/// Compute the non-normalized clockwise normal on the right-hand side of the line segment.1435///1436/// The length of the normal is the distance between the endpoints.1437#[inline]1438pub fn scaled_right_normal(&self) -> Vec2 {1439let scaled_direction = self.scaled_direction();1440Vec2::new(scaled_direction.y, -scaled_direction.x)1441}14421443/// Compute the segment transformed by the given [`Isometry2d`].1444#[inline]1445pub fn transformed(&self, isometry: impl Into<Isometry2d>) -> Self {1446let isometry: Isometry2d = isometry.into();1447Self::new(1448isometry.transform_point(self.point1()),1449isometry.transform_point(self.point2()),1450)1451}14521453/// Compute the segment translated by the given vector.1454#[inline]1455pub fn translated(&self, translation: Vec2) -> Segment2d {1456Self::new(self.point1() + translation, self.point2() + translation)1457}14581459/// Compute the segment rotated around the origin by the given rotation.1460#[inline]1461pub fn rotated(&self, rotation: Rot2) -> Segment2d {1462Segment2d::new(rotation * self.point1(), rotation * self.point2())1463}14641465/// Compute the segment rotated around the given point by the given rotation.1466#[inline]1467pub fn rotated_around(&self, rotation: Rot2, point: Vec2) -> Segment2d {1468// We offset our segment so that our segment is rotated as if from the origin, then we can apply the offset back1469let offset = self.translated(-point);1470let rotated = offset.rotated(rotation);1471rotated.translated(point)1472}14731474/// Compute the segment rotated around its own center.1475#[inline]1476pub fn rotated_around_center(&self, rotation: Rot2) -> Segment2d {1477self.rotated_around(rotation, self.center())1478}14791480/// Compute the segment with its center at the origin, keeping the same direction and length.1481#[inline]1482pub fn centered(&self) -> Segment2d {1483let center = self.center();1484self.translated(-center)1485}14861487/// Compute the segment with a new length, keeping the same direction and center.1488#[inline]1489pub fn resized(&self, length: f32) -> Segment2d {1490let offset_from_origin = self.center();1491let centered = self.translated(-offset_from_origin);1492let ratio = length / self.length();1493let segment = Segment2d::new(centered.point1() * ratio, centered.point2() * ratio);1494segment.translated(offset_from_origin)1495}14961497/// Reverses the direction of the line segment by swapping the endpoints.1498#[inline]1499pub fn reverse(&mut self) {1500let [point1, point2] = &mut self.vertices;1501core::mem::swap(point1, point2);1502}15031504/// Returns the line segment with its direction reversed by swapping the endpoints.1505#[inline]1506#[must_use]1507pub fn reversed(mut self) -> Self {1508self.reverse();1509self1510}15111512/// Returns the point on the [`Segment2d`] that is closest to the specified `point`.1513#[inline]1514pub fn closest_point(&self, point: Vec2) -> Vec2 {1515// `point`1516// x1517// ^|1518// / |1519//`offset`/ |1520// / | `segment_vector`1521// x----.-------------->x1522// 0 t 11523let segment_vector = self.vertices[1] - self.vertices[0];1524let offset = point - self.vertices[0];1525// The signed projection of `offset` onto `segment_vector`, scaled by the length of the segment.1526let projection_scaled = segment_vector.dot(offset);15271528// `point` is too far "left" in the picture1529if projection_scaled <= 0.0 {1530return self.vertices[0];1531}15321533let length_squared = segment_vector.length_squared();1534// `point` is too far "right" in the picture1535if projection_scaled >= length_squared {1536return self.vertices[1];1537}15381539// Point lies somewhere in the middle, we compute the closest point by finding the parameter along the line.1540let t = projection_scaled / length_squared;1541self.vertices[0] + t * segment_vector1542}1543}15441545impl From<[Vec2; 2]> for Segment2d {1546#[inline]1547fn from(vertices: [Vec2; 2]) -> Self {1548Self { vertices }1549}1550}15511552impl From<(Vec2, Vec2)> for Segment2d {1553#[inline]1554fn from((point1, point2): (Vec2, Vec2)) -> Self {1555Self::new(point1, point2)1556}1557}15581559/// A series of connected line segments in 2D space.1560#[cfg(feature = "alloc")]1561#[derive(Clone, Debug, PartialEq)]1562#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1563#[cfg_attr(1564feature = "bevy_reflect",1565derive(Reflect),1566reflect(Debug, PartialEq, Clone)1567)]1568#[cfg_attr(1569all(feature = "serialize", feature = "bevy_reflect"),1570reflect(Serialize, Deserialize)1571)]1572pub struct Polyline2d {1573/// The vertices of the polyline1574pub vertices: Vec<Vec2>,1575}15761577#[cfg(feature = "alloc")]1578impl Primitive2d for Polyline2d {}15791580#[cfg(feature = "alloc")]1581impl FromIterator<Vec2> for Polyline2d {1582fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {1583Self {1584vertices: iter.into_iter().collect(),1585}1586}1587}15881589#[cfg(feature = "alloc")]1590impl Default for Polyline2d {1591fn default() -> Self {1592Self {1593vertices: Vec::from([Vec2::new(-0.5, 0.0), Vec2::new(0.5, 0.0)]),1594}1595}1596}15971598#[cfg(feature = "alloc")]1599impl Polyline2d {1600/// Create a new `Polyline2d` from its vertices1601pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {1602Self::from_iter(vertices)1603}16041605/// Create a new `Polyline2d` from two endpoints with subdivision points.1606/// `subdivisions = 0` creates a simple line with just start and end points.1607/// `subdivisions = 1` adds one point in the middle, creating 2 segments, etc.1608pub fn with_subdivisions(start: Vec2, end: Vec2, subdivisions: usize) -> Self {1609let total_vertices = subdivisions + 2;1610let mut vertices = Vec::with_capacity(total_vertices);16111612let step = (end - start) / (subdivisions + 1) as f32;1613for i in 0..total_vertices {1614vertices.push(start + step * i as f32);1615}16161617Self { vertices }1618}1619}16201621/// A triangle in 2D space1622#[derive(Clone, Copy, Debug, PartialEq)]1623#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1624#[cfg_attr(1625feature = "bevy_reflect",1626derive(Reflect),1627reflect(Debug, PartialEq, Default, Clone)1628)]1629#[cfg_attr(1630all(feature = "serialize", feature = "bevy_reflect"),1631reflect(Serialize, Deserialize)1632)]1633pub struct Triangle2d {1634/// The vertices of the triangle1635pub vertices: [Vec2; 3],1636}16371638impl Primitive2d for Triangle2d {}16391640impl Default for Triangle2d {1641/// Returns the default [`Triangle2d`] with the vertices `[0.0, 0.5]`, `[-0.5, -0.5]`, and `[0.5, -0.5]`.1642fn default() -> Self {1643Self {1644vertices: [Vec2::Y * 0.5, Vec2::new(-0.5, -0.5), Vec2::new(0.5, -0.5)],1645}1646}1647}16481649impl Triangle2d {1650/// Create a new `Triangle2d` from points `a`, `b`, and `c`1651#[inline]1652pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {1653Self {1654vertices: [a, b, c],1655}1656}16571658/// Get the [`WindingOrder`] of the triangle1659#[inline]1660#[doc(alias = "orientation")]1661pub fn winding_order(&self) -> WindingOrder {1662let [a, b, c] = self.vertices;1663let area = (b - a).perp_dot(c - a);1664if area > f32::EPSILON {1665WindingOrder::CounterClockwise1666} else if area < -f32::EPSILON {1667WindingOrder::Clockwise1668} else {1669WindingOrder::Invalid1670}1671}16721673/// Compute the circle passing through all three vertices of the triangle.1674/// The vector in the returned tuple is the circumcenter.1675pub fn circumcircle(&self) -> (Circle, Vec2) {1676// We treat the triangle as translated so that vertex A is at the origin. This simplifies calculations.1677//1678// A = (0, 0)1679// *1680// / \1681// / \1682// / \1683// / \1684// / U \1685// / \1686// *-------------*1687// B C16881689let a = self.vertices[0];1690let (b, c) = (self.vertices[1] - a, self.vertices[2] - a);1691let b_length_sq = b.length_squared();1692let c_length_sq = c.length_squared();16931694// Reference: https://en.wikipedia.org/wiki/Circumcircle#Cartesian_coordinates_21695let inv_d = (2.0 * (b.x * c.y - b.y * c.x)).recip();1696let ux = inv_d * (c.y * b_length_sq - b.y * c_length_sq);1697let uy = inv_d * (b.x * c_length_sq - c.x * b_length_sq);1698let u = Vec2::new(ux, uy);16991700// Compute true circumcenter and circumradius, adding the tip coordinate so that1701// A is translated back to its actual coordinate.1702let center = u + a;1703let radius = u.length();17041705(Circle { radius }, center)1706}17071708/// Checks if the triangle is degenerate, meaning it has zero area.1709///1710/// A triangle is degenerate if the cross product of the vectors `ab` and `ac` has a length less than `10e-7`.1711/// This indicates that the three vertices are collinear or nearly collinear.1712#[inline]1713pub fn is_degenerate(&self) -> bool {1714let [a, b, c] = self.vertices;1715let ab = (b - a).extend(0.);1716let ac = (c - a).extend(0.);1717ab.cross(ac).length() < 10e-71718}17191720/// Checks if the triangle is acute, meaning all angles are less than 90 degrees1721#[inline]1722pub fn is_acute(&self) -> bool {1723let [a, b, c] = self.vertices;1724let ab = b - a;1725let bc = c - b;1726let ca = a - c;17271728// a^2 + b^2 < c^2 for an acute triangle1729let side_lengths = [1730ab.length_squared(),1731bc.length_squared(),1732ca.length_squared(),1733];1734let sum = side_lengths[0] + side_lengths[1] + side_lengths[2];1735let max = side_lengths[0].max(side_lengths[1]).max(side_lengths[2]);1736sum - max > max1737}17381739/// Checks if the triangle is obtuse, meaning one angle is greater than 90 degrees1740#[inline]1741pub fn is_obtuse(&self) -> bool {1742let [a, b, c] = self.vertices;1743let ab = b - a;1744let bc = c - b;1745let ca = a - c;17461747// a^2 + b^2 > c^2 for an obtuse triangle1748let side_lengths = [1749ab.length_squared(),1750bc.length_squared(),1751ca.length_squared(),1752];1753let sum = side_lengths[0] + side_lengths[1] + side_lengths[2];1754let max = side_lengths[0].max(side_lengths[1]).max(side_lengths[2]);1755sum - max < max1756}17571758/// Reverse the [`WindingOrder`] of the triangle1759/// by swapping the first and last vertices.1760#[inline]1761pub fn reverse(&mut self) {1762self.vertices.swap(0, 2);1763}17641765/// This triangle but reversed.1766#[inline]1767#[must_use]1768pub fn reversed(mut self) -> Self {1769self.reverse();1770self1771}1772}17731774impl Measured2d for Triangle2d {1775/// Get the area of the triangle1776#[inline]1777fn area(&self) -> f32 {1778let [a, b, c] = self.vertices;1779ops::abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2.01780}17811782/// Get the perimeter of the triangle1783#[inline]1784fn perimeter(&self) -> f32 {1785let [a, b, c] = self.vertices;17861787let ab = a.distance(b);1788let bc = b.distance(c);1789let ca = c.distance(a);17901791ab + bc + ca1792}1793}17941795/// A rectangle primitive, which is like a square, except that the width and height can be different1796#[derive(Clone, Copy, Debug, PartialEq)]1797#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1798#[cfg_attr(1799feature = "bevy_reflect",1800derive(Reflect),1801reflect(Debug, PartialEq, Default, Clone)1802)]1803#[cfg_attr(1804all(feature = "serialize", feature = "bevy_reflect"),1805reflect(Serialize, Deserialize)1806)]1807#[doc(alias = "Quad")]1808pub struct Rectangle {1809/// Half of the width and height of the rectangle1810pub half_size: Vec2,1811}18121813impl Primitive2d for Rectangle {}18141815impl Default for Rectangle {1816/// Returns the default [`Rectangle`] with a half-width and half-height of `0.5`.1817fn default() -> Self {1818Self {1819half_size: Vec2::splat(0.5),1820}1821}1822}18231824impl Rectangle {1825/// Create a new `Rectangle` from a full width and height1826#[inline]1827pub const fn new(width: f32, height: f32) -> Self {1828Self::from_size(Vec2::new(width, height))1829}18301831/// Create a new `Rectangle` from a given full size1832#[inline]1833pub const fn from_size(size: Vec2) -> Self {1834Self {1835half_size: Vec2::new(size.x / 2.0, size.y / 2.0),1836}1837}18381839/// Create a new `Rectangle` from two corner points1840#[inline]1841pub fn from_corners(point1: Vec2, point2: Vec2) -> Self {1842Self {1843half_size: (point2 - point1).abs() / 2.0,1844}1845}18461847/// Create a `Rectangle` from a single length.1848/// The resulting `Rectangle` will be the same size in every direction.1849#[inline]1850pub const fn from_length(length: f32) -> Self {1851Self {1852half_size: Vec2::splat(length / 2.0),1853}1854}18551856/// Get the size of the rectangle1857#[inline]1858pub fn size(&self) -> Vec2 {18592.0 * self.half_size1860}18611862/// Finds the point on the rectangle that is closest to the given `point`.1863///1864/// If the point is outside the rectangle, the returned point will be on the perimeter of the rectangle.1865/// Otherwise, it will be inside the rectangle and returned as is.1866#[inline]1867pub fn closest_point(&self, point: Vec2) -> Vec2 {1868// Clamp point coordinates to the rectangle1869point.clamp(-self.half_size, self.half_size)1870}1871}18721873impl Measured2d for Rectangle {1874/// Get the area of the rectangle1875#[inline]1876fn area(&self) -> f32 {18774.0 * self.half_size.x * self.half_size.y1878}18791880/// Get the perimeter of the rectangle1881#[inline]1882fn perimeter(&self) -> f32 {18834.0 * (self.half_size.x + self.half_size.y)1884}1885}18861887/// A polygon with N vertices.1888#[cfg(feature = "alloc")]1889#[derive(Clone, Debug, PartialEq)]1890#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1891#[cfg_attr(1892feature = "bevy_reflect",1893derive(Reflect),1894reflect(Debug, PartialEq, Clone)1895)]1896#[cfg_attr(1897all(feature = "serialize", feature = "bevy_reflect"),1898reflect(Serialize, Deserialize)1899)]1900pub struct Polygon {1901/// The vertices of the `Polygon`1902pub vertices: Vec<Vec2>,1903}19041905#[cfg(feature = "alloc")]1906impl Primitive2d for Polygon {}19071908#[cfg(feature = "alloc")]1909impl FromIterator<Vec2> for Polygon {1910fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {1911Self {1912vertices: iter.into_iter().collect(),1913}1914}1915}19161917#[cfg(feature = "alloc")]1918impl Polygon {1919/// Create a new `Polygon` from its vertices1920pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {1921Self::from_iter(vertices)1922}19231924/// Tests if the polygon is simple.1925///1926/// A polygon is simple if it is not self intersecting and not self tangent.1927/// As such, no two edges of the polygon may cross each other and each vertex must not lie on another edge.1928#[cfg(feature = "alloc")]1929pub fn is_simple(&self) -> bool {1930is_polygon_simple(&self.vertices)1931}1932}19331934#[cfg(feature = "alloc")]1935impl From<ConvexPolygon> for Polygon {1936fn from(val: ConvexPolygon) -> Self {1937Polygon {1938vertices: val.vertices,1939}1940}1941}19421943/// A convex polygon with `N` vertices.1944#[cfg(feature = "alloc")]1945#[derive(Clone, Debug, PartialEq)]1946#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]1947#[cfg_attr(1948feature = "bevy_reflect",1949derive(Reflect),1950reflect(Debug, PartialEq, Clone)1951)]1952#[cfg_attr(1953all(feature = "serialize", feature = "bevy_reflect"),1954reflect(Serialize, Deserialize)1955)]1956pub struct ConvexPolygon {1957/// The vertices of the [`ConvexPolygon`].1958vertices: Vec<Vec2>,1959}19601961#[cfg(feature = "alloc")]1962impl Primitive2d for ConvexPolygon {}19631964/// An error that happens when creating a [`ConvexPolygon`].1965#[cfg(feature = "alloc")]1966#[derive(Error, Debug, Clone)]1967pub enum ConvexPolygonError {1968/// The created polygon is not convex.1969#[error("The created polygon is not convex")]1970Concave,1971}19721973#[cfg(feature = "alloc")]1974impl ConvexPolygon {1975fn triangle_winding_order(1976&self,1977a_index: usize,1978b_index: usize,1979c_index: usize,1980) -> WindingOrder {1981let a = self.vertices[a_index];1982let b = self.vertices[b_index];1983let c = self.vertices[c_index];1984Triangle2d::new(a, b, c).winding_order()1985}19861987/// Create a [`ConvexPolygon`] from its `vertices`.1988///1989/// # Errors1990///1991/// Returns [`ConvexPolygonError::Concave`] if the `vertices` do not form a convex polygon.1992pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Result<Self, ConvexPolygonError> {1993let polygon = Self::new_unchecked(vertices);1994let len = polygon.vertices.len();1995let ref_winding_order = polygon.triangle_winding_order(len - 1, 0, 1);1996for i in 1..len {1997let winding_order = polygon.triangle_winding_order(i - 1, i, (i + 1) % len);1998if winding_order != ref_winding_order {1999return Err(ConvexPolygonError::Concave);2000}2001}2002Ok(polygon)2003}20042005/// Create a [`ConvexPolygon`] from its `vertices`, without checks.2006/// Use this version only if you know that the `vertices` make up a convex polygon.2007#[inline]2008pub fn new_unchecked(vertices: impl IntoIterator<Item = Vec2>) -> Self {2009Self {2010vertices: vertices.into_iter().collect(),2011}2012}20132014/// Get the vertices of this polygon2015#[inline]2016pub fn vertices(&self) -> &[Vec2] {2017&self.vertices2018}2019}20202021#[cfg(feature = "alloc")]2022impl TryFrom<Polygon> for ConvexPolygon {2023type Error = ConvexPolygonError;20242025fn try_from(val: Polygon) -> Result<Self, Self::Error> {2026ConvexPolygon::new(val.vertices)2027}2028}20292030/// A polygon centered on the origin where all vertices lie on a circle, equally far apart.2031#[derive(Clone, Copy, Debug, PartialEq)]2032#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]2033#[cfg_attr(2034feature = "bevy_reflect",2035derive(Reflect),2036reflect(Debug, PartialEq, Default, Clone)2037)]2038#[cfg_attr(2039all(feature = "serialize", feature = "bevy_reflect"),2040reflect(Serialize, Deserialize)2041)]2042pub struct RegularPolygon {2043/// The circumcircle on which all vertices lie2044pub circumcircle: Circle,2045/// The number of sides2046pub sides: u32,2047}20482049impl Primitive2d for RegularPolygon {}20502051impl Default for RegularPolygon {2052/// Returns the default [`RegularPolygon`] with six sides (a hexagon) and a circumradius of `0.5`.2053fn default() -> Self {2054Self {2055circumcircle: Circle { radius: 0.5 },2056sides: 6,2057}2058}2059}20602061impl RegularPolygon {2062/// Create a new `RegularPolygon`2063/// from the radius of the circumcircle and a number of sides2064///2065/// # Panics2066///2067/// Panics if `circumradius` is negative2068#[inline]2069pub const fn new(circumradius: f32, sides: u32) -> Self {2070assert!(2071circumradius.is_sign_positive(),2072"polygon has a negative radius"2073);2074assert!(sides > 2, "polygon has less than 3 sides");20752076Self {2077circumcircle: Circle {2078radius: circumradius,2079},2080sides,2081}2082}20832084/// Get the radius of the circumcircle on which all vertices2085/// of the regular polygon lie2086#[inline]2087pub const fn circumradius(&self) -> f32 {2088self.circumcircle.radius2089}20902091/// Get the inradius or apothem of the regular polygon.2092/// This is the radius of the largest circle that can2093/// be drawn within the polygon2094#[inline]2095#[doc(alias = "apothem")]2096pub fn inradius(&self) -> f32 {2097self.circumradius() * ops::cos(PI / self.sides as f32)2098}20992100/// Get the length of one side of the regular polygon2101#[inline]2102pub fn side_length(&self) -> f32 {21032.0 * self.circumradius() * ops::sin(PI / self.sides as f32)2104}21052106/// Get the internal angle of the regular polygon in degrees.2107///2108/// This is the angle formed by two adjacent sides with points2109/// within the angle being in the interior of the polygon2110#[inline]2111pub const fn internal_angle_degrees(&self) -> f32 {2112(self.sides - 2) as f32 / self.sides as f32 * 180.02113}21142115/// Get the internal angle of the regular polygon in radians.2116///2117/// This is the angle formed by two adjacent sides with points2118/// within the angle being in the interior of the polygon2119#[inline]2120pub const fn internal_angle_radians(&self) -> f32 {2121(self.sides - 2) as f32 * PI / self.sides as f322122}21232124/// Get the external angle of the regular polygon in degrees.2125///2126/// This is the angle formed by two adjacent sides with points2127/// within the angle being in the exterior of the polygon2128#[inline]2129pub const fn external_angle_degrees(&self) -> f32 {2130360.0 / self.sides as f322131}21322133/// Get the external angle of the regular polygon in radians.2134///2135/// This is the angle formed by two adjacent sides with points2136/// within the angle being in the exterior of the polygon2137#[inline]2138pub const fn external_angle_radians(&self) -> f32 {21392.0 * PI / self.sides as f322140}21412142/// Returns an iterator over the vertices of the regular polygon,2143/// rotated counterclockwise by the given angle in radians.2144///2145/// With a rotation of 0, a vertex will be placed at the top `(0.0, circumradius)`.2146pub fn vertices(self, rotation: f32) -> impl IntoIterator<Item = Vec2> {2147// Add pi/2 so that the polygon has a vertex at the top (sin is 1.0 and cos is 0.0)2148let start_angle = rotation + FRAC_PI_2;2149let step = core::f32::consts::TAU / self.sides as f32;21502151(0..self.sides).map(move |i| {2152let theta = start_angle + i as f32 * step;2153let (sin, cos) = ops::sin_cos(theta);2154Vec2::new(cos, sin) * self.circumcircle.radius2155})2156}2157}21582159impl Measured2d for RegularPolygon {2160/// Get the area of the regular polygon2161#[inline]2162fn area(&self) -> f32 {2163let angle: f32 = 2.0 * PI / (self.sides as f32);2164(self.sides as f32) * self.circumradius().squared() * ops::sin(angle) / 2.02165}21662167/// Get the perimeter of the regular polygon.2168/// This is the sum of its sides2169#[inline]2170fn perimeter(&self) -> f32 {2171self.sides as f32 * self.side_length()2172}2173}21742175/// A 2D capsule primitive, also known as a stadium or pill shape.2176///2177/// A two-dimensional capsule is defined as a neighborhood of points at a distance (radius) from a line2178#[derive(Clone, Copy, Debug, PartialEq)]2179#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]2180#[cfg_attr(2181feature = "bevy_reflect",2182derive(Reflect),2183reflect(Debug, PartialEq, Default, Clone)2184)]2185#[cfg_attr(2186all(feature = "serialize", feature = "bevy_reflect"),2187reflect(Serialize, Deserialize)2188)]2189#[doc(alias = "stadium", alias = "pill")]2190pub struct Capsule2d {2191/// The radius of the capsule2192pub radius: f32,2193/// Half the height of the capsule, excluding the semicircles2194pub half_length: f32,2195}21962197impl Primitive2d for Capsule2d {}21982199impl Default for Capsule2d {2200/// Returns the default [`Capsule2d`] with a radius of `0.5` and a half-height of `0.5`,2201/// excluding the semicircles.2202fn default() -> Self {2203Self {2204radius: 0.5,2205half_length: 0.5,2206}2207}2208}22092210impl Capsule2d {2211/// Create a new `Capsule2d` from a radius and length2212pub const fn new(radius: f32, length: f32) -> Self {2213Self {2214radius,2215half_length: length / 2.0,2216}2217}22182219/// Get the part connecting the semicircular ends of the capsule as a [`Rectangle`]2220#[inline]2221pub const fn to_inner_rectangle(&self) -> Rectangle {2222Rectangle::new(self.radius * 2.0, self.half_length * 2.0)2223}2224}22252226impl Measured2d for Capsule2d {2227/// Get the area of the capsule2228#[inline]2229fn area(&self) -> f32 {2230// pi*r^2 + (2r)*l2231PI * self.radius.squared() + self.to_inner_rectangle().area()2232}22332234/// Get the perimeter of the capsule2235#[inline]2236fn perimeter(&self) -> f32 {2237// 2pi*r + 2l22382.0 * PI * self.radius + 4.0 * self.half_length2239}2240}22412242/// A 2D shape representing the ring version of a base shape.2243///2244/// The `inner_shape` forms the "hollow" of the `outer_shape`.2245///2246/// The resulting shapes are rings or hollow shapes.2247/// For example, a circle becomes an annulus.2248///2249/// # Warning2250///2251/// The `outer_shape` must contain the `inner_shape` for the generated meshes to be accurate.2252///2253/// If there are vertices in the `inner_shape` that escape the `outer_shape`2254/// (for example, if the `inner_shape` is in fact larger),2255/// it may result in incorrect geometries.2256#[derive(Clone, Copy, Debug, PartialEq)]2257#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]2258pub struct Ring<P: Primitive2d> {2259/// The outer shape2260pub outer_shape: P,2261/// The inner shape (the same shape of a different size)2262pub inner_shape: P,2263}22642265impl<P: Primitive2d> Ring<P> {2266/// Create a new `Ring` from a given `outer_shape` and `inner_shape`.2267///2268/// If the primitive implements [`Inset`] and you would like a uniform thickness, consider using [`ToRing::to_ring`]2269pub const fn new(outer_shape: P, inner_shape: P) -> Self {2270Self {2271outer_shape,2272inner_shape,2273}2274}2275}22762277impl<T: Primitive2d> Primitive2d for Ring<T> {}22782279impl<P: Primitive2d + Clone + Inset> Ring<P> {2280/// Generate a `Ring` from a given `primitive` and a `thickness`.2281pub fn from_primitive_and_thickness(primitive: P, thickness: f32) -> Self {2282let hollow = primitive.clone().inset(thickness);2283Ring::new(primitive, hollow)2284}2285}22862287impl<P: Primitive2d + Measured2d> Measured2d for Ring<P> {2288#[inline]2289fn area(&self) -> f32 {2290self.outer_shape.area() - self.inner_shape.area()2291}22922293#[inline]2294fn perimeter(&self) -> f32 {2295self.outer_shape.perimeter() + self.inner_shape.perimeter()2296}2297}22982299/// Provides a convenience method for converting a primitive to a [`Ring`], with a given thickness.2300///2301/// The primitive must implement [`Inset`].2302pub trait ToRing: Primitive2d + Inset2303where2304Self: Sized,2305{2306/// Construct a `Ring`2307fn to_ring(self, thickness: f32) -> Ring<Self>;2308}23092310impl<P> ToRing for P2311where2312P: Primitive2d + Clone + Inset,2313{2314fn to_ring(self, thickness: f32) -> Ring<Self> {2315Ring::from_primitive_and_thickness(self, thickness)2316}2317}23182319#[cfg(test)]2320mod tests {2321// Reference values were computed by hand and/or with external tools23222323use super::*;2324use approx::{assert_abs_diff_eq, assert_relative_eq};23252326#[test]2327fn rectangle_closest_point() {2328let rectangle = Rectangle::new(2.0, 2.0);2329assert_eq!(rectangle.closest_point(Vec2::X * 10.0), Vec2::X);2330assert_eq!(rectangle.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);2331assert_eq!(2332rectangle.closest_point(Vec2::new(0.25, 0.1)),2333Vec2::new(0.25, 0.1)2334);2335}23362337#[test]2338fn circle_closest_point() {2339let circle = Circle { radius: 1.0 };2340assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);2341assert_eq!(2342circle.closest_point(Vec2::NEG_ONE * 10.0),2343Vec2::NEG_ONE.normalize()2344);2345assert_eq!(2346circle.closest_point(Vec2::new(0.25, 0.1)),2347Vec2::new(0.25, 0.1)2348);2349}23502351#[test]2352fn annulus_closest_point() {2353let annulus = Annulus::new(1.5, 2.0);2354assert_eq!(annulus.closest_point(Vec2::X * 10.0), Vec2::X * 2.0);2355assert_eq!(2356annulus.closest_point(Vec2::NEG_ONE),2357Vec2::NEG_ONE.normalize() * 1.52358);2359assert_eq!(2360annulus.closest_point(Vec2::new(1.55, 0.85)),2361Vec2::new(1.55, 0.85)2362);2363}23642365#[test]2366fn rhombus_closest_point() {2367let rhombus = Rhombus::new(2.0, 1.0);2368assert_eq!(rhombus.closest_point(Vec2::X * 10.0), Vec2::X);2369assert_eq!(2370rhombus.closest_point(Vec2::NEG_ONE * 0.2),2371Vec2::NEG_ONE * 0.22372);2373assert_eq!(2374rhombus.closest_point(Vec2::new(-0.55, 0.35)),2375Vec2::new(-0.5, 0.25)2376);23772378let rhombus = Rhombus::new(0.0, 0.0);2379assert_eq!(rhombus.closest_point(Vec2::X * 10.0), Vec2::ZERO);2380assert_eq!(rhombus.closest_point(Vec2::NEG_ONE * 0.2), Vec2::ZERO);2381assert_eq!(rhombus.closest_point(Vec2::new(-0.55, 0.35)), Vec2::ZERO);2382}23832384#[test]2385fn segment_closest_point() {2386assert_eq!(2387Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(3.0, 0.0))2388.closest_point(Vec2::new(1.0, 6.0)),2389Vec2::new(1.0, 0.0)2390);23912392let segments = [2393Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(0.0, 0.0)),2394Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0)),2395Segment2d::new(Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0)),2396Segment2d::new(Vec2::new(1.0, 0.0), Vec2::new(1.0, 5.0 * f32::EPSILON)),2397];2398let points = [2399Vec2::new(0.0, 0.0),2400Vec2::new(1.0, 0.0),2401Vec2::new(-1.0, 1.0),2402Vec2::new(1.0, 1.0),2403Vec2::new(-1.0, 0.0),2404Vec2::new(5.0, -1.0),2405Vec2::new(1.0, f32::EPSILON),2406];24072408for point in points.iter() {2409for segment in segments.iter() {2410let closest = segment.closest_point(*point);2411assert!(2412point.distance_squared(closest) <= point.distance_squared(segment.point1()),2413"Closest point must always be at least as close as either vertex."2414);2415assert!(2416point.distance_squared(closest) <= point.distance_squared(segment.point2()),2417"Closest point must always be at least as close as either vertex."2418);2419assert!(2420point.distance_squared(closest) <= point.distance_squared(segment.center()),2421"Closest point must always be at least as close as the center."2422);2423let closest_to_closest = segment.closest_point(closest);2424// Closest point must already be on the segment2425assert_relative_eq!(closest_to_closest, closest);2426}2427}2428}24292430#[test]2431fn circle_math() {2432let circle = Circle { radius: 3.0 };2433assert_eq!(circle.diameter(), 6.0, "incorrect diameter");2434assert_eq!(circle.area(), 28.274334, "incorrect area");2435assert_eq!(circle.perimeter(), 18.849556, "incorrect perimeter");2436}24372438#[test]2439fn capsule_math() {2440let capsule = Capsule2d::new(2.0, 9.0);2441assert_eq!(2442capsule.to_inner_rectangle(),2443Rectangle::new(4.0, 9.0),2444"rectangle wasn't created correctly from a capsule"2445);2446assert_eq!(capsule.area(), 48.566371, "incorrect area");2447assert_eq!(capsule.perimeter(), 30.566371, "incorrect perimeter");2448}24492450#[test]2451fn annulus_math() {2452let annulus = Annulus::new(2.5, 3.5);2453assert_eq!(annulus.diameter(), 7.0, "incorrect diameter");2454assert_eq!(annulus.thickness(), 1.0, "incorrect thickness");2455assert_eq!(annulus.area(), 18.849556, "incorrect area");2456assert_eq!(annulus.perimeter(), 37.699112, "incorrect perimeter");2457}24582459#[test]2460fn rhombus_math() {2461let rhombus = Rhombus::new(3.0, 4.0);2462assert_eq!(rhombus.area(), 6.0, "incorrect area");2463assert_eq!(rhombus.perimeter(), 10.0, "incorrect perimeter");2464assert_eq!(rhombus.side(), 2.5, "incorrect side");2465assert_eq!(rhombus.inradius(), 1.2, "incorrect inradius");2466assert_eq!(rhombus.circumradius(), 2.0, "incorrect circumradius");2467let rhombus = Rhombus::new(0.0, 0.0);2468assert_eq!(rhombus.area(), 0.0, "incorrect area");2469assert_eq!(rhombus.perimeter(), 0.0, "incorrect perimeter");2470assert_eq!(rhombus.side(), 0.0, "incorrect side");2471assert_eq!(rhombus.inradius(), 0.0, "incorrect inradius");2472assert_eq!(rhombus.circumradius(), 0.0, "incorrect circumradius");2473let rhombus = Rhombus::from_side(core::f32::consts::SQRT_2);2474assert_abs_diff_eq!(rhombus.half_diagonals, Vec2::new(1.0, 1.0));2475assert_abs_diff_eq!(2476rhombus.half_diagonals,2477Rhombus::from_inradius(FRAC_1_SQRT_2).half_diagonals2478);2479}24802481#[test]2482fn ellipse_math() {2483let ellipse = Ellipse::new(3.0, 1.0);2484assert_eq!(ellipse.area(), 9.424778, "incorrect area");24852486assert_eq!(ellipse.eccentricity(), 0.94280905, "incorrect eccentricity");24872488let line = Ellipse::new(1., 0.);2489assert_eq!(line.eccentricity(), 1., "incorrect line eccentricity");24902491let circle = Ellipse::new(2., 2.);2492assert_eq!(circle.eccentricity(), 0., "incorrect circle eccentricity");2493}24942495#[test]2496fn ellipse_perimeter() {2497let circle = Ellipse::new(1., 1.);2498assert_relative_eq!(circle.perimeter(), 6.2831855);24992500let line = Ellipse::new(75_000., 0.5);2501assert_relative_eq!(line.perimeter(), 300_000.);25022503let ellipse = Ellipse::new(0.5, 2.);2504assert_relative_eq!(ellipse.perimeter(), 8.578423);25052506let ellipse = Ellipse::new(5., 3.);2507assert_relative_eq!(ellipse.perimeter(), 25.526999);2508}25092510#[test]2511fn triangle_math() {2512let triangle = Triangle2d::new(2513Vec2::new(-2.0, -1.0),2514Vec2::new(1.0, 4.0),2515Vec2::new(7.0, 0.0),2516);2517assert_eq!(triangle.area(), 21.0, "incorrect area");2518assert_eq!(triangle.perimeter(), 22.097439, "incorrect perimeter");25192520let degenerate_triangle =2521Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(0., 0.), Vec2::new(1., 0.));2522assert!(degenerate_triangle.is_degenerate());25232524let acute_triangle =2525Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 5.));2526let obtuse_triangle =2527Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 0.5));25282529assert!(acute_triangle.is_acute());2530assert!(!acute_triangle.is_obtuse());2531assert!(!obtuse_triangle.is_acute());2532assert!(obtuse_triangle.is_obtuse());2533}25342535#[test]2536fn triangle_winding_order() {2537let mut cw_triangle = Triangle2d::new(2538Vec2::new(0.0, 2.0),2539Vec2::new(-0.5, -1.2),2540Vec2::new(-1.0, -1.0),2541);2542assert_eq!(cw_triangle.winding_order(), WindingOrder::Clockwise);25432544let ccw_triangle = Triangle2d::new(2545Vec2::new(-1.0, -1.0),2546Vec2::new(-0.5, -1.2),2547Vec2::new(0.0, 2.0),2548);2549assert_eq!(ccw_triangle.winding_order(), WindingOrder::CounterClockwise);25502551// The clockwise triangle should be the same as the counterclockwise2552// triangle when reversed2553cw_triangle.reverse();2554assert_eq!(cw_triangle, ccw_triangle);25552556let invalid_triangle = Triangle2d::new(2557Vec2::new(0.0, 2.0),2558Vec2::new(0.0, -1.0),2559Vec2::new(0.0, -1.2),2560);2561assert_eq!(invalid_triangle.winding_order(), WindingOrder::Invalid);2562}25632564#[test]2565fn rectangle_math() {2566let rectangle = Rectangle::new(3.0, 7.0);2567assert_eq!(2568rectangle,2569Rectangle::from_corners(Vec2::new(-1.5, -3.5), Vec2::new(1.5, 3.5))2570);2571assert_eq!(rectangle.area(), 21.0, "incorrect area");2572assert_eq!(rectangle.perimeter(), 20.0, "incorrect perimeter");2573}25742575#[test]2576fn regular_polygon_math() {2577let polygon = RegularPolygon::new(3.0, 6);2578assert_eq!(polygon.inradius(), 2.598076, "incorrect inradius");2579assert_eq!(polygon.side_length(), 3.0, "incorrect side length");2580assert_relative_eq!(polygon.area(), 23.38268, epsilon = 0.00001);2581assert_eq!(polygon.perimeter(), 18.0, "incorrect perimeter");2582assert_eq!(2583polygon.internal_angle_degrees(),2584120.0,2585"incorrect internal angle"2586);2587assert_eq!(2588polygon.internal_angle_radians(),2589120_f32.to_radians(),2590"incorrect internal angle"2591);2592assert_eq!(2593polygon.external_angle_degrees(),259460.0,2595"incorrect external angle"2596);2597assert_eq!(2598polygon.external_angle_radians(),259960_f32.to_radians(),2600"incorrect external angle"2601);2602}26032604#[test]2605fn triangle_circumcenter() {2606let triangle = Triangle2d::new(2607Vec2::new(10.0, 2.0),2608Vec2::new(-5.0, -3.0),2609Vec2::new(2.0, -1.0),2610);2611let (Circle { radius }, circumcenter) = triangle.circumcircle();26122613// Calculated with external calculator2614assert_eq!(radius, 98.34887);2615assert_eq!(circumcenter, Vec2::new(-28.5, 92.5));2616}26172618#[test]2619fn regular_polygon_vertices() {2620let polygon = RegularPolygon::new(1.0, 4);26212622// Regular polygons have a vertex at the top by default2623let mut vertices = polygon.vertices(0.0).into_iter();2624assert!((vertices.next().unwrap() - Vec2::Y).length() < 1e-7);26252626// Rotate by 45 degrees, forming an axis-aligned square2627let mut rotated_vertices = polygon.vertices(core::f32::consts::FRAC_PI_4).into_iter();26282629// Distance from the origin to the middle of a side, derived using Pythagorean theorem2630let side_distance = FRAC_1_SQRT_2;2631assert!(2632(rotated_vertices.next().unwrap() - Vec2::new(-side_distance, side_distance)).length()2633< 1e-7,2634);2635}2636}263726382639