1#[cfg(feature = "parallel")]
57extern crate rayon;
58
59use std::collections::{HashMap, HashSet};
60
61#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
63pub struct Point { pub x: f32, pub y: f32 }
64
65#[derive(Debug, Clone, PartialEq, PartialOrd)]
67pub struct Line { pub points: Vec<Point> }
68
69#[derive(Debug, Clone, PartialEq)]
71pub struct Field<'a> {
72 pub dimensions: (usize, usize),
74 pub top_left: Point,
76 pub pixel_size: (f32, f32),
78 pub values: &'a Vec<Vec<i16>>,
80}
81
82impl<'a> Field<'a> {
83 pub fn get_contours(&self, z: i16) -> Vec<Line> {
84 #[cfg(not(feature = "parallel"))] {
85 march(self, z)
86 }
87 #[cfg(feature = "parallel")] {
88 march_parallel(self, z)
89 }
90 }
91}
92
93type SegmentsMap = HashMap<(u64, u64), Vec<(Point, Point)>>;
101
102fn add_seg(segments: &mut SegmentsMap, start: Point, end: Point) {
103 segments
104 .entry((start.x as u64, start.y as u64))
105 .or_default()
106 .push((start, end));
107}
108
109#[cfg(not(feature = "parallel"))]
111#[inline]
112fn march<'a>(field: &Field<'a>, z: i16) -> Vec<Line> {
113
114 let (width, height) = field.dimensions;
115
116 let mut segments: SegmentsMap = HashMap::new();
117
118 field.values
119 .windows(2)
120 .filter_map(|r| match &r { &[cur, next] => { Some((cur, next)) }, _ => None })
121 .enumerate()
122 .for_each(|(cur_y, (current_row_zs, next_row_zs))| {
123 current_row_zs
124 .windows(2)
125 .filter_map(|c| match &c { &[cur, next] => { Some((cur, next)) }, _ => None })
126 .enumerate()
127 .map(|(cur_x, (cur, next))| (cur_x, cur_y, cur, next))
128 .zip(
129 next_row_zs
130 .windows(2)
131 .filter_map(|c| match &c { &[cur, next] => { Some((cur, next)) }, _ => None })
132 )
133 .for_each(|((x, y, ulz, urz), (blz, brz))| {
134 let (a, b) = get_segments(
135 (x, y),
136 (width, height),
137 (*ulz, *urz),
138 (*blz, *brz),
139 z
140 );
141 if let Some(a) = a {
142 add_seg(&mut segments, a.0, a.1);
143 }
144 if let Some(b) = b {
145 add_seg(&mut segments, b.0, b.1);
146 }
147 })
148 });
149
150 let mut contours = build_contours(segments, (width as u64, height as u64));
151 normalize_contours(&mut contours, field.top_left, field.pixel_size);
152 contours
153}
154
155#[inline(always)]
156fn get_segments(
157 (x, y): (usize, usize),
158 (width, height): (usize, usize),
159 (ulz, urz): (i16, i16),
160 (blz, brz): (i16, i16),
161 z: i16
162) -> (Option<(Point, Point)>, Option<(Point, Point)>) {
163
164 let mut case = 0_u8;
165
166 if blz > z { case |= 1; }
167 if brz > z { case |= 2; }
168 if urz > z { case |= 4; }
169 if ulz > z { case |= 8; }
170
171 let x = x as f32;
172 let y = y as f32;
173
174 let top = Point { x: x + fraction(z, (ulz, urz)), y };
175 let bottom = Point { x: x + fraction(z, (blz, brz)), y: y + 1.0 };
176 let left = Point { x, y: y + fraction(z, (ulz, blz)) };
177 let right = Point { x: x + 1.0, y: y + fraction(z, (urz, brz)) };
178
179 match case {
180 1 => (Some((bottom, left)), None),
181 2 => (Some((right, bottom)), None),
182 3 => (Some((right, left)), None),
183 4 => (Some((top, right)), None),
184 5 => (Some((top, left)), Some((bottom, right))),
185 6 => (Some((top, bottom)), None),
186 7 => (Some((top, left)), None),
187 8 => (Some((left, top)), None),
188 9 => (Some((bottom, top)), None),
189 10 => (Some((left, bottom)), Some((right, top))),
190 11 => (Some((right, top)), None),
191 12 => (Some((left, right)), None),
192 13 => (Some((bottom, right)), None),
193 14 => (Some((left, bottom)), None),
194 _ => (None, None),
195 }
196}
197
198#[cfg(feature = "parallel")]
199#[inline]
200fn march_parallel<'a>(field: &Field<'a>, z: i16) -> Vec<Line> {
201 use rayon::prelude::ParallelSlice;
202 use rayon::iter::ParallelIterator;
203 use rayon::iter::IndexedParallelIterator;
204
205 let (width, height) = field.dimensions;
206
207 let duplicated_items = field.values
208 .par_windows(2)
209 .enumerate()
210 .filter_map(|(cur_y, r)| match &r { &[cur, next] => { Some((cur_y, cur, next)) }, _ => None })
211 .map(|(cur_y, current_row_zs, next_row_zs)| {
212 current_row_zs
213 .par_windows(2)
214 .enumerate()
215 .filter_map(|(cur_x, c)| match &c { &[cur, next] => { Some((cur_x, cur, next)) }, _ => None })
216 .filter_map(|(cur_x, ulz, urz)| {
217 let blz = next_row_zs.get(cur_x)?;
218 let brz = next_row_zs.get(cur_x + 1)?;
219 Some(get_segments(
220 (cur_x, cur_y),
221 (width, height),
222 (*ulz, *urz),
223 (*blz, *brz),
224 z
225 ))
226 })
227 .collect::<Vec<_>>()
228 })
229 .collect::<Vec<Vec<(Option<(Point, Point)>, Option<(Point, Point)>)>>>();
230
231 let mut segments: SegmentsMap = HashMap::new();
232
233 for r in duplicated_items {
234 for (a, b) in r {
235 if let Some(a) = a {
236 add_seg(&mut segments, a.0, a.1);
237 }
238 if let Some(b) = b {
239 add_seg(&mut segments, b.0, b.1);
240 }
241 }
242 }
243
244 let mut contours = build_contours(segments, (width as u64, height as u64));
245 normalize_contours(&mut contours, field.top_left, field.pixel_size);
246 contours
247}
248
249#[inline]
250fn build_contours(mut segments: SegmentsMap, (w, h): (u64, u64)) -> Vec<Line> {
251 use std::collections::hash_map::Entry;
252
253 let mut contours = vec![];
254
255 let mut boundaries = segments
256 .keys()
257 .cloned()
258 .filter(|s| s.0 == 0 || s.0 == w - 1 || s.1 == 0 || s.1 == h - 1)
259 .collect::<HashSet<_>>();
260
261 while !segments.is_empty() {
262 let first_k = boundaries
266 .iter()
267 .next()
268 .map_or_else(|| *segments.keys().next().unwrap(), |k| *k);
269
270 let mut first_e = match segments.entry(first_k) {
271 Entry::Occupied(o) => o,
272 Entry::Vacant(_) => unreachable!(),
273 };
274
275 let first = first_e.get_mut().pop().unwrap();
276 if first_e.get().is_empty() {
277 first_e.remove_entry();
278 boundaries.remove(&first_k);
279 }
280
281 let mut contour = vec![first.0, first.1];
282
283 loop {
284 let prev = contour[contour.len() - 1];
285
286 let segments_k = (prev.x as u64, prev.y as u64);
287 let mut segments = match segments.entry(segments_k) {
288 Entry::Vacant(_) => break,
289 Entry::Occupied(o) => o,
290 };
291
292 let next = segments
293 .get()
294 .iter()
295 .enumerate()
296 .find(|(_, (s, _))| s == &prev);
297
298 match next {
299 None => break,
300 Some((i, seg)) => {
301 contour.push(seg.1);
302
303 segments.get_mut().swap_remove(i);
304 if segments.get().is_empty() {
305 segments.remove_entry();
306 boundaries.remove(&segments_k);
307 }
308 }
309 }
310 }
311
312 contours.push(Line { points: contour });
313 }
314
315 contours
316}
317
318#[inline]
319fn fraction(z: i16, (z0, z1): (i16, i16)) -> f32 {
320 if z0 == z1 {
321 return 0.5;
322 }
323
324 let t = (z - z0) as f32 / (z1 - z0) as f32;
325 t.max(0.0).min(1.0)
326}
327
328#[cfg(not(feature = "parallel"))]
329fn normalize_contours(
330 lines: &mut Vec<Line>,
331 top_left: Point,
332 pixel_size: (f32, f32)
333) {
334 for l in lines.iter_mut() {
335 for c in l.points.iter_mut() {
336 c.x = top_left.x + (pixel_size.0 * c.x);
337 c.y = top_left.y + (pixel_size.1 * c.y);
338 }
339 }
340}
341
342#[cfg(feature = "parallel")]
343fn normalize_contours(
344 lines: &mut Vec<Line>,
345 top_left: Point,
346 pixel_size: (f32, f32)
347) {
348 use rayon::iter::{ParallelIterator, IntoParallelRefMutIterator};
349 lines.par_iter_mut().for_each(|l| {
350 l.points.par_iter_mut().for_each(|c| {
351 c.x = top_left.x + (pixel_size.0 * c.x);
352 c.y = top_left.y + (pixel_size.1 * c.y);
353 });
354 });
355}