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)
}
}