fj_core/geometry/
boundary.rs

1use std::{
2    cmp::{self, Ordering},
3    hash::{Hash, Hasher},
4};
5
6use fj_math::Point;
7
8use crate::{objects::Vertex, storage::HandleWrapper};
9
10/// A boundary on a curve
11///
12/// This struct is generic, because different situations require different
13/// representations of a boundary. In some cases, curve coordinates are enough,
14/// in other cases, vertices are required, and sometimes you need both.
15#[derive(Clone, Copy, Debug)]
16pub struct CurveBoundary<T: CurveBoundaryElement> {
17    /// The raw representation of the boundary
18    pub inner: [T::Repr; 2],
19}
20
21impl<T: CurveBoundaryElement> CurveBoundary<T> {
22    /// Indicate whether the boundary is normalized
23    ///
24    /// If the boundary is normalized, its bounding elements are in a defined
25    /// order, and calling `normalize` will return an identical instance.
26    pub fn is_normalized(&self) -> bool {
27        let [a, b] = &self.inner;
28        a <= b
29    }
30
31    /// Reverse the direction of the boundary
32    ///
33    /// Returns a new instance of this struct, which has its direction reversed.
34    #[must_use]
35    pub fn reverse(self) -> Self {
36        let [a, b] = self.inner;
37        Self { inner: [b, a] }
38    }
39
40    /// Normalize the boundary
41    ///
42    /// Returns a new instance of this struct, which has the bounding elements
43    /// in a defined order. This can be used to compare boundaries while
44    /// disregarding their direction.
45    #[must_use]
46    pub fn normalize(self) -> Self {
47        if self.is_normalized() {
48            self
49        } else {
50            self.reverse()
51        }
52    }
53}
54
55// Technically, these methods could be implemented for all
56// `CurveBoundaryElement`s, but that would be misleading. While
57// `HandleWrapper<Vertex>` implements `Ord`, which is useful for putting it (and
58// by extension, `CurveBoundary<Vertex>`) into `BTreeMap`s, this `Ord`
59// implementation doesn't actually define the geometrically meaningful ordering
60// that the following methods rely on.
61impl CurveBoundary<Point<1>> {
62    /// Indicate whether the boundary is empty
63    pub fn is_empty(&self) -> bool {
64        let [min, max] = &self.inner;
65        min >= max
66    }
67
68    /// Indicate whether the boundary contains the given element
69    pub fn contains(&self, point: Point<1>) -> bool {
70        let [min, max] = self.normalize().inner;
71        point > min && point < max
72    }
73
74    /// Indicate whether the boundary overlaps another
75    ///
76    /// Boundaries that touch (i.e. their closest boundary elements are equal)
77    /// count as overlapping.
78    pub fn overlaps(&self, other: &Self) -> bool {
79        let [a_low, a_high] = self.normalize().inner;
80        let [b_low, b_high] = other.normalize().inner;
81
82        a_low <= b_high && a_high >= b_low
83    }
84
85    /// Create the difference of this boundary and another
86    ///
87    /// The result will be normalized.
88    pub fn difference(self, other: Self) -> Option<OneOrTwoBoundaries> {
89        let [self_min, self_max] = self.normalize().inner;
90        let [other_min, other_max] = other.normalize().inner;
91
92        match (
93            self_min <= other_min,
94            self_min <= other_max,
95            self_max <= other_min,
96            self_max <= other_max,
97        ) {
98            (true, true, true, true) => {
99                Some(OneOrTwoBoundaries::One(CurveBoundary {
100                    inner: [self_min, self_max],
101                }))
102            }
103            (true, true, false, true) => {
104                let min = self_min;
105                let max = other_min;
106
107                if min == max {
108                    return None;
109                }
110
111                Some(OneOrTwoBoundaries::One(CurveBoundary {
112                    inner: [min, max],
113                }))
114            }
115            (true, true, false, false) => Some(OneOrTwoBoundaries::Two([
116                CurveBoundary {
117                    inner: [self_min, other_min],
118                },
119                CurveBoundary {
120                    inner: [other_max, self_max],
121                },
122            ])),
123            (false, true, false, true) => None,
124            (false, true, false, false) => {
125                Some(OneOrTwoBoundaries::One(CurveBoundary {
126                    inner: [other_max, self_max],
127                }))
128            }
129            (false, false, false, false) => {
130                Some(OneOrTwoBoundaries::One(CurveBoundary {
131                    inner: [self_min, self_max],
132                }))
133            }
134            case => {
135                unreachable!(
136                    "{case:?} is impossible, due to normalization above"
137                );
138            }
139        }
140    }
141
142    /// Create the intersection of this boundary and another
143    ///
144    /// The result will be normalized.
145    #[must_use]
146    pub fn intersection(self, other: Self) -> Self {
147        let self_ = self.normalize();
148        let other = other.normalize();
149
150        let [self_min, self_max] = self_.inner;
151        let [other_min, other_max] = other.inner;
152
153        let min = cmp::max(self_min, other_min);
154        let max = cmp::min(self_max, other_max);
155
156        Self { inner: [min, max] }
157    }
158
159    /// Create the union of this boundary and another
160    ///
161    /// The result will be normalized.
162    ///
163    /// # Panics
164    ///
165    /// Panics, if the two boundaries don't overlap (touching counts as
166    /// overlapping).
167    pub fn union(self, other: Self) -> Self {
168        let self_ = self.normalize();
169        let other = other.normalize();
170
171        assert!(
172            self.overlaps(&other),
173            "Can't merge boundaries that don't at least touch"
174        );
175
176        let [self_min, self_max] = self_.inner;
177        let [other_min, other_max] = other.inner;
178
179        let min = cmp::min(self_min, other_min);
180        let max = cmp::max(self_max, other_max);
181
182        Self { inner: [min, max] }
183    }
184}
185
186impl<S, T: CurveBoundaryElement> From<[S; 2]> for CurveBoundary<T>
187where
188    S: Into<T::Repr>,
189{
190    fn from(boundary: [S; 2]) -> Self {
191        let inner = boundary.map(Into::into);
192        Self { inner }
193    }
194}
195
196impl<T: CurveBoundaryElement> Eq for CurveBoundary<T> {}
197
198impl<T: CurveBoundaryElement> PartialEq for CurveBoundary<T> {
199    fn eq(&self, other: &Self) -> bool {
200        self.inner == other.inner
201    }
202}
203
204impl<T: CurveBoundaryElement> Hash for CurveBoundary<T> {
205    fn hash<H: Hasher>(&self, state: &mut H) {
206        self.inner.hash(state);
207    }
208}
209
210impl<T: CurveBoundaryElement> Ord for CurveBoundary<T> {
211    fn cmp(&self, other: &Self) -> Ordering {
212        self.inner.cmp(&other.inner)
213    }
214}
215
216impl<T: CurveBoundaryElement> PartialOrd for CurveBoundary<T> {
217    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
218        Some(self.cmp(other))
219    }
220}
221
222#[derive(Debug, Eq, PartialEq)]
223pub enum OneOrTwoBoundaries {
224    One(CurveBoundary<Point<1>>),
225    Two([CurveBoundary<Point<1>>; 2]),
226}
227
228/// An element of a curve boundary
229///
230/// Used for the type parameter of [`CurveBoundary`].
231pub trait CurveBoundaryElement {
232    /// The representation the curve boundary element
233    ///
234    /// This is the actual data stored in [`CurveBoundary`].
235    type Repr: Eq + Hash + Ord;
236}
237
238impl CurveBoundaryElement for Point<1> {
239    type Repr = Self;
240}
241
242impl CurveBoundaryElement for Vertex {
243    type Repr = HandleWrapper<Vertex>;
244}
245
246#[cfg(test)]
247mod tests {
248    use fj_math::Point;
249
250    use crate::geometry::{boundary::OneOrTwoBoundaries, CurveBoundary};
251
252    #[test]
253    fn overlaps() {
254        assert!(overlap([0., 2.], [1., 3.])); // regular overlap
255        assert!(overlap([0., 1.], [1., 2.])); // just touching
256        assert!(overlap([2., 0.], [3., 1.])); // not normalized
257        assert!(overlap([1., 3.], [0., 2.])); // lower boundary comes second
258
259        assert!(!overlap([0., 1.], [2., 3.])); // regular non-overlap
260        assert!(!overlap([2., 3.], [0., 1.])); // lower boundary comes second
261
262        fn overlap(a: [f64; 2], b: [f64; 2]) -> bool {
263            let a = array_to_boundary(a);
264            let b = array_to_boundary(b);
265
266            a.overlaps(&b)
267        }
268    }
269
270    #[test]
271    fn difference() {
272        // `a \ b = x`
273
274        // b covers a exactly
275        diff([1., 2.], [1., 2.], &[]);
276        diff([1., 2.], [2., 1.], &[]);
277        diff([2., 1.], [1., 2.], &[]);
278        diff([2., 1.], [2., 1.], &[]);
279
280        // b covers a, overhang right
281        diff([1., 2.], [1., 3.], &[]);
282        diff([1., 2.], [3., 1.], &[]);
283        diff([2., 1.], [1., 3.], &[]);
284        diff([2., 1.], [3., 1.], &[]);
285
286        // b covers a, overhang left
287        diff([1., 2.], [0., 2.], &[]);
288        diff([1., 2.], [2., 0.], &[]);
289        diff([2., 1.], [0., 2.], &[]);
290        diff([2., 1.], [2., 0.], &[]);
291
292        // b covers a, overhang both sides
293        diff([1., 2.], [0., 3.], &[]);
294        diff([1., 2.], [3., 0.], &[]);
295        diff([2., 1.], [0., 3.], &[]);
296        diff([2., 1.], [3., 0.], &[]);
297
298        // a to the left of b, touching
299        diff([0., 1.], [1., 2.], &[[0., 1.]]);
300        diff([0., 1.], [2., 1.], &[[0., 1.]]);
301        diff([1., 0.], [1., 2.], &[[0., 1.]]);
302        diff([1., 0.], [2., 1.], &[[0., 1.]]);
303
304        // a to the left of b, not touching
305        diff([0., 1.], [2., 3.], &[[0., 1.]]);
306        diff([0., 1.], [3., 2.], &[[0., 1.]]);
307        diff([1., 0.], [2., 3.], &[[0., 1.]]);
308        diff([1., 0.], [3., 2.], &[[0., 1.]]);
309
310        // a to the right of b, touching
311        diff([2., 3.], [1., 2.], &[[2., 3.]]);
312        diff([2., 3.], [2., 1.], &[[2., 3.]]);
313        diff([3., 2.], [1., 2.], &[[2., 3.]]);
314        diff([3., 2.], [2., 1.], &[[2., 3.]]);
315
316        // a to the right of b, not touching
317        diff([2., 3.], [0., 1.], &[[2., 3.]]);
318        diff([2., 3.], [1., 0.], &[[2., 3.]]);
319        diff([3., 2.], [0., 1.], &[[2., 3.]]);
320        diff([3., 2.], [1., 0.], &[[2., 3.]]);
321
322        // b intersects a on the right
323        diff([0., 2.], [1., 3.], &[[0., 1.]]);
324        diff([0., 2.], [3., 1.], &[[0., 1.]]);
325        diff([2., 0.], [1., 3.], &[[0., 1.]]);
326        diff([2., 0.], [3., 1.], &[[0., 1.]]);
327
328        // b intersects a on the left
329        diff([1., 3.], [0., 2.], &[[2., 3.]]);
330        diff([1., 3.], [2., 0.], &[[2., 3.]]);
331        diff([3., 1.], [0., 2.], &[[2., 3.]]);
332        diff([3., 1.], [2., 0.], &[[2., 3.]]);
333
334        // a covers b, overhang both sides
335        diff([0., 3.], [1., 2.], &[[0., 1.], [2., 3.]]);
336        diff([0., 3.], [2., 1.], &[[0., 1.], [2., 3.]]);
337        diff([3., 0.], [1., 2.], &[[0., 1.], [2., 3.]]);
338        diff([3., 0.], [2., 1.], &[[0., 1.], [2., 3.]]);
339
340        fn diff(a: [f64; 2], b: [f64; 2], x: &[[f64; 2]]) {
341            print!("{a:?} \\ {b:?} = ");
342
343            let a = array_to_boundary(a);
344            let b = array_to_boundary(b);
345
346            let x = match x {
347                [] => None,
348                &[x] => Some(OneOrTwoBoundaries::One(array_to_boundary(x))),
349                &[x1, x2] => Some(OneOrTwoBoundaries::Two(
350                    [x1, x2].map(array_to_boundary),
351                )),
352                _ => panic!("Invalid result"),
353            };
354
355            let diff = a.difference(b);
356            println!("{diff:?}");
357            assert_eq!(diff, x);
358        }
359    }
360
361    fn array_to_boundary(array: [f64; 2]) -> CurveBoundary<Point<1>> {
362        CurveBoundary::from(array.map(|element| [element]))
363    }
364}