From 5a7438278b3dd8bd76ccab918af5e2a857564120 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Fri, 29 Jul 2022 15:57:44 +0200 Subject: [PATCH 01/11] WIP Implement face/face intersection --- .../src/algorithms/intersection/face_face.rs | 36 +++++++++++++++++++ .../src/algorithms/intersection/mod.rs | 2 ++ 2 files changed, 38 insertions(+) create mode 100644 crates/fj-kernel/src/algorithms/intersection/face_face.rs diff --git a/crates/fj-kernel/src/algorithms/intersection/face_face.rs b/crates/fj-kernel/src/algorithms/intersection/face_face.rs new file mode 100644 index 000000000..737f7f192 --- /dev/null +++ b/crates/fj-kernel/src/algorithms/intersection/face_face.rs @@ -0,0 +1,36 @@ +use fj_math::Point; + +use crate::objects::{Curve, Face}; + +/// An intersection between two faces +pub struct FaceFaceIntersection { + /// The intersection curves, in surface coordinates of the respective face + pub local_intersection_curves: [Curve<2>; 2], + + /// The intersection curve, in global coordinates + pub global_intersection_curve: Curve<3>, + + /// The interval of this intersection, in curve coordinates + pub intersection_interval: [Point<1>; 2], +} + +impl FaceFaceIntersection { + /// Compute the intersections between two faces + pub fn compute( + face_a: &Face, + face_b: &Face, + ) -> impl Iterator { + // TASK: Implement. + let _ = face_a; + let _ = face_b; + std::iter::empty() + } +} + +#[cfg(test)] +mod tests { + #[test] + fn fail() { + panic!() + } +} diff --git a/crates/fj-kernel/src/algorithms/intersection/mod.rs b/crates/fj-kernel/src/algorithms/intersection/mod.rs index df03b6abd..d29eb7d99 100644 --- a/crates/fj-kernel/src/algorithms/intersection/mod.rs +++ b/crates/fj-kernel/src/algorithms/intersection/mod.rs @@ -2,12 +2,14 @@ mod curve_edge; mod curve_face; +mod face_face; mod line_segment; mod surface_surface; pub use self::{ curve_edge::CurveEdgeIntersection, curve_face::{CurveFaceIntersection, CurveFaceIntersectionList}, + face_face::FaceFaceIntersection, line_segment::LineSegmentIntersection, surface_surface::SurfaceSurfaceIntersection, }; From 2d788597d25325f2b5034829d40b466dd41a77bf Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 25 Jul 2022 15:25:46 +0200 Subject: [PATCH 02/11] Update order of `impl` blocks The `Deref` seems more closely associated with the type than a `From` implementation for another type, so it makes sense to put the `impl` block for the `Deref` closer to the type. --- crates/fj-kernel/src/builder/face.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs index e68d61dc4..850ca864b 100644 --- a/crates/fj-kernel/src/builder/face.rs +++ b/crates/fj-kernel/src/builder/face.rs @@ -57,12 +57,6 @@ impl FacePolygon { } } -impl From for Face { - fn from(polygon: FacePolygon) -> Self { - polygon.into_face() - } -} - impl Deref for FacePolygon { type Target = Face; @@ -70,3 +64,9 @@ impl Deref for FacePolygon { &self.face } } + +impl From for Face { + fn from(polygon: FacePolygon) -> Self { + polygon.into_face() + } +} From 67ad640a1fdec07ac5397a3ccceeeb2cdd6efab8 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 25 Jul 2022 15:25:22 +0200 Subject: [PATCH 03/11] Add `Cube` --- crates/fj-kernel/src/builder/mod.rs | 2 +- crates/fj-kernel/src/builder/solid.rs | 105 ++++++++++++++++++++++++-- 2 files changed, 100 insertions(+), 7 deletions(-) diff --git a/crates/fj-kernel/src/builder/mod.rs b/crates/fj-kernel/src/builder/mod.rs index 9edf20fac..e101bdc42 100644 --- a/crates/fj-kernel/src/builder/mod.rs +++ b/crates/fj-kernel/src/builder/mod.rs @@ -9,5 +9,5 @@ pub use self::{ cycle::CycleBuilder, edge::EdgeBuilder, face::{FaceBuilder, FacePolygon}, - solid::SolidBuilder, + solid::{Cube, SolidBuilder}, }; diff --git a/crates/fj-kernel/src/builder/solid.rs b/crates/fj-kernel/src/builder/solid.rs index 53dbb45ba..23a77e0f5 100644 --- a/crates/fj-kernel/src/builder/solid.rs +++ b/crates/fj-kernel/src/builder/solid.rs @@ -1,3 +1,5 @@ +use std::ops::Deref; + use fj_math::Scalar; use crate::{ @@ -13,14 +15,14 @@ impl SolidBuilder { pub fn cube_from_edge_length( &self, edge_length: impl Into, - ) -> Solid { - // Let's define a short-hand for half the edge length. We're going to - // need it a lot. - let h = edge_length.into() / 2.; + ) -> Cube { + // Let's define short-hands for some values. We're going to need them a + // lot. + let h = edge_length.into() / 2.; // half the edge length + const Z: Scalar = Scalar::ZERO; let points = [[-h, -h], [h, -h], [h, h], [-h, h]]; - const Z: Scalar = Scalar::ZERO; let planes = [ Surface::xy_plane().translate([Z, Z, -h]), // bottom Surface::xy_plane().translate([Z, Z, h]), // top @@ -32,7 +34,98 @@ impl SolidBuilder { let faces = planes.map(|plane| Face::build(plane).polygon_from_points(points)); + let [left_face, right_face, front_face, back_face, bottom_face, top_face] = + faces.clone().map(Into::into); + + let solid = Solid::new().with_faces(faces); + + Cube { + solid, + left_face, + right_face, + front_face, + back_face, + bottom_face, + top_face, + } + } +} + +/// A cube +#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct Cube { + solid: Solid, + + left_face: Face, + right_face: Face, + front_face: Face, + back_face: Face, + bottom_face: Face, + top_face: Face, +} + +impl Cube { + /// Access the left face of the cube + pub fn left_face(&self) -> &Face { + &self.left_face + } + + /// Access the right face of the cube + pub fn right_face(&self) -> &Face { + &self.right_face + } + + /// Access the front face of the cube + pub fn front_face(&self) -> &Face { + &self.front_face + } + + /// Access the back face of the cube + pub fn back_face(&self) -> &Face { + &self.back_face + } + + /// Access the bottom face of the cube + pub fn bottom_face(&self) -> &Face { + &self.bottom_face + } + + /// Access the top face of the cube + pub fn top_face(&self) -> &Face { + &self.top_face + } + + /// Consume the cube and return the [`Solid`] it wraps + pub fn into_solid(self) -> Solid { + self.solid + } +} + +impl Deref for Cube { + type Target = Solid; + + fn deref(&self) -> &Self::Target { + &self.solid + } +} + +impl From for Solid { + fn from(cube: Cube) -> Self { + cube.into_solid() + } +} + +impl TransformObject for Cube { + fn transform(mut self, transform: &fj_math::Transform) -> Self { + self.solid = self.solid.transform(transform); + + self.left_face = self.left_face.transform(transform); + self.right_face = self.right_face.transform(transform); + self.front_face = self.front_face.transform(transform); + self.back_face = self.back_face.transform(transform); + self.bottom_face = self.bottom_face.transform(transform); + self.top_face = self.top_face.transform(transform); - Solid::new().with_faces(faces) + self } } From 2053c67d000e529ead400644d0cabce15c78dc3c Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Wed, 27 Jul 2022 14:55:11 +0200 Subject: [PATCH 04/11] Add `Solid::update_face` --- crates/fj-kernel/src/objects/solid.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/fj-kernel/src/objects/solid.rs b/crates/fj-kernel/src/objects/solid.rs index ce54ecd25..667cbb7b7 100644 --- a/crates/fj-kernel/src/objects/solid.rs +++ b/crates/fj-kernel/src/objects/solid.rs @@ -46,6 +46,28 @@ impl Solid { self } + /// Update the given face using the provided closure + /// + /// # Panics + /// + /// Panics, if `face` does not exist in this solid. + /// + /// Panics, if the updated face is equal to one that already exists in the + /// solid. + pub fn update_face( + mut self, + face: &Face, + f: impl FnOnce(&Face) -> Face, + ) -> Self { + let face_exists = self.faces.remove(face); + assert!(face_exists); + + let updated_is_unique = self.faces.insert(f(face)); + assert!(updated_is_unique); + + self + } + /// Access the solid's faces pub fn faces(&self) -> impl Iterator { self.faces.iter() From 9ab07614e7b98e057d502fa4bcc137da0429f1b1 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Thu, 14 Jul 2022 15:02:05 +0200 Subject: [PATCH 05/11] WIP Add union algorithm --- crates/fj-kernel/src/algorithms/mod.rs | 2 + crates/fj-kernel/src/algorithms/union.rs | 153 +++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 crates/fj-kernel/src/algorithms/union.rs diff --git a/crates/fj-kernel/src/algorithms/mod.rs b/crates/fj-kernel/src/algorithms/mod.rs index 402d5ab9f..5f7b96f58 100644 --- a/crates/fj-kernel/src/algorithms/mod.rs +++ b/crates/fj-kernel/src/algorithms/mod.rs @@ -8,6 +8,7 @@ mod reverse; mod sweep; mod transform; mod triangulate; +mod union; pub mod intersection; @@ -17,4 +18,5 @@ pub use self::{ sweep::sweep, transform::{transform_faces, TransformObject}, triangulate::triangulate, + union::union, }; diff --git a/crates/fj-kernel/src/algorithms/union.rs b/crates/fj-kernel/src/algorithms/union.rs new file mode 100644 index 000000000..5df7a1740 --- /dev/null +++ b/crates/fj-kernel/src/algorithms/union.rs @@ -0,0 +1,153 @@ +use std::collections::BTreeSet; + +use crate::objects::Solid; + +use super::intersection::{ + CurveFaceIntersectionList, SurfaceSurfaceIntersection, +}; + +/// Computes the shape that is the union of the two provided shapes +pub fn union(a: impl Into, b: impl Into) -> Solid { + // TASK: Implement algorithm from "Boundary Representation Modelling + // Techniques", section 6.1.1 (pages 127 ff.). + + let a = a.into(); + let b = b.into(); + + let mut faces = BTreeSet::new(); + + // Check the faces of both shapes for intersections. + for face_a in a.faces() { + for face_b in b.faces() { + // First step in determining the intersections between two faces: + // Determine the intersection of their surfaces. + let intersection = SurfaceSurfaceIntersection::compute( + face_a.surface(), + face_b.surface(), + ); + + let intersection = match intersection { + Some(intersection) => intersection, + None => { + // We're not getting an intersection curve, which means the + // surfaces either don't intersect (which can only mean + // they're parallel), or they are coincident. + + // TASK: Handle surfaces being coincident. + continue; + } + }; + + let [curve_a, curve_b] = intersection.local_intersection_curves; + + // Check the curve's intersections against the faces. The result of + // this operation are the intersections between the faces. + let intersections_a = + CurveFaceIntersectionList::compute(&curve_a, face_a); + let intersections_b = + CurveFaceIntersectionList::compute(&curve_b, face_b); + + // Depending on which of the faces the two surface's intersection + // curve intersects with, we can draw conclusions about which of + // them we need to keep. + // + // If exactly one of the faces intersects, we know that this face is + // part of the boundary of the union. We add it to `faces`. + // + // If we're not adding a face here, that is not a definite decision. + // The face might still be added to `faces` in another loop + // iteration, as part of a comparison with another face. + + match (intersections_a.is_empty(), intersections_b.is_empty()) { + // Both faces intersect the intersection curve, which means they + // intersect each other. + (false, false) => { + // TASK: Figure out what this means. The faces need to be + // cut to size somehow. + } + + // Intersection curve intersects only one of the faces. + // + // TASK: This doesn't mean that that face should go into the + // union as-is. It means that some version of the face + // goes into the union, but if there's a proper + // intersection with another face, the face needs to be + // cut to size. + // + // Add it to a list of candidates that still can be + // modified instead. + (false, true) => { + faces.insert(face_a.clone()); + } + (true, false) => { + faces.insert(face_b.clone()); + } + + // Intersection curve intersects none of the faces. This can + // only happen if none of the faces is contained in a shell that + // the other face is a part of. They must be part of distinct + // solids, and both need to end up in the union. + (true, true) => { + faces.insert(face_a.clone()); + faces.insert(face_b.clone()); + } + } + + // TASK: Implement. + let _ = curve; + } + } + + Solid::new().with_faces(faces) +} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use crate::{ + algorithms::{union, TransformObject}, + objects::Solid, + }; + + #[test] + fn distinct() { + let a = Solid::build() + .cube_from_edge_length(1.) + .translate([-1., -1., -1.]); + let b = Solid::build() + .cube_from_edge_length(1.) + .translate([1., 1., 1.]); + + let mut all_faces = Vec::new(); + all_faces.extend(a.faces().cloned()); + all_faces.extend(b.faces().cloned()); + + let union = union(a, b); + + assert_eq!(union, Solid::new().with_faces(all_faces)); + } + + #[test] + fn a_contains_b() { + let a = Solid::build().cube_from_edge_length(2.); + let b = Solid::build().cube_from_edge_length(1.); + + let union = union(a.clone(), b); + + assert_eq!(union, a.into_solid()); + } + + #[test] + fn b_contains_a() { + let a = Solid::build().cube_from_edge_length(1.); + let b = Solid::build().cube_from_edge_length(2.); + + let union = union(a, b.clone()); + + assert_eq!(union, b.into_solid()); + } + + // TASK: intersecting, broken edges in a + // TASK: intersection, broken edges in b +} From f6dcf6a28b4cf26c359e426579f6dde5bb5eb8d1 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Wed, 27 Jul 2022 14:55:23 +0200 Subject: [PATCH 06/11] WIP --- crates/fj-kernel/src/algorithms/union.rs | 112 ++++++++++++++++++++++- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/crates/fj-kernel/src/algorithms/union.rs b/crates/fj-kernel/src/algorithms/union.rs index 5df7a1740..67a380027 100644 --- a/crates/fj-kernel/src/algorithms/union.rs +++ b/crates/fj-kernel/src/algorithms/union.rs @@ -62,8 +62,7 @@ pub fn union(a: impl Into, b: impl Into) -> Solid { // Both faces intersect the intersection curve, which means they // intersect each other. (false, false) => { - // TASK: Figure out what this means. The faces need to be - // cut to size somehow. + // Nothing to do here. We'll resume after the `match`. } // Intersection curve intersects only one of the faces. @@ -78,9 +77,11 @@ pub fn union(a: impl Into, b: impl Into) -> Solid { // modified instead. (false, true) => { faces.insert(face_a.clone()); + continue; } (true, false) => { faces.insert(face_b.clone()); + continue; } // Intersection curve intersects none of the faces. This can @@ -90,11 +91,45 @@ pub fn union(a: impl Into, b: impl Into) -> Solid { (true, true) => { faces.insert(face_a.clone()); faces.insert(face_b.clone()); + continue; } } + // If we reach this point, we have two faces that intersect each + // other. Merge the intersection lists, then update the faces as + // appropriate. + let intersections = intersections_a.merge(&intersections_b); + + // TASK: For a start, we can ignore all those tasks here, and try to + // get the expected interior cycle in there. Then worry about + // making the diff between the actual and expected shapes + // smaller, as a next step. + for face in [face_a, face_b] { + // TASK: Handle the case where the intersections don't cross any + // edges of the face. This means, the intersection is part + // of an interior cycle. + // + // At least in some cases. The problem here is that I + // can't really tell per-edge. An edge might be on the + // inside of the face, so it might *look* like it should + // be part of an interior cycle, but then other + // intersections connect that edge to the outside. + // TASK: Handle the case where the intersections *do* cross + // edges of the face. This must result in these edges + // being shortened. + + // TASK: I probably need to move this piece of code outside of + // the inner loop. I think we need to store the + // intersections per-face, then merge *all* intersections + // of a face, in some way, to cut the face to size. + + // TASK: Implement. + let _ = face; + } + // TASK: Implement. let _ = curve; + let _ = intersections; } } @@ -107,7 +142,7 @@ mod tests { use crate::{ algorithms::{union, TransformObject}, - objects::Solid, + objects::{Cycle, Face, Solid}, }; #[test] @@ -148,6 +183,75 @@ mod tests { assert_eq!(union, b.into_solid()); } - // TASK: intersecting, broken edges in a + #[test] + fn intersecting_with_broken_edges_in_a() { + let a = Solid::build() + .cube_from_edge_length(1.) + .translate([0., 0., 1.]); + let b = Solid::build().cube_from_edge_length(2.); + + let union = union(a.clone(), b.clone()); + + let expected = { + // The face where the two cubes connect. + let connecting_face = b.top_face().clone(); + + // Build the smaller cube that attaches to the larger cube. Since + // `a` intersects with `b`, we need to cut it down here. + let smaller_cube = { + let left = a.left_face().clone(); + let right = a.right_face().clone(); + let front = a.front_face().clone(); + let back = a.back_face().clone(); + + // Let's create some shorthands for values we're going to need a + // lot. + let x = 0.5; // half the width of the smaller cube + let y = 0.25; // half the height of the smaller cube + + let updated_faces = + [&left, &right, &front, &back].into_iter().map(|face| { + ( + face, + Face::build(*face.surface()) + .polygon_from_points([ + [-x, -y], + [x, -y], + [x, y], + [-x, y], + ]) + .into_face(), + ) + }); + + let mut smaller_cube = a.into_solid(); + for (original, updated) in updated_faces { + smaller_cube = + smaller_cube.update_face(original, |_| updated); + } + + smaller_cube + }; + + // Now that we have the smaller cube, we can connect it to the + // larger one. + b.into_solid() + .update_face(&connecting_face, |face| { + // Add a polygon to the connecting face, where the smaller + // cube attaches. + let polygon = Cycle::build(*face.surface()) + .polygon_from_points([ + [0.5, 0.5], + [1.5, 0.5], + [1.5, 1.5], + [0.5, 1.5], + ]); + face.clone().with_interiors([polygon]) + }) + .with_faces(smaller_cube.into_faces()) + }; + assert_eq!(union, expected); + } + // TASK: intersection, broken edges in b } From cbc98bb8d2572fe4ae4c4312716daef5b8247e7d Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Thu, 14 Jul 2022 15:02:05 +0200 Subject: [PATCH 07/11] WIP --- crates/fj-kernel/src/algorithms/union.rs | 28 ++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/crates/fj-kernel/src/algorithms/union.rs b/crates/fj-kernel/src/algorithms/union.rs index 67a380027..53a0e2ce0 100644 --- a/crates/fj-kernel/src/algorithms/union.rs +++ b/crates/fj-kernel/src/algorithms/union.rs @@ -1,6 +1,9 @@ use std::collections::BTreeSet; -use crate::objects::Solid; +use crate::{ + local::Local, + objects::{Edge, GlobalVertex, Solid, Vertex, VerticesOfEdge}, +}; use super::intersection::{ CurveFaceIntersectionList, SurfaceSurfaceIntersection, @@ -127,9 +130,26 @@ pub fn union(a: impl Into, b: impl Into) -> Solid { let _ = face; } - // TASK: Implement. - let _ = curve; - let _ = intersections; + for [start, end] in intersections { + let curve = intersection.global_intersection_curve; + + let [start_global, end_global] = [start, end].map(|point| { + let position = curve.point_from_curve_coords(point); + GlobalVertex::from_position(position) + }); + + let vertices = VerticesOfEdge::from_vertices([ + Vertex::new(start, start_global), + Vertex::new(end, end_global), + ]); + + let edge_a = Edge::new(Local::new(curve_a, curve), vertices); + let edge_b = Edge::new(Local::new(curve_b, curve), vertices); + + // TASK: Implement. + let _ = edge_a; + let _ = edge_b; + } } } From aaf815abed24612617731afbea5dc08a5ca8fb3a Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Fri, 29 Jul 2022 14:49:51 +0200 Subject: [PATCH 08/11] WIP Add tasks --- crates/fj-kernel/src/algorithms/union.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/fj-kernel/src/algorithms/union.rs b/crates/fj-kernel/src/algorithms/union.rs index 53a0e2ce0..29f14f4d2 100644 --- a/crates/fj-kernel/src/algorithms/union.rs +++ b/crates/fj-kernel/src/algorithms/union.rs @@ -133,6 +133,10 @@ pub fn union(a: impl Into, b: impl Into) -> Solid { for [start, end] in intersections { let curve = intersection.global_intersection_curve; + // TASK: This conversion isn't right. It can result in slightly + // different global vertices for different edges, even + // though those might be where those edges connect, and + // thus supposed to be exactly the same. let [start_global, end_global] = [start, end].map(|point| { let position = curve.point_from_curve_coords(point); GlobalVertex::from_position(position) @@ -146,6 +150,13 @@ pub fn union(a: impl Into, b: impl Into) -> Solid { let edge_a = Edge::new(Local::new(curve_a, curve), vertices); let edge_b = Edge::new(Local::new(curve_b, curve), vertices); + // TASK: Even if the conversion to `GlobalVertex` above weren't + // a problem, what would I even do with these edges I + // created? They need to end up in cycles, but at this + // point, I have no way of knowing which cycles those are + // going to be, and in what order the edges need to go in + // there. + // TASK: Implement. let _ = edge_a; let _ = edge_b; From 7d41ece7205ade6f8d75ffaa772dc56aa47b0c65 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Fri, 6 May 2022 14:46:28 +0200 Subject: [PATCH 09/11] Add `fj::Union` --- crates/fj-operations/src/lib.rs | 11 ++++++++ crates/fj-operations/src/union.rs | 45 +++++++++++++++++++++++++++++++ crates/fj/src/lib.rs | 5 ++++ crates/fj/src/union.rs | 30 +++++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 crates/fj-operations/src/union.rs create mode 100644 crates/fj/src/union.rs diff --git a/crates/fj-operations/src/lib.rs b/crates/fj-operations/src/lib.rs index 9105f7baf..dfb804b69 100644 --- a/crates/fj-operations/src/lib.rs +++ b/crates/fj-operations/src/lib.rs @@ -23,6 +23,7 @@ mod group; mod sketch; mod sweep; mod transform; +mod union; use fj_interop::debug::DebugInfo; use fj_kernel::{ @@ -84,6 +85,15 @@ impl Shape for fj::Shape { Self::Transform(shape) => { shape.compute_brep(config, tolerance, debug_info) } + Self::Union(shape) => validate( + shape + .compute_brep(config, tolerance, debug_info)? + .into_inner() + .into_faces() + .into_iter() + .collect(), + config, + ), } } @@ -93,6 +103,7 @@ impl Shape for fj::Shape { Self::Group(shape) => shape.bounding_volume(), Self::Sweep(shape) => shape.bounding_volume(), Self::Transform(shape) => shape.bounding_volume(), + Self::Union(shape) => shape.bounding_volume(), } } } diff --git a/crates/fj-operations/src/union.rs b/crates/fj-operations/src/union.rs new file mode 100644 index 000000000..a964c7d3e --- /dev/null +++ b/crates/fj-operations/src/union.rs @@ -0,0 +1,45 @@ +use fj_interop::debug::DebugInfo; +use fj_kernel::{ + algorithms::{union, Tolerance}, + objects::Solid, + validation::{validate, Validated, ValidationConfig, ValidationError}, +}; +use fj_math::Aabb; + +use super::Shape; + +impl Shape for fj::Union { + type Brep = Solid; + + fn compute_brep( + &self, + config: &ValidationConfig, + tolerance: Tolerance, + debug_info: &mut DebugInfo, + ) -> Result, ValidationError> { + // Can be cleaned up, once `each_ref` is stable: + // https://doc.rust-lang.org/std/primitive.array.html#method.each_ref + let [a, b] = self.shapes(); + let shapes_ref = [a, b]; + + // Can be cleaned up, once `try_map` is stable: + // https://doc.rust-lang.org/std/primitive.array.html#method.try_map + let [a, b] = shapes_ref + .map(|shape| shape.compute_brep(config, tolerance, debug_info)); + let shapes = [a?, b?]; + let [a, b] = shapes.map(|shape| shape.into_inner()); + + let a = Solid::from_faces(a); + let b = Solid::from_faces(b); + + let union = union(a, b); + validate(union, config) + } + + fn bounding_volume(&self) -> Aabb<3> { + // This is a conservative estimate of the bounding box: It's never going + // to be bigger than the bounding box of the original shape that another + // is being subtracted from. + self.shapes()[0].bounding_volume() + } +} diff --git a/crates/fj/src/lib.rs b/crates/fj/src/lib.rs index 74a717750..ac945cc69 100644 --- a/crates/fj/src/lib.rs +++ b/crates/fj/src/lib.rs @@ -25,9 +25,11 @@ mod group; mod shape_2d; mod sweep; mod transform; +mod union; pub use self::{ angle::*, group::Group, shape_2d::*, sweep::Sweep, transform::Transform, + union::Union, }; pub use fj_proc::*; #[cfg(feature = "serde")] @@ -49,4 +51,7 @@ pub enum Shape { /// A transformed 3-dimensional shape Transform(Box), + + /// A union of two shapes + Union(Box), } diff --git a/crates/fj/src/union.rs b/crates/fj/src/union.rs new file mode 100644 index 000000000..3205b0ef4 --- /dev/null +++ b/crates/fj/src/union.rs @@ -0,0 +1,30 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::Shape; + +/// A union between two shapes +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(C)] +pub struct Union { + shapes: [Shape; 2], +} + +impl Union { + /// Create a `Union` from two shapes + pub fn from_shapes(shapes: [Shape; 2]) -> Self { + Self { shapes } + } + + /// Access the shapes that make up the union + pub fn shapes(&self) -> &[Shape; 2] { + &self.shapes + } +} + +impl From for Shape { + fn from(shape: Union) -> Self { + Self::Union(Box::new(shape)) + } +} From ae7591df0677f8121babd9308dc50a2af399e1d1 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 11 Jul 2022 16:25:58 +0200 Subject: [PATCH 10/11] Add convenient syntax for `fj::Union` --- crates/fj/src/syntax.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/fj/src/syntax.rs b/crates/fj/src/syntax.rs index fb887191b..fdfc2565c 100644 --- a/crates/fj/src/syntax.rs +++ b/crates/fj/src/syntax.rs @@ -131,3 +131,28 @@ where } } } + +/// Convenient syntax to create an [`fj::Union`] +/// +/// [`fj::Union`]: crate::Union +pub trait Union { + /// Create a difference between `self` and `other` + fn union(&self, other: &Other) -> crate::Union + where + Other: Clone + Into; +} + +impl Union for T +where + T: Clone + Into, +{ + fn union(&self, other: &Other) -> crate::Union + where + Other: Clone + Into, + { + let a = self.clone().into(); + let b = other.clone().into(); + + crate::Union::from_shapes([a, b]) + } +} From 4338d9c344a766f4d198c6c0a6b10e09728f4f40 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Fri, 6 May 2022 19:06:58 +0200 Subject: [PATCH 11/11] WIP Add union model Screenshot is missing. --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + models/union/Cargo.toml | 10 ++++++++++ models/union/README.md | 11 +++++++++++ models/union/src/lib.rs | 21 +++++++++++++++++++++ 5 files changed, 50 insertions(+) create mode 100644 models/union/Cargo.toml create mode 100644 models/union/README.md create mode 100644 models/union/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 4b914aefd..5fb563fc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3067,6 +3067,13 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +[[package]] +name = "union" +version = "0.1.0" +dependencies = [ + "fj", +] + [[package]] name = "url" version = "2.2.2" diff --git a/Cargo.toml b/Cargo.toml index e542aa91f..00c0deff7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "models/spacer", "models/star", "models/test", + "models/union", "tools/export-validator", "tools/release-operator", diff --git a/models/union/Cargo.toml b/models/union/Cargo.toml new file mode 100644 index 000000000..9d91ae401 --- /dev/null +++ b/models/union/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "union" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies.fj] +path = "../../crates/fj" diff --git a/models/union/README.md b/models/union/README.md new file mode 100644 index 000000000..33d46c818 --- /dev/null +++ b/models/union/README.md @@ -0,0 +1,11 @@ +# Fornjot - Union + +A model that demonstrates unions of bodies. + +To display this model, run the following from the repository root: +``` sh +cargo run -- --model union +``` + +**TASK: Add screenshot.** +![Screenshot of the union model](union.png) diff --git a/models/union/src/lib.rs b/models/union/src/lib.rs new file mode 100644 index 000000000..4576b8346 --- /dev/null +++ b/models/union/src/lib.rs @@ -0,0 +1,21 @@ +use std::collections::HashMap; + +use fj::syntax::*; + +#[no_mangle] +pub extern "C" fn model(_: &HashMap) -> fj::Shape { + #[rustfmt::skip] + let points = vec![ + [-0.5, -0.5], + [ 0.5, -0.5], + [ 0.5, 0.5], + [-0.5, 0.5], + ]; + + let a = fj::Sketch::from_points(points).sweep([0., 0., 1.]); + let b = a.translate([0.5, 0.5, 0.5]); + + let union = a.union(&b); + + union.into() +}