Skip to content

Commit

Permalink
Merge pull request #1952 from hannobraun/curve
Browse files Browse the repository at this point in the history
Fully integrate `Curve`
  • Loading branch information
hannobraun authored Jul 19, 2023
2 parents a3324f3 + 0de5fde commit dade646
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 49 deletions.
72 changes: 45 additions & 27 deletions crates/fj-core/src/algorithms/sweep/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ impl Sweep for (&HalfEdge, &Handle<Vertex>, &Surface, Option<Color>) {

// Next, we need to define the boundaries of the face. Let's start with
// the global vertices and edges.
let (vertices, global_edges) = {
let (vertices, global_edges, curves) = {
let [a, b] = [edge.start_vertex(), next_vertex].map(Clone::clone);
let (edge_up, [_, c]) =
let (curve_up, edge_up, [_, c]) =
b.clone().sweep_with_cache(path, cache, services);
let (edge_down, [_, d]) =
let (curve_down, edge_down, [_, d]) =
a.clone().sweep_with_cache(path, cache, services);

(
Expand All @@ -42,6 +42,12 @@ impl Sweep for (&HalfEdge, &Handle<Vertex>, &Surface, Option<Color>) {
None,
Some(edge_down),
],
[
Some(edge.curve().clone()),
Some(curve_up),
None,
Some(curve_down),
],
)
};

Expand Down Expand Up @@ -78,34 +84,46 @@ impl Sweep for (&HalfEdge, &Handle<Vertex>, &Surface, Option<Color>) {
.zip_ext(surface_points)
.zip_ext(surface_points_next)
.zip_ext(vertices)
.zip_ext(curves)
.zip_ext(global_edges)
.map(|((((boundary, start), end), start_vertex), global_edge)| {
let half_edge = {
let half_edge = HalfEdge::line_segment(
[start, end],
Some(boundary),
services,
)
.replace_start_vertex(start_vertex);

let half_edge = if let Some(global_edge) = global_edge {
half_edge.replace_global_form(global_edge)
} else {
half_edge
.map(
|(
((((boundary, start), end), start_vertex), curve),
global_edge,
)| {
let half_edge = {
let half_edge = HalfEdge::line_segment(
[start, end],
Some(boundary),
services,
)
.replace_start_vertex(start_vertex);

let half_edge = if let Some(curve) = curve {
half_edge.replace_curve(curve)
} else {
half_edge
};

let half_edge = if let Some(global_edge) = global_edge {
half_edge.replace_global_form(global_edge)
} else {
half_edge
};

half_edge.insert(services)
};

half_edge.insert(services)
};

exterior = Some(
exterior
.take()
.unwrap()
.add_half_edges([half_edge.clone()]),
);
exterior = Some(
exterior
.take()
.unwrap()
.add_half_edges([half_edge.clone()]),
);

half_edge
});
half_edge
},
);

let region = Region::new(exterior.unwrap().insert(services), [], color)
.insert(services);
Expand Down
12 changes: 8 additions & 4 deletions crates/fj-core/src/algorithms/sweep/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::collections::BTreeMap;
use fj_math::Vector;

use crate::{
objects::{GlobalEdge, Vertex},
objects::{Curve, GlobalEdge, Vertex},
services::Services,
storage::{Handle, ObjectId},
};
Expand Down Expand Up @@ -45,8 +45,12 @@ pub trait Sweep: Sized {
/// See [`Sweep`].
#[derive(Default)]
pub struct SweepCache {
/// Cache for global vertices
pub global_vertex: BTreeMap<ObjectId, Handle<Vertex>>,
/// Cache for curves
pub curves: BTreeMap<ObjectId, Handle<Curve>>,

/// Cache for vertices
pub vertices: BTreeMap<ObjectId, Handle<Vertex>>,

/// Cache for global edges
pub global_edge: BTreeMap<ObjectId, Handle<GlobalEdge>>,
pub global_edges: BTreeMap<ObjectId, Handle<GlobalEdge>>,
}
21 changes: 12 additions & 9 deletions crates/fj-core/src/algorithms/sweep/vertex.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use fj_math::Vector;

use crate::{
objects::{GlobalEdge, Vertex},
objects::{Curve, GlobalEdge, Vertex},
operations::Insert,
services::Services,
storage::Handle,
Expand All @@ -10,31 +10,34 @@ use crate::{
use super::{Sweep, SweepCache};

impl Sweep for Handle<Vertex> {
type Swept = (Handle<GlobalEdge>, [Self; 2]);
type Swept = (Handle<Curve>, Handle<GlobalEdge>, [Self; 2]);

fn sweep_with_cache(
self,
_: impl Into<Vector<3>>,
cache: &mut SweepCache,
services: &mut Services,
) -> Self::Swept {
let curve = cache
.curves
.entry(self.id())
.or_insert_with(|| Curve::new().insert(services))
.clone();

let a = self.clone();
let b = cache
.global_vertex
.vertices
.entry(self.id())
.or_insert_with(|| Vertex::new().insert(services))
.clone();

let vertices = [a, b];

let global_edge = cache
.global_edge
.global_edges
.entry(self.id())
.or_insert_with(|| GlobalEdge::new().insert(services))
.clone();

// The vertices of the returned `GlobalEdge` are in normalized order,
// which means the order can't be relied upon by the caller. Return the
// ordered vertices in addition.
(global_edge, vertices)
(curve, global_edge, vertices)
}
}
14 changes: 13 additions & 1 deletion crates/fj-core/src/geometry/bounding_vertices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@ use crate::{
};

/// The bounding vertices of an edge
#[derive(Eq, PartialEq)]
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct BoundingVertices {
/// The bounding vertices
pub inner: [HandleWrapper<Vertex>; 2],
}

impl BoundingVertices {
/// Normalize the bounding vertices
///
/// Returns a new instance of this struct, which has the vertices in a
/// defined order. This can be used to compare bounding vertices while
/// disregarding their order.
pub fn normalize(mut self) -> Self {
self.inner.sort();
self
}
}

impl From<[Handle<Vertex>; 2]> for BoundingVertices {
fn from(vertices: [Handle<Vertex>; 2]) -> Self {
Self {
Expand Down
2 changes: 2 additions & 0 deletions crates/fj-core/src/operations/join/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ impl JoinCycle for Cycle {
self.add_half_edges(edges.into_iter().circular_tuple_windows().map(
|((prev, _, _), (half_edge, curve, boundary))| {
HalfEdge::unjoined(curve, boundary, services)
.replace_curve(half_edge.curve().clone())
.replace_start_vertex(prev.start_vertex().clone())
.replace_global_form(half_edge.global_form().clone())
.insert(services)
Expand Down Expand Up @@ -115,6 +116,7 @@ impl JoinCycle for Cycle {
.expect("Expected this cycle to contain edge");

let this_joined = half_edge
.replace_curve(half_edge_other.curve().clone())
.replace_start_vertex(vertex_a)
.replace_global_form(half_edge_other.global_form().clone())
.insert(services);
Expand Down
20 changes: 17 additions & 3 deletions crates/fj-core/src/operations/update/edge.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
use crate::{
objects::{GlobalEdge, HalfEdge, Vertex},
objects::{Curve, GlobalEdge, HalfEdge, Vertex},
storage::Handle,
};

/// Update a [`HalfEdge`]
pub trait UpdateHalfEdge {
/// Update the start vertex of the half-edge
/// Replace the curve of the half-edge
#[must_use]
fn replace_curve(&self, curve: Handle<Curve>) -> Self;

/// Replace the start vertex of the half-edge
#[must_use]
fn replace_start_vertex(&self, start_vertex: Handle<Vertex>) -> Self;

/// Update the global form of the half-edge
/// Replace the global form of the half-edge
#[must_use]
fn replace_global_form(&self, global_form: Handle<GlobalEdge>) -> Self;
}

impl UpdateHalfEdge for HalfEdge {
fn replace_curve(&self, curve: Handle<Curve>) -> Self {
HalfEdge::new(
self.path(),
self.boundary(),
curve,
self.start_vertex().clone(),
self.global_form().clone(),
)
}

fn replace_start_vertex(&self, start_vertex: Handle<Vertex>) -> Self {
HalfEdge::new(
self.path(),
Expand Down
68 changes: 63 additions & 5 deletions crates/fj-core/src/validate/shell.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use std::{collections::HashMap, iter::repeat};
use std::{
collections::{BTreeMap, HashMap},
iter::repeat,
};

use fj_math::{Point, Scalar};

use crate::{
geometry::SurfaceGeometry,
objects::{HalfEdge, Shell, Surface},
storage::{Handle, ObjectId},
queries::BoundingVerticesOfEdge,
storage::{Handle, HandleWrapper, ObjectId},
};

use super::{Validate, ValidationConfig, ValidationError};
Expand Down Expand Up @@ -127,10 +131,34 @@ impl ShellValidationError {
// data-structure like an octree.
for (edge_a, surface_a) in &edges_and_surfaces {
for (edge_b, surface_b) in &edges_and_surfaces {
let identical =
let identical_according_to_global_form =
edge_a.global_form().id() == edge_b.global_form().id();

match identical {
let identical_according_to_curve = {
let on_same_curve =
edge_a.curve().id() == edge_b.curve().id();

let have_same_boundary = {
let bounding_vertices_of = |edge| {
shell
.bounding_vertices_of_edge(edge)
.expect("Expected edge to be part of shell")
.normalize()
};

bounding_vertices_of(edge_a)
== bounding_vertices_of(edge_b)
};

on_same_curve && have_same_boundary
};

assert_eq!(
identical_according_to_curve,
identical_according_to_global_form,
);

match identical_according_to_curve {
true => {
// All points on identical curves should be within
// identical_max_distance, so we shouldn't have any
Expand Down Expand Up @@ -186,6 +214,32 @@ impl ShellValidationError {
_: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
let mut num_edges = BTreeMap::new();

for face in shell.faces() {
for cycle in face.region().all_cycles() {
for half_edge in cycle.half_edges() {
let curve = HandleWrapper::from(half_edge.curve().clone());
let bounding_vertices = cycle
.bounding_vertices_of_edge(half_edge)
.expect(
"Cycle should provide bounds of its own half-edge",
)
.normalize();

let edge = (curve, bounding_vertices);

*num_edges.entry(edge).or_insert(0) += 1;
}
}
}

// Every edge should have exactly one matching edge that shares a curve
// and boundary.
if num_edges.into_values().any(|num| num != 2) {
errors.push(Self::NotWatertight.into());
}

let mut half_edge_to_faces: HashMap<ObjectId, usize> = HashMap::new();

for face in shell.faces() {
Expand All @@ -210,7 +264,7 @@ impl ShellValidationError {
mod tests {
use crate::{
assert_contains_err,
objects::{GlobalEdge, Shell},
objects::{Curve, GlobalEdge, Shell},
operations::{
BuildShell, Insert, UpdateCycle, UpdateFace, UpdateHalfEdge,
UpdateRegion, UpdateShell,
Expand All @@ -237,9 +291,13 @@ mod tests {
.update_exterior(|cycle| {
cycle
.update_nth_half_edge(0, |half_edge| {
let curve =
Curve::new().insert(&mut services);
let global_form =
GlobalEdge::new().insert(&mut services);

half_edge
.replace_curve(curve)
.replace_global_form(global_form)
.insert(&mut services)
})
Expand Down

0 comments on commit dade646

Please sign in to comment.