diff --git a/crates/fj-kernel/src/algorithms/approx/curve.rs b/crates/fj-kernel/src/algorithms/approx/curve.rs index 870925bcc..00e77f588 100644 --- a/crates/fj-kernel/src/algorithms/approx/curve.rs +++ b/crates/fj-kernel/src/algorithms/approx/curve.rs @@ -12,8 +12,8 @@ use std::collections::BTreeMap; use crate::{ + geometry::path::{GlobalPath, SurfacePath}, objects::{Curve, GlobalCurve}, - path::{GlobalPath, SurfacePath}, storage::{Handle, ObjectId}, }; @@ -62,7 +62,7 @@ fn approx_global_curve( // This will probably all be unified eventually, as `SurfacePath` and // `GlobalPath` grow APIs that are better suited to implementing this code // in a more abstract way. - let points = match (curve.path(), curve.surface().u()) { + let points = match (curve.path(), curve.surface().geometry().u) { (SurfacePath::Circle(_), GlobalPath::Circle(_)) => { todo!( "Approximating a circle on a curved surface not supported yet." @@ -90,6 +90,7 @@ fn approx_global_curve( let point_global = curve .surface() + .geometry() .point_from_surface_coords(point_surface); (point_curve, point_global) }) @@ -101,15 +102,17 @@ fn approx_global_curve( [curve.path().point_from_path_coords(point_curve).u] })); - let approx_u = (curve.surface().u(), range_u) + let approx_u = (curve.surface().geometry().u, range_u) .approx_with_cache(tolerance, &mut ()); let mut points = Vec::new(); for (u, _) in approx_u { let t = (u.t - line.origin().u) / line.direction().u; let point_surface = curve.path().point_from_path_coords([t]); - let point_global = - curve.surface().point_from_surface_coords(point_surface); + let point_global = curve + .surface() + .geometry() + .point_from_surface_coords(point_surface); points.push((u, point_global)); } @@ -197,11 +200,11 @@ mod tests { use crate::{ algorithms::approx::{path::RangeOnPath, Approx, ApproxPoint}, - builder::CurveBuilder, + builder::{CurveBuilder, SurfaceBuilder}, + geometry::path::GlobalPath, insert::Insert, - objects::{Objects, Surface}, - partial::PartialCurve, - path::GlobalPath, + objects::Objects, + partial::{PartialCurve, PartialSurface}, }; use super::CurveApprox; @@ -210,9 +213,10 @@ mod tests { fn approx_line_on_flat_surface() -> anyhow::Result<()> { let objects = Objects::new(); - let surface = objects - .surfaces - .insert(Surface::new(GlobalPath::x_axis(), [0., 0., 1.]))?; + let surface = + PartialSurface::from_axes(GlobalPath::x_axis(), [0., 0., 1.]) + .build(&objects)? + .insert(&objects)?; let mut curve = PartialCurve { surface: Some(surface), ..Default::default() @@ -232,10 +236,12 @@ mod tests { { let objects = Objects::new(); - let surface = objects.surfaces.insert(Surface::new( + let surface = PartialSurface::from_axes( GlobalPath::circle_from_radius(1.), [0., 0., 1.], - ))?; + ) + .build(&objects)? + .insert(&objects)?; let mut curve = PartialCurve { surface: Some(surface), ..Default::default() @@ -255,8 +261,9 @@ mod tests { let objects = Objects::new(); let path = GlobalPath::circle_from_radius(1.); - let surface = - objects.surfaces.insert(Surface::new(path, [0., 0., 1.]))?; + let surface = PartialSurface::from_axes(path, [0., 0., 1.]) + .build(&objects)? + .insert(&objects)?; let mut curve = PartialCurve { surface: Some(surface.clone()), ..Default::default() @@ -276,7 +283,7 @@ mod tests { let point_surface = curve.path().point_from_path_coords(point_local); let point_global = - surface.point_from_surface_coords(point_surface); + surface.geometry().point_from_surface_coords(point_surface); ApproxPoint::new(point_surface, point_global) }) .collect::>(); @@ -288,9 +295,10 @@ mod tests { fn approx_circle_on_flat_surface() -> anyhow::Result<()> { let objects = Objects::new(); - let surface = objects - .surfaces - .insert(Surface::new(GlobalPath::x_axis(), [0., 0., 1.]))?; + let surface = + PartialSurface::from_axes(GlobalPath::x_axis(), [0., 0., 1.]) + .build(&objects)? + .insert(&objects)?; let mut curve = PartialCurve { surface: Some(surface), ..Default::default() @@ -306,8 +314,10 @@ mod tests { .approx(tolerance) .into_iter() .map(|(_, point_surface)| { - let point_global = - curve.surface().point_from_surface_coords(point_surface); + let point_global = curve + .surface() + .geometry() + .point_from_surface_coords(point_surface); ApproxPoint::new(point_surface, point_global) }) .collect::>(); diff --git a/crates/fj-kernel/src/algorithms/approx/path.rs b/crates/fj-kernel/src/algorithms/approx/path.rs index 1ce214b79..85335fa76 100644 --- a/crates/fj-kernel/src/algorithms/approx/path.rs +++ b/crates/fj-kernel/src/algorithms/approx/path.rs @@ -32,7 +32,7 @@ use std::iter; use fj_math::{Circle, Point, Scalar, Sign}; -use crate::path::{GlobalPath, SurfacePath}; +use crate::geometry::path::{GlobalPath, SurfacePath}; use super::{Approx, Tolerance}; diff --git a/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs b/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs index ac53e7fff..0ebeafcaf 100644 --- a/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs +++ b/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs @@ -1,8 +1,8 @@ use fj_math::{Point, Segment}; use crate::{ + geometry::path::SurfacePath, objects::{Curve, HalfEdge}, - path::SurfacePath, }; use super::LineSegmentIntersection; diff --git a/crates/fj-kernel/src/algorithms/intersect/ray_edge.rs b/crates/fj-kernel/src/algorithms/intersect/ray_edge.rs index b3a79f44d..962fed93b 100644 --- a/crates/fj-kernel/src/algorithms/intersect/ray_edge.rs +++ b/crates/fj-kernel/src/algorithms/intersect/ray_edge.rs @@ -4,8 +4,8 @@ use fj_math::Segment; use crate::{ algorithms::intersect::{HorizontalRayToTheRight, Intersect}, + geometry::path::SurfacePath, objects::HalfEdge, - path::SurfacePath, storage::Handle, }; diff --git a/crates/fj-kernel/src/algorithms/intersect/ray_face.rs b/crates/fj-kernel/src/algorithms/intersect/ray_face.rs index f5526972f..175abbaff 100644 --- a/crates/fj-kernel/src/algorithms/intersect/ray_face.rs +++ b/crates/fj-kernel/src/algorithms/intersect/ray_face.rs @@ -4,8 +4,8 @@ use fj_math::{Plane, Point, Scalar}; use crate::{ algorithms::intersect::face_point::FacePointIntersection, + geometry::path::GlobalPath, objects::{Face, HalfEdge, Vertex}, - path::GlobalPath, storage::Handle, }; @@ -17,14 +17,14 @@ impl Intersect for (&HorizontalRayToTheRight<3>, &Handle) { fn intersect(self) -> Option { let (ray, face) = self; - let plane = match face.surface().u() { + let plane = match face.surface().geometry().u { GlobalPath::Circle(_) => todo!( "Casting a ray against a swept circle is not supported yet" ), GlobalPath::Line(line) => Plane::from_parametric( line.origin(), line.direction(), - face.surface().v(), + face.surface().geometry().v, ), }; diff --git a/crates/fj-kernel/src/algorithms/intersect/surface_surface.rs b/crates/fj-kernel/src/algorithms/intersect/surface_surface.rs index 43ed585d6..5cdf55261 100644 --- a/crates/fj-kernel/src/algorithms/intersect/surface_surface.rs +++ b/crates/fj-kernel/src/algorithms/intersect/surface_surface.rs @@ -2,8 +2,8 @@ use fj_interop::ext::ArrayExt; use fj_math::{Line, Plane, Point, Scalar}; use crate::{ + geometry::path::{GlobalPath, SurfacePath}, objects::{Curve, GlobalCurve, Objects, Surface}, - path::{GlobalPath, SurfacePath}, storage::Handle, validate::ValidationError, }; @@ -74,12 +74,12 @@ impl SurfaceSurfaceIntersection { fn plane_from_surface(surface: &Surface) -> Plane { let (line, path) = { - let line = match surface.u() { + let line = match surface.geometry().u { GlobalPath::Line(line) => line, _ => todo!("Only plane-plane intersection is currently supported."), }; - (line, surface.v()) + (line, surface.geometry().v) }; Plane::from_parametric(line.origin(), line.direction(), path) diff --git a/crates/fj-kernel/src/algorithms/sweep/curve.rs b/crates/fj-kernel/src/algorithms/sweep/curve.rs index 76ec79719..8ffc6e469 100644 --- a/crates/fj-kernel/src/algorithms/sweep/curve.rs +++ b/crates/fj-kernel/src/algorithms/sweep/curve.rs @@ -1,8 +1,11 @@ use fj_math::{Circle, Line, Vector}; use crate::{ + builder::SurfaceBuilder, + geometry::path::{GlobalPath, SurfacePath}, + insert::Insert, objects::{Curve, Objects, Surface}, - path::{GlobalPath, SurfacePath}, + partial::PartialSurface, storage::Handle, validate::ValidationError, }; @@ -18,7 +21,7 @@ impl Sweep for Handle { _: &mut SweepCache, objects: &Objects, ) -> Result { - match self.surface().u() { + match self.surface().geometry().u { GlobalPath::Circle(_) => { // Sweeping a `Curve` creates a `Surface`. The u-axis of that // `Surface` is a `GlobalPath`, which we are computing below. @@ -44,20 +47,32 @@ impl Sweep for Handle { let u = match self.path() { SurfacePath::Circle(circle) => { - let center = - self.surface().point_from_surface_coords(circle.center()); - let a = self.surface().vector_from_surface_coords(circle.a()); - let b = self.surface().vector_from_surface_coords(circle.b()); + let center = self + .surface() + .geometry() + .point_from_surface_coords(circle.center()); + let a = self + .surface() + .geometry() + .vector_from_surface_coords(circle.a()); + let b = self + .surface() + .geometry() + .vector_from_surface_coords(circle.b()); let circle = Circle::new(center, a, b); GlobalPath::Circle(circle) } SurfacePath::Line(line) => { - let origin = - self.surface().point_from_surface_coords(line.origin()); - let direction = - self.surface().vector_from_surface_coords(line.direction()); + let origin = self + .surface() + .geometry() + .point_from_surface_coords(line.origin()); + let direction = self + .surface() + .geometry() + .vector_from_surface_coords(line.direction()); let line = Line::from_origin_and_direction(origin, direction); @@ -65,6 +80,9 @@ impl Sweep for Handle { } }; - Ok(objects.surfaces.insert(Surface::new(u, path))?) + let surface = PartialSurface::from_axes(u, path) + .build(objects)? + .insert(objects)?; + Ok(surface) } } diff --git a/crates/fj-kernel/src/algorithms/sweep/edge.rs b/crates/fj-kernel/src/algorithms/sweep/edge.rs index 901533c47..3d1489d24 100644 --- a/crates/fj-kernel/src/algorithms/sweep/edge.rs +++ b/crates/fj-kernel/src/algorithms/sweep/edge.rs @@ -4,13 +4,13 @@ use iter_fixed::IntoIteratorFixed; use crate::{ algorithms::{reverse::Reverse, transform::TransformObject}, + geometry::path::SurfacePath, insert::Insert, objects::{ Curve, Cycle, Face, GlobalEdge, HalfEdge, Objects, SurfaceVertex, Vertex, }, partial::HasPartial, - path::SurfacePath, storage::Handle, validate::ValidationError, }; diff --git a/crates/fj-kernel/src/algorithms/sweep/face.rs b/crates/fj-kernel/src/algorithms/sweep/face.rs index cc99370b6..930e19ccf 100644 --- a/crates/fj-kernel/src/algorithms/sweep/face.rs +++ b/crates/fj-kernel/src/algorithms/sweep/face.rs @@ -2,8 +2,8 @@ use fj_math::{Scalar, Vector}; use crate::{ algorithms::{reverse::Reverse, transform::TransformObject}, + geometry::path::GlobalPath, objects::{Face, Objects, Shell}, - path::GlobalPath, storage::Handle, validate::ValidationError, }; @@ -24,14 +24,14 @@ impl Sweep for Handle { let mut faces = Vec::new(); let is_negative_sweep = { - let u = match self.surface().u() { + let u = match self.surface().geometry().u { GlobalPath::Circle(_) => todo!( "Sweeping from faces defined in round surfaces is not \ supported" ), GlobalPath::Line(line) => line.direction(), }; - let v = self.surface().v(); + let v = self.surface().geometry().v; let normal = u.cross(&v); diff --git a/crates/fj-kernel/src/algorithms/sweep/vertex.rs b/crates/fj-kernel/src/algorithms/sweep/vertex.rs index e07754a62..f6e622d1e 100644 --- a/crates/fj-kernel/src/algorithms/sweep/vertex.rs +++ b/crates/fj-kernel/src/algorithms/sweep/vertex.rs @@ -3,11 +3,11 @@ use fj_math::{Line, Point, Scalar, Vector}; use try_insert_ext::EntryInsertExt; use crate::{ + geometry::path::SurfacePath, objects::{ Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects, Surface, SurfaceVertex, Vertex, }, - path::SurfacePath, storage::Handle, validate::ValidationError, }; @@ -56,7 +56,7 @@ impl Sweep for (Handle, Handle) { // not, we have no way of knowing the surface coordinates of the input // `Vertex` on the `Surface`, and we're going to need to do that further // down. There's no way to check for that, unfortunately. - assert_eq!(path, surface.v()); + assert_eq!(path, surface.geometry().v); // With that out of the way, let's start by creating the `GlobalEdge`, // as that is the most straight-forward part of this operations, and diff --git a/crates/fj-kernel/src/algorithms/transform/surface.rs b/crates/fj-kernel/src/algorithms/transform/surface.rs index f05007520..10b6d1d6f 100644 --- a/crates/fj-kernel/src/algorithms/transform/surface.rs +++ b/crates/fj-kernel/src/algorithms/transform/surface.rs @@ -1,22 +1,25 @@ use fj_math::Transform; use crate::{ - objects::{Objects, Surface}, - storage::Handle, - validate::ValidationError, + geometry::surface::SurfaceGeometry, objects::Objects, + partial::PartialSurface, validate::ValidationError, }; use super::TransformObject; -impl TransformObject for Handle { +impl TransformObject for PartialSurface { fn transform( self, transform: &Transform, - objects: &Objects, + _: &Objects, ) -> Result { - Ok(objects.surfaces.insert(Surface::new( - self.u().transform(transform), - transform.transform_vector(&self.v()), - ))?) + let geometry = self.geometry.map(|geometry| { + let u = geometry.u.transform(transform); + let v = transform.transform_vector(&geometry.v); + + SurfaceGeometry { u, v } + }); + + Ok(Self { geometry }) } } diff --git a/crates/fj-kernel/src/algorithms/triangulate/mod.rs b/crates/fj-kernel/src/algorithms/triangulate/mod.rs index a1a0ebe50..a6ade6f9b 100644 --- a/crates/fj-kernel/src/algorithms/triangulate/mod.rs +++ b/crates/fj-kernel/src/algorithms/triangulate/mod.rs @@ -148,12 +148,12 @@ mod tests { let triangles = triangulate(face)?; - let a = surface.point_from_surface_coords(a); - let b = surface.point_from_surface_coords(b); - let e = surface.point_from_surface_coords(e); - let f = surface.point_from_surface_coords(f); - let g = surface.point_from_surface_coords(g); - let h = surface.point_from_surface_coords(h); + let a = surface.geometry().point_from_surface_coords(a); + let b = surface.geometry().point_from_surface_coords(b); + let e = surface.geometry().point_from_surface_coords(e); + let f = surface.geometry().point_from_surface_coords(f); + let g = surface.geometry().point_from_surface_coords(g); + let h = surface.geometry().point_from_surface_coords(h); // Let's test that some correct triangles are present. We don't need to // test them all. @@ -209,11 +209,11 @@ mod tests { let triangles = triangulate(face)?; - let a3 = surface.point_from_surface_coords(a); - let b3 = surface.point_from_surface_coords(b); - let c3 = surface.point_from_surface_coords(c); - let d3 = surface.point_from_surface_coords(d); - let e3 = surface.point_from_surface_coords(e); + let a3 = surface.geometry().point_from_surface_coords(a); + let b3 = surface.geometry().point_from_surface_coords(b); + let c3 = surface.geometry().point_from_surface_coords(c); + let d3 = surface.geometry().point_from_surface_coords(d); + let e3 = surface.geometry().point_from_surface_coords(e); assert!(triangles.contains_triangle([a3, b3, d3])); assert!(triangles.contains_triangle([b3, c3, d3])); diff --git a/crates/fj-kernel/src/builder/curve.rs b/crates/fj-kernel/src/builder/curve.rs index 1380bfff8..85d71a63c 100644 --- a/crates/fj-kernel/src/builder/curve.rs +++ b/crates/fj-kernel/src/builder/curve.rs @@ -1,6 +1,6 @@ use fj_math::{Point, Scalar, Vector}; -use crate::{partial::PartialCurve, path::SurfacePath}; +use crate::{geometry::path::SurfacePath, partial::PartialCurve}; /// Builder API for [`PartialCurve`] pub trait CurveBuilder { diff --git a/crates/fj-kernel/src/builder/edge.rs b/crates/fj-kernel/src/builder/edge.rs index 74794c131..3d276c505 100644 --- a/crates/fj-kernel/src/builder/edge.rs +++ b/crates/fj-kernel/src/builder/edge.rs @@ -5,8 +5,9 @@ use crate::{ insert::Insert, objects::{Curve, Objects, Surface, Vertex, VerticesInNormalizedOrder}, partial::{ - MaybePartial, PartialCurve, PartialGlobalEdge, PartialGlobalVertex, - PartialHalfEdge, PartialSurfaceVertex, PartialVertex, + MaybePartial, MergeWith, PartialCurve, PartialGlobalEdge, + PartialGlobalVertex, PartialHalfEdge, PartialSurfaceVertex, + PartialVertex, }, storage::Handle, validate::ValidationError, @@ -135,8 +136,8 @@ impl HalfEdgeBuilder for PartialHalfEdge { let surface = self .curve() .surface() - .or_else(|| from_surface.surface()) - .or_else(|| to_surface.surface()) + .merge_with(from_surface.surface()) + .merge_with(to_surface.surface()) .expect("Can't infer line segment without a surface"); let points = [&from_surface, &to_surface].map(|vertex| { vertex diff --git a/crates/fj-kernel/src/builder/mod.rs b/crates/fj-kernel/src/builder/mod.rs index 1d96e73d7..7a7b4e70a 100644 --- a/crates/fj-kernel/src/builder/mod.rs +++ b/crates/fj-kernel/src/builder/mod.rs @@ -12,6 +12,7 @@ mod curve; mod cycle; mod edge; mod face; +mod surface; mod vertex; pub use self::{ @@ -22,5 +23,6 @@ pub use self::{ shell::ShellBuilder, sketch::SketchBuilder, solid::SolidBuilder, + surface::SurfaceBuilder, vertex::{GlobalVertexBuilder, SurfaceVertexBuilder, VertexBuilder}, }; diff --git a/crates/fj-kernel/src/builder/shell.rs b/crates/fj-kernel/src/builder/shell.rs index 343b45857..838295820 100644 --- a/crates/fj-kernel/src/builder/shell.rs +++ b/crates/fj-kernel/src/builder/shell.rs @@ -6,10 +6,13 @@ use iter_fixed::IntoIteratorFixed; use crate::{ algorithms::transform::TransformObject, - builder::{FaceBuilder, HalfEdgeBuilder}, + builder::{FaceBuilder, HalfEdgeBuilder, SurfaceBuilder}, insert::Insert, - objects::{Cycle, Face, FaceSet, HalfEdge, Objects, Shell, Surface}, - partial::{HasPartial, PartialCurve, PartialSurfaceVertex, PartialVertex}, + objects::{Cycle, Face, FaceSet, HalfEdge, Objects, Shell}, + partial::{ + HasPartial, PartialCurve, PartialSurface, PartialSurfaceVertex, + PartialVertex, + }, storage::Handle, }; @@ -78,9 +81,10 @@ impl<'a> ShellBuilder<'a> { .map(|vertex| vertex.global_form().position()); let c = a + [Z, Z, edge_length]; - self.objects - .surfaces - .insert(Surface::plane_from_points([a, b, c])) + PartialSurface::plane_from_points([a, b, c]) + .build(self.objects) + .unwrap() + .insert(self.objects) .unwrap() }) .collect::>(); diff --git a/crates/fj-kernel/src/builder/surface.rs b/crates/fj-kernel/src/builder/surface.rs new file mode 100644 index 000000000..cc1d57728 --- /dev/null +++ b/crates/fj-kernel/src/builder/surface.rs @@ -0,0 +1,36 @@ +use fj_math::{Line, Point, Vector}; + +use crate::{ + geometry::{path::GlobalPath, surface::SurfaceGeometry}, + partial::PartialSurface, +}; + +/// Builder API for [`PartialSurface`] +pub trait SurfaceBuilder { + /// Build a surface from its two axes + fn from_axes(u: GlobalPath, v: impl Into>) -> Self; + + /// Construct a plane from 3 points + fn plane_from_points(points: [impl Into>; 3]) -> Self; +} + +impl SurfaceBuilder for PartialSurface { + fn from_axes(u: GlobalPath, v: impl Into>) -> Self { + let v = v.into(); + + Self { + geometry: Some(SurfaceGeometry { u, v }), + } + } + + fn plane_from_points(points: [impl Into>; 3]) -> Self { + let [a, b, c] = points.map(Into::into); + + let u = GlobalPath::Line(Line::from_points([a, b])); + let v = c - a; + + Self { + geometry: Some(SurfaceGeometry { u, v }), + } + } +} diff --git a/crates/fj-kernel/src/builder/vertex.rs b/crates/fj-kernel/src/builder/vertex.rs index 879d5533f..9c3266b00 100644 --- a/crates/fj-kernel/src/builder/vertex.rs +++ b/crates/fj-kernel/src/builder/vertex.rs @@ -1,7 +1,8 @@ use fj_math::Point; use crate::{ - objects::{Curve, GlobalVertex, Surface}, + geometry::surface::SurfaceGeometry, + objects::{Curve, GlobalVertex}, partial::{ HasPartial, MaybePartial, PartialGlobalVertex, PartialSurfaceVertex, PartialVertex, @@ -44,7 +45,7 @@ pub trait GlobalVertexBuilder { /// Update partial global vertex from the given surface and position on it fn from_surface_and_position( - surface: &Surface, + surface: &SurfaceGeometry, position: impl Into>, ) -> Self; } @@ -59,9 +60,12 @@ impl GlobalVertexBuilder for PartialGlobalVertex { let path = curve.path.expect( "Need path to create `GlobalVertex` from curve and position", ); - let surface = curve.surface.expect( - "Need surface to create `GlobalVertex` from curve and position", - ); + let surface = curve + .surface + .expect( + "Need surface to create `GlobalVertex` from curve and position", + ) + .geometry(); let position_surface = path.point_from_path_coords(position); @@ -69,7 +73,7 @@ impl GlobalVertexBuilder for PartialGlobalVertex { } fn from_surface_and_position( - surface: &Surface, + surface: &SurfaceGeometry, position: impl Into>, ) -> Self { PartialGlobalVertex { diff --git a/crates/fj-kernel/src/geometry/mod.rs b/crates/fj-kernel/src/geometry/mod.rs new file mode 100644 index 000000000..654a3c108 --- /dev/null +++ b/crates/fj-kernel/src/geometry/mod.rs @@ -0,0 +1,4 @@ +//! Types that are tied to objects, but aren't objects themselves + +pub mod path; +pub mod surface; diff --git a/crates/fj-kernel/src/path.rs b/crates/fj-kernel/src/geometry/path.rs similarity index 100% rename from crates/fj-kernel/src/path.rs rename to crates/fj-kernel/src/geometry/path.rs diff --git a/crates/fj-kernel/src/geometry/surface.rs b/crates/fj-kernel/src/geometry/surface.rs new file mode 100644 index 000000000..a17abaaa5 --- /dev/null +++ b/crates/fj-kernel/src/geometry/surface.rs @@ -0,0 +1,81 @@ +//! The geometry that defines a surface + +use fj_math::{Line, Point, Vector}; + +use super::path::GlobalPath; + +/// The geometry that defines a surface +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct SurfaceGeometry { + /// The u-axis of the surface + pub u: GlobalPath, + + /// The v-axis of the surface + pub v: Vector<3>, +} + +impl SurfaceGeometry { + /// Convert a point in surface coordinates to model coordinates + pub fn point_from_surface_coords( + &self, + point: impl Into>, + ) -> Point<3> { + let point = point.into(); + self.u.point_from_path_coords([point.u]) + + self.path_to_line().vector_from_line_coords([point.v]) + } + + /// Convert a vector in surface coordinates to model coordinates + pub fn vector_from_surface_coords( + &self, + vector: impl Into>, + ) -> Vector<3> { + let vector = vector.into(); + self.u.vector_from_path_coords([vector.u]) + + self.path_to_line().vector_from_line_coords([vector.v]) + } + + fn path_to_line(&self) -> Line<3> { + Line::from_origin_and_direction(self.u.origin(), self.v) + } +} + +#[cfg(test)] +mod tests { + use fj_math::{Line, Point, Vector}; + use pretty_assertions::assert_eq; + + use crate::geometry::{path::GlobalPath, surface::SurfaceGeometry}; + + #[test] + fn point_from_surface_coords() { + let surface = SurfaceGeometry { + u: GlobalPath::Line(Line::from_origin_and_direction( + Point::from([1., 1., 1.]), + Vector::from([0., 2., 0.]), + )), + v: Vector::from([0., 0., 2.]), + }; + + assert_eq!( + surface.point_from_surface_coords([2., 4.]), + Point::from([1., 5., 9.]), + ); + } + + #[test] + fn vector_from_surface_coords() { + let surface = SurfaceGeometry { + u: GlobalPath::Line(Line::from_origin_and_direction( + Point::from([1., 0., 0.]), + Vector::from([0., 2., 0.]), + )), + v: Vector::from([0., 0., 2.]), + }; + + assert_eq!( + surface.vector_from_surface_coords([2., 4.]), + Vector::from([0., 4., 8.]), + ); + } +} diff --git a/crates/fj-kernel/src/lib.rs b/crates/fj-kernel/src/lib.rs index 607c7b11c..87091d3a1 100644 --- a/crates/fj-kernel/src/lib.rs +++ b/crates/fj-kernel/src/lib.rs @@ -89,10 +89,10 @@ pub mod algorithms; pub mod builder; +pub mod geometry; pub mod insert; pub mod iter; pub mod objects; pub mod partial; -pub mod path; pub mod storage; pub mod validate; diff --git a/crates/fj-kernel/src/objects/curve.rs b/crates/fj-kernel/src/objects/curve.rs index 54837aa5f..2d1b439ef 100644 --- a/crates/fj-kernel/src/objects/curve.rs +++ b/crates/fj-kernel/src/objects/curve.rs @@ -1,5 +1,5 @@ use crate::{ - path::SurfacePath, + geometry::path::SurfacePath, storage::{Handle, HandleWrapper}, }; diff --git a/crates/fj-kernel/src/objects/cycle.rs b/crates/fj-kernel/src/objects/cycle.rs index 412f07b50..91633ef83 100644 --- a/crates/fj-kernel/src/objects/cycle.rs +++ b/crates/fj-kernel/src/objects/cycle.rs @@ -3,7 +3,7 @@ use std::slice; use fj_interop::ext::SliceExt; use fj_math::{Scalar, Winding}; -use crate::{path::SurfacePath, storage::Handle}; +use crate::{geometry::path::SurfacePath, storage::Handle}; use super::{HalfEdge, Surface}; diff --git a/crates/fj-kernel/src/objects/mod.rs b/crates/fj-kernel/src/objects/mod.rs index d0406c0c9..221c245ef 100644 --- a/crates/fj-kernel/src/objects/mod.rs +++ b/crates/fj-kernel/src/objects/mod.rs @@ -100,7 +100,7 @@ use std::convert::Infallible; use fj_math::Vector; use crate::{ - path::GlobalPath, + geometry::{path::GlobalPath, surface::SurfaceGeometry}, storage::{Handle, Store}, validate::{ CycleValidationError, FaceValidationError, HalfEdgeValidationError, @@ -380,12 +380,18 @@ impl Default for Surfaces { fn default() -> Self { let store = Store::new(); - let xy_plane = - store.insert(Surface::new(GlobalPath::x_axis(), Vector::unit_y())); - let xz_plane = - store.insert(Surface::new(GlobalPath::x_axis(), Vector::unit_z())); - let yz_plane = - store.insert(Surface::new(GlobalPath::y_axis(), Vector::unit_z())); + let xy_plane = store.insert(Surface::new(SurfaceGeometry { + u: GlobalPath::x_axis(), + v: Vector::unit_y(), + })); + let xz_plane = store.insert(Surface::new(SurfaceGeometry { + u: GlobalPath::x_axis(), + v: Vector::unit_z(), + })); + let yz_plane = store.insert(Surface::new(SurfaceGeometry { + u: GlobalPath::y_axis(), + v: Vector::unit_z(), + })); Self { store, diff --git a/crates/fj-kernel/src/objects/surface.rs b/crates/fj-kernel/src/objects/surface.rs index 6da597626..0905a0bef 100644 --- a/crates/fj-kernel/src/objects/surface.rs +++ b/crates/fj-kernel/src/objects/surface.rs @@ -1,104 +1,19 @@ -use fj_math::{Line, Point, Vector}; - -use crate::path::GlobalPath; +use crate::geometry::surface::SurfaceGeometry; /// A two-dimensional shape #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct Surface { - u: GlobalPath, - v: Vector<3>, + geometry: SurfaceGeometry, } impl Surface { /// Construct a `Surface` from two paths that define its coordinate system - pub fn new(u: GlobalPath, v: impl Into>) -> Self { - let v = v.into(); - Self { u, v } + pub fn new(geometry: SurfaceGeometry) -> Self { + Self { geometry } } - /// Construct a plane from 3 points - pub fn plane_from_points(points: [impl Into>; 3]) -> Self { - let [a, b, c] = points.map(Into::into); - - let u = GlobalPath::Line(Line::from_points([a, b])); - let v = c - a; - - Self { u, v } - } - - /// Access the path that defines the u-coordinate of this surface - pub fn u(&self) -> GlobalPath { - self.u - } - - /// Access the path that defines the v-coordinate of this surface - pub fn v(&self) -> Vector<3> { - self.v - } - - /// Convert a point in surface coordinates to model coordinates - pub fn point_from_surface_coords( - &self, - point: impl Into>, - ) -> Point<3> { - let point = point.into(); - self.u.point_from_path_coords([point.u]) - + self.path_to_line().vector_from_line_coords([point.v]) - } - - /// Convert a vector in surface coordinates to model coordinates - pub fn vector_from_surface_coords( - &self, - vector: impl Into>, - ) -> Vector<3> { - let vector = vector.into(); - self.u.vector_from_path_coords([vector.u]) - + self.path_to_line().vector_from_line_coords([vector.v]) - } - - fn path_to_line(&self) -> Line<3> { - Line::from_origin_and_direction(self.u.origin(), self.v) - } -} - -#[cfg(test)] -mod tests { - use fj_math::{Line, Point, Vector}; - use pretty_assertions::assert_eq; - - use crate::path::GlobalPath; - - use super::Surface; - - #[test] - fn point_from_surface_coords() { - let swept = Surface { - u: GlobalPath::Line(Line::from_origin_and_direction( - Point::from([1., 1., 1.]), - Vector::from([0., 2., 0.]), - )), - v: Vector::from([0., 0., 2.]), - }; - - assert_eq!( - swept.point_from_surface_coords([2., 4.]), - Point::from([1., 5., 9.]), - ); - } - - #[test] - fn vector_from_surface_coords() { - let swept = Surface { - u: GlobalPath::Line(Line::from_origin_and_direction( - Point::from([1., 0., 0.]), - Vector::from([0., 2., 0.]), - )), - v: Vector::from([0., 0., 2.]), - }; - - assert_eq!( - swept.vector_from_surface_coords([2., 4.]), - Vector::from([0., 4., 8.]), - ); + /// Access the surface's geometry + pub fn geometry(&self) -> SurfaceGeometry { + self.geometry } } diff --git a/crates/fj-kernel/src/partial/maybe_partial.rs b/crates/fj-kernel/src/partial/maybe_partial.rs index 0fef15070..cad49c181 100644 --- a/crates/fj-kernel/src/partial/maybe_partial.rs +++ b/crates/fj-kernel/src/partial/maybe_partial.rs @@ -1,12 +1,12 @@ use fj_math::Point; use crate::{ + geometry::{path::SurfacePath, surface::SurfaceGeometry}, insert::Insert, objects::{ Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects, Surface, SurfaceVertex, Vertex, }, - path::SurfacePath, storage::Handle, validate::{Validate, ValidationError}, }; @@ -212,6 +212,16 @@ impl MaybePartial { } } +impl MaybePartial { + /// Access the geometry + pub fn geometry(&self) -> Option { + match self { + Self::Full(full) => Some(full.geometry()), + Self::Partial(partial) => partial.geometry, + } + } +} + impl MaybePartial { /// Access the position pub fn position(&self) -> Option> { diff --git a/crates/fj-kernel/src/partial/mod.rs b/crates/fj-kernel/src/partial/mod.rs index 2f81c0d25..19289522b 100644 --- a/crates/fj-kernel/src/partial/mod.rs +++ b/crates/fj-kernel/src/partial/mod.rs @@ -47,6 +47,7 @@ pub use self::{ cycle::PartialCycle, edge::{PartialGlobalEdge, PartialHalfEdge}, face::PartialFace, + surface::PartialSurface, vertex::{PartialGlobalVertex, PartialSurfaceVertex, PartialVertex}, }, traits::{HasPartial, Partial}, diff --git a/crates/fj-kernel/src/partial/objects/curve.rs b/crates/fj-kernel/src/partial/objects/curve.rs index f5670e27a..a82a7af2f 100644 --- a/crates/fj-kernel/src/partial/objects/curve.rs +++ b/crates/fj-kernel/src/partial/objects/curve.rs @@ -1,7 +1,7 @@ use crate::{ + geometry::path::SurfacePath, objects::{Curve, GlobalCurve, Objects, Surface}, partial::{MaybePartial, MergeWith, Mergeable}, - path::SurfacePath, storage::Handle, validate::ValidationError, }; diff --git a/crates/fj-kernel/src/partial/objects/mod.rs b/crates/fj-kernel/src/partial/objects/mod.rs index 917f53a56..a4acf77a6 100644 --- a/crates/fj-kernel/src/partial/objects/mod.rs +++ b/crates/fj-kernel/src/partial/objects/mod.rs @@ -2,17 +2,18 @@ pub mod curve; pub mod cycle; pub mod edge; pub mod face; +pub mod surface; pub mod vertex; use crate::objects::{ Curve, Cycle, Face, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, - Objects, SurfaceVertex, Vertex, + Objects, Surface, SurfaceVertex, Vertex, }; use super::{ HasPartial, MaybePartial, Partial, PartialCurve, PartialCycle, PartialFace, PartialGlobalCurve, PartialGlobalEdge, PartialGlobalVertex, - PartialHalfEdge, PartialSurfaceVertex, PartialVertex, + PartialHalfEdge, PartialSurface, PartialSurfaceVertex, PartialVertex, }; macro_rules! impl_traits { @@ -52,6 +53,7 @@ impl_traits!( GlobalEdge, PartialGlobalEdge; GlobalVertex, PartialGlobalVertex; HalfEdge, PartialHalfEdge; + Surface, PartialSurface; SurfaceVertex, PartialSurfaceVertex; Vertex, PartialVertex; ); diff --git a/crates/fj-kernel/src/partial/objects/surface.rs b/crates/fj-kernel/src/partial/objects/surface.rs new file mode 100644 index 000000000..29c4a0ba8 --- /dev/null +++ b/crates/fj-kernel/src/partial/objects/surface.rs @@ -0,0 +1,44 @@ +use crate::{ + geometry::surface::SurfaceGeometry, + objects::{Objects, Surface}, + partial::MergeWith, + validate::ValidationError, +}; + +/// A partial [`Surface`] +/// +/// See [`crate::partial`] for more information +#[derive(Clone, Debug, Default)] +pub struct PartialSurface { + /// The geometry that defines the [`Surface`] + pub geometry: Option, +} + +impl PartialSurface { + /// Build a full [`Surface`] from the partial surface + pub fn build(self, _: &Objects) -> Result { + let geometry = self + .geometry + .expect("Can't build `Surface` without geometry"); + + Ok(Surface::new(geometry)) + } +} + +impl MergeWith for PartialSurface { + fn merge_with(self, other: impl Into) -> Self { + let other = other.into(); + + Self { + geometry: self.geometry.merge_with(other.geometry), + } + } +} + +impl From<&Surface> for PartialSurface { + fn from(surface: &Surface) -> Self { + Self { + geometry: Some(surface.geometry()), + } + } +} diff --git a/crates/fj-kernel/src/partial/objects/vertex.rs b/crates/fj-kernel/src/partial/objects/vertex.rs index 111ddf7fc..75b6c7539 100644 --- a/crates/fj-kernel/src/partial/objects/vertex.rs +++ b/crates/fj-kernel/src/partial/objects/vertex.rs @@ -80,7 +80,7 @@ impl From<&Vertex> for PartialVertex { /// A partial [`SurfaceVertex`] /// /// See [`crate::partial`] for more information. -#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)] +#[derive(Clone, Debug, Default)] pub struct PartialSurfaceVertex { /// The position of the [`SurfaceVertex`] pub position: Option>, @@ -108,7 +108,8 @@ impl PartialSurfaceVertex { let global_form = self .global_form .merge_with(PartialGlobalVertex::from_surface_and_position( - &surface, position, + &surface.geometry(), + position, )) .into_full(objects)?; diff --git a/crates/fj-kernel/src/validate/vertex.rs b/crates/fj-kernel/src/validate/vertex.rs index 186fab353..0ed7318e6 100644 --- a/crates/fj-kernel/src/validate/vertex.rs +++ b/crates/fj-kernel/src/validate/vertex.rs @@ -158,6 +158,7 @@ impl SurfaceVertexValidationError { ) -> Result<(), Self> { let surface_position_as_global = surface_vertex .surface() + .geometry() .point_from_surface_coords(surface_vertex.position()); let global_position = surface_vertex.global_form().position();