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

Use richer face representation in Tetrahedron #1814

Merged
merged 9 commits into from
May 4, 2023
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/algorithms/transform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub trait TransformObject: Sized {

impl<T> TransformObject for Handle<T>
where
T: Clone + Insert + TransformObject + 'static,
T: Clone + Insert<Inserted = Handle<T>> + TransformObject + 'static,
{
fn transform_with_cache(
self,
Expand Down
6 changes: 3 additions & 3 deletions crates/fj-kernel/src/operations/build/face.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use fj_math::Point;

use crate::{
objects::{Cycle, Face, HalfEdge, Surface, Vertex},
operations::Insert,
operations::{Insert, IsInserted, IsInsertedNo},
services::Services,
storage::Handle,
};
Expand Down Expand Up @@ -62,9 +62,9 @@ impl BuildFace for Face {}
/// Currently code that deals with `Polygon` might assume that the polygon has
/// no holes. Unless you create a `Polygon` yourself, or modify a `Polygon`'s
/// `face` field to have interior cycles, this should not affect you.
pub struct Polygon<const D: usize> {
pub struct Polygon<const D: usize, I: IsInserted = IsInsertedNo> {
/// The face that forms the polygon
pub face: Face,
pub face: I::T<Face>,

/// The edges of the polygon
pub edges: [Handle<HalfEdge>; D],
Expand Down
65 changes: 30 additions & 35 deletions crates/fj-kernel/src/operations/build/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ use fj_math::Point;

use crate::{
objects::{Face, Shell},
operations::{Insert, JoinCycle, UpdateFace},
operations::{Insert, IsInsertedYes, JoinCycle, UpdateFace},
services::Services,
storage::Handle,
};

use super::BuildFace;
use super::{BuildFace, Polygon};

/// Build a [`Shell`]
pub trait BuildShell {
Expand Down Expand Up @@ -35,39 +34,35 @@ pub trait BuildShell {
) -> Tetrahedron {
let [a, b, c, d] = points.map(Into::into);

let abc = Face::triangle([a, b, c], services).face;
let abc = Face::triangle([a, b, c], services);
let bad =
Face::triangle([b, a, d], services)
.face
.update_exterior(|cycle| {
cycle
.join_to(abc.exterior(), 0..=0, 0..=0, services)
.insert(services)
});
Face::triangle([b, a, d], services).update_exterior(|cycle| {
cycle
.join_to(abc.face.exterior(), 0..=0, 0..=0, services)
.insert(services)
});
let dac =
Face::triangle([d, a, c], services)
.face
.update_exterior(|cycle| {
cycle
.join_to(abc.exterior(), 1..=1, 2..=2, services)
.join_to(bad.exterior(), 0..=0, 1..=1, services)
.insert(services)
});
Face::triangle([d, a, c], services).update_exterior(|cycle| {
cycle
.join_to(abc.face.exterior(), 1..=1, 2..=2, services)
.join_to(bad.face.exterior(), 0..=0, 1..=1, services)
.insert(services)
});
let cbd =
Face::triangle([c, b, d], services)
.face
.update_exterior(|cycle| {
cycle
.join_to(abc.exterior(), 0..=0, 1..=1, services)
.join_to(bad.exterior(), 1..=1, 2..=2, services)
.join_to(dac.exterior(), 2..=2, 2..=2, services)
.insert(services)
});
Face::triangle([c, b, d], services).update_exterior(|cycle| {
cycle
.join_to(abc.face.exterior(), 0..=0, 1..=1, services)
.join_to(bad.face.exterior(), 1..=1, 2..=2, services)
.join_to(dac.face.exterior(), 2..=2, 2..=2, services)
.insert(services)
});

let faces = [abc, bad, dac, cbd].map(|face| face.insert(services));
let shell = Shell::new(faces.clone());
let triangles =
[abc, bad, dac, cbd].map(|triangle| triangle.insert(services));
let shell =
Shell::new(triangles.iter().map(|triangle| triangle.face.clone()));

let [abc, bad, dac, cbd] = faces;
let [abc, bad, dac, cbd] = triangles;

Tetrahedron {
shell,
Expand All @@ -93,14 +88,14 @@ pub struct Tetrahedron {
pub shell: Shell,

/// The face formed by the points `a`, `b`, and `c`.
pub abc: Handle<Face>,
pub abc: Polygon<3, IsInsertedYes>,

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

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

/// The face formed by the points `c`, `b`, and `d`.
pub cbd: Handle<Face>,
pub cbd: Polygon<3, IsInsertedYes>,
}
53 changes: 51 additions & 2 deletions crates/fj-kernel/src/operations/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,30 @@ use crate::{
storage::Handle,
};

use super::Polygon;

/// Insert an object into its respective store
///
/// This is the only primitive operation that is directly understood by
/// `Service<Objects>`. All other operations are built on top of it.
pub trait Insert: Sized {
/// The type of `Self`, once it has been inserted
///
/// Usually this is just `Handle<Self>`, but there are some more complex
/// cases where this type needs to be customized.
type Inserted;

/// Insert the object into its respective store
fn insert(self, services: &mut Services) -> Handle<Self>;
fn insert(self, services: &mut Services) -> Self::Inserted;
}

macro_rules! impl_insert {
($($ty:ty, $store:ident;)*) => {
$(
impl Insert for $ty {
fn insert(self, services: &mut Services) -> Handle<Self> {
type Inserted = Handle<Self>;

fn insert(self, services: &mut Services) -> Self::Inserted {
let handle = services.objects.$store.reserve();
let object = (handle.clone(), self).into();
services.insert_object(object);
Expand All @@ -42,3 +52,42 @@ impl_insert!(
Surface, surfaces;
Vertex, vertices;
);

/// Indicate whether an object has been inserted
///
/// Intended to be used as a type parameter bound for structs that need to track
/// whether their contents have been inserted or not.
pub trait IsInserted {
/// The type of the object for which the insertion status is tracked
type T<T>;
}

/// Indicate that an object has been inserted
///
/// See [`IsInserted`].
pub struct IsInsertedYes;

impl IsInserted for IsInsertedYes {
type T<T> = Handle<T>;
}

/// Indicate that an object has not been inserted
///
/// See [`IsInserted`].
pub struct IsInsertedNo;

impl IsInserted for IsInsertedNo {
type T<T> = T;
}

impl<const D: usize> Insert for Polygon<D, IsInsertedNo> {
type Inserted = Polygon<D, IsInsertedYes>;

fn insert(self, services: &mut Services) -> Self::Inserted {
Polygon {
face: self.face.insert(services),
edges: self.edges,
vertices: self.vertices,
}
}
}
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/operations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub use self::{
BuildCycle, BuildFace, BuildHalfEdge, BuildShell, BuildSurface,
Polygon, Tetrahedron,
},
insert::Insert,
insert::{Insert, IsInserted, IsInsertedNo, IsInsertedYes},
join::JoinCycle,
update::{UpdateCycle, UpdateFace, UpdateHalfEdge, UpdateShell},
};
41 changes: 41 additions & 0 deletions crates/fj-kernel/src/operations/update/face.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use std::array;

use crate::{
objects::{Cycle, Face},
operations::Polygon,
storage::Handle,
};

Expand Down Expand Up @@ -47,3 +50,41 @@ impl UpdateFace for Face {
)
}
}

impl<const D: usize> UpdateFace for Polygon<D> {
fn update_exterior(
&self,
f: impl FnOnce(&Handle<Cycle>) -> Handle<Cycle>,
) -> Self {
let face = self.face.update_exterior(f);
let edges = array::from_fn(|i| {
face.exterior()
.nth_half_edge(i)
.expect("Operation should not have changed length of cycle")
.clone()
});
let vertices = array::from_fn(|i| {
// The duplicated code here is unfortunate, but unless we get a
// stable `array::each_ref` and something like `array::unzip`, I'm
// not sure how to avoid it.
face.exterior()
.nth_half_edge(i)
.expect("Operation should not have changed length of cycle")
.start_vertex()
.clone()
});

Polygon {
face,
edges,
vertices,
}
}

fn add_interiors(
&self,
_: impl IntoIterator<Item = Handle<Cycle>>,
) -> Self {
panic!("Adding interiors to `Polygon` is not supported.")
}
}
4 changes: 2 additions & 2 deletions crates/fj-kernel/src/validate/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ mod tests {
[[0., 0., 0.], [0., 1., 0.], [1., 0., 0.], [0., 0., 1.]],
&mut services,
);
let invalid = valid.shell.update_face(&valid.abc, |face| {
let invalid = valid.shell.update_face(&valid.abc.face, |face| {
face.update_exterior(|cycle| {
cycle
.update_nth_half_edge(0, |half_edge| {
Expand Down Expand Up @@ -243,7 +243,7 @@ mod tests {
[[0., 0., 0.], [0., 1., 0.], [1., 0., 0.], [0., 0., 1.]],
&mut services,
);
let invalid = valid.shell.remove_face(&valid.abc);
let invalid = valid.shell.remove_face(&valid.abc.face);

valid.shell.validate_and_return_first_error()?;
assert_contains_err!(
Expand Down