Skip to main content

geop_core_topology/
wire.rs

1use std::collections::{BTreeMap, HashSet};
2
3use geop_core_math::geop_error::{GeopError, GeopResult};
4
5use super::ids::{EdgeId, VertexId, WireId};
6use crate::{Model, Sense};
7
8/// Edges and vertices standing on their own, bounding no face: curves in
9/// space, such as the lines, arcs and splines of a 3-D sketch, or where two
10/// bodies' boundaries cross.
11///
12/// A wire owns its entities as a shell owns its faces: `edges` are used by
13/// no coedge, and `vertices` are every vertex of the wire — the ends of its
14/// edges, and points on their own, which no edge ends at. An edge or a
15/// vertex belongs to one wire at most, and a vertex of a wire to nothing
16/// else.
17///
18/// A wire is no [`crate::Body`]: nothing is cut or combined with it. Its
19/// edges must not cross each other except at a shared vertex, as the edges
20/// of a body must not (see [`crate::validation`]).
21#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
22pub struct Wire {
23    pub vertices: Vec<VertexId>,
24    pub edges: Vec<EdgeId>,
25}
26
27impl Model {
28    /// The edges of the wire `wire` in chains: each a run of edges, in
29    /// order, each starting where the one before ends, with the sense it
30    /// runs in along its chain — open between two vertices where the run
31    /// does not go on through (an end, or a branch), or closed. Ordered by
32    /// the smallest edge id each holds, which follows the order the edges
33    /// were made in.
34    pub fn wire_chains(&self, wire: WireId) -> GeopResult<Vec<Vec<(EdgeId, Sense)>>> {
35        let wire = (self.wires.get(&wire))
36            .ok_or_else(|| GeopError::new(format!("Model::wire_chains: no wire {wire}")))?;
37        let mut at: BTreeMap<VertexId, Vec<EdgeId>> = BTreeMap::new();
38        for &e in &wire.edges {
39            let edge = self.get_edge(e)?;
40            at.entry(edge.start_vertex).or_default().push(e);
41            at.entry(edge.end_vertex).or_default().push(e);
42        }
43        // The other edge of a run through `v` from `edge`: none at an end,
44        // at a branch, or where an edge closes on itself.
45        let through = |v: VertexId, edge: EdgeId| match at.get(&v).map(Vec::as_slice) {
46            Some(&[a, b]) if a != b => Some(if a == edge { b } else { a }),
47            _ => None,
48        };
49        // Where `edge` starts and ends, run in `sense`.
50        let ends = |edge: EdgeId, sense: Sense| {
51            let e = self.get_edge(edge)?;
52            Ok::<_, GeopError>(match sense {
53                Sense::Forward => (e.start_vertex, e.end_vertex),
54                Sense::Reversed => (e.end_vertex, e.start_vertex),
55            })
56        };
57        let mut edges = wire.edges.clone();
58        edges.sort();
59        let mut used = HashSet::new();
60        let mut chains = Vec::new();
61        for &first in &edges {
62            if used.contains(&first) {
63                continue;
64            }
65            // Back from `first` to where its run starts, or round to it.
66            let (mut edge, mut sense) = (first, Sense::Forward);
67            loop {
68                let (start, _) = ends(edge, sense)?;
69                let Some(previous) = through(start, edge).filter(|&p| p != first) else {
70                    break;
71                };
72                sense = if self.get_edge(previous)?.end_vertex == start {
73                    Sense::Forward
74                } else {
75                    Sense::Reversed
76                };
77                edge = previous;
78            }
79            // Forward from there.
80            let mut chain = Vec::new();
81            loop {
82                used.insert(edge);
83                chain.push((edge, sense));
84                let (_, end) = ends(edge, sense)?;
85                let Some(next) = through(end, edge).filter(|n| !used.contains(n)) else {
86                    break;
87                };
88                sense = if self.get_edge(next)?.start_vertex == end {
89                    Sense::Forward
90                } else {
91                    Sense::Reversed
92                };
93                edge = next;
94            }
95            chains.push(chain);
96        }
97        Ok(chains)
98    }
99}