alpha2_geometry/
arclen.rs1use kurbo::{ParamCurve, ParamCurveDeriv, PathSeg, Vec2};
9
10pub const ACCURACY: f64 = 1e-3;
13
14const MAX_DEPTH: u32 = 12;
16
17const GAUSS_16: [(f64, f64); 8] = [
20 (0.095_012_509_837_637_44, 0.189_450_610_455_068_5),
21 (0.281_603_550_779_258_9, 0.182_603_415_044_923_6),
22 (0.458_016_777_657_227_4, 0.169_156_519_395_002_5),
23 (0.617_876_244_402_643_8, 0.149_595_988_816_576_7),
24 (0.755_404_408_355_003, 0.124_628_971_255_533_9),
25 (0.865_631_202_387_831_7, 0.095_158_511_682_492_78),
26 (0.944_575_023_073_232_6, 0.062_253_523_938_647_89),
27 (0.989_400_934_991_649_9, 0.027_152_459_411_754_09),
28];
29
30fn speed(derivative: Vec2) -> f64 {
31 crate::affine::length(derivative)
32}
33
34fn segment_speed(segment: &PathSeg, t: f64) -> f64 {
36 match segment {
37 PathSeg::Line(line) => speed(line.p1 - line.p0),
38 PathSeg::Quad(quad) => speed(quad.deriv().eval(t).to_vec2()),
39 PathSeg::Cubic(cubic) => speed(cubic.deriv().eval(t).to_vec2()),
40 }
41}
42
43fn gauss(segment: &PathSeg, t0: f64, t1: f64) -> f64 {
44 let half = 0.5 * (t1 - t0);
45 let middle = f64::midpoint(t0, t1);
46 let mut sum = 0.0;
47 for (x, w) in GAUSS_16 {
48 sum += w
49 * (segment_speed(segment, middle - half * x)
50 + segment_speed(segment, middle + half * x));
51 }
52 sum * half
53}
54
55fn adaptive(segment: &PathSeg, t0: f64, t1: f64, whole: f64, depth: u32) -> f64 {
56 let middle = f64::midpoint(t0, t1);
57 let left = gauss(segment, t0, middle);
58 let right = gauss(segment, middle, t1);
59 if depth >= MAX_DEPTH || (left + right - whole).abs() <= ACCURACY * 0.5 {
60 return left + right;
61 }
62 adaptive(segment, t0, middle, left, depth + 1) + adaptive(segment, middle, t1, right, depth + 1)
63}
64
65#[must_use]
67pub fn length_between(segment: &PathSeg, t0: f64, t1: f64) -> f64 {
68 if t1 <= t0 {
69 return 0.0;
70 }
71 if let PathSeg::Line(line) = segment {
72 return speed(line.p1 - line.p0) * (t1 - t0);
73 }
74 let whole = gauss(segment, t0, t1);
75 adaptive(segment, t0, t1, whole, 0)
76}
77
78#[must_use]
80pub fn length(segment: &PathSeg) -> f64 {
81 length_between(segment, 0.0, 1.0)
82}
83
84#[must_use]
88pub fn parameter_at(segment: &PathSeg, distance: f64, total: f64) -> f64 {
89 if distance <= 0.0 || total <= 0.0 {
90 return 0.0;
91 }
92 if distance >= total {
93 return 1.0;
94 }
95 if let PathSeg::Line(_) = segment {
96 return distance / total;
97 }
98 let (mut low, mut high) = (0.0_f64, 1.0_f64);
100 let mut t = distance / total;
101 for _ in 0..60 {
102 let run = length_between(segment, 0.0, t);
103 let error = run - distance;
104 if error.abs() <= ACCURACY * 0.5 {
105 break;
106 }
107 if error < 0.0 {
108 low = t;
109 } else {
110 high = t;
111 }
112 t = f64::midpoint(low, high);
113 }
114 t
115}
116
117#[must_use]
119pub fn point_at(segment: &PathSeg, t: f64) -> kurbo::Point {
120 segment.eval(t)
121}