Merge pull request #1598 from hannobraun/surface

Move reference to `Surface` from `HalfEdge` to `Cycle`
This commit is contained in:
Hanno Braun 2023-02-17 15:38:53 +01:00 committed by GitHub
commit 06950a8e47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 337 additions and 303 deletions

View File

@ -2,6 +2,8 @@
//! //!
//! See [`CycleApprox`]. //! See [`CycleApprox`].
use std::ops::Deref;
use fj_math::Segment; use fj_math::Segment;
use crate::objects::Cycle; use crate::objects::Cycle;
@ -23,7 +25,10 @@ impl Approx for &Cycle {
let half_edges = self let half_edges = self
.half_edges() .half_edges()
.map(|half_edge| half_edge.approx_with_cache(tolerance, cache)) .map(|half_edge| {
(half_edge, self.surface().deref())
.approx_with_cache(tolerance, cache)
})
.collect(); .collect();
CycleApprox { half_edges } CycleApprox { half_edges }

View File

@ -5,9 +5,10 @@
//! approximations are usually used to build cycle approximations, and this way, //! approximations are usually used to build cycle approximations, and this way,
//! the caller doesn't have to call with duplicate vertices. //! the caller doesn't have to call with duplicate vertices.
use std::ops::Deref; use crate::{
objects::{HalfEdge, Surface},
use crate::{objects::HalfEdge, storage::Handle}; storage::Handle,
};
use super::{ use super::{
curve::{CurveApprox, CurveCache}, curve::{CurveApprox, CurveCache},
@ -15,7 +16,7 @@ use super::{
Approx, ApproxPoint, Tolerance, Approx, ApproxPoint, Tolerance,
}; };
impl Approx for &Handle<HalfEdge> { impl Approx for (&Handle<HalfEdge>, &Surface) {
type Approximation = HalfEdgeApprox; type Approximation = HalfEdgeApprox;
type Cache = CurveCache; type Cache = CurveCache;
@ -24,15 +25,17 @@ impl Approx for &Handle<HalfEdge> {
tolerance: impl Into<Tolerance>, tolerance: impl Into<Tolerance>,
cache: &mut Self::Cache, cache: &mut Self::Cache,
) -> Self::Approximation { ) -> Self::Approximation {
let boundary = self.boundary(); let (half_edge, surface) = self;
let boundary = half_edge.boundary();
let range = RangeOnPath { boundary }; let range = RangeOnPath { boundary };
let first = ApproxPoint::new( let first = ApproxPoint::new(
self.start_vertex().position(), half_edge.start_vertex().position(),
self.start_vertex().global_form().position(), half_edge.start_vertex().global_form().position(),
) )
.with_source((self.clone(), self.boundary()[0])); .with_source((half_edge.clone(), half_edge.boundary()[0]));
let curve_approx = (self.curve(), self.surface().deref(), range) let curve_approx = (half_edge.curve(), surface, range)
.approx_with_cache(tolerance, cache); .approx_with_cache(tolerance, cache);
HalfEdgeApprox { HalfEdgeApprox {

View File

@ -76,7 +76,7 @@ mod tests {
use crate::{ use crate::{
builder::{CurveBuilder, HalfEdgeBuilder}, builder::{CurveBuilder, HalfEdgeBuilder},
partial::{Partial, PartialCurve, PartialHalfEdge, PartialObject}, partial::{PartialCurve, PartialHalfEdge, PartialObject},
services::Services, services::Services,
}; };
@ -86,16 +86,14 @@ mod tests {
fn compute_edge_in_front_of_curve_origin() { fn compute_edge_in_front_of_curve_origin() {
let mut services = Services::new(); let mut services = Services::new();
let surface = Partial::from(services.objects.surfaces.xy_plane()); let surface = services.objects.surfaces.xy_plane();
let mut curve = PartialCurve::default(); let mut curve = PartialCurve::default();
curve.update_as_u_axis(); curve.update_as_u_axis();
let curve = curve.build(&mut services.objects); let curve = curve.build(&mut services.objects);
let half_edge = { let half_edge = {
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points( half_edge.update_as_line_segment_from_points([[1., -1.], [1., 1.]]);
surface, half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
[[1., -1.], [1., 1.]],
);
half_edge.build(&mut services.objects) half_edge.build(&mut services.objects)
}; };
@ -114,16 +112,15 @@ mod tests {
fn compute_edge_behind_curve_origin() { fn compute_edge_behind_curve_origin() {
let mut services = Services::new(); let mut services = Services::new();
let surface = Partial::from(services.objects.surfaces.xy_plane()); let surface = services.objects.surfaces.xy_plane();
let mut curve = PartialCurve::default(); let mut curve = PartialCurve::default();
curve.update_as_u_axis(); curve.update_as_u_axis();
let curve = curve.build(&mut services.objects); let curve = curve.build(&mut services.objects);
let half_edge = { let half_edge = {
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points( half_edge
surface, .update_as_line_segment_from_points([[-1., -1.], [-1., 1.]]);
[[-1., -1.], [-1., 1.]], half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
);
half_edge.build(&mut services.objects) half_edge.build(&mut services.objects)
}; };
@ -142,16 +139,15 @@ mod tests {
fn compute_edge_parallel_to_curve() { fn compute_edge_parallel_to_curve() {
let mut services = Services::new(); let mut services = Services::new();
let surface = Partial::from(services.objects.surfaces.xy_plane()); let surface = services.objects.surfaces.xy_plane();
let mut curve = PartialCurve::default(); let mut curve = PartialCurve::default();
curve.update_as_u_axis(); curve.update_as_u_axis();
let curve = curve.build(&mut services.objects); let curve = curve.build(&mut services.objects);
let half_edge = { let half_edge = {
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points( half_edge
surface, .update_as_line_segment_from_points([[-1., -1.], [1., -1.]]);
[[-1., -1.], [1., -1.]], half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
);
half_edge.build(&mut services.objects) half_edge.build(&mut services.objects)
}; };
@ -165,16 +161,14 @@ mod tests {
fn compute_edge_on_curve() { fn compute_edge_on_curve() {
let mut services = Services::new(); let mut services = Services::new();
let surface = Partial::from(services.objects.surfaces.xy_plane()); let surface = services.objects.surfaces.xy_plane();
let mut curve = PartialCurve::default(); let mut curve = PartialCurve::default();
curve.update_as_u_axis(); curve.update_as_u_axis();
let curve = curve.build(&mut services.objects); let curve = curve.build(&mut services.objects);
let half_edge = { let half_edge = {
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points( half_edge.update_as_line_segment_from_points([[-1., 0.], [1., 0.]]);
surface, half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
[[-1., 0.], [1., 0.]],
);
half_edge.build(&mut services.objects) half_edge.build(&mut services.objects)
}; };

View File

@ -17,6 +17,6 @@ impl Reverse for Handle<Cycle> {
edges.reverse(); edges.reverse();
Cycle::new(edges).insert(objects) Cycle::new(self.surface().clone(), edges).insert(objects)
} }
} }

View File

@ -19,7 +19,6 @@ impl Reverse for Handle<HalfEdge> {
}; };
HalfEdge::new( HalfEdge::new(
self.surface().clone(),
self.curve().clone(), self.curve().clone(),
vertices, vertices,
self.global_form().clone(), self.global_form().clone(),

View File

@ -1,12 +1,10 @@
use std::ops::Deref;
use fj_interop::{ext::ArrayExt, mesh::Color}; use fj_interop::{ext::ArrayExt, mesh::Color};
use fj_math::{Point, Scalar, Vector}; use fj_math::{Point, Scalar, Vector};
use crate::{ use crate::{
builder::{CycleBuilder, HalfEdgeBuilder}, builder::{CycleBuilder, HalfEdgeBuilder},
insert::Insert, insert::Insert,
objects::{Face, HalfEdge, Objects}, objects::{Face, HalfEdge, Objects, Surface},
partial::{Partial, PartialFace, PartialObject}, partial::{Partial, PartialFace, PartialObject},
services::Service, services::Service,
storage::Handle, storage::Handle,
@ -14,7 +12,7 @@ use crate::{
use super::{Sweep, SweepCache}; use super::{Sweep, SweepCache};
impl Sweep for (Handle<HalfEdge>, Color) { impl Sweep for (Handle<HalfEdge>, &Surface, Color) {
type Swept = (Handle<Face>, Handle<HalfEdge>); type Swept = (Handle<Face>, Handle<HalfEdge>);
fn sweep_with_cache( fn sweep_with_cache(
@ -23,7 +21,7 @@ impl Sweep for (Handle<HalfEdge>, Color) {
cache: &mut SweepCache, cache: &mut SweepCache,
objects: &mut Service<Objects>, objects: &mut Service<Objects>,
) -> Self::Swept { ) -> Self::Swept {
let (edge, color) = self; let (edge, surface, color) = self;
let path = path.into(); let path = path.into();
// The result of sweeping an edge is a face. Let's create that. // The result of sweeping an edge is a face. Let's create that.
@ -36,7 +34,7 @@ impl Sweep for (Handle<HalfEdge>, Color) {
// be created by sweeping a curve, so let's sweep the curve of the edge // be created by sweeping a curve, so let's sweep the curve of the edge
// we're sweeping. // we're sweeping.
face.exterior.write().surface = Partial::from( face.exterior.write().surface = Partial::from(
(edge.curve().clone(), edge.surface().deref()) (edge.curve().clone(), surface)
.sweep_with_cache(path, cache, objects), .sweep_with_cache(path, cache, objects),
); );
@ -150,6 +148,8 @@ impl Sweep for (Handle<HalfEdge>, Color) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::ops::Deref;
use fj_interop::{ext::ArrayExt, mesh::Color}; use fj_interop::{ext::ArrayExt, mesh::Color};
use fj_math::Point; use fj_math::Point;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@ -168,38 +168,33 @@ mod tests {
fn sweep() { fn sweep() {
let mut services = Services::new(); let mut services = Services::new();
let surface = services.objects.surfaces.xy_plane();
let half_edge = { let half_edge = {
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points( half_edge.update_as_line_segment_from_points([[0., 0.], [1., 0.]]);
services.objects.surfaces.xy_plane(), half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
[[0., 0.], [1., 0.]],
);
half_edge half_edge
.build(&mut services.objects) .build(&mut services.objects)
.insert(&mut services.objects) .insert(&mut services.objects)
}; };
let (face, _) = (half_edge, Color::default()) let (face, _) = (half_edge, surface.deref(), Color::default())
.sweep([0., 0., 1.], &mut services.objects); .sweep([0., 0., 1.], &mut services.objects);
let expected_face = { let expected_face = {
let surface = Partial::from(services.objects.surfaces.xz_plane()); let surface = services.objects.surfaces.xz_plane();
let bottom = { let bottom = {
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points( half_edge
surface.clone(), .update_as_line_segment_from_points([[0., 0.], [1., 0.]]);
[[0., 0.], [1., 0.]],
);
half_edge half_edge
}; };
let side_up = { let side_up = {
let mut side_up = PartialHalfEdge { let mut side_up = PartialHalfEdge::default();
surface: surface.clone(),
..Default::default()
};
{ {
let [back, front] = side_up let [back, front] = side_up
@ -219,10 +214,7 @@ mod tests {
side_up side_up
}; };
let top = { let top = {
let mut top = PartialHalfEdge { let mut top = PartialHalfEdge::default();
surface: surface.clone(),
..Default::default()
};
{ {
let [(back, back_surface), (front, front_surface)] = let [(back, back_surface), (front, front_surface)] =
@ -238,6 +230,7 @@ mod tests {
top.infer_global_form(); top.infer_global_form();
top.update_as_line_segment(); top.update_as_line_segment();
top.infer_vertex_positions_if_necessary(&surface.geometry());
Partial::from( Partial::from(
top.build(&mut services.objects) top.build(&mut services.objects)
@ -247,10 +240,7 @@ mod tests {
.clone() .clone()
}; };
let side_down = { let side_down = {
let mut side_down = PartialHalfEdge { let mut side_down = PartialHalfEdge::default();
surface,
..Default::default()
};
let [(back, back_surface), (front, front_surface)] = let [(back, back_surface), (front, front_surface)] =
side_down.vertices.each_mut_ext(); side_down.vertices.each_mut_ext();
@ -263,6 +253,8 @@ mod tests {
side_down.infer_global_form(); side_down.infer_global_form();
side_down.update_as_line_segment(); side_down.update_as_line_segment();
side_down
.infer_vertex_positions_if_necessary(&surface.geometry());
Partial::from( Partial::from(
side_down side_down
@ -273,7 +265,10 @@ mod tests {
.clone() .clone()
}; };
let mut cycle = PartialCycle::default(); let mut cycle = PartialCycle {
surface: Partial::from(surface),
..Default::default()
};
cycle.half_edges.extend( cycle.half_edges.extend(
[bottom, side_up, top, side_down].map(Partial::from_partial), [bottom, side_up, top, side_down].map(Partial::from_partial),
); );

View File

@ -1,3 +1,5 @@
use std::ops::Deref;
use fj_interop::ext::ArrayExt; use fj_interop::ext::ArrayExt;
use fj_math::{Scalar, Vector}; use fj_math::{Scalar, Vector};
@ -70,7 +72,8 @@ impl Sweep for Handle<Face> {
let mut original_edges = Vec::new(); let mut original_edges = Vec::new();
let mut top_edges = Vec::new(); let mut top_edges = Vec::new();
for half_edge in cycle.half_edges().cloned() { for half_edge in cycle.half_edges().cloned() {
let (face, top_edge) = (half_edge.clone(), self.color()) let (face, top_edge) =
(half_edge.clone(), self.surface().deref(), self.color())
.sweep_with_cache(path, cache, objects); .sweep_with_cache(path, cache, objects);
faces.push(face); faces.push(face);
@ -128,6 +131,8 @@ impl Sweep for Handle<Face> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::ops::Deref;
use fj_interop::{ext::SliceExt, mesh::Color}; use fj_interop::{ext::SliceExt, mesh::Color};
use crate::{ use crate::{
@ -182,7 +187,7 @@ mod tests {
.reverse(&mut services.objects); .reverse(&mut services.objects);
let mut top = PartialFace::default(); let mut top = PartialFace::default();
top.exterior.write().surface = top.exterior.write().surface =
Partial::from(surface.translate(UP, &mut services.objects)); Partial::from(surface.clone().translate(UP, &mut services.objects));
top.exterior.write().update_as_polygon_from_points(TRIANGLE); top.exterior.write().update_as_polygon_from_points(TRIANGLE);
let top = top let top = top
.build(&mut services.objects) .build(&mut services.objects)
@ -195,17 +200,16 @@ mod tests {
let side_faces = triangle.array_windows_ext().map(|&[a, b]| { let side_faces = triangle.array_windows_ext().map(|&[a, b]| {
let half_edge = { let half_edge = {
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points( half_edge.update_as_line_segment_from_points([a, b]);
services.objects.surfaces.xy_plane(), half_edge
[a, b], .infer_vertex_positions_if_necessary(&surface.geometry());
);
half_edge half_edge
.build(&mut services.objects) .build(&mut services.objects)
.insert(&mut services.objects) .insert(&mut services.objects)
}; };
let (face, _) = let (face, _) = (half_edge, surface.deref(), Color::default())
(half_edge, Color::default()).sweep(UP, &mut services.objects); .sweep(UP, &mut services.objects);
face face
}); });
@ -250,7 +254,7 @@ mod tests {
.insert(&mut services.objects) .insert(&mut services.objects)
.reverse(&mut services.objects); .reverse(&mut services.objects);
let mut top = PartialFace::default(); let mut top = PartialFace::default();
top.exterior.write().surface = Partial::from(surface); top.exterior.write().surface = Partial::from(surface.clone());
top.exterior.write().update_as_polygon_from_points(TRIANGLE); top.exterior.write().update_as_polygon_from_points(TRIANGLE);
let top = top let top = top
.build(&mut services.objects) .build(&mut services.objects)
@ -263,17 +267,16 @@ mod tests {
let side_faces = triangle.array_windows_ext().map(|&[a, b]| { let side_faces = triangle.array_windows_ext().map(|&[a, b]| {
let half_edge = { let half_edge = {
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points( half_edge.update_as_line_segment_from_points([a, b]);
services.objects.surfaces.xy_plane(), half_edge
[a, b], .infer_vertex_positions_if_necessary(&surface.geometry());
);
half_edge half_edge
.build(&mut services.objects) .build(&mut services.objects)
.insert(&mut services.objects) .insert(&mut services.objects)
.reverse(&mut services.objects) .reverse(&mut services.objects)
}; };
let (face, _) = (half_edge, Color::default()) let (face, _) = (half_edge, surface.deref(), Color::default())
.sweep(DOWN, &mut services.objects); .sweep(DOWN, &mut services.objects);
face face
}); });

View File

@ -14,12 +14,16 @@ impl TransformObject for Cycle {
objects: &mut Service<Objects>, objects: &mut Service<Objects>,
cache: &mut TransformCache, cache: &mut TransformCache,
) -> Self { ) -> Self {
let surface = self
.surface()
.clone()
.transform_with_cache(transform, objects, cache);
let half_edges = self.half_edges().map(|half_edge| { let half_edges = self.half_edges().map(|half_edge| {
half_edge half_edge
.clone() .clone()
.transform_with_cache(transform, objects, cache) .transform_with_cache(transform, objects, cache)
}); });
Self::new(half_edges) Self::new(surface, half_edges)
} }
} }

View File

@ -15,10 +15,6 @@ impl TransformObject for HalfEdge {
objects: &mut Service<Objects>, objects: &mut Service<Objects>,
cache: &mut TransformCache, cache: &mut TransformCache,
) -> Self { ) -> Self {
let surface = self
.surface()
.clone()
.transform_with_cache(transform, objects, cache);
let curve = self let curve = self
.curve() .curve()
.clone() .clone()
@ -36,7 +32,7 @@ impl TransformObject for HalfEdge {
.clone() .clone()
.transform_with_cache(transform, objects, cache); .transform_with_cache(transform, objects, cache);
Self::new(surface, curve, boundary, global_form) Self::new(curve, boundary, global_form)
} }
} }

View File

@ -149,7 +149,6 @@ impl CycleBuilder for PartialCycle {
let [_, vertex] = &mut new_half_edge.vertices; let [_, vertex] = &mut new_half_edge.vertices;
vertex.1 = shared_surface_vertex; vertex.1 = shared_surface_vertex;
new_half_edge.surface = self.surface.clone();
new_half_edge.infer_global_form(); new_half_edge.infer_global_form();
} }
@ -247,7 +246,8 @@ impl CycleBuilder for PartialCycle {
let mut this = half_edges.pop_front().expect( let mut this = half_edges.pop_front().expect(
"Pushed correct number of half-edges; should be able to pop", "Pushed correct number of half-edges; should be able to pop",
); );
this.write().update_from_other_edge(&other); this.write()
.update_from_other_edge(&other, &self.surface.read().geometry);
this this
}) })
} }
@ -261,7 +261,8 @@ impl CycleBuilder for PartialCycle {
{ {
edges.map(|other| { edges.map(|other| {
let mut this = self.add_half_edge(); let mut this = self.add_half_edge();
this.write().update_from_other_edge(&other); this.write()
.update_from_other_edge(&other, &self.surface.read().geometry);
this this
}) })
} }

View File

@ -2,8 +2,11 @@ use fj_interop::ext::ArrayExt;
use fj_math::{Point, Scalar}; use fj_math::{Point, Scalar};
use crate::{ use crate::{
geometry::path::{GlobalPath, SurfacePath}, geometry::{
objects::{GlobalEdge, HalfEdge, Surface}, path::{GlobalPath, SurfacePath},
surface::SurfaceGeometry,
},
objects::{GlobalEdge, HalfEdge},
partial::{MaybeSurfacePath, Partial, PartialGlobalEdge, PartialHalfEdge}, partial::{MaybeSurfacePath, Partial, PartialGlobalEdge, PartialHalfEdge},
}; };
@ -25,7 +28,6 @@ pub trait HalfEdgeBuilder {
/// Update partial half-edge to be a line segment, from the given points /// Update partial half-edge to be a line segment, from the given points
fn update_as_line_segment_from_points( fn update_as_line_segment_from_points(
&mut self, &mut self,
surface: impl Into<Partial<Surface>>,
points: [impl Into<Point<2>>; 2], points: [impl Into<Point<2>>; 2],
); );
@ -38,6 +40,12 @@ pub trait HalfEdgeBuilder {
/// it. /// it.
fn infer_global_form(&mut self) -> Partial<GlobalEdge>; fn infer_global_form(&mut self) -> Partial<GlobalEdge>;
/// Infer the vertex positions (surface and global), if not already set
fn infer_vertex_positions_if_necessary(
&mut self,
surface: &SurfaceGeometry,
);
/// Update this edge from another /// Update this edge from another
/// ///
/// Infers as much information about this edge from the other, under the /// Infers as much information about this edge from the other, under the
@ -47,7 +55,11 @@ pub trait HalfEdgeBuilder {
/// under various circumstances. As long as you're only dealing with lines /// under various circumstances. As long as you're only dealing with lines
/// and planes, you should be fine. Otherwise, please read the code of this /// and planes, you should be fine. Otherwise, please read the code of this
/// method carefully, to make sure you don't run into trouble. /// method carefully, to make sure you don't run into trouble.
fn update_from_other_edge(&mut self, other: &Partial<HalfEdge>); fn update_from_other_edge(
&mut self,
other: &Partial<HalfEdge>,
surface: &Option<SurfaceGeometry>,
);
} }
impl HalfEdgeBuilder for PartialHalfEdge { impl HalfEdgeBuilder for PartialHalfEdge {
@ -111,11 +123,8 @@ impl HalfEdgeBuilder for PartialHalfEdge {
fn update_as_line_segment_from_points( fn update_as_line_segment_from_points(
&mut self, &mut self,
surface: impl Into<Partial<Surface>>,
points: [impl Into<Point<2>>; 2], points: [impl Into<Point<2>>; 2],
) { ) {
self.surface = surface.into();
for (vertex, point) in self.vertices.each_mut_ext().zip_ext(points) { for (vertex, point) in self.vertices.each_mut_ext().zip_ext(points) {
let mut surface_form = vertex.1.write(); let mut surface_form = vertex.1.write();
surface_form.position = Some(point.into()); surface_form.position = Some(point.into());
@ -166,14 +175,62 @@ impl HalfEdgeBuilder for PartialHalfEdge {
self.global_form.clone() self.global_form.clone()
} }
fn update_from_other_edge(&mut self, other: &Partial<HalfEdge>) { fn infer_vertex_positions_if_necessary(
&mut self,
surface: &SurfaceGeometry,
) {
let path = self
.curve
.read()
.path
.expect("Can't infer vertex positions without curve");
let MaybeSurfacePath::Defined(path) = path else {
panic!("Can't infer vertex positions with undefined path");
};
for vertex in &mut self.vertices {
let position_curve = vertex
.0
.expect("Can't infer surface position without curve position");
let position_surface = vertex.1.read().position;
// Infer surface position, if not available.
let position_surface = match position_surface {
Some(position_surface) => position_surface,
None => {
let position_surface =
path.point_from_path_coords(position_curve);
vertex.1.write().position = Some(position_surface);
position_surface
}
};
// Infer global position, if not available.
let position_global = vertex.1.read().global_form.read().position;
if position_global.is_none() {
let position_global =
surface.point_from_surface_coords(position_surface);
vertex.1.write().global_form.write().position =
Some(position_global);
}
}
}
fn update_from_other_edge(
&mut self,
other: &Partial<HalfEdge>,
surface: &Option<SurfaceGeometry>,
) {
let global_curve = other.read().curve.read().global_form.clone(); let global_curve = other.read().curve.read().global_form.clone();
self.curve.write().global_form = global_curve.clone(); self.curve.write().global_form = global_curve.clone();
self.global_form.write().curve = global_curve; self.global_form.write().curve = global_curve;
self.curve.write().path = self.curve.write().path =
other.read().curve.read().path.as_ref().and_then(|path| { other.read().curve.read().path.as_ref().and_then(|path| {
match other.read().surface.read().geometry { match surface {
Some(surface) => { Some(surface) => {
// We have information about the other edge's surface // We have information about the other edge's surface
// available. We need to use that to interpret what the // available. We need to use that to interpret what the

View File

@ -12,6 +12,7 @@ use crate::{
/// A cycle of connected half-edges /// A cycle of connected half-edges
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Cycle { pub struct Cycle {
surface: Handle<Surface>,
half_edges: Vec<Handle<HalfEdge>>, half_edges: Vec<Handle<HalfEdge>>,
} }
@ -21,7 +22,10 @@ impl Cycle {
/// # Panics /// # Panics
/// ///
/// Panics, if `half_edges` does not yield at least one half-edge. /// Panics, if `half_edges` does not yield at least one half-edge.
pub fn new(half_edges: impl IntoIterator<Item = Handle<HalfEdge>>) -> Self { pub fn new(
surface: Handle<Surface>,
half_edges: impl IntoIterator<Item = Handle<HalfEdge>>,
) -> Self {
let half_edges = half_edges.into_iter().collect::<Vec<_>>(); let half_edges = half_edges.into_iter().collect::<Vec<_>>();
// This is not a validation check, and thus not part of the validation // This is not a validation check, and thus not part of the validation
@ -33,18 +37,15 @@ impl Cycle {
"Cycle must contain at least one half-edge" "Cycle must contain at least one half-edge"
); );
Self { half_edges } Self {
surface,
half_edges,
}
} }
/// Access the surface that the cycle is in /// Access the surface that the cycle is in
pub fn surface(&self) -> &Handle<Surface> { pub fn surface(&self) -> &Handle<Surface> {
if let Some(half_edge) = self.half_edges.first() { &self.surface
return half_edge.surface();
}
unreachable!(
"Cycle has no half-edges, which the constructor should prevent."
)
} }
/// Access the half-edges that make up the cycle /// Access the half-edges that make up the cycle

View File

@ -4,14 +4,13 @@ use fj_interop::ext::ArrayExt;
use fj_math::Point; use fj_math::Point;
use crate::{ use crate::{
objects::{Curve, GlobalCurve, GlobalVertex, Surface, SurfaceVertex}, objects::{Curve, GlobalCurve, GlobalVertex, SurfaceVertex},
storage::{Handle, HandleWrapper}, storage::{Handle, HandleWrapper},
}; };
/// A half-edge /// A half-edge
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct HalfEdge { pub struct HalfEdge {
surface: Handle<Surface>,
curve: Handle<Curve>, curve: Handle<Curve>,
boundary: [(Point<1>, Handle<SurfaceVertex>); 2], boundary: [(Point<1>, Handle<SurfaceVertex>); 2],
global_form: Handle<GlobalEdge>, global_form: Handle<GlobalEdge>,
@ -20,24 +19,17 @@ pub struct HalfEdge {
impl HalfEdge { impl HalfEdge {
/// Create an instance of `HalfEdge` /// Create an instance of `HalfEdge`
pub fn new( pub fn new(
surface: Handle<Surface>,
curve: Handle<Curve>, curve: Handle<Curve>,
boundary: [(Point<1>, Handle<SurfaceVertex>); 2], boundary: [(Point<1>, Handle<SurfaceVertex>); 2],
global_form: Handle<GlobalEdge>, global_form: Handle<GlobalEdge>,
) -> Self { ) -> Self {
Self { Self {
surface,
curve, curve,
boundary, boundary,
global_form, global_form,
} }
} }
/// Access the surface that the half-edge is defined in
pub fn surface(&self) -> &Handle<Surface> {
&self.surface
}
/// Access the curve that defines the half-edge's geometry /// Access the curve that defines the half-edge's geometry
pub fn curve(&self) -> &Handle<Curve> { pub fn curve(&self) -> &Handle<Curve> {
&self.curve &self.curve
@ -173,14 +165,15 @@ mod tests {
let a_to_b = { let a_to_b = {
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge half_edge.update_as_line_segment_from_points([a, b]);
.update_as_line_segment_from_points(surface.clone(), [a, b]); half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
half_edge.build(&mut services.objects) half_edge.build(&mut services.objects)
}; };
let b_to_a = { let b_to_a = {
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points(surface, [b, a]); half_edge.update_as_line_segment_from_points([b, a]);
half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
half_edge.build(&mut services.objects) half_edge.build(&mut services.objects)
}; };

View File

@ -46,7 +46,7 @@ impl PartialObject for PartialCurve {
/// ///
/// Can be a fully defined [`SurfacePath`], or just the type of path might be /// Can be a fully defined [`SurfacePath`], or just the type of path might be
/// known. /// known.
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub enum MaybeSurfacePath { pub enum MaybeSurfacePath {
/// The surface path is fully defined /// The surface path is fully defined
Defined(SurfacePath), Defined(SurfacePath),

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
builder::HalfEdgeBuilder,
objects::{Cycle, HalfEdge, Objects, Surface}, objects::{Cycle, HalfEdge, Objects, Surface},
partial::{FullToPartialCache, Partial, PartialObject}, partial::{FullToPartialCache, Partial, PartialObject},
services::Service, services::Service,
@ -29,11 +30,15 @@ impl PartialObject for PartialCycle {
} }
fn build(self, objects: &mut Service<Objects>) -> Self::Full { fn build(self, objects: &mut Service<Objects>) -> Self::Full {
let half_edges = self let surface = self.surface.build(objects);
.half_edges let surface_geometry = surface.geometry();
.into_iter() let half_edges = self.half_edges.into_iter().map(|mut half_edge| {
.map(|half_edge| half_edge.build(objects)); half_edge
.write()
.infer_vertex_positions_if_necessary(&surface_geometry);
half_edge.build(objects)
});
Cycle::new(half_edges) Cycle::new(surface, half_edges)
} }
} }

View File

@ -6,7 +6,7 @@ use fj_math::Point;
use crate::{ use crate::{
objects::{ objects::{
Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects, Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects,
Surface, SurfaceVertex, SurfaceVertex,
}, },
partial::{FullToPartialCache, Partial, PartialObject}, partial::{FullToPartialCache, Partial, PartialObject},
services::Service, services::Service,
@ -15,9 +15,6 @@ use crate::{
/// A partial [`HalfEdge`] /// A partial [`HalfEdge`]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PartialHalfEdge { pub struct PartialHalfEdge {
/// The surface that the half-edge is defined in
pub surface: Partial<Surface>,
/// The curve that the half-edge is defined in /// The curve that the half-edge is defined in
pub curve: Partial<Curve>, pub curve: Partial<Curve>,
@ -36,7 +33,6 @@ impl PartialObject for PartialHalfEdge {
cache: &mut FullToPartialCache, cache: &mut FullToPartialCache,
) -> Self { ) -> Self {
Self { Self {
surface: Partial::from_full(half_edge.surface().clone(), cache),
curve: Partial::from_full(half_edge.curve().clone(), cache), curve: Partial::from_full(half_edge.curve().clone(), cache),
vertices: half_edge vertices: half_edge
.boundary() .boundary()
@ -55,53 +51,23 @@ impl PartialObject for PartialHalfEdge {
} }
fn build(self, objects: &mut Service<Objects>) -> Self::Full { fn build(self, objects: &mut Service<Objects>) -> Self::Full {
let surface = self.surface.build(objects);
let curve = self.curve.build(objects); let curve = self.curve.build(objects);
let vertices = self.vertices.map(|mut vertex| { let vertices = self.vertices.map(|vertex| {
let position_surface = vertex.1.read().position; let position_curve = vertex
.0
// Infer surface position, if not available. .expect("Can't build `HalfEdge` without boundary positions");
let position_surface = match position_surface {
Some(position_surface) => position_surface,
None => {
let position_curve = vertex.0.expect(
"Can't infer surface position without curve position",
);
let position_surface =
curve.path().point_from_path_coords(position_curve);
vertex.1.write().position = Some(position_surface);
position_surface
}
};
// Infer global position, if not available.
let position_global = vertex.1.read().global_form.read().position;
if position_global.is_none() {
let position_global = surface
.geometry()
.point_from_surface_coords(position_surface);
vertex.1.write().global_form.write().position =
Some(position_global);
}
let position =
vertex.0.expect("Can't build `Vertex` without position");
let surface_form = vertex.1.build(objects); let surface_form = vertex.1.build(objects);
(position, surface_form) (position_curve, surface_form)
}); });
let global_form = self.global_form.build(objects); let global_form = self.global_form.build(objects);
HalfEdge::new(surface, curve, vertices, global_form) HalfEdge::new(curve, vertices, global_form)
} }
} }
impl Default for PartialHalfEdge { impl Default for PartialHalfEdge {
fn default() -> Self { fn default() -> Self {
let surface = Partial::new();
let curve = Partial::<Curve>::new(); let curve = Partial::<Curve>::new();
let vertices = array::from_fn(|_| { let vertices = array::from_fn(|_| {
let surface_form = Partial::default(); let surface_form = Partial::default();
@ -123,7 +89,6 @@ impl Default for PartialHalfEdge {
}); });
Self { Self {
surface,
curve, curve,
vertices, vertices,
global_form, global_form,

View File

@ -17,6 +17,7 @@ impl Validate for Cycle {
) { ) {
CycleValidationError::check_half_edge_connections(self, errors); CycleValidationError::check_half_edge_connections(self, errors);
CycleValidationError::check_half_edge_boundaries(self, config, errors); CycleValidationError::check_half_edge_boundaries(self, config, errors);
CycleValidationError::check_vertex_positions(self, config, errors);
// We don't need to check that all half-edges are defined in the same // We don't need to check that all half-edges are defined in the same
// surface. We already check that they are connected by identical // surface. We already check that they are connected by identical
@ -70,6 +71,33 @@ pub enum CycleValidationError {
/// The half-edge /// The half-edge
half_edge: Handle<HalfEdge>, half_edge: Handle<HalfEdge>,
}, },
/// Mismatch between [`SurfaceVertex`] and `GlobalVertex` positions
#[error(
"`SurfaceVertex` position doesn't match position of its global form\n\
- Surface position: {surface_position:?}\n\
- Surface position converted to global position: \
{surface_position_as_global:?}\n\
- Global position: {global_position:?}\n\
- Distance between the positions: {distance}\n\
- `SurfaceVertex`: {surface_vertex:#?}"
)]
VertexSurfacePositionMismatch {
/// The position of the surface vertex
surface_position: Point<2>,
/// The surface position converted into a global position
surface_position_as_global: Point<3>,
/// The position of the global vertex
global_position: Point<3>,
/// The distance between the positions
distance: Scalar,
/// The surface vertex
surface_vertex: Handle<SurfaceVertex>,
},
} }
impl CycleValidationError { impl CycleValidationError {
@ -130,15 +158,49 @@ impl CycleValidationError {
} }
} }
} }
fn check_vertex_positions(
cycle: &Cycle,
config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
for half_edge in cycle.half_edges() {
for surface_vertex in half_edge.surface_vertices() {
let surface_position_as_global = cycle
.surface()
.geometry()
.point_from_surface_coords(surface_vertex.position());
let global_position = surface_vertex.global_form().position();
let distance =
surface_position_as_global.distance_to(&global_position);
if distance > config.identical_max_distance {
errors.push(
Self::VertexSurfacePositionMismatch {
surface_position: surface_vertex.position(),
surface_position_as_global,
global_position,
distance,
surface_vertex: surface_vertex.clone(),
}
.into(),
);
}
}
}
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use fj_math::Point; use fj_interop::ext::ArrayExt;
use fj_math::{Point, Scalar, Vector};
use crate::{ use crate::{
builder::CycleBuilder, builder::{CycleBuilder, HalfEdgeBuilder},
objects::Cycle, insert::Insert,
objects::{Cycle, HalfEdge, SurfaceVertex},
partial::{Partial, PartialCycle, PartialObject}, partial::{Partial, PartialCycle, PartialObject},
services::Services, services::Services,
validate::Validate, validate::Validate,
@ -176,7 +238,7 @@ mod tests {
.into_iter() .into_iter()
.map(|half_edge| half_edge.build(&mut services.objects)); .map(|half_edge| half_edge.build(&mut services.objects));
Cycle::new(half_edges) Cycle::new(valid.surface().clone(), half_edges)
}; };
valid.validate_and_return_first_error()?; valid.validate_and_return_first_error()?;
@ -212,7 +274,69 @@ mod tests {
.into_iter() .into_iter()
.map(|half_edge| half_edge.build(&mut services.objects)); .map(|half_edge| half_edge.build(&mut services.objects));
Cycle::new(half_edges) Cycle::new(valid.surface().clone(), half_edges)
};
valid.validate_and_return_first_error()?;
assert!(invalid.validate_and_return_first_error().is_err());
Ok(())
}
#[test]
fn surface_vertex_position_mismatch() -> anyhow::Result<()> {
let mut services = Services::new();
let valid = {
let surface = services.objects.surfaces.xy_plane();
let mut cycle = PartialCycle {
surface: Partial::from(surface.clone()),
..Default::default()
};
let mut half_edge = cycle.add_half_edge();
half_edge.write().update_as_circle_from_radius(1.);
half_edge
.write()
.infer_vertex_positions_if_necessary(&surface.geometry());
cycle.build(&mut services.objects)
};
let invalid = {
let half_edge = {
let half_edge = valid.half_edges().next().unwrap();
let boundary = half_edge
.boundary()
.map(|point| point + Vector::from([Scalar::PI / 2.]));
let mut surface_vertices =
half_edge.surface_vertices().map(Clone::clone);
let mut invalid = None;
for surface_vertex in surface_vertices.each_mut_ext() {
let invalid = invalid.get_or_insert_with(|| {
SurfaceVertex::new(
[0., 1.],
surface_vertex.global_form().clone(),
)
.insert(&mut services.objects)
});
*surface_vertex = invalid.clone();
}
let boundary = boundary.zip_ext(surface_vertices);
HalfEdge::new(
half_edge.curve().clone(),
boundary,
half_edge.global_form().clone(),
)
.insert(&mut services.objects)
};
Cycle::new(valid.surface().clone(), [half_edge])
}; };
valid.validate_and_return_first_error()?; valid.validate_and_return_first_error()?;

View File

@ -1,9 +1,7 @@
use fj_math::{Point, Scalar}; use fj_math::{Point, Scalar};
use crate::{ use crate::{
objects::{ objects::{GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Surface},
GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Surface, SurfaceVertex,
},
storage::Handle, storage::Handle,
}; };
@ -18,7 +16,6 @@ impl Validate for HalfEdge {
HalfEdgeValidationError::check_global_curve_identity(self, errors); HalfEdgeValidationError::check_global_curve_identity(self, errors);
HalfEdgeValidationError::check_global_vertex_identity(self, errors); HalfEdgeValidationError::check_global_vertex_identity(self, errors);
HalfEdgeValidationError::check_vertex_coincidence(self, config, errors); HalfEdgeValidationError::check_vertex_coincidence(self, config, errors);
HalfEdgeValidationError::check_vertex_positions(self, config, errors);
} }
} }
@ -107,33 +104,6 @@ pub enum HalfEdgeValidationError {
/// The half-edge /// The half-edge
half_edge: HalfEdge, half_edge: HalfEdge,
}, },
/// Mismatch between [`SurfaceVertex`] and [`GlobalVertex`] positions
#[error(
"`SurfaceVertex` position doesn't match position of its global form\n\
- Surface position: {surface_position:?}\n\
- Surface position converted to global position: \
{surface_position_as_global:?}\n\
- Global position: {global_position:?}\n\
- Distance between the positions: {distance}\n\
- `SurfaceVertex`: {surface_vertex:#?}"
)]
VertexSurfacePositionMismatch {
/// The position of the surface vertex
surface_position: Point<2>,
/// The surface position converted into a global position
surface_position_as_global: Point<3>,
/// The position of the global vertex
global_position: Point<3>,
/// The distance between the positions
distance: Scalar,
/// The surface vertex
surface_vertex: Handle<SurfaceVertex>,
},
} }
impl HalfEdgeValidationError { impl HalfEdgeValidationError {
@ -206,36 +176,6 @@ impl HalfEdgeValidationError {
); );
} }
} }
fn check_vertex_positions(
half_edge: &HalfEdge,
config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
for surface_vertex in half_edge.surface_vertices() {
let surface_position_as_global = half_edge
.surface()
.geometry()
.point_from_surface_coords(surface_vertex.position());
let global_position = surface_vertex.global_form().position();
let distance =
surface_position_as_global.distance_to(&global_position);
if distance > config.identical_max_distance {
errors.push(
Box::new(Self::VertexSurfacePositionMismatch {
surface_position: surface_vertex.position(),
surface_position_as_global,
global_position,
distance,
surface_vertex: surface_vertex.clone(),
})
.into(),
);
}
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -246,7 +186,7 @@ mod tests {
use crate::{ use crate::{
builder::HalfEdgeBuilder, builder::HalfEdgeBuilder,
insert::Insert, insert::Insert,
objects::{GlobalCurve, HalfEdge, SurfaceVertex}, objects::{GlobalCurve, HalfEdge},
partial::{Partial, PartialHalfEdge, PartialObject}, partial::{Partial, PartialHalfEdge, PartialObject},
services::Services, services::Services,
validate::Validate, validate::Validate,
@ -257,11 +197,11 @@ mod tests {
let mut services = Services::new(); let mut services = Services::new();
let valid = { let valid = {
let surface = services.objects.surfaces.xy_plane();
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points( half_edge.update_as_line_segment_from_points([[0., 0.], [1., 0.]]);
services.objects.surfaces.xy_plane(), half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
[[0., 0.], [1., 0.]],
);
half_edge.build(&mut services.objects) half_edge.build(&mut services.objects)
}; };
@ -277,12 +217,7 @@ mod tests {
.boundary() .boundary()
.zip_ext(valid.surface_vertices().map(Clone::clone)); .zip_ext(valid.surface_vertices().map(Clone::clone));
HalfEdge::new( HalfEdge::new(valid.curve().clone(), vertices, global_form)
valid.surface().clone(),
valid.curve().clone(),
vertices,
global_form,
)
}; };
valid.validate_and_return_first_error()?; valid.validate_and_return_first_error()?;
@ -296,11 +231,11 @@ mod tests {
let mut services = Services::new(); let mut services = Services::new();
let valid = { let valid = {
let surface = services.objects.surfaces.xy_plane();
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points( half_edge.update_as_line_segment_from_points([[0., 0.], [1., 0.]]);
services.objects.surfaces.xy_plane(), half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
[[0., 0.], [1., 0.]],
);
half_edge.build(&mut services.objects) half_edge.build(&mut services.objects)
}; };
@ -324,12 +259,7 @@ mod tests {
.boundary() .boundary()
.zip_ext(valid.surface_vertices().map(Clone::clone)); .zip_ext(valid.surface_vertices().map(Clone::clone));
HalfEdge::new( HalfEdge::new(valid.curve().clone(), vertices, global_form)
valid.surface().clone(),
valid.curve().clone(),
vertices,
global_form,
)
}; };
valid.validate_and_return_first_error()?; valid.validate_and_return_first_error()?;
@ -343,11 +273,11 @@ mod tests {
let mut services = Services::new(); let mut services = Services::new();
let valid = { let valid = {
let surface = services.objects.surfaces.xy_plane();
let mut half_edge = PartialHalfEdge::default(); let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points( half_edge.update_as_line_segment_from_points([[0., 0.], [1., 0.]]);
services.objects.surfaces.xy_plane(), half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
[[0., 0.], [1., 0.]],
);
half_edge.build(&mut services.objects) half_edge.build(&mut services.objects)
}; };
@ -357,7 +287,6 @@ mod tests {
}); });
HalfEdge::new( HalfEdge::new(
valid.surface().clone(),
valid.curve().clone(), valid.curve().clone(),
vertices, vertices,
valid.global_form().clone(), valid.global_form().clone(),
@ -369,44 +298,4 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn surface_vertex_position_mismatch() -> anyhow::Result<()> {
let mut services = Services::new();
let valid = {
let mut half_edge = PartialHalfEdge::default();
half_edge.update_as_line_segment_from_points(
services.objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
);
half_edge.build(&mut services.objects)
};
let invalid = {
let mut surface_vertices =
valid.surface_vertices().map(Clone::clone);
let [_, surface_vertex] = surface_vertices.each_mut_ext();
*surface_vertex = SurfaceVertex::new(
[2., 0.],
surface_vertex.global_form().clone(),
)
.insert(&mut services.objects);
let boundary = valid.boundary().zip_ext(surface_vertices);
HalfEdge::new(
valid.surface().clone(),
valid.curve().clone(),
boundary,
valid.global_form().clone(),
)
};
valid.validate_and_return_first_error()?;
assert!(invalid.validate_and_return_first_error().is_err());
Ok(())
}
} }

View File

@ -27,19 +27,19 @@ impl Shape for fj::Sketch {
let face = match self.chain() { let face = match self.chain() {
fj::Chain::Circle(circle) => { fj::Chain::Circle(circle) => {
let half_edge = {
let surface = Partial::from(surface); let surface = Partial::from(surface);
let mut half_edge = PartialHalfEdge { let half_edge = {
surface, let mut half_edge = PartialHalfEdge::default();
..Default::default()
};
half_edge.update_as_circle_from_radius(circle.radius()); half_edge.update_as_circle_from_radius(circle.radius());
Partial::from_partial(half_edge) Partial::from_partial(half_edge)
}; };
let exterior = { let exterior = {
let mut cycle = PartialCycle::default(); let mut cycle = PartialCycle {
surface,
..Default::default()
};
cycle.half_edges.push(half_edge); cycle.half_edges.push(half_edge);
Partial::from_partial(cycle) Partial::from_partial(cycle)
}; };