use super::{record_csg_op, ClippingProcessor};
use crate::diagnostics::{BoolFailureReason, BoolOp};
use crate::kernel::mesh_bridge::{subtract_many, BatchSubtract};
use crate::mesh::Mesh;
#[must_use]
#[derive(Debug, Clone)]
pub enum GroupCut {
Cut(Mesh),
Retessellated(Mesh),
Rejected(GroupReject),
}
impl GroupCut {
pub fn into_mesh(self) -> Option<Mesh> {
match self {
Self::Cut(m) | Self::Retessellated(m) => Some(m),
Self::Rejected(_) => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GroupReject {
EmptyHost,
NoOverlap,
BudgetTripped,
Nonconforming,
Unchanged,
InvalidOutput,
GateRejected,
}
const MAX_CUTTERS_PER_ARRANGEMENT: usize = 16;
impl ClippingProcessor {
pub fn subtract_mesh_many(&self, host_mesh: &Mesh, cutters: &[&Mesh]) -> GroupCut {
if host_mesh.is_empty() {
return GroupCut::Rejected(GroupReject::EmptyHost);
}
let live: Vec<&Mesh> = cutters
.iter()
.copied()
.filter(|c| !c.is_empty() && Self::bounds_overlap(host_mesh, c))
.collect();
if live.is_empty() {
return GroupCut::Rejected(GroupReject::NoOverlap);
}
let mut cut: Option<Mesh> = None;
for chunk in live.chunks(MAX_CUTTERS_PER_ARRANGEMENT) {
let current = cut.as_ref().unwrap_or(host_mesh);
let chunk_tris: usize = chunk.iter().map(|c| c.triangle_count()).sum();
record_csg_op(0, current.triangle_count(), chunk_tris);
crate::kernel::budget::begin();
let raw = subtract_many(current, chunk);
if crate::kernel::budget::tripped() {
return GroupCut::Rejected(GroupReject::BudgetTripped);
}
let raw = match raw {
BatchSubtract::Cut(raw) => raw,
BatchSubtract::Unchanged => continue,
BatchSubtract::Nonconforming => {
return GroupCut::Rejected(GroupReject::Nonconforming);
}
};
let next = self.consolidate(raw);
if !next.is_empty() && !self.validate_mesh(&next) {
self.record_failure(BoolOp::Difference, BoolFailureReason::KernelOutputInvalid);
return GroupCut::Rejected(GroupReject::InvalidOutput);
}
if self.accept_gates_reject(BoolOp::Difference, &next) {
return GroupCut::Rejected(GroupReject::GateRejected);
}
cut = Some(next);
}
match cut {
Some(result) => {
self.record_topology_tear(BoolOp::Difference, &result);
GroupCut::Cut(result)
}
None => GroupCut::Rejected(GroupReject::Unchanged),
}
}
}