#![forbid(unsafe_code)]
use super::collections::{
FacetToSimplicesMap, FastHashMap, MAX_PRACTICAL_DIMENSION_SIZE, SmallBuffer,
fast_hash_map_with_capacity,
};
use super::util::{stable_hash_u64_slice, usize_to_u8};
use super::{
simplex::Simplex,
tds::{NeighborValidationError, SimplexKey, Tds, TdsError, VertexKey},
vertex::Vertex,
};
use crate::geometry::traits::coordinate::CoordinateConversionError;
use slotmap::Key;
use std::{
fmt::{self, Debug},
iter::FusedIterator,
sync::Arc,
vec::IntoIter,
};
use thiserror::Error;
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum FacetError {
#[error("The simplex does not contain the vertex!")]
SimplexDoesNotContainVertex,
#[error("Vertex UUID not found in mapping: {uuid}")]
VertexNotFound {
uuid: uuid::Uuid,
},
#[error(
"Facet must have exactly {expected} vertices for {dimension}D triangulation, got {actual}"
)]
InsufficientVertices {
expected: usize,
actual: usize,
dimension: usize,
},
#[error("Facet not found in triangulation")]
FacetNotFoundInTriangulation,
#[error(
"Facet key {facet_key:016x} not found in cache with {cache_size} entries - possible invariant violation or key derivation mismatch. Vertex UUIDs: {vertex_uuids:?}"
)]
FacetKeyNotFoundInCache {
facet_key: u64,
cache_size: usize,
vertex_uuids: Vec<uuid::Uuid>,
},
#[error("Expected exactly 1 adjacent simplex for boundary facet, found {found}")]
InvalidAdjacentSimplexCount {
found: usize,
},
#[error("Adjacent simplex not found")]
AdjacentSimplexNotFound,
#[error("Could not find inside vertex for boundary facet")]
InsideVertexNotFound,
#[error("Failed to compute orientation during {context}: {source}")]
OrientationComputationFailed {
context: String,
#[source]
source: CoordinateConversionError,
},
#[error("Invalid facet index {index} for simplex with {facet_count} facets")]
InvalidFacetIndex {
index: u8,
facet_count: usize,
},
#[error(
"Invalid facet index {original_index} (too large for u8 conversion) for {facet_count} facets"
)]
InvalidFacetIndexOverflow {
original_index: usize,
facet_count: usize,
},
#[error("Dimension {dimension} exceeds maximum {max_dimension} for u8 facet indices")]
FacetIndexCapacityExceeded {
dimension: usize,
max_dimension: usize,
},
#[error("Simplex not found in triangulation (potential data corruption)")]
SimplexNotFoundInTriangulation,
#[error("Vertex key not found in triangulation: {key:?}")]
VertexKeyNotFoundInTriangulation {
key: VertexKey,
},
#[error(
"Facet view for simplex {simplex_key:?}, facet {facet_index} belongs to a different TDS"
)]
FacetOwnerMismatch {
simplex_key: SimplexKey,
facet_index: u8,
},
#[error("Facet-to-simplices index belongs to a different TDS")]
FacetIndexOwnerMismatch,
#[error(
"Facet with key {facet_key:016x} has invalid multiplicity {found}, expected 1 (one-sided) or 2 (two-sided)"
)]
InvalidFacetMultiplicity {
facet_key: u64,
found: usize,
},
#[error(
"Facet with key {facet_key:016x} repeats incident simplex facet {handle:?}; expected distinct incident simplex facets"
)]
DuplicateFacetIncidentHandle {
facet_key: u64,
handle: FacetHandle,
},
#[error(
"Facet handle {handle:?} derives key {actual_facet_key:016x}, but index entry expected {expected_facet_key:016x}"
)]
FacetHandleKeyMismatch {
expected_facet_key: u64,
actual_facet_key: u64,
handle: FacetHandle,
},
#[error(
"Boundary facet handle {supplied_handle:?} is not the indexed one-sided handle {indexed_handle:?} for facet key {facet_key:016x}"
)]
BoundaryFacetHandleNotIndexed {
facet_key: u64,
supplied_handle: FacetHandle,
indexed_handle: FacetHandle,
},
#[error("Failed to retrieve boundary facets: {source}")]
BoundaryFacetRetrievalFailed {
#[source]
source: Arc<TdsError>,
},
#[error("Failed to derive canonical facet key: {source}")]
FacetKeyDerivationFailed {
#[source]
source: Arc<TdsError>,
},
#[error("Simplex operation failed: {source}")]
SimplexOperationFailed {
#[source]
source: Arc<TdsError>,
},
}
#[must_use]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FacetHandle {
simplex_key: SimplexKey,
facet_index: u8,
}
impl FacetHandle {
pub fn try_new<U, V, const D: usize>(
tds: &Tds<U, V, D>,
simplex_key: SimplexKey,
facet_index: u8,
) -> Result<Self, FacetError> {
let simplex = tds
.simplex(simplex_key)
.ok_or(FacetError::SimplexNotFoundInTriangulation)?;
let facet_count = simplex.number_of_vertices();
if usize::from(facet_index) >= facet_count {
return Err(FacetError::InvalidFacetIndex {
index: facet_index,
facet_count,
});
}
Ok(Self::from_validated(simplex_key, facet_index))
}
#[inline]
pub(crate) const fn from_validated(simplex_key: SimplexKey, facet_index: u8) -> Self {
Self {
simplex_key,
facet_index,
}
}
#[must_use]
pub const fn simplex_key(&self) -> SimplexKey {
self.simplex_key
}
#[must_use]
pub const fn facet_index(&self) -> u8 {
self.facet_index
}
pub fn view<U, V, const D: usize>(
self,
tds: &Tds<U, V, D>,
) -> Result<FacetView<'_, U, V, D>, FacetError> {
FacetView::try_new(tds, self.simplex_key, self.facet_index)
}
}
#[must_use]
pub struct FacetView<'tds, U, V, const D: usize> {
tds: &'tds Tds<U, V, D>,
simplex: &'tds Simplex<V, D>,
simplex_key: SimplexKey,
facet_index: u8,
facet_vertex_keys: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
key: u64,
vertices: SmallBuffer<&'tds Vertex<U, D>, MAX_PRACTICAL_DIMENSION_SIZE>,
opposite_vertex: &'tds Vertex<U, D>,
}
impl<'tds, U, V, const D: usize> FacetView<'tds, U, V, D> {
#[inline]
#[must_use]
pub const fn simplex_key(&self) -> SimplexKey {
self.simplex_key
}
#[inline]
#[must_use]
pub const fn facet_index(&self) -> u8 {
self.facet_index
}
#[inline]
#[must_use]
pub(crate) const fn tds(&self) -> &'tds Tds<U, V, D> {
self.tds
}
#[inline]
pub const fn handle(&self) -> FacetHandle {
FacetHandle::from_validated(self.simplex_key, self.facet_index)
}
}
impl<'tds, U, V, const D: usize> FacetView<'tds, U, V, D> {
pub fn try_new(
tds: &'tds Tds<U, V, D>,
simplex_key: SimplexKey,
facet_index: u8,
) -> Result<Self, FacetError> {
let simplex = tds
.simplex(simplex_key)
.ok_or(FacetError::SimplexNotFoundInTriangulation)?;
let vertex_count = simplex.number_of_vertices();
if usize::from(facet_index) >= vertex_count {
return Err(FacetError::InvalidFacetIndex {
index: facet_index,
facet_count: vertex_count,
});
}
let mut facet_vertex_keys: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(vertex_count.saturating_sub(1));
let mut vertices: SmallBuffer<&'tds Vertex<U, D>, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(vertex_count.saturating_sub(1));
let mut opposite_vertex = None;
let facet_index_usize = usize::from(facet_index);
for (index, &vertex_key) in simplex.vertices().iter().enumerate() {
let vertex = tds
.vertex(vertex_key)
.ok_or(FacetError::VertexKeyNotFoundInTriangulation { key: vertex_key })?;
if index == facet_index_usize {
opposite_vertex = Some(vertex);
} else {
facet_vertex_keys.push(vertex_key);
vertices.push(vertex);
}
}
let opposite_vertex = opposite_vertex.ok_or(FacetError::InvalidFacetIndex {
index: facet_index,
facet_count: vertex_count,
})?;
let key = Tds::<U, V, D>::periodic_facet_key_from_simplex_vertices(
simplex,
simplex.vertices(),
facet_index_usize,
)
.map_err(|source| FacetError::FacetKeyDerivationFailed {
source: Arc::new(source),
})?;
Ok(Self {
tds,
simplex,
simplex_key,
facet_index,
facet_vertex_keys,
key,
vertices,
opposite_vertex,
})
}
#[must_use]
pub fn vertices(&self) -> impl ExactSizeIterator<Item = &'tds Vertex<U, D>> + '_ {
self.vertices.iter().copied()
}
#[must_use]
pub const fn opposite_vertex(&self) -> &'tds Vertex<U, D> {
self.opposite_vertex
}
#[must_use]
pub const fn simplex(&self) -> &'tds Simplex<V, D> {
self.simplex
}
#[must_use]
pub const fn key(&self) -> u64 {
self.key
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[must_use]
pub(crate) struct FacetIncidence {
kind: FacetIncidenceKind,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum FacetIncidenceKind {
OneSided(FacetHandle),
TwoSided([FacetHandle; 2]),
}
impl FacetIncidence {
fn try_from_index_entry<U, V, const D: usize>(
tds: &Tds<U, V, D>,
facet_key: u64,
handles: &SmallBuffer<FacetHandle, 2>,
) -> Result<Self, FacetError> {
let incidence = Self::try_from_handles(facet_key, handles)?;
match incidence.kind {
FacetIncidenceKind::OneSided(handle) => {
let handle = try_incident_facet_view_for_facet_key(tds, facet_key, handle)
.map(|_| handle)?;
Ok(Self {
kind: FacetIncidenceKind::OneSided(handle),
})
}
FacetIncidenceKind::TwoSided([first, second]) => {
let first =
try_incident_facet_view_for_facet_key(tds, facet_key, first).map(|_| first)?;
let second = try_incident_facet_view_for_facet_key(tds, facet_key, second)
.map(|_| second)?;
Ok(Self {
kind: FacetIncidenceKind::TwoSided([first, second]),
})
}
}
}
fn try_from_handles(
facet_key: u64,
handles: &SmallBuffer<FacetHandle, 2>,
) -> Result<Self, FacetError> {
match handles.as_slice() {
[handle] => Ok(Self {
kind: FacetIncidenceKind::OneSided(*handle),
}),
[first, second] if first != second => Ok(Self {
kind: FacetIncidenceKind::TwoSided([*first, *second]),
}),
[handle, _] => Err(FacetError::DuplicateFacetIncidentHandle {
facet_key,
handle: *handle,
}),
_ => Err(FacetError::InvalidFacetMultiplicity {
facet_key,
found: handles.len(),
}),
}
}
#[inline]
#[must_use]
pub(crate) const fn is_one_sided(self) -> bool {
matches!(self.kind, FacetIncidenceKind::OneSided(_))
}
#[inline]
#[must_use]
pub(crate) const fn incident_simplex_count(self) -> usize {
match self.kind {
FacetIncidenceKind::OneSided(_) => 1,
FacetIncidenceKind::TwoSided(_) => 2,
}
}
#[inline]
#[must_use]
pub(crate) const fn one_sided_handle(self) -> Option<FacetHandle> {
match self.kind {
FacetIncidenceKind::OneSided(handle) => Some(handle),
FacetIncidenceKind::TwoSided(_) => None,
}
}
#[inline]
#[must_use]
pub(crate) const fn two_sided_handles(self) -> Option<[FacetHandle; 2]> {
match self.kind {
FacetIncidenceKind::OneSided(_) => None,
FacetIncidenceKind::TwoSided(handles) => Some(handles),
}
}
}
pub(crate) fn try_incident_facet_view_for_facet_key<U, V, const D: usize>(
tds: &Tds<U, V, D>,
expected_facet_key: u64,
handle: FacetHandle,
) -> Result<FacetView<'_, U, V, D>, FacetError> {
let facet = handle.view(tds)?;
let actual_facet_key = facet.key();
if actual_facet_key == expected_facet_key {
return Ok(facet);
}
Err(FacetError::FacetHandleKeyMismatch {
expected_facet_key,
actual_facet_key,
handle,
})
}
#[must_use]
pub struct FacetIncidenceView<'idx, 'tds, U, V, const D: usize> {
tds: &'tds Tds<U, V, D>,
facet_key: u64,
incidence: &'idx FacetIncidence,
}
impl<U, V, const D: usize> Clone for FacetIncidenceView<'_, '_, U, V, D> {
fn clone(&self) -> Self {
*self
}
}
impl<U, V, const D: usize> Copy for FacetIncidenceView<'_, '_, U, V, D> {}
impl<'tds, U, V, const D: usize> FacetIncidenceView<'_, 'tds, U, V, D> {
#[inline]
#[must_use]
pub(crate) const fn tds(self) -> &'tds Tds<U, V, D> {
self.tds
}
#[inline]
#[must_use]
pub const fn facet_key(self) -> u64 {
self.facet_key
}
#[inline]
#[must_use]
pub const fn is_one_sided(self) -> bool {
self.incidence.is_one_sided()
}
#[inline]
#[must_use]
pub const fn incident_simplex_count(self) -> usize {
self.incidence.incident_simplex_count()
}
#[inline]
#[must_use]
pub const fn one_sided_handle(self) -> Option<FacetHandle> {
self.incidence.one_sided_handle()
}
#[inline]
#[must_use]
pub const fn two_sided_handles(self) -> Option<[FacetHandle; 2]> {
self.incidence.two_sided_handles()
}
}
#[derive(Clone, Debug)]
#[must_use]
pub struct FacetToSimplicesIndex<'tds, U, V, const D: usize> {
tds: &'tds Tds<U, V, D>,
map: FastHashMap<u64, FacetIncidence>,
}
impl<'tds, U, V, const D: usize> FacetToSimplicesIndex<'tds, U, V, D> {
#[inline]
pub(crate) fn try_from_map(
tds: &'tds Tds<U, V, D>,
map: &FacetToSimplicesMap,
) -> Result<Self, FacetError> {
let mut parsed = fast_hash_map_with_capacity(map.len());
for (facet_key, handles) in map {
let incidence = FacetIncidence::try_from_index_entry(tds, *facet_key, handles)?;
parsed.insert(*facet_key, incidence);
}
Ok(Self { tds, map: parsed })
}
#[inline]
#[must_use]
pub(crate) const fn tds(&self) -> &'tds Tds<U, V, D> {
self.tds
}
#[inline]
#[must_use]
pub fn get<'idx>(
&'idx self,
facet_key: &u64,
) -> Option<FacetIncidenceView<'idx, 'tds, U, V, D>> {
self.map.get(facet_key).map(|incidence| FacetIncidenceView {
tds: self.tds,
facet_key: *facet_key,
incidence,
})
}
#[inline]
#[must_use]
pub fn is_one_sided_facet_key(&self, facet_key: &u64) -> bool {
self.map
.get(facet_key)
.is_some_and(|incidence| incidence.is_one_sided())
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.map.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[inline]
pub fn iter<'idx>(
&'idx self,
) -> impl Iterator<Item = FacetIncidenceView<'idx, 'tds, U, V, D>> + 'idx {
let tds = self.tds;
self.map
.iter()
.map(move |(facet_key, incidence)| FacetIncidenceView {
tds,
facet_key: *facet_key,
incidence,
})
}
#[inline]
pub(crate) fn one_sided_handles(&self) -> impl Iterator<Item = FacetHandle> + '_ {
self.map
.values()
.filter_map(|incidence| incidence.one_sided_handle())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[must_use]
pub(crate) enum OneSidedFacetAdjacency {
Open,
PeriodicSelfIdentification,
}
pub(crate) fn classify_one_sided_facet_adjacency<U, V, const D: usize>(
facet: &FacetView<'_, U, V, D>,
) -> Result<OneSidedFacetAdjacency, TdsError> {
let facet_key = facet.key();
let simplex_key = facet.simplex_key();
let facet_index = usize::from(facet.facet_index());
let simplex = facet.simplex();
if facet_index >= simplex.number_of_vertices() {
return Err(TdsError::IndexOutOfBounds {
index: facet_index,
bound: simplex.number_of_vertices(),
context: format!(
"one-sided facet adjacency classification for simplex {simplex_key:?}"
),
});
}
let Some(neighbor) = simplex.neighbor_key(facet_index) else {
return Ok(OneSidedFacetAdjacency::Open);
};
let Some(neighbor_key) = neighbor else {
return Ok(OneSidedFacetAdjacency::Open);
};
if neighbor_key == simplex_key {
if simplex_allows_periodic_self_neighbor(simplex) {
return Ok(OneSidedFacetAdjacency::PeriodicSelfIdentification);
}
return Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::BoundaryFacetHasNonPeriodicSelfNeighbor {
facet_key,
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index,
},
});
}
Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::BoundaryFacetHasNeighbor {
facet_key,
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index,
neighbor_key,
},
})
}
fn simplex_allows_periodic_self_neighbor<V, const D: usize>(simplex: &Simplex<V, D>) -> bool {
let Some(offsets) = simplex.periodic_vertex_offsets() else {
return false;
};
!offsets.is_empty() && offsets.len() == simplex.number_of_vertices()
}
impl<U, V, const D: usize> Debug for FacetView<'_, U, V, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FacetView")
.field("simplex_key", &self.simplex_key)
.field("facet_index", &self.facet_index)
.field("facet_vertex_keys", &self.facet_vertex_keys)
.field("key", &self.key)
.field("dimension", &D)
.finish()
}
}
impl<U, V, const D: usize> Clone for FacetView<'_, U, V, D> {
fn clone(&self) -> Self {
Self {
tds: self.tds,
simplex: self.simplex,
simplex_key: self.simplex_key,
facet_index: self.facet_index,
facet_vertex_keys: self.facet_vertex_keys.clone(),
key: self.key,
vertices: self.vertices.clone(),
opposite_vertex: self.opposite_vertex,
}
}
}
impl<U, V, const D: usize> PartialEq for FacetView<'_, U, V, D> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.tds, other.tds)
&& self.simplex_key == other.simplex_key
&& self.facet_index == other.facet_index
}
}
impl<U, V, const D: usize> Eq for FacetView<'_, U, V, D> {}
#[must_use]
#[derive(Clone)]
pub struct SimplexFacetsIter<'tds, U, V, const D: usize> {
tds: &'tds Tds<U, V, D>,
simplex_key: SimplexKey,
next_facet_index: u16,
facet_count: u16,
}
impl<'tds, U, V, const D: usize> SimplexFacetsIter<'tds, U, V, D> {
pub(crate) fn try_new(
tds: &'tds Tds<U, V, D>,
simplex_key: SimplexKey,
) -> Result<Self, FacetError> {
let simplex = tds
.simplex(simplex_key)
.ok_or(FacetError::SimplexNotFoundInTriangulation)?;
let facet_count_usize = simplex.number_of_vertices();
let max_facet_count = usize::from(u8::MAX) + 1;
if facet_count_usize > max_facet_count {
return Err(FacetError::InvalidFacetIndexOverflow {
original_index: max_facet_count,
facet_count: facet_count_usize,
});
}
let facet_count = u16::try_from(facet_count_usize).map_err(|_| {
FacetError::InvalidFacetIndexOverflow {
original_index: max_facet_count,
facet_count: facet_count_usize,
}
})?;
Ok(Self {
tds,
simplex_key,
next_facet_index: 0,
facet_count,
})
}
}
impl<'tds, U, V, const D: usize> Iterator for SimplexFacetsIter<'tds, U, V, D> {
type Item = Result<FacetView<'tds, U, V, D>, FacetError>;
fn next(&mut self) -> Option<Self::Item> {
if self.next_facet_index >= self.facet_count {
return None;
}
let facet_index = usize_to_u8(
usize::from(self.next_facet_index),
usize::from(self.facet_count),
);
self.next_facet_index += 1;
Some(facet_index.and_then(|idx| FacetView::try_new(self.tds, self.simplex_key, idx)))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.len();
(remaining, Some(remaining))
}
}
impl<U, V, const D: usize> ExactSizeIterator for SimplexFacetsIter<'_, U, V, D> {
fn len(&self) -> usize {
usize::from(self.facet_count.saturating_sub(self.next_facet_index))
}
}
impl<U, V, const D: usize> FusedIterator for SimplexFacetsIter<'_, U, V, D> {}
#[derive(Clone)]
pub struct AllFacetsIter<'tds, U, V, const D: usize> {
tds: &'tds Tds<U, V, D>,
simplex_keys: slotmap::dense::Keys<'tds, SimplexKey, Simplex<V, D>>,
state: AllFacetsIterState,
}
#[derive(Clone)]
enum AllFacetsIterState {
PendingError(FacetError),
PendingSimplex,
InSimplex {
simplex_key: SimplexKey,
next_facet_index: usize,
facet_count: usize,
},
Exhausted,
}
impl<'tds, U, V, const D: usize> AllFacetsIter<'tds, U, V, D> {
#[must_use]
pub(crate) fn from_tds(tds: &'tds Tds<U, V, D>) -> Self {
let state = if D > usize::from(u8::MAX) {
AllFacetsIterState::PendingError(FacetError::FacetIndexCapacityExceeded {
dimension: D,
max_dimension: usize::from(u8::MAX),
})
} else {
AllFacetsIterState::PendingSimplex
};
Self {
tds,
simplex_keys: tds.simplex_key_iter(),
state,
}
}
pub(crate) fn try_new(tds: &'tds Tds<U, V, D>) -> Result<Self, FacetError> {
if D > usize::from(u8::MAX) {
return Err(FacetError::FacetIndexCapacityExceeded {
dimension: D,
max_dimension: usize::from(u8::MAX),
});
}
Ok(Self::from_tds(tds))
}
}
impl<U, V, const D: usize> Tds<U, V, D> {
pub fn try_simplex_facets(
&self,
simplex_key: SimplexKey,
) -> Result<SimplexFacetsIter<'_, U, V, D>, FacetError> {
SimplexFacetsIter::try_new(self, simplex_key)
}
#[must_use]
pub fn facets(&self) -> AllFacetsIter<'_, U, V, D> {
AllFacetsIter::from_tds(self)
}
}
impl<'tds, U, V, const D: usize> Iterator for AllFacetsIter<'tds, U, V, D> {
type Item = Result<FacetView<'tds, U, V, D>, FacetError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match &mut self.state {
AllFacetsIterState::PendingError(error) => {
let error = error.clone();
self.state = AllFacetsIterState::Exhausted;
return Some(Err(error));
}
AllFacetsIterState::InSimplex {
simplex_key,
next_facet_index,
facet_count,
} if *next_facet_index < *facet_count => {
let facet_index = *next_facet_index;
*next_facet_index += 1;
let facet_u8 = match usize_to_u8(facet_index, *facet_count) {
Ok(facet_u8) => facet_u8,
Err(err) => return Some(Err(err)),
};
return Some(FacetView::try_new(self.tds, *simplex_key, facet_u8));
}
AllFacetsIterState::Exhausted => return None,
AllFacetsIterState::PendingSimplex | AllFacetsIterState::InSimplex { .. } => {
if let Some(next_simplex_key) = self.simplex_keys.next() {
if let Some(simplex) = self.tds.simplex(next_simplex_key) {
self.state = AllFacetsIterState::InSimplex {
simplex_key: next_simplex_key,
next_facet_index: 0,
facet_count: simplex.number_of_vertices(),
};
} else {
return Some(Err(FacetError::SimplexNotFoundInTriangulation));
}
} else {
self.state = AllFacetsIterState::Exhausted;
return None;
}
}
}
}
}
}
#[must_use]
#[derive(Clone)]
pub struct BoundaryFacetsIter<'tds, U, V, const D: usize> {
tds: &'tds Tds<U, V, D>,
boundary_facet_handles: IntoIter<FacetHandle>,
}
impl<'tds, U, V, const D: usize> BoundaryFacetsIter<'tds, U, V, D> {
pub(crate) fn try_new(
facet_to_simplices_index: &FacetToSimplicesIndex<'tds, U, V, D>,
mut boundary_facet_handles: Vec<FacetHandle>,
) -> Result<Self, FacetError> {
let tds = facet_to_simplices_index.tds();
AllFacetsIter::try_new(tds)?;
for handle in &mut boundary_facet_handles {
*handle = try_one_sided_handle_from_index(facet_to_simplices_index, *handle)?;
}
sort_handles_by_storage_order(&mut boundary_facet_handles);
Ok(Self {
tds,
boundary_facet_handles: boundary_facet_handles.into_iter(),
})
}
}
impl<'tds, U, V, const D: usize> Iterator for BoundaryFacetsIter<'tds, U, V, D> {
type Item = Result<FacetView<'tds, U, V, D>, FacetError>;
fn next(&mut self) -> Option<Self::Item> {
self.boundary_facet_handles
.next()
.map(|handle| handle.view(self.tds))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.boundary_facet_handles.size_hint()
}
}
impl<U, V, const D: usize> ExactSizeIterator for BoundaryFacetsIter<'_, U, V, D> {}
impl<U, V, const D: usize> FusedIterator for BoundaryFacetsIter<'_, U, V, D> {}
#[must_use]
#[derive(Clone)]
pub struct OneSidedFacetsIter<'tds, U, V, const D: usize> {
tds: &'tds Tds<U, V, D>,
one_sided_facet_handles: IntoIter<FacetHandle>,
}
impl<'tds, U, V, const D: usize> OneSidedFacetsIter<'tds, U, V, D> {
pub(crate) fn try_new(
facet_to_simplices_index: &FacetToSimplicesIndex<'tds, U, V, D>,
) -> Result<Self, FacetError> {
let tds = facet_to_simplices_index.tds();
AllFacetsIter::try_new(tds)?;
let mut one_sided_facet_handles = facet_to_simplices_index
.one_sided_handles()
.collect::<Vec<_>>();
sort_handles_by_storage_order(&mut one_sided_facet_handles);
Ok(Self {
tds,
one_sided_facet_handles: one_sided_facet_handles.into_iter(),
})
}
}
impl<'tds, U, V, const D: usize> Iterator for OneSidedFacetsIter<'tds, U, V, D> {
type Item = Result<FacetView<'tds, U, V, D>, FacetError>;
fn next(&mut self) -> Option<Self::Item> {
self.one_sided_facet_handles
.next()
.map(|handle| handle.view(self.tds))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.one_sided_facet_handles.size_hint()
}
}
impl<U, V, const D: usize> ExactSizeIterator for OneSidedFacetsIter<'_, U, V, D> {}
impl<U, V, const D: usize> FusedIterator for OneSidedFacetsIter<'_, U, V, D> {}
fn try_one_sided_handle_from_index<U, V, const D: usize>(
facet_to_simplices_index: &FacetToSimplicesIndex<'_, U, V, D>,
handle: FacetHandle,
) -> Result<FacetHandle, FacetError> {
let facet = handle.view(facet_to_simplices_index.tds())?;
let facet_key = facet.key();
let Some(incidence) = facet_to_simplices_index.get(&facet_key) else {
let vertex_uuids = facet.vertices().map(Vertex::uuid).collect();
return Err(FacetError::FacetKeyNotFoundInCache {
facet_key,
cache_size: facet_to_simplices_index.len(),
vertex_uuids,
});
};
match incidence.one_sided_handle() {
Some(indexed_handle) if indexed_handle == handle => Ok(handle),
Some(indexed_handle) => Err(FacetError::BoundaryFacetHandleNotIndexed {
facet_key,
supplied_handle: handle,
indexed_handle,
}),
None => Err(FacetError::InvalidAdjacentSimplexCount {
found: incidence.incident_simplex_count(),
}),
}
}
fn sort_handles_by_storage_order(handles: &mut [FacetHandle]) {
handles.sort_unstable_by_key(|handle| {
(handle.simplex_key().data().as_ffi(), handle.facet_index())
});
}
#[must_use]
pub fn facet_key_from_vertices(vertices: &[VertexKey]) -> u64 {
if vertices.is_empty() {
return 0;
}
let mut key_values: SmallBuffer<u64, MAX_PRACTICAL_DIMENSION_SIZE> =
vertices.iter().map(|key| key.data().as_ffi()).collect();
key_values.sort_unstable();
stable_hash_u64_slice(&key_values)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builder::DelaunayTriangulationBuilder;
use crate::construction::{
ConstructionOptions, InitialSimplexStrategy, InsertionOrderStrategy,
};
use crate::core::tds::{Tds, VertexKey};
use crate::core::triangulation::Triangulation;
use crate::core::validation::TopologyGuarantee;
use crate::core::vertex::Vertex;
use crate::geometry::kernel::AdaptiveKernel;
use crate::triangulation::DelaunayTriangulation;
use crate::vertex;
use slotmap::{KeyData, SlotMap};
use std::assert_matches;
use std::{collections::HashSet, mem};
#[test]
fn test_usize_to_u8_conversion() {
assert_eq!(usize_to_u8(0, 4), Ok(0));
assert_eq!(usize_to_u8(1, 4), Ok(1));
assert_eq!(usize_to_u8(255, 256), Ok(255));
assert_eq!(usize_to_u8(u8::MAX as usize, 256), Ok(u8::MAX));
let result = usize_to_u8(256, 10);
assert!(result.is_err());
if let Err(FacetError::InvalidFacetIndexOverflow {
original_index,
facet_count,
}) = result
{
assert_eq!(original_index, 256); assert_eq!(facet_count, 10);
} else {
panic!("Expected InvalidFacetIndexOverflow error");
}
let result = usize_to_u8(usize::MAX, 5);
assert!(result.is_err());
if let Err(FacetError::InvalidFacetIndexOverflow {
original_index,
facet_count,
}) = result
{
assert_eq!(original_index, usize::MAX);
assert_eq!(facet_count, 5);
} else {
panic!("Expected InvalidFacetIndexOverflow error");
}
}
#[test]
fn test_facet_error_handling() {
let vertices = vec![vertex!([0.0]).unwrap(), vertex!([1.0]).unwrap()];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
assert_matches!(
FacetView::try_new(dt.tds(), simplex_key, 99),
Err(FacetError::InvalidFacetIndex { .. })
);
}
#[test]
fn facet_new() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
assert_eq!(facet.simplex_key(), simplex_key);
assert_eq!(facet.facet_index(), 0);
}
#[test]
fn facet_handle_view_roundtrip() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let handle = FacetHandle::try_new(dt.tds(), simplex_key, 1).unwrap();
let view = handle.view(dt.tds()).unwrap();
assert_eq!(view.simplex_key(), simplex_key);
assert_eq!(view.facet_index(), 1);
assert_eq!(view.handle(), handle);
}
#[test]
fn facet_handle_view_revalidates_against_tds() {
let tds: Tds<(), (), 3> = Tds::empty();
let handle = FacetHandle::from_validated(SimplexKey::default(), 0);
assert_matches!(
handle.view(&tds),
Err(FacetError::SimplexNotFoundInTriangulation)
);
}
#[test]
fn test_facet_new_success_coverage() {
let vertices_2d = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.5, 1.0]).unwrap(),
];
let dt_2d = DelaunayTriangulation::builder(&vertices_2d)
.build()
.unwrap();
let simplex_key_2d = dt_2d.simplices().next().unwrap().0;
let result_2d = FacetView::try_new(dt_2d.tds(), simplex_key_2d, 0);
assert!(result_2d.is_ok());
let facet_2d = result_2d.unwrap();
assert_eq!(facet_2d.vertices().count(), 2);
let vertices_1d = vec![vertex!([0.0]).unwrap(), vertex!([1.0]).unwrap()];
let dt_1d = DelaunayTriangulation::builder(&vertices_1d)
.build()
.unwrap();
let simplex_key_1d = dt_1d.simplices().next().unwrap().0;
let result_1d = FacetView::try_new(dt_1d.tds(), simplex_key_1d, 0);
assert!(result_1d.is_ok());
let facet_1d = result_1d.unwrap();
assert_eq!(facet_1d.vertices().count(), 1); }
#[test]
fn facet_new_with_incorrect_vertex() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
assert!(FacetView::try_new(dt.tds(), simplex_key, 4).is_err());
}
#[test]
fn facet_vertices() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
assert_eq!(facet.vertices().count(), 3);
}
#[test]
fn facet_partial_eq() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap();
assert_eq!(facet1, facet2);
assert_ne!(facet1, facet3);
}
#[test]
fn facet_clone() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let cloned_facet = facet.clone();
assert_eq!(facet, cloned_facet);
assert_eq!(facet.simplex_key(), cloned_facet.simplex_key());
assert_eq!(facet.facet_index(), cloned_facet.facet_index());
let simplex1 = facet.simplex();
let simplex2 = cloned_facet.simplex();
assert_eq!(simplex1.uuid(), simplex2.uuid());
let vertex1 = facet.opposite_vertex();
let vertex2 = cloned_facet.opposite_vertex();
assert_eq!(vertex1.uuid(), vertex2.uuid());
}
#[test]
fn facet_debug() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let debug_str = format!("{facet:?}");
assert!(debug_str.contains("FacetView"));
assert!(debug_str.contains("simplex_key"));
assert!(debug_str.contains("facet_index"));
assert!(debug_str.contains("dimension"));
}
#[test]
fn facet_with_typed_data() {
let vertices: Vec<Vertex<i32, 3>> = vec![
vertex!([0.0, 0.0, 0.0]; data = 1).unwrap(),
vertex!([1.0, 0.0, 0.0]; data = 2).unwrap(),
vertex!([0.0, 1.0, 0.0]; data = 3).unwrap(),
vertex!([0.0, 0.0, 1.0]; data = 4).unwrap(),
];
let options = ConstructionOptions::default()
.with_insertion_order(InsertionOrderStrategy::Input)
.with_initial_simplex_strategy(InitialSimplexStrategy::First);
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 3> =
DelaunayTriangulationBuilder::new(&vertices)
.topology_guarantee(TopologyGuarantee::DEFAULT)
.construction_options(options)
.build()
.unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet_vertices: Vec<_> = facet.vertices().collect();
assert_eq!(facet_vertices.len(), 3); let simplex = dt.tds().simplex(simplex_key).expect("simplex exists");
for &vertex_key in simplex.vertices().iter().skip(1) {
let expected_data = dt.tds().vertex(vertex_key).unwrap().data;
assert!(
facet_vertices.iter().any(|v| v.data == expected_data),
"Expected facet vertex data {expected_data:?} not found"
);
}
}
macro_rules! test_facet_dimensions {
($(
$test_name:ident => $dim:expr => $desc:expr => $expected_facet_vertices:expr => $vertices:expr
),+ $(,)?) => {
$(
#[test]
fn $test_name() {
let vertices = $vertices;
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
assert_eq!(facet.vertices().count(), $expected_facet_vertices,
"Facet of {}D {} should have {} vertices", $dim, $desc, $expected_facet_vertices);
}
pastey::paste! {
#[test]
fn [<$test_name _key_consistency>]() {
let vertices = $vertices;
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
assert_eq!(facet1.key(), facet2.key(),
"Same facet should produce same key");
let facet3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap();
assert_ne!(facet1.key(), facet3.key(),
"Different facets should produce different keys");
}
#[test]
fn [<$test_name _equality>]() {
let vertices = $vertices;
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap();
assert!(facet1 == facet2, "Same facet should be equal");
assert!(facet1 != facet3, "Different facets should not be equal");
}
#[test]
fn [<$test_name _all_facets>]() {
let vertices = $vertices;
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let expected_facets = $dim + 1;
let mut facet_keys = HashSet::new();
for i in 0..expected_facets {
let facet = FacetView::try_new(dt.tds(), simplex_key, u8::try_from(i).unwrap()).unwrap();
facet_keys.insert(facet.key());
}
assert_eq!(facet_keys.len(), expected_facets,
"{}D simplex should have {} unique facets", $dim, expected_facets);
}
}
)+
};
}
test_facet_dimensions! {
facet_2d_triangle => 2 => "triangle" => 2 => vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.5, 1.0]).unwrap(),
],
facet_3d_tetrahedron => 3 => "tetrahedron" => 3 => vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
],
facet_4d_simplex => 4 => "4-simplex" => 4 => vec![
vertex!([0.0, 0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 0.0, 1.0]).unwrap(),
],
facet_5d_simplex => 5 => "5-simplex" => 5 => vec![
vertex!([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(),
],
}
#[test]
fn facet_1d_edge() {
let vertices = vec![vertex!([0.0]).unwrap(), vertex!([1.0]).unwrap()];
let options = ConstructionOptions::default()
.with_insertion_order(InsertionOrderStrategy::Input)
.with_initial_simplex_strategy(InitialSimplexStrategy::First);
let dt = DelaunayTriangulation::builder(&vertices)
.construction_options(options)
.build()
.unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
assert_eq!(facet.vertices().count(), 1);
}
#[test]
fn all_facets_iter_rejects_dimension_above_u8_facet_index_capacity() {
let tds: Tds<(), (), 256> = Tds::empty();
let Err(err) = AllFacetsIter::try_new(&tds) else {
panic!("D=256 cannot fit facet indices in u8");
};
assert_matches!(
err,
FacetError::FacetIndexCapacityExceeded {
dimension: 256,
max_dimension: 255,
}
);
}
#[test]
fn tds_facets_reports_dimension_capacity_as_iterator_item() {
let tds: Tds<(), (), 256> = Tds::empty();
let mut facets = tds.facets();
assert_matches!(
facets.next(),
Some(Err(FacetError::FacetIndexCapacityExceeded {
dimension: 256,
max_dimension: 255,
}))
);
assert!(facets.next().is_none());
}
#[test]
fn try_simplex_facets_supports_d255_full_u8_index_range() {
let mut tds: Tds<(), (), 255> = Tds::empty();
let mut vertex_keys = Vec::with_capacity(usize::from(u8::MAX) + 1);
for i in 0..=usize::from(u8::MAX) {
let mut coords = [0.0; 255];
coords[0] = f64::from(u32::try_from(i).unwrap());
let vertex = vertex!(coords).unwrap();
vertex_keys.push(tds.insert_vertex_with_mapping(vertex).unwrap());
}
let simplex_key = tds
.insert_simplex_with_mapping(Simplex::try_new_with_data(vertex_keys, None).unwrap())
.unwrap();
let mut facets = tds.try_simplex_facets(simplex_key).unwrap();
assert_eq!(facets.len(), usize::from(u8::MAX) + 1);
for expected_index in 0..=u8::MAX {
let facet = facets.next().unwrap().unwrap();
assert_eq!(facet.facet_index(), expected_index);
}
assert!(facets.next().is_none());
}
fn overwide_simplex_tds() -> Tds<(), (), 2> {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let mut tds =
Triangulation::<AdaptiveKernel<f64>, (), (), 2>::build_initial_simplex(&vertices)
.unwrap();
let simplex_key = tds.simplex_keys().next().unwrap();
let first_vertex = tds.simplex(simplex_key).unwrap().vertices()[0];
{
let simplex = tds.simplex_mut(simplex_key).unwrap();
while simplex.number_of_vertices() <= usize::from(u8::MAX) + 1 {
simplex.push_vertex_key(first_vertex);
}
}
tds
}
fn tetrahedron_tds() -> Tds<(), (), 3> {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
Triangulation::<AdaptiveKernel<f64>, (), (), 3>::build_initial_simplex(&vertices).unwrap()
}
fn first_facet_view(tds: &Tds<(), (), 3>) -> FacetView<'_, (), (), 3> {
tds.facets().next().unwrap().unwrap()
}
#[test]
fn all_facets_iter_yields_overflow_error() {
let tds = overwide_simplex_tds();
let mut iter = tds.facets();
for facet in iter.by_ref().take(usize::from(u8::MAX) + 1) {
assert!(
facet.is_ok(),
"facet indices up to u8::MAX remain representable"
);
}
assert_matches!(
iter.next(),
Some(Err(FacetError::InvalidFacetIndexOverflow {
original_index: 256,
facet_count: 257,
}))
);
}
#[test]
fn all_facets_iter_stays_exhausted_after_completion() {
let tds = tetrahedron_tds();
let mut iter = tds.facets();
while iter.next().transpose().unwrap().is_some() {}
assert!(iter.next().is_none());
}
#[test]
fn boundary_facets_iter_yields_supplied_handles_in_storage_order() {
let tds = tetrahedron_tds();
let mut facet_to_simplices = FacetToSimplicesMap::default();
for facet in tds.facets() {
let facet = facet.unwrap();
let mut incidents = SmallBuffer::new();
let handle = FacetHandle::from_validated(facet.simplex_key(), facet.facet_index());
incidents.push(handle);
facet_to_simplices.insert(facet.key(), incidents);
}
let facet_to_simplices_index =
FacetToSimplicesIndex::try_from_map(&tds, &facet_to_simplices).unwrap();
let mut boundary_facet_handles = facet_to_simplices_index
.one_sided_handles()
.collect::<Vec<_>>();
boundary_facet_handles.reverse();
let mut iter =
BoundaryFacetsIter::try_new(&facet_to_simplices_index, boundary_facet_handles).unwrap();
assert_eq!(iter.len(), 4);
for expected_index in 0..4 {
let facet = iter.next().transpose().unwrap().unwrap();
assert_eq!(usize::from(facet.facet_index()), expected_index);
}
assert!(iter.next().is_none());
}
#[test]
fn one_sided_facets_iter_reports_len_and_storage_order() {
let tds = tetrahedron_tds();
let mut facet_to_simplices = FacetToSimplicesMap::default();
for facet in tds.facets() {
let facet = facet.unwrap();
let mut incidents = SmallBuffer::new();
let handle = FacetHandle::from_validated(facet.simplex_key(), facet.facet_index());
incidents.push(handle);
facet_to_simplices.insert(facet.key(), incidents);
}
let facet_to_simplices_index =
FacetToSimplicesIndex::try_from_map(&tds, &facet_to_simplices).unwrap();
let mut iter = OneSidedFacetsIter::try_new(&facet_to_simplices_index).unwrap();
assert_eq!(iter.len(), 4);
for expected_index in 0..4 {
let facet = iter.next().transpose().unwrap().unwrap();
assert_eq!(usize::from(facet.facet_index()), expected_index);
}
assert!(iter.next().is_none());
}
#[test]
fn boundary_facets_iter_revalidates_supplied_handles() {
let tds = tetrahedron_tds();
let facet_to_simplices_index =
FacetToSimplicesIndex::try_from_map(&tds, &FacetToSimplicesMap::default()).unwrap();
let stale_handle =
FacetHandle::from_validated(SimplexKey::from(KeyData::from_ffi(0xDEAD)), 0);
let Err(error) = BoundaryFacetsIter::try_new(&facet_to_simplices_index, vec![stale_handle])
else {
panic!("expected stale boundary handle to be rejected");
};
assert_matches!(error, FacetError::SimplexNotFoundInTriangulation);
}
#[test]
fn boundary_facets_iter_rejects_handle_missing_from_index() {
let tds = tetrahedron_tds();
let first_facet = first_facet_view(&tds);
let handle = first_facet.handle();
let facet_to_simplices_index =
FacetToSimplicesIndex::try_from_map(&tds, &FacetToSimplicesMap::default()).unwrap();
let Err(error) = BoundaryFacetsIter::try_new(&facet_to_simplices_index, vec![handle])
else {
panic!("expected missing boundary handle to be rejected");
};
assert_matches!(
error,
FacetError::FacetKeyNotFoundInCache {
cache_size: 0,
vertex_uuids,
..
} if vertex_uuids.len() == 3
);
}
#[test]
fn boundary_facets_iter_rejects_same_key_handle_not_indexed() {
let mut tds = tetrahedron_tds();
let simplex_key = tds.simplex_keys().next().unwrap();
let duplicated_vertex = tds.simplex(simplex_key).unwrap().vertices()[0];
{
let simplex = tds.simplex_mut(simplex_key).unwrap();
simplex.push_vertex_key(duplicated_vertex);
}
let indexed_handle = FacetHandle::from_validated(simplex_key, 0);
let supplied_handle = FacetHandle::from_validated(simplex_key, 4);
let facet_key = indexed_handle.view(&tds).unwrap().key();
assert_eq!(supplied_handle.view(&tds).unwrap().key(), facet_key);
let mut incidents = SmallBuffer::new();
incidents.push(indexed_handle);
let mut facet_to_simplices = FacetToSimplicesMap::default();
facet_to_simplices.insert(facet_key, incidents);
let facet_to_simplices_index =
FacetToSimplicesIndex::try_from_map(&tds, &facet_to_simplices).unwrap();
let Err(error) =
BoundaryFacetsIter::try_new(&facet_to_simplices_index, vec![supplied_handle])
else {
panic!("expected non-indexed boundary handle to be rejected");
};
assert_matches!(
error,
FacetError::BoundaryFacetHandleNotIndexed {
facet_key: observed_facet_key,
supplied_handle: observed_supplied_handle,
indexed_handle: observed_indexed_handle,
} if observed_facet_key == facet_key
&& observed_supplied_handle == supplied_handle
&& observed_indexed_handle == indexed_handle
);
}
#[test]
fn boundary_facets_iter_errors_on_empty_multiplicity() {
let tds = tetrahedron_tds();
let first_facet = first_facet_view(&tds);
let mut facet_to_simplices = FacetToSimplicesMap::default();
facet_to_simplices.insert(first_facet.key(), SmallBuffer::new());
assert_matches!(
FacetToSimplicesIndex::try_from_map(&tds, &facet_to_simplices),
Err(FacetError::InvalidFacetMultiplicity { found: 0, .. })
);
}
#[test]
fn boundary_facets_iter_errors_on_overshared_multiplicity() {
let tds = tetrahedron_tds();
let first_facet = first_facet_view(&tds);
let handle =
FacetHandle::from_validated(first_facet.simplex_key(), first_facet.facet_index());
let mut incidents = SmallBuffer::new();
incidents.push(handle);
incidents.push(handle);
incidents.push(handle);
let mut facet_to_simplices = FacetToSimplicesMap::default();
facet_to_simplices.insert(first_facet.key(), incidents);
assert_matches!(
FacetToSimplicesIndex::try_from_map(&tds, &facet_to_simplices),
Err(FacetError::InvalidFacetMultiplicity { found: 3, .. })
);
}
#[test]
fn facet_index_rejects_duplicate_two_sided_incident_handle() {
let tds = tetrahedron_tds();
let first_facet = first_facet_view(&tds);
let handle =
FacetHandle::from_validated(first_facet.simplex_key(), first_facet.facet_index());
let mut incidents = SmallBuffer::new();
incidents.push(handle);
incidents.push(handle);
let mut facet_to_simplices = FacetToSimplicesMap::default();
facet_to_simplices.insert(first_facet.key(), incidents);
assert_matches!(
FacetToSimplicesIndex::try_from_map(&tds, &facet_to_simplices),
Err(FacetError::DuplicateFacetIncidentHandle {
facet_key,
handle: repeated
}) if facet_key == first_facet.key() && repeated == handle
);
}
#[test]
fn facet_index_rejects_handle_with_mismatched_facet_key() {
let tds = tetrahedron_tds();
let mut facets = tds.facets();
let first = facets.next().unwrap().unwrap();
let second = facets.next().unwrap().unwrap();
let wrong_handle = second.handle();
let mut incidents = SmallBuffer::new();
incidents.push(wrong_handle);
let mut facet_to_simplices = FacetToSimplicesMap::default();
facet_to_simplices.insert(first.key(), incidents);
assert_matches!(
FacetToSimplicesIndex::try_from_map(&tds, &facet_to_simplices),
Err(FacetError::FacetHandleKeyMismatch {
expected_facet_key,
actual_facet_key,
handle,
}) if expected_facet_key == first.key()
&& actual_facet_key == second.key()
&& handle == wrong_handle
);
}
#[test]
fn facet_error_display() {
let simplex_error = FacetError::SimplexDoesNotContainVertex;
assert_eq!(
simplex_error.to_string(),
"The simplex does not contain the vertex!"
);
}
#[test]
fn facet_error_debug() {
let simplex_error = FacetError::SimplexDoesNotContainVertex;
let simplex_debug = format!("{simplex_error:?}");
assert!(simplex_debug.contains("SimplexDoesNotContainVertex"));
}
#[test]
fn test_facet_key_consistency() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); let facet2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap(); let facet3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap();
assert_eq!(
facet1.key(),
facet2.key(),
"Keys should be consistent for the same facet"
);
assert_ne!(
facet1.key(),
facet3.key(),
"Keys should be different for facets with different vertices"
);
}
#[test]
fn facet_vertices_empty_simplex() {
let vertices = vec![vertex!([0.0]).unwrap(), vertex!([1.0]).unwrap()];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
assert_eq!(facet.vertices().count(), 1);
let other_facet = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap();
assert_eq!(other_facet.vertices().count(), 1);
}
#[test]
fn facet_vertices_ordering() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet = FacetView::try_new(dt.tds(), simplex_key, 2).unwrap();
assert_eq!(facet.vertices().count(), 3);
}
#[test]
fn facet_eq_different_vertices() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet2 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap();
let facet3 = FacetView::try_new(dt.tds(), simplex_key, 2).unwrap();
let facet4 = FacetView::try_new(dt.tds(), simplex_key, 3).unwrap();
assert_ne!(facet1, facet2);
assert_ne!(facet1, facet3);
assert_ne!(facet1, facet4);
assert_ne!(facet2, facet3);
assert_ne!(facet2, facet4);
assert_ne!(facet3, facet4);
}
#[test]
fn facet_key_hash() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap();
assert_eq!(facet1.key(), facet2.key());
assert_ne!(facet1.key(), facet3.key());
}
#[test]
fn test_facet_key_from_vertices() {
let mut temp_vertices: SlotMap<VertexKey, ()> = SlotMap::with_key();
let vertices = vec![
temp_vertices.insert(()),
temp_vertices.insert(()),
temp_vertices.insert(()),
];
let key1 = facet_key_from_vertices(&vertices);
let mut reversed_keys = vertices;
reversed_keys.reverse();
let key2 = facet_key_from_vertices(&reversed_keys);
assert_eq!(
key1, key2,
"Facet keys should be identical for the same vertices in different order"
);
let different_keys = vec![
temp_vertices.insert(()),
temp_vertices.insert(()),
temp_vertices.insert(()),
];
let key3 = facet_key_from_vertices(&different_keys);
assert_ne!(
key1, key3,
"Different vertices should produce different keys"
);
let empty_keys: Vec<VertexKey> = vec![];
let key_empty = facet_key_from_vertices(&empty_keys);
assert_eq!(key_empty, 0, "Empty vertex keys should produce key 0");
}
#[test]
fn test_facet_view_creation() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet_view = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
assert_eq!(facet_view.simplex_key(), simplex_key);
assert_eq!(facet_view.facet_index(), 0);
let result = FacetView::try_new(dt.tds(), simplex_key, 10);
assert_matches!(result, Err(FacetError::InvalidFacetIndex { .. }));
}
#[test]
fn test_facet_view_vertices_iteration() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet_view = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet_vertices: Vec<_> = facet_view.vertices().collect();
assert_eq!(facet_vertices.len(), 3);
let simplex = dt.tds().simplex(simplex_key).expect("simplex exists");
let opposite_vertex = dt
.tds()
.vertex(simplex.vertices()[0])
.expect("opposite vertex exists");
assert!(
!facet_vertices
.iter()
.any(|v| v.uuid() == opposite_vertex.uuid())
);
}
#[test]
fn test_facet_view_opposite_vertex() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet_view = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap();
let opposite = facet_view.opposite_vertex();
let simplex = dt.tds().simplex(simplex_key).expect("simplex exists");
let simplex_vertex_keys = simplex.vertices();
let expected_vertex = dt
.tds()
.vertex(simplex_vertex_keys[1])
.expect("vertex exists");
assert_eq!(opposite.uuid(), expected_vertex.uuid());
}
#[test]
fn test_facet_view_key_computation() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet_view = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let key = facet_view.key();
assert_ne!(key, 0);
}
#[test]
fn test_try_simplex_facets() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet_views = dt.tds().try_simplex_facets(simplex_key).unwrap();
let facet_count = facet_views.len();
assert_eq!(facet_count, 4);
for (i, facet_view) in facet_views.enumerate() {
let facet_view = facet_view.unwrap();
assert_eq!(
facet_view.facet_index(),
usize_to_u8(i, facet_count).unwrap()
);
assert_eq!(facet_view.simplex_key(), simplex_key);
}
}
#[test]
fn test_facet_view_equality() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet_view1 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet_view2 = FacetView::try_new(dt.tds(), simplex_key, 0).unwrap();
let facet_view3 = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap();
assert_eq!(facet_view1, facet_view2);
assert_ne!(facet_view1, facet_view3);
}
#[test]
fn test_facet_view_debug() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet_view = FacetView::try_new(dt.tds(), simplex_key, 1).unwrap();
let debug_str = format!("{facet_view:?}");
assert!(debug_str.contains("FacetView"));
assert!(debug_str.contains("simplex_key"));
assert!(debug_str.contains("facet_index"));
assert!(debug_str.contains("dimension"));
}
#[test]
fn test_facet_view_memory_efficiency() {
let lightweight_size = mem::size_of::<FacetView<(), (), 3>>();
let payload_independent_size = mem::size_of::<FacetView<[u8; 1024], [u8; 1024], 3>>();
assert_eq!(
lightweight_size, payload_independent_size,
"FacetView must borrow vertex/simplex payloads rather than owning them"
);
assert!(
lightweight_size <= 256,
"FacetView should stay a compact borrowed view, got {lightweight_size} bytes"
);
}
}