Skip to main content

geop_core_topology/euler/
mekr.rs

1use crate::{
2    Coedge, CoedgeGeometry, CoedgeId, Edge, EdgeId, Model, Sense,
3    argument_validation::{
4        validate_curve_start_and_end, validate_different_loop, validate_pcurve_start_and_end,
5    },
6};
7use geop_core_geometry::nurb_curve::{NurbCurve2D, NurbCurve3D};
8use geop_core_math::{
9    geop_error::{GeopError, GeopResult, WithContext},
10    scalars::Scalar,
11};
12
13impl<S: Scalar> Model<S> {
14    // Make a new edge from coedge1's end vertex to coedge2's start vertex, merging
15    // the two different loops (rings) that coedge1 and coedge2 belong to into a
16    // single loop rooted at coedge1. Both coedges must belong to the same face, but
17    // to different boundary loops of it; the boundary entry for coedge2's ring is
18    // killed since it becomes part of coedge1's ring. pcurve is required to go from
19    // coedge1's end vertex to coedge2's start vertex, as measured through the
20    // face's surface.
21    // returns (new edge, coedge_a: end1 -> start2, coedge_b: start2 -> end1)
22    pub fn mekr(
23        self: &mut Model<S>,
24        coedge1: CoedgeId,
25        coedge2: CoedgeId,
26        curve: NurbCurve3D<S>,
27        pcurve: NurbCurve2D<S>,
28    ) -> GeopResult<(EdgeId, CoedgeId, CoedgeId)> {
29        let ctx = |e: GeopError| {
30            e.with_context(format!(
31                "Model::mekr(
32    coedge1={coedge1}
33    coedge2={coedge2}
34    curve={curve}
35    pcurve={pcurve}
36)"
37            ))
38        };
39
40        let ce1 = self.get_coedge(coedge1)?.clone();
41        let ce2 = self.get_coedge(coedge2)?.clone();
42        if ce1.face != ce2.face {
43            return Err(ctx(GeopError::new(
44                "coedge1 and coedge2 must belong to the same face",
45            )));
46        }
47        validate_different_loop(&self, coedge1, coedge2).with_context(&ctx)?;
48        let face = self.get_face(ce1.face)?.clone();
49
50        let start_id = self.coedge_end_vertex_id(coedge1)?;
51        let end_id = self.coedge_start_vertex_id(coedge2)?;
52        let start = self.get_vertex(start_id)?.clone();
53        let end = self.get_vertex(end_id)?.clone();
54
55        validate_pcurve_start_and_end(&face.surface, &pcurve, &start.point, &end.point)
56            .with_context(&ctx)?;
57        validate_curve_start_and_end(&curve, &start.point, &end.point).with_context(&ctx)?;
58
59        // The ring containing coedge2 disappears into coedge1's ring, so find its
60        // boundary entry now, before the splice below makes every ring look the same.
61        let killed_ring_index = self
62            .find_boundary_containing(ce1.face, coedge2)
63            .with_context(&ctx)?;
64
65        let edge_id = self.insert_edge(Edge {
66            curve,
67            start_vertex: start_id,
68            end_vertex: end_id,
69        });
70
71        let reversed_pcurve = pcurve.reverse();
72        let next1 = ce1.next;
73        let prev2 = ce2.prev;
74
75        // coedge_a: end1 -> start2, spliced in right after coedge1.
76        let coedge_a = self.insert_coedge(Coedge {
77            geometry: CoedgeGeometry::Edge(edge_id),
78            sense: Sense::Forward,
79            pcurve,
80            next: coedge2,
81            prev: coedge1,
82            face: ce1.face,
83        });
84
85        // coedge_b: start2 -> end1, spliced in right after prev2.
86        let coedge_b = self.insert_coedge(Coedge {
87            geometry: CoedgeGeometry::Edge(edge_id),
88            sense: Sense::Reversed,
89            pcurve: reversed_pcurve,
90            next: next1,
91            prev: prev2,
92            face: ce1.face,
93        });
94
95        self.coedges.get_mut(&coedge1).unwrap().next = coedge_a;
96        self.coedges.get_mut(&coedge2).unwrap().prev = coedge_a;
97        self.coedges.get_mut(&prev2).unwrap().next = coedge_b;
98        self.coedges.get_mut(&next1).unwrap().prev = coedge_b;
99
100        // The absorbed ring must be a hole: joining an inner ring to the loop
101        // around it is what `mekr` does, and the outer boundary is what
102        // survives that join. `remove_boundary` rejects the other case rather
103        // than leaving a face with nothing bounding it.
104        // Not via `ctx`: that closure borrows `curve`/`pcurve`, which have
105        // since been moved into the new edge and coedges.
106        self.remove_boundary(ce1.face, killed_ring_index)
107            .with_context(&|e: GeopError| {
108                e.with_context(format!(
109                    "mekr(coedge1={coedge1}, coedge2={coedge2}): absorbing the ring containing coedge2"
110                ))
111            })?;
112
113        Ok((edge_id, coedge_a, coedge_b))
114    }
115}