use crate::TriMesh;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EdgeKey {
lower: u32,
upper: u32,
}
impl EdgeKey {
#[must_use]
pub fn new(a: u32, b: u32) -> Self {
if a <= b {
Self { lower: a, upper: b }
} else {
Self { lower: b, upper: a }
}
}
#[must_use]
pub const fn lower(self) -> u32 {
self.lower
}
#[must_use]
pub const fn upper(self) -> u32 {
self.upper
}
#[must_use]
pub const fn endpoints(self) -> (u32, u32) {
(self.lower, self.upper)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EdgeUse {
pub triangle: usize,
pub forward: bool,
}
fn counting_sort_records(
records: &mut Vec<(EdgeKey, EdgeUse)>,
scratch: &mut Vec<(EdgeKey, EdgeUse)>,
buckets: usize,
) {
debug_assert_eq!(scratch.len(), records.len());
let mut counts: Vec<u32> = Vec::new();
for pass in 0..2 {
counts.clear();
counts.resize(buckets + 2, 0);
for (key, _) in records.iter() {
let bucket = if pass == 0 { key.upper() } else { key.lower() } as usize;
counts[bucket + 1] += 1;
}
for index in 0..=buckets {
counts[index + 1] += counts[index];
}
for record in records.iter() {
let bucket = if pass == 0 {
record.0.upper()
} else {
record.0.lower()
} as usize;
scratch[counts[bucket] as usize] = *record;
counts[bucket] += 1;
}
std::mem::swap(records, scratch);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EdgeAdjacency {
keys: Vec<EdgeKey>,
starts: Vec<u32>,
uses: Vec<EdgeUse>,
vertex_count: usize,
degenerate_triangles: usize,
face_count: usize,
}
impl EdgeAdjacency {
#[must_use]
pub fn build(mesh: &TriMesh) -> Self {
let triangles = mesh.indices.len() / 3;
let mut records: Vec<(EdgeKey, EdgeUse)> = Vec::with_capacity(triangles * 3);
let mut degenerate_triangles = 0;
let mut face_count = 0;
for (triangle, chunk) in mesh.indices.chunks_exact(3).enumerate() {
let (a, b, c) = (chunk[0], chunk[1], chunk[2]);
if a == b || b == c || c == a {
degenerate_triangles += 1;
continue;
}
face_count += 1;
for (from, to) in [(a, b), (b, c), (c, a)] {
records.push((
EdgeKey::new(from, to),
EdgeUse {
triangle,
forward: from <= to,
},
));
}
}
let buckets = records
.iter()
.map(|(key, _)| key.upper() as usize)
.max()
.map_or(0, |highest| highest + 1);
let dense = buckets <= records.len().saturating_mul(2).max(1024);
let fits = u32::try_from(records.len()).is_ok();
let mut scratch: Vec<(EdgeKey, EdgeUse)> = Vec::new();
let counted = dense && fits && scratch.try_reserve_exact(records.len()).is_ok();
if counted {
scratch.resize(
records.len(),
(
EdgeKey::new(0, 0),
EdgeUse {
triangle: 0,
forward: false,
},
),
);
counting_sort_records(&mut records, &mut scratch, buckets);
} else {
records.sort_unstable_by(|left, right| {
left.0
.cmp(&right.0)
.then_with(|| left.1.triangle.cmp(&right.1.triangle))
});
}
let mut keys: Vec<EdgeKey> = Vec::new();
let mut starts: Vec<u32> = Vec::new();
let mut uses: Vec<EdgeUse> = Vec::with_capacity(records.len());
for (key, use_) in records {
if keys.last() != Some(&key) {
keys.push(key);
starts.push(uses.len() as u32);
}
uses.push(use_);
}
starts.push(uses.len() as u32);
Self {
keys,
starts,
uses,
vertex_count: mesh.positions.len(),
degenerate_triangles,
face_count,
}
}
#[must_use]
pub fn edge_count(&self) -> usize {
self.keys.len()
}
#[must_use]
pub const fn degenerate_triangles(&self) -> usize {
self.degenerate_triangles
}
fn span(&self, index: usize) -> &[EdgeUse] {
let from = self.starts[index] as usize;
let to = self.starts[index + 1] as usize;
&self.uses[from..to]
}
pub fn edges(&self) -> impl Iterator<Item = (EdgeKey, &[EdgeUse])> {
self.keys
.iter()
.enumerate()
.map(|(index, key)| (*key, self.span(index)))
}
#[must_use]
pub fn uses(&self, edge: EdgeKey) -> &[EdgeUse] {
self.keys
.binary_search(&edge)
.map_or(&[], |index| self.span(index))
}
pub fn boundary_edges(&self) -> impl Iterator<Item = EdgeKey> + '_ {
self.edges()
.filter(|(_, uses)| uses.len() == 1)
.map(|(key, _)| key)
}
pub fn non_manifold_edges(&self) -> impl Iterator<Item = EdgeKey> + '_ {
self.edges()
.filter(|(_, uses)| uses.len() > 2)
.map(|(key, _)| key)
}
pub fn inconsistent_edges(&self) -> impl Iterator<Item = EdgeKey> + '_ {
self.edges()
.filter(|(_, uses)| uses.len() == 2 && uses[0].forward == uses[1].forward)
.map(|(key, _)| key)
}
#[must_use]
pub fn is_closed_two_manifold(&self) -> bool {
self.edges().all(|(_, uses)| uses.len() == 2) && self.inconsistent_edges().next().is_none()
}
#[must_use]
pub fn boundary_vertices(&self) -> Vec<u32> {
let mut seen = vec![false; self.vertex_count];
let mut out = Vec::new();
for key in self.boundary_edges() {
for corner in [key.lower(), key.upper()] {
if let Some(slot) = seen.get_mut(corner as usize) {
if !*slot {
*slot = true;
out.push(corner);
}
}
}
}
out.sort_unstable();
out
}
#[must_use]
pub fn vertex_neighbours(&self) -> Vec<Vec<u32>> {
let mut out = vec![Vec::new(); self.vertex_count];
for key in &self.keys {
let (a, b) = key.endpoints();
if let Some(list) = out.get_mut(a as usize) {
list.push(b);
}
if let Some(list) = out.get_mut(b as usize) {
list.push(a);
}
}
for list in &mut out {
list.sort_unstable();
list.dedup();
}
out
}
#[must_use]
pub fn triangle_neighbours(&self, triangle: usize) -> Vec<usize> {
let mut out = Vec::new();
for (_, uses) in self.edges() {
if uses.iter().any(|use_| use_.triangle == triangle) {
out.extend(
uses.iter()
.map(|use_| use_.triangle)
.filter(|other| *other != triangle),
);
}
}
out.sort_unstable();
out.dedup();
out
}
#[must_use]
pub fn euler_characteristic(&self) -> i64 {
let mut used = vec![false; self.vertex_count];
for key in &self.keys {
for corner in [key.lower(), key.upper()] {
if let Some(slot) = used.get_mut(corner as usize) {
*slot = true;
}
}
}
let vertices = used.iter().filter(|seen| **seen).count() as i64;
let edges = self.keys.len() as i64;
let faces = self.face_count() as i64;
vertices - edges + faces
}
#[must_use]
pub const fn face_count(&self) -> usize {
self.face_count
}
}