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

Add fj_math::Circle #577

Merged
merged 3 commits into from
May 12, 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
113 changes: 113 additions & 0 deletions crates/fj-math/src/circle.rs
Original file line number Diff line number Diff line change
@@ -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<const D: usize> {
/// The center point of the circle
pub center: Point<D>,

/// 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<D>,

/// 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<D>,
}

impl<const D: usize> Circle<D> {
/// 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<D>>,
) -> 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<1>>,
) -> Point<D> {
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<1>>,
) -> Vector<D> {
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.]),
);
}
}
2 changes: 2 additions & 0 deletions crates/fj-math/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#![deny(missing_docs)]

mod aabb;
mod circle;
mod coordinates;
mod line;
mod point;
Expand All @@ -48,6 +49,7 @@ mod vector;

pub use self::{
aabb::Aabb,
circle::Circle,
coordinates::{Uv, Xyz, T},
line::Line,
point::Point,
Expand Down
11 changes: 10 additions & 1 deletion crates/fj-math/src/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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())
Expand Down
28 changes: 28 additions & 0 deletions crates/fj-math/src/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,25 @@ impl<const D: usize> Vector<D> {
}
}

/// 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
Expand Down Expand Up @@ -315,6 +334,15 @@ impl<const D: usize> approx::AbsDiffEq for Vector<D> {
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] = [];
Expand Down