diff --git a/crates/fj-core/src/algorithms/approx/circle.rs b/crates/fj-core/src/algorithms/approx/circle.rs index 45b1b6b14..4d622a9b7 100644 --- a/crates/fj-core/src/algorithms/approx/circle.rs +++ b/crates/fj-core/src/algorithms/approx/circle.rs @@ -1,8 +1,6 @@ -use fj_math::Point; +use fj_math::{Circle, Point}; -use crate::geometry::{ - curves::circle::Circle, traits::GenPolyline, CurveBoundary, Tolerance, -}; +use crate::geometry::{traits::GenPolyline, CurveBoundary, Tolerance}; /// # Approximate a circle /// diff --git a/crates/fj-core/src/algorithms/approx/curve.rs b/crates/fj-core/src/algorithms/approx/curve.rs index e1deb6767..444e675d8 100644 --- a/crates/fj-core/src/algorithms/approx/curve.rs +++ b/crates/fj-core/src/algorithms/approx/curve.rs @@ -1,11 +1,11 @@ use std::collections::BTreeMap; -use fj_math::Point; +use fj_math::{Circle, Point}; use crate::{ geometry::{ - curves::{circle::Circle, line::Line}, - CurveBoundary, Geometry, Path, SurfaceGeom, Tolerance, + curves::line::Line, CurveBoundary, Geometry, Path, SurfaceGeom, + Tolerance, }, storage::Handle, topology::{Curve, Surface}, @@ -193,14 +193,14 @@ impl CurveApproxCache { mod tests { use std::f64::consts::TAU; - use fj_math::{Point, Vector}; + use fj_math::{Circle, Point, Vector}; use pretty_assertions::assert_eq; use crate::{ algorithms::approx::{ circle::approx_circle, curve::approx_curve, ApproxPoint, }, - geometry::{curves::circle::Circle, CurveBoundary, Path, SurfaceGeom}, + geometry::{CurveBoundary, Path, SurfaceGeom}, operations::build::BuildSurface, topology::Surface, Core, diff --git a/crates/fj-core/src/geometry/curves/circle.rs b/crates/fj-core/src/geometry/curves/circle.rs index 06aed9742..a7ef6a25a 100644 --- a/crates/fj-core/src/geometry/curves/circle.rs +++ b/crates/fj-core/src/geometry/curves/circle.rs @@ -2,183 +2,10 @@ use std::iter; -use approx::AbsDiffEq; -use fj_math::{Aabb, LineSegment, Point, Scalar, Sign, Transform, Vector}; +use fj_math::{Circle, LineSegment, Point, Scalar, Sign}; use crate::geometry::{traits::GenPolyline, CurveBoundary, Tolerance}; -/// An n-dimensional circle -/// -/// The dimensionality of the circle is defined by the const generic `D` -/// parameter. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct Circle { - center: Point, - a: Vector, - b: Vector, -} - -impl Circle { - /// Construct a circle - /// - /// # Panics - /// - /// Panics, if any of the following requirements are not met: - /// - /// - The circle radius (defined by the length of `a` and `b`) must not be - /// zero. - /// - `a` and `b` must be of equal length. - /// - `a` and `b` must be perpendicular to each other. - pub fn new( - center: impl Into>, - a: impl Into>, - b: impl Into>, - ) -> Self { - let center = center.into(); - let a = a.into(); - let b = b.into(); - - assert_eq!( - a.magnitude(), - b.magnitude(), - "`a` and `b` must be of equal length" - ); - assert_ne!( - a.magnitude(), - Scalar::ZERO, - "circle radius must not be zero" - ); - // Requiring the vector to be *precisely* perpendicular is not - // practical, because of numerical inaccuracy. This epsilon value seems - // seems to work for now, but maybe it needs to become configurable. - assert!( - a.dot(&b) < Scalar::default_epsilon(), - "`a` and `b` must be perpendicular to each other" - ); - - Self { center, a, b } - } - - /// Construct a `Circle` from a center point and a radius - pub fn from_center_and_radius( - center: impl Into>, - radius: impl Into, - ) -> Self { - let radius = radius.into(); - - let mut a = [Scalar::ZERO; D]; - let mut b = [Scalar::ZERO; D]; - - a[0] = radius; - b[1] = radius; - - Self::new(center, a, b) - } - - /// Access the center point of the circle - pub fn center(&self) -> Point { - self.center - } - - /// Access the radius of the circle - pub fn radius(&self) -> Scalar { - self.a().magnitude() - } - - /// Access the vector that defines the starting point of the circle - /// - /// The point where this vector points from the circle center, is the zero - /// coordinate of the circle's coordinate system. The length of the vector - /// defines the circle's radius. - /// - /// Please also refer to [`Self::b`]. - pub fn a(&self) -> Vector { - self.a - } - - /// Access the vector that defines the plane of the circle - /// - /// Also defines the direction of the circle's coordinate system. The length - /// is equal to the circle's radius, and this vector is perpendicular to - /// [`Self::a`]. - pub fn b(&self) -> Vector { - self.b - } - - /// Create a new instance that is reversed - #[must_use] - pub fn reverse(mut self) -> Self { - self.b = -self.b; - self - } - - /// Convert a `D`-dimensional point to circle coordinates - /// - /// Converts the provided point into circle coordinates between `0.` - /// (inclusive) and `PI * 2.` (exclusive). - /// - /// Projects the point onto the circle before computing circle coordinate, - /// ignoring the radius. This is done to make this method robust against - /// floating point accuracy issues. - /// - /// Callers are advised to be careful about the points they pass, as the - /// point not being on the curve, intentional or not, will not result in an - /// error. - pub fn point_to_circle_coords( - &self, - point: impl Into>, - ) -> Point<1> { - let vector = (point.into() - self.center).to_uv(); - let atan = Scalar::atan2(vector.v, vector.u); - let coord = if atan >= Scalar::ZERO { - atan - } else { - atan + Scalar::TAU - }; - Point::from([coord]) - } - - /// Convert a point in circle coordinates into a `D`-dimensional point - pub fn point_from_circle_coords( - &self, - point: impl Into>, - ) -> Point { - self.center + self.vector_from_circle_coords(point.into().coords) - } - - /// Convert a vector in circle coordinates into a `D`-dimensional point - pub fn vector_from_circle_coords( - &self, - vector: impl Into>, - ) -> Vector { - let angle = vector.into().t; - let (sin, cos) = angle.sin_cos(); - - self.a * cos + self.b * sin - } - - /// Calculate an AABB for the circle - pub fn aabb(&self) -> Aabb { - let center_to_min_max = Vector::from_component(self.radius()); - - Aabb { - min: self.center() - center_to_min_max, - max: self.center() + center_to_min_max, - } - } -} - -impl Circle<3> { - /// # Transform the circle - pub fn transform(&self, transform: &Transform) -> Self { - Circle::new( - transform.transform_point(&self.center()), - transform.transform_vector(&self.a()), - transform.transform_vector(&self.b()), - ) - } -} - impl GenPolyline for Circle { fn origin(&self) -> Point { self.center() + self.a() @@ -300,9 +127,9 @@ impl CircleApproxParams { #[cfg(test)] mod tests { - use std::f64::consts::{FRAC_PI_2, PI, TAU}; + use std::f64::consts::TAU; - use fj_math::{Point, Scalar, Vector}; + use fj_math::{Point, Scalar}; use crate::geometry::{ curves::circle::Circle, traits::GenPolyline, CurveBoundary, Tolerance, @@ -310,32 +137,6 @@ mod tests { use super::CircleApproxParams; - #[test] - fn point_to_circle_coords() { - let circle = Circle { - center: Point::from([1., 2., 3.]), - a: Vector::from([1., 0., 0.]), - b: Vector::from([0., 1., 0.]), - }; - - assert_eq!( - circle.point_to_circle_coords([2., 2., 3.]), - Point::from([0.]), - ); - assert_eq!( - circle.point_to_circle_coords([1., 3., 3.]), - Point::from([FRAC_PI_2]), - ); - assert_eq!( - circle.point_to_circle_coords([0., 2., 3.]), - Point::from([PI]), - ); - assert_eq!( - circle.point_to_circle_coords([1., 1., 3.]), - Point::from([FRAC_PI_2 * 3.]), - ); - } - #[test] fn increment_for_circle() { test_increment(1., 0.5, 3.); diff --git a/crates/fj-core/src/geometry/path.rs b/crates/fj-core/src/geometry/path.rs index c72632737..bf6e431d3 100644 --- a/crates/fj-core/src/geometry/path.rs +++ b/crates/fj-core/src/geometry/path.rs @@ -2,9 +2,9 @@ //! //! See [`Path`]. -use fj_math::{Point, Scalar, Transform, Vector}; +use fj_math::{Circle, Point, Scalar, Transform, Vector}; -use super::curves::{circle::Circle, line::Line}; +use super::curves::line::Line; /// A path through surface (2D) or global (3D) space #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] diff --git a/crates/fj-core/src/operations/sweep/path.rs b/crates/fj-core/src/operations/sweep/path.rs index 8e901318a..57035b8fe 100644 --- a/crates/fj-core/src/operations/sweep/path.rs +++ b/crates/fj-core/src/operations/sweep/path.rs @@ -1,10 +1,7 @@ -use fj_math::Vector; +use fj_math::{Circle, Vector}; use crate::{ - geometry::{ - curves::{circle::Circle, line::Line}, - Path, SurfaceGeom, - }, + geometry::{curves::line::Line, Path, SurfaceGeom}, operations::build::BuildSurface, storage::Handle, topology::Surface, diff --git a/crates/fj-math/src/circle.rs b/crates/fj-math/src/circle.rs new file mode 100644 index 000000000..d22bdd654 --- /dev/null +++ b/crates/fj-math/src/circle.rs @@ -0,0 +1,208 @@ +use approx::AbsDiffEq; + +use crate::{Aabb, Point, Scalar, Transform, Vector}; + +/// An n-dimensional circle +/// +/// The dimensionality of the circle is defined by the const generic `D` +/// parameter. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct Circle { + center: Point, + a: Vector, + b: Vector, +} + +impl Circle { + /// Construct a circle + /// + /// # Panics + /// + /// Panics, if any of the following requirements are not met: + /// + /// - The circle radius (defined by the length of `a` and `b`) must not be + /// zero. + /// - `a` and `b` must be of equal length. + /// - `a` and `b` must be perpendicular to each other. + pub fn new( + center: impl Into>, + a: impl Into>, + b: impl Into>, + ) -> Self { + let center = center.into(); + let a = a.into(); + let b = b.into(); + + assert_eq!( + a.magnitude(), + b.magnitude(), + "`a` and `b` must be of equal length" + ); + assert_ne!( + a.magnitude(), + Scalar::ZERO, + "circle radius must not be zero" + ); + // Requiring the vector to be *precisely* perpendicular is not + // practical, because of numerical inaccuracy. This epsilon value seems + // seems to work for now, but maybe it needs to become configurable. + assert!( + a.dot(&b) < Scalar::default_epsilon(), + "`a` and `b` must be perpendicular to each other" + ); + + Self { center, a, b } + } + + /// Construct a `Circle` from a center point and a radius + pub fn from_center_and_radius( + center: impl Into>, + radius: impl Into, + ) -> Self { + let radius = radius.into(); + + let mut a = [Scalar::ZERO; D]; + let mut b = [Scalar::ZERO; D]; + + a[0] = radius; + b[1] = radius; + + Self::new(center, a, b) + } + + /// Access the center point of the circle + pub fn center(&self) -> Point { + self.center + } + + /// Access the radius of the circle + pub fn radius(&self) -> Scalar { + self.a().magnitude() + } + + /// Access the vector that defines the starting point of the circle + /// + /// The point where this vector points from the circle center, is the zero + /// coordinate of the circle's coordinate system. The length of the vector + /// defines the circle's radius. + /// + /// Please also refer to [`Self::b`]. + pub fn a(&self) -> Vector { + self.a + } + + /// Access the vector that defines the plane of the circle + /// + /// Also defines the direction of the circle's coordinate system. The length + /// is equal to the circle's radius, and this vector is perpendicular to + /// [`Self::a`]. + pub fn b(&self) -> Vector { + self.b + } + + /// Create a new instance that is reversed + #[must_use] + pub fn reverse(mut self) -> Self { + self.b = -self.b; + self + } + + /// Convert a `D`-dimensional point to circle coordinates + /// + /// Converts the provided point into circle coordinates between `0.` + /// (inclusive) and `PI * 2.` (exclusive). + /// + /// Projects the point onto the circle before computing circle coordinate, + /// ignoring the radius. This is done to make this method robust against + /// floating point accuracy issues. + /// + /// Callers are advised to be careful about the points they pass, as the + /// point not being on the curve, intentional or not, will not result in an + /// error. + pub fn point_to_circle_coords( + &self, + point: impl Into>, + ) -> Point<1> { + let vector = (point.into() - self.center).to_uv(); + let atan = Scalar::atan2(vector.v, vector.u); + let coord = if atan >= Scalar::ZERO { + atan + } else { + atan + Scalar::TAU + }; + Point::from([coord]) + } + + /// Convert a point in circle coordinates into a `D`-dimensional point + pub fn point_from_circle_coords( + &self, + point: impl Into>, + ) -> Point { + self.center + self.vector_from_circle_coords(point.into().coords) + } + + /// Convert a vector in circle coordinates into a `D`-dimensional point + pub fn vector_from_circle_coords( + &self, + vector: impl Into>, + ) -> Vector { + let angle = vector.into().t; + let (sin, cos) = angle.sin_cos(); + + self.a * cos + self.b * sin + } + + /// Calculate an AABB for the circle + pub fn aabb(&self) -> Aabb { + let center_to_min_max = Vector::from_component(self.radius()); + + Aabb { + min: self.center() - center_to_min_max, + max: self.center() + center_to_min_max, + } + } +} + +impl Circle<3> { + /// # Transform the circle + pub fn transform(&self, transform: &Transform) -> Self { + Circle::new( + transform.transform_point(&self.center()), + transform.transform_vector(&self.a()), + transform.transform_vector(&self.b()), + ) + } +} + +#[cfg(test)] +mod tests { + use std::f64::consts::{FRAC_PI_2, PI}; + + use crate::{Circle, Point, Vector}; + + #[test] + fn point_to_circle_coords() { + let circle = Circle { + center: Point::from([1., 2., 3.]), + a: Vector::from([1., 0., 0.]), + b: Vector::from([0., 1., 0.]), + }; + + assert_eq!( + circle.point_to_circle_coords([2., 2., 3.]), + Point::from([0.]), + ); + assert_eq!( + circle.point_to_circle_coords([1., 3., 3.]), + Point::from([FRAC_PI_2]), + ); + assert_eq!( + circle.point_to_circle_coords([0., 2., 3.]), + Point::from([PI]), + ); + assert_eq!( + circle.point_to_circle_coords([1., 1., 3.]), + Point::from([FRAC_PI_2 * 3.]), + ); + } +} diff --git a/crates/fj-math/src/lib.rs b/crates/fj-math/src/lib.rs index d9667df14..e3e07e66f 100644 --- a/crates/fj-math/src/lib.rs +++ b/crates/fj-math/src/lib.rs @@ -34,6 +34,7 @@ mod aabb; mod arc; mod bivector; +mod circle; mod coordinates; mod line_segment; mod point; @@ -47,6 +48,7 @@ pub use self::{ aabb::Aabb, arc::Arc, bivector::Bivector, + circle::Circle, coordinates::{Uv, Xyz, T}, line_segment::LineSegment, point::Point,