Skip to content

Commit

Permalink
Merge pull request #1096 from hannobraun/approx
Browse files Browse the repository at this point in the history
Bypass `GlobalCurve` in curve approximation
  • Loading branch information
hannobraun authored Sep 16, 2022
2 parents b8d5f7f + a7e691c commit 984979e
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 28 deletions.
184 changes: 163 additions & 21 deletions crates/fj-kernel/src/algorithms/approx/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
use std::collections::BTreeMap;

use crate::objects::{Curve, GlobalCurve};
use crate::{
objects::{Curve, GlobalCurve},
path::{GlobalPath, SurfacePath},
};

use super::{path::RangeOnPath, Approx, ApproxPoint, Tolerance};

Expand All @@ -30,8 +33,7 @@ impl Approx for (&Curve, RangeOnPath) {
let global_curve_approx = match cache.get(cache_key) {
Some(approx) => approx,
None => {
let approx = (curve.global_form(), range)
.approx_with_cache(tolerance, &mut ());
let approx = approx_global_curve(curve, range, tolerance);
cache.insert(cache_key, approx)
}
};
Expand All @@ -40,34 +42,87 @@ impl Approx for (&Curve, RangeOnPath) {
global_curve_approx.points.into_iter().map(|point| {
let point_surface =
curve.path().point_from_path_coords(point.local_form);

ApproxPoint::new(point_surface, point.global_form)
.with_source((*curve, point.local_form))
}),
)
}
}

impl Approx for (&GlobalCurve, RangeOnPath) {
type Approximation = GlobalCurveApprox;
type Cache = ();
fn approx_global_curve(
curve: &Curve,
range: RangeOnPath,
tolerance: impl Into<Tolerance>,
) -> GlobalCurveApprox {
// There are different cases of varying complexity. Circles are the hard
// part here, as they need to be approximated, while lines don't need to be.
//
// This will probably all be unified eventually, as `SurfacePath` and
// `GlobalPath` grow APIs that are better suited to implementing this code
// in a more abstract way.
let points = match (curve.path(), curve.surface().u()) {
(SurfacePath::Circle(_), GlobalPath::Circle(_)) => {
todo!(
"Approximating a circle on a curved surface not supported yet."
)
}
(SurfacePath::Circle(_), GlobalPath::Line(_)) => {
(curve.path(), range)
.approx_with_cache(tolerance, &mut ())
.into_iter()
.map(|(point_curve, point_surface)| {
// We're throwing away `point_surface` here, which is a bit
// weird, as we're recomputing it later (outside of this
// function).
//
// It should be fine though:
//
// 1. We're throwing this version away, so there's no danger
// of inconsistency between this and the later version.
// 2. This version should have been computed using the same
// path and parameters and the later version will be, so
// they should be the same anyway.
// 3. Not all other cases handled in this function have a
// surface point available, so it needs to be computed
// later anyway, in the general case.

fn approx_with_cache(
self,
tolerance: impl Into<Tolerance>,
cache: &mut Self::Cache,
) -> Self::Approximation {
let (curve, range) = self;
let point_global = curve
.surface()
.point_from_surface_coords(point_surface);
(point_curve, point_global)
})
.collect()
}
(SurfacePath::Line(line), _) => {
let range_u =
RangeOnPath::from(range.boundary.map(|point_curve| {
[curve.path().point_from_path_coords(point_curve).u]
}));

let points = (curve.path(), range)
.approx_with_cache(tolerance, cache)
.into_iter()
.map(|(point_curve, point_global)| {
ApproxPoint::new(point_curve, point_global)
})
.collect();
let approx_u = (curve.surface().u(), range_u)
.approx_with_cache(tolerance, &mut ());

GlobalCurveApprox { points }
}
let mut points = Vec::new();
for (u, _) in approx_u {
let t = (u.t - line.origin().u) / line.direction().u;
let point_surface = curve.path().point_from_path_coords([t]);
let point_global =
curve.surface().point_from_surface_coords(point_surface);
points.push((u, point_global));
}

points
}
};

let points = points
.into_iter()
.map(|(point_curve, point_global)| {
ApproxPoint::new(point_curve, point_global)
})
.collect();
GlobalCurveApprox { points }
}

/// An approximation of a [`Curve`]
Expand Down Expand Up @@ -130,3 +185,90 @@ pub struct GlobalCurveApprox {
/// The points that approximate the curve
pub points: Vec<ApproxPoint<1>>,
}

#[cfg(test)]
mod tests {
use std::f64::consts::TAU;

use pretty_assertions::assert_eq;

use crate::{
algorithms::approx::{path::RangeOnPath, Approx, ApproxPoint},
objects::{Curve, Surface},
path::GlobalPath,
};

use super::CurveApprox;

#[test]
fn approx_line_on_flat_surface() {
let surface = Surface::new(GlobalPath::x_axis(), [0., 0., 1.]);
let curve =
Curve::build(surface).line_from_points([[1., 1.], [2., 1.]]);
let range = RangeOnPath::from([[0.], [1.]]);

let approx = (&curve, range).approx(1.);

assert_eq!(approx, CurveApprox::empty())
}

#[test]
fn approx_line_on_curved_surface_but_not_along_curve() {
let surface =
Surface::new(GlobalPath::circle_from_radius(1.), [0., 0., 1.]);
let curve =
Curve::build(surface).line_from_points([[1., 1.], [1., 2.]]);
let range = RangeOnPath::from([[0.], [1.]]);

let approx = (&curve, range).approx(1.);

assert_eq!(approx, CurveApprox::empty());
}

#[test]
fn approx_line_on_curved_surface_along_curve() {
let path = GlobalPath::circle_from_radius(1.);
let surface = Surface::new(path, [0., 0., 1.]);
let curve =
Curve::build(surface).line_from_points([[0., 1.], [1., 1.]]);

let range = RangeOnPath::from([[0.], [TAU]]);
let tolerance = 1.;

let approx = (&curve, range).approx(tolerance);

let expected_approx = (path, range)
.approx(tolerance)
.into_iter()
.map(|(point_local, _)| {
let point_surface =
curve.path().point_from_path_coords(point_local);
let point_global =
surface.point_from_surface_coords(point_surface);
ApproxPoint::new(point_surface, point_global)
})
.collect::<Vec<_>>();
assert_eq!(approx.points, expected_approx);
}

#[test]
fn approx_circle_on_flat_surface() {
let surface = Surface::new(GlobalPath::x_axis(), [0., 0., 1.]);
let curve = Curve::build(surface).circle_from_radius(1.);

let range = RangeOnPath::from([[0.], [TAU]]);
let tolerance = 1.;
let approx = (&curve, range).approx(tolerance);

let expected_approx = (curve.path(), range)
.approx(tolerance)
.into_iter()
.map(|(_, point_surface)| {
let point_global =
curve.surface().point_from_surface_coords(point_surface);
ApproxPoint::new(point_surface, point_global)
})
.collect::<Vec<_>>();
assert_eq!(approx.points, expected_approx);
}
}
22 changes: 21 additions & 1 deletion crates/fj-kernel/src/algorithms/approx/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,30 @@ use std::iter;

use fj_math::{Circle, Point, Scalar, Sign};

use crate::path::GlobalPath;
use crate::path::{GlobalPath, SurfacePath};

use super::{Approx, Tolerance};

impl Approx for (SurfacePath, RangeOnPath) {
type Approximation = Vec<(Point<1>, Point<2>)>;
type Cache = ();

fn approx_with_cache(
self,
tolerance: impl Into<Tolerance>,
(): &mut Self::Cache,
) -> Self::Approximation {
let (path, range) = self;

match path {
SurfacePath::Circle(circle) => {
approx_circle(&circle, range, tolerance.into())
}
SurfacePath::Line(_) => vec![],
}
}
}

impl Approx for (GlobalPath, RangeOnPath) {
type Approximation = Vec<(Point<1>, Point<3>)>;
type Cache = ();
Expand Down
9 changes: 3 additions & 6 deletions crates/fj-kernel/src/algorithms/triangulate/polygon.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use fj_math::{Point, PolyChain, Segment};
use fj_math::{Point, PolyChain, Segment, Triangle};

use crate::algorithms::intersect::{
ray_segment::RaySegmentIntersection, HorizontalRayToTheRight, Intersect,
Expand Down Expand Up @@ -42,11 +42,8 @@ impl Polygon {
self
}

pub fn contains_triangle(
&self,
triangle: [impl Into<Point<2>>; 3],
) -> bool {
let [a, b, c] = triangle.map(Into::into);
pub fn contains_triangle(&self, triangle: impl Into<Triangle<2>>) -> bool {
let [a, b, c] = triangle.into().points();

let mut might_be_hole = true;

Expand Down

0 comments on commit 984979e

Please sign in to comment.