use std::collections::HashMap;
use super::Node;
use crate::js::math::{min, round};
#[derive(Clone, Debug, Default)]
pub(crate) struct Crossings(HashMap<u64, Vec<f64>>);
impl Crossings {
fn key(y: f64) -> u64 {
let k = round(y);
if k.is_nan() {
f64::NAN.to_bits()
} else {
(k + 0.0).to_bits()
}
}
pub(crate) fn at(&self, y: f64) -> Option<&[f64]> {
self.0.get(&Crossings::key(y)).map(Vec::as_slice)
}
pub(crate) fn add(&mut self, x: f64, y: f64) {
self.0.entry(Crossings::key(y)).or_default().push(x);
}
#[cfg(test)]
pub(super) fn len(&self) -> usize {
self.0.len()
}
}
pub(crate) fn mark_crossings(crossings: &mut Crossings, route: &[f64]) {
for &[x, y, _] in route.as_chunks::<3>().0 {
crossings.add(x, y);
}
}
pub(crate) fn cluster_title_x(c: &Node, width: f64, crossings: &Crossings, pad: f64) -> f64 {
if width == 0.0 {
return c.x;
}
let top = c.y - c.h / 2.0;
let left = c.x - c.w / 2.0;
let half = width / 2.0;
let candidates = [left + pad - 6.0 + half, c.x, left + c.w - pad + 6.0 - half];
let Some(xs) = crossings.at(top) else {
return candidates[0];
};
let (mut best, mut best_gap) = (candidates[0], -1.0);
for x in candidates {
let mut gap = f64::INFINITY;
for &cx in xs {
if cx > left && cx < left + c.w {
gap = min(gap, (cx - x).abs() - half);
}
}
if gap >= 6.0 {
return x;
}
if gap > best_gap + 0.5 {
(best_gap, best) = (gap, x);
}
}
best
}
pub(crate) fn title_crossed(c: &Node, width: f64, x: f64, crossings: &Crossings) -> bool {
if !(width > 0.0) {
return false;
}
let Some(xs) = crossings.at(c.y - c.h / 2.0) else {
return false;
};
let left = c.x - c.w / 2.0;
xs.iter()
.any(|&cx| cx > left && cx < left + c.w && (cx - x).abs() < width / 2.0 + 4.0)
}
pub(crate) const STRUCK_HALO: f64 = 5.0;
pub(crate) fn struck_depth(parent: impl Fn(usize) -> Option<usize>, index: usize) -> u32 {
let mut depth = 1;
let mut p = parent(index);
while let Some(at) = p
&& depth < 8
{
depth += 1;
p = parent(at);
}
depth
}
pub(crate) fn struck_tint(depth: u32) -> f64 {
100.0 * (1.0 - 0.5f64.powi(depth as i32))
}