Merge pull request #1340 from hannobraun/surface

Clean up surface-related code; prepare for deeper integration into partial object API
This commit is contained in:
Hanno Braun 2022-11-12 18:11:19 +01:00 committed by GitHub
commit 35a02544ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 336 additions and 193 deletions

View File

@ -12,8 +12,8 @@
use std::collections::BTreeMap;
use crate::{
geometry::path::{GlobalPath, SurfacePath},
objects::{Curve, GlobalCurve},
path::{GlobalPath, SurfacePath},
storage::{Handle, ObjectId},
};
@ -62,7 +62,7 @@ fn approx_global_curve(
// 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(), curve.surface().u()) {
let points = match (curve.path(), curve.surface().geometry().u) {
(SurfacePath::Circle(_), GlobalPath::Circle(_)) => {
todo!(
"Approximating a circle on a curved surface not supported yet."
@ -90,6 +90,7 @@ fn approx_global_curve(
let point_global = curve
.surface()
.geometry()
.point_from_surface_coords(point_surface);
(point_curve, point_global)
})
@ -101,15 +102,17 @@ fn approx_global_curve(
[curve.path().point_from_path_coords(point_curve).u]
}));
let approx_u = (curve.surface().u(), range_u)
let approx_u = (curve.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 =
curve.surface().point_from_surface_coords(point_surface);
let point_global = curve
.surface()
.geometry()
.point_from_surface_coords(point_surface);
points.push((u, point_global));
}
@ -197,11 +200,11 @@ mod tests {
use crate::{
algorithms::approx::{path::RangeOnPath, Approx, ApproxPoint},
builder::CurveBuilder,
builder::{CurveBuilder, SurfaceBuilder},
geometry::path::GlobalPath,
insert::Insert,
objects::{Objects, Surface},
partial::PartialCurve,
path::GlobalPath,
objects::Objects,
partial::{PartialCurve, PartialSurface},
};
use super::CurveApprox;
@ -210,9 +213,10 @@ mod tests {
fn approx_line_on_flat_surface() -> anyhow::Result<()> {
let objects = Objects::new();
let surface = objects
.surfaces
.insert(Surface::new(GlobalPath::x_axis(), [0., 0., 1.]))?;
let surface =
PartialSurface::from_axes(GlobalPath::x_axis(), [0., 0., 1.])
.build(&objects)?
.insert(&objects)?;
let mut curve = PartialCurve {
surface: Some(surface),
..Default::default()
@ -232,10 +236,12 @@ mod tests {
{
let objects = Objects::new();
let surface = objects.surfaces.insert(Surface::new(
let surface = PartialSurface::from_axes(
GlobalPath::circle_from_radius(1.),
[0., 0., 1.],
))?;
)
.build(&objects)?
.insert(&objects)?;
let mut curve = PartialCurve {
surface: Some(surface),
..Default::default()
@ -255,8 +261,9 @@ mod tests {
let objects = Objects::new();
let path = GlobalPath::circle_from_radius(1.);
let surface =
objects.surfaces.insert(Surface::new(path, [0., 0., 1.]))?;
let surface = PartialSurface::from_axes(path, [0., 0., 1.])
.build(&objects)?
.insert(&objects)?;
let mut curve = PartialCurve {
surface: Some(surface.clone()),
..Default::default()
@ -276,7 +283,7 @@ mod tests {
let point_surface =
curve.path().point_from_path_coords(point_local);
let point_global =
surface.point_from_surface_coords(point_surface);
surface.geometry().point_from_surface_coords(point_surface);
ApproxPoint::new(point_surface, point_global)
})
.collect::<Vec<_>>();
@ -288,9 +295,10 @@ mod tests {
fn approx_circle_on_flat_surface() -> anyhow::Result<()> {
let objects = Objects::new();
let surface = objects
.surfaces
.insert(Surface::new(GlobalPath::x_axis(), [0., 0., 1.]))?;
let surface =
PartialSurface::from_axes(GlobalPath::x_axis(), [0., 0., 1.])
.build(&objects)?
.insert(&objects)?;
let mut curve = PartialCurve {
surface: Some(surface),
..Default::default()
@ -306,8 +314,10 @@ mod tests {
.approx(tolerance)
.into_iter()
.map(|(_, point_surface)| {
let point_global =
curve.surface().point_from_surface_coords(point_surface);
let point_global = curve
.surface()
.geometry()
.point_from_surface_coords(point_surface);
ApproxPoint::new(point_surface, point_global)
})
.collect::<Vec<_>>();

View File

@ -32,7 +32,7 @@ use std::iter;
use fj_math::{Circle, Point, Scalar, Sign};
use crate::path::{GlobalPath, SurfacePath};
use crate::geometry::path::{GlobalPath, SurfacePath};
use super::{Approx, Tolerance};

View File

@ -1,8 +1,8 @@
use fj_math::{Point, Segment};
use crate::{
geometry::path::SurfacePath,
objects::{Curve, HalfEdge},
path::SurfacePath,
};
use super::LineSegmentIntersection;

View File

@ -4,8 +4,8 @@ use fj_math::Segment;
use crate::{
algorithms::intersect::{HorizontalRayToTheRight, Intersect},
geometry::path::SurfacePath,
objects::HalfEdge,
path::SurfacePath,
storage::Handle,
};

View File

@ -4,8 +4,8 @@ use fj_math::{Plane, Point, Scalar};
use crate::{
algorithms::intersect::face_point::FacePointIntersection,
geometry::path::GlobalPath,
objects::{Face, HalfEdge, Vertex},
path::GlobalPath,
storage::Handle,
};
@ -17,14 +17,14 @@ impl Intersect for (&HorizontalRayToTheRight<3>, &Handle<Face>) {
fn intersect(self) -> Option<Self::Intersection> {
let (ray, face) = self;
let plane = match face.surface().u() {
let plane = match face.surface().geometry().u {
GlobalPath::Circle(_) => todo!(
"Casting a ray against a swept circle is not supported yet"
),
GlobalPath::Line(line) => Plane::from_parametric(
line.origin(),
line.direction(),
face.surface().v(),
face.surface().geometry().v,
),
};

View File

@ -2,8 +2,8 @@ use fj_interop::ext::ArrayExt;
use fj_math::{Line, Plane, Point, Scalar};
use crate::{
geometry::path::{GlobalPath, SurfacePath},
objects::{Curve, GlobalCurve, Objects, Surface},
path::{GlobalPath, SurfacePath},
storage::Handle,
validate::ValidationError,
};
@ -74,12 +74,12 @@ impl SurfaceSurfaceIntersection {
fn plane_from_surface(surface: &Surface) -> Plane {
let (line, path) = {
let line = match surface.u() {
let line = match surface.geometry().u {
GlobalPath::Line(line) => line,
_ => todo!("Only plane-plane intersection is currently supported."),
};
(line, surface.v())
(line, surface.geometry().v)
};
Plane::from_parametric(line.origin(), line.direction(), path)

View File

@ -1,8 +1,11 @@
use fj_math::{Circle, Line, Vector};
use crate::{
builder::SurfaceBuilder,
geometry::path::{GlobalPath, SurfacePath},
insert::Insert,
objects::{Curve, Objects, Surface},
path::{GlobalPath, SurfacePath},
partial::PartialSurface,
storage::Handle,
validate::ValidationError,
};
@ -18,7 +21,7 @@ impl Sweep for Handle<Curve> {
_: &mut SweepCache,
objects: &Objects,
) -> Result<Self::Swept, ValidationError> {
match self.surface().u() {
match self.surface().geometry().u {
GlobalPath::Circle(_) => {
// Sweeping a `Curve` creates a `Surface`. The u-axis of that
// `Surface` is a `GlobalPath`, which we are computing below.
@ -44,20 +47,32 @@ impl Sweep for Handle<Curve> {
let u = match self.path() {
SurfacePath::Circle(circle) => {
let center =
self.surface().point_from_surface_coords(circle.center());
let a = self.surface().vector_from_surface_coords(circle.a());
let b = self.surface().vector_from_surface_coords(circle.b());
let center = self
.surface()
.geometry()
.point_from_surface_coords(circle.center());
let a = self
.surface()
.geometry()
.vector_from_surface_coords(circle.a());
let b = self
.surface()
.geometry()
.vector_from_surface_coords(circle.b());
let circle = Circle::new(center, a, b);
GlobalPath::Circle(circle)
}
SurfacePath::Line(line) => {
let origin =
self.surface().point_from_surface_coords(line.origin());
let direction =
self.surface().vector_from_surface_coords(line.direction());
let origin = self
.surface()
.geometry()
.point_from_surface_coords(line.origin());
let direction = self
.surface()
.geometry()
.vector_from_surface_coords(line.direction());
let line = Line::from_origin_and_direction(origin, direction);
@ -65,6 +80,9 @@ impl Sweep for Handle<Curve> {
}
};
Ok(objects.surfaces.insert(Surface::new(u, path))?)
let surface = PartialSurface::from_axes(u, path)
.build(objects)?
.insert(objects)?;
Ok(surface)
}
}

View File

@ -4,13 +4,13 @@ use iter_fixed::IntoIteratorFixed;
use crate::{
algorithms::{reverse::Reverse, transform::TransformObject},
geometry::path::SurfacePath,
insert::Insert,
objects::{
Curve, Cycle, Face, GlobalEdge, HalfEdge, Objects, SurfaceVertex,
Vertex,
},
partial::HasPartial,
path::SurfacePath,
storage::Handle,
validate::ValidationError,
};

View File

@ -2,8 +2,8 @@ use fj_math::{Scalar, Vector};
use crate::{
algorithms::{reverse::Reverse, transform::TransformObject},
geometry::path::GlobalPath,
objects::{Face, Objects, Shell},
path::GlobalPath,
storage::Handle,
validate::ValidationError,
};
@ -24,14 +24,14 @@ impl Sweep for Handle<Face> {
let mut faces = Vec::new();
let is_negative_sweep = {
let u = match self.surface().u() {
let u = match self.surface().geometry().u {
GlobalPath::Circle(_) => todo!(
"Sweeping from faces defined in round surfaces is not \
supported"
),
GlobalPath::Line(line) => line.direction(),
};
let v = self.surface().v();
let v = self.surface().geometry().v;
let normal = u.cross(&v);

View File

@ -3,11 +3,11 @@ use fj_math::{Line, Point, Scalar, Vector};
use try_insert_ext::EntryInsertExt;
use crate::{
geometry::path::SurfacePath,
objects::{
Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects,
Surface, SurfaceVertex, Vertex,
},
path::SurfacePath,
storage::Handle,
validate::ValidationError,
};
@ -56,7 +56,7 @@ impl Sweep for (Handle<Vertex>, Handle<Surface>) {
// not, we have no way of knowing the surface coordinates of the input
// `Vertex` on the `Surface`, and we're going to need to do that further
// down. There's no way to check for that, unfortunately.
assert_eq!(path, surface.v());
assert_eq!(path, surface.geometry().v);
// With that out of the way, let's start by creating the `GlobalEdge`,
// as that is the most straight-forward part of this operations, and

View File

@ -1,22 +1,25 @@
use fj_math::Transform;
use crate::{
objects::{Objects, Surface},
storage::Handle,
validate::ValidationError,
geometry::surface::SurfaceGeometry, objects::Objects,
partial::PartialSurface, validate::ValidationError,
};
use super::TransformObject;
impl TransformObject for Handle<Surface> {
impl TransformObject for PartialSurface {
fn transform(
self,
transform: &Transform,
objects: &Objects,
_: &Objects,
) -> Result<Self, ValidationError> {
Ok(objects.surfaces.insert(Surface::new(
self.u().transform(transform),
transform.transform_vector(&self.v()),
))?)
let geometry = self.geometry.map(|geometry| {
let u = geometry.u.transform(transform);
let v = transform.transform_vector(&geometry.v);
SurfaceGeometry { u, v }
});
Ok(Self { geometry })
}
}

View File

@ -148,12 +148,12 @@ mod tests {
let triangles = triangulate(face)?;
let a = surface.point_from_surface_coords(a);
let b = surface.point_from_surface_coords(b);
let e = surface.point_from_surface_coords(e);
let f = surface.point_from_surface_coords(f);
let g = surface.point_from_surface_coords(g);
let h = surface.point_from_surface_coords(h);
let a = surface.geometry().point_from_surface_coords(a);
let b = surface.geometry().point_from_surface_coords(b);
let e = surface.geometry().point_from_surface_coords(e);
let f = surface.geometry().point_from_surface_coords(f);
let g = surface.geometry().point_from_surface_coords(g);
let h = surface.geometry().point_from_surface_coords(h);
// Let's test that some correct triangles are present. We don't need to
// test them all.
@ -209,11 +209,11 @@ mod tests {
let triangles = triangulate(face)?;
let a3 = surface.point_from_surface_coords(a);
let b3 = surface.point_from_surface_coords(b);
let c3 = surface.point_from_surface_coords(c);
let d3 = surface.point_from_surface_coords(d);
let e3 = surface.point_from_surface_coords(e);
let a3 = surface.geometry().point_from_surface_coords(a);
let b3 = surface.geometry().point_from_surface_coords(b);
let c3 = surface.geometry().point_from_surface_coords(c);
let d3 = surface.geometry().point_from_surface_coords(d);
let e3 = surface.geometry().point_from_surface_coords(e);
assert!(triangles.contains_triangle([a3, b3, d3]));
assert!(triangles.contains_triangle([b3, c3, d3]));

View File

@ -1,6 +1,6 @@
use fj_math::{Point, Scalar, Vector};
use crate::{partial::PartialCurve, path::SurfacePath};
use crate::{geometry::path::SurfacePath, partial::PartialCurve};
/// Builder API for [`PartialCurve`]
pub trait CurveBuilder {

View File

@ -5,8 +5,9 @@ use crate::{
insert::Insert,
objects::{Curve, Objects, Surface, Vertex, VerticesInNormalizedOrder},
partial::{
MaybePartial, PartialCurve, PartialGlobalEdge, PartialGlobalVertex,
PartialHalfEdge, PartialSurfaceVertex, PartialVertex,
MaybePartial, MergeWith, PartialCurve, PartialGlobalEdge,
PartialGlobalVertex, PartialHalfEdge, PartialSurfaceVertex,
PartialVertex,
},
storage::Handle,
validate::ValidationError,
@ -135,8 +136,8 @@ impl HalfEdgeBuilder for PartialHalfEdge {
let surface = self
.curve()
.surface()
.or_else(|| from_surface.surface())
.or_else(|| to_surface.surface())
.merge_with(from_surface.surface())
.merge_with(to_surface.surface())
.expect("Can't infer line segment without a surface");
let points = [&from_surface, &to_surface].map(|vertex| {
vertex

View File

@ -12,6 +12,7 @@ mod curve;
mod cycle;
mod edge;
mod face;
mod surface;
mod vertex;
pub use self::{
@ -22,5 +23,6 @@ pub use self::{
shell::ShellBuilder,
sketch::SketchBuilder,
solid::SolidBuilder,
surface::SurfaceBuilder,
vertex::{GlobalVertexBuilder, SurfaceVertexBuilder, VertexBuilder},
};

View File

@ -6,10 +6,13 @@ use iter_fixed::IntoIteratorFixed;
use crate::{
algorithms::transform::TransformObject,
builder::{FaceBuilder, HalfEdgeBuilder},
builder::{FaceBuilder, HalfEdgeBuilder, SurfaceBuilder},
insert::Insert,
objects::{Cycle, Face, FaceSet, HalfEdge, Objects, Shell, Surface},
partial::{HasPartial, PartialCurve, PartialSurfaceVertex, PartialVertex},
objects::{Cycle, Face, FaceSet, HalfEdge, Objects, Shell},
partial::{
HasPartial, PartialCurve, PartialSurface, PartialSurfaceVertex,
PartialVertex,
},
storage::Handle,
};
@ -78,9 +81,10 @@ impl<'a> ShellBuilder<'a> {
.map(|vertex| vertex.global_form().position());
let c = a + [Z, Z, edge_length];
self.objects
.surfaces
.insert(Surface::plane_from_points([a, b, c]))
PartialSurface::plane_from_points([a, b, c])
.build(self.objects)
.unwrap()
.insert(self.objects)
.unwrap()
})
.collect::<Vec<_>>();

View File

@ -0,0 +1,36 @@
use fj_math::{Line, Point, Vector};
use crate::{
geometry::{path::GlobalPath, surface::SurfaceGeometry},
partial::PartialSurface,
};
/// Builder API for [`PartialSurface`]
pub trait SurfaceBuilder {
/// Build a surface from its two axes
fn from_axes(u: GlobalPath, v: impl Into<Vector<3>>) -> Self;
/// Construct a plane from 3 points
fn plane_from_points(points: [impl Into<Point<3>>; 3]) -> Self;
}
impl SurfaceBuilder for PartialSurface {
fn from_axes(u: GlobalPath, v: impl Into<Vector<3>>) -> Self {
let v = v.into();
Self {
geometry: Some(SurfaceGeometry { u, v }),
}
}
fn plane_from_points(points: [impl Into<Point<3>>; 3]) -> Self {
let [a, b, c] = points.map(Into::into);
let u = GlobalPath::Line(Line::from_points([a, b]));
let v = c - a;
Self {
geometry: Some(SurfaceGeometry { u, v }),
}
}
}

View File

@ -1,7 +1,8 @@
use fj_math::Point;
use crate::{
objects::{Curve, GlobalVertex, Surface},
geometry::surface::SurfaceGeometry,
objects::{Curve, GlobalVertex},
partial::{
HasPartial, MaybePartial, PartialGlobalVertex, PartialSurfaceVertex,
PartialVertex,
@ -44,7 +45,7 @@ pub trait GlobalVertexBuilder {
/// Update partial global vertex from the given surface and position on it
fn from_surface_and_position(
surface: &Surface,
surface: &SurfaceGeometry,
position: impl Into<Point<2>>,
) -> Self;
}
@ -59,9 +60,12 @@ impl GlobalVertexBuilder for PartialGlobalVertex {
let path = curve.path.expect(
"Need path to create `GlobalVertex` from curve and position",
);
let surface = curve.surface.expect(
"Need surface to create `GlobalVertex` from curve and position",
);
let surface = curve
.surface
.expect(
"Need surface to create `GlobalVertex` from curve and position",
)
.geometry();
let position_surface = path.point_from_path_coords(position);
@ -69,7 +73,7 @@ impl GlobalVertexBuilder for PartialGlobalVertex {
}
fn from_surface_and_position(
surface: &Surface,
surface: &SurfaceGeometry,
position: impl Into<Point<2>>,
) -> Self {
PartialGlobalVertex {

View File

@ -0,0 +1,4 @@
//! Types that are tied to objects, but aren't objects themselves
pub mod path;
pub mod surface;

View File

@ -0,0 +1,81 @@
//! The geometry that defines a surface
use fj_math::{Line, Point, Vector};
use super::path::GlobalPath;
/// The geometry that defines a surface
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct SurfaceGeometry {
/// The u-axis of the surface
pub u: GlobalPath,
/// The v-axis of the surface
pub v: Vector<3>,
}
impl SurfaceGeometry {
/// Convert a point in surface coordinates to model coordinates
pub fn point_from_surface_coords(
&self,
point: impl Into<Point<2>>,
) -> Point<3> {
let point = point.into();
self.u.point_from_path_coords([point.u])
+ self.path_to_line().vector_from_line_coords([point.v])
}
/// Convert a vector in surface coordinates to model coordinates
pub fn vector_from_surface_coords(
&self,
vector: impl Into<Vector<2>>,
) -> Vector<3> {
let vector = vector.into();
self.u.vector_from_path_coords([vector.u])
+ self.path_to_line().vector_from_line_coords([vector.v])
}
fn path_to_line(&self) -> Line<3> {
Line::from_origin_and_direction(self.u.origin(), self.v)
}
}
#[cfg(test)]
mod tests {
use fj_math::{Line, Point, Vector};
use pretty_assertions::assert_eq;
use crate::geometry::{path::GlobalPath, surface::SurfaceGeometry};
#[test]
fn point_from_surface_coords() {
let surface = SurfaceGeometry {
u: GlobalPath::Line(Line::from_origin_and_direction(
Point::from([1., 1., 1.]),
Vector::from([0., 2., 0.]),
)),
v: Vector::from([0., 0., 2.]),
};
assert_eq!(
surface.point_from_surface_coords([2., 4.]),
Point::from([1., 5., 9.]),
);
}
#[test]
fn vector_from_surface_coords() {
let surface = SurfaceGeometry {
u: GlobalPath::Line(Line::from_origin_and_direction(
Point::from([1., 0., 0.]),
Vector::from([0., 2., 0.]),
)),
v: Vector::from([0., 0., 2.]),
};
assert_eq!(
surface.vector_from_surface_coords([2., 4.]),
Vector::from([0., 4., 8.]),
);
}
}

View File

@ -89,10 +89,10 @@
pub mod algorithms;
pub mod builder;
pub mod geometry;
pub mod insert;
pub mod iter;
pub mod objects;
pub mod partial;
pub mod path;
pub mod storage;
pub mod validate;

View File

@ -1,5 +1,5 @@
use crate::{
path::SurfacePath,
geometry::path::SurfacePath,
storage::{Handle, HandleWrapper},
};

View File

@ -3,7 +3,7 @@ use std::slice;
use fj_interop::ext::SliceExt;
use fj_math::{Scalar, Winding};
use crate::{path::SurfacePath, storage::Handle};
use crate::{geometry::path::SurfacePath, storage::Handle};
use super::{HalfEdge, Surface};

View File

@ -100,7 +100,7 @@ use std::convert::Infallible;
use fj_math::Vector;
use crate::{
path::GlobalPath,
geometry::{path::GlobalPath, surface::SurfaceGeometry},
storage::{Handle, Store},
validate::{
CycleValidationError, FaceValidationError, HalfEdgeValidationError,
@ -380,12 +380,18 @@ impl Default for Surfaces {
fn default() -> Self {
let store = Store::new();
let xy_plane =
store.insert(Surface::new(GlobalPath::x_axis(), Vector::unit_y()));
let xz_plane =
store.insert(Surface::new(GlobalPath::x_axis(), Vector::unit_z()));
let yz_plane =
store.insert(Surface::new(GlobalPath::y_axis(), Vector::unit_z()));
let xy_plane = store.insert(Surface::new(SurfaceGeometry {
u: GlobalPath::x_axis(),
v: Vector::unit_y(),
}));
let xz_plane = store.insert(Surface::new(SurfaceGeometry {
u: GlobalPath::x_axis(),
v: Vector::unit_z(),
}));
let yz_plane = store.insert(Surface::new(SurfaceGeometry {
u: GlobalPath::y_axis(),
v: Vector::unit_z(),
}));
Self {
store,

View File

@ -1,104 +1,19 @@
use fj_math::{Line, Point, Vector};
use crate::path::GlobalPath;
use crate::geometry::surface::SurfaceGeometry;
/// A two-dimensional shape
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Surface {
u: GlobalPath,
v: Vector<3>,
geometry: SurfaceGeometry,
}
impl Surface {
/// Construct a `Surface` from two paths that define its coordinate system
pub fn new(u: GlobalPath, v: impl Into<Vector<3>>) -> Self {
let v = v.into();
Self { u, v }
pub fn new(geometry: SurfaceGeometry) -> Self {
Self { geometry }
}
/// Construct a plane from 3 points
pub fn plane_from_points(points: [impl Into<Point<3>>; 3]) -> Self {
let [a, b, c] = points.map(Into::into);
let u = GlobalPath::Line(Line::from_points([a, b]));
let v = c - a;
Self { u, v }
}
/// Access the path that defines the u-coordinate of this surface
pub fn u(&self) -> GlobalPath {
self.u
}
/// Access the path that defines the v-coordinate of this surface
pub fn v(&self) -> Vector<3> {
self.v
}
/// Convert a point in surface coordinates to model coordinates
pub fn point_from_surface_coords(
&self,
point: impl Into<Point<2>>,
) -> Point<3> {
let point = point.into();
self.u.point_from_path_coords([point.u])
+ self.path_to_line().vector_from_line_coords([point.v])
}
/// Convert a vector in surface coordinates to model coordinates
pub fn vector_from_surface_coords(
&self,
vector: impl Into<Vector<2>>,
) -> Vector<3> {
let vector = vector.into();
self.u.vector_from_path_coords([vector.u])
+ self.path_to_line().vector_from_line_coords([vector.v])
}
fn path_to_line(&self) -> Line<3> {
Line::from_origin_and_direction(self.u.origin(), self.v)
}
}
#[cfg(test)]
mod tests {
use fj_math::{Line, Point, Vector};
use pretty_assertions::assert_eq;
use crate::path::GlobalPath;
use super::Surface;
#[test]
fn point_from_surface_coords() {
let swept = Surface {
u: GlobalPath::Line(Line::from_origin_and_direction(
Point::from([1., 1., 1.]),
Vector::from([0., 2., 0.]),
)),
v: Vector::from([0., 0., 2.]),
};
assert_eq!(
swept.point_from_surface_coords([2., 4.]),
Point::from([1., 5., 9.]),
);
}
#[test]
fn vector_from_surface_coords() {
let swept = Surface {
u: GlobalPath::Line(Line::from_origin_and_direction(
Point::from([1., 0., 0.]),
Vector::from([0., 2., 0.]),
)),
v: Vector::from([0., 0., 2.]),
};
assert_eq!(
swept.vector_from_surface_coords([2., 4.]),
Vector::from([0., 4., 8.]),
);
/// Access the surface's geometry
pub fn geometry(&self) -> SurfaceGeometry {
self.geometry
}
}

View File

@ -1,12 +1,12 @@
use fj_math::Point;
use crate::{
geometry::{path::SurfacePath, surface::SurfaceGeometry},
insert::Insert,
objects::{
Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects,
Surface, SurfaceVertex, Vertex,
},
path::SurfacePath,
storage::Handle,
validate::{Validate, ValidationError},
};
@ -212,6 +212,16 @@ impl MaybePartial<HalfEdge> {
}
}
impl MaybePartial<Surface> {
/// Access the geometry
pub fn geometry(&self) -> Option<SurfaceGeometry> {
match self {
Self::Full(full) => Some(full.geometry()),
Self::Partial(partial) => partial.geometry,
}
}
}
impl MaybePartial<SurfaceVertex> {
/// Access the position
pub fn position(&self) -> Option<Point<2>> {

View File

@ -47,6 +47,7 @@ pub use self::{
cycle::PartialCycle,
edge::{PartialGlobalEdge, PartialHalfEdge},
face::PartialFace,
surface::PartialSurface,
vertex::{PartialGlobalVertex, PartialSurfaceVertex, PartialVertex},
},
traits::{HasPartial, Partial},

View File

@ -1,7 +1,7 @@
use crate::{
geometry::path::SurfacePath,
objects::{Curve, GlobalCurve, Objects, Surface},
partial::{MaybePartial, MergeWith, Mergeable},
path::SurfacePath,
storage::Handle,
validate::ValidationError,
};

View File

@ -2,17 +2,18 @@ pub mod curve;
pub mod cycle;
pub mod edge;
pub mod face;
pub mod surface;
pub mod vertex;
use crate::objects::{
Curve, Cycle, Face, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge,
Objects, SurfaceVertex, Vertex,
Objects, Surface, SurfaceVertex, Vertex,
};
use super::{
HasPartial, MaybePartial, Partial, PartialCurve, PartialCycle, PartialFace,
PartialGlobalCurve, PartialGlobalEdge, PartialGlobalVertex,
PartialHalfEdge, PartialSurfaceVertex, PartialVertex,
PartialHalfEdge, PartialSurface, PartialSurfaceVertex, PartialVertex,
};
macro_rules! impl_traits {
@ -52,6 +53,7 @@ impl_traits!(
GlobalEdge, PartialGlobalEdge;
GlobalVertex, PartialGlobalVertex;
HalfEdge, PartialHalfEdge;
Surface, PartialSurface;
SurfaceVertex, PartialSurfaceVertex;
Vertex, PartialVertex;
);

View File

@ -0,0 +1,44 @@
use crate::{
geometry::surface::SurfaceGeometry,
objects::{Objects, Surface},
partial::MergeWith,
validate::ValidationError,
};
/// A partial [`Surface`]
///
/// See [`crate::partial`] for more information
#[derive(Clone, Debug, Default)]
pub struct PartialSurface {
/// The geometry that defines the [`Surface`]
pub geometry: Option<SurfaceGeometry>,
}
impl PartialSurface {
/// Build a full [`Surface`] from the partial surface
pub fn build(self, _: &Objects) -> Result<Surface, ValidationError> {
let geometry = self
.geometry
.expect("Can't build `Surface` without geometry");
Ok(Surface::new(geometry))
}
}
impl MergeWith for PartialSurface {
fn merge_with(self, other: impl Into<Self>) -> Self {
let other = other.into();
Self {
geometry: self.geometry.merge_with(other.geometry),
}
}
}
impl From<&Surface> for PartialSurface {
fn from(surface: &Surface) -> Self {
Self {
geometry: Some(surface.geometry()),
}
}
}

View File

@ -80,7 +80,7 @@ impl From<&Vertex> for PartialVertex {
/// A partial [`SurfaceVertex`]
///
/// See [`crate::partial`] for more information.
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[derive(Clone, Debug, Default)]
pub struct PartialSurfaceVertex {
/// The position of the [`SurfaceVertex`]
pub position: Option<Point<2>>,
@ -108,7 +108,8 @@ impl PartialSurfaceVertex {
let global_form = self
.global_form
.merge_with(PartialGlobalVertex::from_surface_and_position(
&surface, position,
&surface.geometry(),
position,
))
.into_full(objects)?;

View File

@ -158,6 +158,7 @@ impl SurfaceVertexValidationError {
) -> Result<(), Self> {
let surface_position_as_global = surface_vertex
.surface()
.geometry()
.point_from_surface_coords(surface_vertex.position());
let global_position = surface_vertex.global_form().position();