Merge pull request #1599 from hannobraun/surface

Start moving reference to `Surface` from `Cycle` to `Face`
This commit is contained in:
Hanno Braun 2023-02-17 17:05:28 +01:00 committed by GitHub
commit 0fd4db69dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 342 additions and 310 deletions

View File

@ -2,17 +2,15 @@
//!
//! See [`CycleApprox`].
use std::ops::Deref;
use fj_math::Segment;
use crate::objects::Cycle;
use crate::objects::{Cycle, Surface};
use super::{
curve::CurveCache, edge::HalfEdgeApprox, Approx, ApproxPoint, Tolerance,
};
impl Approx for &Cycle {
impl Approx for (&Cycle, &Surface) {
type Approximation = CycleApprox;
type Cache = CurveCache;
@ -21,13 +19,13 @@ impl Approx for &Cycle {
tolerance: impl Into<Tolerance>,
cache: &mut Self::Cache,
) -> Self::Approximation {
let (cycle, surface) = self;
let tolerance = tolerance.into();
let half_edges = self
let half_edges = cycle
.half_edges()
.map(|half_edge| {
(half_edge, self.surface().deref())
.approx_with_cache(tolerance, cache)
(half_edge, surface).approx_with_cache(tolerance, cache)
})
.collect();

View File

@ -2,7 +2,7 @@
//!
//! See [`FaceApprox`].
use std::collections::BTreeSet;
use std::{collections::BTreeSet, ops::Deref};
use fj_interop::mesh::Color;
@ -87,11 +87,13 @@ impl Approx for &Face {
// would need to provide its own approximation, as the edges that bound
// it have nothing to do with its curvature.
let exterior = self.exterior().approx_with_cache(tolerance, cache);
let exterior = (self.exterior().deref(), self.surface().deref())
.approx_with_cache(tolerance, cache);
let mut interiors = BTreeSet::new();
for cycle in self.interiors() {
let cycle = cycle.approx_with_cache(tolerance, cache);
let cycle = (cycle.deref(), self.surface().deref())
.approx_with_cache(tolerance, cache);
interiors.insert(cycle);
}

View File

@ -183,7 +183,10 @@ mod tests {
];
let face = {
let mut face = PartialFace::default();
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior
.write()

View File

@ -93,7 +93,10 @@ mod tests {
services.objects.surfaces.xz_plane(),
]
.map(|surface| {
let mut face = PartialFace::default();
let mut face = PartialFace {
surface: Partial::from(surface.clone()),
..Default::default()
};
face.exterior.write().surface = Partial::from(surface);
face.exterior.write().update_as_polygon_from_points(points);
@ -122,7 +125,10 @@ mod tests {
services.objects.surfaces.xz_plane(),
];
let [a, b] = surfaces.clone().map(|surface| {
let mut face = PartialFace::default();
let mut face = PartialFace {
surface: Partial::from(surface.clone()),
..Default::default()
};
face.exterior.write().surface = Partial::from(surface);
face.exterior.write().update_as_polygon_from_points(points);

View File

@ -148,9 +148,13 @@ mod tests {
fn point_is_outside_face() {
let mut services = Services::new();
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.xy_plane());
let surface = Partial::from(services.objects.surfaces.xy_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[0., 0.],
[1., 1.],
@ -169,9 +173,13 @@ mod tests {
fn ray_hits_vertex_while_passing_outside() {
let mut services = Services::new();
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.xy_plane());
let surface = Partial::from(services.objects.surfaces.xy_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[0., 0.],
[2., 1.],
@ -193,9 +201,13 @@ mod tests {
fn ray_hits_vertex_at_cycle_seam() {
let mut services = Services::new();
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.xy_plane());
let surface = Partial::from(services.objects.surfaces.xy_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[4., 2.],
[0., 4.],
@ -217,9 +229,13 @@ mod tests {
fn ray_hits_vertex_while_staying_inside() {
let mut services = Services::new();
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.xy_plane());
let surface = Partial::from(services.objects.surfaces.xy_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[0., 0.],
[2., 1.],
@ -242,9 +258,13 @@ mod tests {
fn ray_hits_parallel_edge_and_leaves_face_at_vertex() {
let mut services = Services::new();
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.xy_plane());
let surface = Partial::from(services.objects.surfaces.xy_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[0., 0.],
[2., 1.],
@ -267,9 +287,13 @@ mod tests {
fn ray_hits_parallel_edge_and_does_not_leave_face_there() {
let mut services = Services::new();
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.xy_plane());
let surface = Partial::from(services.objects.surfaces.xy_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[0., 0.],
[2., 1.],
@ -293,9 +317,13 @@ mod tests {
fn point_is_coincident_with_edge() {
let mut services = Services::new();
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.xy_plane());
let surface = Partial::from(services.objects.surfaces.xy_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[0., 0.],
[2., 0.],
@ -326,9 +354,13 @@ mod tests {
fn point_is_coincident_with_vertex() {
let mut services = Services::new();
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.xy_plane());
let surface = Partial::from(services.objects.surfaces.xy_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[0., 0.],
[1., 0.],

View File

@ -163,9 +163,13 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.yz_plane());
let surface = Partial::from(services.objects.surfaces.yz_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[-1., -1.],
[1., -1.],
@ -186,9 +190,13 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.yz_plane());
let surface = Partial::from(services.objects.surfaces.yz_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[-1., -1.],
[1., -1.],
@ -212,9 +220,13 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.yz_plane());
let surface = Partial::from(services.objects.surfaces.yz_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[-1., -1.],
[1., -1.],
@ -235,9 +247,13 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.yz_plane());
let surface = Partial::from(services.objects.surfaces.yz_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[-1., -1.],
[1., -1.],
@ -269,9 +285,13 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.yz_plane());
let surface = Partial::from(services.objects.surfaces.yz_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[-1., -1.],
[1., -1.],
@ -303,9 +323,13 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.xy_plane());
let surface = Partial::from(services.objects.surfaces.xy_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[-1., -1.],
[1., -1.],
@ -328,9 +352,13 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.xy_plane());
let surface = Partial::from(services.objects.surfaces.xy_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior.write().update_as_polygon_from_points([
[-1., -1.],
[1., -1.],

View File

@ -1,7 +1,6 @@
use crate::{
insert::Insert,
objects::{Face, Objects},
partial::{FullToPartialCache, Partial, PartialFace, PartialObject},
services::Service,
storage::Handle,
};
@ -10,24 +9,13 @@ use super::Reverse;
impl Reverse for Handle<Face> {
fn reverse(self, objects: &mut Service<Objects>) -> Self {
let mut cache = FullToPartialCache::default();
let exterior = Partial::from_full(
self.exterior().clone().reverse(objects),
&mut cache,
);
let exterior = self.exterior().clone().reverse(objects);
let interiors = self
.interiors()
.map(|cycle| {
Partial::from_full(cycle.clone().reverse(objects), &mut cache)
})
.map(|cycle| cycle.clone().reverse(objects))
.collect::<Vec<_>>();
let face = PartialFace {
exterior,
interiors,
color: Some(self.color()),
};
face.build(objects).insert(objects)
Face::new(self.surface().clone(), exterior, interiors, self.color())
.insert(objects)
}
}

View File

@ -33,10 +33,14 @@ impl Sweep for (Handle<HalfEdge>, &Surface, Color) {
// A face (and everything in it) is defined on a surface. A surface can
// be created by sweeping a curve, so let's sweep the curve of the edge
// we're sweeping.
face.exterior.write().surface = Partial::from(
(edge.curve().clone(), surface)
.sweep_with_cache(path, cache, objects),
);
{
let surface = Partial::from(
(edge.curve().clone(), surface)
.sweep_with_cache(path, cache, objects),
);
face.surface = surface.clone();
face.exterior.write().surface = surface;
}
// Now we're ready to create the edges.
let mut edge_bottom = face.exterior.write().add_half_edge();
@ -266,7 +270,7 @@ mod tests {
};
let mut cycle = PartialCycle {
surface: Partial::from(surface),
surface: Partial::from(surface.clone()),
..Default::default()
};
cycle.half_edges.extend(
@ -274,6 +278,7 @@ mod tests {
);
let face = PartialFace {
surface: Partial::from(surface),
exterior: Partial::from_partial(cycle),
..Default::default()
};

View File

@ -53,12 +53,13 @@ impl Sweep for Handle<Face> {
};
faces.push(bottom_face.clone());
let top_surface =
bottom_face.surface().clone().translate(path, objects);
let mut top_face = PartialFace {
surface: Partial::from(top_surface.clone()),
color: Some(self.color()),
..PartialFace::default()
};
let top_surface =
bottom_face.surface().clone().translate(path, objects);
for (i, cycle) in bottom_face.all_cycles().cloned().enumerate() {
let cycle = cycle.reverse(objects);
@ -84,7 +85,9 @@ impl Sweep for Handle<Face> {
top_cycle.write().surface = Partial::from(top_surface.clone());
top_cycle.write().connect_to_closed_edges(top_edges);
top_cycle
.write()
.connect_to_closed_edges(top_edges, &top_surface.geometry());
for half_edge in &mut top_cycle.write().half_edges {
for (_, surface_vertex) in &mut half_edge.write().vertices {
@ -161,6 +164,7 @@ mod tests {
let mut sketch = PartialSketch::default();
let mut face = sketch.add_face();
face.write().surface = Partial::from(surface.clone());
face.write().exterior.write().surface =
Partial::from(surface.clone());
face.write()
@ -175,23 +179,37 @@ mod tests {
.insert(&mut services.objects)
.sweep(UP, &mut services.objects);
let mut bottom = PartialFace::default();
bottom.exterior.write().surface = Partial::from(surface.clone());
bottom
.exterior
.write()
.update_as_polygon_from_points(TRIANGLE);
let bottom = bottom
.build(&mut services.objects)
.insert(&mut services.objects)
.reverse(&mut services.objects);
let mut top = PartialFace::default();
top.exterior.write().surface =
Partial::from(surface.clone().translate(UP, &mut services.objects));
top.exterior.write().update_as_polygon_from_points(TRIANGLE);
let top = top
.build(&mut services.objects)
.insert(&mut services.objects);
let bottom = {
let mut bottom = PartialFace {
surface: Partial::from(surface.clone()),
..Default::default()
};
bottom.exterior.write().surface = Partial::from(surface.clone());
bottom
.exterior
.write()
.update_as_polygon_from_points(TRIANGLE);
bottom
.build(&mut services.objects)
.insert(&mut services.objects)
.reverse(&mut services.objects)
};
let top = {
let surface = surface.clone().translate(UP, &mut services.objects);
let mut top = PartialFace {
surface: Partial::from(surface.clone()),
..Default::default()
};
top.exterior.write().surface = Partial::from(surface);
top.exterior.write().update_as_polygon_from_points(TRIANGLE);
top.build(&mut services.objects)
.insert(&mut services.objects)
};
assert!(solid.find_face(&bottom).is_some());
assert!(solid.find_face(&top).is_some());
@ -227,6 +245,7 @@ mod tests {
let mut sketch = PartialSketch::default();
let mut face = sketch.add_face();
face.write().surface = Partial::from(surface.clone());
face.write().exterior.write().surface =
Partial::from(surface.clone());
face.write()
@ -241,24 +260,38 @@ mod tests {
.insert(&mut services.objects)
.sweep(DOWN, &mut services.objects);
let mut bottom = PartialFace::default();
bottom.exterior.write().surface = Partial::from(
surface.clone().translate(DOWN, &mut services.objects),
);
bottom
.exterior
.write()
.update_as_polygon_from_points(TRIANGLE);
let bottom = bottom
.build(&mut services.objects)
.insert(&mut services.objects)
.reverse(&mut services.objects);
let mut top = PartialFace::default();
top.exterior.write().surface = Partial::from(surface.clone());
top.exterior.write().update_as_polygon_from_points(TRIANGLE);
let top = top
.build(&mut services.objects)
.insert(&mut services.objects);
let bottom = {
let surface =
surface.clone().translate(DOWN, &mut services.objects);
let mut bottom = PartialFace {
surface: Partial::from(surface.clone()),
..Default::default()
};
bottom.exterior.write().surface = Partial::from(surface);
bottom
.exterior
.write()
.update_as_polygon_from_points(TRIANGLE);
bottom
.build(&mut services.objects)
.insert(&mut services.objects)
.reverse(&mut services.objects)
};
let top = {
let mut top = PartialFace {
surface: Partial::from(surface.clone()),
..Default::default()
};
top.exterior.write().surface = Partial::from(surface.clone());
top.exterior.write().update_as_polygon_from_points(TRIANGLE);
top.build(&mut services.objects)
.insert(&mut services.objects)
};
assert!(solid.find_face(&bottom).is_some());
assert!(solid.find_face(&top).is_some());

View File

@ -17,6 +17,10 @@ impl TransformObject for Face {
// Color does not need to be transformed.
let color = self.color();
let surface = self
.surface()
.clone()
.transform_with_cache(transform, objects, cache);
let exterior = self
.exterior()
.clone()
@ -25,7 +29,7 @@ impl TransformObject for Face {
interior.transform_with_cache(transform, objects, cache)
});
Self::new(exterior, interiors, color)
Self::new(surface, exterior, interiors, color)
}
}

View File

@ -96,9 +96,13 @@ mod tests {
let c = [2., 2.];
let d = [0., 1.];
let mut face = PartialFace::default();
face.exterior.write().surface =
Partial::from(services.objects.surfaces.xy_plane());
let surface = Partial::from(services.objects.surfaces.xy_plane());
let mut face = PartialFace {
surface: surface.clone(),
..Default::default()
};
face.exterior.write().surface = surface;
face.exterior
.write()
.update_as_polygon_from_points([a, b, c, d]);
@ -136,7 +140,10 @@ mod tests {
let h = [3., 1.];
let surface = services.objects.surfaces.xy_plane();
let mut face = PartialFace::default();
let mut face = PartialFace {
surface: Partial::from(surface.clone()),
..Default::default()
};
face.exterior.write().surface = Partial::from(surface.clone());
face.exterior
.write()
@ -200,7 +207,10 @@ mod tests {
let e = [0.0, 1.0];
let surface = services.objects.surfaces.xy_plane();
let mut face = PartialFace::default();
let mut face = PartialFace {
surface: Partial::from(surface.clone()),
..Default::default()
};
face.exterior.write().surface = Partial::from(surface.clone());
face.exterior
.write()

View File

@ -1,10 +1,7 @@
use std::collections::VecDeque;
use fj_interop::ext::ArrayExt;
use fj_math::Point;
use crate::{
builder::SurfaceBuilder,
geometry::surface::SurfaceGeometry,
objects::HalfEdge,
partial::{Partial, PartialCycle},
};
@ -64,35 +61,6 @@ pub trait CycleBuilder {
/// Will update each half-edge in the cycle to be a line segment.
fn update_as_polygon(&mut self);
/// Update cycle as a triangle, from global (3D) points
///
/// Uses the three points to infer a plane that is used as the surface.
///
/// # Implementation Note
///
/// This method is probably just temporary, and will be generalized into a
/// "update as polygon from global points" method sooner or later. For now,
/// I didn't want to deal with the question of how to infer the surface, and
/// how to handle points that don't fit that surface.
fn update_as_triangle_from_global_points(
&mut self,
points: [impl Into<Point<3>>; 3],
) -> [Partial<HalfEdge>; 3];
/// Connect the cycle to the provided half-edges
///
/// Assumes that the provided half-edges, once translated into local
/// equivalents of this cycle, will not form a cycle themselves.
///
/// 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<O>(
&mut self,
edges: O,
) -> O::SizePlusOne<Partial<HalfEdge>>
where
O: ObjectArgument<Partial<HalfEdge>>;
/// Connect the cycles to the provided half-edges
///
/// Assumes that the provided half-edges, once translated into local
@ -102,6 +70,7 @@ pub trait CycleBuilder {
fn connect_to_closed_edges<O>(
&mut self,
edges: O,
surface: &SurfaceGeometry,
) -> O::SameSize<Partial<HalfEdge>>
where
O: ObjectArgument<Partial<HalfEdge>>;
@ -204,65 +173,17 @@ impl CycleBuilder for PartialCycle {
}
}
fn update_as_triangle_from_global_points(
&mut self,
points_global: [impl Into<Point<3>>; 3],
) -> [Partial<HalfEdge>; 3] {
let points_global = points_global.map(Into::into);
let (points_surface, _) = self
.surface
.write()
.update_as_plane_from_points(points_global);
let half_edges = self.update_as_polygon_from_points(points_surface);
for (mut half_edge, point) in half_edges.clone().zip_ext(points_global)
{
let [vertex, _] = &mut half_edge.write().vertices;
vertex.1.write().global_form.write().position = Some(point);
}
half_edges
}
fn connect_to_open_edges<O>(
&mut self,
edges: O,
) -> O::SizePlusOne<Partial<HalfEdge>>
where
O: ObjectArgument<Partial<HalfEdge>>,
{
// 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.add_half_edge());
}
let additional_half_edge = self.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",
);
this.write()
.update_from_other_edge(&other, &self.surface.read().geometry);
this
})
}
fn connect_to_closed_edges<O>(
&mut self,
edges: O,
surface: &SurfaceGeometry,
) -> O::SameSize<Partial<HalfEdge>>
where
O: ObjectArgument<Partial<HalfEdge>>,
{
edges.map(|other| {
let mut this = self.add_half_edge();
this.write()
.update_from_other_edge(&other, &self.surface.read().geometry);
this.write().update_from_other_edge(&other, surface);
this
})
}

View File

@ -58,7 +58,7 @@ pub trait HalfEdgeBuilder {
fn update_from_other_edge(
&mut self,
other: &Partial<HalfEdge>,
surface: &Option<SurfaceGeometry>,
surface: &SurfaceGeometry,
);
}
@ -222,7 +222,7 @@ impl HalfEdgeBuilder for PartialHalfEdge {
fn update_from_other_edge(
&mut self,
other: &Partial<HalfEdge>,
surface: &Option<SurfaceGeometry>,
surface: &SurfaceGeometry,
) {
let global_curve = other.read().curve.read().global_form.clone();
self.curve.write().global_form = global_curve.clone();
@ -230,103 +230,78 @@ impl HalfEdgeBuilder for PartialHalfEdge {
self.curve.write().path =
other.read().curve.read().path.as_ref().and_then(|path| {
match surface {
Some(surface) => {
// We have information about the other edge's surface
// available. We need to use that to interpret what the
// other edge's curve path means for our curve path.
match surface.u {
GlobalPath::Circle(circle) => {
// The other surface is curved. We're entering
// some dodgy territory here, as only some edge
// cases can be represented using our current
// curve/surface representation.
match path {
MaybeSurfacePath::Defined(
SurfacePath::Line(_),
)
| MaybeSurfacePath::UndefinedLine => {
// We're dealing with a line on a
// rounded surface.
//
// Based on the current uses of this
// method, we can make some assumptions:
//
// 1. The line is parallel to the u-axis
// of the other surface.
// 2. The surface that *our* edge is in
// is a plane that is parallel to the
// the plane of the circle that
// defines the curvature of the other
// surface.
//
// These assumptions are necessary
// preconditions for the following code
// to work. But unfortunately, I see no
// way to check those preconditions
// here, as neither the other line nor
// our surface is necessarily defined
// yet.
//
// Handling this case anyway feels like
// a grave sin, but I don't know what
// else to do. If you tracked some
// extremely subtle and annoying bug
// back to this code, I apologize.
//
// I hope that I'll come up with a
// better curve/surface representation
// before this becomes a problem.
Some(
MaybeSurfacePath::UndefinedCircle {
radius: circle.radius(),
},
)
}
_ => {
// The other edge is a line segment in a
// curved surface. No idea how to deal
// with this.
todo!(
"Can't connect edge to circle on \
curved surface"
)
}
}
// We have information about the other edge's surface available.
// We need to use that to interpret what the other edge's curve
// path means for our curve path.
match surface.u {
GlobalPath::Circle(circle) => {
// The other surface is curved. We're entering some
// dodgy territory here, as only some edge cases can be
// represented using our current curve/surface
// representation.
match path {
MaybeSurfacePath::Defined(SurfacePath::Line(_))
| MaybeSurfacePath::UndefinedLine => {
// We're dealing with a line on a rounded
// surface.
//
// Based on the current uses of this method, we
// can make some assumptions:
//
// 1. The line is parallel to the u-axis of the
// other surface.
// 2. The surface that *our* edge is in is a
// plane that is parallel to the the plane of
// the circle that defines the curvature of
// the other surface.
//
// These assumptions are necessary preconditions
// for the following code to work. But
// unfortunately, I see no way to check those
// preconditions here, as neither the other line
// nor our surface is necessarily defined yet.
//
// Handling this case anyway feels like a grave
// sin, but I don't know what else to do. If you
// tracked some extremely subtle and annoying
// bug back to this code, I apologize.
//
// I hope that I'll come up with a better curve/
// surface representation before this becomes a
// problem.
Some(MaybeSurfacePath::UndefinedCircle {
radius: circle.radius(),
})
}
GlobalPath::Line(_) => {
// The other edge is defined on a plane.
match path {
MaybeSurfacePath::Defined(
SurfacePath::Line(_),
)
| MaybeSurfacePath::UndefinedLine => {
// The other edge is a line segment on
// a plane. That means our edge must be
// a line segment too.
Some(MaybeSurfacePath::UndefinedLine)
}
_ => {
// The other edge is a circle or arc on
// a plane. I'm actually not sure what
// that means for our edge. We might be
// able to represent it somehow, but
// let's leave that as an exercise for
// later.
todo!(
"Can't connect edge to circle on \
plane"
)
}
}
_ => {
// The other edge is a line segment in a curved
// surface. No idea how to deal with this.
todo!(
"Can't connect edge to circle on curved \
surface"
)
}
}
}
None => {
// We know nothing about the surface the other edge is
// on. This means we can't infer anything about our
// curve from the other curve.
None
GlobalPath::Line(_) => {
// The other edge is defined on a plane.
match path {
MaybeSurfacePath::Defined(SurfacePath::Line(_))
| MaybeSurfacePath::UndefinedLine => {
// The other edge is a line segment on a plane.
// That means our edge must be a line segment
// too.
Some(MaybeSurfacePath::UndefinedLine)
}
_ => {
// The other edge is a circle or arc on a plane.
// I'm actually not sure what that means for our
// edge. We might be able to represent it
// somehow, but let's leave that as an exercise
// for later.
todo!("Can't connect edge to circle on plane")
}
}
}
}
});

View File

@ -34,6 +34,7 @@ use crate::{
/// [`Shell`]: crate::objects::Shell
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Face {
surface: Handle<Surface>,
exterior: Handle<Cycle>,
interiors: Vec<Handle<Cycle>>,
color: Color,
@ -42,6 +43,7 @@ pub struct Face {
impl Face {
/// Construct an instance of `Face`
pub fn new(
surface: Handle<Surface>,
exterior: Handle<Cycle>,
interiors: impl IntoIterator<Item = Handle<Cycle>>,
color: Color,
@ -49,6 +51,7 @@ impl Face {
let interiors = interiors.into_iter().collect();
Self {
surface,
exterior,
interiors,
color,
@ -57,7 +60,7 @@ impl Face {
/// Access the surface of the face
pub fn surface(&self) -> &Handle<Surface> {
self.exterior().surface()
&self.surface
}
/// Access the cycle that bounds the face on the outside

View File

@ -1,7 +1,7 @@
use fj_interop::mesh::Color;
use crate::{
objects::{Cycle, Face, Objects},
objects::{Cycle, Face, Objects, Surface},
partial::{FullToPartialCache, Partial, PartialObject},
services::Service,
};
@ -9,6 +9,9 @@ use crate::{
/// A partial [`Face`]
#[derive(Clone, Debug, Default)]
pub struct PartialFace {
/// The surface that the face is defined in
pub surface: Partial<Surface>,
/// The cycle that bounds the face on the outside
pub exterior: Partial<Cycle>,
@ -26,6 +29,7 @@ impl PartialObject for PartialFace {
fn from_full(face: &Self::Full, cache: &mut FullToPartialCache) -> Self {
Self {
surface: Partial::from_full(face.surface().clone(), cache),
exterior: Partial::from_full(face.exterior().clone(), cache),
interiors: face
.interiors()
@ -36,11 +40,12 @@ impl PartialObject for PartialFace {
}
fn build(self, objects: &mut Service<Objects>) -> Self::Full {
let surface = self.surface.build(objects);
let exterior = self.exterior.build(objects);
let interiors =
self.interiors.into_iter().map(|cycle| cycle.build(objects));
let color = self.color.unwrap_or_default();
Face::new(exterior, interiors, color)
Face::new(surface, exterior, interiors, color)
}
}

View File

@ -115,7 +115,10 @@ mod tests {
let surface = services.objects.surfaces.xy_plane();
let valid = {
let mut face = PartialFace::default();
let mut face = PartialFace {
surface: Partial::from(surface.clone()),
..Default::default()
};
face.exterior.write().surface = Partial::from(surface);
face.exterior.write().update_as_polygon_from_points([
[0., 0.],
@ -141,7 +144,12 @@ mod tests {
.insert(&mut services.objects);
let interiors = [cycle];
Face::new(valid.exterior().clone(), interiors, valid.color())
Face::new(
valid.surface().clone(),
valid.exterior().clone(),
interiors,
valid.color(),
)
};
valid.validate_and_return_first_error()?;
@ -157,7 +165,10 @@ mod tests {
let surface = services.objects.surfaces.xy_plane();
let valid = {
let mut face = PartialFace::default();
let mut face = PartialFace {
surface: Partial::from(surface.clone()),
..Default::default()
};
face.exterior.write().surface = Partial::from(surface);
face.exterior.write().update_as_polygon_from_points([
[0., 0.],
@ -178,7 +189,12 @@ mod tests {
.map(|cycle| cycle.reverse(&mut services.objects))
.collect::<Vec<_>>();
Face::new(valid.exterior().clone(), interiors, valid.color())
Face::new(
valid.surface().clone(),
valid.exterior().clone(),
interiors,
valid.color(),
)
};
valid.validate_and_return_first_error()?;

View File

@ -82,6 +82,7 @@ impl Shape for fj::Difference2d {
);
let face = PartialFace {
surface: Partial::from(surface.clone()),
exterior: Partial::from(exterior),
interiors,
color: Some(Color(self.color())),

View File

@ -37,7 +37,7 @@ impl Shape for fj::Sketch {
};
let exterior = {
let mut cycle = PartialCycle {
surface,
surface: surface.clone(),
..Default::default()
};
cycle.half_edges.push(half_edge);
@ -45,6 +45,7 @@ impl Shape for fj::Sketch {
};
PartialFace {
surface,
exterior,
color: Some(Color(self.color())),
..Default::default()
@ -59,7 +60,7 @@ impl Shape for fj::Sketch {
let exterior = {
let mut cycle = PartialCycle {
surface: Partial::from(surface),
surface: Partial::from(surface.clone()),
..Default::default()
};
let mut line_segments = vec![];
@ -98,6 +99,7 @@ impl Shape for fj::Sketch {
};
PartialFace {
surface: Partial::from(surface),
exterior,
color: Some(Color(self.color())),
..Default::default()