Thanks @jijinbei.
This commit is contained in:
1 parent
42d4ebeb16
commit
3333f8a62b
34 files changed
+10368
-7
No files matched your search
Vendored
+12
@@ -42,6 +42,18 @@ Changes:
|
||||
wrappers that drop the history; the `*_with_history` forms return the same solid with the
|
||||
history as an `Option` (`None` where a face or edge went unrecorded).
|
||||
|
||||
## truck-geometry 0.5.0 (Apache-2.0, © RICOS Co. Ltd., https://github.com/ricosjp/truck)
|
||||
|
||||
Changes:
|
||||
- `decorators/intersection_curve.rs`: `IntersectionCurve::parameter_division` divides as
|
||||
`algo::curve::parameter_division` does (the same split points, in 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 (a grid pre-search of a B-spline surface
|
||||
for every point: a small hole on a large curved face took seconds, #1038). A hinted result
|
||||
farther from the leader's point than the curve's tolerance (the projection settled on another
|
||||
part of a surface, such as the far side of a small hole) is dropped for the search from
|
||||
nothing, as before. `crates/kernel/src/issue_1038_tests.rs` holds it to that search.
|
||||
|
||||
## truck-stepio 0.3.0 (Apache-2.0, © RICOS Co. Ltd., https://github.com/ricosjp/truck)
|
||||
|
||||
Only the writer (`out`) is used; SolidCraft reads STEP with its own reader.
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"v":1}
|
||||
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "truck-geometry"
|
||||
version = "0.5.0"
|
||||
authors = ["Yoshinori Tanimura <tanimura@ricos.co.jp>"]
|
||||
build = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "geometrical structs: knot vector, B-spline and NURBS"
|
||||
homepage = "https://github.com/ricosjp/truck"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"truck",
|
||||
"graphics",
|
||||
]
|
||||
categories = ["graphics"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/ricosjp/truck"
|
||||
|
||||
[lib]
|
||||
name = "truck_geometry"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.210"
|
||||
features = ["derive"]
|
||||
|
||||
[dependencies.thiserror]
|
||||
version = "1.0.63"
|
||||
|
||||
[dependencies.truck-base]
|
||||
version = "0.5.0"
|
||||
|
||||
[dependencies.truck-geotrait]
|
||||
version = "0.4.0"
|
||||
|
||||
[dev-dependencies.rand]
|
||||
version = "0.8.5"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies.getrandom]
|
||||
version = "0.2.15"
|
||||
features = ["js"]
|
||||
Vendored
+177
@@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
# truck-geometry
|
||||
|
||||
[](https://crates.io/crates/truck-geometry) [](https://docs.rs/truck-geometry)
|
||||
|
||||
Geometrical structs: knot vector, B-spline and NURBS
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Vendored
+190
@@ -0,0 +1,190 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// Error handler for [`Error`](./errors/enum.Error.html)
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Geometrical Errors
|
||||
#[derive(Debug, PartialEq, Error)]
|
||||
pub enum Error {
|
||||
/// The following operations are failed if the knot vector has zero range.
|
||||
/// * Creating `BSplineCurve` or `BSplineSurface`,
|
||||
/// * Calculating bspline basis functions, or
|
||||
/// * Normalizing the knot vector.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let mut knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 0.0]);
|
||||
/// assert_eq!(knot_vec.try_normalize(), Err(Error::ZeroRange));
|
||||
/// assert_eq!(knot_vec.try_bspline_basis_functions(1, 0.0), Err(Error::ZeroRange));
|
||||
///
|
||||
/// let ctrl_pts = vec![Vector2::new(0.0, 0.0), Vector2::new(1.0, 1.0)];
|
||||
/// assert!(matches!(BSplineCurve::try_new(knot_vec, ctrl_pts), Err(Error::ZeroRange)));
|
||||
/// ```
|
||||
#[error("This knot vector consists single value.")]
|
||||
ZeroRange,
|
||||
/// Fails concatting two knot vectors if there is a difference between the back knot of
|
||||
/// the former knot vector and the front knot of the latter knot vector.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let knot_vec1 = KnotVec::from(vec![2.0, 2.0, 3.0, 3.0]);
|
||||
/// assert!(matches!(knot_vec0.try_concat(&knot_vec1, 1), Err(Error::DifferentBackFront(1.0, 2.0))));
|
||||
/// ```
|
||||
#[error("Cannot concat two knot vectors whose the back of the first and the front of the second are different.
|
||||
the back of the first knot vector: {0}
|
||||
the front of the second knot vector: {1}")]
|
||||
DifferentBackFront(f64, f64),
|
||||
/// If the knot vector is not clamped, then one cannot concat the vector with another knot vector.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let knot_vec1 = KnotVec::from(vec![2.0, 2.0, 3.0, 3.0]);
|
||||
/// assert!(matches!(knot_vec0.try_concat(&knot_vec1, 2), Err(Error::NotClampedKnotVector)));
|
||||
/// ```
|
||||
#[error("This knot vector is not clamped.")]
|
||||
NotClampedKnotVector,
|
||||
/// Creating a knot vector by `KnotVec::try_from()` is failed if the given vector is not sorted.
|
||||
/// `<KnotVec as From<Vec<f64>>>::from()` does not panic by this error because sorts the given
|
||||
/// vector before creating the knot vector. So, `KnotVec::try_from()` is more efficient than
|
||||
/// `<KnotVec as From<Vec<f64>>>::from()`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// use std::convert::*;
|
||||
///
|
||||
/// assert!(matches!(KnotVec::try_from(vec![1.0, 3.0, 0.0, 2.0]), Err(Error::NotSortedVector)));
|
||||
/// assert_eq!(
|
||||
/// <KnotVec as From<Vec<f64>>>::from(vec![1.0, 3.0, 0.0, 2.0]),
|
||||
/// KnotVec::try_from(vec![0.0, 1.0, 2.0, 3.0]).unwrap(),
|
||||
/// );
|
||||
/// ```
|
||||
#[error("This knot vector is not sorted.")]
|
||||
NotSortedVector,
|
||||
/// The given degree is too large to calculate bspline basis functions.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
///
|
||||
/// // a knot vector with length = 4.
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// assert!(matches!(
|
||||
/// knot_vec.try_bspline_basis_functions(5, 0.5),
|
||||
/// Err(Error::TooLargeDegree(4, 5)),
|
||||
/// ));
|
||||
/// ```
|
||||
#[error(
|
||||
"This knot vector is too short compared to the degree.
|
||||
the length of knot_vec: {0}
|
||||
the degree: {1}"
|
||||
)]
|
||||
TooLargeDegree(usize, usize),
|
||||
/// The specified knot cannot be removed.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let knot_vec = KnotVec::bezier_knot(2);
|
||||
/// let ctrl_pts = vec![Vector2::new(-1.0, 1.0), Vector2::new(0.0, -1.0), Vector2::new(1.0, 1.0)];
|
||||
/// let mut bspcurve = BSplineCurve::new(knot_vec, ctrl_pts);
|
||||
/// let org_curve = bspcurve.clone();
|
||||
/// bspcurve.add_knot(0.5).add_knot(0.5).add_knot(0.25).add_knot(0.75);
|
||||
/// assert!(bspcurve.try_remove_knot(3).is_ok());
|
||||
/// assert!(matches!(bspcurve.try_remove_knot(2), Err(Error::CannotRemoveKnot(2))));
|
||||
/// ```
|
||||
#[error("The {0}th knot in this knot vector cannot be removed.")]
|
||||
CannotRemoveKnot(usize),
|
||||
/// Empty vector of points cannot construct B-spline.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
///
|
||||
/// let knot_vec = KnotVec::bezier_knot(2);
|
||||
/// let ctrl_pts: Vec<Vector4> = Vec::new();
|
||||
/// assert!(matches!(
|
||||
/// BSplineCurve::try_new(knot_vec, ctrl_pts),
|
||||
/// Err(Error::EmptyControlPoints),
|
||||
/// ));
|
||||
/// ```
|
||||
#[error("The control point must not be empty.")]
|
||||
EmptyControlPoints,
|
||||
/// The knot vector of B-spline curves or B-spline surfaces must be longer than the corresponded
|
||||
/// array of control points.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 1.0, 2.0]);
|
||||
/// let ctrl_pts = vec![Vector2::new(0.0, 0.0), Vector2::new(0.0, 0.0), Vector2::new(0.0, 0.0), Vector2::new(0.0, 0.0)];
|
||||
/// assert!(matches!(
|
||||
/// BSplineCurve::try_new(knot_vec, ctrl_pts),
|
||||
/// Err(Error::TooShortKnotVector(3, 4)),
|
||||
/// ));
|
||||
/// ```
|
||||
#[error(
|
||||
"The knot vector must be more than the control points.
|
||||
the length of knot_vec: {0}
|
||||
the number of control points: {1}"
|
||||
)]
|
||||
TooShortKnotVector(usize, usize),
|
||||
/// The length of the given arrays of control points to create a B-spline surface is irregular.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let knot_vecs = (KnotVec::bezier_knot(2), KnotVec::bezier_knot(2));
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector2::new(1.0, 2.0), Vector2::new(1.0, 2.0)], // length = 2
|
||||
/// vec![Vector2::new(1.0, 2.0)] // length = 1
|
||||
/// ];
|
||||
/// assert!(matches!(
|
||||
/// BSplineSurface::try_new(knot_vecs, ctrl_pts),
|
||||
/// Err(Error::IrregularControlPoints),
|
||||
/// ));
|
||||
/// ```
|
||||
#[error("The number of control points is irregular")]
|
||||
IrregularControlPoints,
|
||||
/// The vector of control points and the one of weights have different length.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let bspcurve = BSplineCurve::new(
|
||||
/// KnotVec::bezier_knot(2),
|
||||
/// vec![Point2::new(0.0, 1.0), Point2::new(2.0, 3.0), Point2::new(4.0, 5.0)],
|
||||
/// );
|
||||
/// let weights = vec![1.0, 2.0]; // less than control points
|
||||
///
|
||||
/// assert!(matches!(
|
||||
/// NurbsCurve::<Vector3>::try_from_bspline_and_weights(bspcurve, weights),
|
||||
/// Err(Error::DifferentLength),
|
||||
/// ));
|
||||
/// ```
|
||||
#[error("The vector of control points and the one of weights have different length.")]
|
||||
DifferentLength,
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn print_messages() {
|
||||
use std::io::Write;
|
||||
let stderr = &mut std::io::stderr();
|
||||
writeln!(stderr, "****** test of the expressions of error messages ******\n").unwrap();
|
||||
writeln!(stderr, "{}\n", Error::ZeroRange).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::DifferentBackFront(0.0, 1.0)).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::NotClampedKnotVector).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::NotSortedVector).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::TooLargeDegree(1, 2)).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::CannotRemoveKnot(7)).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::EmptyControlPoints).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::TooShortKnotVector(1, 2)).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::IrregularControlPoints).unwrap();
|
||||
writeln!(stderr, "*******************************************************").unwrap();
|
||||
}
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
//! Geometrical structs: knot vector, B-spline and NURBS
|
||||
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![deny(clippy::all, rust_2018_idioms)]
|
||||
#![warn(
|
||||
missing_docs,
|
||||
missing_debug_implementations,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unsafe_code,
|
||||
unstable_features,
|
||||
unused_import_braces,
|
||||
unused_qualifications
|
||||
)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Debug, ops::Bound};
|
||||
use truck_base::bounding_box::Bounded;
|
||||
|
||||
const INCLUDE_CURVE_TRIALS: usize = 100;
|
||||
const PRESEARCH_DIVISION: usize = 50;
|
||||
|
||||
/// re-export `truck_base`
|
||||
pub mod base {
|
||||
pub use truck_base::bounding_box::BoundingBox;
|
||||
pub use truck_base::cgmath64::*;
|
||||
pub use truck_base::tolerance::*;
|
||||
pub use truck_base::{assert_near, assert_near2};
|
||||
pub use truck_base::{hash, hash::HashGen};
|
||||
pub use truck_geotrait::*;
|
||||
}
|
||||
/// Declares the nurbs
|
||||
pub mod nurbs;
|
||||
|
||||
/// Enumerats `Error`.
|
||||
pub mod errors;
|
||||
|
||||
/// Declares the specified gememetric items: Plane, Sphere, and so on.
|
||||
pub mod specifieds;
|
||||
|
||||
/// Declares some decorators
|
||||
pub mod decorators;
|
||||
|
||||
/// re-export all modules.
|
||||
pub mod prelude {
|
||||
use crate::*;
|
||||
pub use base::*;
|
||||
pub use decorators::*;
|
||||
pub use errors::*;
|
||||
pub use nurbs::*;
|
||||
pub use specifieds::*;
|
||||
}
|
||||
+1488
File diff suppressed because it is too large.
Load diff
+2230
File diff suppressed because it is too large.
Load diff
+596
@@ -0,0 +1,596 @@
|
||||
use super::*;
|
||||
use crate::errors::Error;
|
||||
use std::slice::SliceIndex;
|
||||
use std::vec::Vec;
|
||||
|
||||
impl KnotVec {
|
||||
/// empty constructor
|
||||
pub const fn new() -> KnotVec { KnotVec(Vec::new()) }
|
||||
|
||||
/// Returns the length of range.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 6.0]);
|
||||
/// assert_eq!(knot_vec.range_length(), 6.0);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn range_length(&self) -> f64 {
|
||||
match self.is_empty() {
|
||||
true => 0.0,
|
||||
false => self[self.len() - 1] - self[0],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether two knot vectors have the same range.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec0 = KnotVec::new(); // empty knot vector
|
||||
/// let knot_vec1 = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let knot_vec2 = KnotVec::from(vec![0.0, 0.5, 1.0]);
|
||||
/// let knot_vec3 = KnotVec::from(vec![0.0, 0.0, 2.0, 2.0]);
|
||||
/// assert!(knot_vec0.same_range(&KnotVec::new())); // both empty knot vector
|
||||
/// assert!(!knot_vec0.same_range(&knot_vec1));
|
||||
/// assert!(knot_vec1.same_range(&knot_vec2)); // the range of both knot vector is [0, 1].
|
||||
/// assert!(!knot_vec1.same_range(&knot_vec3));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn same_range(&self, other: &KnotVec) -> bool {
|
||||
match (self.is_empty(), other.is_empty()) {
|
||||
(false, false) => {
|
||||
self[0].near(&other[0]) && self.range_length().near(&other.range_length())
|
||||
}
|
||||
(true, true) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes one item.
|
||||
#[inline(always)]
|
||||
pub fn remove(&mut self, idx: usize) -> f64 { self.0.remove(idx) }
|
||||
|
||||
/// Returns the maximum index `i` of `self[i] <= x`
|
||||
/// Return `None` if `x < self[0] or self.len() == 0`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 2.0, 3.0, 3.0]);
|
||||
/// let idx = knot_vec.floor(1.5).unwrap();
|
||||
/// assert_eq!(idx, 2);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn floor(&self, x: f64) -> Option<usize> { self.iter().rposition(|t| *t <= x) }
|
||||
|
||||
/// Returns the multiplicity of the `i`th knot
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0]);
|
||||
/// assert_eq!(knot_vec.multiplicity(5), 3);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn multiplicity(&self, i: usize) -> usize {
|
||||
self.iter().filter(|u| self[i].near(u)).count()
|
||||
}
|
||||
|
||||
/// Adds a knot and return the index of the added knot.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 2.0, 3.0, 3.0]);
|
||||
/// let idx0 = knot_vec.add_knot(1.5);
|
||||
/// assert_eq!(idx0, 3);
|
||||
/// let idx1 = knot_vec.add_knot(-1.0);
|
||||
/// assert_eq!(idx1, 0);
|
||||
/// let ansvec = KnotVec::from(vec![-1.0, 0.0, 0.0, 1.0, 1.5, 2.0, 3.0, 3.0]);
|
||||
/// assert_eq!(knot_vec, ansvec);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn add_knot(&mut self, knot: f64) -> usize {
|
||||
match self.floor(knot) {
|
||||
Some(idx) => {
|
||||
self.0.insert(idx + 1, knot);
|
||||
idx + 1
|
||||
}
|
||||
None => {
|
||||
self.0.insert(0, knot);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates B-spline basis functions at `t` with degree `degree`.
|
||||
/// # Panics
|
||||
/// If the length of `self` is not more than `degree`, panic occurs.
|
||||
/// # Remarks
|
||||
/// In this package, the B-spline basis function is based on the characteristic function of
|
||||
/// the right-open intervals [s, t). So, the value corresponding to the end point t = t_n is always 0.0.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size in tests
|
||||
///
|
||||
/// // B-spline basis functions is a partition of unity in (t_k, t_{n - k}).
|
||||
/// let vec = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
|
||||
/// let knot_vec = KnotVec::from(vec);
|
||||
/// let degree = 2;
|
||||
/// for i in 0..N {
|
||||
/// let t = 2.0 + 4.0 / (N as f64) * (i as f64);
|
||||
/// let res = knot_vec.bspline_basis_functions(degree, t);
|
||||
/// let sum = res.iter().fold(0.0, |sum, a| sum + a);
|
||||
/// assert_near2!(sum, 1.0);
|
||||
/// }
|
||||
/// ```
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size in tests
|
||||
///
|
||||
/// // In some case, B-spline basis functions coincide with Bernstein polynomials.
|
||||
/// let vec = vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0];
|
||||
/// let knot_vec = KnotVec::from(vec);
|
||||
/// let degree = 3;
|
||||
/// for i in 0..=N {
|
||||
/// let t = 1.0 / (N as f64) * (i as f64);
|
||||
/// let res = knot_vec.bspline_basis_functions(degree, t);
|
||||
/// let ans = [
|
||||
/// 1.0 * (1.0 - t) * (1.0 - t) * (1.0 - t),
|
||||
/// 3.0 * t * (1.0 - t) * (1.0 - t),
|
||||
/// 3.0 * t * t * (1.0 - t),
|
||||
/// 1.0 * t * t * t,
|
||||
/// ];
|
||||
/// for i in 0..4 { assert_near2!(res[i], ans[i]); }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn bspline_basis_functions(&self, degree: usize, t: f64) -> Vec<f64> {
|
||||
match self.try_bspline_basis_functions(degree, t) {
|
||||
Ok(got) => got,
|
||||
Err(error) => panic!("{}", error),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates B-spline basis functions at `t` with degree `degree`.
|
||||
/// # Failures
|
||||
/// - If the range of the knot vector is zero, returns [`Error::ZeroRange`].
|
||||
/// - If the length of `self` is not more than `degree`, returns [`Error::TooLargeDegree`].
|
||||
/// # Remarks
|
||||
/// In this package, the B-spline basis function is based on the characteristic function of
|
||||
/// the right-open intervals [s, t). So, the value corresponding to the end point t = t_n is always 0.0.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size in tests
|
||||
///
|
||||
/// // B-spline basis functions is a partition of unity in (t_k, t_{n - k}).
|
||||
/// let vec = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
|
||||
/// let knot_vec = KnotVec::from(vec);
|
||||
/// let degree = 2;
|
||||
/// for i in 0..N {
|
||||
/// let t = 2.0 + 4.0 / (N as f64) * (i as f64);
|
||||
/// let res = knot_vec.try_bspline_basis_functions(degree, t).unwrap();
|
||||
/// let sum = res.iter().fold(0.0, |sum, a| sum + a);
|
||||
/// assert_near2!(sum, 1.0);
|
||||
/// }
|
||||
/// ```
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size in tests
|
||||
///
|
||||
/// // In some case, B-spline basis functions coincide with Bernstein polynomials.
|
||||
/// let vec = vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0];
|
||||
/// let knot_vec = KnotVec::from(vec);
|
||||
/// let degree = 3;
|
||||
/// for i in 0..=N {
|
||||
/// let t = i as f64 / N as f64;
|
||||
/// let res = knot_vec.try_bspline_basis_functions(degree, t).unwrap();
|
||||
/// let ans = [
|
||||
/// 1.0 * (1.0 - t) * (1.0 - t) * (1.0 - t),
|
||||
/// 3.0 * t * (1.0 - t) * (1.0 - t),
|
||||
/// 3.0 * t * t * (1.0 - t),
|
||||
/// 1.0 * t * t * t,
|
||||
/// ];
|
||||
/// for i in 0..4 { assert_near2!(res[i], ans[i]); }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn try_bspline_basis_functions(&self, degree: usize, t: f64) -> Result<Vec<f64>> {
|
||||
let n = self.len() - 1;
|
||||
if self[0].near(&self[n]) {
|
||||
return Err(Error::ZeroRange);
|
||||
} else if n < degree {
|
||||
return Err(Error::TooLargeDegree(n + 1, degree));
|
||||
}
|
||||
|
||||
let idx = {
|
||||
let idx = self
|
||||
.floor(t)
|
||||
.unwrap_or_else(|| self.floor(self[0]).unwrap());
|
||||
if idx == n {
|
||||
n - self.multiplicity(n)
|
||||
} else {
|
||||
idx
|
||||
}
|
||||
};
|
||||
let mut res = vec![0.0; n];
|
||||
res[idx] = 1.0;
|
||||
|
||||
for k in 1..=degree {
|
||||
let base = if idx < k { 0 } else { idx - k };
|
||||
let delta = self[base + k] - self[base];
|
||||
let max = if idx + k < n { idx } else { n - k - 1 };
|
||||
let mut a = inv_or_zero(delta) * (t - self[base]);
|
||||
for i in base..=max {
|
||||
let delta = self[i + k + 1] - self[i + 1];
|
||||
let b = inv_or_zero(delta) * (self[i + k + 1] - t);
|
||||
res[i] = a * res[i] + b * res[i + 1];
|
||||
a = 1.0 - b;
|
||||
}
|
||||
}
|
||||
|
||||
res.truncate(n - degree);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn maximum_points(&self, degree: usize) -> Vec<f64> {
|
||||
let n = self.len();
|
||||
let m = n - degree - 1;
|
||||
let range = self.range_length();
|
||||
const N: i32 = 100;
|
||||
|
||||
let mut res = vec![0.0; m];
|
||||
let mut max = vec![0.0; m];
|
||||
for i in 1..N {
|
||||
let t = self[0] + range * (i as f64) / (N as f64);
|
||||
let vals = self.try_bspline_basis_functions(degree, t).unwrap();
|
||||
for j in 0..m {
|
||||
if max[j] < vals[j] {
|
||||
max[j] = vals[j];
|
||||
res[j] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Transforms the knot vector
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use std::vec::Vec;
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
|
||||
/// knot_vec.transform(2.0, 3.0);
|
||||
/// let res : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(res, vec![5.0, 5.0, 7.0, 9.0, 11.0, 13.0, 13.0]);
|
||||
/// ```
|
||||
/// # Panics
|
||||
/// Panic occurs if `scalar` is not positive.
|
||||
pub fn transform(&mut self, scalar: f64, r#move: f64) -> &mut Self {
|
||||
assert!(scalar > 0.0, "The scalar {scalar} is not positive.");
|
||||
self.0
|
||||
.iter_mut()
|
||||
.for_each(move |vec| *vec = *vec * scalar + r#move);
|
||||
self
|
||||
}
|
||||
|
||||
/// Normalizes the knot vector i.e. makes the first value 0 and the last value 1.
|
||||
/// # Failures
|
||||
/// Returns [`Error::ZeroRange`] if the range of the knot vector is so small.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
|
||||
/// knot_vec.try_normalize().unwrap();
|
||||
/// let res : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(res, vec![0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0]);
|
||||
/// ```
|
||||
pub fn try_normalize(&mut self) -> Result<&mut Self> {
|
||||
let range = self.range_length();
|
||||
if range.so_small() {
|
||||
return Err(Error::ZeroRange);
|
||||
}
|
||||
Ok(self.transform(1.0 / range, -self[0] / range))
|
||||
}
|
||||
|
||||
/// Normalizes the knot vector i.e. makes the first value 0 and the last value 1.
|
||||
/// # Panics
|
||||
/// Panic occurs if the range of the knot vector is so small.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
|
||||
/// knot_vec.normalize();
|
||||
/// let res : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(res, vec![0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0]);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn normalize(&mut self) -> &mut Self {
|
||||
self.try_normalize()
|
||||
.unwrap_or_else(|error| panic!("{}", error))
|
||||
}
|
||||
|
||||
/// Translates the knot vector
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use std::vec::Vec;
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
|
||||
/// knot_vec.translate(3.0);
|
||||
/// let res : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(res, vec![4.0, 4.0, 5.0, 6.0, 7.0, 8.0, 8.0]);
|
||||
/// ```
|
||||
pub fn translate(&mut self, x: f64) -> &mut Self { self.transform(1.0, x) }
|
||||
|
||||
/// Inverts the knot vector
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use std::vec::Vec;
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 1.0, 3.0, 5.0, 6.0]);
|
||||
/// knot_vec.invert();
|
||||
/// let res : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(res, vec![1.0, 2.0, 4.0, 6.0, 6.0, 6.0]);
|
||||
///
|
||||
/// ```
|
||||
pub fn invert(&mut self) -> &mut Self {
|
||||
let n = self.len();
|
||||
if n == 0 {
|
||||
return self;
|
||||
}
|
||||
let range = self[0] + self[n - 1];
|
||||
let clone = self.0.clone();
|
||||
for (knot1, knot0) in clone.iter().rev().zip(&mut self.0) {
|
||||
*knot0 = range - knot1;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Determines the knot vector is clamped for the given degree.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0]);
|
||||
/// assert!(knot_vec.is_clamped(2));
|
||||
/// assert!(!knot_vec.is_clamped(3));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn is_clamped(&self, degree: usize) -> bool {
|
||||
self.multiplicity(0) > degree && self.multiplicity(self.len() - 1) > degree
|
||||
}
|
||||
|
||||
/// Concats two knot vectors.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
|
||||
/// let knot_vec1 = KnotVec::from(vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
|
||||
/// knot_vec0.try_concat(&knot_vec1, 2).unwrap();
|
||||
/// assert_eq!(knot_vec0.as_slice(), &[0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
|
||||
/// ```
|
||||
/// # Failures
|
||||
/// - If at least one of `self` or `other` is not clamped, returns [`Error::NotClampedKnotVector`]
|
||||
/// - If the last knot of `self` and the first knot of `other` are different, returns [`Error::DifferentBackFront`].
|
||||
pub fn try_concat(&mut self, other: &KnotVec, degree: usize) -> Result<&mut Self> {
|
||||
if !self.is_clamped(degree) || !other.is_clamped(degree) {
|
||||
return Err(Error::NotClampedKnotVector);
|
||||
}
|
||||
let back = self.0.last().unwrap();
|
||||
let front = other.0.first().unwrap();
|
||||
if front < back || !front.near(back) {
|
||||
return Err(Error::DifferentBackFront(*back, *front));
|
||||
}
|
||||
|
||||
self.0.truncate(self.len() - degree - 1);
|
||||
for knot in &other.0 {
|
||||
self.0.push(*knot);
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Concats two knot vectors.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
|
||||
/// let knot_vec1 = KnotVec::from(vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
|
||||
/// knot_vec0.concat(&knot_vec1, 2);
|
||||
/// assert_eq!(knot_vec0.as_slice(), &[0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
|
||||
/// ```
|
||||
/// # Panics
|
||||
/// Panic occurs if:
|
||||
/// - at least one of `self` or `other` is not clamped.
|
||||
/// - the last knot of `self` and the first knot of `other` are different.
|
||||
#[inline(always)]
|
||||
pub fn concat(&mut self, other: &KnotVec, degree: usize) -> &mut Self {
|
||||
self.try_concat(other, degree)
|
||||
.unwrap_or_else(|error| panic!("{}", error))
|
||||
}
|
||||
|
||||
/// Returns trimmed vector by the specified range.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]);
|
||||
/// let sub_vec = knot_vec.sub_vec(1..3);
|
||||
/// assert_eq!(sub_vec, KnotVec::from(vec![1.0, 2.0]));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn sub_vec<I: SliceIndex<[f64], Output = [f64]>>(&self, range: I) -> KnotVec {
|
||||
KnotVec(Vec::from(&self.0[range]))
|
||||
}
|
||||
|
||||
/// To single-multi description. i.e. decompose the unique vector of knots and the vector of
|
||||
/// multiplicity of knots.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0]);
|
||||
/// let (knots, mults) = knot_vec.to_single_multi();
|
||||
/// assert_eq!(knots, vec![0.0, 1.0, 2.0, 3.0]);
|
||||
/// assert_eq!(mults, vec![3, 1, 4, 2]);
|
||||
/// ```
|
||||
pub fn to_single_multi(&self) -> (Vec<f64>, Vec<usize>) {
|
||||
let mut knots = Vec::new();
|
||||
let mut mults = Vec::new();
|
||||
|
||||
let mut iter = self.as_slice().iter().peekable();
|
||||
let mut mult = 1;
|
||||
while let Some(knot) = iter.next() {
|
||||
if let Some(next) = iter.peek() {
|
||||
if knot.near(next) {
|
||||
mult += 1;
|
||||
} else {
|
||||
knots.push(*knot);
|
||||
mults.push(mult);
|
||||
mult = 1;
|
||||
}
|
||||
} else {
|
||||
knots.push(*knot);
|
||||
mults.push(mult);
|
||||
}
|
||||
}
|
||||
(knots, mults)
|
||||
}
|
||||
|
||||
/// Constructs from single-multi description.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knots = vec![0.0, 1.0, 2.0, 3.0];
|
||||
/// let mults = vec![3, 1, 4, 2];
|
||||
/// let knot_vec = KnotVec::from_single_multi(knots, mults).unwrap();
|
||||
/// assert_eq!(knot_vec, KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0]));
|
||||
/// ```
|
||||
pub fn from_single_multi(knots: Vec<f64>, mults: Vec<usize>) -> Result<KnotVec> {
|
||||
for i in 1..knots.len() {
|
||||
if knots[i - 1] > knots[i] {
|
||||
return Err(Error::NotSortedVector);
|
||||
}
|
||||
}
|
||||
|
||||
let mut vec = Vec::new();
|
||||
for i in 0..knots.len() {
|
||||
for _ in 0..mults[i] {
|
||||
vec.push(knots[i]);
|
||||
}
|
||||
}
|
||||
Ok(KnotVec(vec))
|
||||
}
|
||||
/// Constructs from `Vec<f64>`. do not sort, only check sorted.
|
||||
pub fn try_from(vec: Vec<f64>) -> Result<KnotVec> {
|
||||
for i in 1..vec.len() {
|
||||
if vec[i - 1] > vec[i] {
|
||||
return Err(Error::NotSortedVector);
|
||||
}
|
||||
}
|
||||
Ok(KnotVec(vec))
|
||||
}
|
||||
|
||||
/// Constructs the knot vector for the bezier spline.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// assert_eq!(
|
||||
/// *KnotVec::bezier_knot(3),
|
||||
/// vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0],
|
||||
/// );
|
||||
/// ```
|
||||
pub fn bezier_knot(degree: usize) -> KnotVec {
|
||||
let mut vec = vec![0.0; degree + 1];
|
||||
vec.extend(std::iter::repeat(1.0).take(degree + 1));
|
||||
KnotVec(vec)
|
||||
}
|
||||
|
||||
/// Constructs the uniform knot vector
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// assert_eq!(
|
||||
/// *KnotVec::uniform_knot(2, 5),
|
||||
/// vec![0.0, 0.0, 0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.0, 1.0],
|
||||
/// );
|
||||
/// ```
|
||||
pub fn uniform_knot(degree: usize, division: usize) -> KnotVec {
|
||||
let mut vec = vec![0.0; degree + 1];
|
||||
vec.extend((1..division).map(|i| (i as f64) / (division as f64)));
|
||||
vec.extend(std::iter::repeat(1.0).take(degree + 1));
|
||||
KnotVec(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<f64>> for KnotVec {
|
||||
/// constructs from `Vec<f64>`. The vector will sorted by the order.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knot_vec = KnotVec::from(vec![1.0, 0.0, 3.0, 2.0]);
|
||||
/// let arr : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(arr, vec![0.0, 1.0, 2.0, 3.0]);
|
||||
/// ```
|
||||
fn from(mut vec: Vec<f64>) -> KnotVec {
|
||||
vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
KnotVec(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Vec<f64>> for KnotVec {
|
||||
/// Constructs by the reference of vector. The clone of vector is sorted by the order.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knot_vec = KnotVec::from(&vec![1.0, 0.0, 3.0, 2.0]);
|
||||
/// let arr : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(arr, vec![0.0, 1.0, 2.0, 3.0]);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
fn from(vec: &Vec<f64>) -> KnotVec {
|
||||
let mut copy_vec = vec.clone();
|
||||
copy_vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
KnotVec(copy_vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KnotVec> for Vec<f64> {
|
||||
/// `KnotVec` into `Vec<f64>`
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let vec = vec![0.0, 1.0, 2.0, 3.0];
|
||||
/// let knot_vec = KnotVec::from(&vec);
|
||||
/// let vec0 : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(vec, vec0);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
fn from(knotvec: KnotVec) -> Vec<f64> { knotvec.0 }
|
||||
}
|
||||
|
||||
impl FromIterator<f64> for KnotVec {
|
||||
#[inline(always)]
|
||||
fn from_iter<I: IntoIterator<Item = f64>>(iter: I) -> KnotVec {
|
||||
KnotVec::try_from(iter.into_iter().collect::<Vec<_>>()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a KnotVec {
|
||||
type Item = &'a f64;
|
||||
type IntoIter = std::slice::Iter<'a, f64>;
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> Self::IntoIter { self.0.iter() }
|
||||
}
|
||||
|
||||
impl std::ops::Deref for KnotVec {
|
||||
type Target = Vec<f64>;
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Vec<f64> { &self.0 }
|
||||
}
|
||||
|
||||
impl AsRef<[f64]> for KnotVec {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &[f64] { &self.0 }
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for KnotVec {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where D: serde::Deserializer<'de> {
|
||||
let vec = Vec::<f64>::deserialize(deserializer)?;
|
||||
Self::try_from(vec).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
use crate::{prelude::*, *};
|
||||
use truck_base::cgmath64::control_point::ControlPoint;
|
||||
|
||||
/// knot vector
|
||||
#[derive(Clone, PartialEq, Debug, Default, Serialize)]
|
||||
pub struct KnotVec(Vec<f64>);
|
||||
|
||||
/// B-spline curve
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
///
|
||||
/// // the knot vector
|
||||
/// let knot_vec = KnotVec::from(
|
||||
/// vec![0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0]
|
||||
/// );
|
||||
///
|
||||
/// // sign up the control points in the vector of all points
|
||||
/// let ctrl_pts = vec![ // the vector of the indices of control points
|
||||
/// Vector4::new(0.0, -2.0, 0.0, 2.0),
|
||||
/// Vector4::new(1.0, -1.0, 0.0, 1.0),
|
||||
/// Vector4::new(1.0, 0.0, 0.0, 1.0),
|
||||
/// Vector4::new(1.0, 1.0, 0.0, 1.0),
|
||||
/// Vector4::new(0.0, 2.0, 0.0, 2.0),
|
||||
/// Vector4::new(-1.0, 1.0, 0.0, 1.0),
|
||||
/// Vector4::new(-1.0, 0.0, 0.0, 1.0),
|
||||
/// Vector4::new(-1.0, -1.0, 0.0, 1.0),
|
||||
/// Vector4::new(0.0, -2.0, 0.0, 2.0),
|
||||
/// ];
|
||||
///
|
||||
/// // construct the B-spline curve
|
||||
/// let bspline = BSplineCurve::new(knot_vec, ctrl_pts);
|
||||
///
|
||||
/// // This B-spline curve is a nurbs representation of the unit circle.
|
||||
/// const N : usize = 100; // sample size in test
|
||||
/// for i in 0..N {
|
||||
/// let t = 1.0 / (N as f64) * (i as f64);
|
||||
/// let v = bspline.subs(t); // We can use the instances as a function.
|
||||
/// let c = (v[0] / v[3]).powi(2) + (v[1] / v[3]).powi(2);
|
||||
/// assert_near2!(c, 1.0);
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, PartialEq, Debug, Serialize)]
|
||||
pub struct BSplineCurve<P> {
|
||||
knot_vec: KnotVec, // the knot vector
|
||||
control_points: Vec<P>, // the indices of control points
|
||||
}
|
||||
|
||||
/// B-spline surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size in test
|
||||
///
|
||||
/// // the knot vectors
|
||||
/// let knot_vec0 = KnotVec::bezier_knot(3);
|
||||
/// let knot_vec1 = KnotVec::from(
|
||||
/// vec![0.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0]
|
||||
/// );
|
||||
/// let knot_vecs = (knot_vec0, knot_vec1);
|
||||
///
|
||||
/// // the control points
|
||||
/// let mut v = vec![vec![Vector4::zero(); 7]; 4];
|
||||
/// v[0][0] = Vector4::new(0.0, 0.0, 1.0, 1.0);
|
||||
/// v[0][1] = &v[0][0] / 3.0;
|
||||
/// v[0][2] = v[0][1].clone();
|
||||
/// v[0][3] = v[0][0].clone();
|
||||
/// v[0][4] = v[0][1].clone();
|
||||
/// v[0][5] = v[0][1].clone();
|
||||
/// v[0][6] = v[0][0].clone();
|
||||
/// v[1][0] = Vector4::new(2.0, 0.0, 1.0, 1.0) / 3.0;
|
||||
/// v[1][1] = Vector4::new(2.0, 4.0, 1.0, 1.0) / 9.0;
|
||||
/// v[1][2] = Vector4::new(-2.0, 4.0, 1.0, 1.0) / 9.0;
|
||||
/// v[1][3] = Vector4::new(-2.0, 0.0, 1.0, 1.0) / 3.0;
|
||||
/// v[1][4] = Vector4::new(-2.0, -4.0, 1.0, 1.0) / 9.0;
|
||||
/// v[1][5] = Vector4::new(2.0, -4.0, 1.0, 1.0) / 9.0;
|
||||
/// v[1][6] = Vector4::new(2.0, 0.0, 1.0, 1.0) / 3.0;
|
||||
/// v[2][0] = Vector4::new(2.0, 0.0, -1.0, 1.0) / 3.0;
|
||||
/// v[2][1] = Vector4::new(2.0, 4.0, -1.0, 1.0) / 9.0;
|
||||
/// v[2][2] = Vector4::new(-2.0, 4.0, -1.0, 1.0) / 9.0;
|
||||
/// v[2][3] = Vector4::new(-2.0, 0.0, -1.0, 1.0) / 3.0;
|
||||
/// v[2][4] = Vector4::new(-2.0, -4.0, -1.0, 1.0) / 9.0;
|
||||
/// v[2][5] = Vector4::new(2.0, -4.0, -1.0, 1.0) / 9.0;
|
||||
/// v[2][6] = Vector4::new(2.0, 0.0, -1.0, 1.0) / 3.0;
|
||||
/// v[3][0] = Vector4::new(0.0, 0.0, -1.0, 1.0);
|
||||
/// v[3][1] = &v[3][0] / 3.0;
|
||||
/// v[3][2] = v[3][1].clone();
|
||||
/// v[3][3] = v[3][0].clone();
|
||||
/// v[3][4] = v[3][1].clone();
|
||||
/// v[3][5] = v[3][1].clone();
|
||||
/// v[3][6] = v[3][0].clone();
|
||||
///
|
||||
/// // cunstruct the B-spline curve
|
||||
/// let bspline = BSplineSurface::new(knot_vecs, v);
|
||||
///
|
||||
/// // This B-spline curve is a nurbs representation of the unit sphere.
|
||||
/// for i in 0..N {
|
||||
/// for j in 0..N {
|
||||
/// let u = 1.0 / (N as f64) * (i as f64);
|
||||
/// let v = 1.0 / (N as f64) * (j as f64);
|
||||
/// let v = bspline.subs(u, v); // We can use the instances as a function.
|
||||
/// let c = (v[0] / v[3]).powi(2) + (v[1] / v[3]).powi(2) + (v[2] / v[3]).powi(2);
|
||||
/// assert_near2!(c, 1.0);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, PartialEq, Debug, Serialize)]
|
||||
pub struct BSplineSurface<P> {
|
||||
knot_vecs: (KnotVec, KnotVec),
|
||||
control_points: Vec<Vec<P>>,
|
||||
}
|
||||
|
||||
/// NURBS curve
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct NurbsCurve<V>(BSplineCurve<V>);
|
||||
|
||||
/// NURBS surface
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct NurbsSurface<V>(BSplineSurface<V>);
|
||||
|
||||
mod bspcurve;
|
||||
mod bspsurface;
|
||||
mod knot_vec;
|
||||
mod nurbscurve;
|
||||
mod nurbssurface;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
pub fn inv_or_zero(delta: f64) -> f64 {
|
||||
if delta.so_small() {
|
||||
0.0
|
||||
} else {
|
||||
1.0 / delta
|
||||
}
|
||||
}
|
||||
+619
@@ -0,0 +1,619 @@
|
||||
use super::*;
|
||||
|
||||
impl<V> NurbsCurve<V> {
|
||||
/// Constructs the rationalized B-spline curve.
|
||||
#[inline(always)]
|
||||
pub const fn new(curve: BSplineCurve<V>) -> Self { NurbsCurve(curve) }
|
||||
|
||||
/// Returns the BSpline curve before rationalized.
|
||||
#[inline(always)]
|
||||
pub const fn non_rationalized(&self) -> &BSplineCurve<V> { &self.0 }
|
||||
|
||||
/// Returns the BSpline curve before rationalized.
|
||||
#[inline(always)]
|
||||
pub fn into_non_rationalized(self) -> BSplineCurve<V> { self.0 }
|
||||
|
||||
/// Returns the reference of the knot vector. cf.[`BSplineCurve::knot_vec`]
|
||||
#[inline(always)]
|
||||
pub const fn knot_vec(&self) -> &KnotVec { &self.0.knot_vec }
|
||||
|
||||
/// Returns the `idx`th knot. cf.[`BSplineCurve::knot`]
|
||||
#[inline(always)]
|
||||
pub fn knot(&self, idx: usize) -> f64 { self.0.knot_vec[idx] }
|
||||
|
||||
/// Returns the reference of the control points. cf.[`BSplineCurve::control_points`]
|
||||
#[inline(always)]
|
||||
pub const fn control_points(&self) -> &Vec<V> { &self.0.control_points }
|
||||
|
||||
/// Returns the reference of the control point corresponding to the index `idx`.
|
||||
/// cf.[`BSplineCurve::control_point`]
|
||||
#[inline(always)]
|
||||
pub fn control_point(&self, idx: usize) -> &V { &self.0.control_points[idx] }
|
||||
|
||||
/// Returns the mutable reference of the control point corresponding to index `idx`.
|
||||
/// cf.[`BSplineCurve::control_point_mut`]
|
||||
#[inline(always)]
|
||||
pub fn control_point_mut(&mut self, idx: usize) -> &mut V { &mut self.0.control_points[idx] }
|
||||
|
||||
/// Returns the iterator on all control points. cf.[`BSplineCurve::control_points_mut`]
|
||||
#[inline(always)]
|
||||
pub fn control_points_mut(&mut self) -> impl Iterator<Item = &mut V> {
|
||||
self.0.control_points.iter_mut()
|
||||
}
|
||||
|
||||
/// Applies the given transformation to all control points. cf.[`BSplineCurve::transform_control_points`]
|
||||
#[inline(always)]
|
||||
pub fn transform_control_points<F: FnMut(&mut V)>(&mut self, f: F) {
|
||||
self.0.transform_control_points(f)
|
||||
}
|
||||
|
||||
/// Returns the degree of NURBS curve. cf.[`BSplineCurve::degree`]
|
||||
#[inline(always)]
|
||||
pub fn degree(&self) -> usize { self.0.degree() }
|
||||
|
||||
/// Inverts a curve. cf.[`BSplineCurve::invert`]
|
||||
#[inline(always)]
|
||||
pub fn invert(&mut self) -> &mut Self {
|
||||
self.0.invert();
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns whether the knot vector is clamped or not. cf.[`BSplineCurve::is_clamped`]
|
||||
#[inline(always)]
|
||||
pub fn is_clamped(&self) -> bool { self.0.knot_vec.is_clamped(self.0.degree()) }
|
||||
|
||||
/// Normalizes the knot vector. cf.[`BSplineCurve::knot_normalize`]
|
||||
#[inline(always)]
|
||||
pub fn knot_normalize(&mut self) -> &mut Self {
|
||||
self.0.knot_vec.try_normalize().unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Translates the knot vector. cf.[`BSplineCurve::knot_translate`]
|
||||
#[inline(always)]
|
||||
pub fn knot_translate(&mut self, x: f64) -> &mut Self {
|
||||
self.0.knot_vec.translate(x);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> NurbsCurve<V> {
|
||||
/// Constructs a rationalization curve from the non-rationalized curve and weights.
|
||||
/// # Failures
|
||||
/// the length of `curve.control_points()` and `weights` must be the same.
|
||||
#[inline(always)]
|
||||
pub fn try_from_bspline_and_weights(
|
||||
curve: BSplineCurve<V::Point>,
|
||||
weights: Vec<f64>,
|
||||
) -> Result<Self> {
|
||||
let BSplineCurve {
|
||||
knot_vec,
|
||||
control_points,
|
||||
} = curve;
|
||||
if control_points.len() != weights.len() {
|
||||
return Err(Error::DifferentLength);
|
||||
}
|
||||
let control_points = control_points
|
||||
.into_iter()
|
||||
.zip(weights)
|
||||
.map(|(pt, w)| V::from_point_weight(pt, w))
|
||||
.collect();
|
||||
Ok(Self(BSplineCurve::new_unchecked(knot_vec, control_points)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsCurve<V> {
|
||||
/// Returns the closure of substitution.
|
||||
#[inline(always)]
|
||||
pub fn get_closure(&self) -> impl Fn(f64) -> V::Point + '_ { move |t| self.subs(t) }
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsCurve<V>
|
||||
where V::Point: Tolerance
|
||||
{
|
||||
/// Returns whether all control points are the same or not.
|
||||
/// If the knot vector is clamped, it means whether the curve is constant or not.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
///
|
||||
/// let knot_vec = KnotVec::bezier_knot(2);
|
||||
/// let pt = Vector3::new(1.0, 2.0, 1.0);
|
||||
/// // allows differences upto scalars
|
||||
/// let mut ctrl_pts = vec![pt.clone(), pt.clone() * 2.0, pt.clone() * 3.0];
|
||||
/// let bspcurve = BSplineCurve::new(knot_vec.clone(), ctrl_pts.clone());
|
||||
/// assert!(!bspcurve.is_const());
|
||||
/// let const_curve = NurbsCurve::new(bspcurve);
|
||||
/// assert!(const_curve.is_const());
|
||||
///
|
||||
/// ctrl_pts.push(Vector3::new(2.0, 3.0, 1.0));
|
||||
/// let curve = NurbsCurve::new(BSplineCurve::new(knot_vec.clone(), ctrl_pts.clone()));
|
||||
/// assert!(!curve.is_const());
|
||||
/// ```
|
||||
/// # Remarks
|
||||
/// If the knot vector is not clamped and the BSpline basis function is not partition of unity,
|
||||
/// then perhaps returns true even if the curve is not constant.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::uniform_knot(1, 5);
|
||||
/// let ctrl_pts = vec![Vector2::new(1.0, 2.0), Vector2::new(1.0, 2.0)];
|
||||
/// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts);
|
||||
///
|
||||
/// // bspcurve is not constant.
|
||||
/// assert_eq!(bspcurve.subs(0.0), Vector2::new(0.0, 0.0));
|
||||
/// assert_ne!(bspcurve.subs(0.5), Vector2::new(0.0, 0.0));
|
||||
///
|
||||
/// // bspcurve.is_const() is true
|
||||
/// assert!(bspcurve.is_const());
|
||||
/// ```
|
||||
pub fn is_const(&self) -> bool {
|
||||
let pt = self.0.control_points[0].to_point();
|
||||
self.0
|
||||
.control_points
|
||||
.iter()
|
||||
.all(move |vec| vec.to_point().near(&pt))
|
||||
}
|
||||
/// Determines whether `self` and `other` is near as the B-spline curves or not.
|
||||
///
|
||||
/// Divides each knot interval into the number of degree equal parts,
|
||||
/// and check `|self(t) - other(t)| < TOLERANCE` for each end points `t`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::from(
|
||||
/// vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 4.0, 4.0]
|
||||
/// );
|
||||
/// let ctrl_pts = vec![
|
||||
/// Vector3::new(1.0, 1.0, 1.0),
|
||||
/// Vector3::new(3.0, 2.0, 2.0),
|
||||
/// Vector3::new(2.0, 3.0, 1.0),
|
||||
/// Vector3::new(4.0, 5.0, 2.0),
|
||||
/// Vector3::new(5.0, 4.0, 1.0),
|
||||
/// Vector3::new(1.0, 1.0, 2.0),
|
||||
/// ];
|
||||
/// let curve0 = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
/// let mut curve1 = curve0.clone();
|
||||
/// assert!(curve0.near_as_curve(&curve1));
|
||||
/// *curve1.control_point_mut(1) += Vector3::new(0.01, 0.0002, 0.0);
|
||||
/// assert!(!curve0.near_as_curve(&curve1));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn near_as_curve(&self, other: &Self) -> bool {
|
||||
self.0
|
||||
.sub_near_as_curve(&other.0, 2, move |x, y| x.to_point().near(&y.to_point()))
|
||||
}
|
||||
|
||||
/// Determines `self` and `other` is near in square order as the B-spline curves or not.
|
||||
///
|
||||
/// Divide each knot interval into the number of degree equal parts,
|
||||
/// and check `|self(t) - other(t)| < TOLERANCE`for each end points `t`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::from(
|
||||
/// vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 4.0, 4.0]
|
||||
/// );
|
||||
/// let ctrl_pts = vec![
|
||||
/// Vector3::new(1.0, 1.0, 1.0),
|
||||
/// Vector3::new(3.0, 2.0, 2.0),
|
||||
/// Vector3::new(2.0, 3.0, 1.0),
|
||||
/// Vector3::new(4.0, 5.0, 2.0),
|
||||
/// Vector3::new(5.0, 4.0, 1.0),
|
||||
/// Vector3::new(1.0, 1.0, 2.0),
|
||||
/// ];
|
||||
/// let curve0 = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
/// let mut curve1 = curve0.clone();
|
||||
/// assert!(curve0.near_as_curve(&curve1));
|
||||
/// *curve1.control_point_mut(1) += Vector3::new(0.01, TOLERANCE, 0.0);
|
||||
/// assert!(!curve0.near2_as_curve(&curve1));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn near2_as_curve(&self, other: &Self) -> bool {
|
||||
self.0
|
||||
.sub_near_as_curve(&other.0, 2, move |x, y| x.to_point().near2(&y.to_point()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> NurbsCurve<V> {
|
||||
/// Adds a knot `x`, and do not change `self` as a curve. cf.[`BSplineCurve::add_knot`]
|
||||
pub fn add_knot(&mut self, x: f64) -> &mut Self {
|
||||
self.0.add_knot(x);
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes a knot corresponding to the indices `idx`, and do not change `self` as a curve.
|
||||
/// If cannot remove the knot, do not change `self` and return `self`.
|
||||
/// cf.[`BSplineCurve::remove_knot`]
|
||||
pub fn remove_knot(&mut self, idx: usize) -> &mut Self {
|
||||
let _ = self.try_remove_knot(idx);
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes a knot corresponding to the indice `idx`, and do not change `self` as a curve.
|
||||
/// If the knot cannot be removed, returns [`Error::CannotRemoveKnot`].
|
||||
/// cf.[`BSplineCurve::try_remove_knot`]
|
||||
pub fn try_remove_knot(&mut self, idx: usize) -> Result<&mut Self> {
|
||||
self.0.try_remove_knot(idx)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Elevates 1 degree. cf.[`BSplineCurve::elevate_degree`]
|
||||
pub fn elevate_degree(&mut self) -> &mut Self {
|
||||
self.0.elevate_degree();
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the NURBS curve clamped. cf.[`BSplineCurve::clamp`]
|
||||
#[inline(always)]
|
||||
pub fn clamp(&mut self) -> &mut Self {
|
||||
self.0.clamp();
|
||||
self
|
||||
}
|
||||
|
||||
/// Repeats `Self::try_remove_knot()` from the back knot in turn until the knot cannot be removed.
|
||||
/// cf.[`BSplineCurve::optimize`]
|
||||
pub fn optimize(&mut self) -> &mut Self {
|
||||
self.0.optimize();
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes two splines having the same degrees. cf.[`BSplineCurve::syncro_degree`]
|
||||
pub fn syncro_degree(&mut self, other: &mut Self) {
|
||||
let (degree0, degree1) = (self.degree(), other.degree());
|
||||
for _ in degree0..degree1 {
|
||||
self.elevate_degree();
|
||||
}
|
||||
for _ in degree1..degree0 {
|
||||
other.elevate_degree();
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes two splines having the same normalized knot vectors. cf.[`BSplineCurve::syncro_knots`]
|
||||
pub fn syncro_knots(&mut self, other: &mut Self) { self.0.syncro_knots(&mut other.0) }
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> ParameterTransform
|
||||
for NurbsCurve<V>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parameter_transform(&mut self, scalar: f64, r#move: f64) -> &mut Self {
|
||||
self.0.parameter_transform(scalar, r#move);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> Cut for NurbsCurve<V> {
|
||||
#[inline(always)]
|
||||
fn cut(&mut self, t: f64) -> Self { NurbsCurve(self.0.cut(t)) }
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> Concat<NurbsCurve<V>>
|
||||
for NurbsCurve<V>
|
||||
where <V as Homogeneous<f64>>::Point: Debug
|
||||
{
|
||||
type Output = NurbsCurve<V>;
|
||||
fn try_concat(
|
||||
&self,
|
||||
other: &Self,
|
||||
) -> std::result::Result<Self, ConcatError<<V as Homogeneous<f64>>::Point>> {
|
||||
let curve0 = self.clone();
|
||||
let mut curve1 = other.clone();
|
||||
let w0 = curve0.0.control_points.last().unwrap().weight();
|
||||
let w1 = curve1.0.control_points[0].weight();
|
||||
curve1.transform_control_points(|pt| *pt *= w0 / w1);
|
||||
match curve0.0.try_concat(&curve1.0) {
|
||||
Ok(curve) => Ok(NurbsCurve::new(curve)),
|
||||
Err(err) => Err(err.point_map(|v| v.to_point())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concat_positive_test() {
|
||||
let mut part0 = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::uniform_knot(4, 4),
|
||||
(0..8)
|
||||
.map(|_| {
|
||||
Vector4::new(
|
||||
rand::random::<f64>(),
|
||||
rand::random::<f64>(),
|
||||
rand::random::<f64>(),
|
||||
rand::random::<f64>() + 0.5,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
let mut part1 = part0.cut(0.56);
|
||||
let w = 20.0 * rand::random::<f64>() - 10.0;
|
||||
part1.transform_control_points(|vec| *vec *= w);
|
||||
assert_near!(part0.back(), part1.front());
|
||||
concat_random_test(&part0, &part1, 10);
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> NurbsCurve<V>
|
||||
where V::Point: Tolerance
|
||||
{
|
||||
/// Makes the rational curve locally injective.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size for test
|
||||
///
|
||||
/// let knot_vec = KnotVec::from(
|
||||
/// vec![0.0, 0.0, 0.0, 1.0, 3.0, 4.0, 4.0, 4.0]
|
||||
/// );
|
||||
/// let control_points = vec![
|
||||
/// Vector4::new(1.0, 0.0, 0.0, 1.0),
|
||||
/// Vector4::new(0.0, 1.0, 0.0, 1.0),
|
||||
/// Vector4::new(0.0, 2.0, 0.0, 2.0),
|
||||
/// Vector4::new(0.0, 3.0, 0.0, 3.0),
|
||||
/// Vector4::new(0.0, 0.0, 3.0, 3.0),
|
||||
/// ];
|
||||
///
|
||||
/// let mut curve = NurbsCurve::new(BSplineCurve::new(knot_vec, control_points));
|
||||
/// let mut flag = false;
|
||||
/// for i in 0..N {
|
||||
/// let t = 4.0 * (i as f64) / (N as f64);
|
||||
/// let pt0 = curve.subs(t);
|
||||
/// let pt1 = curve.subs(t + 1.0 / (N as f64));
|
||||
/// flag = flag || pt0.near(&pt1);
|
||||
/// }
|
||||
/// // There exists t such that bspcurve(t) == bspcurve(t + 0.01).
|
||||
/// assert!(flag);
|
||||
///
|
||||
/// curve.make_locally_injective().knot_normalize();
|
||||
/// let mut flag = false;
|
||||
/// for i in 0..N {
|
||||
/// let t = 1.0 * (i as f64) / (N as f64);
|
||||
/// let pt0 = curve.subs(t);
|
||||
/// let pt1 = curve.subs(t + 1.0 / (N as f64));
|
||||
/// flag = flag || pt0.near(&pt1);
|
||||
/// }
|
||||
/// // There does not exist t such that bspcurve(t) == bspcurve(t + 0.01).
|
||||
/// assert!(!flag);
|
||||
/// ```
|
||||
/// # Remarks
|
||||
/// If `self` is a constant curve, then does nothing.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0]);
|
||||
/// let ctrl_pts = vec![
|
||||
/// Vector3::new(1.0, 1.0, 1.0),
|
||||
/// Vector3::new(2.0, 2.0, 2.0),
|
||||
/// Vector3::new(3.0, 3.0, 3.0),
|
||||
/// Vector3::new(4.0, 4.0, 4.0),
|
||||
/// ];
|
||||
/// let mut curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
/// let org_curve = curve.clone();
|
||||
/// curve.make_locally_injective();
|
||||
/// assert_eq!(curve, org_curve);
|
||||
/// ```
|
||||
pub fn make_locally_injective(&mut self) -> &mut Self {
|
||||
let mut iter = self.0.bezier_decomposition().into_iter();
|
||||
while let Some(bezier) = iter.next().map(NurbsCurve::new) {
|
||||
if !bezier.is_const() {
|
||||
*self = bezier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut x = 0.0;
|
||||
for mut bezier in iter.map(NurbsCurve::new) {
|
||||
if bezier.is_const() {
|
||||
x += bezier.0.knot_vec.range_length();
|
||||
} else {
|
||||
let s0 = self.0.control_points.last().unwrap().weight();
|
||||
let s1 = bezier.0.control_points[0].weight();
|
||||
bezier
|
||||
.0
|
||||
.control_points
|
||||
.iter_mut()
|
||||
.for_each(move |vec| *vec *= s0 / s1);
|
||||
self.concat(bezier.knot_translate(-x));
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParameterDivision1D for NurbsCurve<V>
|
||||
where V::Point: MetricSpace<Metric = f64> + HashGen<f64>
|
||||
{
|
||||
type Point = V::Point;
|
||||
#[inline(always)]
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<V::Point>) {
|
||||
algo::curve::parameter_division(self, range, tol)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> SearchNearestParameter<D1> for NurbsCurve<V>
|
||||
where
|
||||
V::Point: MetricSpace<Metric = f64>,
|
||||
<V::Point as EuclideanSpace>::Diff: InnerSpace + Tolerance,
|
||||
{
|
||||
type Point = V::Point;
|
||||
/// Searches the parameter `t` which minimize |self(t) - point| by Newton's method with initial guess `hint`.
|
||||
/// Returns `None` if the number of attempts exceeds `trial` i.e. if `trial == 0`, then the trial is only one time.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
///
|
||||
/// // Defines the half unit circle in x > 0.
|
||||
/// let knot_vec = KnotVec::bezier_knot(2);
|
||||
/// let ctrl_pts = vec![Vector3::new(0.0, -1.0, 1.0), Vector3::new(1.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 1.0)];
|
||||
/// let curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
///
|
||||
/// // search rational nearest parameter
|
||||
/// let pt = Point2::new(1.0, 2.0);
|
||||
/// let hint = 0.8;
|
||||
/// let t = curve.search_nearest_parameter(pt, Some(hint), 100).unwrap();
|
||||
///
|
||||
/// // check the answer
|
||||
/// let res = curve.subs(t);
|
||||
/// let ans = Point2::from_vec(pt.to_vec().normalize());
|
||||
/// assert_near!(res, ans);
|
||||
/// ```
|
||||
/// # Remarks
|
||||
/// It may converge to a local solution depending on the hint.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
///
|
||||
/// // Same curve and point as above example
|
||||
/// let knot_vec = KnotVec::bezier_knot(2);
|
||||
/// let ctrl_pts = vec![Vector3::new(0.0, -1.0, 1.0), Vector3::new(1.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 1.0)];
|
||||
/// let curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
/// let pt = Point2::new(1.0, 2.0);
|
||||
///
|
||||
/// // another hint
|
||||
/// let hint = 0.5;
|
||||
///
|
||||
/// // Newton's method is vibration divergent.
|
||||
/// assert!(curve.search_nearest_parameter(pt, Some(hint), 100).is_none());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: V::Point,
|
||||
hint: H,
|
||||
trial: usize,
|
||||
) -> Option<f64> {
|
||||
let hint = match hint.into() {
|
||||
SPHint1D::Parameter(hint) => hint,
|
||||
SPHint1D::Range(x, y) => {
|
||||
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint1D::None => {
|
||||
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::curve::search_nearest_parameter(self, point, hint, trial)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> SearchParameter<D1> for NurbsCurve<V>
|
||||
where
|
||||
V::Point: MetricSpace<Metric = f64>,
|
||||
<V::Point as EuclideanSpace>::Diff: InnerSpace + Tolerance,
|
||||
{
|
||||
type Point = V::Point;
|
||||
#[inline(always)]
|
||||
fn search_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: V::Point,
|
||||
hint: H,
|
||||
trial: usize,
|
||||
) -> Option<f64> {
|
||||
let hint = match hint.into() {
|
||||
SPHint1D::Parameter(hint) => hint,
|
||||
SPHint1D::Range(x, y) => {
|
||||
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint1D::None => {
|
||||
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::curve::search_parameter(self, point, hint, trial)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> NurbsCurve<V>
|
||||
where V::Point: Bounded<Scalar = f64>
|
||||
{
|
||||
/// Returns the bounding box including all control points.
|
||||
#[inline(always)]
|
||||
pub fn roughly_bounding_box(&self) -> BoundingBox<V::Point> {
|
||||
self.0.control_points.iter().map(|p| p.to_point()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParametricCurve for NurbsCurve<V> {
|
||||
type Point = V::Point;
|
||||
type Vector = <V::Point as EuclideanSpace>::Diff;
|
||||
#[inline(always)]
|
||||
fn subs(&self, t: f64) -> Self::Point { self.0.subs(t).to_point() }
|
||||
#[inline(always)]
|
||||
fn der(&self, t: f64) -> Self::Vector {
|
||||
let pt = self.0.subs(t);
|
||||
let der = self.0.der(t);
|
||||
pt.rat_der(der)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn der2(&self, t: f64) -> Self::Vector {
|
||||
let pt = self.0.subs(t);
|
||||
let der = self.0.der(t);
|
||||
let der2 = self.0.der2(t);
|
||||
pt.rat_der2(der, der2)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> ParameterRange {
|
||||
(
|
||||
Bound::Included(self.0.knot_vec[0]),
|
||||
Bound::Included(self.0.knot_vec[self.0.knot_vec.len() - 1]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> BoundedCurve for NurbsCurve<V> {}
|
||||
|
||||
impl<V: Clone> Invertible for NurbsCurve<V> {
|
||||
#[inline(always)]
|
||||
fn invert(&mut self) { self.invert(); }
|
||||
#[inline(always)]
|
||||
fn inverse(&self) -> Self {
|
||||
let mut curve = self.0.clone();
|
||||
curve.invert();
|
||||
NurbsCurve(curve)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, V: Copy> Transformed<M> for NurbsCurve<V>
|
||||
where M: Copy + std::ops::Mul<V, Output = V>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn transform_by(&mut self, trans: M) {
|
||||
self.0
|
||||
.control_points
|
||||
.iter_mut()
|
||||
.for_each(move |v| *v = trans * *v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> From<BSplineCurve<V::Point>> for NurbsCurve<V> {
|
||||
fn from(bspcurve: BSplineCurve<V::Point>) -> NurbsCurve<V> {
|
||||
NurbsCurve::new(BSplineCurve::new_unchecked(
|
||||
bspcurve.knot_vec,
|
||||
bspcurve
|
||||
.control_points
|
||||
.into_iter()
|
||||
.map(V::from_point)
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parameter_division() {
|
||||
let knot_vec = KnotVec::uniform_knot(2, 3);
|
||||
let ctrl_pts = vec![
|
||||
Vector4::new(0.0, 0.0, 0.0, 1.0),
|
||||
Vector4::new(2.0, 0.0, 0.0, 2.0),
|
||||
Vector4::new(0.0, 3.0, 0.0, 3.0),
|
||||
Vector4::new(0.0, 0.0, 2.0, 2.0),
|
||||
Vector4::new(1.0, 1.0, 1.0, 1.0),
|
||||
];
|
||||
let curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
let tol = 0.01;
|
||||
let (div, pts) = curve.parameter_division(curve.range_tuple(), tol * 0.5);
|
||||
let knot_vec = curve.knot_vec();
|
||||
assert_eq!(knot_vec[0], div[0]);
|
||||
assert_eq!(knot_vec.range_length(), div.last().unwrap() - div[0]);
|
||||
for i in 1..div.len() {
|
||||
let pt0 = curve.subs(div[i - 1]);
|
||||
assert_eq!(pt0, pts[i - 1]);
|
||||
let pt1 = curve.subs(div[i]);
|
||||
assert_eq!(pt1, pts[i]);
|
||||
let value_middle = pt0.midpoint(pt1);
|
||||
let param_middle = curve.subs((div[i - 1] + div[i]) / 2.0);
|
||||
let dist = value_middle.distance(param_middle);
|
||||
assert!(dist < tol, "large distance: {dist}");
|
||||
}
|
||||
}
|
||||
+920
@@ -0,0 +1,920 @@
|
||||
use super::*;
|
||||
|
||||
impl<V> NurbsSurface<V> {
|
||||
/// constructor
|
||||
#[inline(always)]
|
||||
pub const fn new(bspsurface: BSplineSurface<V>) -> Self { NurbsSurface(bspsurface) }
|
||||
|
||||
/// Returns the nurbs surface before rationalized
|
||||
#[inline(always)]
|
||||
pub const fn non_rationalized(&self) -> &BSplineSurface<V> { &self.0 }
|
||||
/// Returns the nurbs surface before rationalized
|
||||
#[inline(always)]
|
||||
pub fn non_rationalized_mut(&mut self) -> &mut BSplineSurface<V> { &mut self.0 }
|
||||
|
||||
/// Returns the nurbs surface before rationalized
|
||||
#[inline(always)]
|
||||
pub fn into_non_rationalized(self) -> BSplineSurface<V> { self.0 }
|
||||
|
||||
/// Returns the reference of the knot vectors
|
||||
#[inline(always)]
|
||||
pub const fn knot_vecs(&self) -> &(KnotVec, KnotVec) { &self.0.knot_vecs }
|
||||
|
||||
/// Returns the u knot vector.
|
||||
#[inline(always)]
|
||||
pub const fn uknot_vec(&self) -> &KnotVec { &self.0.knot_vecs.0 }
|
||||
/// Returns the v knot vector.
|
||||
#[inline(always)]
|
||||
pub const fn vknot_vec(&self) -> &KnotVec { &self.0.knot_vecs.1 }
|
||||
|
||||
/// Returns the `idx`th u knot.
|
||||
#[inline(always)]
|
||||
pub fn uknot(&self, idx: usize) -> f64 { self.0.knot_vecs.0[idx] }
|
||||
/// returns the `idx`th v knot.
|
||||
#[inline(always)]
|
||||
pub fn vknot(&self, idx: usize) -> f64 { self.0.knot_vecs.1[idx] }
|
||||
|
||||
/// Returns the reference of the vector of the control points
|
||||
#[inline(always)]
|
||||
pub const fn control_points(&self) -> &Vec<Vec<V>> { &self.0.control_points }
|
||||
|
||||
/// Returns the reference of the control point corresponding to the index `(idx0, idx1)`.
|
||||
#[inline(always)]
|
||||
pub fn control_point(&self, idx0: usize, idx1: usize) -> &V {
|
||||
&self.0.control_points[idx0][idx1]
|
||||
}
|
||||
|
||||
/// Apply the given transformation to all control points.
|
||||
#[inline(always)]
|
||||
pub fn transform_control_points<F: FnMut(&mut V)>(&mut self, f: F) {
|
||||
self.0.transform_control_points(f)
|
||||
}
|
||||
|
||||
/// Returns the iterator over the control points in the `column_idx`th row.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::bezier_knot(1);
|
||||
/// let vknot_vec = KnotVec::bezier_knot(2);
|
||||
/// let knot_vecs = (uknot_vec, vknot_vec);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
|
||||
/// let mut iter = bspsurface.ctrl_pts_row_iter(1);
|
||||
/// assert_eq!(iter.next(), Some(&Vector3::new(1.0, 0.0, 1.0)));
|
||||
/// assert_eq!(iter.next(), Some(&Vector3::new(1.0, 1.0, 1.0)));
|
||||
/// assert_eq!(iter.next(), None);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn ctrl_pts_row_iter(
|
||||
&self,
|
||||
column_idx: usize,
|
||||
) -> impl ExactSizeIterator<Item = &V> + std::iter::FusedIterator<Item = &V> {
|
||||
self.0.ctrl_pts_row_iter(column_idx)
|
||||
}
|
||||
|
||||
/// Returns the iterator over the control points in the `row_idx`th row.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::bezier_knot(1);
|
||||
/// let vknot_vec = KnotVec::bezier_knot(2);
|
||||
/// let knot_vecs = (uknot_vec, vknot_vec);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
|
||||
/// let mut iter = bspsurface.ctrl_pts_column_iter(1);
|
||||
/// assert_eq!(iter.next(), Some(&Vector3::new(0.0, 1.0, 0.0)));
|
||||
/// assert_eq!(iter.next(), Some(&Vector3::new(1.0, 1.0, 1.0)));
|
||||
/// assert_eq!(iter.next(), Some(&Vector3::new(2.0, 1.0, 2.0)));
|
||||
/// assert_eq!(iter.next(), None);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn ctrl_pts_column_iter(&self, row_idx: usize) -> std::slice::Iter<'_, V> {
|
||||
self.0.control_points[row_idx].iter()
|
||||
}
|
||||
|
||||
/// Returns the mutable reference of the control point corresponding to index `(idx0, idx1)`.
|
||||
#[inline(always)]
|
||||
pub fn control_point_mut(&mut self, idx0: usize, idx1: usize) -> &mut V {
|
||||
&mut self.0.control_points[idx0][idx1]
|
||||
}
|
||||
|
||||
/// Returns the iterator on all control points
|
||||
#[inline(always)]
|
||||
pub fn control_points_mut(&mut self) -> impl Iterator<Item = &mut V> {
|
||||
self.0.control_points.iter_mut().flatten()
|
||||
}
|
||||
/// Returns the degrees of B-spline surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let vknot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
|
||||
/// let knot_vecs = (uknot_vec, vknot_vec);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
|
||||
/// assert_eq!(bspsurface.udegree(), 1);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn udegree(&self) -> usize { self.0.udegree() }
|
||||
|
||||
/// Returns the degrees of B-spline surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let vknot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
|
||||
/// let knot_vecs = (uknot_vec, vknot_vec);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
|
||||
/// assert_eq!(bspsurface.vdegree(), 2);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn vdegree(&self) -> usize { self.0.vdegree() }
|
||||
|
||||
/// Returns the degrees of B-spline surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let vknot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
|
||||
/// let knot_vecs = (uknot_vec, vknot_vec);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
|
||||
/// assert_eq!(bspsurface.degrees(), (1, 2));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn degrees(&self) -> (usize, usize) { (self.udegree(), self.vdegree()) }
|
||||
/// Returns whether the knot vectors are clamped or not.
|
||||
#[inline(always)]
|
||||
pub fn is_clamped(&self) -> bool { self.0.is_clamped() }
|
||||
/// Swaps two parameters.
|
||||
pub fn swap_axes(&mut self) -> &mut Self
|
||||
where V: Clone {
|
||||
self.0.swap_axes();
|
||||
self
|
||||
}
|
||||
/// The range of the parameter of the surface.
|
||||
#[inline(always)]
|
||||
pub fn parameter_range(&self) -> (ParameterRange, ParameterRange) { self.0.parameter_range() }
|
||||
/// Creates the curve whose control points are the `idx`th column control points of `self`.
|
||||
#[inline(always)]
|
||||
pub fn column_curve(&self, row_idx: usize) -> NurbsCurve<V>
|
||||
where V: Clone {
|
||||
NurbsCurve(self.0.column_curve(row_idx))
|
||||
}
|
||||
/// Creates the column sectional curve.
|
||||
#[inline(always)]
|
||||
pub fn row_curve(&self, column_idx: usize) -> NurbsCurve<V>
|
||||
where V: Clone {
|
||||
NurbsCurve(self.0.row_curve(column_idx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> NurbsSurface<V> {
|
||||
/// Constructs a rationalization surface from the non-rationalized surface and weights.
|
||||
/// # Failures
|
||||
/// the length of `surface.control_points()` and `weights` must be the same.
|
||||
#[inline(always)]
|
||||
pub fn try_from_bspline_and_weights(
|
||||
surface: BSplineSurface<V::Point>,
|
||||
weights: Vec<Vec<f64>>,
|
||||
) -> Result<Self> {
|
||||
let BSplineSurface {
|
||||
knot_vecs,
|
||||
control_points,
|
||||
} = surface;
|
||||
if control_points.len() != weights.len() {
|
||||
return Err(Error::DifferentLength);
|
||||
}
|
||||
let control_points = control_points
|
||||
.into_iter()
|
||||
.zip(weights)
|
||||
.map(|(control_points, weights)| {
|
||||
if control_points.len() != weights.len() {
|
||||
return Err(Error::DifferentLength);
|
||||
}
|
||||
Ok(control_points
|
||||
.into_iter()
|
||||
.zip(weights)
|
||||
.map(|(pt, w)| V::from_point_weight(pt, w))
|
||||
.collect::<Vec<_>>())
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(Self(BSplineSurface::new_unchecked(
|
||||
knot_vecs,
|
||||
control_points,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsSurface<V> {
|
||||
/// Substitutes to a NURBS surface.
|
||||
#[inline(always)]
|
||||
pub fn subs(&self, u: f64, v: f64) -> V::Point { self.0.subs(u, v).to_point() }
|
||||
/// Substitutes derived NURBS surface by the first parameter `u`.
|
||||
#[inline(always)]
|
||||
pub fn uder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
|
||||
let pt = self.0.subs(u, v);
|
||||
let ud = self.0.uder(u, v);
|
||||
pt.rat_der(ud)
|
||||
}
|
||||
/// Substitutes derived NURBS surface by the first parameter `v`.
|
||||
#[inline(always)]
|
||||
pub fn vder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
|
||||
let pt = self.0.subs(u, v);
|
||||
let vd = self.0.vder(u, v);
|
||||
pt.rat_der(vd)
|
||||
}
|
||||
/// Substitutes 2nd-ord derived NURBS surface by the first parameter `u`.
|
||||
#[inline(always)]
|
||||
pub fn uuder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
|
||||
let pt = self.0.subs(u, v);
|
||||
let ud = self.0.uder(u, v);
|
||||
let uud = self.0.uuder(u, v);
|
||||
pt.rat_der2(ud, uud)
|
||||
}
|
||||
/// Substitutes 2nd-ord derived NURBS surface by the first parameter `v`.
|
||||
#[inline(always)]
|
||||
pub fn vvder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
|
||||
let pt = self.0.subs(u, v);
|
||||
let vd = self.0.vder(u, v);
|
||||
let vvd = self.0.vvder(u, v);
|
||||
pt.rat_der2(vd, vvd)
|
||||
}
|
||||
/// Substitutes 2nd-ord derived NURBS surface by both parameter `u, v`.
|
||||
#[inline(always)]
|
||||
pub fn uvder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
|
||||
let pt = self.0.subs(u, v);
|
||||
let ud = self.0.uder(u, v);
|
||||
let vd = self.0.vder(u, v);
|
||||
let uvd = self.0.uvder(u, v);
|
||||
pt.rat_cross_der(ud, vd, uvd)
|
||||
}
|
||||
/// Returns the closure of substitution.
|
||||
#[inline(always)]
|
||||
pub fn get_closure(&self) -> impl Fn(f64, f64) -> V::Point + '_ { move |u, v| self.subs(u, v) }
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsSurface<V>
|
||||
where V::Point: Tolerance
|
||||
{
|
||||
/// Returns whether constant curve or not, i.e. all control points are same or not.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::bezier_knot(1);
|
||||
/// let vknot_vec = KnotVec::bezier_knot(2);
|
||||
/// let pt = Vector3::new(1.0, 2.0, 1.0);
|
||||
/// // allows differences upto scalars
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![pt.clone(), pt.clone() * 2.0, pt.clone() * 3.0],
|
||||
/// vec![pt.clone() * 0.5, pt.clone() * 0.25, pt.clone() * 0.125],
|
||||
/// ];
|
||||
/// let mut surface = NurbsSurface::new(BSplineSurface::new((uknot_vec, vknot_vec), ctrl_pts));
|
||||
/// assert!(surface.is_const());
|
||||
///
|
||||
/// *surface.control_point_mut(1, 2) = Vector3::new(2.0, 3.0, 1.0);
|
||||
/// assert!(!surface.is_const());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn is_const(&self) -> bool {
|
||||
let pt = self.0.control_points[0][0].to_point();
|
||||
for vec in self.0.control_points.iter().flat_map(|pts| pts.iter()) {
|
||||
if !vec.to_point().near(&pt) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
/// Determines whether `self` and `other` is near as the B-spline rational surfaces or not.
|
||||
///
|
||||
/// Divides each knot domain into the number of degree equal parts,
|
||||
/// and check `|self(u, v) - other(u, v)| < TOLERANCE` for each end points `(u, v)`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.5, -1.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 1.0), Vector3::new(0.5, 1.0, 1.0), Vector3::new(1.0, 1.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 2.0, 1.0), Vector3::new(0.5, 2.0, 3.0), Vector3::new(1.0, 2.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(0.5, 3.5, 2.0), Vector3::new(1.0, 3.0, 1.0)],
|
||||
/// ];
|
||||
/// let surface0 = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
|
||||
/// let mut surface1 = surface0.clone();
|
||||
/// assert!(surface0.near_as_surface(&surface1));
|
||||
///
|
||||
/// *surface1.control_point_mut(1, 1) = Vector3::new(0.5, 1.0, 0.9);
|
||||
/// assert!(!surface0.near_as_surface(&surface1));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn near_as_surface(&self, other: &Self) -> bool {
|
||||
self.0
|
||||
.sub_near_as_surface(&other.0, 2, move |x, y| x.to_point().near(&y.to_point()))
|
||||
}
|
||||
/// Determines whether `self` and `other` is near in square order as the B-spline rational
|
||||
/// surfaces or not.
|
||||
///
|
||||
/// Divides each knot domain into the number of degree equal parts,
|
||||
/// and check `|self(u, v) - other(u, v)| < TOLERANCE` for each end points `(u, v)`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let eps = TOLERANCE;
|
||||
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.5, -1.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 1.0), Vector3::new(0.5, 1.0, 1.0), Vector3::new(1.0, 1.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 2.0, 1.0), Vector3::new(0.5, 2.0, 3.0), Vector3::new(1.0, 2.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(0.5, 3.5, 2.0), Vector3::new(1.0, 3.0, 1.0)],
|
||||
/// ];
|
||||
/// let surface0 = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
|
||||
/// let mut surface1 = surface0.clone();
|
||||
/// assert!(surface0.near_as_surface(&surface1));
|
||||
///
|
||||
/// *surface1.control_point_mut(1, 1) = Vector3::new(0.5, 1.0, 1.0 - eps);
|
||||
/// assert!(surface0.near_as_surface(&surface1));
|
||||
/// assert!(!surface0.near2_as_surface(&surface1));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn near2_as_surface(&self, other: &Self) -> bool {
|
||||
self.0
|
||||
.sub_near_as_surface(&other.0, 2, move |x, y| x.to_point().near2(&y.to_point()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> NurbsSurface<V> {
|
||||
/// Adds a knot `x` of the first parameter `u`, and do not change `self` as a surface.
|
||||
#[inline(always)]
|
||||
pub fn add_uknot(&mut self, x: f64) -> &mut Self {
|
||||
self.0.add_uknot(x);
|
||||
self
|
||||
}
|
||||
/// Adds a knot `x` of the first parameter `u`, and do not change `self` as a surface.
|
||||
#[inline(always)]
|
||||
pub fn add_vknot(&mut self, x: f64) -> &mut Self {
|
||||
self.0.add_vknot(x);
|
||||
self
|
||||
}
|
||||
/// Removes the uknot corresponding to the indice `idx`, and do not change `self` as a curve.
|
||||
/// If the knot cannot be removed, returns [`Error::CannotRemoveKnot`].
|
||||
#[inline(always)]
|
||||
pub fn try_remove_uknot(&mut self, idx: usize) -> Result<&mut Self> {
|
||||
match self.0.try_remove_uknot(idx) {
|
||||
Ok(_) => Ok(self),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
/// Removes the uknot corresponding to the indices `idx`, and do not change `self` as a curve.
|
||||
/// If cannot remove the knot, do not change `self` and return `self`.
|
||||
#[inline(always)]
|
||||
pub fn remove_uknot(&mut self, idx: usize) -> &mut Self {
|
||||
self.0.remove_uknot(idx);
|
||||
self
|
||||
}
|
||||
/// Removes the uknot corresponding to the indice `idx`, and do not change `self` as a curve.
|
||||
/// If the knot cannot be removed, returns [`Error::CannotRemoveKnot`].
|
||||
#[inline(always)]
|
||||
pub fn try_remove_vknot(&mut self, idx: usize) -> Result<&mut Self> {
|
||||
match self.0.try_remove_vknot(idx) {
|
||||
Ok(_) => Ok(self),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
/// Removes the uknot corresponding to the indices `idx`, and do not change `self` as a curve.
|
||||
/// If cannot remove the knot, do not change `self` and return `self`.
|
||||
#[inline(always)]
|
||||
pub fn remove_vknot(&mut self, idx: usize) -> &mut Self {
|
||||
self.0.remove_vknot(idx);
|
||||
self
|
||||
}
|
||||
/// Elevates the udegree.
|
||||
#[inline(always)]
|
||||
pub fn elevate_udegree(&mut self) -> &mut Self {
|
||||
self.0.elevate_udegree();
|
||||
self
|
||||
}
|
||||
/// Elevates the vdegree.
|
||||
#[inline(always)]
|
||||
pub fn elevate_vdegree(&mut self) -> &mut Self {
|
||||
self.0.elevate_vdegree();
|
||||
self
|
||||
}
|
||||
/// Aligns the udegree with the same degrees.
|
||||
#[inline(always)]
|
||||
pub fn syncro_uvdegrees(&mut self) -> &mut Self {
|
||||
self.0.syncro_uvdegrees();
|
||||
self
|
||||
}
|
||||
/// Makes the uknot vector and the vknot vector the same knot vector.
|
||||
#[inline(always)]
|
||||
pub fn syncro_uvknots(&mut self) -> &mut Self {
|
||||
self.0.syncro_uvknots();
|
||||
self
|
||||
}
|
||||
|
||||
/// Cuts the surface into two surfaces at the parameter `u`
|
||||
#[inline(always)]
|
||||
pub fn ucut(&mut self, u: f64) -> Self { Self::new(self.0.ucut(u)) }
|
||||
|
||||
/// Cuts the surface into two surfaces at the parameter `v`
|
||||
#[inline(always)]
|
||||
pub fn vcut(&mut self, v: f64) -> Self { Self::new(self.0.vcut(v)) }
|
||||
|
||||
/// Normalizes the knot vectors
|
||||
#[inline(always)]
|
||||
pub fn knot_normalize(&mut self) -> &mut Self {
|
||||
self.0.knot_normalize();
|
||||
self
|
||||
}
|
||||
/// Translates the knot vectors.
|
||||
#[inline(always)]
|
||||
pub fn knot_translate(&mut self, x: f64, y: f64) -> &mut Self {
|
||||
self.0.knot_translate(x, y);
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes knots in order from the back
|
||||
#[inline(always)]
|
||||
pub fn optimize(&mut self) -> &mut Self {
|
||||
self.0.optimize();
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the boundary by four splitted curves.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.5, -1.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 2.0), Vector3::new(0.5, 1.0, 3.0), Vector3::new(1.0, 1.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 2.0, 2.0), Vector3::new(0.5, 2.0, 3.0), Vector3::new(1.0, 2.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(0.5, 3.5, 2.0), Vector3::new(1.0, 3.0, 1.0)],
|
||||
/// ];
|
||||
/// let bspsurface = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
|
||||
/// let curves = bspsurface.splitted_boundary();
|
||||
/// assert_eq!(
|
||||
/// curves[0].control_points(),
|
||||
/// &vec![
|
||||
/// Vector3::new(0.0, 0.0, 1.0),
|
||||
/// Vector3::new(0.0, 1.0, 2.0),
|
||||
/// Vector3::new(0.0, 2.0, 2.0),
|
||||
/// Vector3::new(0.0, 3.0, 1.0),
|
||||
/// ],
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// curves[1].control_points(),
|
||||
/// &vec![
|
||||
/// Vector3::new(0.0, 3.0, 1.0),
|
||||
/// Vector3::new(0.5, 3.5, 2.0),
|
||||
/// Vector3::new(1.0, 3.0, 1.0),
|
||||
/// ],
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// curves[2].control_points(),
|
||||
/// &vec![
|
||||
/// Vector3::new(1.0, 3.0, 1.0),
|
||||
/// Vector3::new(1.0, 2.0, 2.0),
|
||||
/// Vector3::new(1.0, 1.0, 2.0),
|
||||
/// Vector3::new(1.0, 0.0, 1.0),
|
||||
/// ],
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// curves[3].control_points(),
|
||||
/// &vec![
|
||||
/// Vector3::new(1.0, 0.0, 1.0),
|
||||
/// Vector3::new(0.5, -1.0, 2.0),
|
||||
/// Vector3::new(0.0, 0.0, 1.0),
|
||||
/// ],
|
||||
/// );
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn splitted_boundary(&self) -> [NurbsCurve<V>; 4] {
|
||||
TryFrom::try_from(
|
||||
self.0
|
||||
.splitted_boundary()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(NurbsCurve::new)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Extracts the boundary of surface
|
||||
#[inline(always)]
|
||||
pub fn boundary(&self) -> NurbsCurve<V> { NurbsCurve::new(self.0.boundary()) }
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> SearchNearestParameter<D2> for NurbsSurface<V>
|
||||
where
|
||||
Self: ParametricSurface<Point = V::Point, Vector = <V::Point as EuclideanSpace>::Diff>,
|
||||
V::Point: EuclideanSpace<Scalar = f64> + MetricSpace<Metric = f64>,
|
||||
<V::Point as EuclideanSpace>::Diff: InnerSpace<Scalar = f64> + Tolerance,
|
||||
{
|
||||
type Point = V::Point;
|
||||
/// Searches the parameter `(u, v)` which minimize `|self(u, v) - point|` by Newton's method
|
||||
/// with initial guess `(u0, v0)`. If the repeated trial does not converge, then returns `None`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(1.0, -2.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 2.0, 2.0), Vector3::new(2.0, 4.0, 4.0), Vector3::new(2.0, 2.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 4.0, 2.0), Vector3::new(2.0, 8.0, 4.0), Vector3::new(2.0, 4.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(1.0, 7.0, 2.0), Vector3::new(1.0, 3.0, 1.0)],
|
||||
/// ];
|
||||
/// let surface = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
|
||||
/// let pt = surface.subs(0.3, 0.7);
|
||||
/// let (u, v) = surface.search_nearest_parameter(pt, Some((0.5, 0.5)), 100).unwrap();
|
||||
/// assert!(u.near(&0.3) && v.near(&0.7));
|
||||
/// ```
|
||||
/// # Remarks
|
||||
/// It may converge to a local solution depending on the hint.
|
||||
/// cf. [`BSplineCurve::search_rational_nearest_parameter`](struct.BSplineCurve.html#method.search_rational_nearest_parameter)
|
||||
#[inline(always)]
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: V::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let hint = match hint.into() {
|
||||
SPHint2D::Parameter(x, y) => (x, y),
|
||||
SPHint2D::Range(range0, range1) => {
|
||||
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint2D::None => {
|
||||
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::surface::search_nearest_parameter(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> NurbsSurface<V>
|
||||
where V::Point: Bounded<Scalar = f64>
|
||||
{
|
||||
/// Returns the bounding box including all control points.
|
||||
#[inline(always)]
|
||||
pub fn roughly_bounding_box(&self) -> BoundingBox<V::Point> {
|
||||
self.0
|
||||
.control_points
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|pt| pt.to_point())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D2> for NurbsSurface<Vector3> {
|
||||
type Point = Point2;
|
||||
/// Search the parameter `(u, v)` such that `self.subs(u, v).rational_projection()` is near `pt`.
|
||||
/// If cannot find, then return `None`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::uniform_knot(2, 2);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.1, 0.0, 0.5), Vector3::new(0.5, 0.0, 0.3), Vector3::new(1.0, 0.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 0.1, 0.1), Vector3::new(0.2, 0.2, 0.1), Vector3::new(0.4, 0.3, 0.4), Vector3::new(1.0, 0.3, 0.7)],
|
||||
/// vec![Vector3::new(0.0, 0.5, 0.4), Vector3::new(0.3, 0.6, 0.5), Vector3::new(0.6, 0.4, 1.0), Vector3::new(1.0, 0.5, 0.4)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 1.0), Vector3::new(0.1, 1.0, 1.0), Vector3::new(0.5, 1.0, 0.5), Vector3::new(1.0, 1.0, 0.3)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts);
|
||||
/// let surface = NurbsSurface::new(bspsurface);
|
||||
///
|
||||
/// let pt = surface.subs(0.3, 0.7);
|
||||
/// let (u, v) = surface.search_parameter(pt, Some((0.5, 0.5)), 100).unwrap();
|
||||
/// assert_near!(surface.subs(u, v), pt);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point2,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let hint = match hint.into() {
|
||||
SPHint2D::Parameter(x, y) => (x, y),
|
||||
SPHint2D::Range(range0, range1) => {
|
||||
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint2D::None => {
|
||||
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::surface::search_parameter2d(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Clone> Invertible for NurbsSurface<V> {
|
||||
#[inline(always)]
|
||||
fn invert(&mut self) { self.swap_axes(); }
|
||||
#[inline(always)]
|
||||
fn inverse(&self) -> Self {
|
||||
let mut surface = self.clone();
|
||||
surface.swap_axes();
|
||||
surface
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParametricSurface for NurbsSurface<V> {
|
||||
type Point = V::Point;
|
||||
type Vector = <V::Point as EuclideanSpace>::Diff;
|
||||
#[inline(always)]
|
||||
fn subs(&self, u: f64, v: f64) -> Self::Point { self.subs(u, v) }
|
||||
#[inline(always)]
|
||||
fn uder(&self, u: f64, v: f64) -> Self::Vector { self.uder(u, v) }
|
||||
#[inline(always)]
|
||||
fn vder(&self, u: f64, v: f64) -> Self::Vector { self.vder(u, v) }
|
||||
#[inline(always)]
|
||||
fn uuder(&self, u: f64, v: f64) -> Self::Vector { self.uuder(u, v) }
|
||||
#[inline(always)]
|
||||
fn uvder(&self, u: f64, v: f64) -> Self::Vector { self.uvder(u, v) }
|
||||
#[inline(always)]
|
||||
fn vvder(&self, u: f64, v: f64) -> Self::Vector { self.vvder(u, v) }
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) { self.parameter_range() }
|
||||
}
|
||||
|
||||
impl ParametricSurface3D for NurbsSurface<Vector4> {
|
||||
#[inline(always)]
|
||||
fn normal(&self, u: f64, v: f64) -> Vector3 {
|
||||
let pt = self.0.subs(u, v);
|
||||
let ud = self.0.uder(u, v);
|
||||
let vd = self.0.vder(u, v);
|
||||
pt.rat_der(ud).cross(pt.rat_der(vd)).normalize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParameterDivision2D for NurbsSurface<V>
|
||||
where V::Point: MetricSpace<Metric = f64> + HashGen<f64>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parameter_division(
|
||||
&self,
|
||||
range: ((f64, f64), (f64, f64)),
|
||||
tol: f64,
|
||||
) -> (Vec<f64>, Vec<f64>) {
|
||||
algo::surface::parameter_division(self, range, tol)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> BoundedSurface for NurbsSurface<V> where Self: ParametricSurface {}
|
||||
|
||||
impl IncludeCurve<NurbsCurve<Vector3>> for NurbsSurface<Vector3> {
|
||||
#[inline(always)]
|
||||
fn include(&self, curve: &NurbsCurve<Vector3>) -> bool {
|
||||
let pt = curve.subs(curve.knot_vec()[0]);
|
||||
let mut hint = match self.search_parameter(pt, None, INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
let uknot_vec = self.uknot_vec();
|
||||
let vknot_vec = self.vknot_vec();
|
||||
let degree = curve.degree() * 6;
|
||||
let (knots, _) = curve.knot_vec().to_single_multi();
|
||||
for i in 1..knots.len() {
|
||||
for j in 1..=degree {
|
||||
let p = j as f64 / degree as f64;
|
||||
let t = knots[i - 1] * (1.0 - p) + knots[i] * p;
|
||||
let pt = curve.subs(t);
|
||||
hint = match self.search_parameter(pt, Some(hint), INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
if !self.subs(hint.0, hint.1).near(&pt)
|
||||
|| hint.0 < uknot_vec[0] - TOLERANCE
|
||||
|| hint.0 - uknot_vec[0] > uknot_vec.range_length() + TOLERANCE
|
||||
|| hint.1 < vknot_vec[0] - TOLERANCE
|
||||
|| hint.1 - vknot_vec[0] > vknot_vec.range_length() + TOLERANCE
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<BSplineCurve<Point3>> for NurbsSurface<Vector4> {
|
||||
#[inline(always)]
|
||||
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
|
||||
let pt = curve.front();
|
||||
let mut hint = match self.search_parameter(pt, None, INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
let uknot_vec = self.uknot_vec();
|
||||
let vknot_vec = self.vknot_vec();
|
||||
let degree = curve.degree() * 6;
|
||||
let (knots, _) = curve.knot_vec().to_single_multi();
|
||||
for i in 1..knots.len() {
|
||||
for j in 1..=degree {
|
||||
let p = j as f64 / degree as f64;
|
||||
let t = knots[i - 1] * (1.0 - p) + knots[i] * p;
|
||||
let pt = curve.subs(t);
|
||||
hint = match self.search_parameter(pt, Some(hint), INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
if !self.subs(hint.0, hint.1).near(&pt)
|
||||
|| hint.0 < uknot_vec[0] - TOLERANCE
|
||||
|| hint.0 - uknot_vec[0] > uknot_vec.range_length() + TOLERANCE
|
||||
|| hint.1 < vknot_vec[0] - TOLERANCE
|
||||
|| hint.1 - vknot_vec[0] > vknot_vec.range_length() + TOLERANCE
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<NurbsCurve<Vector4>> for NurbsSurface<Vector4> {
|
||||
#[inline(always)]
|
||||
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
|
||||
let pt = curve.front();
|
||||
let mut hint = match self.search_parameter(pt, None, INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
let uknot_vec = self.uknot_vec();
|
||||
let vknot_vec = self.vknot_vec();
|
||||
let degree = curve.degree() * 6;
|
||||
let (knots, _) = curve.knot_vec().to_single_multi();
|
||||
for i in 1..knots.len() {
|
||||
for j in 1..=degree {
|
||||
let p = j as f64 / degree as f64;
|
||||
let t = knots[i - 1] * (1.0 - p) + knots[i] * p;
|
||||
let pt = curve.subs(t);
|
||||
hint = match self.search_parameter(pt, Some(hint), INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
if !self.subs(hint.0, hint.1).near(&pt)
|
||||
|| hint.0 < uknot_vec[0] - TOLERANCE
|
||||
|| hint.0 - uknot_vec[0] > uknot_vec.range_length() + TOLERANCE
|
||||
|| hint.1 < vknot_vec[0] - TOLERANCE
|
||||
|| hint.1 - vknot_vec[0] > vknot_vec.range_length() + TOLERANCE
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, V: Copy> Transformed<M> for NurbsSurface<V>
|
||||
where M: Copy + std::ops::Mul<V, Output = V>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn transform_by(&mut self, trans: M) {
|
||||
self.0
|
||||
.control_points
|
||||
.iter_mut()
|
||||
.flatten()
|
||||
.for_each(move |v| *v = trans * *v)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D2> for NurbsSurface<Vector4> {
|
||||
type Point = Point3;
|
||||
/// Search the parameter `(u, v)` such that `self.subs(u, v).rational_projection()` is near `pt`.
|
||||
/// If cannot find, then return `None`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::uniform_knot(2, 2);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector4::new(0.0, 0.0, 0.0, 1.0), Vector4::new(0.1, 0.0, 0.5, 0.4), Vector4::new(1.0, 0.0, 0.6, 2.0), Vector4::new(0.4, 0.0, 0.4, 0.4)],
|
||||
/// vec![Vector4::new(0.0, 0.2, 0.2, 2.0), Vector4::new(0.24, 0.24, 0.1, 1.2), Vector4::new(2.4, 1.8, 2.4, 0.6), Vector4::new(1.4, 0.42, 0.98, 1.4)],
|
||||
/// vec![Vector4::new(0.0, 1.5, 1.2, 3.0), Vector4::new(1.02, 2.04, 1.7, 3.4), Vector4::new(0.42, 0.28, 0.7, 0.7), Vector4::new(0.6, 0.3, 0.0, 0.6)],
|
||||
/// vec![Vector4::new(0.0, 1.0, 1.0, 1.0), Vector4::new(0.2, 2.0, 2.0, 2.0), Vector4::new(0.85, 1.7, 0.85, 1.7), Vector4::new(1.0, 1.0, 0.3, 1.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts);
|
||||
/// let surface = NurbsSurface::new(bspsurface);
|
||||
///
|
||||
/// let pt = surface.subs(0.3, 0.7);
|
||||
/// let (u, v) = surface.search_parameter(pt, Some((0.5, 0.5)), 100).unwrap();
|
||||
/// assert_near!(surface.subs(u, v), pt);
|
||||
/// ```
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let hint = match hint.into() {
|
||||
SPHint2D::Parameter(x, y) => (x, y),
|
||||
SPHint2D::Range(range0, range1) => {
|
||||
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint2D::None => {
|
||||
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::surface::search_parameter3d(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include2d() {
|
||||
let knot_vec = KnotVec::uniform_knot(2, 3);
|
||||
let ctrl_pts = vec![
|
||||
vec![
|
||||
Vector3::new(0.0, 0.0, 1.0),
|
||||
Vector3::new(0.05, 0.0, 0.5),
|
||||
Vector3::new(0.15, 0.0, 0.3),
|
||||
Vector3::new(1.0, 0.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vector3::new(0.0, 0.01, 0.1),
|
||||
Vector3::new(0.02, 0.02, 0.1),
|
||||
Vector3::new(0.16, 0.12, 0.4),
|
||||
Vector3::new(0.7, 0.21, 0.7),
|
||||
],
|
||||
vec![
|
||||
Vector3::new(0.0, 0.02, 0.4),
|
||||
Vector3::new(0.15, 0.3, 0.5),
|
||||
Vector3::new(0.6, 0.4, 1.0),
|
||||
Vector3::new(0.4, 0.2, 0.4),
|
||||
],
|
||||
vec![
|
||||
Vector3::new(0.0, 1.0, 1.0),
|
||||
Vector3::new(0.1, 1.0, 1.0),
|
||||
Vector3::new(0.25, 0.5, 0.5),
|
||||
Vector3::new(0.3, 0.3, 0.3),
|
||||
],
|
||||
];
|
||||
let surface = BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts);
|
||||
let bnd_box = BoundingBox::from_iter(&[Vector2::new(0.2, 0.3), Vector2::new(0.8, 0.6)]);
|
||||
let mut curve = surface.sectional_curve(bnd_box);
|
||||
curve.control_points_mut().for_each(|pt| *pt *= 3.0);
|
||||
let surface = NurbsSurface::new(surface);
|
||||
let curve = NurbsCurve::new(curve);
|
||||
assert!(surface.include(&curve));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include3d() {
|
||||
let knot_vec = KnotVec::bezier_knot(2);
|
||||
let ctrl_pts = vec![
|
||||
vec![
|
||||
Vector4::new(-1.0, -1.0, 2.0, 1.0),
|
||||
Vector4::new(-1.0, 0.0, 0.0, 1.0),
|
||||
Vector4::new(-1.0, 1.0, 2.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vector4::new(0.0, -1.0, 0.0, 1.0),
|
||||
Vector4::new(0.0, 0.0, -2.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, 0.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vector4::new(1.0, -1.0, 2.0, 1.0),
|
||||
Vector4::new(1.0, 0.0, 0.0, 1.0),
|
||||
Vector4::new(1.0, 1.0, 2.0, 1.0),
|
||||
],
|
||||
];
|
||||
let surface = NurbsSurface::new(BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts));
|
||||
|
||||
let knot_vec = KnotVec::from(vec![
|
||||
0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0,
|
||||
]);
|
||||
let ctrl_pts = vec![
|
||||
// the vector of the indices of control points
|
||||
Vector4::new(0.0, -2.0, 2.0, 2.0),
|
||||
Vector4::new(1.0, -1.0, 1.0, 1.0),
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(1.0, 1.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, 2.0, 2.0, 2.0),
|
||||
Vector4::new(-1.0, 1.0, 1.0, 1.0),
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(-1.0, -1.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, -2.0, 2.0, 2.0),
|
||||
];
|
||||
let mut curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
assert!(surface.include(&curve));
|
||||
*curve.control_point_mut(1) += Vector4::new(0.0, 0.0, 0.00001, 0.0);
|
||||
assert!(!surface.include(&curve));
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
use super::*;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
impl<P> UnitCircle<P> {
|
||||
/// constructor
|
||||
#[inline]
|
||||
pub const fn new() -> Self { Self(std::marker::PhantomData) }
|
||||
}
|
||||
|
||||
impl ParametricCurve for UnitCircle<Point2> {
|
||||
type Point = Point2;
|
||||
type Vector = Vector2;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point2::new(f64::cos(t), f64::sin(t)) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector2::new(-f64::sin(t), f64::cos(t)) }
|
||||
#[inline]
|
||||
fn der2(&self, t: f64) -> Self::Vector { Vector2::new(-f64::cos(t), -f64::sin(t)) }
|
||||
#[inline]
|
||||
fn parameter_range(&self) -> ParameterRange {
|
||||
(Bound::Included(0.0), Bound::Excluded(2.0 * PI))
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedCurve for UnitCircle<Point2> {}
|
||||
|
||||
impl ParametricCurve for UnitCircle<Point3> {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point3::new(f64::cos(t), f64::sin(t), 0.0) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector3::new(-f64::sin(t), f64::cos(t), 0.0) }
|
||||
#[inline]
|
||||
fn der2(&self, t: f64) -> Self::Vector { Vector3::new(-f64::cos(t), -f64::sin(t), 0.0) }
|
||||
#[inline]
|
||||
fn period(&self) -> Option<f64> { Some(2.0 * PI) }
|
||||
#[inline]
|
||||
fn parameter_range(&self) -> ParameterRange {
|
||||
(Bound::Included(0.0), Bound::Excluded(2.0 * PI))
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedCurve for UnitCircle<Point3> {}
|
||||
|
||||
impl<P> ParameterDivision1D for UnitCircle<P>
|
||||
where UnitCircle<P>: ParametricCurve<Point = P>
|
||||
{
|
||||
type Point = P;
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<P>) {
|
||||
nonpositive_tolerance!(tol);
|
||||
let tol = f64::min(tol, 0.8);
|
||||
let delta = 2.0 * f64::acos(1.0 - tol);
|
||||
let n = 1 + ((range.1 - range.0) / delta) as usize;
|
||||
let params = (0..=n)
|
||||
.map(|i| {
|
||||
let t = i as f64 / n as f64;
|
||||
range.0 * (1.0 - t) + range.1 * t
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let pts = params.iter().map(|t| self.subs(*t)).collect();
|
||||
(params, pts)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitCircle<Point2> {
|
||||
type Point = Point2;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: Point2,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<f64> {
|
||||
let v = pt.to_vec();
|
||||
if v.magnitude().so_small() {
|
||||
return None;
|
||||
}
|
||||
let v = v.normalize();
|
||||
let theta = f64::acos(f64::clamp(v.x, -1.0, 1.0));
|
||||
let theta = match v.y > 0.0 {
|
||||
true => theta,
|
||||
false => 2.0 * PI - theta,
|
||||
};
|
||||
Some(theta)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_nearest_parameter() {
|
||||
const N: usize = 100;
|
||||
let circle = UnitCircle::<Point2>::new();
|
||||
for i in 0..N {
|
||||
let t = 2.0 * PI * i as f64 / N as f64;
|
||||
let a = 5.0 * rand::random::<f64>() + 0.1;
|
||||
let p = a * circle.subs(t);
|
||||
let s = circle.search_nearest_parameter(p, None, 1).unwrap();
|
||||
let q = a * circle.subs(s);
|
||||
assert_near!(p, q);
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitCircle<Point2> {
|
||||
type Point = Point2;
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, pt: Point2, _: H, _: usize) -> Option<f64> {
|
||||
let v = pt.to_vec();
|
||||
if !v.magnitude().near(&1.0) {
|
||||
return None;
|
||||
}
|
||||
let v = v.normalize();
|
||||
let theta = f64::acos(f64::clamp(v.x, -1.0, 1.0));
|
||||
let theta = match v.y > 0.0 {
|
||||
true => theta,
|
||||
false => 2.0 * PI - theta,
|
||||
};
|
||||
Some(theta)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_parameter() {
|
||||
const N: usize = 100;
|
||||
let circle = UnitCircle::<Point2>::new();
|
||||
for i in 1..N {
|
||||
let t = 2.0 * PI * i as f64 / N as f64;
|
||||
let p = circle.subs(t);
|
||||
let s = circle.search_parameter(p, None, 1).unwrap();
|
||||
assert_near!(s, t);
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitCircle<Point3> {
|
||||
type Point = Point3;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<f64> {
|
||||
UnitCircle::<Point2>::new().search_nearest_parameter(Point2::new(pt.x, pt.y), None, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitCircle<Point3> {
|
||||
type Point = Point3;
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, pt: Point3, _: H, _: usize) -> Option<f64> {
|
||||
if !f64::abs(pt.z).so_small() {
|
||||
return None;
|
||||
}
|
||||
UnitCircle::<Point2>::new().search_parameter(Point2::new(pt.x, pt.y), None, 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parameter_division() {
|
||||
let c = UnitCircle::<Point2>::new();
|
||||
let (_div, pts) = c.parameter_division(c.range_tuple(), 0.05);
|
||||
for a in pts.windows(2) {
|
||||
let p = a[0].midpoint(a[1]);
|
||||
assert!(p.to_vec().magnitude() > 0.95);
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
use super::*;
|
||||
|
||||
impl<P> UnitHyperbola<P> {
|
||||
/// constructor
|
||||
#[inline]
|
||||
pub const fn new() -> UnitHyperbola<P> { UnitHyperbola(std::marker::PhantomData) }
|
||||
}
|
||||
|
||||
impl ParametricCurve for UnitHyperbola<Point2> {
|
||||
type Point = Point2;
|
||||
type Vector = Vector2;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point2::new(f64::cosh(t), f64::sinh(t)) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector2::new(f64::sinh(t), f64::cosh(t)) }
|
||||
#[inline]
|
||||
fn der2(&self, t: f64) -> Self::Vector { Vector2::new(f64::cosh(t), f64::sinh(t)) }
|
||||
}
|
||||
|
||||
impl ParametricCurve for UnitHyperbola<Point3> {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point3::new(f64::cosh(t), f64::sinh(t), 0.0) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector3::new(f64::sinh(t), f64::cosh(t), 0.0) }
|
||||
#[inline]
|
||||
fn der2(&self, t: f64) -> Self::Vector { Vector3::new(f64::cosh(t), f64::sinh(t), 0.0) }
|
||||
}
|
||||
|
||||
impl<P> ParameterDivision1D for UnitHyperbola<P>
|
||||
where
|
||||
UnitHyperbola<P>: ParametricCurve<Point = P>,
|
||||
P: EuclideanSpace<Scalar = f64> + MetricSpace<Metric = f64> + HashGen<f64>,
|
||||
{
|
||||
type Point = P;
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<P>) {
|
||||
algo::curve::parameter_division(self, range, tol)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitHyperbola<Point2> {
|
||||
type Point = Point2;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
p: Point2,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<f64> {
|
||||
let a = -p.y;
|
||||
let b = (p.y * p.y - p.x * p.x) / 4.0 + 1.0;
|
||||
let c = -p.y;
|
||||
let d = p.y * p.y / 4.0;
|
||||
let y = solver::solve_quartic(a, b, c, d)
|
||||
.into_iter()
|
||||
.filter_map(|z| match z.im.so_small() {
|
||||
true => Some(z.re),
|
||||
false => None,
|
||||
})
|
||||
.min_by(|s, t| {
|
||||
p.distance2(self.subs(*s))
|
||||
.partial_cmp(&p.distance2(self.subs(*t)))
|
||||
.unwrap()
|
||||
})?;
|
||||
Some(f64::asinh(y))
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitHyperbola<Point3> {
|
||||
type Point = Point3;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
p: Point3,
|
||||
_: H,
|
||||
_trials: usize,
|
||||
) -> Option<f64> {
|
||||
UnitHyperbola::<Point2>::new().search_nearest_parameter(
|
||||
Point2::new(p.x, p.y),
|
||||
None,
|
||||
_trials,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitHyperbola<Point2> {
|
||||
type Point = Point2;
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, p: Point2, _: H, _: usize) -> Option<f64> {
|
||||
let t = f64::asinh(p.y);
|
||||
match p.near(&self.subs(t)) {
|
||||
true => Some(t),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitHyperbola<Point3> {
|
||||
type Point = Point3;
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, p: Point3, _: H, _: usize) -> Option<f64> {
|
||||
let t = f64::asinh(p.y);
|
||||
match p.near(&self.subs(t)) {
|
||||
true => Some(t),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snp_test() {
|
||||
let curve = UnitHyperbola::<Point2>::new();
|
||||
let p = curve.subs(2.0);
|
||||
let q = p + Vector2::new(-p.x, p.y);
|
||||
let t = curve.search_nearest_parameter(q, None, 0).unwrap();
|
||||
assert_near!(t, 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sp_test() {
|
||||
let curve = UnitHyperbola::<Point2>::new();
|
||||
let t = 100.0 * rand::random::<f64>() - 50.0;
|
||||
let p = curve.subs(t);
|
||||
assert_near!(curve.search_parameter(p, None, 0).unwrap(), t);
|
||||
|
||||
let q = Point2::new(-1.0, 0.0);
|
||||
assert!(curve.search_parameter(q, None, 0).is_none());
|
||||
}
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
use super::*;
|
||||
use truck_base::cgmath64::control_point::ControlPoint;
|
||||
|
||||
impl<P: Copy> Line<P> {
|
||||
/// initialize line from vector
|
||||
#[inline]
|
||||
pub fn from_origin_direction<V>(origin: P, direction: V) -> Self
|
||||
where P: std::ops::Add<V, Output = P> {
|
||||
Self(origin, origin + direction)
|
||||
}
|
||||
/// to a bspline curve
|
||||
#[inline]
|
||||
pub fn to_bspline(self) -> BSplineCurve<P> {
|
||||
BSplineCurve::new(KnotVec::bezier_knot(1), vec![self.0, self.1])
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Line<P>
|
||||
where
|
||||
P: EuclideanSpace<Scalar = f64>,
|
||||
P::Diff: InnerSpace<Scalar = f64>,
|
||||
{
|
||||
/// Returns the projected point to the line.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(1.0, 2.0));
|
||||
/// let pt = Point2::new(0.0, 1.0);
|
||||
/// assert_near!(line.projection(pt), Point2::new(0.4, 0.8));
|
||||
/// ```
|
||||
pub fn projection(self, point: P) -> P {
|
||||
let (u, v) = (point - self.0, self.1 - self.0);
|
||||
self.0 + v * u.dot(v) / v.dot(v)
|
||||
}
|
||||
/// Returns the distance between the line and the point `point`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(3.0, 4.0));
|
||||
///
|
||||
/// // The foot of the perpendicular line is on a line segment
|
||||
/// let pt = Point2::new(3.0, 0.0);
|
||||
/// assert_near!(line.distance_to_point(pt), 2.4);
|
||||
///
|
||||
/// // The foot of the perpendicular line is not on a line segment
|
||||
/// let pt = Point2::new(0.0, -4.0);
|
||||
/// assert_near!(line.distance_to_point(pt), 2.4);
|
||||
/// ```
|
||||
pub fn distance_to_point(self, point: P) -> f64 {
|
||||
let (u, v) = (point - self.0, self.1 - self.0);
|
||||
(u - v * u.dot(v) / v.dot(v)).magnitude()
|
||||
}
|
||||
/// Returns the distance between the sengment and the point `point`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(3.0, 4.0));
|
||||
///
|
||||
/// // The foot of the perpendicular line is on a line segment
|
||||
/// let pt = Point2::new(3.0, 0.0);
|
||||
/// assert_near!(line.distance_to_point_as_segment(pt), 2.4);
|
||||
///
|
||||
/// // The foot of the perpendicular line is not on a line segment
|
||||
/// let pt = Point2::new(0.0, -4.0);
|
||||
/// assert_near!(line.distance_to_point_as_segment(pt), 4.0);
|
||||
/// ```
|
||||
pub fn distance_to_point_as_segment(self, point: P) -> f64 {
|
||||
let (u, v) = (point - self.0, self.1 - self.0);
|
||||
let t = f64::clamp(u.dot(v) / v.dot(v), 0.0, 1.0);
|
||||
(u - v * t).magnitude()
|
||||
}
|
||||
}
|
||||
|
||||
impl Line<Point2> {
|
||||
/// Returns the intersection of two lines and its parameters.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line0 = Line(Point2::new(0.0, 0.0), Point2::new(9.0, 3.0));
|
||||
/// let line1 = Line(Point2::new(0.0, 6.0), Point2::new(9.0, 0.0));
|
||||
/// let (s, t, p) = line0.intersection(line1).unwrap();
|
||||
/// assert_near!(line0.subs(s), Point2::new(6.0, 2.0));
|
||||
/// assert_near!(line1.subs(t), Point2::new(6.0, 2.0));
|
||||
/// assert_near!(p, Point2::new(6.0, 2.0));
|
||||
/// ```
|
||||
/// # Failures
|
||||
/// Returns `None` if two lines are parallel.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line0 = Line(Point2::new(0.0, 0.0), Point2::new(9.0, 3.0));
|
||||
/// let line1 = Line(Point2::new(-1.0, 0.0), Point2::new(8.0, 3.0));
|
||||
/// assert!(line0.intersection(line1).is_none());
|
||||
/// ```
|
||||
pub fn intersection(self, other: Line<Point2>) -> Option<(f64, f64, Point2)> {
|
||||
let mat = Matrix2::from_cols(self.1 - self.0, other.0 - other.1);
|
||||
let v = other.0 - self.0;
|
||||
let params = mat.invert().map(|inv| inv * v)?;
|
||||
Some((params.x, params.y, self.subs(params.x)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: ControlPoint<f64>> ParametricCurve for Line<P> {
|
||||
type Point = P;
|
||||
type Vector = P::Diff;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { self.0 + (self.1 - self.0) * t }
|
||||
#[inline]
|
||||
fn der(&self, _: f64) -> Self::Vector { self.1 - self.0 }
|
||||
#[inline]
|
||||
fn der2(&self, _: f64) -> Self::Vector { Self::Vector::zero() }
|
||||
/// Return `0.0..=1.0` i.e. we regard it as a segment
|
||||
#[inline]
|
||||
fn parameter_range(&self) -> ParameterRange { (Bound::Included(0.0), Bound::Included(1.0)) }
|
||||
}
|
||||
|
||||
impl<P: ControlPoint<f64>> BoundedCurve for Line<P> {}
|
||||
|
||||
impl<P: ControlPoint<f64>> Cut for Line<P> {
|
||||
#[inline]
|
||||
fn cut(&mut self, t: f64) -> Self {
|
||||
let r = self.subs(t);
|
||||
let res = Self(r, self.1);
|
||||
self.1 = r;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: ControlPoint<f64>> ParameterDivision1D for Line<P> {
|
||||
type Point = P;
|
||||
#[inline]
|
||||
fn parameter_division(&self, range: (f64, f64), _: f64) -> (Vec<f64>, Vec<P>) {
|
||||
(
|
||||
vec![range.0, range.1],
|
||||
vec![self.subs(range.0), self.subs(range.1)],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Copy> Invertible for Line<P> {
|
||||
#[inline]
|
||||
fn invert(&mut self) { *self = Self(self.1, self.0); }
|
||||
#[inline]
|
||||
fn inverse(&self) -> Self { Self(self.1, self.0) }
|
||||
}
|
||||
|
||||
impl<P> SearchNearestParameter<D1> for Line<P>
|
||||
where
|
||||
P: ControlPoint<f64>,
|
||||
P::Diff: InnerSpace<Scalar = f64>,
|
||||
{
|
||||
type Point = P;
|
||||
#[inline]
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(&self, pt: P, _: H, _: usize) -> Option<f64> {
|
||||
let b = self.1 - self.0;
|
||||
Some((pt - self.0).dot(b) / b.dot(b))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> SearchParameter<D1> for Line<P>
|
||||
where
|
||||
P: ControlPoint<f64> + Tolerance,
|
||||
P::Diff: InnerSpace<Scalar = f64>,
|
||||
{
|
||||
type Point = P;
|
||||
#[inline]
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, pt: P, _: H, _: usize) -> Option<f64> {
|
||||
let b = self.1 - self.0;
|
||||
let t = (pt - self.0).dot(b) / b.dot(b);
|
||||
match self.subs(t).near(&pt) {
|
||||
true => Some(t),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: EuclideanSpace, M: Transform<P>> Transformed<M> for Line<P> {
|
||||
#[inline]
|
||||
fn transform_by(&mut self, trans: M) {
|
||||
self.0 = trans.transform_point(self.0);
|
||||
self.1 = trans.transform_point(self.1);
|
||||
}
|
||||
#[inline]
|
||||
fn transformed(&self, trans: M) -> Self {
|
||||
Line(trans.transform_point(self.0), trans.transform_point(self.1))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line() {
|
||||
let line = Line(Point2::new(1.0, 0.0), Point2::new(0.0, 1.0));
|
||||
|
||||
// subs
|
||||
assert_near!(line.subs(0.4), Point2::new(0.6, 0.4));
|
||||
|
||||
// inverse
|
||||
let line_inverse = line.inverse();
|
||||
assert_eq!(line.0, line_inverse.1);
|
||||
assert_eq!(line.1, line_inverse.0);
|
||||
|
||||
// cut
|
||||
let mut line0 = line;
|
||||
let line1 = line0.cut(0.4);
|
||||
assert_eq!(line.0, line0.0);
|
||||
assert_near!(line0.1, line.subs(0.4));
|
||||
assert_eq!(line0.1, line1.0);
|
||||
assert_eq!(line1.1, line.1);
|
||||
|
||||
// SNP
|
||||
assert_near!(
|
||||
line.search_nearest_parameter(Point2::new(1.0, 1.0), None, 0)
|
||||
.unwrap(),
|
||||
0.5
|
||||
);
|
||||
assert!(line
|
||||
.search_parameter(Point2::new(1.0, 1.0), None, 0)
|
||||
.is_none());
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
use crate::{prelude::*, *};
|
||||
|
||||
/// line
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(1.0, 1.0));
|
||||
/// assert_near!(line.subs(0.5), Point2::new(0.5, 0.5));
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Line<P>(pub P, pub P);
|
||||
|
||||
/// unit circle
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct UnitCircle<P>(std::marker::PhantomData<P>);
|
||||
|
||||
/// unit hyperbola
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct UnitHyperbola<P>(std::marker::PhantomData<P>);
|
||||
|
||||
/// parabola whose apex is the origin.
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct UnitParabola<P>(std::marker::PhantomData<P>);
|
||||
|
||||
/// plane
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
///
|
||||
/// // arbitrary three points
|
||||
/// let pt0 = Point3::new(0.0, 1.0, 2.0);
|
||||
/// let pt1 = Point3::new(1.0, 1.0, 3.0);
|
||||
/// let pt2 = Point3::new(0.0, 2.0, 3.0);
|
||||
///
|
||||
/// // Creates a plane
|
||||
/// let plane: Plane = Plane::new(pt0, pt1, pt2);
|
||||
/// // The origin of the plane is pt0.
|
||||
/// assert_near!(plane.origin(), pt0);
|
||||
/// // The u-axis of the plane is the vector from pt0 to pt1.
|
||||
/// assert_near!(plane.u_axis(), pt1 - pt0);
|
||||
/// // The v-axis of the plane is the vector from pt0 to pt2.
|
||||
/// assert_near!(plane.v_axis(), pt2 - pt0);
|
||||
/// // The normal is the normalized u-axis × v-axis
|
||||
/// assert_near!(plane.normal(), (pt1 - pt0).cross(pt2 - pt0).normalize());
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Plane {
|
||||
o: Point3,
|
||||
p: Point3,
|
||||
q: Point3,
|
||||
}
|
||||
|
||||
/// sphere
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use std::f64::consts::PI;
|
||||
///
|
||||
/// let center = Point3::new(1.0, 2.0, 3.0);
|
||||
/// let radius = 4.56;
|
||||
///
|
||||
/// let sphere = Sphere::new(center, radius);
|
||||
/// const N: usize = 100;
|
||||
/// for i in 0..=N {
|
||||
/// for j in 0..=N {
|
||||
/// // the parameter u is latitude
|
||||
/// let u = PI * i as f64 / N as f64;
|
||||
/// // the parameter v is longitude
|
||||
/// let v = 2.0 * PI * j as f64 / N as f64;
|
||||
///
|
||||
/// // simple relation between a point and its normal.
|
||||
/// let pt = sphere.subs(u, v);
|
||||
/// let n = sphere.normal(u, v);
|
||||
/// assert_near!(pt - center, n * radius);
|
||||
///
|
||||
/// // the proof of u is latitude and v is longitude
|
||||
/// assert!((PI / 2.0 - u) * (pt.z - center.z) >= 0.0);
|
||||
/// assert!((PI - v) * (pt.y - center.y) >= 0.0);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Sphere {
|
||||
center: Point3,
|
||||
radius: f64,
|
||||
}
|
||||
|
||||
/// torus
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Torus {
|
||||
center: Point3,
|
||||
large_radius: f64,
|
||||
small_radius: f64,
|
||||
}
|
||||
|
||||
mod circle;
|
||||
mod hyperbola;
|
||||
mod line;
|
||||
mod parabola;
|
||||
mod plane;
|
||||
mod sphere;
|
||||
mod torus;
|
||||
|
||||
macro_rules! always_true {
|
||||
($ty: tt) => {
|
||||
impl<P> PartialEq for $ty<P> {
|
||||
fn eq(&self, _: &Self) -> bool { true }
|
||||
}
|
||||
impl<P> Eq for $ty<P> {}
|
||||
};
|
||||
}
|
||||
|
||||
always_true!(UnitCircle);
|
||||
always_true!(UnitParabola);
|
||||
always_true!(UnitHyperbola);
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
use super::*;
|
||||
|
||||
impl<P> UnitParabola<P> {
|
||||
/// constructor
|
||||
#[inline]
|
||||
pub const fn new() -> Self { Self(std::marker::PhantomData) }
|
||||
}
|
||||
|
||||
impl ParametricCurve for UnitParabola<Point2> {
|
||||
type Point = Point2;
|
||||
type Vector = Vector2;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point2::new(t * t, 2.0 * t) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector2::new(2.0 * t, 2.0) }
|
||||
#[inline]
|
||||
fn der2(&self, _: f64) -> Self::Vector { Vector2::new(2.0, 0.0) }
|
||||
}
|
||||
|
||||
impl ParametricCurve for UnitParabola<Point3> {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point3::new(t * t, 2.0 * t, 0.0) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector3::new(2.0 * t, 2.0, 0.0) }
|
||||
#[inline]
|
||||
fn der2(&self, _: f64) -> Self::Vector { Vector3::new(2.0, 0.0, 0.0) }
|
||||
}
|
||||
|
||||
impl<P> ParameterDivision1D for UnitParabola<P>
|
||||
where
|
||||
UnitParabola<P>: ParametricCurve<Point = P>,
|
||||
P: EuclideanSpace<Scalar = f64> + MetricSpace<Metric = f64> + HashGen<f64>,
|
||||
{
|
||||
type Point = P;
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<P>) {
|
||||
algo::curve::parameter_division(self, range, tol)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitParabola<Point2> {
|
||||
type Point = Point2;
|
||||
#[inline]
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: Point2,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<f64> {
|
||||
let p = 2.0 - pt.x;
|
||||
let q = -pt.y;
|
||||
solver::pre_solve_cubic(p, q)
|
||||
.into_iter()
|
||||
.filter_map(|x| match x.im.so_small() {
|
||||
true => Some(x.re),
|
||||
false => None,
|
||||
})
|
||||
.min_by(|s, t| {
|
||||
pt.distance2(self.subs(*s))
|
||||
.partial_cmp(&pt.distance2(self.subs(*t)))
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitParabola<Point3> {
|
||||
type Point = Point3;
|
||||
#[inline]
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: Point3,
|
||||
_hint: H,
|
||||
_trials: usize,
|
||||
) -> Option<f64> {
|
||||
UnitParabola::<Point2>::new().search_nearest_parameter(
|
||||
Point2::new(pt.x, pt.y),
|
||||
_hint,
|
||||
_trials,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitParabola<Point2> {
|
||||
type Point = Point2;
|
||||
#[inline]
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, pt: Point2, _: H, _: usize) -> Option<f64> {
|
||||
let t = pt.y / 2.0;
|
||||
let pt0 = self.subs(t);
|
||||
match pt.near(&pt0) {
|
||||
true => Some(t),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitParabola<Point3> {
|
||||
type Point = Point3;
|
||||
#[inline]
|
||||
fn search_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: Point3,
|
||||
_hint: H,
|
||||
_trials: usize,
|
||||
) -> Option<f64> {
|
||||
match pt.z.so_small() {
|
||||
true => UnitParabola::<Point2>::new().search_parameter(
|
||||
Point2::new(pt.x, pt.y),
|
||||
_hint,
|
||||
_trials,
|
||||
),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snp_test() {
|
||||
let curve = UnitParabola::<Point2>::new();
|
||||
|
||||
let p = Point2::new(-3.0, 0.0);
|
||||
assert_near!(curve.search_nearest_parameter(p, None, 0).unwrap(), 0.0);
|
||||
let p = Point2::new(-3.0, 6.0);
|
||||
assert_near!(curve.search_nearest_parameter(p, None, 0).unwrap(), 1.0);
|
||||
let p = Point2::new(1.5, 1.5);
|
||||
assert_near!(curve.search_nearest_parameter(p, None, 0).unwrap(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sp_test() {
|
||||
let curve = UnitParabola::<Point2>::new();
|
||||
|
||||
let p = Point2::new(4.0, -4.0);
|
||||
assert_near!(curve.search_parameter(p, None, 0).unwrap(), -2.0);
|
||||
let p = Point2::new(-3.0, 6.0);
|
||||
assert!(curve.search_parameter(p, None, 0).is_none());
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
use super::*;
|
||||
|
||||
impl Plane {
|
||||
/// Creates a new plane from three points.
|
||||
#[inline(always)]
|
||||
pub const fn new(origin: Point3, one: Point3, another: Point3) -> Plane {
|
||||
Plane {
|
||||
o: origin,
|
||||
p: one,
|
||||
q: another,
|
||||
}
|
||||
}
|
||||
/// Returns the origin
|
||||
#[inline(always)]
|
||||
pub const fn origin(&self) -> Point3 { self.o }
|
||||
/// Returns the u-axis
|
||||
#[inline(always)]
|
||||
pub fn u_axis(&self) -> Vector3 { self.p - self.o }
|
||||
/// Returns the v-axis
|
||||
#[inline(always)]
|
||||
pub fn v_axis(&self) -> Vector3 { self.q - self.o }
|
||||
/// Returns the normal
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let plane = Plane::new(
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// );
|
||||
/// assert_near!(plane.normal(), Vector3::unit_z());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn normal(&self) -> Vector3 { self.u_axis().cross(self.v_axis()).normalize() }
|
||||
/// Gets the parameter of `pt` in plane's matrix.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let plane = Plane::new(
|
||||
/// Point3::new(1.0, 2.0, 3.0),
|
||||
/// Point3::new(2.0, 1.0, 3.0),
|
||||
/// Point3::new(3.0, 4.0, -1.0),
|
||||
/// );
|
||||
///
|
||||
/// let pt = Point3::new(2.1, -6.5, 4.7);
|
||||
/// let prm = plane.get_parameter(pt);
|
||||
/// let rev = plane.origin()
|
||||
/// + prm[0] * plane.u_axis()
|
||||
/// + prm[1] * plane.v_axis()
|
||||
/// + prm[2] * plane.normal();
|
||||
/// assert_near!(pt, rev);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn get_parameter(&self, pt: Point3) -> Vector3 {
|
||||
let a = self.u_axis();
|
||||
let b = self.v_axis();
|
||||
let c = self.normal();
|
||||
let mat = Matrix3::from_cols(a, b, c).invert().unwrap();
|
||||
mat * (pt - self.o)
|
||||
}
|
||||
/// into B-spline surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let pt0 = Point3::new(0.0, 1.0, 2.0);
|
||||
/// let pt1 = Point3::new(1.0, 1.0, 3.0);
|
||||
/// let pt2 = Point3::new(0.0, 2.0, 3.0);
|
||||
/// let plane: Plane = Plane::new(pt0, pt1, pt2);
|
||||
/// let surface: BSplineSurface<Point3> = plane.into_bspline();
|
||||
/// assert_eq!(surface.range_tuple(), ((0.0, 1.0), (0.0, 1.0)));
|
||||
///
|
||||
/// const N: usize = 100;
|
||||
/// for i in 0..=N {
|
||||
/// for j in 0..=N {
|
||||
/// let u = i as f64 / N as f64;
|
||||
/// let v = j as f64 / N as f64;
|
||||
/// let res = surface.subs(u, v);
|
||||
/// let ans = plane.subs(u, v);
|
||||
/// assert_near!(ans, res);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn into_bspline(&self) -> BSplineSurface<Point3> {
|
||||
let o = self.o;
|
||||
let p = self.p;
|
||||
let q = self.q;
|
||||
BSplineSurface::debug_new(
|
||||
(KnotVec::bezier_knot(1), KnotVec::bezier_knot(1)),
|
||||
vec![vec![o, q], vec![p, p + (q - o)]],
|
||||
)
|
||||
}
|
||||
/// into NURBS surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let pt0 = Point3::new(0.0, 1.0, 2.0);
|
||||
/// let pt1 = Point3::new(1.0, 1.0, 3.0);
|
||||
/// let pt2 = Point3::new(0.0, 2.0, 3.0);
|
||||
/// let plane: Plane = Plane::new(pt0, pt1, pt2);
|
||||
/// let surface: NurbsSurface<Vector4> = plane.into_nurbs();
|
||||
/// assert_eq!(surface.range_tuple(), ((0.0, 1.0), (0.0, 1.0)));
|
||||
///
|
||||
/// const N: usize = 100;
|
||||
/// for i in 0..=N {
|
||||
/// for j in 0..=N {
|
||||
/// let u = i as f64 / N as f64;
|
||||
/// let v = j as f64 / N as f64;
|
||||
/// let res = surface.subs(u, v);
|
||||
/// let ans = plane.subs(u, v);
|
||||
/// assert_near!(ans, res);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn into_nurbs(&self) -> NurbsSurface<Vector4> {
|
||||
let o = self.o.to_homogeneous();
|
||||
let p = self.p.to_homogeneous();
|
||||
let q = self.q.to_homogeneous();
|
||||
NurbsSurface::new(BSplineSurface::debug_new(
|
||||
(KnotVec::bezier_knot(1), KnotVec::bezier_knot(1)),
|
||||
vec![vec![o, q], vec![p, p + q - o]],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ParametricSurface for Plane {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline(always)]
|
||||
fn subs(&self, u: f64, v: f64) -> Point3 {
|
||||
self.o + u * (self.p - self.o) + v * (self.q - self.o)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uder(&self, _: f64, _: f64) -> Vector3 { self.p - self.o }
|
||||
#[inline(always)]
|
||||
fn vder(&self, _: f64, _: f64) -> Vector3 { self.q - self.o }
|
||||
#[inline(always)]
|
||||
fn uuder(&self, _: f64, _: f64) -> Vector3 { Vector3::zero() }
|
||||
#[inline(always)]
|
||||
fn uvder(&self, _: f64, _: f64) -> Vector3 { Vector3::zero() }
|
||||
#[inline(always)]
|
||||
fn vvder(&self, _: f64, _: f64) -> Vector3 { Vector3::zero() }
|
||||
/// as square
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
|
||||
let range = (Bound::Included(0.0), Bound::Included(1.0));
|
||||
(range, range)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParametricSurface3D for Plane {
|
||||
#[inline(always)]
|
||||
fn normal(&self, _: f64, _: f64) -> Vector3 { self.normal() }
|
||||
}
|
||||
|
||||
impl BoundedSurface for Plane {}
|
||||
|
||||
impl Invertible for Plane {
|
||||
#[inline(always)]
|
||||
fn inverse(&self) -> Self {
|
||||
Plane {
|
||||
o: self.o,
|
||||
p: self.q,
|
||||
q: self.p,
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn invert(&mut self) { *self = self.inverse(); }
|
||||
}
|
||||
|
||||
impl IncludeCurve<BSplineCurve<Point3>> for Plane {
|
||||
#[inline(always)]
|
||||
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
|
||||
let origin = self.origin();
|
||||
let normal = self.normal();
|
||||
curve
|
||||
.control_points()
|
||||
.iter()
|
||||
.all(|pt| (pt - origin).dot(normal).so_small())
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<NurbsCurve<Vector4>> for Plane {
|
||||
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
|
||||
let origin = self.origin();
|
||||
let normal = self.normal();
|
||||
let (s, e) = (curve.front(), curve.back());
|
||||
if !(s - origin).dot(normal).so_small() || !(e - origin).dot(normal).so_small() {
|
||||
return false;
|
||||
}
|
||||
curve.non_rationalized().control_points().iter().all(|pt| {
|
||||
if pt[3].so_small() {
|
||||
true
|
||||
} else {
|
||||
let pt = Point3::from_homogeneous(*pt);
|
||||
(pt - origin).dot(normal).so_small()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ParameterDivision2D for Plane {
|
||||
#[inline(always)]
|
||||
fn parameter_division(&self, range: ((f64, f64), (f64, f64)), _: f64) -> (Vec<f64>, Vec<f64>) {
|
||||
(vec![range.0 .0, range.0 .1], vec![range.1 .0, range.1 .1])
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Transform3<Scalar = f64>> Transformed<T> for Plane {
|
||||
#[inline(always)]
|
||||
fn transform_by(&mut self, trans: T) {
|
||||
self.o = trans.transform_point(self.o);
|
||||
self.p = trans.transform_point(self.p);
|
||||
self.q = trans.transform_point(self.q);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn transformed(&self, trans: T) -> Self {
|
||||
Plane {
|
||||
o: trans.transform_point(self.o),
|
||||
p: trans.transform_point(self.p),
|
||||
q: trans.transform_point(self.q),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D2> for Plane {
|
||||
type Point = Point3;
|
||||
#[inline(always)]
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let v = self.get_parameter(point);
|
||||
match v[2].so_small() {
|
||||
true => Some((v[0], v[1])),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D2> for Plane {
|
||||
type Point = Point3;
|
||||
#[inline(always)]
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let v = self.get_parameter(point);
|
||||
Some((v[0], v[1]))
|
||||
}
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
use super::*;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
impl Sphere {
|
||||
/// Creates a sphere
|
||||
#[inline(always)]
|
||||
pub const fn new(center: Point3, radius: f64) -> Sphere { Sphere { center, radius } }
|
||||
/// Returns the center
|
||||
#[inline(always)]
|
||||
pub const fn center(&self) -> Point3 { self.center }
|
||||
/// Returns the radius
|
||||
#[inline(always)]
|
||||
pub const fn radius(&self) -> f64 { self.radius }
|
||||
/// Returns whether the point `pt` is on sphere
|
||||
#[inline(always)]
|
||||
pub fn include(&self, pt: Point3) -> bool { self.center.distance(pt).near(&self.radius) }
|
||||
}
|
||||
|
||||
impl ParametricSurface for Sphere {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline(always)]
|
||||
fn subs(&self, u: f64, v: f64) -> Point3 { self.center() + self.radius * self.normal(u, v) }
|
||||
#[inline(always)]
|
||||
fn uder(&self, u: f64, v: f64) -> Vector3 {
|
||||
self.radius
|
||||
* Vector3::new(
|
||||
f64::cos(u) * f64::cos(v),
|
||||
f64::cos(u) * f64::sin(v),
|
||||
-f64::sin(u),
|
||||
)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vder(&self, u: f64, v: f64) -> Vector3 {
|
||||
self.radius * f64::sin(u) * Vector3::new(-f64::sin(v), f64::cos(v), 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uuder(&self, u: f64, v: f64) -> Vector3 { -self.radius * self.normal(u, v) }
|
||||
#[inline(always)]
|
||||
fn uvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
self.radius * f64::cos(u) * Vector3::new(-f64::sin(v), f64::cos(v), 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
-self.radius * f64::sin(u) * Vector3::new(f64::cos(v), f64::sin(v), 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
|
||||
(
|
||||
(Bound::Included(0.0), Bound::Included(PI)),
|
||||
(Bound::Included(0.0), Bound::Excluded(2.0 * PI)),
|
||||
)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn v_period(&self) -> Option<f64> { Some(2.0 * PI) }
|
||||
}
|
||||
|
||||
impl ParametricSurface3D for Sphere {
|
||||
#[inline(always)]
|
||||
fn normal(&self, u: f64, v: f64) -> Vector3 {
|
||||
Vector3::new(
|
||||
f64::sin(u) * f64::cos(v),
|
||||
f64::sin(u) * f64::sin(v),
|
||||
f64::cos(u),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sphere_derivation_test() {
|
||||
let center = Point3::new(1.0, 2.0, 3.0);
|
||||
let radius = 4.56;
|
||||
let sphere = Sphere::new(center, radius);
|
||||
const N: usize = 100;
|
||||
for i in 0..N {
|
||||
for j in 0..N {
|
||||
let u = PI * i as f64 / N as f64;
|
||||
let v = 2.0 * PI * j as f64 / N as f64;
|
||||
let normal = sphere.normal(u, v);
|
||||
assert!(normal.dot(sphere.uder(u, v)).so_small());
|
||||
assert!(normal.dot(sphere.vder(u, v)).so_small());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedSurface for Sphere {}
|
||||
|
||||
impl IncludeCurve<BSplineCurve<Point3>> for Sphere {
|
||||
#[inline(always)]
|
||||
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
|
||||
curve.is_const() && self.include(curve.front())
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<NurbsCurve<Vector4>> for Sphere {
|
||||
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
|
||||
let (knots, _) = curve.knot_vec().to_single_multi();
|
||||
let degree = curve.degree() * 2;
|
||||
knots
|
||||
.windows(2)
|
||||
.flat_map(move |window| (1..degree).map(move |i| (window, i)))
|
||||
.all(move |(window, i)| {
|
||||
let t = i as f64 / degree as f64;
|
||||
let t = window[0] * (1.0 - t) + window[1] * t;
|
||||
self.include(curve.subs(t))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ParameterDivision2D for Sphere {
|
||||
#[inline(always)]
|
||||
fn parameter_division(
|
||||
&self,
|
||||
(urange, vrange): ((f64, f64), (f64, f64)),
|
||||
tol: f64,
|
||||
) -> (Vec<f64>, Vec<f64>) {
|
||||
nonpositive_tolerance!(tol);
|
||||
assert!(
|
||||
tol < self.radius,
|
||||
"Tolerance is larger than the radius of sphere."
|
||||
);
|
||||
let delta = 2.0 * f64::acos(1.0 - tol / self.radius);
|
||||
let u_div = 1 + ((urange.1 - urange.0) / delta).floor() as usize;
|
||||
let v_div = 1 + ((vrange.1 - vrange.0) / delta).floor() as usize;
|
||||
(
|
||||
(0..=u_div)
|
||||
.map(|i| urange.0 + (urange.1 - urange.0) * i as f64 / u_div as f64)
|
||||
.collect(),
|
||||
(0..=v_div)
|
||||
.map(|j| vrange.0 + (vrange.1 - vrange.0) * j as f64 / v_div as f64)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D2> for Sphere {
|
||||
type Point = Point3;
|
||||
#[inline(always)]
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let radius = point - self.center;
|
||||
if (self.radius * self.radius).near(&radius.magnitude2()) {
|
||||
let radius = radius.normalize();
|
||||
let u = f64::acos(radius[2]);
|
||||
let sinu = f64::sqrt(1.0 - radius[2] * radius[2]);
|
||||
let cosv = f64::clamp(radius[0] / sinu, -1.0, 1.0);
|
||||
let v = if sinu.so_small() {
|
||||
match hint.into() {
|
||||
SPHint2D::Parameter(_, hint) => hint,
|
||||
_ => 0.0,
|
||||
}
|
||||
} else if radius[1] > 0.0 {
|
||||
f64::acos(cosv)
|
||||
} else {
|
||||
2.0 * PI - f64::acos(cosv)
|
||||
};
|
||||
Some((u, v))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D2> for Sphere {
|
||||
type Point = Point3;
|
||||
#[inline(always)]
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let radius = (point - self.center).normalize();
|
||||
let u = f64::acos(radius[2]);
|
||||
let sinu = f64::sqrt(1.0 - radius[2] * radius[2]);
|
||||
let cosv = radius[0] / sinu;
|
||||
let v = if radius[1] > 0.0 {
|
||||
f64::acos(cosv)
|
||||
} else {
|
||||
2.0 * PI - f64::acos(cosv)
|
||||
};
|
||||
Some((u, v))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn exec_search_parameter_test() {
|
||||
let center = Point3::new(
|
||||
100.0 * rand::random::<f64>() - 50.0,
|
||||
100.0 * rand::random::<f64>() - 50.0,
|
||||
100.0 * rand::random::<f64>() - 50.0,
|
||||
);
|
||||
let radius = 100.0 * rand::random::<f64>();
|
||||
let sphere = Sphere::new(center, radius);
|
||||
let u = PI * rand::random::<f64>();
|
||||
let v = 2.0 * PI * rand::random::<f64>();
|
||||
let pt = sphere.subs(u, v);
|
||||
let (u0, v0) = sphere.search_parameter(pt, None, 100).unwrap();
|
||||
assert_near!(Vector2::new(u, v), Vector2::new(u0, v0));
|
||||
let pt = pt
|
||||
+ Vector3::new(
|
||||
(0.1 * rand::random::<f64>() + 0.01) * f64::signum(rand::random::<f64>() - 0.5),
|
||||
(0.1 * rand::random::<f64>() + 0.01) * f64::signum(rand::random::<f64>() - 0.5),
|
||||
(0.1 * rand::random::<f64>() + 0.01) * f64::signum(rand::random::<f64>() - 0.5),
|
||||
);
|
||||
assert!(sphere.search_parameter(pt, None, 100).is_none());
|
||||
let (u, v) = sphere.search_nearest_parameter(pt, None, 100).unwrap();
|
||||
assert_near!(
|
||||
sphere.subs(u, v),
|
||||
center + (pt - center).normalize() * radius
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_parameter_test() { (0..10).for_each(|_| exec_search_parameter_test()) }
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
use super::*;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
impl Torus {
|
||||
/// constructor
|
||||
#[inline(always)]
|
||||
pub fn new(center: Point3, large_radius: f64, small_radius: f64) -> Self {
|
||||
if large_radius <= 0.0 || small_radius <= 0.0 {
|
||||
panic!("radius must be larger than 0");
|
||||
}
|
||||
Self {
|
||||
center,
|
||||
large_radius,
|
||||
small_radius,
|
||||
}
|
||||
}
|
||||
|
||||
/// get center
|
||||
#[inline(always)]
|
||||
pub const fn center(&self) -> Point3 { self.center }
|
||||
|
||||
/// get large radius
|
||||
#[inline(always)]
|
||||
pub const fn large_radius(&self) -> f64 { self.large_radius }
|
||||
|
||||
/// get small radius
|
||||
#[inline(always)]
|
||||
pub const fn small_radius(&self) -> f64 { self.small_radius }
|
||||
}
|
||||
|
||||
impl ParametricSurface for Torus {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline(always)]
|
||||
fn subs(&self, u: f64, v: f64) -> Point3 {
|
||||
let sr = self.small_radius() * Vector2::new(f64::cos(v), f64::sin(v));
|
||||
let lr = (self.large_radius() + sr.x) * Vector2::new(f64::cos(u), f64::sin(u));
|
||||
self.center() + Vector3::new(lr.x, lr.y, sr.y)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sr = self.small_radius() * f64::cos(v);
|
||||
let lr = (self.large_radius() + sr) * Vector2::new(f64::cos(u), f64::sin(u));
|
||||
Vector3::new(-lr.y, lr.x, 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sv = self.small_radius() * Vector2::new(-f64::sin(v), f64::cos(v));
|
||||
Vector3::new(sv.x * f64::cos(u), sv.x * f64::sin(u), sv.y)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uuder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sr = self.small_radius() * f64::cos(v);
|
||||
let lr = (self.large_radius() + sr) * Vector2::new(f64::cos(u), f64::sin(u));
|
||||
Vector3::new(-lr.x, -lr.y, 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sr = -self.small_radius() * f64::sin(v);
|
||||
let lr = sr * Vector2::new(f64::cos(u), f64::sin(u));
|
||||
Vector3::new(-lr.y, lr.x, 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sv = -self.small_radius() * Vector2::new(f64::cos(v), f64::sin(v));
|
||||
Vector3::new(sv.x * f64::cos(u), sv.x * f64::sin(u), sv.y)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
|
||||
const RANGE: (Bound<f64>, Bound<f64>) = (Bound::Included(0.0), Bound::Excluded(2.0 * PI));
|
||||
(RANGE, RANGE)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn u_period(&self) -> Option<f64> { Some(2.0 * PI) }
|
||||
#[inline(always)]
|
||||
fn v_period(&self) -> Option<f64> { Some(2.0 * PI) }
|
||||
}
|
||||
|
||||
impl ParametricSurface3D for Torus {
|
||||
fn normal(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sv = Vector2::new(f64::cos(v), f64::sin(v));
|
||||
Vector3::new(sv.x * f64::cos(u), sv.x * f64::sin(u), sv.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedSurface for Torus {}
|
||||
|
||||
impl SearchParameter<D2> for Torus {
|
||||
type Point = Point3;
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let r = point - self.center();
|
||||
let rxy = Vector2::new(r.x, r.y);
|
||||
let v = f64::asin(f64::clamp(r.z / self.small_radius(), -1.0, 1.0));
|
||||
let minus = rxy.magnitude2() < self.large_radius() * self.large_radius();
|
||||
let v = match (minus, v < 0.0) {
|
||||
(true, _) => PI - v,
|
||||
(false, false) => v,
|
||||
(false, true) => 2.0 * PI + v,
|
||||
};
|
||||
let rxy_n = rxy.normalize();
|
||||
let u = f64::acos(f64::clamp(rxy_n.x, -1.0, 1.0));
|
||||
let u = match rxy_n.y < 0.0 {
|
||||
true => 2.0 * PI - u,
|
||||
false => u,
|
||||
};
|
||||
match self.subs(u, v).near(&point) {
|
||||
true => Some((u, v)),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D2> for Torus {
|
||||
type Point = Point3;
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let r = point - self.center();
|
||||
let rxy = Vector2::new(r.x, r.y);
|
||||
if rxy.so_small() {
|
||||
return None;
|
||||
}
|
||||
let rxy_n = rxy.normalize();
|
||||
let large_r = self.large_radius() * rxy_n.extend(0.0);
|
||||
let diff = r - large_r;
|
||||
if diff.so_small() {
|
||||
return None;
|
||||
}
|
||||
let small_r = diff.normalize();
|
||||
|
||||
let u = f64::acos(f64::clamp(rxy_n.x, -1.0, 1.0));
|
||||
let u = match rxy_n.y < 0.0 {
|
||||
true => 2.0 * PI - u,
|
||||
false => u,
|
||||
};
|
||||
let v = f64::asin(f64::clamp(small_r.z, -1.0, 1.0));
|
||||
let v = match (small_r.dot(large_r) < 0.0, v < 0.0) {
|
||||
(true, _) => PI - v,
|
||||
(false, false) => v,
|
||||
(false, true) => 2.0 * PI + v,
|
||||
};
|
||||
Some((u, v))
|
||||
}
|
||||
}
|
||||
|
||||
impl ParameterDivision2D for Torus {
|
||||
fn parameter_division(
|
||||
&self,
|
||||
(urange, vrange): ((f64, f64), (f64, f64)),
|
||||
tol: f64,
|
||||
) -> (Vec<f64>, Vec<f64>) {
|
||||
let circle = UnitCircle::<Point2>::new();
|
||||
let utol = tol / (self.small_radius() + self.large_radius());
|
||||
let (udiv, _) = circle.parameter_division(urange, utol);
|
||||
let vtol = tol / self.small_radius();
|
||||
let (vdiv, _) = circle.parameter_division(vrange, vtol);
|
||||
(udiv, vdiv)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn surface() {
|
||||
use std::ops::RangeBounds;
|
||||
let small_radius = 5.0 * rand::random::<f64>() + 1.0;
|
||||
let large_radius = 2.0 * small_radius + 5.0 * rand::random::<f64>();
|
||||
let torus = Torus::new(Point3::new(0.0, 0.0, 0.0), large_radius, small_radius);
|
||||
const N: usize = 10;
|
||||
const EPS: f64 = 1.0e-2;
|
||||
for i in 0..N {
|
||||
for j in 0..N {
|
||||
let u = 2.0 * PI * i as f64 / N as f64;
|
||||
let v = 2.0 * PI * j as f64 / N as f64;
|
||||
let p = torus.subs(u, v);
|
||||
let r = large_radius * Point3::new(f64::cos(u), f64::sin(u), 0.0);
|
||||
assert_near!(p.distance(r), small_radius);
|
||||
assert_near!(p.z, small_radius * f64::sin(v));
|
||||
|
||||
let uder0 = torus.uder(u, v);
|
||||
let uder1 = (torus.subs(u + EPS, v) - torus.subs(u - EPS, v)) / (2.0 * EPS);
|
||||
assert!((uder0 - uder1).magnitude() < EPS, "{:?} {:?}", uder0, uder1);
|
||||
|
||||
let vder0 = torus.vder(u, v);
|
||||
let vder1 = (torus.subs(u, v + EPS) - torus.subs(u, v - EPS)) / (2.0 * EPS);
|
||||
assert!((vder0 - vder1).magnitude() < EPS, "{:?} {:?}", vder0, vder1);
|
||||
|
||||
let uuder0 = torus.uuder(u, v);
|
||||
let uuder1 = (torus.uder(u + EPS, v) - torus.uder(u - EPS, v)) / (2.0 * EPS);
|
||||
assert!(
|
||||
(uuder0 - uuder1).magnitude() < EPS,
|
||||
"{:?} {:?}",
|
||||
uuder0,
|
||||
uuder1
|
||||
);
|
||||
|
||||
let uvder0 = torus.uvder(u, v);
|
||||
let uvder1 = (torus.vder(u + EPS, v) - torus.vder(u - EPS, v)) / (2.0 * EPS);
|
||||
assert!(
|
||||
(uvder0 - uvder1).magnitude() < EPS,
|
||||
"{:?} {:?}",
|
||||
uvder0,
|
||||
uvder1
|
||||
);
|
||||
|
||||
let vvder0 = torus.vvder(u, v);
|
||||
let vvder1 = (torus.vder(u, v + EPS) - torus.vder(u, v - EPS)) / (2.0 * EPS);
|
||||
assert!(
|
||||
(vvder0 - vvder1).magnitude() < EPS,
|
||||
"{:?} {:?}",
|
||||
vvder0,
|
||||
vvder1
|
||||
);
|
||||
|
||||
let n0 = torus.normal(u, v);
|
||||
let n1 = torus.uder(u, v).cross(torus.vder(u, v)).normalize();
|
||||
assert_near!(n0, n1);
|
||||
|
||||
let (u0, v0) = torus.search_parameter(p, None, 1).unwrap();
|
||||
let (urange, vrange) = torus.parameter_range();
|
||||
assert!(urange.contains(&u0) && vrange.contains(&v0), "{u0}, {v0}");
|
||||
assert_near!(torus.subs(u0, v0), p);
|
||||
|
||||
let deform = (0.8 * rand::random::<f64>() - 0.4) * small_radius;
|
||||
let q = p + deform * n0;
|
||||
let (u0, v0) = torus.search_nearest_parameter(q, None, 1).unwrap();
|
||||
let (urange, vrange) = torus.parameter_range();
|
||||
assert!(urange.contains(&u0) && vrange.contains(&v0), "{u0}, {v0}");
|
||||
assert_near!(torus.subs(u0, v0), p);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user