Skip to content

Commit

Permalink
Merge pull request #1719 from hannobraun/operations
Browse files Browse the repository at this point in the history
Add "update" operations; use them to clean up `Shell` validation tests
  • Loading branch information
hannobraun authored Mar 23, 2023
2 parents f7b4ca8 + b339ab7 commit 7d666ae
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 51 deletions.
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/operations/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ mod surface;

pub use self::{
face::{BuildFace, Triangle},
shell::BuildShell,
shell::{BuildShell, Tetrahedron},
surface::BuildSurface,
};
51 changes: 43 additions & 8 deletions crates/fj-kernel/src/operations/build/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
objects::{Face, Objects, Shell},
operations::Insert,
services::Service,
storage::Handle,
};

use super::{BuildFace, Triangle};
Expand All @@ -14,28 +15,62 @@ pub trait BuildShell {
fn tetrahedron(
points: [impl Into<Point<3>>; 4],
objects: &mut Service<Objects>,
) -> 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<Face>,

/// The face formed by the points `a`, `b`, and `d`.
pub face_abd: Handle<Face>,

/// The face formed by the points `c`, `a`, and `d`.
pub face_cad: Handle<Face>,

/// The face formed by the points `b`, `c`, and `d`.
pub face_bcd: Handle<Face>,
}
4 changes: 3 additions & 1 deletion crates/fj-kernel/src/operations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
32 changes: 32 additions & 0 deletions crates/fj-kernel/src/operations/update/cycle.rs
Original file line number Diff line number Diff line change
@@ -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<HalfEdge>) -> Handle<HalfEdge>,
) -> Cycle;
}

impl UpdateCycle for Cycle {
fn update_half_edge(
&self,
index: usize,
mut f: impl FnMut(&Handle<HalfEdge>) -> Handle<HalfEdge>,
) -> Cycle {
let half_edges = self.half_edges().enumerate().map(|(i, cycle)| {
if i == index {
f(cycle)
} else {
cycle.clone()
}
});

Cycle::new(half_edges)
}
}
21 changes: 21 additions & 0 deletions crates/fj-kernel/src/operations/update/edge.rs
Original file line number Diff line number Diff line change
@@ -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<GlobalEdge>) -> HalfEdge;
}

impl UpdateHalfEdge for HalfEdge {
fn update_global_form(&self, global_form: Handle<GlobalEdge>) -> HalfEdge {
HalfEdge::new(
self.curve(),
self.boundary(),
self.start_vertex().clone(),
global_form,
)
}
}
29 changes: 29 additions & 0 deletions crates/fj-kernel/src/operations/update/face.rs
Original file line number Diff line number Diff line change
@@ -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<Cycle>) -> Handle<Cycle>,
) -> Face;
}

impl UpdateFace for Face {
fn update_exterior(
&self,
f: impl FnOnce(&Handle<Cycle>) -> Handle<Cycle>,
) -> Face {
let exterior = f(self.exterior());

Face::new(
self.surface().clone(),
exterior,
self.interiors().cloned(),
self.color(),
)
}
}
9 changes: 9 additions & 0 deletions crates/fj-kernel/src/operations/update/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mod cycle;
mod edge;
mod face;
mod shell;

pub use self::{
cycle::UpdateCycle, edge::UpdateHalfEdge, face::UpdateFace,
shell::UpdateShell,
};
45 changes: 45 additions & 0 deletions crates/fj-kernel/src/operations/update/shell.rs
Original file line number Diff line number Diff line change
@@ -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<Face>,
f: impl FnMut(&Handle<Face>) -> Handle<Face>,
) -> Shell;

/// Remove a face from the shell
fn remove_face(&self, handle: &Handle<Face>) -> Shell;
}

impl UpdateShell for Shell {
fn update_face(
&self,
handle: &Handle<Face>,
mut f: impl FnMut(&Handle<Face>) -> Handle<Face>,
) -> 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<Face>) -> Shell {
let faces = self
.faces()
.into_iter()
.filter(|face| face.id() == handle.id())
.cloned();

Shell::new(faces)
}
}
63 changes: 22 additions & 41 deletions crates/fj-kernel/src/validate/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand All @@ -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(
Expand All @@ -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)
Expand Down

0 comments on commit 7d666ae

Please sign in to comment.