mirror of
https://github.com/hannobraun/Fornjot
synced 2025-06-22 19:28:56 +00:00
commit
dade64657c
@ -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);
|
||||
|
@ -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>>,
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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(),
|
||||
|
@ -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)
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user