use axiolid_core::{Point2, Point3, Polygon2, Scalar, Tolerance, Triangle2, Triangle3};
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum BarycentricError {
NonFinite,
Degenerate {
thickness: Scalar,
},
TooFewVertices {
count: usize,
},
ShortEdge {
index: usize,
},
SelfIntersecting {
first: usize,
second: usize,
},
Undefined,
}
impl core::fmt::Display for BarycentricError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NonFinite => f.write_str("a corner or the query point is not finite"),
Self::Degenerate { thickness } => write!(
f,
"shape is {thickness} thick, not thicker than the linear tolerance"
),
Self::TooFewVertices { count } => {
write!(f, "polygon has {count} vertices, need at least 3")
}
Self::ShortEdge { index } => {
write!(f, "polygon edge {index} is not longer than the tolerance")
}
Self::SelfIntersecting { first, second } => {
write!(f, "polygon edges {first} and {second} meet")
}
Self::Undefined => {
f.write_str("mean-value weights cancel here (outside a non-convex polygon)")
}
}
}
}
impl core::error::Error for BarycentricError {}
fn area2(x: Point2, y: Point2, z: Point2) -> Scalar {
(y - x).perp_dot(z - x)
}
fn volume6(x: Point3, y: Point3, z: Point3, w: Point3) -> Scalar {
(y - x).dot((z - x).cross(w - x))
}
pub fn triangle_barycentric2(
triangle: &Triangle2,
point: Point2,
tolerance: Tolerance,
) -> Result<[Scalar; 3], BarycentricError> {
let Triangle2 { a, b, c } = *triangle;
if ![a, b, c, point].iter().all(|p| p.is_finite()) {
return Err(BarycentricError::NonFinite);
}
let total = area2(a, b, c);
let longest = (b - a).length().max((c - b).length()).max((a - c).length());
thick_enough(total.abs(), longest, tolerance)?;
Ok([
area2(point, b, c) / total,
area2(a, point, c) / total,
area2(a, b, point) / total,
])
}
pub fn triangle_barycentric3(
triangle: &Triangle3,
point: Point3,
tolerance: Tolerance,
) -> Result<[Scalar; 3], BarycentricError> {
let Triangle3 { a, b, c } = *triangle;
if ![a, b, c, point].iter().all(|p| p.is_finite()) {
return Err(BarycentricError::NonFinite);
}
let normal = (b - a).cross(c - a);
let longest = (b - a).length().max((c - b).length()).max((a - c).length());
thick_enough(normal.length(), longest, tolerance)?;
let total = normal.dot(normal);
Ok([
normal.dot((b - point).cross(c - point)) / total,
normal.dot((c - point).cross(a - point)) / total,
normal.dot((a - point).cross(b - point)) / total,
])
}
pub fn tetrahedron_barycentric(
corners: [Point3; 4],
point: Point3,
tolerance: Tolerance,
) -> Result<[Scalar; 4], BarycentricError> {
let [a, b, c, d] = corners;
if ![a, b, c, d, point].iter().all(|p| p.is_finite()) {
return Err(BarycentricError::NonFinite);
}
let total = volume6(a, b, c, d);
let largest_face = [(b, c, d), (a, c, d), (a, b, d), (a, b, c)]
.iter()
.map(|&(x, y, z)| (y - x).cross(z - x).length())
.fold(0.0, Scalar::max);
thick_enough(total.abs(), largest_face, tolerance)?;
Ok([
volume6(point, b, c, d) / total,
volume6(a, point, c, d) / total,
volume6(a, b, point, d) / total,
volume6(a, b, c, point) / total,
])
}
fn thick_enough(
measure: Scalar,
base: Scalar,
tolerance: Tolerance,
) -> Result<(), BarycentricError> {
let thickness = if base > 0.0 { measure / base } else { 0.0 };
if thickness > tolerance.linear() {
Ok(())
} else {
Err(BarycentricError::Degenerate { thickness })
}
}
pub fn mean_value_coordinates2(
polygon: &Polygon2,
point: Point2,
tolerance: Tolerance,
) -> Result<Vec<Scalar>, BarycentricError> {
let v = &polygon.vertices;
let n = v.len();
if !point.is_finite() || !v.iter().all(|p| p.is_finite()) {
return Err(BarycentricError::NonFinite);
}
if n < 3 {
return Err(BarycentricError::TooFewVertices { count: n });
}
check_simple(v, tolerance)?;
let perimeter: Scalar = (0..n).map(|i| (v[(i + 1) % n] - v[i]).length()).sum();
thick_enough(2.0 * polygon.signed_area().abs(), perimeter, tolerance)?;
let linear = tolerance.linear();
let mut weights = vec![0.0; n];
let s: Vec<Point2> = v.iter().map(|&q| q - point).collect();
let r: Vec<Scalar> = s.iter().map(|q| q.length()).collect();
if let Some(i) = (0..n)
.filter(|&i| r[i] <= linear)
.min_by(|&i, &j| r[i].total_cmp(&r[j]))
{
weights[i] = 1.0;
return Ok(weights);
}
for i in 0..n {
let j = (i + 1) % n;
let edge = v[j] - v[i];
let length = edge.length();
let t = (point - v[i]).dot(edge) / (length * length);
if (0.0..=1.0).contains(&t) && s[i].perp_dot(s[j]).abs() / length <= linear {
weights[i] = 1.0 - t;
weights[j] = t;
return Ok(weights);
}
}
let half_tangent: Vec<Scalar> = (0..n)
.map(|i| {
let j = (i + 1) % n;
s[i].perp_dot(s[j]) / (r[i] * r[j] + s[i].dot(s[j]))
})
.collect();
let mut sum = 0.0;
let mut magnitude = 0.0;
for i in 0..n {
let w = (half_tangent[(i + n - 1) % n] + half_tangent[i]) / r[i];
weights[i] = w;
sum += w;
magnitude += w.abs();
}
if !sum.is_finite() || sum.abs() <= Scalar::EPSILON * n as Scalar * magnitude {
return Err(BarycentricError::Undefined);
}
for w in &mut weights {
*w /= sum;
}
Ok(weights)
}
fn check_simple(v: &[Point2], tolerance: Tolerance) -> Result<(), BarycentricError> {
let n = v.len();
let linear = tolerance.linear();
let edge = |i: usize| (v[i], v[(i + 1) % n]);
for i in 0..n {
let (p, q) = edge(i);
if (q - p).length() <= linear {
return Err(BarycentricError::ShortEdge { index: i });
}
}
for i in 0..n {
let last = if i == 0 { n - 1 } else { n };
for j in i + 2..last {
let (p, q) = edge(i);
let (r, s) = edge(j);
if segment_distance(p, q, r, s) <= linear {
return Err(BarycentricError::SelfIntersecting {
first: i,
second: j,
});
}
}
}
Ok(())
}
fn segment_distance(p: Point2, q: Point2, r: Point2, s: Point2) -> Scalar {
let o1 = area2(p, q, r);
let o2 = area2(p, q, s);
let o3 = area2(r, s, p);
let o4 = area2(r, s, q);
if o1 * o2 < 0.0 && o3 * o4 < 0.0 {
return 0.0;
}
point_segment(r, p, q)
.min(point_segment(s, p, q))
.min(point_segment(p, r, s))
.min(point_segment(q, r, s))
}
fn point_segment(x: Point2, p: Point2, q: Point2) -> Scalar {
let d = q - p;
let t = ((x - p).dot(d) / d.dot(d)).clamp(0.0, 1.0);
(p + d * t - x).length()
}