Skip to main content

axiolid_spatial/
barycentric.rs

1//! Barycentric and mean-value coordinates, for interpolating values given at
2//! the corners of a triangle, a tetrahedron, a polygon or a closed triangle
3//! mesh (#143).
4//!
5//! Every function returns one weight per corner. The weights sum to one and
6//! reproduce the point: `sum(w_i * corner_i) == point`, up to rounding. A
7//! point on a corner gets weight exactly one there and zero elsewhere, so
8//! corner values are reproduced exactly.
9//!
10//! Shapes thinner than the linear tolerance are refused rather than given
11//! weights that are mostly rounding noise; a triangle's or tetrahedron's
12//! thinness is its least altitude. Weights outside `[0, 1]` are not an
13//! error: they place the point outside the triangle or tetrahedron, which is
14//! how callers extrapolate or test containment.
15//!
16//! Four polygon schemes are offered, trading generality for positivity:
17//! [`mean_value_coordinates2`] answers for any simple polygon, convex or
18//! not; [`wachspress_coordinates2`] and [`discrete_harmonic_coordinates2`]
19//! require a strictly convex polygon (refused by name otherwise), and the
20//! latter can still produce an infinite or cancelled weight at a pole, which
21//! is refused the same way mean-value cancellation is.
22//! [`mean_value_coordinates3`] extends the 2D mean-value scheme to a closed
23//! triangle mesh in space.
24
25use axiolid_core::{Point2, Point3, Polygon2, Scalar, Tolerance, Triangle2, Triangle3};
26
27/// Why coordinates could not be computed.
28#[derive(Debug, Clone, Copy, PartialEq)]
29#[non_exhaustive]
30pub enum BarycentricError {
31    /// A corner or the query point is not finite.
32    NonFinite,
33    /// The triangle, tetrahedron or polygon is thinner than the linear
34    /// tolerance: its least altitude (for a polygon, twice its area over its
35    /// perimeter) is `thickness`.
36    Degenerate {
37        /// The shape's thickness, in length units.
38        thickness: Scalar,
39    },
40    /// A polygon has fewer than three vertices.
41    TooFewVertices {
42        /// The number of vertices given.
43        count: usize,
44    },
45    /// Polygon edge `index`, from vertex `index` to the next, is no longer
46    /// than the linear tolerance.
47    ShortEdge {
48        /// The edge's index.
49        index: usize,
50    },
51    /// Polygon edges `first` and `second` cross, touch or fold back onto
52    /// each other within the linear tolerance, so the polygon is not simple.
53    SelfIntersecting {
54        /// The lower edge index.
55        first: usize,
56        /// The higher edge index.
57        second: usize,
58    },
59    /// The mean-value weights cancel at this point, which can happen only
60    /// outside a non-convex polygon: the coordinates are undefined there.
61    Undefined,
62    /// Wachspress and discrete harmonic coordinates require a strictly
63    /// convex polygon; vertex `index` turns the other way.
64    NotConvex {
65        /// The reflex (or ambiguous) vertex.
66        index: usize,
67    },
68    /// A face of a triangle mesh names vertex `index`, but the mesh has only
69    /// `len` vertices.
70    VertexIndex {
71        /// The face's index.
72        face: usize,
73        /// The out-of-range vertex index it named.
74        index: usize,
75        /// The number of vertices given.
76        len: usize,
77    },
78}
79
80impl core::fmt::Display for BarycentricError {
81    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
82        match self {
83            Self::NonFinite => f.write_str("a corner or the query point is not finite"),
84            Self::Degenerate { thickness } => write!(
85                f,
86                "shape is {thickness} thick, not thicker than the linear tolerance"
87            ),
88            Self::TooFewVertices { count } => {
89                write!(f, "polygon has {count} vertices, need at least 3")
90            }
91            Self::ShortEdge { index } => {
92                write!(f, "polygon edge {index} is not longer than the tolerance")
93            }
94            Self::SelfIntersecting { first, second } => {
95                write!(f, "polygon edges {first} and {second} meet")
96            }
97            Self::Undefined => {
98                f.write_str("mean-value weights cancel here (outside a non-convex polygon)")
99            }
100            Self::NotConvex { index } => {
101                write!(f, "polygon is not convex at vertex {index}")
102            }
103            Self::VertexIndex { face, index, len } => {
104                write!(f, "face {face} names vertex {index}, but only {len} exist")
105            }
106        }
107    }
108}
109
110impl core::error::Error for BarycentricError {}
111
112/// Twice the signed area of `x, y, z`, positive counter-clockwise.
113fn area2(x: Point2, y: Point2, z: Point2) -> Scalar {
114    (y - x).perp_dot(z - x)
115}
116
117/// Six times the signed volume of `x, y, z, w`.
118fn volume6(x: Point3, y: Point3, z: Point3, w: Point3) -> Scalar {
119    (y - x).dot((z - x).cross(w - x))
120}
121
122/// Barycentric coordinates of `point` in a 2D triangle, as weights of
123/// `a`, `b` and `c`.
124///
125/// Each weight is the signed area of the sub-triangle opposite its corner
126/// over the triangle's area, so the result does not depend on the
127/// triangle's winding.
128///
129/// # Errors
130///
131/// [`BarycentricError::NonFinite`], or [`BarycentricError::Degenerate`] when
132/// the triangle's least altitude is not above `tolerance.linear()`.
133pub fn triangle_barycentric2(
134    triangle: &Triangle2,
135    point: Point2,
136    tolerance: Tolerance,
137) -> Result<[Scalar; 3], BarycentricError> {
138    let Triangle2 { a, b, c } = *triangle;
139    if ![a, b, c, point].iter().all(|p| p.is_finite()) {
140        return Err(BarycentricError::NonFinite);
141    }
142    let total = area2(a, b, c);
143    let longest = (b - a).length().max((c - b).length()).max((a - c).length());
144    thick_enough(total.abs(), longest, tolerance)?;
145    Ok([
146        area2(point, b, c) / total,
147        area2(a, point, c) / total,
148        area2(a, b, point) / total,
149    ])
150}
151
152/// Barycentric coordinates of `point` in a 3D triangle, as weights of
153/// `a`, `b` and `c`.
154///
155/// A point off the triangle's plane gets the coordinates of its orthogonal
156/// projection onto that plane: the weights reproduce the projection, not the
157/// point.
158///
159/// # Errors
160///
161/// As [`triangle_barycentric2`].
162pub fn triangle_barycentric3(
163    triangle: &Triangle3,
164    point: Point3,
165    tolerance: Tolerance,
166) -> Result<[Scalar; 3], BarycentricError> {
167    let Triangle3 { a, b, c } = *triangle;
168    if ![a, b, c, point].iter().all(|p| p.is_finite()) {
169        return Err(BarycentricError::NonFinite);
170    }
171    let normal = (b - a).cross(c - a);
172    let longest = (b - a).length().max((c - b).length()).max((a - c).length());
173    thick_enough(normal.length(), longest, tolerance)?;
174    let total = normal.dot(normal);
175    Ok([
176        normal.dot((b - point).cross(c - point)) / total,
177        normal.dot((c - point).cross(a - point)) / total,
178        normal.dot((a - point).cross(b - point)) / total,
179    ])
180}
181
182/// Barycentric coordinates of `point` in a tetrahedron, as weights of its
183/// four corners in order.
184///
185/// Each weight is the signed volume of the sub-tetrahedron opposite its
186/// corner over the tetrahedron's volume, so the corner order's handedness
187/// does not matter.
188///
189/// # Errors
190///
191/// [`BarycentricError::NonFinite`], or [`BarycentricError::Degenerate`] when
192/// the tetrahedron's least altitude is not above `tolerance.linear()`.
193pub fn tetrahedron_barycentric(
194    corners: [Point3; 4],
195    point: Point3,
196    tolerance: Tolerance,
197) -> Result<[Scalar; 4], BarycentricError> {
198    let [a, b, c, d] = corners;
199    if ![a, b, c, d, point].iter().all(|p| p.is_finite()) {
200        return Err(BarycentricError::NonFinite);
201    }
202    let total = volume6(a, b, c, d);
203    // The least altitude is the volume over the largest face.
204    let largest_face = [(b, c, d), (a, c, d), (a, b, d), (a, b, c)]
205        .iter()
206        .map(|&(x, y, z)| (y - x).cross(z - x).length())
207        .fold(0.0, Scalar::max);
208    thick_enough(total.abs(), largest_face, tolerance)?;
209    Ok([
210        volume6(point, b, c, d) / total,
211        volume6(a, point, c, d) / total,
212        volume6(a, b, point, d) / total,
213        volume6(a, b, c, point) / total,
214    ])
215}
216
217/// Refuse a shape whose `measure / base` (an altitude) is not above the
218/// linear tolerance.
219fn thick_enough(
220    measure: Scalar,
221    base: Scalar,
222    tolerance: Tolerance,
223) -> Result<(), BarycentricError> {
224    let thickness = if base > 0.0 { measure / base } else { 0.0 };
225    if thickness > tolerance.linear() {
226        Ok(())
227    } else {
228        Err(BarycentricError::Degenerate { thickness })
229    }
230}
231
232/// Mean-value coordinates of `point` in a simple polygon, one weight per
233/// vertex (Floater 2003, in the form for arbitrary polygons of Hormann and
234/// Floater 2006).
235///
236/// Inside a simple polygon, convex or not, the weights are positive-sum,
237/// smooth, and reproduce the point. On the boundary, within the linear
238/// tolerance, they are the boundary's own linear interpolation: one at a
239/// vertex, or the two endpoint weights of an edge. Outside the polygon they
240/// are defined wherever the raw weights do not cancel, which is everywhere
241/// outside a convex polygon. The polygon may wind either way.
242///
243/// The polygon is checked for simplicity pairwise, `O(n^2)` in its vertex
244/// count, since mean-value coordinates of a crossing polygon mean nothing.
245///
246/// # Errors
247///
248/// [`BarycentricError::NonFinite`], [`BarycentricError::TooFewVertices`],
249/// [`BarycentricError::ShortEdge`], [`BarycentricError::SelfIntersecting`],
250/// [`BarycentricError::Degenerate`] for a polygon with no area to speak of,
251/// and [`BarycentricError::Undefined`] where the weights cancel.
252pub fn mean_value_coordinates2(
253    polygon: &Polygon2,
254    point: Point2,
255    tolerance: Tolerance,
256) -> Result<Vec<Scalar>, BarycentricError> {
257    let v = &polygon.vertices;
258    let n = v.len();
259    if !point.is_finite() || !v.iter().all(|p| p.is_finite()) {
260        return Err(BarycentricError::NonFinite);
261    }
262    if n < 3 {
263        return Err(BarycentricError::TooFewVertices { count: n });
264    }
265    check_simple(v, tolerance)?;
266    let perimeter: Scalar = (0..n).map(|i| (v[(i + 1) % n] - v[i]).length()).sum();
267    thick_enough(2.0 * polygon.signed_area().abs(), perimeter, tolerance)?;
268
269    let linear = tolerance.linear();
270    let mut weights = vec![0.0; n];
271    // On a vertex: that vertex alone.
272    let s: Vec<Point2> = v.iter().map(|&q| q - point).collect();
273    let r: Vec<Scalar> = s.iter().map(|q| q.length()).collect();
274    if let Some(i) = (0..n)
275        .filter(|&i| r[i] <= linear)
276        .min_by(|&i, &j| r[i].total_cmp(&r[j]))
277    {
278        weights[i] = 1.0;
279        return Ok(weights);
280    }
281    // On an edge: its linear interpolation.
282    for i in 0..n {
283        let j = (i + 1) % n;
284        let edge = v[j] - v[i];
285        let length = edge.length();
286        let t = (point - v[i]).dot(edge) / (length * length);
287        if (0.0..=1.0).contains(&t) && s[i].perp_dot(s[j]).abs() / length <= linear {
288            weights[i] = 1.0 - t;
289            weights[j] = t;
290            return Ok(weights);
291        }
292    }
293    // tan(alpha_i / 2) for the signed angle alpha_i the edge from vertex i
294    // to vertex i + 1 subtends at the point: A / (r_i r_j + D), which stays
295    // finite because the point is on no edge.
296    let half_tangent: Vec<Scalar> = (0..n)
297        .map(|i| {
298            let j = (i + 1) % n;
299            s[i].perp_dot(s[j]) / (r[i] * r[j] + s[i].dot(s[j]))
300        })
301        .collect();
302    let mut sum = 0.0;
303    let mut magnitude = 0.0;
304    for i in 0..n {
305        let w = (half_tangent[(i + n - 1) % n] + half_tangent[i]) / r[i];
306        weights[i] = w;
307        sum += w;
308        magnitude += w.abs();
309    }
310    if !sum.is_finite() || sum.abs() <= Scalar::EPSILON * n as Scalar * magnitude {
311        return Err(BarycentricError::Undefined);
312    }
313    for w in &mut weights {
314        *w /= sum;
315    }
316    Ok(weights)
317}
318
319/// Refuse short edges, and non-adjacent edges that come within the linear
320/// tolerance of each other.
321///
322/// Adjacent edges need no test of their own. Two that fold back onto each
323/// other leave the far end of the shorter on the longer, and that end also
324/// belongs to an edge not adjacent to the longer, which this test sees; in a
325/// triangle, where every pair is adjacent, a fold leaves no area and the
326/// thickness test refuses it.
327fn check_simple(v: &[Point2], tolerance: Tolerance) -> Result<(), BarycentricError> {
328    let n = v.len();
329    let linear = tolerance.linear();
330    let edge = |i: usize| (v[i], v[(i + 1) % n]);
331    for i in 0..n {
332        let (p, q) = edge(i);
333        if (q - p).length() <= linear {
334            return Err(BarycentricError::ShortEdge { index: i });
335        }
336    }
337    for i in 0..n {
338        // Skip the edge itself, its successor, and (from edge 0) the last
339        // edge, which precedes it.
340        let last = if i == 0 { n - 1 } else { n };
341        for j in i + 2..last {
342            let (p, q) = edge(i);
343            let (r, s) = edge(j);
344            if segment_distance(p, q, r, s) <= linear {
345                return Err(BarycentricError::SelfIntersecting {
346                    first: i,
347                    second: j,
348                });
349            }
350        }
351    }
352    Ok(())
353}
354
355/// Distance between segments `pq` and `rs`: zero when they cross.
356fn segment_distance(p: Point2, q: Point2, r: Point2, s: Point2) -> Scalar {
357    let o1 = area2(p, q, r);
358    let o2 = area2(p, q, s);
359    let o3 = area2(r, s, p);
360    let o4 = area2(r, s, q);
361    if o1 * o2 < 0.0 && o3 * o4 < 0.0 {
362        return 0.0;
363    }
364    point_segment(r, p, q)
365        .min(point_segment(s, p, q))
366        .min(point_segment(p, r, s))
367        .min(point_segment(q, r, s))
368}
369
370fn point_segment(x: Point2, p: Point2, q: Point2) -> Scalar {
371    let d = q - p;
372    let t = ((x - p).dot(d) / d.dot(d)).clamp(0.0, 1.0);
373    (p + d * t - x).length()
374}
375
376/// `point` on a vertex or an edge of `v`, answered by that boundary's own
377/// linear interpolation: shared by [`wachspress_coordinates2`] and
378/// [`discrete_harmonic_coordinates2`], both of which reduce to ordinary
379/// barycentric interpolation on the boundary, same as
380/// [`mean_value_coordinates2`].
381fn vertex_or_edge_weights(
382    v: &[Point2],
383    point: Point2,
384    tolerance: Tolerance,
385) -> Option<Vec<Scalar>> {
386    let n = v.len();
387    let linear = tolerance.linear();
388    let mut weights = vec![0.0; n];
389    let r: Vec<Scalar> = v.iter().map(|&q| (q - point).length()).collect();
390    if let Some(i) = (0..n)
391        .filter(|&i| r[i] <= linear)
392        .min_by(|&i, &j| r[i].total_cmp(&r[j]))
393    {
394        weights[i] = 1.0;
395        return Some(weights);
396    }
397    for i in 0..n {
398        let j = (i + 1) % n;
399        let edge = v[j] - v[i];
400        let length = edge.length();
401        let t = (point - v[i]).dot(edge) / (length * length);
402        let s_i = v[i] - point;
403        let s_j = v[j] - point;
404        if (0.0..=1.0).contains(&t) && s_i.perp_dot(s_j).abs() / length <= linear {
405            weights[i] = 1.0 - t;
406            weights[j] = t;
407            return Some(weights);
408        }
409    }
410    None
411}
412
413/// Refuse a polygon with a reflex vertex: Wachspress and discrete harmonic
414/// coordinates are defined here only for a strictly convex polygon (as
415/// CGAL's `Wachspress_coordinates_2` requires), any winding.
416///
417/// Each turn `area2(prev, cur, next)` is compared to the polygon's own
418/// orientation, with a threshold in the same area units as the linear
419/// tolerance times the perimeter, so a numerically flat corner (near-180
420/// degrees) is accepted rather than flagged on rounding noise.
421fn check_convex(v: &[Point2], tolerance: Tolerance) -> Result<(), BarycentricError> {
422    let n = v.len();
423    let signed_area: Scalar = (0..n)
424        .map(|i| v[i].perp_dot(v[(i + 1) % n]))
425        .sum::<Scalar>()
426        * 0.5;
427    let orientation = if signed_area >= 0.0 { 1.0 } else { -1.0 };
428    let perimeter: Scalar = (0..n).map(|i| (v[(i + 1) % n] - v[i]).length()).sum();
429    let threshold = tolerance.linear() * perimeter;
430    for i in 0..n {
431        let prev = v[(i + n - 1) % n];
432        let next = v[(i + 1) % n];
433        let turn = area2(prev, v[i], next);
434        if turn * orientation < -threshold {
435            return Err(BarycentricError::NotConvex { index: i });
436        }
437    }
438    Ok(())
439}
440
441/// Wachspress coordinates of `point` in a strictly convex polygon, one
442/// weight per vertex (Wachspress 1975, in the determinant form of Meyer,
443/// Lee, Barr and Desbrun 2002).
444///
445/// For vertex `i`, `w_i = C_i / (A_{i-1} * A_i)` where `C_i` is twice the
446/// area of the corner triangle `(v_{i-1}, v_i, v_{i+1})` and `A_i` is twice
447/// the area of `(v_i, v_{i+1}, point)`; the result is `w_i` normalized to sum
448/// to one. Inside the polygon the weights are positive, smooth, and
449/// reproduce the point; on the boundary, within the linear tolerance, they
450/// are the boundary's own linear interpolation. Outside the polygon they are
451/// the formula's rational extension wherever it does not divide by zero. The
452/// polygon may wind either way.
453///
454/// # Errors
455///
456/// [`BarycentricError::NonFinite`], [`BarycentricError::TooFewVertices`],
457/// [`BarycentricError::ShortEdge`], [`BarycentricError::SelfIntersecting`],
458/// [`BarycentricError::Degenerate`] for a polygon with no area to speak of,
459/// [`BarycentricError::NotConvex`] for a polygon that is not strictly
460/// convex, and [`BarycentricError::Undefined`] where a denominator vanishes
461/// (on the line through a non-adjacent edge, extended past its endpoints).
462pub fn wachspress_coordinates2(
463    polygon: &Polygon2,
464    point: Point2,
465    tolerance: Tolerance,
466) -> Result<Vec<Scalar>, BarycentricError> {
467    let v = &polygon.vertices;
468    let n = v.len();
469    if !point.is_finite() || !v.iter().all(|p| p.is_finite()) {
470        return Err(BarycentricError::NonFinite);
471    }
472    if n < 3 {
473        return Err(BarycentricError::TooFewVertices { count: n });
474    }
475    check_simple(v, tolerance)?;
476    let perimeter: Scalar = (0..n).map(|i| (v[(i + 1) % n] - v[i]).length()).sum();
477    thick_enough(2.0 * polygon.signed_area().abs(), perimeter, tolerance)?;
478    check_convex(v, tolerance)?;
479    if let Some(w) = vertex_or_edge_weights(v, point, tolerance) {
480        return Ok(w);
481    }
482
483    let a: Vec<Scalar> = (0..n).map(|i| area2(v[i], v[(i + 1) % n], point)).collect();
484    let mut weights = vec![0.0; n];
485    let mut sum = 0.0;
486    let mut magnitude = 0.0;
487    for i in 0..n {
488        let prev = (i + n - 1) % n;
489        let c = area2(v[prev], v[i], v[(i + 1) % n]);
490        let w = c / (a[prev] * a[i]);
491        weights[i] = w;
492        sum += w;
493        magnitude += w.abs();
494    }
495    if !sum.is_finite() || sum.abs() <= Scalar::EPSILON * n as Scalar * magnitude {
496        return Err(BarycentricError::Undefined);
497    }
498    for w in &mut weights {
499        *w /= sum;
500    }
501    Ok(weights)
502}
503
504/// Discrete harmonic coordinates of `point` in a strictly convex polygon,
505/// one weight per vertex (Pinkall and Polthier's cotangent weights, applied
506/// to the triangle fan `(point, v_i, v_{i+1})`; Floater's survey calls this
507/// scheme "discrete harmonic").
508///
509/// For vertex `i`, sharing a fan edge with the two triangles on either side
510/// of it, `w_i = (cot(gamma) + cot(beta)) / r_i^2`, where `beta` and `gamma`
511/// are the unsigned angles at `v_i` in those two triangles and `r_i =
512/// |point - v_i|`; the result is `w_i` normalized to sum to one. On the
513/// boundary, within the linear tolerance, the weights are the boundary's own
514/// linear interpolation. Unlike Wachspress coordinates, discrete harmonic
515/// weights are not guaranteed positive inside a convex polygon, and have
516/// poles where a fan angle is a multiple of a straight angle (point,
517/// vertex and a neighbor collinear): both refused as
518/// [`BarycentricError::Undefined`], since the caller cannot act on an
519/// infinite or cancelled weight either way. The polygon may wind either
520/// way.
521///
522/// # Errors
523///
524/// As [`wachspress_coordinates2`].
525pub fn discrete_harmonic_coordinates2(
526    polygon: &Polygon2,
527    point: Point2,
528    tolerance: Tolerance,
529) -> Result<Vec<Scalar>, BarycentricError> {
530    let v = &polygon.vertices;
531    let n = v.len();
532    if !point.is_finite() || !v.iter().all(|p| p.is_finite()) {
533        return Err(BarycentricError::NonFinite);
534    }
535    if n < 3 {
536        return Err(BarycentricError::TooFewVertices { count: n });
537    }
538    check_simple(v, tolerance)?;
539    let perimeter: Scalar = (0..n).map(|i| (v[(i + 1) % n] - v[i]).length()).sum();
540    thick_enough(2.0 * polygon.signed_area().abs(), perimeter, tolerance)?;
541    check_convex(v, tolerance)?;
542    if let Some(w) = vertex_or_edge_weights(v, point, tolerance) {
543        return Ok(w);
544    }
545
546    let mut weights = vec![0.0; n];
547    let mut sum = 0.0;
548    let mut magnitude = 0.0;
549    for i in 0..n {
550        let u = point - v[i];
551        let prev = v[(i + n - 1) % n] - v[i];
552        let next = v[(i + 1) % n] - v[i];
553        let cot_prev = u.dot(prev) / u.perp_dot(prev).abs();
554        let cot_next = u.dot(next) / u.perp_dot(next).abs();
555        let r2 = u.length_squared();
556        let w = (cot_prev + cot_next) / r2;
557        weights[i] = w;
558        sum += w;
559        magnitude += w.abs();
560    }
561    if !sum.is_finite() || sum.abs() <= Scalar::EPSILON * n as Scalar * magnitude {
562        return Err(BarycentricError::Undefined);
563    }
564    for w in &mut weights {
565        *w /= sum;
566    }
567    Ok(weights)
568}
569
570/// Mean-value coordinates of `point` in a closed triangle mesh, one weight
571/// per vertex (Floater, Kos and Reimers 2005).
572///
573/// `faces` lists each triangle as three indices into `vertices`. The mesh's
574/// closedness (every edge shared by exactly two faces, consistently
575/// wound) is the caller's responsibility, same as `mean_value_coordinates2`
576/// leaves polygon repair to its caller: this function does not audit
577/// topology, only computes weights from the faces given. On a vertex, that
578/// vertex alone gets weight one. When `point` lies exactly on a face's
579/// plane, inside that triangle, the weights are that triangle's own 2D
580/// barycentric coordinates (all other vertices zero) -- the formula's own
581/// closed form for the coplanar case, not a fallback. Elsewhere on a face's
582/// plane but outside its triangle, that face contributes nothing (its
583/// spherical angles fold flat without enclosing `point`); a mesh with no
584/// other face to carry the point returns [`BarycentricError::Undefined`].
585///
586/// # Errors
587///
588/// [`BarycentricError::NonFinite`] for a non-finite vertex or point,
589/// [`BarycentricError::TooFewVertices`] for fewer than three vertices or no
590/// faces, [`BarycentricError::VertexIndex`] for a face naming a vertex index
591/// past the end of `vertices`, and [`BarycentricError::Undefined`] where the
592/// weights do not sum to a nonzero, finite total.
593pub fn mean_value_coordinates3(
594    vertices: &[Point3],
595    faces: &[[usize; 3]],
596    point: Point3,
597    tolerance: Tolerance,
598) -> Result<Vec<Scalar>, BarycentricError> {
599    let n = vertices.len();
600    if !point.is_finite() || !vertices.iter().all(|p| p.is_finite()) {
601        return Err(BarycentricError::NonFinite);
602    }
603    if n < 3 || faces.is_empty() {
604        return Err(BarycentricError::TooFewVertices { count: n });
605    }
606    for (f, face) in faces.iter().enumerate() {
607        for &index in face {
608            if index >= n {
609                return Err(BarycentricError::VertexIndex {
610                    face: f,
611                    index,
612                    len: n,
613                });
614            }
615        }
616    }
617
618    let linear = tolerance.linear();
619    let d: Vec<Scalar> = vertices.iter().map(|&q| (q - point).length()).collect();
620    if let Some(i) = (0..n)
621        .filter(|&i| d[i] <= linear)
622        .min_by(|&i, &j| d[i].total_cmp(&d[j]))
623    {
624        let mut weights = vec![0.0; n];
625        weights[i] = 1.0;
626        return Ok(weights);
627    }
628    let u: Vec<Point3> = vertices
629        .iter()
630        .zip(&d)
631        .map(|(&q, &r)| (q - point) / r)
632        .collect();
633
634    let mut weights = vec![0.0; n];
635    for &[fi, fj, fk] in faces {
636        let (ui, uj, uk) = (u[fi], u[fj], u[fk]);
637        let l = [(uj - uk).length(), (uk - ui).length(), (ui - uj).length()];
638        let theta = [
639            2.0 * (l[0] / 2.0).clamp(-1.0, 1.0).asin(),
640            2.0 * (l[1] / 2.0).clamp(-1.0, 1.0).asin(),
641            2.0 * (l[2] / 2.0).clamp(-1.0, 1.0).asin(),
642        ];
643        let h = (theta[0] + theta[1] + theta[2]) / 2.0;
644        if core::f64::consts::PI - h < linear {
645            // `point` is coplanar with, and inside, this triangle: its own
646            // 2D barycentric coordinates, exact at corners.
647            let tri = Triangle3 {
648                a: vertices[fi],
649                b: vertices[fj],
650                c: vertices[fk],
651            };
652            let w = triangle_barycentric3(&tri, point, tolerance)?;
653            let mut weights = vec![0.0; n];
654            weights[fi] = w[0];
655            weights[fj] = w[1];
656            weights[fk] = w[2];
657            return Ok(weights);
658        }
659        let sin_theta = [theta[0].sin(), theta[1].sin(), theta[2].sin()];
660        if sin_theta.iter().any(|s| s.abs() <= Scalar::EPSILON) {
661            // A fan angle at 0 or pi: this face's spherical triangle is
662            // degenerate at `point`, so it cannot enclose it. Skip.
663            continue;
664        }
665        let c = [
666            2.0 * h.sin() * (h - theta[0]).sin() / (sin_theta[1] * sin_theta[2]) - 1.0,
667            2.0 * h.sin() * (h - theta[1]).sin() / (sin_theta[2] * sin_theta[0]) - 1.0,
668            2.0 * h.sin() * (h - theta[2]).sin() / (sin_theta[0] * sin_theta[1]) - 1.0,
669        ];
670        let sign = ui.dot(uj.cross(uk)).signum();
671        let mut degenerate = false;
672        let s: Vec<Scalar> = c
673            .iter()
674            .map(|&ci| {
675                let value = sign * (1.0 - ci * ci).max(0.0).sqrt();
676                if value.abs() <= Scalar::EPSILON {
677                    degenerate = true;
678                }
679                value
680            })
681            .collect();
682        if degenerate {
683            // `point` is on this face's plane but outside its triangle: no
684            // contribution from this face.
685            continue;
686        }
687        weights[fi] +=
688            (theta[0] - c[1] * theta[2] - c[2] * theta[1]) / (d[fi] * sin_theta[1] * s[2]);
689        weights[fj] +=
690            (theta[1] - c[2] * theta[0] - c[0] * theta[2]) / (d[fj] * sin_theta[2] * s[0]);
691        weights[fk] +=
692            (theta[2] - c[0] * theta[1] - c[1] * theta[0]) / (d[fk] * sin_theta[0] * s[1]);
693    }
694    let sum: Scalar = weights.iter().sum();
695    let magnitude: Scalar = weights.iter().map(|w| w.abs()).sum();
696    if !sum.is_finite() || sum.abs() <= Scalar::EPSILON * n as Scalar * magnitude {
697        return Err(BarycentricError::Undefined);
698    }
699    for w in &mut weights {
700        *w /= sum;
701    }
702    Ok(weights)
703}