docvortex_core/
inline_pairs.rs1use crate::geometry::Box4;
3
4pub type Record = (Box4, f64, f64, usize, bool, usize, usize);
5type Choice = (f64, usize, bool, bool);
6
7fn detached(a: Box4, b: Box4, h: f64) -> bool {
9 let width = 0.0_f64.max(a[2] - a[0]);
10 let edge = (b[0] - a[2]).abs().min((a[0] - b[2]).abs());
11 let gap = 0.0_f64.max(b[1] - a[3]).max(a[1] - b[3]);
12 let center = ((a[1] + a[3]) / 2.0 - (b[1] + b[3]) / 2.0).abs();
13 let outside = (b[1] - a[1]).max(a[3] - b[3]);
14 width <= 0.75 * h
15 && edge <= 1.0_f64.max(0.1 * h)
16 && gap <= 0.5_f64.max(0.15 * h)
17 && center >= 0.5_f64.max(0.25 * h)
18 && outside >= 0.5_f64.max(0.2 * h)
19}
20
21fn candidate(a: &Record, b: &Record) -> Option<(f64, bool, bool)> {
23 if a.5 == b.5 || a.6 != b.6 || a.1 <= 0.0 || b.1 <= 0.0 {
24 return None;
25 }
26 let legacy_a = 0.1_f64.max(a.1);
27 let legacy_b = 0.1_f64.max(b.1);
28 let ratio = legacy_a / legacy_b;
29 let canonical = a.4 && !(0.35..=0.8).contains(&ratio) && (0.35..=0.8).contains(&(a.2 / b.2));
30 let (small, base) = if canonical {
31 (a.2, b.2)
32 } else {
33 (legacy_a, legacy_b)
34 };
35 if !(0.35..=0.8).contains(&(small / base)) || (a.3 > 8 && a.0[2] - a.0[0] > 3.0 * base) {
36 return None;
37 }
38 let overlap = 0.0_f64.max(a.0[3].min(b.0[3]) - a.0[1].max(b.0[1]));
39 let overlap_ratio = overlap / 0.1_f64.max(a.0[3] - a.0[1]);
40 let outside_pair = overlap_ratio < 0.5 && detached(a.0, b.0, base);
41 if overlap_ratio < 0.5 && !outside_pair {
42 return None;
43 }
44 let center = ((a.0[1] + a.0[3]) / 2.0 - (b.0[1] + b.0[3]) / 2.0).abs();
45 if center < 0.5_f64.max(0.12 * base) {
46 return None;
47 }
48 let mut best: Option<(f64, bool, f64)> = None;
49 for (prefix, gap) in [(true, b.0[0] - a.0[2]), (false, a.0[0] - b.0[2])] {
50 if gap >= -0.35 * base
51 && gap <= 1.5_f64.max(0.35 * base)
52 && best.is_none_or(|old| gap.abs() < old.0)
53 {
54 best = Some((gap.abs(), prefix, gap));
55 }
56 }
57 let (_, prefix, gap) = best?;
58 let outside = (b.0[1] - a.0[1]).max(a.0[3] - b.0[3]);
59 if outside < 0.5_f64.max(0.08 * base) && gap.abs() > 1.0_f64.max(0.1 * base) {
60 return None;
61 }
62 Some((
63 gap.abs() + (1.0 - overlap_ratio) * base,
64 prefix,
65 outside_pair,
66 ))
67}
68
69pub fn matches(records: Vec<Record>) -> Option<Vec<(usize, usize, bool, bool)>> {
71 let n = records.len();
72 if records.iter().any(|r| {
73 r.6 >= n
74 || r.0.iter().any(|x| !x.is_finite() || x.abs() > 1e100)
75 || r.0[2] <= r.0[0]
76 || r.0[3] <= r.0[1]
77 || !r.1.is_finite()
78 || r.1.abs() > 1e100
79 || !r.2.is_finite()
80 || r.2 <= 0.0
81 || r.2 > 1e100
82 }) {
83 return None;
84 }
85 let count = records.iter().map(|r| r.6).max().map_or(0, |x| x + 1);
86 let mut left = vec![Vec::new(); count];
87 let mut right = left.clone();
88 let mut scales = vec![0.1_f64; count];
89 for (i, r) in records.iter().enumerate() {
90 left[r.6].push((r.0[0], i));
91 right[r.6].push((r.0[2], i));
92 scales[r.6] = scales[r.6].max(r.2).max(0.1_f64.max(r.1));
93 }
94 for values in left.iter_mut().chain(right.iter_mut()) {
95 values.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap().then(a.1.cmp(&b.1)));
96 }
97 let mut best_base: Vec<Option<Choice>> = vec![None; n];
98 let mut best_small: Vec<[Option<(f64, usize)>; 2]> = vec![[None, None]; n];
99 for (i, a) in records.iter().enumerate() {
100 if a.3 == 0 {
101 continue;
102 }
103 let padding = 0.35 * scales[a.6];
104 let gap = 1.5_f64.max(padding);
105 let mut bases = Vec::new();
106 for (values, low, high) in [
107 (&left[a.6], a.0[2] - padding, a.0[2] + gap),
108 (&right[a.6], a.0[0] - gap, a.0[0] + padding),
109 ] {
110 let start = values.partition_point(|v| v.0 < low);
111 let end = values.partition_point(|v| v.0 <= high);
112 bases.extend(values[start..end].iter().map(|v| v.1));
113 }
114 bases.sort_unstable();
115 bases.dedup();
116 for j in bases {
117 if i == j {
118 continue;
119 }
120 if let Some((metric, prefix, outside)) = candidate(a, &records[j]) {
121 if best_base[i].is_none_or(|old| metric < old.0) {
122 best_base[i] = Some((metric, j, prefix, outside));
123 }
124 let slot = usize::from(!prefix);
125 if best_small[j][slot].is_none_or(|old| metric < old.0) {
126 best_small[j][slot] = Some((metric, i));
127 }
128 }
129 }
130 }
131 Some(
132 best_base
133 .iter()
134 .enumerate()
135 .filter_map(|(i, best)| {
136 let (_, base, prefix, outside) = (*best)?;
137 (best_small[base][usize::from(!prefix)].map(|x| x.1) == Some(i))
138 .then_some((i, base, prefix, outside))
139 })
140 .collect(),
141 )
142}