Merge pull request #1952 from hannobraun/curve

Fully integrate `Curve`
This commit is contained in:
Hanno Braun 2023-07-19 10:16:31 +02:00 committed by GitHub
commit dade64657c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 159 additions and 48 deletions

View File

@ -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);
(
@ -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),
],
)
};
@ -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);
.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(global_edge) = global_edge {
half_edge.replace_global_form(global_edge)
} else {
half_edge
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);

View File

@ -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},
};
@ -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>>,
}

View File

@ -1,7 +1,7 @@
use fj_math::Vector;
use crate::{
objects::{GlobalEdge, Vertex},
objects::{Curve, GlobalEdge, Vertex},
operations::Insert,
services::Services,
storage::Handle,
@ -10,7 +10,7 @@ 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,
@ -18,23 +18,26 @@ impl Sweep for Handle<Vertex> {
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)
}
}

View File

@ -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 {

View File

@ -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)
@ -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);

View File

@ -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(),

View File

@ -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};
@ -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
@ -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() {
@ -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,
@ -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)
})