Skip to content

Commit

Permalink
Merge pull request #1012 from hannobraun/approx3
Browse files Browse the repository at this point in the history
Refactor curve approximation code
  • Loading branch information
hannobraun authored Aug 29, 2022
2 parents d9292f6 + 0dfa683 commit b1d21ff
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 41 deletions.
76 changes: 49 additions & 27 deletions crates/fj-kernel/src/algorithms/approx/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,28 @@ use super::{Approx, Tolerance};

impl Approx for Curve {
type Approximation = Vec<(Point<1>, Point<3>)>;

fn approx(&self, tolerance: Tolerance) -> Self::Approximation {
self.global().approx(tolerance)
type Params = RangeOnCurve;

fn approx(
&self,
tolerance: Tolerance,
range: Self::Params,
) -> Self::Approximation {
self.global().approx(tolerance, range)
}
}

impl Approx for GlobalCurve {
type Approximation = Vec<(Point<1>, Point<3>)>;
type Params = RangeOnCurve;

/// Approximate the global curve
///
/// # Implementation Note
///
/// This only works as-is, because only circles need to be approximated
/// right now and because only edges that are full circles are supported, as
/// opposed to edges that only inhabit part of the circle.
///
/// To support that, we will need additional information here, to define
/// between which points the curve needs to be approximated.
fn approx(&self, tolerance: Tolerance) -> Self::Approximation {
fn approx(
&self,
tolerance: Tolerance,
range: Self::Params,
) -> Self::Approximation {
match self.kind() {
CurveKind::Circle(curve) => {
approx_circle(curve, [[Scalar::ZERO], [Scalar::TAU]], tolerance)
}
CurveKind::Circle(curve) => approx_circle(curve, range, tolerance),
CurveKind::Line(_) => Vec::new(),
}
}
Expand All @@ -41,29 +39,27 @@ impl Approx for GlobalCurve {
///
/// `tolerance` specifies how much the approximation is allowed to deviate
/// from the circle.
pub fn approx_circle(
fn approx_circle(
circle: &Circle<3>,
between: [impl Into<Point<1>>; 2],
range: impl Into<RangeOnCurve>,
tolerance: Tolerance,
) -> Vec<(Point<1>, Point<3>)> {
let mut points = Vec::new();

let radius = circle.a().magnitude();

let [start, end] = between.map(Into::into);
let range = (end - start).t;
let range = range.into();

// To approximate the circle, we use a regular polygon for which
// the circle is the circumscribed circle. The `tolerance`
// parameter is the maximum allowed distance between the polygon
// and the circle. This is the same as the difference between
// the circumscribed circle and the incircle.

let n = number_of_vertices_for_circle(tolerance, radius, range.abs());
let n = number_of_vertices_for_circle(tolerance, radius, range.length());

let mut points = Vec::new();

for i in 0..n {
let angle =
start.t + (Scalar::TAU / n as f64 * i as f64) * range.sign();
let angle = range.start().t
+ (Scalar::TAU / n as f64 * i as f64) * range.direction();

let point_curve = Point::from([angle]);
let point_global = circle.point_from_circle_coords(point_curve);
Expand All @@ -86,6 +82,32 @@ fn number_of_vertices_for_circle(
max(n, 3)
}

pub struct RangeOnCurve {
pub boundary: [Point<1>; 2],
}

impl RangeOnCurve {
fn start(&self) -> Point<1> {
self.boundary[0]
}

fn end(&self) -> Point<1> {
self.boundary[1]
}

fn signed_length(&self) -> Scalar {
(self.end() - self.start()).t
}

fn length(&self) -> Scalar {
self.signed_length().abs()
}

fn direction(&self) -> Scalar {
self.signed_length().sign()
}
}

#[cfg(test)]
mod tests {
use fj_math::Scalar;
Expand Down
9 changes: 7 additions & 2 deletions crates/fj-kernel/src/algorithms/approx/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ use super::{Approx, Tolerance};

impl Approx for Cycle {
type Approximation = CycleApprox;
type Params = ();

fn approx(&self, tolerance: Tolerance) -> Self::Approximation {
fn approx(
&self,
tolerance: Tolerance,
(): Self::Params,
) -> Self::Approximation {
let mut points = Vec::new();

for edge in self.edges() {
let edge_points = edge.approx(tolerance);
let edge_points = edge.approx(tolerance, ());

points.extend(edge_points.into_iter().map(|point| {
let (point_curve, point_global) = point;
Expand Down
21 changes: 16 additions & 5 deletions crates/fj-kernel/src/algorithms/approx/edge.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
use fj_math::Point;
use fj_math::{Point, Scalar};

use crate::objects::{Edge, Vertex, VerticesOfEdge};

use super::Approx;
use super::{curve::RangeOnCurve, Approx};

impl Approx for Edge {
type Approximation = Vec<(Point<1>, Point<3>)>;

fn approx(&self, tolerance: super::Tolerance) -> Self::Approximation {
let mut points = self.curve().approx(tolerance);
type Params = ();

fn approx(
&self,
tolerance: super::Tolerance,
(): Self::Params,
) -> Self::Approximation {
let mut points = self.curve().approx(
tolerance,
// The range is only used for circles right now.
RangeOnCurve {
boundary: [[Scalar::ZERO].into(), [Scalar::TAU].into()],
},
);
approx_edge(*self.vertices(), &mut points);

points
Expand Down
13 changes: 9 additions & 4 deletions crates/fj-kernel/src/algorithms/approx/face.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ use super::{Approx, CycleApprox, Tolerance};

impl Approx for Face {
type Approximation = FaceApprox;
type Params = ();

fn approx(&self, tolerance: Tolerance) -> Self::Approximation {
fn approx(
&self,
tolerance: Tolerance,
(): Self::Params,
) -> Self::Approximation {
// Curved faces whose curvature is not fully defined by their edges
// are not supported yet. For that reason, we can fully ignore `face`'s
// `surface` field and just pass the edges to `Self::for_edges`.
Expand All @@ -28,13 +33,13 @@ impl Approx for Face {
let mut interiors = HashSet::new();

for cycle in self.exteriors() {
let cycle = cycle.approx(tolerance);
let cycle = cycle.approx(tolerance, ());

points.extend(cycle.points.iter().copied());
exteriors.push(cycle);
}
for cycle in self.interiors() {
let cycle = cycle.approx(tolerance);
let cycle = cycle.approx(tolerance, ());

points.extend(cycle.points.iter().copied());
interiors.insert(cycle);
Expand Down Expand Up @@ -118,7 +123,7 @@ mod tests {
let g = (g, g.to_xyz());
let h = (h, h.to_xyz());

let approx = face.approx(tolerance);
let approx = face.approx(tolerance, ());
let expected = FaceApprox {
points: set![a, b, c, d, e, f, g, h],
exterior: CycleApprox {
Expand Down
9 changes: 8 additions & 1 deletion crates/fj-kernel/src/algorithms/approx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,16 @@ pub trait Approx {
/// The approximation of the object
type Approximation;

/// Additional parameters required for the approximation
type Params;

/// Approximate the object
///
/// `tolerance` defines how far the approximation is allowed to deviate from
/// the actual object.
fn approx(&self, tolerance: Tolerance) -> Self::Approximation;
fn approx(
&self,
tolerance: Tolerance,
params: Self::Params,
) -> Self::Approximation;
}
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/algorithms/sweep/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ fn create_continuous_side_face(
let placeholder = Surface::xy_plane();

let cycle = Cycle::new(placeholder).with_edges([edge]);
let approx = cycle.approx(tolerance);
let approx = cycle.approx(tolerance, ());

let mut quads = Vec::new();
for segment in approx.segments() {
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/algorithms/triangulate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub fn triangulate(
}

let surface = face.surface();
let approx = face.approx(tolerance);
let approx = face.approx(tolerance, ());

let points: Vec<_> = approx
.points
Expand Down

0 comments on commit b1d21ff

Please sign in to comment.