Thanks @jijinbei.
This commit is contained in:
1 parent
42d4ebeb16
commit
3333f8a62b
34 files changed
+10368
-7
No files matched your search
@@ -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());
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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()) }
|
||||
}
|
||||
@@ -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(¶bola));
|
||||
}
|
||||
|
||||
#[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(¶bola));
|
||||
}
|
||||
|
||||
#[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));
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user