mirror of
https://github.com/hannobraun/Fornjot
synced 2025-01-27 10:29:28 +00:00
Merge pull request #1610 from hannobraun/curve
Merge `GlobalCurve` into `GlobalEdge`
This commit is contained in:
commit
c757811554
@ -1,332 +0,0 @@
|
||||
//! Curve approximation
|
||||
//!
|
||||
//! Since curves are infinite (even circles have an infinite coordinate space,
|
||||
//! even though they connect to themselves in global coordinates), a range must
|
||||
//! be provided to approximate them. The approximation then returns points
|
||||
//! within that range.
|
||||
//!
|
||||
//! The boundaries of the range are not included in the approximation. This is
|
||||
//! done, to give the caller (who knows the boundary anyway) more options on how
|
||||
//! to further process the approximation.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{
|
||||
geometry::path::{GlobalPath, SurfacePath},
|
||||
objects::{Curve, GlobalCurve, Surface},
|
||||
storage::{Handle, ObjectId},
|
||||
};
|
||||
|
||||
use super::{path::RangeOnPath, Approx, ApproxPoint, Tolerance};
|
||||
|
||||
impl Approx for (&Handle<Curve>, &Surface, Handle<GlobalCurve>, RangeOnPath) {
|
||||
type Approximation = CurveApprox;
|
||||
type Cache = CurveCache;
|
||||
|
||||
fn approx_with_cache(
|
||||
self,
|
||||
tolerance: impl Into<Tolerance>,
|
||||
cache: &mut Self::Cache,
|
||||
) -> Self::Approximation {
|
||||
let (curve, surface, global_curve, range) = self;
|
||||
|
||||
let global_curve_approx = match cache.get(global_curve.clone(), range) {
|
||||
Some(approx) => approx,
|
||||
None => {
|
||||
let approx =
|
||||
approx_global_curve(curve, surface, range, tolerance);
|
||||
cache.insert(global_curve, range, approx)
|
||||
}
|
||||
};
|
||||
|
||||
CurveApprox::empty().with_points(
|
||||
global_curve_approx.points.into_iter().map(|point| {
|
||||
let point_surface =
|
||||
curve.path().point_from_path_coords(point.local_form);
|
||||
|
||||
ApproxPoint::new(point_surface, point.global_form)
|
||||
.with_source((curve.clone(), point.local_form))
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn approx_global_curve(
|
||||
curve: &Curve,
|
||||
surface: &Surface,
|
||||
range: RangeOnPath,
|
||||
tolerance: impl Into<Tolerance>,
|
||||
) -> GlobalCurveApprox {
|
||||
// There are different cases of varying complexity. Circles are the hard
|
||||
// part here, as they need to be approximated, while lines don't need to be.
|
||||
//
|
||||
// This will probably all be unified eventually, as `SurfacePath` and
|
||||
// `GlobalPath` grow APIs that are better suited to implementing this code
|
||||
// in a more abstract way.
|
||||
let points = match (curve.path(), surface.geometry().u) {
|
||||
(SurfacePath::Circle(_), GlobalPath::Circle(_)) => {
|
||||
todo!(
|
||||
"Approximating a circle on a curved surface not supported yet."
|
||||
)
|
||||
}
|
||||
(SurfacePath::Circle(_), GlobalPath::Line(_)) => {
|
||||
(curve.path(), range)
|
||||
.approx_with_cache(tolerance, &mut ())
|
||||
.into_iter()
|
||||
.map(|(point_curve, point_surface)| {
|
||||
// We're throwing away `point_surface` here, which is a bit
|
||||
// weird, as we're recomputing it later (outside of this
|
||||
// function).
|
||||
//
|
||||
// It should be fine though:
|
||||
//
|
||||
// 1. We're throwing this version away, so there's no danger
|
||||
// of inconsistency between this and the later version.
|
||||
// 2. This version should have been computed using the same
|
||||
// path and parameters and the later version will be, so
|
||||
// they should be the same anyway.
|
||||
// 3. Not all other cases handled in this function have a
|
||||
// surface point available, so it needs to be computed
|
||||
// later anyway, in the general case.
|
||||
|
||||
let point_global = surface
|
||||
.geometry()
|
||||
.point_from_surface_coords(point_surface);
|
||||
(point_curve, point_global)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
(SurfacePath::Line(line), _) => {
|
||||
let range_u =
|
||||
RangeOnPath::from(range.boundary.map(|point_curve| {
|
||||
[curve.path().point_from_path_coords(point_curve).u]
|
||||
}));
|
||||
|
||||
let approx_u = (surface.geometry().u, range_u)
|
||||
.approx_with_cache(tolerance, &mut ());
|
||||
|
||||
let mut points = Vec::new();
|
||||
for (u, _) in approx_u {
|
||||
let t = (u.t - line.origin().u) / line.direction().u;
|
||||
let point_surface = curve.path().point_from_path_coords([t]);
|
||||
let point_global =
|
||||
surface.geometry().point_from_surface_coords(point_surface);
|
||||
points.push((u, point_global));
|
||||
}
|
||||
|
||||
points
|
||||
}
|
||||
};
|
||||
|
||||
let points = points
|
||||
.into_iter()
|
||||
.map(|(point_curve, point_global)| {
|
||||
ApproxPoint::new(point_curve, point_global)
|
||||
})
|
||||
.collect();
|
||||
GlobalCurveApprox { points }
|
||||
}
|
||||
|
||||
/// An approximation of a [`Curve`]
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct CurveApprox {
|
||||
/// The points that approximate the curve
|
||||
pub points: Vec<ApproxPoint<2>>,
|
||||
}
|
||||
|
||||
impl CurveApprox {
|
||||
/// Create an empty instance of `CurveApprox`
|
||||
pub fn empty() -> Self {
|
||||
Self { points: Vec::new() }
|
||||
}
|
||||
|
||||
/// Add points to the approximation
|
||||
pub fn with_points(
|
||||
mut self,
|
||||
points: impl IntoIterator<Item = ApproxPoint<2>>,
|
||||
) -> Self {
|
||||
self.points.extend(points);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A cache for results of an approximation
|
||||
#[derive(Default)]
|
||||
pub struct CurveCache {
|
||||
inner: BTreeMap<(ObjectId, RangeOnPath), GlobalCurveApprox>,
|
||||
}
|
||||
|
||||
impl CurveCache {
|
||||
/// Create an empty cache
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Insert the approximation of a [`GlobalCurve`]
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
handle: Handle<GlobalCurve>,
|
||||
range: RangeOnPath,
|
||||
approx: GlobalCurveApprox,
|
||||
) -> GlobalCurveApprox {
|
||||
self.inner.insert((handle.id(), range), approx.clone());
|
||||
approx
|
||||
}
|
||||
|
||||
/// Access the approximation for the given [`GlobalCurve`], if available
|
||||
pub fn get(
|
||||
&self,
|
||||
handle: Handle<GlobalCurve>,
|
||||
range: RangeOnPath,
|
||||
) -> Option<GlobalCurveApprox> {
|
||||
if let Some(approx) = self.inner.get(&(handle.id(), range)) {
|
||||
return Some(approx.clone());
|
||||
}
|
||||
if let Some(approx) = self.inner.get(&(handle.id(), range.reverse())) {
|
||||
// If we have a cache entry for the reverse range, we need to use
|
||||
// that too!
|
||||
return Some(approx.clone().reverse());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// An approximation of a [`GlobalCurve`]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct GlobalCurveApprox {
|
||||
/// The points that approximate the curve
|
||||
pub points: Vec<ApproxPoint<1>>,
|
||||
}
|
||||
|
||||
impl GlobalCurveApprox {
|
||||
/// Reverse the order of the approximation
|
||||
pub fn reverse(mut self) -> Self {
|
||||
self.points.reverse();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{f64::consts::TAU, ops::Deref};
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::{
|
||||
algorithms::approx::{path::RangeOnPath, Approx, ApproxPoint},
|
||||
builder::{CurveBuilder, SurfaceBuilder},
|
||||
geometry::path::GlobalPath,
|
||||
insert::Insert,
|
||||
objects::GlobalCurve,
|
||||
partial::{PartialCurve, PartialObject, PartialSurface},
|
||||
services::Services,
|
||||
};
|
||||
|
||||
use super::CurveApprox;
|
||||
|
||||
#[test]
|
||||
fn approx_line_on_flat_surface() {
|
||||
let mut services = Services::new();
|
||||
|
||||
let surface = services.objects.surfaces.xz_plane();
|
||||
let mut curve = PartialCurve::default();
|
||||
curve.update_as_line_from_points([[1., 1.], [2., 1.]]);
|
||||
let curve = curve
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects);
|
||||
let global_curve = GlobalCurve.insert(&mut services.objects);
|
||||
let range = RangeOnPath::from([[0.], [1.]]);
|
||||
|
||||
let approx = (&curve, surface.deref(), global_curve, range).approx(1.);
|
||||
|
||||
assert_eq!(approx, CurveApprox::empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_line_on_curved_surface_but_not_along_curve() {
|
||||
let mut services = Services::new();
|
||||
|
||||
let surface = PartialSurface::from_axes(
|
||||
GlobalPath::circle_from_radius(1.),
|
||||
[0., 0., 1.],
|
||||
)
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects);
|
||||
let mut curve = PartialCurve::default();
|
||||
curve.update_as_line_from_points([[1., 1.], [1., 2.]]);
|
||||
let curve = curve
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects);
|
||||
let global_curve = GlobalCurve.insert(&mut services.objects);
|
||||
let range = RangeOnPath::from([[0.], [1.]]);
|
||||
|
||||
let approx = (&curve, surface.deref(), global_curve, range).approx(1.);
|
||||
|
||||
assert_eq!(approx, CurveApprox::empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_line_on_curved_surface_along_curve() {
|
||||
let mut services = Services::new();
|
||||
|
||||
let path = GlobalPath::circle_from_radius(1.);
|
||||
let surface = PartialSurface::from_axes(path, [0., 0., 1.])
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects);
|
||||
let mut curve = PartialCurve::default();
|
||||
curve.update_as_line_from_points([[0., 1.], [1., 1.]]);
|
||||
let curve = curve
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects);
|
||||
let global_curve = GlobalCurve.insert(&mut services.objects);
|
||||
|
||||
let range = RangeOnPath::from([[0.], [TAU]]);
|
||||
let tolerance = 1.;
|
||||
|
||||
let approx =
|
||||
(&curve, surface.deref(), global_curve, range).approx(tolerance);
|
||||
|
||||
let expected_approx = (path, range)
|
||||
.approx(tolerance)
|
||||
.into_iter()
|
||||
.map(|(point_local, _)| {
|
||||
let point_surface =
|
||||
curve.path().point_from_path_coords(point_local);
|
||||
let point_global =
|
||||
surface.geometry().point_from_surface_coords(point_surface);
|
||||
ApproxPoint::new(point_surface, point_global)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(approx.points, expected_approx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_circle_on_flat_surface() {
|
||||
let mut services = Services::new();
|
||||
|
||||
let surface = services.objects.surfaces.xz_plane();
|
||||
let mut curve = PartialCurve::default();
|
||||
curve.update_as_circle_from_radius(1.);
|
||||
let curve = curve
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects);
|
||||
let global_curve = GlobalCurve.insert(&mut services.objects);
|
||||
|
||||
let range = RangeOnPath::from([[0.], [TAU]]);
|
||||
let tolerance = 1.;
|
||||
let approx =
|
||||
(&curve, surface.deref(), global_curve, range).approx(tolerance);
|
||||
|
||||
let expected_approx = (curve.path(), range)
|
||||
.approx(tolerance)
|
||||
.into_iter()
|
||||
.map(|(_, point_surface)| {
|
||||
let point_global =
|
||||
surface.geometry().point_from_surface_coords(point_surface);
|
||||
ApproxPoint::new(point_surface, point_global)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(approx.points, expected_approx);
|
||||
}
|
||||
}
|
@ -7,12 +7,13 @@ use fj_math::Segment;
|
||||
use crate::objects::{Cycle, Surface};
|
||||
|
||||
use super::{
|
||||
curve::CurveCache, edge::HalfEdgeApprox, Approx, ApproxPoint, Tolerance,
|
||||
edge::{EdgeCache, HalfEdgeApprox},
|
||||
Approx, ApproxPoint, Tolerance,
|
||||
};
|
||||
|
||||
impl Approx for (&Cycle, &Surface) {
|
||||
type Approximation = CycleApprox;
|
||||
type Cache = CurveCache;
|
||||
type Cache = EdgeCache;
|
||||
|
||||
fn approx_with_cache(
|
||||
self,
|
||||
|
@ -5,20 +5,19 @@
|
||||
//! approximations are usually used to build cycle approximations, and this way,
|
||||
//! the caller doesn't have to call with duplicate vertices.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{
|
||||
objects::{HalfEdge, Surface},
|
||||
storage::Handle,
|
||||
geometry::path::{GlobalPath, SurfacePath},
|
||||
objects::{Curve, GlobalEdge, HalfEdge, Surface},
|
||||
storage::{Handle, ObjectId},
|
||||
};
|
||||
|
||||
use super::{
|
||||
curve::{CurveApprox, CurveCache},
|
||||
path::RangeOnPath,
|
||||
Approx, ApproxPoint, Tolerance,
|
||||
};
|
||||
use super::{path::RangeOnPath, Approx, ApproxPoint, Tolerance};
|
||||
|
||||
impl Approx for (&Handle<HalfEdge>, &Surface) {
|
||||
type Approximation = HalfEdgeApprox;
|
||||
type Cache = CurveCache;
|
||||
type Cache = EdgeCache;
|
||||
|
||||
fn approx_with_cache(
|
||||
self,
|
||||
@ -35,29 +34,52 @@ impl Approx for (&Handle<HalfEdge>, &Surface) {
|
||||
half_edge.start_vertex().global_form().position(),
|
||||
)
|
||||
.with_source((half_edge.clone(), half_edge.boundary()[0]));
|
||||
let curve_approx = (
|
||||
half_edge.curve(),
|
||||
surface,
|
||||
half_edge.global_form().curve().clone(),
|
||||
range,
|
||||
)
|
||||
.approx_with_cache(tolerance, cache);
|
||||
|
||||
HalfEdgeApprox {
|
||||
first,
|
||||
curve_approx,
|
||||
}
|
||||
let points = {
|
||||
let approx = match cache.get(half_edge.global_form().clone(), range)
|
||||
{
|
||||
Some(approx) => approx,
|
||||
None => {
|
||||
let approx = approx_edge(
|
||||
half_edge.curve(),
|
||||
surface,
|
||||
range,
|
||||
tolerance,
|
||||
);
|
||||
cache.insert(half_edge.global_form().clone(), range, approx)
|
||||
}
|
||||
};
|
||||
|
||||
approx
|
||||
.points
|
||||
.into_iter()
|
||||
.map(|point| {
|
||||
let point_surface = half_edge
|
||||
.curve()
|
||||
.path()
|
||||
.point_from_path_coords(point.local_form);
|
||||
|
||||
ApproxPoint::new(point_surface, point.global_form)
|
||||
.with_source((
|
||||
half_edge.curve().clone(),
|
||||
point.local_form,
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
HalfEdgeApprox { first, points }
|
||||
}
|
||||
}
|
||||
|
||||
/// An approximation of an [`HalfEdge`]
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct HalfEdgeApprox {
|
||||
/// The point that approximates the first vertex of the curve
|
||||
/// The point that approximates the first vertex of the edge
|
||||
pub first: ApproxPoint<2>,
|
||||
|
||||
/// The approximation of the edge's curve
|
||||
pub curve_approx: CurveApprox,
|
||||
/// The approximation of the edge
|
||||
pub points: Vec<ApproxPoint<2>>,
|
||||
}
|
||||
|
||||
impl HalfEdgeApprox {
|
||||
@ -66,8 +88,283 @@ impl HalfEdgeApprox {
|
||||
let mut points = Vec::new();
|
||||
|
||||
points.push(self.first.clone());
|
||||
points.extend(self.curve_approx.points.clone());
|
||||
points.extend(self.points.iter().cloned());
|
||||
|
||||
points
|
||||
}
|
||||
}
|
||||
|
||||
fn approx_edge(
|
||||
curve: &Curve,
|
||||
surface: &Surface,
|
||||
range: RangeOnPath,
|
||||
tolerance: impl Into<Tolerance>,
|
||||
) -> GlobalEdgeApprox {
|
||||
// There are different cases of varying complexity. Circles are the hard
|
||||
// part here, as they need to be approximated, while lines don't need to be.
|
||||
//
|
||||
// This will probably all be unified eventually, as `SurfacePath` and
|
||||
// `GlobalPath` grow APIs that are better suited to implementing this code
|
||||
// in a more abstract way.
|
||||
let points = match (curve.path(), surface.geometry().u) {
|
||||
(SurfacePath::Circle(_), GlobalPath::Circle(_)) => {
|
||||
todo!(
|
||||
"Approximating a circle on a curved surface not supported yet."
|
||||
)
|
||||
}
|
||||
(SurfacePath::Circle(_), GlobalPath::Line(_)) => {
|
||||
(curve.path(), range)
|
||||
.approx_with_cache(tolerance, &mut ())
|
||||
.into_iter()
|
||||
.map(|(point_curve, point_surface)| {
|
||||
// We're throwing away `point_surface` here, which is a bit
|
||||
// weird, as we're recomputing it later (outside of this
|
||||
// function).
|
||||
//
|
||||
// It should be fine though:
|
||||
//
|
||||
// 1. We're throwing this version away, so there's no danger
|
||||
// of inconsistency between this and the later version.
|
||||
// 2. This version should have been computed using the same
|
||||
// path and parameters and the later version will be, so
|
||||
// they should be the same anyway.
|
||||
// 3. Not all other cases handled in this function have a
|
||||
// surface point available, so it needs to be computed
|
||||
// later anyway, in the general case.
|
||||
|
||||
let point_global = surface
|
||||
.geometry()
|
||||
.point_from_surface_coords(point_surface);
|
||||
(point_curve, point_global)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
(SurfacePath::Line(line), _) => {
|
||||
let range_u =
|
||||
RangeOnPath::from(range.boundary.map(|point_curve| {
|
||||
[curve.path().point_from_path_coords(point_curve).u]
|
||||
}));
|
||||
|
||||
let approx_u = (surface.geometry().u, range_u)
|
||||
.approx_with_cache(tolerance, &mut ());
|
||||
|
||||
let mut points = Vec::new();
|
||||
for (u, _) in approx_u {
|
||||
let t = (u.t - line.origin().u) / line.direction().u;
|
||||
let point_surface = curve.path().point_from_path_coords([t]);
|
||||
let point_global =
|
||||
surface.geometry().point_from_surface_coords(point_surface);
|
||||
points.push((u, point_global));
|
||||
}
|
||||
|
||||
points
|
||||
}
|
||||
};
|
||||
|
||||
let points = points
|
||||
.into_iter()
|
||||
.map(|(point_curve, point_global)| {
|
||||
ApproxPoint::new(point_curve, point_global)
|
||||
})
|
||||
.collect();
|
||||
GlobalEdgeApprox { points }
|
||||
}
|
||||
|
||||
/// A cache for results of an approximation
|
||||
#[derive(Default)]
|
||||
pub struct EdgeCache {
|
||||
inner: BTreeMap<(ObjectId, RangeOnPath), GlobalEdgeApprox>,
|
||||
}
|
||||
|
||||
impl EdgeCache {
|
||||
/// Create an empty cache
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Insert the approximation of a [`GlobalEdge`]
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
handle: Handle<GlobalEdge>,
|
||||
range: RangeOnPath,
|
||||
approx: GlobalEdgeApprox,
|
||||
) -> GlobalEdgeApprox {
|
||||
self.inner.insert((handle.id(), range), approx.clone());
|
||||
approx
|
||||
}
|
||||
|
||||
/// Access the approximation for the given [`GlobalEdge`], if available
|
||||
pub fn get(
|
||||
&self,
|
||||
handle: Handle<GlobalEdge>,
|
||||
range: RangeOnPath,
|
||||
) -> Option<GlobalEdgeApprox> {
|
||||
if let Some(approx) = self.inner.get(&(handle.id(), range)) {
|
||||
return Some(approx.clone());
|
||||
}
|
||||
if let Some(approx) = self.inner.get(&(handle.id(), range.reverse())) {
|
||||
// If we have a cache entry for the reverse range, we need to use
|
||||
// that too!
|
||||
return Some(approx.clone().reverse());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// An approximation of a [`GlobalEdge`]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct GlobalEdgeApprox {
|
||||
/// The points that approximate the edge
|
||||
pub points: Vec<ApproxPoint<1>>,
|
||||
}
|
||||
|
||||
impl GlobalEdgeApprox {
|
||||
/// Reverse the order of the approximation
|
||||
pub fn reverse(mut self) -> Self {
|
||||
self.points.reverse();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{f64::consts::TAU, ops::Deref};
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::{
|
||||
algorithms::approx::{path::RangeOnPath, Approx, ApproxPoint},
|
||||
builder::{HalfEdgeBuilder, SurfaceBuilder},
|
||||
geometry::path::GlobalPath,
|
||||
insert::Insert,
|
||||
partial::{PartialHalfEdge, PartialObject, PartialSurface},
|
||||
services::Services,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn approx_line_on_flat_surface() {
|
||||
let mut services = Services::new();
|
||||
|
||||
let surface = services.objects.surfaces.xz_plane();
|
||||
let half_edge = {
|
||||
let mut half_edge = PartialHalfEdge::default();
|
||||
|
||||
half_edge.update_as_line_segment_from_points([[1., 1.], [2., 1.]]);
|
||||
half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
|
||||
|
||||
half_edge
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects)
|
||||
};
|
||||
|
||||
let tolerance = 1.;
|
||||
let approx = (&half_edge, surface.deref()).approx(tolerance);
|
||||
|
||||
assert_eq!(approx.points, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_line_on_curved_surface_but_not_along_curve() {
|
||||
let mut services = Services::new();
|
||||
|
||||
let surface = PartialSurface::from_axes(
|
||||
GlobalPath::circle_from_radius(1.),
|
||||
[0., 0., 1.],
|
||||
)
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects);
|
||||
let half_edge = {
|
||||
let mut half_edge = PartialHalfEdge::default();
|
||||
|
||||
half_edge.update_as_line_segment_from_points([[1., 1.], [2., 1.]]);
|
||||
half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
|
||||
|
||||
half_edge
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects)
|
||||
};
|
||||
|
||||
let tolerance = 1.;
|
||||
let approx = (&half_edge, surface.deref()).approx(tolerance);
|
||||
|
||||
assert_eq!(approx.points, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_line_on_curved_surface_along_curve() {
|
||||
let mut services = Services::new();
|
||||
|
||||
let path = GlobalPath::circle_from_radius(1.);
|
||||
let range = RangeOnPath::from([[0.], [TAU]]);
|
||||
|
||||
let surface = PartialSurface::from_axes(path, [0., 0., 1.])
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects);
|
||||
let half_edge = {
|
||||
let mut half_edge = PartialHalfEdge::default();
|
||||
|
||||
half_edge.update_as_line_segment_from_points([[0., 1.], [1., 1.]]);
|
||||
|
||||
half_edge.vertices[0].0 = Some(range.boundary[0]);
|
||||
half_edge.vertices[1].0 = Some(range.boundary[1]);
|
||||
|
||||
half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
|
||||
|
||||
half_edge
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects)
|
||||
};
|
||||
|
||||
let tolerance = 1.;
|
||||
let approx = (&half_edge, surface.deref()).approx(tolerance);
|
||||
|
||||
let expected_approx = (path, range)
|
||||
.approx(tolerance)
|
||||
.into_iter()
|
||||
.map(|(point_local, _)| {
|
||||
let point_surface = half_edge
|
||||
.curve()
|
||||
.path()
|
||||
.point_from_path_coords(point_local);
|
||||
let point_global =
|
||||
surface.geometry().point_from_surface_coords(point_surface);
|
||||
ApproxPoint::new(point_surface, point_global)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(approx.points, expected_approx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_circle_on_flat_surface() {
|
||||
let mut services = Services::new();
|
||||
|
||||
let surface = services.objects.surfaces.xz_plane();
|
||||
let half_edge = {
|
||||
let mut half_edge = PartialHalfEdge::default();
|
||||
|
||||
half_edge.update_as_circle_from_radius(1.);
|
||||
half_edge.infer_vertex_positions_if_necessary(&surface.geometry());
|
||||
|
||||
half_edge
|
||||
.build(&mut services.objects)
|
||||
.insert(&mut services.objects)
|
||||
};
|
||||
|
||||
let tolerance = 1.;
|
||||
let approx = (&half_edge, surface.deref()).approx(tolerance);
|
||||
|
||||
let expected_approx =
|
||||
(half_edge.curve().path(), RangeOnPath::from([[0.], [TAU]]))
|
||||
.approx(tolerance)
|
||||
.into_iter()
|
||||
.map(|(_, point_surface)| {
|
||||
let point_global = surface
|
||||
.geometry()
|
||||
.point_from_surface_coords(point_surface);
|
||||
ApproxPoint::new(point_surface, point_global)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(approx.points, expected_approx);
|
||||
}
|
||||
}
|
||||
|
@ -12,12 +12,12 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
curve::CurveCache, cycle::CycleApprox, Approx, ApproxPoint, Tolerance,
|
||||
cycle::CycleApprox, edge::EdgeCache, Approx, ApproxPoint, Tolerance,
|
||||
};
|
||||
|
||||
impl Approx for &FaceSet {
|
||||
type Approximation = BTreeSet<FaceApprox>;
|
||||
type Cache = CurveCache;
|
||||
type Cache = EdgeCache;
|
||||
|
||||
fn approx_with_cache(
|
||||
self,
|
||||
@ -65,7 +65,7 @@ impl Approx for &FaceSet {
|
||||
|
||||
impl Approx for &Face {
|
||||
type Approximation = FaceApprox;
|
||||
type Cache = CurveCache;
|
||||
type Cache = EdgeCache;
|
||||
|
||||
fn approx_with_cache(
|
||||
self,
|
||||
|
@ -1,6 +1,5 @@
|
||||
//! Approximation of objects
|
||||
|
||||
pub mod curve;
|
||||
pub mod cycle;
|
||||
pub mod edge;
|
||||
pub mod face;
|
||||
|
@ -4,11 +4,11 @@ use std::collections::BTreeSet;
|
||||
|
||||
use crate::objects::Shell;
|
||||
|
||||
use super::{curve::CurveCache, face::FaceApprox, Approx, Tolerance};
|
||||
use super::{edge::EdgeCache, face::FaceApprox, Approx, Tolerance};
|
||||
|
||||
impl Approx for &Shell {
|
||||
type Approximation = BTreeSet<FaceApprox>;
|
||||
type Cache = CurveCache;
|
||||
type Cache = EdgeCache;
|
||||
|
||||
fn approx_with_cache(
|
||||
self,
|
||||
|
@ -4,11 +4,11 @@ use std::collections::BTreeSet;
|
||||
|
||||
use crate::objects::Sketch;
|
||||
|
||||
use super::{curve::CurveCache, face::FaceApprox, Approx, Tolerance};
|
||||
use super::{edge::EdgeCache, face::FaceApprox, Approx, Tolerance};
|
||||
|
||||
impl Approx for &Sketch {
|
||||
type Approximation = BTreeSet<FaceApprox>;
|
||||
type Cache = CurveCache;
|
||||
type Cache = EdgeCache;
|
||||
|
||||
fn approx_with_cache(
|
||||
self,
|
||||
|
@ -4,11 +4,11 @@ use std::collections::BTreeSet;
|
||||
|
||||
use crate::objects::Solid;
|
||||
|
||||
use super::{curve::CurveCache, face::FaceApprox, Approx, Tolerance};
|
||||
use super::{edge::EdgeCache, face::FaceApprox, Approx, Tolerance};
|
||||
|
||||
impl Approx for &Solid {
|
||||
type Approximation = BTreeSet<FaceApprox>;
|
||||
type Cache = CurveCache;
|
||||
type Cache = EdgeCache;
|
||||
|
||||
fn approx_with_cache(
|
||||
self,
|
||||
|
@ -2,7 +2,7 @@ use fj_math::Vector;
|
||||
|
||||
use crate::{
|
||||
insert::Insert,
|
||||
objects::{GlobalCurve, GlobalEdge, GlobalVertex, Objects},
|
||||
objects::{GlobalEdge, GlobalVertex, Objects},
|
||||
services::Service,
|
||||
storage::Handle,
|
||||
};
|
||||
@ -18,8 +18,6 @@ impl Sweep for Handle<GlobalVertex> {
|
||||
cache: &mut SweepCache,
|
||||
objects: &mut Service<Objects>,
|
||||
) -> Self::Swept {
|
||||
let curve = GlobalCurve.insert(objects);
|
||||
|
||||
let a = self.clone();
|
||||
let b = cache
|
||||
.global_vertex
|
||||
@ -30,8 +28,7 @@ impl Sweep for Handle<GlobalVertex> {
|
||||
.clone();
|
||||
|
||||
let vertices = [a, b];
|
||||
let global_edge =
|
||||
GlobalEdge::new(curve, vertices.clone()).insert(objects);
|
||||
let global_edge = GlobalEdge::new(vertices.clone()).insert(objects);
|
||||
|
||||
// The vertices of the returned `GlobalEdge` are in normalized order,
|
||||
// which means the order can't be relied upon by the caller. Return the
|
||||
|
@ -1,7 +1,7 @@
|
||||
use fj_math::Transform;
|
||||
|
||||
use crate::{
|
||||
objects::{Curve, GlobalCurve, Objects},
|
||||
objects::{Curve, Objects},
|
||||
services::Service,
|
||||
};
|
||||
|
||||
@ -21,18 +21,3 @@ impl TransformObject for Curve {
|
||||
Self::new(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl TransformObject for GlobalCurve {
|
||||
fn transform_with_cache(
|
||||
self,
|
||||
_: &Transform,
|
||||
_: &mut Service<Objects>,
|
||||
_: &mut TransformCache,
|
||||
) -> Self {
|
||||
// `GlobalCurve` doesn't contain any internal geometry. If it did, that
|
||||
// would just be redundant with the geometry of other objects, and this
|
||||
// other geometry is already being transformed by other implementations
|
||||
// of this trait.
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -43,15 +43,11 @@ impl TransformObject for GlobalEdge {
|
||||
objects: &mut Service<Objects>,
|
||||
cache: &mut TransformCache,
|
||||
) -> Self {
|
||||
let curve = self
|
||||
.curve()
|
||||
.clone()
|
||||
.transform_with_cache(transform, objects, cache);
|
||||
let vertices =
|
||||
self.vertices().access_in_normalized_order().map(|vertex| {
|
||||
vertex.transform_with_cache(transform, objects, cache)
|
||||
});
|
||||
|
||||
Self::new(curve, vertices)
|
||||
Self::new(vertices)
|
||||
}
|
||||
}
|
||||
|
@ -223,9 +223,6 @@ impl HalfEdgeBuilder for PartialHalfEdge {
|
||||
other: &Partial<HalfEdge>,
|
||||
surface: &SurfaceGeometry,
|
||||
) {
|
||||
let global_curve = other.read().global_form.read().curve.clone();
|
||||
self.global_form.write().curve = global_curve;
|
||||
|
||||
self.curve.write().path =
|
||||
other.read().curve.read().path.as_ref().and_then(|path| {
|
||||
// We have information about the other edge's surface available.
|
||||
|
@ -4,12 +4,9 @@
|
||||
//!
|
||||
//! # Implementation Note
|
||||
//!
|
||||
//! This is a bit of an in-between module. It is closely associated with curves
|
||||
//! ([`Curve`]/[`GlobalCurve`]) and [`Surface`]s, but paths are not really
|
||||
//! objects themselves, as logically speaking, they are owned and not referenced
|
||||
//! (practically speaking, all objects are owned and not referenced, but that is
|
||||
//! an implementation detail; see [#1021] for context on where things are
|
||||
//! going).
|
||||
//! This is a bit of an in-between module. It is closely associated with
|
||||
//! [`Curve`] and [`Surface`]s, but paths are not really objects themselves, as
|
||||
//! logically speaking, they are owned and not referenced.
|
||||
//!
|
||||
//! On the other hand, the types in this module don't follow the general style
|
||||
//! of types in `fj-math`.
|
||||
@ -18,9 +15,7 @@
|
||||
//! move to `fj-math`, maybe something else entirely will happen.
|
||||
//!
|
||||
//! [`Curve`]: crate::objects::Curve
|
||||
//! [`GlobalCurve`]: crate::objects::GlobalCurve
|
||||
//! [`Surface`]: crate::objects::Surface
|
||||
//! [#1021]: https://github.com/hannobraun/Fornjot/issues/1021
|
||||
|
||||
use fj_math::{Circle, Line, Point, Scalar, Transform, Vector};
|
||||
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
use crate::{
|
||||
objects::{
|
||||
Curve, Cycle, Face, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge,
|
||||
Objects, Shell, Sketch, Solid, Surface, SurfaceVertex,
|
||||
Curve, Cycle, Face, GlobalEdge, GlobalVertex, HalfEdge, Objects, Shell,
|
||||
Sketch, Solid, Surface, SurfaceVertex,
|
||||
},
|
||||
services::{Service, ServiceObjectsExt},
|
||||
storage::Handle,
|
||||
@ -37,7 +37,6 @@ impl_insert!(
|
||||
Curve, curves;
|
||||
Cycle, cycles;
|
||||
Face, faces;
|
||||
GlobalCurve, global_curves;
|
||||
GlobalEdge, global_edges;
|
||||
GlobalVertex, global_vertices;
|
||||
HalfEdge, half_edges;
|
||||
|
@ -17,7 +17,3 @@ impl Curve {
|
||||
self.path
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve, defined in global (3D) coordinates
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct GlobalCurve;
|
||||
|
@ -1,11 +1,9 @@
|
||||
use std::fmt;
|
||||
|
||||
use fj_interop::ext::ArrayExt;
|
||||
use fj_math::Point;
|
||||
|
||||
use crate::{
|
||||
objects::{Curve, GlobalCurve, GlobalVertex, SurfaceVertex},
|
||||
storage::{Handle, HandleWrapper},
|
||||
objects::{Curve, GlobalVertex, SurfaceVertex},
|
||||
storage::Handle,
|
||||
};
|
||||
|
||||
/// A directed edge, defined in a surface's 2D space
|
||||
@ -95,16 +93,6 @@ impl HalfEdge {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HalfEdge {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let [a, b] = self.boundary();
|
||||
write!(f, "edge from {a:?} to {b:?}")?;
|
||||
write!(f, " on {:?}", self.global_form().curve())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// An undirected edge, defined in global (3D) coordinates
|
||||
///
|
||||
/// In contrast to [`HalfEdge`], `GlobalEdge` is undirected, meaning it has no
|
||||
@ -116,7 +104,6 @@ impl fmt::Display for HalfEdge {
|
||||
/// between [`HalfEdge`] and `GlobalEdge`.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct GlobalEdge {
|
||||
curve: HandleWrapper<GlobalCurve>,
|
||||
vertices: VerticesInNormalizedOrder,
|
||||
}
|
||||
|
||||
@ -126,19 +113,10 @@ impl GlobalEdge {
|
||||
/// The order of `vertices` is irrelevant. Two `GlobalEdge`s with the same
|
||||
/// `curve` and `vertices` will end up being equal, regardless of the order
|
||||
/// of `vertices` here.
|
||||
pub fn new(
|
||||
curve: impl Into<HandleWrapper<GlobalCurve>>,
|
||||
vertices: [Handle<GlobalVertex>; 2],
|
||||
) -> Self {
|
||||
let curve = curve.into();
|
||||
pub fn new(vertices: [Handle<GlobalVertex>; 2]) -> Self {
|
||||
let (vertices, _) = VerticesInNormalizedOrder::new(vertices);
|
||||
|
||||
Self { curve, vertices }
|
||||
}
|
||||
|
||||
/// Access the curve that defines the edge's geometry
|
||||
pub fn curve(&self) -> &Handle<GlobalCurve> {
|
||||
&self.curve
|
||||
Self { vertices }
|
||||
}
|
||||
|
||||
/// Access the vertices that bound the edge on the curve
|
||||
|
@ -79,7 +79,7 @@ mod stores;
|
||||
|
||||
pub use self::{
|
||||
full::{
|
||||
curve::{Curve, GlobalCurve},
|
||||
curve::Curve,
|
||||
cycle::{Cycle, HalfEdgesOfCycle},
|
||||
edge::{GlobalEdge, HalfEdge, VerticesInNormalizedOrder},
|
||||
face::{Face, FaceSet, Handedness},
|
||||
|
@ -2,8 +2,8 @@ use std::any::Any;
|
||||
|
||||
use crate::{
|
||||
objects::{
|
||||
Curve, Cycle, Face, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge,
|
||||
Objects, Shell, Sketch, Solid, Surface, SurfaceVertex,
|
||||
Curve, Cycle, Face, GlobalEdge, GlobalVertex, HalfEdge, Objects, Shell,
|
||||
Sketch, Solid, Surface, SurfaceVertex,
|
||||
},
|
||||
storage::{Handle, ObjectId},
|
||||
validate::{Validate, ValidationError},
|
||||
@ -111,7 +111,6 @@ object!(
|
||||
Curve, "curve", curves;
|
||||
Cycle, "cycle", cycles;
|
||||
Face, "face", faces;
|
||||
GlobalCurve, "global curve", global_curves;
|
||||
GlobalEdge, "global edge", global_edges;
|
||||
GlobalVertex, "global vertex", global_vertices;
|
||||
HalfEdge, "half-edge", half_edges;
|
||||
|
@ -6,8 +6,8 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
Curve, Cycle, Face, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Shell,
|
||||
Sketch, Solid, Surface, SurfaceVertex,
|
||||
Curve, Cycle, Face, GlobalEdge, GlobalVertex, HalfEdge, Shell, Sketch,
|
||||
Solid, Surface, SurfaceVertex,
|
||||
};
|
||||
|
||||
/// The available object stores
|
||||
@ -22,9 +22,6 @@ pub struct Objects {
|
||||
/// Store for [`Face`]s
|
||||
pub faces: Store<Face>,
|
||||
|
||||
/// Store for [`GlobalCurve`]s
|
||||
pub global_curves: Store<GlobalCurve>,
|
||||
|
||||
/// Store for [`GlobalEdge`]s
|
||||
pub global_edges: Store<GlobalEdge>,
|
||||
|
||||
|
@ -16,7 +16,7 @@ mod wrapper;
|
||||
|
||||
pub use self::{
|
||||
objects::{
|
||||
curve::{MaybeSurfacePath, PartialCurve, PartialGlobalCurve},
|
||||
curve::{MaybeSurfacePath, PartialCurve},
|
||||
cycle::PartialCycle,
|
||||
edge::{PartialGlobalEdge, PartialHalfEdge},
|
||||
face::PartialFace,
|
||||
|
@ -2,7 +2,7 @@ use fj_math::Scalar;
|
||||
|
||||
use crate::{
|
||||
geometry::path::SurfacePath,
|
||||
objects::{Curve, GlobalCurve, Objects},
|
||||
objects::{Curve, Objects},
|
||||
partial::{FullToPartialCache, PartialObject},
|
||||
services::Service,
|
||||
};
|
||||
@ -61,19 +61,3 @@ impl From<SurfacePath> for MaybeSurfacePath {
|
||||
Self::Defined(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// A partial [`GlobalCurve`]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PartialGlobalCurve;
|
||||
|
||||
impl PartialObject for PartialGlobalCurve {
|
||||
type Full = GlobalCurve;
|
||||
|
||||
fn from_full(_: &Self::Full, _: &mut FullToPartialCache) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn build(self, _: &mut Service<Objects>) -> Self::Full {
|
||||
GlobalCurve
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,7 @@ use fj_math::Point;
|
||||
|
||||
use crate::{
|
||||
objects::{
|
||||
Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects,
|
||||
SurfaceVertex,
|
||||
Curve, GlobalEdge, GlobalVertex, HalfEdge, Objects, SurfaceVertex,
|
||||
},
|
||||
partial::{FullToPartialCache, Partial, PartialObject},
|
||||
services::Service,
|
||||
@ -84,7 +83,6 @@ impl Default for PartialHalfEdge {
|
||||
|
||||
let global_form = Partial::from_partial(PartialGlobalEdge {
|
||||
vertices: global_vertices,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
Self {
|
||||
@ -98,9 +96,6 @@ impl Default for PartialHalfEdge {
|
||||
/// A partial [`GlobalEdge`]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PartialGlobalEdge {
|
||||
/// The curve that defines the edge's geometry
|
||||
pub curve: Partial<GlobalCurve>,
|
||||
|
||||
/// The vertices that bound the edge on the curve
|
||||
pub vertices: [Partial<GlobalVertex>; 2],
|
||||
}
|
||||
@ -113,7 +108,6 @@ impl PartialObject for PartialGlobalEdge {
|
||||
cache: &mut FullToPartialCache,
|
||||
) -> Self {
|
||||
Self {
|
||||
curve: Partial::from_full(global_edge.curve().clone(), cache),
|
||||
vertices: global_edge
|
||||
.vertices()
|
||||
.access_in_normalized_order()
|
||||
@ -122,9 +116,7 @@ impl PartialObject for PartialGlobalEdge {
|
||||
}
|
||||
|
||||
fn build(self, objects: &mut Service<Objects>) -> Self::Full {
|
||||
let curve = self.curve.build(objects);
|
||||
let vertices = self.vertices.map(|vertex| vertex.build(objects));
|
||||
|
||||
GlobalEdge::new(curve, vertices)
|
||||
GlobalEdge::new(vertices)
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ impl_trait!(
|
||||
Curve, PartialCurve;
|
||||
Cycle, PartialCycle;
|
||||
Face, PartialFace;
|
||||
GlobalCurve, PartialGlobalCurve;
|
||||
GlobalEdge, PartialGlobalEdge;
|
||||
GlobalVertex, PartialGlobalVertex;
|
||||
HalfEdge, PartialHalfEdge;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::objects::{Curve, GlobalCurve};
|
||||
use crate::objects::Curve;
|
||||
|
||||
use super::{Validate, ValidationConfig, ValidationError};
|
||||
|
||||
@ -10,12 +10,3 @@ impl Validate for Curve {
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Validate for GlobalCurve {
|
||||
fn validate_with_config(
|
||||
&self,
|
||||
_: &ValidationConfig,
|
||||
_: &mut Vec<ValidationError>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use fj_math::{Point, Scalar};
|
||||
|
||||
use crate::{
|
||||
objects::{GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Surface},
|
||||
objects::{GlobalEdge, GlobalVertex, HalfEdge, Surface},
|
||||
storage::Handle,
|
||||
};
|
||||
|
||||
@ -30,25 +30,6 @@ impl Validate for GlobalEdge {
|
||||
/// [`HalfEdge`] validation failed
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
pub enum HalfEdgeValidationError {
|
||||
/// [`HalfEdge`]'s [`GlobalCurve`]s do not match
|
||||
#[error(
|
||||
"Global form of `HalfEdge`'s `Curve` does not match `GlobalCurve` of \n\
|
||||
the `HalfEdge`'s `GlobalEdge`\n\
|
||||
- `GlobalCurve` from `Curve`: {global_curve_from_curve:#?}\n\
|
||||
- `GlobalCurve` from `GlobalEdge`: {global_curve_from_global_form:#?}\n\
|
||||
- `HalfEdge`: {half_edge:#?}",
|
||||
)]
|
||||
GlobalCurveMismatch {
|
||||
/// The [`GlobalCurve`] from the [`HalfEdge`]'s `Curve`
|
||||
global_curve_from_curve: Handle<GlobalCurve>,
|
||||
|
||||
/// The [`GlobalCurve`] from the [`HalfEdge`]'s global form
|
||||
global_curve_from_global_form: Handle<GlobalCurve>,
|
||||
|
||||
/// The half-edge
|
||||
half_edge: HalfEdge,
|
||||
},
|
||||
|
||||
/// [`HalfEdge`]'s [`GlobalVertex`] objects do not match
|
||||
#[error(
|
||||
"Global forms of `HalfEdge` vertices do not match vertices of \n\
|
||||
|
Loading…
Reference in New Issue
Block a user