Merge pull request #1812 from hannobraun/builder

Migrate tests away from `FaceBuilder`, towards operations API
This commit is contained in:
Hanno Braun 2023-05-03 11:09:09 +02:00 committed by GitHub
commit fa02019d44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 419 additions and 209 deletions

View File

@ -150,8 +150,9 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
builder::{CycleBuilder, FaceBuilder},
geometry::curve::Curve, geometry::curve::Curve,
objects::{Cycle, Face},
operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services, services::Services,
}; };
@ -178,20 +179,23 @@ mod tests {
[ 1., -1.], [ 1., -1.],
]; ];
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
exterior_points, .update_exterior(|_| {
&mut services, Cycle::polygon(exterior_points, &mut services)
)) .insert(&mut services)
.with_interior(CycleBuilder::polygon( })
.add_interiors([Cycle::polygon(
interior_points, interior_points,
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)]);
let expected = let expected =
CurveFaceIntersection::from_intervals([[[1.], [2.]], [[4.], [5.]]]); CurveFaceIntersection::from_intervals([[[1.], [2.]], [[4.], [5.]]]);
assert_eq!(CurveFaceIntersection::compute(&curve, &face), expected); assert_eq!(CurveFaceIntersection::compute(&curve, &face), expected);
services.only_validate(face);
} }
#[test] #[test]

View File

@ -62,8 +62,9 @@ mod tests {
use crate::{ use crate::{
algorithms::intersect::CurveFaceIntersection, algorithms::intersect::CurveFaceIntersection,
builder::{CycleBuilder, FaceBuilder},
geometry::curve::Curve, geometry::curve::Curve,
objects::{Cycle, Face},
operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services, services::Services,
}; };
@ -85,14 +86,15 @@ mod tests {
services.objects.surfaces.xz_plane(), services.objects.surfaces.xz_plane(),
] ]
.map(|surface| { .map(|surface| {
FaceBuilder::new(surface) Face::unbound(surface, &mut services).update_exterior(|_| {
.with_exterior(CycleBuilder::polygon(points, &mut services)) Cycle::polygon(points, &mut services).insert(&mut services)
.build(&mut services) })
}); });
let intersection = FaceFaceIntersection::compute([&a, &b]); let intersection = FaceFaceIntersection::compute([&a, &b]);
assert!(intersection.is_none()); assert!(intersection.is_none());
services.only_validate([a, b]);
} }
#[test] #[test]
@ -111,9 +113,9 @@ mod tests {
services.objects.surfaces.xz_plane(), services.objects.surfaces.xz_plane(),
]; ];
let [a, b] = surfaces.clone().map(|surface| { let [a, b] = surfaces.clone().map(|surface| {
FaceBuilder::new(surface) Face::unbound(surface, &mut services).update_exterior(|_| {
.with_exterior(CycleBuilder::polygon(points, &mut services)) Cycle::polygon(points, &mut services).insert(&mut services)
.build(&mut services) })
}); });
let intersection = FaceFaceIntersection::compute([&a, &b]); let intersection = FaceFaceIntersection::compute([&a, &b]);
@ -131,5 +133,7 @@ mod tests {
intersection_intervals: expected_intervals intersection_intervals: expected_intervals
}) })
); );
services.only_validate([a, b]);
} }
} }

View File

@ -138,7 +138,8 @@ mod tests {
use crate::{ use crate::{
algorithms::intersect::{face_point::FacePointIntersection, Intersect}, algorithms::intersect::{face_point::FacePointIntersection, Intersect},
builder::{CycleBuilder, FaceBuilder}, objects::{Cycle, Face},
operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services, services::Services,
}; };
@ -146,28 +147,36 @@ mod tests {
fn point_is_outside_face() { fn point_is_outside_face() {
let mut services = Services::new(); let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [1., 1.], [0., 2.]], [[0., 0.], [1., 1.], [0., 2.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let point = Point::from([2., 1.]); let point = Point::from([2., 1.]);
let intersection = (&face, &point).intersect(); let intersection = (&face, &point).intersect();
assert_eq!(intersection, None); assert_eq!(intersection, None);
services.only_validate(face);
} }
#[test] #[test]
fn ray_hits_vertex_while_passing_outside() { fn ray_hits_vertex_while_passing_outside() {
let mut services = Services::new(); let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [2., 1.], [0., 2.]], [[0., 0.], [2., 1.], [0., 2.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let point = Point::from([1., 1.]); let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect(); let intersection = (&face, &point).intersect();
@ -175,18 +184,23 @@ mod tests {
intersection, intersection,
Some(FacePointIntersection::PointIsInsideFace) Some(FacePointIntersection::PointIsInsideFace)
); );
services.only_validate(face);
} }
#[test] #[test]
fn ray_hits_vertex_at_cycle_seam() { fn ray_hits_vertex_at_cycle_seam() {
let mut services = Services::new(); let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[4., 2.], [0., 4.], [0., 0.]], [[4., 2.], [0., 4.], [0., 0.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let point = Point::from([1., 2.]); let point = Point::from([1., 2.]);
let intersection = (&face, &point).intersect(); let intersection = (&face, &point).intersect();
@ -194,18 +208,23 @@ mod tests {
intersection, intersection,
Some(FacePointIntersection::PointIsInsideFace) Some(FacePointIntersection::PointIsInsideFace)
); );
services.only_validate(face);
} }
#[test] #[test]
fn ray_hits_vertex_while_staying_inside() { fn ray_hits_vertex_while_staying_inside() {
let mut services = Services::new(); let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [2., 1.], [3., 0.], [3., 4.]], [[0., 0.], [2., 1.], [3., 0.], [3., 4.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let point = Point::from([1., 1.]); let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect(); let intersection = (&face, &point).intersect();
@ -213,18 +232,23 @@ mod tests {
intersection, intersection,
Some(FacePointIntersection::PointIsInsideFace) Some(FacePointIntersection::PointIsInsideFace)
); );
services.only_validate(face);
} }
#[test] #[test]
fn ray_hits_parallel_edge_and_leaves_face_at_vertex() { fn ray_hits_parallel_edge_and_leaves_face_at_vertex() {
let mut services = Services::new(); let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [2., 1.], [3., 1.], [0., 2.]], [[0., 0.], [2., 1.], [3., 1.], [0., 2.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let point = Point::from([1., 1.]); let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect(); let intersection = (&face, &point).intersect();
@ -232,18 +256,23 @@ mod tests {
intersection, intersection,
Some(FacePointIntersection::PointIsInsideFace) Some(FacePointIntersection::PointIsInsideFace)
); );
services.only_validate(face);
} }
#[test] #[test]
fn ray_hits_parallel_edge_and_does_not_leave_face_there() { fn ray_hits_parallel_edge_and_does_not_leave_face_there() {
let mut services = Services::new(); let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [2., 1.], [3., 1.], [4., 0.], [4., 5.]], [[0., 0.], [2., 1.], [3., 1.], [4., 0.], [4., 5.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let point = Point::from([1., 1.]); let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect(); let intersection = (&face, &point).intersect();
@ -251,18 +280,23 @@ mod tests {
intersection, intersection,
Some(FacePointIntersection::PointIsInsideFace) Some(FacePointIntersection::PointIsInsideFace)
); );
services.only_validate(face);
} }
#[test] #[test]
fn point_is_coincident_with_edge() { fn point_is_coincident_with_edge() {
let mut services = Services::new(); let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [2., 0.], [0., 1.]], [[0., 0.], [2., 0.], [0., 1.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let point = Point::from([1., 0.]); let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect(); let intersection = (&face, &point).intersect();
@ -276,18 +310,23 @@ mod tests {
intersection, intersection,
Some(FacePointIntersection::PointIsOnEdge(edge.clone())) Some(FacePointIntersection::PointIsOnEdge(edge.clone()))
); );
services.only_validate(face);
} }
#[test] #[test]
fn point_is_coincident_with_vertex() { fn point_is_coincident_with_vertex() {
let mut services = Services::new(); let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [1., 0.], [0., 1.]], [[0., 0.], [1., 0.], [0., 1.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let point = Point::from([1., 0.]); let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect(); let intersection = (&face, &point).intersect();
@ -304,5 +343,7 @@ mod tests {
intersection, intersection,
Some(FacePointIntersection::PointIsOnVertex(vertex)) Some(FacePointIntersection::PointIsOnVertex(vertex))
); );
services.only_validate(face);
} }
} }

View File

@ -152,7 +152,8 @@ mod tests {
}, },
transform::TransformObject, transform::TransformObject,
}, },
builder::{CycleBuilder, FaceBuilder}, objects::{Cycle, Face},
operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services, services::Services,
}; };
@ -162,15 +163,20 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let face = face.translate([-1., 0., 0.], &mut services); let face = face.translate([-1., 0., 0.], &mut services);
assert_eq!((&ray, &face).intersect(), None); assert_eq!((&ray, &face).intersect(), None);
services.only_validate(face);
} }
#[test] #[test]
@ -179,18 +185,23 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let face = face.translate([1., 0., 0.], &mut services); let face = face.translate([1., 0., 0.], &mut services);
assert_eq!( assert_eq!(
(&ray, &face).intersect(), (&ray, &face).intersect(),
Some(RayFaceIntersection::RayHitsFace) Some(RayFaceIntersection::RayHitsFace)
); );
services.only_validate(face);
} }
#[test] #[test]
@ -199,15 +210,20 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let face = face.translate([0., 0., 2.], &mut services); let face = face.translate([0., 0., 2.], &mut services);
assert_eq!((&ray, &face).intersect(), None); assert_eq!((&ray, &face).intersect(), None);
services.only_validate(face);
} }
#[test] #[test]
@ -216,12 +232,15 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let face = face.translate([1., 1., 0.], &mut services); let face = face.translate([1., 1., 0.], &mut services);
let edge = face let edge = face
@ -233,6 +252,8 @@ mod tests {
(&ray, &face).intersect(), (&ray, &face).intersect(),
Some(RayFaceIntersection::RayHitsEdge(edge.clone())) Some(RayFaceIntersection::RayHitsEdge(edge.clone()))
); );
services.only_validate(face);
} }
#[test] #[test]
@ -241,12 +262,15 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let face = face.translate([1., 1., 1.], &mut services); let face = face.translate([1., 1., 1.], &mut services);
let vertex = face let vertex = face
@ -261,6 +285,8 @@ mod tests {
(&ray, &face).intersect(), (&ray, &face).intersect(),
Some(RayFaceIntersection::RayHitsVertex(vertex)) Some(RayFaceIntersection::RayHitsVertex(vertex))
); );
services.only_validate(face);
} }
#[test] #[test]
@ -269,17 +295,22 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
assert_eq!( assert_eq!(
(&ray, &face).intersect(), (&ray, &face).intersect(),
Some(RayFaceIntersection::RayHitsFaceAndAreParallel) Some(RayFaceIntersection::RayHitsFaceAndAreParallel)
); );
services.only_validate(face);
} }
#[test] #[test]
@ -288,14 +319,19 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)
});
let face = face.translate([0., 0., 1.], &mut services); let face = face.translate([0., 0., 1.], &mut services);
assert_eq!((&ray, &face).intersect(), None); assert_eq!((&ray, &face).intersect(), None);
services.only_validate(face);
} }
} }

View File

@ -79,8 +79,8 @@ mod tests {
use crate::{ use crate::{
algorithms::approx::{Approx, Tolerance}, algorithms::approx::{Approx, Tolerance},
builder::{CycleBuilder, FaceBuilder}, objects::{Cycle, Face},
objects::Face, operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services, services::Services,
}; };
@ -95,9 +95,13 @@ mod tests {
let c = [2., 2.]; let c = [2., 2.];
let d = [0., 1.]; let d = [0., 1.];
let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) let face =
.with_exterior(CycleBuilder::polygon([a, b, c, d], &mut services)) Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.build(&mut services); .update_exterior(|_| {
Cycle::polygon([a, b, c, d], &mut services)
.insert(&mut services)
});
services.only_validate(&face);
let a = Point::from(a).to_xyz(); let a = Point::from(a).to_xyz();
let b = Point::from(b).to_xyz(); let b = Point::from(b).to_xyz();
@ -130,10 +134,14 @@ mod tests {
let surface = services.objects.surfaces.xy_plane(); let surface = services.objects.surfaces.xy_plane();
let face = FaceBuilder::new(surface.clone()) let face = Face::unbound(surface.clone(), &mut services)
.with_exterior(CycleBuilder::polygon([a, b, c, d], &mut services)) .update_exterior(|_| {
.with_interior(CycleBuilder::polygon([e, f, g, h], &mut services)) Cycle::polygon([a, b, c, d], &mut services)
.build(&mut services); .insert(&mut services)
})
.add_interiors([Cycle::polygon([e, f, g, h], &mut services)
.insert(&mut services)]);
services.only_validate(&face);
let triangles = triangulate(face)?; let triangles = triangulate(face)?;
@ -188,12 +196,12 @@ mod tests {
let surface = services.objects.surfaces.xy_plane(); let surface = services.objects.surfaces.xy_plane();
let face = FaceBuilder::new(surface.clone()) let face = Face::unbound(surface.clone(), &mut services)
.with_exterior(CycleBuilder::polygon( .update_exterior(|_| {
[a, b, c, d, e], Cycle::polygon([a, b, c, d, e], &mut services)
&mut services, .insert(&mut services)
)) });
.build(&mut services); services.only_validate(&face);
let triangles = triangulate(face)?; let triangles = triangulate(face)?;

View File

@ -1,58 +0,0 @@
use fj_interop::mesh::Color;
use crate::{
objects::{Face, Surface},
operations::Insert,
services::Services,
storage::Handle,
};
use super::CycleBuilder;
/// Builder API for [`Face`]
pub struct FaceBuilder {
surface: Handle<Surface>,
exterior: CycleBuilder,
interiors: Vec<CycleBuilder>,
color: Option<Color>,
}
impl FaceBuilder {
/// Create an instance of `FaceBuilder`
pub fn new(surface: Handle<Surface>) -> Self {
Self {
surface,
exterior: CycleBuilder::new(),
interiors: Vec::new(),
color: None,
}
}
/// Replace the face's exterior cycle
pub fn with_exterior(mut self, exterior: CycleBuilder) -> Self {
self.exterior = exterior;
self
}
/// Add an interior cycle to the face
pub fn with_interior(mut self, interior: CycleBuilder) -> Self {
self.interiors.push(interior);
self
}
/// Define the color of the face
pub fn with_color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
/// Build the face
pub fn build(self, services: &mut Services) -> Face {
let exterior = self.exterior.build(services).insert(services);
let interiors = self
.interiors
.into_iter()
.map(|cycle| cycle.build(services).insert(services));
Face::new(self.surface, exterior, interiors, self.color)
}
}

View File

@ -1,6 +1,5 @@
//! API for building objects //! API for building objects
mod cycle; mod cycle;
mod face;
pub use self::{cycle::CycleBuilder, face::FaceBuilder}; pub use self::cycle::CycleBuilder;

View File

@ -75,6 +75,7 @@
mod full; mod full;
mod object; mod object;
mod set;
mod stores; mod stores;
pub use self::{ pub use self::{
@ -89,5 +90,6 @@ pub use self::{
vertex::Vertex, vertex::Vertex,
}, },
object::{Bare, BehindHandle, Form, Object, WithHandle}, object::{Bare, BehindHandle, Form, Object, WithHandle},
set::ObjectSet,
stores::{Objects, Surfaces}, stores::{Objects, Surfaces},
}; };

View File

@ -0,0 +1,106 @@
use std::collections::{btree_set, BTreeSet};
use super::{
BehindHandle, Cycle, Face, GlobalEdge, HalfEdge, Object, Surface, Vertex,
};
/// A graph of objects and their relationships
pub struct ObjectSet {
inner: BTreeSet<Object<BehindHandle>>,
}
impl From<&Face> for ObjectSet {
fn from(face: &Face) -> Self {
let mut self_ = Self {
inner: BTreeSet::new(),
};
face.insert_into_set(&mut self_);
self_
}
}
impl From<Face> for ObjectSet {
fn from(face: Face) -> Self {
Self::from(&face)
}
}
impl<Faces> From<Faces> for ObjectSet
where
Faces: IntoIterator<Item = Face>,
{
fn from(faces: Faces) -> Self {
let mut self_ = Self {
inner: BTreeSet::new(),
};
for face in faces {
face.insert_into_set(&mut self_);
}
self_
}
}
impl IntoIterator for ObjectSet {
type Item = Object<BehindHandle>;
type IntoIter = btree_set::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
trait InsertIntoSet {
fn insert_into_set(&self, objects: &mut ObjectSet);
}
impl InsertIntoSet for Cycle {
fn insert_into_set(&self, objects: &mut ObjectSet) {
for half_edge in self.half_edges() {
objects.inner.insert(half_edge.clone().into());
half_edge.insert_into_set(objects);
}
}
}
impl InsertIntoSet for Face {
fn insert_into_set(&self, objects: &mut ObjectSet) {
objects.inner.insert(self.surface().clone().into());
self.surface().insert_into_set(objects);
objects.inner.insert(self.exterior().clone().into());
self.exterior().insert_into_set(objects);
for interior in self.interiors() {
objects.inner.insert(interior.clone().into());
}
for interior in self.interiors() {
interior.insert_into_set(objects);
}
}
}
impl InsertIntoSet for GlobalEdge {
fn insert_into_set(&self, _: &mut ObjectSet) {}
}
impl InsertIntoSet for HalfEdge {
fn insert_into_set(&self, objects: &mut ObjectSet) {
objects.inner.insert(self.start_vertex().clone().into());
self.start_vertex().insert_into_set(objects);
objects.inner.insert(self.global_form().clone().into());
self.global_form().insert_into_set(objects);
}
}
impl InsertIntoSet for Surface {
fn insert_into_set(&self, _: &mut ObjectSet) {}
}
impl InsertIntoSet for Vertex {
fn insert_into_set(&self, _: &mut ObjectSet) {}
}

View File

@ -8,10 +8,16 @@ use crate::{
storage::Handle, storage::Handle,
}; };
use super::{BuildHalfEdge, BuildSurface}; use super::{BuildCycle, BuildHalfEdge, BuildSurface};
/// Build a [`Face`] /// Build a [`Face`]
pub trait BuildFace { pub trait BuildFace {
/// Build a face with an empty exterior, no interiors, and no color
fn unbound(surface: Handle<Surface>, services: &mut Services) -> Face {
let exterior = Cycle::empty().insert(services);
Face::new(surface, exterior, [], None)
}
/// Build a triangle /// Build a triangle
fn triangle( fn triangle(
points: [impl Into<Point<3>>; 3], points: [impl Into<Point<3>>; 3],

View File

@ -10,6 +10,12 @@ pub trait UpdateFace {
&self, &self,
f: impl FnOnce(&Handle<Cycle>) -> Handle<Cycle>, f: impl FnOnce(&Handle<Cycle>) -> Handle<Cycle>,
) -> Self; ) -> Self;
/// Add the provides interiors to the face
fn add_interiors(
&self,
interiors: impl IntoIterator<Item = Handle<Cycle>>,
) -> Self;
} }
impl UpdateFace for Face { impl UpdateFace for Face {
@ -26,4 +32,18 @@ impl UpdateFace for Face {
self.color(), self.color(),
) )
} }
fn add_interiors(
&self,
interiors: impl IntoIterator<Item = Handle<Cycle>>,
) -> Self {
let interiors = self.interiors().cloned().chain(interiors);
Face::new(
self.surface().clone(),
self.exterior().clone(),
interiors,
self.color(),
)
}
} }

View File

@ -6,7 +6,7 @@ mod objects;
mod service; mod service;
mod validation; mod validation;
use crate::objects::{Object, Objects, WithHandle}; use crate::objects::{Object, ObjectSet, Objects, WithHandle};
pub use self::{ pub use self::{
objects::{InsertObject, Operation}, objects::{InsertObject, Operation},
@ -52,6 +52,15 @@ impl Services {
self.validation.execute(command, &mut Vec::new()); self.validation.execute(command, &mut Vec::new());
} }
} }
/// Validate the provided objects and forget all other validation errors
pub fn only_validate(&mut self, objects: impl Into<ObjectSet>) {
let objects = objects.into();
let mut events = Vec::new();
self.validation
.execute(ValidationCommand::OnlyValidate { objects }, &mut events);
}
} }
impl Default for Services { impl Default for Services {

View File

@ -1,7 +1,7 @@
use std::{collections::BTreeMap, thread}; use std::{collections::BTreeMap, thread};
use crate::{ use crate::{
objects::{BehindHandle, Object}, objects::{BehindHandle, Object, ObjectSet},
storage::ObjectId, storage::ObjectId,
validate::ValidationError, validate::ValidationError,
}; };
@ -39,9 +39,10 @@ impl State for Validation {
type Event = ValidationEvent; type Event = ValidationEvent;
fn decide(&self, command: Self::Command, events: &mut Vec<Self::Event>) { fn decide(&self, command: Self::Command, events: &mut Vec<Self::Event>) {
let ValidationCommand::ValidateObject { object } = command;
let mut errors = Vec::new(); let mut errors = Vec::new();
match command {
ValidationCommand::ValidateObject { object } => {
object.validate(&mut errors); object.validate(&mut errors);
for err in errors { for err in errors {
@ -51,12 +52,29 @@ impl State for Validation {
}); });
} }
} }
ValidationCommand::OnlyValidate { objects } => {
events.push(ValidationEvent::ClearErrors);
for object in objects {
object.validate(&mut errors);
for err in errors.drain(..) {
events.push(ValidationEvent::ValidationFailed {
object: object.clone(),
err,
});
}
}
}
}
}
fn evolve(&mut self, event: &Self::Event) { fn evolve(&mut self, event: &Self::Event) {
match event { match event {
ValidationEvent::ValidationFailed { object, err } => { ValidationEvent::ValidationFailed { object, err } => {
self.errors.insert(object.id(), err.clone()); self.errors.insert(object.id(), err.clone());
} }
ValidationEvent::ClearErrors => self.errors.clear(),
} }
} }
} }
@ -68,6 +86,12 @@ pub enum ValidationCommand {
/// The object to validate /// The object to validate
object: Object<BehindHandle>, object: Object<BehindHandle>,
}, },
/// Validate the provided objects, discard all other validation errors
OnlyValidate {
/// The objects to validate
objects: ObjectSet,
},
} }
/// The event produced by the validation service /// The event produced by the validation service
@ -81,4 +105,7 @@ pub enum ValidationEvent {
/// The validation error /// The validation error
err: ValidationError, err: ValidationError,
}, },
/// All stored validation errors are being cleared
ClearErrors,
} }

View File

@ -73,8 +73,8 @@ mod tests {
use crate::{ use crate::{
algorithms::reverse::Reverse, algorithms::reverse::Reverse,
assert_contains_err, assert_contains_err,
builder::{CycleBuilder, FaceBuilder}, objects::{Cycle, Face},
objects::Face, operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services, services::Services,
validate::{FaceValidationError, Validate, ValidationError}, validate::{FaceValidationError, Validate, ValidationError},
}; };
@ -83,16 +83,20 @@ mod tests {
fn face_invalid_interior_winding() -> anyhow::Result<()> { fn face_invalid_interior_winding() -> anyhow::Result<()> {
let mut services = Services::new(); let mut services = Services::new();
let valid = FaceBuilder::new(services.objects.surfaces.xy_plane()) let valid =
.with_exterior(CycleBuilder::polygon( Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [3., 0.], [0., 3.]], [[0., 0.], [3., 0.], [0., 3.]],
&mut services, &mut services,
)) )
.with_interior(CycleBuilder::polygon( .insert(&mut services)
})
.add_interiors([Cycle::polygon(
[[1., 1.], [1., 2.], [2., 1.]], [[1., 1.], [1., 2.], [2., 1.]],
&mut services, &mut services,
)) )
.build(&mut services); .insert(&mut services)]);
let invalid = { let invalid = {
let interiors = valid let interiors = valid
.interiors() .interiors()
@ -116,6 +120,8 @@ mod tests {
) )
); );
services.only_validate(valid);
Ok(()) Ok(())
} }
} }