use std;
use std::f64::consts::PI;
use crate::consts::*;
use crate::s1::angle::*;
#[cfg(feature = "float_extras")]
use float_extras::f64::nextafter;
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ChordAngle(pub f64);
pub const NEGATIVE: ChordAngle = ChordAngle(-1f64);
pub const RIGHT: ChordAngle = ChordAngle(2f64);
pub const STRAIGHT: ChordAngle = ChordAngle(4f64);
pub const MAXLENGTH2: f64 = 4.0;
impl<'a> From<&'a Angle> for ChordAngle {
fn from(a: &'a Angle) -> Self {
if a.rad() < 0. {
NEGATIVE
} else if a.is_infinite() {
ChordAngle::inf()
} else {
let l = 2. * (0.5 * a.rad().min(PI)).sin();
ChordAngle(l * l)
}
}
}
impl From<Angle> for ChordAngle {
fn from(a: Angle) -> Self {
ChordAngle::from(&a)
}
}
impl<'a> From<&'a Deg> for ChordAngle {
fn from(a: &'a Deg) -> Self {
Angle::from(a).into()
}
}
impl From<Deg> for ChordAngle {
fn from(a: Deg) -> Self {
Angle::from(&a).into()
}
}
impl<'a> From<&'a ChordAngle> for Angle {
fn from(ca: &'a ChordAngle) -> Self {
if ca.0 < 0. {
Rad(-1.).into()
} else if ca.is_infinite() {
Angle::inf()
} else {
Rad(2. * (0.5 * ca.0.sqrt()).asin()).into()
}
}
}
impl From<ChordAngle> for Angle {
fn from(ca: ChordAngle) -> Self {
Angle::from(&ca)
}
}
impl<'a, 'b> std::ops::Add<&'a ChordAngle> for &'b ChordAngle {
type Output = ChordAngle;
fn add(self, other: &'a ChordAngle) -> Self::Output {
if other.0 == 0.0 {
*self
} else if self.0 + other.0 >= 4. {
STRAIGHT
} else {
let x = self.0 * (1. - 0.25 * other.0);
let y = other.0 * (1. - 0.25 * self.0);
ChordAngle(4f64.min(x + y + 2f64 * (x * y).sqrt()))
}
}
}
impl std::ops::Add<ChordAngle> for ChordAngle {
type Output = ChordAngle;
fn add(self, other: ChordAngle) -> Self::Output {
&self + &other
}
}
impl std::ops::Sub<ChordAngle> for ChordAngle {
type Output = ChordAngle;
fn sub(self, other: ChordAngle) -> Self::Output {
if other.0 == 0.0 {
self
} else if self.0 <= other.0 {
ChordAngle(0f64)
} else {
let x = self.0 * (1. - 0.25 * other.0);
let y = other.0 * (1. - 0.25 * self.0);
ChordAngle(0f64.max(x + y - 2. * (x * y).sqrt()))
}
}
}
impl ChordAngle {
pub fn inf() -> Self {
ChordAngle(std::f64::INFINITY)
}
pub fn is_infinite(&self) -> bool {
self.0.is_infinite()
}
pub fn from_squared_length(length2: f64) -> Self {
if length2 > 4. {
STRAIGHT
} else {
ChordAngle(length2)
}
}
pub fn expanded(&self, e: f64) -> Self {
if self.is_special() {
*self
} else {
ChordAngle(0f64.max(4f64.min(self.0 + e)))
}
}
pub fn is_special(&self) -> bool {
self.0 < 0. || self.0.is_infinite()
}
pub fn is_valid(&self) -> bool {
self.0 >= 0. && self.0 <= 4. || self.is_special()
}
pub fn max(self, other: Self) -> Self {
if self.0 < other.0 {
return other;
} else {
return self;
}
}
pub fn max_point_error(&self) -> f64 {
2.5 * DBL_EPSILON * self.0 + 16. * DBL_EPSILON * DBL_EPSILON
}
pub fn max_angle_error(&self) -> f64 {
DBL_EPSILON * self.0
}
pub fn sin(&self) -> f64 {
self.sin2().sqrt()
}
pub fn sin2(&self) -> f64 {
self.0 * (1. - 0.25 * self.0)
}
pub fn cos(&self) -> f64 {
1.0 - 0.5 * self.0
}
pub fn tan(&self) -> f64 {
self.sin() / self.cos()
}
#[cfg(feature = "float_extras")]
pub fn successor(&self) -> Self {
if self.0 >= MAXLENGTH2 {
return ChordAngle::inf();
} else if self.0 < 0. {
return ChordAngle(0.);
} else {
return ChordAngle(nextafter(self.0, 10.));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_chordangle_basics_case(ca1: ChordAngle, ca2: ChordAngle, less_than: bool, equal: bool) {
assert_eq!(less_than, ca1 < ca2);
assert_eq!(equal, ca1 == ca2);
}
#[test]
fn test_chordangle_basics() {
let zero = ChordAngle::default();
test_chordangle_basics_case(NEGATIVE, NEGATIVE, false, true);
test_chordangle_basics_case(NEGATIVE, zero, true, false);
test_chordangle_basics_case(NEGATIVE, STRAIGHT, true, false);
test_chordangle_basics_case(NEGATIVE, ChordAngle::inf(), true, false);
test_chordangle_basics_case(zero, zero, false, true);
test_chordangle_basics_case(zero, STRAIGHT, true, false);
test_chordangle_basics_case(zero, ChordAngle::inf(), true, false);
test_chordangle_basics_case(STRAIGHT, STRAIGHT, false, true);
test_chordangle_basics_case(STRAIGHT, ChordAngle::inf(), true, false);
test_chordangle_basics_case(ChordAngle::inf(), ChordAngle::inf(), false, true);
test_chordangle_basics_case(ChordAngle::inf(), ChordAngle::inf(), false, true);
}
fn test_chordangle_is_functions_case(
ca: ChordAngle,
is_neg: bool,
is_zero: bool,
is_inf: bool,
is_special: bool,
) {
assert_eq!(is_neg, ca.0 < 0.);
assert_eq!(is_zero, ca.0 == 0.);
assert_eq!(is_inf, ca.is_infinite());
assert_eq!(is_special, ca.is_special());
}
#[test]
fn test_chordangle_is_functions() {
let zero: ChordAngle = Default::default();
test_chordangle_is_functions_case(zero, false, true, false, false);
test_chordangle_is_functions_case(NEGATIVE, true, false, false, true);
test_chordangle_is_functions_case(zero, false, true, false, false);
test_chordangle_is_functions_case(STRAIGHT, false, false, false, false);
test_chordangle_is_functions_case(ChordAngle::inf(), false, false, true, true);
}
#[test]
fn test_chordangle_from_angle() {
let angles = vec![
Angle::from(Rad(0.)),
Angle::from(Rad(1.)),
Angle::from(Rad(-1.)),
Angle::from(Rad(PI)),
];
for angle in angles.into_iter() {
let ca = ChordAngle::from(angle);
let got = Angle::from(ca);
assert_eq!(got, angle);
}
assert_eq!(STRAIGHT, ChordAngle::from(Angle::from(Rad(PI))));
assert_eq!(Angle::inf(), Angle::from(ChordAngle::from(Angle::inf())));
}
fn chordangle_eq(a: ChordAngle, b: ChordAngle) {
assert_f64_eq!(a.0, b.0);
}
#[test]
fn test_chordangle_arithmetic() {
let zero = ChordAngle::default();
let deg_30 = ChordAngle::from(Deg(30.));
let deg_60 = ChordAngle::from(Deg(60.));
let deg_90 = ChordAngle::from(Deg(90.));
let deg_120 = ChordAngle::from(Deg(120.));
let deg_180 = STRAIGHT;
chordangle_eq(zero + zero, zero);
chordangle_eq(deg_60 + zero, deg_60);
chordangle_eq(zero + deg_60, deg_60);
chordangle_eq(deg_30 + deg_60, deg_90);
chordangle_eq(deg_60 + deg_30, deg_90);
chordangle_eq(deg_180 + zero, deg_180);
chordangle_eq(deg_60 + deg_30, deg_90);
chordangle_eq(deg_90 + deg_90, deg_180);
chordangle_eq(deg_120 + deg_90, deg_180);
chordangle_eq(deg_120 + deg_120, deg_180);
chordangle_eq(deg_30 + deg_180, deg_180);
chordangle_eq(deg_180 + deg_180, deg_180);
chordangle_eq(zero - zero, zero);
chordangle_eq(deg_60 - deg_60, zero);
chordangle_eq(deg_180 - deg_180, zero);
chordangle_eq(zero - deg_60, zero);
chordangle_eq(deg_30 - deg_90, zero);
chordangle_eq(deg_90 - deg_30, deg_60);
chordangle_eq(deg_90 - deg_60, deg_30);
chordangle_eq(deg_180 - zero, deg_180);
}
#[test]
fn test_chordangle_trigonometry() {
let iters = 40usize;
for i in 0..(iters + 1) {
let radians = PI * (i as f64) / (iters as f64);
let chordangle = ChordAngle::from(Angle::from(Rad(radians)));
assert_f64_eq!(radians.sin(), chordangle.sin());
assert_f64_eq!(radians.cos(), chordangle.cos());
assert_f64_eq!(radians.tan().atan(), chordangle.tan().atan());
}
let angle_90 = ChordAngle::from_squared_length(2.);
let angle_180 = ChordAngle::from_squared_length(4.);
assert_f64_eq!(1., angle_90.sin());
assert_f64_eq!(0., angle_90.cos());
assert!(angle_90.tan().is_infinite());
assert_f64_eq!(0., angle_180.sin());
assert_f64_eq!(-1., angle_180.cos());
assert_f64_eq!(0., angle_180.tan());
}
#[test]
fn test_chordangle_expanded() {
let zero = ChordAngle::default();
assert_eq!(NEGATIVE.expanded(5.), NEGATIVE.expanded(5.));
assert_eq!(ChordAngle::inf().expanded(-5.), ChordAngle::inf());
assert_eq!(zero.expanded(-5.), zero);
assert_eq!(
ChordAngle::from_squared_length(1.25).expanded(0.25),
ChordAngle::from_squared_length(1.5)
);
assert_eq!(
ChordAngle::from_squared_length(0.75).expanded(0.25),
ChordAngle::from_squared_length(1.)
);
}
}