Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "update" operations; use them to clean up Shell validation tests #1719

Merged
merged 9 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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