Ellipse functions (#13025)

# Objective

- Add some useful methods to `Ellipse`

## Solution

- Added `Ellipse::perimeter()` and `::focal_length()`

---------

Co-authored-by: IQuick 143 <IQuick143cz@gmail.com>
This commit is contained in:
Lynn 2024-05-06 23:31:51 +02:00 committed by GitHub
parent fa0745fdd0
commit 4f9f987099
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -118,6 +118,73 @@ impl Ellipse {
(a * a - b * b).sqrt() / a
}
#[inline(always)]
/// Get the focal length of the ellipse. This corresponds to the distance between one of the foci and the center of the ellipse.
///
/// The focal length of an ellipse is related to its eccentricity by `eccentricity = focal_length / semi_major`
pub fn focal_length(&self) -> f32 {
let a = self.semi_major();
let b = self.semi_minor();
(a * a - b * b).sqrt()
}
#[inline(always)]
/// Get an approximation for the perimeter or circumference of the ellipse.
///
/// The approximation is reasonably precise with a relative error less than 0.007%, getting more precise as the eccentricity of the ellipse decreases.
pub fn perimeter(&self) -> f32 {
let a = self.semi_major();
let b = self.semi_minor();
// In the case that `a == b`, the ellipse is a circle
if a / b - 1. < 1e-5 {
return PI * (a + b);
};
// In the case that `a` is much larger than `b`, the ellipse is a line
if a / b > 1e4 {
return 4. * a;
};
// These values are the result of (0.5 choose n)^2 where n is the index in the array
// They could be calculated on the fly but hardcoding them yields more accurate and faster results
// because the actual calculation for these values involves factorials and numbers > 10^23
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,
];
// The algorithm used here is the Gauss-Kummer infinite series expansion of the elliptic integral expression for the perimeter of ellipses
// For more information see https://www.wolframalpha.com/input/?i=gauss-kummer+series
// We only use the terms up to `i == 20` for this approximation
let h = ((a - b) / (a + b)).powi(2);
PI * (a + b)
* (0..=20)
.map(|i| BINOMIAL_COEFFICIENTS[i] * h.powi(i as i32))
.sum::<f32>()
}
/// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse.
#[inline(always)]
pub fn semi_major(&self) -> f32 {
@ -861,6 +928,21 @@ mod tests {
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(