diff --git a/crates/fj-kernel/src/operations/build/mod.rs b/crates/fj-kernel/src/operations/build/mod.rs index be6dfed0e3..8b0bd25210 100644 --- a/crates/fj-kernel/src/operations/build/mod.rs +++ b/crates/fj-kernel/src/operations/build/mod.rs @@ -4,6 +4,6 @@ mod surface; pub use self::{ face::{BuildFace, Triangle}, - shell::BuildShell, + shell::{BuildShell, Tetrahedron}, surface::BuildSurface, }; diff --git a/crates/fj-kernel/src/operations/build/shell.rs b/crates/fj-kernel/src/operations/build/shell.rs index 6d4a917f1b..a5b54957a7 100644 --- a/crates/fj-kernel/src/operations/build/shell.rs +++ b/crates/fj-kernel/src/operations/build/shell.rs @@ -4,6 +4,7 @@ use crate::{ objects::{Face, Objects, Shell}, operations::Insert, services::Service, + storage::Handle, }; use super::{BuildFace, Triangle}; @@ -14,28 +15,62 @@ pub trait BuildShell { fn tetrahedron( points: [impl Into>; 4], objects: &mut Service, - ) -> Shell { + ) -> Tetrahedron { let [a, b, c, d] = points.map(Into::into); let Triangle { - face: base, + face: face_abc, edges: [ab, bc, ca], } = Face::triangle([a, b, c], [None, None, None], objects); let Triangle { - face: side_a, + face: face_abd, edges: [_, bd, da], } = Face::triangle([a, b, d], [Some(ab), None, None], objects); let Triangle { - face: side_b, + face: face_cad, edges: [_, _, dc], } = Face::triangle([c, a, d], [Some(ca), Some(da), None], objects); - let Triangle { face: side_c, .. } = + let Triangle { face: face_bcd, .. } = Face::triangle([b, c, d], [Some(bc), Some(dc), Some(bd)], objects); - let faces = - [base, side_a, side_b, side_c].map(|face| face.insert(objects)); - Shell::new(faces) + let faces = [face_abc, face_abd, face_cad, face_bcd] + .map(|face| face.insert(objects)); + let shell = Shell::new(faces.clone()); + + let [face_abc, face_abd, face_cad, face_bcd] = faces; + + Tetrahedron { + shell, + face_abc, + face_abd, + face_cad, + face_bcd, + } } } impl BuildShell for Shell {} + +/// A tetrahedron +/// +/// A tetrahedron is constructed from 4 points and has 4 faces. For the purpose +/// of naming the fields of this struct, the points are named `a`, `b`, `c`, and +/// `d`, in the order in which they are passed. +/// +/// Returned by [`BuildShell::tetrahedron`]. +pub struct Tetrahedron { + /// The shell that forms the tetrahedron + pub shell: Shell, + + /// The face formed by the points `a`, `b`, and `c`. + pub face_abc: Handle, + + /// The face formed by the points `a`, `b`, and `d`. + pub face_abd: Handle, + + /// The face formed by the points `c`, `a`, and `d`. + pub face_cad: Handle, + + /// The face formed by the points `b`, `c`, and `d`. + pub face_bcd: Handle, +} diff --git a/crates/fj-kernel/src/operations/mod.rs b/crates/fj-kernel/src/operations/mod.rs index 0cfabab4c0..6067f1735f 100644 --- a/crates/fj-kernel/src/operations/mod.rs +++ b/crates/fj-kernel/src/operations/mod.rs @@ -2,8 +2,10 @@ mod build; mod insert; +mod update; pub use self::{ - build::{BuildFace, BuildShell, BuildSurface, Triangle}, + build::{BuildFace, BuildShell, BuildSurface, Tetrahedron, Triangle}, insert::Insert, + update::{UpdateCycle, UpdateFace, UpdateHalfEdge, UpdateShell}, }; diff --git a/crates/fj-kernel/src/operations/update/cycle.rs b/crates/fj-kernel/src/operations/update/cycle.rs new file mode 100644 index 0000000000..43e8d441b5 --- /dev/null +++ b/crates/fj-kernel/src/operations/update/cycle.rs @@ -0,0 +1,32 @@ +use crate::{ + objects::{Cycle, HalfEdge}, + storage::Handle, +}; + +/// Update a [`Cycle`] +pub trait UpdateCycle { + /// Update a half-edge of the cycle + fn update_half_edge( + &self, + index: usize, + f: impl FnMut(&Handle) -> Handle, + ) -> Cycle; +} + +impl UpdateCycle for Cycle { + fn update_half_edge( + &self, + index: usize, + mut f: impl FnMut(&Handle) -> Handle, + ) -> Cycle { + let half_edges = self.half_edges().enumerate().map(|(i, cycle)| { + if i == index { + f(cycle) + } else { + cycle.clone() + } + }); + + Cycle::new(half_edges) + } +} diff --git a/crates/fj-kernel/src/operations/update/edge.rs b/crates/fj-kernel/src/operations/update/edge.rs new file mode 100644 index 0000000000..6a1f0f873c --- /dev/null +++ b/crates/fj-kernel/src/operations/update/edge.rs @@ -0,0 +1,21 @@ +use crate::{ + objects::{GlobalEdge, HalfEdge}, + storage::Handle, +}; + +/// Update a [`HalfEdge`] +pub trait UpdateHalfEdge { + /// Update the global form of the half-edge + fn update_global_form(&self, global_form: Handle) -> HalfEdge; +} + +impl UpdateHalfEdge for HalfEdge { + fn update_global_form(&self, global_form: Handle) -> HalfEdge { + HalfEdge::new( + self.curve(), + self.boundary(), + self.start_vertex().clone(), + global_form, + ) + } +} diff --git a/crates/fj-kernel/src/operations/update/face.rs b/crates/fj-kernel/src/operations/update/face.rs new file mode 100644 index 0000000000..c4e27ebcde --- /dev/null +++ b/crates/fj-kernel/src/operations/update/face.rs @@ -0,0 +1,29 @@ +use crate::{ + objects::{Cycle, Face}, + storage::Handle, +}; + +/// Update a [`Face`] +pub trait UpdateFace { + /// Update the exterior of the face + fn update_exterior( + &self, + f: impl FnOnce(&Handle) -> Handle, + ) -> Face; +} + +impl UpdateFace for Face { + fn update_exterior( + &self, + f: impl FnOnce(&Handle) -> Handle, + ) -> Face { + let exterior = f(self.exterior()); + + Face::new( + self.surface().clone(), + exterior, + self.interiors().cloned(), + self.color(), + ) + } +} diff --git a/crates/fj-kernel/src/operations/update/mod.rs b/crates/fj-kernel/src/operations/update/mod.rs new file mode 100644 index 0000000000..73f5d08744 --- /dev/null +++ b/crates/fj-kernel/src/operations/update/mod.rs @@ -0,0 +1,9 @@ +mod cycle; +mod edge; +mod face; +mod shell; + +pub use self::{ + cycle::UpdateCycle, edge::UpdateHalfEdge, face::UpdateFace, + shell::UpdateShell, +}; diff --git a/crates/fj-kernel/src/operations/update/shell.rs b/crates/fj-kernel/src/operations/update/shell.rs new file mode 100644 index 0000000000..f8717e6669 --- /dev/null +++ b/crates/fj-kernel/src/operations/update/shell.rs @@ -0,0 +1,45 @@ +use crate::{ + objects::{Face, Shell}, + storage::Handle, +}; + +/// Update a [`Shell`] +pub trait UpdateShell { + /// Update a face of the shell + fn update_face( + &self, + handle: &Handle, + f: impl FnMut(&Handle) -> Handle, + ) -> Shell; + + /// Remove a face from the shell + fn remove_face(&self, handle: &Handle) -> Shell; +} + +impl UpdateShell for Shell { + fn update_face( + &self, + handle: &Handle, + mut f: impl FnMut(&Handle) -> Handle, + ) -> Shell { + let faces = self.faces().into_iter().map(|face| { + if face.id() == handle.id() { + f(face) + } else { + face.clone() + } + }); + + Shell::new(faces) + } + + fn remove_face(&self, handle: &Handle) -> Shell { + let faces = self + .faces() + .into_iter() + .filter(|face| face.id() == handle.id()) + .cloned(); + + Shell::new(faces) + } +} diff --git a/crates/fj-kernel/src/validate/shell.rs b/crates/fj-kernel/src/validate/shell.rs index 1e4abccaa2..11d2815b98 100644 --- a/crates/fj-kernel/src/validate/shell.rs +++ b/crates/fj-kernel/src/validate/shell.rs @@ -193,9 +193,11 @@ impl ShellValidationError { mod tests { use crate::{ assert_contains_err, - builder::{CycleBuilder, FaceBuilder}, - objects::Shell, - operations::{BuildShell, Insert}, + objects::{GlobalEdge, Shell}, + operations::{ + BuildShell, Insert, UpdateCycle, UpdateFace, UpdateHalfEdge, + UpdateShell, + }, services::Services, validate::{shell::ShellValidationError, Validate, ValidationError}, }; @@ -208,31 +210,22 @@ mod tests { [[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]], &mut services.objects, ); - let invalid = { - let face1 = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon([ - [0., 0.], - [0., 1.], - [1., 1.], - [1., 0.], - ])) - .build(&mut services.objects) - .insert(&mut services.objects); - - let face2 = FaceBuilder::new(services.objects.surfaces.xz_plane()) - .with_exterior(CycleBuilder::polygon([ - [0., 0.], - [0., 1.], - [1., 1.], - [1., 0.], - ])) - .build(&mut services.objects) - .insert(&mut services.objects); - - Shell::new([face1, face2]) - }; + let invalid = valid.shell.update_face(&valid.face_abc, |face| { + face.update_exterior(|cycle| { + cycle + .update_half_edge(0, |half_edge| { + let global_form = + GlobalEdge::new().insert(&mut services.objects); + half_edge + .update_global_form(global_form) + .insert(&mut services.objects) + }) + .insert(&mut services.objects) + }) + .insert(&mut services.objects) + }); - valid.validate_and_return_first_error()?; + valid.shell.validate_and_return_first_error()?; assert_contains_err!( invalid, ValidationError::Shell( @@ -250,21 +243,9 @@ mod tests { [[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]], &mut services.objects, ); - let invalid = { - // Shell with single face is not watertight - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon([ - [0., 0.], - [0., 1.], - [1., 1.], - [1., 0.], - ])) - .build(&mut services.objects) - .insert(&mut services.objects); - Shell::new([face]) - }; + let invalid = valid.shell.remove_face(&valid.face_abc); - valid.validate_and_return_first_error()?; + valid.shell.validate_and_return_first_error()?; assert_contains_err!( invalid, ValidationError::Shell(ShellValidationError::NotWatertight)