1use crate::{C64, SCALE_FLOOR};
18
19#[derive(Clone, Debug, PartialEq)]
22pub struct Slicer {
23 points: Vec<C64>,
24 grid: Option<Grid>,
25}
26
27#[derive(Clone, Debug, PartialEq)]
29struct Grid {
30 side: usize,
32 step: f64,
34 re0: f64,
36 im0: f64,
37 line: bool,
39 lookup: Vec<usize>,
41}
42
43impl Grid {
44 fn cell(&self, x: f64, x0: f64) -> usize {
46 ((x - x0) / self.step).round().clamp(0.0, (self.side - 1) as f64) as usize
47 }
48
49 fn nearest(&self, z: C64) -> usize {
51 let c = self.cell(z.re, self.re0);
52 if self.line { self.lookup[c] } else { self.lookup[self.cell(z.im, self.im0) * self.side + c] }
53 }
54}
55
56impl Slicer {
57 pub fn new(points: &[C64]) -> Slicer {
59 Slicer { points: points.to_vec(), grid: Slicer::detect(points) }
60 }
61
62 pub fn exhaustive(points: &[C64]) -> Slicer {
64 Slicer { points: points.to_vec(), grid: None }
65 }
66
67 pub fn is_grid(&self) -> bool {
69 self.grid.is_some()
70 }
71
72 pub fn points(&self) -> &[C64] {
74 &self.points
75 }
76
77 fn levels(v: impl Iterator<Item = f64>) -> Vec<f64> {
78 let mut l: Vec<f64> = v.collect();
79 l.sort_by(f64::total_cmp);
80 let span = (l[l.len() - 1] - l[0]).abs().max(SCALE_FLOOR);
81 l.dedup_by(|a, b| (*a - *b).abs() <= 1e-9 * span);
82 l
83 }
84
85 fn regular(l: &[f64]) -> Option<f64> {
86 if l.len() < 2 {
87 return None;
88 }
89 let step = (l[l.len() - 1] - l[0]) / (l.len() - 1) as f64;
90 l.iter().enumerate().all(|(i, v)| (v - (l[0] + step * i as f64)).abs() <= 1e-9 * step.abs()).then_some(step)
91 }
92
93 fn detect(points: &[C64]) -> Option<Grid> {
94 let m = points.len();
95 if m < 2 {
96 return None;
97 }
98 let re = Slicer::levels(points.iter().map(|p| p.re));
99 let step = Slicer::regular(&re)?;
100 let tol = 1e-6 * step;
101 let col = |x: f64| ((x - re[0]) / step).round() as usize;
102 if points.iter().all(|p| p.im.abs() <= tol) {
103 if re.len() != m {
105 return None;
106 }
107 let mut lookup = vec![0; m];
108 for (i, p) in points.iter().enumerate() {
109 lookup[col(p.re)] = i;
110 }
111 return Some(Grid { side: m, step, re0: re[0], im0: 0.0, line: true, lookup });
112 }
113 let im = Slicer::levels(points.iter().map(|p| p.im));
114 let step_im = Slicer::regular(&im)?;
115 let side = re.len();
116 if im.len() != side || side * side != m || (step_im - step).abs() > tol {
117 return None;
118 }
119 let mut lookup = vec![usize::MAX; m];
120 for (i, p) in points.iter().enumerate() {
121 let (c, r) = (col(p.re), ((p.im - im[0]) / step).round() as usize);
122 if c >= side || r >= side || lookup[r * side + c] != usize::MAX {
123 return None;
124 }
125 lookup[r * side + c] = i;
126 }
127 Some(Grid { side, step, re0: re[0], im0: im[0], line: false, lookup })
128 }
129
130 #[inline]
132 pub fn nearest(&self, z: C64) -> usize {
133 if let Some(grid) = &self.grid {
134 grid.nearest(z)
135 } else {
136 let mut best = (f64::INFINITY, 0usize);
137 for (i, p) in self.points.iter().enumerate() {
138 let d = (z - p).norm_sqr();
139 if d < best.0 {
140 best = (d, i);
141 }
142 }
143 best.1
144 }
145 }
146
147 #[inline]
149 pub fn distance2(&self, z: C64) -> f64 {
150 (z - self.points[self.nearest(z)]).norm_sqr()
151 }
152}