From 3333f8a62b0f55f17c85214859907a67263eaa3e Mon Sep 17 00:00:00 2001 From: JIJINBEI <87472238+jijinbei@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:27:55 +0900 Subject: [PATCH] Divide intersection edges from their neighbours' parameters (#1038) (#1412) Thanks @jijinbei. --- ATTRIBUTION.md | 1 + Cargo.lock | 2 - Cargo.toml | 1 + THIRD-PARTY-LICENSES.txt | 10 +- crates/kernel/src/issue_1038_tests.rs | 81 + crates/kernel/src/lib.rs | 2 + vendor/README.md | 12 + vendor/truck-geometry/.cargo-ok | 1 + vendor/truck-geometry/Cargo.toml | 55 + vendor/truck-geometry/LICENSE | 177 ++ vendor/truck-geometry/README.md | 5 + .../src/decorators/curve_on_surface.rs | 228 ++ .../src/decorators/extruded_curve.rs | 203 ++ .../src/decorators/intersection_curve.rs | 330 +++ vendor/truck-geometry/src/decorators/mod.rs | 149 ++ .../src/decorators/processor.rs | 658 +++++ .../src/decorators/revolved_curve.rs | 689 +++++ .../src/decorators/trimmied_curve.rs | 73 + vendor/truck-geometry/src/errors.rs | 190 ++ vendor/truck-geometry/src/lib.rs | 52 + vendor/truck-geometry/src/nurbs/bspcurve.rs | 1488 +++++++++++ vendor/truck-geometry/src/nurbs/bspsurface.rs | 2230 +++++++++++++++++ vendor/truck-geometry/src/nurbs/knot_vec.rs | 596 +++++ vendor/truck-geometry/src/nurbs/mod.rs | 135 + vendor/truck-geometry/src/nurbs/nurbscurve.rs | 619 +++++ .../truck-geometry/src/nurbs/nurbssurface.rs | 920 +++++++ .../truck-geometry/src/specifieds/circle.rs | 161 ++ .../src/specifieds/hyperbola.rs | 125 + vendor/truck-geometry/src/specifieds/line.rs | 217 ++ vendor/truck-geometry/src/specifieds/mod.rs | 115 + .../truck-geometry/src/specifieds/parabola.rs | 137 + vendor/truck-geometry/src/specifieds/plane.rs | 256 ++ .../truck-geometry/src/specifieds/sphere.rs | 219 ++ vendor/truck-geometry/src/specifieds/torus.rs | 238 ++ 34 files changed, 10368 insertions(+), 7 deletions(-) create mode 100644 crates/kernel/src/issue_1038_tests.rs create mode 100644 vendor/truck-geometry/.cargo-ok create mode 100644 vendor/truck-geometry/Cargo.toml create mode 100644 vendor/truck-geometry/LICENSE create mode 100644 vendor/truck-geometry/README.md create mode 100644 vendor/truck-geometry/src/decorators/curve_on_surface.rs create mode 100644 vendor/truck-geometry/src/decorators/extruded_curve.rs create mode 100644 vendor/truck-geometry/src/decorators/intersection_curve.rs create mode 100644 vendor/truck-geometry/src/decorators/mod.rs create mode 100644 vendor/truck-geometry/src/decorators/processor.rs create mode 100644 vendor/truck-geometry/src/decorators/revolved_curve.rs create mode 100644 vendor/truck-geometry/src/decorators/trimmied_curve.rs create mode 100644 vendor/truck-geometry/src/errors.rs create mode 100644 vendor/truck-geometry/src/lib.rs create mode 100644 vendor/truck-geometry/src/nurbs/bspcurve.rs create mode 100644 vendor/truck-geometry/src/nurbs/bspsurface.rs create mode 100644 vendor/truck-geometry/src/nurbs/knot_vec.rs create mode 100644 vendor/truck-geometry/src/nurbs/mod.rs create mode 100644 vendor/truck-geometry/src/nurbs/nurbscurve.rs create mode 100644 vendor/truck-geometry/src/nurbs/nurbssurface.rs create mode 100644 vendor/truck-geometry/src/specifieds/circle.rs create mode 100644 vendor/truck-geometry/src/specifieds/hyperbola.rs create mode 100644 vendor/truck-geometry/src/specifieds/line.rs create mode 100644 vendor/truck-geometry/src/specifieds/mod.rs create mode 100644 vendor/truck-geometry/src/specifieds/parabola.rs create mode 100644 vendor/truck-geometry/src/specifieds/plane.rs create mode 100644 vendor/truck-geometry/src/specifieds/sphere.rs create mode 100644 vendor/truck-geometry/src/specifieds/torus.rs diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 3a899456..b5b8c4d2 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -176,6 +176,7 @@ copied into SolidCraft. | vendor/truck-meshalgo | truck-meshalgo 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | meshing: bounded parameter searches, collapsed-boundary projection, subdivision and provisional fixed-grid previews (vendor/README.md) | | vendor/truck-stepio | truck-stepio 0.3.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | STEP writer: intersection-curve surfaces written at unique ids (vendor/README.md) | | vendor/truck-shapeops | truck-shapeops 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | robust meshing and nearest-parameter fallback in booleans; degenerate intersection-curve rejection; face and edge history of booleans; trace macro; corner choice at vertices several intersection curves end on (vendor/README.md) | +| vendor/truck-geometry | truck-geometry 0.5.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | intersection curves divided from their neighbours' surface parameters (vendor/README.md) | | vendor/matext4cgmath | matext4cgmath 0.1.0, https://github.com/ricosjp/matext4cgmath (RICOS Co. Ltd.) | Apache-2.0 | root refinement stops after 100 Newton steps (vendor/README.md) | ## Vendored renderer compatibility fix diff --git a/Cargo.lock b/Cargo.lock index 4124950c..88f732ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4552,8 +4552,6 @@ dependencies = [ [[package]] name = "truck-geometry" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d9d85f203fdbc2d206a38b8e7c4417a05a2eb589be8b81ecfe9f98faafc425d" dependencies = [ "serde", "thiserror 1.0.69", diff --git a/Cargo.toml b/Cargo.toml index 1066b71b..8955c336 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,6 +126,7 @@ debug = "line-tables-only" # truck-shapeops with SolidCraft fixes (Apache-2.0, see vendor/truck-shapeops and ATTRIBUTION.md). [patch.crates-io] +truck-geometry = { path = "vendor/truck-geometry" } truck-shapeops = { path = "vendor/truck-shapeops" } truck-stepio = { path = "vendor/truck-stepio" } truck-meshalgo = { path = "vendor/truck-meshalgo" } diff --git a/THIRD-PARTY-LICENSES.txt b/THIRD-PARTY-LICENSES.txt index 6254c85e..a702b820 100644 --- a/THIRD-PARTY-LICENSES.txt +++ b/THIRD-PARTY-LICENSES.txt @@ -1536,10 +1536,11 @@ android-activity 0.6.1, arboard 3.6.1, color 0.3.3, fdeflate 0.3.7, fearless_sim image 0.25.10, linebender_resource_handle 0.1.1, miniz_oxide 0.8.9, miniz_oxide 0.9.1, naga 30.0.1, naga-types 30.0.1, pin-project 1.1.13, pin-project-internal 1.1.13, pin-project-lite 0.2.17, portable-atomic 1.15.0, portable-atomic-util 0.2.8, range-alloc 0.1.5, raw-window-handle 0.6.2, -simdutf8 0.1.5, truck-meshalgo 0.4.0, truck-shapeops 0.4.0, truck-stepio 0.3.0, vello_common 0.1.0, -vello_cpu 0.1.0, wgpu 30.0.1, wgpu-core 30.0.1, wgpu-core-deps-apple 30.0.1, -wgpu-core-deps-emscripten 30.0.1, wgpu-core-deps-wasm 30.0.1, wgpu-core-deps-windows-linux-android -30.0.1, wgpu-hal 30.0.1, wgpu-naga-bridge 30.0.1, wgpu-types 30.0.1 +simdutf8 0.1.5, truck-geometry 0.5.0, truck-meshalgo 0.4.0, truck-shapeops 0.4.0, truck-stepio +0.3.0, vello_common 0.1.0, vello_cpu 0.1.0, wgpu 30.0.1, wgpu-core 30.0.1, wgpu-core-deps-apple +30.0.1, wgpu-core-deps-emscripten 30.0.1, wgpu-core-deps-wasm 30.0.1, +wgpu-core-deps-windows-linux-android 30.0.1, wgpu-hal 30.0.1, wgpu-naga-bridge 30.0.1, wgpu-types +30.0.1 ================================================================================ Apache License @@ -15964,7 +15965,6 @@ rustls-platform-verifier-android 0.2.0: MIT OR Apache-2.0 (https://github.com/ru spirv 0.4.0+sdk-1.4.341.0: Apache-2.0 (https://github.com/gfx-rs/rspirv) truck-base 0.5.0: Apache-2.0 (https://github.com/ricosjp/truck) truck-derivers 0.1.0: Apache-2.0 (https://github.com/ricosjp/truck) -truck-geometry 0.5.0: Apache-2.0 (https://github.com/ricosjp/truck) truck-geotrait 0.4.0: Apache-2.0 (https://github.com/ricosjp/truck) truck-modeling 0.6.0: Apache-2.0 (https://github.com/ricosjp/truck) truck-polymesh 0.6.0: Apache-2.0 (https://github.com/ricosjp/truck) diff --git a/crates/kernel/src/issue_1038_tests.rs b/crates/kernel/src/issue_1038_tests.rs new file mode 100644 index 00000000..ca3f16f1 --- /dev/null +++ b/crates/kernel/src/issue_1038_tests.rs @@ -0,0 +1,81 @@ +//! #1038: a small hole on a large curved face divides its intersection edges from each point's +//! neighbour (the vendored `IntersectionCurve::parameter_division`) instead of searching both +//! surfaces from nothing for every point. These pin the hinted division to the search from +//! nothing, so a refresh of the vendored crate can't quietly lose the speedup or change a body. + +use std::time::{Duration, Instant}; + +use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3}; +use truck_modeling as mt; +use truck_modeling::{BoundedCurve, ParameterDivision1D}; + +use crate::{Body, BoolOp, build::extrude, cylinder, ops::boolean}; + +/// A 200 × 200 block whose top is one cubic wave across x (a single B-spline face), with a +/// Ø1 mm hole through it at the middle. +fn holed() -> (Body, Body) { + let plane = Plane::new(Vec3::new(0.0, 200.0, 0.0), Vec3::X, Vec3::Z).unwrap(); + let (a, b, c, d) = (Vec2::new(0.0, 0.0), Vec2::new(200.0, 0.0), Vec2::new(200.0, 40.0), Vec2::new(0.0, 40.0)); + let top = Seg2::Cubic { p0: c, p1: Vec2::new(133.0, 70.0), p2: Vec2::new(67.0, 10.0), p3: d }; + let outer = Loop2 { segs: vec![Seg2::Line { a, b }, Seg2::Line { a: b, b: c }, top, Seg2::Line { a: d, b: a }] }; + let block = extrude(&plane, &[Region2 { outer, holes: vec![] }], 0.0, 200.0).unwrap().pop().unwrap(); + let tool = cylinder(Vec3::new(100.0, 100.0, -10.0), Vec3::Z, 0.5, 100.0).unwrap(); + let cut = boolean(&block, &tool, BoolOp::Cut).unwrap().unwrap(); + (block, cut) +} + +fn intersection_curves(body: &Body) -> Vec { + body.solid.edge_iter().map(|e| e.curve()).filter(|c| matches!(c, mt::Curve::IntersectionCurve(_))).collect() +} + +fn timed(f: impl Fn() -> T) -> (T, Duration) { + let start = Instant::now(); + let out = f(); + (out, start.elapsed()) +} + +#[test] +fn a_small_hole_in_a_large_spline_face_cuts_cleanly() { + let (block, cut) = holed(); + assert!(cut.validity().is_empty(), "{:?}", cut.validity()); + // The hole adds its wall (two half cylinders) and splits nothing: the bottom and the wave + // each gain a loop. + assert_eq!(cut.face_count(), block.face_count() + 2); + let curves = intersection_curves(&cut); + assert!(!curves.is_empty(), "the hole's rim on the wave is an intersection edge"); + // The wave is within a few tenths of a millimetre of flat across a Ø1 hole, so the hole + // removes a cylinder as tall as the wave at its axis (the cubic's middle: z = 40). + let (v0, v1) = (block.volume_at(0.001).unwrap(), cut.volume_at(0.001).unwrap()); + let removed = std::f64::consts::PI * 0.25 * 40.0; + assert!((v0 - v1 - removed).abs() < 0.05 * removed, "removed {} of {removed}", v0 - v1); +} + +#[test] +fn hinted_division_matches_the_search_from_nothing_and_is_faster() { + let (_, cut) = holed(); + let (mut hinted_time, mut scratch_time) = (Duration::ZERO, Duration::ZERO); + for curve in intersection_curves(&cut) { + let mt::Curve::IntersectionCurve(ic) = &curve else { continue }; + let range = ic.range_tuple(); + for tol in [0.01, 0.001] { + let ((hp, hq), ht) = timed(|| ic.parameter_division(range, tol)); + let ((sp, _), st) = timed(|| mt::algo::curve::parameter_division(ic, range, tol)); + hinted_time += ht; + scratch_time += st; + // The same division up to rounding: the hinted projections converge to within the + // Newton tolerance of the searched ones, so a split the chord test only just asks for + // can come out one way or the other, but no more than that. + assert_eq!((hp.first(), hp.last()), (sp.first(), sp.last()), "tol {tol}"); + assert!(hp.len().abs_diff(sp.len()) <= 2, "tol {tol}: {} points hinted, {} from nothing", hp.len(), sp.len()); + // Every hinted point is the curve's own point at its parameter. + for (t, h) in hp.iter().zip(&hq) { + let s = ic.search_triple(*t).unwrap().0; + assert!(mt::MetricSpace::distance(*h, s) < 1e-6, "tol {tol}, t {t}: {h:?} vs {s:?}"); + } + } + } + eprintln!("hinted {hinted_time:?}, from nothing {scratch_time:?}"); + // The hinted path skips each point's grid search of the B-spline face; a loose bound, so a + // loaded runner doesn't trip it, but one a fallback to the search for every point would. + assert!(hinted_time * 2 < scratch_time, "hinted {hinted_time:?}, from nothing {scratch_time:?}"); +} diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs index 21248372..a152328c 100644 --- a/crates/kernel/src/lib.rs +++ b/crates/kernel/src/lib.rs @@ -47,6 +47,8 @@ mod iges_out; #[cfg(test)] mod intersection_curve_tests; #[cfg(test)] +mod issue_1038_tests; +#[cfg(test)] mod issue_1080_tests; #[cfg(test)] mod issue_616_tests; diff --git a/vendor/README.md b/vendor/README.md index e748f75e..3b1e4ea5 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -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. diff --git a/vendor/truck-geometry/.cargo-ok b/vendor/truck-geometry/.cargo-ok new file mode 100644 index 00000000..5f8b7958 --- /dev/null +++ b/vendor/truck-geometry/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/vendor/truck-geometry/Cargo.toml b/vendor/truck-geometry/Cargo.toml new file mode 100644 index 00000000..c7427581 --- /dev/null +++ b/vendor/truck-geometry/Cargo.toml @@ -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 "] +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"] diff --git a/vendor/truck-geometry/LICENSE b/vendor/truck-geometry/LICENSE new file mode 100644 index 00000000..f433b1a5 --- /dev/null +++ b/vendor/truck-geometry/LICENSE @@ -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 diff --git a/vendor/truck-geometry/README.md b/vendor/truck-geometry/README.md new file mode 100644 index 00000000..5d2212db --- /dev/null +++ b/vendor/truck-geometry/README.md @@ -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 diff --git a/vendor/truck-geometry/src/decorators/curve_on_surface.rs b/vendor/truck-geometry/src/decorators/curve_on_surface.rs new file mode 100644 index 00000000..690ed2ce --- /dev/null +++ b/vendor/truck-geometry/src/decorators/curve_on_surface.rs @@ -0,0 +1,228 @@ +use super::*; + +impl PCurve { + /// Creates composited + #[inline(always)] + pub const fn new(curve: C, surface: S) -> PCurve { 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 ParametricCurve for PCurve +where + C: ParametricCurve2D, + S: ParametricSurface, + S::Vector: VectorSpace, +{ + 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 BoundedCurve for PCurve +where + C: BoundedCurve, + PCurve: ParametricCurve, +{ +} + +impl Cut for PCurve +where + C: Cut, + S: Clone, + PCurve: ParametricCurve, +{ + fn cut(&mut self, t: f64) -> Self { + let curve = self.curve.cut(t); + Self { + curve, + surface: self.surface.clone(), + } + } +} + +impl SearchParameter for PCurve +where + C: ParametricCurve2D + SearchParameter, + S: SearchParameter, +{ + type Point = >::Point; + fn search_parameter>( + &self, + point: Self::Point, + hint: H, + trials: usize, + ) -> Option { + 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 SearchNearestParameter for PCurve +where + Self: BoundedCurve, + ::Point: EuclideanSpace::Vector> + + MetricSpace, + ::Vector: InnerSpace + Tolerance, +{ + type Point = ::Point; + fn search_nearest_parameter>( + &self, + point: Self::Point, + hint: H, + trials: usize, + ) -> Option { + 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 ParameterDivision1D for PCurve +where + C: ParametricCurve2D, + S: ParametricSurface, + S::Point: EuclideanSpace + MetricSpace + HashGen, + S::Vector: VectorSpace, +{ + type Point = S::Point; + fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec, Vec) { + algo::curve::parameter_division(self, range, tol) + } +} + +impl Invertible for PCurve +where + C: Invertible, + S: Clone, +{ + fn invert(&mut self) { self.curve.invert() } +} + +impl Transformed for PCurve +where S: Transformed +{ + #[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()); +} diff --git a/vendor/truck-geometry/src/decorators/extruded_curve.rs b/vendor/truck-geometry/src/decorators/extruded_curve.rs new file mode 100644 index 00000000..7128e697 --- /dev/null +++ b/vendor/truck-geometry/src/decorators/extruded_curve.rs @@ -0,0 +1,203 @@ +use super::*; + +impl ExtrudedCurve { + /// 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 ParametricSurface for ExtrudedCurve +where + C: ParametricCurve, + C::Point: EuclideanSpace, + C::Vector: VectorSpace, +{ + 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 { self.curve.period() } +} + +impl ParametricSurface3D for ExtrudedCurve { + #[inline(always)] + fn normal(&self, u: f64, _: f64) -> C::Vector { + self.curve.der(u).cross(self.vector).normalize() + } +} + +impl BoundedSurface for ExtrudedCurve +where + C: BoundedCurve, + Self: ParametricSurface, +{ +} + +impl ParameterDivision2D for ExtrudedCurve { + #[inline(always)] + fn parameter_division( + &self, + (urange, vrange): ((f64, f64), (f64, f64)), + tol: f64, + ) -> (Vec, Vec) { + ( + self.curve.parameter_division(urange, tol).0, + vec![vrange.0, vrange.1], + ) + } +} + +impl SearchParameter for ExtrudedCurve { + type Point = Point2; + #[inline(always)] + fn search_parameter>( + &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 SearchParameter for ExtrudedCurve { + type Point = Point3; + #[inline(always)] + fn search_parameter>( + &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 SearchNearestParameter for ExtrudedCurve { + type Point = Point3; + #[inline(always)] + fn search_nearest_parameter>( + &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 Invertible for ExtrudedCurve { + #[inline(always)] + fn invert(&mut self) { self.curve.invert() } + #[inline(always)] + fn inverse(&self) -> Self { + Self { + curve: self.curve.inverse(), + vector: self.vector, + } + } +} + +impl> Transformed for ExtrudedCurve { + 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)); + } + } +} diff --git a/vendor/truck-geometry/src/decorators/intersection_curve.rs b/vendor/truck-geometry/src/decorators/intersection_curve.rs new file mode 100644 index 00000000..d6390e61 --- /dev/null +++ b/vendor/truck-geometry/src/decorators/intersection_curve.rs @@ -0,0 +1,330 @@ +use super::*; + +#[doc(hidden)] +pub fn double_projection( + 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, +{ + #[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 IntersectionCurve { + /// 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(self, f: impl FnOnce(C) -> D) -> IntersectionCurve { + 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, surface1: Box, leader: C, tol: f64) -> Self { + Self { + surface0, + surface1, + leader, + tol, + } + } +} + +impl IntersectionCurve +where + C: ParametricCurve3D, + S: ParametricSurface3D + SearchNearestParameter, +{ + /// 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 ParametricCurve for IntersectionCurve +where + C: ParametricCurve3D, + S: ParametricSurface3D + SearchNearestParameter, +{ + 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 BoundedCurve for IntersectionCurve +where + C: ParametricCurve3D + BoundedCurve, + S: ParametricSurface3D + SearchNearestParameter, +{ +} + +impl ParameterDivision1D for IntersectionCurve +where + C: ParametricCurve3D, + S: ParametricSurface3D + SearchNearestParameter, +{ + 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, Vec) { + 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 IntersectionCurve +where + C: ParametricCurve3D, + S: ParametricSurface3D + SearchNearestParameter, +{ + // 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 { + 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( + curve: &IntersectionCurve, + range: (f64, f64), + ends: (Triple, Triple), + tol: f64, + trials: usize, +) -> (Vec, Vec) +where + C: ParametricCurve3D, + S: ParametricSurface3D + SearchNearestParameter, +{ + 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 Cut for IntersectionCurve +where + C: Cut, + S: ParametricSurface3D + SearchNearestParameter, +{ + #[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 Invertible for IntersectionCurve { + fn invert(&mut self) { self.leader.invert(); } +} + +impl SearchParameter for IntersectionCurve +where + C: ParametricCurve3D + SearchNearestParameter, + S: ParametricSurface3D + SearchNearestParameter, +{ + type Point = Point3; + fn search_parameter>( + &self, + point: Point3, + hint: H, + trials: usize, + ) -> Option { + 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 SearchNearestParameter for IntersectionCurve +where + C: ParametricCurve3D + SearchNearestParameter, + S: ParametricSurface3D + SearchNearestParameter, +{ + type Point = Point3; + fn search_nearest_parameter>( + &self, + point: Point3, + hint: H, + trials: usize, + ) -> Option { + self.leader().search_nearest_parameter(point, hint, trials) + } +} + +impl Transformed for IntersectionCurve +where + C: Transformed, + S: Transformed, +{ + 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 IntersectionCurve { + /// Optimizes intersection curve of [`Plane`] into [`Line`]. + #[inline] + pub fn optimize(&self) -> Line { + let (s, t) = self.leader.range_tuple(); + Line(self.leader.subs(s), self.leader.subs(t)) + } +} diff --git a/vendor/truck-geometry/src/decorators/mod.rs b/vendor/truck-geometry/src/decorators/mod.rs new file mode 100644 index 00000000..5961c1e5 --- /dev/null +++ b/vendor/truck-geometry/src/decorators/mod.rs @@ -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 { + curve: C, + revolution: Revolution, +} + +/// Linearly extruded curve +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct ExtrudedCurve { + curve: C, + vector: V, +} + +/// invertible and transformable geometric element +/// # Examples +/// Curve processing example +/// ``` +/// use truck_geometry::prelude::*; +/// let curve: BSplineCurve = 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 { + entity: E, + transform: T, + orientation: bool, +} + +/// The composited maps +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct PCurve { + curve: C, + surface: S, +} + +/// Intersection curve between two surfaces. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct IntersectionCurve { + // Considering rotational surfaces, we can consider the case + // where the class `S` holds the curve `C` as a variable. + surface0: Box, + surface1: Box, + leader: C, + tol: f64, +} + +/// trimmed curve for parametric curve +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct TrimmedCurve { + 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; diff --git a/vendor/truck-geometry/src/decorators/processor.rs b/vendor/truck-geometry/src/decorators/processor.rs new file mode 100644 index 00000000..3f44d3d8 --- /dev/null +++ b/vendor/truck-geometry/src/decorators/processor.rs @@ -0,0 +1,658 @@ +use super::*; + +impl Processor { + /// Creates new processor + #[inline(always)] + pub fn new(entity: E) -> Processor { + 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>(self, f: F) -> Processor { + 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>(&self, f: F) -> Processor + 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 + Invertible { + let mut res = self.entity; + res.transform_by(self.transform); + if !self.orientation { + res.invert(); + } + res + } +} + +impl Invertible for Processor { + #[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 Processor { + #[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 ParametricCurve for Processor +where + C: BoundedCurve, + C::Point: EuclideanSpace, + C::Vector: VectorSpace, + T: Transform + 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 { self.entity.period() } +} + +impl BoundedCurve for Processor +where + C: BoundedCurve, + C::Point: EuclideanSpace, + C::Vector: VectorSpace, + T: Transform + Clone, +{ +} + +impl Cut for Processor +where + C: BoundedCurve + Cut, + C::Point: EuclideanSpace, + C::Vector: VectorSpace, + T: Transform + 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 ParametricSurface for Processor +where + S: ParametricSurface, + S::Point: EuclideanSpace, + T: Transform + SquareMatrix + 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 { + match self.orientation { + true => self.entity.u_period(), + false => self.entity.v_period(), + } + } + #[inline(always)] + fn v_period(&self) -> Option { + match self.orientation { + true => self.entity.v_period(), + false => self.entity.u_period(), + } + } +} + +impl ParametricSurface3D for Processor +where + S: ParametricSurface3D, + T: Transform + SquareMatrix + 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 BoundedSurface for Processor +where + S: BoundedSurface, + T: Transform + SquareMatrix + Clone, +{ +} + +impl Deref for Processor { + type Target = E; + #[inline(always)] + fn deref(&self) -> &E { &self.entity } +} + +impl DerefMut for Processor { + #[inline(always)] + fn deref_mut(&mut self) -> &mut E { &mut self.entity } +} + +impl Transformed for Processor +where + T: Mul + 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 IncludeCurve for Processor +where + C: ParametricCurve + Transformed + Clone, + C::Point: EuclideanSpace, + E: IncludeCurve, + T: Transform, +{ + 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 ParameterDivision1D for Processor +where C: ParameterDivision1D + BoundedCurve +{ + type Point = Point2; + fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec, Vec) { + 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 ParameterDivision1D for Processor +where C: ParameterDivision1D + BoundedCurve +{ + type Point = Point3; + fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec, Vec) { + 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 ParameterDivision2D for Processor { + fn parameter_division( + &self, + range: ((f64, f64), (f64, f64)), + tol: f64, + ) -> (Vec, Vec) { + 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 ParameterDivision2D for Processor { + fn parameter_division( + &self, + range: ((f64, f64), (f64, f64)), + tol: f64, + ) -> (Vec, Vec) { + 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 SearchParameter for Processor +where + E: SearchParameter + BoundedCurve, + >::Point: EuclideanSpace, + T: Transform<>::Point>, +{ + type Point = >::Point; + fn search_parameter>( + &self, + point: >::Point, + hint: H, + trials: usize, + ) -> Option { + 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 SearchParameter for Processor +where + E: SearchParameter, + E::Point: EuclideanSpace, + T: Transform, +{ + type Point = E::Point; + fn search_parameter>( + &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 SearchNearestParameter for Processor +where + E: BoundedCurve + SearchNearestParameter, + P: EuclideanSpace, + E::Vector: InnerSpace + Tolerance, + T: Transform

+ Clone, +{ + type Point = P; + fn search_nearest_parameter>( + &self, + point: Self::Point, + hint: H, + trials: usize, + ) -> Option { + 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 SearchNearestParameter for Processor +where + E: ParametricSurface + SearchNearestParameter, + P: EuclideanSpace, + E::Vector: InnerSpace + Tolerance, + T: Transform

+ SquareMatrix + Clone, +{ + type Point = P; + fn search_nearest_parameter>( + &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 = (0..DEGREE + DIVISION) + .map(|i| Point3::new(i as f64, 20.0 * rand::random::() - 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::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 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> = (0..DEGREE + DIVISION) + .map(|i| { + (0..DEGREE + DIVISION) + .map(|j| Point3::new(i as f64, j as f64, 20.0 * rand::random::() - 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::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 2.0, + 4.0 * rand::random::() - 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()) } +} diff --git a/vendor/truck-geometry/src/decorators/revolved_curve.rs b/vendor/truck-geometry/src/decorators/revolved_curve.rs new file mode 100644 index 00000000..ee4bd7c6 --- /dev/null +++ b/vendor/truck-geometry/src/decorators/revolved_curve.rs @@ -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 ParametricSurface for RevolutedCurve { + 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 { self.curve.period() } + #[inline(always)] + fn v_period(&self) -> Option { Some(2.0 * PI) } +} + +impl ParametricSurface3D for RevolutedCurve { + #[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 BoundedSurface for RevolutedCurve {} + +impl Invertible for RevolutedCurve { + #[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 { + curve: C, + revolution: Revolution, +} + +impl ParametricCurve for ProjectedCurve { + 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 { self.curve.period() } +} + +impl BoundedCurve for ProjectedCurve {} + +impl SearchParameter for ProjectedCurve { + type Point = Point2; + fn search_parameter>( + &self, + point: Self::Point, + hint: H, + trials: usize, + ) -> Option { + 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 SearchNearestParameter for ProjectedCurve { + type Point = Point2; + fn search_nearest_parameter>( + &self, + point: Self::Point, + hint: H, + trials: usize, + ) -> Option { + 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 RevolutedCurve { + /// 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 RevolutedCurve { + /// 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 SearchParameter for RevolutedCurve { + type Point = Point3; + fn search_parameter>( + &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 SearchNearestParameter for RevolutedCurve { + type Point = Point3; + fn search_nearest_parameter>( + &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( + surface: &RevolutedCurve, + 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> for RevolutedCurve<&'a BSplineCurve> { + fn include(&self, curve: &BSplineCurve) -> 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> for RevolutedCurve> { + fn include(&self, curve: &BSplineCurve) -> 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> for RevolutedCurve<&'a NurbsCurve> { + fn include(&self, curve: &BSplineCurve) -> 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> for RevolutedCurve> { + fn include(&self, curve: &BSplineCurve) -> 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> for RevolutedCurve<&'a BSplineCurve> { + fn include(&self, curve: &NurbsCurve) -> 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> for RevolutedCurve> { + fn include(&self, curve: &NurbsCurve) -> 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> for RevolutedCurve<&'a NurbsCurve> { + fn include(&self, curve: &NurbsCurve) -> 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> for RevolutedCurve> { + fn include(&self, curve: &NurbsCurve) -> 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 ParameterDivision2D for RevolutedCurve +where C: ParametricCurve3D + ParameterDivision1D +{ + fn parameter_division( + &self, + (urange, vrange): ((f64, f64), (f64, f64)), + tol: f64, + ) -> (Vec, Vec) { + let curve_division = self.curve.parameter_division(urange, tol); + let max = curve_division + .1 + .into_iter() + .fold(0.0, |max2, pt| { + let h = self.revolution.proj_point(pt).y; + f64::max(max2, h) + }) + .sqrt(); + let acos = f64::acos(1.0 - tol / max); + let div: usize = 1 + ((vrange.1 - vrange.0) / acos).floor() as usize; + let circle_division = (0..=div) + .map(|j| vrange.0 + (vrange.1 - vrange.0) * j as f64 / div as f64) + .collect(); + (curve_division.0, circle_division) + } +} + +#[test] +fn revolve_test() { + let pt0 = Point3::new(0.0, 2.0, 1.0); + let pt1 = Point3::new(1.0, 0.0, 0.0); + let curve = BSplineCurve::new(KnotVec::bezier_knot(1), vec![pt0, pt1]); + let surface = RevolutedCurve::by_revolution(curve, Point3::origin(), Vector3::unit_y()); + const N: usize = 100; + for i in 0..=N { + for j in 0..=N { + let u = i as f64 / N as f64; + let v = 2.0 * PI * j as f64 / N as f64; + let res = surface.subs(u, v); + let ans = Point3::new( + u * f64::cos(v) + (1.0 - u) * f64::sin(v), + 2.0 * (1.0 - u), + -u * f64::sin(v) + (1.0 - u) * f64::cos(v), + ); + assert_near!(res, ans); + let res_uder = surface.uder(u, v); + let ans_uder = + Vector3::new(f64::cos(v) - f64::sin(v), -2.0, -f64::sin(v) - f64::cos(v)); + assert_near!(res_uder, ans_uder); + let res_vder = surface.vder(u, v); + let ans_vder = Vector3::new( + -u * f64::sin(v) + (1.0 - u) * f64::cos(v), + 0.0, + -u * f64::cos(v) - (1.0 - u) * f64::sin(v), + ); + assert_near!(res_vder, ans_vder); + let res_uuder = surface.uuder(u, v); + let ans_uuder = Vector3::zero(); + assert_near!(res_uuder, ans_uuder); + let res_uvder = surface.uvder(u, v); + let ans_uvder = + Vector3::new(-f64::sin(v) - f64::cos(v), 0.0, -f64::cos(v) + f64::sin(v)); + assert_near!(res_uvder, ans_uvder); + let res_vvder = surface.vvder(u, v); + let ans_vvder = Vector3::new( + -u * f64::cos(v) - (1.0 - u) * f64::sin(v), + 0.0, + u * f64::sin(v) - (1.0 - u) * f64::cos(v), + ); + assert_near!(res_vvder, ans_vvder); + let normal = surface.normal(u, v); + assert!(normal.dot(res_uder).so_small()); + assert!(normal.dot(res_vder).so_small()); + } + } +} + +#[test] +fn search_parameter() { + let line = BSplineCurve::new( + KnotVec::bezier_knot(1), + vec![Point3::new(0.0, 2.0, 1.0), Point3::new(1.0, 0.0, 0.0)], + ); + let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y()); + let pt = Point3::new(-0.5, 1.0, 0.5); + let (u, v) = surface.search_parameter(pt, Some((0.4, 1.2)), 100).unwrap(); + assert_near!(surface.subs(u, v), pt); +} + +#[test] +fn search_parameter_with_fixed_points() { + let line = BSplineCurve::new( + KnotVec::bezier_knot(2), + vec![ + Point3::new(0.0, 1.0, 0.0), + Point3::new(0.0, 0.0, 1.0), + Point3::new(0.0, -1.0, 0.0), + ], + ); + let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y()); + + let (u, v) = surface + .search_parameter(Point3::new(0.0, 1.0, 0.0), Some((0.5, 0.3)), 10) + .unwrap(); + assert_near!(Vector2::new(u, v), Vector2::new(0.0, 0.3)); + + let (u, v) = surface + .search_parameter(Point3::new(0.0, -1.0, 0.0), Some((0.5, 0.3)), 10) + .unwrap(); + assert_near!(Vector2::new(u, v), Vector2::new(1.0, 0.3)); +} + +#[test] +fn search_nearest_parameter() { + let line = BSplineCurve::new( + KnotVec::bezier_knot(1), + vec![Point3::new(0.0, 2.0, 1.0), Point3::new(1.0, 0.0, 0.0)], + ); + let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y()); + let pt = surface.subs(0.4, 1.2) + 0.1 * surface.normal(0.4, 1.2); + let (u, v) = surface + .search_nearest_parameter(pt, Some((0.4, 1.2)), 100) + .unwrap(); + assert_near!(Vector2::new(u, v), Vector2::new(0.4, 1.2)); +} + +#[test] +fn search_nearest_parameter_with_fixed_points() { + let line = BSplineCurve::new( + KnotVec::bezier_knot(2), + vec![ + Point3::new(0.0, 1.0, 0.0), + Point3::new(0.0, 0.0, 1.0), + Point3::new(0.0, -1.0, 0.0), + ], + ); + let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y()); + + let (u, v) = surface + .search_nearest_parameter(Point3::new(0.0, 2.0, 0.0), Some((0.5, 0.3)), 10) + .unwrap(); + assert_near!(Vector2::new(u, v), Vector2::new(0.0, 0.3)); + + let (u, v) = surface + .search_nearest_parameter(Point3::new(0.0, -2.0, 0.0), Some((0.5, 0.3)), 10) + .unwrap(); + assert_near!(Vector2::new(u, v), Vector2::new(1.0, 0.3)); +} + +#[test] +fn include_curve_normal() { + let line = BSplineCurve::new( + KnotVec::bezier_knot(1), + vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 2.0, 2.0)], + ); + let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y()); + let parabola = BSplineCurve::new( + KnotVec::bezier_knot(2), + vec![ + Point3::new(1.0, 1.0, 0.0), + Point3::new(0.0, 0.0, 1.0), + Point3::new(-1.0, 1.0, 0.0), + ], + ); + assert!(surface.include(¶bola)); +} + +#[test] +fn include_curve_abnormal0() { + let line = BSplineCurve::new( + KnotVec::bezier_knot(1), + vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 2.0, 2.0)], + ); + let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y()); + let parabola = BSplineCurve::new( + KnotVec::bezier_knot(2), + vec![ + Point3::new(1.0, 1.0, 0.0), + Point3::new(0.0, 0.0, 2.0), + Point3::new(-1.0, 1.0, 0.0), + ], + ); + assert!(!surface.include(¶bola)); +} + +#[test] +fn include_curve_abnormal1() { + let curve = NurbsCurve::new(BSplineCurve::new( + KnotVec::bezier_knot(3), + vec![ + Vector4::new(0.0, 3.0, 0.0, 1.0), + Vector4::new(0.0, 3.0, 3.0, 0.5), + Vector4::new(0.0, 0.0, 0.0, 1.0), + Vector4::new(0.0, 0.0, 3.0, 1.0), + ], + )); + let pt0 = curve.subs(0.2); + let pt1 = curve.subs(0.6); + let surface = RevolutedCurve::by_revolution(curve, Point3::origin(), Vector3::unit_y()); + let line = BSplineCurve::new(KnotVec::bezier_knot(1), vec![pt0, pt1]); + assert!(!surface.include(&line)); +} diff --git a/vendor/truck-geometry/src/decorators/trimmied_curve.rs b/vendor/truck-geometry/src/decorators/trimmied_curve.rs new file mode 100644 index 00000000..708d67a6 --- /dev/null +++ b/vendor/truck-geometry/src/decorators/trimmied_curve.rs @@ -0,0 +1,73 @@ +use super::*; + +impl TrimmedCurve { + /// 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 ParametricCurve for TrimmedCurve { + 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 { self.curve.period() } + #[inline(always)] + fn parameter_range(&self) -> ParameterRange { + (Bound::Included(self.range.0), Bound::Included(self.range.1)) + } +} + +impl BoundedCurve for TrimmedCurve {} + +impl Cut for TrimmedCurve { + fn cut(&mut self, t: f64) -> Self { + let (t0, t1) = self.range; + self.range = (t0, t); + Self::new(self.curve.clone(), (t, t1)) + } +} + +impl> SearchNearestParameter for TrimmedCurve { + type Point = C::Point; + #[inline(always)] + fn search_nearest_parameter>( + &self, + pt: C::Point, + hint: H, + trials: usize, + ) -> Option { + self.curve.search_nearest_parameter(pt, hint, trials) + } +} + +impl> SearchParameter for TrimmedCurve { + type Point = C::Point; + #[inline(always)] + fn search_parameter>( + &self, + pt: C::Point, + hint: H, + trials: usize, + ) -> Option { + self.curve.search_parameter(pt, hint, trials) + } +} + +impl ParameterDivision1D for TrimmedCurve { + type Point = C::Point; + fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec, Vec) { + self.curve.parameter_division(range, tol) + } +} diff --git a/vendor/truck-geometry/src/errors.rs b/vendor/truck-geometry/src/errors.rs new file mode 100644 index 00000000..5d1978e8 --- /dev/null +++ b/vendor/truck-geometry/src/errors.rs @@ -0,0 +1,190 @@ +use thiserror::Error; + +/// Error handler for [`Error`](./errors/enum.Error.html) +pub type Result = std::result::Result; + +/// 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. + /// `>>::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 + /// `>>::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!( + /// >>::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 = 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::::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(); +} diff --git a/vendor/truck-geometry/src/lib.rs b/vendor/truck-geometry/src/lib.rs new file mode 100644 index 00000000..50ac5ff2 --- /dev/null +++ b/vendor/truck-geometry/src/lib.rs @@ -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::*; +} diff --git a/vendor/truck-geometry/src/nurbs/bspcurve.rs b/vendor/truck-geometry/src/nurbs/bspcurve.rs new file mode 100644 index 00000000..50eb46bd --- /dev/null +++ b/vendor/truck-geometry/src/nurbs/bspcurve.rs @@ -0,0 +1,1488 @@ +use super::*; +use crate::errors::Error; +use std::ops::*; + +impl

BSplineCurve

{ + /// constructor. + /// # Arguments + /// * `knot_vec` - the knot vector + /// * `control_points` - the vector of the control points + /// # Panics + /// Panics occurs if: + /// * There are no control points. + /// * The number of knots is more than the one of control points. + /// * The range of the knot vector is zero. + pub fn new(knot_vec: KnotVec, control_points: Vec

) -> BSplineCurve

{ + BSplineCurve::try_new(knot_vec, control_points).unwrap_or_else(|e| panic!("{}", e)) + } + + /// constructor. + /// # Arguments + /// * `knot_vec` - the knot vector + /// * `control_points` - the vector of the control points + /// # Failures + /// * If there are no control points, returns [`Error::EmptyControlPoints`]. + /// * If the number of knots is more than the one of control points, returns [`Error::TooShortKnotVector`]. + /// * If the range of the knot vector is zero, returns [`Error::ZeroRange`]. + /// + /// [`Error::EmptyControlPoints`]: errors/enum.Error.html#variant.EmptyControlPoints + /// [`Error::TooShortKnotVector`]: errors/enum.Error.html#variant.TooShortKnotVector + /// [`Error::ZeroRange`]: errors/enum.Error.html#variant.ZeroRange + pub fn try_new(knot_vec: KnotVec, control_points: Vec

) -> Result> { + if control_points.is_empty() { + Err(Error::EmptyControlPoints) + } else if knot_vec.len() <= control_points.len() { + Err(Error::TooShortKnotVector( + knot_vec.len(), + control_points.len(), + )) + } else if knot_vec.range_length().so_small() { + Err(Error::ZeroRange) + } else { + Ok(BSplineCurve::new_unchecked(knot_vec, control_points)) + } + } + + /// constructor. + /// # Arguments + /// * `knot_vec` - the knot vector + /// * `control_points` - the vector of the control points + /// # Remarks + /// This method is prepared only for performance-critical development and is not recommended. + /// This method does NOT check the rules for constructing B-spline curve. + /// The programmer must guarantee these conditions before using this method. + #[inline(always)] + pub const fn new_unchecked(knot_vec: KnotVec, control_points: Vec

) -> BSplineCurve

{ + Self { + knot_vec, + control_points, + } + } + + /// constructor. + /// # Arguments + /// * `knot_vec` - the knot vector + /// * `control_points` - the vector of the control points + /// # Remarks + /// This method checks the rules for constructing B-spline curve in the debug mode. + /// The programmer must guarantee these conditions before using this method. + #[inline(always)] + pub fn debug_new(knot_vec: KnotVec, control_points: Vec

) -> BSplineCurve

{ + match cfg!(debug_assertions) { + true => Self::new(knot_vec, control_points), + false => Self::new_unchecked(knot_vec, control_points), + } + } + + /// Returns the reference of the knot vector + #[inline(always)] + pub const fn knot_vec(&self) -> &KnotVec { &self.knot_vec } + + /// Returns the `idx`th knot + #[inline(always)] + pub fn knot(&self, idx: usize) -> f64 { self.knot_vec[idx] } + + /// Returns the reference of the control points. + #[inline(always)] + pub const fn control_points(&self) -> &Vec

{ &self.control_points } + + /// Returns the reference of the control point corresponding to the index `idx`. + #[inline(always)] + pub fn control_point(&self, idx: usize) -> &P { &self.control_points[idx] } + + /// Returns the mutable reference of the control point corresponding to index `idx`. + #[inline(always)] + pub fn control_point_mut(&mut self, idx: usize) -> &mut P { &mut self.control_points[idx] } + /// Returns the iterator on all control points + #[inline(always)] + pub fn control_points_mut(&mut self) -> impl Iterator { + self.control_points.iter_mut() + } + + /// Apply the given transformation to all control points. + #[inline(always)] + pub fn transform_control_points(&mut self, f: F) { + self.control_points.iter_mut().for_each(f) + } + + /// Returns the degree of B-spline curve + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec = KnotVec::bezier_knot(2); + /// let ctrl_pts = vec![Vector2::new(1.0, 2.0), Vector2::new(2.0, 3.0), Vector2::new(3.0, 4.0)]; + /// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// assert_eq!(bspcurve.degree(), 2); + /// ``` + #[inline(always)] + pub fn degree(&self) -> usize { self.knot_vec.len() - self.control_points.len() - 1 } + /// Inverts a curve + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec = KnotVec::uniform_knot(2, 2); + /// let ctrl_pts = vec![Vector2::new(1.0, 2.0), Vector2::new(2.0, 3.0), Vector2::new(3.0, 4.0), Vector2::new(4.0, 5.0)]; + /// let bspcurve0 = BSplineCurve::new(knot_vec, ctrl_pts); + /// let mut bspcurve1 = bspcurve0.clone(); + /// bspcurve1.invert(); + /// + /// const N: usize = 100; // sample size + /// for i in 0..=N { + /// let t = (i as f64) / (N as f64); + /// assert_near2!(bspcurve0.subs(t), bspcurve1.subs(1.0 - t)); + /// } + /// ``` + #[inline(always)] + pub fn invert(&mut self) -> &mut Self { + self.knot_vec.invert(); + self.control_points.reverse(); + self + } + + /// Returns whether the knot vector is clamped or not. + #[inline(always)] + pub fn is_clamped(&self) -> bool { self.knot_vec.is_clamped(self.degree()) } + + /// Normalizes the knot vector + #[inline(always)] + pub fn knot_normalize(&mut self) -> &mut Self { + self.knot_vec.try_normalize().unwrap(); + self + } + + /// Translates the knot vector + #[inline(always)] + pub fn knot_translate(&mut self, x: f64) -> &mut Self { + self.knot_vec.translate(x); + self + } +} + +impl> BSplineCurve

{ + /// Returns the closure of substitution. + /// # Examples + /// The following test code is the same test with the one of `BSplineCurve::subs()`. + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec = KnotVec::from(vec![-1.0, -1.0, -1.0, 1.0, 1.0, 1.0]); + /// let ctrl_pts = vec![Vector2::new(-1.0, 1.0), Vector2::new(0.0, -1.0), Vector2::new(1.0, 1.0)]; + /// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// + /// const N: usize = 100; // sample size + /// let get_t = |i: usize| -1.0 + 2.0 * (i as f64) / (N as f64); + /// let res: Vec<_> = (0..=N).map(get_t).map(bspcurve.get_closure()).collect(); + /// let ans: Vec<_> = (0..=N).map(get_t).map(|t| Vector2::new(t, t * t)).collect(); + /// res.iter().zip(&ans).for_each(|(v0, v1)| assert_near2!(v0, v1)); + /// ``` + #[inline(always)] + pub fn get_closure(&self) -> impl Fn(f64) -> P + '_ { move |t| self.subs(t) } + #[inline(always)] + fn delta_control_points(&self, i: usize) -> P::Diff { + if i == 0 { + self.control_point(i).to_vec() + } else if i == self.control_points.len() { + self.control_points[i - 1].to_vec() * (-1.0) + } else { + self.control_points[i] - self.control_points[i - 1] + } + } + #[inline(always)] + fn delta2_control_points(&self, i: usize) -> P::Diff { + let k = self.degree(); + let knot_vec = self.knot_vec(); + if i == 0 { + let coef = inv_or_zero(knot_vec[i + k] - knot_vec[i]); + self.delta_control_points(i) * coef + } else if i == self.control_points.len() + 1 { + let coef = inv_or_zero(knot_vec[i - 1 + k] - knot_vec[i - 1]); + self.delta_control_points(i - 1) * (-coef) + } else { + let coef0 = inv_or_zero(knot_vec[i + k] - knot_vec[i]); + let coef1 = inv_or_zero(knot_vec[i - 1 + k] - knot_vec[i - 1]); + self.delta_control_points(i) * coef0 - self.delta_control_points(i - 1) * coef1 + } + } + /// Returns the derived B-spline curve. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec = KnotVec::bezier_knot(2); + /// let ctrl_pts = vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, 0.0), Vector2::new(1.0, 1.0)]; + /// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// let derived = bspcurve.derivation(); + /// + /// // `bpscurve = (t, t^2), derived = (1, 2t)` + /// const N : usize = 100; // sample size + /// for i in 0..=N { + /// let t = 1.0 / (N as f64) * (i as f64); + /// assert_near2!(derived.subs(t), Vector2::new(1.0, 2.0 * t)); + /// } + /// ``` + pub fn derivation(&self) -> BSplineCurve { + let n = self.control_points.len(); + let k = self.degree(); + let knot_vec = self.knot_vec.clone(); + let mut new_points = Vec::with_capacity(n + 1); + if k > 0 { + let (knot_vec, new_points) = (&knot_vec, &mut new_points); + (0..=n).for_each(move |i| { + let delta = knot_vec[i + k] - knot_vec[i]; + let coef = (k as f64) * inv_or_zero(delta); + new_points.push(self.delta_control_points(i) * coef); + }); + } else { + new_points = vec![P::Diff::zero(); n]; + } + BSplineCurve::new_unchecked(knot_vec, new_points) + } + pub(super) fn sub_near_as_curve bool>( + &self, + other: &BSplineCurve

, + div_coef: usize, + ord: F, + ) -> bool { + if !self.knot_vec.same_range(&other.knot_vec) { + return false; + } + + let division = std::cmp::max(self.degree(), other.degree()) * div_coef; + for i in 0..(self.knot_vec.len() - 1) { + let delta = self.knot_vec[i + 1] - self.knot_vec[i]; + if delta.so_small() { + continue; + } + + for j in 0..division { + let t = self.knot_vec[i] + delta * (j as f64) / (division as f64); + if !ord(&self.subs(t), &other.subs(t)) { + return false; + } + } + } + true + } +} + +impl> BSplineCurve { + /// lift up control points to homogeneous coordinate. + pub fn lift_up(curve: BSplineCurve) -> Self { + let control_points = curve + .control_points + .into_iter() + .map(V::from_point) + .collect(); + BSplineCurve::new_unchecked(curve.knot_vec, control_points) + } +} + +impl> ParametricCurve for BSplineCurve

{ + type Point = P; + type Vector = P::Diff; + /// Substitutes to B-spline curve. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec = KnotVec::from(vec![-1.0, -1.0, -1.0, 1.0, 1.0, 1.0]); + /// let ctrl_pts = vec![Vector2::new(-1.0, 1.0), Vector2::new(0.0, -1.0), Vector2::new(1.0, 1.0)]; + /// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// + /// // bspcurve coincides with (t, t * t) in the range [-1.0..1.0]. + /// const N: usize = 100; // sample size + /// for i in 0..=N { + /// let t = -1.0 + 2.0 * (i as f64) / (N as f64); + /// assert_near2!(bspcurve.subs(t), Vector2::new(t, t * t)); + /// } + /// ``` + #[inline(always)] + fn subs(&self, t: f64) -> P { + let basis = self + .knot_vec + .try_bspline_basis_functions(self.degree(), t) + .unwrap(); + self.control_points + .iter() + .zip(basis) + .fold(P::origin(), |sum, (pt, basis)| sum + pt.to_vec() * basis) + } + /// Substitutes to the derived B-spline curve. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec = KnotVec::bezier_knot(2); + /// let ctrl_pts = vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, 0.0), Vector2::new(1.0, 1.0)]; + /// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// + /// // `bpscurve = (t, t^2), derived = (1, 2t)` + /// const N : usize = 100; // sample size + /// for i in 0..=N { + /// let t = 1.0 / (N as f64) * (i as f64); + /// assert_near2!(bspcurve.der(t), Vector2::new(1.0, 2.0 * t)); + /// } + /// ``` + #[inline(always)] + fn der(&self, t: f64) -> P::Diff { + let k = self.degree(); + let knot_vec = self.knot_vec(); + let closure = move |sum: P::Diff, (i, b): (usize, f64)| { + let coef = inv_or_zero(knot_vec[i + k] - knot_vec[i]); + sum + self.delta_control_points(i) * b * coef + }; + knot_vec + .bspline_basis_functions(k - 1, t) + .into_iter() + .enumerate() + .fold(P::Diff::zero(), closure) + * k as f64 + } + /// Substitutes to the 2nd-ord derived B-spline curve. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec = KnotVec::bezier_knot(3); + /// let ctrl_pts = vec![ + /// Vector2::new(0.0, 0.0), + /// Vector2::new(1.0, 1.0), + /// Vector2::new(0.0, 1.0), + /// Vector2::new(1.0, 0.0), + /// ]; + /// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// + /// // bpscurve = (4t^3 - 6t^2 + 3t, -3t^2 + 3t), derived2 = (24t - 12, -6) + /// const N : usize = 100; // sample size + /// for i in 0..=N { + /// let t = 1.0 / (N as f64) * (i as f64); + /// assert_near2!(bspcurve.der2(t), Vector2::new(24.0 * t - 12.0, -6.0)); + /// } + /// ``` + #[inline(always)] + fn der2(&self, t: f64) -> P::Diff { + let k = self.degree(); + if k < 2 { + return P::Diff::zero(); + } + let knot_vec = self.knot_vec(); + let closure = move |sum: P::Diff, (i, b): (usize, f64)| { + let coef = inv_or_zero(knot_vec[i + k - 1] - knot_vec[i]); + sum + self.delta2_control_points(i) * b * coef + }; + knot_vec + .bspline_basis_functions(k - 2, t) + .into_iter() + .enumerate() + .fold(P::Diff::zero(), closure) + * k as f64 + * (k - 1) as f64 + } + #[inline(always)] + fn parameter_range(&self) -> ParameterRange { + ( + Bound::Included(self.knot_vec[0]), + Bound::Included(self.knot_vec[self.knot_vec.len() - 1]), + ) + } +} + +impl> BoundedCurve for BSplineCurve

{} + +impl + Tolerance> BSplineCurve

{ + /// 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 = Vector2::new(1.0, 2.0); + /// let mut ctrl_pts = vec![pt.clone(), pt.clone(), pt.clone()]; + /// let const_bspcurve = BSplineCurve::new(knot_vec.clone(), ctrl_pts.clone()); + /// assert!(const_bspcurve.is_const()); + /// + /// ctrl_pts.push(Vector2::new(2.0, 3.0)); + /// let bspcurve = BSplineCurve::new(knot_vec.clone(), ctrl_pts.clone()); + /// assert!(!bspcurve.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 { + self.control_points + .iter() + .all(move |vec| vec.near(&self.control_points[0])) + } + + /// Adds a knot `x`, and do not change `self` as a curve. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// 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(); + /// + /// // add 4 knots + /// bspcurve.add_knot(0.5).add_knot(0.5).add_knot(0.25).add_knot(0.75); + /// assert_eq!(bspcurve.knot_vec().len(), org_curve.knot_vec().len() + 4); + /// // bspcurve does not change as a curve + /// assert!(bspcurve.near2_as_curve(&org_curve)); + /// ``` + /// # Remarks + /// If the added knot `x` is out of the range of the knot vector, then the knot vector will extended. + /// ``` + /// use truck_geometry::prelude::*; + /// 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); + /// assert_eq!(bspcurve.knot_vec().range_length(), 1.0); + /// assert_eq!(bspcurve.front(), Vector2::new(-1.0, 1.0)); + /// assert_eq!(bspcurve.back(), Vector2::new(1.0, 1.0)); + /// + /// // add knots out of the range of the knot vectors. + /// bspcurve.add_knot(-1.0).add_knot(2.0); + /// assert_eq!(bspcurve.knot_vec().range_length(), 3.0); + /// assert_eq!(bspcurve.front(), Vector2::new(0.0, 0.0)); + /// assert_eq!(bspcurve.back(), Vector2::new(0.0, 0.0)); + /// ``` + pub fn add_knot(&mut self, x: f64) -> &mut Self { + if x < self.knot_vec[0] { + self.knot_vec.add_knot(x); + self.control_points.insert(0, P::origin()); + return self; + } + + let k = self.degree(); + let n = self.control_points.len(); + + let idx = self.knot_vec.add_knot(x); + let start = if idx > k { idx - k } else { 0 }; + let end = if idx > n { + self.control_points.push(P::origin()); + n + 1 + } else { + self.control_points + .insert(idx - 1, *self.control_point(idx - 1)); + idx + }; + for i in start..end { + let i0 = end + start - i - 1; + let delta = self.knot_vec[i0 + k + 1] - self.knot_vec[i0]; + let a = (self.knot_vec[idx] - self.knot_vec[i0]) * inv_or_zero(delta); + let p = self.delta_control_points(i0) * (1.0 - a); + self.control_points[i0] -= p; + } + 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`. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// 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(); + /// + /// // add knots and remove them. + /// bspcurve.add_knot(0.5).add_knot(0.5).add_knot(0.25).add_knot(0.75); + /// bspcurve.remove_knot(3).remove_knot(3).remove_knot(3).remove_knot(3); + /// assert!(bspcurve.near2_as_curve(&org_curve)); + /// assert_eq!(bspcurve.knot_vec().len(), org_curve.knot_vec().len()) + /// ``` + 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`](./errors/enum.Error.html#variant.CannotRemoveKnot). + /// # 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_eq!(bspcurve.try_remove_knot(2), Err(Error::CannotRemoveKnot(2))); + /// ``` + pub fn try_remove_knot(&mut self, idx: usize) -> Result<&mut BSplineCurve

> { + let k = self.degree(); + let n = self.control_points.len(); + let knot_vec = &self.knot_vec; + + if idx < k + 1 || idx >= n { + return Err(Error::CannotRemoveKnot(idx)); + } + + let mut new_points = Vec::with_capacity(k + 1); + new_points.push(*self.control_point(idx - k - 1)); + for i in (idx - k)..idx { + let delta = knot_vec[i + k + 1] - knot_vec[i]; + let a = inv_or_zero(delta) * (knot_vec[idx] - knot_vec[i]); + if a.so_small() { + break; + } else { + let p = *new_points.last().unwrap(); + let p = p + (self.control_points[i] - p) / a; + new_points.push(p); + } + } + + if !new_points.last().unwrap().near(self.control_point(idx)) { + return Err(Error::CannotRemoveKnot(idx)); + } + + for (i, vec) in new_points.into_iter().skip(1).enumerate() { + self.control_points[idx - k + i] = vec; + } + + self.control_points.remove(idx); + self.knot_vec.remove(idx); + Ok(self) + } + + /// elevate 1 degree for bezier curve. + fn elevate_degree_bezier(&mut self) -> &mut Self { + let k = self.degree(); + self.knot_vec.add_knot(self.knot_vec[0]); + self.knot_vec + .add_knot(self.knot_vec[self.knot_vec.len() - 1]); + self.control_points.push(P::origin()); + for i in 0..=(k + 1) { + let i0 = k + 1 - i; + let a = (i0 as f64) / ((k + 1) as f64); + let p = self.delta_control_points(i0) * a; + self.control_points[i0] -= p; + } + self + } + + /// elevate 1 degree. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec = KnotVec::bezier_knot(1); + /// let ctrl_pts = vec![Vector2::new(0.0, 0.0), Vector2::new(1.0, 1.0)]; + /// let mut bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// bspcurve.elevate_degree(); + /// assert_eq!(bspcurve.degree(), 2); + /// assert_eq!(bspcurve.knot_vec(), &KnotVec::bezier_knot(2)); + /// assert_eq!(bspcurve.control_point(1), &Vector2::new(0.5, 0.5)); + /// ``` + pub fn elevate_degree(&mut self) -> &mut Self { + let mut result = CurveCollector::Singleton; + for mut bezier in self.bezier_decomposition() { + result.concat(bezier.elevate_degree_bezier()); + } + *self = result.unwrap(); + self + } + + /// Makes the B-spline curve clamped + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec = KnotVec::from(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]); + /// let ctrl_pts = vec![Vector2::new(0.0, 1.0), Vector2::new(1.0, 2.0), Vector2::new(2.0, 3.0)]; + /// let mut bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// assert!(!bspcurve.is_clamped()); + /// bspcurve.clamp(); + /// assert!(bspcurve.is_clamped()); + /// assert_eq!(bspcurve.knot_vec().len(), 10); + /// ``` + #[inline(always)] + pub fn clamp(&mut self) -> &mut Self { + let degree = self.degree(); + + let s = self.knot_vec.multiplicity(0); + for _ in s..=degree { + self.add_knot(self.knot_vec[0]); + } + + let n = self.knot_vec.len(); + let s = self.knot_vec.multiplicity(n - 1); + for _ in s..=degree { + self.add_knot(self.knot_vec[n - 1]); + } + self + } + + /// Repeats `Self::try_remove_knot()` from the back knot in turn until the knot cannot be removed. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// + /// let knot_vec = KnotVec::bezier_knot(2); + /// let ctrl_pts = vec![Vector2::new(1.0, 2.0), Vector2::new(2.0, 3.0), Vector2::new(3.0, 4.0)]; + /// let mut bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// let org_curve = bspcurve.clone(); + /// + /// // add 4 new knots + /// bspcurve.add_knot(0.5).add_knot(0.5).add_knot(0.25).add_knot(0.75); + /// assert_eq!(bspcurve.knot_vec().len(), KnotVec::bezier_knot(2).len() + 4); + /// + /// // By the optimization, added knots are removed. + /// bspcurve.optimize(); + /// assert_eq!(bspcurve.knot_vec(), &KnotVec::bezier_knot(2)); + /// assert!(bspcurve.near2_as_curve(&org_curve)); + /// ``` + pub fn optimize(&mut self) -> &mut Self { + loop { + let n = self.knot_vec.len(); + let closure = |flag, i| flag && self.try_remove_knot(n - i).is_err(); + if (1..=n).fold(true, closure) { + break; + } + } + self + } + + /// Makes two splines having the same degrees. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// + /// let knot_vec0 = KnotVec::bezier_knot(1); + /// let ctrl_pts0 = vec![Vector2::new(1.0, 2.0), Vector2::new(2.0, 3.0)]; + /// let mut bspcurve0 = BSplineCurve::new(knot_vec0, ctrl_pts0); + /// let knot_vec1 = KnotVec::bezier_knot(2); + /// let ctrl_pts1 = vec![Vector2::new(1.0, 2.0), Vector2::new(2.0, 3.0), Vector2::new(3.0, 4.0)]; + /// let mut bspcurve1 = BSplineCurve::new(knot_vec1, ctrl_pts1); + /// assert_ne!(bspcurve0.degree(), bspcurve1.degree()); + /// + /// let org_curve0 = bspcurve0.clone(); + /// let org_curve1 = bspcurve1.clone(); + /// bspcurve0.syncro_degree(&mut bspcurve1); + /// assert_eq!(bspcurve0.degree(), bspcurve1.degree()); + /// assert!(bspcurve0.near2_as_curve(&org_curve0)); + /// assert!(bspcurve1.near2_as_curve(&org_curve1)); + /// ``` + 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. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// + /// let knot_vec0 = KnotVec::from(vec![0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0]); + /// let ctrl_pts0 = vec![Vector2::new(0.0, 0.0), Vector2::new(1.0, 1.0), Vector2::new(2.0, 2.0), Vector2::new(3.0, 3.0)]; + /// let mut bspcurve0 = BSplineCurve::new(knot_vec0, ctrl_pts0); + /// let mut org_curve0 = bspcurve0.clone(); + /// let knot_vec1 = KnotVec::from(vec![0.0, 0.0, 1.0, 3.0, 4.0, 4.0]); + /// let ctrl_pts1 = vec![Vector2::new(0.0, 0.0), Vector2::new(1.0, 1.0), Vector2::new(2.0, 2.0), Vector2::new(3.0, 3.0)]; + /// let mut bspcurve1 = BSplineCurve::new(knot_vec1, ctrl_pts1); + /// let mut org_curve1 = bspcurve1.clone(); + /// + /// bspcurve0.syncro_knots(&mut bspcurve1); + /// + /// // The knot vectors are made the same. + /// assert_eq!(bspcurve0.knot_vec(), bspcurve1.knot_vec()); + /// assert_eq!( + /// bspcurve0.knot_vec().as_slice(), + /// &[0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0] + /// ); + /// // The degrees are not changed. + /// assert_eq!(bspcurve0.degree(), org_curve0.degree()); + /// assert_eq!(bspcurve1.degree(), org_curve1.degree()); + /// // The knot vector is normalized, however, the shape of curve is not changed. + /// assert!(bspcurve0.near2_as_curve(org_curve0.knot_normalize())); + /// assert!(bspcurve1.near2_as_curve(org_curve1.knot_normalize())); + /// ``` + pub fn syncro_knots(&mut self, other: &mut BSplineCurve

) { + self.knot_normalize(); + other.knot_normalize(); + + let mut i = 0; + let mut j = 0; + while !self.knot(i).near2(&1.0) || !other.knot(j).near2(&1.0) { + if self.knot(i) - other.knot(j) > TOLERANCE { + self.add_knot(other.knot(j)); + } else if other.knot(j) - self.knot(i) > TOLERANCE { + other.add_knot(self.knot(i)); + } + i += 1; + j += 1; + } + + use std::cmp::Ordering; + match usize::cmp(&self.knot_vec.len(), &other.knot_vec.len()) { + Ordering::Less => { + (0..(other.knot_vec.len() - self.knot_vec.len())).for_each(|_| { + self.add_knot(1.0); + }); + } + Ordering::Greater => (0..(self.knot_vec.len() - other.knot_vec.len())).for_each(|_| { + other.add_knot(1.0); + }), + _ => {} + } + } + + /// Separates `self` into Bezier curves by each knots. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// + /// let knot_vec = KnotVec::uniform_knot(2, 2); + /// let ctrl_pts = vec![Vector2::new(0.0, 1.0), Vector2::new(1.0, 2.0), Vector2::new(2.0, 3.0), Vector2::new(3.0, 4.0)]; + /// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// let beziers = bspcurve.bezier_decomposition(); + /// + /// const N: usize = 100; + /// for i in 0..=N { + /// let t = 0.5 * (i as f64) / (N as f64); + /// assert_near2!(bspcurve.subs(t), beziers[0].subs(t)); + /// } + /// for i in 0..=N { + /// let t = 0.5 + 0.5 * (i as f64) / (N as f64); + /// assert_near2!(bspcurve.subs(t), beziers[1].subs(t)); + /// } + /// ``` + pub fn bezier_decomposition(&self) -> Vec> { + let mut bspline = self.clone(); + bspline.clamp(); + let (knots, _) = self.knot_vec.to_single_multi(); + let n = knots.len(); + + let mut result = Vec::new(); + for i in 2..n { + result.push(bspline.cut(knots[n - i])); + } + result.push(bspline); + result.reverse(); + result + } + + /// Makes the 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 ctrl_pts = vec![ + /// Vector3::new(1.0, 0.0, 0.0), + /// Vector3::new(0.0, 1.0, 0.0), + /// Vector3::new(0.0, 1.0, 0.0), + /// Vector3::new(0.0, 1.0, 0.0), + /// Vector3::new(0.0, 0.0, 1.0), + /// ]; + /// + /// let mut bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// let mut flag = false; + /// for i in 0..=N { + /// let t = 4.0 * (i as f64) / (N as f64); + /// flag = flag || bspcurve.subs(t).near(&bspcurve.subs(t + 1.0 / (N as f64))); + /// } + /// // There exists t such that bspcurve(t) == bspcurve(t + 0.01). + /// assert!(flag); + /// + /// bspcurve.make_locally_injective().knot_normalize(); + /// let mut flag = false; + /// for i in 0..=N { + /// let t = 1.0 * (i as f64) / (N as f64); + /// flag = flag || bspcurve.subs(t).near(&bspcurve.subs(t + 1.0 / (N as f64))); + /// } + /// // 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![Vector2::new(1.0, 1.0); 4]; + /// let mut bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// let org_curve = bspcurve.clone(); + /// bspcurve.make_locally_injective(); + /// assert_eq!(bspcurve, org_curve); + /// ``` + pub fn make_locally_injective(&mut self) -> &mut Self { + let mut iter = self.bezier_decomposition().into_iter(); + for bezier in iter.by_ref() { + if !bezier.is_const() { + *self = bezier; + break; + } + } + let mut x = 0.0; + for mut bezier in iter { + if bezier.is_const() { + x += bezier.knot_vec.range_length(); + } else { + self.concat(bezier.knot_translate(-x)); + } + } + self + } + /// Determine 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![ + /// Vector2::new(1.0, 1.0), + /// Vector2::new(3.0, 2.0), + /// Vector2::new(2.0, 3.0), + /// Vector2::new(4.0, 5.0), + /// Vector2::new(5.0, 4.0), + /// Vector2::new(1.0, 1.0), + /// ]; + /// let bspcurve0 = BSplineCurve::new(knot_vec, ctrl_pts); + /// let mut bspcurve1 = bspcurve0.clone(); + /// assert!(bspcurve0.near_as_curve(&bspcurve1)); + /// *bspcurve1.control_point_mut(1) += Vector2::new(0.01, 0.0002); + /// assert!(!bspcurve0.near_as_curve(&bspcurve1)); + /// ``` + #[inline(always)] + pub fn near_as_curve(&self, other: &BSplineCurve

) -> bool { + self.sub_near_as_curve(other, 1, |x, y| x.near(y)) + } + + /// 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 eps = TOLERANCE; + /// 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![ + /// Vector2::new(1.0, 1.0), + /// Vector2::new(3.0, 2.0), + /// Vector2::new(2.0, 3.0), + /// Vector2::new(4.0, 5.0), + /// Vector2::new(5.0, 4.0), + /// Vector2::new(1.0, 1.0), + /// ]; + /// let bspcurve0 = BSplineCurve::new(knot_vec, ctrl_pts); + /// let mut bspcurve1 = bspcurve0.clone(); + /// assert!(bspcurve0.near_as_curve(&bspcurve1)); + /// *bspcurve1.control_point_mut(1) += Vector2::new(eps, 0.0); + /// assert!(!bspcurve0.near2_as_curve(&bspcurve1)); + /// ``` + #[inline(always)] + pub fn near2_as_curve(&self, other: &BSplineCurve

) -> bool { + self.sub_near_as_curve(other, 1, |x, y| x.near2(y)) + } +} + +impl> ParameterTransform for BSplineCurve

{ + #[inline(always)] + fn parameter_transform(&mut self, scalar: f64, r#move: f64) -> &mut Self { + self.knot_vec.transform(scalar, r#move); + self + } +} + +#[test] +fn parameter_transform_random_test() { + let curve = BSplineCurve::new( + KnotVec::uniform_knot(4, 4), + (0..8) + .map(|_| { + Point3::new( + rand::random::(), + rand::random::(), + rand::random::(), + ) + }) + .collect(), + ); + truck_geotrait::parameter_transform_random_test(&curve, 10); +} + +impl + Tolerance> Cut for BSplineCurve

{ + fn cut(&mut self, mut t: f64) -> BSplineCurve

{ + let degree = self.degree(); + + let idx = match self.knot_vec.floor(t) { + Some(idx) => idx, + None => { + let bspline = self.clone(); + let knot_vec = KnotVec::from(vec![t, self.knot_vec[0]]); + let ctrl_pts = vec![P::origin()]; + *self = BSplineCurve::new(knot_vec, ctrl_pts); + return bspline; + } + }; + let s = if t.near(&self.knot_vec[idx]) { + t = self.knot_vec[idx]; + self.knot_vec.multiplicity(idx) + } else { + 0 + }; + + for _ in s..=degree { + self.add_knot(t); + } + + let k = self.knot_vec.floor(t).unwrap(); + let m = self.knot_vec.len(); + let n = self.control_points.len(); + let knot_vec0 = self.knot_vec.sub_vec(0..=k); + let knot_vec1 = self.knot_vec.sub_vec((k - degree)..m); + let control_points0 = Vec::from(&self.control_points[0..(k - degree)]); + let control_points1 = Vec::from(&self.control_points[(k - degree)..n]); + *self = BSplineCurve::new_unchecked(knot_vec0, control_points0); + BSplineCurve::new_unchecked(knot_vec1, control_points1) + } +} + +#[test] +fn cut_random_test() { + let curve = BSplineCurve::new( + KnotVec::uniform_knot(4, 4), + (0..8) + .map(|_| { + Point3::new( + rand::random::(), + rand::random::(), + rand::random::(), + ) + }) + .collect(), + ); + truck_geotrait::cut_random_test(&curve, 10); +} + +impl + Tolerance> Concat> for BSplineCurve

{ + type Output = BSplineCurve

; + /// Concats two B-spline curves. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// ``` + /// # Failure + /// If the back of the knot vector of `self` does not coincides with the front of the one of `other`, + /// ``` + /// use truck_geometry::prelude::*; + /// use truck_geotrait::traits::ConcatError; + /// + /// let knot_vec0 = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]); + /// let ctrl_pts0 = vec![Vector2::new(0.0, 0.0), Vector2::new(1.0, 1.0)]; + /// let mut bspcurve0 = BSplineCurve::new(knot_vec0, ctrl_pts0); + /// let knot_vec1 = KnotVec::from(vec![2.0, 2.0, 3.0, 3.0]); + /// let ctrl_pts1 = vec![Vector2::new(1.0, 1.0), Vector2::new(2.0, 2.0)]; + /// let mut bspcurve1 = BSplineCurve::new(knot_vec1, ctrl_pts1); + /// + /// assert_eq!(bspcurve0.try_concat(&mut bspcurve1), Err(ConcatError::DisconnectedParameters(1.0, 2.0))); + /// ``` + fn try_concat(&self, other: &BSplineCurve

) -> std::result::Result> { + let mut curve0 = self.clone(); + let mut curve1 = other.clone(); + curve0.syncro_degree(&mut curve1); + curve0.clamp(); + curve1.clamp(); + curve0 + .knot_vec + .try_concat(&curve1.knot_vec, curve0.degree()) + .map_err(|err| match err { + Error::DifferentBackFront(a, b) => ConcatError::DisconnectedParameters(a, b), + _ => unreachable!(), + })?; + let front = curve0.control_points.last().unwrap(); + let back = curve1.control_points.first().unwrap(); + if !front.near(back) { + return Err(ConcatError::DisconnectedPoints(*front, *back)); + } + curve0.control_points.extend(curve1.control_points); + Ok(curve0) + } +} + +#[test] +fn concat_positive_test() { + let mut part0 = BSplineCurve::new( + KnotVec::uniform_knot(4, 4), + (0..8) + .map(|_| { + Point3::new( + rand::random::(), + rand::random::(), + rand::random::(), + ) + }) + .collect(), + ); + let part1 = part0.cut(0.56); + concat_random_test(&part0, &part1, 10); +} + +#[test] +fn concat_negative_test() { + let curve0 = BSplineCurve::new( + KnotVec::bezier_knot(1), + vec![Point2::new(0.0, 0.0), Point2::new(0.0, 1.0)], + ); + let mut curve1 = BSplineCurve::new( + KnotVec::bezier_knot(1), + vec![Point2::new(1.0, 1.0), Point2::new(1.0, 1.0)], + ); + assert_eq!( + curve0.try_concat(&curve1), + Err(ConcatError::DisconnectedParameters(1.0, 0.0)) + ); + curve1.knot_translate(1.0); + assert_eq!( + curve0.try_concat(&curve1), + Err(ConcatError::DisconnectedPoints( + Point2::new(0.0, 1.0), + Point2::new(1.0, 1.0) + )) + ); +} + +impl

ParameterDivision1D for BSplineCurve

+where P: ControlPoint + + EuclideanSpace>::Diff> + + MetricSpace + + HashGen +{ + type Point = P; + fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec, Vec

) { + algo::curve::parameter_division(self, range, tol) + } +} + +impl

BSplineCurve

+where + P: ControlPoint + + EuclideanSpace>::Diff> + + MetricSpace + + Tolerance, +

>::Diff: InnerSpace + Tolerance, +{ + /// Determines whether `self` is an arc of `curve` by repeating applying Newton method. + /// + /// The parameter `hint` is the init value, required that `curve.subs(hint)` is the front point of `self`. + /// + /// If `self` is an arc of `curve`, then returns `Some(t)` such that `curve.subs(t)` coincides with + /// the back point of `self`. Otherwise, returns `None`. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec = KnotVec::from( + /// vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 3.0, 3.0] + /// ); + /// let ctrl_pts = vec![ + /// Point3::new(0.0, 0.0, 0.0), + /// Point3::new(1.0, 0.0, 0.0), + /// Point3::new(1.0, 1.0, 0.0), + /// Point3::new(0.0, 1.0, 0.0), + /// Point3::new(0.0, 1.0, 1.0), + /// ]; + /// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// + /// let mut part = bspcurve.clone().cut(0.6); + /// part.cut(2.8); + /// let t = part.is_arc_of(&bspcurve, 0.6).unwrap(); + /// assert_near!(t, 2.8); + /// + /// // hint is required the init value. + /// assert!(part.is_arc_of(&bspcurve, 0.7).is_none()); + /// + /// // normal failure + /// *part.control_point_mut(2) += Vector3::new(1.0, 2.0, 3.0); + /// assert!(part.is_arc_of(&bspcurve, 0.6).is_none()); + /// ``` + pub fn is_arc_of(&self, curve: &BSplineCurve

, mut hint: f64) -> Option { + let degree = std::cmp::max(self.degree(), curve.degree()) * 3 + 1; + let (knots, _) = self.knot_vec.to_single_multi(); + if !self.subs(knots[0]).near(&curve.subs(hint)) { + return None; + } + + for i in 1..knots.len() { + let range = knots[i] - knots[i - 1]; + for j in 1..=degree { + let t = knots[i - 1] + range * (j as f64) / (degree as f64); + let pt = ParametricCurve::subs(self, t); + let res = curve.search_nearest_parameter(pt, Some(hint), 100); + let flag = res.map(|res| hint <= res && curve.subs(res).near(&pt)); + hint = match flag { + Some(true) => res.unwrap(), + _ => return None, + }; + } + } + Some(hint) + } +} +impl

SearchNearestParameter for BSplineCurve

+where + P: ControlPoint + + EuclideanSpace>::Diff> + + MetricSpace + + Tolerance, +

>::Diff: InnerSpace + Tolerance, +{ + type Point = P; + /// 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::*; + /// let knot_vec = KnotVec::from( + /// vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 3.0, 3.0] + /// ); + /// let ctrl_pts = vec![ + /// Point3::new(0.0, 0.0, 0.0), + /// Point3::new(1.0, 0.0, 0.0), + /// Point3::new(1.0, 1.0, 0.0), + /// Point3::new(0.0, 1.0, 0.0), + /// Point3::new(0.0, 1.0, 1.0), + /// ]; + /// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// let pt = ParametricCurve::subs(&bspcurve, 1.2); + /// let t = bspcurve.search_nearest_parameter(pt, Some(0.8), 100).unwrap(); + /// assert_near!(t, 1.2); + /// ``` + /// # Remarks + /// It may converge to a local solution depending on the hint. + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec = KnotVec::from( + /// vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 3.0, 3.0] + /// ); + /// let ctrl_pts = vec![ + /// Point3::new(0.0, 0.0, 0.0), + /// Point3::new(1.0, 0.0, 0.0), + /// Point3::new(1.0, 1.0, 0.0), + /// Point3::new(0.0, 1.0, 0.0), + /// Point3::new(0.0, 1.0, 1.0), + /// ]; + /// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + /// let pt = Point3::new(0.0, 0.5, 1.0); + /// let t = bspcurve.search_nearest_parameter(pt, Some(0.8), 100).unwrap(); + /// let pt0 = ParametricCurve::subs(&bspcurve, t); + /// let pt1 = ParametricCurve::subs(&bspcurve, 3.0); + /// // the point corresponding the obtained parameter is not + /// // the globally nearest point in the curve. + /// assert!((pt0 - pt).magnitude() > (pt1 - pt).magnitude()); + /// ``` + #[inline(always)] + fn search_nearest_parameter>( + &self, + point: P, + hint: H, + trial: usize, + ) -> Option { + 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

SearchParameter for BSplineCurve

+where + P: ControlPoint + + EuclideanSpace>::Diff> + + MetricSpace, +

>::Diff: InnerSpace + Tolerance, +{ + type Point = P; + #[inline(always)] + fn search_parameter>(&self, point: P, hint: H, trial: usize) -> Option { + 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 BSplineCurve

{ + /// Returns the bounding box including all control points. + #[inline(always)] + pub fn roughly_bounding_box(&self) -> BoundingBox

{ self.control_points.iter().collect() } +} + +impl Invertible for BSplineCurve

{ + #[inline(always)] + fn invert(&mut self) { self.invert(); } +} + +impl Transformed for BSplineCurve

+where + P: EuclideanSpace, + M: Transform

, +{ + #[inline(always)] + fn transform_by(&mut self, trans: M) { + self.control_points + .iter_mut() + .for_each(|pt| *pt = trans.transform_point(*pt)) + } +} + +impl<'de, P> Deserialize<'de> for BSplineCurve

+where P: Deserialize<'de> +{ + fn deserialize(deserializer: D) -> std::result::Result + where D: serde::Deserializer<'de> { + #[derive(Deserialize)] + struct BSplineCurve_

{ + knot_vec: KnotVec, + control_points: Vec

, + } + let BSplineCurve_ { + knot_vec, + control_points, + } = BSplineCurve_::

::deserialize(deserializer)?; + Self::try_new(knot_vec, control_points).map_err(serde::de::Error::custom) + } +} + +#[test] +fn test_near_as_curve() { + let knot_vec = KnotVec::from(vec![ + 0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0, + ]); + let control_points = vec![ + Vector4::new(1.0, 0.0, 0.0, 0.0), + Vector4::new(0.0, 1.0, 0.0, 0.0), + Vector4::new(0.0, 0.0, 1.0, 0.0), + Vector4::new(0.0, 0.0, 0.0, 1.0), + Vector4::new(1.0, 1.0, 0.0, 0.0), + Vector4::new(1.0, 0.0, 1.0, 0.0), + Vector4::new(1.0, 0.0, 0.0, 1.0), + Vector4::new(1.0, 1.0, 1.0, 0.0), + ]; + let bspline0 = BSplineCurve::new(knot_vec, control_points.clone()); + let knot_vec = KnotVec::from(vec![ + 0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 2.5, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0, + ]); + let control_points = vec![ + control_points[0], + control_points[1], + control_points[2], + control_points[3] * (5.0 / 6.0) + control_points[2] * (1.0 / 6.0), + control_points[4] * 0.5 + control_points[3] * 0.5, + control_points[5] * (1.0 / 6.0) + control_points[4] * (5.0 / 6.0), + control_points[5], + control_points[6], + control_points[7], + ]; + let bspline1 = BSplineCurve::new(knot_vec, control_points); + let knot_vec = KnotVec::from(vec![ + 0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0, + ]); + let control_points = vec![ + Vector4::new(1.0, 0.0, 0.0, 0.0), + Vector4::new(0.0, 1.0, 0.0, 0.0), + Vector4::new(0.0, 0.0, 1.0, 0.0), + Vector4::new(0.0, 0.0, 0.0, 1.0), + Vector4::new(1.0, 1.01, 0.0, 0.0), + Vector4::new(1.0, 0.0, 1.0, 0.0), + Vector4::new(1.0, 0.0, 0.0, 1.0), + Vector4::new(1.0, 1.0, 1.0, 0.0), + ]; + let bspline2 = BSplineCurve::new(knot_vec, control_points); + assert!(bspline0.near_as_curve(&bspline1)); + assert!(!bspline0.near_as_curve(&bspline2)); +} + +#[test] +fn test_parameter_division() { + let knot_vec = KnotVec::uniform_knot(2, 3); + let ctrl_pts = vec![ + Point3::new(0.0, 0.0, 0.0), + Point3::new(1.0, 0.0, 0.0), + Point3::new(0.0, 1.0, 0.0), + Point3::new(0.0, 0.0, 1.0), + Point3::new(1.0, 1.0, 1.0), + ]; + let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts); + let tol = 0.01; + let (div, pts) = bspcurve.parameter_division(bspcurve.range_tuple(), tol); + let knot_vec = bspcurve.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 = bspcurve.subs(div[i - 1]); + assert_eq!(pt0, pts[i - 1]); + let pt1 = bspcurve.subs(div[i]); + assert_eq!(pt1, pts[i]); + let value_middle = pt0 + (pt1 - pt0) / 2.0; + let param_middle = bspcurve.subs((div[i - 1] + div[i]) / 2.0); + assert!(value_middle.distance(param_middle) < tol); + } +} + +impl

BSplineCurve

+where + P: ControlPoint + + EuclideanSpace>::Diff> + + MetricSpace + + Tolerance + + HashGen, +

>::Diff: InnerSpace + Tolerance, +{ + fn cubic_bezier_interpolation( + pt0: P, + pt1: P, + der0:

::Diff, + der1:

::Diff, + range: (f64, f64), + ) -> Self { + let width = range.1 - range.0; + let mut knot_vec = KnotVec::bezier_knot(3); + knot_vec.transform(width, range.0); + Self::debug_new( + knot_vec, + vec![pt0, pt0 + der0 * width / 3.0, pt1 - der1 * width / 3.0, pt1], + ) + } + + fn sub_cubic_approximation( + curve: &C, + range: (f64, f64), + ends: (P, P), + enders: (

::Diff,

::Diff), + p_tol: f64, + d_tol: f64, + trialis: usize, + ) -> Option + where + C: ParametricCurve::Diff>, + { + let bezier = Self::cubic_bezier_interpolation(ends.0, ends.1, enders.0, enders.1, range); + let gen = ends.0.midpoint(ends.1); + let p = 0.5 + (0.2 * HashGen::hash1(gen) - 0.1); + let t = range.0 * (1.0 - p) + range.1 * p; + let pt0 = bezier.subs(t); + let pt1 = curve.subs(t); + let der0 = bezier.der(t); + let der1 = curve.der(t); + let pt_dist2 = pt0.distance2(pt1); + let der_dist2 = (der0 - der1).magnitude2(); + if pt_dist2 > p_tol * p_tol || der_dist2 > d_tol * d_tol { + if trialis == 0 { + return None; + } + let t = (range.0 + range.1) / 2.0; + let pt = curve.subs(t); + let der = curve.der(t); + let bspcurve0 = Self::sub_cubic_approximation( + curve, + (range.0, t), + (ends.0, pt), + (enders.0, der), + p_tol, + d_tol, + trialis - 1, + ); + let bspcurve1 = Self::sub_cubic_approximation( + curve, + (t, range.1), + (pt, ends.1), + (der, enders.1), + p_tol, + d_tol, + trialis - 1, + ); + match (bspcurve0, bspcurve1) { + (Some(x), Some(y)) => x.try_concat(&y).ok(), + _ => None, + } + } else { + Some(bezier) + } + } + + /// C^1-approximation for `curve` by cubic Bspline curve. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// use std::f64::consts::PI; + /// let circle = UnitCircle::::new(); + /// let bspcurve = BSplineCurve::cubic_approximation(&circle, (-PI, PI), 0.01, 0.01, 10).unwrap(); + /// println!("{bspcurve:?}"); + /// + /// const N: usize = 100; + /// for i in 0..N { + /// let t = i as f64 / N as f64; + /// assert!(circle.subs(t).distance(bspcurve.subs(t)) < 0.02); + /// assert!((circle.der(t) - bspcurve.der(t)).magnitude() < 0.02); + /// } + /// ``` + pub fn cubic_approximation( + curve: &C, + range: (f64, f64), + p_tol: f64, + d_tol: f64, + trials: usize, + ) -> Option + where + C: ParametricCurve::Diff>, + { + let ends = (curve.subs(range.0), curve.subs(range.1)); + let enders = (curve.der(range.0), curve.der(range.1)); + Self::sub_cubic_approximation(curve, range, ends, enders, p_tol, d_tol, trials).map( + |mut x| { + x.optimize(); + x + }, + ) + } +} + +#[test] +fn cubic_bezier_interpolation_test() { + let pt0 = Point2::new(0.0, 0.0); + let pt1 = Point2::new(1.0, 0.0); + let der0 = Vector2::new(0.5, 1.0); + let der1 = Vector2::new(0.5, -1.0); + let bspcurve = BSplineCurve::cubic_bezier_interpolation(pt0, pt1, der0, der1, (1.23, 3.45)); + let der = bspcurve.derivation(); + assert_near!(bspcurve.front(), pt0); + assert_near!(bspcurve.back(), pt1); + assert_near!(der.front(), der0); + assert_near!(der.back(), der1); +} diff --git a/vendor/truck-geometry/src/nurbs/bspsurface.rs b/vendor/truck-geometry/src/nurbs/bspsurface.rs new file mode 100644 index 00000000..fcedac20 --- /dev/null +++ b/vendor/truck-geometry/src/nurbs/bspsurface.rs @@ -0,0 +1,2230 @@ +use super::*; +use crate::errors::Error; +use std::iter::FusedIterator; +use std::ops::*; + +impl

BSplineSurface

{ + /// constructor. + /// # Arguments + /// * `knot_vecs` - the knot vectors + /// * `control_points` - the vector of the control points + /// # Panics + /// There are 3 rules for construct B-spline curve. + /// * The number of knots is more than the one of control points. + /// * There exist at least two different knots. + /// * There are at least one control point. + #[inline(always)] + pub fn new(knot_vecs: (KnotVec, KnotVec), control_points: Vec>) -> BSplineSurface

{ + BSplineSurface::try_new(knot_vecs, control_points).unwrap_or_else(|e| panic!("{}", e)) + } + + /// constructor. + /// # Arguments + /// * `knot_vecs` - the knot vectors + /// * `control_points` - the vector of the control points + /// # Failures + /// There are 3 rules for construct B-spline curve. + /// * The number of knots is more than the one of control points. + /// * There exist at least two different knots. + /// * There are at least one control point. + #[inline(always)] + pub fn try_new( + knot_vecs: (KnotVec, KnotVec), + control_points: Vec>, + ) -> Result> { + if control_points.is_empty() || control_points[0].is_empty() { + Err(Error::EmptyControlPoints) + } else if knot_vecs.0.len() <= control_points.len() { + Err(Error::TooShortKnotVector( + knot_vecs.0.len(), + control_points.len(), + )) + } else if knot_vecs.1.len() <= control_points[0].len() { + Err(Error::TooShortKnotVector( + knot_vecs.1.len(), + control_points[0].len(), + )) + } else if knot_vecs.0.range_length().so_small() || knot_vecs.1.range_length().so_small() { + Err(Error::ZeroRange) + } else { + let len = control_points[0].len(); + if control_points.iter().any(|vec| vec.len() != len) { + Err(Error::IrregularControlPoints) + } else { + Ok(BSplineSurface::new_unchecked(knot_vecs, control_points)) + } + } + } + + /// constructor. + /// # Arguments + /// * `knot_vecs` - the knot vectors + /// * `control_points` - the vector of the control points + /// # Failures + /// This method is prepared only for performance-critical development and is not recommended. + /// This method does NOT check the 3 rules for constructing B-spline surface. + /// The programmer must guarantee these conditions before using this method. + #[inline(always)] + pub const fn new_unchecked( + knot_vecs: (KnotVec, KnotVec), + control_points: Vec>, + ) -> BSplineSurface

{ + BSplineSurface { + knot_vecs, + control_points, + } + } + + /// constructor. + /// # Arguments + /// * `knot_vecs` - the knot vectors + /// * `control_points` - the vector of the control points + /// # Failures + /// This method checks the 3 rules for constructing B-spline surface in the debug mode. + /// The programmer must guarantee these conditions before using this method. + #[inline(always)] + pub fn debug_new( + knot_vecs: (KnotVec, KnotVec), + control_points: Vec>, + ) -> BSplineSurface

{ + match cfg!(debug_assertions) { + true => Self::new(knot_vecs, control_points), + false => Self::new_unchecked(knot_vecs, control_points), + } + } + /// Returns the reference of the knot vectors + #[inline(always)] + pub const fn knot_vecs(&self) -> &(KnotVec, KnotVec) { &self.knot_vecs } + + /// Returns the u knot vector. + #[inline(always)] + pub const fn uknot_vec(&self) -> &KnotVec { &self.knot_vecs.0 } + /// Returns the v knot vector. + #[inline(always)] + pub const fn vknot_vec(&self) -> &KnotVec { &self.knot_vecs.1 } + + /// Returns the `idx`th u knot. + #[inline(always)] + pub fn uknot(&self, idx: usize) -> f64 { self.knot_vecs.0[idx] } + /// returns the `idx`th v knot. + #[inline(always)] + pub fn vknot(&self, idx: usize) -> f64 { self.knot_vecs.1[idx] } + + /// Returns the reference of the vector of the control points + #[inline(always)] + pub const fn control_points(&self) -> &Vec> { &self.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) -> &P { &self.control_points[idx0][idx1] } + /// Apply the given transformation to all control points. + #[inline(always)] + pub fn transform_control_points(&mut self, f: F) { + self.control_points.iter_mut().flatten().for_each(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 + FusedIterator { + self.control_points.iter().map(move |vec| &vec[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<'_, P> { + self.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 P { + &mut self.control_points[idx0][idx1] + } + + /// Returns the iterator on all control points + #[inline(always)] + pub fn control_points_mut(&mut self) -> impl Iterator { + self.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.knot_vecs.0.len() - self.control_points.len() - 1 } + + /// 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.knot_vecs.1.len() - self.control_points[0].len() - 1 } + + /// 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.knot_vecs.0.is_clamped(self.udegree()) && self.knot_vecs.1.is_clamped(self.vdegree()) + } + + /// Swaps two parameters. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs0 = (KnotVec::bezier_knot(1), KnotVec::bezier_knot(2)); + /// let ctrl_pts0 = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let mut bspsurface0 = BSplineSurface::new(knot_vecs0, ctrl_pts0); + /// + /// let knot_vecs1 = (KnotVec::bezier_knot(2), KnotVec::bezier_knot(1)); + /// let ctrl_pts1 = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.0, 1.0)], + /// vec![Vector2::new(0.5, -1.0), Vector2::new(0.5, 2.0)], + /// vec![Vector2::new(1.0, 0.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let mut bspsurface1 = BSplineSurface::new(knot_vecs1, ctrl_pts1); + /// assert_eq!(bspsurface0.swap_axes(), &bspsurface1); + /// ``` + pub fn swap_axes(&mut self) -> &mut Self + where P: Clone { + let knot_vec = self.knot_vecs.0.clone(); + self.knot_vecs.0 = self.knot_vecs.1.clone(); + self.knot_vecs.1 = knot_vec; + + let n0 = self.control_points.len(); + let n1 = self.control_points[0].len(); + let mut new_points = vec![Vec::with_capacity(n0); n1]; + for pts in &self.control_points { + for (vec0, pt) in new_points.iter_mut().zip(pts) { + vec0.push(pt.clone()); + } + } + self.control_points = new_points; + self + } + + /// The range of the parameter of the surface. + #[inline(always)] + pub fn parameter_range(&self) -> (ParameterRange, ParameterRange) { + ( + ( + Bound::Included(self.knot_vecs.0[0]), + Bound::Included(self.knot_vecs.0[self.knot_vecs.0.len() - 1]), + ), + ( + Bound::Included(self.knot_vecs.1[0]), + Bound::Included(self.knot_vecs.1[self.knot_vecs.1.len() - 1]), + ), + ) + } + /// Creates the curve whose control points are the `idx`th column control points of `self`. + /// # 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 bspcurve = bspsurface.column_curve(1); + /// + /// assert_eq!(bspcurve.knot_vec(), &KnotVec::bezier_knot(2)); + /// assert_eq!( + /// bspcurve.control_points(), + /// &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)], + /// ); + /// ``` + pub fn column_curve(&self, row_idx: usize) -> BSplineCurve

+ where P: Clone { + let knot_vec = self.vknot_vec().clone(); + let ctrl_pts = self.control_points[row_idx].clone(); + BSplineCurve::new_unchecked(knot_vec, ctrl_pts) + } + /// Creates the column sectional curve. + /// # 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 bspcurve = bspsurface.row_curve(1); + /// + /// assert_eq!(bspcurve.knot_vec(), &KnotVec::bezier_knot(1)); + /// assert_eq!( + /// bspcurve.control_points(), + /// &vec![Vector3::new(1.0, 0.0, 1.0), Vector3::new(1.0, 1.0, 1.0)], + /// ); + /// ``` + pub fn row_curve(&self, column_idx: usize) -> BSplineCurve

+ where P: Clone { + let knot_vec = self.uknot_vec().clone(); + let ctrl_pts: Vec<_> = self.ctrl_pts_row_iter(column_idx).cloned().collect(); + BSplineCurve::new_unchecked(knot_vec, ctrl_pts) + } +} + +impl> BSplineSurface

{ + /// Returns the closure of substitution. + #[inline(always)] + pub fn get_closure(&self) -> impl Fn(f64, f64) -> P + '_ { move |u, v| self.subs(u, v) } + + #[inline(always)] + fn udelta_control_points(&self, i: usize, j: usize) -> P::Diff { + if i == 0 { + self.control_point(i, j).to_vec() + } else if i == self.control_points.len() { + self.control_point(i - 1, j).to_vec() * (-1.0) + } else { + *self.control_point(i, j) - *self.control_point(i - 1, j) + } + } + + #[inline(always)] + fn udelta2_control_points(&self, i: usize, j: usize) -> P::Diff { + let k = self.udegree(); + let knot_vec = self.uknot_vec(); + if i == 0 { + let coef = inv_or_zero(knot_vec[i + k] - knot_vec[i]); + self.udelta_control_points(i, j) * coef + } else if i == self.control_points.len() + 1 { + let coef = inv_or_zero(knot_vec[i - 1 + k] - knot_vec[i - 1]); + self.udelta_control_points(i - 1, j) * (-coef) + } else { + let coef0 = inv_or_zero(knot_vec[i + k] - knot_vec[i]); + let coef1 = inv_or_zero(knot_vec[i - 1 + k] - knot_vec[i - 1]); + self.udelta_control_points(i, j) * coef0 - self.udelta_control_points(i - 1, j) * coef1 + } + } + + #[inline(always)] + fn vdelta_control_points(&self, i: usize, j: usize) -> P::Diff { + if j == 0 { + self.control_point(i, j).to_vec() + } else if j == self.control_points[0].len() { + self.control_point(i, j - 1).to_vec() * (-1.0) + } else { + *self.control_point(i, j) - *self.control_point(i, j - 1) + } + } + + #[inline(always)] + fn vdelta2_control_points(&self, i: usize, j: usize) -> P::Diff { + let k = self.vdegree(); + let knot_vec = self.vknot_vec(); + if j == 0 { + let coef = inv_or_zero(knot_vec[j + k] - knot_vec[j]); + self.vdelta_control_points(i, j) * coef + } else if j == self.control_points[0].len() + 1 { + let coef = inv_or_zero(knot_vec[j - 1 + k] - knot_vec[j - 1]); + self.vdelta_control_points(i, j - 1) * (-coef) + } else { + let coef0 = inv_or_zero(knot_vec[j + k] - knot_vec[j]); + let coef1 = inv_or_zero(knot_vec[j - 1 + k] - knot_vec[j - 1]); + self.vdelta_control_points(i, j) * coef0 - self.vdelta_control_points(i, j - 1) * coef1 + } + } + + /// Calculate derived B-spline surface by the first parameter `u`. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(1), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let uderivation = bspsurface.uderivation(); + /// + /// // bspsurface: (v, 2v(1 - v)(2u - 1) + u), uderivation: (0.0, 4v(1 - v) + 1) + /// const N: usize = 100; // sample size + /// for i in 1..N { + /// let u = (i as f64) / (N as f64); + /// for j in 0..=N { + /// let v = (j as f64) / (N as f64); + /// assert_near2!( + /// uderivation.subs(u, v), + /// Vector2::new(0.0, 4.0 * v * (1.0 - v) + 1.0), + /// ); + /// } + /// } + /// ``` + pub fn uderivation(&self) -> BSplineSurface { + let n0 = self.control_points.len(); + let n1 = self.control_points[0].len(); + let (k, _) = self.degrees(); + let (uknot_vec, vknot_vec) = self.knot_vecs.clone(); + + let new_points = if k > 0 { + (0..=n0) + .map(|i| { + let delta = uknot_vec[i + k] - uknot_vec[i]; + let coef = (k as f64) * inv_or_zero(delta); + (0..n1) + .map(|j| self.udelta_control_points(i, j) * coef) + .collect() + }) + .collect() + } else { + vec![vec![P::Diff::zero(); n1]; n0] + }; + + BSplineSurface::new_unchecked((uknot_vec, vknot_vec), new_points) + } + + /// Calculate derived B-spline surface by the second parameter `v`. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(1), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let vderivation = bspsurface.vderivation(); + /// + /// // bspsurface: (v, 2v(1 - v)(2u - 1) + u), vderivation: (1, -2(2u - 1)(2v - 1)) + /// const N: usize = 100; // sample size + /// for i in 0..=N { + /// let u = (i as f64) / (N as f64); + /// for j in 0..=N { + /// let v = (j as f64) / (N as f64); + /// assert_near2!( + /// vderivation.subs(u, v), + /// Vector2::new(1.0, -2.0 * (2.0 * u - 1.0) * (2.0 * v - 1.0)), + /// ); + /// } + /// } + /// ``` + pub fn vderivation(&self) -> BSplineSurface { + let n0 = self.control_points.len(); + let n1 = self.control_points[0].len(); + let (_, k) = self.degrees(); + + let (uknot_vec, vknot_vec) = self.knot_vecs.clone(); + + let new_points = if k > 0 { + let mut new_points = vec![Vec::with_capacity(n1 + 1); n0]; + for j in 0..=n1 { + let delta = vknot_vec[j + k] - vknot_vec[j]; + let coef = (k as f64) * inv_or_zero(delta); + for (i, vec) in new_points.iter_mut().enumerate() { + vec.push(self.vdelta_control_points(i, j) * coef) + } + } + new_points + } else { + vec![vec![P::Diff::zero(); n1]; n0] + }; + + BSplineSurface::new_unchecked((uknot_vec, vknot_vec), new_points) + } + + pub(super) fn sub_near_as_surface bool>( + &self, + other: &BSplineSurface

, + div_coef: usize, + ord: F, + ) -> bool { + if !self.knot_vecs.0.same_range(&other.knot_vecs.0) { + return false; + } + if !self.knot_vecs.1.same_range(&other.knot_vecs.1) { + return false; + } + + let (self_degree0, self_degree1) = self.degrees(); + let (other_degree0, other_degree1) = other.degrees(); + let division0 = std::cmp::max(self_degree0, other_degree0) * div_coef; + let division1 = std::cmp::max(self_degree1, other_degree1) * div_coef; + + for i0 in 1..self.knot_vecs.0.len() { + let delta0 = self.knot_vecs.0[i0] - self.knot_vecs.0[i0 - 1]; + if delta0.so_small() { + continue; + } + for j0 in 0..division0 { + let u = self.knot_vecs.0[i0 - 1] + delta0 * (j0 as f64) / (division0 as f64); + for i1 in 1..self.knot_vecs.1.len() { + let delta1 = self.knot_vecs.1[i1] - self.knot_vecs.1[i1 - 1]; + if delta1.so_small() { + continue; + } + for j1 in 0..division1 { + let v = + self.knot_vecs.1[i1 - 1] + delta1 * (j1 as f64) / (division1 as f64); + if !ord(&self.subs(u, v), &other.subs(u, v)) { + return false; + } + } + } + } + } + true + } +} + +impl> BSplineSurface { + /// lift up control points to homogeneous coordinate. + pub fn lift_up(surface: BSplineSurface) -> Self { + let control_points = surface + .control_points + .into_iter() + .map(|vec| vec.into_iter().map(V::from_point).collect()) + .collect(); + BSplineSurface::new_unchecked(surface.knot_vecs, control_points) + } +} + +impl> ParametricSurface for BSplineSurface

{ + type Point = P; + type Vector = P::Diff; + /// Substitutes to a B-spline surface. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(1), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// + /// // bspsurface: (v, 2v(1 - v)(2u - 1) + u) + /// const N: usize = 100; // sample size + /// for i in 0..=N { + /// let u = (i as f64) / (N as f64); + /// for j in 0..=N { + /// let v = (j as f64) / (N as f64); + /// assert_near2!( + /// bspsurface.subs(u, v), + /// Vector2::new(v, 2.0 * v * (1.0 - v) * (2.0 * u - 1.0) + u), + /// ); + /// } + /// } + /// ``` + #[inline(always)] + fn subs(&self, u: f64, v: f64) -> P { + let (degree0, degree1) = self.degrees(); + let BSplineSurface { + knot_vecs: (ref uknot_vec, ref vknot_vec), + ref control_points, + } = self; + let basis0 = uknot_vec.bspline_basis_functions(degree0, u); + let basis1 = vknot_vec.bspline_basis_functions(degree1, v); + let closure = move |sum: P, (vec, b0): (&Vec

, f64)| { + let closure = move |sum: P, (pt, b1): (&P, &f64)| sum + pt.to_vec() * (b0 * b1); + vec.iter().zip(&basis1).fold(sum, closure) + }; + control_points.iter().zip(basis0).fold(P::origin(), closure) + } + /// Substitutes derived B-spline surface by the first parameter `u`. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(1), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// + /// // bspsurface: (v, 2v(1 - v)(2u - 1) + u), uderivation: (0.0, 4v(1 - v) + 1) + /// const N: usize = 100; // sample size + /// for i in 0..=N { + /// let u = (i as f64) / (N as f64); + /// for j in 0..=N { + /// let v = (j as f64) / (N as f64); + /// assert_near2!( + /// bspsurface.uder(u, v), + /// Vector2::new(0.0, 4.0 * v * (1.0 - v) + 1.0), + /// ); + /// } + /// } + /// ``` + #[inline(always)] + fn uder(&self, u: f64, v: f64) -> P::Diff { + let (degree0, degree1) = self.degrees(); + let (uknot_vec, vknot_vec) = self.knot_vecs(); + let basis0 = uknot_vec.bspline_basis_functions(degree0 - 1, u); + let basis1 = vknot_vec.bspline_basis_functions(degree1, v); + let closure = move |sum: P::Diff, (i, b0): (usize, f64)| { + let coef = inv_or_zero(uknot_vec[i + degree0] - uknot_vec[i]); + let closure = |sum: P::Diff, (j, b1): (usize, &f64)| { + sum + self.udelta_control_points(i, j) * coef * b0 * *b1 + }; + basis1.iter().enumerate().fold(sum, closure) + }; + basis0 + .into_iter() + .enumerate() + .fold(P::Diff::zero(), closure) + * degree0 as f64 + } + /// Substitutes derived B-spline surface by the first parameter `v`. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(1), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// + /// // bspsurface: (v, 2v(1 - v)(2u - 1) + u), vderivation: (1, -2(2u - 1)(2v - 1)) + /// const N: usize = 100; // sample size + /// for i in 0..=N { + /// let u = (i as f64) / (N as f64); + /// for j in 0..=N { + /// let v = (j as f64) / (N as f64); + /// assert_near2!( + /// bspsurface.vder(u, v), + /// Vector2::new(1.0, -2.0 * (2.0 * u - 1.0) * (2.0 * v - 1.0)), + /// ); + /// } + /// } + /// ``` + #[inline(always)] + fn vder(&self, u: f64, v: f64) -> P::Diff { + let (degree0, degree1) = self.degrees(); + let (uknot_vec, vknot_vec) = self.knot_vecs(); + let basis0 = uknot_vec.bspline_basis_functions(degree0, u); + let basis1 = vknot_vec.bspline_basis_functions(degree1 - 1, v); + let closure = move |sum: P::Diff, (i, b0): (usize, f64)| { + let coef = inv_or_zero(vknot_vec[i + degree1] - vknot_vec[i]); + let closure = |sum: P::Diff, (j, b1): (usize, &f64)| { + sum + self.vdelta_control_points(j, i) * coef * b0 * *b1 + }; + basis0.iter().enumerate().fold(sum, closure) + }; + basis1 + .into_iter() + .enumerate() + .fold(P::Diff::zero(), closure) + * degree1 as f64 + } + + /// Substitutes 2nd-ord derived B-spline surface by the first parameter `u`. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 0.5), Vector2::new(0.5, 1.0), Vector2::new(1.0, 0.5)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// + /// // bspsurface: (v, 2 u^2 v^2 - 2 u^2 v - 6 u v^2 + 6uv + 2v^2 + u - 2v) + /// // uuder: (0, 4v(v - 1)) + /// const N: usize = 100; // sample size + /// for i in 0..=N { + /// let u = (i as f64) / (N as f64); + /// for j in 0..=N { + /// let v = (j as f64) / (N as f64); + /// assert_near2!( + /// bspsurface.uuder(u, v), + /// Vector2::new(0.0, 4.0 * v * (v - 1.0)), + /// ); + /// } + /// } + /// ``` + #[inline(always)] + fn uuder(&self, u: f64, v: f64) -> P::Diff { + let (degree0, degree1) = self.degrees(); + if degree0 < 2 { + return P::Diff::zero(); + } + let (uknot_vec, vknot_vec) = self.knot_vecs(); + let basis0 = uknot_vec.bspline_basis_functions(degree0 - 2, u); + let basis1 = vknot_vec.bspline_basis_functions(degree1, v); + let closure = move |sum: P::Diff, (i, b0): (usize, f64)| { + let coef = inv_or_zero(uknot_vec[i + degree0 - 1] - uknot_vec[i]); + let closure = |sum: P::Diff, (j, b1): (usize, &f64)| { + sum + self.udelta2_control_points(i, j) * coef * b0 * *b1 + }; + basis1.iter().enumerate().fold(sum, closure) + }; + basis0 + .into_iter() + .enumerate() + .fold(P::Diff::zero(), closure) + * degree0 as f64 + } + + /// Substitutes 2nd-ord derived B-spline surface by the second parameter `v`. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 0.5), Vector2::new(0.5, 1.0), Vector2::new(1.0, 0.5)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// + /// // bspsurface: (v, 2 u^2 v^2 - 2 u^2 v - 6 u v^2 + 6uv + 2v^2 + u - 2v) + /// // vvder: (0, 4(u^2 - 3u + 1)) + /// const N: usize = 100; // sample size + /// for i in 0..=N { + /// let u = (i as f64) / (N as f64); + /// for j in 0..=N { + /// let v = (j as f64) / (N as f64); + /// assert_near2!( + /// bspsurface.vvder(u, v), + /// Vector2::new(0.0, 4.0 * (u * u - 3.0 * u + 1.0)), + /// ); + /// } + /// } + /// ``` + #[inline(always)] + fn vvder(&self, u: f64, v: f64) -> P::Diff { + let (degree0, degree1) = self.degrees(); + if degree1 < 2 { + return P::Diff::zero(); + } + let (uknot_vec, vknot_vec) = self.knot_vecs(); + let basis0 = uknot_vec.bspline_basis_functions(degree0, u); + let basis1 = vknot_vec.bspline_basis_functions(degree1 - 2, v); + let closure = move |sum: P::Diff, (j, b0): (usize, f64)| { + let coef = inv_or_zero(vknot_vec[j + degree1 - 1] - vknot_vec[j]); + let closure = |sum: P::Diff, (i, b1): (usize, &f64)| { + sum + self.vdelta2_control_points(i, j) * coef * b0 * *b1 + }; + basis0.iter().enumerate().fold(sum, closure) + }; + basis1 + .into_iter() + .enumerate() + .fold(P::Diff::zero(), closure) + * degree1 as f64 + } + + /// Substitutes 2nd-ord derived B-spline surface by the both parameters `u, v`. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 0.5), Vector2::new(0.5, 1.0), Vector2::new(1.0, 0.5)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// + /// // bspsurface: (v, 2 u^2 v^2 - 2 u^2 v - 6 u v^2 + 6uv + 2v^2 + u - 2v) + /// // uvder: (0, 8uv - 4u - 12v + 6) + /// const N: usize = 100; // sample size + /// for i in 0..=N { + /// let u = (i as f64) / (N as f64); + /// for j in 0..=N { + /// let v = (j as f64) / (N as f64); + /// assert_near2!( + /// bspsurface.uvder(u, v), + /// Vector2::new(0.0, 8.0 * u * v - 4.0 * u - 12.0 * v + 6.0), + /// ); + /// } + /// } + /// ``` + #[inline(always)] + fn uvder(&self, u: f64, v: f64) -> P::Diff { + let (degree0, degree1) = self.degrees(); + let BSplineSurface { + knot_vecs: (ref uknot_vec, ref vknot_vec), + ref control_points, + } = self; + let basis0 = uknot_vec.bspline_basis_functions(degree0 - 1, u); + let basis1 = vknot_vec.bspline_basis_functions(degree1 - 1, v); + let closure = |sum: P::Diff, (i, vec): (usize, &Vec

)| { + let coef0 = inv_or_zero(uknot_vec[i + degree0] - uknot_vec[i]); + let coef1 = inv_or_zero(uknot_vec[i + degree0 + 1] - uknot_vec[i + 1]); + let b0 = basis0[i] * coef0 - basis0[i + 1] * coef1; + let closure = |sum: P::Diff, (j, pt): (usize, &P)| { + let coef0 = inv_or_zero(vknot_vec[j + degree1] - vknot_vec[j]); + let coef1 = inv_or_zero(vknot_vec[j + degree1 + 1] - vknot_vec[j + 1]); + sum + pt.to_vec() * (basis1[j] * coef0 - basis1[j + 1] * coef1) * b0 + }; + vec.iter().enumerate().fold(sum, closure) + }; + control_points + .iter() + .enumerate() + .fold(P::Diff::zero(), closure) + * degree0 as f64 + * degree1 as f64 + } + #[inline(always)] + fn parameter_range(&self) -> (ParameterRange, ParameterRange) { self.parameter_range() } +} + +impl BSplineSurface { + /// Returns whether all control points are same or not. + /// If the knot vector is clamped, it means whether the curve is constant or not. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let uknot_vec = KnotVec::bezier_knot(1); + /// let vknot_vec = KnotVec::bezier_knot(2); + /// let pt = Vector2::new(1.0, 2.0); + /// let ctrl_pts = vec![ + /// vec![pt.clone(), pt.clone(), pt.clone()], + /// vec![pt.clone(), pt.clone(), pt.clone()], + /// ]; + /// let mut bspsurface = BSplineSurface::new((uknot_vec, vknot_vec), ctrl_pts); + /// assert!(bspsurface.is_const()); + /// + /// *bspsurface.control_point_mut(1, 2) = Vector2::new(2.0, 3.0); + /// assert!(!bspsurface.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 surface is not constant. + /// ``` + /// use truck_geometry::prelude::*; + /// let uknot_vec = KnotVec::uniform_knot(1, 5); + /// let vknot_vec = KnotVec::uniform_knot(1, 5); + /// let pt = Vector2::new(1.0, 2.0); + /// let ctrl_pts = vec![ + /// vec![pt.clone(), pt.clone(), pt.clone()], + /// vec![pt.clone(), pt.clone(), pt.clone()], + /// ]; + /// let mut bspsurface = BSplineSurface::new((uknot_vec, vknot_vec), ctrl_pts); + /// + /// // bspsurface is not constant. + /// assert_eq!(bspsurface.subs(0.0, 0.0), Vector2::new(0.0, 0.0)); + /// assert_ne!(bspsurface.subs(0.5, 0.5), Vector2::new(0.0, 0.0)); + /// + /// // bspsurface.is_const() is true. + /// assert!(bspsurface.is_const()); + /// ``` + #[inline(always)] + pub fn is_const(&self) -> bool { + for vec in self.control_points.iter().flat_map(|pts| pts.iter()) { + if !vec.near(&self.control_points[0][0]) { + return false; + } + } + true + } +} + +impl + Tolerance> BSplineSurface

{ + /// Adds a knot `x` of the first parameter `u`, and do not change `self` as a surface. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(1), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let mut bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let org_surface = bspsurface.clone(); + /// bspsurface.add_uknot(0.0).add_uknot(0.3).add_uknot(0.5).add_uknot(1.0); + /// assert!(bspsurface.near2_as_surface(&org_surface)); + /// assert_eq!(bspsurface.uknot_vec().len(), org_surface.uknot_vec().len() + 4); + /// ``` + pub fn add_uknot(&mut self, x: f64) -> &mut Self { + let k = self.udegree(); + let n0 = self.control_points.len(); + let n1 = self.control_points[0].len(); + let uknot_vec = &mut self.knot_vecs.0; + let control_points = &mut self.control_points; + if x < uknot_vec[0] { + uknot_vec.add_knot(x); + control_points.insert(0, vec![P::origin(); n1]); + return self; + } + + let idx = uknot_vec.add_knot(x); + let start = if idx > k { idx - k } else { 0 }; + let end = if idx > n0 { + control_points.push(vec![P::origin(); n1]); + n0 + 1 + } else { + control_points.insert(idx - 1, control_points[idx - 1].clone()); + idx + }; + for i in start..end { + let i0 = end + start - i - 1; + let delta = self.uknot(i0 + k + 1) - self.uknot(i0); + let a = inv_or_zero(delta) * (self.uknot(idx) - self.uknot(i0)); + for j in 0..n1 { + let p = self.udelta_control_points(i0, j) * (1.0 - a); + self.control_points[i0][j] -= p; + } + } + self + } + + /// Adds a knot `x` for the second parameter, and do not change `self` as a surface. + /// Return `false` if cannot add the knot, i.e. + /// * the index of `x` will be lower than the degree, or + /// * the index of `x` will be higher than the number of control points. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(1), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// ]; + /// let mut bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let org_surface = bspsurface.clone(); + /// bspsurface.add_vknot(0.0).add_vknot(0.3).add_vknot(0.5).add_vknot(1.0); + /// assert!(bspsurface.near2_as_surface(&org_surface)); + /// assert_eq!(bspsurface.vknot_vec().len(), org_surface.vknot_vec().len() + 4); + /// ``` + pub fn add_vknot(&mut self, x: f64) -> &mut Self { + if x < self.knot_vecs.1[0] { + self.knot_vecs.1.add_knot(x); + self.control_points + .iter_mut() + .for_each(|vec| vec.insert(0, P::origin())); + return self; + } + + let k = self.vdegree(); + let n0 = self.control_points.len(); + let n1 = self.control_points[0].len(); + + let idx = self.knot_vecs.1.add_knot(x); + let start = if idx > k { idx - k } else { 0 }; + let end = if idx > n1 { + self.control_points + .iter_mut() + .for_each(|vec| vec.push(P::origin())); + n1 + 1 + } else { + self.control_points + .iter_mut() + .for_each(|vec| vec.insert(idx - 1, vec[idx - 1])); + idx + }; + for j in start..end { + let j0 = end + start - j - 1; + let delta = self.knot_vecs.1[j0 + k + 1] - self.knot_vecs.1[j0]; + let a = inv_or_zero(delta) * (self.knot_vecs.1[idx] - self.knot_vecs.1[j0]); + for i in 0..n0 { + let p = self.vdelta_control_points(i, j0) * (1.0 - a); + self.control_points[i][j0] -= p; + } + } + 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`](./errors/enum.Error.html#variant.CannotRemoveKnot). + /// # 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(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// vec![Vector2::new(0.0, 2.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 2.0)], + /// ]; + /// let mut bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let org_surface = bspsurface.clone(); + /// + /// bspsurface.add_uknot(0.3).add_uknot(0.5); + /// + /// assert!(bspsurface.try_remove_uknot(3).is_ok()); + /// assert_eq!(bspsurface.try_remove_uknot(2), Err(Error::CannotRemoveKnot(2))); + /// + /// assert_eq!(bspsurface.uknot_vec().len(), org_surface.uknot_vec().len() + 1); + /// assert!(bspsurface.near2_as_surface(&org_surface)); + /// ``` + pub fn try_remove_uknot(&mut self, idx: usize) -> Result<&mut Self> { + let k = self.udegree(); + let knot_vec = self.uknot_vec(); + let n = self.control_points.len(); + + if idx < k + 1 || idx >= n { + return Err(Error::CannotRemoveKnot(idx)); + } + + let mut new_points = Vec::with_capacity(k + 1); + let first_vec = self + .ctrl_pts_column_iter(idx - k - 1) + .cloned() + .collect::>(); + new_points.push(first_vec); + for i in (idx - k)..idx { + let delta = knot_vec[i + k + 1] - knot_vec[i]; + let a = inv_or_zero(delta) * (knot_vec[idx] - knot_vec[i]); + if a.so_small() { + break; + } else { + let vec = self + .ctrl_pts_column_iter(i) + .zip(new_points.last().unwrap()) + .map(|(pt0, pt1)| *pt1 + (*pt0 - *pt1) / a) + .collect(); + new_points.push(vec); + } + } + + for (pt0, pt1) in self + .ctrl_pts_column_iter(idx) + .zip(new_points.last().unwrap()) + { + if !pt0.near(pt1) { + return Err(Error::CannotRemoveKnot(idx)); + } + } + + for (i, vec) in new_points.into_iter().skip(1).enumerate() { + self.control_points[idx - k + i] = vec; + } + + self.control_points.remove(idx); + self.knot_vecs.0.remove(idx); + Ok(self) + } + + /// 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`. + /// # 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(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// vec![Vector2::new(0.0, 2.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 2.0)], + /// ]; + /// let mut bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let org_surface = bspsurface.clone(); + /// + /// bspsurface.add_uknot(0.3).add_uknot(0.5); + /// bspsurface.remove_uknot(3).remove_uknot(3); + /// + /// assert!(bspsurface.near2_as_surface(&org_surface)); + /// assert_eq!(bspsurface.uknot_vec().len(), org_surface.uknot_vec().len()) + /// ``` + #[inline(always)] + pub fn remove_uknot(&mut self, idx: usize) -> &mut Self { + let _ = self.try_remove_uknot(idx); + self + } + + /// Removes a vknot corresponding to the indice `idx`, and do not change `self` as a curve. + /// If the knot cannot be removed, returns + /// [`Error::CannotRemoveKnot`](./errors/enum.Error.html#variant.CannotRemoveKnot). + /// # 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(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// vec![Vector2::new(0.0, 2.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 2.0)], + /// ]; + /// let mut bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let org_surface = bspsurface.clone(); + /// + /// bspsurface.add_vknot(0.3).add_vknot(0.5); + /// assert!(bspsurface.try_remove_vknot(3).is_ok()); + /// assert_eq!(bspsurface.try_remove_vknot(2), Err(Error::CannotRemoveKnot(2))); + /// + /// assert!(bspsurface.near2_as_surface(&org_surface)); + /// assert_eq!(bspsurface.vknot_vec().len(), org_surface.vknot_vec().len() + 1); + /// ``` + pub fn try_remove_vknot(&mut self, idx: usize) -> Result<&mut Self> { + let (_, k) = self.degrees(); + let knot_vec = self.vknot_vec(); + let n = self.control_points[0].len(); + + if idx < k + 1 || idx >= n { + return Err(Error::CannotRemoveKnot(idx)); + } + + let mut new_points = Vec::with_capacity(k + 1); + let first_vec = self + .ctrl_pts_row_iter(idx - k - 1) + .cloned() + .collect::>(); + new_points.push(first_vec); + for i in (idx - k)..idx { + let delta = knot_vec[i + k + 1] - knot_vec[i]; + let a = inv_or_zero(delta) * (knot_vec[idx] - knot_vec[i]); + if a.so_small() { + break; + } else { + let vec = self + .ctrl_pts_row_iter(i) + .zip(new_points.last().unwrap()) + .map(|(pt0, pt1)| *pt1 + (*pt0 - *pt1) / a) + .collect(); + new_points.push(vec); + } + } + + for (pt0, pt1) in self.ctrl_pts_row_iter(idx).zip(new_points.last().unwrap()) { + if !pt0.near(pt1) { + return Err(Error::CannotRemoveKnot(idx)); + } + } + + for (i, vec) in new_points.into_iter().skip(1).enumerate() { + for (j, pt) in vec.into_iter().enumerate() { + self.control_points[j][idx - k + i] = pt; + } + } + + for vec in &mut self.control_points { + vec.remove(idx); + } + self.knot_vecs.1.remove(idx); + Ok(self) + } + + /// Removes a 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`. + /// # 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(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// vec![Vector2::new(0.0, 2.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 2.0)], + /// ]; + /// let mut bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let org_surface = bspsurface.clone(); + /// + /// bspsurface.add_vknot(0.3).add_vknot(0.5); + /// bspsurface.remove_vknot(3).remove_vknot(3); + /// + /// assert!(bspsurface.near2_as_surface(&org_surface)); + /// assert_eq!(bspsurface.vknot_vec().len(), org_surface.vknot_vec().len()) + /// ``` + #[inline(always)] + pub fn remove_vknot(&mut self, idx: usize) -> &mut Self { + let _ = self.try_remove_vknot(idx); + self + } + + /// Elevates the vdegree. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// vec![Vector2::new(0.0, 2.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 2.0)], + /// ]; + /// let mut bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let org_surface = bspsurface.clone(); + /// + /// bspsurface.elevate_vdegree(); + /// + /// assert_eq!(bspsurface.udegree(), org_surface.udegree()); + /// assert_eq!(bspsurface.vdegree(), org_surface.vdegree() + 1); + /// assert!(bspsurface.near2_as_surface(&org_surface)); + /// ``` + pub fn elevate_vdegree(&mut self) -> &mut Self { + let mut new_knot_vec = KnotVec::new(); + for (i, vec) in self.control_points.iter_mut().enumerate() { + let knot_vec = self.knot_vecs.1.clone(); + let ctrl_pts = vec.clone(); + let mut curve = BSplineCurve::new(knot_vec, ctrl_pts); + curve.elevate_degree(); + if i == 0 { + new_knot_vec = curve.knot_vec().clone(); + } + *vec = curve.control_points; + } + self.knot_vecs.1 = new_knot_vec; + self + } + + /// Elevates the udegree. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 1.0)], + /// vec![Vector2::new(0.0, 2.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 2.0)], + /// ]; + /// let mut bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let org_surface = bspsurface.clone(); + /// + /// bspsurface.elevate_udegree(); + /// + /// assert_eq!(bspsurface.udegree(), org_surface.udegree() + 1); + /// assert_eq!(bspsurface.vdegree(), org_surface.vdegree()); + /// assert!(bspsurface.near2_as_surface(&org_surface)); + /// ``` + pub fn elevate_udegree(&mut self) -> &mut Self { + self.swap_axes(); + self.elevate_vdegree(); + self.swap_axes(); + self + } + + /// Aligns the udegree with the same degrees. + /// # 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 mut bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let org_surface = bspsurface.clone(); + /// + /// assert_ne!(bspsurface.udegree(), bspsurface.vdegree()); + /// bspsurface.syncro_uvdegrees(); + /// assert_eq!(bspsurface.udegree(), bspsurface.vdegree()); + /// assert!(bspsurface.near2_as_surface(&org_surface)); + /// ``` + pub fn syncro_uvdegrees(&mut self) -> &mut Self { + if self.udegree() > self.vdegree() { + for _ in 0..(self.udegree() - self.vdegree()) { + self.elevate_vdegree(); + } + } + if self.vdegree() > self.udegree() { + for _ in 0..(self.vdegree() - self.udegree()) { + self.elevate_udegree(); + } + } + self + } + + /// Makes the uknot vector and the vknot vector the same knot vector. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let uknot_vec = KnotVec::uniform_knot(1, 2); + /// 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)], + /// vec![Vector3::new(0.0, 2.0, 0.0), Vector3::new(1.0, 2.0, 1.0), Vector3::new(2.0, 2.0, 2.0)], + /// ]; + /// let mut bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let org_surface = bspsurface.clone(); + /// + /// assert_ne!(bspsurface.uknot_vec(), bspsurface.vknot_vec()); + /// bspsurface.syncro_uvknots(); + /// assert_eq!(bspsurface.uknot_vec(), bspsurface.vknot_vec()); + /// assert!(bspsurface.near2_as_surface(&org_surface)); + /// ``` + pub fn syncro_uvknots(&mut self) -> &mut Self { + self.knot_vecs.0.normalize(); + self.knot_vecs.1.normalize(); + let mut i = 0; + let mut j = 0; + while !self.uknot(i).near2(&1.0) || !self.vknot(j).near2(&1.0) { + if self.uknot(i) - self.vknot(j) > TOLERANCE { + self.add_uknot(self.vknot(j)); + } else if self.vknot(j) - self.uknot(i) > TOLERANCE { + self.add_vknot(self.uknot(i)); + } + i += 1; + j += 1; + } + + let ulen = self.uknot_vec().len(); + let vlen = self.vknot_vec().len(); + use std::cmp::Ordering; + match usize::cmp(&ulen, &vlen) { + Ordering::Less => { + for _ in 0..vlen - ulen { + self.add_uknot(1.0); + } + } + Ordering::Greater => { + for _ in 0..ulen - vlen { + self.add_vknot(1.0); + } + } + _ => {} + } + self + } + + /// Cuts the surface into two surfaces at the parameter `u` + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// + /// let knot_vec0 = KnotVec::uniform_knot(2, 2); + /// let knot_vec1 = KnotVec::uniform_knot(2, 2); + /// let ctrl_pts0 = vec![ + /// Vector2::new(0.0, 0.0), Vector2::new(0.5, 0.0), Vector2::new(2.0, 0.0), Vector2::new(2.5, 0.0), + /// ]; + /// let ctrl_pts1 = vec![ + /// Vector2::new(0.0, 1.0), Vector2::new(0.5, 1.0), Vector2::new(2.0, 1.0), Vector2::new(2.5, 1.0), + /// ]; + /// let ctrl_pts2 = vec![ + /// Vector2::new(0.0, 1.5), Vector2::new(0.5, 1.5), Vector2::new(2.0, 1.5), Vector2::new(2.5, 1.5), + /// ]; + /// let ctrl_pts3 = vec![ + /// Vector2::new(0.0, 2.5), Vector2::new(0.5, 2.5), Vector2::new(2.0, 2.5), Vector2::new(2.5, 2.5), + /// ]; + /// let ctrl_pts = vec![ctrl_pts0, ctrl_pts1, ctrl_pts2, ctrl_pts3]; + /// let bspsurface = BSplineSurface::new((knot_vec0, knot_vec1), ctrl_pts); + /// + /// let mut part0 = bspsurface.clone(); + /// let part1 = part0.ucut(0.68); + /// const N: usize = 100; + /// for i in 0..=N { + /// for j in 0..=N { + /// let u = 0.68 * (i as f64) / (N as f64); + /// let v = 1.0 * (j as f64) / (N as f64); + /// assert_near2!(bspsurface.subs(u, v), part0.subs(u, v)); + /// } + /// } + /// for i in 0..=N { + /// for j in 0..=N { + /// let u = 0.68 + 0.32 * (i as f64) / (N as f64); + /// let v = 1.0 * (j as f64) / (N as f64); + /// assert_near2!(bspsurface.subs(u, v), part1.subs(u, v)); + /// } + /// } + /// ``` + pub fn ucut(&mut self, mut u: f64) -> BSplineSurface

{ + let degree = self.udegree(); + + let idx = match self.uknot_vec().floor(u) { + Some(idx) => idx, + None => { + let bspline = self.clone(); + let uknot_vec = KnotVec::from(vec![u, self.vknot_vec()[0]]); + let vknot_vec = self.vknot_vec().clone(); + let ctrl_pts = vec![vec![P::origin(); vknot_vec.len()]]; + *self = BSplineSurface::new((uknot_vec, vknot_vec), ctrl_pts); + return bspline; + } + }; + let s = if u.near(&self.uknot_vec()[idx]) { + u = self.uknot_vec()[idx]; + self.uknot_vec().multiplicity(idx) + } else { + 0 + }; + + for _ in s..=degree { + self.add_uknot(u); + } + + let vknot_vec = self.vknot_vec().clone(); + let k = self.uknot_vec().floor(u).unwrap(); + let m = self.uknot_vec().len(); + let n = self.control_points.len(); + let knot_vec0 = self.uknot_vec().sub_vec(0..=k); + let knot_vec1 = self.uknot_vec().sub_vec((k - degree)..m); + let control_points0 = Vec::from(&self.control_points[0..(k - degree)]); + let control_points1 = Vec::from(&self.control_points[(k - degree)..n]); + *self = BSplineSurface::new((knot_vec0, vknot_vec.clone()), control_points0); + BSplineSurface::new((knot_vec1, vknot_vec), control_points1) + } + /// Cuts the curve to two curves at the parameter `t` + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// + /// let knot_vec0 = KnotVec::uniform_knot(2, 2); + /// let knot_vec1 = KnotVec::uniform_knot(2, 2); + /// let ctrl_pts0 = vec![ + /// Vector2::new(0.0, 0.0), Vector2::new(0.5, 0.0), Vector2::new(2.0, 0.0), Vector2::new(2.5, 0.0), + /// ]; + /// let ctrl_pts1 = vec![ + /// Vector2::new(0.0, 1.0), Vector2::new(0.5, 1.0), Vector2::new(2.0, 1.0), Vector2::new(2.5, 1.0), + /// ]; + /// let ctrl_pts2 = vec![ + /// Vector2::new(0.0, 1.5), Vector2::new(0.5, 1.5), Vector2::new(2.0, 1.5), Vector2::new(2.5, 1.5), + /// ]; + /// let ctrl_pts3 = vec![ + /// Vector2::new(0.0, 2.5), Vector2::new(0.5, 2.5), Vector2::new(2.0, 2.5), Vector2::new(2.5, 2.5), + /// ]; + /// let ctrl_pts = vec![ctrl_pts0, ctrl_pts1, ctrl_pts2, ctrl_pts3]; + /// let bspsurface = BSplineSurface::new((knot_vec0, knot_vec1), ctrl_pts); + /// + /// let mut part0 = bspsurface.clone(); + /// let part1 = part0.vcut(0.68); + /// const N: usize = 100; + /// for i in 0..=N { + /// for j in 0..=N { + /// let u = 1.0 * (i as f64) / (N as f64); + /// let v = 0.68 * (j as f64) / (N as f64); + /// assert_near2!(bspsurface.subs(u, v), part0.subs(u, v)); + /// } + /// } + /// for i in 0..=N { + /// for j in 0..=N { + /// let u = 1.0 * (i as f64) / (N as f64); + /// let v = 0.68 + 0.32 * (j as f64) / (N as f64); + /// assert_near2!(bspsurface.subs(u, v), part1.subs(u, v)); + /// } + /// } + /// ``` + pub fn vcut(&mut self, v: f64) -> BSplineSurface

{ + self.swap_axes(); + let mut res = self.ucut(v); + self.swap_axes(); + res.swap_axes(); + res + } + + /// Creates a sectional curve with normalized knot vector from the parameter `p` to the parameter `q`. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// + /// // a parabola surface: x = 2u - 1, y = 2v - 1, z = x^2 + y^z + /// let knot_vecs = (KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector3::new(-1.0, -1.0, 2.0), Vector3::new(-1.0, 0.0, 0.0), Vector3::new(-1.0, 1.0, 2.0)], + /// vec![Vector3::new(0.0, -1.0, 0.0), Vector3::new(0.0, 0.0, -2.0), Vector3::new(0.0, 1.0, 0.0)], + /// vec![Vector3::new(1.0, -1.0, 2.0), Vector3::new(1.0, 0.0, 0.0), Vector3::new(1.0, 1.0, 2.0)], + /// ]; + /// let mut bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// + /// // add some knots for the test! + /// bspsurface.add_uknot(0.26); + /// # bspsurface.add_uknot(0.64); + /// bspsurface.add_vknot(0.23); + /// # bspsurface.add_vknot(0.82); + /// + /// let bnd_box = BoundingBox::from_iter(&[Vector2::new(0.2, 0.3), Vector2::new(0.8, 0.6)]); + /// let curve = bspsurface.sectional_curve(bnd_box); + /// const N: usize = 100; + /// assert_near2!(curve.subs(0.0), bspsurface.subs(0.2, 0.3)); + /// assert_near2!(curve.subs(1.0), bspsurface.subs(0.8, 0.6)); + /// for i in 0..=N { + /// let t = i as f64 / N as f64; + /// let pt = curve.subs(t); + /// assert_near2!(pt[1], pt[0] * 0.5 - 0.1); + /// assert_near2!(pt[2], pt[0] * pt[0] + pt[1] * pt[1]); + /// } + /// ``` + pub fn sectional_curve(&self, bnd_box: BoundingBox) -> BSplineCurve

{ + let p = bnd_box.min(); + let q = bnd_box.max(); + let mut bspsurface = self.clone(); + if !p[0].near(&bspsurface.uknot(0)) { + bspsurface = bspsurface.ucut(p[0]); + } + if !q[0].near(&bspsurface.uknot(bspsurface.uknot_vec().len() - 1)) { + bspsurface.ucut(q[0]); + } + if !p[0].near(&bspsurface.vknot(0)) { + bspsurface = bspsurface.vcut(p[1]); + } + if !q[0].near(&bspsurface.vknot(bspsurface.vknot_vec().len() - 1)) { + bspsurface.vcut(q[1]); + } + bspsurface.syncro_uvdegrees(); + bspsurface.syncro_uvknots(); + let degree = bspsurface.udegree(); + let comb = combinatorial(degree); + let comb2 = combinatorial(degree * 2); + let (knots, _) = bspsurface.uknot_vec().to_single_multi(); + let mut cc = CurveCollector::Singleton; + for p in 1..knots.len() { + let mut backup = None; + if p + 1 != knots.len() { + backup = Some(bspsurface.ucut(knots[p])); + bspsurface.vcut(knots[p]); + } + let mut knot_vec = KnotVec::bezier_knot(degree * 2); + knot_vec.translate(p as f64 - 1.0); + let ctrl_pts: Vec<_> = (0..=degree * 2) + .map(|k| { + (0..=k).fold(P::origin(), |sum, i| { + if i <= degree && k - i <= degree { + let coef = (comb[i] * comb[k - i]) as f64 / comb2[k] as f64; + sum + bspsurface.control_points[i][k - i].to_vec() * coef + } else { + sum + } + }) + }) + .collect(); + cc.concat(&BSplineCurve::new(knot_vec, ctrl_pts)); + if p + 1 != knots.len() { + bspsurface = backup.unwrap().vcut(knots[p]); + } + } + let mut curve: BSplineCurve

= cc.unwrap(); + curve.knot_normalize(); + curve + } + + /// Creates a surface with normailized knot vectors connecting two curves. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vec0 = KnotVec::bezier_knot(2); + /// let ctrl_pts0 = vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)]; + /// let bspcurve0 = BSplineCurve::new(knot_vec0, ctrl_pts0); + /// + /// let knot_vec1 = KnotVec::bezier_knot(2); + /// let ctrl_pts1 = vec![Vector2::new(0.0, 2.0), Vector2::new(0.5, 1.0), Vector2::new(1.0, 2.0)]; + /// let bspcurve1 = BSplineCurve::new(knot_vec1, ctrl_pts1); + /// + /// let homotopy_surface = BSplineSurface::homotopy(bspcurve0, bspcurve1); + /// assert_eq!( + /// homotopy_surface.control_points(), + /// &vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.0, 2.0)], + /// vec![Vector2::new(0.5, -1.0), Vector2::new(0.5, 1.0)], + /// vec![Vector2::new(1.0, 0.0), Vector2::new(1.0, 2.0)], + /// ], + /// ); + /// ``` + pub fn homotopy( + mut bspcurve0: BSplineCurve

, + mut bspcurve1: BSplineCurve

, + ) -> BSplineSurface

{ + bspcurve0.syncro_degree(&mut bspcurve1); + + //bspcurve0.optimize(); + //bspcurve1.optimize(); + + bspcurve0.syncro_knots(&mut bspcurve1); + + let uknot_vec = bspcurve0.knot_vec().clone(); + let vknot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]); + let mut control_points = Vec::new(); + for i in 0..bspcurve0.control_points().len() { + control_points.push(Vec::new()); + control_points[i].push(*bspcurve0.control_point(i)); + control_points[i].push(*bspcurve1.control_point(i)); + } + BSplineSurface::new_unchecked((uknot_vec, vknot_vec), control_points) + } + + /// Creates a surface by its boundary. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let curve0 = BSplineCurve::new( + /// KnotVec::bezier_knot(1), + /// vec![Vector2::new(0.0, 0.0), Vector2::new(1.0, 0.0)], + /// ); + /// let curve1 = BSplineCurve::new( + /// KnotVec::bezier_knot(2), + /// vec![Vector2::new(1.0, 0.0), Vector2::new(2.0, 0.5), Vector2::new(1.0, 1.0)], + /// ); + /// let curve2 = BSplineCurve::new( + /// KnotVec::bezier_knot(1), + /// vec![Vector2::new(1.0, 1.0), Vector2::new(0.0, 1.0)], + /// ); + /// let curve3 = BSplineCurve::new( + /// KnotVec::bezier_knot(2), + /// vec![Vector2::new(0.0, 1.0), Vector2::new(-1.0, 0.5), Vector2::new(0.0, 0.0)], + /// ); + /// let surface = BSplineSurface::by_boundary(curve0, curve1, curve2, curve3); + /// assert_eq!( + /// surface.control_points(), + /// &vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(-1.0, 0.5), Vector2::new(0.0, 1.0)], + /// vec![Vector2::new(1.0, 0.0), Vector2::new(2.0, 0.5), Vector2::new(1.0, 1.0)], + /// ], + /// ); + /// ``` + /// # Remarks + /// If the end points of curves are not connected, `curve1` and `curve3` take precedence. i.e. + /// `curve1` and `curve3` are contained in the boundary of the surface and `curve0` and + /// `curve2` are not contained. + /// ``` + /// use truck_geometry::prelude::*; + /// let curve0 = BSplineCurve::new( + /// KnotVec::bezier_knot(1), + /// vec![Vector2::new(0.0, 0.0), Vector2::new(1.0, 0.0)], + /// ); + /// let curve1 = BSplineCurve::new( + /// KnotVec::bezier_knot(2), + /// vec![Vector2::new(2.0, 0.0), Vector2::new(3.0, 0.5), Vector2::new(2.0, 1.0)], + /// ); + /// let curve2 = BSplineCurve::new( + /// KnotVec::bezier_knot(1), + /// vec![Vector2::new(1.0, 1.0), Vector2::new(0.0, 1.0)], + /// ); + /// let curve3 = BSplineCurve::new( + /// KnotVec::bezier_knot(2), + /// vec![Vector2::new(-1.0, 1.0), Vector2::new(-2.0, 0.5), Vector2::new(-1.0, 0.0)], + /// ); + /// let surface = BSplineSurface::by_boundary( + /// curve0.clone(), + /// curve1.clone(), + /// curve2.clone(), + /// curve3.clone() + /// ); + /// assert_ne!(surface.subs(0.0, 0.0), curve0.subs(0.0)); + /// assert_eq!(surface.subs(0.0, 0.0), curve3.subs(1.0)); + /// ``` + pub fn by_boundary( + mut curve0: BSplineCurve

, + mut curve1: BSplineCurve

, + mut curve2: BSplineCurve

, + mut curve3: BSplineCurve

, + ) -> BSplineSurface

{ + curve2.invert(); + curve3.invert(); + curve0.syncro_degree(&mut curve2); + curve0.optimize(); + curve2.optimize(); + curve0.syncro_knots(&mut curve2); + curve1.syncro_degree(&mut curve3); + curve1.optimize(); + curve3.optimize(); + curve1.syncro_knots(&mut curve3); + + let knot_vecs = (curve0.knot_vec().clone(), curve3.knot_vec().clone()); + let mut control_points = vec![curve3.control_points().clone()]; + let n = curve0.control_points().len(); + let m = curve3.control_points().len(); + for i in 1..(n - 1) { + let u = (i as f64) / (n as f64); + let pt0 = curve2.control_points[i] + + (curve0.control_points[i] - curve2.control_points[i]) * u; + let mut new_row = vec![*curve0.control_point(i)]; + for j in 1..(m - 1) { + let v = (j as f64) / (m as f64); + let pt1 = curve1.control_points[j] + + (curve3.control_points[j] - curve1.control_points[j]) * v; + new_row.push(pt0 + (pt1 - pt0) / 2.0); + } + new_row.push(*curve2.control_point(i)); + control_points.push(new_row); + } + control_points.push(curve1.control_points().clone()); + BSplineSurface::new(knot_vecs, control_points) + } + + /// Normalizes the knot vectors + #[inline(always)] + pub fn knot_normalize(&mut self) -> &mut Self { + self.knot_vecs.0.normalize(); + self.knot_vecs.1.normalize(); + self + } + + /// Translates the knot vectors. + #[inline(always)] + pub fn knot_translate(&mut self, x: f64, y: f64) -> &mut Self { + self.knot_vecs.0.translate(x); + self.knot_vecs.1.translate(y); + self + } + + /// Removes knots in order from the back + pub fn optimize(&mut self) -> &mut Self { + loop { + let (n0, n1) = (self.knot_vecs.0.len(), self.knot_vecs.1.len()); + let mut flag = true; + for i in 1..=n0 { + flag = flag && self.try_remove_uknot(n0 - i).is_err(); + } + for j in 1..=n1 { + flag = flag && self.try_remove_vknot(n1 - j).is_err(); + } + if flag { + break; + } + } + self + } + + /// Gets the boundary by four splitted curves. + /// # Examples + /// ``` + /// use truck_geometry::prelude::*; + /// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2)); + /// let ctrl_pts = vec![ + /// vec![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 1.0), Vector2::new(1.0, 1.0)], + /// vec![Vector2::new(0.0, 2.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 2.0)], + /// vec![Vector2::new(0.0, 3.0), Vector2::new(0.5, 3.5), Vector2::new(1.0, 3.0)], + /// ]; + /// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let curves = bspsurface.splitted_boundary(); + /// assert_eq!( + /// curves[0].control_points(), + /// &vec![Vector2::new(0.0, 0.0), Vector2::new(0.0, 1.0), Vector2::new(0.0, 2.0), Vector2::new(0.0, 3.0)], + /// ); + /// assert_eq!( + /// curves[1].control_points(), + /// &vec![Vector2::new(0.0, 3.0), Vector2::new(0.5, 3.5), Vector2::new(1.0, 3.0)], + /// ); + /// assert_eq!( + /// curves[2].control_points(), + /// &vec![Vector2::new(1.0, 3.0), Vector2::new(1.0, 2.0), Vector2::new(1.0, 1.0), Vector2::new(1.0, 0.0)], + /// ); + /// assert_eq!( + /// curves[3].control_points(), + /// &vec![Vector2::new(1.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(0.0, 0.0)], + /// ); + /// ``` + pub fn splitted_boundary(&self) -> [BSplineCurve

; 4] { + let (uknot_vec, vknot_vec) = self.knot_vecs.clone(); + let control_points0 = self.control_points.iter().map(|x| x[0]).collect(); + let control_points1 = self.control_points.last().unwrap().clone(); + let control_points2 = self + .control_points + .iter() + .map(|x| *x.last().unwrap()) + .collect(); + let control_points3 = self.control_points[0].clone(); + let curve0 = BSplineCurve::new_unchecked(uknot_vec.clone(), control_points0); + let curve1 = BSplineCurve::new_unchecked(vknot_vec.clone(), control_points1); + let mut curve2 = BSplineCurve::new_unchecked(uknot_vec, control_points2); + curve2.invert(); + let mut curve3 = BSplineCurve::new_unchecked(vknot_vec, control_points3); + curve3.invert(); + [curve0, curve1, curve2, curve3] + } + + /// Extracts the boundary of surface + pub fn boundary(&self) -> BSplineCurve

{ + let (uknot_vec, vknot_vec) = self.knot_vecs.clone(); + let (range0, range1) = (uknot_vec.range_length(), vknot_vec.range_length()); + let [bspline0, mut bspline1, mut bspline2, mut bspline3] = self.splitted_boundary(); + bspline0 + .concat(bspline1.knot_translate(range0)) + .concat(bspline2.invert().knot_translate(range0 + range1)) + .concat(bspline3.invert().knot_translate(range0 * 2.0 + range1)) + } + /// Determines whether `self` and `other` is near as the B-spline 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![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 1.0), Vector2::new(1.0, 1.0)], + /// vec![Vector2::new(0.0, 2.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 2.0)], + /// vec![Vector2::new(0.0, 3.0), Vector2::new(0.5, 3.5), Vector2::new(1.0, 3.0)], + /// ]; + /// let bspsurface0 = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let mut bspsurface1 = bspsurface0.clone(); + /// assert!(bspsurface0.near_as_surface(&bspsurface1)); + /// + /// *bspsurface1.control_point_mut(1, 1) = Vector2::new(0.4, 1.0); + /// assert!(!bspsurface0.near_as_surface(&bspsurface1)); + /// ``` + #[inline(always)] + pub fn near_as_surface(&self, other: &BSplineSurface

) -> bool { + self.sub_near_as_surface(other, 1, |x, y| x.near(y)) + } + /// Determines whether `self` and `other` is near in square order as the B-spline 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![Vector2::new(0.0, 0.0), Vector2::new(0.5, -1.0), Vector2::new(1.0, 0.0)], + /// vec![Vector2::new(0.0, 1.0), Vector2::new(0.5, 1.0), Vector2::new(1.0, 1.0)], + /// vec![Vector2::new(0.0, 2.0), Vector2::new(0.5, 2.0), Vector2::new(1.0, 2.0)], + /// vec![Vector2::new(0.0, 3.0), Vector2::new(0.5, 3.5), Vector2::new(1.0, 3.0)], + /// ]; + /// let bspsurface0 = BSplineSurface::new(knot_vecs, ctrl_pts); + /// let mut bspsurface1 = bspsurface0.clone(); + /// assert!(bspsurface0.near_as_surface(&bspsurface1)); + /// + /// *bspsurface1.control_point_mut(1, 1) += Vector2::new(eps, eps / 2.0); + /// assert!(bspsurface0.near_as_surface(&bspsurface1)); + /// assert!(!bspsurface0.near2_as_surface(&bspsurface1)); + /// ``` + #[inline(always)] + pub fn near2_as_surface(&self, other: &BSplineSurface

) -> bool { + self.sub_near_as_surface(other, 1, |x, y| x.near2(y)) + } +} + +impl BSplineSurface { + /// Returns the bounding box including all control points. + #[inline(always)] + pub fn roughly_bounding_box(&self) -> BoundingBox { + self.control_points.iter().flatten().collect() + } +} + +impl> ParameterDivision2D for BSplineSurface

+where P: EuclideanSpace>::Diff> + + MetricSpace + + HashGen +{ + #[inline(always)] + fn parameter_division( + &self, + range: ((f64, f64), (f64, f64)), + tol: f64, + ) -> (Vec, Vec) { + algo::surface::parameter_division(self, range, tol) + } +} + +impl ParametricSurface3D for BSplineSurface {} + +impl BoundedSurface for BSplineSurface where BSplineSurface: ParametricSurface {} + +impl Invertible for BSplineSurface { + #[inline(always)] + fn invert(&mut self) { self.swap_axes(); } +} + +impl SearchParameter for BSplineSurface { + type Point = Point2; + fn search_parameter>( + &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 SearchParameter for BSplineSurface { + type Point = Point3; + fn search_parameter>( + &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

SearchNearestParameter for BSplineSurface

+where + P: ControlPoint + + EuclideanSpace>::Diff> + + MetricSpace, +

>::Diff: InnerSpace + Tolerance, +{ + type Point = P; + fn search_nearest_parameter>( + &self, + point: P, + 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 IncludeCurve> for BSplineSurface { + fn include(&self, curve: &BSplineCurve) -> bool { + let pt = curve.front(); + let mut hint = algo::surface::presearch(self, pt, self.range_tuple(), PRESEARCH_DIVISION); + hint = match algo::surface::search_parameter2d(self, pt, hint, 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 = ParametricCurve::subs(curve, t); + hint = match algo::surface::search_parameter2d(self, pt, hint, INCLUDE_CURVE_TRIALS) + { + Some(got) => got, + None => return false, + }; + if !ParametricSurface::subs(self, 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> for BSplineSurface { + fn include(&self, curve: &BSplineCurve) -> bool { + let pt = curve.front(); + let mut hint = algo::surface::presearch(self, pt, self.range_tuple(), PRESEARCH_DIVISION); + hint = match algo::surface::search_parameter3d(self, pt, hint, 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 = ParametricCurve::subs(curve, t); + hint = match algo::surface::search_parameter3d(self, pt, hint, INCLUDE_CURVE_TRIALS) + { + Some(got) => got, + None => return false, + }; + if !ParametricSurface::subs(self, 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> for BSplineSurface { + fn include(&self, curve: &NurbsCurve) -> bool { + let pt = curve.subs(curve.knot_vec()[0]); + let mut hint = algo::surface::presearch(self, pt, self.range_tuple(), PRESEARCH_DIVISION); + hint = match algo::surface::search_parameter3d(self, pt, hint, 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 algo::surface::search_parameter3d(self, pt, hint, INCLUDE_CURVE_TRIALS) + { + Some(got) => got, + None => return false, + }; + if !ParametricSurface::subs(self, 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 + } +} + +macro_rules! impl_mat_multi { + ($vector: ty, $matrix: ty) => { + impl Mul> for $matrix { + type Output = BSplineSurface<$vector>; + fn mul(self, mut spline: BSplineSurface<$vector>) -> Self::Output { + spline + .control_points + .iter_mut() + .flat_map(|vec| vec.iter_mut()) + .for_each(|vec| *vec = self * *vec); + spline + } + } + impl Mul<&BSplineSurface<$vector>> for $matrix { + type Output = BSplineSurface<$vector>; + fn mul(self, spline: &BSplineSurface<$vector>) -> Self::Output { self * spline.clone() } + } + }; +} + +macro_rules! impl_scalar_multi { + ($vector: ty, $scalar: ty) => { + impl_mat_multi!($vector, $scalar); + impl Mul<$scalar> for &BSplineSurface<$vector> { + type Output = BSplineSurface<$vector>; + fn mul(self, scalar: $scalar) -> Self::Output { scalar * self } + } + impl Mul<$scalar> for BSplineSurface<$vector> { + type Output = BSplineSurface<$vector>; + fn mul(self, scalar: $scalar) -> Self::Output { scalar * self } + } + }; +} + +impl_mat_multi!(Vector2, Matrix2); +impl_scalar_multi!(Vector2, f64); +impl_mat_multi!(Vector3, Matrix3); +impl_scalar_multi!(Vector3, f64); +impl_mat_multi!(Vector4, Matrix4); +impl_scalar_multi!(Vector4, f64); + +impl> Transformed for BSplineSurface

+where M: Transform

+{ + #[inline(always)] + fn transform_by(&mut self, trans: M) { + self.control_points + .iter_mut() + .flatten() + .for_each(|p| *p = trans.transform_point(*p)) + } +} + +impl<'de, P> Deserialize<'de> for BSplineSurface

+where P: Deserialize<'de> +{ + fn deserialize(deserializer: D) -> std::result::Result + where D: serde::Deserializer<'de> { + #[derive(Deserialize)] + struct BSplineSurface_

{ + knot_vecs: (KnotVec, KnotVec), + control_points: Vec>, + } + let BSplineSurface_ { + knot_vecs, + control_points, + } = BSplineSurface_::

::deserialize(deserializer)?; + Self::try_new(knot_vecs, control_points).map_err(serde::de::Error::custom) + } +} + +fn combinatorial(n: usize) -> Vec { + let mut res = vec![1]; + for i in 1..=n { + res.push(res[i - 1] * (n - i + 1) / i); + } + res +} + +#[test] +fn test_include_bspcurve2() { + let knot_vec = KnotVec::uniform_knot(2, 3); + let ctrl_pts = vec![ + vec![ + Point2::new(0.0, 0.0), + Point2::new(0.1, 0.0), + Point2::new(0.5, 0.0), + Point2::new(0.7, 0.0), + Point2::new(1.0, 0.0), + ], + vec![ + Point2::new(0.0, 0.1), + Point2::new(0.2, 0.2), + Point2::new(0.4, 0.3), + Point2::new(0.6, 0.2), + Point2::new(1.0, 0.3), + ], + vec![ + Point2::new(0.0, 0.5), + Point2::new(0.3, 0.6), + Point2::new(0.6, 0.4), + Point2::new(0.9, 0.6), + Point2::new(1.0, 0.5), + ], + vec![ + Point2::new(0.0, 0.7), + Point2::new(0.2, 0.8), + Point2::new(0.3, 0.6), + Point2::new(0.5, 0.9), + Point2::new(1.0, 0.7), + ], + vec![ + Point2::new(0.0, 1.0), + Point2::new(0.1, 1.0), + Point2::new(0.5, 1.0), + Point2::new(0.7, 1.0), + Point2::new(1.0, 1.0), + ], + ]; + let surface = BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts); + + let knot_vec0 = KnotVec::bezier_knot(2); + let ctrl_pts0 = vec![ + Point2::new(0.0, 0.0), + Point2::new(1.0, 1.0), + Point2::new(0.0, 1.0), + ]; + let curve0 = BSplineCurve::new(knot_vec0, ctrl_pts0); + assert!(surface.include(&curve0)); + + let knot_vec1 = KnotVec::bezier_knot(2); + let ctrl_pts1 = vec![ + Point2::new(0.0, 0.0), + Point2::new(2.5, 1.0), + Point2::new(0.0, 1.0), + ]; + let curve1 = BSplineCurve::new(knot_vec1, ctrl_pts1); + assert!(!surface.include(&curve1)); +} + +#[test] +fn test_include_bspcurve3() { + let knot_vec = KnotVec::uniform_knot(2, 3); + let ctrl_pts = vec![ + vec![ + Point3::new(0.0, 0.0, 0.0), + Point3::new(0.1, 0.0, 0.5), + Point3::new(0.5, 0.0, 0.3), + Point3::new(1.0, 0.0, 1.0), + ], + vec![ + Point3::new(0.0, 0.1, 0.1), + Point3::new(0.2, 0.2, 0.1), + Point3::new(0.4, 0.3, 0.4), + Point3::new(1.0, 0.3, 0.7), + ], + vec![ + Point3::new(0.0, 0.5, 0.4), + Point3::new(0.3, 0.6, 0.5), + Point3::new(0.6, 0.4, 1.0), + Point3::new(1.0, 0.5, 0.0), + ], + vec![ + Point3::new(0.0, 1.0, 1.0), + Point3::new(0.1, 1.0, 1.0), + Point3::new(0.5, 1.0, 0.5), + Point3::new(1.0, 1.0, 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); + assert!(surface.include(&curve)); + *curve.control_point_mut(2) += Vector3::new(0.0, 0.0, 0.001); + assert!(!surface.include(&curve)); +} diff --git a/vendor/truck-geometry/src/nurbs/knot_vec.rs b/vendor/truck-geometry/src/nurbs/knot_vec.rs new file mode 100644 index 00000000..c4a448b9 --- /dev/null +++ b/vendor/truck-geometry/src/nurbs/knot_vec.rs @@ -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 { 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 { + 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> { + 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 { + 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 = 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 = 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 = 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 = 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 = 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>(&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, Vec) { + 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, mults: Vec) -> Result { + 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`. do not sort, only check sorted. + pub fn try_from(vec: Vec) -> Result { + 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> for KnotVec { + /// constructs from `Vec`. 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 = knot_vec.into(); + /// assert_eq!(arr, vec![0.0, 1.0, 2.0, 3.0]); + /// ``` + fn from(mut vec: Vec) -> KnotVec { + vec.sort_by(|a, b| a.partial_cmp(b).unwrap()); + KnotVec(vec) + } +} + +impl From<&Vec> 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 = knot_vec.into(); + /// assert_eq!(arr, vec![0.0, 1.0, 2.0, 3.0]); + /// ``` + #[inline(always)] + fn from(vec: &Vec) -> KnotVec { + let mut copy_vec = vec.clone(); + copy_vec.sort_by(|a, b| a.partial_cmp(b).unwrap()); + KnotVec(copy_vec) + } +} + +impl From for Vec { + /// `KnotVec` into `Vec` + /// ``` + /// 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 = knot_vec.into(); + /// assert_eq!(vec, vec0); + /// ``` + #[inline(always)] + fn from(knotvec: KnotVec) -> Vec { knotvec.0 } +} + +impl FromIterator for KnotVec { + #[inline(always)] + fn from_iter>(iter: I) -> KnotVec { + KnotVec::try_from(iter.into_iter().collect::>()).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; + #[inline(always)] + fn deref(&self) -> &Vec { &self.0 } +} + +impl AsRef<[f64]> for KnotVec { + #[inline(always)] + fn as_ref(&self) -> &[f64] { &self.0 } +} + +impl<'de> Deserialize<'de> for KnotVec { + fn deserialize(deserializer: D) -> std::result::Result + where D: serde::Deserializer<'de> { + let vec = Vec::::deserialize(deserializer)?; + Self::try_from(vec).map_err(serde::de::Error::custom) + } +} diff --git a/vendor/truck-geometry/src/nurbs/mod.rs b/vendor/truck-geometry/src/nurbs/mod.rs new file mode 100644 index 00000000..24a55313 --- /dev/null +++ b/vendor/truck-geometry/src/nurbs/mod.rs @@ -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); + +/// 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

{ + knot_vec: KnotVec, // the knot vector + control_points: Vec

, // 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

{ + knot_vecs: (KnotVec, KnotVec), + control_points: Vec>, +} + +/// NURBS curve +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct NurbsCurve(BSplineCurve); + +/// NURBS surface +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct NurbsSurface(BSplineSurface); + +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 + } +} diff --git a/vendor/truck-geometry/src/nurbs/nurbscurve.rs b/vendor/truck-geometry/src/nurbs/nurbscurve.rs new file mode 100644 index 00000000..b6a769a2 --- /dev/null +++ b/vendor/truck-geometry/src/nurbs/nurbscurve.rs @@ -0,0 +1,619 @@ +use super::*; + +impl NurbsCurve { + /// Constructs the rationalized B-spline curve. + #[inline(always)] + pub const fn new(curve: BSplineCurve) -> Self { NurbsCurve(curve) } + + /// Returns the BSpline curve before rationalized. + #[inline(always)] + pub const fn non_rationalized(&self) -> &BSplineCurve { &self.0 } + + /// Returns the BSpline curve before rationalized. + #[inline(always)] + pub fn into_non_rationalized(self) -> BSplineCurve { 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 { &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 { + 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(&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> NurbsCurve { + /// 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, + weights: Vec, + ) -> Result { + 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 + ControlPoint> NurbsCurve { + /// Returns the closure of substitution. + #[inline(always)] + pub fn get_closure(&self) -> impl Fn(f64) -> V::Point + '_ { move |t| self.subs(t) } +} + +impl + ControlPoint> NurbsCurve +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 + ControlPoint + Tolerance> NurbsCurve { + /// 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 + ControlPoint + Tolerance> ParameterTransform + for NurbsCurve +{ + #[inline(always)] + fn parameter_transform(&mut self, scalar: f64, r#move: f64) -> &mut Self { + self.0.parameter_transform(scalar, r#move); + self + } +} + +impl + ControlPoint + Tolerance> Cut for NurbsCurve { + #[inline(always)] + fn cut(&mut self, t: f64) -> Self { NurbsCurve(self.0.cut(t)) } +} + +impl + ControlPoint + Tolerance> Concat> + for NurbsCurve +where >::Point: Debug +{ + type Output = NurbsCurve; + fn try_concat( + &self, + other: &Self, + ) -> std::result::Result>::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::(), + rand::random::(), + rand::random::(), + rand::random::() + 0.5, + ) + }) + .collect(), + )); + let mut part1 = part0.cut(0.56); + let w = 20.0 * rand::random::() - 10.0; + part1.transform_control_points(|vec| *vec *= w); + assert_near!(part0.back(), part1.front()); + concat_random_test(&part0, &part1, 10); +} + +impl + ControlPoint + Tolerance> NurbsCurve +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 + ControlPoint> ParameterDivision1D for NurbsCurve +where V::Point: MetricSpace + HashGen +{ + type Point = V::Point; + #[inline(always)] + fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec, Vec) { + algo::curve::parameter_division(self, range, tol) + } +} + +impl + ControlPoint> SearchNearestParameter for NurbsCurve +where + V::Point: MetricSpace, + ::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>( + &self, + point: V::Point, + hint: H, + trial: usize, + ) -> Option { + 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 + ControlPoint> SearchParameter for NurbsCurve +where + V::Point: MetricSpace, + ::Diff: InnerSpace + Tolerance, +{ + type Point = V::Point; + #[inline(always)] + fn search_parameter>( + &self, + point: V::Point, + hint: H, + trial: usize, + ) -> Option { + 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> NurbsCurve +where V::Point: Bounded +{ + /// Returns the bounding box including all control points. + #[inline(always)] + pub fn roughly_bounding_box(&self) -> BoundingBox { + self.0.control_points.iter().map(|p| p.to_point()).collect() + } +} + +impl + ControlPoint> ParametricCurve for NurbsCurve { + type Point = V::Point; + type Vector = ::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 + ControlPoint> BoundedCurve for NurbsCurve {} + +impl Invertible for NurbsCurve { + #[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 Transformed for NurbsCurve +where M: Copy + std::ops::Mul +{ + #[inline(always)] + fn transform_by(&mut self, trans: M) { + self.0 + .control_points + .iter_mut() + .for_each(move |v| *v = trans * *v) + } +} + +impl> From> for NurbsCurve { + fn from(bspcurve: BSplineCurve) -> NurbsCurve { + 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}"); + } +} diff --git a/vendor/truck-geometry/src/nurbs/nurbssurface.rs b/vendor/truck-geometry/src/nurbs/nurbssurface.rs new file mode 100644 index 00000000..16984dab --- /dev/null +++ b/vendor/truck-geometry/src/nurbs/nurbssurface.rs @@ -0,0 +1,920 @@ +use super::*; + +impl NurbsSurface { + /// constructor + #[inline(always)] + pub const fn new(bspsurface: BSplineSurface) -> Self { NurbsSurface(bspsurface) } + + /// Returns the nurbs surface before rationalized + #[inline(always)] + pub const fn non_rationalized(&self) -> &BSplineSurface { &self.0 } + /// Returns the nurbs surface before rationalized + #[inline(always)] + pub fn non_rationalized_mut(&mut self) -> &mut BSplineSurface { &mut self.0 } + + /// Returns the nurbs surface before rationalized + #[inline(always)] + pub fn into_non_rationalized(self) -> BSplineSurface { 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> { &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(&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 + std::iter::FusedIterator { + 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 { + 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 + 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 + where V: Clone { + NurbsCurve(self.0.row_curve(column_idx)) + } +} + +impl> NurbsSurface { + /// 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, + weights: Vec>, + ) -> Result { + 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::>()) + }) + .collect::>>()?; + Ok(Self(BSplineSurface::new_unchecked( + knot_vecs, + control_points, + ))) + } +} + +impl + ControlPoint> NurbsSurface { + /// 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) -> ::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) -> ::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) -> ::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) -> ::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) -> ::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 + ControlPoint> NurbsSurface +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 + ControlPoint + Tolerance> NurbsSurface { + /// 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; 4] { + TryFrom::try_from( + self.0 + .splitted_boundary() + .iter() + .cloned() + .map(NurbsCurve::new) + .collect::>(), + ) + .unwrap() + } + + /// Extracts the boundary of surface + #[inline(always)] + pub fn boundary(&self) -> NurbsCurve { NurbsCurve::new(self.0.boundary()) } +} + +impl> SearchNearestParameter for NurbsSurface +where + Self: ParametricSurface::Diff>, + V::Point: EuclideanSpace + MetricSpace, + ::Diff: InnerSpace + 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>( + &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> NurbsSurface +where V::Point: Bounded +{ + /// Returns the bounding box including all control points. + #[inline(always)] + pub fn roughly_bounding_box(&self) -> BoundingBox { + self.0 + .control_points + .iter() + .flatten() + .map(|pt| pt.to_point()) + .collect() + } +} + +impl SearchParameter for NurbsSurface { + 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>( + &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 Invertible for NurbsSurface { + #[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 + ControlPoint> ParametricSurface for NurbsSurface { + type Point = V::Point; + type Vector = ::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 { + #[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 + ControlPoint> ParameterDivision2D for NurbsSurface +where V::Point: MetricSpace + HashGen +{ + #[inline(always)] + fn parameter_division( + &self, + range: ((f64, f64), (f64, f64)), + tol: f64, + ) -> (Vec, Vec) { + algo::surface::parameter_division(self, range, tol) + } +} + +impl BoundedSurface for NurbsSurface where Self: ParametricSurface {} + +impl IncludeCurve> for NurbsSurface { + #[inline(always)] + fn include(&self, curve: &NurbsCurve) -> 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> for NurbsSurface { + #[inline(always)] + fn include(&self, curve: &BSplineCurve) -> 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> for NurbsSurface { + #[inline(always)] + fn include(&self, curve: &NurbsCurve) -> 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 Transformed for NurbsSurface +where M: Copy + std::ops::Mul +{ + #[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 for NurbsSurface { + 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>( + &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)); +} diff --git a/vendor/truck-geometry/src/specifieds/circle.rs b/vendor/truck-geometry/src/specifieds/circle.rs new file mode 100644 index 00000000..876be2f5 --- /dev/null +++ b/vendor/truck-geometry/src/specifieds/circle.rs @@ -0,0 +1,161 @@ +use super::*; +use std::f64::consts::PI; + +impl

UnitCircle

{ + /// constructor + #[inline] + pub const fn new() -> Self { Self(std::marker::PhantomData) } +} + +impl ParametricCurve for UnitCircle { + 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 {} + +impl ParametricCurve for UnitCircle { + 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 { Some(2.0 * PI) } + #[inline] + fn parameter_range(&self) -> ParameterRange { + (Bound::Included(0.0), Bound::Excluded(2.0 * PI)) + } +} + +impl BoundedCurve for UnitCircle {} + +impl

ParameterDivision1D for UnitCircle

+where UnitCircle

: ParametricCurve +{ + type Point = P; + fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec, Vec

) { + 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::>(); + let pts = params.iter().map(|t| self.subs(*t)).collect(); + (params, pts) + } +} + +impl SearchNearestParameter for UnitCircle { + type Point = Point2; + fn search_nearest_parameter>( + &self, + pt: Point2, + _: H, + _: usize, + ) -> Option { + 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::::new(); + for i in 0..N { + let t = 2.0 * PI * i as f64 / N as f64; + let a = 5.0 * rand::random::() + 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 for UnitCircle { + type Point = Point2; + fn search_parameter>(&self, pt: Point2, _: H, _: usize) -> Option { + 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::::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 for UnitCircle { + type Point = Point3; + fn search_nearest_parameter>( + &self, + pt: Point3, + _: H, + _: usize, + ) -> Option { + UnitCircle::::new().search_nearest_parameter(Point2::new(pt.x, pt.y), None, 0) + } +} + +impl SearchParameter for UnitCircle { + type Point = Point3; + fn search_parameter>(&self, pt: Point3, _: H, _: usize) -> Option { + if !f64::abs(pt.z).so_small() { + return None; + } + UnitCircle::::new().search_parameter(Point2::new(pt.x, pt.y), None, 0) + } +} + +#[test] +fn parameter_division() { + let c = UnitCircle::::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); + } +} diff --git a/vendor/truck-geometry/src/specifieds/hyperbola.rs b/vendor/truck-geometry/src/specifieds/hyperbola.rs new file mode 100644 index 00000000..a0c30bf8 --- /dev/null +++ b/vendor/truck-geometry/src/specifieds/hyperbola.rs @@ -0,0 +1,125 @@ +use super::*; + +impl

UnitHyperbola

{ + /// constructor + #[inline] + pub const fn new() -> UnitHyperbola

{ UnitHyperbola(std::marker::PhantomData) } +} + +impl ParametricCurve for UnitHyperbola { + 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 { + 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

ParameterDivision1D for UnitHyperbola

+where + UnitHyperbola

: ParametricCurve, + P: EuclideanSpace + MetricSpace + HashGen, +{ + type Point = P; + fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec, Vec

) { + algo::curve::parameter_division(self, range, tol) + } +} + +impl SearchNearestParameter for UnitHyperbola { + type Point = Point2; + fn search_nearest_parameter>( + &self, + p: Point2, + _: H, + _: usize, + ) -> Option { + 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 for UnitHyperbola { + type Point = Point3; + fn search_nearest_parameter>( + &self, + p: Point3, + _: H, + _trials: usize, + ) -> Option { + UnitHyperbola::::new().search_nearest_parameter( + Point2::new(p.x, p.y), + None, + _trials, + ) + } +} + +impl SearchParameter for UnitHyperbola { + type Point = Point2; + fn search_parameter>(&self, p: Point2, _: H, _: usize) -> Option { + let t = f64::asinh(p.y); + match p.near(&self.subs(t)) { + true => Some(t), + false => None, + } + } +} + +impl SearchParameter for UnitHyperbola { + type Point = Point3; + fn search_parameter>(&self, p: Point3, _: H, _: usize) -> Option { + let t = f64::asinh(p.y); + match p.near(&self.subs(t)) { + true => Some(t), + false => None, + } + } +} + +#[test] +fn snp_test() { + let curve = UnitHyperbola::::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::::new(); + let t = 100.0 * rand::random::() - 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()); +} diff --git a/vendor/truck-geometry/src/specifieds/line.rs b/vendor/truck-geometry/src/specifieds/line.rs new file mode 100644 index 00000000..22b7afda --- /dev/null +++ b/vendor/truck-geometry/src/specifieds/line.rs @@ -0,0 +1,217 @@ +use super::*; +use truck_base::cgmath64::control_point::ControlPoint; + +impl Line

{ + /// initialize line from vector + #[inline] + pub fn from_origin_direction(origin: P, direction: V) -> Self + where P: std::ops::Add { + Self(origin, origin + direction) + } + /// to a bspline curve + #[inline] + pub fn to_bspline(self) -> BSplineCurve

{ + BSplineCurve::new(KnotVec::bezier_knot(1), vec![self.0, self.1]) + } +} + +impl

Line

+where + P: EuclideanSpace, + P::Diff: InnerSpace, +{ + /// 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 { + /// 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) -> 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> ParametricCurve for Line

{ + 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> BoundedCurve for Line

{} + +impl> Cut for Line

{ + #[inline] + fn cut(&mut self, t: f64) -> Self { + let r = self.subs(t); + let res = Self(r, self.1); + self.1 = r; + res + } +} + +impl> ParameterDivision1D for Line

{ + type Point = P; + #[inline] + fn parameter_division(&self, range: (f64, f64), _: f64) -> (Vec, Vec

) { + ( + vec![range.0, range.1], + vec![self.subs(range.0), self.subs(range.1)], + ) + } +} + +impl Invertible for Line

{ + #[inline] + fn invert(&mut self) { *self = Self(self.1, self.0); } + #[inline] + fn inverse(&self) -> Self { Self(self.1, self.0) } +} + +impl

SearchNearestParameter for Line

+where + P: ControlPoint, + P::Diff: InnerSpace, +{ + type Point = P; + #[inline] + fn search_nearest_parameter>(&self, pt: P, _: H, _: usize) -> Option { + let b = self.1 - self.0; + Some((pt - self.0).dot(b) / b.dot(b)) + } +} + +impl

SearchParameter for Line

+where + P: ControlPoint + Tolerance, + P::Diff: InnerSpace, +{ + type Point = P; + #[inline] + fn search_parameter>(&self, pt: P, _: H, _: usize) -> Option { + 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> Transformed for Line

{ + #[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()); +} diff --git a/vendor/truck-geometry/src/specifieds/mod.rs b/vendor/truck-geometry/src/specifieds/mod.rs new file mode 100644 index 00000000..fc7c24a1 --- /dev/null +++ b/vendor/truck-geometry/src/specifieds/mod.rs @@ -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

(pub P, pub P); + +/// unit circle +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +pub struct UnitCircle

(std::marker::PhantomData

); + +/// unit hyperbola +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +pub struct UnitHyperbola

(std::marker::PhantomData

); + +/// parabola whose apex is the origin. +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +pub struct UnitParabola

(std::marker::PhantomData

); + +/// 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

PartialEq for $ty

{ + fn eq(&self, _: &Self) -> bool { true } + } + impl

Eq for $ty

{} + }; +} + +always_true!(UnitCircle); +always_true!(UnitParabola); +always_true!(UnitHyperbola); diff --git a/vendor/truck-geometry/src/specifieds/parabola.rs b/vendor/truck-geometry/src/specifieds/parabola.rs new file mode 100644 index 00000000..58b57f8d --- /dev/null +++ b/vendor/truck-geometry/src/specifieds/parabola.rs @@ -0,0 +1,137 @@ +use super::*; + +impl

UnitParabola

{ + /// constructor + #[inline] + pub const fn new() -> Self { Self(std::marker::PhantomData) } +} + +impl ParametricCurve for UnitParabola { + 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 { + 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

ParameterDivision1D for UnitParabola

+where + UnitParabola

: ParametricCurve, + P: EuclideanSpace + MetricSpace + HashGen, +{ + type Point = P; + fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec, Vec

) { + algo::curve::parameter_division(self, range, tol) + } +} + +impl SearchNearestParameter for UnitParabola { + type Point = Point2; + #[inline] + fn search_nearest_parameter>( + &self, + pt: Point2, + _: H, + _: usize, + ) -> Option { + 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 for UnitParabola { + type Point = Point3; + #[inline] + fn search_nearest_parameter>( + &self, + pt: Point3, + _hint: H, + _trials: usize, + ) -> Option { + UnitParabola::::new().search_nearest_parameter( + Point2::new(pt.x, pt.y), + _hint, + _trials, + ) + } +} + +impl SearchParameter for UnitParabola { + type Point = Point2; + #[inline] + fn search_parameter>(&self, pt: Point2, _: H, _: usize) -> Option { + let t = pt.y / 2.0; + let pt0 = self.subs(t); + match pt.near(&pt0) { + true => Some(t), + false => None, + } + } +} + +impl SearchParameter for UnitParabola { + type Point = Point3; + #[inline] + fn search_parameter>( + &self, + pt: Point3, + _hint: H, + _trials: usize, + ) -> Option { + match pt.z.so_small() { + true => UnitParabola::::new().search_parameter( + Point2::new(pt.x, pt.y), + _hint, + _trials, + ), + false => None, + } + } +} + +#[test] +fn snp_test() { + let curve = UnitParabola::::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::::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()); +} diff --git a/vendor/truck-geometry/src/specifieds/plane.rs b/vendor/truck-geometry/src/specifieds/plane.rs new file mode 100644 index 00000000..c5ffebdc --- /dev/null +++ b/vendor/truck-geometry/src/specifieds/plane.rs @@ -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 = 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 { + 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 = 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 { + 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> for Plane { + #[inline(always)] + fn include(&self, curve: &BSplineCurve) -> bool { + let origin = self.origin(); + let normal = self.normal(); + curve + .control_points() + .iter() + .all(|pt| (pt - origin).dot(normal).so_small()) + } +} + +impl IncludeCurve> for Plane { + fn include(&self, curve: &NurbsCurve) -> 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, Vec) { + (vec![range.0 .0, range.0 .1], vec![range.1 .0, range.1 .1]) + } +} + +impl> Transformed 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 for Plane { + type Point = Point3; + #[inline(always)] + fn search_parameter>( + &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 for Plane { + type Point = Point3; + #[inline(always)] + fn search_nearest_parameter>( + &self, + point: Point3, + _: H, + _: usize, + ) -> Option<(f64, f64)> { + let v = self.get_parameter(point); + Some((v[0], v[1])) + } +} diff --git a/vendor/truck-geometry/src/specifieds/sphere.rs b/vendor/truck-geometry/src/specifieds/sphere.rs new file mode 100644 index 00000000..257997e5 --- /dev/null +++ b/vendor/truck-geometry/src/specifieds/sphere.rs @@ -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 { 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> for Sphere { + #[inline(always)] + fn include(&self, curve: &BSplineCurve) -> bool { + curve.is_const() && self.include(curve.front()) + } +} + +impl IncludeCurve> for Sphere { + fn include(&self, curve: &NurbsCurve) -> 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, Vec) { + 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 for Sphere { + type Point = Point3; + #[inline(always)] + fn search_parameter>( + &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 for Sphere { + type Point = Point3; + #[inline(always)] + fn search_nearest_parameter>( + &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::() - 50.0, + 100.0 * rand::random::() - 50.0, + 100.0 * rand::random::() - 50.0, + ); + let radius = 100.0 * rand::random::(); + let sphere = Sphere::new(center, radius); + let u = PI * rand::random::(); + let v = 2.0 * PI * rand::random::(); + 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::() + 0.01) * f64::signum(rand::random::() - 0.5), + (0.1 * rand::random::() + 0.01) * f64::signum(rand::random::() - 0.5), + (0.1 * rand::random::() + 0.01) * f64::signum(rand::random::() - 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()) } diff --git a/vendor/truck-geometry/src/specifieds/torus.rs b/vendor/truck-geometry/src/specifieds/torus.rs new file mode 100644 index 00000000..d53a8bfd --- /dev/null +++ b/vendor/truck-geometry/src/specifieds/torus.rs @@ -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, Bound) = (Bound::Included(0.0), Bound::Excluded(2.0 * PI)); + (RANGE, RANGE) + } + #[inline(always)] + fn u_period(&self) -> Option { Some(2.0 * PI) } + #[inline(always)] + fn v_period(&self) -> Option { 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 for Torus { + type Point = Point3; + fn search_parameter>( + &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 for Torus { + type Point = Point3; + fn search_nearest_parameter>( + &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, Vec) { + let circle = UnitCircle::::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::() + 1.0; + let large_radius = 2.0 * small_radius + 5.0 * rand::random::(); + 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::() - 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); + } + } +}