Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CurveBoundaries #2044

Merged
merged 13 commits into from
Oct 6, 2023
91 changes: 16 additions & 75 deletions crates/fj-core/src/algorithms/approx/curve/approx.rs
Original file line number Diff line number Diff line change
@@ -1,105 +1,44 @@
use std::collections::VecDeque;

use fj_math::Point;

use crate::geometry::CurveBoundary;
use crate::geometry::{CurveBoundaries, CurveBoundary};

use super::{CurveApproxPoints, CurveApproxSegment};

/// Partial approximation of a curve
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct CurveApprox {
segments: Vec<(CurveBoundary<Point<1>>, CurveApproxPoints)>,
segments: CurveBoundaries<CurveApproxPoints>,
}

impl CurveApprox {
/// Get the single segment that covers the provided boundary, if available
pub fn into_single_segment(
mut self,
self,
boundary: CurveBoundary<Point<1>>,
) -> Option<CurveApproxSegment> {
match self.segments.pop() {
Some((b, points)) if self.segments.is_empty() && b == boundary => {
// We just removed a single segment, there are no others, and
// the removed segment's boundary matches the boundary provided
// to us.
//
// This is what the caller was asking for. Return it!
Some(CurveApproxSegment {
boundary: b,
points,
})
}
_ => {
// Either we don't have any segments in here, or we have more
// than one (which implies there are gaps between them), or we
// have a single one that doesn't cover the full boundary we
// were asked for.
//
// Either way, we don't have what the caller wants.
None
}
}
self.segments
.into_single_payload(boundary)
.map(|points| CurveApproxSegment { boundary, points })
}

/// Reverse the approximation
pub fn reverse(&mut self) {
self.segments.reverse();

for (boundary, segment) in &mut self.segments {
*boundary = boundary.reverse();
segment.reverse();
}
}

/// Reduce the approximation to the subset defined by the provided boundary
pub fn make_subset(&mut self, boundary: CurveBoundary<Point<1>>) {
for (b, segment) in &mut self.segments {
*b = b.subset(boundary);
segment.make_subset(boundary.normalize());
}

self.segments.retain(|(boundary, _)| !boundary.is_empty());
self.segments.make_subset(boundary);
}

/// Merge the provided segment into the approximation
pub fn merge(
&mut self,
new_segment: CurveApproxSegment,
) -> CurveApproxSegment {
let mut overlapping_segments = VecDeque::new();

let mut i = 0;
loop {
let Some((boundary, _)) = self.segments.get(i) else {
break;
};

if boundary.overlaps(&new_segment.boundary) {
let segment = self.segments.swap_remove(i);
overlapping_segments.push_back(segment);
continue;
}

i += 1;
}

let mut merged_boundary = new_segment.boundary;
let mut merged_segment = new_segment.points;

for (boundary, segment) in overlapping_segments {
assert!(
merged_boundary.overlaps(&boundary),
"Shouldn't merge segments that don't overlap."
);

merged_boundary = merged_boundary.union(boundary);
merged_segment.merge(&segment, boundary);
}

self.segments
.push((merged_boundary, merged_segment.clone()));
self.segments.sort();
let (merged_boundary, merged_segment) = self
.segments
.merge(new_segment.boundary, new_segment.points);

CurveApproxSegment {
boundary: merged_boundary,
Expand All @@ -111,10 +50,12 @@ impl CurveApprox {
impl<const N: usize> From<[CurveApproxSegment; N]> for CurveApprox {
fn from(segments: [CurveApproxSegment; N]) -> Self {
Self {
segments: segments
.into_iter()
.map(|segment| (segment.boundary, segment.points))
.collect(),
segments: CurveBoundaries {
inner: segments
.into_iter()
.map(|segment| (segment.boundary, segment.points))
.collect(),
},
}
}
}
Expand Down
22 changes: 19 additions & 3 deletions crates/fj-core/src/algorithms/approx/curve/points.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use fj_math::Point;

use crate::{algorithms::approx::ApproxPoint, geometry::CurveBoundary};
use crate::{
algorithms::approx::ApproxPoint,
geometry::{CurveBoundariesPayload, CurveBoundary},
};

/// Points of a curve approximation
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
Expand All @@ -16,9 +19,8 @@ impl CurveApproxPoints {
}

/// Reverse the orientation of the approximation
pub fn reverse(&mut self) -> &mut Self {
pub fn reverse(&mut self) {
self.inner.reverse();
self
}

/// Reduce the approximation to the subset defined by the provided boundary
Expand All @@ -45,3 +47,17 @@ impl CurveApproxPoints {
self.inner.sort();
}
}

impl CurveBoundariesPayload for CurveApproxPoints {
fn reverse(&mut self) {
self.reverse();
}

fn make_subset(&mut self, boundary: CurveBoundary<Point<1>>) {
self.make_subset(boundary)
}

fn merge(&mut self, other: &Self, other_boundary: CurveBoundary<Point<1>>) {
self.merge(other, other_boundary)
}
}
2 changes: 2 additions & 0 deletions crates/fj-core/src/geometry/boundary/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod multiple;
pub mod single;
134 changes: 134 additions & 0 deletions crates/fj-core/src/geometry/boundary/multiple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use std::collections::VecDeque;

use fj_math::Point;

use crate::geometry::CurveBoundary;

/// A collection of multiple [`CurveBoundary`] instances
///
/// Has a type parameter, `T`, which can be used to attach a payload to each
/// boundary.
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct CurveBoundaries<T: CurveBoundariesPayload = ()> {
/// The [`CurveBoundary`] instances
pub inner: Vec<(CurveBoundary<Point<1>>, T)>,
}

impl<T: CurveBoundariesPayload> CurveBoundaries<T> {
/// Transform `self` into the payload of the single boundary requested
///
/// If there are no boundaries or multiple boundaries in `self`, or if the
/// one available boundary is not equal to the one requested, return `None`.
pub fn into_single_payload(
mut self,
boundary: CurveBoundary<Point<1>>,
) -> Option<T> {
match self.inner.pop() {
Some((b, payload)) if self.inner.is_empty() && b == boundary => {
// We just removed a single element, there are no others, and
// the removed element's boundary matches the boundary provided
// to us.
//
// This is what the caller was asking for. Return it!
Some(payload)
}
_ => {
// Either we don't have any elements in here, or we have more
// than one (which implies there are gaps between them), or we
// have a single one that doesn't cover the full boundary we
// were asked for.
//
// Either way, we don't have what the caller wants.
None
}
}
}

/// Reverse each boundary, and their order
pub fn reverse(&mut self) {
self.inner.reverse();

for (boundary, payload) in &mut self.inner {
*boundary = boundary.reverse();
payload.reverse();
}
}

/// Reduce `self` to the subset defined by the provided boundary
pub fn make_subset(&mut self, boundary: CurveBoundary<Point<1>>) {
for (b, segment) in &mut self.inner {
*b = b.subset(boundary);
segment.make_subset(boundary);
}

self.inner.retain(|(boundary, _)| !boundary.is_empty());
}

/// Merge the provided boundary into `self`
///
/// Return the merged boundary and payload.
pub fn merge(
&mut self,
new_boundary: CurveBoundary<Point<1>>,
new_payload: T,
) -> (CurveBoundary<Point<1>>, T) {
let mut overlapping_payloads = VecDeque::new();

let mut i = 0;
loop {
let Some((boundary, _)) = self.inner.get(i) else {
break;
};

if boundary.overlaps(&new_boundary) {
let payload = self.inner.swap_remove(i);
overlapping_payloads.push_back(payload);
continue;
}

i += 1;
}

let mut merged_boundary = new_boundary;
let mut merged_payload = new_payload;

for (boundary, payload) in overlapping_payloads {
assert!(
merged_boundary.overlaps(&boundary),
"Shouldn't merge boundaries that don't overlap."
);

merged_boundary = merged_boundary.union(boundary);
merged_payload.merge(&payload, boundary);
}

self.inner.push((merged_boundary, merged_payload.clone()));
self.inner.sort();

(merged_boundary, merged_payload)
}
}

impl<T: CurveBoundariesPayload> Default for CurveBoundaries<T> {
fn default() -> Self {
Self { inner: Vec::new() }
}
}

/// A payload that can be used in [`CurveBoundaries`]
pub trait CurveBoundariesPayload: Clone + Ord {
/// Reverse the orientation of the payload
fn reverse(&mut self);

/// Reduce the payload to the subset defined by the provided boundary
fn make_subset(&mut self, boundary: CurveBoundary<Point<1>>);

/// Merge the provided payload
fn merge(&mut self, other: &Self, other_boundary: CurveBoundary<Point<1>>);
}

impl CurveBoundariesPayload for () {
fn reverse(&mut self) {}
fn make_subset(&mut self, _: CurveBoundary<Point<1>>) {}
fn merge(&mut self, _: &Self, _: CurveBoundary<Point<1>>) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl CurveBoundary<Point<1>> {

/// Indicate whether the boundary contains the given element
pub fn contains(&self, point: Point<1>) -> bool {
let [min, max] = self.inner;
let [min, max] = self.normalize().inner;
point > min && point < max
}

Expand Down
5 changes: 4 additions & 1 deletion crates/fj-core/src/geometry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ mod path;
mod surface;

pub use self::{
boundary::{CurveBoundary, CurveBoundaryElement},
boundary::{
multiple::{CurveBoundaries, CurveBoundariesPayload},
single::{CurveBoundary, CurveBoundaryElement},
},
path::{GlobalPath, SurfacePath},
surface::SurfaceGeometry,
};