#![forbid(unsafe_code)]
use crate::core::traits::data_type::DataType;
use crate::core::vertex::Vertex;
use crate::geometry::traits::coordinate::CoordinateScalar;
#[must_use]
pub fn dedup_vertices_exact<T, U, const D: usize>(
vertices: &[Vertex<T, U, D>],
) -> Vec<Vertex<T, U, D>>
where
T: CoordinateScalar,
U: DataType,
{
let mut unique: Vec<Vertex<T, U, D>> = Vec::with_capacity(vertices.len());
'outer: for &v in vertices {
for u in &unique {
if coords_equal_exact(v.point().coords(), u.point().coords()) {
continue 'outer; }
}
unique.push(v);
}
unique
}
pub fn dedup_vertices_epsilon<T, U, const D: usize>(
vertices: &[Vertex<T, U, D>],
epsilon: T,
) -> Vec<Vertex<T, U, D>>
where
T: CoordinateScalar,
U: DataType,
{
if epsilon < T::zero() {
eprintln!("dedup_vertices_epsilon received negative epsilon; enforcing contract");
}
assert!(
epsilon >= T::zero(),
"dedup_vertices_epsilon expects non-negative epsilon",
);
let mut unique: Vec<Vertex<T, U, D>> = Vec::with_capacity(vertices.len());
'outer: for &v in vertices {
for u in &unique {
if coords_within_epsilon(v.point().coords(), u.point().coords(), epsilon) {
continue 'outer; }
}
unique.push(v);
}
unique
}
pub fn filter_vertices_excluding<T, U, const D: usize>(
vertices: &[Vertex<T, U, D>],
reference: &[Vertex<T, U, D>],
) -> Vec<Vertex<T, U, D>>
where
T: CoordinateScalar,
U: DataType,
{
let mut filtered = Vec::with_capacity(vertices.len());
'outer: for &v in vertices {
for ref_v in reference {
if coords_equal_exact(v.point().coords(), ref_v.point().coords()) {
continue 'outer; }
}
filtered.push(v);
}
filtered
}
#[inline]
pub(crate) fn coords_equal_exact<T: CoordinateScalar, const D: usize>(
a: &[T; D],
b: &[T; D],
) -> bool {
a.iter().zip(b.iter()).all(|(x, y)| x.ordered_eq(y))
}
#[inline]
pub(crate) fn coords_within_epsilon<T: CoordinateScalar, const D: usize>(
a: &[T; D],
b: &[T; D],
epsilon: T,
) -> bool {
let dist_sq: T = a
.iter()
.zip(b.iter())
.map(|(x, y)| (*x - *y) * (*x - *y))
.fold(T::zero(), |acc, d| acc + d);
let epsilon_sq = epsilon * epsilon;
if cfg!(debug_assertions) && dist_sq == epsilon_sq {
eprintln!(
"[dedup_vertices_epsilon] distance equals epsilon; keeping point (strict < epsilon)"
);
}
dist_sq < epsilon_sq
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::Coordinate;
use crate::vertex;
use approx::assert_relative_eq;
#[test]
fn test_dedup_vertices_exact_comprehensive() {
let v1: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([0.0, 0.0])])
.into_iter()
.next()
.unwrap();
let v2: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([0.0, 0.0])])
.into_iter()
.next()
.unwrap();
let v3: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([1.0, 1.0])])
.into_iter()
.next()
.unwrap();
let vertices = vec![v1, v2, v3];
let unique = dedup_vertices_exact(&vertices);
assert_eq!(unique.len(), 2, "Should remove exact duplicate");
let v1_nan: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([f64::NAN, f64::NAN])])
.into_iter()
.next()
.unwrap();
let v2_nan: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([f64::NAN, f64::NAN])])
.into_iter()
.next()
.unwrap();
let v3_regular: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([1.0, 1.0])])
.into_iter()
.next()
.unwrap();
let vertices_nan = vec![v1_nan, v2_nan, v3_regular];
let unique_nan = dedup_vertices_exact(&vertices_nan);
assert_eq!(
unique_nan.len(),
2,
"NaN should be considered equal to NaN for deduplication"
);
let v1_pos_zero: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([0.0, 0.0])])
.into_iter()
.next()
.unwrap();
let v2_neg_zero: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([-0.0, -0.0])])
.into_iter()
.next()
.unwrap();
let v3_one: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([1.0, 1.0])])
.into_iter()
.next()
.unwrap();
let vertices_zero = vec![v1_pos_zero, v2_neg_zero, v3_one];
let unique_zero = dedup_vertices_exact(&vertices_zero);
assert_eq!(
unique_zero.len(),
2,
"+0.0 and -0.0 should be considered equal for deduplication"
);
}
#[test]
fn test_dedup_vertices_epsilon_basic() {
let v1: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([0.0, 0.0])])
.into_iter()
.next()
.unwrap();
let v2: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([1e-11, 1e-11])])
.into_iter()
.next()
.unwrap();
let v3: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([1.0, 1.0])])
.into_iter()
.next()
.unwrap();
let vertices = vec![v1, v2, v3];
let unique = dedup_vertices_epsilon(&vertices, 1e-10);
assert_eq!(
unique.len(),
2,
"Near-duplicate within epsilon should be removed"
);
}
#[test]
fn test_dedup_vertices_epsilon_boundary() {
let v1: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([0.0, 0.0])])
.into_iter()
.next()
.unwrap();
let v2: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([1e-10, 0.0])])
.into_iter()
.next()
.unwrap();
let v3: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([0.99e-10, 0.0])])
.into_iter()
.next()
.unwrap();
let vertices = vec![v1, v2, v3];
let unique = dedup_vertices_epsilon(&vertices, 1e-10);
assert_eq!(
unique.len(),
2,
"Distance exactly equal to epsilon should NOT be filtered (strict < semantics)"
);
}
#[test]
fn test_dedup_vertices_epsilon_preserves_first_occurrence() {
let points = [
Point::new([0.0, 0.0]),
Point::new([1e-11, 1e-11]), Point::new([1.0, 1.0]),
Point::new([1.0 + 1e-11, 1.0 + 1e-11]), ];
let vertices: Vec<Vertex<f64, (), 2>> = Vertex::from_points(&points);
let unique = dedup_vertices_epsilon(&vertices, 1e-10);
assert_eq!(unique.len(), 2, "Should keep first of each cluster");
let unique_coords: Vec<_> = unique
.iter()
.map(<&Vertex<_, _, _> as Into<[f64; 2]>>::into)
.collect();
assert_relative_eq!(unique_coords[0][0], 0.0, epsilon = 1e-12);
assert_relative_eq!(unique_coords[0][1], 0.0, epsilon = 1e-12);
assert_relative_eq!(unique_coords[1][0], 1.0, epsilon = 1e-12);
assert_relative_eq!(unique_coords[1][1], 1.0, epsilon = 1e-12);
}
#[test]
fn test_filter_vertices_excluding_comprehensive() {
let v1: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([0.0, 0.0])])
.into_iter()
.next()
.unwrap();
let v2: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([1.0, 1.0])])
.into_iter()
.next()
.unwrap();
let v3: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([2.0, 2.0])])
.into_iter()
.next()
.unwrap();
let reference_basic = vec![v1];
let vertices_basic = vec![v1, v2, v3];
let filtered_basic = filter_vertices_excluding(&vertices_basic, &reference_basic);
assert_eq!(
filtered_basic.len(),
2,
"Should exclude vertex matching reference"
);
let v_nan: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([f64::NAN, f64::NAN])])
.into_iter()
.next()
.unwrap();
let reference_nan = vec![v_nan];
let vertices_with_nan: Vec<Vertex<f64, (), 2>> =
Vertex::from_points(&[Point::new([f64::NAN, f64::NAN]), Point::new([1.0, 1.0])]);
let filtered_nan = filter_vertices_excluding(&vertices_with_nan, &reference_nan);
assert_eq!(
filtered_nan.len(),
1,
"NaN reference should exclude NaN vertex"
);
let v_pos_zero: Vertex<f64, (), 2> = Vertex::from_points(&[Point::new([0.0, 0.0])])
.into_iter()
.next()
.unwrap();
let reference_zero = vec![v_pos_zero];
let vertices_with_neg_zero: Vec<Vertex<f64, (), 2>> =
Vertex::from_points(&[Point::new([-0.0, -0.0]), Point::new([1.0, 1.0])]);
let filtered_zero = filter_vertices_excluding(&vertices_with_neg_zero, &reference_zero);
assert_eq!(
filtered_zero.len(),
1,
"+0.0 reference should exclude -0.0 vertex"
);
let points = [
Point::new([0.0, 0.0]),
Point::new([1.0, 1.0]),
Point::new([2.0, 2.0]),
Point::new([3.0, 3.0]),
];
let vertices: Vec<Vertex<f64, (), 2>> = Vertex::from_points(&points);
let reference = vec![vertices[0], vertices[2]]; let filtered = filter_vertices_excluding(&vertices, &reference);
assert_eq!(filtered.len(), 2, "Should exclude both reference vertices");
let filtered_coords: Vec<_> = filtered
.iter()
.map(<&Vertex<_, _, _> as Into<[f64; 2]>>::into)
.collect();
assert_relative_eq!(filtered_coords[0][0], 1.0, epsilon = 1e-12);
assert_relative_eq!(filtered_coords[0][1], 1.0, epsilon = 1e-12);
assert_relative_eq!(filtered_coords[1][0], 3.0, epsilon = 1e-12);
assert_relative_eq!(filtered_coords[1][1], 3.0, epsilon = 1e-12);
}
#[test]
fn test_filter_vertices_excluding_empty_reference() {
let vertices: Vec<Vertex<f64, (), 1>> =
vec![vertex!([0.0]), vertex!([1.0]), vertex!([2.0])];
let reference: Vec<Vertex<f64, (), 1>> = vec![];
let filtered = filter_vertices_excluding(&vertices, &reference);
assert_eq!(filtered.len(), vertices.len());
}
}