#![forbid(unsafe_code)]
use axiolid_core::{PlaneFrame, Point2, Tolerance};
use axiolid_mesh::TriangleMeshView;
use axiolid_overlay::{
overlay, union_soup, FillRule, OverlayError, OverlayInput, OverlayOperation, Polygon, Ring,
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ProjectionError {
InvalidPlane,
NonFinitePosition,
IndexOutOfRange,
Planar(OverlayError),
}
impl From<OverlayError> for ProjectionError {
fn from(error: OverlayError) -> Self {
Self::Planar(error)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct ProjectionEvidence {
pub input_triangles: usize,
pub degenerate_triangles: usize,
pub output_polygons: usize,
pub output_holes: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Projection {
pub polygons: Vec<Polygon>,
pub evidence: ProjectionEvidence,
}
pub type Plane = PlaneFrame;
pub fn project_mesh<M: TriangleMeshView + ?Sized>(
mesh: &M,
plane: Plane,
tolerance: Tolerance,
) -> Result<Projection, ProjectionError> {
let triangles = collect_triangles(mesh, plane, tolerance)?;
let polygons = union_all(triangles.rings, tolerance)?;
let evidence = ProjectionEvidence {
input_triangles: mesh.triangle_count(),
degenerate_triangles: triangles.degenerate,
output_polygons: polygons.len(),
output_holes: polygons.iter().map(|p| p.holes.len()).sum(),
};
Ok(Projection { polygons, evidence })
}
struct Collected {
rings: Vec<Ring>,
degenerate: usize,
}
fn collect_triangles<M: TriangleMeshView + ?Sized>(
mesh: &M,
plane: Plane,
tolerance: Tolerance,
) -> Result<Collected, ProjectionError> {
let mut rings = Vec::new();
let mut degenerate = 0;
let positions = mesh.position_count();
for index in 0..mesh.triangle_count() {
let corners = mesh.triangle(index);
let mut projected = [Point2::new(0.0, 0.0); 3];
for (slot, corner) in corners.iter().enumerate() {
let corner = usize::try_from(*corner).map_err(|_| ProjectionError::IndexOutOfRange)?;
if corner >= positions {
return Err(ProjectionError::IndexOutOfRange);
}
let point = mesh.position(corner);
if !point.is_finite() {
return Err(ProjectionError::NonFinitePosition);
}
projected[slot] = plane.project(point);
}
let cross = (projected[1] - projected[0]).perp_dot(projected[2] - projected[0]);
if cross.abs() <= 2.0 * tolerance.linear().powi(2) {
degenerate += 1;
continue;
}
let mut points = projected.to_vec();
if cross < 0.0 {
points.reverse();
}
rings.push(Ring { points });
}
Ok(Collected { rings, degenerate })
}
fn plane_frame() -> axiolid_core::Frame2 {
axiolid_core::Frame2 {
origin: Point2::new(0.0, 0.0),
x: axiolid_core::Vec2::new(1.0, 0.0),
y: axiolid_core::Vec2::new(0.0, 1.0),
}
}
fn union_all(rings: Vec<Ring>, tolerance: Tolerance) -> Result<Vec<Polygon>, ProjectionError> {
Ok(union_soup(&rings, tolerance)?)
}
pub fn intersect_prism<M: TriangleMeshView + ?Sized>(
mesh: &M,
plane: Plane,
region: &[Polygon],
tolerance: Tolerance,
) -> Result<Projection, ProjectionError> {
let footprint = project_mesh(mesh, plane, tolerance)?;
let subject = OverlayInput {
frame: plane_frame(),
polygons: footprint.polygons,
};
let clip = OverlayInput {
frame: plane_frame(),
polygons: region.to_vec(),
};
let clipped = overlay(
&subject,
&clip,
OverlayOperation::Intersection,
FillRule::NonZero,
tolerance,
)?;
let evidence = ProjectionEvidence {
output_polygons: clipped.polygons.len(),
output_holes: clipped.polygons.iter().map(|p| p.holes.len()).sum(),
..footprint.evidence
};
Ok(Projection {
polygons: clipped.polygons,
evidence,
})
}