use axiolid_contracts::{
Backend, CancellationGranularity, Determinism, ExecutionOptions, GeomResult, ScratchRequirement,
};
use axiolid_core::BooleanOperator;
use axiolid_mesh::TriMesh;
use axiolid_mesh_contracts::SolidRequirements;
use crate::{merge_fates, BooleanEvidence, BooleanOutcome};
pub trait MeshBoolean: Backend {
fn scratch_requirement(&self) -> ScratchRequirement {
ScratchRequirement::Unbounded
}
fn determinism(&self) -> Determinism {
Determinism::BestEffort
}
fn cancellation_granularity(&self) -> CancellationGranularity {
CancellationGranularity::None
}
fn solid_requirements(&self) -> SolidRequirements {
SolidRequirements::Oriented
}
fn boolean(
&self,
subject: &TriMesh,
tool: &TriMesh,
operation: BooleanOperator,
options: &ExecutionOptions,
) -> GeomResult<BooleanOutcome>;
fn subtract_many(
&self,
subject: &TriMesh,
tools: &[TriMesh],
options: &ExecutionOptions,
) -> GeomResult<BooleanOutcome> {
let mut evidence = BooleanEvidence {
subject_triangles: subject.triangle_count(),
tool_triangles: tools.iter().map(TriMesh::triangle_count).sum(),
output_triangles: subject.triangle_count(),
output_components: 1,
..BooleanEvidence::default()
};
let mut result = subject.clone();
for tool in tools {
options.check_cancelled()?;
let outcome = self.boolean(&result, tool, BooleanOperator::Difference, options)?;
evidence.absorb(outcome.evidence);
result = outcome.mesh;
}
Ok(BooleanOutcome::new(result, evidence))
}
fn union_many(
&self,
solids: &[TriMesh],
options: &ExecutionOptions,
) -> GeomResult<BooleanOutcome> {
let Some((first, rest)) = solids.split_first() else {
return Ok(BooleanOutcome::new(
TriMesh::default(),
BooleanEvidence::default(),
));
};
let mut evidence = BooleanEvidence {
subject_triangles: first.triangle_count(),
tool_triangles: rest.iter().map(TriMesh::triangle_count).sum(),
output_triangles: first.triangle_count(),
output_components: 1,
..BooleanEvidence::default()
};
let mut result = first.clone();
for solid in rest {
options.check_cancelled()?;
let outcome = self.boolean(&result, solid, BooleanOperator::Union, options)?;
evidence.absorb(outcome.evidence);
result = outcome.mesh;
}
Ok(BooleanOutcome::new(result, evidence))
}
}
pub fn symmetric_difference_via_composition<P>(
provider: &P,
subject: &TriMesh,
tool: &TriMesh,
options: &ExecutionOptions,
) -> GeomResult<BooleanOutcome>
where
P: MeshBoolean + ?Sized,
{
options.check_cancelled()?;
let union = provider.boolean(subject, tool, BooleanOperator::Union, options)?;
options.check_cancelled()?;
let intersection = provider.boolean(subject, tool, BooleanOperator::Intersection, options)?;
if intersection.mesh.indices.is_empty() {
let mut evidence = union.evidence;
evidence.sub_operations = 2;
evidence.coincident_faces_encountered |= intersection.evidence.coincident_faces_encountered;
return Ok(BooleanOutcome::new(union.mesh, evidence));
}
options.check_cancelled()?;
let difference = provider.boolean(
&union.mesh,
&intersection.mesh,
BooleanOperator::Difference,
options,
)?;
let mut evidence = difference.evidence;
evidence.attribute_fates =
merge_fates(&union.evidence.attribute_fates, evidence.attribute_fates);
evidence.subject_triangles = subject.triangle_count();
evidence.tool_triangles = tool.triangle_count();
evidence.sub_operations = 3;
evidence.coincident_faces_encountered |= union.evidence.coincident_faces_encountered
|| intersection.evidence.coincident_faces_encountered;
Ok(BooleanOutcome::new(difference.mesh, evidence))
}