#[derive(Debug, Clone, PartialEq)]
pub struct Contour {
pub level: f64,
pub x: Vec<f64>,
pub y: Vec<f64>,
}
pub fn contours(values: &[f64], columns: usize, levels: &[f64]) -> Vec<Contour> {
assert!(
columns > 0 && values.len().is_multiple_of(columns),
"contours requires a rectangular grid"
);
let rows = values.len() / columns;
levels
.iter()
.map(|&level| {
let mut line = Contour {
level,
x: Vec::new(),
y: Vec::new(),
};
for r in 0..rows.saturating_sub(1) {
for c in 0..columns - 1 {
let corners = [
values[r * columns + c], values[r * columns + c + 1], values[(r + 1) * columns + c + 1], values[(r + 1) * columns + c], ];
if corners.iter().any(|v| !v.is_finite()) {
continue;
}
march(&mut line, (c as f64, r as f64), corners, level);
}
}
line
})
.collect()
}
const EDGES: [Edge; 4] = [
Edge {
from: (0.0, 0.0),
to: (1.0, 0.0),
a: 0,
b: 1,
},
Edge {
from: (1.0, 0.0),
to: (1.0, 1.0),
a: 1,
b: 2,
},
Edge {
from: (0.0, 1.0),
to: (1.0, 1.0),
a: 3,
b: 2,
},
Edge {
from: (0.0, 0.0),
to: (0.0, 1.0),
a: 0,
b: 3,
},
];
struct Edge {
from: (f64, f64),
to: (f64, f64),
a: usize,
b: usize,
}
fn march(line: &mut Contour, origin: (f64, f64), corners: [f64; 4], level: f64) {
let case = corners
.iter()
.enumerate()
.filter(|&(_, &v)| v >= level)
.fold(0usize, |bits, (index, _)| bits | 1 << index);
let center_inside = corners.iter().sum::<f64>() / 4.0 >= level;
let segments: &[(usize, usize)] = match case {
0 | 15 => &[],
1 | 14 => &[(3, 0)],
2 | 13 => &[(0, 1)],
3 | 12 => &[(3, 1)],
4 | 11 => &[(1, 2)],
6 | 9 => &[(0, 2)],
7 | 8 => &[(2, 3)],
5 => {
if center_inside {
&[(0, 1), (2, 3)]
} else {
&[(3, 0), (1, 2)]
}
}
_ => {
if center_inside {
&[(3, 0), (1, 2)]
} else {
&[(0, 1), (2, 3)]
}
}
};
for &(from, to) in segments {
for edge in [from, to] {
let (x, y) = crossing(edge, corners, level);
line.x.push(origin.0 + x);
line.y.push(origin.1 + y);
}
line.x.push(f64::NAN);
line.y.push(f64::NAN);
}
}
fn crossing(edge: usize, corners: [f64; 4], level: f64) -> (f64, f64) {
let Edge { from, to, a, b } = EDGES[edge];
let (a, b) = (corners[a], corners[b]);
let t = crate::numeric::inverse_lerp(a, b, level);
(
crate::numeric::lerp(from.0, to.0, t),
crate::numeric::lerp(from.1, to.1, t),
)
}
#[cfg(test)]
#[path = "tests/contour_tests.rs"]
mod tests;