use crate::float_helpers::{lit, to_degrees};
use crate::graph::NeighborCandidate;
use crate::hex::direction::HexDirection;
use crate::hex::graph::HexNeighborValidator;
use crate::Float;
use nalgebra::Vector2;
fn direction_sextant<F: Float>(offset: &Vector2<F>) -> HexDirection {
let deg = to_degrees(offset.y.atan2(offset.x));
if deg >= lit(-30.0) && deg < lit(30.0) {
HexDirection::East
} else if deg >= lit(30.0) && deg < lit(90.0) {
HexDirection::SouthEast
} else if deg >= lit(90.0) && deg < lit(150.0) {
HexDirection::SouthWest
} else if deg < lit(-150.0) || deg >= lit(150.0) {
HexDirection::West
} else if deg >= lit(-150.0) && deg < lit(-90.0) {
HexDirection::NorthWest
} else {
HexDirection::NorthEast
}
}
pub struct SpatialHexValidator<F: Float = f32> {
pub min_spacing: F,
pub max_spacing: F,
}
impl<F: Float> HexNeighborValidator<F> for SpatialHexValidator<F> {
type PointData = ();
fn validate(
&self,
_source_index: usize,
_source_data: &(),
candidate: &NeighborCandidate<F>,
_candidate_data: &(),
) -> Option<(HexDirection, F)> {
if candidate.distance < self.min_spacing || candidate.distance > self.max_spacing {
return None;
}
let direction = direction_sextant(&candidate.offset);
Some((direction, candidate.distance))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::GridGraphParams;
use crate::hex::graph::HexGridGraph;
use crate::hex::traverse::{hex_assign_grid_coordinates, hex_connected_components};
use nalgebra::Point2;
fn hex_lattice(radius: i32, spacing: f32) -> Vec<Point2<f32>> {
let sqrt3 = 3.0f32.sqrt();
let mut points = Vec::new();
for q in -radius..=radius {
for r in -radius..=radius {
if (q + r).abs() > radius {
continue;
}
let x = spacing * (q as f32 + r as f32 * 0.5);
let y = spacing * (r as f32 * sqrt3 / 2.0);
points.push(Point2::new(x, y));
}
}
points
}
#[test]
fn spatial_hex_center_has_six_neighbors() {
let spacing = 50.0;
let points = hex_lattice(2, spacing);
let data = vec![(); points.len()];
let validator = SpatialHexValidator {
min_spacing: spacing * 0.5,
max_spacing: spacing * 1.5,
};
let graph = HexGridGraph::build(
&points,
&data,
&validator,
&GridGraphParams {
k_neighbors: 12,
..Default::default()
},
);
let center = points
.iter()
.position(|p| p.x.abs() < 0.01 && p.y.abs() < 0.01)
.unwrap();
assert_eq!(6, graph.neighbors[center].len());
}
#[test]
fn spatial_hex_edge_nodes_have_three() {
let spacing = 50.0;
let points = hex_lattice(1, spacing);
let data = vec![(); points.len()];
let validator = SpatialHexValidator {
min_spacing: spacing * 0.5,
max_spacing: spacing * 1.5,
};
let graph = HexGridGraph::build(
&points,
&data,
&validator,
&GridGraphParams {
k_neighbors: 12,
..Default::default()
},
);
for (i, p) in points.iter().enumerate() {
if p.x.abs() < 0.01 && p.y.abs() < 0.01 {
assert_eq!(6, graph.neighbors[i].len());
} else {
assert_eq!(3, graph.neighbors[i].len());
}
}
}
#[test]
fn spatial_hex_rejects_out_of_range() {
let points = vec![
Point2::new(0.0f32, 0.0),
Point2::new(3.0, 0.0), ];
let data = vec![(); 2];
let validator = SpatialHexValidator {
min_spacing: 10.0,
max_spacing: 50.0,
};
let graph = HexGridGraph::build(&points, &data, &validator, &GridGraphParams::default());
assert!(graph.neighbors[0].is_empty());
}
#[test]
fn spatial_hex_single_component_and_correct_coordinates() {
let spacing = 50.0;
let points = hex_lattice(2, spacing);
let data = vec![(); points.len()];
let validator = SpatialHexValidator {
min_spacing: spacing * 0.5,
max_spacing: spacing * 1.5,
};
let graph = HexGridGraph::build(
&points,
&data,
&validator,
&GridGraphParams {
k_neighbors: 12,
..Default::default()
},
);
let components = hex_connected_components(&graph);
assert_eq!(1, components.len());
assert_eq!(points.len(), components[0].len());
let coords = hex_assign_grid_coordinates(&graph, &components[0]);
assert_eq!(points.len(), coords.len());
let coord_set: std::collections::HashSet<(i32, i32)> =
coords.iter().map(|&(_, g)| (g.i, g.j)).collect();
assert_eq!(points.len(), coord_set.len());
}
}