mirror of
https://github.com/hannobraun/Fornjot
synced 2025-09-05 17:06:58 +00:00
Merge pull request #1941 from hannobraun/boundary
Use dedicated struct to represent boundaries on curves
This commit is contained in:
commit
c92997af66
@ -10,12 +10,12 @@ use std::collections::BTreeMap;
|
||||
use fj_math::Point;
|
||||
|
||||
use crate::{
|
||||
geometry::{GlobalPath, SurfacePath},
|
||||
geometry::{BoundaryOnCurve, GlobalPath, SurfacePath},
|
||||
objects::{GlobalEdge, HalfEdge, Surface, Vertex},
|
||||
storage::{Handle, ObjectId},
|
||||
};
|
||||
|
||||
use super::{path::RangeOnPath, Approx, ApproxPoint, Tolerance};
|
||||
use super::{Approx, ApproxPoint, Tolerance};
|
||||
|
||||
impl Approx for (&HalfEdge, &Surface) {
|
||||
type Approximation = HalfEdgeApprox;
|
||||
@ -28,9 +28,6 @@ impl Approx for (&HalfEdge, &Surface) {
|
||||
) -> Self::Approximation {
|
||||
let (half_edge, surface) = self;
|
||||
|
||||
let boundary = half_edge.boundary();
|
||||
let range = RangeOnPath { boundary };
|
||||
|
||||
let position_surface = half_edge.start_position();
|
||||
let position_global = match cache.get_position(half_edge.start_vertex())
|
||||
{
|
||||
@ -87,20 +84,22 @@ impl Approx for (&HalfEdge, &Surface) {
|
||||
//
|
||||
// Only item 2. is something we can do right here. Item 1. requires
|
||||
// a change to the object graph.
|
||||
let cached_approx =
|
||||
cache.get_edge(half_edge.global_form().clone(), range);
|
||||
let cached_approx = cache.get_edge(
|
||||
half_edge.global_form().clone(),
|
||||
half_edge.boundary(),
|
||||
);
|
||||
let approx = match cached_approx {
|
||||
Some(approx) => approx,
|
||||
None => {
|
||||
let approx = approx_edge(
|
||||
&half_edge.path(),
|
||||
surface,
|
||||
range,
|
||||
half_edge.boundary(),
|
||||
tolerance,
|
||||
);
|
||||
cache.insert_edge(
|
||||
half_edge.global_form().clone(),
|
||||
range,
|
||||
half_edge.boundary(),
|
||||
approx,
|
||||
)
|
||||
}
|
||||
@ -148,7 +147,7 @@ impl HalfEdgeApprox {
|
||||
fn approx_edge(
|
||||
path: &SurfacePath,
|
||||
surface: &Surface,
|
||||
range: RangeOnPath,
|
||||
boundary: BoundaryOnCurve,
|
||||
tolerance: impl Into<Tolerance>,
|
||||
) -> GlobalEdgeApprox {
|
||||
// There are different cases of varying complexity. Circles are the hard
|
||||
@ -164,7 +163,7 @@ fn approx_edge(
|
||||
)
|
||||
}
|
||||
(SurfacePath::Circle(_), GlobalPath::Line(_)) => {
|
||||
(path, range)
|
||||
(path, boundary)
|
||||
.approx_with_cache(tolerance, &mut ())
|
||||
.into_iter()
|
||||
.map(|(point_curve, point_surface)| {
|
||||
@ -192,7 +191,7 @@ fn approx_edge(
|
||||
}
|
||||
(SurfacePath::Line(line), _) => {
|
||||
let range_u =
|
||||
RangeOnPath::from(range.boundary.map(|point_curve| {
|
||||
BoundaryOnCurve::from(boundary.inner.map(|point_curve| {
|
||||
[path.point_from_path_coords(point_curve).u]
|
||||
}));
|
||||
|
||||
@ -224,7 +223,7 @@ fn approx_edge(
|
||||
/// A cache for results of an approximation
|
||||
#[derive(Default)]
|
||||
pub struct EdgeCache {
|
||||
edge_approx: BTreeMap<(ObjectId, RangeOnPath), GlobalEdgeApprox>,
|
||||
edge_approx: BTreeMap<(ObjectId, BoundaryOnCurve), GlobalEdgeApprox>,
|
||||
vertex_approx: BTreeMap<ObjectId, Point<3>>,
|
||||
}
|
||||
|
||||
@ -238,15 +237,15 @@ impl EdgeCache {
|
||||
pub fn get_edge(
|
||||
&self,
|
||||
handle: Handle<GlobalEdge>,
|
||||
range: RangeOnPath,
|
||||
boundary: BoundaryOnCurve,
|
||||
) -> Option<GlobalEdgeApprox> {
|
||||
if let Some(approx) = self.edge_approx.get(&(handle.id(), range)) {
|
||||
if let Some(approx) = self.edge_approx.get(&(handle.id(), boundary)) {
|
||||
return Some(approx.clone());
|
||||
}
|
||||
if let Some(approx) =
|
||||
self.edge_approx.get(&(handle.id(), range.reverse()))
|
||||
self.edge_approx.get(&(handle.id(), boundary.reverse()))
|
||||
{
|
||||
// If we have a cache entry for the reverse range, we need to use
|
||||
// If we have a cache entry for the reverse boundary, we need to use
|
||||
// that too!
|
||||
return Some(approx.clone().reverse());
|
||||
}
|
||||
@ -258,11 +257,11 @@ impl EdgeCache {
|
||||
pub fn insert_edge(
|
||||
&mut self,
|
||||
handle: Handle<GlobalEdge>,
|
||||
range: RangeOnPath,
|
||||
boundary: BoundaryOnCurve,
|
||||
approx: GlobalEdgeApprox,
|
||||
) -> GlobalEdgeApprox {
|
||||
self.edge_approx
|
||||
.insert((handle.id(), range), approx.clone())
|
||||
.insert((handle.id(), boundary), approx.clone())
|
||||
.unwrap_or(approx)
|
||||
}
|
||||
|
||||
@ -303,8 +302,8 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::{
|
||||
algorithms::approx::{path::RangeOnPath, Approx, ApproxPoint},
|
||||
geometry::{GlobalPath, SurfaceGeometry},
|
||||
algorithms::approx::{Approx, ApproxPoint},
|
||||
geometry::{BoundaryOnCurve, GlobalPath, SurfaceGeometry},
|
||||
objects::{HalfEdge, Surface},
|
||||
operations::BuildHalfEdge,
|
||||
services::Services,
|
||||
@ -346,7 +345,7 @@ mod tests {
|
||||
let mut services = Services::new();
|
||||
|
||||
let path = GlobalPath::circle_from_radius(1.);
|
||||
let range = RangeOnPath::from([[0.], [TAU]]);
|
||||
let boundary = BoundaryOnCurve::from([[0.], [TAU]]);
|
||||
|
||||
let surface = Surface::new(SurfaceGeometry {
|
||||
u: path,
|
||||
@ -354,14 +353,14 @@ mod tests {
|
||||
});
|
||||
let half_edge = HalfEdge::line_segment(
|
||||
[[0., 1.], [TAU, 1.]],
|
||||
Some(range.boundary),
|
||||
Some(boundary.inner),
|
||||
&mut services,
|
||||
);
|
||||
|
||||
let tolerance = 1.;
|
||||
let approx = (&half_edge, &surface).approx(tolerance);
|
||||
|
||||
let expected_approx = (path, range)
|
||||
let expected_approx = (path, boundary)
|
||||
.approx(tolerance)
|
||||
.into_iter()
|
||||
.map(|(point_local, _)| {
|
||||
@ -386,7 +385,7 @@ mod tests {
|
||||
let approx = (&half_edge, surface.deref()).approx(tolerance);
|
||||
|
||||
let expected_approx =
|
||||
(&half_edge.path(), RangeOnPath::from([[0.], [TAU]]))
|
||||
(&half_edge.path(), BoundaryOnCurve::from([[0.], [TAU]]))
|
||||
.approx(tolerance)
|
||||
.into_iter()
|
||||
.map(|(_, point_surface)| {
|
||||
|
@ -32,11 +32,11 @@ use std::iter;
|
||||
|
||||
use fj_math::{Circle, Point, Scalar, Sign};
|
||||
|
||||
use crate::geometry::{GlobalPath, SurfacePath};
|
||||
use crate::geometry::{BoundaryOnCurve, GlobalPath, SurfacePath};
|
||||
|
||||
use super::{Approx, Tolerance};
|
||||
|
||||
impl Approx for (&SurfacePath, RangeOnPath) {
|
||||
impl Approx for (&SurfacePath, BoundaryOnCurve) {
|
||||
type Approximation = Vec<(Point<1>, Point<2>)>;
|
||||
type Cache = ();
|
||||
|
||||
@ -56,7 +56,7 @@ impl Approx for (&SurfacePath, RangeOnPath) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Approx for (GlobalPath, RangeOnPath) {
|
||||
impl Approx for (GlobalPath, BoundaryOnCurve) {
|
||||
type Approximation = Vec<(Point<1>, Point<3>)>;
|
||||
type Cache = ();
|
||||
|
||||
@ -76,46 +76,21 @@ impl Approx for (GlobalPath, RangeOnPath) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The range on which a path should be approximated
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct RangeOnPath {
|
||||
/// The boundary of the range
|
||||
pub boundary: [Point<1>; 2],
|
||||
}
|
||||
|
||||
impl RangeOnPath {
|
||||
/// Reverse the direction of the range
|
||||
pub fn reverse(self) -> Self {
|
||||
let [a, b] = self.boundary;
|
||||
Self { boundary: [b, a] }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<[T; 2]> for RangeOnPath
|
||||
where
|
||||
T: Into<Point<1>>,
|
||||
{
|
||||
fn from(boundary: [T; 2]) -> Self {
|
||||
let boundary = boundary.map(Into::into);
|
||||
Self { boundary }
|
||||
}
|
||||
}
|
||||
|
||||
/// Approximate a circle
|
||||
///
|
||||
/// `tolerance` specifies how much the approximation is allowed to deviate
|
||||
/// from the circle.
|
||||
fn approx_circle<const D: usize>(
|
||||
circle: &Circle<D>,
|
||||
range: impl Into<RangeOnPath>,
|
||||
boundary: impl Into<BoundaryOnCurve>,
|
||||
tolerance: Tolerance,
|
||||
) -> Vec<(Point<1>, Point<D>)> {
|
||||
let range = range.into();
|
||||
let boundary = boundary.into();
|
||||
|
||||
let params = PathApproxParams::for_circle(circle, tolerance);
|
||||
let mut points = Vec::new();
|
||||
|
||||
for point_curve in params.points(range) {
|
||||
for point_curve in params.points(boundary) {
|
||||
let point_global = circle.point_from_circle_coords(point_curve);
|
||||
points.push((point_curve, point_global));
|
||||
}
|
||||
@ -152,11 +127,11 @@ impl PathApproxParams {
|
||||
|
||||
pub fn points(
|
||||
&self,
|
||||
range: impl Into<RangeOnPath>,
|
||||
boundary: impl Into<BoundaryOnCurve>,
|
||||
) -> impl Iterator<Item = Point<1>> + '_ {
|
||||
let range = range.into();
|
||||
let boundary = boundary.into();
|
||||
|
||||
let [a, b] = range.boundary.map(|point| point.t / self.increment());
|
||||
let [a, b] = boundary.inner.map(|point| point.t / self.increment());
|
||||
let direction = (b - a).sign();
|
||||
let [min, max] = if a < b { [a, b] } else { [b, a] };
|
||||
|
||||
@ -195,7 +170,7 @@ mod tests {
|
||||
|
||||
use fj_math::{Circle, Point, Scalar};
|
||||
|
||||
use crate::algorithms::approx::{path::RangeOnPath, Tolerance};
|
||||
use crate::algorithms::approx::{path::BoundaryOnCurve, Tolerance};
|
||||
|
||||
use super::PathApproxParams;
|
||||
|
||||
@ -245,7 +220,7 @@ mod tests {
|
||||
test_path([[TAU - 2.], [0.]], [2., 1.]);
|
||||
|
||||
fn test_path(
|
||||
range: impl Into<RangeOnPath>,
|
||||
boundary: impl Into<BoundaryOnCurve>,
|
||||
expected_coords: impl IntoIterator<Item = impl Into<Scalar>>,
|
||||
) {
|
||||
// Choose radius and tolerance such, that we need 4 vertices to
|
||||
@ -257,7 +232,7 @@ mod tests {
|
||||
let circle = Circle::from_center_and_radius([0., 0.], radius);
|
||||
let params = PathApproxParams::for_circle(&circle, tolerance);
|
||||
|
||||
let points = params.points(range).collect::<Vec<_>>();
|
||||
let points = params.points(boundary).collect::<Vec<_>>();
|
||||
|
||||
let expected_points = expected_coords
|
||||
.into_iter()
|
||||
|
@ -18,7 +18,7 @@ impl super::BoundingVolume<2> for HalfEdge {
|
||||
})
|
||||
}
|
||||
SurfacePath::Line(_) => {
|
||||
let points = self.boundary().map(|point_curve| {
|
||||
let points = self.boundary().inner.map(|point_curve| {
|
||||
self.path().point_from_path_coords(point_curve)
|
||||
});
|
||||
|
||||
|
@ -44,6 +44,7 @@ impl CurveEdgeIntersection {
|
||||
|
||||
let edge_vertices = half_edge
|
||||
.boundary()
|
||||
.inner
|
||||
.map(|point| edge_path_as_line.point_from_line_coords(point));
|
||||
|
||||
Segment::from_points(edge_vertices)
|
||||
|
@ -26,6 +26,7 @@ impl Intersect for (&HorizontalRayToTheRight<2>, &Handle<HalfEdge>) {
|
||||
|
||||
let points = edge
|
||||
.boundary()
|
||||
.inner
|
||||
.map(|point| line.point_from_line_coords(point));
|
||||
let segment = Segment::from_points(points);
|
||||
|
||||
|
@ -47,7 +47,7 @@ impl Sweep for (&HalfEdge, &Handle<Vertex>, &Surface, Option<Color>) {
|
||||
|
||||
// Let's figure out the surface coordinates of the edge vertices.
|
||||
let surface_points = {
|
||||
let [a, b] = edge.boundary();
|
||||
let [a, b] = edge.boundary().inner;
|
||||
|
||||
[
|
||||
[a.t, Scalar::ZERO],
|
||||
@ -65,7 +65,7 @@ impl Sweep for (&HalfEdge, &Handle<Vertex>, &Surface, Option<Color>) {
|
||||
|
||||
// Now, the boundaries of each edge.
|
||||
let boundaries = {
|
||||
let [a, b] = edge.boundary();
|
||||
let [a, b] = edge.boundary().inner;
|
||||
let [c, d] = [0., 1.].map(|coord| Point::from([coord]));
|
||||
|
||||
[[a, b], [c, d], [b, a], [d, c]]
|
||||
|
26
crates/fj-core/src/geometry/boundary.rs
Normal file
26
crates/fj-core/src/geometry/boundary.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use fj_math::Point;
|
||||
|
||||
/// A boundary on a curve
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct BoundaryOnCurve {
|
||||
/// The raw representation of the boundary
|
||||
pub inner: [Point<1>; 2],
|
||||
}
|
||||
|
||||
impl BoundaryOnCurve {
|
||||
/// Reverse the direction of the boundary
|
||||
pub fn reverse(self) -> Self {
|
||||
let [a, b] = self.inner;
|
||||
Self { inner: [b, a] }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<[T; 2]> for BoundaryOnCurve
|
||||
where
|
||||
T: Into<Point<1>>,
|
||||
{
|
||||
fn from(boundary: [T; 2]) -> Self {
|
||||
let inner = boundary.map(Into::into);
|
||||
Self { inner }
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
//! Types that are tied to objects, but aren't objects themselves
|
||||
|
||||
mod boundary;
|
||||
mod path;
|
||||
mod surface;
|
||||
|
||||
pub use self::{
|
||||
boundary::BoundaryOnCurve,
|
||||
path::{GlobalPath, SurfacePath},
|
||||
surface::SurfaceGeometry,
|
||||
};
|
||||
|
@ -82,7 +82,7 @@ impl Cycle {
|
||||
.next()
|
||||
.expect("Invalid cycle: expected at least one half-edge");
|
||||
|
||||
let [a, b] = first.boundary();
|
||||
let [a, b] = first.boundary().inner;
|
||||
let edge_direction_positive = a < b;
|
||||
|
||||
let circle = match first.path() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use fj_math::Point;
|
||||
|
||||
use crate::{
|
||||
geometry::SurfacePath,
|
||||
geometry::{BoundaryOnCurve, SurfacePath},
|
||||
objects::Vertex,
|
||||
storage::{Handle, HandleWrapper},
|
||||
};
|
||||
@ -41,7 +41,7 @@ use crate::{
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct HalfEdge {
|
||||
path: SurfacePath,
|
||||
boundary: [Point<1>; 2],
|
||||
boundary: BoundaryOnCurve,
|
||||
start_vertex: HandleWrapper<Vertex>,
|
||||
global_form: HandleWrapper<GlobalEdge>,
|
||||
}
|
||||
@ -50,13 +50,13 @@ impl HalfEdge {
|
||||
/// Create an instance of `HalfEdge`
|
||||
pub fn new(
|
||||
path: SurfacePath,
|
||||
boundary: [Point<1>; 2],
|
||||
boundary: impl Into<BoundaryOnCurve>,
|
||||
start_vertex: Handle<Vertex>,
|
||||
global_form: Handle<GlobalEdge>,
|
||||
) -> Self {
|
||||
Self {
|
||||
path,
|
||||
boundary,
|
||||
boundary: boundary.into(),
|
||||
start_vertex: start_vertex.into(),
|
||||
global_form: global_form.into(),
|
||||
}
|
||||
@ -68,7 +68,7 @@ impl HalfEdge {
|
||||
}
|
||||
|
||||
/// Access the boundary points of the half-edge on the curve
|
||||
pub fn boundary(&self) -> [Point<1>; 2] {
|
||||
pub fn boundary(&self) -> BoundaryOnCurve {
|
||||
self.boundary
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ impl HalfEdge {
|
||||
// `HalfEdge` "owns" its start position. There is no competing code that
|
||||
// could compute the surface position from slightly different data.
|
||||
|
||||
let [start, _] = self.boundary;
|
||||
let [start, _] = self.boundary.inner;
|
||||
self.path.point_from_path_coords(start)
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ use fj_interop::ext::ArrayExt;
|
||||
use fj_math::{Arc, Point, Scalar};
|
||||
|
||||
use crate::{
|
||||
geometry::SurfacePath,
|
||||
geometry::{BoundaryOnCurve, SurfacePath},
|
||||
objects::{GlobalEdge, HalfEdge, Vertex},
|
||||
operations::Insert,
|
||||
services::Services,
|
||||
@ -13,7 +13,7 @@ pub trait BuildHalfEdge {
|
||||
/// Create a half-edge that is not joined to another
|
||||
fn unjoined(
|
||||
path: SurfacePath,
|
||||
boundary: [Point<1>; 2],
|
||||
boundary: impl Into<BoundaryOnCurve>,
|
||||
services: &mut Services,
|
||||
) -> HalfEdge {
|
||||
let start_vertex = Vertex::new().insert(services);
|
||||
|
@ -1,10 +1,9 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use fj_math::Point;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{
|
||||
geometry::SurfacePath,
|
||||
geometry::{BoundaryOnCurve, SurfacePath},
|
||||
objects::{Cycle, HalfEdge},
|
||||
operations::{BuildHalfEdge, Insert, UpdateCycle, UpdateHalfEdge},
|
||||
services::Services,
|
||||
@ -17,7 +16,9 @@ pub trait JoinCycle {
|
||||
#[must_use]
|
||||
fn add_joined_edges<Es>(&self, edges: Es, services: &mut Services) -> Self
|
||||
where
|
||||
Es: IntoIterator<Item = (Handle<HalfEdge>, SurfacePath, [Point<1>; 2])>,
|
||||
Es: IntoIterator<
|
||||
Item = (Handle<HalfEdge>, SurfacePath, BoundaryOnCurve),
|
||||
>,
|
||||
Es::IntoIter: Clone + ExactSizeIterator;
|
||||
|
||||
/// Join the cycle to another
|
||||
@ -62,7 +63,9 @@ pub trait JoinCycle {
|
||||
impl JoinCycle for Cycle {
|
||||
fn add_joined_edges<Es>(&self, edges: Es, services: &mut Services) -> Self
|
||||
where
|
||||
Es: IntoIterator<Item = (Handle<HalfEdge>, SurfacePath, [Point<1>; 2])>,
|
||||
Es: IntoIterator<
|
||||
Item = (Handle<HalfEdge>, SurfacePath, BoundaryOnCurve),
|
||||
>,
|
||||
Es::IntoIter: Clone + ExactSizeIterator,
|
||||
{
|
||||
self.add_half_edges(edges.into_iter().circular_tuple_windows().map(
|
||||
|
@ -11,14 +11,9 @@ impl Reverse for Cycle {
|
||||
let mut edges = self
|
||||
.half_edge_pairs()
|
||||
.map(|(current, next)| {
|
||||
let boundary = {
|
||||
let [a, b] = current.boundary();
|
||||
[b, a]
|
||||
};
|
||||
|
||||
HalfEdge::new(
|
||||
current.path(),
|
||||
boundary,
|
||||
current.boundary().reverse(),
|
||||
next.start_vertex().clone(),
|
||||
current.global_form().clone(),
|
||||
)
|
||||
|
@ -65,7 +65,7 @@ impl CycleValidationError {
|
||||
) {
|
||||
for (first, second) in cycle.half_edge_pairs() {
|
||||
let end_of_first = {
|
||||
let [_, end] = first.boundary();
|
||||
let [_, end] = first.boundary().inner;
|
||||
first.path().point_from_path_coords(end)
|
||||
};
|
||||
let start_of_second = second.start_position();
|
||||
|
@ -54,7 +54,7 @@ impl HalfEdgeValidationError {
|
||||
config: &ValidationConfig,
|
||||
errors: &mut Vec<ValidationError>,
|
||||
) {
|
||||
let [back_position, front_position] = half_edge.boundary();
|
||||
let [back_position, front_position] = half_edge.boundary().inner;
|
||||
let distance = (back_position - front_position).magnitude();
|
||||
|
||||
if distance < config.distinct_min_distance {
|
||||
|
@ -73,8 +73,8 @@ fn distances(
|
||||
percent: f64,
|
||||
(edge, surface): (&Handle<HalfEdge>, SurfaceGeometry),
|
||||
) -> Point<3> {
|
||||
let boundary = edge.boundary();
|
||||
let path_coords = boundary[0] + (boundary[1] - boundary[0]) * percent;
|
||||
let [start, end] = edge.boundary().inner;
|
||||
let path_coords = start + (end - start) * percent;
|
||||
let surface_coords = edge.path().point_from_path_coords(path_coords);
|
||||
surface.point_from_surface_coords(surface_coords)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user