checkerboard_calibrate/chessboard/
approx.rs1pub fn approx_poly_dp(src: &[(i32, i32)], eps: f64, closed: bool) -> Vec<(i32, i32)> {
18 let count = src.len();
19 if count == 0 {
20 return Vec::new();
21 }
22 let eps = eps * eps;
23
24 let mut dst: Vec<(i32, i32)> = Vec::with_capacity(count);
25 let mut stack: Vec<(usize, usize)> = Vec::new();
26
27 let mut slice = (0usize, 0usize);
28 let mut right = (0usize, 0usize);
29 let mut pos = 0usize;
30 let mut start_pt = (0i32, 0i32);
31
32 let is_closed = closed || src[0] == src[count - 1];
35
36 if !is_closed {
37 stack.push((0, count - 1));
38 } else {
39 right.0 = 0;
40 let mut le_eps = false;
41 for _ in 0..3 {
42 let mut max_dist = 0.0f64;
43 pos = (pos + right.0) % count;
44 start_pt = src[pos];
45 pos = (pos + 1) % count;
46 for j in 1..count {
47 let pt = src[pos];
48 pos = (pos + 1) % count;
49 let dx = (pt.0 - start_pt.0) as f64;
50 let dy = (pt.1 - start_pt.1) as f64;
51 let dist = dx * dx + dy * dy;
52 if dist > max_dist {
53 max_dist = dist;
54 right.0 = j;
55 }
56 }
57 le_eps = max_dist <= eps;
58 }
59
60 if !le_eps {
61 let tmp = pos % count;
62 right.1 = tmp;
63 slice.0 = tmp;
64 let newv = (right.0 + slice.0) % count;
65 slice.1 = newv;
66 right.0 = newv;
67 stack.push(right);
68 stack.push(slice);
69 } else {
70 dst.push(start_pt);
73 }
74 }
75
76 while let Some(s) = stack.pop() {
78 slice = s;
79 let end_pt = src[slice.1];
80 pos = slice.0;
81 start_pt = src[pos];
82 pos = (pos + 1) % count;
83
84 let le_eps = if pos != slice.1 {
85 let dx = (end_pt.0 - start_pt.0) as f64;
86 let dy = (end_pt.1 - start_pt.1) as f64;
87 let mut max_dist = 0.0f64;
88 while pos != slice.1 {
89 let pt = src[pos];
90 pos = (pos + 1) % count;
91 let dist =
92 ((pt.1 - start_pt.1) as f64 * dx - (pt.0 - start_pt.0) as f64 * dy).abs();
93 if dist > max_dist {
94 max_dist = dist;
95 right.0 = (pos + count - 1) % count;
96 }
97 }
98 max_dist * max_dist <= eps * (dx * dx + dy * dy)
99 } else {
100 true
101 };
102
103 if le_eps {
104 dst.push(start_pt);
105 } else {
106 right.1 = slice.1;
107 slice.1 = right.0;
108 stack.push(right);
109 stack.push(slice);
110 }
111 }
112
113 if !is_closed {
114 dst.push(src[count - 1]);
115 }
116
117 cleanup_collinear(&mut dst, eps, is_closed);
118 dst
119}
120
121fn cleanup_collinear(dst: &mut Vec<(i32, i32)>, eps: f64, closed: bool) {
125 let count = dst.len();
126 if count < 3 {
127 return;
128 }
129 let mut new_count = count;
130
131 let mut pos = if closed { count - 1 } else { 0 };
132 let mut start_pt = dst[pos];
133 pos = (pos + 1) % count;
134 let mut wpos = pos;
135 let mut pt = dst[pos];
136 pos = (pos + 1) % count;
137
138 let lo = if closed { 0 } else { 1 };
139 let hi = count - if closed { 0 } else { 1 };
140 let mut i = lo;
141 while i < hi && new_count > 2 {
142 let end_pt = dst[pos];
143 pos = (pos + 1) % count;
144
145 let dx = (end_pt.0 - start_pt.0) as f64;
146 let dy = (end_pt.1 - start_pt.1) as f64;
147 let dist = ((pt.0 - start_pt.0) as f64 * dy - (pt.1 - start_pt.1) as f64 * dx).abs();
148 let sip = (pt.0 - start_pt.0) as f64 * (end_pt.0 - pt.0) as f64
149 + (pt.1 - start_pt.1) as f64 * (end_pt.1 - pt.1) as f64;
150
151 if dist * dist <= 0.5 * eps * (dx * dx + dy * dy) && dx != 0.0 && dy != 0.0 && sip >= 0.0 {
152 new_count -= 1;
153 start_pt = end_pt;
154 dst[wpos] = end_pt;
155 wpos = (wpos + 1) % count;
156 pt = dst[pos];
157 pos = (pos + 1) % count;
158 i += 2;
159 continue;
160 }
161
162 start_pt = pt;
163 dst[wpos] = pt;
164 wpos = (wpos + 1) % count;
165 pt = end_pt;
166 i += 1;
167 }
168
169 if !closed {
170 dst[wpos] = pt;
171 }
172 dst.truncate(new_count);
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn square_with_dense_edges_reduces_to_four_corners() {
181 let mut pts = Vec::new();
183 for x in 0..=100 {
184 pts.push((x, 0));
185 }
186 for y in 1..=100 {
187 pts.push((100, y));
188 }
189 for x in (0..100).rev() {
190 pts.push((x, 100));
191 }
192 for y in (1..100).rev() {
193 pts.push((0, y));
194 }
195
196 let approx = approx_poly_dp(&pts, 3.0, true);
197 assert_eq!(approx.len(), 4, "expected 4 corners, got {approx:?}");
198 let set: std::collections::BTreeSet<_> = approx.iter().copied().collect();
199 for corner in [(0, 0), (100, 0), (100, 100), (0, 100)] {
200 assert!(
201 set.contains(&corner),
202 "missing corner {corner:?} in {approx:?}"
203 );
204 }
205 }
206
207 #[test]
208 fn collinear_points_collapse() {
209 let pts = vec![(0, 0), (10, 0), (20, 0), (30, 0), (30, 10), (0, 10)];
211 let approx = approx_poly_dp(&pts, 1.0, true);
212 assert!(approx.len() <= 4, "got {approx:?}");
214 assert!(approx.contains(&(0, 0)));
215 }
216}