Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Continue cleanup of HalfEdge #1536

Merged
merged 8 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions crates/fj-kernel/src/algorithms/intersect/face_point.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Intersection between faces and points in 2D

use fj_math::Point;
use itertools::Itertools;

use crate::{
objects::{Face, HalfEdge, SurfaceVertex},
Expand Down Expand Up @@ -33,7 +34,9 @@ impl Intersect for (&Handle<Face>, &Point<2>) {
.cloned()
.and_then(|edge| (&ray, &edge).intersect());

for half_edge in cycle.half_edges() {
for (half_edge, next_half_edge) in
cycle.half_edges().circular_tuple_windows::<(_, _)>()
{
let hit = (&ray, half_edge).intersect();

let count_hit = match (hit, previous_hit) {
Expand All @@ -48,15 +51,13 @@ impl Intersect for (&Handle<Face>, &Point<2>) {
));
}
(Some(RaySegmentIntersection::RayStartsOnOnFirstVertex), _) => {
let [vertex, _] =
half_edge.surface_vertices().map(Clone::clone);
let vertex = half_edge.start_vertex().clone();
return Some(
FacePointIntersection::PointIsOnVertex(vertex)
);
}
(Some(RaySegmentIntersection::RayStartsOnSecondVertex), _) => {
let [_, vertex] =
half_edge.surface_vertices().map(Clone::clone);
let vertex = next_half_edge.start_vertex().clone();
return Some(
FacePointIntersection::PointIsOnVertex(vertex)
);
Expand Down
101 changes: 100 additions & 1 deletion crates/fj-kernel/src/validate/cycle.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use fj_interop::ext::ArrayExt;
use fj_math::{Point, Scalar};
use itertools::Itertools;

use crate::{
Expand All @@ -10,10 +12,11 @@ use super::{Validate, ValidationConfig, ValidationError};
impl Validate for Cycle {
fn validate_with_config(
&self,
_: &ValidationConfig,
config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
CycleValidationError::check_half_edge_connections(self, errors);
CycleValidationError::check_half_edge_boundaries(self, config, errors);

// 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
Expand All @@ -37,6 +40,28 @@ pub enum CycleValidationError {
/// The back vertex of the next half-edge
next: Handle<SurfaceVertex>,
},

/// Mismatch between half-edge boundary and surface vertex position
#[error(
"Half-edge boundary on curve doesn't match surface vertex position\n\
- Position on curve: {position_on_curve:#?}\n\
- Surface vertex: {surface_vertex:#?}\n\
- Curve position converted to surface: {curve_position_on_surface:?}\n\
- Distance between the positions: {distance}"
)]
HalfEdgeBoundaryMismatch {
/// The position on the curve
position_on_curve: Point<1>,

/// The surface vertex
surface_vertex: Handle<SurfaceVertex>,

/// The curve position converted into a surface position
curve_position_on_surface: Point<2>,

/// The distance between the positions
distance: Scalar,
},
}

impl CycleValidationError {
Expand All @@ -59,10 +84,48 @@ impl CycleValidationError {
}
}
}

fn check_half_edge_boundaries(
cycle: &Cycle,
config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
for (half_edge, next) in
cycle.half_edges().circular_tuple_windows::<(_, _)>()
{
let boundary_and_vertices = half_edge
.boundary()
.zip_ext([half_edge.start_vertex(), next.start_vertex()]);
for (position_on_curve, surface_vertex) in boundary_and_vertices {
let curve_position_on_surface = half_edge
.curve()
.path()
.point_from_path_coords(position_on_curve);
let surface_position = surface_vertex.position();

let distance =
curve_position_on_surface.distance_to(&surface_position);

if distance > config.identical_max_distance {
errors.push(
Self::HalfEdgeBoundaryMismatch {
position_on_curve,
surface_vertex: surface_vertex.clone(),
curve_position_on_surface,
distance,
}
.into(),
);
}
}
}
}
}

#[cfg(test)]
mod tests {
use fj_math::Point;

use crate::{
builder::CycleBuilder,
objects::Cycle,
Expand Down Expand Up @@ -111,4 +174,40 @@ mod tests {

Ok(())
}

#[test]
fn vertex_position_mismatch() -> anyhow::Result<()> {
let mut services = Services::new();

let valid = {
let mut cycle = PartialCycle {
surface: Partial::from(services.objects.surfaces.xy_plane()),
..Default::default()
};
cycle.update_as_polygon_from_points([[0., 0.], [1., 0.], [0., 1.]]);
cycle.build(&mut services.objects)
};
let invalid = {
let mut half_edges = valid
.half_edges()
.map(|half_edge| Partial::from(half_edge.clone()))
.collect::<Vec<_>>();

// Update a single boundary position so it becomes wrong.
if let Some(half_edge) = half_edges.first_mut() {
half_edge.write().vertices[0].0.replace(Point::from([-1.]));
}

let half_edges = half_edges
.into_iter()
.map(|half_edge| half_edge.build(&mut services.objects));

Cycle::new(half_edges)
};

valid.validate_and_return_first_error()?;
assert!(invalid.validate_and_return_first_error().is_err());

Ok(())
}
}
148 changes: 23 additions & 125 deletions crates/fj-kernel/src/validate/edge.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
use fj_interop::ext::ArrayExt;
use fj_math::{Point, Scalar};

use crate::{
objects::{
GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Surface,
SurfaceVertex, VerticesInNormalizedOrder,
},
objects::{GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Surface},
storage::Handle,
};

Expand All @@ -21,7 +17,6 @@ impl Validate for HalfEdge {
HalfEdgeValidationError::check_global_vertex_identity(self, errors);
HalfEdgeValidationError::check_surface_identity(self, errors);
HalfEdgeValidationError::check_vertex_positions(self, config, errors);
HalfEdgeValidationError::check_position(self, config, errors);

// We don't need to check anything about surfaces here. We already check
// curves, which makes sure the vertices are consistent with each other,
Expand Down Expand Up @@ -64,17 +59,16 @@ pub enum HalfEdgeValidationError {
#[error(
"Global forms of `HalfEdge` vertices do not match vertices of \n\
`HalfEdge`'s global form\n\
- `GlobalVertex` objects from `Vertex` objects: \
{global_vertices_from_vertices:#?}\n\
- `GlobalVertex` from start vertex: {global_vertex_from_half_edge:#?}\n\
- `GlobalVertex` objects from `GlobalEdge`: \
{global_vertices_from_global_form:#?}\n\
- `HalfEdge`: {half_edge:#?}"
)]
GlobalVertexMismatch {
/// The [`GlobalVertex`] from the [`HalfEdge`]'s vertices
global_vertices_from_vertices: [Handle<GlobalVertex>; 2],
/// The [`GlobalVertex`] from the [`HalfEdge`]'s start vertex
global_vertex_from_half_edge: Handle<GlobalVertex>,

/// The [`GlobalCurve`] from the [`HalfEdge`]'s global form
/// The [`GlobalVertex`] instances from the [`HalfEdge`]'s global form
global_vertices_from_global_form: [Handle<GlobalVertex>; 2],

/// The half-edge
Expand Down Expand Up @@ -115,28 +109,6 @@ pub enum HalfEdgeValidationError {
/// The half-edge
half_edge: HalfEdge,
},

/// Mismatch between position of the vertex and position of its surface form
#[error(
"Position on curve doesn't match surface vertex position\n\
- Position on curve: {position_on_curve:#?}\n\
- Surface vertex: {surface_vertex:#?}\n\
- Curve position converted to surface: {curve_position_on_surface:?}\n\
- Distance between the positions: {distance}"
)]
VertexPositionMismatch {
/// The position on the curve
position_on_curve: Point<1>,

/// The surface vertex
surface_vertex: Handle<SurfaceVertex>,

/// The curve position converted into a surface position
curve_position_on_surface: Point<2>,

/// The distance between the positions
distance: Scalar,
},
}

impl HalfEdgeValidationError {
Expand Down Expand Up @@ -164,32 +136,23 @@ impl HalfEdgeValidationError {
half_edge: &HalfEdge,
errors: &mut Vec<ValidationError>,
) {
let global_vertices_from_vertices = {
let (global_vertices_from_vertices, _) =
VerticesInNormalizedOrder::new(
half_edge.surface_vertices().each_ref_ext().map(
|surface_vertex| surface_vertex.global_form().clone(),
),
);

global_vertices_from_vertices.access_in_normalized_order()
};
let global_vertex_from_half_edge =
half_edge.start_vertex().global_form().clone();
let global_vertices_from_global_form = half_edge
.global_form()
.vertices()
.access_in_normalized_order();

let ids_from_vertices = global_vertices_from_vertices
.each_ref_ext()
.map(|global_vertex| global_vertex.id());
let ids_from_global_form = global_vertices_from_global_form
.each_ref_ext()
.map(|global_vertex| global_vertex.id());
let matching_global_vertex = global_vertices_from_global_form
.iter()
.find(|global_vertex| {
global_vertex.id() == global_vertex_from_half_edge.id()
});

if ids_from_vertices != ids_from_global_form {
if matching_global_vertex.is_none() {
errors.push(
Box::new(Self::GlobalVertexMismatch {
global_vertices_from_vertices,
global_vertex_from_half_edge,
global_vertices_from_global_form,
half_edge: half_edge.clone(),
})
Expand All @@ -203,19 +166,16 @@ impl HalfEdgeValidationError {
errors: &mut Vec<ValidationError>,
) {
let curve_surface = half_edge.curve().surface();
let surface_form_surface = half_edge.start_vertex().surface();

for surface_vertex in half_edge.surface_vertices() {
let surface_form_surface = surface_vertex.surface();

if curve_surface.id() != surface_form_surface.id() {
errors.push(
Box::new(Self::SurfaceMismatch {
curve_surface: curve_surface.clone(),
surface_form_surface: surface_form_surface.clone(),
})
.into(),
);
}
if curve_surface.id() != surface_form_surface.id() {
errors.push(
Box::new(Self::SurfaceMismatch {
curve_surface: curve_surface.clone(),
surface_form_surface: surface_form_surface.clone(),
})
.into(),
);
}
}

Expand All @@ -239,37 +199,6 @@ impl HalfEdgeValidationError {
);
}
}

fn check_position(
half_edge: &HalfEdge,
config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
for (position_on_curve, surface_vertex) in
half_edge.boundary().zip_ext(half_edge.surface_vertices())
{
let curve_position_on_surface = half_edge
.curve()
.path()
.point_from_path_coords(position_on_curve);
let surface_position = surface_vertex.position();

let distance =
curve_position_on_surface.distance_to(&surface_position);

if distance > config.identical_max_distance {
errors.push(
Box::new(Self::VertexPositionMismatch {
position_on_curve,
surface_vertex: surface_vertex.clone(),
curve_position_on_surface,
distance,
})
.into(),
);
}
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -431,35 +360,4 @@ mod tests {

Ok(())
}

#[test]
fn 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 vertices = valid.surface_vertices().map(|surface_vertex| {
(Point::from([2.]), surface_vertex.clone())
});

HalfEdge::new(
valid.curve().clone(),
vertices,
valid.global_form().clone(),
)
};

valid.validate_and_return_first_error()?;
assert!(invalid.validate_and_return_first_error().is_err());

Ok(())
}
}