use crate::direction::{NeighborDirection, NodeNeighbor};
use kiddo::{KdTree, SquaredEuclidean};
use nalgebra::{Point2, Vector2};
pub struct NeighborCandidate {
pub index: usize,
pub offset: Vector2<f32>,
pub distance: f32,
}
pub trait NeighborValidator {
type PointData;
fn validate(
&self,
source_index: usize,
source_data: &Self::PointData,
candidate: &NeighborCandidate,
candidate_data: &Self::PointData,
) -> Option<(NeighborDirection, f32)>;
}
#[derive(Clone, Debug)]
pub struct GridGraphParams {
pub k_neighbors: usize,
pub max_distance: f32,
}
impl Default for GridGraphParams {
fn default() -> Self {
Self {
k_neighbors: 8,
max_distance: f32::MAX,
}
}
}
pub struct GridGraph {
pub neighbors: Vec<Vec<NodeNeighbor>>,
}
impl GridGraph {
pub fn build<V: NeighborValidator>(
positions: &[Point2<f32>],
point_data: &[V::PointData],
validator: &V,
params: &GridGraphParams,
) -> Self {
assert_eq!(
positions.len(),
point_data.len(),
"positions and point_data must have the same length"
);
let coords: Vec<[f32; 2]> = positions.iter().map(|p| [p.x, p.y]).collect();
let tree: KdTree<f32, 2> = (&coords).into();
let max_dist_sq = params.max_distance * params.max_distance;
let mut neighbors = Vec::with_capacity(positions.len());
for (i, pos) in positions.iter().enumerate() {
let query = [pos.x, pos.y];
let results = tree.nearest_n::<SquaredEuclidean>(&query, params.k_neighbors);
let mut candidates = Vec::new();
for nn in results {
let j = nn.item as usize;
if j == i {
continue;
}
let dist_sq = nn.distance;
if dist_sq > max_dist_sq {
continue;
}
let neighbor_pos = positions[j];
let offset = Vector2::new(neighbor_pos.x - pos.x, neighbor_pos.y - pos.y);
let distance = dist_sq.sqrt();
let candidate = NeighborCandidate {
index: j,
offset,
distance,
};
if let Some((direction, score)) =
validator.validate(i, &point_data[i], &candidate, &point_data[j])
{
candidates.push(NodeNeighbor {
direction,
index: j,
distance,
score,
});
}
}
neighbors.push(select_neighbors(candidates));
}
Self { neighbors }
}
}
fn select_neighbors(candidates: Vec<NodeNeighbor>) -> Vec<NodeNeighbor> {
let mut best: [Option<NodeNeighbor>; 4] = [None, None, None, None];
for candidate in candidates {
let slot = match candidate.direction {
NeighborDirection::Right => &mut best[0],
NeighborDirection::Left => &mut best[1],
NeighborDirection::Up => &mut best[2],
NeighborDirection::Down => &mut best[3],
};
let replace = match slot {
None => true,
Some(current) => {
candidate.score < current.score
|| (candidate.score == current.score && candidate.distance < current.distance)
}
};
if replace {
*slot = Some(candidate);
}
}
best.into_iter().flatten().collect()
}