#![forbid(unsafe_code)]
use crate::core::algorithms::incremental_insertion::InsertionError;
use crate::core::tds::CellKey;
use crate::core::triangulation::TopologyGuarantee;
use crate::triangulation::delaunay::{DelaunayCheckPolicy, DelaunayRepairPolicy};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TopologicalOperation {
InsertVertex,
DeleteVertex,
FacetFlip,
CavityFlip,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepairDecision {
Proceed,
Skip {
reason: RepairSkipReason,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepairSkipReason {
PolicyDisabled,
Inadmissible {
operation: TopologicalOperation,
required: TopologyGuarantee,
found: TopologyGuarantee,
},
}
impl DelaunayRepairPolicy {
#[must_use]
pub const fn decide(
self,
insertion_count: usize,
topology: TopologyGuarantee,
operation: TopologicalOperation,
) -> RepairDecision {
if !self.should_repair(insertion_count) {
return RepairDecision::Skip {
reason: RepairSkipReason::PolicyDisabled,
};
}
if !operation.is_admissible_under(topology) {
return RepairDecision::Skip {
reason: RepairSkipReason::Inadmissible {
operation,
required: operation.required_topology(),
found: topology,
},
};
}
RepairDecision::Proceed
}
}
impl TopologicalOperation {
#[must_use]
pub const fn requires_pl_manifold(self) -> bool {
matches!(self, Self::CavityFlip)
}
#[must_use]
pub const fn is_admissible_under(self, topology: TopologyGuarantee) -> bool {
match topology {
TopologyGuarantee::PLManifold | TopologyGuarantee::PLManifoldStrict => true,
TopologyGuarantee::Pseudomanifold => !self.requires_pl_manifold(),
}
}
#[must_use]
pub const fn required_topology(self) -> TopologyGuarantee {
if self.requires_pl_manifold() {
TopologyGuarantee::PLManifold
} else {
TopologyGuarantee::Pseudomanifold
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InsertionResult {
#[default]
Inserted,
SkippedDuplicate,
SkippedDegeneracy,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct InsertionStatistics {
pub attempts: usize,
pub cells_removed_during_repair: usize,
pub result: InsertionResult,
}
impl InsertionStatistics {
#[must_use]
pub const fn used_perturbation(&self) -> bool {
self.attempts > 1
}
#[must_use]
pub const fn success(&self) -> bool {
matches!(self.result, InsertionResult::Inserted)
}
#[must_use]
pub const fn skipped(&self) -> bool {
matches!(
self.result,
InsertionResult::SkippedDuplicate | InsertionResult::SkippedDegeneracy
)
}
#[must_use]
pub const fn skipped_duplicate(&self) -> bool {
matches!(self.result, InsertionResult::SkippedDuplicate)
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct DelaunayInsertionState {
pub last_inserted_cell: Option<CellKey>,
pub delaunay_repair_policy: DelaunayRepairPolicy,
pub delaunay_check_policy: DelaunayCheckPolicy,
pub delaunay_repair_insertion_count: usize,
pub use_global_repair_fallback: bool,
}
impl DelaunayInsertionState {
#[must_use]
pub const fn new() -> Self {
Self {
last_inserted_cell: None,
delaunay_repair_policy: DelaunayRepairPolicy::EveryInsertion,
delaunay_check_policy: DelaunayCheckPolicy::EndOnly,
delaunay_repair_insertion_count: 0,
use_global_repair_fallback: true,
}
}
}
#[derive(Debug, Clone)]
pub enum InsertionOutcome {
Inserted {
vertex_key: crate::core::tds::VertexKey,
hint: Option<crate::core::tds::CellKey>,
},
Skipped {
error: InsertionError,
},
}
#[derive(Clone, Copy, Debug, Default)]
#[expect(
clippy::struct_excessive_bools,
reason = "A small set of boolean flags is clearer here than bitflags or an enum"
)]
pub struct SuspicionFlags {
pub perturbation_used: bool,
pub empty_conflict_region: bool,
pub fallback_star_split: bool,
pub repair_loop_entered: bool,
pub cells_removed: bool,
pub neighbor_pointers_rebuilt: bool,
}
impl SuspicionFlags {
#[inline]
#[must_use]
pub const fn is_suspicious(&self) -> bool {
self.perturbation_used
|| self.empty_conflict_region
|| self.fallback_star_split
|| self.repair_loop_entered
|| self.cells_removed
|| self.neighbor_pointers_rebuilt
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_topological_operation_admissibility() {
assert!(!TopologicalOperation::FacetFlip.requires_pl_manifold());
assert!(TopologicalOperation::CavityFlip.requires_pl_manifold());
assert!(!TopologicalOperation::InsertVertex.requires_pl_manifold());
assert!(!TopologicalOperation::DeleteVertex.requires_pl_manifold());
assert!(TopologicalOperation::FacetFlip.is_admissible_under(TopologyGuarantee::PLManifold));
assert!(
TopologicalOperation::FacetFlip
.is_admissible_under(TopologyGuarantee::PLManifoldStrict)
);
assert!(
TopologicalOperation::FacetFlip.is_admissible_under(TopologyGuarantee::Pseudomanifold)
);
assert!(
TopologicalOperation::InsertVertex
.is_admissible_under(TopologyGuarantee::Pseudomanifold)
);
}
#[test]
fn test_repair_policy_decide_respects_policy_and_topology() {
let op = TopologicalOperation::FacetFlip;
let decision =
DelaunayRepairPolicy::EveryInsertion.decide(1, TopologyGuarantee::PLManifold, op);
assert!(matches!(decision, RepairDecision::Proceed));
let decision =
DelaunayRepairPolicy::EveryInsertion.decide(1, TopologyGuarantee::Pseudomanifold, op);
assert!(matches!(decision, RepairDecision::Proceed));
let decision = DelaunayRepairPolicy::Never.decide(1, TopologyGuarantee::PLManifold, op);
assert!(matches!(
decision,
RepairDecision::Skip {
reason: RepairSkipReason::PolicyDisabled
}
));
}
#[test]
fn test_repair_policy_decide_inadmissible_under_pseudomanifold() {
let op = TopologicalOperation::CavityFlip;
let decision =
DelaunayRepairPolicy::EveryInsertion.decide(1, TopologyGuarantee::Pseudomanifold, op);
assert!(matches!(
decision,
RepairDecision::Skip {
reason: RepairSkipReason::Inadmissible {
operation: TopologicalOperation::CavityFlip,
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
}
}
));
}
#[test]
fn test_required_topology_returns_correct_guarantee() {
assert_eq!(
TopologicalOperation::CavityFlip.required_topology(),
TopologyGuarantee::PLManifold
);
assert_eq!(
TopologicalOperation::FacetFlip.required_topology(),
TopologyGuarantee::Pseudomanifold
);
assert_eq!(
TopologicalOperation::InsertVertex.required_topology(),
TopologyGuarantee::Pseudomanifold
);
assert_eq!(
TopologicalOperation::DeleteVertex.required_topology(),
TopologyGuarantee::Pseudomanifold
);
}
}