@@ -118,6 +118,73 @@ impl Ellipse {
118
118
( a * a - b * b) . sqrt ( ) / a
119
119
}
120
120
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
+
121
188
/// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse.
122
189
#[ inline( always) ]
123
190
pub fn semi_major ( & self ) -> f32 {
@@ -861,6 +928,21 @@ mod tests {
861
928
assert_eq ! ( circle. eccentricity( ) , 0. , "incorrect circle eccentricity" ) ;
862
929
}
863
930
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
+
864
946
#[ test]
865
947
fn triangle_math ( ) {
866
948
let triangle = Triangle2d :: new (
0 commit comments