Skip to content

Commit

Permalink
Merge pull request #483 from hannobraun/arc
Browse files Browse the repository at this point in the history
Add support for arcs
  • Loading branch information
hannobraun authored Apr 14, 2022
2 parents fdae56d + a9edbf9 commit 31ba592
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
use std::f64::consts::PI;
use std::cmp::max;

use fj_math::{Point, Scalar, Transform, Vector};

use crate::algorithms::Tolerance;

/// A circle
/// An arc
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Circle {
/// The center point of the circle
pub struct Arc {
/// The center point of the circle the arc is on
pub center: Point<3>,

/// The radius of the circle
/// The radius of the circle the arc is on
///
/// The radius is represented by a vector that points from the center to the
/// circumference. The point on the circumference that it points to defines
/// the origin of the circle's 1-dimensional curve coordinate system.
/// the origin of the arc's 1-dimensional curve coordinate system.
pub radius: Vector<2>,

/// The length of the arc in radians
///
/// If the length is positive, the arc is oriented counter-clockwise. If it
/// is negative, the arc is oriented clockwise.
pub length: Scalar,
}

impl Circle {
impl Arc {
/// Access the origin of the curve's coordinate system
pub fn origin(&self) -> Point<3> {
self.center
Expand All @@ -34,6 +40,7 @@ impl Circle {
Self {
center: transform.transform_point(&self.center),
radius,
length: self.length,
}
}

Expand Down Expand Up @@ -78,10 +85,10 @@ impl Circle {
Vector::from([x, y, Scalar::ZERO])
}

/// Approximate the circle
/// Approximate the arc
///
/// `tolerance` specifies how much the approximation is allowed to deviate
/// from the circle.
/// from the arc.
pub fn approx(&self, tolerance: Tolerance, out: &mut Vec<Point<3>>) {
let radius = self.radius.magnitude();

Expand All @@ -91,22 +98,31 @@ impl Circle {
// and the circle. This is the same as the difference between
// the circumscribed circle and the incircle.

let n = Self::number_of_vertices(tolerance, radius);
let n = Self::number_of_vertices(tolerance, radius, self.length);

for i in 0..n {
let angle = 2. * PI / n as f64 * i as f64;
let angle = self.length / n as f64 * i as f64;
let point = self.point_curve_to_model(&Point::from([angle]));
out.push(point);
}
}

fn number_of_vertices(tolerance: Tolerance, radius: Scalar) -> u64 {
if tolerance.inner() > radius / Scalar::TWO {
3
} else {
(Scalar::PI / (Scalar::ONE - (tolerance.inner() / radius)).acos())
fn number_of_vertices(
tolerance: Tolerance,
radius: Scalar,
length: Scalar,
) -> u64 {
let length = length.abs();

let n =
(length / 2. / (Scalar::ONE - (tolerance.inner() / radius)).acos())
.ceil()
.into_u64()
.into_u64();

if length >= Scalar::PI * 2. {
max(n, 3)
} else {
max(n, 2)
}
}
}
Expand All @@ -119,13 +135,14 @@ mod tests {

use crate::algorithms::Tolerance;

use super::Circle;
use super::Arc;

#[test]
fn point_model_to_curve() {
let circle = Circle {
let circle = Arc {
center: Point::from([1., 2., 3.]),
radius: Vector::from([1., 0.]),
length: Scalar::PI * 2.,
};

assert_eq!(
Expand All @@ -148,28 +165,37 @@ mod tests {

#[test]
fn number_of_vertices() {
verify_result(50., 100., 3);
verify_result(10., 100., 7);
verify_result(1., 100., 23);
// full circle
verify_result(50., 100., Scalar::PI * 2., 3);
verify_result(10., 100., Scalar::PI * 2., 7);
verify_result(1., 100., Scalar::PI * 2., 23);

// half circle
verify_result(50., 100., Scalar::PI, 2);
verify_result(10., 100., Scalar::PI, 4);
verify_result(1., 100., Scalar::PI, 12);

fn verify_result(
tolerance: impl Into<Tolerance>,
radius: impl Into<Scalar>,
length: Scalar,
n: u64,
) {
let tolerance = tolerance.into();
let radius = radius.into();

assert_eq!(n, Circle::number_of_vertices(tolerance, radius));
assert_eq!(n, Arc::number_of_vertices(tolerance, radius, length));

assert!(calculate_error(radius, n) <= tolerance.inner());
assert!(calculate_error(radius, length, n) <= tolerance.inner());
if n > 3 {
assert!(calculate_error(radius, n - 1) >= tolerance.inner());
assert!(
calculate_error(radius, length, n - 1) >= tolerance.inner()
);
}
}

fn calculate_error(radius: Scalar, n: u64) -> Scalar {
radius - radius * (Scalar::PI / Scalar::from_u64(n)).cos()
fn calculate_error(radius: Scalar, length: Scalar, n: u64) -> Scalar {
radius - radius * (length / 2. / Scalar::from_u64(n)).cos()
}
}
}
20 changes: 10 additions & 10 deletions crates/fj-kernel/src/geometry/curves/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
mod circle;
mod arc;
mod line;

use crate::algorithms::Tolerance;

pub use self::{circle::Circle, line::Line};
pub use self::{arc::Arc, line::Line};

use fj_math::{Point, Transform, Vector};

Expand All @@ -18,8 +18,8 @@ use fj_math::{Point, Transform, Vector};
/// while edges are bounded portions of curves.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum Curve {
/// A circle
Circle(Circle),
/// An arc
Arc(Arc),

/// A line
Line(Line),
Expand All @@ -37,7 +37,7 @@ impl Curve {
/// Access the origin of the curve's coordinate system
pub fn origin(&self) -> Point<3> {
match self {
Self::Circle(curve) => curve.origin(),
Self::Arc(curve) => curve.origin(),
Self::Line(curve) => curve.origin(),
}
}
Expand All @@ -46,7 +46,7 @@ impl Curve {
#[must_use]
pub fn transform(self, transform: &Transform) -> Self {
match self {
Self::Circle(curve) => Self::Circle(curve.transform(transform)),
Self::Arc(curve) => Self::Arc(curve.transform(transform)),
Self::Line(curve) => Self::Line(curve.transform(transform)),
}
}
Expand All @@ -62,23 +62,23 @@ impl Curve {
/// an error.
pub fn point_model_to_curve(&self, point: &Point<3>) -> Point<1> {
match self {
Self::Circle(curve) => curve.point_model_to_curve(point),
Self::Arc(curve) => curve.point_model_to_curve(point),
Self::Line(curve) => curve.point_model_to_curve(point),
}
}

/// Convert a point on the curve into model coordinates
pub fn point_curve_to_model(&self, point: &Point<1>) -> Point<3> {
match self {
Self::Circle(curve) => curve.point_curve_to_model(point),
Self::Arc(curve) => curve.point_curve_to_model(point),
Self::Line(curve) => curve.point_curve_to_model(point),
}
}

/// Convert a vector on the curve into model coordinates
pub fn vector_curve_to_model(&self, point: &Vector<1>) -> Vector<3> {
match self {
Self::Circle(curve) => curve.vector_curve_to_model(point),
Self::Arc(curve) => curve.vector_curve_to_model(point),
Self::Line(curve) => curve.vector_curve_to_model(point),
}
}
Expand All @@ -100,7 +100,7 @@ impl Curve {
/// themselves.
pub fn approx(&self, tolerance: Tolerance, out: &mut Vec<Point<3>>) {
match self {
Self::Circle(circle) => circle.approx(tolerance, out),
Self::Arc(curve) => curve.approx(tolerance, out),
Self::Line(_) => {}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/geometry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod points;
mod surfaces;

pub use self::{
curves::{Circle, Curve, Line},
curves::{Arc, Curve, Line},
points::Point,
surfaces::{Surface, SweptCurve},
};
5 changes: 3 additions & 2 deletions crates/fj-kernel/src/topology/builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use fj_math::{Point, Scalar, Vector};

use crate::{
geometry::{Circle, Curve, Line, Surface},
geometry::{Arc, Curve, Line, Surface},
shape::{Handle, Shape, ValidationResult},
};

Expand Down Expand Up @@ -48,9 +48,10 @@ impl<'r> EdgeBuilder<'r> {

/// Build a circle from a radius
pub fn build_circle(self, radius: Scalar) -> ValidationResult<Edge> {
let curve = self.shape.insert(Curve::Circle(Circle {
let curve = self.shape.insert(Curve::Arc(Arc {
center: Point::origin(),
radius: Vector::from([radius, Scalar::ZERO]),
length: Scalar::PI * 2.,
}))?;
let edge = self.shape.insert(Edge {
curve,
Expand Down

0 comments on commit 31ba592

Please sign in to comment.