Merge pull request #2000 from hannobraun/edge

Rename `HalfEdge` to `Edge`
This commit is contained in:
Hanno Braun 2023-08-18 13:22:18 +02:00 committed by GitHub
commit 6c41170f58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 425 additions and 492 deletions

View File

@ -9,7 +9,7 @@ use fj_math::Segment;
use crate::objects::{Cycle, Surface}; use crate::objects::{Cycle, Surface};
use super::{ use super::{
edge::{EdgeCache, HalfEdgeApprox}, edge::{EdgeApprox, EdgeCache},
Approx, ApproxPoint, Tolerance, Approx, ApproxPoint, Tolerance,
}; };
@ -25,14 +25,14 @@ impl Approx for (&Cycle, &Surface) {
let (cycle, surface) = self; let (cycle, surface) = self;
let tolerance = tolerance.into(); let tolerance = tolerance.into();
let half_edges = cycle let edges = cycle
.half_edges() .edges()
.map(|half_edge| { .map(|edge| {
(half_edge.deref(), surface).approx_with_cache(tolerance, cache) (edge.deref(), surface).approx_with_cache(tolerance, cache)
}) })
.collect(); .collect();
CycleApprox { half_edges } CycleApprox { edges }
} }
} }
@ -40,7 +40,7 @@ impl Approx for (&Cycle, &Surface) {
#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct CycleApprox { pub struct CycleApprox {
/// The approximated edges that make up the approximated cycle /// The approximated edges that make up the approximated cycle
pub half_edges: Vec<HalfEdgeApprox>, pub edges: Vec<EdgeApprox>,
} }
impl CycleApprox { impl CycleApprox {
@ -48,7 +48,7 @@ impl CycleApprox {
pub fn points(&self) -> Vec<ApproxPoint<2>> { pub fn points(&self) -> Vec<ApproxPoint<2>> {
let mut points = Vec::new(); let mut points = Vec::new();
for approx in &self.half_edges { for approx in &self.edges {
points.extend(approx.points()); points.extend(approx.points());
} }

View File

@ -11,14 +11,14 @@ use fj_math::Point;
use crate::{ use crate::{
geometry::{CurveBoundary, GlobalPath, SurfacePath}, geometry::{CurveBoundary, GlobalPath, SurfacePath},
objects::{Curve, HalfEdge, Surface, Vertex}, objects::{Curve, Edge, Surface, Vertex},
storage::{Handle, HandleWrapper}, storage::{Handle, HandleWrapper},
}; };
use super::{curve::CurveApproxSegment, Approx, ApproxPoint, Tolerance}; use super::{curve::CurveApproxSegment, Approx, ApproxPoint, Tolerance};
impl Approx for (&HalfEdge, &Surface) { impl Approx for (&Edge, &Surface) {
type Approximation = HalfEdgeApprox; type Approximation = EdgeApprox;
type Cache = EdgeCache; type Cache = EdgeCache;
fn approx_with_cache( fn approx_with_cache(
@ -26,50 +26,48 @@ impl Approx for (&HalfEdge, &Surface) {
tolerance: impl Into<Tolerance>, tolerance: impl Into<Tolerance>,
cache: &mut Self::Cache, cache: &mut Self::Cache,
) -> Self::Approximation { ) -> Self::Approximation {
let (half_edge, surface) = self; let (edge, surface) = self;
let position_surface = half_edge.start_position(); let position_surface = edge.start_position();
let position_global = match cache.get_position(half_edge.start_vertex()) let position_global = match cache.get_position(edge.start_vertex()) {
{
Some(position) => position, Some(position) => position,
None => { None => {
let position_global = surface let position_global = surface
.geometry() .geometry()
.point_from_surface_coords(position_surface); .point_from_surface_coords(position_surface);
cache.insert_position(half_edge.start_vertex(), position_global) cache.insert_position(edge.start_vertex(), position_global)
} }
}; };
let first = ApproxPoint::new(position_surface, position_global); let first = ApproxPoint::new(position_surface, position_global);
let points = { let points = {
// We cache approximated `HalfEdge`s using the `Curve`s they // We cache approximated `Edge`s using the `Curve`s they reference
// reference and their boundary on that curve as the key. That bakes // and their boundary on that curve as the key. That bakes in the
// in the undesirable assumption that all coincident `HalfEdge`s are // undesirable assumption that all coincident `Edge`s are also
// also congruent. Let me explain. // congruent. Let me explain.
// //
// When two `HalfEdge`s are coincident, we need to make sure their // When two `Edge`s are coincident, we need to make sure their
// approximations are identical where they overlap. Otherwise, we'll // approximations are identical where they overlap. Otherwise, we'll
// get an invalid triangle mesh in the end. Hence, we cache // get an invalid triangle mesh in the end. Hence, we cache
// approximations. // approximations.
// //
// Caching works like this: We check whether there already is a // Caching works like this: We check whether there already is a
// cache entry for the curve/boundary. If there isn't, we create the // cache entry for the curve/boundary. If there isn't, we create the
// 3D approximation from the 2D `HalfEdge`. Next time we check for a // 3D approximation from the 2D `Edge`. Next time we check for a
// coincident `HalfEdge`, we'll find the cache and use that, getting // coincident `Edge`, we'll find the cache and use that, getting
// the exact same 3D approximation, instead of generating a slightly // the exact same 3D approximation, instead of generating a slightly
// different one from the different 2D `HalfEdge`. // different one from the different 2D `Edge`.
// //
// So what if we had two coincident `HalfEdge`s that aren't // So what if we had two coincident `fEdge`s that aren't congruent?
// congruent? Meaning, they overlap partially, but not fully. Then // Meaning, they overlap partially, but not fully. Then obviously,
// obviously, they wouldn't refer to the same combination of curve // they wouldn't refer to the same combination of curve and
// and boundary. And since those are the key in our cache, those // boundary. And since those are the key in our cache, those `Edge`s
// `HalfEdge`s would not share an approximation where they overlap, // would not share an approximation where they overlap, leading to
// leading to exactly the problems that the cache is supposed to // exactly the problems that the cache is supposed to prevent.
// prevent.
// //
// As of this writing, it is a documented (but not validated) // As of this writing, it is a documented (but not validated)
// limitation, that coincident `HalfEdge`s must always be congruent. // limitation, that coincident `Edge`s must always be congruent.
// However, we're going to need to lift this limitation going // However, we're going to need to lift this limitation going
// forward, as it is, well, too limiting. This means things here // forward, as it is, well, too limiting. This means things here
// will need to change. // will need to change.
@ -80,19 +78,19 @@ impl Approx for (&HalfEdge, &Surface) {
// able to deliver partial results for a given boundary, then // able to deliver partial results for a given boundary, then
// generating (and caching) the rest of it on the fly. // generating (and caching) the rest of it on the fly.
let cached_approx = let cached_approx =
cache.get_edge(half_edge.curve().clone(), half_edge.boundary()); cache.get_edge(edge.curve().clone(), edge.boundary());
let approx = match cached_approx { let approx = match cached_approx {
Some(approx) => approx, Some(approx) => approx,
None => { None => {
let approx = approx_edge( let approx = approx_edge(
&half_edge.path(), &edge.path(),
surface, surface,
half_edge.boundary(), edge.boundary(),
tolerance, tolerance,
); );
cache.insert_edge( cache.insert_edge(
half_edge.curve().clone(), edge.curve().clone(),
half_edge.boundary(), edge.boundary(),
approx, approx,
) )
} }
@ -102,22 +100,21 @@ impl Approx for (&HalfEdge, &Surface) {
.points .points
.into_iter() .into_iter()
.map(|point| { .map(|point| {
let point_surface = half_edge let point_surface =
.path() edge.path().point_from_path_coords(point.local_form);
.point_from_path_coords(point.local_form);
ApproxPoint::new(point_surface, point.global_form) ApproxPoint::new(point_surface, point.global_form)
}) })
.collect() .collect()
}; };
HalfEdgeApprox { first, points } EdgeApprox { first, points }
} }
} }
/// An approximation of an [`HalfEdge`] /// An approximation of an [`Edge`]
#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct HalfEdgeApprox { pub struct EdgeApprox {
/// The point that approximates the first vertex of the edge /// The point that approximates the first vertex of the edge
pub first: ApproxPoint<2>, pub first: ApproxPoint<2>,
@ -125,7 +122,7 @@ pub struct HalfEdgeApprox {
pub points: Vec<ApproxPoint<2>>, pub points: Vec<ApproxPoint<2>>,
} }
impl HalfEdgeApprox { impl EdgeApprox {
/// Compute the points that approximate the edge /// Compute the points that approximate the edge
pub fn points(&self) -> Vec<ApproxPoint<2>> { pub fn points(&self) -> Vec<ApproxPoint<2>> {
let mut points = Vec::new(); let mut points = Vec::new();
@ -287,8 +284,8 @@ mod tests {
use crate::{ use crate::{
algorithms::approx::{Approx, ApproxPoint}, algorithms::approx::{Approx, ApproxPoint},
geometry::{CurveBoundary, GlobalPath, SurfaceGeometry}, geometry::{CurveBoundary, GlobalPath, SurfaceGeometry},
objects::{HalfEdge, Surface}, objects::{Edge, Surface},
operations::BuildHalfEdge, operations::BuildEdge,
services::Services, services::Services,
}; };
@ -297,11 +294,11 @@ mod tests {
let mut services = Services::new(); let mut services = Services::new();
let surface = services.objects.surfaces.xz_plane(); let surface = services.objects.surfaces.xz_plane();
let half_edge = let edge =
HalfEdge::line_segment([[1., 1.], [2., 1.]], None, &mut services); Edge::line_segment([[1., 1.], [2., 1.]], None, &mut services);
let tolerance = 1.; let tolerance = 1.;
let approx = (&half_edge, surface.deref()).approx(tolerance); let approx = (&edge, surface.deref()).approx(tolerance);
assert_eq!(approx.points, Vec::new()); assert_eq!(approx.points, Vec::new());
} }
@ -314,11 +311,11 @@ mod tests {
u: GlobalPath::circle_from_radius(1.), u: GlobalPath::circle_from_radius(1.),
v: [0., 0., 1.].into(), v: [0., 0., 1.].into(),
}); });
let half_edge = let edge =
HalfEdge::line_segment([[1., 1.], [2., 1.]], None, &mut services); Edge::line_segment([[1., 1.], [2., 1.]], None, &mut services);
let tolerance = 1.; let tolerance = 1.;
let approx = (&half_edge, &surface).approx(tolerance); let approx = (&edge, &surface).approx(tolerance);
assert_eq!(approx.points, Vec::new()); assert_eq!(approx.points, Vec::new());
} }
@ -334,21 +331,21 @@ mod tests {
u: path, u: path,
v: [0., 0., 1.].into(), v: [0., 0., 1.].into(),
}); });
let half_edge = HalfEdge::line_segment( let edge = Edge::line_segment(
[[0., 1.], [TAU, 1.]], [[0., 1.], [TAU, 1.]],
Some(boundary.inner), Some(boundary.inner),
&mut services, &mut services,
); );
let tolerance = 1.; let tolerance = 1.;
let approx = (&half_edge, &surface).approx(tolerance); let approx = (&edge, &surface).approx(tolerance);
let expected_approx = (path, boundary) let expected_approx = (path, boundary)
.approx(tolerance) .approx(tolerance)
.into_iter() .into_iter()
.map(|(point_local, _)| { .map(|(point_local, _)| {
let point_surface = let point_surface =
half_edge.path().point_from_path_coords(point_local); edge.path().point_from_path_coords(point_local);
let point_global = let point_global =
surface.geometry().point_from_surface_coords(point_surface); surface.geometry().point_from_surface_coords(point_surface);
ApproxPoint::new(point_surface, point_global) ApproxPoint::new(point_surface, point_global)
@ -362,13 +359,13 @@ mod tests {
let mut services = Services::new(); let mut services = Services::new();
let surface = services.objects.surfaces.xz_plane(); let surface = services.objects.surfaces.xz_plane();
let half_edge = HalfEdge::circle([0., 0.], 1., &mut services); let edge = Edge::circle([0., 0.], 1., &mut services);
let tolerance = 1.; let tolerance = 1.;
let approx = (&half_edge, surface.deref()).approx(tolerance); let approx = (&edge, surface.deref()).approx(tolerance);
let expected_approx = let expected_approx =
(&half_edge.path(), CurveBoundary::from([[0.], [TAU]])) (&edge.path(), CurveBoundary::from([[0.], [TAU]]))
.approx(tolerance) .approx(tolerance)
.into_iter() .into_iter()
.map(|(_, point_surface)| { .map(|(_, point_surface)| {

View File

@ -6,10 +6,8 @@ impl super::BoundingVolume<2> for Cycle {
fn aabb(&self) -> Option<Aabb<2>> { fn aabb(&self) -> Option<Aabb<2>> {
let mut aabb: Option<Aabb<2>> = None; let mut aabb: Option<Aabb<2>> = None;
for half_edge in self.half_edges() { for edge in self.edges() {
let new_aabb = half_edge let new_aabb = edge.aabb().expect("`Edge` can always compute AABB");
.aabb()
.expect("`HalfEdge` can always compute AABB");
aabb = Some(aabb.map_or(new_aabb, |aabb| aabb.merged(&new_aabb))); aabb = Some(aabb.map_or(new_aabb, |aabb| aabb.merged(&new_aabb)));
} }

View File

@ -1,8 +1,8 @@
use fj_math::{Aabb, Vector}; use fj_math::{Aabb, Vector};
use crate::{geometry::SurfacePath, objects::HalfEdge}; use crate::{geometry::SurfacePath, objects::Edge};
impl super::BoundingVolume<2> for HalfEdge { impl super::BoundingVolume<2> for Edge {
fn aabb(&self) -> Option<Aabb<2>> { fn aabb(&self) -> Option<Aabb<2>> {
match self.path() { match self.path() {
SurfacePath::Circle(circle) => { SurfacePath::Circle(circle) => {

View File

@ -1,10 +1,10 @@
use fj_math::{Point, Segment}; use fj_math::{Point, Segment};
use crate::{geometry::SurfacePath, objects::HalfEdge}; use crate::{geometry::SurfacePath, objects::Edge};
use super::LineSegmentIntersection; use super::LineSegmentIntersection;
/// The intersection between a curve and a [`HalfEdge`] /// The intersection between a curve and an [`Edge`]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum CurveEdgeIntersection { pub enum CurveEdgeIntersection {
/// The curve and edge intersect at a point /// The curve and edge intersect at a point
@ -26,23 +26,22 @@ impl CurveEdgeIntersection {
/// # Panics /// # Panics
/// ///
/// Currently, only intersections between lines and line segments can be /// Currently, only intersections between lines and line segments can be
/// computed. Panics, if a different type of curve or [`HalfEdge`] is /// computed. Panics, if a different type of curve or [`Edge`] is passed.
/// passed. pub fn compute(path: &SurfacePath, edge: &Edge) -> Option<Self> {
pub fn compute(path: &SurfacePath, half_edge: &HalfEdge) -> Option<Self> {
let path_as_line = match path { let path_as_line = match path {
SurfacePath::Line(line) => line, SurfacePath::Line(line) => line,
_ => todo!("Curve-edge intersection only supports lines"), _ => todo!("Curve-edge intersection only supports lines"),
}; };
let edge_as_segment = { let edge_as_segment = {
let edge_path_as_line = match half_edge.path() { let edge_path_as_line = match edge.path() {
SurfacePath::Line(line) => line, SurfacePath::Line(line) => line,
_ => { _ => {
todo!("Curve-edge intersection only supports line segments") todo!("Curve-edge intersection only supports line segments")
} }
}; };
let edge_vertices = half_edge let edge_vertices = edge
.boundary() .boundary()
.inner .inner
.map(|point| edge_path_as_line.point_from_line_coords(point)); .map(|point| edge_path_as_line.point_from_line_coords(point));
@ -73,7 +72,7 @@ mod tests {
use fj_math::Point; use fj_math::Point;
use crate::{ use crate::{
geometry::SurfacePath, objects::HalfEdge, operations::BuildHalfEdge, geometry::SurfacePath, objects::Edge, operations::BuildEdge,
services::Services, services::Services,
}; };
@ -84,10 +83,10 @@ mod tests {
let mut services = Services::new(); let mut services = Services::new();
let path = SurfacePath::u_axis(); let path = SurfacePath::u_axis();
let half_edge = let edge =
HalfEdge::line_segment([[1., -1.], [1., 1.]], None, &mut services); Edge::line_segment([[1., -1.], [1., 1.]], None, &mut services);
let intersection = CurveEdgeIntersection::compute(&path, &half_edge); let intersection = CurveEdgeIntersection::compute(&path, &edge);
assert_eq!( assert_eq!(
intersection, intersection,
@ -102,13 +101,10 @@ mod tests {
let mut services = Services::new(); let mut services = Services::new();
let path = SurfacePath::u_axis(); let path = SurfacePath::u_axis();
let half_edge = HalfEdge::line_segment( let edge =
[[-1., -1.], [-1., 1.]], Edge::line_segment([[-1., -1.], [-1., 1.]], None, &mut services);
None,
&mut services,
);
let intersection = CurveEdgeIntersection::compute(&path, &half_edge); let intersection = CurveEdgeIntersection::compute(&path, &edge);
assert_eq!( assert_eq!(
intersection, intersection,
@ -123,13 +119,10 @@ mod tests {
let mut services = Services::new(); let mut services = Services::new();
let path = SurfacePath::u_axis(); let path = SurfacePath::u_axis();
let half_edge = HalfEdge::line_segment( let edge =
[[-1., -1.], [1., -1.]], Edge::line_segment([[-1., -1.], [1., -1.]], None, &mut services);
None,
&mut services,
);
let intersection = CurveEdgeIntersection::compute(&path, &half_edge); let intersection = CurveEdgeIntersection::compute(&path, &edge);
assert!(intersection.is_none()); assert!(intersection.is_none());
} }
@ -139,10 +132,10 @@ mod tests {
let mut services = Services::new(); let mut services = Services::new();
let path = SurfacePath::u_axis(); let path = SurfacePath::u_axis();
let half_edge = let edge =
HalfEdge::line_segment([[-1., 0.], [1., 0.]], None, &mut services); Edge::line_segment([[-1., 0.], [1., 0.]], None, &mut services);
let intersection = CurveEdgeIntersection::compute(&path, &half_edge); let intersection = CurveEdgeIntersection::compute(&path, &edge);
assert_eq!( assert_eq!(
intersection, intersection,

View File

@ -29,15 +29,12 @@ impl CurveFaceIntersection {
/// Compute the intersection /// Compute the intersection
pub fn compute(path: &SurfacePath, face: &Face) -> Self { pub fn compute(path: &SurfacePath, face: &Face) -> Self {
let half_edges = face let edges = face.region().all_cycles().flat_map(|cycle| cycle.edges());
.region()
.all_cycles()
.flat_map(|cycle| cycle.half_edges());
let mut intersections = Vec::new(); let mut intersections = Vec::new();
for half_edge in half_edges { for edge in edges {
let intersection = CurveEdgeIntersection::compute(path, half_edge); let intersection = CurveEdgeIntersection::compute(path, edge);
if let Some(intersection) = intersection { if let Some(intersection) = intersection {
match intersection { match intersection {

View File

@ -3,7 +3,7 @@
use fj_math::Point; use fj_math::Point;
use crate::{ use crate::{
objects::{Face, HalfEdge}, objects::{Edge, Face},
storage::Handle, storage::Handle,
}; };
@ -28,13 +28,13 @@ impl Intersect for (&Face, &Point<2>) {
// as long as we initialize the `previous_hit` variable with the // as long as we initialize the `previous_hit` variable with the
// result of the last segment. // result of the last segment.
let mut previous_hit = cycle let mut previous_hit = cycle
.half_edges() .edges()
.last() .last()
.cloned() .cloned()
.and_then(|edge| (&ray, &edge).intersect()); .and_then(|edge| (&ray, &edge).intersect());
for (half_edge, next_half_edge) in cycle.half_edge_pairs() { for (edge, next_edge) in cycle.edge_pairs() {
let hit = (&ray, half_edge).intersect(); let hit = (&ray, edge).intersect();
let count_hit = match (hit, previous_hit) { let count_hit = match (hit, previous_hit) {
( (
@ -44,17 +44,17 @@ impl Intersect for (&Face, &Point<2>) {
// If the ray starts on the boundary of the face, // If the ray starts on the boundary of the face,
// there's nothing to else check. // there's nothing to else check.
return Some(FacePointIntersection::PointIsOnEdge( return Some(FacePointIntersection::PointIsOnEdge(
half_edge.clone() edge.clone()
)); ));
} }
(Some(RaySegmentIntersection::RayStartsOnOnFirstVertex), _) => { (Some(RaySegmentIntersection::RayStartsOnOnFirstVertex), _) => {
let vertex = half_edge.start_position(); let vertex = edge.start_position();
return Some( return Some(
FacePointIntersection::PointIsOnVertex(vertex) FacePointIntersection::PointIsOnVertex(vertex)
); );
} }
(Some(RaySegmentIntersection::RayStartsOnSecondVertex), _) => { (Some(RaySegmentIntersection::RayStartsOnSecondVertex), _) => {
let vertex = next_half_edge.start_position(); let vertex = next_edge.start_position();
return Some( return Some(
FacePointIntersection::PointIsOnVertex(vertex) FacePointIntersection::PointIsOnVertex(vertex)
); );
@ -122,7 +122,7 @@ pub enum FacePointIntersection {
PointIsInsideFace, PointIsInsideFace,
/// The point is coincident with an edge /// The point is coincident with an edge
PointIsOnEdge(Handle<HalfEdge>), PointIsOnEdge(Handle<Edge>),
/// The point is coincident with a vertex /// The point is coincident with a vertex
PointIsOnVertex(Point<2>), PointIsOnVertex(Point<2>),
@ -335,7 +335,7 @@ mod tests {
let edge = face let edge = face
.region() .region()
.exterior() .exterior()
.half_edges() .edges()
.find(|edge| edge.start_position() == Point::from([0., 0.])) .find(|edge| edge.start_position() == Point::from([0., 0.]))
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
@ -370,11 +370,9 @@ mod tests {
let vertex = face let vertex = face
.region() .region()
.exterior() .exterior()
.half_edges() .edges()
.find(|half_edge| { .find(|edge| edge.start_position() == Point::from([1., 0.]))
half_edge.start_position() == Point::from([1., 0.]) .map(|edge| edge.start_position())
})
.map(|half_edge| half_edge.start_position())
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
intersection, intersection,

View File

@ -5,13 +5,13 @@ use fj_math::Segment;
use crate::{ use crate::{
algorithms::intersect::{HorizontalRayToTheRight, Intersect}, algorithms::intersect::{HorizontalRayToTheRight, Intersect},
geometry::SurfacePath, geometry::SurfacePath,
objects::HalfEdge, objects::Edge,
storage::Handle, storage::Handle,
}; };
use super::ray_segment::RaySegmentIntersection; use super::ray_segment::RaySegmentIntersection;
impl Intersect for (&HorizontalRayToTheRight<2>, &Handle<HalfEdge>) { impl Intersect for (&HorizontalRayToTheRight<2>, &Handle<Edge>) {
type Intersection = RaySegmentIntersection; type Intersection = RaySegmentIntersection;
fn intersect(self) -> Option<Self::Intersection> { fn intersect(self) -> Option<Self::Intersection> {

View File

@ -5,7 +5,7 @@ use fj_math::{Plane, Point, Scalar};
use crate::{ use crate::{
algorithms::intersect::face_point::FacePointIntersection, algorithms::intersect::face_point::FacePointIntersection,
geometry::GlobalPath, geometry::GlobalPath,
objects::{Face, HalfEdge}, objects::{Edge, Face},
storage::Handle, storage::Handle,
}; };
@ -134,7 +134,7 @@ pub enum RayFaceIntersection {
RayHitsFaceAndAreParallel, RayHitsFaceAndAreParallel,
/// The ray hits an edge /// The ray hits an edge
RayHitsEdge(Handle<HalfEdge>), RayHitsEdge(Handle<Edge>),
/// The ray hits a vertex /// The ray hits a vertex
RayHitsVertex(Point<2>), RayHitsVertex(Point<2>),
@ -262,7 +262,7 @@ mod tests {
let edge = face let edge = face
.region() .region()
.exterior() .exterior()
.half_edges() .edges()
.find(|edge| edge.start_position() == Point::from([-1., 1.])) .find(|edge| edge.start_position() == Point::from([-1., 1.]))
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
@ -297,11 +297,9 @@ mod tests {
let vertex = face let vertex = face
.region() .region()
.exterior() .exterior()
.half_edges() .edges()
.find(|half_edge| { .find(|edge| edge.start_position() == Point::from([-1., -1.]))
half_edge.start_position() == Point::from([-1., -1.]) .map(|edge| edge.start_position())
})
.map(|half_edge| half_edge.start_position())
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
(&ray, &face).intersect(), (&ray, &face).intersect(),

View File

@ -2,16 +2,16 @@ use fj_interop::{ext::ArrayExt, mesh::Color};
use fj_math::{Point, Scalar, Vector}; use fj_math::{Point, Scalar, Vector};
use crate::{ use crate::{
objects::{Cycle, Face, HalfEdge, Region, Surface, Vertex}, objects::{Cycle, Edge, Face, Region, Surface, Vertex},
operations::{BuildHalfEdge, Insert, UpdateCycle, UpdateHalfEdge}, operations::{BuildEdge, Insert, UpdateCycle, UpdateEdge},
services::Services, services::Services,
storage::Handle, storage::Handle,
}; };
use super::{Sweep, SweepCache}; use super::{Sweep, SweepCache};
impl Sweep for (&HalfEdge, &Handle<Vertex>, &Surface, Option<Color>) { impl Sweep for (&Edge, &Handle<Vertex>, &Surface, Option<Color>) {
type Swept = (Handle<Face>, Handle<HalfEdge>); type Swept = (Handle<Face>, Handle<Edge>);
fn sweep_with_cache( fn sweep_with_cache(
self, self,
@ -80,31 +80,27 @@ impl Sweep for (&HalfEdge, &Handle<Vertex>, &Surface, Option<Color>) {
.zip_ext(vertices) .zip_ext(vertices)
.zip_ext(curves) .zip_ext(curves)
.map(|((((boundary, start), end), start_vertex), curve)| { .map(|((((boundary, start), end), start_vertex), curve)| {
let half_edge = { let edge = {
let half_edge = HalfEdge::line_segment( let edge = Edge::line_segment(
[start, end], [start, end],
Some(boundary), Some(boundary),
services, services,
) )
.replace_start_vertex(start_vertex); .replace_start_vertex(start_vertex);
let half_edge = if let Some(curve) = curve { let edge = if let Some(curve) = curve {
half_edge.replace_curve(curve) edge.replace_curve(curve)
} else { } else {
half_edge edge
}; };
half_edge.insert(services) edge.insert(services)
}; };
exterior = Some( exterior =
exterior Some(exterior.take().unwrap().add_edges([edge.clone()]));
.take()
.unwrap()
.add_half_edges([half_edge.clone()]),
);
half_edge edge
}); });
let region = Region::new(exterior.unwrap().insert(services), [], color) let region = Region::new(exterior.unwrap().insert(services), [], color)

View File

@ -61,9 +61,9 @@ impl Sweep for Handle<Face> {
let cycle = cycle.reverse(services); let cycle = cycle.reverse(services);
let mut top_edges = Vec::new(); let mut top_edges = Vec::new();
for (half_edge, next) in cycle.half_edge_pairs() { for (edge, next) in cycle.edge_pairs() {
let (face, top_edge) = ( let (face, top_edge) = (
half_edge.deref(), edge.deref(),
next.start_vertex(), next.start_vertex(),
self.surface().deref(), self.surface().deref(),
self.region().color(), self.region().color(),
@ -72,11 +72,7 @@ impl Sweep for Handle<Face> {
faces.push(face); faces.push(face);
top_edges.push(( top_edges.push((top_edge, edge.path(), edge.boundary()));
top_edge,
half_edge.path(),
half_edge.boundary(),
));
} }
let top_cycle = Cycle::empty() let top_cycle = Cycle::empty()

View File

@ -11,12 +11,11 @@ impl TransformObject for Cycle {
services: &mut Services, services: &mut Services,
cache: &mut TransformCache, cache: &mut TransformCache,
) -> Self { ) -> Self {
let half_edges = self.half_edges().map(|half_edge| { let edges = self.edges().map(|edge| {
half_edge edge.clone()
.clone()
.transform_with_cache(transform, services, cache) .transform_with_cache(transform, services, cache)
}); });
Self::new(half_edges) Self::new(edges)
} }
} }

View File

@ -1,10 +1,10 @@
use fj_math::Transform; use fj_math::Transform;
use crate::{objects::HalfEdge, services::Services}; use crate::{objects::Edge, services::Services};
use super::{TransformCache, TransformObject}; use super::{TransformCache, TransformObject};
impl TransformObject for HalfEdge { impl TransformObject for Edge {
fn transform_with_cache( fn transform_with_cache(
self, self,
transform: &Transform, transform: &Transform,

View File

@ -1,10 +1,10 @@
/// A curve /// A curve
/// ///
/// `Curve` represents a curve in space, but holds no data to define that curve. /// `Curve` represents a curve in space, but holds no data to define that curve.
/// It is referenced by [`HalfEdge`], which defines the curve in the coordinates /// It is referenced by [`Edge`], which defines the curve in the coordinates of
/// of its surface. /// its surface.
/// ///
/// `Curve` exists to allow identifying which [`HalfEdge`]s are supposed to be /// `Curve` exists to allow identifying which [`Edge`]s are supposed to be
/// coincident in global space. /// coincident in global space.
/// ///
/// # Equality /// # Equality
@ -20,7 +20,7 @@
/// `Eq`/`Ord`/..., you can use `HandleWrapper<Curve>` to do that. It will use /// `Eq`/`Ord`/..., you can use `HandleWrapper<Curve>` to do that. It will use
/// `Handle::id` to provide those `Eq`/`Ord`/... implementations. /// `Handle::id` to provide those `Eq`/`Ord`/... implementations.
/// ///
/// [`HalfEdge`]: crate::objects::HalfEdge /// [`Edge`]: crate::objects::Edge
#[derive(Clone, Debug, Default, Hash)] #[derive(Clone, Debug, Default, Hash)]
pub struct Curve {} pub struct Curve {}

View File

@ -1,68 +1,61 @@
use std::slice;
use fj_math::{Scalar, Winding}; use fj_math::{Scalar, Winding};
use itertools::Itertools; use itertools::Itertools;
use crate::{geometry::SurfacePath, objects::HalfEdge, storage::Handle}; use crate::{geometry::SurfacePath, objects::Edge, storage::Handle};
/// A cycle of connected half-edges /// A cycle of connected edges
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Cycle { pub struct Cycle {
half_edges: Vec<Handle<HalfEdge>>, edges: Vec<Handle<Edge>>,
} }
impl Cycle { impl Cycle {
/// Create an instance of `Cycle` /// Create an instance of `Cycle`
pub fn new(half_edges: impl IntoIterator<Item = Handle<HalfEdge>>) -> Self { pub fn new(edges: impl IntoIterator<Item = Handle<Edge>>) -> Self {
let half_edges = half_edges.into_iter().collect::<Vec<_>>(); let edges = edges.into_iter().collect::<Vec<_>>();
Self { half_edges } Self { edges }
} }
/// Access the half-edges that make up the cycle /// Access the edges that make up the cycle
pub fn half_edges(&self) -> HalfEdgesOfCycle { pub fn edges(&self) -> impl Iterator<Item = &Handle<Edge>> {
self.half_edges.iter() self.edges.iter()
} }
/// Access the half-edges in pairs /// Access neighboring edges in pairs
pub fn half_edge_pairs( pub fn edge_pairs(
&self, &self,
) -> impl Iterator<Item = (&Handle<HalfEdge>, &Handle<HalfEdge>)> { ) -> impl Iterator<Item = (&Handle<Edge>, &Handle<Edge>)> {
self.half_edges.iter().circular_tuple_windows() self.edges.iter().circular_tuple_windows()
} }
/// Access the half-edge with the provided index /// Access the edge with the provided index
pub fn nth_half_edge(&self, index: usize) -> Option<&Handle<HalfEdge>> { pub fn nth_edge(&self, index: usize) -> Option<&Handle<Edge>> {
self.half_edges.get(index) self.edges.get(index)
} }
/// Access the half-edge after the provided one /// Access the edge after the provided one
/// ///
/// Returns `None`, if the provided `HalfEdge` is not part of the cycle. /// Returns `None`, if the provided [`Edge`] is not part of the cycle.
pub fn half_edge_after( pub fn edge_after(&self, edge: &Handle<Edge>) -> Option<&Handle<Edge>> {
&self, self.index_of(edge).map(|index| {
half_edge: &Handle<HalfEdge>, let next_index = (index + 1) % self.edges.len();
) -> Option<&Handle<HalfEdge>> { &self.edges[next_index]
self.index_of(half_edge).map(|index| {
let next_index = (index + 1) % self.half_edges.len();
&self.half_edges[next_index]
}) })
} }
/// Return the index of the provided half-edge, if it is in this cycle /// Return the index of the provided edge, if it is in this cycle
pub fn index_of(&self, half_edge: &Handle<HalfEdge>) -> Option<usize> { pub fn index_of(&self, edge: &Handle<Edge>) -> Option<usize> {
self.half_edges self.edges.iter().position(|e| e.id() == edge.id())
.iter()
.position(|edge| edge.id() == half_edge.id())
} }
/// Return the number of half-edges in the cycle /// Return the number of edges in the cycle
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.half_edges.len() self.edges.len()
} }
/// Indicate whether the cycle is empty /// Indicate whether the cycle is empty
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.half_edges.is_empty() self.edges.is_empty()
} }
/// Indicate the cycle's winding, assuming a right-handed coordinate system /// Indicate the cycle's winding, assuming a right-handed coordinate system
@ -74,11 +67,11 @@ impl Cycle {
// The cycle could be made up of one or two circles. If that is the // The cycle could be made up of one or two circles. If that is the
// case, the winding of the cycle is determined by the winding of the // case, the winding of the cycle is determined by the winding of the
// first circle. // first circle.
if self.half_edges.len() < 3 { if self.edges.len() < 3 {
let first = self let first = self
.half_edges() .edges()
.next() .next()
.expect("Invalid cycle: expected at least one half-edge"); .expect("Invalid cycle: expected at least one edge");
let [a, b] = first.boundary().inner; let [a, b] = first.boundary().inner;
let edge_direction_positive = a < b; let edge_direction_positive = a < b;
@ -104,8 +97,8 @@ impl Cycle {
let mut sum = Scalar::ZERO; let mut sum = Scalar::ZERO;
for (a, b) in self.half_edge_pairs() { for (a, b) in self.edge_pairs() {
let [a, b] = [a, b].map(|half_edge| half_edge.start_position()); let [a, b] = [a, b].map(|edge| edge.start_position());
sum += (b.u - a.u) * (b.v + a.v); sum += (b.u - a.u) * (b.v + a.v);
} }
@ -120,8 +113,3 @@ impl Cycle {
unreachable!("Encountered invalid cycle: {self:#?}"); unreachable!("Encountered invalid cycle: {self:#?}");
} }
} }
/// An iterator over the half-edges of a [`Cycle`]
///
/// Returned by [`Cycle::half_edges`].
pub type HalfEdgesOfCycle<'a> = slice::Iter<'a, Handle<HalfEdge>>;

View File

@ -9,36 +9,36 @@ use crate::{
/// A directed edge, defined in a surface's 2D space /// A directed edge, defined in a surface's 2D space
/// ///
/// When multiple faces, which are bound by edges, are combined to form a solid, /// When multiple faces, which are bound by edges, are combined to form a solid,
/// the `HalfEdge`s that bound the face on the surface are then coincident with /// the `Edge`s that bound the face on the surface are then coincident with the
/// the `HalfEdge`s of other faces, where those faces touch. Those coincident /// `Edge`s of other faces, where those faces touch. Those coincident `Edge`s
/// `HalfEdge`s are different representations of the same edge, and this fact /// are different representations of the same edge, and this fact must be
/// must be represented in the following way: /// represented in the following way:
/// ///
/// - The coincident `HalfEdge`s must refer to the same `Curve`. /// - The coincident `Edge`s must refer to the same `Curve`.
/// - The coincident `HalfEdge`s must have the same boundary. /// - The coincident `Edge`s must have the same boundary.
/// ///
/// There is another, implicit requirement hidden here: /// There is another, implicit requirement hidden here:
/// ///
/// `HalfEdge`s that are coincident, i.e. located in the same space, must always /// `Edge`s that are coincident, i.e. located in the same space, must always be
/// be congruent. This means they must coincide *exactly*. The overlap must be /// congruent. This means they must coincide *exactly*. The overlap must be
/// complete. None of the coincident `HalfEdge`s must overlap with just a /// complete. None of the coincident `Edge`s must overlap with just a section of
/// section of another. /// another.
/// ///
/// # Implementation Note /// # Implementation Note
/// ///
/// The limitation that coincident `HalfEdge`s must be congruent is currently /// The limitation that coincident `Edge`s must be congruent is currently
/// being lifted: /// being lifted:
/// <https://github.com/hannobraun/fornjot/issues/1937> /// <https://github.com/hannobraun/fornjot/issues/1937>
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct HalfEdge { pub struct Edge {
path: SurfacePath, path: SurfacePath,
boundary: CurveBoundary<Point<1>>, boundary: CurveBoundary<Point<1>>,
curve: HandleWrapper<Curve>, curve: HandleWrapper<Curve>,
start_vertex: HandleWrapper<Vertex>, start_vertex: HandleWrapper<Vertex>,
} }
impl HalfEdge { impl Edge {
/// Create an instance of `HalfEdge` /// Create an instance of `Edge`
pub fn new( pub fn new(
path: SurfacePath, path: SurfacePath,
boundary: impl Into<CurveBoundary<Point<1>>>, boundary: impl Into<CurveBoundary<Point<1>>>,
@ -53,32 +53,32 @@ impl HalfEdge {
} }
} }
/// Access the curve that defines the half-edge's geometry /// Access the curve that defines the edge's geometry
pub fn path(&self) -> SurfacePath { pub fn path(&self) -> SurfacePath {
self.path self.path
} }
/// Access the boundary points of the half-edge on the curve /// Access the boundary points of the edge on the curve
pub fn boundary(&self) -> CurveBoundary<Point<1>> { pub fn boundary(&self) -> CurveBoundary<Point<1>> {
self.boundary self.boundary
} }
/// Compute the surface position where the half-edge starts /// Compute the surface position where the edge starts
pub fn start_position(&self) -> Point<2> { pub fn start_position(&self) -> Point<2> {
// Computing the surface position from the curve position is fine. // Computing the surface position from the curve position is fine.
// `HalfEdge` "owns" its start position. There is no competing code that // `Edge` "owns" its start position. There is no competing code that
// could compute the surface position from slightly different data. // could compute the surface position from slightly different data.
let [start, _] = self.boundary.inner; let [start, _] = self.boundary.inner;
self.path.point_from_path_coords(start) self.path.point_from_path_coords(start)
} }
/// Access the curve of the half-edge /// Access the curve of the edge
pub fn curve(&self) -> &Handle<Curve> { pub fn curve(&self) -> &Handle<Curve> {
&self.curve &self.curve
} }
/// Access the vertex from where this half-edge starts /// Access the vertex from where this edge starts
pub fn start_vertex(&self) -> &Handle<Vertex> { pub fn start_vertex(&self) -> &Handle<Vertex> {
&self.start_vertex &self.start_vertex
} }

View File

@ -26,10 +26,10 @@ use crate::{
/// ///
/// Interior cycles must have the opposite winding of the exterior cycle, /// Interior cycles must have the opposite winding of the exterior cycle,
/// meaning on the front side of the face, they must appear clockwise. This /// meaning on the front side of the face, they must appear clockwise. This
/// means that all [`HalfEdge`]s that bound a `Face` have the interior of the /// means that all [`Edge`]s that bound a `Face` have the interior of the face
/// face on their left side (on the face's front side). /// on their left side (on the face's front side).
/// ///
/// [`HalfEdge`]: crate::objects::HalfEdge /// [`Edge`]: crate::objects::Edge
/// [`Shell`]: crate::objects::Shell /// [`Shell`]: crate::objects::Shell
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Face { pub struct Face {

View File

@ -7,10 +7,10 @@ use crate::{objects::Cycle, storage::Handle};
/// ///
/// Interior cycles must have the opposite winding of the exterior cycle, /// Interior cycles must have the opposite winding of the exterior cycle,
/// meaning on the front side of the region, they must appear clockwise. This /// meaning on the front side of the region, they must appear clockwise. This
/// means that all [`HalfEdge`]s that bound a `Region` have the interior of the /// means that all [`Edge`]s that bound a `Region` have the interior of the
/// region on their left side (on the region's front side). /// region on their left side (on the region's front side).
/// ///
/// [`HalfEdge`]: crate::objects::HalfEdge /// [`Edge`]: crate::objects::Edge
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Region { pub struct Region {
exterior: Handle<Cycle>, exterior: Handle<Cycle>,

View File

@ -47,8 +47,8 @@ mod stores;
pub use self::{ pub use self::{
kinds::{ kinds::{
curve::Curve, curve::Curve,
cycle::{Cycle, HalfEdgesOfCycle}, cycle::Cycle,
edge::HalfEdge, edge::Edge,
face::{Face, FaceSet, Handedness}, face::{Face, FaceSet, Handedness},
region::Region, region::Region,
shell::Shell, shell::Shell,

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
objects::{ objects::{
Curve, Cycle, Face, HalfEdge, Objects, Region, Shell, Sketch, Solid, Curve, Cycle, Edge, Face, Objects, Region, Shell, Sketch, Solid,
Surface, Vertex, Surface, Vertex,
}, },
storage::{Handle, HandleWrapper, ObjectId}, storage::{Handle, HandleWrapper, ObjectId},
@ -94,7 +94,7 @@ object!(
Curve, "curve", curves; Curve, "curve", curves;
Cycle, "cycle", cycles; Cycle, "cycle", cycles;
Face, "face", faces; Face, "face", faces;
HalfEdge, "half-edge", half_edges; Edge, "edge", edges;
Region, "region", regions; Region, "region", regions;
Shell, "shell", shells; Shell, "shell", shells;
Sketch, "sketch", sketches; Sketch, "sketch", sketches;

View File

@ -1,8 +1,6 @@
use std::collections::{btree_set, BTreeSet}; use std::collections::{btree_set, BTreeSet};
use super::{ use super::{BehindHandle, Curve, Cycle, Edge, Face, Object, Surface, Vertex};
BehindHandle, Curve, Cycle, Face, HalfEdge, Object, Surface, Vertex,
};
/// A graph of objects and their relationships /// A graph of objects and their relationships
pub struct ObjectSet { pub struct ObjectSet {
@ -63,9 +61,9 @@ impl InsertIntoSet for Curve {
impl InsertIntoSet for Cycle { impl InsertIntoSet for Cycle {
fn insert_into_set(&self, objects: &mut ObjectSet) { fn insert_into_set(&self, objects: &mut ObjectSet) {
for half_edge in self.half_edges() { for edge in self.edges() {
objects.inner.insert(half_edge.clone().into()); objects.inner.insert(edge.clone().into());
half_edge.insert_into_set(objects); edge.insert_into_set(objects);
} }
} }
} }
@ -89,7 +87,7 @@ impl InsertIntoSet for Face {
} }
} }
impl InsertIntoSet for HalfEdge { impl InsertIntoSet for Edge {
fn insert_into_set(&self, objects: &mut ObjectSet) { fn insert_into_set(&self, objects: &mut ObjectSet) {
objects.inner.insert(self.curve().clone().into()); objects.inner.insert(self.curve().clone().into());
self.curve().insert_into_set(objects); self.curve().insert_into_set(objects);

View File

@ -6,7 +6,7 @@ use crate::{
}; };
use super::{ use super::{
Curve, Cycle, Face, HalfEdge, Region, Shell, Sketch, Solid, Surface, Vertex, Curve, Cycle, Edge, Face, Region, Shell, Sketch, Solid, Surface, Vertex,
}; };
/// The available object stores /// The available object stores
@ -18,12 +18,12 @@ pub struct Objects {
/// Store for [`Cycle`]s /// Store for [`Cycle`]s
pub cycles: Store<Cycle>, pub cycles: Store<Cycle>,
/// Store for [`Edge`]s
pub edges: Store<Edge>,
/// Store for [`Face`]s /// Store for [`Face`]s
pub faces: Store<Face>, pub faces: Store<Face>,
/// Store for [`HalfEdge`]s
pub half_edges: Store<HalfEdge>,
/// Store for [`Region`]s /// Store for [`Region`]s
pub regions: Store<Region>, pub regions: Store<Region>,

View File

@ -2,8 +2,8 @@ use fj_math::{Point, Scalar};
use itertools::Itertools; use itertools::Itertools;
use crate::{ use crate::{
objects::{Cycle, HalfEdge}, objects::{Cycle, Edge},
operations::{BuildHalfEdge, Insert, UpdateCycle}, operations::{BuildEdge, Insert, UpdateCycle},
services::Services, services::Services,
}; };
@ -20,9 +20,8 @@ pub trait BuildCycle {
radius: impl Into<Scalar>, radius: impl Into<Scalar>,
services: &mut Services, services: &mut Services,
) -> Cycle { ) -> Cycle {
let circle = let circle = Edge::circle(center, radius, services).insert(services);
HalfEdge::circle(center, radius, services).insert(services); Cycle::empty().add_edges([circle])
Cycle::empty().add_half_edges([circle])
} }
/// Build a polygon /// Build a polygon
@ -32,16 +31,16 @@ pub trait BuildCycle {
Ps: IntoIterator<Item = P>, Ps: IntoIterator<Item = P>,
Ps::IntoIter: Clone + ExactSizeIterator, Ps::IntoIter: Clone + ExactSizeIterator,
{ {
let half_edges = points let edges = points
.into_iter() .into_iter()
.map(Into::into) .map(Into::into)
.circular_tuple_windows() .circular_tuple_windows()
.map(|(start, end)| { .map(|(start, end)| {
HalfEdge::line_segment([start, end], None, services) Edge::line_segment([start, end], None, services)
.insert(services) .insert(services)
}); });
Cycle::new(half_edges) Cycle::new(edges)
} }
} }

View File

@ -3,23 +3,23 @@ use fj_math::{Arc, Point, Scalar};
use crate::{ use crate::{
geometry::{CurveBoundary, SurfacePath}, geometry::{CurveBoundary, SurfacePath},
objects::{Curve, HalfEdge, Vertex}, objects::{Curve, Edge, Vertex},
operations::Insert, operations::Insert,
services::Services, services::Services,
}; };
/// Build a [`HalfEdge`] /// Build an [`Edge`]
pub trait BuildHalfEdge { pub trait BuildEdge {
/// Create a half-edge that is not joined to another /// Create an edge that is not joined to another
fn unjoined( fn unjoined(
path: SurfacePath, path: SurfacePath,
boundary: impl Into<CurveBoundary<Point<1>>>, boundary: impl Into<CurveBoundary<Point<1>>>,
services: &mut Services, services: &mut Services,
) -> HalfEdge { ) -> Edge {
let curve = Curve::new().insert(services); let curve = Curve::new().insert(services);
let start_vertex = Vertex::new().insert(services); let start_vertex = Vertex::new().insert(services);
HalfEdge::new(path, boundary, curve, start_vertex) Edge::new(path, boundary, curve, start_vertex)
} }
/// Create an arc /// Create an arc
@ -32,7 +32,7 @@ pub trait BuildHalfEdge {
end: impl Into<Point<2>>, end: impl Into<Point<2>>,
angle_rad: impl Into<Scalar>, angle_rad: impl Into<Scalar>,
services: &mut Services, services: &mut Services,
) -> HalfEdge { ) -> Edge {
let angle_rad = angle_rad.into(); let angle_rad = angle_rad.into();
if angle_rad <= -Scalar::TAU || angle_rad >= Scalar::TAU { if angle_rad <= -Scalar::TAU || angle_rad >= Scalar::TAU {
panic!("arc angle must be in the range (-2pi, 2pi) radians"); panic!("arc angle must be in the range (-2pi, 2pi) radians");
@ -45,7 +45,7 @@ pub trait BuildHalfEdge {
let boundary = let boundary =
[arc.start_angle, arc.end_angle].map(|coord| Point::from([coord])); [arc.start_angle, arc.end_angle].map(|coord| Point::from([coord]));
HalfEdge::unjoined(path, boundary, services) Edge::unjoined(path, boundary, services)
} }
/// Create a circle /// Create a circle
@ -53,12 +53,12 @@ pub trait BuildHalfEdge {
center: impl Into<Point<2>>, center: impl Into<Point<2>>,
radius: impl Into<Scalar>, radius: impl Into<Scalar>,
services: &mut Services, services: &mut Services,
) -> HalfEdge { ) -> Edge {
let path = SurfacePath::circle_from_center_and_radius(center, radius); let path = SurfacePath::circle_from_center_and_radius(center, radius);
let boundary = let boundary =
[Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord])); [Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord]));
HalfEdge::unjoined(path, boundary, services) Edge::unjoined(path, boundary, services)
} }
/// Create a line segment /// Create a line segment
@ -66,15 +66,15 @@ pub trait BuildHalfEdge {
points_surface: [impl Into<Point<2>>; 2], points_surface: [impl Into<Point<2>>; 2],
boundary: Option<[Point<1>; 2]>, boundary: Option<[Point<1>; 2]>,
services: &mut Services, services: &mut Services,
) -> HalfEdge { ) -> Edge {
let boundary = let boundary =
boundary.unwrap_or_else(|| [[0.], [1.]].map(Point::from)); boundary.unwrap_or_else(|| [[0.], [1.]].map(Point::from));
let path = SurfacePath::line_from_points_with_coords( let path = SurfacePath::line_from_points_with_coords(
boundary.zip_ext(points_surface), boundary.zip_ext(points_surface),
); );
HalfEdge::unjoined(path, boundary, services) Edge::unjoined(path, boundary, services)
} }
} }
impl BuildHalfEdge for HalfEdge {} impl BuildEdge for Edge {}

View File

@ -4,7 +4,7 @@ use fj_interop::ext::ArrayExt;
use fj_math::Point; use fj_math::Point;
use crate::{ use crate::{
objects::{Cycle, Face, HalfEdge, Region, Surface, Vertex}, objects::{Cycle, Edge, Face, Region, Surface, Vertex},
operations::{ operations::{
BuildCycle, BuildRegion, BuildSurface, Insert, IsInserted, IsInsertedNo, BuildCycle, BuildRegion, BuildSurface, Insert, IsInserted, IsInsertedNo,
}, },
@ -32,19 +32,20 @@ pub trait BuildFace {
let face = Face::polygon(surface, points_surface, services); let face = Face::polygon(surface, points_surface, services);
let edges = { let edges = {
let mut half_edges = face.region().exterior().half_edges().cloned(); let mut edges = face.region().exterior().edges().cloned();
assert_eq!(half_edges.clone().count(), 3);
array::from_fn(|_| half_edges.next()).map(|half_edge| { let array = array::from_fn(|_| edges.next()).map(|edge| {
half_edge edge.expect("Just asserted that there are three edges")
.expect("Just asserted that there are three half-edges")
})
};
let vertices =
edges.each_ref_ext().map(|half_edge: &Handle<HalfEdge>| {
half_edge.start_vertex().clone()
}); });
assert!(edges.next().is_none());
array
};
let vertices = edges
.each_ref_ext()
.map(|edge: &Handle<Edge>| edge.start_vertex().clone());
Polygon { Polygon {
face, face,
edges, edges,
@ -82,7 +83,7 @@ pub struct Polygon<const D: usize, I: IsInserted = IsInsertedNo> {
pub face: I::T<Face>, pub face: I::T<Face>,
/// The edges of the polygon /// The edges of the polygon
pub edges: [Handle<HalfEdge>; D], pub edges: [Handle<Edge>; D],
/// The vertices of the polygon /// The vertices of the polygon
pub vertices: [Handle<Vertex>; D], pub vertices: [Handle<Vertex>; D],
@ -98,7 +99,7 @@ impl<const D: usize, I: IsInserted> Polygon<D, I> {
face.borrow() face.borrow()
.region() .region()
.exterior() .exterior()
.nth_half_edge(i) .nth_edge(i)
.expect("Operation should not have changed length of cycle") .expect("Operation should not have changed length of cycle")
.clone() .clone()
}); });
@ -109,7 +110,7 @@ impl<const D: usize, I: IsInserted> Polygon<D, I> {
face.borrow() face.borrow()
.region() .region()
.exterior() .exterior()
.nth_half_edge(i) .nth_edge(i)
.expect("Operation should not have changed length of cycle") .expect("Operation should not have changed length of cycle")
.start_vertex() .start_vertex()
.clone() .clone()

View File

@ -46,7 +46,7 @@ pub trait BuildShell {
region region
.update_exterior(|cycle| { .update_exterior(|cycle| {
cycle cycle
.update_nth_half_edge(0, |edge| { .update_nth_edge(0, |edge| {
edge.reverse_curve_coordinate_systems(services) edge.reverse_curve_coordinate_systems(services)
.insert(services) .insert(services)
}) })
@ -64,7 +64,7 @@ pub trait BuildShell {
region region
.update_exterior(|cycle| { .update_exterior(|cycle| {
cycle cycle
.update_nth_half_edge(1, |edge| { .update_nth_edge(1, |edge| {
edge.reverse_curve_coordinate_systems(services) edge.reverse_curve_coordinate_systems(services)
.insert(services) .insert(services)
}) })
@ -74,7 +74,7 @@ pub trait BuildShell {
2..=2, 2..=2,
services, services,
) )
.update_nth_half_edge(0, |edge| { .update_nth_edge(0, |edge| {
edge.reverse_curve_coordinate_systems(services) edge.reverse_curve_coordinate_systems(services)
.insert(services) .insert(services)
}) })
@ -92,7 +92,7 @@ pub trait BuildShell {
region region
.update_exterior(|cycle| { .update_exterior(|cycle| {
cycle cycle
.update_nth_half_edge(0, |edge| { .update_nth_edge(0, |edge| {
edge.reverse_curve_coordinate_systems(services) edge.reverse_curve_coordinate_systems(services)
.insert(services) .insert(services)
}) })
@ -102,7 +102,7 @@ pub trait BuildShell {
1..=1, 1..=1,
services, services,
) )
.update_nth_half_edge(1, |edge| { .update_nth_edge(1, |edge| {
edge.reverse_curve_coordinate_systems(services) edge.reverse_curve_coordinate_systems(services)
.insert(services) .insert(services)
}) })
@ -112,7 +112,7 @@ pub trait BuildShell {
2..=2, 2..=2,
services, services,
) )
.update_nth_half_edge(2, |edge| { .update_nth_edge(2, |edge| {
edge.reverse_curve_coordinate_systems(services) edge.reverse_curve_coordinate_systems(services)
.insert(services) .insert(services)
}) })

View File

@ -1,7 +1,6 @@
use crate::{ use crate::{
objects::{ objects::{
Curve, Cycle, Face, HalfEdge, Region, Shell, Sketch, Solid, Surface, Curve, Cycle, Edge, Face, Region, Shell, Sketch, Solid, Surface, Vertex,
Vertex,
}, },
operations::{Polygon, TetrahedronShell}, operations::{Polygon, TetrahedronShell},
services::Services, services::Services,
@ -47,7 +46,7 @@ impl_insert!(
Curve, curves; Curve, curves;
Cycle, cycles; Cycle, cycles;
Face, faces; Face, faces;
HalfEdge, half_edges; Edge, edges;
Region, regions; Region, regions;
Shell, shells; Shell, shells;
Sketch, sketches; Sketch, sketches;

View File

@ -5,8 +5,8 @@ use itertools::Itertools;
use crate::{ use crate::{
geometry::{CurveBoundary, SurfacePath}, geometry::{CurveBoundary, SurfacePath},
objects::{Cycle, HalfEdge}, objects::{Cycle, Edge},
operations::{BuildHalfEdge, Insert, UpdateCycle, UpdateHalfEdge}, operations::{BuildEdge, Insert, UpdateCycle, UpdateEdge},
services::Services, services::Services,
storage::Handle, storage::Handle,
}; };
@ -18,18 +18,18 @@ pub trait JoinCycle {
fn add_joined_edges<Es>(&self, edges: Es, services: &mut Services) -> Self fn add_joined_edges<Es>(&self, edges: Es, services: &mut Services) -> Self
where where
Es: IntoIterator< Es: IntoIterator<
Item = (Handle<HalfEdge>, SurfacePath, CurveBoundary<Point<1>>), Item = (Handle<Edge>, SurfacePath, CurveBoundary<Point<1>>),
>, >,
Es::IntoIter: Clone + ExactSizeIterator; Es::IntoIter: Clone + ExactSizeIterator;
/// Join the cycle to another /// Join the cycle to another
/// ///
/// Joins the cycle to the other at the provided ranges. The ranges specify /// Joins the cycle to the other at the provided ranges. The ranges specify
/// the indices of the half-edges that are joined together. /// the indices of the edges that are joined together.
/// ///
/// A modulo operation is applied to all indices before use, so in a cycle /// A modulo operation is applied to all indices before use, so in a cycle
/// of 3 half-edges, indices `0` and `3` refer to the same half-edge. This /// of 3 edges, indices `0` and `3` refer to the same edge. This allows for
/// allows for specifying a range that crosses the "seam" of the cycle. /// specifying a range that crosses the "seam" of the cycle.
/// ///
/// # Panics /// # Panics
/// ///
@ -40,7 +40,7 @@ pub trait JoinCycle {
/// This method makes some assumptions that need to be met, if the operation /// This method makes some assumptions that need to be met, if the operation
/// is to result in a valid shape: /// is to result in a valid shape:
/// ///
/// - **The joined half-edges must be coincident.** /// - **The joined edges must be coincident.**
/// - **The locally defined curve coordinate systems of the edges must /// - **The locally defined curve coordinate systems of the edges must
/// match.** /// match.**
/// ///
@ -77,14 +77,14 @@ impl JoinCycle for Cycle {
fn add_joined_edges<Es>(&self, edges: Es, services: &mut Services) -> Self fn add_joined_edges<Es>(&self, edges: Es, services: &mut Services) -> Self
where where
Es: IntoIterator< Es: IntoIterator<
Item = (Handle<HalfEdge>, SurfacePath, CurveBoundary<Point<1>>), Item = (Handle<Edge>, SurfacePath, CurveBoundary<Point<1>>),
>, >,
Es::IntoIter: Clone + ExactSizeIterator, Es::IntoIter: Clone + ExactSizeIterator,
{ {
self.add_half_edges(edges.into_iter().circular_tuple_windows().map( self.add_edges(edges.into_iter().circular_tuple_windows().map(
|((prev, _, _), (half_edge, curve, boundary))| { |((prev, _, _), (edge, curve, boundary))| {
HalfEdge::unjoined(curve, boundary, services) Edge::unjoined(curve, boundary, services)
.replace_curve(half_edge.curve().clone()) .replace_curve(edge.curve().clone())
.replace_start_vertex(prev.start_vertex().clone()) .replace_start_vertex(prev.start_vertex().clone())
.insert(services) .insert(services)
}, },
@ -110,34 +110,34 @@ impl JoinCycle for Cycle {
let index = index % self.len(); let index = index % self.len();
let index_other = index_other % self.len(); let index_other = index_other % self.len();
let half_edge = self let edge = self
.nth_half_edge(index) .nth_edge(index)
.expect("Index must be valid, due to use of `%` above"); .expect("Index must be valid, due to use of `%` above");
let half_edge_other = other let edge_other = other
.nth_half_edge(index_other) .nth_edge(index_other)
.expect("Index must be valid, due to use of `%` above"); .expect("Index must be valid, due to use of `%` above");
let vertex_a = other let vertex_a = other
.half_edge_after(half_edge_other) .edge_after(edge_other)
.expect("Cycle must contain edge; just obtained edge from it") .expect("Cycle must contain edge; just obtained edge from it")
.start_vertex() .start_vertex()
.clone(); .clone();
let vertex_b = half_edge_other.start_vertex().clone(); let vertex_b = edge_other.start_vertex().clone();
let next_edge = cycle let next_edge = cycle
.half_edge_after(half_edge) .edge_after(edge)
.expect("Cycle must contain edge; just obtained edge from it"); .expect("Cycle must contain edge; just obtained edge from it");
let this_joined = half_edge let this_joined = edge
.replace_curve(half_edge_other.curve().clone()) .replace_curve(edge_other.curve().clone())
.replace_start_vertex(vertex_a) .replace_start_vertex(vertex_a)
.insert(services); .insert(services);
let next_joined = let next_joined =
next_edge.replace_start_vertex(vertex_b).insert(services); next_edge.replace_start_vertex(vertex_b).insert(services);
cycle = cycle cycle = cycle
.replace_half_edge(half_edge, this_joined) .replace_edge(edge, this_joined)
.replace_half_edge(next_edge, next_joined) .replace_edge(next_edge, next_joined)
} }
cycle cycle

View File

@ -10,7 +10,7 @@ mod update;
pub use self::{ pub use self::{
build::{ build::{
cycle::BuildCycle, cycle::BuildCycle,
edge::BuildHalfEdge, edge::BuildEdge,
face::{BuildFace, Polygon}, face::{BuildFace, Polygon},
region::BuildRegion, region::BuildRegion,
shell::{BuildShell, TetrahedronShell}, shell::{BuildShell, TetrahedronShell},
@ -23,7 +23,7 @@ pub use self::{
merge::Merge, merge::Merge,
reverse::Reverse, reverse::Reverse,
update::{ update::{
cycle::UpdateCycle, edge::UpdateHalfEdge, face::UpdateFace, cycle::UpdateCycle, edge::UpdateEdge, face::UpdateFace,
region::UpdateRegion, shell::UpdateShell, sketch::UpdateSketch, region::UpdateRegion, shell::UpdateShell, sketch::UpdateSketch,
solid::UpdateSolid, solid::UpdateSolid,
}, },

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
objects::{Cycle, HalfEdge}, objects::{Cycle, Edge},
operations::Insert, operations::Insert,
services::Services, services::Services,
}; };
@ -9,9 +9,9 @@ use super::{Reverse, ReverseCurveCoordinateSystems};
impl Reverse for Cycle { impl Reverse for Cycle {
fn reverse(&self, services: &mut Services) -> Self { fn reverse(&self, services: &mut Services) -> Self {
let mut edges = self let mut edges = self
.half_edge_pairs() .edge_pairs()
.map(|(current, next)| { .map(|(current, next)| {
HalfEdge::new( Edge::new(
current.path(), current.path(),
current.boundary().reverse(), current.boundary().reverse(),
current.curve().clone(), current.curve().clone(),
@ -32,7 +32,7 @@ impl ReverseCurveCoordinateSystems for Cycle {
&self, &self,
services: &mut Services, services: &mut Services,
) -> Self { ) -> Self {
let edges = self.half_edges().map(|edge| { let edges = self.edges().map(|edge| {
edge.reverse_curve_coordinate_systems(services) edge.reverse_curve_coordinate_systems(services)
.insert(services) .insert(services)
}); });

View File

@ -1,13 +1,13 @@
use crate::{objects::HalfEdge, services::Services}; use crate::{objects::Edge, services::Services};
use super::ReverseCurveCoordinateSystems; use super::ReverseCurveCoordinateSystems;
impl ReverseCurveCoordinateSystems for HalfEdge { impl ReverseCurveCoordinateSystems for Edge {
fn reverse_curve_coordinate_systems(&self, _: &mut Services) -> Self { fn reverse_curve_coordinate_systems(&self, _: &mut Services) -> Self {
let path = self.path().reverse(); let path = self.path().reverse();
let boundary = self.boundary().reverse(); let boundary = self.boundary().reverse();
HalfEdge::new( Edge::new(
path, path,
boundary, boundary,
self.curve().clone(), self.curve().clone(),

View File

@ -1,98 +1,92 @@
use crate::{ use crate::{
objects::{Cycle, HalfEdge}, objects::{Cycle, Edge},
storage::Handle, storage::Handle,
}; };
/// Update a [`Cycle`] /// Update a [`Cycle`]
pub trait UpdateCycle { pub trait UpdateCycle {
/// Add half-edges to the cycle /// Add edges to the cycle
#[must_use] #[must_use]
fn add_half_edges( fn add_edges(&self, edges: impl IntoIterator<Item = Handle<Edge>>) -> Self;
&self,
half_edges: impl IntoIterator<Item = Handle<HalfEdge>>,
) -> Self;
/// Replace the provided half-edge /// Replace the provided edge
/// ///
/// # Panics /// # Panics
/// ///
/// Panics, unless this operation replaces exactly one half-edge. /// Panics, unless this operation replaces exactly one edge.
#[must_use] #[must_use]
fn replace_half_edge( fn replace_edge(
&self, &self,
original: &Handle<HalfEdge>, original: &Handle<Edge>,
replacement: Handle<HalfEdge>, replacement: Handle<Edge>,
) -> Self; ) -> Self;
/// Update the half-edge at the given index /// Update the edge at the given index
/// ///
/// # Panics /// # Panics
/// ///
/// Panics, unless this operation updates exactly one half-edge. /// Panics, unless this operation updates exactly one edge.
#[must_use] #[must_use]
fn update_nth_half_edge( fn update_nth_edge(
&self, &self,
index: usize, index: usize,
f: impl FnMut(&Handle<HalfEdge>) -> Handle<HalfEdge>, f: impl FnMut(&Handle<Edge>) -> Handle<Edge>,
) -> Self; ) -> Self;
} }
impl UpdateCycle for Cycle { impl UpdateCycle for Cycle {
fn add_half_edges( fn add_edges(&self, edges: impl IntoIterator<Item = Handle<Edge>>) -> Self {
&self, let edges = self.edges().cloned().chain(edges);
half_edges: impl IntoIterator<Item = Handle<HalfEdge>>, Cycle::new(edges)
) -> Self {
let half_edges = self.half_edges().cloned().chain(half_edges);
Cycle::new(half_edges)
} }
fn replace_half_edge( fn replace_edge(
&self, &self,
original: &Handle<HalfEdge>, original: &Handle<Edge>,
replacement: Handle<HalfEdge>, replacement: Handle<Edge>,
) -> Self { ) -> Self {
let mut num_replacements = 0; let mut num_replacements = 0;
let half_edges = self.half_edges().map(|half_edge| { let edges = self.edges().map(|edge| {
if half_edge.id() == original.id() { if edge.id() == original.id() {
num_replacements += 1; num_replacements += 1;
replacement.clone() replacement.clone()
} else { } else {
half_edge.clone() edge.clone()
} }
}); });
let cycle = Cycle::new(half_edges); let cycle = Cycle::new(edges);
assert_eq!( assert_eq!(
num_replacements, 1, num_replacements, 1,
"Expected operation to replace exactly one half-edge" "Expected operation to replace exactly one edge"
); );
cycle cycle
} }
fn update_nth_half_edge( fn update_nth_edge(
&self, &self,
index: usize, index: usize,
mut f: impl FnMut(&Handle<HalfEdge>) -> Handle<HalfEdge>, mut f: impl FnMut(&Handle<Edge>) -> Handle<Edge>,
) -> Self { ) -> Self {
let mut num_replacements = 0; let mut num_replacements = 0;
let half_edges = self.half_edges().enumerate().map(|(i, half_edge)| { let edges = self.edges().enumerate().map(|(i, edge)| {
if i == index { if i == index {
num_replacements += 1; num_replacements += 1;
f(half_edge) f(edge)
} else { } else {
half_edge.clone() edge.clone()
} }
}); });
let cycle = Cycle::new(half_edges); let cycle = Cycle::new(edges);
assert_eq!( assert_eq!(
num_replacements, 1, num_replacements, 1,
"Expected operation to replace exactly one half-edge" "Expected operation to replace exactly one edge"
); );
cycle cycle

View File

@ -2,32 +2,32 @@ use fj_math::Point;
use crate::{ use crate::{
geometry::{CurveBoundary, SurfacePath}, geometry::{CurveBoundary, SurfacePath},
objects::{Curve, HalfEdge, Vertex}, objects::{Curve, Edge, Vertex},
storage::Handle, storage::Handle,
}; };
/// Update a [`HalfEdge`] /// Update a [`Edge`]
pub trait UpdateHalfEdge { pub trait UpdateEdge {
/// Replace the path of the half-edge /// Replace the path of the edge
#[must_use] #[must_use]
fn replace_path(&self, path: SurfacePath) -> Self; fn replace_path(&self, path: SurfacePath) -> Self;
/// Replace the boundary of the half-edge /// Replace the boundary of the edge
#[must_use] #[must_use]
fn replace_boundary(&self, boundary: CurveBoundary<Point<1>>) -> Self; fn replace_boundary(&self, boundary: CurveBoundary<Point<1>>) -> Self;
/// Replace the curve of the half-edge /// Replace the curve of the edge
#[must_use] #[must_use]
fn replace_curve(&self, curve: Handle<Curve>) -> Self; fn replace_curve(&self, curve: Handle<Curve>) -> Self;
/// Replace the start vertex of the half-edge /// Replace the start vertex of the 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;
} }
impl UpdateHalfEdge for HalfEdge { impl UpdateEdge for Edge {
fn replace_path(&self, path: SurfacePath) -> Self { fn replace_path(&self, path: SurfacePath) -> Self {
HalfEdge::new( Edge::new(
path, path,
self.boundary(), self.boundary(),
self.curve().clone(), self.curve().clone(),
@ -36,7 +36,7 @@ impl UpdateHalfEdge for HalfEdge {
} }
fn replace_boundary(&self, boundary: CurveBoundary<Point<1>>) -> Self { fn replace_boundary(&self, boundary: CurveBoundary<Point<1>>) -> Self {
HalfEdge::new( Edge::new(
self.path(), self.path(),
boundary, boundary,
self.curve().clone(), self.curve().clone(),
@ -45,7 +45,7 @@ impl UpdateHalfEdge for HalfEdge {
} }
fn replace_curve(&self, curve: Handle<Curve>) -> Self { fn replace_curve(&self, curve: Handle<Curve>) -> Self {
HalfEdge::new( Edge::new(
self.path(), self.path(),
self.boundary(), self.boundary(),
curve, curve,
@ -54,7 +54,7 @@ impl UpdateHalfEdge for HalfEdge {
} }
fn replace_start_vertex(&self, start_vertex: Handle<Vertex>) -> Self { fn replace_start_vertex(&self, start_vertex: Handle<Vertex>) -> Self {
HalfEdge::new( Edge::new(
self.path(), self.path(),
self.boundary(), self.boundary(),
self.curve().clone(), self.curve().clone(),

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
objects::{Face, HalfEdge, Shell, Surface}, objects::{Edge, Face, Shell, Surface},
storage::Handle, storage::Handle,
}; };
@ -8,21 +8,21 @@ pub trait AllEdgesWithSurface {
/// Access all edges referenced by the object and the surface they're on /// Access all edges referenced by the object and the surface they're on
fn all_edges_with_surface( fn all_edges_with_surface(
&self, &self,
result: &mut Vec<(Handle<HalfEdge>, Handle<Surface>)>, result: &mut Vec<(Handle<Edge>, Handle<Surface>)>,
); );
} }
impl AllEdgesWithSurface for Face { impl AllEdgesWithSurface for Face {
fn all_edges_with_surface( fn all_edges_with_surface(
&self, &self,
result: &mut Vec<(Handle<HalfEdge>, Handle<Surface>)>, result: &mut Vec<(Handle<Edge>, Handle<Surface>)>,
) { ) {
for cycle in self.region().all_cycles() { for cycle in self.region().all_cycles() {
result.extend( result.extend(
cycle cycle
.half_edges() .edges()
.cloned() .cloned()
.map(|half_edge| (half_edge, self.surface().clone())), .map(|edge| (edge, self.surface().clone())),
); );
} }
} }
@ -31,7 +31,7 @@ impl AllEdgesWithSurface for Face {
impl AllEdgesWithSurface for Shell { impl AllEdgesWithSurface for Shell {
fn all_edges_with_surface( fn all_edges_with_surface(
&self, &self,
result: &mut Vec<(Handle<HalfEdge>, Handle<Surface>)>, result: &mut Vec<(Handle<Edge>, Handle<Surface>)>,
) { ) {
for face in self.faces() { for face in self.faces() {
face.all_edges_with_surface(result); face.all_edges_with_surface(result);

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
geometry::CurveBoundary, geometry::CurveBoundary,
objects::{Cycle, Face, HalfEdge, Region, Shell, Vertex}, objects::{Cycle, Edge, Face, Region, Shell, Vertex},
storage::Handle, storage::Handle,
}; };
@ -12,17 +12,17 @@ pub trait BoundingVerticesOfEdge {
/// method is called on. /// method is called on.
fn bounding_vertices_of_edge( fn bounding_vertices_of_edge(
&self, &self,
edge: &Handle<HalfEdge>, edge: &Handle<Edge>,
) -> Option<CurveBoundary<Vertex>>; ) -> Option<CurveBoundary<Vertex>>;
} }
impl BoundingVerticesOfEdge for Cycle { impl BoundingVerticesOfEdge for Cycle {
fn bounding_vertices_of_edge( fn bounding_vertices_of_edge(
&self, &self,
edge: &Handle<HalfEdge>, edge: &Handle<Edge>,
) -> Option<CurveBoundary<Vertex>> { ) -> Option<CurveBoundary<Vertex>> {
let start = edge.start_vertex().clone(); let start = edge.start_vertex().clone();
let end = self.half_edge_after(edge)?.start_vertex().clone(); let end = self.edge_after(edge)?.start_vertex().clone();
Some(CurveBoundary::from([start, end])) Some(CurveBoundary::from([start, end]))
} }
@ -31,7 +31,7 @@ impl BoundingVerticesOfEdge for Cycle {
impl BoundingVerticesOfEdge for Region { impl BoundingVerticesOfEdge for Region {
fn bounding_vertices_of_edge( fn bounding_vertices_of_edge(
&self, &self,
edge: &Handle<HalfEdge>, edge: &Handle<Edge>,
) -> Option<CurveBoundary<Vertex>> { ) -> Option<CurveBoundary<Vertex>> {
for cycle in self.all_cycles() { for cycle in self.all_cycles() {
if let Some(vertices) = cycle.bounding_vertices_of_edge(edge) { if let Some(vertices) = cycle.bounding_vertices_of_edge(edge) {
@ -46,7 +46,7 @@ impl BoundingVerticesOfEdge for Region {
impl BoundingVerticesOfEdge for Face { impl BoundingVerticesOfEdge for Face {
fn bounding_vertices_of_edge( fn bounding_vertices_of_edge(
&self, &self,
edge: &Handle<HalfEdge>, edge: &Handle<Edge>,
) -> Option<CurveBoundary<Vertex>> { ) -> Option<CurveBoundary<Vertex>> {
self.region().bounding_vertices_of_edge(edge) self.region().bounding_vertices_of_edge(edge)
} }
@ -55,7 +55,7 @@ impl BoundingVerticesOfEdge for Face {
impl BoundingVerticesOfEdge for Shell { impl BoundingVerticesOfEdge for Shell {
fn bounding_vertices_of_edge( fn bounding_vertices_of_edge(
&self, &self,
edge: &Handle<HalfEdge>, edge: &Handle<Edge>,
) -> Option<CurveBoundary<Vertex>> { ) -> Option<CurveBoundary<Vertex>> {
for face in self.faces() { for face in self.faces() {
if let Some(vertices) = face.bounding_vertices_of_edge(edge) { if let Some(vertices) = face.bounding_vertices_of_edge(edge) {

View File

@ -3,8 +3,8 @@
//! Objects have methods that provide access to anything that the object itself //! Objects have methods that provide access to anything that the object itself
//! has direct access to. However, not all potentially interesting information //! has direct access to. However, not all potentially interesting information
//! can be accessed that way. An example are the bounding vertices of an edge: //! can be accessed that way. An example are the bounding vertices of an edge:
//! `HalfEdge` only stores its starting vertex, so you need a `Cycle` to get //! `Edge` only stores its starting vertex, so you need a `Cycle` to get both
//! both vertices. //! vertices.
//! //!
//! This module provides traits express such non-trivial queries, and implements //! This module provides traits express such non-trivial queries, and implements
//! them for various objects that have the information to answer the query. //! them for various objects that have the information to answer the query.

View File

@ -1,6 +1,6 @@
use fj_math::{Point, Scalar}; use fj_math::{Point, Scalar};
use crate::objects::{Cycle, HalfEdge}; use crate::objects::{Cycle, Edge};
use super::{Validate, ValidationConfig, ValidationError}; use super::{Validate, ValidationConfig, ValidationError};
@ -10,60 +10,58 @@ impl Validate for Cycle {
config: &ValidationConfig, config: &ValidationConfig,
errors: &mut Vec<ValidationError>, errors: &mut Vec<ValidationError>,
) { ) {
CycleValidationError::check_half_edges_disconnected( CycleValidationError::check_edges_disconnected(self, config, errors);
self, config, errors, CycleValidationError::check_enough_edges(self, config, errors);
);
CycleValidationError::check_enough_half_edges(self, config, errors);
} }
} }
/// [`Cycle`] validation failed /// [`Cycle`] validation failed
#[derive(Clone, Debug, thiserror::Error)] #[derive(Clone, Debug, thiserror::Error)]
pub enum CycleValidationError { pub enum CycleValidationError {
/// [`Cycle`]'s half-edges are not connected /// [`Cycle`]'s edges are not connected
#[error( #[error(
"Adjacent `HalfEdge`s are distinct\n\ "Adjacent `Edge`s are distinct\n\
- End position of first `HalfEdge`: {end_of_first:?}\n\ - End position of first `Edge`: {end_of_first:?}\n\
- Start position of second `HalfEdge`: {start_of_second:?}\n\ - Start position of second `Edge`: {start_of_second:?}\n\
- `HalfEdge`s: {half_edges:#?}" - `Edge`s: {edges:#?}"
)] )]
HalfEdgesDisconnected { EdgesDisconnected {
/// The end position of the first [`HalfEdge`] /// The end position of the first [`Edge`]
end_of_first: Point<2>, end_of_first: Point<2>,
/// The start position of the second [`HalfEdge`] /// The start position of the second [`Edge`]
start_of_second: Point<2>, start_of_second: Point<2>,
/// The distance between the two vertices /// The distance between the two vertices
distance: Scalar, distance: Scalar,
/// The half-edge /// The edges
half_edges: Box<(HalfEdge, HalfEdge)>, edges: Box<(Edge, Edge)>,
}, },
/// [`Cycle`]'s should have at least one `HalfEdge` /// [`Cycle`]'s should have at least one [`Edge`]
#[error("Expected at least one `HalfEdge`\n")] #[error("Expected at least one `Edge`\n")]
NotEnoughHalfEdges, NotEnoughEdges,
} }
impl CycleValidationError { impl CycleValidationError {
fn check_enough_half_edges( fn check_enough_edges(
cycle: &Cycle, cycle: &Cycle,
_config: &ValidationConfig, _config: &ValidationConfig,
errors: &mut Vec<ValidationError>, errors: &mut Vec<ValidationError>,
) { ) {
// If there are no half edges // If there are no half edges
if cycle.half_edges().next().is_none() { if cycle.edges().next().is_none() {
errors.push(Self::NotEnoughHalfEdges.into()); errors.push(Self::NotEnoughEdges.into());
} }
} }
fn check_half_edges_disconnected( fn check_edges_disconnected(
cycle: &Cycle, cycle: &Cycle,
config: &ValidationConfig, config: &ValidationConfig,
errors: &mut Vec<ValidationError>, errors: &mut Vec<ValidationError>,
) { ) {
for (first, second) in cycle.half_edge_pairs() { for (first, second) in cycle.edge_pairs() {
let end_of_first = { let end_of_first = {
let [_, end] = first.boundary().inner; let [_, end] = first.boundary().inner;
first.path().point_from_path_coords(end) first.path().point_from_path_coords(end)
@ -74,11 +72,11 @@ impl CycleValidationError {
if distance > config.identical_max_distance { if distance > config.identical_max_distance {
errors.push( errors.push(
Self::HalfEdgesDisconnected { Self::EdgesDisconnected {
end_of_first, end_of_first,
start_of_second, start_of_second,
distance, distance,
half_edges: Box::new(( edges: Box::new((
first.clone_object(), first.clone_object(),
second.clone_object(), second.clone_object(),
)), )),
@ -95,14 +93,14 @@ mod tests {
use crate::{ use crate::{
assert_contains_err, assert_contains_err,
objects::{Cycle, HalfEdge}, objects::{Cycle, Edge},
operations::{BuildCycle, BuildHalfEdge, Insert, UpdateCycle}, operations::{BuildCycle, BuildEdge, Insert, UpdateCycle},
services::Services, services::Services,
validate::{cycle::CycleValidationError, Validate, ValidationError}, validate::{cycle::CycleValidationError, Validate, ValidationError},
}; };
#[test] #[test]
fn half_edges_connected() -> anyhow::Result<()> { fn edges_connected() -> anyhow::Result<()> {
let mut services = Services::new(); let mut services = Services::new();
let valid = let valid =
@ -111,35 +109,26 @@ mod tests {
valid.validate_and_return_first_error()?; valid.validate_and_return_first_error()?;
let disconnected = { let disconnected = {
let half_edges = [ let edges = [
HalfEdge::line_segment( Edge::line_segment([[0., 0.], [1., 0.]], None, &mut services),
[[0., 0.], [1., 0.]], Edge::line_segment([[0., 0.], [1., 0.]], None, &mut services),
None,
&mut services,
),
HalfEdge::line_segment(
[[0., 0.], [1., 0.]],
None,
&mut services,
),
]; ];
let half_edges = let edges = edges.map(|edge| edge.insert(&mut services));
half_edges.map(|half_edge| half_edge.insert(&mut services));
Cycle::empty().add_half_edges(half_edges) Cycle::empty().add_edges(edges)
}; };
assert_contains_err!( assert_contains_err!(
disconnected, disconnected,
ValidationError::Cycle( ValidationError::Cycle(
CycleValidationError::HalfEdgesDisconnected { .. } CycleValidationError::EdgesDisconnected { .. }
) )
); );
let empty = Cycle::new([]); let empty = Cycle::new([]);
assert_contains_err!( assert_contains_err!(
empty, empty,
ValidationError::Cycle(CycleValidationError::NotEnoughHalfEdges) ValidationError::Cycle(CycleValidationError::NotEnoughEdges)
); );
Ok(()) Ok(())
} }

View File

@ -1,28 +1,28 @@
use fj_math::{Point, Scalar}; use fj_math::{Point, Scalar};
use crate::objects::HalfEdge; use crate::objects::Edge;
use super::{Validate, ValidationConfig, ValidationError}; use super::{Validate, ValidationConfig, ValidationError};
impl Validate for HalfEdge { impl Validate for Edge {
fn validate_with_config( fn validate_with_config(
&self, &self,
config: &ValidationConfig, config: &ValidationConfig,
errors: &mut Vec<ValidationError>, errors: &mut Vec<ValidationError>,
) { ) {
HalfEdgeValidationError::check_vertex_coincidence(self, config, errors); EdgeValidationError::check_vertex_coincidence(self, config, errors);
} }
} }
/// [`HalfEdge`] validation failed /// [`Edge`] validation failed
#[derive(Clone, Debug, thiserror::Error)] #[derive(Clone, Debug, thiserror::Error)]
pub enum HalfEdgeValidationError { pub enum EdgeValidationError {
/// [`HalfEdge`]'s vertices are coincident /// [`Edge`]'s vertices are coincident
#[error( #[error(
"Vertices of `HalfEdge` on curve are coincident\n\ "Vertices of `Edge` on curve are coincident\n\
- Position of back vertex: {back_position:?}\n\ - Position of back vertex: {back_position:?}\n\
- Position of front vertex: {front_position:?}\n\ - Position of front vertex: {front_position:?}\n\
- `HalfEdge`: {half_edge:#?}" - `Edge`: {edge:#?}"
)] )]
VerticesAreCoincident { VerticesAreCoincident {
/// The position of the back vertex /// The position of the back vertex
@ -34,18 +34,18 @@ pub enum HalfEdgeValidationError {
/// The distance between the two vertices /// The distance between the two vertices
distance: Scalar, distance: Scalar,
/// The half-edge /// The edge
half_edge: HalfEdge, edge: Edge,
}, },
} }
impl HalfEdgeValidationError { impl EdgeValidationError {
fn check_vertex_coincidence( fn check_vertex_coincidence(
half_edge: &HalfEdge, edge: &Edge,
config: &ValidationConfig, config: &ValidationConfig,
errors: &mut Vec<ValidationError>, errors: &mut Vec<ValidationError>,
) { ) {
let [back_position, front_position] = half_edge.boundary().inner; let [back_position, front_position] = edge.boundary().inner;
let distance = (back_position - front_position).magnitude(); let distance = (back_position - front_position).magnitude();
if distance < config.distinct_min_distance { if distance < config.distinct_min_distance {
@ -54,7 +54,7 @@ impl HalfEdgeValidationError {
back_position, back_position,
front_position, front_position,
distance, distance,
half_edge: half_edge.clone(), edge: edge.clone(),
} }
.into(), .into(),
); );
@ -68,22 +68,22 @@ mod tests {
use crate::{ use crate::{
assert_contains_err, assert_contains_err,
objects::HalfEdge, objects::Edge,
operations::BuildHalfEdge, operations::BuildEdge,
services::Services, services::Services,
validate::{HalfEdgeValidationError, Validate, ValidationError}, validate::{EdgeValidationError, Validate, ValidationError},
}; };
#[test] #[test]
fn half_edge_vertices_are_coincident() -> anyhow::Result<()> { fn edge_vertices_are_coincident() -> anyhow::Result<()> {
let mut services = Services::new(); let mut services = Services::new();
let valid = let valid =
HalfEdge::line_segment([[0., 0.], [1., 0.]], None, &mut services); Edge::line_segment([[0., 0.], [1., 0.]], None, &mut services);
let invalid = { let invalid = {
let boundary = [Point::from([0.]); 2]; let boundary = [Point::from([0.]); 2];
HalfEdge::new( Edge::new(
valid.path(), valid.path(),
boundary, boundary,
valid.curve().clone(), valid.curve().clone(),
@ -94,8 +94,8 @@ mod tests {
valid.validate_and_return_first_error()?; valid.validate_and_return_first_error()?;
assert_contains_err!( assert_contains_err!(
invalid, invalid,
ValidationError::HalfEdge( ValidationError::Edge(
HalfEdgeValidationError::VerticesAreCoincident { .. } EdgeValidationError::VerticesAreCoincident { .. }
) )
); );

View File

@ -38,18 +38,18 @@ pub enum FaceValidationError {
impl FaceValidationError { impl FaceValidationError {
fn check_interior_winding(face: &Face, errors: &mut Vec<ValidationError>) { fn check_interior_winding(face: &Face, errors: &mut Vec<ValidationError>) {
if face.region().exterior().half_edges().count() == 0 { if face.region().exterior().edges().count() == 0 {
// Can't determine winding, if the cycle has no half-edges. Sounds // Can't determine winding, if the cycle has no edges. Sounds like a
// like a job for a different validation check. // job for a different validation check.
return; return;
} }
let exterior_winding = face.region().exterior().winding(); let exterior_winding = face.region().exterior().winding();
for interior in face.region().interiors() { for interior in face.region().interiors() {
if interior.half_edges().count() == 0 { if interior.edges().count() == 0 {
// Can't determine winding, if the cycle has no half-edges. // Can't determine winding, if the cycle has no edges. Sounds
// Sounds like a job for a different validation check. // like a job for a different validation check.
continue; continue;
} }
let interior_winding = interior.winding(); let interior_winding = interior.winding();

View File

@ -12,7 +12,7 @@ mod surface;
mod vertex; mod vertex;
pub use self::{ pub use self::{
cycle::CycleValidationError, edge::HalfEdgeValidationError, cycle::CycleValidationError, edge::EdgeValidationError,
face::FaceValidationError, shell::ShellValidationError, face::FaceValidationError, shell::ShellValidationError,
solid::SolidValidationError, solid::SolidValidationError,
}; };
@ -103,14 +103,14 @@ pub enum ValidationError {
#[error("`Cycle` validation error")] #[error("`Cycle` validation error")]
Cycle(#[from] CycleValidationError), Cycle(#[from] CycleValidationError),
/// `Edge` validation error
#[error("`Edge` validation error")]
Edge(#[from] EdgeValidationError),
/// `Face` validation error /// `Face` validation error
#[error("`Face` validation error")] #[error("`Face` validation error")]
Face(#[from] FaceValidationError), Face(#[from] FaceValidationError),
/// `HalfEdge` validation error
#[error("`HalfEdge` validation error")]
HalfEdge(#[from] HalfEdgeValidationError),
/// `Shell` validation error /// `Shell` validation error
#[error("`Shell` validation error")] #[error("`Shell` validation error")]
Shell(#[from] ShellValidationError), Shell(#[from] ShellValidationError),

View File

@ -4,7 +4,7 @@ use fj_math::{Point, Scalar};
use crate::{ use crate::{
geometry::SurfaceGeometry, geometry::SurfaceGeometry,
objects::{HalfEdge, Shell, Surface}, objects::{Edge, Shell, Surface},
queries::{AllEdgesWithSurface, BoundingVerticesOfEdge}, queries::{AllEdgesWithSurface, BoundingVerticesOfEdge},
storage::{Handle, HandleWrapper}, storage::{Handle, HandleWrapper},
}; };
@ -35,23 +35,22 @@ pub enum ShellValidationError {
)] )]
CurveCoordinateSystemMismatch(Vec<CurveCoordinateSystemMismatch>), CurveCoordinateSystemMismatch(Vec<CurveCoordinateSystemMismatch>),
/// [`Shell`] contains global_edges not referred to by two half-edges /// [`Shell`] is not watertight
#[error("Shell is not watertight")] #[error("Shell is not watertight")]
NotWatertight, NotWatertight,
/// [`Shell`] contains half-edges that are coincident, but refer to /// [`Shell`] contains edges that are coincident, but not identical
/// different global_edges
#[error( #[error(
"`Shell` contains `HalfEdge`s that are coincident but refer to \ "`Shell` contains `Edge`s that are coincident but refer to different \
different `GlobalEdge`s\n\ `Curve`s\n\
Edge 1: {0:#?}\n\ Edge 1: {0:#?}\n\
Edge 2: {1:#?}" Edge 2: {1:#?}"
)] )]
CoincidentEdgesNotIdentical(Handle<HalfEdge>, Handle<HalfEdge>), CoincidentEdgesNotIdentical(Handle<Edge>, Handle<Edge>),
/// [`Shell`] contains half-edges that are identical, but do not coincide /// [`Shell`] contains edges that are identical, but do not coincide
#[error( #[error(
"Shell contains HalfEdges that are identical but do not coincide\n\ "Shell contains `Edge`s that are identical but do not coincide\n\
Edge 1: {edge_a:#?}\n\ Edge 1: {edge_a:#?}\n\
Surface for edge 1: {surface_a:#?}\n\ Surface for edge 1: {surface_a:#?}\n\
Edge 2: {edge_b:#?}\n\ Edge 2: {edge_b:#?}\n\
@ -59,13 +58,13 @@ pub enum ShellValidationError {
)] )]
IdenticalEdgesNotCoincident { IdenticalEdgesNotCoincident {
/// The first edge /// The first edge
edge_a: Handle<HalfEdge>, edge_a: Handle<Edge>,
/// The surface that the first edge is on /// The surface that the first edge is on
surface_a: Handle<Surface>, surface_a: Handle<Surface>,
/// The second edge /// The second edge
edge_b: Handle<HalfEdge>, edge_b: Handle<Edge>,
/// The surface that the second edge is on /// The surface that the second edge is on
surface_b: Handle<Surface>, surface_b: Handle<Surface>,
@ -80,14 +79,14 @@ pub enum ShellValidationError {
/// ///
/// Returns an [`Iterator`] of the distance at each sample. /// Returns an [`Iterator`] of the distance at each sample.
fn distances( fn distances(
edge_a: Handle<HalfEdge>, edge_a: Handle<Edge>,
surface_a: Handle<Surface>, surface_a: Handle<Surface>,
edge_b: Handle<HalfEdge>, edge_b: Handle<Edge>,
surface_b: Handle<Surface>, surface_b: Handle<Surface>,
) -> impl Iterator<Item = Scalar> { ) -> impl Iterator<Item = Scalar> {
fn sample( fn sample(
percent: f64, percent: f64,
(edge, surface): (&Handle<HalfEdge>, SurfaceGeometry), (edge, surface): (&Handle<Edge>, SurfaceGeometry),
) -> Point<3> { ) -> Point<3> {
let [start, end] = edge.boundary().inner; let [start, end] = edge.boundary().inner;
let path_coords = start + (end - start) * percent; let path_coords = start + (end - start) * percent;
@ -133,9 +132,9 @@ impl ShellValidationError {
} }
fn compare_curve_coords( fn compare_curve_coords(
edge_a: &Handle<HalfEdge>, edge_a: &Handle<Edge>,
surface_a: &Handle<Surface>, surface_a: &Handle<Surface>,
edge_b: &Handle<HalfEdge>, edge_b: &Handle<Edge>,
surface_b: &Handle<Surface>, surface_b: &Handle<Surface>,
config: &ValidationConfig, config: &ValidationConfig,
mismatches: &mut Vec<CurveCoordinateSystemMismatch>, mismatches: &mut Vec<CurveCoordinateSystemMismatch>,
@ -299,13 +298,11 @@ impl ShellValidationError {
for face in shell.faces() { for face in shell.faces() {
for cycle in face.region().all_cycles() { for cycle in face.region().all_cycles() {
for half_edge in cycle.half_edges() { for edge in cycle.edges() {
let curve = HandleWrapper::from(half_edge.curve().clone()); let curve = HandleWrapper::from(edge.curve().clone());
let bounding_vertices = cycle let bounding_vertices = cycle
.bounding_vertices_of_edge(half_edge) .bounding_vertices_of_edge(edge)
.expect( .expect("Cycle should provide bounds of its own edge")
"Cycle should provide bounds of its own half-edge",
)
.normalize(); .normalize();
let edge = (curve, bounding_vertices); let edge = (curve, bounding_vertices);
@ -330,7 +327,7 @@ impl ShellValidationError {
for face in shell.faces() { for face in shell.faces() {
for cycle in face.region().all_cycles() { for cycle in face.region().all_cycles() {
for edge in cycle.half_edges() { for edge in cycle.edges() {
let curve = HandleWrapper::from(edge.curve().clone()); let curve = HandleWrapper::from(edge.curve().clone());
let boundary = cycle let boundary = cycle
.bounding_vertices_of_edge(edge) .bounding_vertices_of_edge(edge)
@ -365,8 +362,8 @@ impl ShellValidationError {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CurveCoordinateSystemMismatch { pub struct CurveCoordinateSystemMismatch {
pub edge_a: Handle<HalfEdge>, pub edge_a: Handle<Edge>,
pub edge_b: Handle<HalfEdge>, pub edge_b: Handle<Edge>,
pub point_curve: Point<1>, pub point_curve: Point<1>,
pub point_a: Point<3>, pub point_a: Point<3>,
pub point_b: Point<3>, pub point_b: Point<3>,
@ -379,8 +376,8 @@ mod tests {
assert_contains_err, assert_contains_err,
objects::{Curve, Shell}, objects::{Curve, Shell},
operations::{ operations::{
BuildShell, Insert, Reverse, UpdateCycle, UpdateFace, BuildShell, Insert, Reverse, UpdateCycle, UpdateEdge, UpdateFace,
UpdateHalfEdge, UpdateRegion, UpdateShell, UpdateRegion, UpdateShell,
}, },
services::Services, services::Services,
validate::{shell::ShellValidationError, Validate, ValidationError}, validate::{shell::ShellValidationError, Validate, ValidationError},
@ -403,13 +400,10 @@ mod tests {
region region
.update_exterior(|cycle| { .update_exterior(|cycle| {
cycle cycle
.update_nth_half_edge(0, |half_edge| { .update_nth_edge(0, |edge| {
half_edge edge.replace_path(edge.path().reverse())
.replace_path(
half_edge.path().reverse(),
)
.replace_boundary( .replace_boundary(
half_edge.boundary().reverse(), edge.boundary().reverse(),
) )
.insert(&mut services) .insert(&mut services)
}) })
@ -448,12 +442,11 @@ mod tests {
region region
.update_exterior(|cycle| { .update_exterior(|cycle| {
cycle cycle
.update_nth_half_edge(0, |half_edge| { .update_nth_edge(0, |edge| {
let curve = let curve =
Curve::new().insert(&mut services); Curve::new().insert(&mut services);
half_edge edge.replace_curve(curve)
.replace_curve(curve)
.insert(&mut services) .insert(&mut services)
}) })
.insert(&mut services) .insert(&mut services)

View File

@ -74,7 +74,7 @@ impl SolidValidationError {
.flat_map(|face| { .flat_map(|face| {
face.region() face.region()
.all_cycles() .all_cycles()
.flat_map(|cycle| cycle.half_edges().cloned()) .flat_map(|cycle| cycle.edges().cloned())
.zip(repeat(face.surface().geometry())) .zip(repeat(face.surface().geometry()))
}) })
.map(|(h, s)| { .map(|(h, s)| {