@@ -8,7 +8,10 @@ use crate::{
88 Point , Rect , Shape , Vec2 ,
99} ;
1010use core:: {
11- f64:: consts:: { FRAC_PI_2 , PI } ,
11+ f64:: {
12+ self ,
13+ consts:: { FRAC_PI_2 , PI } ,
14+ } ,
1215 iter,
1316 ops:: { Mul , Range } ,
1417} ;
@@ -205,11 +208,7 @@ impl ParamCurve for Arc {
205208}
206209
207210impl ParamCurveArclen for Arc {
208- fn arclen ( & self , _accuracy : f64 ) -> f64 {
209- // TODO: wire up accuracy. The Carlson numerical approximation provides a bound on the relative
210- // error
211- let relative_error = 1e-20 ;
212-
211+ fn arclen ( & self , accuracy : f64 ) -> f64 {
213212 // Normalize ellipse to have radius y >= radius x, required for the parameter assumptions
214213 // of `incomplete_elliptic_integral_second_kind`.
215214 let ( radii, mut start_angle) = if self . radii . y >= self . radii . x {
@@ -248,23 +247,43 @@ impl ParamCurveArclen for Arc {
248247 // that range.
249248 let mut arclen = 0. ;
250249
250+ // The available accuracy (tolerance) is distributed over the calculation of the two
251+ // incomplete and one complete elliptic integrals.
252+ let accuracy_per_incomplete_integral = 1. / 3. * accuracy / radii. y ;
251253 if start_angle >= PI / 2. {
252- arclen += incomplete_elliptic_integral_second_kind ( relative_error, PI - start_angle, m) ;
254+ arclen += incomplete_elliptic_integral_second_kind (
255+ accuracy_per_incomplete_integral,
256+ PI - start_angle,
257+ m,
258+ ) ;
253259 quarter_turns -= 1. ;
254260 } else {
255- arclen -= incomplete_elliptic_integral_second_kind ( relative_error, start_angle, m) ;
261+ arclen -= incomplete_elliptic_integral_second_kind (
262+ accuracy_per_incomplete_integral,
263+ start_angle,
264+ m,
265+ ) ;
256266 }
257267
258268 if end_angle >= PI / 2. {
259- arclen -= incomplete_elliptic_integral_second_kind ( relative_error, PI - end_angle, m) ;
269+ arclen -= incomplete_elliptic_integral_second_kind (
270+ accuracy_per_incomplete_integral,
271+ PI - end_angle,
272+ m,
273+ ) ;
260274 quarter_turns += 1. ;
261275 } else {
262- arclen += incomplete_elliptic_integral_second_kind ( relative_error, end_angle, m) ;
276+ arclen += incomplete_elliptic_integral_second_kind (
277+ accuracy_per_incomplete_integral,
278+ end_angle,
279+ m,
280+ ) ;
263281 }
264282 arclen *= radii. y ;
265283
266- // Note: this uses the complete elliptic integral, which can be special-cased.
267- arclen += 0.25 * quarter_turns * complete_elliptic_perimeter ( self . radii , relative_error) ;
284+ arclen += 1. / 4.
285+ * quarter_turns
286+ * complete_elliptic_perimeter ( radii, 1. / 4. / 3. * accuracy * quarter_turns. max ( 1. ) ) ;
268287
269288 arclen
270289 }
@@ -326,22 +345,49 @@ impl Mul<Arc> for Affine {
326345/// elliptic integrals" (Carlson, Bille C.): <https://arxiv.org/abs/math/9409227v1>
327346///
328347/// RF = 1/2 ∫ 1 / ( sqrt(t+x) sqrt(t+y) sqrt(t+z) ) dt from 0 to inf
329- fn carlson_rf ( relative_error : f64 , x : f64 , y : f64 , z : f64 ) -> f64 {
348+ fn carlson_rf ( accuracy : f64 , x : f64 , y : f64 , z : f64 ) -> f64 {
330349 // At most one of (x, y, z) may be 0.
331350 debug_assert ! ( ( x == 0. ) as u8 + ( y == 0. ) as u8 + ( z == 0. ) as u8 <= 1 ) ;
332351
352+ // This mostly follows "Numerical computation of real or complex elliptic integrals", but using
353+ // an absolute upper error bound rather than a relative one.
354+ //
355+ // From "Numerical computation of real or complex elliptic integrals" we have
356+ //
357+ // X_n = (a_0 - x_0) / (4^n a_n)
358+ // (and the same for variables (Y,y), (Z,z)).
359+ //
360+ // From "Computing Elliptic Integrals by Duplication" we have an upper error bound of
361+ //
362+ // |err_n| < a_n^(-1/2) epsilon_n^6 / (4 (1 - epsilon_n))
363+ // with epsilon_n = max(X_n, Y_n, Z_n)
364+ // = max(a_0 - x_0, a_0 - y_0, a_0 - z_0) / (4^n a_n).
365+ //
366+ // Define e_0 = max(a_0 - x_0, a_0 - y_0, a_0 - z_0). Rewrite for ease of computation,
367+ //
368+ // |err_n| < a_n^(-1/2) epsilon_n^6 / (4 (1 - epsilon_n))
369+ // = a_n^(-1/2) e_0^6 / (4^n a_n)^6 / (4 (1 - epsilon_n))
370+ // -> |err_n| a_n^(1/2) (4^n a_n)^6 / e_0^6 < 1 / (4 (1 - epsilon_n))
371+ // -> |err_n| a_n^(1/2) a_n^6 4^(6n + 1) / e_0^6 < 1 / (1 - epsilon_n)
372+ // -> |err_n| a_n^(1/2) a_n^6 4^(6n + 1) / e_0^6 (1 - epsilon_n) < 1.
373+ //
374+ // To reach an error upper bound of `accuracy`, iterate until
375+ // 1 <= accuracy * a_n^(1/2) a_n^6 4^(6n + 1) / e_0^6 (1 - epsilon_n).
376+
333377 let mut x = x;
334378 let mut y = y;
335379 let mut z = z;
336380
337- let a0 = ( x + y + z) / 3. ;
338- let mut q = ( 3. * relative_error) . powf ( -1. / 6. )
339- * ( a0 - x) . abs ( ) . max ( ( a0 - y) . abs ( ) ) . max ( ( a0 - z) . abs ( ) ) ;
381+ let mut a = ( x + y + z) / 3. ;
340382
341- let mut a = a0;
342- let mut m = 0 ;
383+ // These are partial terms of the inequality derived above. The multiply by (powers of) 4 are
384+ // performed per iteration for computational efficiency.
385+ let mut e = a - x. min ( y) . min ( z) ;
386+ let mut r = accuracy * 4. * e. powi ( -6 ) ;
387+
388+ // let mut q = 1. / (3. * f64::EPSILON).cbrt().sqrt() * (a - x.min(y).min(z));
343389 loop {
344- if q <= a . abs ( ) {
390+ if 1. <= r * a . powi ( 6 ) * a . sqrt ( ) * ( 1. - e / a ) {
345391 break ;
346392 }
347393
@@ -351,81 +397,114 @@ fn carlson_rf(relative_error: f64, x: f64, y: f64, z: f64) -> f64 {
351397 y = ( y + lambda) / 4. ;
352398 z = ( z + lambda) / 4. ;
353399
354- q /= 4. ;
355- m += 1 ;
400+ r *= 4f64 . powi ( 6 ) ;
401+ e /= 4. ;
356402 }
357403
358- let x = ( a0 - x) / 4f64 . powi ( m ) * a;
359- let y = ( a0 - y) / 4f64 . powi ( m ) * a;
404+ let x = 1. - x / a;
405+ let y = 1. - y / a;
360406 let z = -x - y;
361407
362408 let e2 = x * y - z. powi ( 2 ) ;
363409 let e3 = x * y * z;
364410
365- 1. / a. sqrt ( )
366- * ( 1. - 1. / 10. * e2 + 1. / 14. * e3 + 1. / 24. * e2. powi ( 2 ) - 3. / 44. * e2 * e3)
411+ ( 1. + ( -1. / 10. * e2 + 1. / 14. * e3 + 1. / 24. * e2. powi ( 2 ) - 3. / 44. * e2 * e3) ) / a. sqrt ( )
367412}
368413
369414/// Approximation of the Carlson RD function as defined in "Numerical computation of real or
370415/// complex elliptic integrals" (Carlson, Bille C.): <https://arxiv.org/abs/math/9409227v1>
371416///
372417/// RD = 3/2 ∫ 1 / ( sqrt(t+x) sqrt(t+y) (t+z)^(3/2) ) dt from 0 to inf
373- fn carlson_rd ( relative_error : f64 , x : f64 , y : f64 , z : f64 ) -> f64 {
418+ fn carlson_rd ( accuracy : f64 , x : f64 , y : f64 , z : f64 ) -> f64 {
374419 // At most one of (x, y) may be 0, z must be nonzero.
375420 debug_assert ! ( z != 0. ) ;
376421 debug_assert ! ( x != 0. || y != 0. ) ;
377422
423+ // As above for RF, find the absolute upper error bound rather than a relative one, the
424+ // derivation of which is along the same lines.
425+ //
426+ // Again,
427+ //
428+ // X_n = (a_0 - x_0) / (4^n a_n)
429+ // (and the same for variables (Y,y), (Z,z)).
430+ //
431+ // From "Computing Elliptic Integrals by Duplication" we have
432+ //
433+ // |err_n| < 4^-n a_n^(-3/2) 3 epsilon_n^6 / (1 - epsilon_n)^(3/2)
434+ // with epsilon_n = max(X_n, Y_n, Z_n)
435+ // = max(a_0 - x_0, a_0 - y_0, a_0 - z_0) / (4^n a_n).
436+ //
437+ // Define e_0 = max(a_0 - x_0, a_0 - y_0, a_0 - z_0). Rewriting for ease of computation,
438+ //
439+ // |err_n| < 4^-n a_n^(-3/2) 3 epsilon_n^6 / (1 - epsilon_n)^(3/2)
440+ // = 4^-n a_n^(-3/2) 3 e_0^6 / 4^(6n) a_n^6 / (1 - epsilon_n)^(3/2)
441+ // -> |err_n| 4^(7n) a_n^(3/2) a_n^6 / (3 e_0^6) < 1 / (1 - epsilon_n)^(3/2)
442+ // -> |err_n| 4^(7n) a_n^(3/2) a_n^6 (1/3) / e_0^6 < (1 / 1 - epsilon_n)^(3/2),
443+ // raise to the power 2/3,
444+ // -> |err_n|^(2/3) 4^(14/3 n) a_n a_n^4 (1/3)^(2/3) / e_0^4 < 1 / (1 - epsilon_n)
445+ // -> |err_n|^(2/3) 4^(14/3 n) a_n^5 (1/3)^(2/3) / e_0^4 (1 - epsilon_n) < 1
446+ //
447+ // That means, to reach an error upper bound of `accuracy`, iterate until
448+ // 1 <= accuracy^(2/3) 4^(14/3 n) a_n^5 (1/3)^(2/3) / e_0^4 (1 - epsilon)
449+
378450 let mut x = x;
379451 let mut y = y;
380452 let mut z = z;
381453
382454 let a0 = ( x + y + 3. * z) / 5. ;
383- let mut q = ( relative_error / 4. ) . powf ( -1. / 6. )
384- * ( a0 - x) . abs ( ) . max ( ( a0 - y) . abs ( ) ) . max ( ( a0 - z) . abs ( ) ) ;
455+ let mut a = a0;
385456
386457 let mut sum = 0. ;
387- let mut a = a0;
388- let mut m = 0 ;
458+ let mut mul = 1. ;
459+
460+ // These are partial terms of the inequality derived above. The multiply by (powers of) 4 are
461+ // performed per iteration for computational efficiency.
462+ let mut e = a - x. min ( y) . min ( z) ;
463+ let mut r = ( accuracy / 3. ) . powf ( 2. / 3. ) * e. powi ( -4 ) ;
464+
389465 loop {
390- if q <= a . abs ( ) {
466+ if 1. <= r * a . powi ( 5 ) * ( 1. - e / a ) {
391467 break ;
392468 }
393469
394470 let lambda = ( x * y) . sqrt ( ) + ( x * z) . sqrt ( ) + ( y * z) . sqrt ( ) ;
395- sum += 4f64 . powi ( -m ) / ( z. sqrt ( ) * ( z + lambda) ) ;
471+ sum += mul / ( z. sqrt ( ) * ( z + lambda) ) ;
396472 a = ( a + lambda) / 4. ;
397473 x = ( x + lambda) / 4. ;
398474 y = ( y + lambda) / 4. ;
399475 z = ( z + lambda) / 4. ;
400476
401- q /= 4. ;
402- m += 1 ;
477+ r *= 4f64 . powf ( 14. / 3. ) ;
478+ e /= 4. ;
479+ mul /= 4. ;
403480 }
404481
405- let x = ( a0 - x) / ( 4f64 . powi ( 4 ) * a ) ;
406- let y = ( a0 - y) / ( 4f64 . powi ( 4 ) * a ) ;
407- let z = - ( x + y) / 3. ;
482+ let x = 1. - x / a ;
483+ let y = 1. - y / a ;
484+ let z = ( -x - y) / 3. ;
408485
409486 let e2 = x * y - 6. * z. powi ( 2 ) ;
410487 let e3 = ( 3. * x * y - 8. * z. powi ( 2 ) ) * z;
411- let e4 = 3. * x * y - z. powi ( 2 ) * z. powi ( 2 ) ;
488+ let e4 = 3. * ( x * y - z. powi ( 2 ) ) * z. powi ( 2 ) ;
412489 let e5 = x * y * z. powi ( 3 ) ;
413490
414- 4f64 . powi ( -m) * 1. / ( a * a. sqrt ( ) )
415- * ( 1. - 3. / 14. * e2 + 1. / 6. * e3 + 9. / 88. * e2. powi ( 2 )
416- - 3. / 22. * e4
417- - 9. / 52. * e2 * e3
418- + 3. / 26. * e5)
491+ ( 1. - 3. / 14. * e2 + 1. / 6. * e3 + 9. / 88. * e2. powi ( 2 ) - 3. / 22. * e4 - 9. / 52. * e2 * e3
492+ + 3. / 26. * e5)
493+ * mul
494+ / ( a * a. sqrt ( ) )
419495 + 3. * sum
420496}
421497
422498/// Numerically approximate the incomplete elliptic integral of the second kind from 0 to `phi`
423499/// parameterized by `m = k^2` in Legendre's trigonometric form.
424500///
501+ /// The absolute error between the calculated integral and the true integral is bounded by
502+ /// `accuracy` (modulo floating point rounding errors).
503+ ///
425504/// Assumes:
426505/// 0 <= phi <= pi / 2
427506/// and 0 <= m sin^2(phi) <= 1
428- fn incomplete_elliptic_integral_second_kind ( relative_error : f64 , phi : f64 , m : f64 ) -> f64 {
507+ fn incomplete_elliptic_integral_second_kind ( accuracy : f64 , phi : f64 , m : f64 ) -> f64 {
429508 // Approximate the incomplete elliptic integral through Carlson symmetric forms:
430509 // https://en.wikipedia.org/w/index.php?title=Carlson_symmetric_form&oldid=1223277638#Incomplete_elliptic_integrals
431510
@@ -442,8 +521,25 @@ fn incomplete_elliptic_integral_second_kind(relative_error: f64, phi: f64, m: f6
442521 // note: this actually allows calculating from -1/2 pi <= phi <= 1/2 pi, but there are some
443522 // alternative translations from the Legendre form that are potentially better, that do
444523 // restrict the domain to 0 <= phi <= 1/2 pi.
445- sin * carlson_rf ( relative_error, cos2, 1. - m * sin2, 1. )
446- - 1. / 3. * m * sin3 * carlson_rd ( relative_error, cos2, 1. - m * sin2, 1. )
524+ let term1 = if sin == 0. {
525+ 0.
526+ } else {
527+ sin * carlson_rf (
528+ accuracy / ( 2. * sin) ,
529+ // 1e-30,
530+ cos2,
531+ 1. - m * sin2,
532+ 1. ,
533+ )
534+ } ;
535+
536+ let term2 = if sin == 0. || m == 0. {
537+ 0.
538+ } else {
539+ 1. / 3. * m * sin3 * carlson_rd ( accuracy * 3. / 2. / ( m * sin3) , cos2, 1. - m * sin2, 1. )
540+ } ;
541+
542+ term1 - term2
447543}
448544
449545#[ cfg( test) ]
@@ -468,10 +564,6 @@ mod tests {
468564
469565 #[ test]
470566 fn length ( ) {
471- // TODO: when arclen actually uses specified accuracy, update EPSILON and the accuracy
472- // params
473- const EPSILON : f64 = 1e-6 ;
474-
475567 // Circular checks:
476568 for ( start_angle, sweep_angle, length) in [
477569 ( 0. , 1. , 1. ) ,
@@ -482,37 +574,35 @@ mod tests {
482574 ( 2.5 , 10. , 10. ) ,
483575 ] {
484576 let a = Arc :: new ( ( 0. , 0. ) , ( 1. , 1. ) , start_angle, sweep_angle, 0. ) ;
485- let arc_length = a. arclen ( 0.000_1 ) ;
577+ let arc_length = a. arclen ( 1e-7 ) ;
486578 assert ! (
487- ( arc_length - length) . abs( ) <= EPSILON ,
579+ ( arc_length - length) . abs( ) <= 1e-6 ,
488580 "Got arc length {arc_length}, expected {length} for circular arc {a:?}"
489581 ) ;
490582 }
491583
492584 let a = Arc :: new ( ( 0. , 0. ) , ( 1. , 1. ) , 0. , PI * 4. , 0. ) ;
493- assert ! ( ( a. arclen( 0.000_1 ) - PI * 4. ) . abs( ) <= EPSILON ) ;
585+ assert ! ( ( a. arclen( 1e-13 ) - PI * 4. ) . abs( ) <= 1e-12 ) ;
494586
495587 let a = Arc :: new ( ( 0. , 0. ) , ( 2.23 , 3.05 ) , 0. , 0.2 , 0. ) ;
496- assert ! ( ( a. arclen( 0.000_1 ) - 0.60811714277 ) . abs( ) <= EPSILON ) ;
588+ assert ! ( ( a. arclen( 1e-13 ) - 0.608_117_142_773_153_8 ) . abs( ) <= 1e-12 ) ;
497589
498590 let a = Arc :: new ( ( 0. , 0. ) , ( 3.05 , 2.23 ) , 0. , 0.2 , 0. ) ;
499- assert ! ( ( a. arclen( 0.000_1 ) - 0.448555 ) . abs( ) <= EPSILON ) ;
591+ assert ! ( ( a. arclen( 1e-13 ) - 0.448_554_961_296_305_9 ) . abs( ) <= 1e-12 ) ;
500592 }
501593
502594 #[ test]
503595 fn length_compare_with_bez_length ( ) {
504- const EPSILON : f64 = 1e-3 ;
505-
506596 for radii in [ ( 1. , 1. ) , ( 0.5 , 1. ) , ( 2. , 1. ) ] {
507597 for start_angle in [ 0. , 0.5 , 1. , 2. , PI , -1. ] {
508598 for sweep_angle in [ 0. , 0.5 , 1. , 2. , PI , -1. ] {
509599 let a = Arc :: new ( ( 0. , 0. ) , radii, start_angle, sweep_angle, 0. ) ;
510600
511- let arc_length = a. arclen ( 0.000_1 ) ;
512- let bez_length = a. path_segments ( 0.000_1 ) . perimeter ( 0.000_1 ) ;
601+ let arc_length = a. arclen ( 1e-8 ) ;
602+ let bez_length = a. path_segments ( 1e-8 ) . perimeter ( 1e-8 ) ;
513603
514604 assert ! (
515- ( arc_length - bez_length) . abs( ) < EPSILON ,
605+ ( arc_length - bez_length) . abs( ) < 1e-7 ,
516606 "Numerically approximated arc length ({arc_length}) does not match bezier segment perimeter length ({bez_length}) for arc {a:?}"
517607 ) ;
518608 }
@@ -522,33 +612,27 @@ mod tests {
522612
523613 #[ test]
524614 fn carlson_numerical_checks ( ) {
525- // TODO: relative bound on error doesn't seem to be quite correct yet, use a large epsilon
526- // for now
527- const EPSILON : f64 = 1e-6 ;
528-
529615 // Numerical checks from section 3 of "Numerical computation of real or complex elliptic
530616 // integrals" (Carlson, Bille C.): https://arxiv.org/abs/math/9409227v1 (real-valued calls)
531- assert ! ( ( carlson_rf( 1e-20 , 1. , 2. , 0. ) - 1.311_028_777_146_1 ) . abs( ) <= EPSILON ) ;
532- assert ! ( ( carlson_rf( 1e-20 , 2. , 3. , 4. ) - 0.584_082_841_677_15 ) . abs( ) <= EPSILON ) ;
617+ assert ! ( ( carlson_rf( 1e-13 , 1. , 2. , 0. ) - 1.311_028_777_146_1 ) . abs( ) <= 1e-12 ) ;
618+ assert ! ( ( carlson_rf( 1e-13 , 2. , 3. , 4. ) - 0.584_082_841_677_15 ) . abs( ) <= 1e-12 ) ;
533619
534- assert ! ( ( carlson_rd( 1e-20 , 0. , 2. , 1. ) - 1.797_210_352_103_4 ) . abs( ) <= EPSILON ) ;
535- assert ! ( ( carlson_rd( 1e-20 , 2. , 3. , 4. ) - 0.165_105_272_942_61 ) . abs( ) <= EPSILON ) ;
620+ assert ! ( ( carlson_rd( 1e-13 , 0. , 2. , 1. ) - 1.797_210_352_103_4 ) . abs( ) <= 1e-12 ) ;
621+ assert ! ( ( carlson_rd( 1e-13 , 2. , 3. , 4. ) - 0.165_105_272_942_61 ) . abs( ) <= 1e-12 ) ;
536622 }
537623
538624 #[ test]
539625 fn elliptic_e_numerical_checks ( ) {
540- const EPSILON : f64 = 1e-6 ;
541-
542626 for ( phi, m, elliptic_e) in [
543627 ( 0.0 , 0.0 , 0.0 ) ,
544628 ( 0.5 , 0.0 , 0.5 ) ,
545629 ( 1.0 , 0.0 , 1.0 ) ,
546630 ( 0.0 , 1.0 , 0.0 ) ,
547- ( 1.0 , 1.0 , 0.84147098 ) ,
631+ ( 1.0 , 1.0 , 0.841_470_984_807_896_5 ) ,
548632 ] {
549- let elliptic_e_approx = incomplete_elliptic_integral_second_kind ( 1e-20 , phi, m) ;
633+ let elliptic_e_approx = incomplete_elliptic_integral_second_kind ( 1e-13 , phi, m) ;
550634 assert ! (
551- ( elliptic_e_approx - elliptic_e) . abs( ) < EPSILON ,
635+ ( elliptic_e_approx - elliptic_e) . abs( ) < 1e-12 ,
552636 "Approximated elliptic e {elliptic_e_approx} does not match known value {elliptic_e} for E({phi}|{m})"
553637 ) ;
554638 }
0 commit comments