#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(not(test), no_std)]
use core::f32;
#[cfg(feature = "approx")]
mod approx;
mod math;
mod ops;
#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
#[repr(transparent)]
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::Pod))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Angle {
radians: f32,
}
impl Angle {
#[must_use]
#[inline]
pub const fn from_radians(radians: f32) -> Self {
Self { radians }
}
#[must_use]
#[inline]
pub const fn from_degrees(degrees: f32) -> Self {
Self {
radians: degrees * (f32::consts::PI / 180.0),
}
}
#[must_use]
#[inline]
pub const fn from_turns(turns: f32) -> Self {
Self {
radians: turns * (2.0 * f32::consts::PI),
}
}
#[must_use]
#[inline]
pub const fn from_percentage(percentage: f32) -> Self {
Self {
radians: percentage * (2.0 * f32::consts::PI),
}
}
#[must_use]
#[inline]
#[cfg(any(feature = "std", feature = "libm"))]
pub fn from_acos(value: f32) -> Self {
Self {
radians: math::acos_f32(value),
}
}
#[must_use]
#[inline]
#[cfg(any(feature = "std", feature = "libm"))]
pub fn from_asin(value: f32) -> Self {
Self {
radians: math::asin_f32(value),
}
}
#[must_use]
#[inline]
#[cfg(any(feature = "std", feature = "libm"))]
pub fn from_atan(value: f32) -> Self {
Self {
radians: math::atan_f32(value),
}
}
#[must_use]
#[inline]
#[cfg(any(feature = "std", feature = "libm"))]
pub fn from_atan2(y: f32, x: f32) -> Self {
Self {
radians: math::atan2_f32(y, x),
}
}
#[must_use]
#[inline]
#[cfg(any(feature = "std", feature = "libm"))]
pub fn from_cartesian(x: f32, y: f32) -> Self {
if x == 0.0 && y == 0.0 {
return Self::from_radians(0.0);
}
let radians = math::atan2_f32(y, x);
let radians = if radians < 0.0 {
radians + 2.0 * f32::consts::PI
} else {
radians
};
Self { radians }
}
#[must_use]
pub const fn as_radians(self) -> f32 {
self.radians
}
#[must_use]
#[inline]
pub const fn as_degrees(self) -> f32 {
self.radians * (180.0 / f32::consts::PI)
}
#[must_use]
#[inline]
pub const fn as_turns(self) -> f32 {
self.radians / (2.0 * f32::consts::PI)
}
#[must_use]
#[inline]
pub const fn as_percentage(self) -> f32 {
self.radians / (2.0 * f32::consts::PI)
}
#[must_use]
#[inline]
pub const fn normalize(self) -> Self {
let radians = math::rem_euclid_f32_const(self.radians, 2.0 * f32::consts::PI);
Self { radians }
}
#[must_use]
#[inline]
pub const fn abs(self) -> Self {
Self {
radians: self.radians.abs(),
}
}
#[must_use]
#[inline]
#[cfg(any(feature = "std", feature = "libm"))]
pub fn cos(self) -> f32 {
math::cos_f32(self.radians)
}
#[must_use]
#[inline]
#[cfg(any(feature = "std", feature = "libm"))]
pub fn sin(self) -> f32 {
math::sin_f32(self.radians)
}
#[must_use]
#[inline]
#[cfg(any(feature = "std", feature = "libm"))]
pub fn tan(self) -> f32 {
math::tan_f32(self.radians)
}
#[must_use]
#[inline]
#[cfg(any(feature = "std", feature = "libm"))]
pub fn cot(self) -> f32 {
if self.radians == 0.0 {
f32::INFINITY
} else {
1.0 / self.tan()
}
}
#[must_use]
#[inline]
#[cfg(any(feature = "std", feature = "libm"))]
pub fn sec(self) -> f32 {
if self.radians == f32::consts::PI / 2.0 || self.radians == 3.0 * f32::consts::PI / 2.0 {
f32::INFINITY
} else {
1.0 / self.cos()
}
}
#[must_use]
#[inline]
#[cfg(any(feature = "std", feature = "libm"))]
pub fn csc(self) -> f32 {
if self.radians == 0.0 || self.radians == f32::consts::PI {
f32::INFINITY
} else {
1.0 / self.sin()
}
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn from_radians() {
let angle = Angle::from_radians(1.0);
assert_eq!(angle.as_radians(), 1.0);
}
#[test]
fn from_degrees() {
let angle = Angle::from_degrees(180.0);
assert_eq!(angle.as_radians(), f32::consts::PI);
}
#[test]
fn from_turns() {
let angle = Angle::from_turns(0.5);
assert_eq!(angle.as_radians(), f32::consts::PI);
}
#[test]
fn from_percentage() {
let angle = Angle::from_percentage(0.25);
assert_eq!(angle.as_radians(), f32::consts::PI / 2.);
}
#[test]
fn normalize() {
let angle = Angle::from_radians(-1.0);
let normalized = angle.normalize();
assert!((normalized.as_radians() - (2.0 * f32::consts::PI - 1.0)).abs() < f32::EPSILON);
}
#[test]
fn as_degrees() {
let angle = Angle::from_radians(f32::consts::PI);
assert_eq!(angle.as_degrees(), 180.0);
}
#[test]
fn as_turns() {
let angle = Angle::from_radians(f32::consts::PI);
assert_eq!(angle.as_turns(), 0.5);
}
#[test]
fn as_percentage() {
let angle = Angle::from_radians(f32::consts::PI);
assert_eq!(angle.as_percentage(), 0.5);
}
#[test]
fn abs() {
let angle = Angle::from_radians(-1.0);
assert_eq!(angle.abs().as_radians(), 1.0);
}
#[test]
fn from_acos() {
let angle = Angle::from_acos(0.0);
assert!((angle.as_radians() - f32::consts::PI / 2.0).abs() < f32::EPSILON);
}
#[test]
fn from_asin() {
let angle = Angle::from_asin(0.0);
assert!((angle.as_radians() - 0.0).abs() < f32::EPSILON);
}
#[test]
fn from_atan() {
let angle = Angle::from_atan(1.0);
assert!((angle.as_radians() - f32::consts::PI / 4.0).abs() < f32::EPSILON);
}
#[test]
fn from_atan2() {
let angle = Angle::from_atan2(1.0, 1.0);
assert!((angle.as_radians() - f32::consts::PI / 4.0).abs() < f32::EPSILON);
}
#[test]
fn from_cartesian() {
let angle = Angle::from_cartesian(1.0, 1.0);
assert!((angle.as_radians() - f32::consts::PI / 4.0).abs() < f32::EPSILON);
let angle_zero = Angle::from_cartesian(0.0, 0.0);
assert_eq!(angle_zero.as_radians(), 0.0);
let angle_negative = Angle::from_cartesian(-1.0, -1.0);
assert!((angle_negative.as_radians() - (5.0 * f32::consts::PI / 4.0)).abs() < f32::EPSILON);
}
#[test]
fn cos() {
let angle = Angle::from_radians(f32::consts::PI / 3.0);
assert!((angle.cos() - 0.5).abs() < f32::EPSILON);
}
#[test]
fn sin() {
let angle = Angle::from_radians(f32::consts::PI / 6.0);
assert!((angle.sin() - 0.5).abs() < f32::EPSILON);
}
#[test]
fn tan() {
let angle = Angle::from_radians(f32::consts::PI / 4.0);
assert!((angle.tan() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn cot() {
let angle = Angle::from_radians(f32::consts::PI / 4.0);
assert!((angle.cot() - 1.0).abs() < f32::EPSILON);
let angle_zero = Angle::from_radians(0.0);
assert!(angle_zero.cot().is_infinite());
}
#[test]
fn sec() {
let angle = Angle::from_radians(f32::consts::PI / 3.0);
let expected = 2.0;
let epsilon = 1e-6;
assert!((angle.sec() - expected).abs() < epsilon);
}
#[test]
fn csc() {
let angle = Angle::from_radians(f32::consts::PI / 6.0);
assert!((angle.csc() - 2.0).abs() < f32::EPSILON);
let angle_zero = Angle::from_radians(0.0);
assert!(angle_zero.csc().is_infinite());
}
}