Skip to content

Commit

Permalink
Merge pull request #1941 from hannobraun/boundary
Browse files Browse the repository at this point in the history
Use dedicated struct to represent boundaries on curves
  • Loading branch information
hannobraun authored Jul 17, 2023
2 parents 234d8d2 + 8607aa0 commit c92997a
Show file tree
Hide file tree
Showing 16 changed files with 90 additions and 88 deletions.
49 changes: 24 additions & 25 deletions crates/fj-core/src/algorithms/approx/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ use std::collections::BTreeMap;
use fj_math::Point;

use crate::{
geometry::{GlobalPath, SurfacePath},
geometry::{BoundaryOnCurve, GlobalPath, SurfacePath},
objects::{GlobalEdge, HalfEdge, Surface, Vertex},
storage::{Handle, ObjectId},
};

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

impl Approx for (&HalfEdge, &Surface) {
type Approximation = HalfEdgeApprox;
Expand All @@ -28,9 +28,6 @@ impl Approx for (&HalfEdge, &Surface) {
) -> Self::Approximation {
let (half_edge, surface) = self;

let boundary = half_edge.boundary();
let range = RangeOnPath { boundary };

let position_surface = half_edge.start_position();
let position_global = match cache.get_position(half_edge.start_vertex())
{
Expand Down Expand Up @@ -87,20 +84,22 @@ impl Approx for (&HalfEdge, &Surface) {
//
// Only item 2. is something we can do right here. Item 1. requires
// a change to the object graph.
let cached_approx =
cache.get_edge(half_edge.global_form().clone(), range);
let cached_approx = cache.get_edge(
half_edge.global_form().clone(),
half_edge.boundary(),
);
let approx = match cached_approx {
Some(approx) => approx,
None => {
let approx = approx_edge(
&half_edge.path(),
surface,
range,
half_edge.boundary(),
tolerance,
);
cache.insert_edge(
half_edge.global_form().clone(),
range,
half_edge.boundary(),
approx,
)
}
Expand Down Expand Up @@ -148,7 +147,7 @@ impl HalfEdgeApprox {
fn approx_edge(
path: &SurfacePath,
surface: &Surface,
range: RangeOnPath,
boundary: BoundaryOnCurve,
tolerance: impl Into<Tolerance>,
) -> GlobalEdgeApprox {
// There are different cases of varying complexity. Circles are the hard
Expand All @@ -164,7 +163,7 @@ fn approx_edge(
)
}
(SurfacePath::Circle(_), GlobalPath::Line(_)) => {
(path, range)
(path, boundary)
.approx_with_cache(tolerance, &mut ())
.into_iter()
.map(|(point_curve, point_surface)| {
Expand Down Expand Up @@ -192,7 +191,7 @@ fn approx_edge(
}
(SurfacePath::Line(line), _) => {
let range_u =
RangeOnPath::from(range.boundary.map(|point_curve| {
BoundaryOnCurve::from(boundary.inner.map(|point_curve| {
[path.point_from_path_coords(point_curve).u]
}));

Expand Down Expand Up @@ -224,7 +223,7 @@ fn approx_edge(
/// A cache for results of an approximation
#[derive(Default)]
pub struct EdgeCache {
edge_approx: BTreeMap<(ObjectId, RangeOnPath), GlobalEdgeApprox>,
edge_approx: BTreeMap<(ObjectId, BoundaryOnCurve), GlobalEdgeApprox>,
vertex_approx: BTreeMap<ObjectId, Point<3>>,
}

Expand All @@ -238,15 +237,15 @@ impl EdgeCache {
pub fn get_edge(
&self,
handle: Handle<GlobalEdge>,
range: RangeOnPath,
boundary: BoundaryOnCurve,
) -> Option<GlobalEdgeApprox> {
if let Some(approx) = self.edge_approx.get(&(handle.id(), range)) {
if let Some(approx) = self.edge_approx.get(&(handle.id(), boundary)) {
return Some(approx.clone());
}
if let Some(approx) =
self.edge_approx.get(&(handle.id(), range.reverse()))
self.edge_approx.get(&(handle.id(), boundary.reverse()))
{
// If we have a cache entry for the reverse range, we need to use
// If we have a cache entry for the reverse boundary, we need to use
// that too!
return Some(approx.clone().reverse());
}
Expand All @@ -258,11 +257,11 @@ impl EdgeCache {
pub fn insert_edge(
&mut self,
handle: Handle<GlobalEdge>,
range: RangeOnPath,
boundary: BoundaryOnCurve,
approx: GlobalEdgeApprox,
) -> GlobalEdgeApprox {
self.edge_approx
.insert((handle.id(), range), approx.clone())
.insert((handle.id(), boundary), approx.clone())
.unwrap_or(approx)
}

Expand Down Expand Up @@ -303,8 +302,8 @@ mod tests {
use pretty_assertions::assert_eq;

use crate::{
algorithms::approx::{path::RangeOnPath, Approx, ApproxPoint},
geometry::{GlobalPath, SurfaceGeometry},
algorithms::approx::{Approx, ApproxPoint},
geometry::{BoundaryOnCurve, GlobalPath, SurfaceGeometry},
objects::{HalfEdge, Surface},
operations::BuildHalfEdge,
services::Services,
Expand Down Expand Up @@ -346,22 +345,22 @@ mod tests {
let mut services = Services::new();

let path = GlobalPath::circle_from_radius(1.);
let range = RangeOnPath::from([[0.], [TAU]]);
let boundary = BoundaryOnCurve::from([[0.], [TAU]]);

let surface = Surface::new(SurfaceGeometry {
u: path,
v: [0., 0., 1.].into(),
});
let half_edge = HalfEdge::line_segment(
[[0., 1.], [TAU, 1.]],
Some(range.boundary),
Some(boundary.inner),
&mut services,
);

let tolerance = 1.;
let approx = (&half_edge, &surface).approx(tolerance);

let expected_approx = (path, range)
let expected_approx = (path, boundary)
.approx(tolerance)
.into_iter()
.map(|(point_local, _)| {
Expand All @@ -386,7 +385,7 @@ mod tests {
let approx = (&half_edge, surface.deref()).approx(tolerance);

let expected_approx =
(&half_edge.path(), RangeOnPath::from([[0.], [TAU]]))
(&half_edge.path(), BoundaryOnCurve::from([[0.], [TAU]]))
.approx(tolerance)
.into_iter()
.map(|(_, point_surface)| {
Expand Down
49 changes: 12 additions & 37 deletions crates/fj-core/src/algorithms/approx/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ use std::iter;

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

use crate::geometry::{GlobalPath, SurfacePath};
use crate::geometry::{BoundaryOnCurve, GlobalPath, SurfacePath};

use super::{Approx, Tolerance};

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

Expand All @@ -56,7 +56,7 @@ impl Approx for (&SurfacePath, RangeOnPath) {
}
}

impl Approx for (GlobalPath, RangeOnPath) {
impl Approx for (GlobalPath, BoundaryOnCurve) {
type Approximation = Vec<(Point<1>, Point<3>)>;
type Cache = ();

Expand All @@ -76,46 +76,21 @@ impl Approx for (GlobalPath, RangeOnPath) {
}
}

/// The range on which a path should be approximated
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct RangeOnPath {
/// The boundary of the range
pub boundary: [Point<1>; 2],
}

impl RangeOnPath {
/// Reverse the direction of the range
pub fn reverse(self) -> Self {
let [a, b] = self.boundary;
Self { boundary: [b, a] }
}
}

impl<T> From<[T; 2]> for RangeOnPath
where
T: Into<Point<1>>,
{
fn from(boundary: [T; 2]) -> Self {
let boundary = boundary.map(Into::into);
Self { boundary }
}
}

/// Approximate a circle
///
/// `tolerance` specifies how much the approximation is allowed to deviate
/// from the circle.
fn approx_circle<const D: usize>(
circle: &Circle<D>,
range: impl Into<RangeOnPath>,
boundary: impl Into<BoundaryOnCurve>,
tolerance: Tolerance,
) -> Vec<(Point<1>, Point<D>)> {
let range = range.into();
let boundary = boundary.into();

let params = PathApproxParams::for_circle(circle, tolerance);
let mut points = Vec::new();

for point_curve in params.points(range) {
for point_curve in params.points(boundary) {
let point_global = circle.point_from_circle_coords(point_curve);
points.push((point_curve, point_global));
}
Expand Down Expand Up @@ -152,11 +127,11 @@ impl PathApproxParams {

pub fn points(
&self,
range: impl Into<RangeOnPath>,
boundary: impl Into<BoundaryOnCurve>,
) -> impl Iterator<Item = Point<1>> + '_ {
let range = range.into();
let boundary = boundary.into();

let [a, b] = range.boundary.map(|point| point.t / self.increment());
let [a, b] = boundary.inner.map(|point| point.t / self.increment());
let direction = (b - a).sign();
let [min, max] = if a < b { [a, b] } else { [b, a] };

Expand Down Expand Up @@ -195,7 +170,7 @@ mod tests {

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

use crate::algorithms::approx::{path::RangeOnPath, Tolerance};
use crate::algorithms::approx::{path::BoundaryOnCurve, Tolerance};

use super::PathApproxParams;

Expand Down Expand Up @@ -245,7 +220,7 @@ mod tests {
test_path([[TAU - 2.], [0.]], [2., 1.]);

fn test_path(
range: impl Into<RangeOnPath>,
boundary: impl Into<BoundaryOnCurve>,
expected_coords: impl IntoIterator<Item = impl Into<Scalar>>,
) {
// Choose radius and tolerance such, that we need 4 vertices to
Expand All @@ -257,7 +232,7 @@ mod tests {
let circle = Circle::from_center_and_radius([0., 0.], radius);
let params = PathApproxParams::for_circle(&circle, tolerance);

let points = params.points(range).collect::<Vec<_>>();
let points = params.points(boundary).collect::<Vec<_>>();

let expected_points = expected_coords
.into_iter()
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-core/src/algorithms/bounding_volume/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl super::BoundingVolume<2> for HalfEdge {
})
}
SurfacePath::Line(_) => {
let points = self.boundary().map(|point_curve| {
let points = self.boundary().inner.map(|point_curve| {
self.path().point_from_path_coords(point_curve)
});

Expand Down
1 change: 1 addition & 0 deletions crates/fj-core/src/algorithms/intersect/curve_edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl CurveEdgeIntersection {

let edge_vertices = half_edge
.boundary()
.inner
.map(|point| edge_path_as_line.point_from_line_coords(point));

Segment::from_points(edge_vertices)
Expand Down
1 change: 1 addition & 0 deletions crates/fj-core/src/algorithms/intersect/ray_edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ impl Intersect for (&HorizontalRayToTheRight<2>, &Handle<HalfEdge>) {

let points = edge
.boundary()
.inner
.map(|point| line.point_from_line_coords(point));
let segment = Segment::from_points(points);

Expand Down
4 changes: 2 additions & 2 deletions crates/fj-core/src/algorithms/sweep/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl Sweep for (&HalfEdge, &Handle<Vertex>, &Surface, Option<Color>) {

// Let's figure out the surface coordinates of the edge vertices.
let surface_points = {
let [a, b] = edge.boundary();
let [a, b] = edge.boundary().inner;

[
[a.t, Scalar::ZERO],
Expand All @@ -65,7 +65,7 @@ impl Sweep for (&HalfEdge, &Handle<Vertex>, &Surface, Option<Color>) {

// Now, the boundaries of each edge.
let boundaries = {
let [a, b] = edge.boundary();
let [a, b] = edge.boundary().inner;
let [c, d] = [0., 1.].map(|coord| Point::from([coord]));

[[a, b], [c, d], [b, a], [d, c]]
Expand Down
26 changes: 26 additions & 0 deletions crates/fj-core/src/geometry/boundary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use fj_math::Point;

/// A boundary on a curve
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct BoundaryOnCurve {
/// The raw representation of the boundary
pub inner: [Point<1>; 2],
}

impl BoundaryOnCurve {
/// Reverse the direction of the boundary
pub fn reverse(self) -> Self {
let [a, b] = self.inner;
Self { inner: [b, a] }
}
}

impl<T> From<[T; 2]> for BoundaryOnCurve
where
T: Into<Point<1>>,
{
fn from(boundary: [T; 2]) -> Self {
let inner = boundary.map(Into::into);
Self { inner }
}
}
2 changes: 2 additions & 0 deletions crates/fj-core/src/geometry/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Types that are tied to objects, but aren't objects themselves
mod boundary;
mod path;
mod surface;

pub use self::{
boundary::BoundaryOnCurve,
path::{GlobalPath, SurfacePath},
surface::SurfaceGeometry,
};
2 changes: 1 addition & 1 deletion crates/fj-core/src/objects/kinds/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl Cycle {
.next()
.expect("Invalid cycle: expected at least one half-edge");

let [a, b] = first.boundary();
let [a, b] = first.boundary().inner;
let edge_direction_positive = a < b;

let circle = match first.path() {
Expand Down
Loading

0 comments on commit c92997a

Please sign in to comment.