Skip to content

Commit

Permalink
Ellipse functions (bevyengine#13025)
Browse files Browse the repository at this point in the history
# Objective

- Add some useful methods to `Ellipse`

## Solution

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

---------

Co-authored-by: IQuick 143 <[email protected]>
  • Loading branch information
lynn-lumen and IQuick143 authored May 6, 2024
1 parent fa0745f commit 4f9f987
Showing 1 changed file with 82 additions and 0 deletions.
82 changes: 82 additions & 0 deletions crates/bevy_math/src/primitives/dim2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 4f9f987

Please sign in to comment.