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 // Next, we need to define the boundaries of the face. Let's start with
// the global vertices and edges. // 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 [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); 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); a.clone().sweep_with_cache(path, cache, services);
( (
@ -42,6 +42,12 @@ impl Sweep for (&HalfEdge, &Handle<Vertex>, &Surface, Option<Color>) {
None, None,
Some(edge_down), 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)
.zip_ext(surface_points_next) .zip_ext(surface_points_next)
.zip_ext(vertices) .zip_ext(vertices)
.zip_ext(curves)
.zip_ext(global_edges) .zip_ext(global_edges)
.map(|((((boundary, start), end), start_vertex), global_edge)| { .map(
let half_edge = { |(
let half_edge = HalfEdge::line_segment( ((((boundary, start), end), start_vertex), curve),
[start, end], global_edge,
Some(boundary), )| {
services, let half_edge = {
) let half_edge = HalfEdge::line_segment(
.replace_start_vertex(start_vertex); [start, end],
Some(boundary),
services,
)
.replace_start_vertex(start_vertex);
let half_edge = if let Some(global_edge) = global_edge { let half_edge = if let Some(curve) = curve {
half_edge.replace_global_form(global_edge) half_edge.replace_curve(curve)
} else { } else {
half_edge 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( half_edge
exterior },
.take() );
.unwrap()
.add_half_edges([half_edge.clone()]),
);
half_edge
});
let region = Region::new(exterior.unwrap().insert(services), [], color) let region = Region::new(exterior.unwrap().insert(services), [], color)
.insert(services); .insert(services);

View File

@ -11,7 +11,7 @@ use std::collections::BTreeMap;
use fj_math::Vector; use fj_math::Vector;
use crate::{ use crate::{
objects::{GlobalEdge, Vertex}, objects::{Curve, GlobalEdge, Vertex},
services::Services, services::Services,
storage::{Handle, ObjectId}, storage::{Handle, ObjectId},
}; };
@ -45,8 +45,12 @@ pub trait Sweep: Sized {
/// See [`Sweep`]. /// See [`Sweep`].
#[derive(Default)] #[derive(Default)]
pub struct SweepCache { pub struct SweepCache {
/// Cache for global vertices /// Cache for curves
pub global_vertex: BTreeMap<ObjectId, Handle<Vertex>>, pub curves: BTreeMap<ObjectId, Handle<Curve>>,
/// Cache for vertices
pub vertices: BTreeMap<ObjectId, Handle<Vertex>>,
/// Cache for global edges /// 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 fj_math::Vector;
use crate::{ use crate::{
objects::{GlobalEdge, Vertex}, objects::{Curve, GlobalEdge, Vertex},
operations::Insert, operations::Insert,
services::Services, services::Services,
storage::Handle, storage::Handle,
@ -10,7 +10,7 @@ use crate::{
use super::{Sweep, SweepCache}; use super::{Sweep, SweepCache};
impl Sweep for Handle<Vertex> { impl Sweep for Handle<Vertex> {
type Swept = (Handle<GlobalEdge>, [Self; 2]); type Swept = (Handle<Curve>, Handle<GlobalEdge>, [Self; 2]);
fn sweep_with_cache( fn sweep_with_cache(
self, self,
@ -18,23 +18,26 @@ impl Sweep for Handle<Vertex> {
cache: &mut SweepCache, cache: &mut SweepCache,
services: &mut Services, services: &mut Services,
) -> Self::Swept { ) -> Self::Swept {
let curve = cache
.curves
.entry(self.id())
.or_insert_with(|| Curve::new().insert(services))
.clone();
let a = self.clone(); let a = self.clone();
let b = cache let b = cache
.global_vertex .vertices
.entry(self.id()) .entry(self.id())
.or_insert_with(|| Vertex::new().insert(services)) .or_insert_with(|| Vertex::new().insert(services))
.clone(); .clone();
let vertices = [a, b]; let vertices = [a, b];
let global_edge = cache let global_edge = cache
.global_edge .global_edges
.entry(self.id()) .entry(self.id())
.or_insert_with(|| GlobalEdge::new().insert(services)) .or_insert_with(|| GlobalEdge::new().insert(services))
.clone(); .clone();
// The vertices of the returned `GlobalEdge` are in normalized order, (curve, global_edge, vertices)
// which means the order can't be relied upon by the caller. Return the
// ordered vertices in addition.
(global_edge, vertices)
} }
} }

View File

@ -4,12 +4,24 @@ use crate::{
}; };
/// The bounding vertices of an edge /// The bounding vertices of an edge
#[derive(Eq, PartialEq)] #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct BoundingVertices { pub struct BoundingVertices {
/// The bounding vertices /// The bounding vertices
pub inner: [HandleWrapper<Vertex>; 2], 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 { impl From<[Handle<Vertex>; 2]> for BoundingVertices {
fn from(vertices: [Handle<Vertex>; 2]) -> Self { fn from(vertices: [Handle<Vertex>; 2]) -> Self {
Self { Self {

View File

@ -71,6 +71,7 @@ impl JoinCycle for Cycle {
self.add_half_edges(edges.into_iter().circular_tuple_windows().map( self.add_half_edges(edges.into_iter().circular_tuple_windows().map(
|((prev, _, _), (half_edge, curve, boundary))| { |((prev, _, _), (half_edge, curve, boundary))| {
HalfEdge::unjoined(curve, boundary, services) HalfEdge::unjoined(curve, boundary, services)
.replace_curve(half_edge.curve().clone())
.replace_start_vertex(prev.start_vertex().clone()) .replace_start_vertex(prev.start_vertex().clone())
.replace_global_form(half_edge.global_form().clone()) .replace_global_form(half_edge.global_form().clone())
.insert(services) .insert(services)
@ -115,6 +116,7 @@ impl JoinCycle for Cycle {
.expect("Expected this cycle to contain edge"); .expect("Expected this cycle to contain edge");
let this_joined = half_edge let this_joined = half_edge
.replace_curve(half_edge_other.curve().clone())
.replace_start_vertex(vertex_a) .replace_start_vertex(vertex_a)
.replace_global_form(half_edge_other.global_form().clone()) .replace_global_form(half_edge_other.global_form().clone())
.insert(services); .insert(services);

View File

@ -1,20 +1,34 @@
use crate::{ use crate::{
objects::{GlobalEdge, HalfEdge, Vertex}, objects::{Curve, GlobalEdge, HalfEdge, Vertex},
storage::Handle, storage::Handle,
}; };
/// Update a [`HalfEdge`] /// Update a [`HalfEdge`]
pub trait UpdateHalfEdge { 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] #[must_use]
fn replace_start_vertex(&self, start_vertex: Handle<Vertex>) -> Self; 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] #[must_use]
fn replace_global_form(&self, global_form: Handle<GlobalEdge>) -> Self; fn replace_global_form(&self, global_form: Handle<GlobalEdge>) -> Self;
} }
impl UpdateHalfEdge for HalfEdge { 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 { fn replace_start_vertex(&self, start_vertex: Handle<Vertex>) -> Self {
HalfEdge::new( HalfEdge::new(
self.path(), 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 fj_math::{Point, Scalar};
use crate::{ use crate::{
geometry::SurfaceGeometry, geometry::SurfaceGeometry,
objects::{HalfEdge, Shell, Surface}, objects::{HalfEdge, Shell, Surface},
storage::{Handle, ObjectId}, queries::BoundingVerticesOfEdge,
storage::{Handle, HandleWrapper, ObjectId},
}; };
use super::{Validate, ValidationConfig, ValidationError}; use super::{Validate, ValidationConfig, ValidationError};
@ -127,10 +131,34 @@ impl ShellValidationError {
// data-structure like an octree. // data-structure like an octree.
for (edge_a, surface_a) in &edges_and_surfaces { for (edge_a, surface_a) in &edges_and_surfaces {
for (edge_b, surface_b) 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(); 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 => { true => {
// All points on identical curves should be within // All points on identical curves should be within
// identical_max_distance, so we shouldn't have any // identical_max_distance, so we shouldn't have any
@ -186,6 +214,32 @@ impl ShellValidationError {
_: &ValidationConfig, _: &ValidationConfig,
errors: &mut Vec<ValidationError>, 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(); let mut half_edge_to_faces: HashMap<ObjectId, usize> = HashMap::new();
for face in shell.faces() { for face in shell.faces() {
@ -210,7 +264,7 @@ impl ShellValidationError {
mod tests { mod tests {
use crate::{ use crate::{
assert_contains_err, assert_contains_err,
objects::{GlobalEdge, Shell}, objects::{Curve, GlobalEdge, Shell},
operations::{ operations::{
BuildShell, Insert, UpdateCycle, UpdateFace, UpdateHalfEdge, BuildShell, Insert, UpdateCycle, UpdateFace, UpdateHalfEdge,
UpdateRegion, UpdateShell, UpdateRegion, UpdateShell,
@ -237,9 +291,13 @@ mod tests {
.update_exterior(|cycle| { .update_exterior(|cycle| {
cycle cycle
.update_nth_half_edge(0, |half_edge| { .update_nth_half_edge(0, |half_edge| {
let curve =
Curve::new().insert(&mut services);
let global_form = let global_form =
GlobalEdge::new().insert(&mut services); GlobalEdge::new().insert(&mut services);
half_edge half_edge
.replace_curve(curve)
.replace_global_form(global_form) .replace_global_form(global_form)
.insert(&mut services) .insert(&mut services)
}) })