use crate::Float;
use crate::{NeighborDirection, NodeNeighbor};
use kiddo::{KdTree, SquaredEuclidean};
use nalgebra::{Point2, Vector2};
pub struct NeighborCandidate<F: Float = f32> {
pub index: usize,
pub offset: Vector2<F>,
pub distance: F,
}
pub trait NeighborValidator<F: Float = f32> {
type PointData;
fn validate(
&self,
source_index: usize,
source_data: &Self::PointData,
candidate: &NeighborCandidate<F>,
candidate_data: &Self::PointData,
) -> Option<(NeighborDirection, F)>;
}
#[derive(Clone, Debug)]
pub struct GridGraphParams<F: Float = f32> {
pub k_neighbors: usize,
pub max_distance: F,
}
impl<F: Float> Default for GridGraphParams<F> {
fn default() -> Self {
Self {
k_neighbors: 8,
max_distance: F::max_value().unwrap_or_else(|| F::from_subset(&1e30)),
}
}
}
pub struct GridGraph<F: Float = f32> {
pub neighbors: Vec<Vec<NodeNeighbor<F>>>,
}
impl<F: Float + kiddo::float::kdtree::Axis> GridGraph<F> {
pub fn build<V: NeighborValidator<F>>(
positions: &[Point2<F>],
point_data: &[V::PointData],
validator: &V,
params: &GridGraphParams<F>,
) -> Self {
assert_eq!(
positions.len(),
point_data.len(),
"positions and point_data must have the same length"
);
let coords: Vec<[F; 2]> = positions.iter().map(|p| [p.x, p.y]).collect();
let tree: KdTree<F, 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<F: Float>(candidates: Vec<NodeNeighbor<F>>) -> Vec<NodeNeighbor<F>> {
let mut best: [Option<NodeNeighbor<F>>; 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()
}