#![forbid(unsafe_code)]
pub use crate::core::algorithms::flips::DelaunayRepairStats;
pub use crate::core::algorithms::pl_manifold_repair::{
PlManifoldRepairError, PlManifoldRepairStats,
};
use crate::core::algorithms::flips::DelaunayRepairError;
use crate::core::algorithms::pl_manifold_repair::{
PlManifoldRepairConfig, repair_facet_oversharing,
};
use crate::core::delaunay_triangulation::{DelaunayRepairHeuristicConfig, DelaunayTriangulation};
use crate::core::traits::data_type::DataType;
use crate::core::vertex::Vertex;
use crate::geometry::kernel::{ExactPredicates, Kernel};
use crate::geometry::traits::coordinate::{CoordinateScalar, ScalarAccumulative};
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DelaunayizeConfig {
pub topology_max_iterations: usize,
pub topology_max_cells_removed: usize,
pub fallback_rebuild: bool,
pub delaunay_max_flips: Option<usize>,
}
impl Default for DelaunayizeConfig {
fn default() -> Self {
Self {
topology_max_iterations: 64,
topology_max_cells_removed: 10_000,
fallback_rebuild: false,
delaunay_max_flips: None,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct DelaunayizeOutcome<T, U, V, const D: usize>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
pub topology_repair: PlManifoldRepairStats<T, U, V, D>,
pub delaunay_repair: DelaunayRepairStats,
pub used_fallback_rebuild: bool,
}
#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum DelaunayizeError {
#[error("Topology repair failed: {0}")]
TopologyRepairFailed(#[from] PlManifoldRepairError),
#[error("Delaunay repair failed ({context}): {source}")]
DelaunayRepairFailed {
#[source]
source: DelaunayRepairError,
context: String,
},
}
impl From<DelaunayRepairError> for DelaunayizeError {
fn from(err: DelaunayRepairError) -> Self {
Self::DelaunayRepairFailed {
source: err,
context: "fallback not enabled".to_string(),
}
}
}
pub fn delaunayize_by_flips<K, U, V, const D: usize>(
dt: &mut DelaunayTriangulation<K, U, V, D>,
config: DelaunayizeConfig,
) -> Result<DelaunayizeOutcome<K::Scalar, U, V, D>, DelaunayizeError>
where
K: Kernel<D> + ExactPredicates,
K::Scalar: ScalarAccumulative,
U: DataType,
V: DataType,
{
let fallback_vertices: Option<Vec<Vertex<K::Scalar, U, D>>> = if config.fallback_rebuild {
Some(
dt.as_triangulation()
.tds
.vertices()
.map(|(_, v)| Vertex::new_with_uuid(*v.point(), v.uuid(), v.data))
.collect(),
)
} else {
None
};
let pl_config = PlManifoldRepairConfig {
max_iterations: config.topology_max_iterations,
max_cells_removed: config.topology_max_cells_removed,
};
let topology_stats = match repair_facet_oversharing(
&mut dt.as_triangulation_mut().tds,
&pl_config,
) {
Ok(stats) => stats,
Err(topo_err) => {
if let Some(ref verts) = fallback_vertices {
match DelaunayTriangulation::with_kernel(&dt.as_triangulation().kernel, verts) {
Ok(rebuilt) => {
*dt = rebuilt;
return Ok(DelaunayizeOutcome {
topology_repair: PlManifoldRepairStats::default(),
delaunay_repair: DelaunayRepairStats::default(),
used_fallback_rebuild: true,
});
}
Err(rebuild_err) => {
return Err(DelaunayizeError::TopologyRepairFailed(
PlManifoldRepairError::Tds(
crate::core::triangulation_data_structure::TdsError::InconsistentDataStructure {
message: format!(
"topology repair failed ({topo_err}); fallback rebuild also failed: {rebuild_err}"
),
},
),
));
}
}
}
return Err(topo_err.into());
}
};
let delaunay_result = if let Some(max_flips) = config.delaunay_max_flips {
dt.repair_delaunay_with_flips_advanced(DelaunayRepairHeuristicConfig {
max_flips: Some(max_flips),
..DelaunayRepairHeuristicConfig::default()
})
.map(|outcome| outcome.stats)
} else {
dt.repair_delaunay_with_flips()
};
match delaunay_result {
Ok(delaunay_stats) => Ok(DelaunayizeOutcome {
topology_repair: topology_stats,
delaunay_repair: delaunay_stats,
used_fallback_rebuild: false,
}),
Err(repair_err) => {
if config.fallback_rebuild {
let vertices: Vec<Vertex<K::Scalar, U, D>> = dt
.as_triangulation()
.tds
.vertices()
.map(|(_, v)| Vertex::new_with_uuid(*v.point(), v.uuid(), v.data))
.collect();
match DelaunayTriangulation::with_kernel(&dt.as_triangulation().kernel, &vertices) {
Ok(rebuilt) => {
*dt = rebuilt;
Ok(DelaunayizeOutcome {
topology_repair: topology_stats,
delaunay_repair: DelaunayRepairStats::default(),
used_fallback_rebuild: true,
})
}
Err(rebuild_err) => Err(DelaunayizeError::DelaunayRepairFailed {
source: repair_err,
context: format!("fallback rebuild also failed: {rebuild_err}"),
}),
}
} else {
Err(DelaunayizeError::from(repair_err))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vertex;
fn init_tracing() {
let _ = tracing_subscriber::fmt::try_init();
}
#[test]
fn test_config_defaults() {
init_tracing();
let config = DelaunayizeConfig::default();
assert_eq!(config.topology_max_iterations, 64);
assert_eq!(config.topology_max_cells_removed, 10_000);
assert!(!config.fallback_rebuild);
}
#[test]
fn test_already_delaunay_3d() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let mut dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let outcome = delaunayize_by_flips(&mut dt, DelaunayizeConfig::default()).unwrap();
assert!(outcome.topology_repair.succeeded);
assert!(!outcome.used_fallback_rebuild);
assert!(dt.validate().is_ok());
}
#[test]
fn test_already_delaunay_2d() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 1.0]),
];
let mut dt: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::new(&vertices).unwrap();
let outcome = delaunayize_by_flips(&mut dt, DelaunayizeConfig::default()).unwrap();
assert!(outcome.topology_repair.succeeded);
assert!(dt.validate().is_ok());
}
#[test]
fn test_outcome_populated_on_success() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
vertex!([0.5, 0.5, 0.5]),
];
let mut dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let outcome = delaunayize_by_flips(&mut dt, DelaunayizeConfig::default()).unwrap();
assert!(outcome.topology_repair.succeeded);
assert_eq!(outcome.topology_repair.cells_removed, 0);
assert!(!outcome.used_fallback_rebuild);
}
#[test]
fn test_fallback_disabled_by_default() {
init_tracing();
let config = DelaunayizeConfig::default();
assert!(!config.fallback_rebuild);
}
#[test]
fn test_fallback_enabled_on_valid_triangulation() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let mut dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let config = DelaunayizeConfig {
fallback_rebuild: true,
..DelaunayizeConfig::default()
};
let outcome = delaunayize_by_flips(&mut dt, config).unwrap();
assert!(!outcome.used_fallback_rebuild);
}
#[test]
fn test_deterministic_repeated_runs() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
vertex!([0.5, 0.5, 0.5]),
];
let config = DelaunayizeConfig::default();
let mut dt1: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let outcome1 = delaunayize_by_flips(&mut dt1, config).unwrap();
let mut dt2: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let outcome2 = delaunayize_by_flips(&mut dt2, config).unwrap();
assert_eq!(
outcome1.topology_repair.cells_removed,
outcome2.topology_repair.cells_removed
);
assert_eq!(
outcome1.used_fallback_rebuild,
outcome2.used_fallback_rebuild
);
}
}