use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::single::SingleFloatOverlay;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ProjectionAxis {
X,
Y,
Z,
}
impl ProjectionAxis {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(ProjectionAxis::X),
1 => Some(ProjectionAxis::Y),
2 => Some(ProjectionAxis::Z),
_ => None,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct MeshOutline {
pub contours: Vec<Vec<[f32; 2]>>,
pub axis_min: f32,
pub axis_max: f32,
}
#[inline]
fn project(p: [f64; 3], axis: ProjectionAxis, flipped: bool) -> [f64; 2] {
let (u, v) = match axis {
ProjectionAxis::X => (p[2], p[1]),
ProjectionAxis::Y => (p[0], p[2]),
ProjectionAxis::Z => (p[0], p[1]),
};
[if flipped { -u } else { u }, v]
}
#[inline]
fn axis_coord(p: [f64; 3], axis: ProjectionAxis) -> f64 {
match axis {
ProjectionAxis::X => p[0],
ProjectionAxis::Y => p[1],
ProjectionAxis::Z => p[2],
}
}
const DEGENERATE_AREA: f64 = 1.0e-8;
pub const MAX_OVERLAY_TRIANGLES: usize = 50_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NoOutline {
Empty,
OverBudget { triangles: usize },
}
pub fn mesh_outline_2d(
positions: &[f32],
indices: &[u32],
axis: ProjectionAxis,
flipped: bool,
) -> Result<MeshOutline, NoOutline> {
if indices.len() < 3 {
return Err(NoOutline::Empty);
}
let vertex_count = positions.len() / 3;
let mut subject: Vec<Vec<[f64; 2]>> = Vec::new();
let mut clip: Vec<Vec<[f64; 2]>> = Vec::new();
let mut axis_min = f64::INFINITY;
let mut axis_max = f64::NEG_INFINITY;
for tri in indices.chunks_exact(3) {
let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
if i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count {
continue;
}
let p0 = [
positions[i0 * 3] as f64,
positions[i0 * 3 + 1] as f64,
positions[i0 * 3 + 2] as f64,
];
let p1 = [
positions[i1 * 3] as f64,
positions[i1 * 3 + 1] as f64,
positions[i1 * 3 + 2] as f64,
];
let p2 = [
positions[i2 * 3] as f64,
positions[i2 * 3 + 1] as f64,
positions[i2 * 3 + 2] as f64,
];
for p in [p0, p1, p2] {
let a = axis_coord(p, axis);
axis_min = axis_min.min(a);
axis_max = axis_max.max(a);
}
let a0 = project(p0, axis, flipped);
let a1 = project(p1, axis, flipped);
let a2 = project(p2, axis, flipped);
let area = (a1[0] - a0[0]) * (a2[1] - a0[1]) - (a2[0] - a0[0]) * (a1[1] - a0[1]);
if area.abs() < DEGENERATE_AREA {
continue;
}
let max_edge_sq = ((a1[0]-a0[0]).powi(2) + (a1[1]-a0[1]).powi(2))
.max((a2[0]-a1[0]).powi(2) + (a2[1]-a1[1]).powi(2))
.max((a0[0]-a2[0]).powi(2) + (a0[1]-a2[1]).powi(2));
if max_edge_sq > 0.0 && area.abs() / max_edge_sq.sqrt() < 1.0e-6 {
continue;
}
let path: Vec<[f64; 2]> = if area >= 0.0 {
vec![a0, a1, a2]
} else {
vec![a0, a2, a1]
};
if subject.is_empty() {
subject.push(path);
} else {
clip.push(path);
}
if subject.len() + clip.len() > MAX_OVERLAY_TRIANGLES {
return Err(NoOutline::OverBudget { triangles: indices.len() / 3 });
}
}
if subject.is_empty() {
return Err(NoOutline::Empty);
}
let shapes: Vec<Vec<Vec<[f64; 2]>>> = if clip.is_empty() {
vec![subject.clone()]
} else {
subject.overlay(&clip, OverlayRule::Union, FillRule::NonZero)
};
let mut contours: Vec<Vec<[f32; 2]>> = Vec::new();
for shape in shapes {
for ring in shape {
if ring.len() >= 3 {
contours.push(ring.iter().map(|pt| [pt[0] as f32, pt[1] as f32]).collect());
}
}
}
if contours.is_empty() {
return Err(NoOutline::Empty);
}
Ok(MeshOutline {
contours,
axis_min: axis_min as f32,
axis_max: axis_max as f32,
})
}
#[cfg(test)]
#[path = "projection_outline_tests.rs"]
mod tests;