diff --git a/.circleci/config.yml b/.circleci/config.yml
index ee64d771..b21798d9 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -47,10 +47,10 @@ jobs:
- checkout
- run:
name: test
- command: cargo test --all-features
+ command: cargo test --features arbitrary --features serde-serialize --features abomonation-serialize --features sparse --features debug --features io --features compare --features libm
- run:
name: test nalgebra-glm
- command: cargo test -p nalgebra-glm --all-features
+ command: cargo test -p nalgebra-glm --features arbitrary --features serde-serialize --features abomonation-serialize --features sparse --features debug --features io --features compare --features libm
build-wasm:
executor: rust-executor
steps:
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 356ddf23..5ee5fec9 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,7 +1,7 @@
# These are supported funding model platforms
-github: [ "sebcrozet" ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
-patreon: sebcrozet # Replace with a single Patreon username
+github: [ "dimforge" ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57f18f25..853529f0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,15 +4,37 @@ documented here.
This project adheres to [Semantic Versioning](https://semver.org/).
-## [0.22.0] - WIP
+## [0.23.0] - WIP
### Added
+ * The `.inverse_transform_unit_vector(v)` was added to `Rotation2/3`, `Isometry2/3`, `UnitQuaternion`, and `UnitComplex`.
+ It applies the corresponding rotation to a unit vector `Unit`.
+ * The `Point.map(f)` and `Point.apply(f)` to apply a function to each component of the point, similarly to `Vector.map(f)`
+ and `Vector.apply(f)`.
+ * The `Quaternion::from([N; 4])` conversion to build a quaternion from an array of four elements.
+ * The `Isometry::from(Translation)` conversion to build an isometry from a translation.
+ * The `Vector::ith_axis(i)` which build a unit vector, e.g., `Unit>` with its i-th component set to 1.0 and the
+ others set to zero.
+ * The `Isometry.lerp_slerp` and `Isometry.try_lerp_slerp` methods to interpolate between two isometries using linear
+ interpolation for the translational part, and spherical interpolation for the rotational part.
+ * The `Rotation2.slerp`, `Rotation3.slerp`, and `UnitQuaternion.slerp` method for
+ spherical interpolation.
+
+## [0.22.0]
+In this release, we are using the new version 0.2 of simba. One major change of that version is that the
+use of `libm` is now opt-in when building targetting `no-std` environment. If you are using floating-point
+operations with nalgebra in a `no-std` environment, you will need to enable the new `libm` feature
+of nalgebra for your code to compile again.
+
+### Added
+ * The `libm` feature that enables `libm` when building for `no-std` environment.
+ * The `libm-force` feature that enables `libm` even when building for a not `no-std` environment.
* `Cholesky::new_unchecked` which build a Cholesky decomposition without checking that its input is
positive-definite. It can be use with SIMD types.
* The `Default` trait is now implemented for matrices, and quaternions. They are all filled with zeros,
except for `UnitQuaternion` which is initialized with the identity.
* Matrix exponential `matrix.exp()`.
-
+ * The `Vector::ith(i, x)` that builds a vector filled with zeros except for the `i`-th component set to `x`.
## [0.21.0]
In this release, we are no longer relying on traits from the __alga__ crate for our generic code.
diff --git a/Cargo.toml b/Cargo.toml
index 4adf2ab1..a36df272 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "nalgebra"
-version = "0.21.1"
+version = "0.23.0"
authors = [ "Sébastien Crozet " ]
description = "Linear algebra library with transformations and statically-sized or dynamically-sized matrices."
@@ -10,7 +10,7 @@ repository = "https://github.com/rustsim/nalgebra"
readme = "README.md"
categories = [ "science" ]
keywords = [ "linear", "algebra", "matrix", "vector", "math" ]
-license = "BSD-3-Clause"
+license = "Apache-2.0"
edition = "2018"
exclude = ["/ci/*", "/.travis.yml", "/Makefile"]
@@ -24,32 +24,36 @@ default = [ "std" ]
std = [ "matrixmultiply", "rand/std", "rand_distr", "simba/std" ]
stdweb = [ "rand/stdweb" ]
arbitrary = [ "quickcheck" ]
-serde-serialize = [ "serde", "serde_derive", "num-complex/serde" ]
+serde-serialize = [ "serde", "num-complex/serde" ]
abomonation-serialize = [ "abomonation" ]
sparse = [ ]
debug = [ "approx/num-complex", "rand/std" ]
alloc = [ ]
io = [ "pest", "pest_derive" ]
+compare = [ "matrixcompare-core" ]
+libm = [ "simba/libm" ]
+libm-force = [ "simba/libm_force" ]
+
[dependencies]
-typenum = "1.11"
-generic-array = "0.13"
+typenum = "1.12"
+generic-array = "0.14"
rand = { version = "0.7", default-features = false }
num-traits = { version = "0.2", default-features = false }
-num-complex = { version = "0.2", default-features = false }
-num-rational = { version = "0.2", default-features = false }
-approx = { version = "0.3", default-features = false }
-simba = { version = "0.1", default-features = false }
+num-complex = { version = "0.3", default-features = false }
+num-rational = { version = "0.3", default-features = false }
+approx = { version = "0.4", default-features = false }
+simba = { version = "0.3", default-features = false }
alga = { version = "0.9", default-features = false, optional = true }
-rand_distr = { version = "0.2", optional = true }
+rand_distr = { version = "0.3", optional = true }
matrixmultiply = { version = "0.2", optional = true }
-serde = { version = "1.0", optional = true }
-serde_derive = { version = "1.0", optional = true }
+serde = { version = "1.0", features = [ "derive" ], optional = true }
abomonation = { version = "0.7", optional = true }
mint = { version = "0.5", optional = true }
quickcheck = { version = "0.9", optional = true }
-pest = { version = "2.0", optional = true }
-pest_derive = { version = "2.0", optional = true }
+pest = { version = "2", optional = true }
+pest_derive = { version = "2", optional = true }
+matrixcompare-core = { version = "0.1", optional = true }
[dev-dependencies]
serde_json = "1.0"
@@ -61,6 +65,9 @@ rand_isaac = "0.2"
### https://github.com/rust-lang/cargo/issues/4866
#criterion = "0.2.10"
+# For matrix comparison macro
+matrixcompare = "0.1.3"
+
[workspace]
members = [ "nalgebra-lapack", "nalgebra-glm" ]
@@ -71,6 +78,3 @@ path = "benches/lib.rs"
[profile.bench]
lto = true
-
-#[patch.crates-io]
-#simba = { path = "../simba" }
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index f7f80044..97f4383a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,27 +1,201 @@
-Copyright (c) 2013, Sébastien Crozet
-All rights reserved.
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-1. Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
+ 1. Definitions.
-2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
-3. Neither the name of the author nor the names of its contributors may be used
- to endorse or promote products derived from this software without specific
- prior written permission.
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ "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
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2020 Sébastien Crozet
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/Makefile b/Makefile
index 01707964..0af9f3f2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
all:
- cargo test --features "debug arbitrary serde-serialize abomonation-serialize"
+ cargo test --features "debug arbitrary serde-serialize abomonation-serialize compare"
# cargo check --features "debug arbitrary serde-serialize"
doc:
@@ -9,4 +9,4 @@ bench:
cargo bench
test:
- cargo test --features "debug arbitrary serde-serialize abomonation-serialize"
+ cargo test --features "debug arbitrary serde-serialize abomonation-serialize compare"
diff --git a/README.md b/README.md
index df3a82f3..72d058b5 100644
--- a/README.md
+++ b/README.md
@@ -5,14 +5,14 @@
-
-
+
+
-
-
+
+
@@ -29,11 +29,3 @@
-----
-
-
- Click this button if you wish to donate to support the development of nalgebra:
-
-
-
-
-
diff --git a/examples/matrixcompare.rs b/examples/matrixcompare.rs
new file mode 100644
index 00000000..713be25f
--- /dev/null
+++ b/examples/matrixcompare.rs
@@ -0,0 +1,82 @@
+extern crate nalgebra as na;
+
+use matrixcompare::comparators::{AbsoluteElementwiseComparator, ExactElementwiseComparator};
+use matrixcompare::compare_matrices;
+use na::{MatrixMN, U3, U4};
+
+fn compare_integers_fail() {
+ println!("Comparing two integer matrices.");
+
+ #[rustfmt::skip]
+ let a = MatrixMN::<_, U3, U4>::from_row_slice(&[
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, -2, 11
+ ]);
+
+ #[rustfmt::skip]
+ let b = MatrixMN::<_, U3, U4>::from_row_slice(&[
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11
+ ]);
+
+ if let Err(err) = compare_matrices(a, b, &ExactElementwiseComparator) {
+ println!("{}", err);
+ }
+}
+
+fn compare_different_size() {
+ println!("Comparing matrices of different size.");
+ #[rustfmt::skip]
+ let a = MatrixMN::<_, U3, U3>::from_row_slice(&[
+ 0, 1, 2,
+ 4, 5, 6,
+ 8, 9, 10,
+ ]);
+
+ #[rustfmt::skip]
+ let b = MatrixMN::<_, U3, U4>::from_row_slice(&[
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11
+ ]);
+
+ if let Err(err) = compare_matrices(a, b, &ExactElementwiseComparator) {
+ println!("{}", err);
+ }
+}
+
+fn compare_f64_abs_tol_fail() {
+ println!("Comparing two f64 matrices.");
+
+ #[rustfmt::skip]
+ let a = MatrixMN::::from_row_slice(&[
+ 0.0, 1.0, 2.0 + 1e-10,
+ 4.0, 5.0, 6.0,
+ 8.0, 9.0, 10.0,
+ ]);
+
+ #[rustfmt::skip]
+ let b = MatrixMN::<_, U3, U3>::from_row_slice(&[
+ 0.0, 1.0, 2.0,
+ 4.0, 5.0, 6.0,
+ 8.0, 9.0, 10.0
+ ]);
+
+ let cmp = AbsoluteElementwiseComparator { tol: 1e-12 };
+ if let Err(err) = compare_matrices(a, b, &cmp) {
+ println!("{}", err);
+ }
+}
+
+fn main() {
+ // This example mostly serves the purpose of demonstrating the kind of error messages
+ // that are given upon comparison failure.
+ // The more typical use case is using `assert_matrix_eq!` in tests.
+ compare_integers_fail();
+ println!("======================================================");
+ compare_f64_abs_tol_fail();
+ println!("======================================================");
+ compare_different_size();
+}
diff --git a/examples/reshaping.rs b/examples/reshaping.rs
new file mode 100644
index 00000000..60cb9bdd
--- /dev/null
+++ b/examples/reshaping.rs
@@ -0,0 +1,54 @@
+#![cfg_attr(rustfmt, rustfmt_skip)]
+
+extern crate nalgebra as na;
+
+use na::{DMatrix, Dynamic, Matrix2x3, Matrix3x2, U2, U3};
+
+fn main() {
+ // Matrices can be reshaped in-place without moving or copying values.
+ let m1 = Matrix2x3::new(
+ 1.1, 1.2, 1.3,
+ 2.1, 2.2, 2.3
+ );
+ let m2 = Matrix3x2::new(
+ 1.1, 2.2,
+ 2.1, 1.3,
+ 1.2, 2.3
+ );
+
+ let m3 = m1.reshape_generic(U3, U2);
+ assert_eq!(m3, m2);
+
+ // Note that, for statically sized matrices, invalid reshapes will not compile:
+ //let m4 = m3.reshape_generic(U3, U3);
+
+ // If dynamically sized matrices are used, the reshaping is checked at run-time.
+ let dm1 = DMatrix::from_row_slice(
+ 4,
+ 3,
+ &[
+ 1.0, 0.0, 0.0,
+ 0.0, 0.0, 1.0,
+ 0.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0
+ ],
+ );
+ let dm2 = DMatrix::from_row_slice(
+ 6,
+ 2,
+ &[
+ 1.0, 0.0,
+ 0.0, 1.0,
+ 0.0, 0.0,
+ 0.0, 1.0,
+ 0.0, 0.0,
+ 0.0, 0.0,
+ ],
+ );
+
+ let dm3 = dm1.reshape_generic(Dynamic::new(6), Dynamic::new(2));
+ assert_eq!(dm3, dm2);
+
+ // Invalid reshapings of dynamic matrices will panic at run-time.
+ //let dm4 = dm3.reshape_generic(Dynamic::new(6), Dynamic::new(6));
+}
diff --git a/nalgebra-glm/Cargo.toml b/nalgebra-glm/Cargo.toml
index b72e2988..f4e12f8e 100644
--- a/nalgebra-glm/Cargo.toml
+++ b/nalgebra-glm/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "nalgebra-glm"
-version = "0.7.0"
+version = "0.9.0"
authors = ["sebcrozet "]
description = "A computer-graphics oriented API for nalgebra, inspired by the C++ GLM library."
@@ -23,6 +23,6 @@ abomonation-serialize = [ "nalgebra/abomonation-serialize" ]
[dependencies]
num-traits = { version = "0.2", default-features = false }
-approx = { version = "0.3", default-features = false }
-simba = { version = "0.1", default-features = false }
-nalgebra = { path = "..", version = "0.21", default-features = false }
+approx = { version = "0.4", default-features = false }
+simba = { version = "0.3", default-features = false }
+nalgebra = { path = "..", version = "0.23", default-features = false }
diff --git a/nalgebra-glm/src/ext/matrix_clip_space.rs b/nalgebra-glm/src/ext/matrix_clip_space.rs
index 260c5196..5ee725ae 100644
--- a/nalgebra-glm/src/ext/matrix_clip_space.rs
+++ b/nalgebra-glm/src/ext/matrix_clip_space.rs
@@ -530,7 +530,7 @@ pub fn perspective_lh_no(aspect: N, fovy: N, near: N, far: N) -> T
);
assert!(
!relative_eq!(aspect, N::zero()),
- "The apsect ratio must not be zero."
+ "The aspect ratio must not be zero."
);
let one = N::one();
@@ -566,7 +566,7 @@ pub fn perspective_lh_zo(aspect: N, fovy: N, near: N, far: N) -> T
);
assert!(
!relative_eq!(aspect, N::zero()),
- "The apsect ratio must not be zero."
+ "The aspect ratio must not be zero."
);
let one = N::one();
@@ -632,7 +632,7 @@ pub fn perspective_rh_no(aspect: N, fovy: N, near: N, far: N) -> T
);
assert!(
!relative_eq!(aspect, N::zero()),
- "The apsect ratio must not be zero."
+ "The aspect ratio must not be zero."
);
let negone = -N::one();
@@ -669,7 +669,7 @@ pub fn perspective_rh_zo(aspect: N, fovy: N, near: N, far: N) -> T
);
assert!(
!relative_eq!(aspect, N::zero()),
- "The apsect ratio must not be zero."
+ "The aspect ratio must not be zero."
);
let negone = -N::one();
diff --git a/nalgebra-lapack/Cargo.toml b/nalgebra-lapack/Cargo.toml
index b262e8b9..e70359cc 100644
--- a/nalgebra-lapack/Cargo.toml
+++ b/nalgebra-lapack/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "nalgebra-lapack"
-version = "0.13.0"
+version = "0.14.0"
authors = [ "Sébastien Crozet ", "Andrew Straw " ]
description = "Linear algebra library with transformations and satically-sized or dynamically-sized matrices."
@@ -23,10 +23,10 @@ accelerate = ["lapack-src/accelerate"]
intel-mkl = ["lapack-src/intel-mkl"]
[dependencies]
-nalgebra = { version = "0.21", path = ".." }
+nalgebra = { version = "0.22" } # , path = ".." }
num-traits = "0.2"
num-complex = { version = "0.2", default-features = false }
-simba = "0.1"
+simba = "0.2"
serde = { version = "1.0", optional = true }
serde_derive = { version = "1.0", optional = true }
lapack = { version = "0.16", default-features = false }
@@ -34,7 +34,7 @@ lapack-src = { version = "0.5", default-features = false }
# clippy = "*"
[dev-dependencies]
-nalgebra = { version = "0.21", path = "..", features = [ "arbitrary" ] }
+nalgebra = { version = "0.22", features = [ "arbitrary" ] } # path = ".." }
quickcheck = "0.9"
approx = "0.3"
rand = "0.7"
diff --git a/rustfmt.toml b/rustfmt.toml
index 8dc8e61a..e69de29b 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1,3 +0,0 @@
-unstable_features = true
-indent_style = "Block"
-where_single_line = true
\ No newline at end of file
diff --git a/src/base/array_storage.rs b/src/base/array_storage.rs
index 1743f06b..b86e3a80 100644
--- a/src/base/array_storage.rs
+++ b/src/base/array_storage.rs
@@ -24,7 +24,9 @@ use typenum::Prod;
use crate::base::allocator::Allocator;
use crate::base::default_allocator::DefaultAllocator;
use crate::base::dimension::{DimName, U1};
-use crate::base::storage::{ContiguousStorage, ContiguousStorageMut, Owned, Storage, StorageMut};
+use crate::base::storage::{
+ ContiguousStorage, ContiguousStorageMut, Owned, ReshapableStorage, Storage, StorageMut,
+};
use crate::base::Scalar;
/*
@@ -267,6 +269,25 @@ where
{
}
+impl ReshapableStorage for ArrayStorage
+where
+ N: Scalar,
+ R1: DimName,
+ C1: DimName,
+ R1::Value: Mul,
+ Prod: ArrayLength,
+ R2: DimName,
+ C2: DimName,
+ R2::Value: Mul>,
+ Prod: ArrayLength,
+{
+ type Output = ArrayStorage;
+
+ fn reshape_generic(self, _: R2, _: C2) -> Self::Output {
+ ArrayStorage { data: self.data }
+ }
+}
+
/*
*
* Allocation-less serde impls.
diff --git a/src/base/blas.rs b/src/base/blas.rs
index add17af2..f245f24a 100644
--- a/src/base/blas.rs
+++ b/src/base/blas.rs
@@ -284,7 +284,16 @@ where
{
assert!(
self.nrows() == rhs.nrows(),
- "Dot product dimensions mismatch."
+ "Dot product dimensions mismatch for shapes {:?} and {:?}: left rows != right rows.",
+ self.shape(),
+ rhs.shape(),
+ );
+
+ assert!(
+ self.ncols() == rhs.ncols(),
+ "Dot product dimensions mismatch for shapes {:?} and {:?}: left cols != right cols.",
+ self.shape(),
+ rhs.shape(),
);
// So we do some special cases for common fixed-size vectors of dimension lower than 8
@@ -361,8 +370,8 @@ where
while self.nrows() - i >= 8 {
acc0 += unsafe {
- conjugate(self.get_unchecked((i + 0, j)).inlined_clone())
- * rhs.get_unchecked((i + 0, j)).inlined_clone()
+ conjugate(self.get_unchecked((i, j)).inlined_clone())
+ * rhs.get_unchecked((i, j)).inlined_clone()
};
acc1 += unsafe {
conjugate(self.get_unchecked((i + 1, j)).inlined_clone())
@@ -496,8 +505,9 @@ where
ShapeConstraint: DimEq + DimEq,
{
let (nrows, ncols) = self.shape();
- assert!(
- (ncols, nrows) == rhs.shape(),
+ assert_eq!(
+ (ncols, nrows),
+ rhs.shape(),
"Transposed dot product dimension mismatch."
);
diff --git a/src/base/cg.rs b/src/base/cg.rs
index 6a7db4bd..935388af 100644
--- a/src/base/cg.rs
+++ b/src/base/cg.rs
@@ -11,11 +11,12 @@ use crate::base::allocator::Allocator;
use crate::base::dimension::{DimName, DimNameDiff, DimNameSub, U1};
use crate::base::storage::{Storage, StorageMut};
use crate::base::{
- DefaultAllocator, Matrix3, Matrix4, MatrixN, Scalar, SquareMatrix, Unit, Vector, Vector3,
- VectorN,
+ DefaultAllocator, Matrix3, Matrix4, MatrixN, Scalar, SquareMatrix, Unit, Vector, Vector2,
+ Vector3, VectorN,
};
use crate::geometry::{
- Isometry, IsometryMatrix3, Orthographic3, Perspective3, Point, Point3, Rotation2, Rotation3,
+ Isometry, IsometryMatrix3, Orthographic3, Perspective3, Point, Point2, Point3, Rotation2,
+ Rotation3,
};
use simba::scalar::{ClosedAdd, ClosedMul, RealField};
@@ -70,6 +71,26 @@ impl Matrix3 {
pub fn new_rotation(angle: N) -> Self {
Rotation2::new(angle).to_homogeneous()
}
+
+ /// Creates a new homogeneous matrix that applies a scaling factor for each dimension with respect to point.
+ ///
+ /// Can be used to implement "zoom_to" functionality.
+ #[inline]
+ pub fn new_nonuniform_scaling_wrt_point(scaling: &Vector2, pt: &Point2) -> Self {
+ let _0 = N::zero();
+ let _1 = N::one();
+ Matrix3::new(
+ scaling.x,
+ _0,
+ pt.x - pt.x * scaling.x,
+ _0,
+ scaling.y,
+ pt.y - pt.y * scaling.y,
+ _0,
+ _0,
+ _1,
+ )
+ }
}
impl Matrix4 {
@@ -90,6 +111,33 @@ impl Matrix4 {
Isometry::rotation_wrt_point(rot, pt).to_homogeneous()
}
+ /// Creates a new homogeneous matrix that applies a scaling factor for each dimension with respect to point.
+ ///
+ /// Can be used to implement "zoom_to" functionality.
+ #[inline]
+ pub fn new_nonuniform_scaling_wrt_point(scaling: &Vector3, pt: &Point3) -> Self {
+ let _0 = N::zero();
+ let _1 = N::one();
+ Matrix4::new(
+ scaling.x,
+ _0,
+ _0,
+ pt.x - pt.x * scaling.x,
+ _0,
+ scaling.y,
+ _0,
+ pt.y - pt.y * scaling.y,
+ _0,
+ _0,
+ scaling.z,
+ pt.z - pt.z * scaling.z,
+ _0,
+ _0,
+ _0,
+ _1,
+ )
+ }
+
/// Builds a 3D homogeneous rotation matrix from an axis and an angle (multiplied together).
///
/// Returns the identity matrix if the given argument is zero.
diff --git a/src/base/construction.rs b/src/base/construction.rs
index ae8c10d4..aa4cf956 100644
--- a/src/base/construction.rs
+++ b/src/base/construction.rs
@@ -1011,6 +1011,20 @@ where
N: Scalar + Zero + One,
DefaultAllocator: Allocator,
{
+ /// The column vector with `val` as its i-th component.
+ #[inline]
+ pub fn ith(i: usize, val: N) -> Self {
+ let mut res = Self::zeros();
+ res[i] = val;
+ res
+ }
+
+ /// The column unit vector with `N::one()` as its i-th component.
+ #[inline]
+ pub fn ith_axis(i: usize) -> Unit {
+ Unit::new_unchecked(Self::ith(i, N::one()))
+ }
+
/// The column vector with a 1 as its first component, and zero elsewhere.
#[inline]
pub fn x() -> Self
diff --git a/src/base/dimension.rs b/src/base/dimension.rs
index c171ff23..3f77998e 100644
--- a/src/base/dimension.rs
+++ b/src/base/dimension.rs
@@ -23,7 +23,7 @@ impl Dynamic {
/// A dynamic size equal to `value`.
#[inline]
pub fn new(value: usize) -> Self {
- Self { value: value }
+ Self { value }
}
}
diff --git a/src/base/edition.rs b/src/base/edition.rs
index b3133648..d69582d9 100644
--- a/src/base/edition.rs
+++ b/src/base/edition.rs
@@ -13,7 +13,7 @@ use crate::base::dimension::Dynamic;
use crate::base::dimension::{
Dim, DimAdd, DimDiff, DimMin, DimMinimum, DimName, DimSub, DimSum, U1,
};
-use crate::base::storage::{Storage, StorageMut};
+use crate::base::storage::{ReshapableStorage, Storage, StorageMut};
#[cfg(any(feature = "std", feature = "alloc"))]
use crate::base::DMatrix;
use crate::base::{DefaultAllocator, Matrix, MatrixMN, RowVector, Scalar, Vector};
@@ -745,7 +745,7 @@ impl> Matrix {
self.resize_generic(R2::name(), C2::name(), val)
}
- /// Resizes `self` such that it has dimensions `new_nrows × now_ncols`.
+ /// Resizes `self` such that it has dimensions `new_nrows × new_ncols`.
///
/// The values are copied such that `self[(i, j)] == result[(i, j)]`. If the result has more
/// rows and/or columns than `self`, then the extra rows or columns are filled with `val`.
@@ -813,6 +813,80 @@ impl> Matrix {
}
}
+impl Matrix
+where
+ N: Scalar,
+ R: Dim,
+ C: Dim,
+{
+ /// Reshapes `self` such that it has dimensions `new_nrows × new_ncols`.
+ ///
+ /// This will reinterpret `self` as if it is a matrix with `new_nrows` rows and `new_ncols`
+ /// columns. The arrangements of the component in the output matrix are the same as what
+ /// would be obtained by `Matrix::from_slice_generic(self.as_slice(), new_nrows, new_ncols)`.
+ ///
+ /// If `self` is a dynamically-sized matrix, then its components are neither copied nor moved.
+ /// If `self` is staticyll-sized, then a copy may happen in some situations.
+ /// This function will panic if the given dimensions are such that the number of elements of
+ /// the input matrix are not equal to the number of elements of the output matrix.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use nalgebra::{Matrix3x2, Matrix2x3, DMatrix, U2, U3, Dynamic};
+ ///
+ /// let m1 = Matrix2x3::new(
+ /// 1.1, 1.2, 1.3,
+ /// 2.1, 2.2, 2.3
+ /// );
+ /// let m2 = Matrix3x2::new(
+ /// 1.1, 2.2,
+ /// 2.1, 1.3,
+ /// 1.2, 2.3
+ /// );
+ /// let reshaped = m1.reshape_generic(U3, U2);
+ /// assert_eq!(reshaped, m2);
+ ///
+ /// let dm1 = DMatrix::from_row_slice(
+ /// 4,
+ /// 3,
+ /// &[
+ /// 1.0, 0.0, 0.0,
+ /// 0.0, 0.0, 1.0,
+ /// 0.0, 0.0, 0.0,
+ /// 0.0, 1.0, 0.0
+ /// ],
+ /// );
+ /// let dm2 = DMatrix::from_row_slice(
+ /// 6,
+ /// 2,
+ /// &[
+ /// 1.0, 0.0,
+ /// 0.0, 1.0,
+ /// 0.0, 0.0,
+ /// 0.0, 1.0,
+ /// 0.0, 0.0,
+ /// 0.0, 0.0,
+ /// ],
+ /// );
+ /// let reshaped = dm1.reshape_generic(Dynamic::new(6), Dynamic::new(2));
+ /// assert_eq!(reshaped, dm2);
+ /// ```
+ pub fn reshape_generic(
+ self,
+ new_nrows: R2,
+ new_ncols: C2,
+ ) -> Matrix
+ where
+ R2: Dim,
+ C2: Dim,
+ S: ReshapableStorage,
+ {
+ let data = self.data.reshape_generic(new_nrows, new_ncols);
+ Matrix::from_data(data)
+ }
+}
+
#[cfg(any(feature = "std", feature = "alloc"))]
impl DMatrix {
/// Resizes this matrix in-place.
diff --git a/src/base/matrix.rs b/src/base/matrix.rs
index e4821bf8..ca7fa77b 100644
--- a/src/base/matrix.rs
+++ b/src/base/matrix.rs
@@ -159,13 +159,39 @@ impl Abomonation for Matrix> matrixcompare_core::Matrix
+ for Matrix
+{
+ fn rows(&self) -> usize {
+ self.nrows()
+ }
+
+ fn cols(&self) -> usize {
+ self.ncols()
+ }
+
+ fn access(&self) -> matrixcompare_core::Access {
+ matrixcompare_core::Access::Dense(self)
+ }
+}
+
+#[cfg(feature = "compare")]
+impl> matrixcompare_core::DenseAccess
+ for Matrix
+{
+ fn fetch_single(&self, row: usize, col: usize) -> N {
+ self.index((row, col)).clone()
+ }
+}
+
impl Matrix {
/// Creates a new matrix with the given data without statically checking that the matrix
/// dimension matches the storage dimension.
#[inline]
pub unsafe fn from_data_statically_unchecked(data: S) -> Matrix {
Matrix {
- data: data,
+ data,
_phantoms: PhantomData,
}
}
@@ -538,8 +564,9 @@ impl> Matrix {
let mut res = unsafe { MatrixMN::new_uninitialized_generic(nrows, ncols) };
- assert!(
- (nrows.value(), ncols.value()) == rhs.shape(),
+ assert_eq!(
+ (nrows.value(), ncols.value()),
+ rhs.shape(),
"Matrix simultaneous traversal error: dimension mismatch."
);
@@ -578,9 +605,14 @@ impl> Matrix {
let mut res = unsafe { MatrixMN::new_uninitialized_generic(nrows, ncols) };
- assert!(
- (nrows.value(), ncols.value()) == b.shape()
- && (nrows.value(), ncols.value()) == c.shape(),
+ assert_eq!(
+ (nrows.value(), ncols.value()),
+ b.shape(),
+ "Matrix simultaneous traversal error: dimension mismatch."
+ );
+ assert_eq!(
+ (nrows.value(), ncols.value()),
+ c.shape(),
"Matrix simultaneous traversal error: dimension mismatch."
);
@@ -636,8 +668,9 @@ impl> Matrix {
let mut res = init;
- assert!(
- (nrows.value(), ncols.value()) == rhs.shape(),
+ assert_eq!(
+ (nrows.value(), ncols.value()),
+ rhs.shape(),
"Matrix simultaneous traversal error: dimension mismatch."
);
@@ -884,8 +917,9 @@ impl> Matrix {
{
let (nrows, ncols) = self.shape();
- assert!(
- (nrows, ncols) == rhs.shape(),
+ assert_eq!(
+ (nrows, ncols),
+ rhs.shape(),
"Matrix simultaneous traversal error: dimension mismatch."
);
@@ -922,12 +956,14 @@ impl> Matrix {
{
let (nrows, ncols) = self.shape();
- assert!(
- (nrows, ncols) == b.shape(),
+ assert_eq!(
+ (nrows, ncols),
+ b.shape(),
"Matrix simultaneous traversal error: dimension mismatch."
);
- assert!(
- (nrows, ncols) == c.shape(),
+ assert_eq!(
+ (nrows, ncols),
+ c.shape(),
"Matrix simultaneous traversal error: dimension mismatch."
);
@@ -1427,8 +1463,9 @@ where
#[inline]
fn lt(&self, right: &Self) -> bool {
- assert!(
- self.shape() == right.shape(),
+ assert_eq!(
+ self.shape(),
+ right.shape(),
"Matrix comparison error: dimensions mismatch."
);
self.iter().zip(right.iter()).all(|(a, b)| a.lt(b))
@@ -1436,8 +1473,9 @@ where
#[inline]
fn le(&self, right: &Self) -> bool {
- assert!(
- self.shape() == right.shape(),
+ assert_eq!(
+ self.shape(),
+ right.shape(),
"Matrix comparison error: dimensions mismatch."
);
self.iter().zip(right.iter()).all(|(a, b)| a.le(b))
@@ -1445,8 +1483,9 @@ where
#[inline]
fn gt(&self, right: &Self) -> bool {
- assert!(
- self.shape() == right.shape(),
+ assert_eq!(
+ self.shape(),
+ right.shape(),
"Matrix comparison error: dimensions mismatch."
);
self.iter().zip(right.iter()).all(|(a, b)| a.gt(b))
@@ -1454,8 +1493,9 @@ where
#[inline]
fn ge(&self, right: &Self) -> bool {
- assert!(
- self.shape() == right.shape(),
+ assert_eq!(
+ self.shape(),
+ right.shape(),
"Matrix comparison error: dimensions mismatch."
);
self.iter().zip(right.iter()).all(|(a, b)| a.ge(b))
@@ -1602,7 +1642,11 @@ impl
+ SameNumberOfColumns,
{
- assert!(self.shape() == (2, 1), "2D perpendicular product ");
+ assert!(
+ self.shape() == (2, 1),
+ "2D perpendicular product requires (2, 1) vector but found {:?}",
+ self.shape()
+ );
unsafe {
self.get_unchecked((0, 0)).inlined_clone() * b.get_unchecked((1, 0)).inlined_clone()
@@ -1626,13 +1670,11 @@ impl + SameNumberOfColumns,
{
let shape = self.shape();
- assert!(
- shape == b.shape(),
- "Vector cross product dimension mismatch."
- );
+ assert_eq!(shape, b.shape(), "Vector cross product dimension mismatch.");
assert!(
(shape.0 == 3 && shape.1 == 1) || (shape.0 == 1 && shape.1 == 3),
- "Vector cross product dimension mismatch."
+ "Vector cross product dimension mismatch: must be (3, 1) or (1, 3) but found {:?}.",
+ shape
);
if shape.0 == 3 {
diff --git a/src/base/ops.rs b/src/base/ops.rs
index 12b26d1a..7f0c6e4e 100644
--- a/src/base/ops.rs
+++ b/src/base/ops.rs
@@ -154,8 +154,8 @@ macro_rules! componentwise_binop_impl(
out: &mut Matrix)
where SB: Storage,
SC: StorageMut {
- assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch.");
- assert!(self.shape() == out.shape(), "Matrix addition/subtraction output dimensions mismatch.");
+ assert_eq!(self.shape(), rhs.shape(), "Matrix addition/subtraction dimensions mismatch.");
+ assert_eq!(self.shape(), out.shape(), "Matrix addition/subtraction output dimensions mismatch.");
// This is the most common case and should be deduced at compile-time.
// FIXME: use specialization instead?
@@ -188,7 +188,7 @@ macro_rules! componentwise_binop_impl(
C2: Dim,
SA: StorageMut,
SB: Storage {
- assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch.");
+ assert_eq!(self.shape(), rhs.shape(), "Matrix addition/subtraction dimensions mismatch.");
// This is the most common case and should be deduced at compile-time.
// FIXME: use specialization instead?
@@ -218,7 +218,7 @@ macro_rules! componentwise_binop_impl(
where R2: Dim,
C2: Dim,
SB: StorageMut {
- assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch.");
+ assert_eq!(self.shape(), rhs.shape(), "Matrix addition/subtraction dimensions mismatch.");
// This is the most common case and should be deduced at compile-time.
// FIXME: use specialization instead?
@@ -277,7 +277,7 @@ macro_rules! componentwise_binop_impl(
#[inline]
fn $method(self, rhs: &'b Matrix) -> Self::Output {
- assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch.");
+ assert_eq!(self.shape(), rhs.shape(), "Matrix addition/subtraction dimensions mismatch.");
let mut res = self.into_owned_sum::();
res.$method_assign_statically_unchecked(rhs);
res
@@ -296,7 +296,7 @@ macro_rules! componentwise_binop_impl(
#[inline]
fn $method(self, rhs: Matrix) -> Self::Output {
let mut rhs = rhs.into_owned_sum::();
- assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch.");
+ assert_eq!(self.shape(), rhs.shape(), "Matrix addition/subtraction dimensions mismatch.");
self.$method_assign_statically_unchecked_rhs(&mut rhs);
rhs
}
@@ -728,11 +728,21 @@ where
assert!(
nrows1 == nrows2,
- "Matrix multiplication dimensions mismatch."
+ "Matrix multiplication dimensions mismatch {:?} and {:?}: left rows != right rows.",
+ self.shape(),
+ rhs.shape()
);
assert!(
- nrows3 == ncols1 && ncols3 == ncols2,
- "Matrix multiplication output dimensions mismatch."
+ ncols1 == nrows3,
+ "Matrix multiplication output dimensions mismatch {:?} and {:?}: left cols != right rows.",
+ self.shape(),
+ out.shape()
+ );
+ assert!(
+ ncols2 == ncols3,
+ "Matrix multiplication output dimensions mismatch {:?} and {:?}: left cols != right cols",
+ rhs.shape(),
+ out.shape()
);
for i in 0..ncols1 {
diff --git a/src/base/storage.rs b/src/base/storage.rs
index 9f039ba2..598cb061 100644
--- a/src/base/storage.rs
+++ b/src/base/storage.rs
@@ -171,7 +171,7 @@ pub unsafe trait StorageMut: Storage {
/// A matrix storage that is stored contiguously in memory.
///
-/// The storage requirement means that for any value of `i` in `[0, nrows * ncols[`, the value
+/// The storage requirement means that for any value of `i` in `[0, nrows * ncols - 1]`, the value
/// `.get_unchecked_linear` returns one of the matrix component. This trait is unsafe because
/// failing to comply to this may cause Undefined Behaviors.
pub unsafe trait ContiguousStorage:
@@ -181,10 +181,26 @@ pub unsafe trait ContiguousStorage:
/// A mutable matrix storage that is stored contiguously in memory.
///
-/// The storage requirement means that for any value of `i` in `[0, nrows * ncols[`, the value
+/// The storage requirement means that for any value of `i` in `[0, nrows * ncols - 1]`, the value
/// `.get_unchecked_linear` returns one of the matrix component. This trait is unsafe because
/// failing to comply to this may cause Undefined Behaviors.
pub unsafe trait ContiguousStorageMut:
ContiguousStorage + StorageMut
{
}
+
+/// A matrix storage that can be reshaped in-place.
+pub trait ReshapableStorage: Storage
+where
+ N: Scalar,
+ R1: Dim,
+ C1: Dim,
+ R2: Dim,
+ C2: Dim,
+{
+ /// The reshaped storage type.
+ type Output: Storage;
+
+ /// Reshapes the storage into the output storage type.
+ fn reshape_generic(self, nrows: R2, ncols: C2) -> Self::Output;
+}
diff --git a/src/base/vec_storage.rs b/src/base/vec_storage.rs
index 26948693..37f31213 100644
--- a/src/base/vec_storage.rs
+++ b/src/base/vec_storage.rs
@@ -8,7 +8,9 @@ use crate::base::allocator::Allocator;
use crate::base::constraint::{SameNumberOfRows, ShapeConstraint};
use crate::base::default_allocator::DefaultAllocator;
use crate::base::dimension::{Dim, DimName, Dynamic, U1};
-use crate::base::storage::{ContiguousStorage, ContiguousStorageMut, Owned, Storage, StorageMut};
+use crate::base::storage::{
+ ContiguousStorage, ContiguousStorageMut, Owned, ReshapableStorage, Storage, StorageMut,
+};
use crate::base::{Scalar, Vector};
#[cfg(feature = "abomonation-serialize")]
@@ -41,11 +43,7 @@ impl VecStorage {
nrows.value() * ncols.value() == data.len(),
"Data storage buffer dimension mismatch."
);
- Self {
- data: data,
- nrows: nrows,
- ncols: ncols,
- }
+ Self { data, nrows, ncols }
}
/// The underlying data storage.
@@ -229,6 +227,42 @@ unsafe impl ContiguousStorageMut for VecStorag
{
}
+impl ReshapableStorage for VecStorage
+where
+ N: Scalar,
+ C1: Dim,
+ C2: Dim,
+{
+ type Output = VecStorage;
+
+ fn reshape_generic(self, nrows: Dynamic, ncols: C2) -> Self::Output {
+ assert_eq!(nrows.value() * ncols.value(), self.data.len());
+ VecStorage {
+ data: self.data,
+ nrows,
+ ncols,
+ }
+ }
+}
+
+impl ReshapableStorage for VecStorage
+where
+ N: Scalar,
+ C1: Dim,
+ R2: DimName,
+{
+ type Output = VecStorage;
+
+ fn reshape_generic(self, nrows: R2, ncols: Dynamic) -> Self::Output {
+ assert_eq!(nrows.value() * ncols.value(), self.data.len());
+ VecStorage {
+ data: self.data,
+ nrows,
+ ncols,
+ }
+ }
+}
+
unsafe impl StorageMut for VecStorage
where
DefaultAllocator: Allocator,
@@ -244,6 +278,42 @@ where
}
}
+impl ReshapableStorage for VecStorage
+where
+ N: Scalar,
+ R1: DimName,
+ C2: Dim,
+{
+ type Output = VecStorage;
+
+ fn reshape_generic(self, nrows: Dynamic, ncols: C2) -> Self::Output {
+ assert_eq!(nrows.value() * ncols.value(), self.data.len());
+ VecStorage {
+ data: self.data,
+ nrows,
+ ncols,
+ }
+ }
+}
+
+impl ReshapableStorage for VecStorage
+where
+ N: Scalar,
+ R1: DimName,
+ R2: DimName,
+{
+ type Output = VecStorage;
+
+ fn reshape_generic(self, nrows: R2, ncols: Dynamic) -> Self::Output {
+ assert_eq!(nrows.value() * ncols.value(), self.data.len());
+ VecStorage {
+ data: self.data,
+ nrows,
+ ncols,
+ }
+ }
+}
+
#[cfg(feature = "abomonation-serialize")]
impl Abomonation for VecStorage {
unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> {
diff --git a/src/geometry/abstract_rotation.rs b/src/geometry/abstract_rotation.rs
index ff057a09..52471851 100644
--- a/src/geometry/abstract_rotation.rs
+++ b/src/geometry/abstract_rotation.rs
@@ -1,6 +1,6 @@
use crate::allocator::Allocator;
use crate::geometry::{Rotation, UnitComplex, UnitQuaternion};
-use crate::{DefaultAllocator, DimName, Point, Scalar, SimdRealField, VectorN, U2, U3};
+use crate::{DefaultAllocator, DimName, Point, Scalar, SimdRealField, Unit, VectorN, U2, U3};
use simba::scalar::ClosedMul;
@@ -24,6 +24,13 @@ pub trait AbstractRotation: PartialEq + ClosedMul + Clone
fn inverse_transform_vector(&self, v: &VectorN) -> VectorN
where
DefaultAllocator: Allocator;
+ /// Apply the inverse rotation to the given unit vector.
+ fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit>
+ where
+ DefaultAllocator: Allocator,
+ {
+ Unit::new_unchecked(self.inverse_transform_vector(&**v))
+ }
/// Apply the inverse rotation to the given point.
fn inverse_transform_point(&self, p: &Point) -> Point
where
@@ -74,6 +81,14 @@ where
self.inverse_transform_vector(v)
}
+ #[inline]
+ fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit>
+ where
+ DefaultAllocator: Allocator,
+ {
+ self.inverse_transform_unit_vector(v)
+ }
+
#[inline]
fn inverse_transform_point(&self, p: &Point) -> Point
where
diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs
index 2e4b7cd5..edb27b5a 100755
--- a/src/geometry/isometry.rs
+++ b/src/geometry/isometry.rs
@@ -14,10 +14,12 @@ use simba::scalar::{RealField, SubsetOf};
use simba::simd::SimdRealField;
use crate::base::allocator::Allocator;
-use crate::base::dimension::{DimName, DimNameAdd, DimNameSum, U1};
+use crate::base::dimension::{DimName, DimNameAdd, DimNameSum, U1, U2, U3};
use crate::base::storage::Owned;
-use crate::base::{DefaultAllocator, MatrixN, Scalar, VectorN};
-use crate::geometry::{AbstractRotation, Point, Translation};
+use crate::base::{DefaultAllocator, MatrixN, Scalar, Unit, VectorN};
+use crate::geometry::{
+ AbstractRotation, Point, Rotation2, Rotation3, Translation, UnitComplex, UnitQuaternion,
+};
/// A direct isometry, i.e., a rotation followed by a translation, aka. a rigid-body motion, aka. an element of a Special Euclidean (SE) group.
#[repr(C)]
@@ -82,21 +84,23 @@ where
}
}
-impl + Copy> Copy
- for Isometry
+impl Copy for Isometry
where
DefaultAllocator: Allocator,
Owned: Copy,
{
}
-impl + Clone> Clone for Isometry
+impl Clone for Isometry
where
DefaultAllocator: Allocator,
{
#[inline]
fn clone(&self) -> Self {
- Self::from_parts(self.translation.clone(), self.rotation.clone())
+ Self {
+ rotation: self.rotation.clone(),
+ translation: self.translation.clone(),
+ }
}
}
@@ -348,6 +352,237 @@ where
pub fn inverse_transform_vector(&self, v: &VectorN) -> VectorN {
self.rotation.inverse_transform_vector(v)
}
+
+ /// Transform the given unit vector by the inverse of this isometry, ignoring the
+ /// translation component of the isometry. This may be
+ /// less expensive than computing the entire isometry inverse and then
+ /// transforming the point.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # #[macro_use] extern crate approx;
+ /// # use std::f32;
+ /// # use nalgebra::{Isometry3, Translation3, UnitQuaternion, Vector3};
+ /// let tra = Translation3::new(0.0, 0.0, 3.0);
+ /// let rot = UnitQuaternion::from_scaled_axis(Vector3::z() * f32::consts::FRAC_PI_2);
+ /// let iso = Isometry3::from_parts(tra, rot);
+ ///
+ /// let transformed_point = iso.inverse_transform_unit_vector(&Vector3::x_axis());
+ /// assert_relative_eq!(transformed_point, -Vector3::y_axis(), epsilon = 1.0e-6);
+ /// ```
+ #[inline]
+ pub fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit> {
+ self.rotation.inverse_transform_unit_vector(v)
+ }
+}
+
+impl Isometry> {
+ /// Interpolates between two isometries using a linear interpolation for the translation part,
+ /// and a spherical interpolation for the rotation part.
+ ///
+ /// Panics if the angle between both rotations is 180 degrees (in which case the interpolation
+ /// is not well-defined). Use `.try_lerp_slerp` instead to avoid the panic.
+ ///
+ /// # Examples:
+ ///
+ /// ```
+ /// # use nalgebra::{Vector3, Translation3, Isometry3, UnitQuaternion};
+ ///
+ /// let t1 = Translation3::new(1.0, 2.0, 3.0);
+ /// let t2 = Translation3::new(4.0, 8.0, 12.0);
+ /// let q1 = UnitQuaternion::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0);
+ /// let q2 = UnitQuaternion::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0);
+ /// let iso1 = Isometry3::from_parts(t1, q1);
+ /// let iso2 = Isometry3::from_parts(t2, q2);
+ ///
+ /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0);
+ ///
+ /// assert_eq!(iso3.translation.vector, Vector3::new(2.0, 4.0, 6.0));
+ /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0));
+ /// ```
+ #[inline]
+ pub fn lerp_slerp(&self, other: &Self, t: N) -> Self
+ where
+ N: RealField,
+ {
+ let tr = self.translation.vector.lerp(&other.translation.vector, t);
+ let rot = self.rotation.slerp(&other.rotation, t);
+ Self::from_parts(tr.into(), rot)
+ }
+
+ /// Attempts to interpolate between two isometries using a linear interpolation for the translation part,
+ /// and a spherical interpolation for the rotation part.
+ ///
+ /// Retuns `None` if the angle between both rotations is 180 degrees (in which case the interpolation
+ /// is not well-defined).
+ ///
+ /// # Examples:
+ ///
+ /// ```
+ /// # use nalgebra::{Vector3, Translation3, Isometry3, UnitQuaternion};
+ ///
+ /// let t1 = Translation3::new(1.0, 2.0, 3.0);
+ /// let t2 = Translation3::new(4.0, 8.0, 12.0);
+ /// let q1 = UnitQuaternion::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0);
+ /// let q2 = UnitQuaternion::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0);
+ /// let iso1 = Isometry3::from_parts(t1, q1);
+ /// let iso2 = Isometry3::from_parts(t2, q2);
+ ///
+ /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0);
+ ///
+ /// assert_eq!(iso3.translation.vector, Vector3::new(2.0, 4.0, 6.0));
+ /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0));
+ /// ```
+ #[inline]
+ pub fn try_lerp_slerp(&self, other: &Self, t: N, epsilon: N) -> Option
+ where
+ N: RealField,
+ {
+ let tr = self.translation.vector.lerp(&other.translation.vector, t);
+ let rot = self.rotation.try_slerp(&other.rotation, t, epsilon)?;
+ Some(Self::from_parts(tr.into(), rot))
+ }
+}
+
+impl Isometry> {
+ /// Interpolates between two isometries using a linear interpolation for the translation part,
+ /// and a spherical interpolation for the rotation part.
+ ///
+ /// Panics if the angle between both rotations is 180 degrees (in which case the interpolation
+ /// is not well-defined). Use `.try_lerp_slerp` instead to avoid the panic.
+ ///
+ /// # Examples:
+ ///
+ /// ```
+ /// # use nalgebra::{Vector3, Translation3, Rotation3, IsometryMatrix3};
+ ///
+ /// let t1 = Translation3::new(1.0, 2.0, 3.0);
+ /// let t2 = Translation3::new(4.0, 8.0, 12.0);
+ /// let q1 = Rotation3::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0);
+ /// let q2 = Rotation3::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0);
+ /// let iso1 = IsometryMatrix3::from_parts(t1, q1);
+ /// let iso2 = IsometryMatrix3::from_parts(t2, q2);
+ ///
+ /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0);
+ ///
+ /// assert_eq!(iso3.translation.vector, Vector3::new(2.0, 4.0, 6.0));
+ /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0));
+ /// ```
+ #[inline]
+ pub fn lerp_slerp(&self, other: &Self, t: N) -> Self
+ where
+ N: RealField,
+ {
+ let tr = self.translation.vector.lerp(&other.translation.vector, t);
+ let rot = self.rotation.slerp(&other.rotation, t);
+ Self::from_parts(tr.into(), rot)
+ }
+
+ /// Attempts to interpolate between two isometries using a linear interpolation for the translation part,
+ /// and a spherical interpolation for the rotation part.
+ ///
+ /// Retuns `None` if the angle between both rotations is 180 degrees (in which case the interpolation
+ /// is not well-defined).
+ ///
+ /// # Examples:
+ ///
+ /// ```
+ /// # use nalgebra::{Vector3, Translation3, Rotation3, IsometryMatrix3};
+ ///
+ /// let t1 = Translation3::new(1.0, 2.0, 3.0);
+ /// let t2 = Translation3::new(4.0, 8.0, 12.0);
+ /// let q1 = Rotation3::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0);
+ /// let q2 = Rotation3::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0);
+ /// let iso1 = IsometryMatrix3::from_parts(t1, q1);
+ /// let iso2 = IsometryMatrix3::from_parts(t2, q2);
+ ///
+ /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0);
+ ///
+ /// assert_eq!(iso3.translation.vector, Vector3::new(2.0, 4.0, 6.0));
+ /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0));
+ /// ```
+ #[inline]
+ pub fn try_lerp_slerp(&self, other: &Self, t: N, epsilon: N) -> Option
+ where
+ N: RealField,
+ {
+ let tr = self.translation.vector.lerp(&other.translation.vector, t);
+ let rot = self.rotation.try_slerp(&other.rotation, t, epsilon)?;
+ Some(Self::from_parts(tr.into(), rot))
+ }
+}
+
+impl Isometry> {
+ /// Interpolates between two isometries using a linear interpolation for the translation part,
+ /// and a spherical interpolation for the rotation part.
+ ///
+ /// Panics if the angle between both rotations is 180 degrees (in which case the interpolation
+ /// is not well-defined). Use `.try_lerp_slerp` instead to avoid the panic.
+ ///
+ /// # Examples:
+ ///
+ /// ```
+ /// # #[macro_use] extern crate approx;
+ /// # use nalgebra::{Vector2, Translation2, UnitComplex, Isometry2};
+ ///
+ /// let t1 = Translation2::new(1.0, 2.0);
+ /// let t2 = Translation2::new(4.0, 8.0);
+ /// let q1 = UnitComplex::new(std::f32::consts::FRAC_PI_4);
+ /// let q2 = UnitComplex::new(-std::f32::consts::PI);
+ /// let iso1 = Isometry2::from_parts(t1, q1);
+ /// let iso2 = Isometry2::from_parts(t2, q2);
+ ///
+ /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0);
+ ///
+ /// assert_eq!(iso3.translation.vector, Vector2::new(2.0, 4.0));
+ /// assert_relative_eq!(iso3.rotation.angle(), std::f32::consts::FRAC_PI_2);
+ /// ```
+ #[inline]
+ pub fn lerp_slerp(&self, other: &Self, t: N) -> Self
+ where
+ N: RealField,
+ {
+ let tr = self.translation.vector.lerp(&other.translation.vector, t);
+ let rot = self.rotation.slerp(&other.rotation, t);
+ Self::from_parts(tr.into(), rot)
+ }
+}
+
+impl Isometry> {
+ /// Interpolates between two isometries using a linear interpolation for the translation part,
+ /// and a spherical interpolation for the rotation part.
+ ///
+ /// Panics if the angle between both rotations is 180 degrees (in which case the interpolation
+ /// is not well-defined). Use `.try_lerp_slerp` instead to avoid the panic.
+ ///
+ /// # Examples:
+ ///
+ /// ```
+ /// # #[macro_use] extern crate approx;
+ /// # use nalgebra::{Vector2, Translation2, Rotation2, IsometryMatrix2};
+ ///
+ /// let t1 = Translation2::new(1.0, 2.0);
+ /// let t2 = Translation2::new(4.0, 8.0);
+ /// let q1 = Rotation2::new(std::f32::consts::FRAC_PI_4);
+ /// let q2 = Rotation2::new(-std::f32::consts::PI);
+ /// let iso1 = IsometryMatrix2::from_parts(t1, q1);
+ /// let iso2 = IsometryMatrix2::from_parts(t2, q2);
+ ///
+ /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0);
+ ///
+ /// assert_eq!(iso3.translation.vector, Vector2::new(2.0, 4.0));
+ /// assert_relative_eq!(iso3.rotation.angle(), std::f32::consts::FRAC_PI_2);
+ /// ```
+ #[inline]
+ pub fn lerp_slerp(&self, other: &Self, t: N) -> Self
+ where
+ N: RealField,
+ {
+ let tr = self.translation.vector.lerp(&other.translation.vector, t);
+ let rot = self.rotation.slerp(&other.rotation, t);
+ Self::from_parts(tr.into(), rot)
+ }
}
// NOTE: we don't require `R: Rotation<...>` here because this is not useful for the implementation
diff --git a/src/geometry/isometry_alias.rs b/src/geometry/isometry_alias.rs
index ba9a69e7..69d33cb4 100644
--- a/src/geometry/isometry_alias.rs
+++ b/src/geometry/isometry_alias.rs
@@ -13,3 +13,15 @@ pub type IsometryMatrix2 = Isometry>;
/// A 3-dimensional direct isometry using a rotation matrix for its rotational part. Also known as a rigid-body motion, or as an element of SE(3).
pub type IsometryMatrix3 = Isometry>;
+
+// This tests that the types correctly implement `Copy`, without having to run tests
+// (when targeting no-std for example).
+#[allow(dead_code)]
+fn ensure_copy() {
+ fn is_copy() {}
+
+ is_copy::>();
+ is_copy::>();
+ is_copy::>();
+ is_copy::>();
+}
diff --git a/src/geometry/isometry_conversion.rs b/src/geometry/isometry_conversion.rs
index 7cb6fe85..bdb128f9 100644
--- a/src/geometry/isometry_conversion.rs
+++ b/src/geometry/isometry_conversion.rs
@@ -151,6 +151,17 @@ where
}
}
+impl> From>
+ for Isometry
+where
+ DefaultAllocator: Allocator,
+{
+ #[inline]
+ fn from(tra: Translation) -> Self {
+ Self::from_parts(tra, R::identity())
+ }
+}
+
impl From> for MatrixN>
where
D: DimNameAdd,
diff --git a/src/geometry/isometry_ops.rs b/src/geometry/isometry_ops.rs
index 8b00fcda..e676d492 100644
--- a/src/geometry/isometry_ops.rs
+++ b/src/geometry/isometry_ops.rs
@@ -149,6 +149,7 @@ isometry_binop_impl_all!(
[ref ref] => {
let shift = self.rotation.transform_vector(&rhs.translation.vector);
+ #[allow(clippy::suspicious_arithmetic_impl)]
Isometry::from_parts(Translation::from(&self.translation.vector + shift),
self.rotation.clone() * rhs.rotation.clone()) // FIXME: too bad we have to clone.
};
@@ -157,10 +158,10 @@ isometry_binop_impl_all!(
isometry_binop_impl_all!(
Div, div;
self: Isometry, rhs: Isometry, Output = Isometry;
- [val val] => self * rhs.inverse();
- [ref val] => self * rhs.inverse();
- [val ref] => self * rhs.inverse();
- [ref ref] => self * rhs.inverse();
+ [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() };
+ [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() };
+ [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() };
+ [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() };
);
// Isometry ×= Translation
@@ -289,6 +290,7 @@ isometry_binop_impl_all!(
[ref val] => self * &right;
[val ref] => &self * right;
[ref ref] => {
+ #[allow(clippy::suspicious_arithmetic_impl)]
let new_tr = &self.translation.vector + self.rotation.transform_vector(&right.vector);
Isometry::from_parts(Translation::from(new_tr), self.rotation.clone())
};
@@ -427,10 +429,10 @@ isometry_from_composition_impl_all!(
self: Rotation, right: Isometry>,
Output = Isometry>;
// FIXME: don't call inverse explicitly?
- [val val] => self * right.inverse();
- [ref val] => self * right.inverse();
- [val ref] => self * right.inverse();
- [ref ref] => self * right.inverse();
+ [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() };
+ [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() };
+ [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() };
+ [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() };
);
// Isometry × UnitQuaternion
@@ -479,10 +481,10 @@ isometry_from_composition_impl_all!(
self: UnitQuaternion, right: Isometry>,
Output = Isometry>;
// FIXME: don't call inverse explicitly?
- [val val] => self * right.inverse();
- [ref val] => self * right.inverse();
- [val ref] => self * right.inverse();
- [ref ref] => self * right.inverse();
+ [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() };
+ [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() };
+ [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() };
+ [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() };
);
// Translation × Rotation
diff --git a/src/geometry/orthographic.rs b/src/geometry/orthographic.rs
index a7d5372f..9f0eadaf 100644
--- a/src/geometry/orthographic.rs
+++ b/src/geometry/orthographic.rs
@@ -139,7 +139,7 @@ impl Orthographic3 {
/// ```
#[inline]
pub fn from_matrix_unchecked(matrix: Matrix4) -> Self {
- Self { matrix: matrix }
+ Self { matrix }
}
/// Creates a new orthographic projection matrix from an aspect ratio and the vertical field of view.
@@ -151,7 +151,7 @@ impl Orthographic3 {
);
assert!(
!relative_eq!(aspect, N::zero()),
- "The apsect ratio must not be zero."
+ "The aspect ratio must not be zero."
);
let half: N = crate::convert(0.5);
diff --git a/src/geometry/perspective.rs b/src/geometry/perspective.rs
index 325a450c..f550bcc6 100644
--- a/src/geometry/perspective.rs
+++ b/src/geometry/perspective.rs
@@ -75,7 +75,7 @@ impl Perspective3 {
);
assert!(
!relative_eq!(aspect, N::zero()),
- "The apsect ratio must not be zero."
+ "The aspect ratio must not be zero."
);
let matrix = Matrix4::identity();
@@ -97,7 +97,7 @@ impl Perspective3 {
/// projection.
#[inline]
pub fn from_matrix_unchecked(matrix: Matrix4) -> Self {
- Self { matrix: matrix }
+ Self { matrix }
}
/// Retrieves the inverse of the underlying homogeneous matrix.
@@ -294,7 +294,7 @@ impl Arbitrary for Perspective3 {
impl From> for Matrix4 {
#[inline]
- fn from(orth: Perspective3) -> Self {
- orth.into_inner()
+ fn from(pers: Perspective3) -> Self {
+ pers.into_inner()
}
}
diff --git a/src/geometry/point.rs b/src/geometry/point.rs
index ab26589b..e0c26054 100644
--- a/src/geometry/point.rs
+++ b/src/geometry/point.rs
@@ -102,6 +102,45 @@ impl Point
where
DefaultAllocator: Allocator,
{
+ /// Returns a point containing the result of `f` applied to each of its entries.
+ ///
+ /// # Example
+ /// ```
+ /// # use nalgebra::{Point2, Point3};
+ /// let p = Point2::new(1.0, 2.0);
+ /// assert_eq!(p.map(|e| e * 10.0), Point2::new(10.0, 20.0));
+ ///
+ /// // This works in any dimension.
+ /// let p = Point3::new(1.1, 2.1, 3.1);
+ /// assert_eq!(p.map(|e| e as u32), Point3::new(1, 2, 3));
+ /// ```
+ #[inline]
+ pub fn map N2>(&self, f: F) -> Point
+ where
+ DefaultAllocator: Allocator,
+ {
+ self.coords.map(f).into()
+ }
+
+ /// Replaces each component of `self` by the result of a closure `f` applied on it.
+ ///
+ /// # Example
+ /// ```
+ /// # use nalgebra::{Point2, Point3};
+ /// let mut p = Point2::new(1.0, 2.0);
+ /// p.apply(|e| e * 10.0);
+ /// assert_eq!(p, Point2::new(10.0, 20.0));
+ ///
+ /// // This works in any dimension.
+ /// let mut p = Point3::new(1.0, 2.0, 3.0);
+ /// p.apply(|e| e * 10.0);
+ /// assert_eq!(p, Point3::new(10.0, 20.0, 30.0));
+ /// ```
+ #[inline]
+ pub fn apply N>(&mut self, f: F) {
+ self.coords.apply(f)
+ }
+
/// Converts this point into a vector in homogeneous coordinates, i.e., appends a `1` at the
/// end of it.
///
@@ -135,7 +174,7 @@ where
#[deprecated(note = "Use Point::from(vector) instead.")]
#[inline]
pub fn from_coordinates(coords: VectorN) -> Self {
- Self { coords: coords }
+ Self { coords }
}
/// The dimension of this point.
diff --git a/src/geometry/point_coordinates.rs b/src/geometry/point_coordinates.rs
index b56e120e..268c9b0d 100644
--- a/src/geometry/point_coordinates.rs
+++ b/src/geometry/point_coordinates.rs
@@ -1,4 +1,3 @@
-use std::mem;
use std::ops::{Deref, DerefMut};
use crate::base::allocator::Allocator;
@@ -22,7 +21,7 @@ macro_rules! deref_impl(
#[inline]
fn deref(&self) -> &Self::Target {
- unsafe { mem::transmute(self) }
+ &*self.coords
}
}
@@ -30,7 +29,7 @@ macro_rules! deref_impl(
where DefaultAllocator: Allocator {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
- unsafe { mem::transmute(self) }
+ &mut *self.coords
}
}
}
diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs
index 0c996ede..69d54967 100755
--- a/src/geometry/quaternion.rs
+++ b/src/geometry/quaternion.rs
@@ -389,6 +389,7 @@ where
/// ```
#[inline]
pub fn outer(&self, other: &Self) -> Self {
+ #[allow(clippy::eq_op)]
(self * other - other * self).half()
}
@@ -1542,6 +1543,26 @@ where
pub fn inverse_transform_vector(&self, v: &Vector3) -> Vector3 {
self.inverse() * v
}
+
+ /// Rotate a vector by the inverse of this unit quaternion. This may be
+ /// cheaper than inverting the unit quaternion and transforming the
+ /// vector.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # #[macro_use] extern crate approx;
+ /// # use std::f32;
+ /// # use nalgebra::{UnitQuaternion, Vector3};
+ /// let rot = UnitQuaternion::from_axis_angle(&Vector3::z_axis(), f32::consts::FRAC_PI_2);
+ /// let transformed_vector = rot.inverse_transform_unit_vector(&Vector3::x_axis());
+ ///
+ /// assert_relative_eq!(transformed_vector, -Vector3::y_axis(), epsilon = 1.0e-6);
+ /// ```
+ #[inline]
+ pub fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit> {
+ self.inverse() * v
+ }
}
impl Default for UnitQuaternion {
diff --git a/src/geometry/quaternion_construction.rs b/src/geometry/quaternion_construction.rs
index 175275d4..33e05867 100644
--- a/src/geometry/quaternion_construction.rs
+++ b/src/geometry/quaternion_construction.rs
@@ -374,7 +374,8 @@ where
}
/// The unit quaternion needed to make `a` and `b` be collinear and point toward the same
- /// direction.
+ /// direction. Returns `None` if both `a` and `b` are collinear and point to opposite directions, as then the
+ /// rotation desired is not unique.
///
/// # Example
/// ```
@@ -491,18 +492,18 @@ where
// The cosinus may be out of [-1, 1] because of inaccuracies.
if cos <= -N::one() {
- return None;
+ None
} else if cos >= N::one() {
- return Some(Self::identity());
+ Some(Self::identity())
} else {
- return Some(Self::from_axis_angle(&axis, cos.acos() * s));
+ Some(Self::from_axis_angle(&axis, cos.acos() * s))
}
} else if na.dot(&nb) < N::zero() {
// PI
//
// The rotation axis is undefined but the angle not zero. This is not a
// simple rotation.
- return None;
+ None
} else {
// Zero
Some(Self::identity())
diff --git a/src/geometry/quaternion_conversion.rs b/src/geometry/quaternion_conversion.rs
index 37a8bebd..330c97e4 100644
--- a/src/geometry/quaternion_conversion.rs
+++ b/src/geometry/quaternion_conversion.rs
@@ -265,6 +265,15 @@ impl From> for Quaternion {
}
}
+impl From<[N; 4]> for Quaternion {
+ #[inline]
+ fn from(coords: [N; 4]) -> Self {
+ Self {
+ coords: coords.into(),
+ }
+ }
+}
+
impl From<[Quaternion; 2]> for Quaternion
where
N: From<[::Element; 2]>,
diff --git a/src/geometry/quaternion_ops.rs b/src/geometry/quaternion_ops.rs
index e18c1b16..d91d7fd8 100644
--- a/src/geometry/quaternion_ops.rs
+++ b/src/geometry/quaternion_ops.rs
@@ -121,11 +121,10 @@ quaternion_op_impl!(
'b);
quaternion_op_impl!(
-Add, add;
-(U4, U1), (U4, U1);
-self: Quaternion, rhs: Quaternion, Output = Quaternion;
-Quaternion::from(self.coords + rhs.coords);
-);
+ Add, add;
+ (U4, U1), (U4, U1);
+ self: Quaternion, rhs: Quaternion, Output = Quaternion;
+ Quaternion::from(self.coords + rhs.coords); );
// Quaternion - Quaternion
quaternion_op_impl!(
@@ -150,11 +149,10 @@ quaternion_op_impl!(
'b);
quaternion_op_impl!(
-Sub, sub;
-(U4, U1), (U4, U1);
-self: Quaternion, rhs: Quaternion, Output = Quaternion;
-Quaternion::from(self.coords - rhs.coords);
-);
+ Sub, sub;
+ (U4, U1), (U4, U1);
+ self: Quaternion, rhs: Quaternion, Output = Quaternion;
+ Quaternion::from(self.coords - rhs.coords); );
// Quaternion × Quaternion
quaternion_op_impl!(
@@ -183,11 +181,10 @@ quaternion_op_impl!(
'b);
quaternion_op_impl!(
-Mul, mul;
-(U4, U1), (U4, U1);
-self: Quaternion, rhs: Quaternion, Output = Quaternion;
-&self * &rhs;
-);
+ Mul, mul;
+ (U4, U1), (U4, U1);
+ self: Quaternion, rhs: Quaternion, Output = Quaternion;
+ &self * &rhs; );
// UnitQuaternion × UnitQuaternion
quaternion_op_impl!(
@@ -212,18 +209,17 @@ quaternion_op_impl!(
'b);
quaternion_op_impl!(
-Mul, mul;
-(U4, U1), (U4, U1);
-self: UnitQuaternion, rhs: UnitQuaternion, Output = UnitQuaternion;
-&self * &rhs;
-);
+ Mul, mul;
+ (U4, U1), (U4, U1);
+ self: UnitQuaternion, rhs: UnitQuaternion, Output = UnitQuaternion;
+ &self * &rhs; );
// UnitQuaternion ÷ UnitQuaternion
quaternion_op_impl!(
Div, div;
(U4, U1), (U4, U1);
self: &'a UnitQuaternion, rhs: &'b UnitQuaternion, Output = UnitQuaternion;
- self * rhs.inverse();
+ #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() };
'a, 'b);
quaternion_op_impl!(
@@ -241,11 +237,10 @@ quaternion_op_impl!(
'b);
quaternion_op_impl!(
-Div, div;
-(U4, U1), (U4, U1);
-self: UnitQuaternion, rhs: UnitQuaternion, Output = UnitQuaternion;
-&self / &rhs;
-);
+ Div, div;
+ (U4, U1), (U4, U1);
+ self: UnitQuaternion, rhs: UnitQuaternion