@@ -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