use axiolid_contracts::ExecutionOptions;
use axiolid_core::{BooleanOperator, Point3, Scalar, Tolerance, Vec3};
use axiolid_mesh::TriMesh;
use axiolid_mesh_boolean_contract::MeshBoolean;
use crate::DecomposeError;
pub enum Splitter<'a> {
HandRolled,
Provider(&'a dyn MeshBoolean),
}
impl std::fmt::Debug for Splitter<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::HandRolled => f.write_str("Splitter::HandRolled"),
Self::Provider(_) => f.write_str("Splitter::Provider"),
}
}
}
impl Splitter<'_> {
pub fn split(
&self,
mesh: &TriMesh,
normal: Vec3,
offset: Scalar,
tolerance: Tolerance,
) -> Result<(Option<TriMesh>, Option<TriMesh>), DecomposeError> {
match self {
Self::HandRolled => Ok((
crate::clip(mesh, normal, offset, tolerance),
crate::clip(mesh, -normal, -offset, tolerance),
)),
Self::Provider(provider) => {
let behind = intersect_half_space(*provider, mesh, normal, offset, tolerance)?;
let front = intersect_half_space(*provider, mesh, -normal, -offset, tolerance)?;
Ok((behind, front))
}
}
}
}
fn intersect_half_space(
provider: &dyn MeshBoolean,
mesh: &TriMesh,
normal: Vec3,
offset: Scalar,
tolerance: Tolerance,
) -> Result<Option<TriMesh>, DecomposeError> {
let Some(reach) = enclosing_reach(mesh) else {
return Ok(None);
};
let tool = half_space_box(normal, offset, reach);
let options = ExecutionOptions::new(tolerance);
let outcome = provider
.boolean(mesh, &tool, BooleanOperator::Intersection, &options)
.map_err(|error| DecomposeError::SplitFailed(error.to_string()))?;
if outcome.mesh.indices.is_empty() {
return Ok(None);
}
Ok(Some(outcome.mesh))
}
fn enclosing_reach(mesh: &TriMesh) -> Option<Scalar> {
if mesh.positions.is_empty() {
return None;
}
let mut min = Point3::new(Scalar::INFINITY, Scalar::INFINITY, Scalar::INFINITY);
let mut max = Point3::new(
Scalar::NEG_INFINITY,
Scalar::NEG_INFINITY,
Scalar::NEG_INFINITY,
);
for p in &mesh.positions {
min = Point3::new(min.x.min(p.x), min.y.min(p.y), min.z.min(p.z));
max = Point3::new(max.x.max(p.x), max.y.max(p.y), max.z.max(p.z));
}
let span = max - min;
if !span.is_finite() {
return None;
}
let diagonal = span.length();
if diagonal <= 0.0 {
return None;
}
Some(diagonal * 2.0)
}
fn half_space_box(normal: Vec3, offset: Scalar, reach: Scalar) -> TriMesh {
let unit = normal.normalize();
let seed = if unit.x.abs() <= unit.y.abs() && unit.x.abs() <= unit.z.abs() {
Vec3::X
} else if unit.y.abs() <= unit.z.abs() {
Vec3::Y
} else {
Vec3::Z
};
let u = unit.cross(seed).normalize();
let v = unit.cross(u);
let centre = unit * offset;
let corner = |du: Scalar, dv: Scalar, dn: Scalar| centre + u * du + v * dv + unit * dn;
let positions = vec![
corner(-reach, -reach, 0.0),
corner(reach, -reach, 0.0),
corner(reach, reach, 0.0),
corner(-reach, reach, 0.0),
corner(-reach, -reach, -reach * 2.0),
corner(reach, -reach, -reach * 2.0),
corner(reach, reach, -reach * 2.0),
corner(-reach, reach, -reach * 2.0),
];
let indices = vec![
0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, 7, 2, 7, 3, 3, 7, 4, 3, 4, 0, ];
TriMesh::new(positions, indices)
}