use axiolid_contracts::{GeomError, GeomResult, Sign};
use axiolid_core::{Point2, Point3, Scalar, Vec3};
use axiolid_mesh::TriMesh;
use axiolid_reference::arithmetic::{
expansion_sign, expansion_sum, grow_expansion, scale_expansion,
};
use axiolid_reference::expansion::two_product;
pub use crate::extrude_exact::extrude_profile_exact;
pub fn extrude(
points: &[Point2],
triangles: &[[u32; 3]],
loops: &[core::ops::Range<usize>],
direction: Vec3,
depth: Scalar,
) -> GeomResult<TriMesh> {
if !depth.is_finite() || depth <= 0.0 {
return Err(GeomError::InvalidInput(format!(
"extrusion depth must be positive and finite, got {depth}"
)));
}
if !direction.is_finite() || direction.length() <= 0.0 {
return Err(GeomError::InvalidInput(
"extrusion direction must be a finite non-zero vector".to_owned(),
));
}
let offset = direction.normalize() * depth;
if !offset.is_finite() {
return Err(GeomError::Degenerate(
"extrusion direction could not be normalised".to_owned(),
));
}
let n = points.len();
let mut positions = Vec::with_capacity(n * 2);
positions.extend(points.iter().map(|p| Point3::new(p.x, p.y, 0.0)));
positions.extend(points.iter().map(|p| Point3::new(p.x, p.y, 0.0) + offset));
let mut indices: Vec<u32> = Vec::with_capacity(triangles.len() * 6 + n * 6);
let top = n as u32;
for t in triangles {
indices.extend_from_slice(&[t[0] + top, t[1] + top, t[2] + top]);
indices.extend_from_slice(&[t[0], t[2], t[1]]);
}
for range in loops {
let len = range.len();
if len < 3 {
return Err(GeomError::InvalidInput(format!(
"extrusion loop needs at least 3 vertices, got {len}"
)));
}
for k in 0..len {
let a = (range.start + k) as u32;
let b = (range.start + (k + 1) % len) as u32;
indices.extend_from_slice(&[a, b, b + top]);
indices.extend_from_slice(&[a, b + top, a + top]);
}
}
Ok(TriMesh::new(positions, indices))
}
pub fn extrude_profile(
rings: &crate::profile::Rings,
direction: Vec3,
depth: Scalar,
_tolerance: axiolid_core::Tolerance,
) -> GeomResult<TriMesh> {
let (points, triangles) = crate::profile::triangulate(rings)?;
let mut loops = Vec::with_capacity(1 + rings.holes.len());
let mut start = 0usize;
loops.push(start..rings.outer.len());
start += rings.outer.len();
for hole in &rings.holes {
loops.push(start..start + hole.len());
start += hole.len();
}
extrude(&points, &triangles, &loops, direction, depth)
}
#[must_use]
pub fn outward_orientation(mesh: &TriMesh) -> Option<bool> {
if mesh.indices.len() < 12 {
return None;
}
let mut total: Vec<f64> = vec![0.0];
for corner in mesh.indices.chunks_exact(3) {
let a = mesh.positions[corner[0] as usize];
let b = mesh.positions[corner[1] as usize];
let c = mesh.positions[corner[2] as usize];
total = expansion_sum(&total, &triple_product(a, b, c));
}
match expansion_sign(&total) {
Sign::Positive => Some(true),
Sign::Negative => Some(false),
Sign::Zero => None,
_ => None,
}
}
#[must_use]
fn triple_product(a: Point3, b: Point3, c: Point3) -> Vec<f64> {
let term = |p: f64, q: f64, r: f64, s: f64, k: f64| {
scale_expansion(&exact_difference_of_products(p, q, r, s), k)
};
let x = term(b.y, c.z, c.y, b.z, a.x);
let y = term(b.z, c.x, c.z, b.x, a.y);
let z = term(b.x, c.y, c.x, b.y, a.z);
expansion_sum(&expansion_sum(&x, &y), &z)
}
#[must_use]
fn exact_difference_of_products(p: f64, q: f64, r: f64, s: f64) -> Vec<f64> {
let (pq, pq_err) = two_product(p, q);
let (rs, rs_err) = two_product(r, s);
let e = grow_expansion(&[pq_err], -rs_err);
let e = grow_expansion(&e, pq);
grow_expansion(&e, -rs)
}