Divide intersection edges from their neighbours' parameters (#1038) (#1412)

Thanks @jijinbei.
This commit is contained in:
JIJINBEI authored and GitHub committed 2026-10-16 00:27:55 -04:00
1 parent 42d4ebeb16
commit 3333f8a62b
34 files changed
+10368 -7

No files matched your search

+228
View File
@@ -0,0 +1,228 @@
use super::*;
impl<C, S> PCurve<C, S> {
/// Creates composited
#[inline(always)]
pub const fn new(curve: C, surface: S) -> PCurve<C, S> { PCurve { curve, surface } }
/// Returns the reference to the parameter curve
#[inline(always)]
pub const fn curve(&self) -> &C { &self.curve }
/// Returns the reference to the surface
#[inline(always)]
pub const fn surface(&self) -> &S { &self.surface }
/// Decompose the pcurve into its parameter curve and surface.
#[inline(always)]
pub fn decompose(self) -> (C, S) { (self.curve, self.surface) }
}
impl<C, S> ParametricCurve for PCurve<C, S>
where
C: ParametricCurve2D,
S: ParametricSurface,
S::Vector: VectorSpace<Scalar = f64>,
{
type Point = S::Point;
type Vector = S::Vector;
#[inline(always)]
fn subs(&self, t: f64) -> Self::Point {
let pt = self.curve.subs(t);
self.surface.subs(pt[0], pt[1])
}
#[inline(always)]
fn der(&self, t: f64) -> Self::Vector {
let pt = self.curve.subs(t);
let der = self.curve.der(t);
self.surface.uder(pt[0], pt[1]) * der[0] + self.surface.vder(pt[0], pt[1]) * der[1]
}
#[inline(always)]
fn parameter_range(&self) -> ParameterRange { self.curve.parameter_range() }
#[inline(always)]
fn der2(&self, t: f64) -> Self::Vector {
let pt = self.curve.subs(t);
let der = self.curve.der(t);
let der2 = self.curve.der2(t);
self.surface.uuder(pt[0], pt[1]) * der[0] * der[0]
+ self.surface.uvder(pt[0], pt[1]) * der[0] * der[1] * 2.0
+ self.surface.vvder(pt[0], pt[1]) * der[1] * der[1]
+ self.surface.uder(pt[0], pt[1]) * der2[0]
+ self.surface.vder(pt[0], pt[1]) * der2[1]
}
}
impl<C, S> BoundedCurve for PCurve<C, S>
where
C: BoundedCurve,
PCurve<C, S>: ParametricCurve,
{
}
impl<C, S> Cut for PCurve<C, S>
where
C: Cut,
S: Clone,
PCurve<C, S>: ParametricCurve,
{
fn cut(&mut self, t: f64) -> Self {
let curve = self.curve.cut(t);
Self {
curve,
surface: self.surface.clone(),
}
}
}
impl<C, S> SearchParameter<D1> for PCurve<C, S>
where
C: ParametricCurve2D + SearchParameter<D1, Point = Point2>,
S: SearchParameter<D2>,
{
type Point = <S as SearchParameter<D2>>::Point;
fn search_parameter<H: Into<SPHint1D>>(
&self,
point: Self::Point,
hint: H,
trials: usize,
) -> Option<f64> {
let hint = hint.into();
let shint = match hint {
SPHint1D::Parameter(hint) => {
let p = self.curve.subs(hint);
SPHint2D::Parameter(p.x, p.y)
}
SPHint1D::Range(x, y) => {
let p = self.curve.subs(y);
let ranges = (0..PRESEARCH_DIVISION).fold(
((p.x, p.x), (p.y, p.y)),
|((x0, x1), (y0, y1)), i| {
let t = x + (y - x) * i as f64 / PRESEARCH_DIVISION as f64;
let p = self.curve.subs(t);
(
(f64::min(x0, p.x), f64::max(x1, p.x)),
(f64::min(y0, p.y), f64::max(y1, p.y)),
)
},
);
SPHint2D::Range(ranges.0, ranges.1)
}
SPHint1D::None => SPHint2D::None,
};
let (x, y) = self.surface.search_parameter(point, shint, trials)?;
self.curve.search_parameter(Point2::new(x, y), hint, trials)
}
}
impl<C, S> SearchNearestParameter<D1> for PCurve<C, S>
where
Self: BoundedCurve,
<Self as ParametricCurve>::Point: EuclideanSpace<Scalar = f64, Diff = <Self as ParametricCurve>::Vector>
+ MetricSpace<Metric = f64>,
<Self as ParametricCurve>::Vector: InnerSpace<Scalar = f64> + Tolerance,
{
type Point = <Self as ParametricCurve>::Point;
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
point: Self::Point,
hint: H,
trials: usize,
) -> Option<f64> {
let hint = match hint.into() {
SPHint1D::Parameter(hint) => hint,
SPHint1D::Range(x, y) => {
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
}
SPHint1D::None => {
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
}
};
algo::curve::search_nearest_parameter(self, point, hint, trials)
}
}
impl<C, S> ParameterDivision1D for PCurve<C, S>
where
C: ParametricCurve2D,
S: ParametricSurface,
S::Point: EuclideanSpace<Scalar = f64> + MetricSpace<Metric = f64> + HashGen<f64>,
S::Vector: VectorSpace<Scalar = f64>,
{
type Point = S::Point;
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<S::Point>) {
algo::curve::parameter_division(self, range, tol)
}
}
impl<C, S> Invertible for PCurve<C, S>
where
C: Invertible,
S: Clone,
{
fn invert(&mut self) { self.curve.invert() }
}
impl<C: Clone, S: Clone, T> Transformed<T> for PCurve<C, S>
where S: Transformed<T>
{
#[inline(always)]
fn transform_by(&mut self, trans: T) { self.surface.transform_by(trans); }
fn transformed(&self, trans: T) -> Self {
Self {
curve: self.curve.clone(),
surface: self.surface.transformed(trans),
}
}
}
#[test]
fn pcurve_test() {
let curve = BSplineCurve::new(
KnotVec::bezier_knot(2),
vec![
Point2::new(1.0, 1.0),
Point2::new(1.0, 0.0),
Point2::new(0.0, 0.0),
],
);
let surface = BSplineSurface::new(
(KnotVec::bezier_knot(2), KnotVec::bezier_knot(1)),
vec![
vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 1.0, 0.0)],
vec![Point3::new(0.0, 0.0, 1.0), Point3::new(0.0, 1.0, 1.0)],
vec![Point3::new(1.0, 0.0, 1.0), Point3::new(1.0, 1.0, 1.0)],
],
);
let pcurve = PCurve::new(curve, surface);
assert_eq!(pcurve.range_tuple(), (0.0, 1.0));
const N: usize = 100;
for i in 0..=N {
let t = i as f64 / N as f64;
assert_near!(
pcurve.subs(t),
Point3::new(
(1.0 - t * t) * (1.0 - t * t),
(1.0 - t) * (1.0 - t),
1.0 - t * t * t * t,
),
);
assert_near!(
pcurve.der(t),
Vector3::new(4.0 * t * (t * t - 1.0), 2.0 * (t - 1.0), -4.0 * t * t * t,),
);
assert_near!(
pcurve.der2(t),
Vector3::new(4.0 * (3.0 * t * t - 1.0), 2.0, -12.0 * t * t,),
);
}
let t = 0.675;
let pt = pcurve.subs(t);
assert_near!(pcurve.search_parameter(pt, None, 100).unwrap(), t);
let pt = pt + Vector3::new(0.01, 0.06, -0.03);
assert!(pcurve.search_parameter(pt, None, 100).is_none());
let t = pcurve.search_nearest_parameter(pt, None, 100).unwrap();
assert!(pcurve.der(t).dot(pcurve.subs(t) - pt).so_small());
}
+203
View File
@@ -0,0 +1,203 @@
use super::*;
impl<C, V: Copy> ExtrudedCurve<C, V> {
/// Creates a linear extruded curve by extrusion.
#[inline(always)]
pub const fn by_extrusion(curve: C, vector: V) -> Self { Self { curve, vector } }
/// Returns the curve before extruded.
#[inline(always)]
pub const fn entity_curve(&self) -> &C { &self.curve }
/// Into the curve before revoluted.
#[inline(always)]
pub fn into_entity_curve(self) -> C { self.curve }
/// Returns the vector of extruded curve.
#[inline(always)]
pub const fn extruding_vector(&self) -> V { self.vector }
}
impl<C> ParametricSurface for ExtrudedCurve<C, C::Vector>
where
C: ParametricCurve,
C::Point: EuclideanSpace<Scalar = f64, Diff = C::Vector>,
C::Vector: VectorSpace<Scalar = f64>,
{
type Point = C::Point;
type Vector = C::Vector;
#[inline(always)]
fn subs(&self, u: f64, v: f64) -> C::Point { self.curve.subs(u) + self.vector * v }
#[inline(always)]
fn uder(&self, u: f64, _: f64) -> C::Vector { self.curve.der(u) }
#[inline(always)]
fn vder(&self, _: f64, _: f64) -> C::Vector { self.vector }
#[inline(always)]
fn uuder(&self, u: f64, _: f64) -> C::Vector { self.curve.der2(u) }
#[inline(always)]
fn uvder(&self, _: f64, _: f64) -> C::Vector { C::Vector::zero() }
#[inline(always)]
fn vvder(&self, _: f64, _: f64) -> C::Vector { C::Vector::zero() }
#[inline(always)]
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
(
self.curve.parameter_range(),
(Bound::Included(0.0), Bound::Included(1.0)),
)
}
#[inline(always)]
fn u_period(&self) -> Option<f64> { self.curve.period() }
}
impl<C: ParametricCurve3D> ParametricSurface3D for ExtrudedCurve<C, Vector3> {
#[inline(always)]
fn normal(&self, u: f64, _: f64) -> C::Vector {
self.curve.der(u).cross(self.vector).normalize()
}
}
impl<C, V> BoundedSurface for ExtrudedCurve<C, V>
where
C: BoundedCurve,
Self: ParametricSurface,
{
}
impl<C: ParameterDivision1D, V> ParameterDivision2D for ExtrudedCurve<C, V> {
#[inline(always)]
fn parameter_division(
&self,
(urange, vrange): ((f64, f64), (f64, f64)),
tol: f64,
) -> (Vec<f64>, Vec<f64>) {
(
self.curve.parameter_division(urange, tol).0,
vec![vrange.0, vrange.1],
)
}
}
impl<C: ParametricCurve2D + BoundedCurve> SearchParameter<D2> for ExtrudedCurve<C, Vector2> {
type Point = Point2;
#[inline(always)]
fn search_parameter<H: Into<SPHint2D>>(
&self,
point: Point2,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
let hint = match hint.into() {
SPHint2D::Parameter(x, y) => (x, y),
SPHint2D::Range(range0, range1) => {
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
}
SPHint2D::None => {
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
}
};
algo::surface::search_parameter2d(self, point, hint, trials)
}
}
impl<C: ParametricCurve3D + BoundedCurve> SearchParameter<D2> for ExtrudedCurve<C, Vector3> {
type Point = Point3;
#[inline(always)]
fn search_parameter<H: Into<SPHint2D>>(
&self,
point: Point3,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
let hint = match hint.into() {
SPHint2D::Parameter(x, y) => (x, y),
SPHint2D::Range(range0, range1) => {
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
}
SPHint2D::None => {
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
}
};
algo::surface::search_parameter3d(self, point, hint, trials)
}
}
impl<C: ParametricCurve3D + BoundedCurve> SearchNearestParameter<D2> for ExtrudedCurve<C, Vector3> {
type Point = Point3;
#[inline(always)]
fn search_nearest_parameter<H: Into<SPHint2D>>(
&self,
point: Point3,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
let hint = match hint.into() {
SPHint2D::Parameter(x, y) => (x, y),
SPHint2D::Range(range0, range1) => {
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
}
SPHint2D::None => {
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
}
};
algo::surface::search_nearest_parameter(self, point, hint, trials)
}
}
impl<C: Invertible> Invertible for ExtrudedCurve<C, Vector3> {
#[inline(always)]
fn invert(&mut self) { self.curve.invert() }
#[inline(always)]
fn inverse(&self) -> Self {
Self {
curve: self.curve.inverse(),
vector: self.vector,
}
}
}
impl<C: Transformed<Matrix4>> Transformed<Matrix4> for ExtrudedCurve<C, Vector3> {
fn transform_by(&mut self, trans: Matrix4) {
self.curve.transform_by(trans);
self.vector = trans.transform_vector(self.vector);
}
fn transformed(&self, trans: Matrix4) -> Self {
Self {
curve: self.curve.transformed(trans),
vector: trans.transform_vector(self.vector),
}
}
}
#[test]
fn extruded_curve_test() {
let cpts = vec![
Point3::new(0.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
];
let spts = vec![
vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 0.0, 1.0)],
vec![Point3::new(0.0, 1.0, 0.0), Point3::new(0.0, 1.0, 1.0)],
vec![Point3::new(1.0, 0.0, 0.0), Point3::new(1.0, 0.0, 1.0)],
];
let curve = BSplineCurve::new(KnotVec::bezier_knot(2), cpts);
let surface0 = ExtrudedCurve::by_extrusion(curve, Vector3::unit_z());
let surface1 = BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(1)), spts);
assert_eq!(surface0.range_tuple(), surface1.range_tuple());
const N: usize = 10;
for i in 0..=N {
for j in 0..=N {
let u = i as f64 / N as f64;
let v = j as f64 / N as f64;
assert_near!(
surface0.subs(u, v),
ParametricSurface::subs(&surface1, u, v)
);
assert_near!(surface0.uder(u, v), surface1.uder(u, v));
assert_near!(surface0.vder(u, v), surface1.vder(u, v));
assert_near!(surface0.uuder(u, v), surface1.uuder(u, v));
assert_near!(surface0.uvder(u, v), surface1.uvder(u, v));
assert_near!(surface0.vvder(u, v), surface1.vvder(u, v));
assert_near!(surface0.normal(u, v), surface1.normal(u, v));
}
}
}
@@ -0,0 +1,330 @@
use super::*;
#[doc(hidden)]
pub fn double_projection<S>(
surface0: &S,
hint0: Option<(f64, f64)>,
surface1: &S,
hint1: Option<(f64, f64)>,
mut point: Point3,
normal: Vector3,
trials: usize,
) -> Option<(Point3, Point2, Point2)>
where
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
{
#[cfg(all(test, debug_assertions))]
let mut log = Vec::new();
let mut uv0 = surface0.search_nearest_parameter(point, hint0, 10)?;
let mut uv1 = surface1.search_nearest_parameter(point, hint1, 10)?;
for _ in 0..trials {
#[cfg(all(test, debug_assertions))]
log.push((point, uv0, uv1));
uv0 = surface0.search_nearest_parameter(point, Some(uv0), 10)?;
let pt0 = surface0.subs(uv0.0, uv0.1);
uv1 = surface1.search_nearest_parameter(point, Some(uv1), 10)?;
let pt1 = surface1.subs(uv1.0, uv1.1);
if point.near(&pt0) && point.near(&pt1) && pt0.near(&pt1) {
return Some((point, Point2::from(uv0), Point2::from(uv1)));
} else {
let n0 = surface0.normal(uv0.0, uv0.1);
let n1 = surface1.normal(uv1.0, uv1.1);
let mat = Matrix3::from_cols(n0, n1, normal).transpose();
let inv = mat.invert()?;
let pt = inv * Vector3::new(pt0.dot(n0), pt1.dot(n1), point.dot(normal));
point = Point3::from_vec(pt);
}
}
#[cfg(all(test, debug_assertions))]
{
eprintln!("Newton method is not converges");
log.into_iter().for_each(|t| eprintln!("{t:?}"));
}
None
}
/// Mutable editor for `IntersectionCurve`.
#[doc(hidden)]
#[derive(Debug)]
pub struct IntersectionCurveEditor<'a, C, S> {
pub surface0: &'a mut S,
pub surface1: &'a mut S,
pub leader: &'a mut C,
pub tol: &'a mut f64,
}
impl<C, S> IntersectionCurve<C, S> {
/// This curve is a part of intersection of `self.surface0()` and `self.surface1()`.
#[inline(always)]
pub fn surface0(&self) -> &S { &self.surface0 }
/// This curve is a part of intersection of `self.surface0()` and `self.surface1()`.
#[inline(always)]
pub fn surface1(&self) -> &S { &self.surface1 }
/// Returns the polyline leading this curve.
#[inline(always)]
pub fn leader(&self) -> &C { &self.leader }
/// Returns editor for `IntersectionCurve`. This method is only for developers, do not use.
#[doc(hidden)]
#[inline(always)]
pub fn editor(&mut self) -> IntersectionCurveEditor<'_, C, S> {
IntersectionCurveEditor {
surface0: &mut self.surface0,
surface1: &mut self.surface1,
leader: &mut self.leader,
tol: &mut self.tol,
}
}
/// Change leader.
#[doc(hidden)]
#[inline(always)]
pub fn change_leader<D>(self, f: impl FnOnce(C) -> D) -> IntersectionCurve<D, S> {
IntersectionCurve {
surface0: self.surface0,
surface1: self.surface1,
leader: f(self.leader),
tol: self.tol,
}
}
/// The tolerance for generating this intersection curve.
#[inline(always)]
pub fn tolerance(&self) -> f64 { self.tol }
/// Creates intersection curve with unchecked bound. This method is only for developer of `truck`, deplicated for users.
#[inline(always)]
pub fn new_unchecked(surface0: Box<S>, surface1: Box<S>, leader: C, tol: f64) -> Self {
Self {
surface0,
surface1,
leader,
tol,
}
}
}
impl<C, S> IntersectionCurve<C, S>
where
C: ParametricCurve3D,
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
{
/// Search triple value of the point corresponding to the parameter `t`.
/// - the coordinate on 3D space
/// - the uv coordinate on `self.surface0()`
/// - the uv coordinate on `self.surface1()`
#[inline(always)]
pub fn search_triple(&self, t: f64) -> Option<(Point3, Point2, Point2)> {
double_projection(
self.surface0(),
None,
self.surface1(),
None,
self.leader.subs(t),
self.leader.der(t),
100,
)
}
}
impl<C, S> ParametricCurve for IntersectionCurve<C, S>
where
C: ParametricCurve3D,
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
{
type Point = Point3;
type Vector = Vector3;
fn subs(&self, t: f64) -> Point3 { self.search_triple(t).unwrap().0 }
fn der(&self, t: f64) -> Vector3 {
let n = self.leader.der(t);
let (_, p0, p1) = self.search_triple(t).unwrap();
let d = self
.surface0
.normal(p0.x, p0.y)
.cross(self.surface1.normal(p1.x, p1.y))
.normalize();
d * (n.dot(n) / d.dot(n))
}
/// This method is unimplemented! Should panic!!
#[inline(always)]
fn der2(&self, _: f64) -> Vector3 {
unimplemented!();
}
#[inline(always)]
fn parameter_range(&self) -> ParameterRange { self.leader.parameter_range() }
}
impl<C, S> BoundedCurve for IntersectionCurve<C, S>
where
C: ParametricCurve3D + BoundedCurve,
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
{
}
impl<C, S> ParameterDivision1D for IntersectionCurve<C, S>
where
C: ParametricCurve3D,
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
{
type Point = Point3;
// SolidCraft: the division of `algo::curve::parameter_division` (the same split points and
// the same order), but each point's projection onto the two surfaces starts from the
// parameters of the point that begins its piece instead of a search of the whole surfaces:
// that search ran for every point and made dividing an intersection edge on a large B-spline
// surface take seconds. A point the hinted projection misses is searched for as before.
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<Point3>) {
nonpositive_tolerance!(tol);
let start = self.hinted_triple(range.0, None);
let end = self.hinted_triple(range.1, None);
hinted_sub_division(self, range, (start, end), tol, 100)
}
}
// SolidCraft: a point of the curve with its parameters on both surfaces.
type Triple = (Point3, Point2, Point2);
impl<C, S> IntersectionCurve<C, S>
where
C: ParametricCurve3D,
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
{
// SolidCraft: `search_triple` started from `hint`'s parameters, falling back to the search
// from nothing (and, where that fails too, to `subs` as before). A hinted result is taken
// only within the curve's tolerance of the leader's point, where the leader lies: started
// from a neighbour (or from a periodic surface's seam), the projection can settle on another
// part of a surface, such as the far side of a small hole, which the search from nothing
// would not.
fn hinted_triple(&self, t: f64, hint: Option<Triple>) -> Triple {
let lead = self.leader.subs(t);
let near = self.tol.max(TOLERANCE);
let hinted = hint.and_then(|(_, uv0, uv1)| {
double_projection(
self.surface0(),
Some((uv0.x, uv0.y)),
self.surface1(),
Some((uv1.x, uv1.y)),
lead,
self.leader.der(t),
100,
)
.filter(|(p, _, _)| p.distance(lead) <= near)
});
match hinted.or_else(|| self.search_triple(t)) {
Some(triple) => triple,
None => (self.subs(t), Point2::origin(), Point2::origin()),
}
}
}
// SolidCraft: `algo::curve::sub_parameter_division`, carrying each end's parameters.
fn hinted_sub_division<C, S>(
curve: &IntersectionCurve<C, S>,
range: (f64, f64),
ends: (Triple, Triple),
tol: f64,
trials: usize,
) -> (Vec<f64>, Vec<Point3>)
where
C: ParametricCurve3D,
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
{
let (p0, p1) = (ends.0 .0, ends.1 .0);
let gen = p0.midpoint(p1);
let p = 0.5 + (0.2 * HashGen::hash1(gen) - 0.1);
let t = range.0 * (1.0 - p) + range.1 * p;
let mid = p0 + (p1 - p0) * p;
let dist2 = curve.hinted_triple(t, Some(ends.0)).0.distance2(mid);
if dist2 < tol * tol || trials == 0 {
(vec![range.0, range.1], vec![p0, p1])
} else {
let mid_param = (range.0 + range.1) / 2.0;
let mid_value = curve.hinted_triple(mid_param, Some(ends.0));
let (mut params, mut pts) =
hinted_sub_division(curve, (range.0, mid_param), (ends.0, mid_value), tol, trials - 1);
let _ = (params.pop(), pts.pop());
let (new_params, new_pts) =
hinted_sub_division(curve, (mid_param, range.1), (mid_value, ends.1), tol, trials - 1);
params.extend(new_params);
pts.extend(new_pts);
(params, pts)
}
}
impl<C, S> Cut for IntersectionCurve<C, S>
where
C: Cut<Point = Point3, Vector = Vector3>,
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
{
#[inline(always)]
fn cut(&mut self, t: f64) -> Self {
Self {
surface0: self.surface0.clone(),
surface1: self.surface1.clone(),
leader: self.leader.cut(t),
tol: self.tol,
}
}
}
impl<C: Invertible, S: Clone> Invertible for IntersectionCurve<C, S> {
fn invert(&mut self) { self.leader.invert(); }
}
impl<C, S> SearchParameter<D1> for IntersectionCurve<C, S>
where
C: ParametricCurve3D + SearchNearestParameter<D1, Point = Point3>,
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
{
type Point = Point3;
fn search_parameter<H: Into<SPHint1D>>(
&self,
point: Point3,
hint: H,
trials: usize,
) -> Option<f64> {
let t = self
.leader()
.search_nearest_parameter(point, hint, trials)
.unwrap();
let pt = self.subs(t);
match pt.near(&point) {
true => Some(t),
false => None,
}
}
}
/// Only derive from leading curve. Not precise.
impl<C, S> SearchNearestParameter<D1> for IntersectionCurve<C, S>
where
C: ParametricCurve3D + SearchNearestParameter<D1, Point = Point3>,
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
{
type Point = Point3;
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
point: Point3,
hint: H,
trials: usize,
) -> Option<f64> {
self.leader().search_nearest_parameter(point, hint, trials)
}
}
impl<C, S> Transformed<Matrix4> for IntersectionCurve<C, S>
where
C: Transformed<Matrix4>,
S: Transformed<Matrix4>,
{
fn transform_by(&mut self, trans: Matrix4) {
self.surface0.transform_by(trans);
self.surface1.transform_by(trans);
self.leader.transform_by(trans);
self.tol *= trans.norm_l2();
}
}
impl<C: BoundedCurve> IntersectionCurve<C, Plane> {
/// Optimizes intersection curve of [`Plane`] into [`Line`].
#[inline]
pub fn optimize(&self) -> Line<C::Point> {
let (s, t) = self.leader.range_tuple();
Line(self.leader.subs(s), self.leader.subs(t))
}
}
+149
View File
@@ -0,0 +1,149 @@
use crate::{prelude::*, *};
use std::ops::{Deref, DerefMut, Mul};
/// revolution
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
struct Revolution {
origin: Point3,
axis: Vector3,
}
/// surface constructed by revoluting a curve
/// # Examples
/// Revoluted sphere
/// ```
/// use truck_geometry::prelude::*;
/// use std::f64::consts::PI;
/// let knot_vec = KnotVec::bezier_knot(2);
/// let control_points = vec![
/// Vector4::new(1.0, 0.0, 0.0, 1.0),
/// Vector4::new(0.0, 1.0, 0.0, 0.0),
/// Vector4::new(-1.0, 0.0, 0.0, 1.0),
/// ];
/// // upper half circle on xy-plane
/// let uhcircle = NurbsCurve::new(BSplineCurve::new(knot_vec, control_points));
/// // sphere constructed by revolute circle
/// let sphere = RevolutedCurve::by_revolution(
/// uhcircle, Point3::origin(), Vector3::unit_x(),
/// );
/// const N: usize = 30;
/// for i in 0..=N {
/// for j in 0..=N {
/// let u = i as f64 / N as f64;
/// let v = 2.0 * PI * j as f64 / N as f64;
/// let pt: Vector3 = sphere.subs(u, v).to_vec();
/// assert_near2!(pt.magnitude2(), 1.0);
/// assert_near!(pt, sphere.normal(u, v));
/// }
/// }
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct RevolutedCurve<C> {
curve: C,
revolution: Revolution,
}
/// Linearly extruded curve
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct ExtrudedCurve<C, V> {
curve: C,
vector: V,
}
/// invertible and transformable geometric element
/// # Examples
/// Curve processing example
/// ```
/// use truck_geometry::prelude::*;
/// let curve: BSplineCurve<Point3> = BSplineCurve::new(
/// KnotVec::bezier_knot(2),
/// vec![
/// Point3::new(0.0, 0.0, 0.0),
/// Point3::new(0.0, 0.0, 1.0),
/// Point3::new(1.0, 0.0, 0.0),
/// ],
/// );
/// let mut processed = Processor::<_, Matrix4>::new(curve.clone());
///
/// // both curves are the same curve
/// const N: usize = 100;
/// for i in 0..=N {
/// let t = i as f64 / N as f64;
/// assert_eq!(curve.subs(t), processed.subs(t));
/// }
///
/// // Processed curve can inverted!
/// processed.invert();
/// for i in 0..=N {
/// let t = i as f64 / N as f64;
/// assert_eq!(curve.subs(1.0 - t), processed.subs(t));
/// }
/// ```
/// Surface processing example
/// ```
/// use truck_geometry::prelude::*;
/// use std::f64::consts::PI;
///
/// let sphere = Sphere::new(Point3::new(1.0, 2.0, 3.0), 2.45);
/// let mut processed = Processor::<_, Matrix4>::new(sphere);
///
/// // both surfaces are the same surface
/// const N: usize = 100;
/// for i in 0..=N {
/// for j in 0..=N {
/// let u = PI * i as f64 / N as f64;
/// let v = 2.0 * PI * j as f64 / N as f64;
/// assert_eq!(sphere.subs(u, v), processed.subs(u, v));
/// }
/// }
///
/// // Processed surface can be inverted!
/// // Here, "invert surface" means swap (u, v)-axes.
/// processed.invert();
/// for i in 0..=N {
/// for j in 0..=N {
/// let u = PI * i as f64 / N as f64;
/// let v = 2.0 * PI * j as f64 / N as f64;
/// assert_eq!(sphere.subs(u, v), processed.subs(v, u));
/// }
/// }
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Processor<E, T> {
entity: E,
transform: T,
orientation: bool,
}
/// The composited maps
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct PCurve<C, S> {
curve: C,
surface: S,
}
/// Intersection curve between two surfaces.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IntersectionCurve<C, S> {
// Considering rotational surfaces, we can consider the case
// where the class `S` holds the curve `C` as a variable.
surface0: Box<S>,
surface1: Box<S>,
leader: C,
tol: f64,
}
/// trimmed curve for parametric curve
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct TrimmedCurve<C> {
curve: C,
range: (f64, f64),
}
mod curve_on_surface;
mod extruded_curve;
mod intersection_curve;
mod processor;
mod revolved_curve;
mod trimmied_curve;
pub use intersection_curve::double_projection;
+658
View File
@@ -0,0 +1,658 @@
use super::*;
impl<E, T: One> Processor<E, T> {
/// Creates new processor
#[inline(always)]
pub fn new(entity: E) -> Processor<E, T> {
Processor {
entity,
transform: T::one(),
orientation: true,
}
}
/// Returns the reference of entity
#[inline(always)]
pub const fn entity(&self) -> &E { &self.entity }
/// Returns the reference of transform
#[inline(always)]
pub const fn transform(&self) -> &T { &self.transform }
/// Returns the orientation of surface
#[inline(always)]
pub const fn orientation(&self) -> bool { self.orientation }
#[inline(always)]
fn sign(&self) -> f64 {
match self.orientation {
true => 1.0,
false => -1.0,
}
}
/// apply the function to the entity geometry
#[inline(always)]
pub fn map<G, F: FnOnce(E) -> G>(self, f: F) -> Processor<G, T> {
Processor {
entity: f(self.entity),
transform: self.transform,
orientation: self.orientation,
}
}
/// apply the function to the entity geometry
#[inline(always)]
pub fn map_ref<G, F: FnOnce(&E) -> G>(&self, f: F) -> Processor<G, T>
where T: Copy {
Processor {
entity: f(&self.entity),
transform: self.transform,
orientation: self.orientation,
}
}
/// apply the transform and inverse
pub fn constract(self) -> E
where E: Transformed<T> + Invertible {
let mut res = self.entity;
res.transform_by(self.transform);
if !self.orientation {
res.invert();
}
res
}
}
impl<E: Clone, T: Clone> Invertible for Processor<E, T> {
#[inline(always)]
fn invert(&mut self) { self.orientation = !self.orientation; }
#[inline(always)]
fn inverse(&self) -> Self {
Processor {
entity: self.entity.clone(),
transform: self.transform.clone(),
orientation: !self.orientation,
}
}
}
impl<C: BoundedCurve, T> Processor<C, T> {
#[inline(always)]
fn get_curve_parameter(&self, t: f64) -> f64 {
let (t0, t1) = self.range_tuple();
match self.orientation {
true => t,
false => t0 + t1 - t,
}
}
}
impl<C, T> ParametricCurve for Processor<C, T>
where
C: BoundedCurve,
C::Point: EuclideanSpace<Diff = C::Vector>,
C::Vector: VectorSpace<Scalar = f64>,
T: Transform<C::Point> + Clone,
{
type Point = C::Point;
type Vector = C::Vector;
#[inline(always)]
fn subs(&self, t: f64) -> C::Point {
let t = self.get_curve_parameter(t);
self.transform.transform_point(self.entity.subs(t))
}
#[inline(always)]
fn der(&self, t: f64) -> Self::Vector {
let t = self.get_curve_parameter(t);
self.transform.transform_vector(self.entity.der(t)) * self.sign()
}
#[inline(always)]
fn der2(&self, t: f64) -> Self::Vector {
let t = self.get_curve_parameter(t);
self.transform.transform_vector(self.entity.der2(t))
}
#[inline(always)]
fn parameter_range(&self) -> ParameterRange { self.entity.parameter_range() }
#[inline(always)]
fn period(&self) -> Option<f64> { self.entity.period() }
}
impl<C, T> BoundedCurve for Processor<C, T>
where
C: BoundedCurve,
C::Point: EuclideanSpace<Diff = C::Vector>,
C::Vector: VectorSpace<Scalar = f64>,
T: Transform<C::Point> + Clone,
{
}
impl<C, T> Cut for Processor<C, T>
where
C: BoundedCurve + Cut,
C::Point: EuclideanSpace<Diff = C::Vector>,
C::Vector: VectorSpace<Scalar = f64>,
T: Transform<C::Point> + Clone,
{
fn cut(&mut self, t: f64) -> Self {
let t = self.get_curve_parameter(t);
let mut entity = self.entity.cut(t);
if !self.orientation {
std::mem::swap(&mut entity, &mut self.entity);
}
Self {
entity,
transform: self.transform.clone(),
orientation: self.orientation,
}
}
}
impl<S, T> ParametricSurface for Processor<S, T>
where
S: ParametricSurface,
S::Point: EuclideanSpace<Scalar = f64, Diff = S::Vector>,
T: Transform<S::Point> + SquareMatrix<Scalar = f64> + Clone,
{
type Point = S::Point;
type Vector = S::Vector;
#[inline(always)]
fn subs(&self, u: f64, v: f64) -> Self::Point {
match self.orientation {
true => self.transform.transform_point(self.entity.subs(u, v)),
false => self.transform.transform_point(self.entity.subs(v, u)),
}
}
#[inline(always)]
fn uder(&self, u: f64, v: f64) -> Self::Vector {
match self.orientation {
true => self.transform.transform_vector(self.entity.uder(u, v)),
false => self.transform.transform_vector(self.entity.vder(v, u)),
}
}
#[inline(always)]
fn vder(&self, u: f64, v: f64) -> Self::Vector {
match self.orientation {
true => self.transform.transform_vector(self.entity.vder(u, v)),
false => self.transform.transform_vector(self.entity.uder(v, u)),
}
}
#[inline(always)]
fn uuder(&self, u: f64, v: f64) -> Self::Vector {
match self.orientation {
true => self.transform.transform_vector(self.entity.uuder(u, v)),
false => self.transform.transform_vector(self.entity.vvder(v, u)),
}
}
#[inline(always)]
fn uvder(&self, u: f64, v: f64) -> Self::Vector {
match self.orientation {
true => self.transform.transform_vector(self.entity.uvder(u, v)),
false => self.transform.transform_vector(self.entity.uvder(v, u)),
}
}
#[inline(always)]
fn vvder(&self, u: f64, v: f64) -> Self::Vector {
match self.orientation {
true => self.transform.transform_vector(self.entity.vvder(u, v)),
false => self.transform.transform_vector(self.entity.uuder(v, u)),
}
}
#[inline(always)]
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
let (urange, vrange) = self.entity.parameter_range();
match self.orientation {
true => (urange, vrange),
false => (vrange, urange),
}
}
#[inline(always)]
fn u_period(&self) -> Option<f64> {
match self.orientation {
true => self.entity.u_period(),
false => self.entity.v_period(),
}
}
#[inline(always)]
fn v_period(&self) -> Option<f64> {
match self.orientation {
true => self.entity.v_period(),
false => self.entity.u_period(),
}
}
}
impl<S, T> ParametricSurface3D for Processor<S, T>
where
S: ParametricSurface3D,
T: Transform<Point3> + SquareMatrix<Scalar = f64> + Clone,
{
#[inline(always)]
fn normal(&self, u: f64, v: f64) -> Self::Vector {
let transtrans = self.transform.transpose();
let n = match self.orientation {
true => self.entity.normal(u, v),
false => -self.entity.normal(v, u),
};
let n = transtrans
.inverse_transform_vector(n)
.expect("invalid transform");
(n / self.transform.determinant()).normalize()
}
}
impl<S, T> BoundedSurface for Processor<S, T>
where
S: BoundedSurface<Point = Point3, Vector = Vector3>,
T: Transform<S::Point> + SquareMatrix<Scalar = f64> + Clone,
{
}
impl<E, T> Deref for Processor<E, T> {
type Target = E;
#[inline(always)]
fn deref(&self) -> &E { &self.entity }
}
impl<E, T> DerefMut for Processor<E, T> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut E { &mut self.entity }
}
impl<E, T> Transformed<T> for Processor<E, T>
where
T: Mul<T, Output = T> + Copy,
E: Clone,
{
#[inline(always)]
fn transform_by(&mut self, trans: T) { self.transform = trans * self.transform; }
#[inline(always)]
fn transformed(&self, trans: T) -> Self {
Self {
entity: self.entity.clone(),
transform: trans * self.transform,
orientation: self.orientation,
}
}
}
impl<E, T, C> IncludeCurve<C> for Processor<E, T>
where
C: ParametricCurve + Transformed<T> + Clone,
C::Point: EuclideanSpace,
E: IncludeCurve<C>,
T: Transform<C::Point>,
{
fn include(&self, curve: &C) -> bool {
let inv = self
.transform
.inverse_transform()
.expect("irregular transform");
let curve = curve.clone().transformed(inv);
self.entity.include(&curve)
}
}
impl<C> ParameterDivision1D for Processor<C, Matrix3>
where C: ParameterDivision1D<Point = Point2> + BoundedCurve<Point = Point2>
{
type Point = Point2;
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<Self::Point>) {
let a = self.transform;
let range = match self.orientation {
true => range,
false => (
self.get_curve_parameter(range.1),
self.get_curve_parameter(range.0),
),
};
let (_, k, _) = a
.iwasawa_decomposition()
.expect("transform matrix must be invertible!");
let n = f64::abs(k[0][0])
.max(f64::abs(k[1][1]))
.max(f64::abs(k[2][2]));
let (mut params, mut points) = self.entity.parameter_division(range, tol / n);
points
.iter_mut()
.for_each(|pt| *pt = a.transform_point(*pt));
if !self.orientation {
params
.iter_mut()
.for_each(|t| *t = self.get_curve_parameter(*t));
points.reverse();
}
(params, points)
}
}
impl<C> ParameterDivision1D for Processor<C, Matrix4>
where C: ParameterDivision1D<Point = Point3> + BoundedCurve<Point = Point3>
{
type Point = Point3;
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<Self::Point>) {
let a = self.transform;
let range = match self.orientation {
true => range,
false => (
self.get_curve_parameter(range.1),
self.get_curve_parameter(range.0),
),
};
let (_, k, _) = a
.iwasawa_decomposition()
.expect("transform matrix must be invertible!");
let n = f64::abs(k[0][0])
.max(f64::abs(k[1][1]))
.max(f64::abs(k[2][2]))
/ f64::abs(k[3][3]);
let (mut params, mut points) = self.entity.parameter_division(range, tol / n);
points
.iter_mut()
.for_each(|pt| *pt = a.transform_point(*pt));
if !self.orientation {
params
.iter_mut()
.for_each(|t| *t = self.get_curve_parameter(*t));
points.reverse();
}
(params, points)
}
}
impl<S: ParameterDivision2D> ParameterDivision2D for Processor<S, Matrix3> {
fn parameter_division(
&self,
range: ((f64, f64), (f64, f64)),
tol: f64,
) -> (Vec<f64>, Vec<f64>) {
let a = self.transform;
let range = match self.orientation {
true => range,
false => (range.1, range.0),
};
let (_, k, _) = a
.iwasawa_decomposition()
.expect("transform matrix must be invertible!");
let n = f64::abs(k[0][0])
.max(f64::abs(k[1][1]))
.max(f64::abs(k[2][2]));
let (udiv, vdiv) = self.entity.parameter_division(range, tol / n);
match self.orientation {
true => (udiv, vdiv),
false => (vdiv, udiv),
}
}
}
impl<S: ParameterDivision2D> ParameterDivision2D for Processor<S, Matrix4> {
fn parameter_division(
&self,
range: ((f64, f64), (f64, f64)),
tol: f64,
) -> (Vec<f64>, Vec<f64>) {
let a = self.transform;
let range = match self.orientation {
true => range,
false => (range.1, range.0),
};
let (_, k, _) = a
.iwasawa_decomposition()
.expect("transform matrix must be invertible!");
let n = f64::abs(k[0][0])
.max(f64::abs(k[1][1]))
.max(f64::abs(k[2][2]))
/ f64::abs(k[3][3]);
let (udiv, vdiv) = self.entity.parameter_division(range, tol / n);
match self.orientation {
true => (udiv, vdiv),
false => (vdiv, udiv),
}
}
}
impl<E, T> SearchParameter<D1> for Processor<E, T>
where
E: SearchParameter<D1> + BoundedCurve,
<E as SearchParameter<D1>>::Point: EuclideanSpace,
T: Transform<<E as SearchParameter<D1>>::Point>,
{
type Point = <E as SearchParameter<D1>>::Point;
fn search_parameter<H: Into<SPHint1D>>(
&self,
point: <E as SearchParameter<D1>>::Point,
hint: H,
trials: usize,
) -> Option<f64> {
let inv = self.transform.inverse_transform().unwrap();
let t = self
.entity
.search_parameter(inv.transform_point(point), hint, trials)?;
Some(self.get_curve_parameter(t))
}
}
impl<E, T> SearchParameter<D2> for Processor<E, T>
where
E: SearchParameter<D2>,
E::Point: EuclideanSpace,
T: Transform<E::Point>,
{
type Point = E::Point;
fn search_parameter<H: Into<SPHint2D>>(
&self,
point: E::Point,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
let inv = self.transform.inverse_transform().unwrap();
let (u, v) = self
.entity
.search_parameter(inv.transform_point(point), hint, trials)?;
match self.orientation {
true => Some((u, v)),
false => Some((v, u)),
}
}
}
impl<P, E, T> SearchNearestParameter<D1> for Processor<E, T>
where
E: BoundedCurve<Point = P> + SearchNearestParameter<D1, Point = P>,
P: EuclideanSpace<Scalar = f64, Diff = E::Vector>,
E::Vector: InnerSpace<Scalar = f64> + Tolerance,
T: Transform<P> + Clone,
{
type Point = P;
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
point: Self::Point,
hint: H,
trials: usize,
) -> Option<f64> {
let inv = self.transform.inverse_transform().unwrap();
let hint =
self.entity
.search_nearest_parameter(inv.transform_point(point), hint, trials)?;
let hint = self.get_curve_parameter(hint);
algo::curve::search_nearest_parameter(self, point, hint, trials)
}
}
impl<P, E, T> SearchNearestParameter<D2> for Processor<E, T>
where
E: ParametricSurface<Point = P> + SearchNearestParameter<D2, Point = P>,
P: EuclideanSpace<Scalar = f64, Diff = E::Vector>,
E::Vector: InnerSpace<Scalar = f64> + Tolerance,
T: Transform<P> + SquareMatrix<Scalar = f64> + Clone,
{
type Point = P;
fn search_nearest_parameter<H: Into<SPHint2D>>(
&self,
point: Self::Point,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
let inv = self.transform.inverse_transform().unwrap();
let hint =
self.entity
.search_nearest_parameter(inv.transform_point(point), hint, trials)?;
let hint = match self.orientation {
true => hint,
false => (hint.1, hint.0),
};
algo::surface::search_nearest_parameter(self, point, hint, trials)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn exec_compatible_with_bspcurve() {
const DEGREE: usize = 3;
const DIVISION: usize = 4;
let knot_vec = KnotVec::uniform_knot(DEGREE, DIVISION);
let control_points: Vec<Point3> = (0..DEGREE + DIVISION)
.map(|i| Point3::new(i as f64, 20.0 * rand::random::<f64>() - 10.0, 0.0))
.collect();
let mut curve = BSplineCurve::new(knot_vec, control_points);
let mut processor = Processor::new(curve.clone());
let mat = Matrix3::new(
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
);
if mat.determinant().so_small() {
println!("omitted: {mat:?}");
return;
}
curve.transform_by(mat);
processor.transform_by(mat);
assert_eq!(curve.parameter_range(), processor.parameter_range());
const N: usize = 100;
for i in 0..=N {
let t = i as f64 / N as f64;
assert_near!(ParametricCurve::subs(&curve, t), processor.subs(t));
assert_near!(ParametricCurve::der(&curve, t), processor.der(t));
assert_near!(ParametricCurve::der2(&curve, t), processor.der2(t));
}
curve.invert();
processor.invert();
assert_eq!(curve.parameter_range(), processor.parameter_range());
for i in 0..=N {
let t = i as f64 / N as f64;
assert_near!(ParametricCurve::subs(&curve, t), processor.subs(t));
assert_near!(ParametricCurve::der(&curve, t), processor.der(t));
assert_near!(ParametricCurve::der2(&curve, t), processor.der2(t));
}
}
#[test]
fn compatible_with_bspcurve() { (0..10).for_each(|_| exec_compatible_with_bspcurve()) }
fn exec_compatible_with_bspsurface() {
const DEGREE: usize = 3;
const DIVISION: usize = 4;
let knot_vec = KnotVec::uniform_knot(DEGREE, DIVISION);
let knot_vecs = (knot_vec.clone(), knot_vec);
let control_points: Vec<Vec<Point3>> = (0..DEGREE + DIVISION)
.map(|i| {
(0..DEGREE + DIVISION)
.map(|j| Point3::new(i as f64, j as f64, 20.0 * rand::random::<f64>() - 10.0))
.collect()
})
.collect();
let mut surface = BSplineSurface::new(knot_vecs, control_points);
let mut processor = Processor::new(surface.clone());
let mat = Matrix3::new(
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
4.0 * rand::random::<f64>() - 2.0,
);
if mat.determinant().so_small() {
println!("omitted: {mat:?}");
return;
}
surface.transform_by(mat);
processor.transform_by(mat);
assert_eq!(surface.range_tuple(), processor.range_tuple());
const N: usize = 30;
for i in 0..=N {
for j in 0..=N {
let u = i as f64 / N as f64;
let v = j as f64 / N as f64;
let pt0 = ParametricSurface::subs(&surface, u, v);
let pt1 = processor.subs(u, v);
assert_near!(pt0, pt1);
let uder0 = surface.uder(u, v);
let uder1 = processor.uder(u, v);
assert_near!(uder0, uder1);
let vder0 = surface.vder(u, v);
let vder1 = processor.vder(u, v);
assert_near!(vder0, vder1);
let uuder0 = surface.uuder(u, v);
let uuder1 = processor.uuder(u, v);
assert_near!(uuder0, uuder1);
let uvder0 = surface.uvder(u, v);
let uvder1 = processor.uvder(u, v);
assert_near!(uvder0, uvder1);
let vvder0 = surface.vvder(u, v);
let vvder1 = processor.vvder(u, v);
assert_near!(vvder0, vvder1);
let n0 = surface.normal(u, v);
let n1 = processor.normal(u, v);
assert_near!(n0, n1);
}
}
surface.swap_axes();
processor.invert();
assert_eq!(surface.range_tuple(), processor.range_tuple());
for i in 0..=N {
for j in 0..=N {
let u = i as f64 / N as f64;
let v = j as f64 / N as f64;
let pt0 = ParametricSurface::subs(&surface, u, v);
let pt1 = processor.subs(u, v);
assert_near!(pt0, pt1);
let uder0 = surface.uder(u, v);
let uder1 = processor.uder(u, v);
assert_near!(uder0, uder1);
let vder0 = surface.vder(u, v);
let vder1 = processor.vder(u, v);
assert_near!(vder0, vder1);
let uuder0 = surface.uuder(u, v);
let uuder1 = processor.uuder(u, v);
assert_near!(uuder0, uuder1);
let uvder0 = surface.uvder(u, v);
let uvder1 = processor.uvder(u, v);
assert_near!(uvder0, uvder1);
let vvder0 = surface.vvder(u, v);
let vvder1 = processor.vvder(u, v);
assert_near!(vvder0, vvder1);
let n0 = surface.normal(u, v);
let n1 = processor.normal(u, v);
assert_near!(n0, n1);
}
}
}
#[test]
fn compatible_with_bspsurface() { (0..3).for_each(|_| exec_compatible_with_bspsurface()) }
}
+689
View File
@@ -0,0 +1,689 @@
use super::*;
use std::f64::consts::PI;
impl Revolution {
fn new(origin: Point3, axis: Vector3) -> Self {
Self {
origin,
axis: axis.normalize(),
}
}
#[inline(always)]
fn point_rotation_matrix(self, v: f64) -> Matrix4 {
Matrix4::from_translation(self.origin.to_vec())
* Matrix4::from_axis_angle(self.axis, Rad(v))
* Matrix4::from_translation(-self.origin.to_vec())
}
#[inline(always)]
fn vector_rotation_matrix(self, v: f64) -> Matrix4 {
Matrix4::from_axis_angle(self.axis, Rad(v))
}
#[inline(always)]
fn derivation_rotation_matrix(self, v: f64) -> Matrix4 {
let n = self.axis;
Matrix3::new(
n[0] * n[0] * f64::sin(v) - f64::sin(v),
n[0] * n[1] * f64::sin(v) + n[2] * f64::cos(v),
n[0] * n[2] * f64::sin(v) - n[1] * f64::cos(v),
n[0] * n[1] * f64::sin(v) - n[2] * f64::cos(v),
n[1] * n[1] * f64::sin(v) - f64::sin(v),
n[1] * n[2] * f64::sin(v) + n[0] * f64::cos(v),
n[0] * n[2] * f64::sin(v) + n[1] * f64::cos(v),
n[1] * n[2] * f64::sin(v) - n[0] * f64::cos(v),
n[2] * n[2] * f64::sin(v) - f64::sin(v),
)
.into()
}
#[inline(always)]
fn invert(&mut self) { self.axis *= -1.0; }
#[inline(always)]
fn inverse(mut self) -> Self {
self.axis *= -1.0;
self
}
#[inline(always)]
fn contains(self, p: Point3) -> bool { (p - self.origin).cross(self.axis).so_small() }
#[inline(always)]
fn proj_point(&self, p: Point3) -> Point2 {
let r = p - self.origin;
let z = r.dot(self.axis);
let h = r - z * self.axis;
Point2::new(z, h.magnitude())
}
#[inline(always)]
fn proj_vector(&self, p: Point3, v: Vector3) -> Vector2 {
let r = self.proj_point(p).y;
let vz = v.dot(self.axis);
let vxy = v - vz * self.axis;
let vq = (p - self.origin).dot(vxy) / r;
Vector2::new(vz, vq)
}
#[inline(always)]
fn proj_vector2(&self, p: Point3, v: Vector3, v2: Vector3) -> Vector2 {
let r = self.proj_point(p).y;
let v2z = v2.dot(self.axis);
let v2xy = v2 - v2z * self.axis;
let vz = v.dot(self.axis);
let vxy = v - vz * self.axis;
let a = (vxy.dot(vxy) + (p - self.origin).dot(v2xy)) / r;
let b = f64::powi((p - self.origin).dot(vxy), 2) / (r * r * r);
Vector2::new(v2z, a - b)
}
#[inline(always)]
fn proj_angle(&self, p: Point3, q: Point3) -> f64 {
let (p, q) = (p - self.origin, q - self.origin);
let hp = (p - p.dot(self.axis) * self.axis).normalize();
let hq = (q - q.dot(self.axis) * self.axis).normalize();
let t = f64::acos(f64::clamp(hp.dot(hq), -1.0, 1.0));
match hp.cross(hq).dot(self.axis) < 0.0 {
false => t,
true => 2.0 * PI - t,
}
}
}
impl<C: ParametricCurve3D> ParametricSurface for RevolutedCurve<C> {
type Point = Point3;
type Vector = Vector3;
#[inline(always)]
fn subs(&self, u: f64, v: f64) -> Point3 {
let mat = self.revolution.point_rotation_matrix(v);
mat.transform_point(self.curve.subs(u))
}
#[inline(always)]
fn uder(&self, u: f64, v: f64) -> Vector3 {
let mat = self.revolution.vector_rotation_matrix(v);
mat.transform_vector(self.curve.der(u))
}
#[inline(always)]
fn vder(&self, u: f64, v: f64) -> Vector3 {
let pt = self.curve.subs(u);
let radius = self.axis().cross(pt - self.origin());
let mat = self.revolution.vector_rotation_matrix(v);
mat.transform_vector(radius)
}
#[inline(always)]
fn uuder(&self, u: f64, v: f64) -> Vector3 {
let mat = self.revolution.vector_rotation_matrix(v);
mat.transform_vector(self.curve.der2(u))
}
#[inline(always)]
fn vvder(&self, u: f64, v: f64) -> Vector3 {
let pt = self.curve.subs(u);
let z = self.revolution.proj_point(pt).x;
let radius = pt - self.origin() - z * self.axis();
let mat = self.revolution.vector_rotation_matrix(v);
-mat.transform_vector(radius)
}
#[inline(always)]
fn uvder(&self, u: f64, v: f64) -> Vector3 {
let mat = self.revolution.derivation_rotation_matrix(v);
mat.transform_vector(self.curve.der(u))
}
#[inline(always)]
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
(
self.curve.parameter_range(),
(Bound::Included(0.0), Bound::Excluded(2.0 * PI)),
)
}
#[inline(always)]
fn u_period(&self) -> Option<f64> { self.curve.period() }
#[inline(always)]
fn v_period(&self) -> Option<f64> { Some(2.0 * PI) }
}
impl<C: ParametricCurve3D + BoundedCurve> ParametricSurface3D for RevolutedCurve<C> {
#[inline(always)]
fn normal(&self, u: f64, v: f64) -> Vector3 {
let (u0, u1) = self.curve.range_tuple();
let (uder, vder) = if u.near(&u0) {
let pt = self.curve.subs(u);
let radius = self.axis().cross(pt - self.origin());
if radius.so_small() {
let uder = self.curve.der(u);
(uder, self.axis().cross(uder))
} else {
(self.uder(u, v), self.vder(u, v))
}
} else if u.near(&u1) {
let pt = self.curve.subs(u);
let radius = self.axis().cross(pt - self.origin());
if radius.so_small() {
let uder = self.curve.der(u);
(uder, uder.cross(self.axis()))
} else {
(self.uder(u, v), self.vder(u, v))
}
} else {
(self.uder(u, v), self.vder(u, v))
};
uder.cross(vder).normalize()
}
}
impl<C: ParametricCurve3D + BoundedCurve> BoundedSurface for RevolutedCurve<C> {}
impl<C: Clone> Invertible for RevolutedCurve<C> {
#[inline(always)]
fn invert(&mut self) { self.revolution.invert() }
#[inline(always)]
fn inverse(&self) -> Self {
RevolutedCurve {
curve: self.curve.clone(),
revolution: self.revolution.inverse(),
}
}
}
#[derive(Clone, Debug)]
struct ProjectedCurve<C> {
curve: C,
revolution: Revolution,
}
impl<C: ParametricCurve3D> ParametricCurve for ProjectedCurve<C> {
type Point = Point2;
type Vector = Vector2;
#[inline(always)]
fn subs(&self, t: f64) -> Self::Point { self.revolution.proj_point(self.curve.subs(t)) }
#[inline(always)]
fn der(&self, t: f64) -> Self::Vector {
self.revolution
.proj_vector(self.curve.subs(t), self.curve.der(t))
}
#[inline(always)]
fn der2(&self, t: f64) -> Self::Vector {
self.revolution
.proj_vector2(self.curve.subs(t), self.curve.der(t), self.curve.der2(t))
}
#[inline(always)]
fn parameter_range(&self) -> ParameterRange { self.curve.parameter_range() }
#[inline(always)]
fn period(&self) -> Option<f64> { self.curve.period() }
}
impl<C: ParametricCurve3D + BoundedCurve> BoundedCurve for ProjectedCurve<C> {}
impl<C: ParametricCurve3D + BoundedCurve> SearchParameter<D1> for ProjectedCurve<C> {
type Point = Point2;
fn search_parameter<H: Into<SPHint1D>>(
&self,
point: Self::Point,
hint: H,
trials: usize,
) -> Option<f64> {
let hint = match hint.into() {
SPHint1D::Parameter(t) => t,
SPHint1D::Range(x, y) => {
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
}
SPHint1D::None => {
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
}
};
algo::curve::search_parameter(self, point, hint, trials)
}
}
impl<C: ParametricCurve3D + BoundedCurve> SearchNearestParameter<D1> for ProjectedCurve<C> {
type Point = Point2;
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
point: Self::Point,
hint: H,
trials: usize,
) -> Option<f64> {
let hint = match hint.into() {
SPHint1D::Parameter(t) => t,
SPHint1D::Range(x, y) => {
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
}
SPHint1D::None => {
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
}
};
algo::curve::search_nearest_parameter(self, point, hint, trials)
}
}
impl<C> RevolutedCurve<C> {
/// Creates a surface by revoluting a curve.
#[inline(always)]
pub fn by_revolution(curve: C, origin: Point3, axis: Vector3) -> Self {
RevolutedCurve {
curve,
revolution: Revolution::new(origin, axis),
}
}
/// Returns the curve before revoluted.
#[inline(always)]
pub const fn entity_curve(&self) -> &C { &self.curve }
/// Into the curve before revoluted.
#[inline(always)]
pub fn into_entity_curve(self) -> C { self.curve }
/// Returns origin of revolution
#[inline(always)]
pub const fn origin(&self) -> Point3 { self.revolution.origin }
/// Returns axis of revolution
#[inline(always)]
pub const fn axis(&self) -> Vector3 { self.revolution.axis }
}
impl<C: ParametricCurve3D + BoundedCurve> RevolutedCurve<C> {
/// Returns true if the front point of the curve is on the axis of rotation.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let line = BSplineCurve::new(
/// KnotVec::bezier_knot(1),
/// vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 0.0, 1.0)],
/// );
/// let surface0 = RevolutedCurve::by_revolution(line.clone(), Point3::origin(), Vector3::unit_y());
/// assert!(surface0.is_front_fixed());
/// let surface1 = RevolutedCurve::by_revolution(line, Point3::new(1.0, 0.0, 0.0), Vector3::unit_y());
/// assert!(!surface1.is_front_fixed());
/// ```
#[inline(always)]
pub fn is_front_fixed(&self) -> bool { self.revolution.contains(self.curve.front()) }
/// Returns true if the back point of the curve is on the axis of rotation.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let line = BSplineCurve::new(
/// KnotVec::bezier_knot(1),
/// vec![Point3::new(0.0, 0.0, 1.0), Point3::new(0.0, 0.0, 0.0)],
/// );
/// let surface0 = RevolutedCurve::by_revolution(line.clone(), Point3::origin(), Vector3::unit_y());
/// assert!(surface0.is_back_fixed());
/// let surface1 = RevolutedCurve::by_revolution(line, Point3::new(1.0, 0.0, 0.0), Vector3::unit_y());
/// assert!(!surface1.is_back_fixed());
/// ```
#[inline(always)]
pub fn is_back_fixed(&self) -> bool { self.revolution.contains(self.curve.back()) }
}
impl<C: ParametricCurve3D + BoundedCurve> SearchParameter<D2> for RevolutedCurve<C> {
type Point = Point3;
fn search_parameter<H: Into<SPHint2D>>(
&self,
point: Point3,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
let (t0, t1) = self.curve.range_tuple();
if self.is_front_fixed() && self.curve.front().near(&point) {
match hint.into() {
SPHint2D::Parameter(_, y) => Some((t0, y)),
SPHint2D::Range((_, y), _) => Some((t0, y)),
SPHint2D::None => Some((t0, 0.0)),
}
} else if self.is_back_fixed() && self.curve.back().near(&point) {
match hint.into() {
SPHint2D::Parameter(_, y) => Some((t1, y)),
SPHint2D::Range(_, (_, y)) => Some((t1, y)),
SPHint2D::None => Some((t1, 2.0 * PI)),
}
} else {
let proj_curve = ProjectedCurve {
curve: &self.curve,
revolution: self.revolution,
};
let p = self.revolution.proj_point(point);
let hint0 = match hint.into() {
SPHint2D::Parameter(x, _) => SPHint1D::Parameter(x),
SPHint2D::Range((x0, _), (x1, _)) => SPHint1D::Range(x0, x1),
SPHint2D::None => SPHint1D::None,
};
let t = proj_curve.search_parameter(p, hint0, trials)?;
let p = self.curve.subs(t);
let ang = self.revolution.proj_angle(p, point);
match self.subs(t, ang).near(&point) {
true => Some((t, ang)),
false => None,
}
}
}
}
impl<C: ParametricCurve3D + BoundedCurve> SearchNearestParameter<D2> for RevolutedCurve<C> {
type Point = Point3;
fn search_nearest_parameter<H: Into<SPHint2D>>(
&self,
point: Point3,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
let (t0, t1) = self.curve.range_tuple();
let on_axis = move |o: Point3, normal: Vector3| {
let op = point - o;
op.cross(self.revolution.axis).so_small() && op.dot(normal) >= 0.0
};
if self.is_front_fixed() && on_axis(self.curve.front(), self.normal(t0, 0.0)) {
match hint.into() {
SPHint2D::Parameter(_, y) => Some((t0, y)),
SPHint2D::Range((_, y), _) => Some((t0, y)),
SPHint2D::None => Some((t0, 0.0)),
}
} else if self.is_back_fixed() && on_axis(self.curve.back(), self.normal(t1, 0.0)) {
match hint.into() {
SPHint2D::Parameter(_, y) => Some((t1, y)),
SPHint2D::Range(_, (_, y)) => Some((t1, y)),
SPHint2D::None => Some((t1, 2.0 * PI)),
}
} else {
let proj_curve = ProjectedCurve {
curve: &self.curve,
revolution: self.revolution,
};
let p = self.revolution.proj_point(point);
let hint0 = match hint.into() {
SPHint2D::Parameter(x, _) => SPHint1D::Parameter(x),
SPHint2D::Range((x0, _), (x1, _)) => SPHint1D::Range(x0, x1),
SPHint2D::None => SPHint1D::None,
};
let t = proj_curve.search_nearest_parameter(p, hint0, trials)?;
let p = self.curve.subs(t);
Some((t, self.revolution.proj_angle(p, point)))
}
}
}
fn sub_include<C0, C1>(
surface: &RevolutedCurve<C0>,
curve: &C1,
knots: &[f64],
degree: usize,
) -> bool
where
C0: ParametricCurve3D + BoundedCurve,
C1: ParametricCurve3D + BoundedCurve,
{
let first = curve.subs(knots[0]);
let mut hint = match surface.search_parameter(first, None, INCLUDE_CURVE_TRIALS) {
Some(hint) => hint,
None => return false,
};
knots
.windows(2)
.flat_map(move |knot| {
(1..=degree).map(move |i| {
let s = i as f64 / degree as f64;
knot[0] * (1.0 - s) + knot[1] * s
})
})
.all(move |t| {
let pt = ParametricCurve::subs(curve, t);
surface
.search_parameter(pt, Some(hint), INCLUDE_CURVE_TRIALS)
.or_else(|| surface.search_parameter(pt, None, INCLUDE_CURVE_TRIALS))
.map(|res| hint = res)
.is_some()
})
}
impl<'a> IncludeCurve<BSplineCurve<Point3>> for RevolutedCurve<&'a BSplineCurve<Point3>> {
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
let knots = curve.knot_vec().to_single_multi().0;
let degree = usize::max(2, usize::max(curve.degree(), self.curve.degree()));
sub_include(self, curve, &knots, degree)
}
}
impl IncludeCurve<BSplineCurve<Point3>> for RevolutedCurve<BSplineCurve<Point3>> {
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
let knots = curve.knot_vec().to_single_multi().0;
let degree = usize::max(2, usize::max(curve.degree(), self.curve.degree()));
sub_include(self, curve, &knots, degree)
}
}
impl<'a> IncludeCurve<BSplineCurve<Point3>> for RevolutedCurve<&'a NurbsCurve<Vector4>> {
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
let knots = curve.knot_vec().to_single_multi().0;
let degree = curve.degree() + usize::max(2, self.curve.degree());
sub_include(self, curve, &knots, degree)
}
}
impl IncludeCurve<BSplineCurve<Point3>> for RevolutedCurve<NurbsCurve<Vector4>> {
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
let knots = curve.knot_vec().to_single_multi().0;
let degree = curve.degree() + usize::max(2, self.curve.degree());
sub_include(self, curve, &knots, degree)
}
}
impl<'a> IncludeCurve<NurbsCurve<Vector4>> for RevolutedCurve<&'a BSplineCurve<Point3>> {
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
let knots = curve.knot_vec().to_single_multi().0;
let degree = curve.degree() + usize::max(2, self.curve.degree());
sub_include(self, curve, &knots, degree)
}
}
impl IncludeCurve<NurbsCurve<Vector4>> for RevolutedCurve<BSplineCurve<Point3>> {
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
let knots = curve.knot_vec().to_single_multi().0;
let degree = curve.degree() + usize::max(2, self.curve.degree());
sub_include(self, curve, &knots, degree)
}
}
impl<'a> IncludeCurve<NurbsCurve<Vector4>> for RevolutedCurve<&'a NurbsCurve<Vector4>> {
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
let knots = curve.knot_vec().to_single_multi().0;
let degree = curve.degree() + usize::max(2, self.curve.degree());
sub_include(self, curve, &knots, degree)
}
}
impl IncludeCurve<NurbsCurve<Vector4>> for RevolutedCurve<NurbsCurve<Vector4>> {
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
let knots = curve.knot_vec().to_single_multi().0;
let degree = curve.degree() + usize::max(2, self.curve.degree());
sub_include(self, curve, &knots, degree)
}
}
impl<C> ParameterDivision2D for RevolutedCurve<C>
where C: ParametricCurve3D + ParameterDivision1D<Point = Point3>
{
fn parameter_division(
&self,
(urange, vrange): ((f64, f64), (f64, f64)),
tol: f64,
) -> (Vec<f64>, Vec<f64>) {
let curve_division = self.curve.parameter_division(urange, tol);
let max = curve_division
.1
.into_iter()
.fold(0.0, |max2, pt| {
let h = self.revolution.proj_point(pt).y;
f64::max(max2, h)
})
.sqrt();
let acos = f64::acos(1.0 - tol / max);
let div: usize = 1 + ((vrange.1 - vrange.0) / acos).floor() as usize;
let circle_division = (0..=div)
.map(|j| vrange.0 + (vrange.1 - vrange.0) * j as f64 / div as f64)
.collect();
(curve_division.0, circle_division)
}
}
#[test]
fn revolve_test() {
let pt0 = Point3::new(0.0, 2.0, 1.0);
let pt1 = Point3::new(1.0, 0.0, 0.0);
let curve = BSplineCurve::new(KnotVec::bezier_knot(1), vec![pt0, pt1]);
let surface = RevolutedCurve::by_revolution(curve, Point3::origin(), Vector3::unit_y());
const N: usize = 100;
for i in 0..=N {
for j in 0..=N {
let u = i as f64 / N as f64;
let v = 2.0 * PI * j as f64 / N as f64;
let res = surface.subs(u, v);
let ans = Point3::new(
u * f64::cos(v) + (1.0 - u) * f64::sin(v),
2.0 * (1.0 - u),
-u * f64::sin(v) + (1.0 - u) * f64::cos(v),
);
assert_near!(res, ans);
let res_uder = surface.uder(u, v);
let ans_uder =
Vector3::new(f64::cos(v) - f64::sin(v), -2.0, -f64::sin(v) - f64::cos(v));
assert_near!(res_uder, ans_uder);
let res_vder = surface.vder(u, v);
let ans_vder = Vector3::new(
-u * f64::sin(v) + (1.0 - u) * f64::cos(v),
0.0,
-u * f64::cos(v) - (1.0 - u) * f64::sin(v),
);
assert_near!(res_vder, ans_vder);
let res_uuder = surface.uuder(u, v);
let ans_uuder = Vector3::zero();
assert_near!(res_uuder, ans_uuder);
let res_uvder = surface.uvder(u, v);
let ans_uvder =
Vector3::new(-f64::sin(v) - f64::cos(v), 0.0, -f64::cos(v) + f64::sin(v));
assert_near!(res_uvder, ans_uvder);
let res_vvder = surface.vvder(u, v);
let ans_vvder = Vector3::new(
-u * f64::cos(v) - (1.0 - u) * f64::sin(v),
0.0,
u * f64::sin(v) - (1.0 - u) * f64::cos(v),
);
assert_near!(res_vvder, ans_vvder);
let normal = surface.normal(u, v);
assert!(normal.dot(res_uder).so_small());
assert!(normal.dot(res_vder).so_small());
}
}
}
#[test]
fn search_parameter() {
let line = BSplineCurve::new(
KnotVec::bezier_knot(1),
vec![Point3::new(0.0, 2.0, 1.0), Point3::new(1.0, 0.0, 0.0)],
);
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
let pt = Point3::new(-0.5, 1.0, 0.5);
let (u, v) = surface.search_parameter(pt, Some((0.4, 1.2)), 100).unwrap();
assert_near!(surface.subs(u, v), pt);
}
#[test]
fn search_parameter_with_fixed_points() {
let line = BSplineCurve::new(
KnotVec::bezier_knot(2),
vec![
Point3::new(0.0, 1.0, 0.0),
Point3::new(0.0, 0.0, 1.0),
Point3::new(0.0, -1.0, 0.0),
],
);
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
let (u, v) = surface
.search_parameter(Point3::new(0.0, 1.0, 0.0), Some((0.5, 0.3)), 10)
.unwrap();
assert_near!(Vector2::new(u, v), Vector2::new(0.0, 0.3));
let (u, v) = surface
.search_parameter(Point3::new(0.0, -1.0, 0.0), Some((0.5, 0.3)), 10)
.unwrap();
assert_near!(Vector2::new(u, v), Vector2::new(1.0, 0.3));
}
#[test]
fn search_nearest_parameter() {
let line = BSplineCurve::new(
KnotVec::bezier_knot(1),
vec![Point3::new(0.0, 2.0, 1.0), Point3::new(1.0, 0.0, 0.0)],
);
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
let pt = surface.subs(0.4, 1.2) + 0.1 * surface.normal(0.4, 1.2);
let (u, v) = surface
.search_nearest_parameter(pt, Some((0.4, 1.2)), 100)
.unwrap();
assert_near!(Vector2::new(u, v), Vector2::new(0.4, 1.2));
}
#[test]
fn search_nearest_parameter_with_fixed_points() {
let line = BSplineCurve::new(
KnotVec::bezier_knot(2),
vec![
Point3::new(0.0, 1.0, 0.0),
Point3::new(0.0, 0.0, 1.0),
Point3::new(0.0, -1.0, 0.0),
],
);
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
let (u, v) = surface
.search_nearest_parameter(Point3::new(0.0, 2.0, 0.0), Some((0.5, 0.3)), 10)
.unwrap();
assert_near!(Vector2::new(u, v), Vector2::new(0.0, 0.3));
let (u, v) = surface
.search_nearest_parameter(Point3::new(0.0, -2.0, 0.0), Some((0.5, 0.3)), 10)
.unwrap();
assert_near!(Vector2::new(u, v), Vector2::new(1.0, 0.3));
}
#[test]
fn include_curve_normal() {
let line = BSplineCurve::new(
KnotVec::bezier_knot(1),
vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 2.0, 2.0)],
);
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
let parabola = BSplineCurve::new(
KnotVec::bezier_knot(2),
vec![
Point3::new(1.0, 1.0, 0.0),
Point3::new(0.0, 0.0, 1.0),
Point3::new(-1.0, 1.0, 0.0),
],
);
assert!(surface.include(&parabola));
}
#[test]
fn include_curve_abnormal0() {
let line = BSplineCurve::new(
KnotVec::bezier_knot(1),
vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 2.0, 2.0)],
);
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
let parabola = BSplineCurve::new(
KnotVec::bezier_knot(2),
vec![
Point3::new(1.0, 1.0, 0.0),
Point3::new(0.0, 0.0, 2.0),
Point3::new(-1.0, 1.0, 0.0),
],
);
assert!(!surface.include(&parabola));
}
#[test]
fn include_curve_abnormal1() {
let curve = NurbsCurve::new(BSplineCurve::new(
KnotVec::bezier_knot(3),
vec![
Vector4::new(0.0, 3.0, 0.0, 1.0),
Vector4::new(0.0, 3.0, 3.0, 0.5),
Vector4::new(0.0, 0.0, 0.0, 1.0),
Vector4::new(0.0, 0.0, 3.0, 1.0),
],
));
let pt0 = curve.subs(0.2);
let pt1 = curve.subs(0.6);
let surface = RevolutedCurve::by_revolution(curve, Point3::origin(), Vector3::unit_y());
let line = BSplineCurve::new(KnotVec::bezier_knot(1), vec![pt0, pt1]);
assert!(!surface.include(&line));
}
+73
View File
@@ -0,0 +1,73 @@
use super::*;
impl<C> TrimmedCurve<C> {
/// constructor
#[inline(always)]
pub const fn new(curve: C, range: (f64, f64)) -> Self { Self { curve, range } }
/// Returns the reference of non-trimmed curve
#[inline(always)]
pub const fn curve(&self) -> &C { &self.curve }
/// Returns the mutable reference of non-trimmed curve
#[inline(always)]
pub fn curve_mut(&mut self) -> &mut C { &mut self.curve }
}
impl<C: ParametricCurve> ParametricCurve for TrimmedCurve<C> {
type Point = C::Point;
type Vector = C::Vector;
#[inline(always)]
fn subs(&self, t: f64) -> Self::Point { self.curve.subs(t) }
#[inline(always)]
fn der(&self, t: f64) -> Self::Vector { self.curve.der(t) }
#[inline(always)]
fn der2(&self, t: f64) -> Self::Vector { self.curve.der2(t) }
#[inline(always)]
fn period(&self) -> Option<f64> { self.curve.period() }
#[inline(always)]
fn parameter_range(&self) -> ParameterRange {
(Bound::Included(self.range.0), Bound::Included(self.range.1))
}
}
impl<C: ParametricCurve> BoundedCurve for TrimmedCurve<C> {}
impl<C: ParametricCurve> Cut for TrimmedCurve<C> {
fn cut(&mut self, t: f64) -> Self {
let (t0, t1) = self.range;
self.range = (t0, t);
Self::new(self.curve.clone(), (t, t1))
}
}
impl<C: SearchNearestParameter<D1>> SearchNearestParameter<D1> for TrimmedCurve<C> {
type Point = C::Point;
#[inline(always)]
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
pt: C::Point,
hint: H,
trials: usize,
) -> Option<f64> {
self.curve.search_nearest_parameter(pt, hint, trials)
}
}
impl<C: SearchParameter<D1>> SearchParameter<D1> for TrimmedCurve<C> {
type Point = C::Point;
#[inline(always)]
fn search_parameter<H: Into<SPHint1D>>(
&self,
pt: C::Point,
hint: H,
trials: usize,
) -> Option<f64> {
self.curve.search_parameter(pt, hint, trials)
}
}
impl<C: ParameterDivision1D> ParameterDivision1D for TrimmedCurve<C> {
type Point = C::Point;
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<Self::Point>) {
self.curve.parameter_division(range, tol)
}
}
+190
View File
@@ -0,0 +1,190 @@
use thiserror::Error;
/// Error handler for [`Error`](./errors/enum.Error.html)
pub type Result<T> = std::result::Result<T, Error>;
/// Geometrical Errors
#[derive(Debug, PartialEq, Error)]
pub enum Error {
/// The following operations are failed if the knot vector has zero range.
/// * Creating `BSplineCurve` or `BSplineSurface`,
/// * Calculating bspline basis functions, or
/// * Normalizing the knot vector.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// use truck_geometry::errors::Error;
/// let mut knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 0.0]);
/// assert_eq!(knot_vec.try_normalize(), Err(Error::ZeroRange));
/// assert_eq!(knot_vec.try_bspline_basis_functions(1, 0.0), Err(Error::ZeroRange));
///
/// let ctrl_pts = vec![Vector2::new(0.0, 0.0), Vector2::new(1.0, 1.0)];
/// assert!(matches!(BSplineCurve::try_new(knot_vec, ctrl_pts), Err(Error::ZeroRange)));
/// ```
#[error("This knot vector consists single value.")]
ZeroRange,
/// Fails concatting two knot vectors if there is a difference between the back knot of
/// the former knot vector and the front knot of the latter knot vector.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// use truck_geometry::errors::Error;
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
/// let knot_vec1 = KnotVec::from(vec![2.0, 2.0, 3.0, 3.0]);
/// assert!(matches!(knot_vec0.try_concat(&knot_vec1, 1), Err(Error::DifferentBackFront(1.0, 2.0))));
/// ```
#[error("Cannot concat two knot vectors whose the back of the first and the front of the second are different.
the back of the first knot vector: {0}
the front of the second knot vector: {1}")]
DifferentBackFront(f64, f64),
/// If the knot vector is not clamped, then one cannot concat the vector with another knot vector.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// use truck_geometry::errors::Error;
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
/// let knot_vec1 = KnotVec::from(vec![2.0, 2.0, 3.0, 3.0]);
/// assert!(matches!(knot_vec0.try_concat(&knot_vec1, 2), Err(Error::NotClampedKnotVector)));
/// ```
#[error("This knot vector is not clamped.")]
NotClampedKnotVector,
/// Creating a knot vector by `KnotVec::try_from()` is failed if the given vector is not sorted.
/// `<KnotVec as From<Vec<f64>>>::from()` does not panic by this error because sorts the given
/// vector before creating the knot vector. So, `KnotVec::try_from()` is more efficient than
/// `<KnotVec as From<Vec<f64>>>::from()`.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// use truck_geometry::errors::Error;
/// use std::convert::*;
///
/// assert!(matches!(KnotVec::try_from(vec![1.0, 3.0, 0.0, 2.0]), Err(Error::NotSortedVector)));
/// assert_eq!(
/// <KnotVec as From<Vec<f64>>>::from(vec![1.0, 3.0, 0.0, 2.0]),
/// KnotVec::try_from(vec![0.0, 1.0, 2.0, 3.0]).unwrap(),
/// );
/// ```
#[error("This knot vector is not sorted.")]
NotSortedVector,
/// The given degree is too large to calculate bspline basis functions.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// use truck_geometry::errors::Error;
///
/// // a knot vector with length = 4.
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
/// assert!(matches!(
/// knot_vec.try_bspline_basis_functions(5, 0.5),
/// Err(Error::TooLargeDegree(4, 5)),
/// ));
/// ```
#[error(
"This knot vector is too short compared to the degree.
the length of knot_vec: {0}
the degree: {1}"
)]
TooLargeDegree(usize, usize),
/// The specified knot cannot be removed.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// use truck_geometry::errors::Error;
/// let knot_vec = KnotVec::bezier_knot(2);
/// let ctrl_pts = vec![Vector2::new(-1.0, 1.0), Vector2::new(0.0, -1.0), Vector2::new(1.0, 1.0)];
/// let mut bspcurve = BSplineCurve::new(knot_vec, ctrl_pts);
/// let org_curve = bspcurve.clone();
/// bspcurve.add_knot(0.5).add_knot(0.5).add_knot(0.25).add_knot(0.75);
/// assert!(bspcurve.try_remove_knot(3).is_ok());
/// assert!(matches!(bspcurve.try_remove_knot(2), Err(Error::CannotRemoveKnot(2))));
/// ```
#[error("The {0}th knot in this knot vector cannot be removed.")]
CannotRemoveKnot(usize),
/// Empty vector of points cannot construct B-spline.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// use truck_geometry::errors::Error;
///
/// let knot_vec = KnotVec::bezier_knot(2);
/// let ctrl_pts: Vec<Vector4> = Vec::new();
/// assert!(matches!(
/// BSplineCurve::try_new(knot_vec, ctrl_pts),
/// Err(Error::EmptyControlPoints),
/// ));
/// ```
#[error("The control point must not be empty.")]
EmptyControlPoints,
/// The knot vector of B-spline curves or B-spline surfaces must be longer than the corresponded
/// array of control points.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// use truck_geometry::errors::Error;
/// let knot_vec = KnotVec::from(vec![0.0, 1.0, 2.0]);
/// let ctrl_pts = vec![Vector2::new(0.0, 0.0), Vector2::new(0.0, 0.0), Vector2::new(0.0, 0.0), Vector2::new(0.0, 0.0)];
/// assert!(matches!(
/// BSplineCurve::try_new(knot_vec, ctrl_pts),
/// Err(Error::TooShortKnotVector(3, 4)),
/// ));
/// ```
#[error(
"The knot vector must be more than the control points.
the length of knot_vec: {0}
the number of control points: {1}"
)]
TooShortKnotVector(usize, usize),
/// The length of the given arrays of control points to create a B-spline surface is irregular.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// use truck_geometry::errors::Error;
/// let knot_vecs = (KnotVec::bezier_knot(2), KnotVec::bezier_knot(2));
/// let ctrl_pts = vec![
/// vec![Vector2::new(1.0, 2.0), Vector2::new(1.0, 2.0)], // length = 2
/// vec![Vector2::new(1.0, 2.0)] // length = 1
/// ];
/// assert!(matches!(
/// BSplineSurface::try_new(knot_vecs, ctrl_pts),
/// Err(Error::IrregularControlPoints),
/// ));
/// ```
#[error("The number of control points is irregular")]
IrregularControlPoints,
/// The vector of control points and the one of weights have different length.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// use truck_geometry::errors::Error;
/// let bspcurve = BSplineCurve::new(
/// KnotVec::bezier_knot(2),
/// vec![Point2::new(0.0, 1.0), Point2::new(2.0, 3.0), Point2::new(4.0, 5.0)],
/// );
/// let weights = vec![1.0, 2.0]; // less than control points
///
/// assert!(matches!(
/// NurbsCurve::<Vector3>::try_from_bspline_and_weights(bspcurve, weights),
/// Err(Error::DifferentLength),
/// ));
/// ```
#[error("The vector of control points and the one of weights have different length.")]
DifferentLength,
}
#[test]
#[rustfmt::skip]
fn print_messages() {
use std::io::Write;
let stderr = &mut std::io::stderr();
writeln!(stderr, "****** test of the expressions of error messages ******\n").unwrap();
writeln!(stderr, "{}\n", Error::ZeroRange).unwrap();
writeln!(stderr, "{}\n", Error::DifferentBackFront(0.0, 1.0)).unwrap();
writeln!(stderr, "{}\n", Error::NotClampedKnotVector).unwrap();
writeln!(stderr, "{}\n", Error::NotSortedVector).unwrap();
writeln!(stderr, "{}\n", Error::TooLargeDegree(1, 2)).unwrap();
writeln!(stderr, "{}\n", Error::CannotRemoveKnot(7)).unwrap();
writeln!(stderr, "{}\n", Error::EmptyControlPoints).unwrap();
writeln!(stderr, "{}\n", Error::TooShortKnotVector(1, 2)).unwrap();
writeln!(stderr, "{}\n", Error::IrregularControlPoints).unwrap();
writeln!(stderr, "*******************************************************").unwrap();
}
+52
View File
@@ -0,0 +1,52 @@
//! Geometrical structs: knot vector, B-spline and NURBS
#![cfg_attr(not(debug_assertions), deny(warnings))]
#![deny(clippy::all, rust_2018_idioms)]
#![warn(
missing_docs,
missing_debug_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications
)]
use serde::{Deserialize, Serialize};
use std::{fmt::Debug, ops::Bound};
use truck_base::bounding_box::Bounded;
const INCLUDE_CURVE_TRIALS: usize = 100;
const PRESEARCH_DIVISION: usize = 50;
/// re-export `truck_base`
pub mod base {
pub use truck_base::bounding_box::BoundingBox;
pub use truck_base::cgmath64::*;
pub use truck_base::tolerance::*;
pub use truck_base::{assert_near, assert_near2};
pub use truck_base::{hash, hash::HashGen};
pub use truck_geotrait::*;
}
/// Declares the nurbs
pub mod nurbs;
/// Enumerats `Error`.
pub mod errors;
/// Declares the specified gememetric items: Plane, Sphere, and so on.
pub mod specifieds;
/// Declares some decorators
pub mod decorators;
/// re-export all modules.
pub mod prelude {
use crate::*;
pub use base::*;
pub use decorators::*;
pub use errors::*;
pub use nurbs::*;
pub use specifieds::*;
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+596
View File
@@ -0,0 +1,596 @@
use super::*;
use crate::errors::Error;
use std::slice::SliceIndex;
use std::vec::Vec;
impl KnotVec {
/// empty constructor
pub const fn new() -> KnotVec { KnotVec(Vec::new()) }
/// Returns the length of range.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vec = KnotVec::from(vec![0.0, 6.0]);
/// assert_eq!(knot_vec.range_length(), 6.0);
/// ```
#[inline(always)]
pub fn range_length(&self) -> f64 {
match self.is_empty() {
true => 0.0,
false => self[self.len() - 1] - self[0],
}
}
/// Returns whether two knot vectors have the same range.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vec0 = KnotVec::new(); // empty knot vector
/// let knot_vec1 = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
/// let knot_vec2 = KnotVec::from(vec![0.0, 0.5, 1.0]);
/// let knot_vec3 = KnotVec::from(vec![0.0, 0.0, 2.0, 2.0]);
/// assert!(knot_vec0.same_range(&KnotVec::new())); // both empty knot vector
/// assert!(!knot_vec0.same_range(&knot_vec1));
/// assert!(knot_vec1.same_range(&knot_vec2)); // the range of both knot vector is [0, 1].
/// assert!(!knot_vec1.same_range(&knot_vec3));
/// ```
#[inline(always)]
pub fn same_range(&self, other: &KnotVec) -> bool {
match (self.is_empty(), other.is_empty()) {
(false, false) => {
self[0].near(&other[0]) && self.range_length().near(&other.range_length())
}
(true, true) => true,
_ => false,
}
}
/// Removes one item.
#[inline(always)]
pub fn remove(&mut self, idx: usize) -> f64 { self.0.remove(idx) }
/// Returns the maximum index `i` of `self[i] <= x`
/// Return `None` if `x < self[0] or self.len() == 0`.
/// # Examples
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let mut knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 2.0, 3.0, 3.0]);
/// let idx = knot_vec.floor(1.5).unwrap();
/// assert_eq!(idx, 2);
/// ```
#[inline(always)]
pub fn floor(&self, x: f64) -> Option<usize> { self.iter().rposition(|t| *t <= x) }
/// Returns the multiplicity of the `i`th knot
/// # Examples
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0]);
/// assert_eq!(knot_vec.multiplicity(5), 3);
/// ```
#[inline(always)]
pub fn multiplicity(&self, i: usize) -> usize {
self.iter().filter(|u| self[i].near(u)).count()
}
/// Adds a knot and return the index of the added knot.
/// # Examples
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let mut knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 2.0, 3.0, 3.0]);
/// let idx0 = knot_vec.add_knot(1.5);
/// assert_eq!(idx0, 3);
/// let idx1 = knot_vec.add_knot(-1.0);
/// assert_eq!(idx1, 0);
/// let ansvec = KnotVec::from(vec![-1.0, 0.0, 0.0, 1.0, 1.5, 2.0, 3.0, 3.0]);
/// assert_eq!(knot_vec, ansvec);
/// ```
#[inline(always)]
pub fn add_knot(&mut self, knot: f64) -> usize {
match self.floor(knot) {
Some(idx) => {
self.0.insert(idx + 1, knot);
idx + 1
}
None => {
self.0.insert(0, knot);
0
}
}
}
/// Calculates B-spline basis functions at `t` with degree `degree`.
/// # Panics
/// If the length of `self` is not more than `degree`, panic occurs.
/// # Remarks
/// In this package, the B-spline basis function is based on the characteristic function of
/// the right-open intervals [s, t). So, the value corresponding to the end point t = t_n is always 0.0.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// const N : usize = 100; // sample size in tests
///
/// // B-spline basis functions is a partition of unity in (t_k, t_{n - k}).
/// let vec = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
/// let knot_vec = KnotVec::from(vec);
/// let degree = 2;
/// for i in 0..N {
/// let t = 2.0 + 4.0 / (N as f64) * (i as f64);
/// let res = knot_vec.bspline_basis_functions(degree, t);
/// let sum = res.iter().fold(0.0, |sum, a| sum + a);
/// assert_near2!(sum, 1.0);
/// }
/// ```
/// ```
/// use truck_geometry::prelude::*;
/// const N : usize = 100; // sample size in tests
///
/// // In some case, B-spline basis functions coincide with Bernstein polynomials.
/// let vec = vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0];
/// let knot_vec = KnotVec::from(vec);
/// let degree = 3;
/// for i in 0..=N {
/// let t = 1.0 / (N as f64) * (i as f64);
/// let res = knot_vec.bspline_basis_functions(degree, t);
/// let ans = [
/// 1.0 * (1.0 - t) * (1.0 - t) * (1.0 - t),
/// 3.0 * t * (1.0 - t) * (1.0 - t),
/// 3.0 * t * t * (1.0 - t),
/// 1.0 * t * t * t,
/// ];
/// for i in 0..4 { assert_near2!(res[i], ans[i]); }
/// }
/// ```
pub fn bspline_basis_functions(&self, degree: usize, t: f64) -> Vec<f64> {
match self.try_bspline_basis_functions(degree, t) {
Ok(got) => got,
Err(error) => panic!("{}", error),
}
}
/// Calculates B-spline basis functions at `t` with degree `degree`.
/// # Failures
/// - If the range of the knot vector is zero, returns [`Error::ZeroRange`].
/// - If the length of `self` is not more than `degree`, returns [`Error::TooLargeDegree`].
/// # Remarks
/// In this package, the B-spline basis function is based on the characteristic function of
/// the right-open intervals [s, t). So, the value corresponding to the end point t = t_n is always 0.0.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// const N : usize = 100; // sample size in tests
///
/// // B-spline basis functions is a partition of unity in (t_k, t_{n - k}).
/// let vec = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
/// let knot_vec = KnotVec::from(vec);
/// let degree = 2;
/// for i in 0..N {
/// let t = 2.0 + 4.0 / (N as f64) * (i as f64);
/// let res = knot_vec.try_bspline_basis_functions(degree, t).unwrap();
/// let sum = res.iter().fold(0.0, |sum, a| sum + a);
/// assert_near2!(sum, 1.0);
/// }
/// ```
/// ```
/// use truck_geometry::prelude::*;
/// const N : usize = 100; // sample size in tests
///
/// // In some case, B-spline basis functions coincide with Bernstein polynomials.
/// let vec = vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0];
/// let knot_vec = KnotVec::from(vec);
/// let degree = 3;
/// for i in 0..=N {
/// let t = i as f64 / N as f64;
/// let res = knot_vec.try_bspline_basis_functions(degree, t).unwrap();
/// let ans = [
/// 1.0 * (1.0 - t) * (1.0 - t) * (1.0 - t),
/// 3.0 * t * (1.0 - t) * (1.0 - t),
/// 3.0 * t * t * (1.0 - t),
/// 1.0 * t * t * t,
/// ];
/// for i in 0..4 { assert_near2!(res[i], ans[i]); }
/// }
/// ```
pub fn try_bspline_basis_functions(&self, degree: usize, t: f64) -> Result<Vec<f64>> {
let n = self.len() - 1;
if self[0].near(&self[n]) {
return Err(Error::ZeroRange);
} else if n < degree {
return Err(Error::TooLargeDegree(n + 1, degree));
}
let idx = {
let idx = self
.floor(t)
.unwrap_or_else(|| self.floor(self[0]).unwrap());
if idx == n {
n - self.multiplicity(n)
} else {
idx
}
};
let mut res = vec![0.0; n];
res[idx] = 1.0;
for k in 1..=degree {
let base = if idx < k { 0 } else { idx - k };
let delta = self[base + k] - self[base];
let max = if idx + k < n { idx } else { n - k - 1 };
let mut a = inv_or_zero(delta) * (t - self[base]);
for i in base..=max {
let delta = self[i + k + 1] - self[i + 1];
let b = inv_or_zero(delta) * (self[i + k + 1] - t);
res[i] = a * res[i] + b * res[i + 1];
a = 1.0 - b;
}
}
res.truncate(n - degree);
Ok(res)
}
#[doc(hidden)]
pub fn maximum_points(&self, degree: usize) -> Vec<f64> {
let n = self.len();
let m = n - degree - 1;
let range = self.range_length();
const N: i32 = 100;
let mut res = vec![0.0; m];
let mut max = vec![0.0; m];
for i in 1..N {
let t = self[0] + range * (i as f64) / (N as f64);
let vals = self.try_bspline_basis_functions(degree, t).unwrap();
for j in 0..m {
if max[j] < vals[j] {
max[j] = vals[j];
res[j] = t;
}
}
}
res
}
/// Transforms the knot vector
/// # Examples
/// ```
/// use std::vec::Vec;
/// use truck_geometry::prelude::KnotVec;
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
/// knot_vec.transform(2.0, 3.0);
/// let res : Vec<f64> = knot_vec.into();
/// assert_eq!(res, vec![5.0, 5.0, 7.0, 9.0, 11.0, 13.0, 13.0]);
/// ```
/// # Panics
/// Panic occurs if `scalar` is not positive.
pub fn transform(&mut self, scalar: f64, r#move: f64) -> &mut Self {
assert!(scalar > 0.0, "The scalar {scalar} is not positive.");
self.0
.iter_mut()
.for_each(move |vec| *vec = *vec * scalar + r#move);
self
}
/// Normalizes the knot vector i.e. makes the first value 0 and the last value 1.
/// # Failures
/// Returns [`Error::ZeroRange`] if the range of the knot vector is so small.
/// # Examples
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
/// knot_vec.try_normalize().unwrap();
/// let res : Vec<f64> = knot_vec.into();
/// assert_eq!(res, vec![0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0]);
/// ```
pub fn try_normalize(&mut self) -> Result<&mut Self> {
let range = self.range_length();
if range.so_small() {
return Err(Error::ZeroRange);
}
Ok(self.transform(1.0 / range, -self[0] / range))
}
/// Normalizes the knot vector i.e. makes the first value 0 and the last value 1.
/// # Panics
/// Panic occurs if the range of the knot vector is so small.
/// # Examples
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
/// knot_vec.normalize();
/// let res : Vec<f64> = knot_vec.into();
/// assert_eq!(res, vec![0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0]);
/// ```
#[inline(always)]
pub fn normalize(&mut self) -> &mut Self {
self.try_normalize()
.unwrap_or_else(|error| panic!("{}", error))
}
/// Translates the knot vector
/// # Example
/// ```
/// use std::vec::Vec;
/// use truck_geometry::prelude::KnotVec;
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
/// knot_vec.translate(3.0);
/// let res : Vec<f64> = knot_vec.into();
/// assert_eq!(res, vec![4.0, 4.0, 5.0, 6.0, 7.0, 8.0, 8.0]);
/// ```
pub fn translate(&mut self, x: f64) -> &mut Self { self.transform(1.0, x) }
/// Inverts the knot vector
/// # Example
/// ```
/// use std::vec::Vec;
/// use truck_geometry::prelude::KnotVec;
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 1.0, 3.0, 5.0, 6.0]);
/// knot_vec.invert();
/// let res : Vec<f64> = knot_vec.into();
/// assert_eq!(res, vec![1.0, 2.0, 4.0, 6.0, 6.0, 6.0]);
///
/// ```
pub fn invert(&mut self) -> &mut Self {
let n = self.len();
if n == 0 {
return self;
}
let range = self[0] + self[n - 1];
let clone = self.0.clone();
for (knot1, knot0) in clone.iter().rev().zip(&mut self.0) {
*knot0 = range - knot1;
}
self
}
/// Determines the knot vector is clamped for the given degree.
/// # Examples
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0]);
/// assert!(knot_vec.is_clamped(2));
/// assert!(!knot_vec.is_clamped(3));
/// ```
#[inline(always)]
pub fn is_clamped(&self, degree: usize) -> bool {
self.multiplicity(0) > degree && self.multiplicity(self.len() - 1) > degree
}
/// Concats two knot vectors.
/// # Examples
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
/// let knot_vec1 = KnotVec::from(vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
/// knot_vec0.try_concat(&knot_vec1, 2).unwrap();
/// assert_eq!(knot_vec0.as_slice(), &[0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
/// ```
/// # Failures
/// - If at least one of `self` or `other` is not clamped, returns [`Error::NotClampedKnotVector`]
/// - If the last knot of `self` and the first knot of `other` are different, returns [`Error::DifferentBackFront`].
pub fn try_concat(&mut self, other: &KnotVec, degree: usize) -> Result<&mut Self> {
if !self.is_clamped(degree) || !other.is_clamped(degree) {
return Err(Error::NotClampedKnotVector);
}
let back = self.0.last().unwrap();
let front = other.0.first().unwrap();
if front < back || !front.near(back) {
return Err(Error::DifferentBackFront(*back, *front));
}
self.0.truncate(self.len() - degree - 1);
for knot in &other.0 {
self.0.push(*knot);
}
Ok(self)
}
/// Concats two knot vectors.
/// # Examples
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
/// let knot_vec1 = KnotVec::from(vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
/// knot_vec0.concat(&knot_vec1, 2);
/// assert_eq!(knot_vec0.as_slice(), &[0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
/// ```
/// # Panics
/// Panic occurs if:
/// - at least one of `self` or `other` is not clamped.
/// - the last knot of `self` and the first knot of `other` are different.
#[inline(always)]
pub fn concat(&mut self, other: &KnotVec, degree: usize) -> &mut Self {
self.try_concat(other, degree)
.unwrap_or_else(|error| panic!("{}", error))
}
/// Returns trimmed vector by the specified range.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vec = KnotVec::from(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]);
/// let sub_vec = knot_vec.sub_vec(1..3);
/// assert_eq!(sub_vec, KnotVec::from(vec![1.0, 2.0]));
/// ```
#[inline(always)]
pub fn sub_vec<I: SliceIndex<[f64], Output = [f64]>>(&self, range: I) -> KnotVec {
KnotVec(Vec::from(&self.0[range]))
}
/// To single-multi description. i.e. decompose the unique vector of knots and the vector of
/// multiplicity of knots.
/// # Examples
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0]);
/// let (knots, mults) = knot_vec.to_single_multi();
/// assert_eq!(knots, vec![0.0, 1.0, 2.0, 3.0]);
/// assert_eq!(mults, vec![3, 1, 4, 2]);
/// ```
pub fn to_single_multi(&self) -> (Vec<f64>, Vec<usize>) {
let mut knots = Vec::new();
let mut mults = Vec::new();
let mut iter = self.as_slice().iter().peekable();
let mut mult = 1;
while let Some(knot) = iter.next() {
if let Some(next) = iter.peek() {
if knot.near(next) {
mult += 1;
} else {
knots.push(*knot);
mults.push(mult);
mult = 1;
}
} else {
knots.push(*knot);
mults.push(mult);
}
}
(knots, mults)
}
/// Constructs from single-multi description.
/// # Examples
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let knots = vec![0.0, 1.0, 2.0, 3.0];
/// let mults = vec![3, 1, 4, 2];
/// let knot_vec = KnotVec::from_single_multi(knots, mults).unwrap();
/// assert_eq!(knot_vec, KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0]));
/// ```
pub fn from_single_multi(knots: Vec<f64>, mults: Vec<usize>) -> Result<KnotVec> {
for i in 1..knots.len() {
if knots[i - 1] > knots[i] {
return Err(Error::NotSortedVector);
}
}
let mut vec = Vec::new();
for i in 0..knots.len() {
for _ in 0..mults[i] {
vec.push(knots[i]);
}
}
Ok(KnotVec(vec))
}
/// Constructs from `Vec<f64>`. do not sort, only check sorted.
pub fn try_from(vec: Vec<f64>) -> Result<KnotVec> {
for i in 1..vec.len() {
if vec[i - 1] > vec[i] {
return Err(Error::NotSortedVector);
}
}
Ok(KnotVec(vec))
}
/// Constructs the knot vector for the bezier spline.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// assert_eq!(
/// *KnotVec::bezier_knot(3),
/// vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0],
/// );
/// ```
pub fn bezier_knot(degree: usize) -> KnotVec {
let mut vec = vec![0.0; degree + 1];
vec.extend(std::iter::repeat(1.0).take(degree + 1));
KnotVec(vec)
}
/// Constructs the uniform knot vector
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// assert_eq!(
/// *KnotVec::uniform_knot(2, 5),
/// vec![0.0, 0.0, 0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.0, 1.0],
/// );
/// ```
pub fn uniform_knot(degree: usize, division: usize) -> KnotVec {
let mut vec = vec![0.0; degree + 1];
vec.extend((1..division).map(|i| (i as f64) / (division as f64)));
vec.extend(std::iter::repeat(1.0).take(degree + 1));
KnotVec(vec)
}
}
impl From<Vec<f64>> for KnotVec {
/// constructs from `Vec<f64>`. The vector will sorted by the order.
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let knot_vec = KnotVec::from(vec![1.0, 0.0, 3.0, 2.0]);
/// let arr : Vec<f64> = knot_vec.into();
/// assert_eq!(arr, vec![0.0, 1.0, 2.0, 3.0]);
/// ```
fn from(mut vec: Vec<f64>) -> KnotVec {
vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
KnotVec(vec)
}
}
impl From<&Vec<f64>> for KnotVec {
/// Constructs by the reference of vector. The clone of vector is sorted by the order.
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let knot_vec = KnotVec::from(&vec![1.0, 0.0, 3.0, 2.0]);
/// let arr : Vec<f64> = knot_vec.into();
/// assert_eq!(arr, vec![0.0, 1.0, 2.0, 3.0]);
/// ```
#[inline(always)]
fn from(vec: &Vec<f64>) -> KnotVec {
let mut copy_vec = vec.clone();
copy_vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
KnotVec(copy_vec)
}
}
impl From<KnotVec> for Vec<f64> {
/// `KnotVec` into `Vec<f64>`
/// ```
/// use truck_geometry::prelude::KnotVec;
/// let vec = vec![0.0, 1.0, 2.0, 3.0];
/// let knot_vec = KnotVec::from(&vec);
/// let vec0 : Vec<f64> = knot_vec.into();
/// assert_eq!(vec, vec0);
/// ```
#[inline(always)]
fn from(knotvec: KnotVec) -> Vec<f64> { knotvec.0 }
}
impl FromIterator<f64> for KnotVec {
#[inline(always)]
fn from_iter<I: IntoIterator<Item = f64>>(iter: I) -> KnotVec {
KnotVec::try_from(iter.into_iter().collect::<Vec<_>>()).unwrap()
}
}
impl<'a> IntoIterator for &'a KnotVec {
type Item = &'a f64;
type IntoIter = std::slice::Iter<'a, f64>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter { self.0.iter() }
}
impl std::ops::Deref for KnotVec {
type Target = Vec<f64>;
#[inline(always)]
fn deref(&self) -> &Vec<f64> { &self.0 }
}
impl AsRef<[f64]> for KnotVec {
#[inline(always)]
fn as_ref(&self) -> &[f64] { &self.0 }
}
impl<'de> Deserialize<'de> for KnotVec {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where D: serde::Deserializer<'de> {
let vec = Vec::<f64>::deserialize(deserializer)?;
Self::try_from(vec).map_err(serde::de::Error::custom)
}
}
+135
View File
@@ -0,0 +1,135 @@
use crate::{prelude::*, *};
use truck_base::cgmath64::control_point::ControlPoint;
/// knot vector
#[derive(Clone, PartialEq, Debug, Default, Serialize)]
pub struct KnotVec(Vec<f64>);
/// B-spline curve
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
///
/// // the knot vector
/// let knot_vec = KnotVec::from(
/// vec![0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0]
/// );
///
/// // sign up the control points in the vector of all points
/// let ctrl_pts = vec![ // the vector of the indices of control points
/// Vector4::new(0.0, -2.0, 0.0, 2.0),
/// Vector4::new(1.0, -1.0, 0.0, 1.0),
/// Vector4::new(1.0, 0.0, 0.0, 1.0),
/// Vector4::new(1.0, 1.0, 0.0, 1.0),
/// Vector4::new(0.0, 2.0, 0.0, 2.0),
/// Vector4::new(-1.0, 1.0, 0.0, 1.0),
/// Vector4::new(-1.0, 0.0, 0.0, 1.0),
/// Vector4::new(-1.0, -1.0, 0.0, 1.0),
/// Vector4::new(0.0, -2.0, 0.0, 2.0),
/// ];
///
/// // construct the B-spline curve
/// let bspline = BSplineCurve::new(knot_vec, ctrl_pts);
///
/// // This B-spline curve is a nurbs representation of the unit circle.
/// const N : usize = 100; // sample size in test
/// for i in 0..N {
/// let t = 1.0 / (N as f64) * (i as f64);
/// let v = bspline.subs(t); // We can use the instances as a function.
/// let c = (v[0] / v[3]).powi(2) + (v[1] / v[3]).powi(2);
/// assert_near2!(c, 1.0);
/// }
/// ```
#[derive(Clone, PartialEq, Debug, Serialize)]
pub struct BSplineCurve<P> {
knot_vec: KnotVec, // the knot vector
control_points: Vec<P>, // the indices of control points
}
/// B-spline surface
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// const N : usize = 100; // sample size in test
///
/// // the knot vectors
/// let knot_vec0 = KnotVec::bezier_knot(3);
/// let knot_vec1 = KnotVec::from(
/// vec![0.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0]
/// );
/// let knot_vecs = (knot_vec0, knot_vec1);
///
/// // the control points
/// let mut v = vec![vec![Vector4::zero(); 7]; 4];
/// v[0][0] = Vector4::new(0.0, 0.0, 1.0, 1.0);
/// v[0][1] = &v[0][0] / 3.0;
/// v[0][2] = v[0][1].clone();
/// v[0][3] = v[0][0].clone();
/// v[0][4] = v[0][1].clone();
/// v[0][5] = v[0][1].clone();
/// v[0][6] = v[0][0].clone();
/// v[1][0] = Vector4::new(2.0, 0.0, 1.0, 1.0) / 3.0;
/// v[1][1] = Vector4::new(2.0, 4.0, 1.0, 1.0) / 9.0;
/// v[1][2] = Vector4::new(-2.0, 4.0, 1.0, 1.0) / 9.0;
/// v[1][3] = Vector4::new(-2.0, 0.0, 1.0, 1.0) / 3.0;
/// v[1][4] = Vector4::new(-2.0, -4.0, 1.0, 1.0) / 9.0;
/// v[1][5] = Vector4::new(2.0, -4.0, 1.0, 1.0) / 9.0;
/// v[1][6] = Vector4::new(2.0, 0.0, 1.0, 1.0) / 3.0;
/// v[2][0] = Vector4::new(2.0, 0.0, -1.0, 1.0) / 3.0;
/// v[2][1] = Vector4::new(2.0, 4.0, -1.0, 1.0) / 9.0;
/// v[2][2] = Vector4::new(-2.0, 4.0, -1.0, 1.0) / 9.0;
/// v[2][3] = Vector4::new(-2.0, 0.0, -1.0, 1.0) / 3.0;
/// v[2][4] = Vector4::new(-2.0, -4.0, -1.0, 1.0) / 9.0;
/// v[2][5] = Vector4::new(2.0, -4.0, -1.0, 1.0) / 9.0;
/// v[2][6] = Vector4::new(2.0, 0.0, -1.0, 1.0) / 3.0;
/// v[3][0] = Vector4::new(0.0, 0.0, -1.0, 1.0);
/// v[3][1] = &v[3][0] / 3.0;
/// v[3][2] = v[3][1].clone();
/// v[3][3] = v[3][0].clone();
/// v[3][4] = v[3][1].clone();
/// v[3][5] = v[3][1].clone();
/// v[3][6] = v[3][0].clone();
///
/// // cunstruct the B-spline curve
/// let bspline = BSplineSurface::new(knot_vecs, v);
///
/// // This B-spline curve is a nurbs representation of the unit sphere.
/// for i in 0..N {
/// for j in 0..N {
/// let u = 1.0 / (N as f64) * (i as f64);
/// let v = 1.0 / (N as f64) * (j as f64);
/// let v = bspline.subs(u, v); // We can use the instances as a function.
/// let c = (v[0] / v[3]).powi(2) + (v[1] / v[3]).powi(2) + (v[2] / v[3]).powi(2);
/// assert_near2!(c, 1.0);
/// }
/// }
/// ```
#[derive(Clone, PartialEq, Debug, Serialize)]
pub struct BSplineSurface<P> {
knot_vecs: (KnotVec, KnotVec),
control_points: Vec<Vec<P>>,
}
/// NURBS curve
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct NurbsCurve<V>(BSplineCurve<V>);
/// NURBS surface
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct NurbsSurface<V>(BSplineSurface<V>);
mod bspcurve;
mod bspsurface;
mod knot_vec;
mod nurbscurve;
mod nurbssurface;
#[doc(hidden)]
#[inline(always)]
pub fn inv_or_zero(delta: f64) -> f64 {
if delta.so_small() {
0.0
} else {
1.0 / delta
}
}
+619
View File
@@ -0,0 +1,619 @@
use super::*;
impl<V> NurbsCurve<V> {
/// Constructs the rationalized B-spline curve.
#[inline(always)]
pub const fn new(curve: BSplineCurve<V>) -> Self { NurbsCurve(curve) }
/// Returns the BSpline curve before rationalized.
#[inline(always)]
pub const fn non_rationalized(&self) -> &BSplineCurve<V> { &self.0 }
/// Returns the BSpline curve before rationalized.
#[inline(always)]
pub fn into_non_rationalized(self) -> BSplineCurve<V> { self.0 }
/// Returns the reference of the knot vector. cf.[`BSplineCurve::knot_vec`]
#[inline(always)]
pub const fn knot_vec(&self) -> &KnotVec { &self.0.knot_vec }
/// Returns the `idx`th knot. cf.[`BSplineCurve::knot`]
#[inline(always)]
pub fn knot(&self, idx: usize) -> f64 { self.0.knot_vec[idx] }
/// Returns the reference of the control points. cf.[`BSplineCurve::control_points`]
#[inline(always)]
pub const fn control_points(&self) -> &Vec<V> { &self.0.control_points }
/// Returns the reference of the control point corresponding to the index `idx`.
/// cf.[`BSplineCurve::control_point`]
#[inline(always)]
pub fn control_point(&self, idx: usize) -> &V { &self.0.control_points[idx] }
/// Returns the mutable reference of the control point corresponding to index `idx`.
/// cf.[`BSplineCurve::control_point_mut`]
#[inline(always)]
pub fn control_point_mut(&mut self, idx: usize) -> &mut V { &mut self.0.control_points[idx] }
/// Returns the iterator on all control points. cf.[`BSplineCurve::control_points_mut`]
#[inline(always)]
pub fn control_points_mut(&mut self) -> impl Iterator<Item = &mut V> {
self.0.control_points.iter_mut()
}
/// Applies the given transformation to all control points. cf.[`BSplineCurve::transform_control_points`]
#[inline(always)]
pub fn transform_control_points<F: FnMut(&mut V)>(&mut self, f: F) {
self.0.transform_control_points(f)
}
/// Returns the degree of NURBS curve. cf.[`BSplineCurve::degree`]
#[inline(always)]
pub fn degree(&self) -> usize { self.0.degree() }
/// Inverts a curve. cf.[`BSplineCurve::invert`]
#[inline(always)]
pub fn invert(&mut self) -> &mut Self {
self.0.invert();
self
}
/// Returns whether the knot vector is clamped or not. cf.[`BSplineCurve::is_clamped`]
#[inline(always)]
pub fn is_clamped(&self) -> bool { self.0.knot_vec.is_clamped(self.0.degree()) }
/// Normalizes the knot vector. cf.[`BSplineCurve::knot_normalize`]
#[inline(always)]
pub fn knot_normalize(&mut self) -> &mut Self {
self.0.knot_vec.try_normalize().unwrap();
self
}
/// Translates the knot vector. cf.[`BSplineCurve::knot_translate`]
#[inline(always)]
pub fn knot_translate(&mut self, x: f64) -> &mut Self {
self.0.knot_vec.translate(x);
self
}
}
impl<V: Homogeneous<f64>> NurbsCurve<V> {
/// Constructs a rationalization curve from the non-rationalized curve and weights.
/// # Failures
/// the length of `curve.control_points()` and `weights` must be the same.
#[inline(always)]
pub fn try_from_bspline_and_weights(
curve: BSplineCurve<V::Point>,
weights: Vec<f64>,
) -> Result<Self> {
let BSplineCurve {
knot_vec,
control_points,
} = curve;
if control_points.len() != weights.len() {
return Err(Error::DifferentLength);
}
let control_points = control_points
.into_iter()
.zip(weights)
.map(|(pt, w)| V::from_point_weight(pt, w))
.collect();
Ok(Self(BSplineCurve::new_unchecked(knot_vec, control_points)))
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsCurve<V> {
/// Returns the closure of substitution.
#[inline(always)]
pub fn get_closure(&self) -> impl Fn(f64) -> V::Point + '_ { move |t| self.subs(t) }
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsCurve<V>
where V::Point: Tolerance
{
/// Returns whether all control points are the same or not.
/// If the knot vector is clamped, it means whether the curve is constant or not.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
///
/// let knot_vec = KnotVec::bezier_knot(2);
/// let pt = Vector3::new(1.0, 2.0, 1.0);
/// // allows differences upto scalars
/// let mut ctrl_pts = vec![pt.clone(), pt.clone() * 2.0, pt.clone() * 3.0];
/// let bspcurve = BSplineCurve::new(knot_vec.clone(), ctrl_pts.clone());
/// assert!(!bspcurve.is_const());
/// let const_curve = NurbsCurve::new(bspcurve);
/// assert!(const_curve.is_const());
///
/// ctrl_pts.push(Vector3::new(2.0, 3.0, 1.0));
/// let curve = NurbsCurve::new(BSplineCurve::new(knot_vec.clone(), ctrl_pts.clone()));
/// assert!(!curve.is_const());
/// ```
/// # Remarks
/// If the knot vector is not clamped and the BSpline basis function is not partition of unity,
/// then perhaps returns true even if the curve is not constant.
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vec = KnotVec::uniform_knot(1, 5);
/// let ctrl_pts = vec![Vector2::new(1.0, 2.0), Vector2::new(1.0, 2.0)];
/// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts);
///
/// // bspcurve is not constant.
/// assert_eq!(bspcurve.subs(0.0), Vector2::new(0.0, 0.0));
/// assert_ne!(bspcurve.subs(0.5), Vector2::new(0.0, 0.0));
///
/// // bspcurve.is_const() is true
/// assert!(bspcurve.is_const());
/// ```
pub fn is_const(&self) -> bool {
let pt = self.0.control_points[0].to_point();
self.0
.control_points
.iter()
.all(move |vec| vec.to_point().near(&pt))
}
/// Determines whether `self` and `other` is near as the B-spline curves or not.
///
/// Divides each knot interval into the number of degree equal parts,
/// and check `|self(t) - other(t)| < TOLERANCE` for each end points `t`.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vec = KnotVec::from(
/// vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 4.0, 4.0]
/// );
/// let ctrl_pts = vec![
/// Vector3::new(1.0, 1.0, 1.0),
/// Vector3::new(3.0, 2.0, 2.0),
/// Vector3::new(2.0, 3.0, 1.0),
/// Vector3::new(4.0, 5.0, 2.0),
/// Vector3::new(5.0, 4.0, 1.0),
/// Vector3::new(1.0, 1.0, 2.0),
/// ];
/// let curve0 = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
/// let mut curve1 = curve0.clone();
/// assert!(curve0.near_as_curve(&curve1));
/// *curve1.control_point_mut(1) += Vector3::new(0.01, 0.0002, 0.0);
/// assert!(!curve0.near_as_curve(&curve1));
/// ```
#[inline(always)]
pub fn near_as_curve(&self, other: &Self) -> bool {
self.0
.sub_near_as_curve(&other.0, 2, move |x, y| x.to_point().near(&y.to_point()))
}
/// Determines `self` and `other` is near in square order as the B-spline curves or not.
///
/// Divide each knot interval into the number of degree equal parts,
/// and check `|self(t) - other(t)| < TOLERANCE`for each end points `t`.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vec = KnotVec::from(
/// vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 4.0, 4.0]
/// );
/// let ctrl_pts = vec![
/// Vector3::new(1.0, 1.0, 1.0),
/// Vector3::new(3.0, 2.0, 2.0),
/// Vector3::new(2.0, 3.0, 1.0),
/// Vector3::new(4.0, 5.0, 2.0),
/// Vector3::new(5.0, 4.0, 1.0),
/// Vector3::new(1.0, 1.0, 2.0),
/// ];
/// let curve0 = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
/// let mut curve1 = curve0.clone();
/// assert!(curve0.near_as_curve(&curve1));
/// *curve1.control_point_mut(1) += Vector3::new(0.01, TOLERANCE, 0.0);
/// assert!(!curve0.near2_as_curve(&curve1));
/// ```
#[inline(always)]
pub fn near2_as_curve(&self, other: &Self) -> bool {
self.0
.sub_near_as_curve(&other.0, 2, move |x, y| x.to_point().near2(&y.to_point()))
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> NurbsCurve<V> {
/// Adds a knot `x`, and do not change `self` as a curve. cf.[`BSplineCurve::add_knot`]
pub fn add_knot(&mut self, x: f64) -> &mut Self {
self.0.add_knot(x);
self
}
/// Removes a knot corresponding to the indices `idx`, and do not change `self` as a curve.
/// If cannot remove the knot, do not change `self` and return `self`.
/// cf.[`BSplineCurve::remove_knot`]
pub fn remove_knot(&mut self, idx: usize) -> &mut Self {
let _ = self.try_remove_knot(idx);
self
}
/// Removes a knot corresponding to the indice `idx`, and do not change `self` as a curve.
/// If the knot cannot be removed, returns [`Error::CannotRemoveKnot`].
/// cf.[`BSplineCurve::try_remove_knot`]
pub fn try_remove_knot(&mut self, idx: usize) -> Result<&mut Self> {
self.0.try_remove_knot(idx)?;
Ok(self)
}
/// Elevates 1 degree. cf.[`BSplineCurve::elevate_degree`]
pub fn elevate_degree(&mut self) -> &mut Self {
self.0.elevate_degree();
self
}
/// Makes the NURBS curve clamped. cf.[`BSplineCurve::clamp`]
#[inline(always)]
pub fn clamp(&mut self) -> &mut Self {
self.0.clamp();
self
}
/// Repeats `Self::try_remove_knot()` from the back knot in turn until the knot cannot be removed.
/// cf.[`BSplineCurve::optimize`]
pub fn optimize(&mut self) -> &mut Self {
self.0.optimize();
self
}
/// Makes two splines having the same degrees. cf.[`BSplineCurve::syncro_degree`]
pub fn syncro_degree(&mut self, other: &mut Self) {
let (degree0, degree1) = (self.degree(), other.degree());
for _ in degree0..degree1 {
self.elevate_degree();
}
for _ in degree1..degree0 {
other.elevate_degree();
}
}
/// Makes two splines having the same normalized knot vectors. cf.[`BSplineCurve::syncro_knots`]
pub fn syncro_knots(&mut self, other: &mut Self) { self.0.syncro_knots(&mut other.0) }
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> ParameterTransform
for NurbsCurve<V>
{
#[inline(always)]
fn parameter_transform(&mut self, scalar: f64, r#move: f64) -> &mut Self {
self.0.parameter_transform(scalar, r#move);
self
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> Cut for NurbsCurve<V> {
#[inline(always)]
fn cut(&mut self, t: f64) -> Self { NurbsCurve(self.0.cut(t)) }
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> Concat<NurbsCurve<V>>
for NurbsCurve<V>
where <V as Homogeneous<f64>>::Point: Debug
{
type Output = NurbsCurve<V>;
fn try_concat(
&self,
other: &Self,
) -> std::result::Result<Self, ConcatError<<V as Homogeneous<f64>>::Point>> {
let curve0 = self.clone();
let mut curve1 = other.clone();
let w0 = curve0.0.control_points.last().unwrap().weight();
let w1 = curve1.0.control_points[0].weight();
curve1.transform_control_points(|pt| *pt *= w0 / w1);
match curve0.0.try_concat(&curve1.0) {
Ok(curve) => Ok(NurbsCurve::new(curve)),
Err(err) => Err(err.point_map(|v| v.to_point())),
}
}
}
#[test]
fn concat_positive_test() {
let mut part0 = NurbsCurve::new(BSplineCurve::new(
KnotVec::uniform_knot(4, 4),
(0..8)
.map(|_| {
Vector4::new(
rand::random::<f64>(),
rand::random::<f64>(),
rand::random::<f64>(),
rand::random::<f64>() + 0.5,
)
})
.collect(),
));
let mut part1 = part0.cut(0.56);
let w = 20.0 * rand::random::<f64>() - 10.0;
part1.transform_control_points(|vec| *vec *= w);
assert_near!(part0.back(), part1.front());
concat_random_test(&part0, &part1, 10);
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> NurbsCurve<V>
where V::Point: Tolerance
{
/// Makes the rational curve locally injective.
/// # Example
/// ```
/// use truck_geometry::prelude::*;
/// const N : usize = 100; // sample size for test
///
/// let knot_vec = KnotVec::from(
/// vec![0.0, 0.0, 0.0, 1.0, 3.0, 4.0, 4.0, 4.0]
/// );
/// let control_points = vec![
/// Vector4::new(1.0, 0.0, 0.0, 1.0),
/// Vector4::new(0.0, 1.0, 0.0, 1.0),
/// Vector4::new(0.0, 2.0, 0.0, 2.0),
/// Vector4::new(0.0, 3.0, 0.0, 3.0),
/// Vector4::new(0.0, 0.0, 3.0, 3.0),
/// ];
///
/// let mut curve = NurbsCurve::new(BSplineCurve::new(knot_vec, control_points));
/// let mut flag = false;
/// for i in 0..N {
/// let t = 4.0 * (i as f64) / (N as f64);
/// let pt0 = curve.subs(t);
/// let pt1 = curve.subs(t + 1.0 / (N as f64));
/// flag = flag || pt0.near(&pt1);
/// }
/// // There exists t such that bspcurve(t) == bspcurve(t + 0.01).
/// assert!(flag);
///
/// curve.make_locally_injective().knot_normalize();
/// let mut flag = false;
/// for i in 0..N {
/// let t = 1.0 * (i as f64) / (N as f64);
/// let pt0 = curve.subs(t);
/// let pt1 = curve.subs(t + 1.0 / (N as f64));
/// flag = flag || pt0.near(&pt1);
/// }
/// // There does not exist t such that bspcurve(t) == bspcurve(t + 0.01).
/// assert!(!flag);
/// ```
/// # Remarks
/// If `self` is a constant curve, then does nothing.
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0]);
/// let ctrl_pts = vec![
/// Vector3::new(1.0, 1.0, 1.0),
/// Vector3::new(2.0, 2.0, 2.0),
/// Vector3::new(3.0, 3.0, 3.0),
/// Vector3::new(4.0, 4.0, 4.0),
/// ];
/// let mut curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
/// let org_curve = curve.clone();
/// curve.make_locally_injective();
/// assert_eq!(curve, org_curve);
/// ```
pub fn make_locally_injective(&mut self) -> &mut Self {
let mut iter = self.0.bezier_decomposition().into_iter();
while let Some(bezier) = iter.next().map(NurbsCurve::new) {
if !bezier.is_const() {
*self = bezier;
break;
}
}
let mut x = 0.0;
for mut bezier in iter.map(NurbsCurve::new) {
if bezier.is_const() {
x += bezier.0.knot_vec.range_length();
} else {
let s0 = self.0.control_points.last().unwrap().weight();
let s1 = bezier.0.control_points[0].weight();
bezier
.0
.control_points
.iter_mut()
.for_each(move |vec| *vec *= s0 / s1);
self.concat(bezier.knot_translate(-x));
}
}
self
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParameterDivision1D for NurbsCurve<V>
where V::Point: MetricSpace<Metric = f64> + HashGen<f64>
{
type Point = V::Point;
#[inline(always)]
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<V::Point>) {
algo::curve::parameter_division(self, range, tol)
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> SearchNearestParameter<D1> for NurbsCurve<V>
where
V::Point: MetricSpace<Metric = f64>,
<V::Point as EuclideanSpace>::Diff: InnerSpace + Tolerance,
{
type Point = V::Point;
/// Searches the parameter `t` which minimize |self(t) - point| by Newton's method with initial guess `hint`.
/// Returns `None` if the number of attempts exceeds `trial` i.e. if `trial == 0`, then the trial is only one time.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
///
/// // Defines the half unit circle in x > 0.
/// let knot_vec = KnotVec::bezier_knot(2);
/// let ctrl_pts = vec![Vector3::new(0.0, -1.0, 1.0), Vector3::new(1.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 1.0)];
/// let curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
///
/// // search rational nearest parameter
/// let pt = Point2::new(1.0, 2.0);
/// let hint = 0.8;
/// let t = curve.search_nearest_parameter(pt, Some(hint), 100).unwrap();
///
/// // check the answer
/// let res = curve.subs(t);
/// let ans = Point2::from_vec(pt.to_vec().normalize());
/// assert_near!(res, ans);
/// ```
/// # Remarks
/// It may converge to a local solution depending on the hint.
/// ```
/// use truck_geometry::prelude::*;
///
/// // Same curve and point as above example
/// let knot_vec = KnotVec::bezier_knot(2);
/// let ctrl_pts = vec![Vector3::new(0.0, -1.0, 1.0), Vector3::new(1.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 1.0)];
/// let curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
/// let pt = Point2::new(1.0, 2.0);
///
/// // another hint
/// let hint = 0.5;
///
/// // Newton's method is vibration divergent.
/// assert!(curve.search_nearest_parameter(pt, Some(hint), 100).is_none());
/// ```
#[inline(always)]
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
point: V::Point,
hint: H,
trial: usize,
) -> Option<f64> {
let hint = match hint.into() {
SPHint1D::Parameter(hint) => hint,
SPHint1D::Range(x, y) => {
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
}
SPHint1D::None => {
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
}
};
algo::curve::search_nearest_parameter(self, point, hint, trial)
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> SearchParameter<D1> for NurbsCurve<V>
where
V::Point: MetricSpace<Metric = f64>,
<V::Point as EuclideanSpace>::Diff: InnerSpace + Tolerance,
{
type Point = V::Point;
#[inline(always)]
fn search_parameter<H: Into<SPHint1D>>(
&self,
point: V::Point,
hint: H,
trial: usize,
) -> Option<f64> {
let hint = match hint.into() {
SPHint1D::Parameter(hint) => hint,
SPHint1D::Range(x, y) => {
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
}
SPHint1D::None => {
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
}
};
algo::curve::search_parameter(self, point, hint, trial)
}
}
impl<V: Homogeneous<f64>> NurbsCurve<V>
where V::Point: Bounded<Scalar = f64>
{
/// Returns the bounding box including all control points.
#[inline(always)]
pub fn roughly_bounding_box(&self) -> BoundingBox<V::Point> {
self.0.control_points.iter().map(|p| p.to_point()).collect()
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParametricCurve for NurbsCurve<V> {
type Point = V::Point;
type Vector = <V::Point as EuclideanSpace>::Diff;
#[inline(always)]
fn subs(&self, t: f64) -> Self::Point { self.0.subs(t).to_point() }
#[inline(always)]
fn der(&self, t: f64) -> Self::Vector {
let pt = self.0.subs(t);
let der = self.0.der(t);
pt.rat_der(der)
}
#[inline(always)]
fn der2(&self, t: f64) -> Self::Vector {
let pt = self.0.subs(t);
let der = self.0.der(t);
let der2 = self.0.der2(t);
pt.rat_der2(der, der2)
}
#[inline(always)]
fn parameter_range(&self) -> ParameterRange {
(
Bound::Included(self.0.knot_vec[0]),
Bound::Included(self.0.knot_vec[self.0.knot_vec.len() - 1]),
)
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> BoundedCurve for NurbsCurve<V> {}
impl<V: Clone> Invertible for NurbsCurve<V> {
#[inline(always)]
fn invert(&mut self) { self.invert(); }
#[inline(always)]
fn inverse(&self) -> Self {
let mut curve = self.0.clone();
curve.invert();
NurbsCurve(curve)
}
}
impl<M, V: Copy> Transformed<M> for NurbsCurve<V>
where M: Copy + std::ops::Mul<V, Output = V>
{
#[inline(always)]
fn transform_by(&mut self, trans: M) {
self.0
.control_points
.iter_mut()
.for_each(move |v| *v = trans * *v)
}
}
impl<V: Homogeneous<f64>> From<BSplineCurve<V::Point>> for NurbsCurve<V> {
fn from(bspcurve: BSplineCurve<V::Point>) -> NurbsCurve<V> {
NurbsCurve::new(BSplineCurve::new_unchecked(
bspcurve.knot_vec,
bspcurve
.control_points
.into_iter()
.map(V::from_point)
.collect(),
))
}
}
#[test]
fn test_parameter_division() {
let knot_vec = KnotVec::uniform_knot(2, 3);
let ctrl_pts = vec![
Vector4::new(0.0, 0.0, 0.0, 1.0),
Vector4::new(2.0, 0.0, 0.0, 2.0),
Vector4::new(0.0, 3.0, 0.0, 3.0),
Vector4::new(0.0, 0.0, 2.0, 2.0),
Vector4::new(1.0, 1.0, 1.0, 1.0),
];
let curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
let tol = 0.01;
let (div, pts) = curve.parameter_division(curve.range_tuple(), tol * 0.5);
let knot_vec = curve.knot_vec();
assert_eq!(knot_vec[0], div[0]);
assert_eq!(knot_vec.range_length(), div.last().unwrap() - div[0]);
for i in 1..div.len() {
let pt0 = curve.subs(div[i - 1]);
assert_eq!(pt0, pts[i - 1]);
let pt1 = curve.subs(div[i]);
assert_eq!(pt1, pts[i]);
let value_middle = pt0.midpoint(pt1);
let param_middle = curve.subs((div[i - 1] + div[i]) / 2.0);
let dist = value_middle.distance(param_middle);
assert!(dist < tol, "large distance: {dist}");
}
}
+920
View File
@@ -0,0 +1,920 @@
use super::*;
impl<V> NurbsSurface<V> {
/// constructor
#[inline(always)]
pub const fn new(bspsurface: BSplineSurface<V>) -> Self { NurbsSurface(bspsurface) }
/// Returns the nurbs surface before rationalized
#[inline(always)]
pub const fn non_rationalized(&self) -> &BSplineSurface<V> { &self.0 }
/// Returns the nurbs surface before rationalized
#[inline(always)]
pub fn non_rationalized_mut(&mut self) -> &mut BSplineSurface<V> { &mut self.0 }
/// Returns the nurbs surface before rationalized
#[inline(always)]
pub fn into_non_rationalized(self) -> BSplineSurface<V> { self.0 }
/// Returns the reference of the knot vectors
#[inline(always)]
pub const fn knot_vecs(&self) -> &(KnotVec, KnotVec) { &self.0.knot_vecs }
/// Returns the u knot vector.
#[inline(always)]
pub const fn uknot_vec(&self) -> &KnotVec { &self.0.knot_vecs.0 }
/// Returns the v knot vector.
#[inline(always)]
pub const fn vknot_vec(&self) -> &KnotVec { &self.0.knot_vecs.1 }
/// Returns the `idx`th u knot.
#[inline(always)]
pub fn uknot(&self, idx: usize) -> f64 { self.0.knot_vecs.0[idx] }
/// returns the `idx`th v knot.
#[inline(always)]
pub fn vknot(&self, idx: usize) -> f64 { self.0.knot_vecs.1[idx] }
/// Returns the reference of the vector of the control points
#[inline(always)]
pub const fn control_points(&self) -> &Vec<Vec<V>> { &self.0.control_points }
/// Returns the reference of the control point corresponding to the index `(idx0, idx1)`.
#[inline(always)]
pub fn control_point(&self, idx0: usize, idx1: usize) -> &V {
&self.0.control_points[idx0][idx1]
}
/// Apply the given transformation to all control points.
#[inline(always)]
pub fn transform_control_points<F: FnMut(&mut V)>(&mut self, f: F) {
self.0.transform_control_points(f)
}
/// Returns the iterator over the control points in the `column_idx`th row.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let uknot_vec = KnotVec::bezier_knot(1);
/// let vknot_vec = KnotVec::bezier_knot(2);
/// let knot_vecs = (uknot_vec, vknot_vec);
/// let ctrl_pts = vec![
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
/// ];
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
/// let mut iter = bspsurface.ctrl_pts_row_iter(1);
/// assert_eq!(iter.next(), Some(&Vector3::new(1.0, 0.0, 1.0)));
/// assert_eq!(iter.next(), Some(&Vector3::new(1.0, 1.0, 1.0)));
/// assert_eq!(iter.next(), None);
/// ```
#[inline(always)]
pub fn ctrl_pts_row_iter(
&self,
column_idx: usize,
) -> impl ExactSizeIterator<Item = &V> + std::iter::FusedIterator<Item = &V> {
self.0.ctrl_pts_row_iter(column_idx)
}
/// Returns the iterator over the control points in the `row_idx`th row.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let uknot_vec = KnotVec::bezier_knot(1);
/// let vknot_vec = KnotVec::bezier_knot(2);
/// let knot_vecs = (uknot_vec, vknot_vec);
/// let ctrl_pts = vec![
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
/// ];
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
/// let mut iter = bspsurface.ctrl_pts_column_iter(1);
/// assert_eq!(iter.next(), Some(&Vector3::new(0.0, 1.0, 0.0)));
/// assert_eq!(iter.next(), Some(&Vector3::new(1.0, 1.0, 1.0)));
/// assert_eq!(iter.next(), Some(&Vector3::new(2.0, 1.0, 2.0)));
/// assert_eq!(iter.next(), None);
/// ```
#[inline(always)]
pub fn ctrl_pts_column_iter(&self, row_idx: usize) -> std::slice::Iter<'_, V> {
self.0.control_points[row_idx].iter()
}
/// Returns the mutable reference of the control point corresponding to index `(idx0, idx1)`.
#[inline(always)]
pub fn control_point_mut(&mut self, idx0: usize, idx1: usize) -> &mut V {
&mut self.0.control_points[idx0][idx1]
}
/// Returns the iterator on all control points
#[inline(always)]
pub fn control_points_mut(&mut self) -> impl Iterator<Item = &mut V> {
self.0.control_points.iter_mut().flatten()
}
/// Returns the degrees of B-spline surface
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let uknot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
/// let vknot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
/// let knot_vecs = (uknot_vec, vknot_vec);
/// let ctrl_pts = vec![
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
/// ];
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
/// assert_eq!(bspsurface.udegree(), 1);
/// ```
#[inline(always)]
pub fn udegree(&self) -> usize { self.0.udegree() }
/// Returns the degrees of B-spline surface
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let uknot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
/// let vknot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
/// let knot_vecs = (uknot_vec, vknot_vec);
/// let ctrl_pts = vec![
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
/// ];
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
/// assert_eq!(bspsurface.vdegree(), 2);
/// ```
#[inline(always)]
pub fn vdegree(&self) -> usize { self.0.vdegree() }
/// Returns the degrees of B-spline surface
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let uknot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
/// let vknot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
/// let knot_vecs = (uknot_vec, vknot_vec);
/// let ctrl_pts = vec![
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
/// ];
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
/// assert_eq!(bspsurface.degrees(), (1, 2));
/// ```
#[inline(always)]
pub fn degrees(&self) -> (usize, usize) { (self.udegree(), self.vdegree()) }
/// Returns whether the knot vectors are clamped or not.
#[inline(always)]
pub fn is_clamped(&self) -> bool { self.0.is_clamped() }
/// Swaps two parameters.
pub fn swap_axes(&mut self) -> &mut Self
where V: Clone {
self.0.swap_axes();
self
}
/// The range of the parameter of the surface.
#[inline(always)]
pub fn parameter_range(&self) -> (ParameterRange, ParameterRange) { self.0.parameter_range() }
/// Creates the curve whose control points are the `idx`th column control points of `self`.
#[inline(always)]
pub fn column_curve(&self, row_idx: usize) -> NurbsCurve<V>
where V: Clone {
NurbsCurve(self.0.column_curve(row_idx))
}
/// Creates the column sectional curve.
#[inline(always)]
pub fn row_curve(&self, column_idx: usize) -> NurbsCurve<V>
where V: Clone {
NurbsCurve(self.0.row_curve(column_idx))
}
}
impl<V: Homogeneous<f64>> NurbsSurface<V> {
/// Constructs a rationalization surface from the non-rationalized surface and weights.
/// # Failures
/// the length of `surface.control_points()` and `weights` must be the same.
#[inline(always)]
pub fn try_from_bspline_and_weights(
surface: BSplineSurface<V::Point>,
weights: Vec<Vec<f64>>,
) -> Result<Self> {
let BSplineSurface {
knot_vecs,
control_points,
} = surface;
if control_points.len() != weights.len() {
return Err(Error::DifferentLength);
}
let control_points = control_points
.into_iter()
.zip(weights)
.map(|(control_points, weights)| {
if control_points.len() != weights.len() {
return Err(Error::DifferentLength);
}
Ok(control_points
.into_iter()
.zip(weights)
.map(|(pt, w)| V::from_point_weight(pt, w))
.collect::<Vec<_>>())
})
.collect::<Result<Vec<_>>>()?;
Ok(Self(BSplineSurface::new_unchecked(
knot_vecs,
control_points,
)))
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsSurface<V> {
/// Substitutes to a NURBS surface.
#[inline(always)]
pub fn subs(&self, u: f64, v: f64) -> V::Point { self.0.subs(u, v).to_point() }
/// Substitutes derived NURBS surface by the first parameter `u`.
#[inline(always)]
pub fn uder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
let pt = self.0.subs(u, v);
let ud = self.0.uder(u, v);
pt.rat_der(ud)
}
/// Substitutes derived NURBS surface by the first parameter `v`.
#[inline(always)]
pub fn vder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
let pt = self.0.subs(u, v);
let vd = self.0.vder(u, v);
pt.rat_der(vd)
}
/// Substitutes 2nd-ord derived NURBS surface by the first parameter `u`.
#[inline(always)]
pub fn uuder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
let pt = self.0.subs(u, v);
let ud = self.0.uder(u, v);
let uud = self.0.uuder(u, v);
pt.rat_der2(ud, uud)
}
/// Substitutes 2nd-ord derived NURBS surface by the first parameter `v`.
#[inline(always)]
pub fn vvder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
let pt = self.0.subs(u, v);
let vd = self.0.vder(u, v);
let vvd = self.0.vvder(u, v);
pt.rat_der2(vd, vvd)
}
/// Substitutes 2nd-ord derived NURBS surface by both parameter `u, v`.
#[inline(always)]
pub fn uvder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
let pt = self.0.subs(u, v);
let ud = self.0.uder(u, v);
let vd = self.0.vder(u, v);
let uvd = self.0.uvder(u, v);
pt.rat_cross_der(ud, vd, uvd)
}
/// Returns the closure of substitution.
#[inline(always)]
pub fn get_closure(&self) -> impl Fn(f64, f64) -> V::Point + '_ { move |u, v| self.subs(u, v) }
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsSurface<V>
where V::Point: Tolerance
{
/// Returns whether constant curve or not, i.e. all control points are same or not.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let uknot_vec = KnotVec::bezier_knot(1);
/// let vknot_vec = KnotVec::bezier_knot(2);
/// let pt = Vector3::new(1.0, 2.0, 1.0);
/// // allows differences upto scalars
/// let ctrl_pts = vec![
/// vec![pt.clone(), pt.clone() * 2.0, pt.clone() * 3.0],
/// vec![pt.clone() * 0.5, pt.clone() * 0.25, pt.clone() * 0.125],
/// ];
/// let mut surface = NurbsSurface::new(BSplineSurface::new((uknot_vec, vknot_vec), ctrl_pts));
/// assert!(surface.is_const());
///
/// *surface.control_point_mut(1, 2) = Vector3::new(2.0, 3.0, 1.0);
/// assert!(!surface.is_const());
/// ```
#[inline(always)]
pub fn is_const(&self) -> bool {
let pt = self.0.control_points[0][0].to_point();
for vec in self.0.control_points.iter().flat_map(|pts| pts.iter()) {
if !vec.to_point().near(&pt) {
return false;
}
}
true
}
/// Determines whether `self` and `other` is near as the B-spline rational surfaces or not.
///
/// Divides each knot domain into the number of degree equal parts,
/// and check `|self(u, v) - other(u, v)| < TOLERANCE` for each end points `(u, v)`.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
/// let ctrl_pts = vec![
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.5, -1.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
/// vec![Vector3::new(0.0, 1.0, 1.0), Vector3::new(0.5, 1.0, 1.0), Vector3::new(1.0, 1.0, 1.0)],
/// vec![Vector3::new(0.0, 2.0, 1.0), Vector3::new(0.5, 2.0, 3.0), Vector3::new(1.0, 2.0, 1.0)],
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(0.5, 3.5, 2.0), Vector3::new(1.0, 3.0, 1.0)],
/// ];
/// let surface0 = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
/// let mut surface1 = surface0.clone();
/// assert!(surface0.near_as_surface(&surface1));
///
/// *surface1.control_point_mut(1, 1) = Vector3::new(0.5, 1.0, 0.9);
/// assert!(!surface0.near_as_surface(&surface1));
/// ```
#[inline(always)]
pub fn near_as_surface(&self, other: &Self) -> bool {
self.0
.sub_near_as_surface(&other.0, 2, move |x, y| x.to_point().near(&y.to_point()))
}
/// Determines whether `self` and `other` is near in square order as the B-spline rational
/// surfaces or not.
///
/// Divides each knot domain into the number of degree equal parts,
/// and check `|self(u, v) - other(u, v)| < TOLERANCE` for each end points `(u, v)`.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let eps = TOLERANCE;
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
/// let ctrl_pts = vec![
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.5, -1.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
/// vec![Vector3::new(0.0, 1.0, 1.0), Vector3::new(0.5, 1.0, 1.0), Vector3::new(1.0, 1.0, 1.0)],
/// vec![Vector3::new(0.0, 2.0, 1.0), Vector3::new(0.5, 2.0, 3.0), Vector3::new(1.0, 2.0, 1.0)],
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(0.5, 3.5, 2.0), Vector3::new(1.0, 3.0, 1.0)],
/// ];
/// let surface0 = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
/// let mut surface1 = surface0.clone();
/// assert!(surface0.near_as_surface(&surface1));
///
/// *surface1.control_point_mut(1, 1) = Vector3::new(0.5, 1.0, 1.0 - eps);
/// assert!(surface0.near_as_surface(&surface1));
/// assert!(!surface0.near2_as_surface(&surface1));
/// ```
#[inline(always)]
pub fn near2_as_surface(&self, other: &Self) -> bool {
self.0
.sub_near_as_surface(&other.0, 2, move |x, y| x.to_point().near2(&y.to_point()))
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> NurbsSurface<V> {
/// Adds a knot `x` of the first parameter `u`, and do not change `self` as a surface.
#[inline(always)]
pub fn add_uknot(&mut self, x: f64) -> &mut Self {
self.0.add_uknot(x);
self
}
/// Adds a knot `x` of the first parameter `u`, and do not change `self` as a surface.
#[inline(always)]
pub fn add_vknot(&mut self, x: f64) -> &mut Self {
self.0.add_vknot(x);
self
}
/// Removes the uknot corresponding to the indice `idx`, and do not change `self` as a curve.
/// If the knot cannot be removed, returns [`Error::CannotRemoveKnot`].
#[inline(always)]
pub fn try_remove_uknot(&mut self, idx: usize) -> Result<&mut Self> {
match self.0.try_remove_uknot(idx) {
Ok(_) => Ok(self),
Err(error) => Err(error),
}
}
/// Removes the uknot corresponding to the indices `idx`, and do not change `self` as a curve.
/// If cannot remove the knot, do not change `self` and return `self`.
#[inline(always)]
pub fn remove_uknot(&mut self, idx: usize) -> &mut Self {
self.0.remove_uknot(idx);
self
}
/// Removes the uknot corresponding to the indice `idx`, and do not change `self` as a curve.
/// If the knot cannot be removed, returns [`Error::CannotRemoveKnot`].
#[inline(always)]
pub fn try_remove_vknot(&mut self, idx: usize) -> Result<&mut Self> {
match self.0.try_remove_vknot(idx) {
Ok(_) => Ok(self),
Err(error) => Err(error),
}
}
/// Removes the uknot corresponding to the indices `idx`, and do not change `self` as a curve.
/// If cannot remove the knot, do not change `self` and return `self`.
#[inline(always)]
pub fn remove_vknot(&mut self, idx: usize) -> &mut Self {
self.0.remove_vknot(idx);
self
}
/// Elevates the udegree.
#[inline(always)]
pub fn elevate_udegree(&mut self) -> &mut Self {
self.0.elevate_udegree();
self
}
/// Elevates the vdegree.
#[inline(always)]
pub fn elevate_vdegree(&mut self) -> &mut Self {
self.0.elevate_vdegree();
self
}
/// Aligns the udegree with the same degrees.
#[inline(always)]
pub fn syncro_uvdegrees(&mut self) -> &mut Self {
self.0.syncro_uvdegrees();
self
}
/// Makes the uknot vector and the vknot vector the same knot vector.
#[inline(always)]
pub fn syncro_uvknots(&mut self) -> &mut Self {
self.0.syncro_uvknots();
self
}
/// Cuts the surface into two surfaces at the parameter `u`
#[inline(always)]
pub fn ucut(&mut self, u: f64) -> Self { Self::new(self.0.ucut(u)) }
/// Cuts the surface into two surfaces at the parameter `v`
#[inline(always)]
pub fn vcut(&mut self, v: f64) -> Self { Self::new(self.0.vcut(v)) }
/// Normalizes the knot vectors
#[inline(always)]
pub fn knot_normalize(&mut self) -> &mut Self {
self.0.knot_normalize();
self
}
/// Translates the knot vectors.
#[inline(always)]
pub fn knot_translate(&mut self, x: f64, y: f64) -> &mut Self {
self.0.knot_translate(x, y);
self
}
/// Removes knots in order from the back
#[inline(always)]
pub fn optimize(&mut self) -> &mut Self {
self.0.optimize();
self
}
/// Get the boundary by four splitted curves.
/// # Example
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
/// let ctrl_pts = vec![
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.5, -1.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
/// vec![Vector3::new(0.0, 1.0, 2.0), Vector3::new(0.5, 1.0, 3.0), Vector3::new(1.0, 1.0, 2.0)],
/// vec![Vector3::new(0.0, 2.0, 2.0), Vector3::new(0.5, 2.0, 3.0), Vector3::new(1.0, 2.0, 2.0)],
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(0.5, 3.5, 2.0), Vector3::new(1.0, 3.0, 1.0)],
/// ];
/// let bspsurface = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
/// let curves = bspsurface.splitted_boundary();
/// assert_eq!(
/// curves[0].control_points(),
/// &vec![
/// Vector3::new(0.0, 0.0, 1.0),
/// Vector3::new(0.0, 1.0, 2.0),
/// Vector3::new(0.0, 2.0, 2.0),
/// Vector3::new(0.0, 3.0, 1.0),
/// ],
/// );
/// assert_eq!(
/// curves[1].control_points(),
/// &vec![
/// Vector3::new(0.0, 3.0, 1.0),
/// Vector3::new(0.5, 3.5, 2.0),
/// Vector3::new(1.0, 3.0, 1.0),
/// ],
/// );
/// assert_eq!(
/// curves[2].control_points(),
/// &vec![
/// Vector3::new(1.0, 3.0, 1.0),
/// Vector3::new(1.0, 2.0, 2.0),
/// Vector3::new(1.0, 1.0, 2.0),
/// Vector3::new(1.0, 0.0, 1.0),
/// ],
/// );
/// assert_eq!(
/// curves[3].control_points(),
/// &vec![
/// Vector3::new(1.0, 0.0, 1.0),
/// Vector3::new(0.5, -1.0, 2.0),
/// Vector3::new(0.0, 0.0, 1.0),
/// ],
/// );
/// ```
#[inline(always)]
pub fn splitted_boundary(&self) -> [NurbsCurve<V>; 4] {
TryFrom::try_from(
self.0
.splitted_boundary()
.iter()
.cloned()
.map(NurbsCurve::new)
.collect::<Vec<_>>(),
)
.unwrap()
}
/// Extracts the boundary of surface
#[inline(always)]
pub fn boundary(&self) -> NurbsCurve<V> { NurbsCurve::new(self.0.boundary()) }
}
impl<V: Homogeneous<f64>> SearchNearestParameter<D2> for NurbsSurface<V>
where
Self: ParametricSurface<Point = V::Point, Vector = <V::Point as EuclideanSpace>::Diff>,
V::Point: EuclideanSpace<Scalar = f64> + MetricSpace<Metric = f64>,
<V::Point as EuclideanSpace>::Diff: InnerSpace<Scalar = f64> + Tolerance,
{
type Point = V::Point;
/// Searches the parameter `(u, v)` which minimize `|self(u, v) - point|` by Newton's method
/// with initial guess `(u0, v0)`. If the repeated trial does not converge, then returns `None`.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
/// let ctrl_pts = vec![
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(1.0, -2.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
/// vec![Vector3::new(0.0, 2.0, 2.0), Vector3::new(2.0, 4.0, 4.0), Vector3::new(2.0, 2.0, 2.0)],
/// vec![Vector3::new(0.0, 4.0, 2.0), Vector3::new(2.0, 8.0, 4.0), Vector3::new(2.0, 4.0, 2.0)],
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(1.0, 7.0, 2.0), Vector3::new(1.0, 3.0, 1.0)],
/// ];
/// let surface = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
/// let pt = surface.subs(0.3, 0.7);
/// let (u, v) = surface.search_nearest_parameter(pt, Some((0.5, 0.5)), 100).unwrap();
/// assert!(u.near(&0.3) && v.near(&0.7));
/// ```
/// # Remarks
/// It may converge to a local solution depending on the hint.
/// cf. [`BSplineCurve::search_rational_nearest_parameter`](struct.BSplineCurve.html#method.search_rational_nearest_parameter)
#[inline(always)]
fn search_nearest_parameter<H: Into<SPHint2D>>(
&self,
point: V::Point,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
let hint = match hint.into() {
SPHint2D::Parameter(x, y) => (x, y),
SPHint2D::Range(range0, range1) => {
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
}
SPHint2D::None => {
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
}
};
algo::surface::search_nearest_parameter(self, point, hint, trials)
}
}
impl<V: Homogeneous<f64>> NurbsSurface<V>
where V::Point: Bounded<Scalar = f64>
{
/// Returns the bounding box including all control points.
#[inline(always)]
pub fn roughly_bounding_box(&self) -> BoundingBox<V::Point> {
self.0
.control_points
.iter()
.flatten()
.map(|pt| pt.to_point())
.collect()
}
}
impl SearchParameter<D2> for NurbsSurface<Vector3> {
type Point = Point2;
/// Search the parameter `(u, v)` such that `self.subs(u, v).rational_projection()` is near `pt`.
/// If cannot find, then return `None`.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vec = KnotVec::uniform_knot(2, 2);
/// let ctrl_pts = vec![
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.1, 0.0, 0.5), Vector3::new(0.5, 0.0, 0.3), Vector3::new(1.0, 0.0, 1.0)],
/// vec![Vector3::new(0.0, 0.1, 0.1), Vector3::new(0.2, 0.2, 0.1), Vector3::new(0.4, 0.3, 0.4), Vector3::new(1.0, 0.3, 0.7)],
/// vec![Vector3::new(0.0, 0.5, 0.4), Vector3::new(0.3, 0.6, 0.5), Vector3::new(0.6, 0.4, 1.0), Vector3::new(1.0, 0.5, 0.4)],
/// vec![Vector3::new(0.0, 1.0, 1.0), Vector3::new(0.1, 1.0, 1.0), Vector3::new(0.5, 1.0, 0.5), Vector3::new(1.0, 1.0, 0.3)],
/// ];
/// let bspsurface = BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts);
/// let surface = NurbsSurface::new(bspsurface);
///
/// let pt = surface.subs(0.3, 0.7);
/// let (u, v) = surface.search_parameter(pt, Some((0.5, 0.5)), 100).unwrap();
/// assert_near!(surface.subs(u, v), pt);
/// ```
#[inline(always)]
fn search_parameter<H: Into<SPHint2D>>(
&self,
point: Point2,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
let hint = match hint.into() {
SPHint2D::Parameter(x, y) => (x, y),
SPHint2D::Range(range0, range1) => {
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
}
SPHint2D::None => {
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
}
};
algo::surface::search_parameter2d(self, point, hint, trials)
}
}
impl<V: Clone> Invertible for NurbsSurface<V> {
#[inline(always)]
fn invert(&mut self) { self.swap_axes(); }
#[inline(always)]
fn inverse(&self) -> Self {
let mut surface = self.clone();
surface.swap_axes();
surface
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParametricSurface for NurbsSurface<V> {
type Point = V::Point;
type Vector = <V::Point as EuclideanSpace>::Diff;
#[inline(always)]
fn subs(&self, u: f64, v: f64) -> Self::Point { self.subs(u, v) }
#[inline(always)]
fn uder(&self, u: f64, v: f64) -> Self::Vector { self.uder(u, v) }
#[inline(always)]
fn vder(&self, u: f64, v: f64) -> Self::Vector { self.vder(u, v) }
#[inline(always)]
fn uuder(&self, u: f64, v: f64) -> Self::Vector { self.uuder(u, v) }
#[inline(always)]
fn uvder(&self, u: f64, v: f64) -> Self::Vector { self.uvder(u, v) }
#[inline(always)]
fn vvder(&self, u: f64, v: f64) -> Self::Vector { self.vvder(u, v) }
#[inline(always)]
fn parameter_range(&self) -> (ParameterRange, ParameterRange) { self.parameter_range() }
}
impl ParametricSurface3D for NurbsSurface<Vector4> {
#[inline(always)]
fn normal(&self, u: f64, v: f64) -> Vector3 {
let pt = self.0.subs(u, v);
let ud = self.0.uder(u, v);
let vd = self.0.vder(u, v);
pt.rat_der(ud).cross(pt.rat_der(vd)).normalize()
}
}
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParameterDivision2D for NurbsSurface<V>
where V::Point: MetricSpace<Metric = f64> + HashGen<f64>
{
#[inline(always)]
fn parameter_division(
&self,
range: ((f64, f64), (f64, f64)),
tol: f64,
) -> (Vec<f64>, Vec<f64>) {
algo::surface::parameter_division(self, range, tol)
}
}
impl<V> BoundedSurface for NurbsSurface<V> where Self: ParametricSurface {}
impl IncludeCurve<NurbsCurve<Vector3>> for NurbsSurface<Vector3> {
#[inline(always)]
fn include(&self, curve: &NurbsCurve<Vector3>) -> bool {
let pt = curve.subs(curve.knot_vec()[0]);
let mut hint = match self.search_parameter(pt, None, INCLUDE_CURVE_TRIALS) {
Some(got) => got,
None => return false,
};
let uknot_vec = self.uknot_vec();
let vknot_vec = self.vknot_vec();
let degree = curve.degree() * 6;
let (knots, _) = curve.knot_vec().to_single_multi();
for i in 1..knots.len() {
for j in 1..=degree {
let p = j as f64 / degree as f64;
let t = knots[i - 1] * (1.0 - p) + knots[i] * p;
let pt = curve.subs(t);
hint = match self.search_parameter(pt, Some(hint), INCLUDE_CURVE_TRIALS) {
Some(got) => got,
None => return false,
};
if !self.subs(hint.0, hint.1).near(&pt)
|| hint.0 < uknot_vec[0] - TOLERANCE
|| hint.0 - uknot_vec[0] > uknot_vec.range_length() + TOLERANCE
|| hint.1 < vknot_vec[0] - TOLERANCE
|| hint.1 - vknot_vec[0] > vknot_vec.range_length() + TOLERANCE
{
return false;
}
}
}
true
}
}
impl IncludeCurve<BSplineCurve<Point3>> for NurbsSurface<Vector4> {
#[inline(always)]
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
let pt = curve.front();
let mut hint = match self.search_parameter(pt, None, INCLUDE_CURVE_TRIALS) {
Some(got) => got,
None => return false,
};
let uknot_vec = self.uknot_vec();
let vknot_vec = self.vknot_vec();
let degree = curve.degree() * 6;
let (knots, _) = curve.knot_vec().to_single_multi();
for i in 1..knots.len() {
for j in 1..=degree {
let p = j as f64 / degree as f64;
let t = knots[i - 1] * (1.0 - p) + knots[i] * p;
let pt = curve.subs(t);
hint = match self.search_parameter(pt, Some(hint), INCLUDE_CURVE_TRIALS) {
Some(got) => got,
None => return false,
};
if !self.subs(hint.0, hint.1).near(&pt)
|| hint.0 < uknot_vec[0] - TOLERANCE
|| hint.0 - uknot_vec[0] > uknot_vec.range_length() + TOLERANCE
|| hint.1 < vknot_vec[0] - TOLERANCE
|| hint.1 - vknot_vec[0] > vknot_vec.range_length() + TOLERANCE
{
return false;
}
}
}
true
}
}
impl IncludeCurve<NurbsCurve<Vector4>> for NurbsSurface<Vector4> {
#[inline(always)]
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
let pt = curve.front();
let mut hint = match self.search_parameter(pt, None, INCLUDE_CURVE_TRIALS) {
Some(got) => got,
None => return false,
};
let uknot_vec = self.uknot_vec();
let vknot_vec = self.vknot_vec();
let degree = curve.degree() * 6;
let (knots, _) = curve.knot_vec().to_single_multi();
for i in 1..knots.len() {
for j in 1..=degree {
let p = j as f64 / degree as f64;
let t = knots[i - 1] * (1.0 - p) + knots[i] * p;
let pt = curve.subs(t);
hint = match self.search_parameter(pt, Some(hint), INCLUDE_CURVE_TRIALS) {
Some(got) => got,
None => return false,
};
if !self.subs(hint.0, hint.1).near(&pt)
|| hint.0 < uknot_vec[0] - TOLERANCE
|| hint.0 - uknot_vec[0] > uknot_vec.range_length() + TOLERANCE
|| hint.1 < vknot_vec[0] - TOLERANCE
|| hint.1 - vknot_vec[0] > vknot_vec.range_length() + TOLERANCE
{
return false;
}
}
}
true
}
}
impl<M, V: Copy> Transformed<M> for NurbsSurface<V>
where M: Copy + std::ops::Mul<V, Output = V>
{
#[inline(always)]
fn transform_by(&mut self, trans: M) {
self.0
.control_points
.iter_mut()
.flatten()
.for_each(move |v| *v = trans * *v)
}
}
impl SearchParameter<D2> for NurbsSurface<Vector4> {
type Point = Point3;
/// Search the parameter `(u, v)` such that `self.subs(u, v).rational_projection()` is near `pt`.
/// If cannot find, then return `None`.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let knot_vec = KnotVec::uniform_knot(2, 2);
/// let ctrl_pts = vec![
/// vec![Vector4::new(0.0, 0.0, 0.0, 1.0), Vector4::new(0.1, 0.0, 0.5, 0.4), Vector4::new(1.0, 0.0, 0.6, 2.0), Vector4::new(0.4, 0.0, 0.4, 0.4)],
/// vec![Vector4::new(0.0, 0.2, 0.2, 2.0), Vector4::new(0.24, 0.24, 0.1, 1.2), Vector4::new(2.4, 1.8, 2.4, 0.6), Vector4::new(1.4, 0.42, 0.98, 1.4)],
/// vec![Vector4::new(0.0, 1.5, 1.2, 3.0), Vector4::new(1.02, 2.04, 1.7, 3.4), Vector4::new(0.42, 0.28, 0.7, 0.7), Vector4::new(0.6, 0.3, 0.0, 0.6)],
/// vec![Vector4::new(0.0, 1.0, 1.0, 1.0), Vector4::new(0.2, 2.0, 2.0, 2.0), Vector4::new(0.85, 1.7, 0.85, 1.7), Vector4::new(1.0, 1.0, 0.3, 1.0)],
/// ];
/// let bspsurface = BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts);
/// let surface = NurbsSurface::new(bspsurface);
///
/// let pt = surface.subs(0.3, 0.7);
/// let (u, v) = surface.search_parameter(pt, Some((0.5, 0.5)), 100).unwrap();
/// assert_near!(surface.subs(u, v), pt);
/// ```
fn search_parameter<H: Into<SPHint2D>>(
&self,
point: Point3,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
let hint = match hint.into() {
SPHint2D::Parameter(x, y) => (x, y),
SPHint2D::Range(range0, range1) => {
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
}
SPHint2D::None => {
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
}
};
algo::surface::search_parameter3d(self, point, hint, trials)
}
}
#[test]
fn test_include2d() {
let knot_vec = KnotVec::uniform_knot(2, 3);
let ctrl_pts = vec![
vec![
Vector3::new(0.0, 0.0, 1.0),
Vector3::new(0.05, 0.0, 0.5),
Vector3::new(0.15, 0.0, 0.3),
Vector3::new(1.0, 0.0, 1.0),
],
vec![
Vector3::new(0.0, 0.01, 0.1),
Vector3::new(0.02, 0.02, 0.1),
Vector3::new(0.16, 0.12, 0.4),
Vector3::new(0.7, 0.21, 0.7),
],
vec![
Vector3::new(0.0, 0.02, 0.4),
Vector3::new(0.15, 0.3, 0.5),
Vector3::new(0.6, 0.4, 1.0),
Vector3::new(0.4, 0.2, 0.4),
],
vec![
Vector3::new(0.0, 1.0, 1.0),
Vector3::new(0.1, 1.0, 1.0),
Vector3::new(0.25, 0.5, 0.5),
Vector3::new(0.3, 0.3, 0.3),
],
];
let surface = BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts);
let bnd_box = BoundingBox::from_iter(&[Vector2::new(0.2, 0.3), Vector2::new(0.8, 0.6)]);
let mut curve = surface.sectional_curve(bnd_box);
curve.control_points_mut().for_each(|pt| *pt *= 3.0);
let surface = NurbsSurface::new(surface);
let curve = NurbsCurve::new(curve);
assert!(surface.include(&curve));
}
#[test]
fn test_include3d() {
let knot_vec = KnotVec::bezier_knot(2);
let ctrl_pts = vec![
vec![
Vector4::new(-1.0, -1.0, 2.0, 1.0),
Vector4::new(-1.0, 0.0, 0.0, 1.0),
Vector4::new(-1.0, 1.0, 2.0, 1.0),
],
vec![
Vector4::new(0.0, -1.0, 0.0, 1.0),
Vector4::new(0.0, 0.0, -2.0, 1.0),
Vector4::new(0.0, 1.0, 0.0, 1.0),
],
vec![
Vector4::new(1.0, -1.0, 2.0, 1.0),
Vector4::new(1.0, 0.0, 0.0, 1.0),
Vector4::new(1.0, 1.0, 2.0, 1.0),
],
];
let surface = NurbsSurface::new(BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts));
let knot_vec = KnotVec::from(vec![
0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0,
]);
let ctrl_pts = vec![
// the vector of the indices of control points
Vector4::new(0.0, -2.0, 2.0, 2.0),
Vector4::new(1.0, -1.0, 1.0, 1.0),
Vector4::new(1.0, 0.0, 1.0, 1.0),
Vector4::new(1.0, 1.0, 1.0, 1.0),
Vector4::new(0.0, 2.0, 2.0, 2.0),
Vector4::new(-1.0, 1.0, 1.0, 1.0),
Vector4::new(-1.0, 0.0, 1.0, 1.0),
Vector4::new(-1.0, -1.0, 1.0, 1.0),
Vector4::new(0.0, -2.0, 2.0, 2.0),
];
let mut curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
assert!(surface.include(&curve));
*curve.control_point_mut(1) += Vector4::new(0.0, 0.0, 0.00001, 0.0);
assert!(!surface.include(&curve));
}
+161
View File
@@ -0,0 +1,161 @@
use super::*;
use std::f64::consts::PI;
impl<P> UnitCircle<P> {
/// constructor
#[inline]
pub const fn new() -> Self { Self(std::marker::PhantomData) }
}
impl ParametricCurve for UnitCircle<Point2> {
type Point = Point2;
type Vector = Vector2;
#[inline]
fn subs(&self, t: f64) -> Self::Point { Point2::new(f64::cos(t), f64::sin(t)) }
#[inline]
fn der(&self, t: f64) -> Self::Vector { Vector2::new(-f64::sin(t), f64::cos(t)) }
#[inline]
fn der2(&self, t: f64) -> Self::Vector { Vector2::new(-f64::cos(t), -f64::sin(t)) }
#[inline]
fn parameter_range(&self) -> ParameterRange {
(Bound::Included(0.0), Bound::Excluded(2.0 * PI))
}
}
impl BoundedCurve for UnitCircle<Point2> {}
impl ParametricCurve for UnitCircle<Point3> {
type Point = Point3;
type Vector = Vector3;
#[inline]
fn subs(&self, t: f64) -> Self::Point { Point3::new(f64::cos(t), f64::sin(t), 0.0) }
#[inline]
fn der(&self, t: f64) -> Self::Vector { Vector3::new(-f64::sin(t), f64::cos(t), 0.0) }
#[inline]
fn der2(&self, t: f64) -> Self::Vector { Vector3::new(-f64::cos(t), -f64::sin(t), 0.0) }
#[inline]
fn period(&self) -> Option<f64> { Some(2.0 * PI) }
#[inline]
fn parameter_range(&self) -> ParameterRange {
(Bound::Included(0.0), Bound::Excluded(2.0 * PI))
}
}
impl BoundedCurve for UnitCircle<Point3> {}
impl<P> ParameterDivision1D for UnitCircle<P>
where UnitCircle<P>: ParametricCurve<Point = P>
{
type Point = P;
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<P>) {
nonpositive_tolerance!(tol);
let tol = f64::min(tol, 0.8);
let delta = 2.0 * f64::acos(1.0 - tol);
let n = 1 + ((range.1 - range.0) / delta) as usize;
let params = (0..=n)
.map(|i| {
let t = i as f64 / n as f64;
range.0 * (1.0 - t) + range.1 * t
})
.collect::<Vec<_>>();
let pts = params.iter().map(|t| self.subs(*t)).collect();
(params, pts)
}
}
impl SearchNearestParameter<D1> for UnitCircle<Point2> {
type Point = Point2;
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
pt: Point2,
_: H,
_: usize,
) -> Option<f64> {
let v = pt.to_vec();
if v.magnitude().so_small() {
return None;
}
let v = v.normalize();
let theta = f64::acos(f64::clamp(v.x, -1.0, 1.0));
let theta = match v.y > 0.0 {
true => theta,
false => 2.0 * PI - theta,
};
Some(theta)
}
}
#[test]
fn search_nearest_parameter() {
const N: usize = 100;
let circle = UnitCircle::<Point2>::new();
for i in 0..N {
let t = 2.0 * PI * i as f64 / N as f64;
let a = 5.0 * rand::random::<f64>() + 0.1;
let p = a * circle.subs(t);
let s = circle.search_nearest_parameter(p, None, 1).unwrap();
let q = a * circle.subs(s);
assert_near!(p, q);
}
}
impl SearchParameter<D1> for UnitCircle<Point2> {
type Point = Point2;
fn search_parameter<H: Into<SPHint1D>>(&self, pt: Point2, _: H, _: usize) -> Option<f64> {
let v = pt.to_vec();
if !v.magnitude().near(&1.0) {
return None;
}
let v = v.normalize();
let theta = f64::acos(f64::clamp(v.x, -1.0, 1.0));
let theta = match v.y > 0.0 {
true => theta,
false => 2.0 * PI - theta,
};
Some(theta)
}
}
#[test]
fn search_parameter() {
const N: usize = 100;
let circle = UnitCircle::<Point2>::new();
for i in 1..N {
let t = 2.0 * PI * i as f64 / N as f64;
let p = circle.subs(t);
let s = circle.search_parameter(p, None, 1).unwrap();
assert_near!(s, t);
}
}
impl SearchNearestParameter<D1> for UnitCircle<Point3> {
type Point = Point3;
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
pt: Point3,
_: H,
_: usize,
) -> Option<f64> {
UnitCircle::<Point2>::new().search_nearest_parameter(Point2::new(pt.x, pt.y), None, 0)
}
}
impl SearchParameter<D1> for UnitCircle<Point3> {
type Point = Point3;
fn search_parameter<H: Into<SPHint1D>>(&self, pt: Point3, _: H, _: usize) -> Option<f64> {
if !f64::abs(pt.z).so_small() {
return None;
}
UnitCircle::<Point2>::new().search_parameter(Point2::new(pt.x, pt.y), None, 0)
}
}
#[test]
fn parameter_division() {
let c = UnitCircle::<Point2>::new();
let (_div, pts) = c.parameter_division(c.range_tuple(), 0.05);
for a in pts.windows(2) {
let p = a[0].midpoint(a[1]);
assert!(p.to_vec().magnitude() > 0.95);
}
}
+125
View File
@@ -0,0 +1,125 @@
use super::*;
impl<P> UnitHyperbola<P> {
/// constructor
#[inline]
pub const fn new() -> UnitHyperbola<P> { UnitHyperbola(std::marker::PhantomData) }
}
impl ParametricCurve for UnitHyperbola<Point2> {
type Point = Point2;
type Vector = Vector2;
#[inline]
fn subs(&self, t: f64) -> Self::Point { Point2::new(f64::cosh(t), f64::sinh(t)) }
#[inline]
fn der(&self, t: f64) -> Self::Vector { Vector2::new(f64::sinh(t), f64::cosh(t)) }
#[inline]
fn der2(&self, t: f64) -> Self::Vector { Vector2::new(f64::cosh(t), f64::sinh(t)) }
}
impl ParametricCurve for UnitHyperbola<Point3> {
type Point = Point3;
type Vector = Vector3;
#[inline]
fn subs(&self, t: f64) -> Self::Point { Point3::new(f64::cosh(t), f64::sinh(t), 0.0) }
#[inline]
fn der(&self, t: f64) -> Self::Vector { Vector3::new(f64::sinh(t), f64::cosh(t), 0.0) }
#[inline]
fn der2(&self, t: f64) -> Self::Vector { Vector3::new(f64::cosh(t), f64::sinh(t), 0.0) }
}
impl<P> ParameterDivision1D for UnitHyperbola<P>
where
UnitHyperbola<P>: ParametricCurve<Point = P>,
P: EuclideanSpace<Scalar = f64> + MetricSpace<Metric = f64> + HashGen<f64>,
{
type Point = P;
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<P>) {
algo::curve::parameter_division(self, range, tol)
}
}
impl SearchNearestParameter<D1> for UnitHyperbola<Point2> {
type Point = Point2;
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
p: Point2,
_: H,
_: usize,
) -> Option<f64> {
let a = -p.y;
let b = (p.y * p.y - p.x * p.x) / 4.0 + 1.0;
let c = -p.y;
let d = p.y * p.y / 4.0;
let y = solver::solve_quartic(a, b, c, d)
.into_iter()
.filter_map(|z| match z.im.so_small() {
true => Some(z.re),
false => None,
})
.min_by(|s, t| {
p.distance2(self.subs(*s))
.partial_cmp(&p.distance2(self.subs(*t)))
.unwrap()
})?;
Some(f64::asinh(y))
}
}
impl SearchNearestParameter<D1> for UnitHyperbola<Point3> {
type Point = Point3;
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
p: Point3,
_: H,
_trials: usize,
) -> Option<f64> {
UnitHyperbola::<Point2>::new().search_nearest_parameter(
Point2::new(p.x, p.y),
None,
_trials,
)
}
}
impl SearchParameter<D1> for UnitHyperbola<Point2> {
type Point = Point2;
fn search_parameter<H: Into<SPHint1D>>(&self, p: Point2, _: H, _: usize) -> Option<f64> {
let t = f64::asinh(p.y);
match p.near(&self.subs(t)) {
true => Some(t),
false => None,
}
}
}
impl SearchParameter<D1> for UnitHyperbola<Point3> {
type Point = Point3;
fn search_parameter<H: Into<SPHint1D>>(&self, p: Point3, _: H, _: usize) -> Option<f64> {
let t = f64::asinh(p.y);
match p.near(&self.subs(t)) {
true => Some(t),
false => None,
}
}
}
#[test]
fn snp_test() {
let curve = UnitHyperbola::<Point2>::new();
let p = curve.subs(2.0);
let q = p + Vector2::new(-p.x, p.y);
let t = curve.search_nearest_parameter(q, None, 0).unwrap();
assert_near!(t, 2.0);
}
#[test]
fn sp_test() {
let curve = UnitHyperbola::<Point2>::new();
let t = 100.0 * rand::random::<f64>() - 50.0;
let p = curve.subs(t);
assert_near!(curve.search_parameter(p, None, 0).unwrap(), t);
let q = Point2::new(-1.0, 0.0);
assert!(curve.search_parameter(q, None, 0).is_none());
}
+217
View File
@@ -0,0 +1,217 @@
use super::*;
use truck_base::cgmath64::control_point::ControlPoint;
impl<P: Copy> Line<P> {
/// initialize line from vector
#[inline]
pub fn from_origin_direction<V>(origin: P, direction: V) -> Self
where P: std::ops::Add<V, Output = P> {
Self(origin, origin + direction)
}
/// to a bspline curve
#[inline]
pub fn to_bspline(self) -> BSplineCurve<P> {
BSplineCurve::new(KnotVec::bezier_knot(1), vec![self.0, self.1])
}
}
impl<P> Line<P>
where
P: EuclideanSpace<Scalar = f64>,
P::Diff: InnerSpace<Scalar = f64>,
{
/// Returns the projected point to the line.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(1.0, 2.0));
/// let pt = Point2::new(0.0, 1.0);
/// assert_near!(line.projection(pt), Point2::new(0.4, 0.8));
/// ```
pub fn projection(self, point: P) -> P {
let (u, v) = (point - self.0, self.1 - self.0);
self.0 + v * u.dot(v) / v.dot(v)
}
/// Returns the distance between the line and the point `point`.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(3.0, 4.0));
///
/// // The foot of the perpendicular line is on a line segment
/// let pt = Point2::new(3.0, 0.0);
/// assert_near!(line.distance_to_point(pt), 2.4);
///
/// // The foot of the perpendicular line is not on a line segment
/// let pt = Point2::new(0.0, -4.0);
/// assert_near!(line.distance_to_point(pt), 2.4);
/// ```
pub fn distance_to_point(self, point: P) -> f64 {
let (u, v) = (point - self.0, self.1 - self.0);
(u - v * u.dot(v) / v.dot(v)).magnitude()
}
/// Returns the distance between the sengment and the point `point`.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(3.0, 4.0));
///
/// // The foot of the perpendicular line is on a line segment
/// let pt = Point2::new(3.0, 0.0);
/// assert_near!(line.distance_to_point_as_segment(pt), 2.4);
///
/// // The foot of the perpendicular line is not on a line segment
/// let pt = Point2::new(0.0, -4.0);
/// assert_near!(line.distance_to_point_as_segment(pt), 4.0);
/// ```
pub fn distance_to_point_as_segment(self, point: P) -> f64 {
let (u, v) = (point - self.0, self.1 - self.0);
let t = f64::clamp(u.dot(v) / v.dot(v), 0.0, 1.0);
(u - v * t).magnitude()
}
}
impl Line<Point2> {
/// Returns the intersection of two lines and its parameters.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let line0 = Line(Point2::new(0.0, 0.0), Point2::new(9.0, 3.0));
/// let line1 = Line(Point2::new(0.0, 6.0), Point2::new(9.0, 0.0));
/// let (s, t, p) = line0.intersection(line1).unwrap();
/// assert_near!(line0.subs(s), Point2::new(6.0, 2.0));
/// assert_near!(line1.subs(t), Point2::new(6.0, 2.0));
/// assert_near!(p, Point2::new(6.0, 2.0));
/// ```
/// # Failures
/// Returns `None` if two lines are parallel.
/// ```
/// use truck_geometry::prelude::*;
/// let line0 = Line(Point2::new(0.0, 0.0), Point2::new(9.0, 3.0));
/// let line1 = Line(Point2::new(-1.0, 0.0), Point2::new(8.0, 3.0));
/// assert!(line0.intersection(line1).is_none());
/// ```
pub fn intersection(self, other: Line<Point2>) -> Option<(f64, f64, Point2)> {
let mat = Matrix2::from_cols(self.1 - self.0, other.0 - other.1);
let v = other.0 - self.0;
let params = mat.invert().map(|inv| inv * v)?;
Some((params.x, params.y, self.subs(params.x)))
}
}
impl<P: ControlPoint<f64>> ParametricCurve for Line<P> {
type Point = P;
type Vector = P::Diff;
#[inline]
fn subs(&self, t: f64) -> Self::Point { self.0 + (self.1 - self.0) * t }
#[inline]
fn der(&self, _: f64) -> Self::Vector { self.1 - self.0 }
#[inline]
fn der2(&self, _: f64) -> Self::Vector { Self::Vector::zero() }
/// Return `0.0..=1.0` i.e. we regard it as a segment
#[inline]
fn parameter_range(&self) -> ParameterRange { (Bound::Included(0.0), Bound::Included(1.0)) }
}
impl<P: ControlPoint<f64>> BoundedCurve for Line<P> {}
impl<P: ControlPoint<f64>> Cut for Line<P> {
#[inline]
fn cut(&mut self, t: f64) -> Self {
let r = self.subs(t);
let res = Self(r, self.1);
self.1 = r;
res
}
}
impl<P: ControlPoint<f64>> ParameterDivision1D for Line<P> {
type Point = P;
#[inline]
fn parameter_division(&self, range: (f64, f64), _: f64) -> (Vec<f64>, Vec<P>) {
(
vec![range.0, range.1],
vec![self.subs(range.0), self.subs(range.1)],
)
}
}
impl<P: Copy> Invertible for Line<P> {
#[inline]
fn invert(&mut self) { *self = Self(self.1, self.0); }
#[inline]
fn inverse(&self) -> Self { Self(self.1, self.0) }
}
impl<P> SearchNearestParameter<D1> for Line<P>
where
P: ControlPoint<f64>,
P::Diff: InnerSpace<Scalar = f64>,
{
type Point = P;
#[inline]
fn search_nearest_parameter<H: Into<SPHint1D>>(&self, pt: P, _: H, _: usize) -> Option<f64> {
let b = self.1 - self.0;
Some((pt - self.0).dot(b) / b.dot(b))
}
}
impl<P> SearchParameter<D1> for Line<P>
where
P: ControlPoint<f64> + Tolerance,
P::Diff: InnerSpace<Scalar = f64>,
{
type Point = P;
#[inline]
fn search_parameter<H: Into<SPHint1D>>(&self, pt: P, _: H, _: usize) -> Option<f64> {
let b = self.1 - self.0;
let t = (pt - self.0).dot(b) / b.dot(b);
match self.subs(t).near(&pt) {
true => Some(t),
false => None,
}
}
}
impl<P: EuclideanSpace, M: Transform<P>> Transformed<M> for Line<P> {
#[inline]
fn transform_by(&mut self, trans: M) {
self.0 = trans.transform_point(self.0);
self.1 = trans.transform_point(self.1);
}
#[inline]
fn transformed(&self, trans: M) -> Self {
Line(trans.transform_point(self.0), trans.transform_point(self.1))
}
}
#[test]
fn line() {
let line = Line(Point2::new(1.0, 0.0), Point2::new(0.0, 1.0));
// subs
assert_near!(line.subs(0.4), Point2::new(0.6, 0.4));
// inverse
let line_inverse = line.inverse();
assert_eq!(line.0, line_inverse.1);
assert_eq!(line.1, line_inverse.0);
// cut
let mut line0 = line;
let line1 = line0.cut(0.4);
assert_eq!(line.0, line0.0);
assert_near!(line0.1, line.subs(0.4));
assert_eq!(line0.1, line1.0);
assert_eq!(line1.1, line.1);
// SNP
assert_near!(
line.search_nearest_parameter(Point2::new(1.0, 1.0), None, 0)
.unwrap(),
0.5
);
assert!(line
.search_parameter(Point2::new(1.0, 1.0), None, 0)
.is_none());
}
+115
View File
@@ -0,0 +1,115 @@
use crate::{prelude::*, *};
/// line
/// # Example
/// ```
/// use truck_geometry::prelude::*;
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(1.0, 1.0));
/// assert_near!(line.subs(0.5), Point2::new(0.5, 0.5));
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Line<P>(pub P, pub P);
/// unit circle
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
pub struct UnitCircle<P>(std::marker::PhantomData<P>);
/// unit hyperbola
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
pub struct UnitHyperbola<P>(std::marker::PhantomData<P>);
/// parabola whose apex is the origin.
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
pub struct UnitParabola<P>(std::marker::PhantomData<P>);
/// plane
/// # Example
/// ```
/// use truck_geometry::prelude::*;
///
/// // arbitrary three points
/// let pt0 = Point3::new(0.0, 1.0, 2.0);
/// let pt1 = Point3::new(1.0, 1.0, 3.0);
/// let pt2 = Point3::new(0.0, 2.0, 3.0);
///
/// // Creates a plane
/// let plane: Plane = Plane::new(pt0, pt1, pt2);
/// // The origin of the plane is pt0.
/// assert_near!(plane.origin(), pt0);
/// // The u-axis of the plane is the vector from pt0 to pt1.
/// assert_near!(plane.u_axis(), pt1 - pt0);
/// // The v-axis of the plane is the vector from pt0 to pt2.
/// assert_near!(plane.v_axis(), pt2 - pt0);
/// // The normal is the normalized u-axis × v-axis
/// assert_near!(plane.normal(), (pt1 - pt0).cross(pt2 - pt0).normalize());
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Plane {
o: Point3,
p: Point3,
q: Point3,
}
/// sphere
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// use std::f64::consts::PI;
///
/// let center = Point3::new(1.0, 2.0, 3.0);
/// let radius = 4.56;
///
/// let sphere = Sphere::new(center, radius);
/// const N: usize = 100;
/// for i in 0..=N {
/// for j in 0..=N {
/// // the parameter u is latitude
/// let u = PI * i as f64 / N as f64;
/// // the parameter v is longitude
/// let v = 2.0 * PI * j as f64 / N as f64;
///
/// // simple relation between a point and its normal.
/// let pt = sphere.subs(u, v);
/// let n = sphere.normal(u, v);
/// assert_near!(pt - center, n * radius);
///
/// // the proof of u is latitude and v is longitude
/// assert!((PI / 2.0 - u) * (pt.z - center.z) >= 0.0);
/// assert!((PI - v) * (pt.y - center.y) >= 0.0);
/// }
/// }
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Sphere {
center: Point3,
radius: f64,
}
/// torus
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Torus {
center: Point3,
large_radius: f64,
small_radius: f64,
}
mod circle;
mod hyperbola;
mod line;
mod parabola;
mod plane;
mod sphere;
mod torus;
macro_rules! always_true {
($ty: tt) => {
impl<P> PartialEq for $ty<P> {
fn eq(&self, _: &Self) -> bool { true }
}
impl<P> Eq for $ty<P> {}
};
}
always_true!(UnitCircle);
always_true!(UnitParabola);
always_true!(UnitHyperbola);
+137
View File
@@ -0,0 +1,137 @@
use super::*;
impl<P> UnitParabola<P> {
/// constructor
#[inline]
pub const fn new() -> Self { Self(std::marker::PhantomData) }
}
impl ParametricCurve for UnitParabola<Point2> {
type Point = Point2;
type Vector = Vector2;
#[inline]
fn subs(&self, t: f64) -> Self::Point { Point2::new(t * t, 2.0 * t) }
#[inline]
fn der(&self, t: f64) -> Self::Vector { Vector2::new(2.0 * t, 2.0) }
#[inline]
fn der2(&self, _: f64) -> Self::Vector { Vector2::new(2.0, 0.0) }
}
impl ParametricCurve for UnitParabola<Point3> {
type Point = Point3;
type Vector = Vector3;
#[inline]
fn subs(&self, t: f64) -> Self::Point { Point3::new(t * t, 2.0 * t, 0.0) }
#[inline]
fn der(&self, t: f64) -> Self::Vector { Vector3::new(2.0 * t, 2.0, 0.0) }
#[inline]
fn der2(&self, _: f64) -> Self::Vector { Vector3::new(2.0, 0.0, 0.0) }
}
impl<P> ParameterDivision1D for UnitParabola<P>
where
UnitParabola<P>: ParametricCurve<Point = P>,
P: EuclideanSpace<Scalar = f64> + MetricSpace<Metric = f64> + HashGen<f64>,
{
type Point = P;
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<P>) {
algo::curve::parameter_division(self, range, tol)
}
}
impl SearchNearestParameter<D1> for UnitParabola<Point2> {
type Point = Point2;
#[inline]
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
pt: Point2,
_: H,
_: usize,
) -> Option<f64> {
let p = 2.0 - pt.x;
let q = -pt.y;
solver::pre_solve_cubic(p, q)
.into_iter()
.filter_map(|x| match x.im.so_small() {
true => Some(x.re),
false => None,
})
.min_by(|s, t| {
pt.distance2(self.subs(*s))
.partial_cmp(&pt.distance2(self.subs(*t)))
.unwrap()
})
}
}
impl SearchNearestParameter<D1> for UnitParabola<Point3> {
type Point = Point3;
#[inline]
fn search_nearest_parameter<H: Into<SPHint1D>>(
&self,
pt: Point3,
_hint: H,
_trials: usize,
) -> Option<f64> {
UnitParabola::<Point2>::new().search_nearest_parameter(
Point2::new(pt.x, pt.y),
_hint,
_trials,
)
}
}
impl SearchParameter<D1> for UnitParabola<Point2> {
type Point = Point2;
#[inline]
fn search_parameter<H: Into<SPHint1D>>(&self, pt: Point2, _: H, _: usize) -> Option<f64> {
let t = pt.y / 2.0;
let pt0 = self.subs(t);
match pt.near(&pt0) {
true => Some(t),
false => None,
}
}
}
impl SearchParameter<D1> for UnitParabola<Point3> {
type Point = Point3;
#[inline]
fn search_parameter<H: Into<SPHint1D>>(
&self,
pt: Point3,
_hint: H,
_trials: usize,
) -> Option<f64> {
match pt.z.so_small() {
true => UnitParabola::<Point2>::new().search_parameter(
Point2::new(pt.x, pt.y),
_hint,
_trials,
),
false => None,
}
}
}
#[test]
fn snp_test() {
let curve = UnitParabola::<Point2>::new();
let p = Point2::new(-3.0, 0.0);
assert_near!(curve.search_nearest_parameter(p, None, 0).unwrap(), 0.0);
let p = Point2::new(-3.0, 6.0);
assert_near!(curve.search_nearest_parameter(p, None, 0).unwrap(), 1.0);
let p = Point2::new(1.5, 1.5);
assert_near!(curve.search_nearest_parameter(p, None, 0).unwrap(), 1.0);
}
#[test]
fn sp_test() {
let curve = UnitParabola::<Point2>::new();
let p = Point2::new(4.0, -4.0);
assert_near!(curve.search_parameter(p, None, 0).unwrap(), -2.0);
let p = Point2::new(-3.0, 6.0);
assert!(curve.search_parameter(p, None, 0).is_none());
}
+256
View File
@@ -0,0 +1,256 @@
use super::*;
impl Plane {
/// Creates a new plane from three points.
#[inline(always)]
pub const fn new(origin: Point3, one: Point3, another: Point3) -> Plane {
Plane {
o: origin,
p: one,
q: another,
}
}
/// Returns the origin
#[inline(always)]
pub const fn origin(&self) -> Point3 { self.o }
/// Returns the u-axis
#[inline(always)]
pub fn u_axis(&self) -> Vector3 { self.p - self.o }
/// Returns the v-axis
#[inline(always)]
pub fn v_axis(&self) -> Vector3 { self.q - self.o }
/// Returns the normal
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let plane = Plane::new(
/// Point3::new(0.0, 0.0, 0.0),
/// Point3::new(1.0, 0.0, 0.0),
/// Point3::new(0.0, 1.0, 0.0),
/// );
/// assert_near!(plane.normal(), Vector3::unit_z());
/// ```
#[inline(always)]
pub fn normal(&self) -> Vector3 { self.u_axis().cross(self.v_axis()).normalize() }
/// Gets the parameter of `pt` in plane's matrix.
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let plane = Plane::new(
/// Point3::new(1.0, 2.0, 3.0),
/// Point3::new(2.0, 1.0, 3.0),
/// Point3::new(3.0, 4.0, -1.0),
/// );
///
/// let pt = Point3::new(2.1, -6.5, 4.7);
/// let prm = plane.get_parameter(pt);
/// let rev = plane.origin()
/// + prm[0] * plane.u_axis()
/// + prm[1] * plane.v_axis()
/// + prm[2] * plane.normal();
/// assert_near!(pt, rev);
/// ```
#[inline(always)]
pub fn get_parameter(&self, pt: Point3) -> Vector3 {
let a = self.u_axis();
let b = self.v_axis();
let c = self.normal();
let mat = Matrix3::from_cols(a, b, c).invert().unwrap();
mat * (pt - self.o)
}
/// into B-spline surface
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let pt0 = Point3::new(0.0, 1.0, 2.0);
/// let pt1 = Point3::new(1.0, 1.0, 3.0);
/// let pt2 = Point3::new(0.0, 2.0, 3.0);
/// let plane: Plane = Plane::new(pt0, pt1, pt2);
/// let surface: BSplineSurface<Point3> = plane.into_bspline();
/// assert_eq!(surface.range_tuple(), ((0.0, 1.0), (0.0, 1.0)));
///
/// const N: usize = 100;
/// for i in 0..=N {
/// for j in 0..=N {
/// let u = i as f64 / N as f64;
/// let v = j as f64 / N as f64;
/// let res = surface.subs(u, v);
/// let ans = plane.subs(u, v);
/// assert_near!(ans, res);
/// }
/// }
/// ```
#[inline(always)]
pub fn into_bspline(&self) -> BSplineSurface<Point3> {
let o = self.o;
let p = self.p;
let q = self.q;
BSplineSurface::debug_new(
(KnotVec::bezier_knot(1), KnotVec::bezier_knot(1)),
vec![vec![o, q], vec![p, p + (q - o)]],
)
}
/// into NURBS surface
/// # Examples
/// ```
/// use truck_geometry::prelude::*;
/// let pt0 = Point3::new(0.0, 1.0, 2.0);
/// let pt1 = Point3::new(1.0, 1.0, 3.0);
/// let pt2 = Point3::new(0.0, 2.0, 3.0);
/// let plane: Plane = Plane::new(pt0, pt1, pt2);
/// let surface: NurbsSurface<Vector4> = plane.into_nurbs();
/// assert_eq!(surface.range_tuple(), ((0.0, 1.0), (0.0, 1.0)));
///
/// const N: usize = 100;
/// for i in 0..=N {
/// for j in 0..=N {
/// let u = i as f64 / N as f64;
/// let v = j as f64 / N as f64;
/// let res = surface.subs(u, v);
/// let ans = plane.subs(u, v);
/// assert_near!(ans, res);
/// }
/// }
/// ```
#[inline(always)]
pub fn into_nurbs(&self) -> NurbsSurface<Vector4> {
let o = self.o.to_homogeneous();
let p = self.p.to_homogeneous();
let q = self.q.to_homogeneous();
NurbsSurface::new(BSplineSurface::debug_new(
(KnotVec::bezier_knot(1), KnotVec::bezier_knot(1)),
vec![vec![o, q], vec![p, p + q - o]],
))
}
}
impl ParametricSurface for Plane {
type Point = Point3;
type Vector = Vector3;
#[inline(always)]
fn subs(&self, u: f64, v: f64) -> Point3 {
self.o + u * (self.p - self.o) + v * (self.q - self.o)
}
#[inline(always)]
fn uder(&self, _: f64, _: f64) -> Vector3 { self.p - self.o }
#[inline(always)]
fn vder(&self, _: f64, _: f64) -> Vector3 { self.q - self.o }
#[inline(always)]
fn uuder(&self, _: f64, _: f64) -> Vector3 { Vector3::zero() }
#[inline(always)]
fn uvder(&self, _: f64, _: f64) -> Vector3 { Vector3::zero() }
#[inline(always)]
fn vvder(&self, _: f64, _: f64) -> Vector3 { Vector3::zero() }
/// as square
#[inline(always)]
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
let range = (Bound::Included(0.0), Bound::Included(1.0));
(range, range)
}
}
impl ParametricSurface3D for Plane {
#[inline(always)]
fn normal(&self, _: f64, _: f64) -> Vector3 { self.normal() }
}
impl BoundedSurface for Plane {}
impl Invertible for Plane {
#[inline(always)]
fn inverse(&self) -> Self {
Plane {
o: self.o,
p: self.q,
q: self.p,
}
}
#[inline(always)]
fn invert(&mut self) { *self = self.inverse(); }
}
impl IncludeCurve<BSplineCurve<Point3>> for Plane {
#[inline(always)]
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
let origin = self.origin();
let normal = self.normal();
curve
.control_points()
.iter()
.all(|pt| (pt - origin).dot(normal).so_small())
}
}
impl IncludeCurve<NurbsCurve<Vector4>> for Plane {
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
let origin = self.origin();
let normal = self.normal();
let (s, e) = (curve.front(), curve.back());
if !(s - origin).dot(normal).so_small() || !(e - origin).dot(normal).so_small() {
return false;
}
curve.non_rationalized().control_points().iter().all(|pt| {
if pt[3].so_small() {
true
} else {
let pt = Point3::from_homogeneous(*pt);
(pt - origin).dot(normal).so_small()
}
})
}
}
impl ParameterDivision2D for Plane {
#[inline(always)]
fn parameter_division(&self, range: ((f64, f64), (f64, f64)), _: f64) -> (Vec<f64>, Vec<f64>) {
(vec![range.0 .0, range.0 .1], vec![range.1 .0, range.1 .1])
}
}
impl<T: Transform3<Scalar = f64>> Transformed<T> for Plane {
#[inline(always)]
fn transform_by(&mut self, trans: T) {
self.o = trans.transform_point(self.o);
self.p = trans.transform_point(self.p);
self.q = trans.transform_point(self.q);
}
#[inline(always)]
fn transformed(&self, trans: T) -> Self {
Plane {
o: trans.transform_point(self.o),
p: trans.transform_point(self.p),
q: trans.transform_point(self.q),
}
}
}
impl SearchParameter<D2> for Plane {
type Point = Point3;
#[inline(always)]
fn search_parameter<H: Into<SPHint2D>>(
&self,
point: Point3,
_: H,
_: usize,
) -> Option<(f64, f64)> {
let v = self.get_parameter(point);
match v[2].so_small() {
true => Some((v[0], v[1])),
false => None,
}
}
}
impl SearchNearestParameter<D2> for Plane {
type Point = Point3;
#[inline(always)]
fn search_nearest_parameter<H: Into<SPHint2D>>(
&self,
point: Point3,
_: H,
_: usize,
) -> Option<(f64, f64)> {
let v = self.get_parameter(point);
Some((v[0], v[1]))
}
}
+219
View File
@@ -0,0 +1,219 @@
use super::*;
use std::f64::consts::PI;
impl Sphere {
/// Creates a sphere
#[inline(always)]
pub const fn new(center: Point3, radius: f64) -> Sphere { Sphere { center, radius } }
/// Returns the center
#[inline(always)]
pub const fn center(&self) -> Point3 { self.center }
/// Returns the radius
#[inline(always)]
pub const fn radius(&self) -> f64 { self.radius }
/// Returns whether the point `pt` is on sphere
#[inline(always)]
pub fn include(&self, pt: Point3) -> bool { self.center.distance(pt).near(&self.radius) }
}
impl ParametricSurface for Sphere {
type Point = Point3;
type Vector = Vector3;
#[inline(always)]
fn subs(&self, u: f64, v: f64) -> Point3 { self.center() + self.radius * self.normal(u, v) }
#[inline(always)]
fn uder(&self, u: f64, v: f64) -> Vector3 {
self.radius
* Vector3::new(
f64::cos(u) * f64::cos(v),
f64::cos(u) * f64::sin(v),
-f64::sin(u),
)
}
#[inline(always)]
fn vder(&self, u: f64, v: f64) -> Vector3 {
self.radius * f64::sin(u) * Vector3::new(-f64::sin(v), f64::cos(v), 0.0)
}
#[inline(always)]
fn uuder(&self, u: f64, v: f64) -> Vector3 { -self.radius * self.normal(u, v) }
#[inline(always)]
fn uvder(&self, u: f64, v: f64) -> Vector3 {
self.radius * f64::cos(u) * Vector3::new(-f64::sin(v), f64::cos(v), 0.0)
}
#[inline(always)]
fn vvder(&self, u: f64, v: f64) -> Vector3 {
-self.radius * f64::sin(u) * Vector3::new(f64::cos(v), f64::sin(v), 0.0)
}
#[inline(always)]
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
(
(Bound::Included(0.0), Bound::Included(PI)),
(Bound::Included(0.0), Bound::Excluded(2.0 * PI)),
)
}
#[inline(always)]
fn v_period(&self) -> Option<f64> { Some(2.0 * PI) }
}
impl ParametricSurface3D for Sphere {
#[inline(always)]
fn normal(&self, u: f64, v: f64) -> Vector3 {
Vector3::new(
f64::sin(u) * f64::cos(v),
f64::sin(u) * f64::sin(v),
f64::cos(u),
)
}
}
#[test]
fn sphere_derivation_test() {
let center = Point3::new(1.0, 2.0, 3.0);
let radius = 4.56;
let sphere = Sphere::new(center, radius);
const N: usize = 100;
for i in 0..N {
for j in 0..N {
let u = PI * i as f64 / N as f64;
let v = 2.0 * PI * j as f64 / N as f64;
let normal = sphere.normal(u, v);
assert!(normal.dot(sphere.uder(u, v)).so_small());
assert!(normal.dot(sphere.vder(u, v)).so_small());
}
}
}
impl BoundedSurface for Sphere {}
impl IncludeCurve<BSplineCurve<Point3>> for Sphere {
#[inline(always)]
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
curve.is_const() && self.include(curve.front())
}
}
impl IncludeCurve<NurbsCurve<Vector4>> for Sphere {
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
let (knots, _) = curve.knot_vec().to_single_multi();
let degree = curve.degree() * 2;
knots
.windows(2)
.flat_map(move |window| (1..degree).map(move |i| (window, i)))
.all(move |(window, i)| {
let t = i as f64 / degree as f64;
let t = window[0] * (1.0 - t) + window[1] * t;
self.include(curve.subs(t))
})
}
}
impl ParameterDivision2D for Sphere {
#[inline(always)]
fn parameter_division(
&self,
(urange, vrange): ((f64, f64), (f64, f64)),
tol: f64,
) -> (Vec<f64>, Vec<f64>) {
nonpositive_tolerance!(tol);
assert!(
tol < self.radius,
"Tolerance is larger than the radius of sphere."
);
let delta = 2.0 * f64::acos(1.0 - tol / self.radius);
let u_div = 1 + ((urange.1 - urange.0) / delta).floor() as usize;
let v_div = 1 + ((vrange.1 - vrange.0) / delta).floor() as usize;
(
(0..=u_div)
.map(|i| urange.0 + (urange.1 - urange.0) * i as f64 / u_div as f64)
.collect(),
(0..=v_div)
.map(|j| vrange.0 + (vrange.1 - vrange.0) * j as f64 / v_div as f64)
.collect(),
)
}
}
impl SearchParameter<D2> for Sphere {
type Point = Point3;
#[inline(always)]
fn search_parameter<H: Into<SPHint2D>>(
&self,
point: Point3,
hint: H,
_: usize,
) -> Option<(f64, f64)> {
let radius = point - self.center;
if (self.radius * self.radius).near(&radius.magnitude2()) {
let radius = radius.normalize();
let u = f64::acos(radius[2]);
let sinu = f64::sqrt(1.0 - radius[2] * radius[2]);
let cosv = f64::clamp(radius[0] / sinu, -1.0, 1.0);
let v = if sinu.so_small() {
match hint.into() {
SPHint2D::Parameter(_, hint) => hint,
_ => 0.0,
}
} else if radius[1] > 0.0 {
f64::acos(cosv)
} else {
2.0 * PI - f64::acos(cosv)
};
Some((u, v))
} else {
None
}
}
}
impl SearchNearestParameter<D2> for Sphere {
type Point = Point3;
#[inline(always)]
fn search_nearest_parameter<H: Into<SPHint2D>>(
&self,
point: Point3,
_: H,
_: usize,
) -> Option<(f64, f64)> {
let radius = (point - self.center).normalize();
let u = f64::acos(radius[2]);
let sinu = f64::sqrt(1.0 - radius[2] * radius[2]);
let cosv = radius[0] / sinu;
let v = if radius[1] > 0.0 {
f64::acos(cosv)
} else {
2.0 * PI - f64::acos(cosv)
};
Some((u, v))
}
}
#[cfg(test)]
fn exec_search_parameter_test() {
let center = Point3::new(
100.0 * rand::random::<f64>() - 50.0,
100.0 * rand::random::<f64>() - 50.0,
100.0 * rand::random::<f64>() - 50.0,
);
let radius = 100.0 * rand::random::<f64>();
let sphere = Sphere::new(center, radius);
let u = PI * rand::random::<f64>();
let v = 2.0 * PI * rand::random::<f64>();
let pt = sphere.subs(u, v);
let (u0, v0) = sphere.search_parameter(pt, None, 100).unwrap();
assert_near!(Vector2::new(u, v), Vector2::new(u0, v0));
let pt = pt
+ Vector3::new(
(0.1 * rand::random::<f64>() + 0.01) * f64::signum(rand::random::<f64>() - 0.5),
(0.1 * rand::random::<f64>() + 0.01) * f64::signum(rand::random::<f64>() - 0.5),
(0.1 * rand::random::<f64>() + 0.01) * f64::signum(rand::random::<f64>() - 0.5),
);
assert!(sphere.search_parameter(pt, None, 100).is_none());
let (u, v) = sphere.search_nearest_parameter(pt, None, 100).unwrap();
assert_near!(
sphere.subs(u, v),
center + (pt - center).normalize() * radius
);
}
#[test]
fn search_parameter_test() { (0..10).for_each(|_| exec_search_parameter_test()) }
+238
View File
@@ -0,0 +1,238 @@
use super::*;
use std::f64::consts::PI;
impl Torus {
/// constructor
#[inline(always)]
pub fn new(center: Point3, large_radius: f64, small_radius: f64) -> Self {
if large_radius <= 0.0 || small_radius <= 0.0 {
panic!("radius must be larger than 0");
}
Self {
center,
large_radius,
small_radius,
}
}
/// get center
#[inline(always)]
pub const fn center(&self) -> Point3 { self.center }
/// get large radius
#[inline(always)]
pub const fn large_radius(&self) -> f64 { self.large_radius }
/// get small radius
#[inline(always)]
pub const fn small_radius(&self) -> f64 { self.small_radius }
}
impl ParametricSurface for Torus {
type Point = Point3;
type Vector = Vector3;
#[inline(always)]
fn subs(&self, u: f64, v: f64) -> Point3 {
let sr = self.small_radius() * Vector2::new(f64::cos(v), f64::sin(v));
let lr = (self.large_radius() + sr.x) * Vector2::new(f64::cos(u), f64::sin(u));
self.center() + Vector3::new(lr.x, lr.y, sr.y)
}
#[inline(always)]
fn uder(&self, u: f64, v: f64) -> Vector3 {
let sr = self.small_radius() * f64::cos(v);
let lr = (self.large_radius() + sr) * Vector2::new(f64::cos(u), f64::sin(u));
Vector3::new(-lr.y, lr.x, 0.0)
}
#[inline(always)]
fn vder(&self, u: f64, v: f64) -> Vector3 {
let sv = self.small_radius() * Vector2::new(-f64::sin(v), f64::cos(v));
Vector3::new(sv.x * f64::cos(u), sv.x * f64::sin(u), sv.y)
}
#[inline(always)]
fn uuder(&self, u: f64, v: f64) -> Vector3 {
let sr = self.small_radius() * f64::cos(v);
let lr = (self.large_radius() + sr) * Vector2::new(f64::cos(u), f64::sin(u));
Vector3::new(-lr.x, -lr.y, 0.0)
}
#[inline(always)]
fn uvder(&self, u: f64, v: f64) -> Vector3 {
let sr = -self.small_radius() * f64::sin(v);
let lr = sr * Vector2::new(f64::cos(u), f64::sin(u));
Vector3::new(-lr.y, lr.x, 0.0)
}
#[inline(always)]
fn vvder(&self, u: f64, v: f64) -> Vector3 {
let sv = -self.small_radius() * Vector2::new(f64::cos(v), f64::sin(v));
Vector3::new(sv.x * f64::cos(u), sv.x * f64::sin(u), sv.y)
}
#[inline(always)]
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
const RANGE: (Bound<f64>, Bound<f64>) = (Bound::Included(0.0), Bound::Excluded(2.0 * PI));
(RANGE, RANGE)
}
#[inline(always)]
fn u_period(&self) -> Option<f64> { Some(2.0 * PI) }
#[inline(always)]
fn v_period(&self) -> Option<f64> { Some(2.0 * PI) }
}
impl ParametricSurface3D for Torus {
fn normal(&self, u: f64, v: f64) -> Vector3 {
let sv = Vector2::new(f64::cos(v), f64::sin(v));
Vector3::new(sv.x * f64::cos(u), sv.x * f64::sin(u), sv.y)
}
}
impl BoundedSurface for Torus {}
impl SearchParameter<D2> for Torus {
type Point = Point3;
fn search_parameter<H: Into<SPHint2D>>(
&self,
point: Point3,
_: H,
_: usize,
) -> Option<(f64, f64)> {
let r = point - self.center();
let rxy = Vector2::new(r.x, r.y);
let v = f64::asin(f64::clamp(r.z / self.small_radius(), -1.0, 1.0));
let minus = rxy.magnitude2() < self.large_radius() * self.large_radius();
let v = match (minus, v < 0.0) {
(true, _) => PI - v,
(false, false) => v,
(false, true) => 2.0 * PI + v,
};
let rxy_n = rxy.normalize();
let u = f64::acos(f64::clamp(rxy_n.x, -1.0, 1.0));
let u = match rxy_n.y < 0.0 {
true => 2.0 * PI - u,
false => u,
};
match self.subs(u, v).near(&point) {
true => Some((u, v)),
false => None,
}
}
}
impl SearchNearestParameter<D2> for Torus {
type Point = Point3;
fn search_nearest_parameter<H: Into<SPHint2D>>(
&self,
point: Point3,
_: H,
_: usize,
) -> Option<(f64, f64)> {
let r = point - self.center();
let rxy = Vector2::new(r.x, r.y);
if rxy.so_small() {
return None;
}
let rxy_n = rxy.normalize();
let large_r = self.large_radius() * rxy_n.extend(0.0);
let diff = r - large_r;
if diff.so_small() {
return None;
}
let small_r = diff.normalize();
let u = f64::acos(f64::clamp(rxy_n.x, -1.0, 1.0));
let u = match rxy_n.y < 0.0 {
true => 2.0 * PI - u,
false => u,
};
let v = f64::asin(f64::clamp(small_r.z, -1.0, 1.0));
let v = match (small_r.dot(large_r) < 0.0, v < 0.0) {
(true, _) => PI - v,
(false, false) => v,
(false, true) => 2.0 * PI + v,
};
Some((u, v))
}
}
impl ParameterDivision2D for Torus {
fn parameter_division(
&self,
(urange, vrange): ((f64, f64), (f64, f64)),
tol: f64,
) -> (Vec<f64>, Vec<f64>) {
let circle = UnitCircle::<Point2>::new();
let utol = tol / (self.small_radius() + self.large_radius());
let (udiv, _) = circle.parameter_division(urange, utol);
let vtol = tol / self.small_radius();
let (vdiv, _) = circle.parameter_division(vrange, vtol);
(udiv, vdiv)
}
}
#[test]
fn surface() {
use std::ops::RangeBounds;
let small_radius = 5.0 * rand::random::<f64>() + 1.0;
let large_radius = 2.0 * small_radius + 5.0 * rand::random::<f64>();
let torus = Torus::new(Point3::new(0.0, 0.0, 0.0), large_radius, small_radius);
const N: usize = 10;
const EPS: f64 = 1.0e-2;
for i in 0..N {
for j in 0..N {
let u = 2.0 * PI * i as f64 / N as f64;
let v = 2.0 * PI * j as f64 / N as f64;
let p = torus.subs(u, v);
let r = large_radius * Point3::new(f64::cos(u), f64::sin(u), 0.0);
assert_near!(p.distance(r), small_radius);
assert_near!(p.z, small_radius * f64::sin(v));
let uder0 = torus.uder(u, v);
let uder1 = (torus.subs(u + EPS, v) - torus.subs(u - EPS, v)) / (2.0 * EPS);
assert!((uder0 - uder1).magnitude() < EPS, "{:?} {:?}", uder0, uder1);
let vder0 = torus.vder(u, v);
let vder1 = (torus.subs(u, v + EPS) - torus.subs(u, v - EPS)) / (2.0 * EPS);
assert!((vder0 - vder1).magnitude() < EPS, "{:?} {:?}", vder0, vder1);
let uuder0 = torus.uuder(u, v);
let uuder1 = (torus.uder(u + EPS, v) - torus.uder(u - EPS, v)) / (2.0 * EPS);
assert!(
(uuder0 - uuder1).magnitude() < EPS,
"{:?} {:?}",
uuder0,
uuder1
);
let uvder0 = torus.uvder(u, v);
let uvder1 = (torus.vder(u + EPS, v) - torus.vder(u - EPS, v)) / (2.0 * EPS);
assert!(
(uvder0 - uvder1).magnitude() < EPS,
"{:?} {:?}",
uvder0,
uvder1
);
let vvder0 = torus.vvder(u, v);
let vvder1 = (torus.vder(u, v + EPS) - torus.vder(u, v - EPS)) / (2.0 * EPS);
assert!(
(vvder0 - vvder1).magnitude() < EPS,
"{:?} {:?}",
vvder0,
vvder1
);
let n0 = torus.normal(u, v);
let n1 = torus.uder(u, v).cross(torus.vder(u, v)).normalize();
assert_near!(n0, n1);
let (u0, v0) = torus.search_parameter(p, None, 1).unwrap();
let (urange, vrange) = torus.parameter_range();
assert!(urange.contains(&u0) && vrange.contains(&v0), "{u0}, {v0}");
assert_near!(torus.subs(u0, v0), p);
let deform = (0.8 * rand::random::<f64>() - 0.4) * small_radius;
let q = p + deform * n0;
let (u0, v0) = torus.search_nearest_parameter(q, None, 1).unwrap();
let (urange, vrange) = torus.parameter_range();
assert!(urange.contains(&u0) && vrange.contains(&v0), "{u0}, {v0}");
assert_near!(torus.subs(u0, v0), p);
}
}
}