diff --git a/crates/fj-kernel/src/algorithms/intersect/curve_face.rs b/crates/fj-kernel/src/algorithms/intersect/curve_face.rs index 051e5d87a..8e41209be 100644 --- a/crates/fj-kernel/src/algorithms/intersect/curve_face.rs +++ b/crates/fj-kernel/src/algorithms/intersect/curve_face.rs @@ -150,8 +150,9 @@ where #[cfg(test)] mod tests { use crate::{ - builder::CycleBuilder, geometry::curve::Curve, insert::Insert, - objects::Face, services::Services, + builder::{CycleBuilder, FaceBuilder}, + geometry::curve::Curve, + services::Services, }; use super::CurveFaceIntersection; @@ -177,16 +178,10 @@ mod tests { [ 1., -1.], ]; - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon(exterior_points) - .build(&mut services.objects) - .insert(&mut services.objects), - vec![CycleBuilder::polygon(interior_points) - .build(&mut services.objects) - .insert(&mut services.objects)], - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon(exterior_points)) + .with_interior(CycleBuilder::polygon(interior_points)) + .build(&mut services.objects); let expected = CurveFaceIntersection::from_intervals([[[1.], [2.]], [[4.], [5.]]]); diff --git a/crates/fj-kernel/src/algorithms/intersect/face_face.rs b/crates/fj-kernel/src/algorithms/intersect/face_face.rs index deff5c23b..10d748afa 100644 --- a/crates/fj-kernel/src/algorithms/intersect/face_face.rs +++ b/crates/fj-kernel/src/algorithms/intersect/face_face.rs @@ -61,8 +61,9 @@ mod tests { use pretty_assertions::assert_eq; use crate::{ - algorithms::intersect::CurveFaceIntersection, builder::CycleBuilder, - geometry::curve::Curve, insert::Insert, objects::Face, + algorithms::intersect::CurveFaceIntersection, + builder::{CycleBuilder, FaceBuilder}, + geometry::curve::Curve, services::Services, }; @@ -84,14 +85,9 @@ mod tests { services.objects.surfaces.xz_plane(), ] .map(|surface| { - Face::new( - surface, - CycleBuilder::polygon(points) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ) + FaceBuilder::new(surface) + .with_exterior(CycleBuilder::polygon(points)) + .build(&mut services.objects) }); let intersection = FaceFaceIntersection::compute([&a, &b]); @@ -115,14 +111,9 @@ mod tests { services.objects.surfaces.xz_plane(), ]; let [a, b] = surfaces.clone().map(|surface| { - Face::new( - surface, - CycleBuilder::polygon(points) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ) + FaceBuilder::new(surface) + .with_exterior(CycleBuilder::polygon(points)) + .build(&mut services.objects) }); let intersection = FaceFaceIntersection::compute([&a, &b]); diff --git a/crates/fj-kernel/src/algorithms/intersect/face_point.rs b/crates/fj-kernel/src/algorithms/intersect/face_point.rs index b9fff0978..1dcb4d579 100644 --- a/crates/fj-kernel/src/algorithms/intersect/face_point.rs +++ b/crates/fj-kernel/src/algorithms/intersect/face_point.rs @@ -138,9 +138,7 @@ mod tests { use crate::{ algorithms::intersect::{face_point::FacePointIntersection, Intersect}, - builder::CycleBuilder, - insert::Insert, - objects::Face, + builder::{CycleBuilder, FaceBuilder}, services::Services, }; @@ -148,14 +146,13 @@ mod tests { fn point_is_outside_face() { let mut services = Services::new(); - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([[0., 0.], [1., 1.], [0., 2.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([ + [0., 0.], + [1., 1.], + [0., 2.], + ])) + .build(&mut services.objects); let point = Point::from([2., 1.]); let intersection = (&face, &point).intersect(); @@ -166,14 +163,13 @@ mod tests { fn ray_hits_vertex_while_passing_outside() { let mut services = Services::new(); - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([[0., 0.], [2., 1.], [0., 2.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([ + [0., 0.], + [2., 1.], + [0., 2.], + ])) + .build(&mut services.objects); let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -187,14 +183,13 @@ mod tests { fn ray_hits_vertex_at_cycle_seam() { let mut services = Services::new(); - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([[4., 2.], [0., 4.], [0., 0.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([ + [4., 2.], + [0., 4.], + [0., 0.], + ])) + .build(&mut services.objects); let point = Point::from([1., 2.]); let intersection = (&face, &point).intersect(); @@ -208,14 +203,14 @@ mod tests { fn ray_hits_vertex_while_staying_inside() { let mut services = Services::new(); - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([[0., 0.], [2., 1.], [3., 0.], [3., 4.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([ + [0., 0.], + [2., 1.], + [3., 0.], + [3., 4.], + ])) + .build(&mut services.objects); let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -229,14 +224,14 @@ mod tests { fn ray_hits_parallel_edge_and_leaves_face_at_vertex() { let mut services = Services::new(); - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([[0., 0.], [2., 1.], [3., 1.], [0., 2.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([ + [0., 0.], + [2., 1.], + [3., 1.], + [0., 2.], + ])) + .build(&mut services.objects); let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -250,20 +245,15 @@ mod tests { fn ray_hits_parallel_edge_and_does_not_leave_face_there() { let mut services = Services::new(); - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([ + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([ [0., 0.], [2., 1.], [3., 1.], [4., 0.], [4., 5.], - ]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + ])) + .build(&mut services.objects); let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -277,14 +267,13 @@ mod tests { fn point_is_coincident_with_edge() { let mut services = Services::new(); - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([[0., 0.], [2., 0.], [0., 1.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([ + [0., 0.], + [2., 0.], + [0., 1.], + ])) + .build(&mut services.objects); let point = Point::from([1., 0.]); let intersection = (&face, &point).intersect(); @@ -304,14 +293,13 @@ mod tests { fn point_is_coincident_with_vertex() { let mut services = Services::new(); - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([[0., 0.], [1., 0.], [0., 1.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([ + [0., 0.], + [1., 0.], + [0., 1.], + ])) + .build(&mut services.objects); let point = Point::from([1., 0.]); let intersection = (&face, &point).intersect(); diff --git a/crates/fj-kernel/src/algorithms/intersect/ray_face.rs b/crates/fj-kernel/src/algorithms/intersect/ray_face.rs index a57f782b0..a8277410f 100644 --- a/crates/fj-kernel/src/algorithms/intersect/ray_face.rs +++ b/crates/fj-kernel/src/algorithms/intersect/ray_face.rs @@ -151,9 +151,7 @@ mod tests { }, transform::TransformObject, }, - builder::CycleBuilder, - insert::Insert, - objects::Face, + builder::{CycleBuilder, FaceBuilder}, services::Services, }; @@ -163,14 +161,14 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = Face::new( - services.objects.surfaces.yz_plane(), - CycleBuilder::polygon([[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) + .with_exterior(CycleBuilder::polygon([ + [-1., -1.], + [1., -1.], + [1., 1.], + [-1., 1.], + ])) + .build(&mut services.objects); let face = face.translate([-1., 0., 0.], &mut services.objects); assert_eq!((&ray, &face).intersect(), None); @@ -182,14 +180,14 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = Face::new( - services.objects.surfaces.yz_plane(), - CycleBuilder::polygon([[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) + .with_exterior(CycleBuilder::polygon([ + [-1., -1.], + [1., -1.], + [1., 1.], + [-1., 1.], + ])) + .build(&mut services.objects); let face = face.translate([1., 0., 0.], &mut services.objects); assert_eq!( @@ -204,14 +202,14 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = Face::new( - services.objects.surfaces.yz_plane(), - CycleBuilder::polygon([[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) + .with_exterior(CycleBuilder::polygon([ + [-1., -1.], + [1., -1.], + [1., 1.], + [-1., 1.], + ])) + .build(&mut services.objects); let face = face.translate([0., 0., 2.], &mut services.objects); assert_eq!((&ray, &face).intersect(), None); @@ -223,14 +221,14 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = Face::new( - services.objects.surfaces.yz_plane(), - CycleBuilder::polygon([[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) + .with_exterior(CycleBuilder::polygon([ + [-1., -1.], + [1., -1.], + [1., 1.], + [-1., 1.], + ])) + .build(&mut services.objects); let face = face.translate([1., 1., 0.], &mut services.objects); let edge = face @@ -250,14 +248,14 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = Face::new( - services.objects.surfaces.yz_plane(), - CycleBuilder::polygon([[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) + .with_exterior(CycleBuilder::polygon([ + [-1., -1.], + [1., -1.], + [1., 1.], + [-1., 1.], + ])) + .build(&mut services.objects); let face = face.translate([1., 1., 1.], &mut services.objects); let vertex = face @@ -280,14 +278,14 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([ + [-1., -1.], + [1., -1.], + [1., 1.], + [-1., 1.], + ])) + .build(&mut services.objects); assert_eq!( (&ray, &face).intersect(), @@ -301,14 +299,14 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([ + [-1., -1.], + [1., -1.], + [1., 1.], + [-1., 1.], + ])) + .build(&mut services.objects); let face = face.translate([0., 0., 1.], &mut services.objects); assert_eq!((&ray, &face).intersect(), None); diff --git a/crates/fj-kernel/src/algorithms/triangulate/mod.rs b/crates/fj-kernel/src/algorithms/triangulate/mod.rs index 14e7b6e04..28281353c 100644 --- a/crates/fj-kernel/src/algorithms/triangulate/mod.rs +++ b/crates/fj-kernel/src/algorithms/triangulate/mod.rs @@ -79,8 +79,7 @@ mod tests { use crate::{ algorithms::approx::{Approx, Tolerance}, - builder::CycleBuilder, - insert::Insert, + builder::{CycleBuilder, FaceBuilder}, objects::Face, services::Services, }; @@ -96,14 +95,9 @@ mod tests { let c = [2., 2.]; let d = [0., 1.]; - let face = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([a, b, c, d]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([a, b, c, d])) + .build(&mut services.objects); let a = Point::from(a).to_xyz(); let b = Point::from(b).to_xyz(); @@ -136,16 +130,10 @@ mod tests { let surface = services.objects.surfaces.xy_plane(); - let face = Face::new( - surface.clone(), - CycleBuilder::polygon([a, b, c, d]) - .build(&mut services.objects) - .insert(&mut services.objects), - vec![CycleBuilder::polygon([e, f, g, h]) - .build(&mut services.objects) - .insert(&mut services.objects)], - None, - ); + let face = FaceBuilder::new(surface.clone()) + .with_exterior(CycleBuilder::polygon([a, b, c, d])) + .with_interior(CycleBuilder::polygon([e, f, g, h])) + .build(&mut services.objects); let triangles = triangulate(face)?; @@ -200,14 +188,9 @@ mod tests { let surface = services.objects.surfaces.xy_plane(); - let face = Face::new( - surface.clone(), - CycleBuilder::polygon([a, b, c, d, e]) - .build(&mut services.objects) - .insert(&mut services.objects), - Vec::new(), - None, - ); + let face = FaceBuilder::new(surface.clone()) + .with_exterior(CycleBuilder::polygon([a, b, c, d, e])) + .build(&mut services.objects); let triangles = triangulate(face)?; diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs new file mode 100644 index 000000000..667dc4a62 --- /dev/null +++ b/crates/fj-kernel/src/builder/face.rs @@ -0,0 +1,58 @@ +use fj_interop::mesh::Color; + +use crate::{ + insert::Insert, + objects::{Face, Objects, Surface}, + services::Service, + storage::Handle, +}; + +use super::CycleBuilder; + +/// Builder API for [`Face`] +pub struct FaceBuilder { + surface: Handle, + exterior: CycleBuilder, + interiors: Vec, + color: Option, +} +impl FaceBuilder { + /// Create an instance of `FaceBuilder` + pub fn new(surface: Handle) -> Self { + Self { + surface, + exterior: CycleBuilder::new(), + interiors: Vec::new(), + color: None, + } + } + + /// Replace the face's exterior cycle + pub fn with_exterior(mut self, exterior: CycleBuilder) -> Self { + self.exterior = exterior; + self + } + + /// Add an interior cycle to the face + pub fn with_interior(mut self, interior: CycleBuilder) -> Self { + self.interiors.push(interior); + self + } + + /// Define the color of the face + pub fn with_color(mut self, color: Color) -> Self { + self.color = Some(color); + self + } + + /// Build the face + pub fn build(self, objects: &mut Service) -> Face { + let exterior = self.exterior.build(objects).insert(objects); + let interiors = self + .interiors + .into_iter() + .map(|cycle| cycle.build(objects).insert(objects)); + + Face::new(self.surface, exterior, interiors, self.color) + } +} diff --git a/crates/fj-kernel/src/builder/mod.rs b/crates/fj-kernel/src/builder/mod.rs index ef35bcfa3..5349098a1 100644 --- a/crates/fj-kernel/src/builder/mod.rs +++ b/crates/fj-kernel/src/builder/mod.rs @@ -3,5 +3,6 @@ // These are new-style builders that build on top of the partial object API. mod cycle; mod edge; +mod face; -pub use self::{cycle::CycleBuilder, edge::HalfEdgeBuilder}; +pub use self::{cycle::CycleBuilder, edge::HalfEdgeBuilder, face::FaceBuilder}; diff --git a/crates/fj-kernel/src/validate/face.rs b/crates/fj-kernel/src/validate/face.rs index fe3107183..556725030 100644 --- a/crates/fj-kernel/src/validate/face.rs +++ b/crates/fj-kernel/src/validate/face.rs @@ -72,8 +72,7 @@ impl FaceValidationError { mod tests { use crate::{ algorithms::reverse::Reverse, - builder::CycleBuilder, - insert::Insert, + builder::{CycleBuilder, FaceBuilder}, objects::Face, services::Services, validate::{FaceValidationError, Validate, ValidationError}, @@ -83,16 +82,18 @@ mod tests { fn face_invalid_interior_winding() -> anyhow::Result<()> { let mut services = Services::new(); - let valid = Face::new( - services.objects.surfaces.xy_plane(), - CycleBuilder::polygon([[0., 0.], [3., 0.], [0., 3.]]) - .build(&mut services.objects) - .insert(&mut services.objects), - vec![CycleBuilder::polygon([[1., 1.], [1., 2.], [2., 1.]]) - .build(&mut services.objects) - .insert(&mut services.objects)], - None, - ); + let valid = FaceBuilder::new(services.objects.surfaces.xy_plane()) + .with_exterior(CycleBuilder::polygon([ + [0., 0.], + [3., 0.], + [0., 3.], + ])) + .with_interior(CycleBuilder::polygon([ + [1., 1.], + [1., 2.], + [2., 1.], + ])) + .build(&mut services.objects); let invalid = { let interiors = valid .interiors()