use core::f32::consts::{FRAC_1_SQRT_2, FRAC_PI_2, FRAC_PI_3, PI};
use derive_more::derive::From;
#[cfg(feature = "alloc")]
use thiserror::Error;
use super::{Measured2d, Primitive2d, WindingOrder};
use crate::{
ops::{self, FloatPow},
primitives::Inset,
Dir2, InvalidDirectionError, Isometry2d, Ray2d, Rot2, Vec2,
};
#[cfg(feature = "alloc")]
use super::polygon::is_polygon_simple;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Circle {
pub radius: f32,
}
impl Primitive2d for Circle {}
impl Default for Circle {
fn default() -> Self {
Self { radius: 0.5 }
}
}
impl Circle {
#[inline]
pub const fn new(radius: f32) -> Self {
Self { radius }
}
#[inline]
pub const fn diameter(&self) -> f32 {
2.0 * self.radius
}
#[inline]
pub fn closest_point(&self, point: Vec2) -> Vec2 {
let distance_squared = point.length_squared();
if distance_squared <= self.radius.squared() {
point
} else {
let dir_to_point = point / ops::sqrt(distance_squared);
self.radius * dir_to_point
}
}
}
impl Measured2d for Circle {
#[inline]
fn area(&self) -> f32 {
PI * self.radius.squared()
}
#[inline]
#[doc(alias = "circumference")]
fn perimeter(&self) -> f32 {
2.0 * PI * self.radius
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[doc(alias("CircularArc", "CircleArc"))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Arc2d {
pub radius: f32,
pub half_angle: f32,
}
impl Primitive2d for Arc2d {}
impl Default for Arc2d {
fn default() -> Self {
Self {
radius: 0.5,
half_angle: 2.0 * FRAC_PI_3,
}
}
}
impl Arc2d {
#[inline]
pub const fn new(radius: f32, half_angle: f32) -> Self {
Self { radius, half_angle }
}
#[inline]
pub const fn from_radians(radius: f32, angle: f32) -> Self {
Self {
radius,
half_angle: angle / 2.0,
}
}
#[inline]
pub const fn from_degrees(radius: f32, angle: f32) -> Self {
Self {
radius,
half_angle: angle.to_radians() / 2.0,
}
}
#[inline]
pub const fn from_turns(radius: f32, fraction: f32) -> Self {
Self {
radius,
half_angle: fraction * PI,
}
}
#[inline]
pub const fn angle(&self) -> f32 {
self.half_angle * 2.0
}
#[inline]
pub const fn length(&self) -> f32 {
self.angle() * self.radius
}
#[inline]
pub fn right_endpoint(&self) -> Vec2 {
self.radius * Vec2::from_angle(FRAC_PI_2 - self.half_angle)
}
#[inline]
pub fn left_endpoint(&self) -> Vec2 {
self.radius * Vec2::from_angle(FRAC_PI_2 + self.half_angle)
}
#[inline]
pub fn endpoints(&self) -> [Vec2; 2] {
[self.left_endpoint(), self.right_endpoint()]
}
#[inline]
pub fn midpoint(&self) -> Vec2 {
self.radius * Vec2::Y
}
#[inline]
pub fn half_chord_length(&self) -> f32 {
self.radius * ops::sin(self.half_angle)
}
#[inline]
pub fn chord_length(&self) -> f32 {
2.0 * self.half_chord_length()
}
#[inline]
pub fn chord_midpoint(&self) -> Vec2 {
self.apothem() * Vec2::Y
}
#[inline]
pub fn apothem(&self) -> f32 {
let sign = if self.is_minor() { 1.0 } else { -1.0 };
sign * ops::sqrt(self.radius.squared() - self.half_chord_length().squared())
}
pub fn sagitta(&self) -> f32 {
self.radius - self.apothem()
}
#[inline]
pub const fn is_minor(&self) -> bool {
self.half_angle <= FRAC_PI_2
}
#[inline]
pub const fn is_major(&self) -> bool {
self.half_angle >= FRAC_PI_2
}
}
#[derive(Clone, Copy, Debug, PartialEq, From)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct CircularSector {
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
pub arc: Arc2d,
}
impl Primitive2d for CircularSector {}
impl Default for CircularSector {
fn default() -> Self {
Self::from(Arc2d::default())
}
}
impl Measured2d for CircularSector {
#[inline]
fn area(&self) -> f32 {
self.arc.radius.squared() * self.arc.half_angle
}
#[inline]
fn perimeter(&self) -> f32 {
if self.half_angle() >= PI {
self.arc.radius * 2.0 * PI
} else {
2.0 * self.radius() + self.arc_length()
}
}
}
impl CircularSector {
#[inline]
pub const fn new(radius: f32, angle: f32) -> Self {
Self {
arc: Arc2d::new(radius, angle),
}
}
#[inline]
pub const fn from_radians(radius: f32, angle: f32) -> Self {
Self {
arc: Arc2d::from_radians(radius, angle),
}
}
#[inline]
pub const fn from_degrees(radius: f32, angle: f32) -> Self {
Self {
arc: Arc2d::from_degrees(radius, angle),
}
}
#[inline]
pub const fn from_turns(radius: f32, fraction: f32) -> Self {
Self {
arc: Arc2d::from_turns(radius, fraction),
}
}
#[inline]
pub const fn half_angle(&self) -> f32 {
self.arc.half_angle
}
#[inline]
pub const fn angle(&self) -> f32 {
self.arc.angle()
}
#[inline]
pub const fn radius(&self) -> f32 {
self.arc.radius
}
#[inline]
pub const fn arc_length(&self) -> f32 {
self.arc.length()
}
#[inline]
pub fn half_chord_length(&self) -> f32 {
self.arc.half_chord_length()
}
#[inline]
pub fn chord_length(&self) -> f32 {
self.arc.chord_length()
}
#[inline]
pub fn chord_midpoint(&self) -> Vec2 {
self.arc.chord_midpoint()
}
#[inline]
pub fn apothem(&self) -> f32 {
self.arc.apothem()
}
#[inline]
pub fn sagitta(&self) -> f32 {
self.arc.sagitta()
}
}
#[derive(Clone, Copy, Debug, PartialEq, From)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct CircularSegment {
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
pub arc: Arc2d,
}
impl Primitive2d for CircularSegment {}
impl Default for CircularSegment {
fn default() -> Self {
Self::from(Arc2d::default())
}
}
impl Measured2d for CircularSegment {
#[inline]
fn area(&self) -> f32 {
0.5 * self.arc.radius.squared() * (self.arc.angle() - ops::sin(self.arc.angle()))
}
#[inline]
fn perimeter(&self) -> f32 {
self.chord_length() + self.arc_length()
}
}
impl CircularSegment {
#[inline]
pub const fn new(radius: f32, half_angle: f32) -> Self {
Self {
arc: Arc2d::new(radius, half_angle),
}
}
#[inline]
pub const fn from_radians(radius: f32, angle: f32) -> Self {
Self {
arc: Arc2d::from_radians(radius, angle),
}
}
#[inline]
pub const fn from_degrees(radius: f32, angle: f32) -> Self {
Self {
arc: Arc2d::from_degrees(radius, angle),
}
}
#[inline]
pub const fn from_turns(radius: f32, fraction: f32) -> Self {
Self {
arc: Arc2d::from_turns(radius, fraction),
}
}
#[inline]
pub const fn half_angle(&self) -> f32 {
self.arc.half_angle
}
#[inline]
pub const fn angle(&self) -> f32 {
self.arc.angle()
}
#[inline]
pub const fn radius(&self) -> f32 {
self.arc.radius
}
#[inline]
pub const fn arc_length(&self) -> f32 {
self.arc.length()
}
#[inline]
#[doc(alias = "half_base_length")]
pub fn half_chord_length(&self) -> f32 {
self.arc.half_chord_length()
}
#[inline]
#[doc(alias = "base_length")]
#[doc(alias = "base")]
pub fn chord_length(&self) -> f32 {
self.arc.chord_length()
}
#[inline]
#[doc(alias = "base_midpoint")]
pub fn chord_midpoint(&self) -> Vec2 {
self.arc.chord_midpoint()
}
#[inline]
pub fn apothem(&self) -> f32 {
self.arc.apothem()
}
#[inline]
#[doc(alias = "height")]
pub fn sagitta(&self) -> f32 {
self.arc.sagitta()
}
}
#[cfg(test)]
mod arc_tests {
use core::f32::consts::FRAC_PI_4;
use core::f32::consts::SQRT_2;
use approx::assert_abs_diff_eq;
use super::*;
struct ArcTestCase {
radius: f32,
half_angle: f32,
angle: f32,
length: f32,
right_endpoint: Vec2,
left_endpoint: Vec2,
endpoints: [Vec2; 2],
midpoint: Vec2,
half_chord_length: f32,
chord_length: f32,
chord_midpoint: Vec2,
apothem: f32,
sagitta: f32,
is_minor: bool,
is_major: bool,
sector_area: f32,
sector_perimeter: f32,
segment_area: f32,
segment_perimeter: f32,
}
impl ArcTestCase {
fn check_arc(&self, arc: Arc2d) {
assert_abs_diff_eq!(self.radius, arc.radius);
assert_abs_diff_eq!(self.half_angle, arc.half_angle);
assert_abs_diff_eq!(self.angle, arc.angle());
assert_abs_diff_eq!(self.length, arc.length());
assert_abs_diff_eq!(self.right_endpoint, arc.right_endpoint());
assert_abs_diff_eq!(self.left_endpoint, arc.left_endpoint());
assert_abs_diff_eq!(self.endpoints[0], arc.endpoints()[0]);
assert_abs_diff_eq!(self.endpoints[1], arc.endpoints()[1]);
assert_abs_diff_eq!(self.midpoint, arc.midpoint());
assert_abs_diff_eq!(self.half_chord_length, arc.half_chord_length());
assert_abs_diff_eq!(self.chord_length, arc.chord_length(), epsilon = 0.00001);
assert_abs_diff_eq!(self.chord_midpoint, arc.chord_midpoint());
assert_abs_diff_eq!(self.apothem, arc.apothem());
assert_abs_diff_eq!(self.sagitta, arc.sagitta());
assert_eq!(self.is_minor, arc.is_minor());
assert_eq!(self.is_major, arc.is_major());
}
fn check_sector(&self, sector: CircularSector) {
assert_abs_diff_eq!(self.radius, sector.radius());
assert_abs_diff_eq!(self.half_angle, sector.half_angle());
assert_abs_diff_eq!(self.angle, sector.angle());
assert_abs_diff_eq!(self.half_chord_length, sector.half_chord_length());
assert_abs_diff_eq!(self.chord_length, sector.chord_length(), epsilon = 0.00001);
assert_abs_diff_eq!(self.chord_midpoint, sector.chord_midpoint());
assert_abs_diff_eq!(self.apothem, sector.apothem());
assert_abs_diff_eq!(self.sagitta, sector.sagitta());
assert_abs_diff_eq!(self.sector_area, sector.area());
assert_abs_diff_eq!(self.sector_perimeter, sector.perimeter());
}
fn check_segment(&self, segment: CircularSegment) {
assert_abs_diff_eq!(self.radius, segment.radius());
assert_abs_diff_eq!(self.half_angle, segment.half_angle());
assert_abs_diff_eq!(self.angle, segment.angle());
assert_abs_diff_eq!(self.half_chord_length, segment.half_chord_length());
assert_abs_diff_eq!(self.chord_length, segment.chord_length(), epsilon = 0.00001);
assert_abs_diff_eq!(self.chord_midpoint, segment.chord_midpoint());
assert_abs_diff_eq!(self.apothem, segment.apothem());
assert_abs_diff_eq!(self.sagitta, segment.sagitta());
assert_abs_diff_eq!(self.segment_area, segment.area());
assert_abs_diff_eq!(self.segment_perimeter, segment.perimeter());
}
}
#[test]
fn zero_angle() {
let tests = ArcTestCase {
radius: 1.0,
half_angle: 0.0,
angle: 0.0,
length: 0.0,
left_endpoint: Vec2::Y,
right_endpoint: Vec2::Y,
endpoints: [Vec2::Y, Vec2::Y],
midpoint: Vec2::Y,
half_chord_length: 0.0,
chord_length: 0.0,
chord_midpoint: Vec2::Y,
apothem: 1.0,
sagitta: 0.0,
is_minor: true,
is_major: false,
sector_area: 0.0,
sector_perimeter: 2.0,
segment_area: 0.0,
segment_perimeter: 0.0,
};
tests.check_arc(Arc2d::new(1.0, 0.0));
tests.check_sector(CircularSector::new(1.0, 0.0));
tests.check_segment(CircularSegment::new(1.0, 0.0));
}
#[test]
fn zero_radius() {
let tests = ArcTestCase {
radius: 0.0,
half_angle: FRAC_PI_4,
angle: FRAC_PI_2,
length: 0.0,
left_endpoint: Vec2::ZERO,
right_endpoint: Vec2::ZERO,
endpoints: [Vec2::ZERO, Vec2::ZERO],
midpoint: Vec2::ZERO,
half_chord_length: 0.0,
chord_length: 0.0,
chord_midpoint: Vec2::ZERO,
apothem: 0.0,
sagitta: 0.0,
is_minor: true,
is_major: false,
sector_area: 0.0,
sector_perimeter: 0.0,
segment_area: 0.0,
segment_perimeter: 0.0,
};
tests.check_arc(Arc2d::new(0.0, FRAC_PI_4));
tests.check_sector(CircularSector::new(0.0, FRAC_PI_4));
tests.check_segment(CircularSegment::new(0.0, FRAC_PI_4));
}
#[test]
fn quarter_circle() {
let sqrt_half: f32 = ops::sqrt(0.5);
let tests = ArcTestCase {
radius: 1.0,
half_angle: FRAC_PI_4,
angle: FRAC_PI_2,
length: FRAC_PI_2,
left_endpoint: Vec2::new(-sqrt_half, sqrt_half),
right_endpoint: Vec2::splat(sqrt_half),
endpoints: [Vec2::new(-sqrt_half, sqrt_half), Vec2::splat(sqrt_half)],
midpoint: Vec2::Y,
half_chord_length: sqrt_half,
chord_length: ops::sqrt(2.0),
chord_midpoint: Vec2::new(0.0, sqrt_half),
apothem: sqrt_half,
sagitta: 1.0 - sqrt_half,
is_minor: true,
is_major: false,
sector_area: FRAC_PI_4,
sector_perimeter: FRAC_PI_2 + 2.0,
segment_area: FRAC_PI_4 - 0.5,
segment_perimeter: FRAC_PI_2 + SQRT_2,
};
tests.check_arc(Arc2d::from_turns(1.0, 0.25));
tests.check_sector(CircularSector::from_turns(1.0, 0.25));
tests.check_segment(CircularSegment::from_turns(1.0, 0.25));
}
#[test]
fn half_circle() {
let tests = ArcTestCase {
radius: 1.0,
half_angle: FRAC_PI_2,
angle: PI,
length: PI,
left_endpoint: Vec2::NEG_X,
right_endpoint: Vec2::X,
endpoints: [Vec2::NEG_X, Vec2::X],
midpoint: Vec2::Y,
half_chord_length: 1.0,
chord_length: 2.0,
chord_midpoint: Vec2::ZERO,
apothem: 0.0,
sagitta: 1.0,
is_minor: true,
is_major: true,
sector_area: FRAC_PI_2,
sector_perimeter: PI + 2.0,
segment_area: FRAC_PI_2,
segment_perimeter: PI + 2.0,
};
tests.check_arc(Arc2d::from_radians(1.0, PI));
tests.check_sector(CircularSector::from_radians(1.0, PI));
tests.check_segment(CircularSegment::from_radians(1.0, PI));
}
#[test]
fn full_circle() {
let tests = ArcTestCase {
radius: 1.0,
half_angle: PI,
angle: 2.0 * PI,
length: 2.0 * PI,
left_endpoint: Vec2::NEG_Y,
right_endpoint: Vec2::NEG_Y,
endpoints: [Vec2::NEG_Y, Vec2::NEG_Y],
midpoint: Vec2::Y,
half_chord_length: 0.0,
chord_length: 0.0,
chord_midpoint: Vec2::NEG_Y,
apothem: -1.0,
sagitta: 2.0,
is_minor: false,
is_major: true,
sector_area: PI,
sector_perimeter: 2.0 * PI,
segment_area: PI,
segment_perimeter: 2.0 * PI,
};
tests.check_arc(Arc2d::from_degrees(1.0, 360.0));
tests.check_sector(CircularSector::from_degrees(1.0, 360.0));
tests.check_segment(CircularSegment::from_degrees(1.0, 360.0));
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Ellipse {
pub half_size: Vec2,
}
impl Primitive2d for Ellipse {}
impl Default for Ellipse {
fn default() -> Self {
Self {
half_size: Vec2::new(1.0, 0.5),
}
}
}
impl Ellipse {
#[inline]
pub const fn new(half_width: f32, half_height: f32) -> Self {
Self {
half_size: Vec2::new(half_width, half_height),
}
}
#[inline]
pub const fn from_size(size: Vec2) -> Self {
Self {
half_size: Vec2::new(size.x / 2.0, size.y / 2.0),
}
}
#[inline]
pub fn eccentricity(&self) -> f32 {
let a = self.semi_major();
let b = self.semi_minor();
ops::sqrt(a * a - b * b) / a
}
#[inline]
pub fn focal_length(&self) -> f32 {
let a = self.semi_major();
let b = self.semi_minor();
ops::sqrt(a * a - b * b)
}
#[inline]
pub fn semi_major(&self) -> f32 {
self.half_size.max_element()
}
#[inline]
pub fn semi_minor(&self) -> f32 {
self.half_size.min_element()
}
}
impl Measured2d for Ellipse {
#[inline]
fn area(&self) -> f32 {
PI * self.half_size.x * self.half_size.y
}
#[inline]
fn perimeter(&self) -> f32 {
let a = self.semi_major();
let b = self.semi_minor();
if a / b - 1. < 1e-5 {
return PI * (a + b);
};
if a / b > 1e4 {
return 4. * a;
};
const BINOMIAL_COEFFICIENTS: [f32; 21] = [
1.,
0.25,
0.015625,
0.00390625,
0.0015258789,
0.00074768066,
0.00042057037,
0.00025963783,
0.00017140154,
0.000119028846,
0.00008599834,
0.00006414339,
0.000049109784,
0.000038430585,
0.000030636627,
0.000024815668,
0.000020380836,
0.000016942893,
0.000014236736,
0.000012077564,
0.000010333865,
];
let h = ((a - b) / (a + b)).squared();
PI * (a + b)
* (0..=20)
.map(|i| BINOMIAL_COEFFICIENTS[i] * ops::powf(h, i as f32))
.sum::<f32>()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "Ring")]
pub struct Annulus {
pub inner_circle: Circle,
pub outer_circle: Circle,
}
impl Primitive2d for Annulus {}
impl Default for Annulus {
fn default() -> Self {
Self {
inner_circle: Circle::new(0.5),
outer_circle: Circle::new(1.0),
}
}
}
impl Annulus {
#[inline]
pub const fn new(inner_radius: f32, outer_radius: f32) -> Self {
Self {
inner_circle: Circle::new(inner_radius),
outer_circle: Circle::new(outer_radius),
}
}
#[inline]
pub const fn diameter(&self) -> f32 {
self.outer_circle.diameter()
}
#[inline]
pub const fn thickness(&self) -> f32 {
self.outer_circle.radius - self.inner_circle.radius
}
#[inline]
pub fn closest_point(&self, point: Vec2) -> Vec2 {
let distance_squared = point.length_squared();
if self.inner_circle.radius.squared() <= distance_squared {
if distance_squared <= self.outer_circle.radius.squared() {
point
} else {
let dir_to_point = point / ops::sqrt(distance_squared);
self.outer_circle.radius * dir_to_point
}
} else {
let dir_to_point = point / ops::sqrt(distance_squared);
self.inner_circle.radius * dir_to_point
}
}
}
impl Measured2d for Annulus {
#[inline]
fn area(&self) -> f32 {
PI * (self.outer_circle.radius.squared() - self.inner_circle.radius.squared())
}
#[inline]
#[doc(alias = "circumference")]
fn perimeter(&self) -> f32 {
2.0 * PI * (self.outer_circle.radius + self.inner_circle.radius)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "Diamond")]
pub struct Rhombus {
pub half_diagonals: Vec2,
}
impl Primitive2d for Rhombus {}
impl Default for Rhombus {
fn default() -> Self {
Self {
half_diagonals: Vec2::splat(0.5),
}
}
}
impl Rhombus {
#[inline]
pub const fn new(horizontal_diagonal: f32, vertical_diagonal: f32) -> Self {
Self {
half_diagonals: Vec2::new(horizontal_diagonal / 2.0, vertical_diagonal / 2.0),
}
}
#[inline]
pub const fn from_side(side: f32) -> Self {
Self {
half_diagonals: Vec2::splat(side * FRAC_1_SQRT_2),
}
}
#[inline]
pub const fn from_inradius(inradius: f32) -> Self {
let half_diagonal = inradius * 2.0 / core::f32::consts::SQRT_2;
Self {
half_diagonals: Vec2::new(half_diagonal, half_diagonal),
}
}
#[inline]
pub fn side(&self) -> f32 {
self.half_diagonals.length()
}
#[inline]
pub const fn circumradius(&self) -> f32 {
self.half_diagonals.x.max(self.half_diagonals.y)
}
#[inline]
#[doc(alias = "apothem")]
pub fn inradius(&self) -> f32 {
let side = self.side();
if side == 0.0 {
0.0
} else {
(self.half_diagonals.x * self.half_diagonals.y) / side
}
}
#[inline]
pub fn closest_point(&self, point: Vec2) -> Vec2 {
let point_abs = point.abs();
let half_diagonals = self.half_diagonals.abs();
let normal = Vec2::new(half_diagonals.y, half_diagonals.x);
let normal_magnitude_squared = normal.length_squared();
if normal_magnitude_squared == 0.0 {
return Vec2::ZERO; }
let distance_unnormalised = normal.dot(point_abs) - half_diagonals.x * half_diagonals.y;
if distance_unnormalised <= 0.0 {
return point;
}
let mut result = point_abs - normal * distance_unnormalised / normal_magnitude_squared;
if result.x <= 0.0 {
result = Vec2::new(0.0, half_diagonals.y);
} else if result.y <= 0.0 {
result = Vec2::new(half_diagonals.x, 0.0);
}
result.copysign(point)
}
}
impl Measured2d for Rhombus {
#[inline]
fn area(&self) -> f32 {
2.0 * self.half_diagonals.x * self.half_diagonals.y
}
#[inline]
fn perimeter(&self) -> f32 {
4.0 * self.side()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Plane2d {
pub normal: Dir2,
}
impl Primitive2d for Plane2d {}
impl Default for Plane2d {
fn default() -> Self {
Self { normal: Dir2::Y }
}
}
impl Plane2d {
#[inline]
pub fn new(normal: Vec2) -> Self {
Self {
normal: Dir2::new(normal).expect("normal must be nonzero and finite"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Line2d {
pub direction: Dir2,
}
impl Primitive2d for Line2d {}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "LineSegment2d")]
pub struct Segment2d {
pub vertices: [Vec2; 2],
}
impl Primitive2d for Segment2d {}
impl Default for Segment2d {
fn default() -> Self {
Self {
vertices: [Vec2::new(-0.5, 0.0), Vec2::new(0.5, 0.0)],
}
}
}
impl Segment2d {
#[inline]
pub const fn new(point1: Vec2, point2: Vec2) -> Self {
Self {
vertices: [point1, point2],
}
}
#[inline]
pub fn from_direction_and_length(direction: Dir2, length: f32) -> Self {
let endpoint = 0.5 * length * direction;
Self {
vertices: [-endpoint, endpoint],
}
}
#[inline]
pub fn from_scaled_direction(scaled_direction: Vec2) -> Self {
let endpoint = 0.5 * scaled_direction;
Self {
vertices: [-endpoint, endpoint],
}
}
#[inline]
pub fn from_ray_and_length(ray: Ray2d, length: f32) -> Self {
Self {
vertices: [ray.origin, ray.get_point(length)],
}
}
#[inline]
pub const fn point1(&self) -> Vec2 {
self.vertices[0]
}
#[inline]
pub const fn point2(&self) -> Vec2 {
self.vertices[1]
}
#[inline]
#[doc(alias = "midpoint")]
pub fn center(&self) -> Vec2 {
self.point1().midpoint(self.point2())
}
#[inline]
pub fn length(&self) -> f32 {
self.point1().distance(self.point2())
}
#[inline]
pub fn length_squared(&self) -> f32 {
self.point1().distance_squared(self.point2())
}
#[inline]
pub fn direction(&self) -> Dir2 {
self.try_direction().unwrap_or_else(|err| {
panic!("Failed to compute the direction of a line segment: {err}")
})
}
#[inline]
pub fn try_direction(&self) -> Result<Dir2, InvalidDirectionError> {
Dir2::new(self.scaled_direction())
}
#[inline]
pub fn scaled_direction(&self) -> Vec2 {
self.point2() - self.point1()
}
#[inline]
pub fn left_normal(&self) -> Dir2 {
self.try_left_normal().unwrap_or_else(|err| {
panic!("Failed to compute the left-hand side normal of a line segment: {err}")
})
}
#[inline]
pub fn try_left_normal(&self) -> Result<Dir2, InvalidDirectionError> {
Dir2::new(self.scaled_left_normal())
}
#[inline]
pub fn scaled_left_normal(&self) -> Vec2 {
let scaled_direction = self.scaled_direction();
Vec2::new(-scaled_direction.y, scaled_direction.x)
}
#[inline]
pub fn right_normal(&self) -> Dir2 {
self.try_right_normal().unwrap_or_else(|err| {
panic!("Failed to compute the right-hand side normal of a line segment: {err}")
})
}
#[inline]
pub fn try_right_normal(&self) -> Result<Dir2, InvalidDirectionError> {
Dir2::new(self.scaled_right_normal())
}
#[inline]
pub fn scaled_right_normal(&self) -> Vec2 {
let scaled_direction = self.scaled_direction();
Vec2::new(scaled_direction.y, -scaled_direction.x)
}
#[inline]
pub fn transformed(&self, isometry: impl Into<Isometry2d>) -> Self {
let isometry: Isometry2d = isometry.into();
Self::new(
isometry.transform_point(self.point1()),
isometry.transform_point(self.point2()),
)
}
#[inline]
pub fn translated(&self, translation: Vec2) -> Segment2d {
Self::new(self.point1() + translation, self.point2() + translation)
}
#[inline]
pub fn rotated(&self, rotation: Rot2) -> Segment2d {
Segment2d::new(rotation * self.point1(), rotation * self.point2())
}
#[inline]
pub fn rotated_around(&self, rotation: Rot2, point: Vec2) -> Segment2d {
let offset = self.translated(-point);
let rotated = offset.rotated(rotation);
rotated.translated(point)
}
#[inline]
pub fn rotated_around_center(&self, rotation: Rot2) -> Segment2d {
self.rotated_around(rotation, self.center())
}
#[inline]
pub fn centered(&self) -> Segment2d {
let center = self.center();
self.translated(-center)
}
#[inline]
pub fn resized(&self, length: f32) -> Segment2d {
let offset_from_origin = self.center();
let centered = self.translated(-offset_from_origin);
let ratio = length / self.length();
let segment = Segment2d::new(centered.point1() * ratio, centered.point2() * ratio);
segment.translated(offset_from_origin)
}
#[inline]
pub fn reverse(&mut self) {
let [point1, point2] = &mut self.vertices;
core::mem::swap(point1, point2);
}
#[inline]
#[must_use]
pub fn reversed(mut self) -> Self {
self.reverse();
self
}
#[inline]
pub fn closest_point(&self, point: Vec2) -> Vec2 {
let segment_vector = self.vertices[1] - self.vertices[0];
let offset = point - self.vertices[0];
let projection_scaled = segment_vector.dot(offset);
if projection_scaled <= 0.0 {
return self.vertices[0];
}
let length_squared = segment_vector.length_squared();
if projection_scaled >= length_squared {
return self.vertices[1];
}
let t = projection_scaled / length_squared;
self.vertices[0] + t * segment_vector
}
}
impl From<[Vec2; 2]> for Segment2d {
#[inline]
fn from(vertices: [Vec2; 2]) -> Self {
Self { vertices }
}
}
impl From<(Vec2, Vec2)> for Segment2d {
#[inline]
fn from((point1, point2): (Vec2, Vec2)) -> Self {
Self::new(point1, point2)
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Polyline2d {
pub vertices: Vec<Vec2>,
}
#[cfg(feature = "alloc")]
impl Primitive2d for Polyline2d {}
#[cfg(feature = "alloc")]
impl FromIterator<Vec2> for Polyline2d {
fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {
Self {
vertices: iter.into_iter().collect(),
}
}
}
#[cfg(feature = "alloc")]
impl Default for Polyline2d {
fn default() -> Self {
Self {
vertices: Vec::from([Vec2::new(-0.5, 0.0), Vec2::new(0.5, 0.0)]),
}
}
}
#[cfg(feature = "alloc")]
impl Polyline2d {
pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
Self::from_iter(vertices)
}
pub fn with_subdivisions(start: Vec2, end: Vec2, subdivisions: usize) -> Self {
let total_vertices = subdivisions + 2;
let mut vertices = Vec::with_capacity(total_vertices);
let step = (end - start) / (subdivisions + 1) as f32;
for i in 0..total_vertices {
vertices.push(start + step * i as f32);
}
Self { vertices }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Triangle2d {
pub vertices: [Vec2; 3],
}
impl Primitive2d for Triangle2d {}
impl Default for Triangle2d {
fn default() -> Self {
Self {
vertices: [Vec2::Y * 0.5, Vec2::new(-0.5, -0.5), Vec2::new(0.5, -0.5)],
}
}
}
impl Triangle2d {
#[inline]
pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {
Self {
vertices: [a, b, c],
}
}
#[inline]
#[doc(alias = "orientation")]
pub fn winding_order(&self) -> WindingOrder {
let [a, b, c] = self.vertices;
let area = (b - a).perp_dot(c - a);
if area > f32::EPSILON {
WindingOrder::CounterClockwise
} else if area < -f32::EPSILON {
WindingOrder::Clockwise
} else {
WindingOrder::Invalid
}
}
pub fn circumcircle(&self) -> (Circle, Vec2) {
let a = self.vertices[0];
let (b, c) = (self.vertices[1] - a, self.vertices[2] - a);
let b_length_sq = b.length_squared();
let c_length_sq = c.length_squared();
let inv_d = (2.0 * (b.x * c.y - b.y * c.x)).recip();
let ux = inv_d * (c.y * b_length_sq - b.y * c_length_sq);
let uy = inv_d * (b.x * c_length_sq - c.x * b_length_sq);
let u = Vec2::new(ux, uy);
let center = u + a;
let radius = u.length();
(Circle { radius }, center)
}
#[inline]
pub fn is_degenerate(&self) -> bool {
let [a, b, c] = self.vertices;
let ab = (b - a).extend(0.);
let ac = (c - a).extend(0.);
ab.cross(ac).length() < 10e-7
}
#[inline]
pub fn is_acute(&self) -> bool {
let [a, b, c] = self.vertices;
let ab = b - a;
let bc = c - b;
let ca = a - c;
let side_lengths = [
ab.length_squared(),
bc.length_squared(),
ca.length_squared(),
];
let sum = side_lengths[0] + side_lengths[1] + side_lengths[2];
let max = side_lengths[0].max(side_lengths[1]).max(side_lengths[2]);
sum - max > max
}
#[inline]
pub fn is_obtuse(&self) -> bool {
let [a, b, c] = self.vertices;
let ab = b - a;
let bc = c - b;
let ca = a - c;
let side_lengths = [
ab.length_squared(),
bc.length_squared(),
ca.length_squared(),
];
let sum = side_lengths[0] + side_lengths[1] + side_lengths[2];
let max = side_lengths[0].max(side_lengths[1]).max(side_lengths[2]);
sum - max < max
}
#[inline]
pub fn reverse(&mut self) {
self.vertices.swap(0, 2);
}
#[inline]
#[must_use]
pub fn reversed(mut self) -> Self {
self.reverse();
self
}
}
impl Measured2d for Triangle2d {
#[inline]
fn area(&self) -> f32 {
let [a, b, c] = self.vertices;
ops::abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2.0
}
#[inline]
fn perimeter(&self) -> f32 {
let [a, b, c] = self.vertices;
let ab = a.distance(b);
let bc = b.distance(c);
let ca = c.distance(a);
ab + bc + ca
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "Quad")]
pub struct Rectangle {
pub half_size: Vec2,
}
impl Primitive2d for Rectangle {}
impl Default for Rectangle {
fn default() -> Self {
Self {
half_size: Vec2::splat(0.5),
}
}
}
impl Rectangle {
#[inline]
pub const fn new(width: f32, height: f32) -> Self {
Self::from_size(Vec2::new(width, height))
}
#[inline]
pub const fn from_size(size: Vec2) -> Self {
Self {
half_size: Vec2::new(size.x / 2.0, size.y / 2.0),
}
}
#[inline]
pub fn from_corners(point1: Vec2, point2: Vec2) -> Self {
Self {
half_size: (point2 - point1).abs() / 2.0,
}
}
#[inline]
pub const fn from_length(length: f32) -> Self {
Self {
half_size: Vec2::splat(length / 2.0),
}
}
#[inline]
pub fn size(&self) -> Vec2 {
2.0 * self.half_size
}
#[inline]
pub fn closest_point(&self, point: Vec2) -> Vec2 {
point.clamp(-self.half_size, self.half_size)
}
}
impl Measured2d for Rectangle {
#[inline]
fn area(&self) -> f32 {
4.0 * self.half_size.x * self.half_size.y
}
#[inline]
fn perimeter(&self) -> f32 {
4.0 * (self.half_size.x + self.half_size.y)
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Polygon {
pub vertices: Vec<Vec2>,
}
#[cfg(feature = "alloc")]
impl Primitive2d for Polygon {}
#[cfg(feature = "alloc")]
impl FromIterator<Vec2> for Polygon {
fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {
Self {
vertices: iter.into_iter().collect(),
}
}
}
#[cfg(feature = "alloc")]
impl Polygon {
pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
Self::from_iter(vertices)
}
#[cfg(feature = "alloc")]
pub fn is_simple(&self) -> bool {
is_polygon_simple(&self.vertices)
}
}
#[cfg(feature = "alloc")]
impl From<ConvexPolygon> for Polygon {
fn from(val: ConvexPolygon) -> Self {
Polygon {
vertices: val.vertices,
}
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct ConvexPolygon {
vertices: Vec<Vec2>,
}
#[cfg(feature = "alloc")]
impl Primitive2d for ConvexPolygon {}
#[cfg(feature = "alloc")]
#[derive(Error, Debug, Clone)]
pub enum ConvexPolygonError {
#[error("The created polygon is not convex")]
Concave,
}
#[cfg(feature = "alloc")]
impl ConvexPolygon {
fn triangle_winding_order(
&self,
a_index: usize,
b_index: usize,
c_index: usize,
) -> WindingOrder {
let a = self.vertices[a_index];
let b = self.vertices[b_index];
let c = self.vertices[c_index];
Triangle2d::new(a, b, c).winding_order()
}
pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Result<Self, ConvexPolygonError> {
let polygon = Self::new_unchecked(vertices);
let len = polygon.vertices.len();
let ref_winding_order = polygon.triangle_winding_order(len - 1, 0, 1);
for i in 1..len {
let winding_order = polygon.triangle_winding_order(i - 1, i, (i + 1) % len);
if winding_order != ref_winding_order {
return Err(ConvexPolygonError::Concave);
}
}
Ok(polygon)
}
#[inline]
pub fn new_unchecked(vertices: impl IntoIterator<Item = Vec2>) -> Self {
Self {
vertices: vertices.into_iter().collect(),
}
}
#[inline]
pub fn vertices(&self) -> &[Vec2] {
&self.vertices
}
}
#[cfg(feature = "alloc")]
impl TryFrom<Polygon> for ConvexPolygon {
type Error = ConvexPolygonError;
fn try_from(val: Polygon) -> Result<Self, Self::Error> {
ConvexPolygon::new(val.vertices)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct RegularPolygon {
pub circumcircle: Circle,
pub sides: u32,
}
impl Primitive2d for RegularPolygon {}
impl Default for RegularPolygon {
fn default() -> Self {
Self {
circumcircle: Circle { radius: 0.5 },
sides: 6,
}
}
}
impl RegularPolygon {
#[inline]
pub const fn new(circumradius: f32, sides: u32) -> Self {
assert!(
circumradius.is_sign_positive(),
"polygon has a negative radius"
);
assert!(sides > 2, "polygon has less than 3 sides");
Self {
circumcircle: Circle {
radius: circumradius,
},
sides,
}
}
#[inline]
pub const fn circumradius(&self) -> f32 {
self.circumcircle.radius
}
#[inline]
#[doc(alias = "apothem")]
pub fn inradius(&self) -> f32 {
self.circumradius() * ops::cos(PI / self.sides as f32)
}
#[inline]
pub fn side_length(&self) -> f32 {
2.0 * self.circumradius() * ops::sin(PI / self.sides as f32)
}
#[inline]
pub const fn internal_angle_degrees(&self) -> f32 {
(self.sides - 2) as f32 / self.sides as f32 * 180.0
}
#[inline]
pub const fn internal_angle_radians(&self) -> f32 {
(self.sides - 2) as f32 * PI / self.sides as f32
}
#[inline]
pub const fn external_angle_degrees(&self) -> f32 {
360.0 / self.sides as f32
}
#[inline]
pub const fn external_angle_radians(&self) -> f32 {
2.0 * PI / self.sides as f32
}
pub fn vertices(self, rotation: f32) -> impl IntoIterator<Item = Vec2> {
let start_angle = rotation + FRAC_PI_2;
let step = core::f32::consts::TAU / self.sides as f32;
(0..self.sides).map(move |i| {
let theta = start_angle + i as f32 * step;
let (sin, cos) = ops::sin_cos(theta);
Vec2::new(cos, sin) * self.circumcircle.radius
})
}
}
impl Measured2d for RegularPolygon {
#[inline]
fn area(&self) -> f32 {
let angle: f32 = 2.0 * PI / (self.sides as f32);
(self.sides as f32) * self.circumradius().squared() * ops::sin(angle) / 2.0
}
#[inline]
fn perimeter(&self) -> f32 {
self.sides as f32 * self.side_length()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "stadium", alias = "pill")]
pub struct Capsule2d {
pub radius: f32,
pub half_length: f32,
}
impl Primitive2d for Capsule2d {}
impl Default for Capsule2d {
fn default() -> Self {
Self {
radius: 0.5,
half_length: 0.5,
}
}
}
impl Capsule2d {
pub const fn new(radius: f32, length: f32) -> Self {
Self {
radius,
half_length: length / 2.0,
}
}
#[inline]
pub const fn to_inner_rectangle(&self) -> Rectangle {
Rectangle::new(self.radius * 2.0, self.half_length * 2.0)
}
}
impl Measured2d for Capsule2d {
#[inline]
fn area(&self) -> f32 {
PI * self.radius.squared() + self.to_inner_rectangle().area()
}
#[inline]
fn perimeter(&self) -> f32 {
2.0 * PI * self.radius + 4.0 * self.half_length
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct Ring<P: Primitive2d> {
pub outer_shape: P,
pub inner_shape: P,
}
impl<P: Primitive2d> Ring<P> {
pub const fn new(outer_shape: P, inner_shape: P) -> Self {
Self {
outer_shape,
inner_shape,
}
}
}
impl<T: Primitive2d> Primitive2d for Ring<T> {}
impl<P: Primitive2d + Clone + Inset> Ring<P> {
pub fn from_primitive_and_thickness(primitive: P, thickness: f32) -> Self {
let hollow = primitive.clone().inset(thickness);
Ring::new(primitive, hollow)
}
}
impl<P: Primitive2d + Measured2d> Measured2d for Ring<P> {
#[inline]
fn area(&self) -> f32 {
self.outer_shape.area() - self.inner_shape.area()
}
#[inline]
fn perimeter(&self) -> f32 {
self.outer_shape.perimeter() + self.inner_shape.perimeter()
}
}
pub trait ToRing: Primitive2d + Inset
where
Self: Sized,
{
fn to_ring(self, thickness: f32) -> Ring<Self>;
}
impl<P> ToRing for P
where
P: Primitive2d + Clone + Inset,
{
fn to_ring(self, thickness: f32) -> Ring<Self> {
Ring::from_primitive_and_thickness(self, thickness)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::{assert_abs_diff_eq, assert_relative_eq};
#[test]
fn rectangle_closest_point() {
let rectangle = Rectangle::new(2.0, 2.0);
assert_eq!(rectangle.closest_point(Vec2::X * 10.0), Vec2::X);
assert_eq!(rectangle.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);
assert_eq!(
rectangle.closest_point(Vec2::new(0.25, 0.1)),
Vec2::new(0.25, 0.1)
);
}
#[test]
fn circle_closest_point() {
let circle = Circle { radius: 1.0 };
assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);
assert_eq!(
circle.closest_point(Vec2::NEG_ONE * 10.0),
Vec2::NEG_ONE.normalize()
);
assert_eq!(
circle.closest_point(Vec2::new(0.25, 0.1)),
Vec2::new(0.25, 0.1)
);
}
#[test]
fn annulus_closest_point() {
let annulus = Annulus::new(1.5, 2.0);
assert_eq!(annulus.closest_point(Vec2::X * 10.0), Vec2::X * 2.0);
assert_eq!(
annulus.closest_point(Vec2::NEG_ONE),
Vec2::NEG_ONE.normalize() * 1.5
);
assert_eq!(
annulus.closest_point(Vec2::new(1.55, 0.85)),
Vec2::new(1.55, 0.85)
);
}
#[test]
fn rhombus_closest_point() {
let rhombus = Rhombus::new(2.0, 1.0);
assert_eq!(rhombus.closest_point(Vec2::X * 10.0), Vec2::X);
assert_eq!(
rhombus.closest_point(Vec2::NEG_ONE * 0.2),
Vec2::NEG_ONE * 0.2
);
assert_eq!(
rhombus.closest_point(Vec2::new(-0.55, 0.35)),
Vec2::new(-0.5, 0.25)
);
let rhombus = Rhombus::new(0.0, 0.0);
assert_eq!(rhombus.closest_point(Vec2::X * 10.0), Vec2::ZERO);
assert_eq!(rhombus.closest_point(Vec2::NEG_ONE * 0.2), Vec2::ZERO);
assert_eq!(rhombus.closest_point(Vec2::new(-0.55, 0.35)), Vec2::ZERO);
}
#[test]
fn segment_closest_point() {
assert_eq!(
Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(3.0, 0.0))
.closest_point(Vec2::new(1.0, 6.0)),
Vec2::new(1.0, 0.0)
);
let segments = [
Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(0.0, 0.0)),
Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0)),
Segment2d::new(Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0)),
Segment2d::new(Vec2::new(1.0, 0.0), Vec2::new(1.0, 5.0 * f32::EPSILON)),
];
let points = [
Vec2::new(0.0, 0.0),
Vec2::new(1.0, 0.0),
Vec2::new(-1.0, 1.0),
Vec2::new(1.0, 1.0),
Vec2::new(-1.0, 0.0),
Vec2::new(5.0, -1.0),
Vec2::new(1.0, f32::EPSILON),
];
for point in points.iter() {
for segment in segments.iter() {
let closest = segment.closest_point(*point);
assert!(
point.distance_squared(closest) <= point.distance_squared(segment.point1()),
"Closest point must always be at least as close as either vertex."
);
assert!(
point.distance_squared(closest) <= point.distance_squared(segment.point2()),
"Closest point must always be at least as close as either vertex."
);
assert!(
point.distance_squared(closest) <= point.distance_squared(segment.center()),
"Closest point must always be at least as close as the center."
);
let closest_to_closest = segment.closest_point(closest);
assert_relative_eq!(closest_to_closest, closest);
}
}
}
#[test]
fn circle_math() {
let circle = Circle { radius: 3.0 };
assert_eq!(circle.diameter(), 6.0, "incorrect diameter");
assert_eq!(circle.area(), 28.274334, "incorrect area");
assert_eq!(circle.perimeter(), 18.849556, "incorrect perimeter");
}
#[test]
fn capsule_math() {
let capsule = Capsule2d::new(2.0, 9.0);
assert_eq!(
capsule.to_inner_rectangle(),
Rectangle::new(4.0, 9.0),
"rectangle wasn't created correctly from a capsule"
);
assert_eq!(capsule.area(), 48.566371, "incorrect area");
assert_eq!(capsule.perimeter(), 30.566371, "incorrect perimeter");
}
#[test]
fn annulus_math() {
let annulus = Annulus::new(2.5, 3.5);
assert_eq!(annulus.diameter(), 7.0, "incorrect diameter");
assert_eq!(annulus.thickness(), 1.0, "incorrect thickness");
assert_eq!(annulus.area(), 18.849556, "incorrect area");
assert_eq!(annulus.perimeter(), 37.699112, "incorrect perimeter");
}
#[test]
fn rhombus_math() {
let rhombus = Rhombus::new(3.0, 4.0);
assert_eq!(rhombus.area(), 6.0, "incorrect area");
assert_eq!(rhombus.perimeter(), 10.0, "incorrect perimeter");
assert_eq!(rhombus.side(), 2.5, "incorrect side");
assert_eq!(rhombus.inradius(), 1.2, "incorrect inradius");
assert_eq!(rhombus.circumradius(), 2.0, "incorrect circumradius");
let rhombus = Rhombus::new(0.0, 0.0);
assert_eq!(rhombus.area(), 0.0, "incorrect area");
assert_eq!(rhombus.perimeter(), 0.0, "incorrect perimeter");
assert_eq!(rhombus.side(), 0.0, "incorrect side");
assert_eq!(rhombus.inradius(), 0.0, "incorrect inradius");
assert_eq!(rhombus.circumradius(), 0.0, "incorrect circumradius");
let rhombus = Rhombus::from_side(core::f32::consts::SQRT_2);
assert_abs_diff_eq!(rhombus.half_diagonals, Vec2::new(1.0, 1.0));
assert_abs_diff_eq!(
rhombus.half_diagonals,
Rhombus::from_inradius(FRAC_1_SQRT_2).half_diagonals
);
}
#[test]
fn ellipse_math() {
let ellipse = Ellipse::new(3.0, 1.0);
assert_eq!(ellipse.area(), 9.424778, "incorrect area");
assert_eq!(ellipse.eccentricity(), 0.94280905, "incorrect eccentricity");
let line = Ellipse::new(1., 0.);
assert_eq!(line.eccentricity(), 1., "incorrect line eccentricity");
let circle = Ellipse::new(2., 2.);
assert_eq!(circle.eccentricity(), 0., "incorrect circle eccentricity");
}
#[test]
fn ellipse_perimeter() {
let circle = Ellipse::new(1., 1.);
assert_relative_eq!(circle.perimeter(), 6.2831855);
let line = Ellipse::new(75_000., 0.5);
assert_relative_eq!(line.perimeter(), 300_000.);
let ellipse = Ellipse::new(0.5, 2.);
assert_relative_eq!(ellipse.perimeter(), 8.578423);
let ellipse = Ellipse::new(5., 3.);
assert_relative_eq!(ellipse.perimeter(), 25.526999);
}
#[test]
fn triangle_math() {
let triangle = Triangle2d::new(
Vec2::new(-2.0, -1.0),
Vec2::new(1.0, 4.0),
Vec2::new(7.0, 0.0),
);
assert_eq!(triangle.area(), 21.0, "incorrect area");
assert_eq!(triangle.perimeter(), 22.097439, "incorrect perimeter");
let degenerate_triangle =
Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(0., 0.), Vec2::new(1., 0.));
assert!(degenerate_triangle.is_degenerate());
let acute_triangle =
Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 5.));
let obtuse_triangle =
Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 0.5));
assert!(acute_triangle.is_acute());
assert!(!acute_triangle.is_obtuse());
assert!(!obtuse_triangle.is_acute());
assert!(obtuse_triangle.is_obtuse());
}
#[test]
fn triangle_winding_order() {
let mut cw_triangle = Triangle2d::new(
Vec2::new(0.0, 2.0),
Vec2::new(-0.5, -1.2),
Vec2::new(-1.0, -1.0),
);
assert_eq!(cw_triangle.winding_order(), WindingOrder::Clockwise);
let ccw_triangle = Triangle2d::new(
Vec2::new(-1.0, -1.0),
Vec2::new(-0.5, -1.2),
Vec2::new(0.0, 2.0),
);
assert_eq!(ccw_triangle.winding_order(), WindingOrder::CounterClockwise);
cw_triangle.reverse();
assert_eq!(cw_triangle, ccw_triangle);
let invalid_triangle = Triangle2d::new(
Vec2::new(0.0, 2.0),
Vec2::new(0.0, -1.0),
Vec2::new(0.0, -1.2),
);
assert_eq!(invalid_triangle.winding_order(), WindingOrder::Invalid);
}
#[test]
fn rectangle_math() {
let rectangle = Rectangle::new(3.0, 7.0);
assert_eq!(
rectangle,
Rectangle::from_corners(Vec2::new(-1.5, -3.5), Vec2::new(1.5, 3.5))
);
assert_eq!(rectangle.area(), 21.0, "incorrect area");
assert_eq!(rectangle.perimeter(), 20.0, "incorrect perimeter");
}
#[test]
fn regular_polygon_math() {
let polygon = RegularPolygon::new(3.0, 6);
assert_eq!(polygon.inradius(), 2.598076, "incorrect inradius");
assert_eq!(polygon.side_length(), 3.0, "incorrect side length");
assert_relative_eq!(polygon.area(), 23.38268, epsilon = 0.00001);
assert_eq!(polygon.perimeter(), 18.0, "incorrect perimeter");
assert_eq!(
polygon.internal_angle_degrees(),
120.0,
"incorrect internal angle"
);
assert_eq!(
polygon.internal_angle_radians(),
120_f32.to_radians(),
"incorrect internal angle"
);
assert_eq!(
polygon.external_angle_degrees(),
60.0,
"incorrect external angle"
);
assert_eq!(
polygon.external_angle_radians(),
60_f32.to_radians(),
"incorrect external angle"
);
}
#[test]
fn triangle_circumcenter() {
let triangle = Triangle2d::new(
Vec2::new(10.0, 2.0),
Vec2::new(-5.0, -3.0),
Vec2::new(2.0, -1.0),
);
let (Circle { radius }, circumcenter) = triangle.circumcircle();
assert_eq!(radius, 98.34887);
assert_eq!(circumcenter, Vec2::new(-28.5, 92.5));
}
#[test]
fn regular_polygon_vertices() {
let polygon = RegularPolygon::new(1.0, 4);
let mut vertices = polygon.vertices(0.0).into_iter();
assert!((vertices.next().unwrap() - Vec2::Y).length() < 1e-7);
let mut rotated_vertices = polygon.vertices(core::f32::consts::FRAC_PI_4).into_iter();
let side_distance = FRAC_1_SQRT_2;
assert!(
(rotated_vertices.next().unwrap() - Vec2::new(-side_distance, side_distance)).length()
< 1e-7,
);
}
}