use alloc::{collections::BTreeMap, rc::Rc};
use core::cell::RefCell;
use libm::{ceil, fabs, round};
use s2json::{Point, VectorPoint};
static MS_LUT: &[&[u8]] = &[
&[], &[3, 2], &[2, 1], &[3, 1], &[1, 0], &[3, 0, 1, 2], &[2, 0], &[3, 0], &[3, 0], &[2, 0], &[3, 2, 1, 0], &[1, 0], &[3, 1], &[2, 1], &[3, 2], &[], ];
#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
pub struct OrderedF64(pub f64);
impl Eq for OrderedF64 {}
impl Ord for OrderedF64 {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.partial_cmp(&other.0).unwrap_or(std::cmp::Ordering::Equal)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct GridPoint {
pub x: i32,
pub y: i32,
}
impl From<GridPoint> for VectorPoint {
fn from(value: GridPoint) -> Self {
VectorPoint::from_xy(value.x as f64 / 32_768., value.y as f64 / 32_768.)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct IsolineSegment {
pub from: GridPoint,
pub to: GridPoint,
pub visited: bool,
}
impl IsolineSegment {
pub fn new(from: GridPoint, to: GridPoint) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(IsolineSegment { from, to, visited: false }))
}
}
pub type MarchingSquaresResult = BTreeMap<OrderedF64, Vec<Rc<RefCell<IsolineSegment>>>>;
pub fn marching_squares(
heightmap: &[f64],
width: usize,
height: usize,
padding: f64,
thresholds: &[f64],
) -> MarchingSquaresResult {
let mut all_segments_by_level: MarchingSquaresResult = BTreeMap::new();
for y in 0..height - 1 {
for x in 0..width - 1 {
let h0 = heightmap[y * width + x];
let h1 = heightmap[y * width + (x + 1)];
let h2 = heightmap[(y + 1) * width + (x + 1)];
let h3 = heightmap[(y + 1) * width + x];
let corners = [h0, h1, h2, h3];
let min = corners.into_iter().reduce(f64::min).unwrap();
let max = corners.into_iter().reduce(f64::max).unwrap();
for t in thresholds {
if *t >= min && *t <= max {
let segments = march_cell(
&corners,
width as f64,
height as f64,
padding,
x as f64,
y as f64,
*t,
);
if !segments.is_empty() {
let level_segments =
all_segments_by_level.entry(OrderedF64(*t)).or_default();
level_segments.extend(segments);
}
}
}
}
}
all_segments_by_level
}
fn march_cell(
corners: &[f64],
width: f64,
height: f64,
padding: f64,
x: f64,
y: f64,
t: f64,
) -> Vec<Rc<RefCell<IsolineSegment>>> {
let mut case_index = 0;
if corners[0] >= t {
case_index |= 8;
} if corners[1] >= t {
case_index |= 4;
} if corners[2] >= t {
case_index |= 2;
} if corners[3] >= t {
case_index |= 1;
}
let edges = MS_LUT[case_index];
let mut segments: Vec<Rc<RefCell<IsolineSegment>>> = vec![];
if edges.len() == 0 {
return segments;
}
for i in (0..edges.len()).step_by(2) {
let from = interpolate(edges[i], corners, width, height, padding, x, y, t);
let to = interpolate(edges[i + 1], corners, width, height, padding, x, y, t);
segments.push(IsolineSegment::new(from, to));
}
segments
}
fn interpolate(
edge: u8,
corners: &[f64],
width: f64,
height: f64,
padding: f64,
x: f64,
y: f64,
t: f64,
) -> GridPoint {
let point: Point;
if edge == 0 {
point = Point(x + safe_interp(corners[0], corners[1], t), y); } else if edge == 1 {
point = Point(x + 1., y + safe_interp(corners[1], corners[2], t)); } else if edge == 2 {
point = Point(x + safe_interp(corners[3], corners[2], t), y + 1.); } else if edge == 3 {
point = Point(x, y + safe_interp(corners[0], corners[3], t)); } else {
point = Point(x, y); }
remap(point, width, height, padding)
}
fn safe_interp(v1: f64, v2: f64, t: f64) -> f64 {
if fabs(v1 - v2) < 1e-10 {
return 0.5;
}
return (t - v1) / (v2 - v1);
}
fn remap(point: Point, width: f64, height: f64, padding: f64) -> GridPoint {
let active_width = width - 1. - 2. * padding;
let active_height = height - 1. - 2. * padding;
GridPoint {
x: round(((point.0 + 0.5 - padding) * 32_768.) / active_width) as i32,
y: round(((point.1 + 0.5 - padding) * 32_768.) / active_height) as i32,
}
}