Skip to main content

ogeom_mesh/editable/
fill.rs

1//! Filling a rim of an editable mesh with a patch of new faces.
2//!
3//! # The rim, as loops
4//!
5//! A rim is named by its [`RimId`], its lowest-numbered half-edge. A rim
6//! that passes through one point more than once, as where two holes touch
7//! at a corner, is parted there into loops that each pass through every
8//! point once, and each loop is filled on its own. The patches meet at the
9//! point the rim passed more than once and close the fans about it into
10//! one, so the point becomes a single vertex again.
11//!
12//! A loop of two edges, and a rim edge that more faces than the rim's own
13//! use, cannot be filled into a manifold, and are refused.
14//!
15//! # The triangulation
16//!
17//! A loop of up to [`OPTIMAL_UP_TO`] edges is triangulated on its own
18//! vertices, by exact dynamic programming over every triangulation. The
19//! programme minimises first the *largest bend* and then the area. A bend
20//! is the angle between the normals of the two faces that meet across an
21//! edge, and the largest is taken over every edge of the patch, including
22//! the rim's edges with the faces already on them. A face with collinear
23//! corners has no normal, so the triangulation never makes one, and the
24//! bend across a rim edge whose face has none counts as zero.
25//!
26//! A bend `β` is compared as `4 sin²(β/2)`, read from the sum of the unit
27//! directions from the edge to the two far corners. That grows with the
28//! bend, needs no angle, and keeps its precision for small bends.
29//!
30//! A bend depends on the faces on both sides of an edge, so the
31//! programme's state is a triangle on an edge, not the edge alone. A first
32//! pass finds the least largest bend, `θ`. A second pass finds the least
33//! area among the triangulations that bend nowhere more than `θ`. Both
34//! are exact over all triangulations of the loop: `θ` to the bit, as
35//! computed, and the area to the rounding of its sum. Ties go to the
36//! lowest-numbered apex. Memory grows as `n³/6` numbers for a loop of `n`
37//! edges, about 170 MB at the bound. Time grows as `n³` while the cheapest
38//! apexes on each edge fit the faces beyond it, as on smooth holes, and as
39//! `n⁴` at worst.
40//!
41//! The triangulation draws no edge between two points the mesh already
42//! joins. Such an edge would have four faces.
43//!
44//! A longer loop is flattened onto the plane across which it encloses the
45//! most area, normal to its vector area, and ear-clipped there by exact
46//! orientation tests. A loop whose shadow on that plane crosses or touches
47//! itself is refused.
48//!
49//! # Refinement
50//!
51//! With [`Fill::refine`], the patch is refined to the spacing of the mesh
52//! about it, by Liepa's scheme. Each rim vertex takes the mean length of
53//! the mesh's edges at it as its spacing `σ`, and each new vertex the mean
54//! of its triangle's corners'. A triangle is split at its centroid `c`,
55//! into three, when `c` stands farther than `σ/√2` from each corner `m`
56//! (for `σ` both `c`'s and `m`'s own). That is Liepa's density factor: it
57//! leaves edges about as long as the rim's. The centroid must also stand
58//! that far from every vertex of the patch, so that no two vertices crowd
59//! each other. New vertices lie within the rim's convex hull, at least the
60//! smallest rim spacing over `√2` apart, so the splitting ends.
61//!
62//! After each round of splits, each edge inside the patch is flipped while
63//! that raises the smallest angle of its two faces. A flip is not made if
64//! it would turn a face over against the other, or draw an edge the patch
65//! or the mesh already has. Each flip raises the patch's sorted angles
66//! lexicographically, so the flipping ends too.
67//!
68//! # Fairing
69//!
70//! Only the vertices the refinement made move. Without refinement the
71//! patch is the triangulation itself.
72//!
73//! The Laplacian is the cotangent one, weighted by the patch as refined
74//! and, at the rim, by the mesh's faces about it. Weights read from the
75//! geometry, rather than one per neighbour, let the result converge as the
76//! mesh is refined, however unevenly the mesh about the hole is spaced.
77//!
78//! - [`Fairing::Flat`] spans the rim tightly: a membrane, with the
79//!   Laplacian vanishing at each new vertex and the rim held fixed. It is
80//!   exact for linear functions, so a hole in a plane stays in the plane.
81//! - [`Fairing::Curved`] carries the surface's curvature across the hole.
82//!   It minimises the sum, over the patch's vertices and the rim's, of the
83//!   squared Laplacian times each vertex's area (a third of its faces').
84//!   The Laplacian at a rim vertex reaches one ring into the surrounding
85//!   mesh, which is held fixed. This is the discrete thin-plate energy,
86//!   and its stationary condition is a bi-Laplacian vanishing over the
87//!   patch.
88//!
89//! The three coordinates are solved as one system, by conjugate gradients
90//! on the normal equations. The fill therefore does not depend on the
91//! axes: a hole in a plane is filled in that plane, to rounding. The
92//! solve takes about as many steps as there are new vertices, so its time
93//! grows as their square.
94
95use super::{Editable, NONE, RimId, Scope, index, prev};
96use core::f64::consts::FRAC_1_SQRT_2;
97use core::ops::Range;
98use ogeom_core::{Exact, FastMap, FastSet, OgeomResult, Predicates, Sign, ogeom_bail};
99use ogeom_math::{Point, SparseMatrix, Vector, least_squares_cgnr};
100
101/// The longest loop triangulated optimally, by exact dynamic programming;
102/// a longer one is ear-clipped on its plane.
103pub const OPTIMAL_UP_TO: usize = 500;
104
105/// How the new vertices of a refined patch are placed.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
107pub enum Fairing {
108    /// Carrying the surface's curvature across the hole: the thin-plate
109    /// patch, with one ring of the mesh about it held fixed.
110    Curved,
111    /// The tightest patch: a membrane, harmonic over the patch, with the
112    /// rim held fixed.
113    Flat,
114}
115
116/// How a rim is filled.
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
118pub struct Fill {
119    /// Where the vertices the refinement makes settle.
120    pub fairing: Fairing,
121    /// Whether to refine the patch to the spacing of the mesh about it.
122    /// Without refinement the patch has only the rim's vertices, and
123    /// nothing is faired.
124    pub refine: bool,
125}
126
127/// What a fill made.
128#[derive(Debug, Clone, PartialEq)]
129pub struct FillReport {
130    /// The faces made, numbered after every face there was.
131    pub faces: Range<u32>,
132    /// The vertices the refinement made, numbered after every vertex there
133    /// was. Empty without refinement.
134    pub vertices: Range<u32>,
135    /// The loops the rim was parted into at the points it passes more than
136    /// once, each filled on its own: one for a rim passing every point
137    /// once.
138    pub loops: usize,
139    /// How many of the loops were longer than [`OPTIMAL_UP_TO`] edges, and
140    /// were flattened and ear-clipped.
141    pub flattened: usize,
142    /// The largest bend, in radians, of the triangulation before
143    /// refinement. A bend is the angle between the normals of two faces
144    /// across an edge of the triangulation, rim edges included. For loops
145    /// triangulated optimally, no triangulation of them bends less.
146    pub largest_bend: f64,
147}
148
149impl Editable {
150    /// Fill the rim `rim` with a patch of new faces, triangulated, refined
151    /// and faired as `fill` asks. See the [module documentation](self) for
152    /// how.
153    ///
154    /// The patch shares every rim edge with the face beyond it, so the mesh
155    /// is closed about it. A mesh whose every rim is filled is closed. No
156    /// vertex of the mesh moves. A rim passing through a point more than
157    /// once is closed about that point, whose vertices become one: the
158    /// lowest-numbered keeps its number. New faces and vertices take
159    /// numbers after every one there is, and the new vertices have no
160    /// [`source_vertex`]. Nothing changes when the fill is refused.
161    ///
162    /// [`source_vertex`]: Editable::source_vertex
163    ///
164    /// # Errors
165    ///
166    /// - [`OgeomError::Dangling`](ogeom_core::OgeomError::Dangling) if
167    ///   `rim` names no rim: a half-edge that is not on a rim, or not the
168    ///   lowest-numbered of its rim.
169    /// - [`OgeomError::Construction`](ogeom_core::OgeomError::Construction)
170    ///   if the rim cannot be filled into a manifold: a loop of two edges,
171    ///   or a rim edge more faces use. Also if a loop has no triangulation
172    ///   without a face of collinear corners or an edge the mesh already
173    ///   has, if the loops' triangulations would draw one edge twice, or if
174    ///   a long loop's shadow on its plane crosses itself.
175    /// - [`OgeomError::NotDone`](ogeom_core::OgeomError::NotDone) if ear
176    ///   clipping finds no ear it may cut.
177    /// - [`OgeomError::Numeric`](ogeom_core::OgeomError::Numeric) if the
178    ///   fairing solve does not settle.
179    /// - [`OgeomError::Range`](ogeom_core::OgeomError::Range) if the faces
180    ///   and vertices made would pass `u32` numbering.
181    pub fn fill(&mut self, rim: RimId, fill: &Fill) -> OgeomResult<FillReport> {
182        let edges = self.rim_named(rim)?;
183        let (mut patch, loops) = self.gather(&edges)?;
184        let mut flattened = 0;
185        let mut largest_bend = 0.0_f64;
186        let mut drawn: FastSet<(u32, u32)> = FastSet::default();
187        for lp in &loops {
188            let ring = patch.ring(self, lp);
189            let free = |i: usize, j: usize| {
190                let (a, b) = (ring.ids[i], ring.ids[j]);
191                !patch.joined.contains(&(a.min(b), a.max(b)))
192            };
193            let triangles = if lp.len() <= OPTIMAL_UP_TO {
194                let Some(best) = optimal(&ring, &free) else {
195                    ogeom_bail!(
196                        Construction,
197                        "rim {} has a loop of {} edges with no triangulation free of collinear \
198                         corners and of edges the mesh already has",
199                        rim.0,
200                        lp.len()
201                    );
202                };
203                best
204            } else {
205                flattened += 1;
206                ear_clip(&ring, &free)?
207            };
208            largest_bend = largest_bend.max(radians(measure(&ring, &triangles).0));
209            // Each new edge has a face on either side, wound opposite ways:
210            // counted once, from the side running it upward.
211            let n = ring.ids.len();
212            for t in &triangles {
213                for k in 0..3 {
214                    let (i, j) = (t[k], t[(k + 1) % 3]);
215                    if i > j || j - i == 1 || (i == 0 && j == n - 1) {
216                        continue;
217                    }
218                    let (a, b) = (ring.ids[i], ring.ids[j]);
219                    if !drawn.insert((a.min(b), a.max(b))) {
220                        ogeom_bail!(
221                            Construction,
222                            "the loops of rim {} would draw one edge twice",
223                            rim.0
224                        );
225                    }
226                }
227                patch.triangles.push(t.map(|k| ring.ids[k]));
228            }
229        }
230        if fill.refine {
231            patch.refine();
232        }
233        patch.fair(fill.fairing)?;
234        let faces = self.corners.len().saturating_add(patch.triangles.len());
235        let vertices = self.positions.len().saturating_add(patch.positions.len());
236        if faces.saturating_mul(3) >= NONE as usize || vertices >= NONE as usize {
237            ogeom_bail!(
238                Range,
239                "filling rim {} would make {faces} faces and {vertices} vertices, past u32 \
240                 numbering",
241                rim.0
242            );
243        }
244        let (faces, vertices) = self.commit(&patch);
245        Ok(FillReport {
246            faces,
247            vertices,
248            loops: loops.len(),
249            flattened,
250            largest_bend,
251        })
252    }
253
254    /// The half-edges of the rim `rim` names, in order from it.
255    fn rim_named(&self, rim: RimId) -> OgeomResult<Vec<u32>> {
256        let h = rim.0;
257        let k = h as usize;
258        if k >= self.twin.len() || !self.live[k / 3] || self.twin[k] != NONE {
259            ogeom_bail!(Dangling, "half-edge {h} is not on a rim");
260        }
261        let mut edges = Vec::new();
262        let mut g = h;
263        loop {
264            if g < h {
265                ogeom_bail!(
266                    Dangling,
267                    "half-edge {h} is on the rim of half-edge {g}, which names it"
268                );
269            }
270            edges.push(g);
271            g = self.rim_after(g);
272            if g == h {
273                break;
274            }
275        }
276        Ok(edges)
277    }
278
279    /// The patch's rim points, with what the mesh says about each, and the
280    /// rim parted into loops at the points it passes more than once.
281    fn gather(&self, edges: &[u32]) -> OgeomResult<(Patch, Vec<Vec<u32>>)> {
282        let mut local: FastMap<u32, u32> = FastMap::default();
283        let mut vertex = Vec::new();
284        for &h in edges {
285            let v = self.tail(h);
286            local.entry(self.point[v as usize]).or_insert_with(|| {
287                vertex.push(v);
288                index(vertex.len() - 1)
289            });
290        }
291        let r = vertex.len();
292        let mut points: Vec<u32> = vertex.iter().map(|&v| self.point[v as usize]).collect();
293        points.sort_unstable();
294        let faces = self.about(&points);
295
296        // Edges are told apart by their points, so every face at a rim
297        // point counts for the edges there. The Laplacian and the spacing
298        // read only the fans the rim runs through: a vertex of the rim.
299        let on_rim: FastSet<u32> = edges.iter().map(|&h| self.tail(h)).collect();
300        let mut uses: FastMap<u64, u32> = FastMap::default();
301        let mut joined = FastSet::default();
302        let mut next_to: Vec<Vec<(u32, f64)>> = vec![Vec::new(); r];
303        let mut mass = vec![0.0; r];
304        for &f in &faces {
305            let c = self.corners[f as usize];
306            let pull = spokes(c.map(|v| self.positions[v as usize]));
307            for k in 0..3 {
308                *uses.entry(self.edge_key(3 * f + index(k))).or_insert(0) += 1;
309                let ends = [c[k], c[(k + 1) % 3]].map(|v| local.get(&self.point[v as usize]));
310                if let [Some(&l), Some(&m)] = ends {
311                    joined.insert((l.min(m), l.max(m)));
312                }
313                if !on_rim.contains(&c[k]) {
314                    continue;
315                }
316                if let Some(&l) = local.get(&self.point[c[k] as usize]) {
317                    let (a, b) = (c[(k + 1) % 3], c[(k + 2) % 3]);
318                    let (wa, wb) = pull.map_or((0.0, 0.0), |(w, third)| {
319                        mass[l as usize] += third;
320                        (w[(k + 2) % 3], w[(k + 1) % 3])
321                    });
322                    next_to[l as usize].push((self.point[a as usize], wa));
323                    next_to[l as usize].push((self.point[b as usize], wb));
324                }
325            }
326        }
327        for &h in edges {
328            let n = uses.get(&self.edge_key(h)).copied().unwrap_or(0);
329            if n != 1 {
330                ogeom_bail!(
331                    Construction,
332                    "rim edge {h} lies on an edge {n} faces use, which a patch cannot close"
333                );
334            }
335        }
336
337        let positions: Vec<Point> = vertex.iter().map(|&v| self.positions[v as usize]).collect();
338        let mut near = Vec::with_capacity(r);
339        let mut spacing = Vec::with_capacity(r);
340        for (l, list) in next_to.iter_mut().enumerate() {
341            merge(list);
342            let mut around = Near {
343                rim: Vec::new(),
344                ring: Vec::new(),
345                mass: mass[l],
346            };
347            let mut length = 0.0;
348            for &(q, w) in list.iter() {
349                let at = self.positions[q as usize];
350                length += at.distance(positions[l]);
351                match local.get(&q) {
352                    Some(&m) => around.rim.push((m, w)),
353                    None => around.ring.push((at, w)),
354                }
355            }
356            spacing.push(length / f64::from(index(list.len().max(1))));
357            near.push(around);
358        }
359
360        // Parted where a point comes round again: the edges since it last
361        // left that point close a loop.
362        let mut at = vec![usize::MAX; r];
363        let mut stack: Vec<u32> = Vec::new();
364        let mut loops = Vec::new();
365        let slot = |h: u32| local[&self.point[self.tail(h) as usize]] as usize;
366        for &h in edges {
367            let l = slot(h);
368            if at[l] != usize::MAX {
369                let closed: Vec<u32> = stack.drain(at[l]..).collect();
370                for &e in &closed {
371                    at[slot(e)] = usize::MAX;
372                }
373                loops.push(closed);
374            }
375            at[l] = stack.len();
376            stack.push(h);
377        }
378        loops.push(stack);
379        if let Some(short) = loops.iter().find(|l| l.len() < 3) {
380            ogeom_bail!(
381                Construction,
382                "rim {} has a loop of {} edges, which no face can fill",
383                edges[0],
384                short.len()
385            );
386        }
387
388        Ok((
389            Patch {
390                vertex,
391                local,
392                positions,
393                spacing,
394                triangles: Vec::new(),
395                joined,
396                near,
397            },
398            loops,
399        ))
400    }
401
402    /// Add the patch's vertices and faces, and join them to the mesh.
403    /// Returns the faces and vertices made.
404    fn commit(&mut self, patch: &Patch) -> (Range<u32>, Range<u32>) {
405        let first_face = index(self.corners.len());
406        let first_vertex = index(self.positions.len());
407        let r = patch.vertex.len();
408        let mut global = patch.vertex.clone();
409        for &p in &patch.positions[r..] {
410            global.push(self.add_vertex(p));
411        }
412        let mut points: Vec<u32> = patch
413            .vertex
414            .iter()
415            .map(|&v| self.point[v as usize])
416            .collect();
417        points.sort_unstable();
418        let mut faces = self.about(&points);
419        points.extend(first_vertex..index(self.positions.len()));
420        for t in &patch.triangles {
421            faces.push(self.add_face(t.map(|l| global[l as usize])));
422        }
423        self.relink(&Scope::Points(points), &faces);
424        (
425            first_face..index(self.corners.len()),
426            first_vertex..index(self.positions.len()),
427        )
428    }
429}
430
431/// A patch being built, numbered on its own: the rim's points first, one
432/// number each, then the vertices the refinement makes.
433struct Patch {
434    /// For each rim point, a vertex of the mesh standing for it.
435    vertex: Vec<u32>,
436    /// The patch number of each rim point.
437    local: FastMap<u32, u32>,
438    positions: Vec<Point>,
439    /// The spacing about each vertex: for a rim point, the mean length of
440    /// the mesh's edges at it; for a new vertex, the mean of its
441    /// triangle's corners'.
442    spacing: Vec<f64>,
443    /// The faces, wound as the mesh's.
444    triangles: Vec<[u32; 3]>,
445    /// The pairs of rim points the mesh joins, smaller first.
446    joined: FastSet<(u32, u32)>,
447    /// For each rim point, what the mesh's faces about it add to the
448    /// Laplacian there.
449    near: Vec<Near>,
450}
451
452/// What the mesh's faces about a rim point add to the cotangent Laplacian
453/// there.
454struct Near {
455    /// The rim points it shares an edge with, by patch number, each with
456    /// the edge's weight.
457    rim: Vec<(u32, f64)>,
458    /// The other points it shares an edge with, by position, each with the
459    /// edge's weight.
460    ring: Vec<(Point, f64)>,
461    /// A third of the faces' area.
462    mass: f64,
463}
464
465/// For a triangle, half the cotangent of the angle at each corner, the
466/// weight of the edge facing it in the cotangent Laplacian, and a third of
467/// the area; `None` for one with collinear corners, which has no angles.
468fn spokes(c: [Point; 3]) -> Option<([f64; 3], f64)> {
469    let twice = (c[1] - c[0]).cross(c[2] - c[0]).magnitude();
470    if twice == 0.0 {
471        return None;
472    }
473    let half_cot = |k: usize| {
474        let (a, b) = (c[(k + 1) % 3] - c[k], c[(k + 2) % 3] - c[k]);
475        a.dot(b) / twice / 2.0
476    };
477    Some(([half_cot(0), half_cot(1), half_cot(2)], twice / 6.0))
478}
479
480/// Sort `list` by its first members and sum the second over each run of
481/// equal first.
482fn merge(list: &mut Vec<(u32, f64)>) {
483    list.sort_by_key(|e| e.0);
484    let mut out: Vec<(u32, f64)> = Vec::with_capacity(list.len());
485    for &(k, w) in list.iter() {
486        match out.last_mut() {
487            Some(last) if last.0 == k => last.1 += w,
488            _ => out.push((k, w)),
489        }
490    }
491    *list = out;
492}
493
494/// A loop to triangulate.
495struct Loop {
496    /// The patch numbers of its points, in the rim's order.
497    ids: Vec<u32>,
498    points: Vec<Point>,
499    /// Beyond the edge from point `i` to `i + 1`, the far corner of the
500    /// face on it, where that face has a normal.
501    beyond: Vec<Option<Point>>,
502}
503
504impl Patch {
505    /// The loop of the rim's half-edges `edges`.
506    fn ring(&self, mesh: &Editable, edges: &[u32]) -> Loop {
507        let at = |v: u32| mesh.positions[v as usize];
508        let ids: Vec<u32> = edges
509            .iter()
510            .map(|&h| self.local[&mesh.point[mesh.tail(h) as usize]])
511            .collect();
512        let points = ids.iter().map(|&l| self.positions[l as usize]).collect();
513        let beyond = edges
514            .iter()
515            .map(|&h| {
516                let (a, b, o) = (at(mesh.tail(h)), at(mesh.head(h)), at(mesh.tail(prev(h))));
517                ((b - a).cross(o - a) != Vector::ZERO).then_some(o)
518            })
519            .collect();
520        Loop {
521            ids,
522            points,
523            beyond,
524        }
525    }
526
527    /// Refine to the mesh's spacing: centroid splits and flips, as the
528    /// module documentation says.
529    fn refine(&mut self) {
530        let widest = self.spacing.iter().copied().fold(0.0, f64::max);
531        if widest <= 0.0 {
532            return;
533        }
534        // Cells as wide as the farthest a crowding vertex can stand, so the
535        // 27 about a point hold every one.
536        let width = widest * FRAC_1_SQRT_2;
537        #[allow(clippy::cast_possible_truncation, reason = "saturating")]
538        let cell = |p: Point| [p.x, p.y, p.z].map(|x| (x / width).floor() as i64);
539        let mut grid: FastMap<[i64; 3], Vec<u32>> = FastMap::default();
540        for (v, &p) in self.positions.iter().enumerate() {
541            grid.entry(cell(p)).or_default().push(index(v));
542        }
543        loop {
544            let mut split = false;
545            for t in 0..self.triangles.len() {
546                let [a, b, c] = self.triangles[t];
547                let corners = [a, b, c].map(|v| self.positions[v as usize]);
548                let sum = corners
549                    .iter()
550                    .fold(Vector::ZERO, |s, &p| s + (p - Point::ORIGIN));
551                let g = Point::ORIGIN + sum / 3.0;
552                let s = [a, b, c]
553                    .iter()
554                    .map(|&v| self.spacing[v as usize])
555                    .sum::<f64>()
556                    / 3.0;
557                // Farther than σ/√2: twice the square past σ².
558                let far = |d2: f64, sigma: f64| 2.0 * d2 > sigma * sigma;
559                let corners_clear = [a, b, c].iter().zip(&corners).all(|(&v, &p)| {
560                    let d2 = g.square_distance(p);
561                    far(d2, self.spacing[v as usize])
562                });
563                if !corners_clear {
564                    continue;
565                }
566                let [x, y, z] = cell(g);
567                let mut crowded = false;
568                'cells: for dx in -1..=1_i64 {
569                    for dy in -1..=1_i64 {
570                        for dz in -1..=1_i64 {
571                            let key = [
572                                x.saturating_add(dx),
573                                y.saturating_add(dy),
574                                z.saturating_add(dz),
575                            ];
576                            if let Some(list) = grid.get(&key)
577                                && list.iter().any(|&v| {
578                                    !far(g.square_distance(self.positions[v as usize]), s)
579                                })
580                            {
581                                crowded = true;
582                                break 'cells;
583                            }
584                        }
585                    }
586                }
587                if crowded {
588                    continue;
589                }
590                let v = index(self.positions.len());
591                self.positions.push(g);
592                self.spacing.push(s);
593                grid.entry(cell(g)).or_default().push(v);
594                self.triangles[t] = [a, b, v];
595                self.triangles.push([b, c, v]);
596                self.triangles.push([c, a, v]);
597                split = true;
598            }
599            if !split {
600                break;
601            }
602            self.relax();
603        }
604    }
605
606    /// Flip each edge inside the patch while that raises the smallest
607    /// angle of its two faces, and neither turns over against the other
608    /// nor draws an edge there already is.
609    fn relax(&mut self) {
610        let r = index(self.vertex.len());
611        let mut at: FastMap<(u32, u32), u32> = FastMap::default();
612        for (t, c) in self.triangles.iter().enumerate() {
613            for k in 0..3 {
614                at.insert((c[k], c[(k + 1) % 3]), index(t));
615            }
616        }
617        let positions = &self.positions;
618        let p = |v: u32| positions[v as usize];
619        loop {
620            let mut flipped = false;
621            'triangles: for t in 0..self.triangles.len() {
622                for k in 0..3 {
623                    let tri = self.triangles[t];
624                    let (u, v, c) = (tri[k], tri[(k + 1) % 3], tri[(k + 2) % 3]);
625                    if u > v {
626                        continue;
627                    }
628                    let Some(&t2) = at.get(&(v, u)) else { continue };
629                    let other = self.triangles[t2 as usize];
630                    let Some(&d) = other.iter().find(|&&w| w != u && w != v) else {
631                        continue;
632                    };
633                    if c == d
634                        || at.contains_key(&(c, d))
635                        || at.contains_key(&(d, c))
636                        || (c < r && d < r && self.joined.contains(&(c.min(d), c.max(d))))
637                    {
638                        continue;
639                    }
640                    let n1 = (p(d) - p(u)).cross(p(c) - p(u));
641                    let n2 = (p(v) - p(d)).cross(p(c) - p(d));
642                    if n1 == Vector::ZERO || n2 == Vector::ZERO || n1.dot(n2) <= 0.0 {
643                        continue;
644                    }
645                    let before =
646                        smallest_angle(p(u), p(v), p(c)).min(smallest_angle(p(v), p(u), p(d)));
647                    let after =
648                        smallest_angle(p(u), p(d), p(c)).min(smallest_angle(p(d), p(v), p(c)));
649                    if after <= before {
650                        continue;
651                    }
652                    at.remove(&(u, v));
653                    at.remove(&(v, u));
654                    self.triangles[t] = [u, d, c];
655                    self.triangles[t2 as usize] = [d, v, c];
656                    for (e, f) in [((u, d), t), ((d, c), t), ((c, u), t)] {
657                        at.insert(e, index(f));
658                    }
659                    for e in [(d, v), (v, c), (c, d)] {
660                        at.insert(e, t2);
661                    }
662                    flipped = true;
663                    continue 'triangles;
664                }
665            }
666            if !flipped {
667                break;
668            }
669        }
670    }
671
672    /// Move the new vertices as `fairing` asks.
673    fn fair(&mut self, fairing: Fairing) -> OgeomResult<()> {
674        let r = self.vertex.len();
675        let total = self.positions.len();
676        let unknowns = total - r;
677        if unknowns == 0 {
678            return Ok(());
679        }
680        // The cotangent Laplacian of the patch as it stands, with the
681        // mesh's faces about the rim.
682        let mut spoke: Vec<Vec<(u32, f64)>> = vec![Vec::new(); total];
683        let mut mass = vec![0.0; total];
684        for t in &self.triangles {
685            let Some((w, third)) = spokes(t.map(|v| self.positions[v as usize])) else {
686                continue;
687            };
688            for k in 0..3 {
689                mass[t[k] as usize] += third;
690                let (a, b) = (t[(k + 1) % 3], t[(k + 2) % 3]);
691                spoke[a as usize].push((b, w[k]));
692                spoke[b as usize].push((a, w[k]));
693            }
694        }
695        for (l, near) in self.near.iter().enumerate() {
696            spoke[l].extend(&near.rim);
697            mass[l] += near.mass;
698        }
699        for list in &mut spoke {
700            merge(list);
701        }
702        let column = |l: u32| (l as usize).checked_sub(r);
703        let rows: Vec<usize> = (0..total)
704            .filter(|&w| {
705                w >= r
706                    || (fairing == Fairing::Curved
707                        && mass[w] > 0.0
708                        && spoke[w].iter().any(|&(m, _)| m as usize >= r))
709            })
710            .collect();
711        // The three coordinates as one system, the x rows and columns
712        // first, then y, then z. A Curved row is the Laplacian times the
713        // root of its vertex's area, so that the squares sum to the
714        // thin-plate energy.
715        let mut triplets = Vec::new();
716        let mut rhs = vec![0.0; 3 * rows.len()];
717        let none: Vec<(Point, f64)> = Vec::new();
718        for (row, &w) in rows.iter().enumerate() {
719            let ring = if w < r { &self.near[w].ring } else { &none };
720            // A vertex whose faces all have collinear corners has no
721            // weights, and its row is empty however scaled.
722            let scale = match fairing {
723                Fairing::Curved if mass[w] > 0.0 => 1.0 / mass[w].sqrt(),
724                _ => 1.0,
725            };
726            let here = self.positions[w];
727            let mut lap = Vector::ZERO;
728            let mut sum = 0.0;
729            let mut put = |c: usize, value: f64| {
730                for k in 0..3 {
731                    triplets.push((k * rows.len() + row, k * unknowns + c, value));
732                }
733            };
734            for &(m, weight) in &spoke[w] {
735                lap += (self.positions[m as usize] - here) * weight;
736                sum += weight;
737                if let Some(c) = column(m) {
738                    put(c, scale * weight);
739                }
740            }
741            for &(q, weight) in ring {
742                lap += (q - here) * weight;
743                sum += weight;
744            }
745            if let Some(c) = column(index(w)) {
746                put(c, -scale * sum);
747            }
748            let lap = lap * scale;
749            rhs[row] = -lap.x;
750            rhs[rows.len() + row] = -lap.y;
751            rhs[2 * rows.len() + row] = -lap.z;
752        }
753        let a = SparseMatrix::from_triplets(3 * rows.len(), 3 * unknowns, &triplets);
754        // Conjugate gradients end in as many steps as the normal matrix
755        // has distinct eigenvalues, at most `unknowns` for its three equal
756        // blocks; three times that is allowed for rounding.
757        let Some(d) = least_squares_cgnr(&a, &rhs, SETTLED, 3 * unknowns) else {
758            ogeom_bail!(
759                Numeric,
760                "the fairing of {unknowns} new vertices did not settle"
761            );
762        };
763        for c in 0..unknowns {
764            self.positions[r + c] += Vector::new(d[c], d[unknowns + c], d[2 * unknowns + c]);
765        }
766        Ok(())
767    }
768}
769
770/// Where the fairing solve stops: the residual's gradient this small a
771/// part of where it began. On the patches measured, of up to some
772/// thousands of vertices, the displacement is then within a few units of
773/// rounding of the exact one, for about a tenth more steps than a stop a
774/// million times looser.
775const SETTLED: f64 = 1e-12;
776
777/// The smallest angle of the triangle `a b c`.
778fn smallest_angle(a: Point, b: Point, c: Point) -> f64 {
779    let angle = |u: Vector, v: Vector| u.cross(v).magnitude().atan2(u.dot(v));
780    angle(b - a, c - a)
781        .min(angle(c - b, a - b))
782        .min(angle(a - c, b - c))
783}
784
785/// A frame about the line from one point to another, in which [`turn`]
786/// reads the way a point stands from it.
787///
788/// [`turn`]: Axis::turn
789struct Axis {
790    from: Point,
791    e1: Vector,
792    e2: Vector,
793}
794
795impl Axis {
796    fn new(a: Point, b: Point) -> Self {
797        let u = b - a;
798        let length = u.magnitude();
799        // The coordinate axis least along the line makes a frame with it.
800        let pick = if u.x.abs() <= u.y.abs() && u.x.abs() <= u.z.abs() {
801            Vector::X
802        } else if u.y.abs() <= u.z.abs() {
803            Vector::Y
804        } else {
805            Vector::Z
806        };
807        let e1 = u.cross(pick);
808        let e1 = e1 / e1.magnitude();
809        let e2 = u.cross(e1) / length;
810        Self { from: a, e1, e2 }
811    }
812
813    /// The unit direction, square to the line, from it to `p`.
814    fn turn(&self, p: Point) -> [f64; 2] {
815        let w = p - self.from;
816        let (x, y) = (w.dot(self.e1), w.dot(self.e2));
817        // Scaled to the larger first, so the squares neither overflow nor
818        // underflow.
819        let m = x.abs().max(y.abs());
820        let (x, y) = (x / m, y / m);
821        let r = (x * x + y * y).sqrt();
822        [x / r, y / r]
823    }
824}
825
826/// How far two faces sharing an edge are bent, from the turns of their far
827/// corners about it: `4 sin²(β / 2)` for the bend `β`, π less the angle
828/// between the two half-planes, so 0 for faces flat across the edge and 4
829/// for faces folded onto each other. It grows with the bend, and is read
830/// from the sum of the two directions, which keeps its precision for small
831/// bends.
832fn bend(a: [f64; 2], b: [f64; 2]) -> f64 {
833    let (x, y) = (a[0] + b[0], a[1] + b[1]);
834    x * x + y * y
835}
836
837/// The bend `β`, in radians, of the measure [`bend`] gives.
838fn radians(fold: f64) -> f64 {
839    2.0 * (fold.sqrt() / 2.0).min(1.0).asin()
840}
841
842/// The frame of the edge between loop points `i` and `j`, from the lower.
843fn axis_of(ring: &Loop, i: usize, j: usize) -> Axis {
844    Axis::new(ring.points[i.min(j)], ring.points[i.max(j)])
845}
846
847/// The largest bend, as [`bend`] measures it, and the area of a
848/// triangulation of `ring`, given as triples of loop indices.
849fn measure(ring: &Loop, triangles: &[[usize; 3]]) -> (f64, f64) {
850    let n = ring.points.len();
851    let p = &ring.points;
852    let mut apexes: FastMap<(usize, usize), Vec<usize>> = FastMap::default();
853    let mut area = 0.0;
854    for t in triangles {
855        area += (p[t[1]] - p[t[0]]).cross(p[t[2]] - p[t[0]]).magnitude() / 2.0;
856        for k in 0..3 {
857            let (i, j) = (t[k], t[(k + 1) % 3]);
858            apexes
859                .entry((i.min(j), i.max(j)))
860                .or_default()
861                .push(t[(k + 2) % 3]);
862        }
863    }
864    let mut keys: Vec<(usize, usize)> = apexes.keys().copied().collect();
865    keys.sort_unstable();
866    let mut largest = 0.0_f64;
867    for (i, j) in keys {
868        let axis = axis_of(ring, i, j);
869        let sides = &apexes[&(i, j)];
870        let b = if j - i == 1 || (i == 0 && j == n - 1) {
871            let edge = if j - i == 1 { i } else { n - 1 };
872            ring.beyond[edge].map_or(0.0, |o| bend(axis.turn(o), axis.turn(p[sides[0]])))
873        } else {
874            bend(axis.turn(p[sides[0]]), axis.turn(p[sides[1]]))
875        };
876        largest = largest.max(b);
877    }
878    (largest, area)
879}
880
881/// The triangulation of `ring` with the least largest bend, then the least
882/// area, drawing a new edge between loop points `i < j` only where
883/// `free(i, j)`. Triangles are wound as the mesh's faces, against the loop;
884/// `None` where every triangulation has a face of collinear corners or an
885/// edge not free.
886fn optimal(ring: &Loop, free: &dyn Fn(usize, usize) -> bool) -> Option<Vec<[usize; 3]>> {
887    const NO: f64 = f64::INFINITY;
888    let n = ring.points.len();
889    let p = &ring.points;
890    let mut open = vec![false; n * n];
891    for i in 0..n {
892        for j in i + 1..n {
893            open[i * n + j] = j - i == 1 || (i == 0 && j == n - 1) || free(i, j);
894        }
895    }
896    // The triangles on edge (i, k), one per apex between, from `start`.
897    let mut start = vec![0_usize; n * n];
898    let mut total = 0;
899    for i in 0..n {
900        for k in i + 2..n {
901            start[i * n + k] = total;
902            total += k - i - 1;
903        }
904    }
905    let slot = |i: usize, m: usize, k: usize| start[i * n + k] + (m - i - 1);
906    let valid = |i: usize, m: usize, k: usize| {
907        open[i * n + m]
908            && open[m * n + k]
909            && open[i * n + k]
910            && (p[m] - p[i]).cross(p[k] - p[i]) != Vector::ZERO
911    };
912    // The triangle a value about edge (a, b) and a point `c` outside goes
913    // to.
914    let consumer = |a: usize, b: usize, c: usize| {
915        if c < a { slot(c, a, b) } else { slot(a, b, c) }
916    };
917    let outside = |a: usize, b: usize| (0..a).chain(b + 1..n);
918
919    // First pass: the largest bend within each triangle's side of its
920    // edge, least over the triangulations there.
921    let mut value = vec![0.0_f64; total];
922    let each = |value: &mut Vec<f64>, init: &dyn Fn(usize, usize, usize) -> f64| {
923        for i in 0..n {
924            for k in i + 2..n {
925                for m in i + 1..k {
926                    value[slot(i, m, k)] = if valid(i, m, k) { init(i, m, k) } else { NO };
927                }
928            }
929        }
930    };
931    each(&mut value, &|_, _, _| 0.0);
932    for a in 0..n - 1 {
933        let axis = axis_of(ring, a, a + 1);
934        let far = ring.beyond[a].map(|o| axis.turn(o));
935        for c in outside(a, a + 1) {
936            let b = far.map_or(0.0, |t| bend(t, axis.turn(p[c])));
937            let s = consumer(a, a + 1, c);
938            value[s] = value[s].max(b);
939        }
940    }
941    let mut candidates: Vec<(f64, usize, [f64; 2])> = Vec::new();
942    let mut theta = NO;
943    for len in 2..n {
944        for a in 0..n - len {
945            let b = a + len;
946            let axis = axis_of(ring, a, b);
947            gather_candidates(&mut candidates, &value, &axis, p, a, b, &slot);
948            let least = |t: Option<[f64; 2]>| {
949                let mut best = NO;
950                for &(w, _, tx) in &candidates {
951                    if w >= best {
952                        break;
953                    }
954                    best = best.min(w.max(t.map_or(0.0, |t| bend(tx, t))));
955                }
956                best
957            };
958            if len == n - 1 {
959                theta = least(ring.beyond[n - 1].map(|o| axis.turn(o)));
960            } else {
961                for c in outside(a, b) {
962                    let f = least(Some(axis.turn(p[c])));
963                    let s = consumer(a, b, c);
964                    value[s] = value[s].max(f);
965                }
966            }
967        }
968    }
969    if theta == NO {
970        return None;
971    }
972
973    // Second pass: the least area within each triangle's side of its
974    // edge, over the triangulations there bending nowhere more than θ.
975    each(&mut value, &|i, m, k| {
976        (p[m] - p[i]).cross(p[k] - p[i]).magnitude() / 2.0
977    });
978    let fits = |far: Option<[f64; 2]>, t: [f64; 2]| far.is_none_or(|o| bend(o, t) <= theta);
979    for a in 0..n - 1 {
980        let axis = axis_of(ring, a, a + 1);
981        let far = ring.beyond[a].map(|o| axis.turn(o));
982        for c in outside(a, a + 1) {
983            if !fits(far, axis.turn(p[c])) {
984                value[consumer(a, a + 1, c)] = NO;
985            }
986        }
987    }
988    let mut root = None;
989    for len in 2..n {
990        for a in 0..n - len {
991            let b = a + len;
992            let axis = axis_of(ring, a, b);
993            gather_candidates(&mut candidates, &value, &axis, p, a, b, &slot);
994            let first = |t: Option<[f64; 2]>| {
995                candidates
996                    .iter()
997                    .find(|&&(_, _, tx)| fits(t, tx))
998                    .map(|&(g, x, _)| (g, x))
999            };
1000            if len == n - 1 {
1001                root = first(ring.beyond[n - 1].map(|o| axis.turn(o)));
1002            } else {
1003                for c in outside(a, b) {
1004                    let s = consumer(a, b, c);
1005                    value[s] = match first(Some(axis.turn(p[c]))) {
1006                        Some((g, _)) => value[s] + g,
1007                        None => NO,
1008                    };
1009                }
1010            }
1011        }
1012    }
1013    let (_, m) = root?;
1014
1015    // Each side's best apex given the triangle across its edge, as the
1016    // second pass chose it: least area, then lowest apex.
1017    let choose = |a: usize, b: usize, c: usize| {
1018        let axis = axis_of(ring, a, b);
1019        let t = axis.turn(p[c]);
1020        let mut best: Option<(f64, usize)> = None;
1021        for x in a + 1..b {
1022            let g = value[slot(a, x, b)];
1023            if g < NO && bend(axis.turn(p[x]), t) <= theta && best.is_none_or(|(h, _)| g < h) {
1024                best = Some((g, x));
1025            }
1026        }
1027        best.map(|(_, x)| x)
1028    };
1029    let mut triangles = Vec::with_capacity(n - 2);
1030    let mut stack = vec![(0, m, n - 1)];
1031    while let Some((i, m, k)) = stack.pop() {
1032        triangles.push([k, m, i]);
1033        if m - i >= 2 {
1034            stack.push((i, choose(i, m, k)?, m));
1035        }
1036        if k - m >= 2 {
1037            stack.push((m, choose(m, k, i)?, k));
1038        }
1039    }
1040    Some(triangles)
1041}
1042
1043/// The apexes on edge `(a, b)` with a finite value: the value, the apex,
1044/// and its turn about the edge, sorted by value and then apex.
1045fn gather_candidates(
1046    candidates: &mut Vec<(f64, usize, [f64; 2])>,
1047    value: &[f64],
1048    axis: &Axis,
1049    p: &[Point],
1050    a: usize,
1051    b: usize,
1052    slot: &dyn Fn(usize, usize, usize) -> usize,
1053) {
1054    candidates.clear();
1055    for x in a + 1..b {
1056        let w = value[slot(a, x, b)];
1057        if w < f64::INFINITY {
1058            candidates.push((w, x, axis.turn(p[x])));
1059        }
1060    }
1061    candidates.sort_unstable_by(|u, v| u.0.total_cmp(&v.0).then(u.1.cmp(&v.1)));
1062}
1063
1064/// Ear-clip `ring` on the plane across which it encloses the most area,
1065/// drawing a new edge between loop points `i < j` only where `free(i, j)`.
1066/// Triangles are wound as the mesh's faces, against the loop.
1067fn ear_clip(ring: &Loop, free: &dyn Fn(usize, usize) -> bool) -> OgeomResult<Vec<[usize; 3]>> {
1068    let n = ring.points.len();
1069    let p = &ring.points;
1070    // Against the loop, the patch's own way round.
1071    let order: Vec<usize> = (0..n).map(|j| (n - j) % n).collect();
1072    let centre = p.iter().fold(Vector::ZERO, |s, &q| s + (q - Point::ORIGIN)) / f64::from(index(n));
1073    let at = |j: usize| p[order[j]] - Point::ORIGIN - centre;
1074    let mut normal = Vector::ZERO;
1075    for j in 0..n {
1076        normal += at(j).cross(at((j + 1) % n));
1077    }
1078    if normal == Vector::ZERO {
1079        ogeom_bail!(
1080            Construction,
1081            "a loop of {n} edges encloses no area across any plane"
1082        );
1083    }
1084    let axis = Axis::new(Point::ORIGIN, Point::ORIGIN + normal);
1085    // `e1`, `e2` and the normal are right-handed: the loop runs
1086    // counter-clockwise in those coordinates.
1087    let q: Vec<[f64; 2]> = (0..n)
1088        .map(|j| {
1089            let w = at(j);
1090            [w.dot(axis.e1), w.dot(axis.e2)]
1091        })
1092        .collect();
1093    let orient = |a: usize, b: usize, c: usize| Exact::orient2d(q[a], q[b], q[c]);
1094    if !simple(&q) {
1095        ogeom_bail!(
1096            Construction,
1097            "the shadow of a loop of {n} edges on its plane crosses itself"
1098        );
1099    }
1100
1101    let mut next: Vec<usize> = (0..n).map(|j| (j + 1) % n).collect();
1102    let mut before: Vec<usize> = (0..n).map(|j| (j + n - 1) % n).collect();
1103    let mut gone = vec![false; n];
1104    let convex = |before: &[usize], next: &[usize], j: usize| {
1105        orient(before[j], j, next[j]) == Sign::Positive
1106    };
1107    let mut triangles = Vec::with_capacity(n - 2);
1108    let mut left = n;
1109    let mut j = 0;
1110    let mut misses = 0;
1111    while left > 3 {
1112        let (a, c) = (before[j], next[j]);
1113        let ear = convex(&before, &next, j)
1114            && free(order[a].min(order[c]), order[a].max(order[c]))
1115            && (0..n).all(|v| {
1116                gone[v]
1117                    || v == a
1118                    || v == j
1119                    || v == c
1120                    || convex(&before, &next, v)
1121                    || orient(a, j, v) == Sign::Negative
1122                    || orient(j, c, v) == Sign::Negative
1123                    || orient(c, a, v) == Sign::Negative
1124            });
1125        if ear {
1126            triangles.push([order[a], order[j], order[c]]);
1127            gone[j] = true;
1128            next[a] = c;
1129            before[c] = a;
1130            left -= 1;
1131            misses = 0;
1132            j = a;
1133        } else {
1134            j = next[j];
1135            misses += 1;
1136            if misses > left {
1137                ogeom_bail!(
1138                    NotDone,
1139                    "ear clipping a loop of {n} edges found no ear to cut with {left} points left"
1140                );
1141            }
1142        }
1143    }
1144    let (a, c) = (before[j], next[j]);
1145    if orient(a, j, c) != Sign::Positive {
1146        ogeom_bail!(
1147            NotDone,
1148            "ear clipping a loop of {n} edges left a last triangle with no area"
1149        );
1150    }
1151    triangles.push([order[a], order[j], order[c]]);
1152    Ok(triangles)
1153}
1154
1155/// Whether the closed polygon `q` is simple: no two of its edges meet but
1156/// neighbours at their shared corner, and no neighbours run back along
1157/// each other. Edges are swept in order of their least `x`.
1158fn simple(q: &[[f64; 2]]) -> bool {
1159    let n = q.len();
1160    let edge = |j: usize| (q[j], q[(j + 1) % n]);
1161    let mut order: Vec<usize> = (0..n).collect();
1162    let low = |j: usize| edge(j).0[0].min(edge(j).1[0]);
1163    let high = |j: usize| edge(j).0[0].max(edge(j).1[0]);
1164    order.sort_by(|&a, &b| low(a).total_cmp(&low(b)).then(a.cmp(&b)));
1165    let mut active: Vec<usize> = Vec::new();
1166    for &j in &order {
1167        active.retain(|&k| high(k) >= low(j));
1168        for &k in &active {
1169            let (a, b) = edge(j);
1170            let (c, d) = edge(k);
1171            let meets = if (j + 1) % n == k {
1172                folds_back(a, b, d)
1173            } else if (k + 1) % n == j {
1174                folds_back(c, d, b)
1175            } else {
1176                segments_meet(a, b, c, d)
1177            };
1178            if meets {
1179                return false;
1180            }
1181        }
1182        active.push(j);
1183    }
1184    true
1185}
1186
1187/// Whether the edge from `b` to `c` runs back along the edge from `a` to
1188/// `b`.
1189fn folds_back(a: [f64; 2], b: [f64; 2], c: [f64; 2]) -> bool {
1190    Exact::orient2d(a, b, c) == Sign::Zero
1191        && (a[0] - b[0]) * (c[0] - b[0]) + (a[1] - b[1]) * (c[1] - b[1]) > 0.0
1192}
1193
1194/// Whether the closed segments `a b` and `c d` share a point, exactly.
1195fn segments_meet(a: [f64; 2], b: [f64; 2], c: [f64; 2], d: [f64; 2]) -> bool {
1196    let within = |p: [f64; 2], q: [f64; 2], r: [f64; 2]| {
1197        r[0] >= p[0].min(q[0])
1198            && r[0] <= p[0].max(q[0])
1199            && r[1] >= p[1].min(q[1])
1200            && r[1] <= p[1].max(q[1])
1201    };
1202    let (o1, o2) = (Exact::orient2d(a, b, c), Exact::orient2d(a, b, d));
1203    let (o3, o4) = (Exact::orient2d(c, d, a), Exact::orient2d(c, d, b));
1204    if (o1 == Sign::Zero && within(a, b, c))
1205        || (o2 == Sign::Zero && within(a, b, d))
1206        || (o3 == Sign::Zero && within(c, d, a))
1207        || (o4 == Sign::Zero && within(c, d, b))
1208    {
1209        return true;
1210    }
1211    let opposite = |s: Sign, t: Sign| s != Sign::Zero && t != Sign::Zero && s != t;
1212    opposite(o1, o2) && opposite(o3, o4)
1213}
1214
1215#[cfg(test)]
1216#[allow(
1217    clippy::unwrap_used,
1218    clippy::cast_precision_loss,
1219    clippy::cast_possible_truncation,
1220    reason = "test code"
1221)]
1222mod tests {
1223    use super::super::tests::{check, grid, mesh, torus};
1224    use super::*;
1225    use core::f64::consts::{PI, TAU};
1226    use ogeom_topo::Triangulation;
1227    use proptest::prelude::*;
1228
1229    const CURVED: Fill = Fill {
1230        fairing: Fairing::Curved,
1231        refine: true,
1232    };
1233    const FLAT: Fill = Fill {
1234        fairing: Fairing::Flat,
1235        refine: true,
1236    };
1237    const BARE: Fill = Fill {
1238        fairing: Fairing::Flat,
1239        refine: false,
1240    };
1241
1242    /// A sphere of latitude bands and longitude sides about poles on `z`,
1243    /// wound outward.
1244    fn sphere(radius: f64, bands: u32, sides: u32) -> Triangulation {
1245        let mut points = vec![Point::new(0.0, 0.0, radius)];
1246        for j in 1..bands {
1247            let polar = PI * f64::from(j) / f64::from(bands);
1248            let (s, c) = (radius * polar.sin(), radius * polar.cos());
1249            for i in 0..sides {
1250                let a = TAU * f64::from(i) / f64::from(sides);
1251                points.push(Point::new(s * a.cos(), s * a.sin(), c));
1252            }
1253        }
1254        let south = 1 + (bands - 1) * sides;
1255        points.push(Point::new(0.0, 0.0, -radius));
1256        let at = |j: u32, i: u32| 1 + (j - 1) * sides + i % sides;
1257        let mut triangles = Vec::new();
1258        for i in 0..sides {
1259            triangles.push([0, at(1, i), at(1, i + 1)]);
1260            triangles.push([south, at(bands - 1, i + 1), at(bands - 1, i)]);
1261        }
1262        for j in 1..bands - 1 {
1263            for i in 0..sides {
1264                let (a, b, c, d) = (at(j, i), at(j + 1, i), at(j + 1, i + 1), at(j, i + 1));
1265                triangles.extend([[a, b, c], [a, c, d]]);
1266            }
1267        }
1268        mesh(points, triangles)
1269    }
1270
1271    /// The faces whose centroids pass `keep`.
1272    fn faces_where(e: &Editable, keep: impl Fn(Point) -> bool) -> Vec<u32> {
1273        (0..index(e.face_count()))
1274            .filter(|&f| {
1275                e.face(f).is_some_and(|c| {
1276                    let sum = c.iter().fold(Vector::ZERO, |s, &v| {
1277                        s + (e.position(v).unwrap() - Point::ORIGIN)
1278                    });
1279                    keep(Point::ORIGIN + sum / 3.0)
1280                })
1281            })
1282            .collect()
1283    }
1284
1285    /// Fill every rim, each as `fill` asks.
1286    fn fill_all(e: &mut Editable, fill: &Fill) -> Vec<FillReport> {
1287        let mut reports = Vec::new();
1288        while let Some(rim) = e.rims().first() {
1289            reports.push(e.fill(rim.id(), fill).unwrap());
1290            check(e);
1291        }
1292        reports
1293    }
1294
1295    /// The area of the faces `faces`.
1296    fn area_of(e: &Editable, faces: Range<u32>) -> f64 {
1297        faces
1298            .map(|f| {
1299                let [a, b, c] = e.face(f).unwrap().map(|v| e.position(v).unwrap());
1300                (b - a).cross(c - a).magnitude() / 2.0
1301            })
1302            .sum()
1303    }
1304
1305    /// Every triangulation of the polygon `i..=k` on its edge `(i, k)`,
1306    /// wound as [`optimal`]'s.
1307    fn every(i: usize, k: usize) -> Vec<Vec<[usize; 3]>> {
1308        if k - i < 2 {
1309            return vec![Vec::new()];
1310        }
1311        let mut all = Vec::new();
1312        for m in i + 1..k {
1313            for left in every(i, m) {
1314                for right in every(m, k) {
1315                    let mut t = left.clone();
1316                    t.extend(&right);
1317                    t.push([k, m, i]);
1318                    all.push(t);
1319                }
1320            }
1321        }
1322        all
1323    }
1324
1325    /// A small generator for the loops the programme is checked on.
1326    struct Draw(u64);
1327
1328    impl Draw {
1329        fn next(&mut self) -> f64 {
1330            self.0 ^= self.0 << 13;
1331            self.0 ^= self.0 >> 7;
1332            self.0 ^= self.0 << 17;
1333            (self.0 >> 11) as f64 / (1_u64 << 53) as f64
1334        }
1335    }
1336
1337    proptest! {
1338        #![proptest_config(ProptestConfig::with_cases(256))]
1339
1340        /// The programme's triangulation bends no more than any of a loop's
1341        /// triangulations, to the bit, and has the least area of those
1342        /// that bend as little, to the rounding of the sums: checked
1343        /// against every triangulation of loops of up to nine edges, with
1344        /// diagonals barred and rim faces with no normal among them.
1345        #[test]
1346        fn the_programme_is_optimal_over_every_triangulation(
1347            n in 3_usize..=9,
1348            seed in 1_u64..u64::MAX,
1349        ) {
1350            let mut draw = Draw(seed);
1351            let mut points = Vec::new();
1352            let mut beyond = Vec::new();
1353            for i in 0..n {
1354                let a = TAU * i as f64 / n as f64;
1355                let r = 1.0 + 0.6 * (draw.next() - 0.5);
1356                points.push(Point::new(r * a.cos(), r * a.sin(), draw.next() - 0.5));
1357            }
1358            for i in 0..n {
1359                let (p, q) = (points[i], points[(i + 1) % n]);
1360                let out = Vector::new(p.x + q.x, p.y + q.y, 0.0) * 0.5;
1361                let o = p + (q - p) * 0.5 + out * draw.next()
1362                    + Vector::new(0.0, 0.0, 2.0 * draw.next() - 1.0);
1363                beyond.push((draw.next() > 0.1).then_some(o));
1364            }
1365            let mut barred = vec![false; n * n];
1366            for b in &mut barred {
1367                *b = draw.next() < 0.15;
1368            }
1369            let ring = Loop { ids: (0..index(n)).collect(), points, beyond };
1370            let free = |i: usize, j: usize| !barred[i * n + j];
1371            let open = |i: usize, j: usize| {
1372                let (i, j) = (i.min(j), i.max(j));
1373                j - i == 1 || (i == 0 && j == n - 1) || free(i, j)
1374            };
1375            let p = &ring.points;
1376            let fits = |t: &[[usize; 3]]| {
1377                t.iter().all(|&[k, m, i]| {
1378                    open(i, m) && open(m, k) && open(i, k)
1379                        && (p[m] - p[i]).cross(p[k] - p[i]) != Vector::ZERO
1380                })
1381            };
1382            let best = every(0, n - 1)
1383                .into_iter()
1384                .filter(|t| fits(t))
1385                .map(|t| measure(&ring, &t))
1386                .min_by(|a, b| a.0.total_cmp(&b.0).then(a.1.total_cmp(&b.1)));
1387            let found = optimal(&ring, &free);
1388            match (best, found) {
1389                (None, None) => {}
1390                (Some((bend, area)), Some(t)) => {
1391                    prop_assert_eq!(t.len(), n - 2);
1392                    prop_assert!(fits(&t));
1393                    let (b, a) = measure(&ring, &t);
1394                    prop_assert_eq!(b, bend);
1395                    prop_assert!((a - area).abs() <= 1e-12 * area, "{} against {}", a, area);
1396                }
1397                (best, found) => prop_assert!(false, "{:?} against {:?}", best, found),
1398            }
1399        }
1400    }
1401
1402    #[test]
1403    fn a_sphere_cap_filled_curved_comes_back_to_the_sphere() {
1404        // A cap of polar angle α = π/8 taken off unit spheres of three
1405        // resolutions. The Laplacians are those of the patch as
1406        // triangulated, flat across the rim's plane, and of the sphere's
1407        // faces about it. In the limit the patch is biharmonic over the
1408        // disc of radius a = sin α, meeting the sphere's position and its
1409        // slope along the sphere at the rim: height A + B s², with
1410        // 2 B a = -sin α, so its top stands at cos α + sin² α / 2.
1411        let alpha = PI / 8.0;
1412        let top = alpha.cos() + alpha.sin().powi(2) / 2.0;
1413        let mut worst = Vec::new();
1414        let mut highest = Vec::new();
1415        for (bands, sides) in [(16, 24), (32, 48), (64, 96)] {
1416            let m = sphere(1.0, bands, sides);
1417            let mut e = Editable::from(&m, 0.0).unwrap();
1418            let rim_z = alpha.cos();
1419            e.remove_faces(&faces_where(&e, |c| c.z > rim_z));
1420            let rims = e.rims();
1421            assert_eq!(rims.len(), 1);
1422            assert_eq!(rims[0].edges.len(), sides as usize);
1423            let report = e.fill(rims[0].id(), &CURVED).unwrap();
1424            check(&e);
1425            assert!(e.is_closed());
1426            assert!(e.non_manifold().is_empty());
1427            assert_eq!(report.loops, 1);
1428            assert_eq!(report.flattened, 0);
1429            assert!(!report.vertices.is_empty());
1430            for (v, &p) in m.positions.iter().enumerate() {
1431                if let Some(q) = e.position(index(v)) {
1432                    assert_eq!(q, p);
1433                }
1434            }
1435            let new = || report.vertices.clone().map(|v| e.position(v).unwrap());
1436            worst.push(
1437                new()
1438                    .map(|p| ((p - Point::ORIGIN).magnitude() - 1.0).abs())
1439                    .fold(0.0, f64::max),
1440            );
1441            highest.push(new().map(|p| p.z).fold(f64::MIN, f64::max));
1442        }
1443        // Measured: 0.28 %, 0.38 % and 0.33 % of the radius, and on to
1444        // 0.31 % and 0.30 % at 128 and 256 bands, tending to the top's
1445        // 0.29 %.
1446        assert!(worst.iter().all(|&w| w < 0.01), "{worst:?}");
1447        let gaps: Vec<f64> = highest.iter().map(|h| (h - top).abs()).collect();
1448        assert!(
1449            gaps.windows(2).all(|g| g[1] < g[0]),
1450            "{highest:?} against {top}"
1451        );
1452    }
1453
1454    #[test]
1455    fn a_flat_hole_filled_flat_stays_in_its_plane() {
1456        // A block of three by three cells with an arm of two, out of a grid
1457        // tilted out of every axis plane.
1458        let n = 8;
1459        let u = Vector::new(0.6, 0.3, -0.2);
1460        let v = Vector::new(-0.1, 0.4, 0.7);
1461        let normal = u.cross(v) / u.cross(v).magnitude();
1462        let origin = Point::new(3.0, -2.0, 5.0);
1463        let mut m = grid(n);
1464        for p in &mut m.positions {
1465            *p = origin + u * p.x + v * p.y;
1466        }
1467        let cell_area = u.cross(v).magnitude();
1468        let mut cells = vec![(4, 1), (5, 1)];
1469        for i in 1..4 {
1470            for j in 1..4 {
1471                cells.push((i, j));
1472            }
1473        }
1474        let removed: Vec<u32> = cells
1475            .iter()
1476            .flat_map(|&(i, j)| [2 * (n * j + i), 2 * (n * j + i) + 1])
1477            .collect();
1478        for fill in [FLAT, BARE] {
1479            let mut e = Editable::from(&m, 0.0).unwrap();
1480            e.remove_faces(&removed);
1481            let rim = e.rims().into_iter().find(|r| r.edges.len() == 16).unwrap();
1482            let report = e.fill(rim.id(), &fill).unwrap();
1483            check(&e);
1484            assert_eq!(e.rims().len(), 1);
1485            assert!(e.non_manifold().is_empty());
1486            assert!(
1487                report.largest_bend < 1e-14,
1488                "{fill:?} {}",
1489                report.largest_bend
1490            );
1491            assert_eq!(report.vertices.is_empty(), !fill.refine);
1492            for v in report.vertices.clone() {
1493                let h = (e.position(v).unwrap() - origin).dot(normal);
1494                assert!(h.abs() < 1e-14, "{fill:?} {h}");
1495            }
1496            let area = area_of(&e, report.faces.clone());
1497            let hole = cells.len() as f64 * cell_area;
1498            assert!((area - hole).abs() < 1e-13 * hole, "{fill:?} {area} {hole}");
1499        }
1500    }
1501
1502    #[test]
1503    fn filling_every_hole_closes_a_torus() {
1504        let m = torus(16, 12);
1505        let whole = Editable::from(&m, 0.0).unwrap();
1506        // A lone face, a quad, a block of three by two quads, and two quads
1507        // touching at a corner.
1508        let mut removed = vec![5, 40, 41];
1509        for (i, j) in [
1510            (6, 3),
1511            (6, 4),
1512            (7, 3),
1513            (7, 4),
1514            (8, 3),
1515            (8, 4),
1516            (12, 8),
1517            (13, 9),
1518        ] {
1519            removed.extend([2 * (i * 12 + j), 2 * (i * 12 + j) + 1]);
1520        }
1521        for fill in [CURVED, FLAT, BARE] {
1522            let mut e = whole.clone();
1523            e.remove_faces(&removed);
1524            assert_eq!(e.rims().len(), 4);
1525            let reports = fill_all(&mut e, &fill);
1526            assert_eq!(reports.len(), 4);
1527            assert!(e.is_closed(), "{fill:?}");
1528            assert!(e.non_manifold().is_empty());
1529            assert_eq!(e.components().len(), 1);
1530            // A torus: V - E + F = 0, each face's three edges each shared.
1531            let t = e.to_triangulation();
1532            let (v, f) = (t.positions.len(), t.triangles.len());
1533            assert_eq!(2 * v, f, "{fill:?}");
1534            // The block's two inner vertices went with it.
1535            if !fill.refine {
1536                assert_eq!(v, m.positions.len() - 2);
1537            }
1538        }
1539    }
1540
1541    #[test]
1542    fn holes_touching_at_a_point_are_closed_through_it() {
1543        // Two cells of a grid meeting at a corner leave one rim through
1544        // that point twice, at two copies of it.
1545        let m = grid(4);
1546        let mut e = Editable::from(&m, 0.0).unwrap();
1547        let cell = |i: u32, j: u32| [2 * (4 * j + i), 2 * (4 * j + i) + 1];
1548        e.remove_faces(&[cell(1, 1), cell(2, 2)].concat());
1549        let pinch = Point::new(2.0, 2.0, 0.0);
1550        assert_eq!(e.non_manifold().len(), 2);
1551        let rims = e.rims();
1552        assert_eq!(rims.len(), 2);
1553        let inner = rims.iter().find(|r| r.edges.len() == 8).unwrap();
1554        let at_pinch = inner
1555            .edges
1556            .iter()
1557            .filter(|&&h| e.position(e.edge(h).unwrap()[0]) == Some(pinch))
1558            .count();
1559        assert_eq!(at_pinch, 2);
1560        for fill in [BARE, CURVED] {
1561            let mut f = e.clone();
1562            let report = f.fill(inner.id(), &fill).unwrap();
1563            check(&f);
1564            assert_eq!(report.loops, 2);
1565            assert!(f.non_manifold().is_empty(), "{fill:?}");
1566            assert_eq!(f.rims().len(), 1);
1567            let back = f.to_triangulation();
1568            assert_eq!(
1569                back.positions.iter().filter(|&&p| p == pinch).count(),
1570                1,
1571                "{fill:?}"
1572            );
1573            if !fill.refine {
1574                assert_eq!(back.positions, m.positions);
1575                assert_eq!(back.triangles.len(), m.triangles.len());
1576            }
1577        }
1578    }
1579
1580    #[test]
1581    fn an_edge_the_mesh_has_is_not_drawn_again() {
1582        // A square hole in a grid whose diagonal from (1, 1) to (2, 2) is
1583        // already an edge, of a fin standing on it. The fill draws the
1584        // other diagonal, and the fin's edge keeps its one face.
1585        let mut m = grid(3);
1586        let fin = index(m.positions.len());
1587        m.positions.push(Point::new(1.5, 1.5, 1.0));
1588        m.triangles.push([5, 10, fin]);
1589        let mut e = Editable::from(&m, 0.0).unwrap();
1590        e.remove_faces(&[8, 9]);
1591        let hole = e.rims().into_iter().find(|r| r.edges.len() == 4).unwrap();
1592        let report = e.fill(hole.id(), &BARE).unwrap();
1593        check(&e);
1594        let at = |v: u32| e.position(v).unwrap();
1595        for f in report.faces {
1596            let corners = e.face(f).unwrap().map(at);
1597            assert!(
1598                !(corners.contains(&Point::new(1.0, 1.0, 0.0))
1599                    && corners.contains(&Point::new(2.0, 2.0, 0.0))),
1600                "{corners:?}"
1601            );
1602        }
1603        let rims = e.rims();
1604        assert_eq!(rims.len(), 2);
1605        assert!(rims.iter().any(|r| r.edges.len() == 3));
1606    }
1607
1608    #[test]
1609    fn a_long_loop_is_ear_clipped_on_its_plane() {
1610        // A flat ring of 600 sides between radii 1 and 2: both rims are
1611        // past the programme's bound.
1612        let sides = 600;
1613        let mut positions = Vec::new();
1614        for r in [1.0, 2.0] {
1615            for i in 0..sides {
1616                let a = TAU * f64::from(i) / f64::from(sides);
1617                positions.push(Point::new(r * a.cos(), r * a.sin(), 0.0));
1618            }
1619        }
1620        let mut triangles = Vec::new();
1621        for i in 0..sides {
1622            let j = (i + 1) % sides;
1623            triangles.extend([[i, sides + i, sides + j], [i, sides + j, j]]);
1624        }
1625        let m = mesh(positions, triangles);
1626        let polygon = |r: f64| f64::from(sides) / 2.0 * r * r * (TAU / f64::from(sides)).sin();
1627        for fill in [BARE, FLAT] {
1628            let mut e = Editable::from(&m, 0.0).unwrap();
1629            let rims = e.rims();
1630            assert_eq!(rims.len(), 2);
1631            let inner = rims
1632                .iter()
1633                .find(|r| e.position(e.edge(r.edges[0]).unwrap()[0]).unwrap().x.abs() <= 1.0)
1634                .unwrap();
1635            let report = e.fill(inner.id(), &fill).unwrap();
1636            check(&e);
1637            assert_eq!(report.flattened, 1);
1638            let area = area_of(&e, report.faces.clone());
1639            assert!((area - polygon(1.0)).abs() < 1e-12, "{area}");
1640            for v in report.vertices.clone() {
1641                assert_eq!(e.position(v).unwrap().z, 0.0);
1642            }
1643            let report = e.fill(e.rims()[0].id(), &fill).unwrap();
1644            assert_eq!(report.flattened, 1);
1645            check(&e);
1646            assert!(e.is_closed());
1647            assert!(e.non_manifold().is_empty());
1648        }
1649    }
1650
1651    #[test]
1652    fn a_long_loop_whose_shadow_crosses_itself_is_refused() {
1653        // A ribbon along a trefoil knot, 600 quads long: each rim's shadow
1654        // crosses itself three times.
1655        let steps = 600;
1656        let mut positions = Vec::new();
1657        for lift in [0.0, 0.05] {
1658            for i in 0..steps {
1659                let t = TAU * f64::from(i) / f64::from(steps);
1660                let r = 3.0 + (3.0 * t).cos();
1661                positions.push(Point::new(
1662                    r * (2.0 * t).cos(),
1663                    r * (2.0 * t).sin(),
1664                    (3.0 * t).sin() + lift,
1665                ));
1666            }
1667        }
1668        let mut triangles = Vec::new();
1669        for i in 0..steps {
1670            let j = (i + 1) % steps;
1671            triangles.extend([[i, j, steps + j], [i, steps + j, steps + i]]);
1672        }
1673        let mut e = Editable::from(&mesh(positions, triangles), 0.0).unwrap();
1674        let before = e.clone();
1675        let rim = e.rims()[0].id();
1676        let err = e.fill(rim, &BARE).unwrap_err();
1677        assert!(matches!(err, ogeom_core::OgeomError::Construction(_)));
1678        assert!(err.to_string().contains("crosses itself"), "{err}");
1679        assert_eq!(e, before);
1680    }
1681
1682    #[test]
1683    fn a_name_that_is_not_a_rim_is_refused() {
1684        let mut e = Editable::from(&grid(2), 0.0).unwrap();
1685        let rim = &e.rims()[0];
1686        let joined = (0..index(3 * e.face_count()))
1687            .find(|h| !rim.edges.contains(h))
1688            .unwrap();
1689        let later = rim.edges[1];
1690        let before = e.clone();
1691        for h in [joined, later, u32::MAX] {
1692            let err = e.fill(RimId(h), &BARE).unwrap_err();
1693            assert!(matches!(err, ogeom_core::OgeomError::Dangling(_)), "{err}");
1694        }
1695        assert_eq!(e, before);
1696    }
1697
1698    #[test]
1699    fn the_fill_is_deterministic() {
1700        let m = sphere(2.0, 18, 22);
1701        let mut a = Editable::from(&m, 0.0).unwrap();
1702        let removed = faces_where(&a, |c| c.x + 0.4 * c.y > 1.2 || c.z < -1.5);
1703        a.remove_faces(&removed);
1704        let mut b = a.clone();
1705        let ra = fill_all(&mut a, &CURVED);
1706        let rb = fill_all(&mut b, &CURVED);
1707        assert_eq!(ra, rb);
1708        assert_eq!(a, b);
1709        assert!(a.is_closed());
1710    }
1711}