mirror of
https://github.com/hannobraun/Fornjot
synced 2025-09-09 10:56:29 +00:00
Move Line
back to fj-math
See previous commit for an explanation.
This commit is contained in:
parent
a67d0bf29f
commit
1d2ce022ed
@ -1,12 +1,9 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use fj_math::{Circle, Point};
|
||||
use fj_math::{Circle, Line, Point};
|
||||
|
||||
use crate::{
|
||||
geometry::{
|
||||
curves::line::Line, CurveBoundary, Geometry, Path, SurfaceGeom,
|
||||
Tolerance,
|
||||
},
|
||||
geometry::{CurveBoundary, Geometry, Path, SurfaceGeom, Tolerance},
|
||||
storage::Handle,
|
||||
topology::{Curve, Surface},
|
||||
};
|
||||
|
@ -1,6 +1,4 @@
|
||||
use fj_math::Point;
|
||||
|
||||
use crate::geometry::curves::line::Line;
|
||||
use fj_math::{Line, Point};
|
||||
|
||||
/// Approximate a line
|
||||
///
|
||||
|
@ -1,6 +1,4 @@
|
||||
use fj_math::{Aabb, LineSegment, Point, Scalar, Vector};
|
||||
|
||||
use crate::geometry::curves::line::Line;
|
||||
use fj_math::{Aabb, Line, LineSegment, Point, Scalar, Vector};
|
||||
|
||||
/// An intersection between a [`Line`] and a [`LineSegment`]
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
@ -68,9 +66,7 @@ impl LineSegmentIntersection {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use fj_math::{LineSegment, Point, Scalar, Vector};
|
||||
|
||||
use crate::geometry::curves::line::Line;
|
||||
use fj_math::{Line, LineSegment, Point, Scalar, Vector};
|
||||
|
||||
use super::LineSegmentIntersection;
|
||||
|
||||
|
@ -1,180 +1,9 @@
|
||||
//! # Geometry code specific to lines
|
||||
|
||||
use fj_math::{LineSegment, Point, Scalar, Transform, Triangle, Vector};
|
||||
use fj_math::{Line, LineSegment, Point};
|
||||
|
||||
use crate::geometry::{traits::GenPolyline, CurveBoundary, Tolerance};
|
||||
|
||||
/// An n-dimensional line, defined by an origin and a direction
|
||||
///
|
||||
/// The dimensionality of the line is defined by the const generic `D`
|
||||
/// parameter.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
#[repr(C)]
|
||||
pub struct Line<const D: usize> {
|
||||
origin: Point<D>,
|
||||
direction: Vector<D>,
|
||||
}
|
||||
|
||||
impl<const D: usize> Line<D> {
|
||||
/// Create a line from a point and a vector
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics, if `direction` has a length of zero.
|
||||
pub fn from_origin_and_direction(
|
||||
origin: Point<D>,
|
||||
direction: Vector<D>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
direction.magnitude() != Scalar::ZERO,
|
||||
"Can't construct `Line`. Direction is zero: {direction:?}"
|
||||
);
|
||||
|
||||
Self { origin, direction }
|
||||
}
|
||||
|
||||
/// Create a line from two points
|
||||
///
|
||||
/// Also returns the lines coordinates of the provided points on the new
|
||||
/// line.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics, if the points are coincident.
|
||||
pub fn from_points(
|
||||
points: [impl Into<Point<D>>; 2],
|
||||
) -> (Self, [Point<1>; 2]) {
|
||||
let [a, b] = points.map(Into::into);
|
||||
|
||||
let line = Self::from_origin_and_direction(a, b - a);
|
||||
let coords = [[0.], [1.]].map(Point::from);
|
||||
|
||||
(line, coords)
|
||||
}
|
||||
|
||||
/// Create a line from two points that include line coordinates
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics, if the points are coincident.
|
||||
pub fn from_points_with_line_coords(
|
||||
points: [(impl Into<Point<1>>, impl Into<Point<D>>); 2],
|
||||
) -> Self {
|
||||
let [(a_line, a_global), (b_line, b_global)] =
|
||||
points.map(|(point_line, point_global)| {
|
||||
(point_line.into(), point_global.into())
|
||||
});
|
||||
|
||||
let direction = (b_global - a_global) / (b_line - a_line).t;
|
||||
let origin = a_global + direction * -a_line.t;
|
||||
|
||||
Self::from_origin_and_direction(origin, direction)
|
||||
}
|
||||
|
||||
/// Access the origin of the line
|
||||
///
|
||||
/// The origin is a point on the line which, together with the `direction`
|
||||
/// field, defines the line fully. The origin also defines the origin of the
|
||||
/// line's 1-dimensional coordinate system.
|
||||
pub fn origin(&self) -> Point<D> {
|
||||
self.origin
|
||||
}
|
||||
|
||||
/// Access the direction of the line
|
||||
///
|
||||
/// The length of this vector defines the unit of the line's curve
|
||||
/// coordinate system. The coordinate `1.` is always where the direction
|
||||
/// vector points, from `origin`.
|
||||
pub fn direction(&self) -> Vector<D> {
|
||||
self.direction
|
||||
}
|
||||
|
||||
/// Determine if this line is coincident with another line
|
||||
///
|
||||
/// # Implementation Note
|
||||
///
|
||||
/// This method only returns `true`, if the lines are precisely coincident.
|
||||
/// This will probably not be enough going forward, but it'll do for now.
|
||||
pub fn is_coincident_with(&self, other: &Self) -> bool {
|
||||
let other_origin_is_not_on_self = {
|
||||
let a = other.origin;
|
||||
let b = self.origin;
|
||||
let c = self.origin + self.direction;
|
||||
|
||||
// The triangle is valid only, if the three points are not on the
|
||||
// same line.
|
||||
Triangle::from_points([a, b, c]).is_valid()
|
||||
};
|
||||
|
||||
if other_origin_is_not_on_self {
|
||||
return false;
|
||||
}
|
||||
|
||||
let d1 = self.direction.normalize();
|
||||
let d2 = other.direction.normalize();
|
||||
|
||||
d1 == d2 || d1 == -d2
|
||||
}
|
||||
|
||||
/// Create a new instance that is reversed
|
||||
#[must_use]
|
||||
pub fn reverse(mut self) -> Self {
|
||||
self.origin += self.direction;
|
||||
self.direction = -self.direction;
|
||||
self
|
||||
}
|
||||
|
||||
/// Convert a `D`-dimensional point to line coordinates
|
||||
///
|
||||
/// Projects the point onto the line before the conversion. This is done to
|
||||
/// make this method robust against floating point accuracy issues.
|
||||
///
|
||||
/// Callers are advised to be careful about the points they pass, as the
|
||||
/// point not being on the line, intentional or not, will never result in an
|
||||
/// error.
|
||||
pub fn point_to_line_coords(&self, point: impl Into<Point<D>>) -> Point<1> {
|
||||
Point {
|
||||
coords: self.vector_to_line_coords(point.into() - self.origin),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a `D`-dimensional vector to line coordinates
|
||||
pub fn vector_to_line_coords(
|
||||
&self,
|
||||
vector: impl Into<Vector<D>>,
|
||||
) -> Vector<1> {
|
||||
let t = vector.into().scalar_projection_onto(&self.direction)
|
||||
/ self.direction.magnitude();
|
||||
Vector::from([t])
|
||||
}
|
||||
|
||||
/// Convert a point in line coordinates into a `D`-dimensional point
|
||||
pub fn point_from_line_coords(
|
||||
&self,
|
||||
point: impl Into<Point<1>>,
|
||||
) -> Point<D> {
|
||||
self.origin + self.vector_from_line_coords(point.into().coords)
|
||||
}
|
||||
|
||||
/// Convert a vector in line coordinates into a `D`-dimensional vector
|
||||
pub fn vector_from_line_coords(
|
||||
&self,
|
||||
vector: impl Into<Vector<1>>,
|
||||
) -> Vector<D> {
|
||||
self.direction * vector.into().t
|
||||
}
|
||||
}
|
||||
|
||||
impl Line<3> {
|
||||
/// # Transform the line
|
||||
pub fn transform(&self, transform: &Transform) -> Self {
|
||||
Self::from_origin_and_direction(
|
||||
transform.transform_point(&self.origin()),
|
||||
transform.transform_vector(&self.direction()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const D: usize> GenPolyline<D> for Line<D> {
|
||||
fn origin(&self) -> Point<D> {
|
||||
self.origin()
|
||||
@ -197,72 +26,3 @@ impl<const D: usize> GenPolyline<D> for Line<D> {
|
||||
boundary.inner.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use approx::assert_abs_diff_eq;
|
||||
use fj_math::{Point, Scalar, Vector};
|
||||
|
||||
use super::Line;
|
||||
|
||||
#[test]
|
||||
fn from_points_with_line_coords() {
|
||||
let line = Line::from_points_with_line_coords([
|
||||
([0.], [0., 0.]),
|
||||
([1.], [1., 0.]),
|
||||
]);
|
||||
assert_eq!(line.origin(), Point::from([0., 0.]));
|
||||
assert_eq!(line.direction(), Vector::from([1., 0.]));
|
||||
|
||||
let line = Line::from_points_with_line_coords([
|
||||
([1.], [0., 1.]),
|
||||
([0.], [1., 1.]),
|
||||
]);
|
||||
assert_eq!(line.origin(), Point::from([1., 1.]));
|
||||
assert_eq!(line.direction(), Vector::from([-1., 0.]));
|
||||
|
||||
let line = Line::from_points_with_line_coords([
|
||||
([-1.], [0., 2.]),
|
||||
([0.], [1., 2.]),
|
||||
]);
|
||||
assert_eq!(line.origin(), Point::from([1., 2.]));
|
||||
assert_eq!(line.direction(), Vector::from([1., 0.]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_coincident_with() {
|
||||
let (line, _) = Line::from_points([[0., 0.], [1., 0.]]);
|
||||
|
||||
let (a, _) = Line::from_points([[0., 0.], [1., 0.]]);
|
||||
let (b, _) = Line::from_points([[0., 0.], [-1., 0.]]);
|
||||
let (c, _) = Line::from_points([[0., 1.], [1., 1.]]);
|
||||
|
||||
assert!(line.is_coincident_with(&a));
|
||||
assert!(line.is_coincident_with(&b));
|
||||
assert!(!line.is_coincident_with(&c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_point_to_line_coords() {
|
||||
let line = Line {
|
||||
origin: Point::from([1., 2., 3.]),
|
||||
direction: Vector::from([2., 3., 5.]),
|
||||
};
|
||||
|
||||
verify(line, -1.);
|
||||
verify(line, 0.);
|
||||
verify(line, 1.);
|
||||
verify(line, 2.);
|
||||
|
||||
fn verify(line: Line<3>, t: f64) {
|
||||
let point = line.point_from_line_coords([t]);
|
||||
let t_result = line.point_to_line_coords(point);
|
||||
|
||||
assert_abs_diff_eq!(
|
||||
Point::from([t]),
|
||||
t_result,
|
||||
epsilon = Scalar::from(1e-8)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
//!
|
||||
//! See [`Path`].
|
||||
|
||||
use fj_math::{Circle, Point, Scalar, Transform, Vector};
|
||||
|
||||
use super::curves::line::Line;
|
||||
use fj_math::{Circle, Line, Point, Scalar, Transform, Vector};
|
||||
|
||||
/// A path through surface (2D) or global (3D) space
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
|
@ -111,10 +111,10 @@ impl SurfaceGeom {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use fj_math::{Point, Vector};
|
||||
use fj_math::{Line, Point, Vector};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::geometry::{curves::line::Line, Path, SurfaceGeom, Tolerance};
|
||||
use crate::geometry::{Path, SurfaceGeom, Tolerance};
|
||||
|
||||
#[test]
|
||||
fn point_from_surface_coords() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use fj_math::{Circle, Vector};
|
||||
use fj_math::{Circle, Line, Vector};
|
||||
|
||||
use crate::{
|
||||
geometry::{curves::line::Line, Path, SurfaceGeom},
|
||||
geometry::{Path, SurfaceGeom},
|
||||
operations::build::BuildSurface,
|
||||
storage::Handle,
|
||||
topology::Surface,
|
||||
|
@ -36,6 +36,7 @@ mod arc;
|
||||
mod bivector;
|
||||
mod circle;
|
||||
mod coordinates;
|
||||
mod line;
|
||||
mod line_segment;
|
||||
mod point;
|
||||
mod poly_chain;
|
||||
@ -50,6 +51,7 @@ pub use self::{
|
||||
bivector::Bivector,
|
||||
circle::Circle,
|
||||
coordinates::{Uv, Xyz, T},
|
||||
line::Line,
|
||||
line_segment::LineSegment,
|
||||
point::Point,
|
||||
poly_chain::PolyChain,
|
||||
|
242
crates/fj-math/src/line.rs
Normal file
242
crates/fj-math/src/line.rs
Normal file
@ -0,0 +1,242 @@
|
||||
use crate::{Point, Scalar, Transform, Triangle, Vector};
|
||||
|
||||
/// An n-dimensional line, defined by an origin and a direction
|
||||
///
|
||||
/// The dimensionality of the line is defined by the const generic `D`
|
||||
/// parameter.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
#[repr(C)]
|
||||
pub struct Line<const D: usize> {
|
||||
origin: Point<D>,
|
||||
direction: Vector<D>,
|
||||
}
|
||||
|
||||
impl<const D: usize> Line<D> {
|
||||
/// Create a line from a point and a vector
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics, if `direction` has a length of zero.
|
||||
pub fn from_origin_and_direction(
|
||||
origin: Point<D>,
|
||||
direction: Vector<D>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
direction.magnitude() != Scalar::ZERO,
|
||||
"Can't construct `Line`. Direction is zero: {direction:?}"
|
||||
);
|
||||
|
||||
Self { origin, direction }
|
||||
}
|
||||
|
||||
/// Create a line from two points
|
||||
///
|
||||
/// Also returns the lines coordinates of the provided points on the new
|
||||
/// line.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics, if the points are coincident.
|
||||
pub fn from_points(
|
||||
points: [impl Into<Point<D>>; 2],
|
||||
) -> (Self, [Point<1>; 2]) {
|
||||
let [a, b] = points.map(Into::into);
|
||||
|
||||
let line = Self::from_origin_and_direction(a, b - a);
|
||||
let coords = [[0.], [1.]].map(Point::from);
|
||||
|
||||
(line, coords)
|
||||
}
|
||||
|
||||
/// Create a line from two points that include line coordinates
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics, if the points are coincident.
|
||||
pub fn from_points_with_line_coords(
|
||||
points: [(impl Into<Point<1>>, impl Into<Point<D>>); 2],
|
||||
) -> Self {
|
||||
let [(a_line, a_global), (b_line, b_global)] =
|
||||
points.map(|(point_line, point_global)| {
|
||||
(point_line.into(), point_global.into())
|
||||
});
|
||||
|
||||
let direction = (b_global - a_global) / (b_line - a_line).t;
|
||||
let origin = a_global + direction * -a_line.t;
|
||||
|
||||
Self::from_origin_and_direction(origin, direction)
|
||||
}
|
||||
|
||||
/// Access the origin of the line
|
||||
///
|
||||
/// The origin is a point on the line which, together with the `direction`
|
||||
/// field, defines the line fully. The origin also defines the origin of the
|
||||
/// line's 1-dimensional coordinate system.
|
||||
pub fn origin(&self) -> Point<D> {
|
||||
self.origin
|
||||
}
|
||||
|
||||
/// Access the direction of the line
|
||||
///
|
||||
/// The length of this vector defines the unit of the line's curve
|
||||
/// coordinate system. The coordinate `1.` is always where the direction
|
||||
/// vector points, from `origin`.
|
||||
pub fn direction(&self) -> Vector<D> {
|
||||
self.direction
|
||||
}
|
||||
|
||||
/// Determine if this line is coincident with another line
|
||||
///
|
||||
/// # Implementation Note
|
||||
///
|
||||
/// This method only returns `true`, if the lines are precisely coincident.
|
||||
/// This will probably not be enough going forward, but it'll do for now.
|
||||
pub fn is_coincident_with(&self, other: &Self) -> bool {
|
||||
let other_origin_is_not_on_self = {
|
||||
let a = other.origin;
|
||||
let b = self.origin;
|
||||
let c = self.origin + self.direction;
|
||||
|
||||
// The triangle is valid only, if the three points are not on the
|
||||
// same line.
|
||||
Triangle::from_points([a, b, c]).is_valid()
|
||||
};
|
||||
|
||||
if other_origin_is_not_on_self {
|
||||
return false;
|
||||
}
|
||||
|
||||
let d1 = self.direction.normalize();
|
||||
let d2 = other.direction.normalize();
|
||||
|
||||
d1 == d2 || d1 == -d2
|
||||
}
|
||||
|
||||
/// Create a new instance that is reversed
|
||||
#[must_use]
|
||||
pub fn reverse(mut self) -> Self {
|
||||
self.origin += self.direction;
|
||||
self.direction = -self.direction;
|
||||
self
|
||||
}
|
||||
|
||||
/// Convert a `D`-dimensional point to line coordinates
|
||||
///
|
||||
/// Projects the point onto the line before the conversion. This is done to
|
||||
/// make this method robust against floating point accuracy issues.
|
||||
///
|
||||
/// Callers are advised to be careful about the points they pass, as the
|
||||
/// point not being on the line, intentional or not, will never result in an
|
||||
/// error.
|
||||
pub fn point_to_line_coords(&self, point: impl Into<Point<D>>) -> Point<1> {
|
||||
Point {
|
||||
coords: self.vector_to_line_coords(point.into() - self.origin),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a `D`-dimensional vector to line coordinates
|
||||
pub fn vector_to_line_coords(
|
||||
&self,
|
||||
vector: impl Into<Vector<D>>,
|
||||
) -> Vector<1> {
|
||||
let t = vector.into().scalar_projection_onto(&self.direction)
|
||||
/ self.direction.magnitude();
|
||||
Vector::from([t])
|
||||
}
|
||||
|
||||
/// Convert a point in line coordinates into a `D`-dimensional point
|
||||
pub fn point_from_line_coords(
|
||||
&self,
|
||||
point: impl Into<Point<1>>,
|
||||
) -> Point<D> {
|
||||
self.origin + self.vector_from_line_coords(point.into().coords)
|
||||
}
|
||||
|
||||
/// Convert a vector in line coordinates into a `D`-dimensional vector
|
||||
pub fn vector_from_line_coords(
|
||||
&self,
|
||||
vector: impl Into<Vector<1>>,
|
||||
) -> Vector<D> {
|
||||
self.direction * vector.into().t
|
||||
}
|
||||
}
|
||||
|
||||
impl Line<3> {
|
||||
/// # Transform the line
|
||||
pub fn transform(&self, transform: &Transform) -> Self {
|
||||
Self::from_origin_and_direction(
|
||||
transform.transform_point(&self.origin()),
|
||||
transform.transform_vector(&self.direction()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use approx::assert_abs_diff_eq;
|
||||
|
||||
use crate::{Point, Scalar, Vector};
|
||||
|
||||
use super::Line;
|
||||
|
||||
#[test]
|
||||
fn from_points_with_line_coords() {
|
||||
let line = Line::from_points_with_line_coords([
|
||||
([0.], [0., 0.]),
|
||||
([1.], [1., 0.]),
|
||||
]);
|
||||
assert_eq!(line.origin(), Point::from([0., 0.]));
|
||||
assert_eq!(line.direction(), Vector::from([1., 0.]));
|
||||
|
||||
let line = Line::from_points_with_line_coords([
|
||||
([1.], [0., 1.]),
|
||||
([0.], [1., 1.]),
|
||||
]);
|
||||
assert_eq!(line.origin(), Point::from([1., 1.]));
|
||||
assert_eq!(line.direction(), Vector::from([-1., 0.]));
|
||||
|
||||
let line = Line::from_points_with_line_coords([
|
||||
([-1.], [0., 2.]),
|
||||
([0.], [1., 2.]),
|
||||
]);
|
||||
assert_eq!(line.origin(), Point::from([1., 2.]));
|
||||
assert_eq!(line.direction(), Vector::from([1., 0.]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_coincident_with() {
|
||||
let (line, _) = Line::from_points([[0., 0.], [1., 0.]]);
|
||||
|
||||
let (a, _) = Line::from_points([[0., 0.], [1., 0.]]);
|
||||
let (b, _) = Line::from_points([[0., 0.], [-1., 0.]]);
|
||||
let (c, _) = Line::from_points([[0., 1.], [1., 1.]]);
|
||||
|
||||
assert!(line.is_coincident_with(&a));
|
||||
assert!(line.is_coincident_with(&b));
|
||||
assert!(!line.is_coincident_with(&c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_point_to_line_coords() {
|
||||
let line = Line {
|
||||
origin: Point::from([1., 2., 3.]),
|
||||
direction: Vector::from([2., 3., 5.]),
|
||||
};
|
||||
|
||||
verify(line, -1.);
|
||||
verify(line, 0.);
|
||||
verify(line, 1.);
|
||||
verify(line, 2.);
|
||||
|
||||
fn verify(line: Line<3>, t: f64) {
|
||||
let point = line.point_from_line_coords([t]);
|
||||
let t_result = line.point_to_line_coords(point);
|
||||
|
||||
assert_abs_diff_eq!(
|
||||
Point::from([t]),
|
||||
t_result,
|
||||
epsilon = Scalar::from(1e-8)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user