use geo::Coord;
use crate::adj::AdjacencyMatrix;
use crate::dcel::{Dcel, HalfEdgeId, VertexId};
use crate::unit::UnitId;
use super::Region;
impl Region {
pub fn with_forced_adjacencies(mut self, pairs: &[(UnitId, UnitId)]) -> Self {
if pairs.is_empty() { return self; }
self.adjacent = self.adjacent.with_extra_edges(pairs);
self.touching = self.touching.with_extra_edges(pairs);
self
}
#[inline]
pub fn are_adjacent(&self, a: UnitId, b: UnitId) -> bool {
self.adjacent.contains(a, b)
}
#[inline]
pub fn neighbors(&self, unit: UnitId) -> &[UnitId] {
self.adjacent.neighbors(unit)
}
#[inline] pub fn adjacency(&self) -> &AdjacencyMatrix { &self.adjacent }
#[inline] pub fn touching(&self) -> &AdjacencyMatrix { &self.touching }
}
pub(crate) fn build_adjacent(
dcel: &Dcel<Coord<f64>>,
face_to_unit: &[UnitId],
edge_length: &[f64],
num_units: usize,
) -> AdjacencyMatrix {
let mut triples = Vec::<(UnitId, UnitId, f64)>::new();
for h in 0..dcel.num_half_edges() {
let he = dcel.half_edge(HalfEdgeId(h));
let unit = face_to_unit[he.face.0];
let other = face_to_unit[dcel.half_edge(he.twin).face.0];
if unit != other {
triples.push((unit, other, edge_length[h / 2]));
}
}
AdjacencyMatrix::from_directed_pairs_weighted(num_units, triples)
}
pub(crate) fn build_touching(dcel: &Dcel<Coord<f64>>, face_to_unit: &[UnitId], num_units: usize) -> AdjacencyMatrix {
let mut pairs = Vec::<(UnitId, UnitId)>::new();
for v in 0..dcel.num_vertices() {
let start = match dcel.vertex(VertexId(v)).half_edge {
Some(he) => he,
None => continue,
};
let units: Vec<UnitId> = dcel
.vertex_star(start)
.map(|he| face_to_unit[dcel.half_edge(he).face.0])
.collect();
for &a in &units {
for &b in &units {
if a != b {
pairs.push((a, b));
}
}
}
}
AdjacencyMatrix::from_directed_pairs(num_units, pairs)
}
#[cfg(test)]
mod tests {
use crate::unit::UnitId;
use crate::region::test_helpers::make_two_unit_region;
#[test]
fn adjacent_units_are_adjacent() {
let r = make_two_unit_region();
assert!(r.are_adjacent(UnitId(0), UnitId(1)));
assert!(r.are_adjacent(UnitId(1), UnitId(0)));
}
#[test]
fn unit_is_not_adjacent_to_itself() {
let r = make_two_unit_region();
assert!(!r.are_adjacent(UnitId(0), UnitId(0)));
assert!(!r.are_adjacent(UnitId(1), UnitId(1)));
}
#[test]
fn each_unit_has_one_rook_neighbour() {
let r = make_two_unit_region();
assert_eq!(r.neighbors(UnitId(0)), &[UnitId(1)]);
assert_eq!(r.neighbors(UnitId(1)), &[UnitId(0)]);
}
#[test]
fn neighbours_are_sorted() {
let r = make_two_unit_region();
for uid in r.unit_ids() {
let ns = r.neighbors(uid);
for w in ns.windows(2) {
assert!(w[0] < w[1]);
}
}
}
#[test]
fn rook_matrix_covers_all_units() {
let r = make_two_unit_region();
assert_eq!(r.adjacency().num_units(), 2);
}
#[test]
fn rook_adjacency_is_symmetric() {
let r = make_two_unit_region();
let adj = r.adjacency();
for uid in r.unit_ids() {
for &nb in adj.neighbors(uid) {
assert!(adj.contains(nb, uid),
"asymmetry: {uid} -> {nb} but not reverse");
}
}
}
#[test]
fn queen_matrix_covers_all_units() {
let r = make_two_unit_region();
assert_eq!(r.touching().num_units(), 2);
}
#[test]
fn queen_is_superset_of_rook() {
let r = make_two_unit_region();
let rook = r.adjacency();
let queen = r.touching();
for uid in r.unit_ids() {
for &nb in rook.neighbors(uid) {
assert!(queen.contains(uid, nb),
"Rook edge ({uid},{nb}) missing from Queen matrix");
}
}
}
#[test]
fn rook_adjacency_has_weights() {
let r = make_two_unit_region();
assert!(r.adjacency().has_weights());
}
#[test]
fn edge_weight_at_matches_shared_boundary_length() {
let r = make_two_unit_region();
for uid in r.unit_ids() {
let offset = r.adjacency().offset(uid);
for (i, &nb) in r.neighbors(uid).iter().enumerate() {
let csr_weight = r.edge_weight_at(offset + i);
let dcel_weight = r.shared_boundary_length(uid, nb);
assert!(
(csr_weight - dcel_weight).abs() < 1e-9,
"weight mismatch for ({uid},{nb}): csr={csr_weight} dcel={dcel_weight}"
);
}
}
}
#[test]
fn queen_adjacency_is_symmetric() {
let r = make_two_unit_region();
let q = r.touching();
for uid in r.unit_ids() {
for &nb in q.neighbors(uid) {
assert!(q.contains(nb, uid));
}
}
}
}