use crate::series::Origin;
pub type Point = (f64, f64);
pub type Segment = (Point, Point);
pub fn segments(
grid: &[Vec<f64>],
x_edges: &[f64],
y_edges: &[f64],
levels: &[f64],
origin: Origin,
) -> Vec<(f64, Vec<Segment>)> {
let rows = grid.len();
if rows < 2 {
return Vec::new();
}
let cols = grid[0].len();
if cols < 2 {
return Vec::new();
}
let y_idx = |r: usize| match origin {
Origin::TopLeft => rows - 1 - r,
Origin::BottomLeft => r,
};
levels
.iter()
.map(|&level| {
let mut segs = Vec::new();
for row in 0..(rows - 1) {
for col in 0..(cols - 1) {
cell_segments(
grid, x_edges, y_edges, &y_idx, row, col, level, &mut segs,
);
}
}
(level, segs)
})
.collect()
}
fn cell_segments<F>(
grid: &[Vec<f64>],
x_edges: &[f64],
y_edges: &[f64],
y_idx: &F,
row: usize,
col: usize,
level: f64,
out: &mut Vec<Segment>,
) where
F: Fn(usize) -> usize,
{
let x_l = x_edges[col];
let x_r = x_edges[col + 1];
let y_row = y_idx(row);
let y_row_next = y_idx(row + 1);
let y_b = y_edges[y_row.min(y_row_next)];
let y_t = y_edges[y_row.max(y_row_next)];
let (row_b, row_t) = if y_row < y_row_next {
(row, row + 1)
} else {
(row + 1, row)
};
let bl = grid[row_b][col];
let br = grid[row_b][col + 1];
let tr = grid[row_t][col + 1];
let tl = grid[row_t][col];
let mut idx = 0u8;
if bl >= level {
idx |= 1;
}
if br >= level {
idx |= 2;
}
if tr >= level {
idx |= 4;
}
if tl >= level {
idx |= 8;
}
let bottom = || (interp(x_l, x_r, bl, br, level), y_b);
let right = || (x_r, interp(y_b, y_t, br, tr, level));
let top = || (interp(x_r, x_l, tr, tl, level), y_t);
let left = || (x_l, interp(y_t, y_b, tl, bl, level));
match idx {
0 | 15 => {}
1 => out.push((left(), bottom())),
2 => out.push((bottom(), right())),
3 => out.push((left(), right())),
4 => out.push((right(), top())),
6 => out.push((bottom(), top())),
7 => out.push((left(), top())),
8 => out.push((top(), left())),
9 => out.push((top(), bottom())),
11 => out.push((top(), right())),
12 => out.push((right(), left())),
13 => out.push((right(), bottom())),
14 => out.push((bottom(), left())),
5 => {
if (bl + br + tr + tl) / 4.0 >= level {
out.push((left(), top()));
out.push((right(), bottom()));
} else {
out.push((left(), bottom()));
out.push((right(), top()));
}
}
10 => {
if (bl + br + tr + tl) / 4.0 >= level {
out.push((bottom(), left()));
out.push((top(), right()));
} else {
out.push((bottom(), right()));
out.push((top(), left()));
}
}
_ => {}
}
}
fn interp(x1: f64, x2: f64, v1: f64, v2: f64, level: f64) -> f64 {
if v1 == v2 {
return (x1 + x2) / 2.0;
}
let t = (level - v1) / (v2 - v1);
x1 + t * (x2 - x1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flat_grid_produces_no_segments_off_level() {
let grid = vec![vec![0.0, 0.0], vec![0.0, 0.0]];
let edges = vec![0.0, 1.0];
let result = segments(&grid, &edges, &edges, &[0.5], Origin::BottomLeft);
assert_eq!(result[0].1.len(), 0);
}
#[test]
fn single_cell_with_one_corner_above_emits_segment() {
let grid = vec![vec![0.0, 0.0], vec![1.0, 0.0]];
let edges = vec![0.0, 1.0];
let result = segments(&grid, &edges, &edges, &[0.5], Origin::BottomLeft);
assert_eq!(result[0].1.len(), 1);
}
}