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