mod coherence;
mod uniqueness;
pub use self::{
coherence::{CoherenceIssues, VertexCoherenceMismatch},
uniqueness::UniquenessIssues,
};
use std::{collections::HashSet, ops::Deref};
use fj_math::Scalar;
use crate::iter::ObjectIters;
pub trait Validate: Sized {
fn validate(self) -> Result<Validated<Self>, ValidationError> {
self.validate_with_config(&ValidationConfig::default())
}
fn validate_with_config(
self,
config: &ValidationConfig,
) -> Result<Validated<Self>, ValidationError>;
}
impl<T> Validate for T
where
T: for<'r> ObjectIters<'r>,
{
fn validate_with_config(
self,
config: &ValidationConfig,
) -> Result<Validated<Self>, ValidationError> {
let mut global_vertices = HashSet::new();
for global_vertex in self.global_vertex_iter() {
uniqueness::validate_vertex(
global_vertex,
&global_vertices,
config.distinct_min_distance,
)?;
global_vertices.insert(*global_vertex);
}
for vertex in self.vertex_iter() {
coherence::validate_vertex(vertex, config.identical_max_distance)?;
}
Ok(Validated(self))
}
}
#[derive(Debug, Clone, Copy)]
pub struct ValidationConfig {
pub distinct_min_distance: Scalar,
pub identical_max_distance: Scalar,
}
impl Default for ValidationConfig {
fn default() -> Self {
Self {
distinct_min_distance: Scalar::from_f64(5e-7),
identical_max_distance: Scalar::from_f64(5e-14),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Validated<T>(T);
impl<T> Validated<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> Deref for Validated<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
#[error("Coherence validation failed")]
Coherence(#[from] CoherenceIssues),
#[error("Geometric validation failed")]
Geometric,
#[error("Uniqueness validation failed")]
Uniqueness(#[from] UniquenessIssues),
}
#[cfg(test)]
mod tests {
use fj_math::{Point, Scalar};
use crate::{
algorithms::validate::{Validate, ValidationConfig, ValidationError},
objects::{
Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Surface,
SurfaceVertex, Vertex,
},
partial::HasPartial,
path::SurfacePath,
stores::Stores,
};
#[test]
fn coherence_edge() {
let stores = Stores::new();
let surface = stores.surfaces.insert(Surface::xy_plane());
let points_surface = [[0., 0.], [1., 0.]];
let points_global = [[0., 0., 0.], [1., 0., 0.]];
let curve = {
let path = SurfacePath::line_from_points(points_surface);
let global_form = GlobalCurve::new(&stores);
Curve::new(surface.clone(), path, global_form, &stores)
};
let [a_global, b_global] = points_global
.map(|point| GlobalVertex::from_position(point, &stores));
let [a_surface, b_surface] = {
let [a_surface, b_surface] = points_surface;
[(a_surface, a_global), (b_surface, b_global)].map(
|(point_surface, vertex_global)| {
SurfaceVertex::new(
point_surface,
surface.clone(),
vertex_global,
)
},
)
};
let deviation = Scalar::from_f64(0.25);
let a = Vertex::new(
Point::from([Scalar::ZERO + deviation]),
curve.clone(),
a_surface,
);
let b =
Vertex::new(Point::from([Scalar::ONE]), curve.clone(), b_surface);
let vertices = [a, b];
let global_edge = GlobalEdge::partial()
.from_curve_and_vertices(&curve, &vertices)
.build(&stores);
let half_edge = HalfEdge::new(vertices, global_edge);
let result =
half_edge.clone().validate_with_config(&ValidationConfig {
identical_max_distance: deviation * 2.,
..ValidationConfig::default()
});
assert!(result.is_ok());
let result = half_edge.validate_with_config(&ValidationConfig {
identical_max_distance: deviation / 2.,
..ValidationConfig::default()
});
assert!(result.is_err());
}
#[test]
fn uniqueness_vertex() -> anyhow::Result<()> {
let stores = Stores::new();
let mut shape = Vec::new();
let deviation = Scalar::from_f64(0.25);
let a = Point::from([0., 0., 0.]);
let mut b = a;
b.x += deviation;
let config = ValidationConfig {
distinct_min_distance: deviation * 2.,
..ValidationConfig::default()
};
shape.push(GlobalVertex::from_position(a, &stores));
shape.clone().validate_with_config(&config)?;
shape.push(GlobalVertex::from_position(b, &stores));
let result = shape.validate_with_config(&config);
assert!(matches!(result, Err(ValidationError::Uniqueness(_))));
Ok(())
}
}