diff --git a/crates/fj-math/src/circle.rs b/crates/fj-math/src/circle.rs new file mode 100644 index 000000000..29212cf90 --- /dev/null +++ b/crates/fj-math/src/circle.rs @@ -0,0 +1,113 @@ +use crate::{Point, Scalar, Vector}; + +/// An n-dimensional circle +/// +/// The dimensionality of the circle is defined by the const generic `D` +/// parameter. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct Circle { + /// The center point of the circle + pub center: Point, + + /// A vector from the center to the starting point of the circle + /// + /// The length of this vector defines the circle radius. Please also refer + /// to the documentation of `b`. + pub a: Vector, + + /// A second vector that defines the plane of the circle + /// + /// The vector must be of equal length to `a` (the circle radius) and must + /// be perpendicular to it. Code working with circles might assume that + /// these conditions are met. + pub b: Vector, +} + +impl Circle { + /// 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::PI * 2. + }; + 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 + } +} + +#[cfg(test)] +mod tests { + use std::f64::consts::{FRAC_PI_2, PI}; + + use crate::{Point, Vector}; + + use super::Circle; + + #[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 b9ef5ad6f..071eb7e48 100644 --- a/crates/fj-math/src/lib.rs +++ b/crates/fj-math/src/lib.rs @@ -36,6 +36,7 @@ #![deny(missing_docs)] mod aabb; +mod circle; mod coordinates; mod line; mod point; @@ -48,6 +49,7 @@ mod vector; pub use self::{ aabb::Aabb, + circle::Circle, coordinates::{Uv, Xyz, T}, line::Line, point::Point, diff --git a/crates/fj-math/src/transform.rs b/crates/fj-math/src/transform.rs index 2827adf6f..62a565666 100644 --- a/crates/fj-math/src/transform.rs +++ b/crates/fj-math/src/transform.rs @@ -2,7 +2,7 @@ use std::ops; use nalgebra::Perspective3; -use crate::{Line, Scalar}; +use crate::{Circle, Line, Scalar}; use super::{Aabb, Point, Segment, Triangle, Vector}; @@ -79,6 +79,15 @@ impl Transform { ]) } + /// Transform the given circle + pub fn transform_circle(&self, circle: &Circle<3>) -> Circle<3> { + Circle { + center: self.transform_point(&circle.center), + a: self.transform_vector(&circle.a), + b: self.transform_vector(&circle.b), + } + } + /// Inverse transform pub fn inverse(&self) -> Transform { Self(self.0.inverse()) diff --git a/crates/fj-math/src/vector.rs b/crates/fj-math/src/vector.rs index 6ad489ed1..c9a1282e5 100644 --- a/crates/fj-math/src/vector.rs +++ b/crates/fj-math/src/vector.rs @@ -46,6 +46,25 @@ impl Vector { } } + /// Convert the vector into a 2-dimensional vector + /// + /// If the vector is 0-, or 1-dimensional, the missing components will be + /// initialized to zero. + /// + /// If the vector has higher dimensionality than two, the superfluous + /// components will be discarded. + pub fn to_uv(self) -> Vector<2> { + let zero = Scalar::ZERO; + + let components = match self.components.as_slice() { + [] => [zero, zero], + &[t] => [t, zero], + &[u, v, ..] => [u, v], + }; + + Vector { components } + } + /// Convert the vector into a 3-dimensional vector /// /// If the vector is 0-, 1-, or 2-dimensional, the missing components will @@ -315,6 +334,15 @@ impl approx::AbsDiffEq for Vector { mod tests { use crate::{Scalar, Vector}; + #[test] + fn to_uv() { + let d0: [f64; 0] = []; + assert_eq!(Vector::from(d0).to_uv(), Vector::from([0., 0.])); + assert_eq!(Vector::from([1.]).to_uv(), Vector::from([1., 0.])); + assert_eq!(Vector::from([1., 2.]).to_uv(), Vector::from([1., 2.])); + assert_eq!(Vector::from([1., 2., 3.]).to_uv(), Vector::from([1., 2.]),); + } + #[test] fn to_xyz() { let d0: [f64; 0] = [];