Skip to main content

geop_core_topology/
coedge.rs

1use crate::{CoedgeId, VertexId};
2use geop_core_math::geop_error::{GeopError, GeopResult};
3
4use super::ids::{EdgeId, FaceId};
5use super::model::{Curve2, Curve3};
6
7/// Whether a coedge traverses its underlying edge in the same direction
8/// (forward: start→end) or the opposite direction (reversed: end→start).
9#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
10pub enum Sense {
11    Forward,
12    Reversed,
13}
14
15impl Sense {
16    /// The sense of a coedge running its edge forward if `forward`.
17    pub fn from_forward(forward: bool) -> Sense {
18        if forward {
19            Sense::Forward
20        } else {
21            Sense::Reversed
22        }
23    }
24
25    pub fn opposite(self) -> Sense {
26        match self {
27            Sense::Forward => Sense::Reversed,
28            Sense::Reversed => Sense::Forward,
29        }
30    }
31
32    /// `curve`, run the way a coedge of this sense runs it.
33    pub fn orient(self, curve: &Curve3) -> Curve3 {
34        match self {
35            Sense::Forward => curve.clone(),
36            Sense::Reversed => curve.reverse(),
37        }
38    }
39}
40
41/// What a coedge's own `(u, v)` boundary segment is actually backed by.
42#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
43pub enum CoedgeGeometry {
44    /// A real, shared edge — this coedge is one of exactly the two
45    /// (opposite `Sense`) that trace it, one per adjoining face. The usual
46    /// case.
47    Edge(EdgeId),
48    /// A degenerate loop segment that sits at a single, already-existing
49    /// vertex the whole way — no edge, and (unlike `Edge`) no second
50    /// coedge sharing it, since there's nothing to share between two
51    /// faces: it belongs to exactly one face's own loop. This is how a
52    /// loop closes over a surface row that's collapsed to a single point
53    /// (e.g. a pole) — the loop still needs *some* pcurve tracing that
54    /// row's full parameter range even though there's no 3-D geometry
55    /// there to back a real edge. Adds neither a new vertex nor a new
56    /// edge, so it leaves the Euler–Poincaré count as it is.
57    Vertex(VertexId),
58}
59
60#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
61pub struct Coedge {
62    pub geometry: CoedgeGeometry,
63    pub sense: Sense,
64    #[serde(with = "geop_core_math::exact")]
65    pub pcurve: Curve2,
66    pub next: CoedgeId,
67    pub prev: CoedgeId,
68    // face
69    pub face: FaceId,
70}
71
72impl Coedge {
73    /// This coedge's own edge — an error for a `Vertex`-backed coedge
74    /// (there's no edge to compare or collect there). Convenience for
75    /// code that only ever applies to edge-backed coedges.
76    pub fn edge(&self) -> GeopResult<EdgeId> {
77        match self.geometry {
78            CoedgeGeometry::Edge(id) => Ok(id),
79            CoedgeGeometry::Vertex(v) => Err(GeopError::new(format!(
80                "coedge is vertex-backed (vertex {}), not edge-backed",
81                v.0
82            ))),
83        }
84    }
85}