I'm trying to find and delete duplicate vertices in a list. Duplicate in this case means coincident within a certain tolerance like f32::EPSILON
or something similarly small. I thought I'd use a BTreeSet
with a custom entry type along with the nice range()
API to implement a way to efficiently search for coincident points instead of taking the naive loop-in-a-loop approach. However, I can't get the element ordering to work properly. Here's my code so far:
use nalgebra::Point3;
use std::{cmp::Ordering, collections::BTreeSet, ops::Bound};
#[derive(Debug, Clone, Copy)]
struct SortMe {
pos: Point3<f64>,
}
impl SortMe {
fn new(x: f64, y: f64) -> Self {
Self {
pos: Point3::new(x, y, 0.0),
}
}
}
impl Eq for SortMe {}
impl PartialEq for SortMe {
fn eq(&self, other: &Self) -> bool {
self.pos == other.pos
}
}
impl PartialOrd for SortMe {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for SortMe {
fn cmp(&self, other: &Self) -> Ordering {
let x = self
.pos
.x
.partial_cmp(&other.pos.x)
// A failed comparison will fall through to comparing the Y coordinate
.unwrap_or(Ordering::Equal);
let y = self
.pos
.y
.partial_cmp(&other.pos.y)
.unwrap_or(Ordering::Equal);
x.then(y)
}
}
And my test case:
fn main() {
let minus_x = SortMe::new(-1.0, 0.0);
let plus_x = SortMe::new(1.0, 0.0);
let minus_y = SortMe::new(0.0, -1.0);
let plus_y = SortMe::new(0.0, 1.0);
let top_left = SortMe::new(-1.0, -1.0);
let top_right = SortMe::new(1.0, -1.0);
let bot_left = SortMe::new(-1.0, 1.0);
let bot_right = SortMe::new(1.0, 1.0);
let center = SortMe::new(0.0, 0.0);
let set = BTreeSet::from([
minus_x, plus_x, minus_y, plus_y, top_left, top_right, bot_left, bot_right, center,
]);
let radius = 0.1;
// Bounding box around `center` point
let tl = SortMe::new(-radius, -radius);
let br = SortMe::new(radius, radius);
let items = set.range((Bound::Included(tl), Bound::Included(br)));
let matching = items.collect::<Vec<_>>();
dbg!(&matching);
// Should be empty, but contains 3 elements!
assert!(matching.is_empty());
// Actually contains this:
// [
// SortMe {
// pos: [
// 0.0,
// -1.0,
// 0.0,
// ],
// },
// SortMe {
// pos: [
// 0.0,
// 0.0,
// 0.0,
// ],
// },
// SortMe {
// pos: [
// 0.0,
// 1.0,
// 0.0,
// ],
// },
// ]
}
In the example code, all the points except center
lie outside its bounding box, so no values should be returned for the given range I've created (bounding box with "radius" 0.1
). Am I misunderstanding how BTreeMap
s work? Why are some of the points matched with a range that doesn't include them?
By the way, this all works perfectly with a single f64
(i.e. single axis). I currently have a function that uses a BTreeSet
to find points with close X coordinates, then a naive loop to filter for nearby Y coordinates which works ok but I dunno, it's inelegant. It seems that the combination of two axes makes things start to fail but I feel like it should work.