fj_kernel/objects/full/edge.rs
1use fj_math::Point;
2
3use crate::{
4 geometry::curve::Curve,
5 objects::Vertex,
6 storage::{Handle, HandleWrapper},
7};
8
9/// A directed edge, defined in a surface's 2D space
10///
11/// The concept of an "edge" in Fornjot is represented by two structs,
12/// `HalfEdge` and [`GlobalEdge`]. `HalfEdge` has two attributes that make it
13/// distinct from [`GlobalEdge`]:
14///
15/// - `HalfEdge` is directed, meaning it has a defined start and end vertex.
16/// - `HalfEdge` is defined in the 2-dimensional space of a surface.
17///
18/// When multiple faces, which are bound by edges, are combined to form a solid,
19/// the `HalfEdge`s that bound the face on the surface are then coincident with
20/// the `HalfEdge`s of other faces, where those faces touch. Those coincident
21/// `HalfEdge`s are different representations of the same edge. This edge is
22/// represented by an instance of [`GlobalEdge`].
23///
24/// There are some requirements that a `HalfEdge` needs to uphold to be valid:
25///
26/// 1. Coincident `HalfEdge`s *must* refer to the same [`GlobalEdge`].
27/// 2. `HalfEdge`s that are coincident, i.e. located in the same space, must
28/// always be congruent. This means they must coincide *exactly*. The overlap
29/// must be complete. None of the coincident `HalfEdge`s must overlap with
30/// just a section of another.
31///
32/// That second requirement means that a `HalfEdge` might need to be split into
33/// multiple smaller `HalfEdge`s that are each coincident with a `HalfEdge` in
34/// another face.
35///
36/// # Implementation Note
37///
38/// There is no validation code that verifies whether coincident `HalfEdge`s
39/// refer to the same [`GlobalEdge`] or not:
40/// <https://github.com/hannobraun/Fornjot/issues/1594>
41///
42/// Conversely, there is no validation code to verify that coincident
43/// `HalfEdge`s are congruent:
44/// <https://github.com/hannobraun/Fornjot/issues/1608>
45#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
46pub struct HalfEdge {
47 curve: Curve,
48 boundary: [Point<1>; 2],
49 start_vertex: HandleWrapper<Vertex>,
50 global_form: HandleWrapper<GlobalEdge>,
51}
52
53impl HalfEdge {
54 /// Create an instance of `HalfEdge`
55 pub fn new(
56 curve: Curve,
57 boundary: [Point<1>; 2],
58 start_vertex: Handle<Vertex>,
59 global_form: Handle<GlobalEdge>,
60 ) -> Self {
61 Self {
62 curve,
63 boundary,
64 start_vertex: start_vertex.into(),
65 global_form: global_form.into(),
66 }
67 }
68
69 /// Access the curve that defines the half-edge's geometry
70 pub fn curve(&self) -> Curve {
71 self.curve
72 }
73
74 /// Access the boundary points of the half-edge on the curve
75 pub fn boundary(&self) -> [Point<1>; 2] {
76 self.boundary
77 }
78
79 /// Compute the surface position where the half-edge starts
80 pub fn start_position(&self) -> Point<2> {
81 // Computing the surface position from the curve position is fine.
82 // `HalfEdge` "owns" its start position. There is no competing code that
83 // could compute the surface position from slightly different data.
84
85 let [start, _] = self.boundary;
86 self.curve.point_from_path_coords(start)
87 }
88
89 /// Access the vertex from where this half-edge starts
90 pub fn start_vertex(&self) -> &Handle<Vertex> {
91 &self.start_vertex
92 }
93
94 /// Access the global form of the half-edge
95 pub fn global_form(&self) -> &Handle<GlobalEdge> {
96 &self.global_form
97 }
98}
99
100/// An undirected edge, defined in global (3D) coordinates
101///
102/// In contrast to [`HalfEdge`], `GlobalEdge` is undirected, meaning it has no
103/// defined direction. This means it can be used to determine whether two
104/// [`HalfEdge`]s map to the same `GlobalEdge`, regardless of their direction.
105///
106/// See [`HalfEdge`]'s documentation for more information on the relationship
107/// between [`HalfEdge`] and `GlobalEdge`.
108///
109/// # Equality
110///
111/// `GlobalEdge` contains no data and exists purely to be used within a
112/// `Handle`, where `Handle::id` can be used to compare different instances of
113/// `GlobalEdge`.
114///
115/// If `GlobalEdge` had `Eq`/`PartialEq` implementations, it containing no data
116/// would mean that all instances of `GlobalEdge` would be considered equal.
117/// This would be very error-prone.
118///
119/// If you need to reference a `GlobalEdge` from a struct that needs to derive
120/// `Eq`/`Ord`/..., you can use `HandleWrapper<GlobalEdge>` to do that. It will
121/// use `Handle::id` to provide those `Eq`/`Ord`/... implementations.
122#[derive(Clone, Debug, Default, Hash)]
123pub struct GlobalEdge {}
124
125impl GlobalEdge {
126 /// Create a new instance
127 ///
128 /// The order of `vertices` is irrelevant. Two `GlobalEdge`s with the same
129 /// `curve` and `vertices` will end up being equal, regardless of the order
130 /// of `vertices` here.
131 pub fn new() -> Self {
132 Self::default()
133 }
134}