Merge pull request #1310 from hannobraun/partial

Continue cleanup of partial object API
This commit is contained in:
Hanno Braun 2022-11-04 16:07:09 +01:00 committed by GitHub
commit edcd61ffaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 306 additions and 138 deletions

View File

@ -92,8 +92,7 @@ mod tests {
.update_as_u_axis() .update_as_u_axis()
.build(&objects)?; .build(&objects)?;
let half_edge = HalfEdge::partial() let half_edge = HalfEdge::partial()
.with_surface(Some(surface)) .update_as_line_segment_from_points(surface, [[1., -1.], [1., 1.]])
.update_as_line_segment_from_points([[1., -1.], [1., 1.]])
.build(&objects)?; .build(&objects)?;
let intersection = CurveEdgeIntersection::compute(&curve, &half_edge); let intersection = CurveEdgeIntersection::compute(&curve, &half_edge);
@ -117,8 +116,10 @@ mod tests {
.update_as_u_axis() .update_as_u_axis()
.build(&objects)?; .build(&objects)?;
let half_edge = HalfEdge::partial() let half_edge = HalfEdge::partial()
.with_surface(Some(surface)) .update_as_line_segment_from_points(
.update_as_line_segment_from_points([[-1., -1.], [-1., 1.]]) surface,
[[-1., -1.], [-1., 1.]],
)
.build(&objects)?; .build(&objects)?;
let intersection = CurveEdgeIntersection::compute(&curve, &half_edge); let intersection = CurveEdgeIntersection::compute(&curve, &half_edge);
@ -142,8 +143,10 @@ mod tests {
.update_as_u_axis() .update_as_u_axis()
.build(&objects)?; .build(&objects)?;
let half_edge = HalfEdge::partial() let half_edge = HalfEdge::partial()
.with_surface(Some(surface)) .update_as_line_segment_from_points(
.update_as_line_segment_from_points([[-1., -1.], [1., -1.]]) surface,
[[-1., -1.], [1., -1.]],
)
.build(&objects)?; .build(&objects)?;
let intersection = CurveEdgeIntersection::compute(&curve, &half_edge); let intersection = CurveEdgeIntersection::compute(&curve, &half_edge);
@ -162,8 +165,7 @@ mod tests {
.update_as_u_axis() .update_as_u_axis()
.build(&objects)?; .build(&objects)?;
let half_edge = HalfEdge::partial() let half_edge = HalfEdge::partial()
.with_surface(Some(surface)) .update_as_line_segment_from_points(surface, [[-1., 0.], [1., 0.]])
.update_as_line_segment_from_points([[-1., 0.], [1., 0.]])
.build(&objects)?; .build(&objects)?;
let intersection = CurveEdgeIntersection::compute(&curve, &half_edge); let intersection = CurveEdgeIntersection::compute(&curve, &half_edge);

View File

@ -199,8 +199,10 @@ mod tests {
let objects = Objects::new(); let objects = Objects::new();
let half_edge = HalfEdge::partial() let half_edge = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane())) .update_as_line_segment_from_points(
.update_as_line_segment_from_points([[0., 0.], [1., 0.]]) objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?; .build(&objects)?;
let face = let face =
@ -210,8 +212,10 @@ mod tests {
let surface = objects.surfaces.xz_plane(); let surface = objects.surfaces.xz_plane();
let bottom = HalfEdge::partial() let bottom = HalfEdge::partial()
.with_surface(Some(surface.clone())) .update_as_line_segment_from_points(
.update_as_line_segment_from_points([[0., 0.], [1., 0.]]) surface.clone(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?; .build(&objects)?;
let side_up = HalfEdge::partial() let side_up = HalfEdge::partial()
.with_surface(Some(surface.clone())) .with_surface(Some(surface.clone()))

View File

@ -125,8 +125,10 @@ mod tests {
.array_windows_ext() .array_windows_ext()
.map(|&[a, b]| { .map(|&[a, b]| {
let half_edge = HalfEdge::partial() let half_edge = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane())) .update_as_line_segment_from_points(
.update_as_line_segment_from_points([a, b]) objects.surfaces.xy_plane(),
[a, b],
)
.build(&objects)?; .build(&objects)?;
(half_edge, Color::default()).sweep(UP, &objects) (half_edge, Color::default()).sweep(UP, &objects)
}) })
@ -167,8 +169,10 @@ mod tests {
.array_windows_ext() .array_windows_ext()
.map(|&[a, b]| { .map(|&[a, b]| {
let half_edge = HalfEdge::partial() let half_edge = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane())) .update_as_line_segment_from_points(
.update_as_line_segment_from_points([a, b]) objects.surfaces.xy_plane(),
[a, b],
)
.build(&objects)? .build(&objects)?
.reverse(&objects)?; .reverse(&objects)?;
(half_edge, Color::default()).sweep(DOWN, &objects) (half_edge, Color::default()).sweep(DOWN, &objects)

View File

@ -191,8 +191,7 @@ mod tests {
(vertex, surface.clone()).sweep([0., 0., 1.], &objects)?; (vertex, surface.clone()).sweep([0., 0., 1.], &objects)?;
let expected_half_edge = HalfEdge::partial() let expected_half_edge = HalfEdge::partial()
.with_surface(Some(surface)) .update_as_line_segment_from_points(surface, [[0., 0.], [0., 1.]])
.update_as_line_segment_from_points([[0., 0.], [0., 1.]])
.build(&objects)?; .build(&objects)?;
assert_eq!(half_edge, expected_half_edge); assert_eq!(half_edge, expected_half_edge);
Ok(()) Ok(())

View File

@ -12,22 +12,11 @@ impl TransformObject for PartialCycle {
transform: &Transform, transform: &Transform,
objects: &Objects, objects: &Objects,
) -> Result<Self, ValidationError> { ) -> Result<Self, ValidationError> {
let surface = self
.surface()
.map(|surface| surface.transform(transform, objects))
.transpose()?;
let half_edges = self let half_edges = self
.half_edges() .half_edges()
.map(|edge| { .map(|edge| edge.into_partial().transform(transform, objects))
Ok(edge
.into_partial()
.transform(transform, objects)?
.with_surface(surface.clone()))
})
.collect::<Result<Vec<_>, ValidationError>>()?; .collect::<Result<Vec<_>, ValidationError>>()?;
Ok(Self::default() Ok(Self::default().with_half_edges(half_edges))
.with_surface(surface)
.with_half_edges(half_edges))
} }
} }

View File

@ -15,15 +15,10 @@ impl TransformObject for PartialHalfEdge {
transform: &Transform, transform: &Transform,
objects: &Objects, objects: &Objects,
) -> Result<Self, ValidationError> { ) -> Result<Self, ValidationError> {
let surface = self
.surface()
.map(|surface| surface.transform(transform, objects))
.transpose()?;
let curve: MaybePartial<_> = self let curve: MaybePartial<_> = self
.curve() .curve()
.into_partial() .into_partial()
.transform(transform, objects)? .transform(transform, objects)?
.with_surface(surface.clone())
.into(); .into();
let vertices = self.vertices().try_map_ext( let vertices = self.vertices().try_map_ext(
|vertex| -> Result<_, ValidationError> { |vertex| -> Result<_, ValidationError> {
@ -41,7 +36,6 @@ impl TransformObject for PartialHalfEdge {
.into(); .into();
Ok(Self::default() Ok(Self::default()
.with_surface(surface)
.with_curve(Some(curve)) .with_curve(Some(curve))
.with_vertices(Some(vertices)) .with_vertices(Some(vertices))
.with_global_form(global_form)) .with_global_form(global_form))

View File

@ -1,8 +1,9 @@
use fj_math::Point; use fj_math::Point;
use crate::{ use crate::{
objects::{Curve, HalfEdge, SurfaceVertex, Vertex}, objects::{Curve, HalfEdge, Surface, SurfaceVertex, Vertex},
partial::{HasPartial, MaybePartial, PartialCycle}, partial::{HasPartial, MaybePartial, PartialCycle},
storage::Handle,
}; };
use super::{CurveBuilder, HalfEdgeBuilder}; use super::{CurveBuilder, HalfEdgeBuilder};
@ -18,6 +19,7 @@ pub trait CycleBuilder {
/// Update the partial cycle with a polygonal chain from the provided points /// Update the partial cycle with a polygonal chain from the provided points
fn with_poly_chain_from_points( fn with_poly_chain_from_points(
self, self,
surface: Handle<Surface>,
points: impl IntoIterator<Item = impl Into<Point<2>>>, points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self; ) -> Self;
@ -49,9 +51,8 @@ impl CycleBuilder for PartialCycle {
let mut half_edges = Vec::new(); let mut half_edges = Vec::new();
for vertex_next in iter { for vertex_next in iter {
if let Some(vertex_prev) = previous { if let Some(vertex_prev) = previous {
let surface = self let surface = vertex_prev
.surface() .surface()
.clone()
.expect("Need surface to extend cycle with poly-chain"); .expect("Need surface to extend cycle with poly-chain");
let position_prev = vertex_prev let position_prev = vertex_prev
@ -61,12 +62,8 @@ impl CycleBuilder for PartialCycle {
.position() .position()
.expect("Need surface position to extend cycle"); .expect("Need surface position to extend cycle");
let from = vertex_prev.update_partial(|partial| { let from = vertex_prev;
partial.with_surface(Some(surface.clone())) let to = vertex_next;
});
let to = vertex_next.update_partial(|partial| {
partial.with_surface(Some(surface.clone()))
});
previous = Some(to.clone()); previous = Some(to.clone());
@ -99,10 +96,13 @@ impl CycleBuilder for PartialCycle {
fn with_poly_chain_from_points( fn with_poly_chain_from_points(
self, self,
surface: Handle<Surface>,
points: impl IntoIterator<Item = impl Into<Point<2>>>, points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self { ) -> Self {
self.with_poly_chain(points.into_iter().map(|position| { self.with_poly_chain(points.into_iter().map(|position| {
SurfaceVertex::partial().with_position(Some(position)) SurfaceVertex::partial()
.with_surface(Some(surface.clone()))
.with_position(Some(position))
})) }))
} }
@ -127,8 +127,7 @@ impl CycleBuilder for PartialCycle {
self.with_half_edges(Some( self.with_half_edges(Some(
HalfEdge::partial() HalfEdge::partial()
.with_surface(Some(surface)) .update_as_line_segment_from_points(surface, vertices),
.update_as_line_segment_from_points(vertices),
)) ))
} }
} }

View File

@ -3,7 +3,7 @@ use fj_math::{Point, Scalar};
use crate::{ use crate::{
objects::{ objects::{
Curve, GlobalVertex, Objects, SurfaceVertex, Vertex, Curve, GlobalVertex, Objects, Surface, SurfaceVertex, Vertex,
VerticesInNormalizedOrder, VerticesInNormalizedOrder,
}, },
partial::{HasPartial, PartialGlobalEdge, PartialHalfEdge}, partial::{HasPartial, PartialGlobalEdge, PartialHalfEdge},
@ -31,6 +31,7 @@ pub trait HalfEdgeBuilder: Sized {
/// Update partial half-edge as a line segment, from the given points /// Update partial half-edge as a line segment, from the given points
fn update_as_line_segment_from_points( fn update_as_line_segment_from_points(
self, self,
surface: Handle<Surface>,
points: [impl Into<Point<2>>; 2], points: [impl Into<Point<2>>; 2],
) -> Self; ) -> Self;
@ -44,9 +45,10 @@ impl HalfEdgeBuilder for PartialHalfEdge {
radius: impl Into<Scalar>, radius: impl Into<Scalar>,
objects: &Objects, objects: &Objects,
) -> Result<Self, ValidationError> { ) -> Result<Self, ValidationError> {
let curve = Curve::partial() let curve = self
.curve()
.into_partial()
.with_global_form(Some(self.extract_global_curve())) .with_global_form(Some(self.extract_global_curve()))
.with_surface(self.surface())
.update_as_circle_from_radius(radius); .update_as_circle_from_radius(radius);
let path = curve.path().expect("Expected path that was just created"); let path = curve.path().expect("Expected path that was just created");
@ -55,7 +57,8 @@ impl HalfEdgeBuilder for PartialHalfEdge {
[Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord])); [Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord]));
let global_vertex = self let global_vertex = self
.extract_global_vertices() .global_form()
.vertices()
.map(|[global_form, _]| global_form) .map(|[global_form, _]| global_form)
.unwrap_or_else(|| { .unwrap_or_else(|| {
GlobalVertex::partial() GlobalVertex::partial()
@ -65,7 +68,7 @@ impl HalfEdgeBuilder for PartialHalfEdge {
let surface_vertex = SurfaceVertex::partial() let surface_vertex = SurfaceVertex::partial()
.with_position(Some(path.point_from_path_coords(a_curve))) .with_position(Some(path.point_from_path_coords(a_curve)))
.with_surface(self.surface()) .with_surface(curve.surface())
.with_global_form(Some(global_vertex)) .with_global_form(Some(global_vertex))
.build(objects)?; .build(objects)?;
@ -83,18 +86,20 @@ impl HalfEdgeBuilder for PartialHalfEdge {
fn update_as_line_segment_from_points( fn update_as_line_segment_from_points(
self, self,
surface: Handle<Surface>,
points: [impl Into<Point<2>>; 2], points: [impl Into<Point<2>>; 2],
) -> Self { ) -> Self {
let surface = self.surface();
let vertices = points.map(|point| { let vertices = points.map(|point| {
let surface_form = SurfaceVertex::partial() let surface_form = SurfaceVertex::partial()
.with_surface(surface.clone()) .with_surface(Some(surface.clone()))
.with_position(Some(point)); .with_position(Some(point));
Vertex::partial().with_surface_form(Some(surface_form)) Vertex::partial().with_surface_form(Some(surface_form))
}); });
self.with_vertices(Some(vertices)).update_as_line_segment() self.with_surface(Some(surface))
.with_vertices(Some(vertices))
.update_as_line_segment()
} }
fn update_as_line_segment(self) -> Self { fn update_as_line_segment(self) -> Self {
@ -103,6 +108,7 @@ impl HalfEdgeBuilder for PartialHalfEdge {
[&from, &to].map(|vertex| vertex.surface_form()); [&from, &to].map(|vertex| vertex.surface_form());
let surface = self let surface = self
.curve()
.surface() .surface()
.or_else(|| from_surface.surface()) .or_else(|| from_surface.surface())
.or_else(|| to_surface.surface()) .or_else(|| to_surface.surface())
@ -147,7 +153,8 @@ impl HalfEdgeBuilder for PartialHalfEdge {
must_switch_order must_switch_order
}; };
self.extract_global_vertices() self.global_form()
.vertices()
.map( .map(
|[a, b]| { |[a, b]| {
if must_switch_order { if must_switch_order {

View File

@ -50,10 +50,14 @@ impl<'a> FaceBuilder<'a> {
mut self, mut self,
points: impl IntoIterator<Item = impl Into<Point<2>>>, points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self { ) -> Self {
let surface = self
.surface
.as_ref()
.expect("Need surface to create polygon");
self.exterior = Some( self.exterior = Some(
Cycle::partial() Cycle::partial()
.with_surface(self.surface.clone()) .with_poly_chain_from_points(surface.clone(), points)
.with_poly_chain_from_points(points)
.close_with_line_segment() .close_with_line_segment()
.build(self.objects) .build(self.objects)
.unwrap(), .unwrap(),
@ -75,10 +79,14 @@ impl<'a> FaceBuilder<'a> {
mut self, mut self,
points: impl IntoIterator<Item = impl Into<Point<2>>>, points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self { ) -> Self {
let surface = self
.surface
.as_ref()
.expect("Need surface to build polygon.");
self.interiors.push( self.interiors.push(
Cycle::partial() Cycle::partial()
.with_surface(self.surface.clone()) .with_poly_chain_from_points(surface.clone(), points)
.with_poly_chain_from_points(points)
.close_with_line_segment() .close_with_line_segment()
.build(self.objects) .build(self.objects)
.unwrap(), .unwrap(),

View File

@ -89,12 +89,11 @@ impl<'a> ShellBuilder<'a> {
.zip(&surfaces) .zip(&surfaces)
.map(|(half_edge, surface)| { .map(|(half_edge, surface)| {
HalfEdge::partial() HalfEdge::partial()
.with_surface(Some(surface.clone()))
.with_global_form(Some(half_edge.global_form().clone())) .with_global_form(Some(half_edge.global_form().clone()))
.update_as_line_segment_from_points([ .update_as_line_segment_from_points(
[Z, Z], surface.clone(),
[edge_length, Z], [[Z, Z], [edge_length, Z]],
]) )
.build(self.objects) .build(self.objects)
.unwrap() .unwrap()
}) })
@ -188,10 +187,8 @@ impl<'a> ShellBuilder<'a> {
.zip(sides_up) .zip(sides_up)
.zip(tops.clone()) .zip(tops.clone())
.zip(sides_down) .zip(sides_down)
.zip(surfaces) .map(|(((bottom, side_up), top), side_down)| {
.map(|((((bottom, side_up), top), side_down), surface)| {
let cycle = Cycle::partial() let cycle = Cycle::partial()
.with_surface(Some(surface))
.with_half_edges([bottom, side_up, top, side_down]) .with_half_edges([bottom, side_up, top, side_down])
.build(self.objects) .build(self.objects)
.unwrap(); .unwrap();

View File

@ -399,8 +399,10 @@ mod tests {
let surface = objects.surfaces.xy_plane(); let surface = objects.surfaces.xy_plane();
let object = Cycle::partial() let object = Cycle::partial()
.with_surface(Some(surface)) .with_poly_chain_from_points(
.with_poly_chain_from_points([[0., 0.], [1., 0.], [0., 1.]]) surface,
[[0., 0.], [1., 0.], [0., 1.]],
)
.close_with_line_segment() .close_with_line_segment()
.build(&objects); .build(&objects);
@ -485,8 +487,10 @@ mod tests {
let objects = Objects::new(); let objects = Objects::new();
let object = HalfEdge::partial() let object = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane())) .update_as_line_segment_from_points(
.update_as_line_segment_from_points([[0., 0.], [1., 0.]]) objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects); .build(&objects);
assert_eq!(1, object.curve_iter().count()); assert_eq!(1, object.curve_iter().count());

View File

@ -165,12 +165,10 @@ mod tests {
let b = [1., 0.]; let b = [1., 0.];
let a_to_b = HalfEdge::partial() let a_to_b = HalfEdge::partial()
.with_surface(Some(surface.clone())) .update_as_line_segment_from_points(surface.clone(), [a, b])
.update_as_line_segment_from_points([a, b])
.build(&objects)?; .build(&objects)?;
let b_to_a = HalfEdge::partial() let b_to_a = HalfEdge::partial()
.with_surface(Some(surface)) .update_as_line_segment_from_points(surface, [b, a])
.update_as_line_segment_from_points([b, a])
.build(&objects)?; .build(&objects)?;
assert_eq!(a_to_b.global_form(), b_to_a.global_form()); assert_eq!(a_to_b.global_form(), b_to_a.global_form());

View File

@ -64,6 +64,20 @@ impl<T: HasPartial> MaybePartial<T> {
} }
} }
/// Merge this `MaybePartial` with another of the same type
pub fn merge_with(self, other: Self) -> Self {
match (self, other) {
(Self::Full(_), Self::Full(_)) => {
panic!("Can't merge two full objects")
}
(Self::Full(full), Self::Partial(_))
| (Self::Partial(_), Self::Full(full)) => Self::Full(full),
(Self::Partial(a), Self::Partial(b)) => {
Self::Partial(a.merge_with(b))
}
}
}
/// Return or build a full object /// Return or build a full object
/// ///
/// If this already is a full object, it is returned. If this is a partial /// If this already is a full object, it is returned. If this is a partial
@ -160,6 +174,14 @@ impl MaybePartial<GlobalEdge> {
} }
impl MaybePartial<HalfEdge> { impl MaybePartial<HalfEdge> {
/// Access the curve
pub fn curve(&self) -> MaybePartial<Curve> {
match self {
Self::Full(full) => full.curve().clone().into(),
Self::Partial(partial) => partial.curve(),
}
}
/// Access the front vertex /// Access the front vertex
pub fn front(&self) -> MaybePartial<Vertex> { pub fn front(&self) -> MaybePartial<Vertex> {
match self { match self {

View File

@ -37,6 +37,7 @@
mod maybe_partial; mod maybe_partial;
mod objects; mod objects;
mod traits; mod traits;
mod util;
pub use self::{ pub use self::{
maybe_partial::MaybePartial, maybe_partial::MaybePartial,

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
objects::{Curve, GlobalCurve, Objects, Surface}, objects::{Curve, GlobalCurve, Objects, Surface},
partial::MaybePartial, partial::{util::merge_options, MaybePartial},
path::SurfacePath, path::SurfacePath,
storage::Handle, storage::Handle,
validate::ValidationError, validate::ValidationError,
@ -59,6 +59,26 @@ impl PartialCurve {
self self
} }
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
// This is harder than it should be, as `global_form` uses the redundant
// `Option<MaybePartial<_>>` representation. There's some code relying
// on that though, so we have to live with it for now.
let global_form = match (self.global_form, other.global_form) {
(Some(a), Some(b)) => Some(a.merge_with(b)),
(Some(global_form), None) | (None, Some(global_form)) => {
Some(global_form)
}
(None, None) => None,
};
Self {
path: merge_options(self.path, other.path),
surface: merge_options(self.surface, other.surface),
global_form,
}
}
/// Build a full [`Curve`] from the partial curve /// Build a full [`Curve`] from the partial curve
pub fn build( pub fn build(
self, self,
@ -102,6 +122,11 @@ impl From<&Curve> for PartialCurve {
pub struct PartialGlobalCurve; pub struct PartialGlobalCurve;
impl PartialGlobalCurve { impl PartialGlobalCurve {
/// Merge this partial object with another
pub fn merge_with(self, _: Self) -> Self {
Self
}
/// Build a full [`GlobalCurve`] from the partial global curve /// Build a full [`GlobalCurve`] from the partial global curve
pub fn build( pub fn build(
self, self,

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
objects::{Cycle, HalfEdge, Objects, Surface}, objects::{Cycle, HalfEdge, Objects, Surface},
partial::MaybePartial, partial::{util::merge_options, MaybePartial},
storage::Handle, storage::Handle,
validate::ValidationError, validate::ValidationError,
}; };
@ -10,46 +10,80 @@ use crate::{
/// See [`crate::partial`] for more information. /// See [`crate::partial`] for more information.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct PartialCycle { pub struct PartialCycle {
surface: Option<Handle<Surface>>,
half_edges: Vec<MaybePartial<HalfEdge>>, half_edges: Vec<MaybePartial<HalfEdge>>,
} }
impl PartialCycle { impl PartialCycle {
/// Access the surface that the [`Cycle`] is defined in
pub fn surface(&self) -> Option<Handle<Surface>> {
self.surface.clone()
}
/// Access the half-edges that make up the [`Cycle`] /// Access the half-edges that make up the [`Cycle`]
pub fn half_edges(&self) -> impl Iterator<Item = MaybePartial<HalfEdge>> { pub fn half_edges(&self) -> impl Iterator<Item = MaybePartial<HalfEdge>> {
self.half_edges.clone().into_iter() self.half_edges.clone().into_iter()
} }
/// Update the partial cycle with the given surface /// Access the surface that the [`Cycle`]'s [`HalfEdge`]s are defined in
pub fn with_surface(mut self, surface: Option<Handle<Surface>>) -> Self { pub fn surface(&self) -> Option<Handle<Surface>> {
if let Some(surface) = surface { self.half_edges
self.surface = Some(surface); .first()
} .and_then(|half_edge| half_edge.curve().surface())
self
} }
/// Update the partial cycle with the given half-edges /// Add the provided half-edges to the partial cycle
///
/// This will merge all the surfaces of the added half-edges. All added
/// half-edges will end up with the same merged surface.
///
/// # Panics
///
/// Panics, if the surfaces can't be merged.
pub fn with_half_edges( pub fn with_half_edges(
mut self, mut self,
half_edges: impl IntoIterator<Item = impl Into<MaybePartial<HalfEdge>>>, half_edges: impl IntoIterator<Item = impl Into<MaybePartial<HalfEdge>>>,
) -> Self { ) -> Self {
self.half_edges let half_edges = half_edges.into_iter().map(Into::into);
.extend(half_edges.into_iter().map(Into::into));
let mut surface = self.surface();
for half_edge in half_edges {
surface = merge_options(surface, half_edge.curve().surface());
self.half_edges.push(half_edge);
}
self.with_surface(surface)
}
/// Update the partial cycle with the provided surface
///
/// All [`HalfEdge`]s will be updated with this surface.
pub fn with_surface(mut self, surface: Option<Handle<Surface>>) -> Self {
if let Some(surface) = surface {
for half_edge in &mut self.half_edges {
*half_edge = half_edge.clone().update_partial(|half_edge| {
half_edge.with_surface(Some(surface.clone()))
});
}
}
self self
} }
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
let a_is_empty = self.half_edges.is_empty();
let b_is_empty = other.half_edges.is_empty();
let half_edges = match (a_is_empty, b_is_empty) {
(true, true) => {
panic!("Can't merge `PartialHalfEdge`, if both have half-edges")
}
(true, false) => self.half_edges,
(false, true) => other.half_edges,
(false, false) => self.half_edges, // doesn't matter which we use
};
Self { half_edges }
}
/// Build a full [`Cycle`] from the partial cycle /// Build a full [`Cycle`] from the partial cycle
pub fn build( pub fn build(
mut self, mut self,
objects: &Objects, objects: &Objects,
) -> Result<Handle<Cycle>, ValidationError> { ) -> Result<Handle<Cycle>, ValidationError> {
let surface = self.surface.expect("Need surface to build `Cycle`");
let surface_for_edges = surface.clone();
let half_edges = { let half_edges = {
let last_vertex = self let last_vertex = self
.half_edges .half_edges
@ -65,11 +99,7 @@ impl PartialCycle {
.map(|(half_edge, vertex, surface_vertex)| .map(|(half_edge, vertex, surface_vertex)|
-> Result<_, ValidationError> -> Result<_, ValidationError>
{ {
let surface_vertex = surface_vertex let surface_vertex = surface_vertex.into_full(objects)?;
.update_partial(|surface_vertex| {
surface_vertex.with_surface(Some(surface.clone()))
})
.into_full(objects)?;
*half_edge = *half_edge =
half_edge.clone().update_partial(|half_edge| { half_edge.clone().update_partial(|half_edge| {
@ -98,9 +128,7 @@ impl PartialCycle {
partial.with_surface_form(previous_vertex) partial.with_surface_form(previous_vertex)
}); });
half_edge half_edge.with_back_vertex(Some(back))
.with_surface(Some(surface_for_edges.clone()))
.with_back_vertex(Some(back))
}) })
.into_full(objects)?; .into_full(objects)?;
@ -121,7 +149,6 @@ impl PartialCycle {
impl From<&Cycle> for PartialCycle { impl From<&Cycle> for PartialCycle {
fn from(cycle: &Cycle) -> Self { fn from(cycle: &Cycle) -> Self {
Self { Self {
surface: Some(cycle.surface().clone()),
half_edges: cycle.half_edges().cloned().map(Into::into).collect(), half_edges: cycle.half_edges().cloned().map(Into::into).collect(),
} }
} }

View File

@ -6,7 +6,7 @@ use crate::{
Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects, Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects,
Surface, Vertex, Surface, Vertex,
}, },
partial::MaybePartial, partial::{util::merge_arrays, MaybePartial},
storage::Handle, storage::Handle,
validate::ValidationError, validate::ValidationError,
}; };
@ -16,18 +16,12 @@ use crate::{
/// See [`crate::partial`] for more information. /// See [`crate::partial`] for more information.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct PartialHalfEdge { pub struct PartialHalfEdge {
surface: Option<Handle<Surface>>,
curve: MaybePartial<Curve>, curve: MaybePartial<Curve>,
vertices: [MaybePartial<Vertex>; 2], vertices: [MaybePartial<Vertex>; 2],
global_form: MaybePartial<GlobalEdge>, global_form: MaybePartial<GlobalEdge>,
} }
impl PartialHalfEdge { impl PartialHalfEdge {
/// Access the surface that the [`HalfEdge`]'s [`Curve`] is defined in
pub fn surface(&self) -> Option<Handle<Surface>> {
self.surface.clone()
}
/// Access the curve that the [`HalfEdge`] is defined in /// Access the curve that the [`HalfEdge`] is defined in
pub fn curve(&self) -> MaybePartial<Curve> { pub fn curve(&self) -> MaybePartial<Curve> {
self.curve.clone() self.curve.clone()
@ -52,17 +46,24 @@ impl PartialHalfEdge {
.unwrap_or_else(|| self.global_form.curve()) .unwrap_or_else(|| self.global_form.curve())
} }
/// Access the vertices of the global form, if available
pub fn extract_global_vertices(
&self,
) -> Option<[MaybePartial<GlobalVertex>; 2]> {
self.global_form.vertices()
}
/// Update the partial half-edge with the given surface /// Update the partial half-edge with the given surface
pub fn with_surface(mut self, surface: Option<Handle<Surface>>) -> Self { pub fn with_surface(mut self, surface: Option<Handle<Surface>>) -> Self {
if let Some(surface) = surface { if let Some(surface) = surface {
self.surface = Some(surface); self.curve = self.curve.update_partial(|curve| {
curve.with_surface(Some(surface.clone()))
});
self.vertices = self.vertices.map(|vertex| {
vertex.update_partial(|vertex| {
let surface_form = vertex.surface_form().update_partial(
|surface_vertex| {
surface_vertex.with_surface(Some(surface.clone()))
},
);
vertex.with_surface_form(Some(surface_form))
})
});
} }
self self
} }
@ -78,7 +79,7 @@ impl PartialHalfEdge {
self self
} }
/// Update the partial half-edge with the given from vertex /// Update the partial half-edge with the given back vertex
pub fn with_back_vertex( pub fn with_back_vertex(
mut self, mut self,
vertex: Option<impl Into<MaybePartial<Vertex>>>, vertex: Option<impl Into<MaybePartial<Vertex>>>,
@ -90,7 +91,7 @@ impl PartialHalfEdge {
self self
} }
/// Update the partial half-edge with the given from vertex /// Update the partial half-edge with the given front vertex
pub fn with_front_vertex( pub fn with_front_vertex(
mut self, mut self,
vertex: Option<impl Into<MaybePartial<Vertex>>>, vertex: Option<impl Into<MaybePartial<Vertex>>>,
@ -125,16 +126,21 @@ impl PartialHalfEdge {
self self
} }
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
Self {
curve: self.curve.merge_with(other.curve),
vertices: merge_arrays(self.vertices, other.vertices),
global_form: self.global_form.merge_with(other.global_form),
}
}
/// Build a full [`HalfEdge`] from the partial half-edge /// Build a full [`HalfEdge`] from the partial half-edge
pub fn build( pub fn build(
self, self,
objects: &Objects, objects: &Objects,
) -> Result<Handle<HalfEdge>, ValidationError> { ) -> Result<Handle<HalfEdge>, ValidationError> {
let surface = self.surface; let curve = self.curve.into_full(objects)?;
let curve = self
.curve
.update_partial(|curve| curve.with_surface(surface))
.into_full(objects)?;
let vertices = self.vertices.try_map_ext(|vertex| { let vertices = self.vertices.try_map_ext(|vertex| {
vertex vertex
.update_partial(|vertex| vertex.with_curve(Some(curve.clone()))) .update_partial(|vertex| vertex.with_curve(Some(curve.clone())))
@ -160,7 +166,6 @@ impl From<&HalfEdge> for PartialHalfEdge {
half_edge.vertices().clone().map(Into::into); half_edge.vertices().clone().map(Into::into);
Self { Self {
surface: Some(half_edge.curve().surface().clone()),
curve: half_edge.curve().clone().into(), curve: half_edge.curve().clone().into(),
vertices: [back_vertex, front_vertex], vertices: [back_vertex, front_vertex],
global_form: half_edge.global_form().clone().into(), global_form: half_edge.global_form().clone().into(),
@ -210,6 +215,23 @@ impl PartialGlobalEdge {
self self
} }
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
// This is harder than it needs to be, because `vertices` uses the
// redundant combination of `Option` and `MaybePartial`. There's some
// code relying on that, however, so we have to live with it for now.
let vertices = match (self.vertices, other.vertices) {
(Some(a), Some(b)) => Some(merge_arrays(a, b)),
(Some(vertices), None) | (None, Some(vertices)) => Some(vertices),
(None, None) => None,
};
Self {
curve: self.curve.merge_with(other.curve),
vertices,
}
}
/// Build a full [`GlobalEdge`] from the partial global edge /// Build a full [`GlobalEdge`] from the partial global edge
pub fn build( pub fn build(
self, self,

View File

@ -27,6 +27,10 @@ macro_rules! impl_traits {
impl Partial for $partial { impl Partial for $partial {
type Full = $full; type Full = $full;
fn merge_with(self, other: Self) -> Self {
self.merge_with(other)
}
fn build(self, objects: &Objects) fn build(self, objects: &Objects)
-> Result< -> Result<
Handle<Self::Full>, Handle<Self::Full>,

View File

@ -3,7 +3,7 @@ use fj_math::Point;
use crate::{ use crate::{
builder::GlobalVertexBuilder, builder::GlobalVertexBuilder,
objects::{Curve, GlobalVertex, Objects, Surface, SurfaceVertex, Vertex}, objects::{Curve, GlobalVertex, Objects, Surface, SurfaceVertex, Vertex},
partial::MaybePartial, partial::{util::merge_options, MaybePartial},
storage::Handle, storage::Handle,
validate::ValidationError, validate::ValidationError,
}; };
@ -67,6 +67,15 @@ impl PartialVertex {
self self
} }
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
Self {
position: merge_options(self.position, other.position),
curve: self.curve.merge_with(other.curve),
surface_form: self.surface_form.merge_with(other.surface_form),
}
}
/// Build a full [`Vertex`] from the partial vertex /// Build a full [`Vertex`] from the partial vertex
/// ///
/// # Panics /// # Panics
@ -170,6 +179,15 @@ impl PartialSurfaceVertex {
self self
} }
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
Self {
position: merge_options(self.position, other.position),
surface: merge_options(self.surface, other.surface),
global_form: self.global_form.merge_with(other.global_form),
}
}
/// Build a full [`SurfaceVertex`] from the partial surface vertex /// Build a full [`SurfaceVertex`] from the partial surface vertex
pub fn build( pub fn build(
self, self,
@ -232,6 +250,13 @@ impl PartialGlobalVertex {
self self
} }
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
Self {
position: merge_options(self.position, other.position),
}
}
/// Build a full [`GlobalVertex`] from the partial global vertex /// Build a full [`GlobalVertex`] from the partial global vertex
pub fn build( pub fn build(
self, self,

View File

@ -68,6 +68,9 @@ pub trait Partial: Default + for<'a> From<&'a Self::Full> {
/// The type representing the full variant of this object /// The type representing the full variant of this object
type Full; type Full;
/// Merge another partial object of the same type into this one
fn merge_with(self, other: Self) -> Self;
/// Build a full object from this partial one /// Build a full object from this partial one
/// ///
/// Implementations of this method will typically try to infer any missing /// Implementations of this method will typically try to infer any missing

View File

@ -0,0 +1,26 @@
use fj_interop::ext::ArrayExt;
use super::{HasPartial, MaybePartial};
pub fn merge_options<T>(a: Option<T>, b: Option<T>) -> Option<T>
where
T: Eq,
{
if a == b {
return a;
}
// We know that `a != b`, or we wouldn't have made it here.
if a.is_some() && b.is_some() {
panic!("Can't merge `Option`s if both are defined");
}
a.xor(b)
}
pub fn merge_arrays<T: HasPartial>(
a: [MaybePartial<T>; 2],
b: [MaybePartial<T>; 2],
) -> [MaybePartial<T>; 2] {
a.zip_ext(b).map(|(a, b)| a.merge_with(b))
}

View File

@ -207,8 +207,10 @@ mod tests {
let objects = Objects::new(); let objects = Objects::new();
let valid = HalfEdge::partial() let valid = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane())) .update_as_line_segment_from_points(
.update_as_line_segment_from_points([[0., 0.], [1., 0.]]) objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?; .build(&objects)?;
let invalid = { let invalid = {
let mut vertices = valid.vertices().clone(); let mut vertices = valid.vertices().clone();
@ -232,8 +234,10 @@ mod tests {
let objects = Objects::new(); let objects = Objects::new();
let valid = HalfEdge::partial() let valid = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane())) .update_as_line_segment_from_points(
.update_as_line_segment_from_points([[0., 0.], [1., 0.]]) objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?; .build(&objects)?;
let invalid = HalfEdge::new( let invalid = HalfEdge::new(
valid.vertices().clone(), valid.vertices().clone(),
@ -255,8 +259,10 @@ mod tests {
let objects = Objects::new(); let objects = Objects::new();
let valid = HalfEdge::partial() let valid = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane())) .update_as_line_segment_from_points(
.update_as_line_segment_from_points([[0., 0.], [1., 0.]]) objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?; .build(&objects)?;
let invalid = HalfEdge::new( let invalid = HalfEdge::new(
valid.vertices().clone(), valid.vertices().clone(),
@ -285,8 +291,10 @@ mod tests {
let objects = Objects::new(); let objects = Objects::new();
let valid = HalfEdge::partial() let valid = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane())) .update_as_line_segment_from_points(
.update_as_line_segment_from_points([[0., 0.], [1., 0.]]) objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?; .build(&objects)?;
let invalid = HalfEdge::new( let invalid = HalfEdge::new(
valid.vertices().clone().try_map_ext(|vertex| { valid.vertices().clone().try_map_ext(|vertex| {