From 4bf6c6331027a23d74e9152bc8311dd74aa8f3e8 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 23 Oct 2023 12:52:57 +0200 Subject: [PATCH] Simplify module structure --- crates/fj-core/src/geometry/boundary/mod.rs | 365 ++++++++++++++++- .../fj-core/src/geometry/boundary/single.rs | 366 ------------------ crates/fj-core/src/geometry/mod.rs | 2 +- 3 files changed, 365 insertions(+), 368 deletions(-) delete mode 100644 crates/fj-core/src/geometry/boundary/single.rs diff --git a/crates/fj-core/src/geometry/boundary/mod.rs b/crates/fj-core/src/geometry/boundary/mod.rs index 50c0729925..5108c10b7f 100644 --- a/crates/fj-core/src/geometry/boundary/mod.rs +++ b/crates/fj-core/src/geometry/boundary/mod.rs @@ -1 +1,364 @@ -pub mod single; +use std::{ + cmp::{self, Ordering}, + hash::{Hash, Hasher}, +}; + +use fj_math::Point; + +use crate::{objects::Vertex, storage::HandleWrapper}; + +/// A boundary on a curve +/// +/// This struct is generic, because different situations require different +/// representations of a boundary. In some cases, curve coordinates are enough, +/// in other cases, vertices are required, and sometimes you need both. +#[derive(Clone, Copy, Debug)] +pub struct CurveBoundary { + /// The raw representation of the boundary + pub inner: [T::Repr; 2], +} + +impl CurveBoundary { + /// Indicate whether the boundary is normalized + /// + /// If the boundary is normalized, its bounding elements are in a defined + /// order, and calling `normalize` will return an identical instance. + pub fn is_normalized(&self) -> bool { + let [a, b] = &self.inner; + a <= b + } + + /// Reverse the direction of the boundary + /// + /// Returns a new instance of this struct, which has its direction reversed. + #[must_use] + pub fn reverse(self) -> Self { + let [a, b] = self.inner; + Self { inner: [b, a] } + } + + /// Normalize the boundary + /// + /// Returns a new instance of this struct, which has the bounding elements + /// in a defined order. This can be used to compare boundaries while + /// disregarding their direction. + #[must_use] + pub fn normalize(self) -> Self { + if self.is_normalized() { + self + } else { + self.reverse() + } + } +} + +// Technically, these methods could be implemented for all +// `CurveBoundaryElement`s, but that would be misleading. While +// `HandleWrapper` implements `Ord`, which is useful for putting it (and +// by extension, `CurveBoundary`) into `BTreeMap`s, this `Ord` +// implementation doesn't actually define the geometrically meaningful ordering +// that the following methods rely on. +impl CurveBoundary> { + /// Indicate whether the boundary is empty + pub fn is_empty(&self) -> bool { + let [min, max] = &self.inner; + min >= max + } + + /// Indicate whether the boundary contains the given element + pub fn contains(&self, point: Point<1>) -> bool { + let [min, max] = self.normalize().inner; + point > min && point < max + } + + /// Indicate whether the boundary overlaps another + /// + /// Boundaries that touch (i.e. their closest boundary elements are equal) + /// count as overlapping. + pub fn overlaps(&self, other: &Self) -> bool { + let [a_low, a_high] = self.normalize().inner; + let [b_low, b_high] = other.normalize().inner; + + a_low <= b_high && a_high >= b_low + } + + /// Create the difference of this boundary and another + /// + /// The result will be normalized. + pub fn difference(self, other: Self) -> Option { + let [self_min, self_max] = self.normalize().inner; + let [other_min, other_max] = other.normalize().inner; + + match ( + self_min <= other_min, + self_min <= other_max, + self_max <= other_min, + self_max <= other_max, + ) { + (true, true, true, true) => { + Some(OneOrTwoBoundaries::One(CurveBoundary { + inner: [self_min, self_max], + })) + } + (true, true, false, true) => { + let min = self_min; + let max = other_min; + + if min == max { + return None; + } + + Some(OneOrTwoBoundaries::One(CurveBoundary { + inner: [min, max], + })) + } + (true, true, false, false) => Some(OneOrTwoBoundaries::Two([ + CurveBoundary { + inner: [self_min, other_min], + }, + CurveBoundary { + inner: [other_max, self_max], + }, + ])), + (false, true, false, true) => None, + (false, true, false, false) => { + Some(OneOrTwoBoundaries::One(CurveBoundary { + inner: [other_max, self_max], + })) + } + (false, false, false, false) => { + Some(OneOrTwoBoundaries::One(CurveBoundary { + inner: [self_min, self_max], + })) + } + case => { + unreachable!( + "{case:?} is impossible, due to normalization above" + ); + } + } + } + + /// Create the intersection of this boundary and another + /// + /// The result will be normalized. + #[must_use] + pub fn intersection(self, other: Self) -> Self { + let self_ = self.normalize(); + let other = other.normalize(); + + let [self_min, self_max] = self_.inner; + let [other_min, other_max] = other.inner; + + let min = cmp::max(self_min, other_min); + let max = cmp::min(self_max, other_max); + + Self { inner: [min, max] } + } + + /// Create the union of this boundary and another + /// + /// The result will be normalized. + /// + /// # Panics + /// + /// Panics, if the two boundaries don't overlap (touching counts as + /// overlapping). + pub fn union(self, other: Self) -> Self { + let self_ = self.normalize(); + let other = other.normalize(); + + assert!( + self.overlaps(&other), + "Can't merge boundaries that don't at least touch" + ); + + let [self_min, self_max] = self_.inner; + let [other_min, other_max] = other.inner; + + let min = cmp::min(self_min, other_min); + let max = cmp::max(self_max, other_max); + + Self { inner: [min, max] } + } +} + +impl From<[S; 2]> for CurveBoundary +where + S: Into, +{ + fn from(boundary: [S; 2]) -> Self { + let inner = boundary.map(Into::into); + Self { inner } + } +} + +impl Eq for CurveBoundary {} + +impl PartialEq for CurveBoundary { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl Hash for CurveBoundary { + fn hash(&self, state: &mut H) { + self.inner.hash(state); + } +} + +impl Ord for CurveBoundary { + fn cmp(&self, other: &Self) -> Ordering { + self.inner.cmp(&other.inner) + } +} + +impl PartialOrd for CurveBoundary { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Debug, Eq, PartialEq)] +pub enum OneOrTwoBoundaries { + One(CurveBoundary>), + Two([CurveBoundary>; 2]), +} + +/// An element of a curve boundary +/// +/// Used for the type parameter of [`CurveBoundary`]. +pub trait CurveBoundaryElement { + /// The representation the curve boundary element + /// + /// This is the actual data stored in [`CurveBoundary`]. + type Repr: Eq + Hash + Ord; +} + +impl CurveBoundaryElement for Point<1> { + type Repr = Self; +} + +impl CurveBoundaryElement for Vertex { + type Repr = HandleWrapper; +} + +#[cfg(test)] +mod tests { + use fj_math::Point; + + use crate::geometry::{boundary::OneOrTwoBoundaries, CurveBoundary}; + + #[test] + fn overlaps() { + assert!(overlap([0., 2.], [1., 3.])); // regular overlap + assert!(overlap([0., 1.], [1., 2.])); // just touching + assert!(overlap([2., 0.], [3., 1.])); // not normalized + assert!(overlap([1., 3.], [0., 2.])); // lower boundary comes second + + assert!(!overlap([0., 1.], [2., 3.])); // regular non-overlap + assert!(!overlap([2., 3.], [0., 1.])); // lower boundary comes second + + fn overlap(a: [f64; 2], b: [f64; 2]) -> bool { + let a = array_to_boundary(a); + let b = array_to_boundary(b); + + a.overlaps(&b) + } + } + + #[test] + fn difference() { + // `a \ b = x` + + // b covers a exactly + diff([1., 2.], [1., 2.], &[]); + diff([1., 2.], [2., 1.], &[]); + diff([2., 1.], [1., 2.], &[]); + diff([2., 1.], [2., 1.], &[]); + + // b covers a, overhang right + diff([1., 2.], [1., 3.], &[]); + diff([1., 2.], [3., 1.], &[]); + diff([2., 1.], [1., 3.], &[]); + diff([2., 1.], [3., 1.], &[]); + + // b covers a, overhang left + diff([1., 2.], [0., 2.], &[]); + diff([1., 2.], [2., 0.], &[]); + diff([2., 1.], [0., 2.], &[]); + diff([2., 1.], [2., 0.], &[]); + + // b covers a, overhang both sides + diff([1., 2.], [0., 3.], &[]); + diff([1., 2.], [3., 0.], &[]); + diff([2., 1.], [0., 3.], &[]); + diff([2., 1.], [3., 0.], &[]); + + // a to the left of b, touching + diff([0., 1.], [1., 2.], &[[0., 1.]]); + diff([0., 1.], [2., 1.], &[[0., 1.]]); + diff([1., 0.], [1., 2.], &[[0., 1.]]); + diff([1., 0.], [2., 1.], &[[0., 1.]]); + + // a to the left of b, not touching + diff([0., 1.], [2., 3.], &[[0., 1.]]); + diff([0., 1.], [3., 2.], &[[0., 1.]]); + diff([1., 0.], [2., 3.], &[[0., 1.]]); + diff([1., 0.], [3., 2.], &[[0., 1.]]); + + // a to the right of b, touching + diff([2., 3.], [1., 2.], &[[2., 3.]]); + diff([2., 3.], [2., 1.], &[[2., 3.]]); + diff([3., 2.], [1., 2.], &[[2., 3.]]); + diff([3., 2.], [2., 1.], &[[2., 3.]]); + + // a to the right of b, not touching + diff([2., 3.], [0., 1.], &[[2., 3.]]); + diff([2., 3.], [1., 0.], &[[2., 3.]]); + diff([3., 2.], [0., 1.], &[[2., 3.]]); + diff([3., 2.], [1., 0.], &[[2., 3.]]); + + // b intersects a on the right + diff([0., 2.], [1., 3.], &[[0., 1.]]); + diff([0., 2.], [3., 1.], &[[0., 1.]]); + diff([2., 0.], [1., 3.], &[[0., 1.]]); + diff([2., 0.], [3., 1.], &[[0., 1.]]); + + // b intersects a on the left + diff([1., 3.], [0., 2.], &[[2., 3.]]); + diff([1., 3.], [2., 0.], &[[2., 3.]]); + diff([3., 1.], [0., 2.], &[[2., 3.]]); + diff([3., 1.], [2., 0.], &[[2., 3.]]); + + // a covers b, overhang both sides + diff([0., 3.], [1., 2.], &[[0., 1.], [2., 3.]]); + diff([0., 3.], [2., 1.], &[[0., 1.], [2., 3.]]); + diff([3., 0.], [1., 2.], &[[0., 1.], [2., 3.]]); + diff([3., 0.], [2., 1.], &[[0., 1.], [2., 3.]]); + + fn diff(a: [f64; 2], b: [f64; 2], x: &[[f64; 2]]) { + print!("{a:?} \\ {b:?} = "); + + let a = array_to_boundary(a); + let b = array_to_boundary(b); + + let x = match x { + [] => None, + &[x] => Some(OneOrTwoBoundaries::One(array_to_boundary(x))), + &[x1, x2] => Some(OneOrTwoBoundaries::Two( + [x1, x2].map(array_to_boundary), + )), + _ => panic!("Invalid result"), + }; + + let diff = a.difference(b); + println!("{diff:?}"); + assert_eq!(diff, x); + } + } + + fn array_to_boundary(array: [f64; 2]) -> CurveBoundary> { + CurveBoundary::from(array.map(|element| [element])) + } +} diff --git a/crates/fj-core/src/geometry/boundary/single.rs b/crates/fj-core/src/geometry/boundary/single.rs deleted file mode 100644 index 5441c12653..0000000000 --- a/crates/fj-core/src/geometry/boundary/single.rs +++ /dev/null @@ -1,366 +0,0 @@ -use std::{ - cmp::{self, Ordering}, - hash::{Hash, Hasher}, -}; - -use fj_math::Point; - -use crate::{objects::Vertex, storage::HandleWrapper}; - -/// A boundary on a curve -/// -/// This struct is generic, because different situations require different -/// representations of a boundary. In some cases, curve coordinates are enough, -/// in other cases, vertices are required, and sometimes you need both. -#[derive(Clone, Copy, Debug)] -pub struct CurveBoundary { - /// The raw representation of the boundary - pub inner: [T::Repr; 2], -} - -impl CurveBoundary { - /// Indicate whether the boundary is normalized - /// - /// If the boundary is normalized, its bounding elements are in a defined - /// order, and calling `normalize` will return an identical instance. - pub fn is_normalized(&self) -> bool { - let [a, b] = &self.inner; - a <= b - } - - /// Reverse the direction of the boundary - /// - /// Returns a new instance of this struct, which has its direction reversed. - #[must_use] - pub fn reverse(self) -> Self { - let [a, b] = self.inner; - Self { inner: [b, a] } - } - - /// Normalize the boundary - /// - /// Returns a new instance of this struct, which has the bounding elements - /// in a defined order. This can be used to compare boundaries while - /// disregarding their direction. - #[must_use] - pub fn normalize(self) -> Self { - if self.is_normalized() { - self - } else { - self.reverse() - } - } -} - -// Technically, these methods could be implemented for all -// `CurveBoundaryElement`s, but that would be misleading. While -// `HandleWrapper` implements `Ord`, which is useful for putting it (and -// by extension, `CurveBoundary`) into `BTreeMap`s, this `Ord` -// implementation doesn't actually define the geometrically meaningful ordering -// that the following methods rely on. -impl CurveBoundary> { - /// Indicate whether the boundary is empty - pub fn is_empty(&self) -> bool { - let [min, max] = &self.inner; - min >= max - } - - /// Indicate whether the boundary contains the given element - pub fn contains(&self, point: Point<1>) -> bool { - let [min, max] = self.normalize().inner; - point > min && point < max - } - - /// Indicate whether the boundary overlaps another - /// - /// Boundaries that touch (i.e. their closest boundary elements are equal) - /// count as overlapping. - pub fn overlaps(&self, other: &Self) -> bool { - let [a_low, a_high] = self.normalize().inner; - let [b_low, b_high] = other.normalize().inner; - - a_low <= b_high && a_high >= b_low - } - - /// Create the difference of this boundary and another - /// - /// The result will be normalized. - pub fn difference(self, other: Self) -> Option { - let [self_min, self_max] = self.normalize().inner; - let [other_min, other_max] = other.normalize().inner; - - match ( - self_min <= other_min, - self_min <= other_max, - self_max <= other_min, - self_max <= other_max, - ) { - (true, true, true, true) => { - Some(OneOrTwoBoundaries::One(CurveBoundary { - inner: [self_min, self_max], - })) - } - (true, true, false, true) => { - let min = self_min; - let max = other_min; - - if min == max { - return None; - } - - Some(OneOrTwoBoundaries::One(CurveBoundary { - inner: [min, max], - })) - } - (true, true, false, false) => Some(OneOrTwoBoundaries::Two([ - CurveBoundary { - inner: [self_min, other_min], - }, - CurveBoundary { - inner: [other_max, self_max], - }, - ])), - (false, true, false, true) => None, - (false, true, false, false) => { - Some(OneOrTwoBoundaries::One(CurveBoundary { - inner: [other_max, self_max], - })) - } - (false, false, false, false) => { - Some(OneOrTwoBoundaries::One(CurveBoundary { - inner: [self_min, self_max], - })) - } - case => { - unreachable!( - "{case:?} is impossible, due to normalization above" - ); - } - } - } - - /// Create the intersection of this boundary and another - /// - /// The result will be normalized. - #[must_use] - pub fn intersection(self, other: Self) -> Self { - let self_ = self.normalize(); - let other = other.normalize(); - - let [self_min, self_max] = self_.inner; - let [other_min, other_max] = other.inner; - - let min = cmp::max(self_min, other_min); - let max = cmp::min(self_max, other_max); - - Self { inner: [min, max] } - } - - /// Create the union of this boundary and another - /// - /// The result will be normalized. - /// - /// # Panics - /// - /// Panics, if the two boundaries don't overlap (touching counts as - /// overlapping). - pub fn union(self, other: Self) -> Self { - let self_ = self.normalize(); - let other = other.normalize(); - - assert!( - self.overlaps(&other), - "Can't merge boundaries that don't at least touch" - ); - - let [self_min, self_max] = self_.inner; - let [other_min, other_max] = other.inner; - - let min = cmp::min(self_min, other_min); - let max = cmp::max(self_max, other_max); - - Self { inner: [min, max] } - } -} - -impl From<[S; 2]> for CurveBoundary -where - S: Into, -{ - fn from(boundary: [S; 2]) -> Self { - let inner = boundary.map(Into::into); - Self { inner } - } -} - -impl Eq for CurveBoundary {} - -impl PartialEq for CurveBoundary { - fn eq(&self, other: &Self) -> bool { - self.inner == other.inner - } -} - -impl Hash for CurveBoundary { - fn hash(&self, state: &mut H) { - self.inner.hash(state); - } -} - -impl Ord for CurveBoundary { - fn cmp(&self, other: &Self) -> Ordering { - self.inner.cmp(&other.inner) - } -} - -impl PartialOrd for CurveBoundary { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[derive(Debug, Eq, PartialEq)] -pub enum OneOrTwoBoundaries { - One(CurveBoundary>), - Two([CurveBoundary>; 2]), -} - -/// An element of a curve boundary -/// -/// Used for the type parameter of [`CurveBoundary`]. -pub trait CurveBoundaryElement { - /// The representation the curve boundary element - /// - /// This is the actual data stored in [`CurveBoundary`]. - type Repr: Eq + Hash + Ord; -} - -impl CurveBoundaryElement for Point<1> { - type Repr = Self; -} - -impl CurveBoundaryElement for Vertex { - type Repr = HandleWrapper; -} - -#[cfg(test)] -mod tests { - use fj_math::Point; - - use crate::geometry::{ - boundary::single::OneOrTwoBoundaries, CurveBoundary, - }; - - #[test] - fn overlaps() { - assert!(overlap([0., 2.], [1., 3.])); // regular overlap - assert!(overlap([0., 1.], [1., 2.])); // just touching - assert!(overlap([2., 0.], [3., 1.])); // not normalized - assert!(overlap([1., 3.], [0., 2.])); // lower boundary comes second - - assert!(!overlap([0., 1.], [2., 3.])); // regular non-overlap - assert!(!overlap([2., 3.], [0., 1.])); // lower boundary comes second - - fn overlap(a: [f64; 2], b: [f64; 2]) -> bool { - let a = array_to_boundary(a); - let b = array_to_boundary(b); - - a.overlaps(&b) - } - } - - #[test] - fn difference() { - // `a \ b = x` - - // b covers a exactly - diff([1., 2.], [1., 2.], &[]); - diff([1., 2.], [2., 1.], &[]); - diff([2., 1.], [1., 2.], &[]); - diff([2., 1.], [2., 1.], &[]); - - // b covers a, overhang right - diff([1., 2.], [1., 3.], &[]); - diff([1., 2.], [3., 1.], &[]); - diff([2., 1.], [1., 3.], &[]); - diff([2., 1.], [3., 1.], &[]); - - // b covers a, overhang left - diff([1., 2.], [0., 2.], &[]); - diff([1., 2.], [2., 0.], &[]); - diff([2., 1.], [0., 2.], &[]); - diff([2., 1.], [2., 0.], &[]); - - // b covers a, overhang both sides - diff([1., 2.], [0., 3.], &[]); - diff([1., 2.], [3., 0.], &[]); - diff([2., 1.], [0., 3.], &[]); - diff([2., 1.], [3., 0.], &[]); - - // a to the left of b, touching - diff([0., 1.], [1., 2.], &[[0., 1.]]); - diff([0., 1.], [2., 1.], &[[0., 1.]]); - diff([1., 0.], [1., 2.], &[[0., 1.]]); - diff([1., 0.], [2., 1.], &[[0., 1.]]); - - // a to the left of b, not touching - diff([0., 1.], [2., 3.], &[[0., 1.]]); - diff([0., 1.], [3., 2.], &[[0., 1.]]); - diff([1., 0.], [2., 3.], &[[0., 1.]]); - diff([1., 0.], [3., 2.], &[[0., 1.]]); - - // a to the right of b, touching - diff([2., 3.], [1., 2.], &[[2., 3.]]); - diff([2., 3.], [2., 1.], &[[2., 3.]]); - diff([3., 2.], [1., 2.], &[[2., 3.]]); - diff([3., 2.], [2., 1.], &[[2., 3.]]); - - // a to the right of b, not touching - diff([2., 3.], [0., 1.], &[[2., 3.]]); - diff([2., 3.], [1., 0.], &[[2., 3.]]); - diff([3., 2.], [0., 1.], &[[2., 3.]]); - diff([3., 2.], [1., 0.], &[[2., 3.]]); - - // b intersects a on the right - diff([0., 2.], [1., 3.], &[[0., 1.]]); - diff([0., 2.], [3., 1.], &[[0., 1.]]); - diff([2., 0.], [1., 3.], &[[0., 1.]]); - diff([2., 0.], [3., 1.], &[[0., 1.]]); - - // b intersects a on the left - diff([1., 3.], [0., 2.], &[[2., 3.]]); - diff([1., 3.], [2., 0.], &[[2., 3.]]); - diff([3., 1.], [0., 2.], &[[2., 3.]]); - diff([3., 1.], [2., 0.], &[[2., 3.]]); - - // a covers b, overhang both sides - diff([0., 3.], [1., 2.], &[[0., 1.], [2., 3.]]); - diff([0., 3.], [2., 1.], &[[0., 1.], [2., 3.]]); - diff([3., 0.], [1., 2.], &[[0., 1.], [2., 3.]]); - diff([3., 0.], [2., 1.], &[[0., 1.], [2., 3.]]); - - fn diff(a: [f64; 2], b: [f64; 2], x: &[[f64; 2]]) { - print!("{a:?} \\ {b:?} = "); - - let a = array_to_boundary(a); - let b = array_to_boundary(b); - - let x = match x { - [] => None, - &[x] => Some(OneOrTwoBoundaries::One(array_to_boundary(x))), - &[x1, x2] => Some(OneOrTwoBoundaries::Two( - [x1, x2].map(array_to_boundary), - )), - _ => panic!("Invalid result"), - }; - - let diff = a.difference(b); - println!("{diff:?}"); - assert_eq!(diff, x); - } - } - - fn array_to_boundary(array: [f64; 2]) -> CurveBoundary> { - CurveBoundary::from(array.map(|element| [element])) - } -} diff --git a/crates/fj-core/src/geometry/mod.rs b/crates/fj-core/src/geometry/mod.rs index d67ca02b41..0ed948703e 100644 --- a/crates/fj-core/src/geometry/mod.rs +++ b/crates/fj-core/src/geometry/mod.rs @@ -5,7 +5,7 @@ mod path; mod surface; pub use self::{ - boundary::single::{CurveBoundary, CurveBoundaryElement}, + boundary::{CurveBoundary, CurveBoundaryElement}, path::{GlobalPath, SurfacePath}, surface::SurfaceGeometry, };