Skip to content

Commit

Permalink
Merge pull request #1783 from hannobraun/join
Browse files Browse the repository at this point in the history
Add join operation
  • Loading branch information
hannobraun authored Apr 24, 2023
2 parents d1b0a63 + 1f22276 commit bc1175e
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 74 deletions.
10 changes: 10 additions & 0 deletions crates/fj-kernel/src/objects/full/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ impl Cycle {
.position(|edge| edge.id() == half_edge.id())
}

/// Return the number of half-edges in the cycle
pub fn len(&self) -> usize {
self.half_edges.len()
}

/// Indicate whether the cycle is empty
pub fn is_empty(&self) -> bool {
self.half_edges.is_empty()
}

/// Indicate the cycle's winding, assuming a right-handed coordinate system
///
/// Please note that this is not *the* winding of the cycle, only one of the
Expand Down
104 changes: 30 additions & 74 deletions crates/fj-kernel/src/operations/build/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use fj_math::Point;

use crate::{
objects::{Face, Objects, Shell},
operations::{Insert, UpdateCycle, UpdateFace, UpdateHalfEdge},
operations::{Insert, JoinCycle, UpdateFace},
services::Service,
storage::Handle,
};

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

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

let [Polygon {
face: face_abc,
edges: [ab, bc, ca],
vertices: [a, b, c],
}, Polygon {
face: face_bad,
edges: [ba, ad, db],
vertices: [_, _, d],
}, Polygon {
face: face_dac,
edges: [da, ac, cd],
..
}, Polygon {
face: face_cbd,
edges: [cb, bd, dc],
..
}] = [
Face::triangle([a, b, c], objects),
Face::triangle([b, a, d], objects),
Face::triangle([d, a, c], objects),
Face::triangle([c, b, d], objects),
];

let face_bad = face_bad.update_exterior(|cycle| {
let ba_joined = ba
.replace_start_vertex(b.clone())
.replace_global_form(ab.global_form().clone())
.insert(objects);
let ad_joined = ad.replace_start_vertex(a.clone()).insert(objects);

cycle
.replace_half_edge(&ba, ba_joined)
.replace_half_edge(&ad, ad_joined)
.insert(objects)
});
let face_dac = face_dac.update_exterior(|cycle| {
let da_joined = da
.replace_start_vertex(d.clone())
.replace_global_form(ad.global_form().clone())
.insert(objects);
let ac_joined = ac
.replace_start_vertex(a)
.replace_global_form(ca.global_form().clone())
.insert(objects);
let cd_joined = cd.replace_start_vertex(c.clone()).insert(objects);

cycle
.replace_half_edge(&da, da_joined)
.replace_half_edge(&ac, ac_joined)
.replace_half_edge(&cd, cd_joined)
.insert(objects)
});
let face_cbd = face_cbd.update_exterior(|cycle| {
let cb_joined = cb
.replace_start_vertex(c)
.replace_global_form(bc.global_form().clone())
.insert(objects);
let bd_joined = bd
.replace_start_vertex(b)
.replace_global_form(db.global_form().clone())
.insert(objects);
let dc_joined = dc
.replace_start_vertex(d)
.replace_global_form(cd.global_form().clone())
.insert(objects);

cycle
.replace_half_edge(&cb, cb_joined)
.replace_half_edge(&bd, bd_joined)
.replace_half_edge(&dc, dc_joined)
.insert(objects)
});
let face_abc = Face::triangle([a, b, c], objects).face;
let face_bad =
Face::triangle([b, a, d], objects)
.face
.update_exterior(|cycle| {
cycle
.join_to(face_abc.exterior(), 0..=0, 0..=0, objects)
.insert(objects)
});
let face_dac =
Face::triangle([d, a, c], objects)
.face
.update_exterior(|cycle| {
cycle
.join_to(face_abc.exterior(), 1..=1, 2..=2, objects)
.join_to(face_bad.exterior(), 0..=0, 1..=1, objects)
.insert(objects)
});
let face_cbd =
Face::triangle([c, b, d], objects)
.face
.update_exterior(|cycle| {
cycle
.join_to(face_abc.exterior(), 0..=0, 1..=1, objects)
.join_to(face_bad.exterior(), 1..=1, 2..=2, objects)
.join_to(face_dac.exterior(), 2..=2, 2..=2, objects)
.insert(objects)
});

let faces = [face_abc, face_bad, face_dac, face_cbd]
.map(|face| face.insert(objects));
Expand Down
100 changes: 100 additions & 0 deletions crates/fj-kernel/src/operations/join/cycle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use std::ops::RangeInclusive;

use crate::{
objects::{Cycle, Objects},
operations::{Insert, UpdateCycle, UpdateHalfEdge},
services::Service,
};

/// Join a [`Cycle`] to another
pub trait JoinCycle {
/// Join the cycle to another
///
/// Joins the cycle to the other at the provided ranges. The ranges specify
/// the indices of the half-edges that are joined together.
///
/// A modulo operation is applied to all indices before use, so in a cycle
/// of 3 half-edges, indices `0` and `3` refer to the same half-edge. This
/// allows for specifying a range that crosses the "seam" of the cycle.
///
/// # Panics
///
/// Panics, if the ranges have different lengths.
///
/// # Implementation Note
///
/// The use of the `RangeInclusive` type might be a bit limiting, as other
/// range types might be more convenient in a given use case. This
/// implementation was chosen for now, as it wasn't clear whether the
/// additional complexity of using `RangeBounds` would be worth it.
///
/// A solution based on `SliceIndex` could theoretically be used, but that
/// trait is partially unstable. In addition, it's not clear how it could be
/// used generically, as it could yield a range (which can be iterated over)
/// or a single item (which can not). This is not a hard problem in
/// principle (a single item could just be an iterator of length 1), but I
/// don't see it how to address this in Rust in a reasonable way.
///
/// Maybe a custom trait that is implemented for `usize` and all range types
/// would be the best solution.
fn join_to(
&self,
other: &Cycle,
range: RangeInclusive<usize>,
other_range: RangeInclusive<usize>,
objects: &mut Service<Objects>,
) -> Self;
}

impl JoinCycle for Cycle {
fn join_to(
&self,
other: &Cycle,
range: RangeInclusive<usize>,
range_other: RangeInclusive<usize>,
objects: &mut Service<Objects>,
) -> Self {
assert_eq!(
range.end() - range.start(),
range_other.end() - range_other.start()
);

let mut cycle = self.clone();

for (index, index_other) in range.zip(range_other) {
let index = index % self.len();
let index_other = index_other % self.len();

let half_edge = self
.nth_half_edge(index)
.expect("Index must be valid, due to use of `%` above");
let half_edge_other = other
.nth_half_edge(index_other)
.expect("Index must be valid, due to use of `%` above");

let vertex_a = other
.half_edge_after(half_edge_other)
.expect("Expected other cycle to contain edge")
.start_vertex()
.clone();
let vertex_b = half_edge_other.start_vertex().clone();

let next_edge = cycle
.half_edge_after(half_edge)
.expect("Expected this cycle to contain edge");

let this_joined = half_edge
.replace_start_vertex(vertex_a)
.replace_global_form(half_edge_other.global_form().clone())
.insert(objects);
let next_joined =
next_edge.replace_start_vertex(vertex_b).insert(objects);

cycle = cycle
.replace_half_edge(half_edge, this_joined)
.replace_half_edge(next_edge, next_joined)
}

cycle
}
}
3 changes: 3 additions & 0 deletions crates/fj-kernel/src/operations/join/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod cycle;

pub use self::cycle::JoinCycle;
2 changes: 2 additions & 0 deletions crates/fj-kernel/src/operations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
mod build;
mod insert;
mod join;
mod update;

pub use self::{
Expand All @@ -10,5 +11,6 @@ pub use self::{
Polygon, Tetrahedron,
},
insert::Insert,
join::JoinCycle,
update::{UpdateCycle, UpdateFace, UpdateHalfEdge, UpdateShell},
};

0 comments on commit bc1175e

Please sign in to comment.