use egui::{Pos2, Rect, Vec2};
const TARGET_PER_CELL: f32 = 8.0;
#[derive(Clone, Debug)]
pub struct ScreenGrid {
origin: Pos2,
cell: f32,
cols: usize,
rows: usize,
bins: Vec<Vec<u32>>,
len: usize,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NearestHit {
pub index: u32,
pub dist: f32,
pub candidates: usize,
}
impl ScreenGrid {
#[must_use]
pub fn build(points: &[Pos2]) -> Self {
Self::build_inner(points, None)
}
#[must_use]
pub fn build_with_cell(points: &[Pos2], cell: f32) -> Self {
Self::build_inner(points, Some(cell))
}
fn build_inner(points: &[Pos2], forced_cell: Option<f32>) -> Self {
let finite = |p: &Pos2| p.x.is_finite() && p.y.is_finite();
let mut min = Pos2::new(f32::INFINITY, f32::INFINITY);
let mut max = Pos2::new(f32::NEG_INFINITY, f32::NEG_INFINITY);
let mut n_finite = 0usize;
for p in points.iter().filter(|p| finite(p)) {
n_finite += 1;
min.x = min.x.min(p.x);
min.y = min.y.min(p.y);
max.x = max.x.max(p.x);
max.y = max.y.max(p.y);
}
if n_finite == 0 {
return Self {
origin: Pos2::ZERO,
cell: 1.0,
cols: 0,
rows: 0,
bins: Vec::new(),
len: points.len(),
};
}
let span_x = (max.x - min.x).max(1.0);
let span_y = (max.y - min.y).max(1.0);
let cell = match forced_cell {
Some(c) if c.is_finite() && c > 0.0 => c,
_ => {
let target_cells = (n_finite as f32 / TARGET_PER_CELL).max(1.0);
((span_x * span_y) / target_cells).sqrt().max(1e-3)
}
};
const MAX_CELLS: usize = 1 << 22;
let mut cols = ((span_x / cell).ceil() as usize + 1).max(1);
let mut rows = ((span_y / cell).ceil() as usize + 1).max(1);
let mut cell = cell;
while cols.saturating_mul(rows) > MAX_CELLS {
cell *= 2.0;
cols = ((span_x / cell).ceil() as usize + 1).max(1);
rows = ((span_y / cell).ceil() as usize + 1).max(1);
}
let mut bins: Vec<Vec<u32>> = vec![Vec::new(); cols * rows];
for (i, p) in points.iter().enumerate() {
if !finite(p) {
continue;
}
let gx = (((p.x - min.x) / cell).floor() as isize).clamp(0, cols as isize - 1) as usize;
let gy = (((p.y - min.y) / cell).floor() as isize).clamp(0, rows as isize - 1) as usize;
bins[gy * cols + gx].push(i as u32);
}
Self { origin: min, cell, cols, rows, bins, len: points.len() }
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn dims(&self) -> (usize, usize) {
(self.cols, self.rows)
}
#[must_use]
pub fn cell(&self) -> f32 {
self.cell
}
#[must_use]
pub fn max_occupancy(&self) -> usize {
self.bins.iter().map(Vec::len).max().unwrap_or(0)
}
fn cell_of(&self, p: Pos2) -> (isize, isize) {
(
((p.x - self.origin.x) / self.cell).floor() as isize,
((p.y - self.origin.y) / self.cell).floor() as isize,
)
}
pub fn query_rect(&self, r: Rect, out: &mut Vec<u32>) -> usize {
if self.cols == 0 || self.rows == 0 {
return 0;
}
let (x0, y0) = self.cell_of(r.min);
let (x1, y1) = self.cell_of(r.max);
let x0 = x0.clamp(0, self.cols as isize - 1) as usize;
let x1 = x1.clamp(0, self.cols as isize - 1) as usize;
let y0 = y0.clamp(0, self.rows as isize - 1) as usize;
let y1 = y1.clamp(0, self.rows as isize - 1) as usize;
if r.max.x < self.origin.x
|| r.max.y < self.origin.y
|| r.min.x > self.origin.x + self.cols as f32 * self.cell
|| r.min.y > self.origin.y + self.rows as f32 * self.cell
{
return 0;
}
let before = out.len();
for gy in y0..=y1 {
for gx in x0..=x1 {
out.extend_from_slice(&self.bins[gy * self.cols + gx]);
}
}
out[before..].sort_unstable();
out.len() - before
}
#[must_use]
pub fn nearest(&self, points: &[Pos2], probe: Pos2, max_dist: f32) -> Option<NearestHit> {
if self.cols == 0 || self.rows == 0 || !probe.x.is_finite() || !probe.y.is_finite() {
return None;
}
let max_dist = if max_dist.is_finite() && max_dist > 0.0 { max_dist } else { return None };
let (cx, cy) = self.cell_of(probe);
let max_ring = ((max_dist / self.cell).ceil() as isize).max(0);
let mut best: Option<(u32, f32)> = None;
let mut candidates = 0usize;
for ring in 0..=max_ring {
if let Some((_, d)) = best {
if (ring as f32 - 1.0) * self.cell > d {
break;
}
}
let visit = |gx: isize, gy: isize, best: &mut Option<(u32, f32)>, cand: &mut usize| {
if gx < 0 || gy < 0 || gx >= self.cols as isize || gy >= self.rows as isize {
return;
}
for &i in &self.bins[gy as usize * self.cols + gx as usize] {
let Some(p) = points.get(i as usize) else { continue };
*cand += 1;
let d = probe.distance(*p);
if d > max_dist {
continue;
}
match best {
Some((bi, bd)) if *bd < d || (*bd == d && *bi <= i) => {}
_ => *best = Some((i, d)),
}
}
};
if ring == 0 {
visit(cx, cy, &mut best, &mut candidates);
} else {
for gx in (cx - ring)..=(cx + ring) {
visit(gx, cy - ring, &mut best, &mut candidates);
visit(gx, cy + ring, &mut best, &mut candidates);
}
for gy in (cy - ring + 1)..=(cy + ring - 1) {
visit(cx - ring, gy, &mut best, &mut candidates);
visit(cx + ring, gy, &mut best, &mut candidates);
}
}
}
best.map(|(index, dist)| NearestHit { index, dist, candidates })
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LabelCandidate {
pub anchor: Pos2,
pub size: Vec2,
pub priority: f32,
pub pinned: bool,
}
impl LabelCandidate {
#[must_use]
pub fn new(anchor: Pos2, size: Vec2, priority: f32) -> Self {
Self { anchor, size, priority, pinned: false }
}
#[must_use]
pub fn pinned(mut self, on: bool) -> Self {
self.pinned = on;
self
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PlacedLabel {
pub index: usize,
pub rect: Rect,
pub slot: u8,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct LabelPlacement {
pub placed: Vec<PlacedLabel>,
pub suppressed: usize,
pub considered: usize,
pub collision_tests: usize,
}
impl LabelPlacement {
#[must_use]
pub fn density(&self) -> f32 {
if self.considered == 0 { 0.0 } else { self.placed.len() as f32 / self.considered as f32 }
}
#[must_use]
pub fn state_json(&self) -> serde_json::Value {
serde_json::json!({
"placed": self.placed.len(),
"suppressed": self.suppressed,
"considered": self.considered,
"collision_tests": self.collision_tests,
})
}
}
#[derive(Clone, Copy, Debug)]
pub struct PlaceOpts {
pub pad: f32,
pub variable: bool,
pub clip_to_viewport: bool,
pub max_considered: usize,
pub max_placed: usize,
}
impl Default for PlaceOpts {
fn default() -> Self {
Self { pad: 2.0, variable: true, clip_to_viewport: false, max_considered: 0, max_placed: 0 }
}
}
#[must_use]
pub fn place_labels(cands: &[LabelCandidate], viewport: Rect, opts: PlaceOpts) -> LabelPlacement {
let mut out = LabelPlacement::default();
if cands.is_empty() {
return out;
}
let mut order: Vec<usize> = (0..cands.len()).collect();
order.sort_by(|&a, &b| {
let (ca, cb) = (&cands[a], &cands[b]);
cb.pinned
.cmp(&ca.pinned)
.then(cb.priority.total_cmp(&ca.priority))
.then(a.cmp(&b))
});
let max_w = cands.iter().map(|c| c.size.x + 2.0 * opts.pad).fold(1.0f32, f32::max);
let max_h = cands.iter().map(|c| c.size.y + 2.0 * opts.pad).fold(1.0f32, f32::max);
let cell = max_w.max(max_h).max(1.0);
let cols = ((viewport.width() / cell).ceil() as usize + 2).max(1);
let rows = ((viewport.height() / cell).ceil() as usize + 2).max(1);
let mut bins: Vec<Vec<u32>> = vec![Vec::new(); cols * rows];
let mut placed_rects: Vec<Rect> = Vec::with_capacity(cands.len());
let bin_of = |p: Pos2| -> (usize, usize) {
let gx = ((p.x - viewport.min.x) / cell).floor();
let gy = ((p.y - viewport.min.y) / cell).floor();
(
(gx.max(0.0) as usize).min(cols - 1),
(gy.max(0.0) as usize).min(rows - 1),
)
};
for &i in &order {
if opts.max_considered > 0 && out.considered >= opts.max_considered {
break;
}
let c = &cands[i];
if !c.anchor.x.is_finite() || !c.anchor.y.is_finite() {
out.suppressed += 1;
continue;
}
let half = (c.size + Vec2::splat(2.0 * opts.pad)) * 0.5;
if !viewport.expand2(half).contains(c.anchor) {
continue;
}
out.considered += 1;
if !c.pinned && opts.max_placed > 0 && out.placed.len() >= opts.max_placed {
out.suppressed += 1;
continue;
}
let ladder: &[Vec2] = if opts.variable {
&[
Vec2::ZERO,
Vec2::new(0.0, 1.0),
Vec2::new(0.0, -1.0),
Vec2::new(1.0, 0.0),
Vec2::new(-1.0, 0.0),
]
} else {
&[Vec2::ZERO]
};
let mut chosen: Option<(Rect, u8)> = None;
for (slot, dir) in ladder.iter().enumerate() {
let centre = c.anchor + Vec2::new(dir.x * (half.x * 2.0 + 1.0), dir.y * (half.y * 2.0 + 1.0));
let r = Rect::from_center_size(centre, half * 2.0);
if opts.clip_to_viewport && !viewport.contains_rect(r) {
continue;
}
if c.pinned && slot == 0 {
chosen = Some((r, 0));
break;
}
let (bx0, by0) = bin_of(r.min);
let (bx1, by1) = bin_of(r.max);
let mut collides = false;
'scan: for gy in by0..=by1 {
for gx in bx0..=bx1 {
for &pi in &bins[gy * cols + gx] {
out.collision_tests += 1;
if placed_rects[pi as usize].intersects(r) {
collides = true;
break 'scan;
}
}
}
}
if !collides {
chosen = Some((r, slot as u8));
break;
}
}
match chosen {
Some((rect, slot)) => {
let pi = placed_rects.len() as u32;
placed_rects.push(rect);
let (bx0, by0) = bin_of(rect.min);
let (bx1, by1) = bin_of(rect.max);
for gy in by0..=by1 {
for gx in bx0..=bx1 {
bins[gy * cols + gx].push(pi);
}
}
out.placed.push(PlacedLabel { index: i, rect, slot });
}
None => out.suppressed += 1,
}
}
out
}
#[must_use]
pub fn max_label_overlap(rects: &[Rect], viewport: Rect, scale: f32) -> u32 {
let scale = if scale.is_finite() && scale > 0.0 { scale } else { 1.0 };
let w = ((viewport.width() / scale).ceil() as usize).clamp(1, 4096);
let h = ((viewport.height() / scale).ceil() as usize).clamp(1, 4096);
let mut cover = vec![0u32; w * h];
let mut peak = 0u32;
for r in rects {
let x0 = (((r.min.x - viewport.min.x) / scale).round() as isize).clamp(0, w as isize) as usize;
let x1 = (((r.max.x - viewport.min.x) / scale).round() as isize).clamp(0, w as isize) as usize;
let y0 = (((r.min.y - viewport.min.y) / scale).round() as isize).clamp(0, h as isize) as usize;
let y1 = (((r.max.y - viewport.min.y) / scale).round() as isize).clamp(0, h as isize) as usize;
for y in y0..y1 {
for x in x0..x1 {
let c = &mut cover[y * w + x];
*c += 1;
peak = peak.max(*c);
}
}
}
peak
}
const DIR_BUCKETS: usize = 4;
#[derive(Clone, Copy, Debug)]
pub struct EdgeThinOpts {
pub cell: f32,
pub keep_per_bucket: usize,
}
impl Default for EdgeThinOpts {
fn default() -> Self {
Self { cell: 24.0, keep_per_bucket: 3 }
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct EdgeThinResult {
pub keep: Vec<u32>,
pub dropped: usize,
pub considered: usize,
pub peak_density_before: usize,
pub peak_density: usize,
}
impl EdgeThinResult {
#[must_use]
pub fn state_json(&self) -> serde_json::Value {
serde_json::json!({
"kept": self.keep.len(),
"dropped": self.dropped,
"considered": self.considered,
"peak_density_before": self.peak_density_before,
"peak_density": self.peak_density,
})
}
}
#[inline]
fn monotonic_key(v: f32) -> u32 {
let b = v.to_bits();
if b & 0x8000_0000 != 0 { !b } else { b ^ 0x8000_0000 }
}
fn walk_bins(
a: Pos2,
b: Pos2,
origin: Pos2,
cell: f32,
cols: usize,
rows: usize,
mut f: impl FnMut(usize),
) {
let inv = 1.0 / cell;
let (ax, ay) = ((a.x - origin.x) * inv, (a.y - origin.y) * inv);
let (bx, by) = ((b.x - origin.x) * inv, (b.y - origin.y) * inv);
let steps = ((bx - ax).abs().max((by - ay).abs()).ceil() as usize).clamp(1, 8192);
let inv_steps = 1.0 / steps as f32;
let (dx, dy) = ((bx - ax) * inv_steps, (by - ay) * inv_steps);
let (mut x, mut y) = (ax, ay);
let (fcols, frows) = (cols as f32, rows as f32);
let mut last = usize::MAX;
for _ in 0..=steps {
if x >= 0.0 && y >= 0.0 && x < fcols && y < frows {
let idx = y as usize * cols + x as usize;
if idx != last {
f(idx);
last = idx;
}
}
x += dx;
y += dy;
}
}
#[must_use]
pub fn thin_edges(
segments: &[(Pos2, Pos2)],
weight: &[f32],
always: &[bool],
viewport: Rect,
opts: EdgeThinOpts,
) -> EdgeThinResult {
let mut out = EdgeThinResult::default();
if segments.is_empty() {
return out;
}
let cell = if opts.cell.is_finite() && opts.cell > 0.0 { opts.cell } else { 24.0 };
let cols = ((viewport.width() / cell).ceil() as usize + 1).clamp(1, 8192);
let rows = ((viewport.height() / cell).ceil() as usize + 1).clamp(1, 8192);
let origin = viewport.min;
let finite = |p: Pos2| p.x.is_finite() && p.y.is_finite();
let dir_of = |a: Pos2, b: Pos2| -> usize {
let folded = (b.y - a.y).atan2(b.x - a.x).rem_euclid(std::f32::consts::PI);
(((folded / std::f32::consts::PI) * DIR_BUCKETS as f32).floor() as usize).min(DIR_BUCKETS - 1)
};
let mut on_screen: Vec<u32> = Vec::with_capacity(segments.len());
for (i, &(a, b)) in segments.iter().enumerate() {
if finite(a) && finite(b) && viewport.intersects(Rect::from_two_pos(a, b)) {
on_screen.push(i as u32);
}
}
out.considered = on_screen.len();
if out.considered == 0 {
return out;
}
let w = |i: u32| weight.get(i as usize).copied().unwrap_or(0.0);
let keep_always = |i: u32| always.get(i as usize).copied().unwrap_or(false);
let mut order: Vec<(u8, u32, u32)> = on_screen
.iter()
.map(|&i| (u8::from(!keep_always(i)), !monotonic_key(w(i)), i))
.collect();
order.sort_unstable();
let order: Vec<u32> = order.into_iter().map(|(_, _, i)| i).collect();
let quota = opts.keep_per_bucket as u32;
let mut occ = vec![0u32; cols * rows * DIR_BUCKETS];
let mut before = vec![0u32; cols * rows];
let mut after = vec![0u32; cols * rows];
let (mut peak_before, mut peak_after) = (0u32, 0u32);
let mut keep: Vec<u32> = Vec::with_capacity(order.len());
let mut path: Vec<usize> = Vec::new();
for &i in &order {
let (a, b) = segments[i as usize];
let d = dir_of(a, b);
path.clear();
walk_bins(a, b, origin, cell, cols, rows, |idx| path.push(idx));
for &idx in &path {
before[idx] += 1;
peak_before = peak_before.max(before[idx]);
}
let forced = keep_always(i);
let fits = forced || (quota > 0 && path.iter().all(|&idx| occ[idx * DIR_BUCKETS + d] < quota));
if !fits {
continue;
}
for &idx in &path {
occ[idx * DIR_BUCKETS + d] += 1;
after[idx] += 1;
peak_after = peak_after.max(after[idx]);
}
keep.push(i);
}
keep.sort_unstable();
out.dropped = out.considered - keep.len();
out.peak_density_before = peak_before as usize;
out.peak_density = peak_after as usize;
out.keep = keep;
out
}
#[must_use]
pub fn edge_ink_profile(
segments: &[(Pos2, Pos2)],
keep: &[u32],
viewport: Rect,
saturate: u32,
) -> (usize, u32, usize) {
let w = (viewport.width().ceil() as usize).clamp(1, 2048);
let h = (viewport.height().ceil() as usize).clamp(1, 2048);
let mut cover = vec![0u32; w * h];
let mut peak = 0u32;
let mut plot = |x: isize, y: isize, peak: &mut u32| {
if x < 0 || y < 0 || x >= w as isize || y >= h as isize {
return;
}
let c = &mut cover[y as usize * w + x as usize];
*c += 1;
*peak = (*peak).max(*c);
};
for &i in keep {
let Some(&(a, b)) = segments.get(i as usize) else { continue };
let (ax, ay) = (a.x - viewport.min.x, a.y - viewport.min.y);
let (bx, by) = (b.x - viewport.min.x, b.y - viewport.min.y);
if !(ax.is_finite() && ay.is_finite() && bx.is_finite() && by.is_finite()) {
continue;
}
let steps = ((bx - ax).abs().max((by - ay).abs()).ceil() as usize).clamp(1, 4096);
for s in 0..=steps {
let t = s as f32 / steps as f32;
plot((ax + (bx - ax) * t) as isize, (ay + (by - ay) * t) as isize, &mut peak);
}
}
let lit = cover.iter().filter(|&&c| c > 0).count();
let sat = cover.iter().filter(|&&c| c >= saturate).count();
(lit, peak, sat)
}
pub struct FrameInput<'a> {
pub centres: &'a [Pos2],
pub label_size: &'a [Vec2],
pub label_priority: &'a [f32],
pub label_pinned: &'a [bool],
pub edges: &'a [(u32, u32)],
pub edge_always: &'a [bool],
pub edge_weight: &'a [f32],
pub viewport: Rect,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FrameOpts {
pub thin_labels: bool,
pub thin_edges: bool,
pub label_pad: f32,
pub edge_cell: f32,
pub keep_per_bucket: usize,
}
impl Default for FrameOpts {
fn default() -> Self {
Self { thin_labels: false, thin_edges: false, label_pad: 2.0, edge_cell: 24.0, keep_per_bucket: 3 }
}
}
impl FrameOpts {
#[must_use]
pub fn legible() -> Self {
Self { thin_labels: true, thin_edges: true, ..Self::default() }
}
#[must_use]
pub fn is_off(&self) -> bool {
!self.thin_labels && !self.thin_edges
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FramePlan {
pub edges: Option<Vec<u32>>,
pub labels: Option<LabelPlacement>,
pub report: FrameReport,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FrameReport {
pub labels_placed: usize,
pub labels_suppressed: usize,
pub labels_considered: usize,
pub edges_drawn: usize,
pub edges_thinned: usize,
pub edges_considered: usize,
pub edge_peak_before: usize,
pub edge_peak_after: usize,
}
impl FrameReport {
#[must_use]
pub fn state_json(&self) -> serde_json::Value {
serde_json::json!({
"labels_placed": self.labels_placed,
"labels_suppressed": self.labels_suppressed,
"labels_considered": self.labels_considered,
"edges_drawn": self.edges_drawn,
"edges_thinned": self.edges_thinned,
"edges_considered": self.edges_considered,
"edge_peak_before": self.edge_peak_before,
"edge_peak_after": self.edge_peak_after,
})
}
}
#[must_use]
pub fn plan_frame(input: &FrameInput<'_>, opts: FrameOpts) -> FramePlan {
let mut plan = FramePlan::default();
if opts.thin_edges {
let segs: Vec<(Pos2, Pos2)> = input
.edges
.iter()
.map(|&(a, b)| {
match (input.centres.get(a as usize), input.centres.get(b as usize)) {
(Some(&pa), Some(&pb)) => (pa, pb),
_ => (Pos2::new(f32::NAN, f32::NAN), Pos2::new(f32::NAN, f32::NAN)),
}
})
.collect();
let res = thin_edges(
&segs,
input.edge_weight,
input.edge_always,
input.viewport,
EdgeThinOpts { cell: opts.edge_cell, keep_per_bucket: opts.keep_per_bucket },
);
plan.report.edges_drawn = res.keep.len();
plan.report.edges_thinned = res.dropped;
plan.report.edges_considered = res.considered;
plan.report.edge_peak_before = res.peak_density_before;
plan.report.edge_peak_after = res.peak_density;
plan.edges = Some(res.keep);
} else {
plan.report.edges_drawn = input.edges.len();
plan.report.edges_considered = input.edges.len();
}
if opts.thin_labels {
let cands: Vec<LabelCandidate> = input
.centres
.iter()
.enumerate()
.map(|(i, &c)| {
let size = input.label_size.get(i).copied().unwrap_or(Vec2::ZERO);
LabelCandidate {
anchor: if size.x > 0.0 && size.y > 0.0 {
c
} else {
Pos2::new(f32::MAX * 0.5, f32::MAX * 0.5)
},
size,
priority: input.label_priority.get(i).copied().unwrap_or(0.0),
pinned: input.label_pinned.get(i).copied().unwrap_or(false),
}
})
.collect();
let (mut lw, mut lh) = (f32::MAX, f32::MAX);
for sz in input.label_size.iter().filter(|s| s.x > 0.0 && s.y > 0.0) {
lw = lw.min(sz.x);
lh = lh.min(sz.y);
}
let capacity = if lw.is_finite() && lh.is_finite() && lw > 0.0 && lh > 0.0 {
((input.viewport.width() / lw) * (input.viewport.height() / lh)).ceil() as usize
} else {
0
};
let p = place_labels(
&cands,
input.viewport,
PlaceOpts {
pad: opts.label_pad,
variable: true,
clip_to_viewport: false,
max_considered: capacity.saturating_mul(8).max(2048),
max_placed: 0,
},
);
plan.report.labels_placed = p.placed.len();
plan.report.labels_suppressed = p.suppressed;
plan.report.labels_considered = p.considered;
plan.labels = Some(p);
}
plan
}
#[cfg(test)]
mod tests {
use super::*;
use egui::{pos2, vec2};
fn lattice(n: usize, span: f32) -> Vec<Pos2> {
let side = (n as f32).sqrt().ceil() as usize;
(0..n)
.map(|i| {
let (r, c) = (i / side, i % side);
pos2(c as f32 / side as f32 * span, r as f32 / side as f32 * span)
})
.collect()
}
#[test]
fn empty_and_degenerate_inputs_are_lossless_not_panics() {
let g = ScreenGrid::build(&[]);
assert!(g.is_empty());
assert_eq!(g.nearest(&[], pos2(0.0, 0.0), 10.0), None);
let pts = vec![pos2(5.0, 5.0); 100];
let g = ScreenGrid::build(&pts);
let hit = g.nearest(&pts, pos2(5.0, 5.0), 1.0).expect("degenerate extent still answers");
assert_eq!(hit.index, 0, "ties resolve to the lowest index");
let pts = vec![pos2(f32::NAN, 0.0), pos2(3.0, 3.0)];
let g = ScreenGrid::build(&pts);
assert_eq!(g.nearest(&pts, pos2(3.0, 3.0), 1.0).map(|h| h.index), Some(1));
}
#[test]
fn nearest_agrees_with_brute_force_everywhere() {
for n in [1usize, 7, 500, 5000] {
let pts = lattice(n, 900.0);
let g = ScreenGrid::build(&pts);
for k in 0..40 {
let probe = pos2((k * 37 % 900) as f32 + 0.5, (k * 53 % 900) as f32 + 0.5);
let max_d = 200.0;
let brute = pts
.iter()
.enumerate()
.map(|(i, p)| (i as u32, probe.distance(*p)))
.filter(|&(_, d)| d <= max_d)
.min_by(|a, b| a.1.total_cmp(&b.1).then(a.0.cmp(&b.0)));
let got = g.nearest(&pts, probe, max_d);
match (brute, got) {
(None, None) => {}
(Some((bi, bd)), Some(h)) => {
assert!(
(h.dist - bd).abs() < 1e-4,
"n={n} probe={probe:?}: grid {} vs brute {bd}",
h.dist
);
assert_eq!(h.index, bi, "n={n}: same winner as the linear scan");
}
(b, g2) => panic!("n={n} probe={probe:?}: brute={b:?} grid={g2:?}"),
}
}
}
}
#[test]
fn hover_cost_tracks_the_neighbourhood_not_the_dataset() {
let mut seen: Vec<(usize, usize)> = Vec::new();
for n in [1000usize, 10_000, 100_000, 1_000_000] {
let pts = lattice(n, 4000.0);
let g = ScreenGrid::build(&pts);
let hit = g.nearest(&pts, pos2(2000.0, 2000.0), 12.0).expect("a hit near the middle");
assert!(
hit.candidates <= 128,
"n={n}: examined {} points for one hover probe",
hit.candidates
);
assert!(
g.max_occupancy() <= 64,
"n={n}: fullest bin holds {} (lattice mis-sized over the extent)",
g.max_occupancy()
);
seen.push((n, hit.candidates));
}
let (n0, c0) = seen[0];
let (n1, c1) = *seen.last().unwrap();
assert!(
c1 <= c0 * 2,
"dataset {n0} -> {n1} (×{}) but probe cost {c0} -> {c1}",
n1 / n0
);
}
#[test]
fn query_rect_is_a_superset_of_the_true_contents_and_sorted() {
let pts = lattice(2000, 800.0);
let g = ScreenGrid::build(&pts);
let r = Rect::from_min_max(pos2(100.0, 100.0), pos2(180.0, 180.0));
let mut out = Vec::new();
g.query_rect(r, &mut out);
assert!(out.windows(2).all(|w| w[0] <= w[1]), "ascending index order");
for (i, p) in pts.iter().enumerate() {
if r.contains(*p) {
assert!(out.contains(&(i as u32)), "point {i} inside the rect must be returned");
}
}
assert!(out.len() < pts.len() / 4, "a small rect returns a small slice, got {}", out.len());
let mut off = Vec::new();
assert_eq!(g.query_rect(Rect::from_min_max(pos2(-9e3, -9e3), pos2(-8e3, -8e3)), &mut off), 0);
}
fn viewport() -> Rect {
Rect::from_min_size(pos2(0.0, 0.0), vec2(800.0, 600.0))
}
#[test]
fn placed_labels_never_share_a_pixel_and_the_unthinned_control_does() {
let cands: Vec<LabelCandidate> = (0..400)
.map(|i| {
let x = 100.0 + ((i * 17) % 200) as f32;
let y = 100.0 + ((i * 29) % 150) as f32;
LabelCandidate::new(pos2(x, y), vec2(46.0, 12.0), (400 - i) as f32)
})
.collect();
let out = place_labels(&cands, viewport(), PlaceOpts::default());
let placed: Vec<Rect> = out.placed.iter().map(|p| p.rect).collect();
assert_eq!(
max_label_overlap(&placed, viewport(), 1.0),
1,
"collision avoidance: no pixel carries two labels"
);
assert!(out.suppressed > 0, "a real pile must actually suppress losers");
assert!(!out.placed.is_empty(), "and must still draw the winners");
let unthinned: Vec<Rect> = cands
.iter()
.map(|c| Rect::from_center_size(c.anchor, c.size))
.collect();
let control = max_label_overlap(&unthinned, viewport(), 1.0);
assert!(control >= 5, "the control must overplot badly, got peak {control}");
}
#[test]
fn higher_priority_wins_a_contested_pixel() {
let cands = vec![
LabelCandidate::new(pos2(400.0, 300.0), vec2(60.0, 14.0), 1.0),
LabelCandidate::new(pos2(402.0, 301.0), vec2(60.0, 14.0), 9.0),
];
let opts = PlaceOpts { variable: false, ..PlaceOpts::default() };
let out = place_labels(&cands, viewport(), opts);
assert_eq!(out.placed.len(), 1);
assert_eq!(out.placed[0].index, 1, "the priority-9 label is the one drawn");
assert_eq!(out.suppressed, 1);
}
#[test]
fn pinned_labels_are_never_suppressed() {
let opts = PlaceOpts { variable: false, ..PlaceOpts::default() };
let cands = vec![
LabelCandidate::new(pos2(400.0, 300.0), vec2(60.0, 14.0), 100.0),
LabelCandidate::new(pos2(401.0, 300.0), vec2(60.0, 14.0), 0.0).pinned(true),
];
let out = place_labels(&cands, viewport(), opts);
assert_eq!(out.placed.len(), 1);
assert_eq!(out.placed[0].index, 1, "the pinned label is the one drawn");
assert_eq!(out.suppressed, 1, "the ordinary label lost, as it must");
let both = vec![
LabelCandidate::new(pos2(400.0, 300.0), vec2(60.0, 14.0), 0.0).pinned(true),
LabelCandidate::new(pos2(401.0, 300.0), vec2(60.0, 14.0), 0.0).pinned(true),
];
let out = place_labels(&both, viewport(), opts);
assert_eq!(out.placed.len(), 2, "pinned labels are never dropped");
assert_eq!(out.suppressed, 0);
}
#[test]
fn variable_placement_places_more_than_one_shot() {
let cands: Vec<LabelCandidate> = (0..120)
.map(|i| {
let x = 100.0 + ((i * 23) % 300) as f32;
let y = 100.0 + ((i * 31) % 200) as f32;
LabelCandidate::new(pos2(x, y), vec2(40.0, 12.0), i as f32)
})
.collect();
let one = place_labels(&cands, viewport(), PlaceOpts { variable: false, ..Default::default() });
let var = place_labels(&cands, viewport(), PlaceOpts { variable: true, ..Default::default() });
assert!(
var.placed.len() > one.placed.len(),
"variable placement {} vs one-shot {}",
var.placed.len(),
one.placed.len()
);
let rects: Vec<Rect> = var.placed.iter().map(|p| p.rect).collect();
assert_eq!(max_label_overlap(&rects, viewport(), 1.0), 1, "and still collision-free");
}
#[test]
fn offscreen_labels_cost_nothing() {
let mut cands: Vec<LabelCandidate> = (0..20)
.map(|i| LabelCandidate::new(pos2(20.0 + i as f32 * 35.0, 300.0), vec2(24.0, 12.0), 1.0))
.collect();
cands.extend((0..100_000).map(|i| {
LabelCandidate::new(pos2(50_000.0 + i as f32, -40_000.0), vec2(24.0, 12.0), 1.0)
}));
let out = place_labels(&cands, viewport(), PlaceOpts::default());
assert_eq!(out.considered, 20, "only the on-screen candidates were considered");
assert_eq!(out.suppressed, 0);
assert!(
out.collision_tests < 400,
"bin-local tests, not quadratic: {} tests",
out.collision_tests
);
}
#[test]
fn label_placement_is_deterministic() {
let cands: Vec<LabelCandidate> = (0..300)
.map(|i| {
LabelCandidate::new(
pos2(((i * 41) % 700) as f32, ((i * 67) % 500) as f32),
vec2(38.0, 12.0),
(i % 5) as f32,
)
})
.collect();
let first = place_labels(&cands, viewport(), PlaceOpts::default());
for _ in 0..64 {
assert_eq!(place_labels(&cands, viewport(), PlaceOpts::default()), first);
}
}
fn hairball(n: usize) -> Vec<(Pos2, Pos2)> {
let mut s = 0x2545_F491_4F6C_DD1Du64;
let mut next = move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
(s >> 11) as f32 / (1u64 << 53) as f32
};
(0..n)
.map(|_| {
(
pos2(next() * 800.0, next() * 600.0),
pos2(next() * 800.0, next() * 600.0),
)
})
.collect()
}
#[test]
fn thinning_collapses_saturated_ink_and_the_control_proves_it_was_saturated() {
let segs = hairball(20_000);
let vp = viewport();
let all: Vec<u32> = (0..segs.len() as u32).collect();
let (lit_before, peak_before, sat_before) = edge_ink_profile(&segs, &all, vp, 4);
let out = thin_edges(&segs, &[], &[], vp, EdgeThinOpts::default());
let (lit_after, peak_after, sat_after) = edge_ink_profile(&segs, &out.keep, vp, 4);
assert!(sat_before > 10_000, "the control hairball must really be saturated: {sat_before}");
assert!(
sat_after * 4 < sat_before,
"thinning must cut saturated pixels hard: {sat_before} -> {sat_after}"
);
assert!(peak_after < peak_before, "peak pile {peak_before} -> {peak_after}");
assert!(lit_after > 0 && lit_after < lit_before, "still draws a graph: {lit_before} -> {lit_after}");
assert!(out.peak_density < out.peak_density_before);
assert_eq!(out.keep.len() + out.dropped, out.considered);
}
#[test]
fn a_sparse_graph_loses_no_edges() {
let segs: Vec<(Pos2, Pos2)> = (0..12)
.map(|i| {
let y = 40.0 + i as f32 * 45.0;
(pos2(30.0, y), pos2(760.0, y + 6.0))
})
.collect();
let out = thin_edges(&segs, &[], &[], viewport(), EdgeThinOpts::default());
assert_eq!(out.dropped, 0, "nothing to thin in a sparse graph");
assert_eq!(out.keep.len(), segs.len());
}
#[test]
fn always_keep_edges_survive_any_density() {
let segs = hairball(5000);
let mut always = vec![false; segs.len()];
for i in (0..segs.len()).step_by(500) {
always[i] = true;
}
let out = thin_edges(&segs, &[], &always, viewport(), EdgeThinOpts { cell: 24.0, keep_per_bucket: 0 });
for (i, &f) in always.iter().enumerate() {
if f {
assert!(out.keep.contains(&(i as u32)), "flagged edge {i} was thinned away");
}
}
assert_eq!(out.keep.len(), always.iter().filter(|&&f| f).count(), "quota 0 keeps only the flagged");
}
#[test]
fn the_heaviest_edge_in_a_bucket_is_the_survivor() {
let segs: Vec<(Pos2, Pos2)> = (0..4)
.map(|i| (pos2(100.0, 100.0 + i as f32), pos2(140.0, 100.0 + i as f32)))
.collect();
let weight = vec![0.1, 0.2, 9.0, 0.3];
let out = thin_edges(&segs, &weight, &[], viewport(), EdgeThinOpts { cell: 24.0, keep_per_bucket: 1 });
assert_eq!(out.keep, vec![2], "the weight-9 edge is the one drawn");
}
#[test]
fn edge_thinning_is_deterministic() {
let segs = hairball(4000);
let weight: Vec<f32> = (0..segs.len()).map(|i| (i % 7) as f32).collect();
let first = thin_edges(&segs, &weight, &[], viewport(), EdgeThinOpts::default());
for _ in 0..64 {
assert_eq!(thin_edges(&segs, &weight, &[], viewport(), EdgeThinOpts::default()), first);
}
assert!(first.keep.windows(2).all(|w| w[0] < w[1]), "ascending, so paint order is preserved");
}
#[test]
fn offscreen_edges_cost_nothing() {
let mut segs = hairball(50);
segs.extend((0..50_000).map(|i| {
(pos2(-90_000.0 - i as f32, -90_000.0), pos2(-89_000.0 - i as f32, -89_000.0))
}));
let out = thin_edges(&segs, &[], &[], viewport(), EdgeThinOpts::default());
assert_eq!(out.considered, 50, "only on-screen edges were considered");
}
}