1use crate::Vec2;
11use core::cmp::Ordering;
12
13#[cfg(not(feature = "std"))]
14use crate::common::FloatFuncs;
15
16pub(crate) fn min_dist_param(
30 bez1: &[Vec2],
31 bez2: &[Vec2],
32 u: (f64, f64),
33 v: (f64, f64),
34 epsilon: f64,
35 best_alpha: Option<f64>,
36) -> (f64, f64, f64) {
37 assert!(!bez1.is_empty() && !bez2.is_empty());
38 let n = u8::try_from(bez1.len() - 1).expect("At most 256 control points");
39 let m = u8::try_from(bez2.len() - 1).expect("At most 256 control points");
40 let (umin, umax) = u;
41 let (vmin, vmax) = v;
42 let umid = (umin + umax) / 2.0;
43 let vmid = (vmin + vmax) / 2.0;
44 let svalues: [(f64, f64, f64); 4] = [
45 (S(umin, vmin, bez1, bez2), umin, vmin),
46 (S(umin, vmax, bez1, bez2), umin, vmax),
47 (S(umax, vmin, bez1, bez2), umax, vmin),
48 (S(umax, vmax, bez1, bez2), umax, vmax),
49 ];
50 let alpha: f64 = svalues.iter().map(|(a, _, _)| *a).reduce(f64::min).unwrap();
51 if let Some(best) = best_alpha {
52 if alpha > best {
53 return (alpha, umid, vmid);
54 }
55 }
56
57 if (umax - umin).abs() < epsilon || (vmax - vmin).abs() < epsilon {
58 return (alpha, umid, vmid);
59 }
60
61 let mut is_outside = true;
63 let mut min_drk = None;
64 let mut min_ij = None;
65 for r in 0..(2 * n) {
66 for k in 0..(2 * m) {
67 let d_rk = D_rk(r, k, bez1, bez2);
68 if d_rk < alpha {
69 is_outside = false;
70 }
71 if min_drk.is_none() || Some(d_rk) < min_drk {
72 min_drk = Some(d_rk);
73 min_ij = Some((r, k));
74 }
75 }
76 }
77 if is_outside {
78 return (alpha, umid, vmid);
79 }
80
81 let mut at_boundary0on_bez1 = true;
83 let mut at_boundary1on_bez1 = true;
84 let mut at_boundary0on_bez2 = true;
85 let mut at_boundary1on_bez2 = true;
86 for i in 0..2 * n {
87 for j in 0..2 * m {
88 let dij = D_rk(i, j, bez1, bez2);
89 let dkj = D_rk(0, j, bez1, bez2);
90 if dij < dkj {
91 at_boundary0on_bez1 = false;
92 }
93 let dkj = D_rk(2 * n, j, bez1, bez2);
94 if dij < dkj {
95 at_boundary1on_bez1 = false;
96 }
97 let dkj = D_rk(i, 0, bez1, bez2);
98 if dij < dkj {
99 at_boundary0on_bez2 = false;
100 }
101 let dkj = D_rk(i, 2 * m, bez1, bez2);
102 if dij < dkj {
103 at_boundary1on_bez2 = false;
104 }
105 }
106 }
107 if at_boundary0on_bez1 && at_boundary0on_bez2 {
108 return svalues[0];
109 }
110 if at_boundary0on_bez1 && at_boundary1on_bez2 {
111 return svalues[1];
112 }
113 if at_boundary1on_bez1 && at_boundary0on_bez2 {
114 return svalues[2];
115 }
116 if at_boundary1on_bez1 && at_boundary1on_bez2 {
117 return svalues[3];
118 }
119
120 let (min_i, min_j) = min_ij.unwrap();
121 let new_umid = umin + (umax - umin) * (min_i as f64 / (2 * n) as f64);
122 let new_vmid = vmin + (vmax - vmin) * (min_j as f64 / (2 * m) as f64);
123
124 let results = [
126 min_dist_param(
127 bez1,
128 bez2,
129 (umin, new_umid),
130 (vmin, new_vmid),
131 epsilon,
132 Some(alpha),
133 ),
134 min_dist_param(
135 bez1,
136 bez2,
137 (umin, new_umid),
138 (new_vmid, vmax),
139 epsilon,
140 Some(alpha),
141 ),
142 min_dist_param(
143 bez1,
144 bez2,
145 (new_umid, umax),
146 (vmin, new_vmid),
147 epsilon,
148 Some(alpha),
149 ),
150 min_dist_param(
151 bez1,
152 bez2,
153 (new_umid, umax),
154 (new_vmid, vmax),
155 epsilon,
156 Some(alpha),
157 ),
158 ];
159
160 *results
161 .iter()
162 .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Less))
163 .unwrap()
164}
165
166#[allow(non_snake_case)]
169fn S(u: f64, v: f64, bez1: &[Vec2], bez2: &[Vec2]) -> f64 {
170 #[expect(
171 clippy::cast_possible_truncation,
172 reason = "By the checks in `min_dist_param` we know `bez1.len()` and `bez2.len()` are less than or equal to 256"
173 )]
174 let (n, m) = { ((bez1.len() - 1) as u8, (bez2.len() - 1) as u8) };
175 let mut summand = 0.0;
176 for r in 0..=2 * n {
177 for k in 0..=2 * m {
178 summand +=
179 D_rk(r, k, bez1, bez2) * basis_function(2 * n, r, u) * basis_function(2 * m, k, v);
180 }
181 }
182 summand
183}
184
185#[allow(non_snake_case)]
187fn C_rk(r: u8, k: u8, bez1: &[Vec2], bez2: &[Vec2]) -> f64 {
188 #[expect(
189 clippy::cast_possible_truncation,
190 reason = "By the checks in `min_dist_param` we know `bez1.len()` and `bez2.len()` are less than or equal to 256"
191 )]
192 let (n, m) = { ((bez1.len() - 1) as u8, (bez2.len() - 1) as u8) };
193 let upsilon = r.min(n);
194 let theta = r - n.min(r);
195 let mut left: Vec2 = Vec2::ZERO;
196 for i in theta..upsilon + 1 {
197 let item = bez1[usize::from(i)];
198 left += item * (choose(n, i) * choose(n, r - i)) as f64 / (choose(2 * n, r) as f64);
199 }
200
201 let varsigma = k.min(m);
202 let sigma = k - m.min(k);
203 let mut right: Vec2 = Vec2::ZERO;
204 for j in sigma..varsigma + 1 {
205 let item = bez2[usize::from(j)];
206 right += item * (choose(m, j) * choose(m, k - j)) as f64 / (choose(2 * m, k) as f64);
207 }
208 left.dot(right)
209}
210
211#[allow(non_snake_case)]
215fn A_r(r: u8, p: &[Vec2]) -> f64 {
216 #[expect(
217 clippy::cast_possible_truncation,
218 reason = "By the checks in `min_dist_param` we know `p.len()` is less than or equal to 256"
219 )]
220 let n = (p.len() - 1) as u8;
221 let upsilon = r.min(n);
222 let theta = r - n.min(r);
223 (theta..=upsilon)
224 .map(|i| {
225 let dot = p[usize::from(i)].dot(p[usize::from(r - i)]); let factor = (choose(n, i) * choose(n, r - i)) as f64 / (choose(2 * n, r) as f64);
227 dot * factor
228 })
229 .sum()
230}
231
232#[allow(non_snake_case)]
233fn D_rk(r: u8, k: u8, bez1: &[Vec2], bez2: &[Vec2]) -> f64 {
234 A_r(r, bez1) + A_r(k, bez2) - 2.0 * C_rk(r, k, bez1, bez2)
236}
237
238fn basis_function(n: u8, i: u8, u: f64) -> f64 {
240 choose(n, i) as f64 * (1.0 - u).powi((n - i) as i32) * u.powi(i as i32)
241}
242
243fn choose(n: u8, k: u8) -> u32 {
245 let mut n = n as u32;
246 let k = k as u32;
247 if k > n {
248 return 0;
249 }
250 let mut p = 1;
251 for i in 1..=(n - k) {
252 p *= n;
253 p /= i;
254 n -= 1;
255 }
256 p
257}
258
259#[cfg(test)]
260mod tests {
261 use crate::mindist::A_r;
262 use crate::mindist::{D_rk, choose};
263 use crate::param_curve::ParamCurve;
264 use crate::{CubicBez, Line, PathSeg, Vec2};
265
266 #[test]
267 fn test_choose() {
268 assert_eq!(choose(6, 0), 1);
269 assert_eq!(choose(6, 1), 6);
270 assert_eq!(choose(6, 2), 15);
271 }
272
273 #[test]
274 fn test_d_rk() {
275 let bez1 = vec![
276 Vec2::new(129.0, 139.0),
277 Vec2::new(190.0, 139.0),
278 Vec2::new(201.0, 364.0),
279 Vec2::new(90.0, 364.0),
280 ];
281 let bez2 = vec![
282 Vec2::new(309.0, 159.0),
283 Vec2::new(178.0, 159.0),
284 Vec2::new(215.0, 408.0),
285 Vec2::new(309.0, 408.0),
286 ];
287 let b = A_r(1, &bez2);
288 assert!((b - 80283.0).abs() < 0.005, "B_1(Q)={b:?}");
289 let d = D_rk(0, 1, &bez1, &bez2);
290 assert!((d - 9220.0).abs() < 0.005, "D={d:?}");
291 }
292
293 #[test]
294 fn test_mindist() {
295 let bez1 = PathSeg::Cubic(CubicBez::new(
296 (129.0, 139.0),
297 (190.0, 139.0),
298 (201.0, 364.0),
299 (90.0, 364.0),
300 ));
301 let bez2 = PathSeg::Cubic(CubicBez::new(
302 (309.0, 159.0),
303 (178.0, 159.0),
304 (215.0, 408.0),
305 (309.0, 408.0),
306 ));
307 let mindist = bez1.min_dist(bez2, 0.001);
308 assert!((mindist.distance - 50.9966).abs() < 0.5);
309 }
310
311 #[test]
312 fn test_overflow() {
313 let bez1 = PathSeg::Cubic(CubicBez::new(
314 (232.0, 126.0),
315 (134.0, 126.0),
316 (139.0, 232.0),
317 (141.0, 301.0),
318 ));
319 let bez2 = PathSeg::Line(Line::new((359.0, 416.0), (367.0, 755.0)));
320 let mindist = bez1.min_dist(bez2, 0.001);
321 assert!((mindist.distance - 246.4731222669117).abs() < 0.5);
322 }
323
324 #[test]
325 fn test_out_of_order() {
326 let bez1 = PathSeg::Cubic(CubicBez::new(
327 (287.0, 182.0),
328 (346.0, 277.0),
329 (356.0, 299.0),
330 (359.0, 416.0),
331 ));
332 let bez2 = PathSeg::Line(Line::new((141.0, 301.0), (152.0, 709.0)));
333 let mindist1 = bez1.min_dist(bez2, 0.5);
334 let mindist2 = bez2.min_dist(bez1, 0.5);
335 assert!((mindist1.distance - mindist2.distance).abs() < 0.5);
336 }
337
338 #[test]
339 fn test_line_curve() {
340 let line = PathSeg::Line(Line::new((929.0, 335.0), (911.0, 340.0)));
341
342 let line_as_bez = PathSeg::Cubic(CubicBez::new(
343 line.eval(0.0),
344 line.eval(1.0 / 3.0),
345 line.eval(2.0 / 3.0),
346 line.eval(1.0),
347 ));
348
349 let bez2 = PathSeg::Cubic(CubicBez::new(
350 (1052.0, 401.0),
351 (1048.0, 305.0),
352 (1046.0, 216.0),
353 (1054.0, 146.0),
354 ));
355 let mindist_as_bez = line_as_bez.min_dist(bez2, 0.5);
356 let mindist_as_line = line.min_dist(bez2, 0.5);
357 assert!((mindist_as_line.distance - mindist_as_bez.distance).abs() < 0.5);
358 }
359}