Skip to content

Commit 4f9f987

Browse files
Ellipse functions (#13025)
# Objective - Add some useful methods to `Ellipse` ## Solution - Added `Ellipse::perimeter()` and `::focal_length()` --------- Co-authored-by: IQuick 143 <[email protected]>
1 parent fa0745f commit 4f9f987

File tree

1 file changed

+82
-0
lines changed
  • crates/bevy_math/src/primitives

1 file changed

+82
-0
lines changed

crates/bevy_math/src/primitives/dim2.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,73 @@ impl Ellipse {
118118
(a * a - b * b).sqrt() / a
119119
}
120120

121+
#[inline(always)]
122+
/// Get the focal length of the ellipse. This corresponds to the distance between one of the foci and the center of the ellipse.
123+
///
124+
/// The focal length of an ellipse is related to its eccentricity by `eccentricity = focal_length / semi_major`
125+
pub fn focal_length(&self) -> f32 {
126+
let a = self.semi_major();
127+
let b = self.semi_minor();
128+
129+
(a * a - b * b).sqrt()
130+
}
131+
132+
#[inline(always)]
133+
/// Get an approximation for the perimeter or circumference of the ellipse.
134+
///
135+
/// The approximation is reasonably precise with a relative error less than 0.007%, getting more precise as the eccentricity of the ellipse decreases.
136+
pub fn perimeter(&self) -> f32 {
137+
let a = self.semi_major();
138+
let b = self.semi_minor();
139+
140+
// In the case that `a == b`, the ellipse is a circle
141+
if a / b - 1. < 1e-5 {
142+
return PI * (a + b);
143+
};
144+
145+
// In the case that `a` is much larger than `b`, the ellipse is a line
146+
if a / b > 1e4 {
147+
return 4. * a;
148+
};
149+
150+
// These values are the result of (0.5 choose n)^2 where n is the index in the array
151+
// They could be calculated on the fly but hardcoding them yields more accurate and faster results
152+
// because the actual calculation for these values involves factorials and numbers > 10^23
153+
const BINOMIAL_COEFFICIENTS: [f32; 21] = [
154+
1.,
155+
0.25,
156+
0.015625,
157+
0.00390625,
158+
0.0015258789,
159+
0.00074768066,
160+
0.00042057037,
161+
0.00025963783,
162+
0.00017140154,
163+
0.000119028846,
164+
0.00008599834,
165+
0.00006414339,
166+
0.000049109784,
167+
0.000038430585,
168+
0.000030636627,
169+
0.000024815668,
170+
0.000020380836,
171+
0.000016942893,
172+
0.000014236736,
173+
0.000012077564,
174+
0.000010333865,
175+
];
176+
177+
// The algorithm used here is the Gauss-Kummer infinite series expansion of the elliptic integral expression for the perimeter of ellipses
178+
// For more information see https://www.wolframalpha.com/input/?i=gauss-kummer+series
179+
// We only use the terms up to `i == 20` for this approximation
180+
let h = ((a - b) / (a + b)).powi(2);
181+
182+
PI * (a + b)
183+
* (0..=20)
184+
.map(|i| BINOMIAL_COEFFICIENTS[i] * h.powi(i as i32))
185+
.sum::<f32>()
186+
}
187+
121188
/// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse.
122189
#[inline(always)]
123190
pub fn semi_major(&self) -> f32 {
@@ -861,6 +928,21 @@ mod tests {
861928
assert_eq!(circle.eccentricity(), 0., "incorrect circle eccentricity");
862929
}
863930

931+
#[test]
932+
fn ellipse_perimeter() {
933+
let circle = Ellipse::new(1., 1.);
934+
assert_relative_eq!(circle.perimeter(), 6.2831855);
935+
936+
let line = Ellipse::new(75_000., 0.5);
937+
assert_relative_eq!(line.perimeter(), 300_000.);
938+
939+
let ellipse = Ellipse::new(0.5, 2.);
940+
assert_relative_eq!(ellipse.perimeter(), 8.578423);
941+
942+
let ellipse = Ellipse::new(5., 3.);
943+
assert_relative_eq!(ellipse.perimeter(), 25.526999);
944+
}
945+
864946
#[test]
865947
fn triangle_math() {
866948
let triangle = Triangle2d::new(

0 commit comments

Comments
 (0)