Skip to content

Commit

Permalink
Merge pull request #2017 from hannobraun/approx
Browse files Browse the repository at this point in the history
Support partial curve approximation matches
  • Loading branch information
hannobraun authored Sep 11, 2023
2 parents 7b904ac + 1d09a56 commit 2fa28f9
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 12 deletions.
13 changes: 13 additions & 0 deletions crates/fj-core/src/algorithms/approx/curve/approx.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use fj_math::Point;

use crate::geometry::CurveBoundary;

use super::CurveApproxSegment;

/// Partial approximation of a curve
Expand All @@ -19,6 +23,15 @@ impl CurveApprox {
self
}

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

self.segments.retain(|segment| !segment.is_empty());
}

/// Merge the provided segment into the approximation
pub fn merge(
&mut self,
Expand Down
129 changes: 117 additions & 12 deletions crates/fj-core/src/algorithms/approx/curve/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,13 @@ impl CurveApproxCache {
) -> Option<CurveApprox> {
let curve = HandleWrapper::from(curve.clone());

// Approximations within the cache are all stored in normalized form. If
// the caller requests a non-normalized boundary, that means we need to
// adjust the result before we return it, so let's remember whether the
// normalization resulted in a reversal.
let was_already_normalized = boundary.is_normalized();
let normalized_boundary = boundary.normalize();

let mut approx = self.inner.get(&curve).cloned()?;
approx.make_subset(boundary);

approx
.segments
.retain(|segment| segment.boundary == normalized_boundary);

if !was_already_normalized {
// Approximations within the cache are stored in normalized form. If the
// caller requests a non-normalized boundary, that means we need to
// adjust the result before we return it.
if !boundary.is_normalized() {
approx.reverse();
}

Expand All @@ -53,6 +46,10 @@ impl CurveApproxCache {
) -> CurveApproxSegment {
let curve = HandleWrapper::from(curve);

// Overlapping approximations need to result in the same points,
// regardless of what direction those overlapping approximations happen
// to have. To make sure this is always the case, we normalize each new
// approximated segment before doing *anything* with it.
new_segment.normalize();

let (approx, segment) = match self.inner.remove(&curve) {
Expand Down Expand Up @@ -256,4 +253,112 @@ pub mod tests {
})
);
}

#[test]
fn partial_match_that_overlaps_start() {
let mut services = Services::new();

let mut cache = CurveApproxCache::default();
let curve = Curve::new().insert(&mut services);

cache.insert(
curve.clone(),
CurveApproxSegment {
boundary: CurveBoundary::from([[0.], [1.]]),
points: vec![
ApproxPoint::new([0.125], [0.125, 0.125, 0.125]),
ApproxPoint::new([0.375], [0.375, 0.375, 0.375]),
ApproxPoint::new([0.625], [0.625, 0.625, 0.625]),
ApproxPoint::new([0.875], [0.875, 0.875, 0.875]),
],
}
.clone(),
);

let cached = cache.get(&curve, &CurveBoundary::from([[-0.5], [0.5]]));
assert_eq!(
cached,
Some(CurveApprox {
segments: vec![CurveApproxSegment {
boundary: CurveBoundary::from([[0.], [0.5]]),
points: vec![
ApproxPoint::new([0.125], [0.125, 0.125, 0.125]),
ApproxPoint::new([0.375], [0.375, 0.375, 0.375]),
],
}]
})
);
}

#[test]
fn partial_match_that_overlaps_end() {
let mut services = Services::new();

let mut cache = CurveApproxCache::default();
let curve = Curve::new().insert(&mut services);

cache.insert(
curve.clone(),
CurveApproxSegment {
boundary: CurveBoundary::from([[0.], [1.]]),
points: vec![
ApproxPoint::new([0.125], [0.125, 0.125, 0.125]),
ApproxPoint::new([0.375], [0.375, 0.375, 0.375]),
ApproxPoint::new([0.625], [0.625, 0.625, 0.625]),
ApproxPoint::new([0.875], [0.875, 0.875, 0.875]),
],
}
.clone(),
);

let cached = cache.get(&curve, &CurveBoundary::from([[0.5], [1.5]]));
assert_eq!(
cached,
Some(CurveApprox {
segments: vec![CurveApproxSegment {
boundary: CurveBoundary::from([[0.5], [1.0]]),
points: vec![
ApproxPoint::new([0.625], [0.625, 0.625, 0.625]),
ApproxPoint::new([0.875], [0.875, 0.875, 0.875]),
],
}]
})
);
}

#[test]
fn partial_match_in_the_middle() {
let mut services = Services::new();

let mut cache = CurveApproxCache::default();
let curve = Curve::new().insert(&mut services);

cache.insert(
curve.clone(),
CurveApproxSegment {
boundary: CurveBoundary::from([[0.], [1.]]),
points: vec![
ApproxPoint::new([0.125], [0.125, 0.125, 0.125]),
ApproxPoint::new([0.375], [0.375, 0.375, 0.375]),
ApproxPoint::new([0.625], [0.625, 0.625, 0.625]),
ApproxPoint::new([0.875], [0.875, 0.875, 0.875]),
],
}
.clone(),
);

let cached = cache.get(&curve, &CurveBoundary::from([[0.25], [0.75]]));
assert_eq!(
cached,
Some(CurveApprox {
segments: vec![CurveApproxSegment {
boundary: CurveBoundary::from([[0.25], [0.75]]),
points: vec![
ApproxPoint::new([0.375], [0.375, 0.375, 0.375]),
ApproxPoint::new([0.625], [0.625, 0.625, 0.625]),
],
}]
})
);
}
}
43 changes: 43 additions & 0 deletions crates/fj-core/src/algorithms/approx/curve/segment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ pub struct CurveApproxSegment {
}

impl CurveApproxSegment {
/// Indicate whether the segment is empty
pub fn is_empty(&self) -> bool {
let is_empty = {
let [min, max] = self.boundary.inner;
min >= max
};

if is_empty {
assert!(
self.points.is_empty(),
"Empty approximation still has points"
);
}

is_empty
}

/// Indicate whether the segment is normalized
pub fn is_normalized(&self) -> bool {
self.boundary.is_normalized()
Expand All @@ -44,6 +61,32 @@ impl CurveApproxSegment {

self
}

/// Reduce the approximation to the subset defined by the provided boundary
pub fn make_subset(&mut self, boundary: CurveBoundary<Point<1>>) {
assert!(
self.boundary.is_normalized(),
"Expected normalized segment for making subset."
);
assert!(
boundary.is_normalized(),
"Expected subset to be defined by normalized boundary."
);

let [min, max] = boundary.inner;

self.boundary.inner = {
let [self_min, self_max] = self.boundary.inner;

let min = cmp::max(self_min, min);
let max = cmp::min(self_max, max);

[min, max]
};

self.points
.retain(|point| point.local_form > min && point.local_form < max);
}
}

impl Ord for CurveApproxSegment {
Expand Down

0 comments on commit 2fa28f9

Please sign in to comment.