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

Refactor curve approximation code #1012

Merged
merged 9 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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