From 6230ca017439cbed887752612b1c35c848f986bd Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Thu, 5 Jan 2023 16:44:51 +0100 Subject: [PATCH 1/8] Add new `CycleBuilder` method --- crates/fj-kernel/src/builder/cycle.rs | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/fj-kernel/src/builder/cycle.rs b/crates/fj-kernel/src/builder/cycle.rs index 08ed10ab0..ca2ca9a37 100644 --- a/crates/fj-kernel/src/builder/cycle.rs +++ b/crates/fj-kernel/src/builder/cycle.rs @@ -34,6 +34,19 @@ pub trait CycleBuilder { point: impl Into>, ) -> Partial; + /// Add a new half-edge that starts at the provided point + /// + /// Opens the cycle between the last and first edge, updates the last edge + /// to go the provided point, and adds a new half-edge from the provided + /// point the the first edge. + /// + /// If the cycle doesn't have any edges yet, the new edge connects to + /// itself, starting and ending at the provided point. + fn add_half_edge_from_global_point_to_start( + &mut self, + point: impl Into>, + ) -> Partial; + /// Update cycle as a polygon from the provided points fn update_as_polygon_from_points( &mut self, @@ -127,6 +140,25 @@ impl CycleBuilder for PartialCycle { half_edge } + fn add_half_edge_from_global_point_to_start( + &mut self, + point: impl Into>, + ) -> Partial { + let mut half_edge = self.add_half_edge(); + + half_edge + .write() + .back_mut() + .write() + .surface_form + .write() + .global_form + .write() + .position = Some(point.into()); + + half_edge + } + fn update_as_polygon_from_points( &mut self, points: O, From f4cc960fbd96558bb3e31602350add0e184e232d Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Fri, 13 Jan 2023 13:52:19 +0100 Subject: [PATCH 2/8] Make path repr. in `PartialCurve` more flexible --- crates/fj-kernel/src/builder/curve.rs | 6 +-- crates/fj-kernel/src/partial/mod.rs | 2 +- crates/fj-kernel/src/partial/objects/curve.rs | 51 +++++++++++++++++-- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/crates/fj-kernel/src/builder/curve.rs b/crates/fj-kernel/src/builder/curve.rs index 4de09e5ab..c094c14a5 100644 --- a/crates/fj-kernel/src/builder/curve.rs +++ b/crates/fj-kernel/src/builder/curve.rs @@ -60,7 +60,7 @@ impl CurveBuilder for PartialCurve { radius: impl Into, ) -> SurfacePath { let path = SurfacePath::circle_from_radius(radius); - self.path = Some(path); + self.path = Some(path.into()); path } @@ -70,7 +70,7 @@ impl CurveBuilder for PartialCurve { radius: impl Into, ) -> SurfacePath { let path = SurfacePath::circle_from_center_and_radius(center, radius); - self.path = Some(path); + self.path = Some(path.into()); path } @@ -79,7 +79,7 @@ impl CurveBuilder for PartialCurve { points: [impl Into>; 2], ) -> SurfacePath { let (path, _) = SurfacePath::line_from_points(points); - self.path = Some(path); + self.path = Some(path.into()); path } } diff --git a/crates/fj-kernel/src/partial/mod.rs b/crates/fj-kernel/src/partial/mod.rs index 9ca5d0cc5..b67103567 100644 --- a/crates/fj-kernel/src/partial/mod.rs +++ b/crates/fj-kernel/src/partial/mod.rs @@ -16,7 +16,7 @@ mod wrapper; pub use self::{ objects::{ - curve::{PartialCurve, PartialGlobalCurve}, + curve::{MaybeSurfacePath, PartialCurve, PartialGlobalCurve}, cycle::PartialCycle, edge::{PartialGlobalEdge, PartialHalfEdge}, face::PartialFace, diff --git a/crates/fj-kernel/src/partial/objects/curve.rs b/crates/fj-kernel/src/partial/objects/curve.rs index 9210f5945..c041384a3 100644 --- a/crates/fj-kernel/src/partial/objects/curve.rs +++ b/crates/fj-kernel/src/partial/objects/curve.rs @@ -9,7 +9,7 @@ use crate::{ #[derive(Clone, Debug, Default)] pub struct PartialCurve { /// The path that defines the curve - pub path: Option, + pub path: Option, /// The surface the curve is defined in pub surface: Partial, @@ -23,14 +23,21 @@ impl PartialObject for PartialCurve { fn from_full(curve: &Self::Full, cache: &mut FullToPartialCache) -> Self { Self { - path: Some(curve.path()), + path: Some(curve.path().into()), surface: Partial::from_full(curve.surface().clone(), cache), global_form: Partial::from_full(curve.global_form().clone(), cache), } } fn build(self, objects: &mut Service) -> Self::Full { - let path = self.path.expect("Need path to build curve"); + let path = match self.path.expect("Need path to build curve") { + MaybeSurfacePath::Defined(path) => path, + undefined => { + panic!( + "Trying to build curve with undefined path: {undefined:?}" + ) + } + }; let surface = self.surface.build(objects); let global_form = self.global_form.build(objects); @@ -38,6 +45,44 @@ impl PartialObject for PartialCurve { } } +/// The definition of a surface path within [`PartialCurve`] +/// +/// Can be a fully defined [`SurfacePath`], or just the type of path might be +/// known. +#[derive(Clone, Debug)] +pub enum MaybeSurfacePath { + /// The surface path is fully defined + Defined(SurfacePath), + + /// The surface path is undefined, but we know it is a circle + UndefinedCircle, + + /// The surface path is undefined, but we know it is a line + UndefinedLine, +} + +impl MaybeSurfacePath { + /// Convert into an undefined variant + /// + /// If `self` is defined, it is converted into the applicable undefined + /// variant. If it is undefined, a copy is returned. + pub fn to_undefined(&self) -> Self { + match self { + Self::Defined(path) => match path { + SurfacePath::Circle(_) => Self::UndefinedCircle, + SurfacePath::Line(_) => Self::UndefinedLine, + }, + undefined => undefined.clone(), + } + } +} + +impl From for MaybeSurfacePath { + fn from(path: SurfacePath) -> Self { + Self::Defined(path) + } +} + /// A partial [`GlobalCurve`] #[derive(Clone, Debug, Default)] pub struct PartialGlobalCurve; From 425b9d94188fa505d77f7c501b3d5d751942e9a5 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Wed, 11 Jan 2023 15:36:23 +0100 Subject: [PATCH 3/8] Add `FaceBuilder::connect_to_open_edges` --- crates/fj-kernel/src/builder/face.rs | 80 +++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs index 33ec5f27d..7a262a8da 100644 --- a/crates/fj-kernel/src/builder/face.rs +++ b/crates/fj-kernel/src/builder/face.rs @@ -1,15 +1,91 @@ +use std::collections::VecDeque; + use crate::{ - objects::Cycle, - partial::{Partial, PartialCycle, PartialFace}, + objects::{Cycle, HalfEdge}, + partial::{MaybeSurfacePath, Partial, PartialCycle, PartialFace}, }; +use super::{CycleBuilder, ObjectArgument}; + /// Builder API for [`PartialFace`] pub trait FaceBuilder { + /// Connect the face to another face at the provided half-edges + /// + /// Assumes that the provided half-edges, once translated into local + /// equivalents of this face, will not form a cycle. + /// + /// Returns the local equivalents of the provided half-edges and, as the + /// last entry, an additional half-edge that closes the cycle. + fn connect_to_open_edges( + &mut self, + edges: O, + ) -> O::SizePlusOne> + where + O: ObjectArgument>; + /// Add an interior cycle fn add_interior(&mut self) -> Partial; } impl FaceBuilder for PartialFace { + fn connect_to_open_edges( + &mut self, + edges: O, + ) -> O::SizePlusOne> + where + O: ObjectArgument>, + { + // We need to create the additional half-edge last, but at the same time + // need to provide it to the `map_plus_one` method first. Really no + // choice but to create them all in one go, as we do here. + let mut half_edges = VecDeque::new(); + for _ in 0..edges.num_objects() { + half_edges.push_back(self.exterior.write().add_half_edge()); + } + let additional_half_edge = self.exterior.write().add_half_edge(); + + edges.map_plus_one(additional_half_edge, |other| { + let mut this = half_edges.pop_front().expect( + "Pushed correct number of half-edges; should be able to pop", + ); + + let global_curve = other.read().curve().read().global_form.clone(); + this.write().curve().write().global_form = global_curve.clone(); + this.write().global_form.write().curve = global_curve; + + this.write().curve().write().path = other + .read() + .curve() + .read() + .path + .as_ref() + .map(MaybeSurfacePath::to_undefined); + + for (this, other) in this + .write() + .vertices + .iter_mut() + .zip(other.read().vertices.iter().rev()) + { + this.write().position = other.read().position; + this.write() + .surface_form + .write() + .global_form + .write() + .position = other + .read() + .surface_form + .read() + .global_form + .read() + .position; + } + + this + }) + } + fn add_interior(&mut self) -> Partial { let cycle = Partial::from_partial(PartialCycle { surface: self.exterior.read().surface.clone(), From 6675a8de4c3257358cf35be98e455cf083dddbe7 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Fri, 13 Jan 2023 14:39:44 +0100 Subject: [PATCH 4/8] Add `HalfEdgeBuilder::update_from_other_edge` --- crates/fj-kernel/src/builder/edge.rs | 39 ++++++++++++++++++++++++++-- crates/fj-kernel/src/builder/face.rs | 39 +++------------------------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/crates/fj-kernel/src/builder/edge.rs b/crates/fj-kernel/src/builder/edge.rs index 5839a92ea..e0eb06cb0 100644 --- a/crates/fj-kernel/src/builder/edge.rs +++ b/crates/fj-kernel/src/builder/edge.rs @@ -2,8 +2,8 @@ use fj_interop::ext::ArrayExt; use fj_math::{Point, Scalar}; use crate::{ - objects::{GlobalEdge, Surface}, - partial::{Partial, PartialGlobalEdge, PartialHalfEdge}, + objects::{GlobalEdge, HalfEdge, Surface}, + partial::{MaybeSurfacePath, Partial, PartialGlobalEdge, PartialHalfEdge}, }; use super::{CurveBuilder, VertexBuilder}; @@ -46,6 +46,12 @@ pub trait HalfEdgeBuilder { /// Updates the global form referenced by this half-edge, and also returns /// it. fn infer_global_form(&mut self) -> Partial; + + /// Update this edge from another + /// + /// Infers as much information about this edge from the other, under the + /// assumption that the other edge is on a different surface. + fn update_from_other_edge(&mut self, other: &Partial); } impl HalfEdgeBuilder for PartialHalfEdge { @@ -177,6 +183,35 @@ impl HalfEdgeBuilder for PartialHalfEdge { self.global_form.clone() } + + fn update_from_other_edge(&mut self, other: &Partial) { + let global_curve = other.read().curve().read().global_form.clone(); + self.curve().write().global_form = global_curve.clone(); + self.global_form.write().curve = global_curve; + + self.curve().write().path = other + .read() + .curve() + .read() + .path + .as_ref() + .map(MaybeSurfacePath::to_undefined); + + for (this, other) in self + .vertices + .iter_mut() + .zip(other.read().vertices.iter().rev()) + { + this.write().position = other.read().position; + this.write() + .surface_form + .write() + .global_form + .write() + .position = + other.read().surface_form.read().global_form.read().position; + } + } } /// Builder API for [`PartialGlobalEdge`] diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs index 7a262a8da..26f636a5b 100644 --- a/crates/fj-kernel/src/builder/face.rs +++ b/crates/fj-kernel/src/builder/face.rs @@ -2,10 +2,10 @@ use std::collections::VecDeque; use crate::{ objects::{Cycle, HalfEdge}, - partial::{MaybeSurfacePath, Partial, PartialCycle, PartialFace}, + partial::{Partial, PartialCycle, PartialFace}, }; -use super::{CycleBuilder, ObjectArgument}; +use super::{CycleBuilder, HalfEdgeBuilder, ObjectArgument}; /// Builder API for [`PartialFace`] pub trait FaceBuilder { @@ -48,40 +48,7 @@ impl FaceBuilder for PartialFace { let mut this = half_edges.pop_front().expect( "Pushed correct number of half-edges; should be able to pop", ); - - let global_curve = other.read().curve().read().global_form.clone(); - this.write().curve().write().global_form = global_curve.clone(); - this.write().global_form.write().curve = global_curve; - - this.write().curve().write().path = other - .read() - .curve() - .read() - .path - .as_ref() - .map(MaybeSurfacePath::to_undefined); - - for (this, other) in this - .write() - .vertices - .iter_mut() - .zip(other.read().vertices.iter().rev()) - { - this.write().position = other.read().position; - this.write() - .surface_form - .write() - .global_form - .write() - .position = other - .read() - .surface_form - .read() - .global_form - .read() - .position; - } - + this.write().update_from_other_edge(&other); this }) } From 66122d5056929b94d8d54b53cd80f71ac8598bd7 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Wed, 11 Jan 2023 15:49:10 +0100 Subject: [PATCH 5/8] Add `FaceBuilder::connect_to_closed_edges` --- crates/fj-kernel/src/builder/face.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs index 26f636a5b..d2f405d13 100644 --- a/crates/fj-kernel/src/builder/face.rs +++ b/crates/fj-kernel/src/builder/face.rs @@ -23,6 +23,19 @@ pub trait FaceBuilder { where O: ObjectArgument>; + /// Connect the face to another face at the provided half-edges + /// + /// Assumes that the provided half-edges, once translated into local + /// equivalents of this face, form a cycle. + /// + /// Returns the local equivalents of the provided half-edges. + fn connect_to_closed_edges( + &mut self, + edges: O, + ) -> O::SameSize> + where + O: ObjectArgument>; + /// Add an interior cycle fn add_interior(&mut self) -> Partial; } @@ -53,6 +66,20 @@ impl FaceBuilder for PartialFace { }) } + fn connect_to_closed_edges( + &mut self, + edges: O, + ) -> O::SameSize> + where + O: ObjectArgument>, + { + edges.map(|other| { + let mut this = self.exterior.write().add_half_edge(); + this.write().update_from_other_edge(&other); + this + }) + } + fn add_interior(&mut self) -> Partial { let cycle = Partial::from_partial(PartialCycle { surface: self.exterior.read().surface.clone(), From 9ae79841fdff536d029720a3ba45764040065c17 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Fri, 13 Jan 2023 16:37:59 +0100 Subject: [PATCH 6/8] Implement `ArrayExt` for arrays of length 3 --- crates/fj-interop/src/ext.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/fj-interop/src/ext.rs b/crates/fj-interop/src/ext.rs index ab84f97d0..fc4a747d1 100644 --- a/crates/fj-interop/src/ext.rs +++ b/crates/fj-interop/src/ext.rs @@ -51,6 +51,32 @@ impl ArrayExt for [T; 2] { } } +impl ArrayExt for [T; 3] { + fn each_ref_ext(&self) -> [&T; 3] { + let [a, b, c] = self; + [a, b, c] + } + + fn each_mut_ext(&mut self) -> [&mut T; 3] { + let [a, b, c] = self; + [a, b, c] + } + + fn try_map_ext(self, f: F) -> Result<[U; 3], E> + where + F: FnMut(T) -> Result, + { + let [a, b, c] = self.map(f); + Ok([a?, b?, c?]) + } + + fn zip_ext(self, rhs: [U; 3]) -> [(T, U); 3] { + let [a, b, c] = self; + let [q, r, s] = rhs; + [(a, q), (b, r), (c, s)] + } +} + impl ArrayExt for [T; 4] { fn each_ref_ext(&self) -> [&T; 4] { let [a, b, c, d] = self; From 26861fd872aa862d96fb733969dcba8b90f0aac0 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Wed, 11 Jan 2023 15:50:28 +0100 Subject: [PATCH 7/8] Add `FaceBuilder::update_surface_as_plane` --- crates/fj-kernel/src/builder/face.rs | 59 +++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs index d2f405d13..f0c967efb 100644 --- a/crates/fj-kernel/src/builder/face.rs +++ b/crates/fj-kernel/src/builder/face.rs @@ -1,11 +1,13 @@ use std::collections::VecDeque; +use fj_interop::ext::ArrayExt; + use crate::{ - objects::{Cycle, HalfEdge}, + objects::{Cycle, HalfEdge, Surface}, partial::{Partial, PartialCycle, PartialFace}, }; -use super::{CycleBuilder, HalfEdgeBuilder, ObjectArgument}; +use super::{CycleBuilder, HalfEdgeBuilder, ObjectArgument, SurfaceBuilder}; /// Builder API for [`PartialFace`] pub trait FaceBuilder { @@ -38,6 +40,18 @@ pub trait FaceBuilder { /// Add an interior cycle fn add_interior(&mut self) -> Partial; + + /// Update the face's surface as a plane + /// + /// The plane geometry is inferred from three of the face's vertices. Also + /// infers any undefined `SurfaceVertex` positions. + /// + /// # Panics + /// + /// Assumes that the face exterior has exactly three vertices to use. Panics + /// otherwise. This is a temporary limitation, not a fundamental one. It + /// could be overcome with some more work. + fn update_surface_as_plane(&mut self) -> Partial; } impl FaceBuilder for PartialFace { @@ -88,4 +102,45 @@ impl FaceBuilder for PartialFace { self.interiors.push(cycle.clone()); cycle } + + fn update_surface_as_plane(&mut self) -> Partial { + let mut exterior = self.exterior.write(); + let mut vertices = { + exterior.half_edges.iter().map(|half_edge| { + half_edge.read().back().read().surface_form.clone() + }) + }; + + let vertices = { + let array = [ + vertices.next().expect("Expected exactly three vertices"), + vertices.next().expect("Expected exactly three vertices"), + vertices.next().expect("Expected exactly three vertices"), + ]; + + assert!( + vertices.next().is_none(), + "Expected exactly three vertices" + ); + + array + }; + let points = vertices.each_ref_ext().map(|vertex| { + vertex + .read() + .global_form + .read() + .position + .expect("Need global position to infer plane") + }); + + let points_surface = + exterior.surface.write().update_as_plane_from_points(points); + + for (mut surface_vertex, point) in vertices.zip_ext(points_surface) { + surface_vertex.write().position = Some(point); + } + + exterior.surface.clone() + } } From bdc5a2a07f378581ba4a259e689e781bdd499c8e Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Fri, 13 Jan 2023 16:58:57 +0100 Subject: [PATCH 8/8] Fix `update_as_triangle_from_global_points` It never actually set the global positions. --- crates/fj-kernel/src/builder/cycle.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/fj-kernel/src/builder/cycle.rs b/crates/fj-kernel/src/builder/cycle.rs index ca2ca9a37..10ce138b2 100644 --- a/crates/fj-kernel/src/builder/cycle.rs +++ b/crates/fj-kernel/src/builder/cycle.rs @@ -1,3 +1,4 @@ +use fj_interop::ext::ArrayExt; use fj_math::Point; use crate::{ @@ -183,10 +184,28 @@ impl CycleBuilder for PartialCycle { &mut self, points_global: [impl Into>; 3], ) -> [Partial; 3] { + let points_global = points_global.map(Into::into); + let points_surface = self .surface .write() .update_as_plane_from_points(points_global); - self.update_as_polygon_from_points(points_surface) + + let half_edges = self.update_as_polygon_from_points(points_surface); + + for (mut half_edge, point) in half_edges.clone().zip_ext(points_global) + { + half_edge + .write() + .back_mut() + .write() + .surface_form + .write() + .global_form + .write() + .position = Some(point); + } + + half_edges } }