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

Make Curve generic over its dimensionality #579

Merged
merged 8 commits into from
May 13, 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
6 changes: 3 additions & 3 deletions crates/fj-kernel/src/algorithms/approx/curves.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ use super::Tolerance;
/// The `approximate_between` methods of the curves then need to make sure to
/// only return points in between those vertices, not the vertices themselves.
pub fn approx_curve(
curve: &Curve,
curve: &Curve<3>,
tolerance: Tolerance,
out: &mut Vec<geometry::Point<1>>,
out: &mut Vec<geometry::Point<1, 3>>,
) {
match curve {
Curve::Circle(curve) => approx_circle(curve, tolerance, out),
Expand All @@ -38,7 +38,7 @@ pub fn approx_curve(
pub fn approx_circle(
circle: &Circle<3>,
tolerance: Tolerance,
out: &mut Vec<geometry::Point<1>>,
out: &mut Vec<geometry::Point<1, 3>>,
) {
let radius = circle.a.magnitude();

Expand Down
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/algorithms/approx/cycles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use super::{curves::approx_curve, edges::approximate_edge, Tolerance};
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct CycleApprox {
/// The points that approximate the cycle
pub points: Vec<geometry::Point<3>>,
pub points: Vec<geometry::Point<3, 3>>,
}

impl CycleApprox {
Expand Down
4 changes: 2 additions & 2 deletions crates/fj-kernel/src/algorithms/approx/edges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::{geometry, topology::EdgeVertex};

pub fn approximate_edge(
vertices: Option<[EdgeVertex; 2]>,
mut points: Vec<geometry::Point<1>>,
) -> Vec<geometry::Point<1>> {
mut points: Vec<geometry::Point<1, 3>>,
) -> Vec<geometry::Point<1, 3>> {
// Insert the exact vertices of this edge into the approximation. This means
// we don't rely on the curve approximation to deliver accurate
// representations of these vertices, which they might not be able to do.
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/algorithms/approx/faces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub struct FaceApprox {
///
/// These could be actual vertices from the model, points that approximate
/// an edge, or points that approximate a face.
pub points: HashSet<geometry::Point<3>>,
pub points: HashSet<geometry::Point<3, 3>>,

/// Approximation of the exterior cycle
pub exterior: CycleApprox,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use fj_math::{Line, Point, Scalar, Vector};
use crate::geometry::{Curve, Surface};

/// Test intersection between two surfaces
pub fn surface_surface(a: &Surface, b: &Surface) -> Option<Curve> {
pub fn surface_surface(a: &Surface, b: &Surface) -> Option<Curve<3>> {
// Algorithm from Real-Time Collision Detection by Christer Ericson. See
// section 5.4.4, Intersection of Two Planes.

Expand Down
6 changes: 3 additions & 3 deletions crates/fj-kernel/src/algorithms/triangulation/delaunay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::geometry;

/// Create a Delaunay triangulation of all points
pub fn triangulate(
points: Vec<geometry::Point<2>>,
) -> Vec<[geometry::Point<2>; 3]> {
points: Vec<geometry::Point<2, 3>>,
) -> Vec<[geometry::Point<2, 3>; 3]> {
use spade::Triangulation as _;

let triangulation = spade::DelaunayTriangulation::<_>::bulk_load(points)
Expand All @@ -31,7 +31,7 @@ pub fn triangulate(
}

// Enables the use of `geometry::Point` in the triangulation.
impl HasPosition for geometry::Point<2> {
impl HasPosition for geometry::Point<2, 3> {
type Scalar = Scalar;

fn position(&self) -> spade::Point2<Self::Scalar> {
Expand Down
58 changes: 33 additions & 25 deletions crates/fj-kernel/src/geometry/curves.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@ use crate::geometry;
/// The nomenclature is inspired by Boundary Representation Modelling Techniques
/// by Ian Stroud. "Curve" refers to unbounded one-dimensional geometry, while
/// while edges are bounded portions of curves.
///
/// The `D` parameter defines the dimensions in which the curve is defined.
/// Typically, only `2` or `3` make sense, which means the curve is defined on
/// a surface or in a space, respectively.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum Curve {
pub enum Curve<const D: usize> {
/// A circle
Circle(Circle<3>),
Circle(Circle<D>),

/// A line
Line(Line<3>),
Line(Line<D>),
}

impl Curve {
impl Curve<3> {
/// Construct a `Curve` that represents the x-axis
pub fn x_axis() -> Self {
Self::Line(Line {
Expand All @@ -45,8 +49,21 @@ impl Curve {
})
}

/// Create a new instance that is transformed by `transform`
#[must_use]
pub fn transform(self, transform: &Transform) -> Self {
match self {
Self::Circle(curve) => {
Self::Circle(transform.transform_circle(&curve))
}
Self::Line(curve) => Self::Line(transform.transform_line(&curve)),
}
}
}

impl<const D: usize> Curve<D> {
/// Access the origin of the curve's coordinate system
pub fn origin(&self) -> Point<3> {
pub fn origin(&self) -> Point<D> {
match self {
Self::Circle(curve) => curve.center,
Self::Line(curve) => curve.origin,
Expand All @@ -62,17 +79,6 @@ impl Curve {
}
}

/// Create a new instance that is transformed by `transform`
#[must_use]
pub fn transform(self, transform: &Transform) -> Self {
match self {
Self::Circle(curve) => {
Self::Circle(transform.transform_circle(&curve))
}
Self::Line(curve) => Self::Line(transform.transform_line(&curve)),
}
}

/// Convert a point in model coordinates to curve coordinates
///
/// Projects the point onto the curve before computing curve coordinate.
Expand All @@ -84,23 +90,25 @@ impl Curve {
/// an error.
pub fn point_to_curve_coords(
&self,
point: impl Into<Point<3>>,
) -> geometry::Point<1> {
let point_3d = point.into();
point: impl Into<Point<D>>,
) -> geometry::Point<1, D> {
let point_canonical = point.into();

let point_1d = match self {
Self::Circle(curve) => curve.point_to_circle_coords(point_3d),
Self::Line(curve) => curve.point_to_line_coords(point_3d),
let point_native = match self {
Self::Circle(curve) => {
curve.point_to_circle_coords(point_canonical)
}
Self::Line(curve) => curve.point_to_line_coords(point_canonical),
};

geometry::Point::new(point_1d, point_3d)
geometry::Point::new(point_native, point_canonical)
}

/// Convert a point on the curve into model coordinates
pub fn point_from_curve_coords(
&self,
point: impl Into<Point<1>>,
) -> Point<3> {
) -> Point<D> {
match self {
Self::Circle(curve) => curve.point_from_circle_coords(point),
Self::Line(curve) => curve.point_from_line_coords(point),
Expand All @@ -111,7 +119,7 @@ impl Curve {
pub fn vector_from_curve_coords(
&self,
point: impl Into<Vector<1>>,
) -> Vector<3> {
) -> Vector<D> {
match self {
Self::Circle(curve) => curve.vector_from_circle_coords(point),
Self::Line(curve) => curve.vector_from_line_coords(point),
Expand Down
53 changes: 28 additions & 25 deletions crates/fj-kernel/src/geometry/points.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
/// A point that can be losslessly converted into its canonical form
/// A point that stores a native and a canonical form
///
/// The canonical form is always the 3D representation. It needs to be provided
/// when constructing the point, along with the point's native form.
/// The native form of a point is whatever representation is most appropriate in
/// the current context. The canonical form is the representation that the
/// native form was created from.
///
/// Typically, the canonical form is more general and has higher dimensionality
/// (for example, a point in a 3D space), while the native form is more specific
/// and has lower dimensionality (for example, the point in 2D surface
/// coordinates, on surface within that 3D space).
///
/// The purpose of storing both forms is to be able to losslessly convert the
/// point back to its canonical form. Even if this conversion can be computed on
/// the fly, such a conversion might not result in the original canonical form,
/// due to floating point accuracy issues. Hence, such a conversion would not be
/// lossless, which could result in bugs.
///
/// The `N` parameter defines the dimensionality of the native form, while the
/// `C` parameter defines the dimensionality of the canonical form.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Point<const D: usize> {
/// This point's native form
///
/// The native form of the point is its representation in its native
/// coordinate system. This could be a 1-dimensional curve, 2-dimensional
/// surface, or 3-dimensional model coordinate system.
native: fj_math::Point<D>,

/// The canonical form of the point
///
/// This is always the 3D representation of the point. Since this is always
/// kept here, unchanged, as the point is converted into other coordinate
/// systems, it allows for a lossless conversion back into 3D coordinates,
/// unaffected by floating point accuracy issues.
canonical: fj_math::Point<3>,
pub struct Point<const N: usize, const C: usize> {
native: fj_math::Point<N>,
canonical: fj_math::Point<C>,
}

impl<const D: usize> Point<D> {
impl<const N: usize, const C: usize> Point<N, C> {
/// Construct a new instance
///
/// Both the native and the canonical form must be provide. The caller must
/// guarantee that both of them match.
/// Both the native and the canonical form must be provided. The caller must
/// guarantee that both of them match, i.e. define the same point.
pub fn new(
native: impl Into<fj_math::Point<D>>,
canonical: impl Into<fj_math::Point<3>>,
native: impl Into<fj_math::Point<N>>,
canonical: impl Into<fj_math::Point<C>>,
) -> Self {
Self {
native: native.into(),
Expand All @@ -36,12 +39,12 @@ impl<const D: usize> Point<D> {
}

/// Access the point's native form
pub fn native(&self) -> fj_math::Point<D> {
pub fn native(&self) -> fj_math::Point<N> {
self.native
}

/// Access the point's canonical form
pub fn canonical(&self) -> fj_math::Point<3> {
pub fn canonical(&self) -> fj_math::Point<C> {
self.canonical
}
}
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/geometry/surfaces/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl Surface {
pub fn point_to_surface_coords(
&self,
point_3d: impl Into<Point<3>>,
) -> geometry::Point<2> {
) -> geometry::Point<2, 3> {
let point_3d = point_3d.into();

let point_2d = match self {
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/geometry/surfaces/swept.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::geometry::Curve;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct SweptCurve {
/// The curve that this surface was swept from
pub curve: Curve,
pub curve: Curve<3>,

/// The path that the curve was swept along
pub path: Vector<3>,
Expand Down
4 changes: 2 additions & 2 deletions crates/fj-kernel/src/shape/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ impl Shape {
/// Access an iterator over all curves
///
/// The caller must not make any assumptions about the order of curves.
pub fn curves(&self) -> Iter<Curve> {
pub fn curves(&self) -> Iter<Curve<3>> {
self.stores.curves.iter()
}

Expand Down Expand Up @@ -385,7 +385,7 @@ mod tests {
}
}

fn add_curve(&mut self) -> Handle<Curve> {
fn add_curve(&mut self) -> Handle<Curve<3>> {
self.insert(Curve::x_axis()).unwrap()
}

Expand Down
4 changes: 2 additions & 2 deletions crates/fj-kernel/src/shape/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub trait Object:
}

impl private::Sealed for Point<3> {}
impl private::Sealed for Curve {}
impl private::Sealed for Curve<3> {}
impl private::Sealed for Surface {}

impl private::Sealed for Vertex {}
Expand All @@ -23,7 +23,7 @@ impl private::Sealed for Cycle {}
impl private::Sealed for Face {}

impl Object for Point<3> {}
impl Object for Curve {}
impl Object for Curve<3> {}
impl Object for Surface {}

impl Object for Vertex {}
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/shape/stores.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl Stores {
}

pub type Points = Store<Point<3>>;
pub type Curves = Store<Curve>;
pub type Curves = Store<Curve<3>>;
pub type Surfaces = Store<Surface>;

pub type Vertices = Store<Vertex>;
Expand Down
6 changes: 3 additions & 3 deletions crates/fj-kernel/src/shape/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl Validate for Point<3> {
}
}

impl Validate for Curve {
impl Validate for Curve<3> {
fn validate(&self, _: Scalar, _: &Stores) -> Result<(), ValidationError> {
Ok(())
}
Expand Down Expand Up @@ -202,7 +202,7 @@ pub enum ValidationError {
impl ValidationError {
/// Indicate whether validation found a missing curve
#[cfg(test)]
pub fn missing_curve(&self, curve: &Handle<Curve>) -> bool {
pub fn missing_curve(&self, curve: &Handle<Curve<3>>) -> bool {
if let Self::Structural(StructuralIssues { missing_curve, .. }) = self {
return missing_curve.as_ref() == Some(curve);
}
Expand Down Expand Up @@ -270,7 +270,7 @@ impl From<StructuralIssues> for ValidationError {
#[derive(Debug, Default)]
pub struct StructuralIssues {
/// Missing curve found in edge validation
pub missing_curve: Option<Handle<Curve>>,
pub missing_curve: Option<Handle<Curve<3>>>,

/// Missing vertices found in edge validation
pub missing_vertices: HashSet<Handle<Vertex>>,
Expand Down
8 changes: 4 additions & 4 deletions crates/fj-kernel/src/topology/edges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub struct Edge {
/// The edge can be a segment of the curve that is bounded by two vertices,
/// or if the curve is continuous (i.e. connects to itself), the edge could
/// be defined by the whole curve, and have no bounding vertices.
pub curve: Handle<Curve>,
pub curve: Handle<Curve<3>>,

/// Access the vertices that bound the edge on the curve
///
Expand All @@ -38,7 +38,7 @@ pub struct Edge {
impl Edge {
/// Construct an instance of `Edge`
pub fn new(
curve: Handle<Curve>,
curve: Handle<Curve<3>>,
vertices: Option<[Handle<Vertex>; 2]>,
) -> Self {
let vertices = vertices.map(|vertices| {
Expand All @@ -61,7 +61,7 @@ impl Edge {
///
/// This is a convenience method that saves the caller from dealing with the
/// [`Handle`].
pub fn curve(&self) -> Curve {
pub fn curve(&self) -> Curve<3> {
self.curve.get()
}

Expand Down Expand Up @@ -98,5 +98,5 @@ pub struct EdgeVertex {
/// The local representation of the vertex
///
/// Represents the vertex in terms of the coordinates of the edge's curve.
pub local: geometry::Point<1>,
pub local: geometry::Point<1, 3>,
}