louis-e/arnis · error
NaN encountered while sorting scanline intersections
Error message
NaN encountered while sorting scanline intersections
What it means
`compute_scanline_spans` sorts the x-coordinates where polygon edges cross a scanline using `a.partial_cmp(b).expect("NaN encountered while sorting scanline intersections")`. Since f64 has no total order, a NaN intersection coordinate makes `partial_cmp` return None and the code panics — NaN can only come from upstream geometry (malformed polygon vertices or a degenerate edge producing 0/0).
Source
Thrown at src/element_processing/water_areas.rs:357
max_x: i32,
) -> Vec<(i32, i32)> {
let mut xs: Vec<f64> = Vec::new();
for edge in edges {
// Crossing test: (z1 > z) != (z2 > z)
// Matches geo's convention (bottom-inclusive, top-exclusive).
if (edge.z1 > z) != (edge.z2 > z) {
let t = (z - edge.z1) / (edge.z2 - edge.z1);
xs.push(edge.x1 + t * (edge.x2 - edge.x1));
}
}
if xs.is_empty() {
return Vec::new();
}
xs.sort_unstable_by(|a, b| {
a.partial_cmp(b)
.expect("NaN encountered while sorting scanline intersections")
});
debug_assert!(
xs.len().is_multiple_of(2),
"Odd number of scanline crossings ({}) at z={}, possible malformed polygon",
xs.len(),
z
);
// Pair consecutive crossings into fill spans (even-odd rule)
let mut spans = Vec::with_capacity(xs.len() / 2);
let mut i = 0;
while i + 1 < xs.len() {
let start = (xs[i].ceil() as i32).max(min_x);
let end = (xs[i + 1].floor() as i32).min(max_x);
if start <= end {
spans.push((start, end));
}View on GitHub (pinned to 34048924d9)
Solutions
- Filter or fix polygon vertices upstream: reject/repair polygons containing NaN or infinite coordinates before scanline conversion
- Skip degenerate edges (zero height) when computing intersections so no 0/0 occurs
- Validate the projection/transform output for NaN before building polygons
- As a stopgap, replace the expect with a total-order fallback (e.g. `a.partial_cmp(b).unwrap_or(Ordering::Equal)`) and log the offending polygon
Example fix
// before
.expect("NaN encountered while sorting scanline intersections")
// after
.unwrap_or_else(|| {
eprintln!("NaN in scanline at z={z}; skipping polygon");
std::cmp::Ordering::Equal
}) Defensive patterns
Strategy: validation
Validate before calling
fn polygon_has_finite_coords(poly: &[[f64; 2]]) -> bool { poly.iter().all(|p| p[0].is_finite() && p[1].is_finite()) } Type guard
fn is_finite_point(p: &[f64; 2]) -> bool { p[0].is_finite() && p[1].is_finite() } Try / catch
// If you must sort possibly-NaN floats: xs.sort_unstable_by(|a,b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Prevention
- Reject polygons containing NaN/inf coordinates at import time
- Skip zero-height edges when computing scanline intersections
- Validate projection outputs for finiteness before geometry ops
When it happens
Trigger: A water-area polygon with NaN or infinite vertex coordinates (bad/missing OSM tags, division by zero when intersecting a horizontal scanline with a degenerate vertical edge), producing NaN scanline intersections passed into the sort.
Common situations: Importing map data where a node coordinate is NaN; zero-length edges in the polygon; a coordinate transform (projection) producing NaN for points outside its domain.
Related errors
- Invalid id
- Failed to create tile XZBBox
- bundled font atlas is valid
- scrap metal list is non-empty
- glass color array is non-empty
AI-assisted analysis of louis-e/arnis@34048924d9 (2026-09-03).
Data as JSON: /api/errors/1fb2ce8b7ac5fbf3.
Report an issue: GitHub.