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

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

No files matched your search

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