diff --git a/crates/fj-core/src/algorithms/approx/cycle.rs b/crates/fj-core/src/algorithms/approx/cycle.rs index dfedcdf38..981fcb8f0 100644 --- a/crates/fj-core/src/algorithms/approx/cycle.rs +++ b/crates/fj-core/src/algorithms/approx/cycle.rs @@ -5,7 +5,7 @@ use fj_math::Segment; use crate::{ - geometry::Geometry, + geometry::{CurveBoundary, Geometry}, storage::Handle, topology::{Cycle, Surface}, }; @@ -28,9 +28,24 @@ pub fn approx_cycle( let half_edges = cycle .half_edges() - .iter() - .map(|half_edge| { - let boundary = geometry.of_half_edge(half_edge).boundary; + .pairs() + .map(|(half_edge, next_half_edge)| { + let boundary = CurveBoundary { + inner: [ + geometry + .of_vertex(half_edge.start_vertex()) + .unwrap() + .local_on(half_edge.curve()) + .unwrap() + .position, + geometry + .of_vertex(next_half_edge.start_vertex()) + .unwrap() + .local_on(half_edge.curve()) + .unwrap() + .position, + ], + }; let [start_position_curve, _] = boundary.inner; let start = approx_vertex( diff --git a/crates/fj-core/src/algorithms/bounding_volume/cycle.rs b/crates/fj-core/src/algorithms/bounding_volume/cycle.rs index a934a38a1..1c19db6ae 100644 --- a/crates/fj-core/src/algorithms/bounding_volume/cycle.rs +++ b/crates/fj-core/src/algorithms/bounding_volume/cycle.rs @@ -12,8 +12,8 @@ impl super::BoundingVolume<2> for (&Cycle, &Handle) { let mut aabb: Option> = None; - for half_edge in cycle.half_edges() { - let new_aabb = (half_edge, surface) + for (half_edge, half_edge_next) in cycle.half_edges().pairs() { + let new_aabb = (half_edge, half_edge_next.start_vertex(), surface) .aabb(geometry) .expect("`HalfEdge` can always compute AABB"); aabb = Some(aabb.map_or(new_aabb, |aabb| aabb.merged(&new_aabb))); diff --git a/crates/fj-core/src/algorithms/bounding_volume/half_edge.rs b/crates/fj-core/src/algorithms/bounding_volume/half_edge.rs index 5f5322f62..ba8ee4408 100644 --- a/crates/fj-core/src/algorithms/bounding_volume/half_edge.rs +++ b/crates/fj-core/src/algorithms/bounding_volume/half_edge.rs @@ -3,14 +3,15 @@ use fj_math::{Aabb, Vector}; use crate::{ geometry::{Geometry, SurfacePath}, storage::Handle, - topology::{HalfEdge, Surface}, + topology::{HalfEdge, Surface, Vertex}, }; -impl super::BoundingVolume<2> for (&Handle, &Handle) { +impl super::BoundingVolume<2> + for (&Handle, &Handle, &Handle) +{ fn aabb(self, geometry: &Geometry) -> Option> { - let (half_edge, surface) = self; + let (half_edge, end_vertex, surface) = self; - let half_edge_geom = geometry.of_half_edge(half_edge); let path = geometry .of_curve(half_edge.curve()) .unwrap() @@ -32,9 +33,17 @@ impl super::BoundingVolume<2> for (&Handle, &Handle) { }) } SurfacePath::Line(_) => { - let points = half_edge_geom.boundary.inner.map(|point_curve| { - path.point_from_path_coords(point_curve) - }); + let points = + [half_edge.start_vertex(), end_vertex].map(|vertex| { + let point_curve = geometry + .of_vertex(vertex) + .unwrap() + .local_on(half_edge.curve()) + .unwrap() + .position; + + path.point_from_path_coords(point_curve) + }); Some(Aabb::<2>::from_points(points)) } diff --git a/crates/fj-core/src/operations/split/face.rs b/crates/fj-core/src/operations/split/face.rs index 8b8b53e67..c742ac478 100644 --- a/crates/fj-core/src/operations/split/face.rs +++ b/crates/fj-core/src/operations/split/face.rs @@ -104,29 +104,43 @@ impl SplitFace for Shell { // Build the edge that's going to divide the new faces. let dividing_half_edge_a_to_d = { + let start = core + .layers + .geometry + .of_curve(b.curve()) + .unwrap() + .local_on(face.surface()) + .unwrap() + .path + .point_from_path_coords( + core.layers + .geometry + .of_vertex(b.start_vertex()) + .unwrap() + .local_on(b.curve()) + .unwrap() + .position, + ); + let end = core + .layers + .geometry + .of_curve(d.curve()) + .unwrap() + .local_on(face.surface()) + .unwrap() + .path + .point_from_path_coords( + core.layers + .geometry + .of_vertex(d.start_vertex()) + .unwrap() + .local_on(d.curve()) + .unwrap() + .position, + ); + let (half_edge, boundary) = HalfEdge::line_segment( - [ - core.layers.geometry.of_half_edge(&b).start_position( - &core - .layers - .geometry - .of_curve(b.curve()) - .unwrap() - .local_on(face.surface()) - .unwrap() - .path, - ), - core.layers.geometry.of_half_edge(&d).start_position( - &core - .layers - .geometry - .of_curve(d.curve()) - .unwrap() - .local_on(face.surface()) - .unwrap() - .path, - ), - ], + [start, end], face.surface().clone(), core, ); diff --git a/crates/fj-core/src/operations/split/half_edge.rs b/crates/fj-core/src/operations/split/half_edge.rs index 3e304e933..ba9ee5d64 100644 --- a/crates/fj-core/src/operations/split/half_edge.rs +++ b/crates/fj-core/src/operations/split/half_edge.rs @@ -46,7 +46,27 @@ impl SplitHalfEdge for Cycle { let point = point.into(); let geometry = *core.layers.geometry.of_half_edge(half_edge); - let [start, end] = geometry.boundary.inner; + let [start, end] = [ + core.layers + .geometry + .of_vertex(half_edge.start_vertex()) + .unwrap() + .local_on(half_edge.curve()) + .unwrap() + .position, + core.layers + .geometry + .of_vertex( + self.half_edges() + .after(half_edge) + .expect("Expected half-edge to be in cycle") + .start_vertex(), + ) + .unwrap() + .local_on(half_edge.curve()) + .unwrap() + .position, + ]; let a = HalfEdge::new( half_edge.curve().clone(), diff --git a/crates/fj-core/src/operations/sweep/half_edge.rs b/crates/fj-core/src/operations/sweep/half_edge.rs index d04dc5bdf..522a33e23 100644 --- a/crates/fj-core/src/operations/sweep/half_edge.rs +++ b/crates/fj-core/src/operations/sweep/half_edge.rs @@ -59,7 +59,22 @@ impl SweepHalfEdge for Handle { ) -> SweptHalfEdge { let path = path.into(); - let half_edge_geom = *core.layers.geometry.of_half_edge(self); + let boundary = [ + core.layers + .geometry + .of_vertex(self.start_vertex()) + .unwrap() + .local_on(self.curve()) + .unwrap() + .position, + core.layers + .geometry + .of_vertex(&end_vertex) + .unwrap() + .local_on(self.curve()) + .unwrap() + .position, + ]; let curve_geom = core .layers .geometry @@ -94,7 +109,7 @@ impl SweepHalfEdge for Handle { // Let's figure out the surface coordinates of the edge vertices. let surface_points = { - let [a, b] = half_edge_geom.boundary.inner; + let [a, b] = boundary; [ [a.t, Scalar::ZERO], @@ -112,7 +127,7 @@ impl SweepHalfEdge for Handle { // Now, the boundaries of each edge. let boundaries = { - let [a, b] = half_edge_geom.boundary.inner; + let [a, b] = boundary; let [c, d] = [0., 1.].map(|coord| Point::from([coord])); [[a, b], [c, d], [b, a], [d, c]] diff --git a/crates/fj-core/src/topology/objects/cycle.rs b/crates/fj-core/src/topology/objects/cycle.rs index 2ad742a51..509072bee 100644 --- a/crates/fj-core/src/topology/objects/cycle.rs +++ b/crates/fj-core/src/topology/objects/cycle.rs @@ -46,7 +46,6 @@ impl Cycle { .next() .expect("Invalid cycle: expected at least one edge"); - let half_edge_geom = geometry.of_half_edge(first); let curve_geom = geometry .of_curve(first.curve()) .unwrap() @@ -54,7 +53,29 @@ impl Cycle { .unwrap() .clone(); - let [a, b] = half_edge_geom.boundary.inner; + let [a, b] = [ + curve_geom.path.point_from_path_coords( + geometry + .of_vertex(first.start_vertex()) + .unwrap() + .local_on(first.curve()) + .unwrap() + .position, + ), + curve_geom.path.point_from_path_coords( + geometry + .of_vertex( + self.half_edges() + .after(first) + .expect("Just got half-edge from this cycle") + .start_vertex(), + ) + .unwrap() + .local_on(first.curve()) + .unwrap() + .position, + ), + ]; let edge_direction_positive = a < b; let circle = match curve_geom.path { @@ -80,14 +101,20 @@ impl Cycle { for (a, b) in self.half_edges().pairs() { let [a, b] = [a, b].map(|half_edge| { - geometry.of_half_edge(half_edge).start_position( - &geometry - .of_curve(half_edge.curve()) - .unwrap() - .local_on(surface) - .unwrap() - .path, - ) + geometry + .of_curve(half_edge.curve()) + .unwrap() + .local_on(surface) + .unwrap() + .path + .point_from_path_coords( + geometry + .of_vertex(half_edge.start_vertex()) + .unwrap() + .local_on(half_edge.curve()) + .unwrap() + .position, + ) }); sum += (b.u - a.u) * (b.v + a.v); diff --git a/crates/fj-core/src/validate/solid.rs b/crates/fj-core/src/validate/solid.rs index f51191aa7..64d0ebae9 100644 --- a/crates/fj-core/src/validate/solid.rs +++ b/crates/fj-core/src/validate/solid.rs @@ -117,9 +117,14 @@ impl SolidValidationError { Some(( geometry.of_surface(s).point_from_surface_coords( - geometry - .of_half_edge(&h) - .start_position(&local_curve_geometry.path), + local_curve_geometry.path.point_from_path_coords( + geometry + .of_vertex(h.start_vertex()) + .unwrap() + .local_on(h.curve()) + .unwrap() + .position, + ), ), h.start_vertex().clone(), )) diff --git a/crates/fj-core/src/validation/checks/coincident_half_edges_are_not_siblings.rs b/crates/fj-core/src/validation/checks/coincident_half_edges_are_not_siblings.rs index cb1529dfe..ca13ffd2c 100644 --- a/crates/fj-core/src/validation/checks/coincident_half_edges_are_not_siblings.rs +++ b/crates/fj-core/src/validation/checks/coincident_half_edges_are_not_siblings.rs @@ -5,7 +5,8 @@ use fj_math::{Point, Scalar}; use crate::{ geometry::{CurveBoundary, Geometry}, queries::{ - AllHalfEdgesWithSurface, BoundingVerticesOfHalfEdge, SiblingOfHalfEdge, + AllHalfEdgesWithSurface, BoundingVerticesOfHalfEdge, CycleOfHalfEdge, + SiblingOfHalfEdge, }, storage::Handle, topology::{Curve, HalfEdge, Shell, Surface, Vertex}, @@ -109,8 +110,22 @@ impl ValidationCheck for CoincidentHalfEdgesAreNotSiblings { let Some(mut distances) = distances( half_edge_a.clone(), + object + .find_cycle_of_half_edge(half_edge_a) + .unwrap() + .half_edges() + .after(half_edge_a) + .unwrap() + .start_vertex(), surface_a, half_edge_b.clone(), + object + .find_cycle_of_half_edge(half_edge_b) + .unwrap() + .half_edges() + .after(half_edge_b) + .unwrap() + .start_vertex(), surface_b, geometry, ) else { @@ -152,25 +167,39 @@ impl ValidationCheck for CoincidentHalfEdgesAreNotSiblings { /// Returns an [`Iterator`] of the distance at each sample. fn distances( half_edge_a: Handle, + end_vertex_a: &Handle, surface_a: &Handle, half_edge_b: Handle, + end_vertex_b: &Handle, surface_b: &Handle, geometry: &Geometry, ) -> Option> { fn sample( percent: f64, half_edge: &Handle, + end_vertex: &Handle, surface: &Handle, geometry: &Geometry, ) -> Option> { - let [start, end] = geometry.of_half_edge(half_edge).boundary.inner; + let [start, end] = [ + geometry + .of_vertex(half_edge.start_vertex()) + .unwrap() + .local_on(half_edge.curve()) + .unwrap() + .position, + geometry + .of_vertex(end_vertex) + .unwrap() + .local_on(half_edge.curve()) + .unwrap() + .position, + ]; let path_coords = start + (end - start) * percent; - // let path = geometry.of_half_edge(half_edge).path; let path = geometry .of_curve(half_edge.curve())? .local_on(surface)? .path; - // assert_eq!(path, path_from_curve); let surface_coords = path.point_from_path_coords(path_coords); Some( geometry @@ -188,8 +217,15 @@ fn distances( let mut distances = Vec::new(); for i in 0..sample_count { let percent = i as f64 * step; - let sample1 = sample(percent, &half_edge_a, surface_a, geometry)?; - let sample2 = sample(1.0 - percent, &half_edge_b, surface_b, geometry)?; + let sample1 = + sample(percent, &half_edge_a, end_vertex_a, surface_a, geometry)?; + let sample2 = sample( + 1.0 - percent, + &half_edge_b, + end_vertex_b, + surface_b, + geometry, + )?; distances.push(sample1.distance_to(&sample2)) } Some(distances.into_iter()) diff --git a/crates/fj-core/src/validation/checks/curve_geometry_mismatch.rs b/crates/fj-core/src/validation/checks/curve_geometry_mismatch.rs index bd8fb440b..adf1f8188 100644 --- a/crates/fj-core/src/validation/checks/curve_geometry_mismatch.rs +++ b/crates/fj-core/src/validation/checks/curve_geometry_mismatch.rs @@ -3,7 +3,7 @@ use itertools::Itertools; use crate::{ geometry::Geometry, - queries::AllHalfEdgesWithSurface, + queries::{AllHalfEdgesWithSurface, CycleOfHalfEdge}, storage::Handle, topology::{HalfEdge, Shell}, validation::{ValidationCheck, ValidationConfig}, @@ -130,8 +130,28 @@ impl ValidationCheck for CurveGeometryMismatch { // we have right now are circles, 3 would be enough to check // for coincidence. But the first and last might be // identical, so let's add an extra one. - let [a, d] = - geometry.of_half_edge(&half_edge_a).boundary.inner; + let [a, d] = [ + geometry + .of_vertex(half_edge_a.start_vertex()) + .unwrap() + .local_on(half_edge_a.curve()) + .unwrap() + .position, + geometry + .of_vertex( + object + .find_cycle_of_half_edge(&half_edge_a) + .unwrap() + .half_edges() + .after(&half_edge_a) + .unwrap() + .start_vertex(), + ) + .unwrap() + .local_on(half_edge_a.curve()) + .unwrap() + .position, + ]; let b = a + (d - a) * 1. / 3.; let c = a + (d - a) * 2. / 3.; diff --git a/crates/fj-core/src/validation/checks/half_edge_connection.rs b/crates/fj-core/src/validation/checks/half_edge_connection.rs index 376cdf1e0..4a181b56f 100644 --- a/crates/fj-core/src/validation/checks/half_edge_connection.rs +++ b/crates/fj-core/src/validation/checks/half_edge_connection.rs @@ -99,7 +99,12 @@ fn check_cycle<'r>( ) -> impl Iterator + 'r { cycle.half_edges().pairs().filter_map(|(first, second)| { let end_pos_of_first_half_edge = { - let [_, end] = geometry.of_half_edge(first).boundary.inner; + let end = geometry + .of_vertex(second.start_vertex()) + .unwrap() + .local_on(first.curve()) + .unwrap() + .position; geometry .of_curve(first.curve()) .unwrap() @@ -117,9 +122,18 @@ fn check_cycle<'r>( return None; }; - let start_pos_of_second_half_edge = geometry - .of_half_edge(second) - .start_position(&local_curve_geometry.path); + let start_pos_of_second_half_edge = { + let point_curve = geometry + .of_vertex(second.start_vertex()) + .unwrap() + .local_on(second.curve()) + .unwrap() + .position; + + local_curve_geometry + .path + .point_from_path_coords(point_curve) + }; let distance_between_positions = (end_pos_of_first_half_edge - start_pos_of_second_half_edge)